Skip to content

Commit f3c83d8

Browse files
committed
Fix JSONML text double decoding with keepStrings
1 parent 18e8ef6 commit f3c83d8

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

‎src/main/java/org/json/JSONML.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ private static Object parse(
274274
if (token instanceof String) {
275275
String strToken = (String) token;
276276
if (config.isKeepStrings()) {
277-
value = XML.unescape(strToken);
277+
value = strToken;
278278
} else {
279279
value = XML.stringToValue(strToken);
280280
}

‎src/test/java/org/json/junit/JSONMLTest.java‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,98 @@ public void testToJSONArray_reversibility() {
724724
assertNotEquals(revertedXml, originalXml);
725725
}
726726

727+
/**
728+
* Entity-looking text is decoded once, including when strings are kept.
729+
*/
730+
@Test
731+
public void shouldDecodeEntitiesOnceWhenKeepingStringsInJSONArray() {
732+
final String[][] cases = {
733+
{"<", "<"},
734+
{"&", "&"},
735+
{"A", "A"},
736+
{"<", "<"},
737+
{"&lt;&gt;&amp;&quot;&apos;&#65;&#x42;", "<>&\"'AB"}
738+
};
739+
for (String[] testCase : cases) {
740+
final String xml = "<p title=\"" + testCase[0] + "\">" + testCase[0] + "</p>";
741+
final JSONArray json = JSONML.toJSONArray(xml, true);
742+
assertEquals(xml, testCase[1], json.getJSONObject(1).get("title"));
743+
assertEquals(xml, testCase[1], json.get(2));
744+
assertEquals(xml, testCase[1], JSONML.toJSONArray(xml, false).get(2));
745+
assertTrue(xml, json.similar(JSONML.toJSONArray(new XMLTokener(JSONML.toString(json)),
746+
JSONMLParserConfiguration.KEEP_STRINGS)));
747+
}
748+
}
749+
750+
/**
751+
* Object form preserves the same decoded attribute and text values.
752+
*/
753+
@Test
754+
public void shouldDecodeEntitiesOnceWhenKeepingStringsInJSONObject() {
755+
final String xml = "<p title=\"&amp;lt;\">&amp;lt;</p>";
756+
final JSONObject json = JSONML.toJSONObject(xml,
757+
JSONMLParserConfiguration.ORIGINAL.withKeepStrings(true));
758+
assertEquals("&lt;", json.get("title"));
759+
assertEquals("&lt;", json.getJSONArray("childNodes").get(0));
760+
assertEquals("&lt;", JSONML.toJSONObject(xml).getJSONArray("childNodes").get(0));
761+
assertTrue(json.similar(JSONML.toJSONObject(new XMLTokener(JSONML.toString(json)), true)));
762+
}
763+
764+
/**
765+
* Keeping strings disables type conversion without disabling entity decoding.
766+
*/
767+
@Test
768+
public void shouldPreserveValueTypesWhenKeepingStrings() {
769+
final String[] values = {"42", "true", "false", "null"};
770+
final String[] content = {"&#52;&#50;", "&#116;rue", "false", "null"};
771+
final Object[] typedValues = {Integer.valueOf(42), Boolean.TRUE, Boolean.FALSE, JSONObject.NULL};
772+
for (int i = 0; i < values.length; i++) {
773+
final String xml = "<p value=\"" + values[i] + "\">" + content[i] + "</p>";
774+
final JSONArray array = JSONML.toJSONArray(new XMLTokener(xml), true);
775+
final JSONObject object = JSONML.toJSONObject(xml, true);
776+
assertEquals(values[i], array.getJSONObject(1).get("value"));
777+
assertEquals(values[i], array.get(2));
778+
assertEquals(values[i], object.get("value"));
779+
assertEquals(values[i], object.getJSONArray("childNodes").get(0));
780+
final JSONArray typedArray = JSONML.toJSONArray(xml);
781+
final JSONObject typedObject = JSONML.toJSONObject(xml);
782+
assertEquals(typedValues[i], typedArray.getJSONObject(1).get("value"));
783+
assertEquals(typedValues[i], typedArray.get(2));
784+
assertEquals(typedValues[i], typedObject.get("value"));
785+
assertEquals(typedValues[i], typedObject.getJSONArray("childNodes").get(0));
786+
}
787+
}
788+
789+
/**
790+
* CDATA entity syntax remains literal content.
791+
*/
792+
@Test
793+
public void shouldPreserveCdataWhenKeepingStrings() {
794+
final String content = "&lt; &#65; &amp; 42 true null";
795+
final String xml = "<p><![CDATA[" + content + "]]></p>";
796+
assertEquals(content, JSONML.toJSONArray(xml, true).get(1));
797+
assertEquals(content, JSONML.toJSONObject(xml, true).getJSONArray("childNodes").get(0));
798+
}
799+
800+
/**
801+
* Escaped invalid character references are text, while raw references remain invalid.
802+
*/
803+
@Test
804+
public void shouldPreserveEscapedInvalidCharacterReferences() {
805+
final String[] references = {"#0", "#x110000"};
806+
for (String reference : references) {
807+
final String expected = "&" + reference + ";";
808+
final String xml = "<p>&amp;" + reference + ";</p>";
809+
assertEquals(expected, JSONML.toJSONArray(xml, true).get(1));
810+
assertEquals(expected, JSONML.toJSONObject(xml, true).getJSONArray("childNodes").get(0));
811+
assertEquals(expected, JSONML.toJSONArray(xml).get(1));
812+
assertEquals(expected, JSONML.toJSONObject(xml).getJSONArray("childNodes").get(0));
813+
final String invalidXml = "<p>" + expected + "</p>";
814+
assertThrows(JSONException.class, () -> JSONML.toJSONArray(invalidXml, true));
815+
assertThrows(JSONException.class, () -> JSONML.toJSONObject(invalidXml, true));
816+
}
817+
}
818+
727819
/**
728820
* JSON string cannot be reverted to original xml when type guessing is used.
729821
* When we force all the values as string, the original text comes back.

0 commit comments

Comments
 (0)