Escape angle brackets in Android formatter, if the string contains a placeholder to enable users to use the official way to retrieve these strings. Improves #212.

This commit is contained in:
Sebastian Ludwig 2017-12-12 15:26:38 +01:00
parent 0688167c59
commit b5cd295e3a
5 changed files with 33 additions and 12 deletions

View file

@ -1,3 +1,7 @@
# next version
- Improvement: Better support for placeholders in HTML styled Android strings (#212)
# 1.0.1 (2017-10-17)
- Bugfix: Always prefer the passed-in formatter (#221)

View file

@ -78,7 +78,12 @@ 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.
* HTML tags will be escaped by replacing `<` with `&lt`
* Tags inside `<![CDATA[` won't be escaped.
* Supports [basic styling][androidstyling] with `<b>`, `<i>`, `<u>` and `<a>` links.
* These tags will *not* be escaped, if the string doesn't contain placeholders so you can reference them directly in your layouts or use [`getText()`](https://developer.android.com/reference/android/content/res/Resources.html#getText(int)) to read them programatically.
* These tags *will* be escaped, if the string contains placeholders. You can use [`getString()`](https://developer.android.com/reference/android/content/res/Resources.html#getString(int,%20java.lang.Object...)) combined with [`fromHtml`](https://developer.android.com/reference/android/text/Html.html#fromHtml(java.lang.String)) as shown in the [documentation][androidstyling] to display them.
* 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)

View file

@ -116,10 +116,15 @@ module Twine
value = gsub_unless(value, "'", "\\'") { |substring| substring =~ inside_cdata }
value = gsub_unless(value, /&/, '&amp;') { |substring| substring =~ inside_cdata || substring =~ inside_opening_anchor_tag }
# escape opening angle brackes unless it's a supported styling 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
angle_bracket = /<(?!(\/?(b|u|i|a|\!\[CDATA)))/ # matches all `<` but <b>, <u>, <i>, <a> and <![CDATA
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)

View file

@ -6,6 +6,11 @@ module Twine
PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH = '([-+0#])?(\d+|\*)?(\.(\d+|\*))?(hh?|ll?|L|z|j|t|q)?'
PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH = '(\d+\$)?' + PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH
PLACEHOLDER_TYPES = '[diufFeEgGxXoscpaA]'
PLACEHOLDER_REGEX = /%#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH}#{PLACEHOLDER_TYPES}/
def number_of_twine_placeholders(input)
input.scan(PLACEHOLDER_REGEX).size
end
def convert_twine_string_placeholder(input)
# %@ -> %s
@ -19,15 +24,13 @@ module Twine
# %@ -> %s
value = convert_twine_string_placeholder(input)
placeholder_syntax = PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH + PLACEHOLDER_TYPES
placeholder_regex = /%#{placeholder_syntax}/
number_of_placeholders = value.scan(placeholder_regex).size
number_of_placeholders = number_of_twine_placeholders(input)
return value if number_of_placeholders == 0
# got placeholders -> need to double single percent signs
# % -> %% (but %% -> %%, %d -> %d)
placeholder_syntax = PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH + PLACEHOLDER_TYPES
single_percent_regex = /([^%])(%)(?!(%|#{placeholder_syntax}))/
value.gsub! single_percent_regex, '\1%%'
@ -61,8 +64,7 @@ module Twine
def convert_placeholders_from_twine_to_flash(input)
value = convert_twine_string_placeholder(input)
placeholder_regex = /%#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH}#{PLACEHOLDER_TYPES}/
value.gsub(placeholder_regex).each_with_index do |match, index|
value.gsub(PLACEHOLDER_REGEX).each_with_index do |match, index|
"{#{index}}"
end
end

View file

@ -50,6 +50,10 @@ class TestAndroidFormatter < FormatterTest
'<i>italic</i>' => '<i>italic</i>',
'<u>underline</u>' => '<u>underline</u>',
'<b>%@</b>' => '&lt;b>%s&lt;/b>',
'<i>%@</i>' => '&lt;i>%s&lt;/i>',
'<u>%@</u>' => '&lt;u>%s&lt;/u>',
'<span>inline</span>' => '&lt;span>inline&lt;/span>',
'<p>paragraph</p>' => '&lt;p>paragraph&lt;/p>',
@ -58,9 +62,10 @@ class TestAndroidFormatter < FormatterTest
'<a href="target"></a>"out"' => '<a href="target"></a>\"out\"',
'<a href="http://url.com?param=1&param2=3&param3=%20">link</a>' => '<a href="http://url.com?param=1&param2=3&param3=%20">link</a>',
'<p>escaped</p><![CDATA[]]>' => '&lt;p>escaped&lt;/p><![CDATA[]]>',
'<![CDATA[]]><p>escaped</p>' => '<![CDATA[]]>&lt;p>escaped&lt;/p>',
'<![CDATA[<p>unescaped</p>]]>' => '<![CDATA[<p>unescaped</p>]]>',
'<p>escaped</p><![CDATA[]]>' => '&lt;p>escaped&lt;/p><![CDATA[]]>',
'<![CDATA[]]><p>escaped</p>' => '<![CDATA[]]>&lt;p>escaped&lt;/p>',
'<![CDATA[<p>unescaped</p>]]>' => '<![CDATA[<p>unescaped</p>]]>',
'<![CDATA[<p>unescaped with %@</p>]]>' => '<![CDATA[<p>unescaped with %s</p>]]>',
'<![CDATA[]]><![CDATA[<p>unescaped</p>]]>' => '<![CDATA[]]><![CDATA[<p>unescaped</p>]]>',
'<![CDATA[&]]>' => '<![CDATA[&]]>',