Class: Twine::Formatters::Android

Inherits:
Abstract
  • Object
show all
Includes:
Placeholders
Defined in:
lib/twine/formatters/android.rb

Constant Summary

Constants included from Placeholders

Placeholders::PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH, Placeholders::PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH, Placeholders::PLACEHOLDER_REGEX, Placeholders::PLACEHOLDER_TYPES

Constants inherited from Abstract

Twine::Formatters::Abstract::LANGUAGE_CODE_WITH_OPTIONAL_REGION_CODE

Instance Attribute Summary

Attributes inherited from Abstract

#options, #twine_file

Instance Method Summary collapse

Methods included from Placeholders

#convert_placeholders_from_android_to_twine, #convert_placeholders_from_flash_to_twine, #convert_placeholders_from_twine_to_android, #convert_placeholders_from_twine_to_flash, #convert_twine_string_placeholder, #number_of_twine_placeholders

Methods inherited from Abstract

#escape_quotes, #format_definition, #format_file, #format_key, #format_key_value, #format_section, #initialize, #set_comment_for_key, #should_include_definition

Constructor Details

This class inherits a constructor from Twine::Formatters::Abstract

Instance Method Details

#can_handle_directory?(path) ⇒ Boolean

Returns:

  • (Boolean)


18
19
20
# File 'lib/twine/formatters/android.rb', line 18

def can_handle_directory?(path)
  Dir.entries(path).any? { |item| /^values.*$/.match(item) }
end

#default_file_nameObject



22
23
24
# File 'lib/twine/formatters/android.rb', line 22

def default_file_name
  'strings.xml'
end

#determine_language_given_path(path) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/twine/formatters/android.rb', line 26

def determine_language_given_path(path)
  path_arr = path.split(File::SEPARATOR)
  path_arr.each do |segment|
    if segment == 'values'
      return @twine_file.language_codes[0]
    else
      # The language is defined by a two-letter ISO 639-1 language code, optionally followed by a two letter ISO 3166-1-alpha-2 region code (preceded by lowercase "r").
      # see http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
      match = /^values-([a-z]{2}(-r[a-z]{2})?)$/i.match(segment)

      return match[1].sub('-r', '-') if match
    end
  end

  return super
end

#escape_value(value) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/twine/formatters/android.rb', line 111

