@@ -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+ {"&lt;" , "<" },
734+ {"&amp;" , "&" },
735+ {"&#65;" , "A" },
736+ {"&lt;" , "<" },
737+ {"<>&"'AB" , "<>&\" '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=\" &lt;\" >&lt;</p>" ;
756+ final JSONObject json = JSONML .toJSONObject (xml ,
757+ JSONMLParserConfiguration .ORIGINAL .withKeepStrings (true ));
758+ assertEquals ("<" , json .get ("title" ));
759+ assertEquals ("<" , json .getJSONArray ("childNodes" ).get (0 ));
760+ assertEquals ("<" , 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 = {"42" , "true" , "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 = "< A & 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>&" + 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