From 60b0eb2adf71f0896b7cc19a826e0c1941734c92 Mon Sep 17 00:00:00 2001 From: Sebastian Ludwig Date: Tue, 3 May 2016 22:49:43 +0200 Subject: [PATCH 1/2] Allowing xliff tags in android values and escaping special characters as recommended by Android docs. --- lib/twine/formatters/android.rb | 45 +++++++++++++++++++++++++-------- test/test_formatters.rb | 22 +++++++++++++--- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/lib/twine/formatters/android.rb b/lib/twine/formatters/android.rb index acc5da8..24c28aa 100644 --- a/lib/twine/formatters/android.rb +++ b/lib/twine/formatters/android.rb @@ -112,19 +112,44 @@ module Twine "\t%{value}" end - def format_value(value) - # Android enforces the following rules on the values - # 1) apostrophes and quotes must be escaped with a backslash + def escape_value(value) + # escape double and single quotes, & signs and tags value = escape_quotes(value) value.gsub!("'", "\\\\'") - # 2) HTML escape the string - value = CGI.escapeHTML(value) - # 3) convert placeholders (e.g. %@ -> %s) - value = convert_placeholders_from_twine_to_android(value) - # 4) escape non resource identifier @ signs (http://developer.android.com/guide/topics/resources/accessing-resources.html#ResourcesFromXml) + value.gsub!(/&/, '&') + value.gsub!('<', '<') + + # 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_]+)/ # @[:]/ - value.gsub!(resource_identifier_regex, '\@') - # 5) replace beginning and end spaces with \u0020. Otherwise Android strips them. + value.gsub(resource_identifier_regex, '\@') + end + + # see http://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() (http://stackoverflow.com/questions/9891996/) + def format_value(value) + value = value.dup + + # capture xliff tags and replace them with a placeholder + xliff_tags = [] + value.gsub! // 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>)/ do "#{$1}#{escape_value($2)}#{$3}" end + value.sub! 'TWINE_XLIFF_TAG_PLACEHOLDER', xliff_tag + end + + # convert placeholders (e.g. %@ -> %s) + value = convert_placeholders_from_twine_to_android(value) + + # replace beginning and end spaces with \u0020. Otherwise Android strips them. value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length } end diff --git a/test/test_formatters.rb b/test/test_formatters.rb index c40a12a..b8f0001 100644 --- a/test/test_formatters.rb +++ b/test/test_formatters.rb @@ -101,10 +101,24 @@ class TestAndroidFormatter < FormatterTest assert_equal "value\\u0020", @formatter.format_value('value ') end - def test_format_value_escapes_single_quotes - skip 'not working with ruby 2.0' - # http://stackoverflow.com/questions/18735608/cgiescapehtml-is-escaping-single-quote - assert_equal "not \\'so\\' easy", @formatter.format_value("not 'so' easy") + def test_format_value_escaping + values = { + 'this & that' => 'this & that', + 'this < that' => 'this < that', + "it's complicated" => "it\\'s complicated", + 'a "good" way' => 'a \"good\" way', + 'bold' => '<b>bold</b>', + 'link' => '<a href=\"target\">link</a>', + + '' => '', + 'untouched' => 'untouched', + 'untouched' => 'untouched', + 'first inbetween second' => 'first inbetween second' + } + + values.each do |input, expected| + assert_equal expected, @formatter.format_value(input) + end end def test_format_value_escapes_non_resource_identifier_at_signs From a53384c5d7c5308db9736f31173f3a7d4c102416 Mon Sep 17 00:00:00 2001 From: Sebastian Ludwig Date: Tue, 3 May 2016 22:58:21 +0200 Subject: [PATCH 2/2] Added test to verify the android formatter unescapes values properly. --- test/test_formatters.rb | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/test/test_formatters.rb b/test/test_formatters.rb index b8f0001..3636e75 100644 --- a/test/test_formatters.rb +++ b/test/test_formatters.rb @@ -39,6 +39,20 @@ end class TestAndroidFormatter < FormatterTest def setup super Twine::Formatters::Android + + @escape_test_values = { + 'this & that' => 'this & that', + 'this < that' => 'this < that', + "it's complicated" => "it\\'s complicated", + 'a "good" way' => 'a \"good\" way', + 'bold' => '<b>bold</b>', + 'link' => '<a href=\"target\">link</a>', + + '' => '', + 'untouched' => 'untouched', + 'untouched' => 'untouched', + 'first inbetween second' => 'first inbetween second' + } end def test_read_format @@ -83,6 +97,13 @@ class TestAndroidFormatter < FormatterTest assert_equal '@value', @empty_twine_file.definitions_by_key['key1'].translations['en'] end + def test_set_translation_unescaping + @escape_test_values.each do |expected, input| + @formatter.set_translation_for_key 'key1', 'en', input + assert_equal expected, @empty_twine_file.definitions_by_key['key1'].translations['en'] + end + end + def test_format_file formatter = Twine::Formatters::Android.new formatter.twine_file = @twine_file @@ -102,21 +123,7 @@ class TestAndroidFormatter < FormatterTest end def test_format_value_escaping - values = { - 'this & that' => 'this & that', - 'this < that' => 'this < that', - "it's complicated" => "it\\'s complicated", - 'a "good" way' => 'a \"good\" way', - 'bold' => '<b>bold</b>', - 'link' => '<a href=\"target\">link</a>', - - '' => '', - 'untouched' => 'untouched', - 'untouched' => 'untouched', - 'first inbetween second' => 'first inbetween second' - } - - values.each do |input, expected| + @escape_test_values.each do |input, expected| assert_equal expected, @formatter.format_value(input) end end