def escape_value(value)
  inside_cdata = /<\!\[CDATA\[((?!\]\]>).)*$/       # opening CDATA tag ('<![CDATA[') not followed by a closing tag (']]>')
  inside_opening_anchor_tag = /<a\s?((?!>).)*$/     # anchor tag start ('<a ') not followed by a '>'

  # escape double and single quotes and & signs
  value = gsub_unless(value, '"', '\\"') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }
  value = gsub_unless(value, "'", "\\'") { |substring| substring =~ inside_cdata }
  value = gsub_unless(value, /&/, '&amp;') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }

  # if `value` contains a placeholder, escape all angle brackets
  # if not, escape opening angle brackes unless it's a supported styling tag
  # https://github.com/scelis/twine/issues/212
  # https://stackoverflow.com/questions/3235131/#18199543
  if number_of_twine_placeholders(value) > 0
    angle_bracket = /<(?!(\/?(\!\[CDATA)))/           # matches all `<` but <![CDATA
  else  
    angle_bracket = /<(?!(\/?(b|u|i|a|\!\[CDATA)))/   # matches all `<` but <b>, <u>, <i>, <a> and <![CDATA
  end
  value = gsub_unless(value, angle_bracket, '&lt;') { |substring| substring =~ inside_cdata }

  # escape non resource identifier @ signs (http://developer.android.com/guide/topics/resources/accessing-resources.html#ResourcesFromXml)
  resource_identifier_regex = /@(?!([a-z\.]+:)?[a-z+]+\/[a-zA-Z_]+)/   # @[<package_name>:]<resource_type>/<resource_name>
  value.gsub(resource_identifier_regex, '\@')
end

#extensionObject



14
15
16
# File 'lib/twine/formatters/android.rb', line 14

def extension
  '.xml'
end

#format_comment(definition, lang) ⇒ Object



95
96
97
# File 'lib/twine/formatters/android.rb', line 95

def format_comment(definition, lang)
  "\t<!-- #{definition.comment.gsub('--', '')} -->\n" if definition.comment
end

#format_header(lang) ⇒ Object



79
80
81
# File 'lib/twine/formatters/android.rb', line 79

def format_header(lang)
  "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
end

#format_nameObject



10
11
12
# File 'lib/twine/formatters/android.rb', line 10

def format_name
  'android'
end

#format_section_header(section) ⇒ Object



91
92
93
# File 'lib/twine/formatters/android.rb', line 91

def format_section_header(section)
  "\t<!-- SECTION: #{section.name} -->"
end

#format_sections(twine_file, lang) ⇒ Object



83
84
85
86
87
88
89
# File 'lib/twine/formatters/android.rb', line 83

def format_sections(twine_file, lang)
  result = '<resources>'
  
  result += super + "\n"

  result += "</resources>\n"
end

#format_value(value) ⇒ Object

see developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling however unescaped HTML markup like in “Welcome to Android!” is stripped when retrieved with getString() (stackoverflow.com/questions/9891996/)



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/twine/formatters/android.rb', line 138

def format_value(value)
  value = value.dup

  # convert placeholders (e.g. %@ -> %s)
  value = convert_placeholders_from_twine_to_android(value)

  # capture xliff tags and replace them with a placeholder
  xliff_tags = []
  value.gsub! /<xliff:g.+?<\/xliff:g>/ do
    xliff_tags << $&
    'TWINE_XLIFF_TAG_PLACEHOLDER'
  end

  # escape everything outside xliff tags
  value = escape_value(value)

  # put xliff tags back into place
  xliff_tags.each do |xliff_tag|
    # escape content of xliff tags
    xliff_tag.gsub! /(<xliff:g.*?>)(.*)(<\/xliff:g>)/ do "#{$1}#{escape_value($2)}#{$3}" end
    value.sub! 'TWINE_XLIFF_TAG_PLACEHOLDER', xliff_tag
  end
  
  # replace beginning and end spaces with \u0020. Otherwise Android strips them.
  value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
end

#gsub_unless(text, pattern, replacement) ⇒ Object



103
104
105
106
107
108
# File 'lib/twine/formatters/android.rb', line 103

def gsub_unless(text, pattern, replacement)
  text.gsub(pattern) do |match|
    match_start_position = Regexp.last_match.offset(0)[0]
    yield(text[0, match_start_position]) ? match : replacement
  end
end

#key_value_patternObject



99
100
101
# File 'lib/twine/formatters/android.rb', line 99

def key_value_pattern
  "\t<string name=\"%{key}\">%{value}</string>"
end

#output_path_for_language(lang) ⇒ Object



43
44
45
# File 'lib/twine/formatters/android.rb', line 43

def output_path_for_language(lang)
  "values-#{lang}".gsub(/-(\p{Lu})/, '-r\1')
end

#read(io, lang) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/twine/formatters/android.rb', line 57

def read(io, lang)
  document = REXML::Document.new io, :compress_whitespace => %w{ string }
  document.context[:attribute_quote] = :quote
  comment = nil
  document.root.children.each do |child|
    if child.is_a? REXML::Comment
      content = child.string.strip
      comment = content if content.length > 0 and not content.start_with?("SECTION:")
    elsif child.is_a? REXML::Element
      next unless child.name == 'string'

      key = child.attributes['name']

      content = child.children.map(&:to_s).join
      set_translation_for_key(key, lang, content)
      set_comment_for_key(key, comment) if comment

      comment = nil
    end 
  end
end

#set_translation_for_key(key, lang, value) ⇒ Object



47
48
49
50
51
52
53
54
55
# File 'lib/twine/formatters/android.rb', line 47

def set_translation_for_key(key, lang, value)
  value = CGI.unescapeHTML(value)
  value.gsub!('\\\'', '\'')
  value.gsub!('\\"', '"')
  value = convert_placeholders_from_android_to_twine(value)
  value.gsub!('\@', '@')
  value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
  super(key, lang, value)
end