Skip to content

Commit 176a6f2

Browse files
authored
Merge pull request #208 from transifex/TX-12814-escape-characters-in-key
TX-12814 - Escaped character is not working properly with the STRUCTURED_JSON file format
2 parents fcfe304 + b6f135c commit 176a6f2

File tree

2 files changed

+105
-42
lines changed

2 files changed

+105
-42
lines changed

openformats/formats/json.py

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,41 @@ def _extract(self, parsed, nest=None):
8080
format(key, self.transcriber.line_number))
8181
self.existing_keys.add(key)
8282

83-
if isinstance(value, (six.binary_type, six.text_type)):
84-
if not value.strip():
85-
continue
83+
if self.name == "STRUCTURED_JSON":
84+
try:
85+
(string_value, _), = value.find_children(self.STRING_KEY)
86+
except:
87+
# Ignore other types of values like lists
88+
pass
89+
else:
90+
if string_value:
91+
if isinstance(string_value, (six.binary_type,
92+
six.text_type)):
93+
if string_value.strip():
94+
openstring = self._create_openstring(
95+
key, value)
96+
97+
if openstring:
98+
self.stringset.append(openstring)
99+
100+
elif isinstance(value, DumbJson):
101+
self._extract(value, key)
102+
else:
103+
if isinstance(value, (six.binary_type, six.text_type)):
104+
if not value.strip():
105+
continue
86106

87-
openstring = self._create_openstring(key, value,
88-
value_position)
89-
if openstring:
90-
self.stringset.append(openstring)
107+
openstring = self._create_openstring(key, value,
108+
value_position)
109+
if openstring:
110+
self.stringset.append(openstring)
91111

92-
elif isinstance(value, DumbJson):
93-
self._extract(value, key)
112+
elif isinstance(value, DumbJson):
113+
self._extract(value, key)
94114

95-
else:
96-
# Ignore other JSON types (bools, nulls, numbers)
97-
pass
115+
else:
116+
# Ignore other JSON types (bools, nulls, numbers)
117+
pass
98118

99119
elif parsed.type == list:
100120
for index, (item, item_position) in enumerate(parsed):
@@ -468,42 +488,67 @@ class StructuredJsonHandler(JsonHandler):
468488
STRUCTURE_FIELDS = {CONTEXT_KEY, DEVELOPER_COMMENT_KEY,
469489
CHARACTER_LIMIT_KEY}
470490

471-
def _create_pluralized_string(self, icu_string, value_position):
491+
def _create_openstring(self, key, payload_dict):
492+
"""Return a new OpenString based on the given key and payload_dict
493+
and update the transcriber accordingly based on the provided position.
494+
495+
496+
:param str key: the string key
497+
:param DumbJson payload_dict: the string and metadata
498+
:return: an OpenString or None
499+
"""
500+
# First attempt to parse this as a special node,
501+
# e.g. a pluralized string.
502+
# If it cannot be parsed that way (returns None), parse it like
503+
# a regular string.
504+
parser = ICUParser(allow_numeric_plural_values=False)
505+
(string_value, _), = payload_dict.find_children(self.STRING_KEY)
506+
icu_string = parser.parse(key, string_value)
507+
if icu_string:
508+
return self._create_pluralized_string(icu_string, payload_dict)
509+
510+
return self._create_regular_string(
511+
key, payload_dict
512+
)
513+
514+
def _create_pluralized_string(self, icu_string, payload_dict):
472515
"""Create a pluralized string based on the given information.
473516
474517
Also updates the transcriber accordingly.
475518
476519
:param ICUString icu_string: The ICUString object that will generate
477520
the pluralized string
521+
"param DumbJson payload_dict: the string and metadata
478522
:return: an OpenString object
479523
:rtype: OpenString
480524
"""
481-
icu_string.key = self._parse_key(icu_string.key)
482-
# Don't create strings for metadata fields
483-
if not icu_string.key:
484-
return None
525+
(_, string_position), = payload_dict.find_children(self.STRING_KEY)
526+
payload_dict = json.loads(
527+
payload_dict.source[payload_dict.start:payload_dict.end+1])
528+
comment_value = payload_dict.get(self.DEVELOPER_COMMENT_KEY)
529+
limit_value = payload_dict.get(self.CHARACTER_LIMIT_KEY)
530+
context_value = payload_dict.get(self.CONTEXT_KEY)
485531

486-
structure = self._get_string_structure(icu_string.key)
487532
openstring = OpenString(
488533
icu_string.key,
489534
icu_string.strings_by_rule,
490535
pluralized=icu_string.pluralized,
491536
order=next(self._order),
492-
developer_comment=structure[self.DEVELOPER_COMMENT_KEY],
493-
character_limit=structure[self.CHARACTER_LIMIT_KEY],
494-
context=structure[self.CONTEXT_KEY]
537+
developer_comment=comment_value or '',
538+
character_limit=limit_value,
539+
context=context_value or ''
495540
)
496541

497542
current_pos = icu_string.current_position
498543
string_to_replace = icu_string.string_to_replace
499544

500-
self.transcriber.copy_until(value_position + current_pos)
545+
self.transcriber.copy_until(string_position + current_pos)
501546
self.transcriber.add(openstring.template_replacement)
502547
self.transcriber.skip(len(string_to_replace))
503548

504549
return openstring
505550

