diff options
author | Sebastian Ludwig <sebastian@lurado.de> | 2017-09-12 18:35:12 +0300 |
---|---|---|
committer | Sebastian Ludwig <sebastian@lurado.de> | 2017-09-18 13:26:07 +0300 |
commit | f7092c7605279de76177341a51199f19d9b7ed6b (patch) | |
tree | 755a81586928487d06bd4a8c764c98e2ffa7e212 | |
parent | 9dc3845cae85d17f349bf47beafdbc152c1af53d (diff) |
Close #212: Change Android escaping to preserve basic styling tags and anything inside CDATA.
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | lib/twine/formatters/android.rb | 26 | ||||
-rw-r--r-- | test/test_formatters.rb | 23 |
3 files changed, 45 insertions, 8 deletions
@@ -78,6 +78,7 @@ Twine currently supports the following output formats: * [iOS and OS X String Resources][applestrings] (format: apple) * [Android String Resources][androidstrings] (format: android) + * Supports [basic styling][androidstyling] with \<b\>, \<i\>, \<u\> and \<a\> links. These tags will *not* be escaped. Use [`getText()`](https://developer.android.com/reference/android/content/res/Resources.html#getText(int)) to read these strings. Also tags inside `<![CDATA[` won't be escaped. See [\#212](https://github.com/scelis/twine/issues/212) for details. * [Gettext PO Files][gettextpo] (format: gettext) * [jquery-localize Language Files][jquerylocalize] (format: jquery) * [Django PO Files][djangopo] (format: django) @@ -212,9 +213,10 @@ Many thanks to all of the contributors to the Twine project, including: [INI]: http://en.wikipedia.org/wiki/INI_file [applestrings]: http://developer.apple.com/documentation/Cocoa/Conceptual/LoadingResources/Strings/Strings.html [androidstrings]: http://developer.android.com/guide/topics/resources/string-resource.html +[androidstyling]: http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling [gettextpo]: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html [jquerylocalize]: https://github.com/coderifous/jquery-localize [djangopo]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/ [tizen]: https://developer.tizen.org/documentation/articles/localization [flash]: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/resources/IResourceManager.html#getString() -[printf]: https://en.wikipedia.org/wiki/Printf_format_string +[printf]: https://en.wikipedia.org/wiki/Printf_format_string
\ No newline at end of file diff --git a/lib/twine/formatters/android.rb b/lib/twine/formatters/android.rb index b9dba3e..ced90b7 100644 --- a/lib/twine/formatters/android.rb +++ b/lib/twine/formatters/android.rb @@ -99,12 +99,28 @@ module Twine "\t<string name=\"%{key}\">%{value}</string>" end + 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 + + # http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling def escape_value(value) - # escape double and single quotes, & signs and tags - value = escape_quotes(value) - value.gsub!("'", "\\\\'") - value.gsub!(/&/, '&') - value.gsub!('<', '<') + 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, /&/, '&') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag } + + # escape opening angle brackes unless it's a supported styling tag + # https://github.com/scelis/twine/issues/212 + # https://stackoverflow.com/questions/3235131/#18199543 + angle_bracket = /<(?!(\/?(b|u|i|a|\!\[CDATA)))/ # matches all `<` but <b>, <u>, <i>, <a> and <![CDATA + value = gsub_unless(value, angle_bracket, '<') { |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> diff --git a/test/test_formatters.rb b/test/test_formatters.rb index a63b0a5..ae30787 100644 --- a/test/test_formatters.rb +++ b/test/test_formatters.rb @@ -45,8 +45,27 @@ class TestAndroidFormatter < FormatterTest 'this < that' => 'this < that', "it's complicated" => "it\\'s complicated", 'a "good" way' => 'a \"good\" way', - '<b>bold</b>' => '<b>bold</b>', - '<a href="target">link</a>' => '<a href=\"target\">link</a>', + + '<b>bold</b>' => '<b>bold</b>', + '<i>italic</i>' => '<i>italic</i>', + '<u>underline</u>' => '<u>underline</u>', + + '<span>inline</span>' => '<span>inline</span>', + '<p>paragraph</p>' => '<p>paragraph</p>', + + '<a href="target">link</a>' => '<a href="target">link</a>', + '<a href="target">"link"</a>' => '<a href="target">\"link\"</a>', + '<a href="target"></a>"out"' => '<a href="target"></a>\"out\"', + '<a href="http://url.com?param=1¶m2=3¶m3=%20">link</a>' => '<a href="http://url.com?param=1¶m2=3¶m3=%20">link</a>', + + '<p>escaped</p><![CDATA[]]>' => '<p>escaped</p><![CDATA[]]>', + '<![CDATA[]]><p>escaped</p>' => '<![CDATA[]]><p>escaped</p>', + '<![CDATA[<p>unescaped</p>]]>' => '<![CDATA[<p>unescaped</p>]]>', + '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>' => '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>', + + '<![CDATA[&]]>' => '<![CDATA[&]]>', + '<![CDATA[\']]>' => '<![CDATA[\']]>', + '<![CDATA["]]>' => '<![CDATA["]]>', '<xliff:g></xliff:g>' => '<xliff:g></xliff:g>', '<xliff:g>untouched</xliff:g>' => '<xliff:g>untouched</xliff:g>', |