506-
def _create_regular_string(self, key, value, value_position):
551+
def _create_regular_string(self, key, payload_dict):
507552
"""
508553
Return a new OpenString based on the given key and value
509554
and update the transcriber accordingly.
@@ -512,22 +557,22 @@ def _create_regular_string(self, key, value, value_position):
512557
:param value: the translation string
513558
:return: an OpenString or None
514559
"""
515-
key = self._parse_key(key)
560+
(string_value, string_position), = payload_dict.find_children(self.STRING_KEY)
561+
payload_dict = json.loads(
562+
payload_dict.source[payload_dict.start:payload_dict.end+1])
563+
comment_value = payload_dict.get(self.DEVELOPER_COMMENT_KEY)
564+
limit_value = payload_dict.get(self.CHARACTER_LIMIT_KEY)
565+
context_value = payload_dict.get(self.CONTEXT_KEY)
516566

517-
# Don't create strings for metadata fields
518-
if not key:
519-
return None
520-
521-
structure = self._get_string_structure(key)
522567
openstring = OpenString(
523-
key, value, order=next(self._order),
524-
developer_comment=structure[self.DEVELOPER_COMMENT_KEY],
525-
character_limit=structure[self.CHARACTER_LIMIT_KEY],
526-
context=structure[self.CONTEXT_KEY]
568+
key, string_value, order=next(self._order),
569+
developer_comment=comment_value or '',
570+
character_limit=limit_value,
571+
context=context_value or ''
527572
)
528-
self.transcriber.copy_until(value_position)
573+
self.transcriber.copy_until(string_position)
529574
self.transcriber.add(openstring.template_replacement)
530-
self.transcriber.skip(len(value))
575+
self.transcriber.skip(len(string_value))
531576

532577
return openstring
533578

openformats/tests/formats/structuredkeyvaluejson/test_keyvaluejson.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,24 @@ def test_dots_in_key(self):
5959
self.assertEqual(stringset[0].__dict__, openstring.__dict__)
6060
self.assertEqual(compiled, source)
6161

62+
def test_escaped_character_in_key(self):
63+
first_level_key = "a\/b"
64+
source = '{"%s": {"c": {"string": "%s"}}}' % (first_level_key, self.random_string)
65+
openstring = OpenString(
66+
"{}.c".format(self.handler._escape_key(first_level_key)),
67+
self.random_string, order=0
68+
)
69+
random_hash = openstring.template_replacement
70+
71+
template, stringset = self.handler.parse(source)
72+
compiled = self.handler.compile(template, [openstring])
73+
74+
self.assertEqual(template,
75+
'{"a\/b": {"c": {"string": "%s"}}}' % random_hash)
76+
self.assertEqual(len(stringset), 1)
77+
self.assertEqual(stringset[0].__dict__, openstring.__dict__)
78+
self.assertEqual(compiled, source)
79+
6280
def test_embedded_dicts(self):
6381
source = '{"a": {"b": {"string": "%s"}}}' % self.random_string
6482
openstring = OpenString("a.b", self.random_string, order=0)
@@ -217,19 +235,19 @@ def test_invalid_plural_format(self):
217235
# Test various cases of messed-up braces
218236
self._test_parse_error_message(
219237
'{ "total_files": {"string": "{ item_count, plural, one {You have {file_count file.} other {You have {file_count} files.} }" }}', # noqa
220-
'Invalid format of pluralized entry with key: "total_files.string"'
238+
'Invalid format of pluralized entry with key: "total_files"'
221239
)
222240
self._test_parse_error_message(
223241
'{ "total_files": {"string": "{ item_count, plural, one {You have file_count} file.} other {You have {file_count} files.} }" }}', # noqa
224-
'Invalid format of pluralized entry with key: "total_files.string"'
242+
'Invalid format of pluralized entry with key: "total_files"'
225243
)
226244
self._test_parse_error_message(
227245
'{ "total_files": {"string": "{ item_count, plural, one {You have {file_count} file. other {You have {file_count} files.} }" }}', # noqa
228-
'Invalid format of pluralized entry with key: "total_files.string"'
246+
'Invalid format of pluralized entry with key: "total_files"'
229247
)
230248
self._test_parse_error_message(
231249
'{ "total_files": {"string": "{ item_count, plural, one {You have {file_count} file}. other {You have file_count} files.} }" }}', # noqa
232-
'Invalid format of pluralized entry with key: "total_files.string"'
250+
'Invalid format of pluralized entry with key: "total_files"'
233251
)
234252

235253
def test_invalid_plural_rules(self):
@@ -238,15 +256,15 @@ def test_invalid_plural_rules(self):
238256
# Anything else, including their TX int equivalents are invalid.
239257
self._test_parse_error_message(
240258
'{ "total_files": {"string": "{ item_count, plural, 1 {file} 5 {{file_count} files} }" }}', # noqa
241-
'Invalid plural rule(s): "1, 5" in pluralized entry with key: total_files.string' # noqa
259+
'Invalid plural rule(s): "1, 5" in pluralized entry with key: total_files' # noqa
242260
)
243261
self._test_parse_error_message(
244262
'{ "total_files": {"string": "{ item_count, plural, once {file} mother {{file_count} files} }" }}', # noqa
245-
'Invalid plural rule(s): "once, mother" in pluralized entry with key: total_files.string' # noqa
263+
'Invalid plural rule(s): "once, mother" in pluralized entry with key: total_files' # noqa
246264
)
247265
self._test_parse_error_message(
248266
'{ "total_files": {"string": "{ item_count, plural, =3 {file} other {{file_count} files} }" }}', # noqa
249-
'Invalid plural rule(s): "=3" in pluralized entry with key: total_files.string' # noqa
267+
'Invalid plural rule(s): "=3" in pluralized entry with key: total_files' # noqa
250268
)
251269

252270
def test_irrelevant_whitespace_ignored(self):

0 commit comments

Comments
 (0)