package org.json.junit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.XML; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; /** * Tests for JSON-Java XML.java * Note: noSpace() will be tested by JSONMLTest */ public class XMLTest { /** * JUnit supports temporary files and folders that are cleaned up after the test. * https://garygregory.wordpress.com/2010/01/20/junit-tip-use-rules-to-manage-temporary-files-and-folders/ */ @Rule public TemporaryFolder testFolder = new TemporaryFolder(); /** * JSONObject from a null XML string. * Expects a NullPointerException */ @Test(expected=NullPointerException.class) public void shouldHandleNullXML() { String xmlStr = null; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("jsonObject should be empty", jsonObject.length() == 0); } /** * Empty JSONObject from an empty XML string. */ @Test public void shouldHandleEmptyXML() { String xmlStr = ""; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("jsonObject should be empty", jsonObject.length() == 0); } /** * Empty JSONObject from a non-XML string. */ @Test public void shouldHandleNonXML() { String xmlStr = "{ \"this is\": \"not xml\"}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("xml string should be empty", jsonObject.length() == 0); } /** * Invalid XML string (tag contains a frontslash). * Expects a JSONException */ @Test public void shouldHandleInvalidSlashInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " abc street\n"+ "
\n"+ "
"; try { XML.toJSONObject(xmlStr); assertTrue("Expecting a JSONException", false); } catch (JSONException e) { assertTrue("Expecting an exception message", "Misshaped tag at 176 [character 14 line 5]". equals(e.getMessage())); } } /** * Invalid XML string ('!' char in tag) * Expects a JSONException */ @Test public void shouldHandleInvalidBangInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " \n"+ "
\n"+ "
"; try { XML.toJSONObject(xmlStr); assertTrue("Expecting a JSONException", false); } catch (JSONException e) { assertTrue("Expecting an exception message", "Misshaped meta tag at 215 [character 13 line 8]". equals(e.getMessage())); } } /** * Invalid XML string ('!' char and no closing tag brace) * Expects a JSONException */ @Test public void shouldHandleInvalidBangNoCloseInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " \n"+ ""; try { XML.toJSONObject(xmlStr); assertTrue("Expecting a JSONException", false); } catch (JSONException e) { assertTrue("Expecting an exception message", "Misshaped meta tag at 214 [character 13 line 8]". equals(e.getMessage())); } } /** * Invalid XML string (no end brace for tag) * Expects JSONException */ @Test public void shouldHandleNoCloseStartTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " \n"+ ""; try { XML.toJSONObject(xmlStr); assertTrue("Expecting a JSONException", false); } catch (JSONException e) { assertTrue("Expecting an exception message", "Misplaced '<' at 193 [character 4 line 7]". equals(e.getMessage())); } } /** * Invalid XML string (partial CDATA chars in tag name) * Expects JSONException */ @Test public void shouldHandleInvalidCDATABangInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " Joe Tester\n"+ " \n"+ "
\n"+ "
"; try { XML.toJSONObject(xmlStr); assertTrue("Expecting a JSONException", false); } catch (JSONException e) { assertTrue("Expecting an exception message", "Expected 'CDATA[' at 204 [character 11 line 6]". equals(e.getMessage())); } } /** * Null JSONObject in XML.toString() */ @Test public void shouldHandleNullJSONXML() { JSONObject jsonObject= null; String actualXml=XML.toString(jsonObject); assertEquals("generated XML does not equal expected XML","\"null\"",actualXml); } /** * Empty JSONObject in XML.toString() */ @Test public void shouldHandleEmptyJSONXML() { JSONObject jsonObject= new JSONObject(); String xmlStr = XML.toString(jsonObject); assertTrue("xml string should be empty", xmlStr.length() == 0); } /** * No SML start tag. The ending tag ends up being treated as content. */ @Test public void shouldHandleNoStartTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " >\n"+ "
\n"+ "
"; String expectedStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Valid XML to JSONObject */ @Test public void shouldHandleSimpleXML() { String xmlStr = "\n"+ "\n"+ "
\n"+ " Joe Tester\n"+ " [CDATA[Baker street 5]\n"+ " \n"+ " true\n"+ " false\n"+ " null\n"+ " 42\n"+ " -23\n"+ " -23.45\n"+ " -23x.45\n"+ " 1, 2, 3, 4.1, 5.2\n"+ "
\n"+ "
"; String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",TrueValue:true,\n"+ "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+ "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":-23x.45,\n"+ "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ "},\"xsi:noNamespaceSchemaLocation\":"+ "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ "XMLSchema-instance\"}}"; compareStringToJSONObject(xmlStr, expectedStr); compareReaderToJSONObject(xmlStr, expectedStr); compareFileToJSONObject(xmlStr, expectedStr); } /** * Valid XML with comments to JSONObject */ @Test public void shouldHandleCommentsInXML() { String xmlStr = "\n"+ "\n"+ "\n"+ "
\n"+ " comment ]]>\n"+ " Joe Tester\n"+ " \n"+ " Baker street 5\n"+ "
\n"+ "
"; JSONObject jsonObject = XML.toJSONObject(xmlStr); String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+ "street 5\",\"name\":\"Joe Tester\",\"content\":\" this is -- "+ " comment \"}}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Valid XML to XML.toString() */ @Test public void shouldHandleToString() { String xmlStr = "\n"+ "\n"+ "
\n"+ " [CDATA[Joe & T > e < s " t ' er]]\n"+ " Baker street 5\n"+ " 1, 2, 3, 4.1, 5.2\n"+ "
\n"+ "
"; String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\","+ "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\","+ "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ "},\"xsi:noNamespaceSchemaLocation\":"+ "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ "XMLSchema-instance\"}}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); String xmlToStr = XML.toString(jsonObject); JSONObject finalJsonObject = XML.toJSONObject(xmlToStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); } /** * Converting a JSON doc containing '>' content to JSONObject, then * XML.toString() should result in valid XML. */ @Test public void shouldHandleContentNoArraytoString() { String expectedStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = "
>"+ "
test.xsdhttp://www.w3.org/2001/XMLSche"+ "ma-instance
"; assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+ finalStr+"]", expectedFinalStr.equals(finalStr)); } /** * Converting a JSON doc containing a 'content' array to JSONObject, then * XML.toString() should result in valid XML. * TODO: This is probably an error in how the 'content' keyword is used. */ @Test public void shouldHandleContentArraytoString() { String expectedStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ "content\":[1, 2, 3]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = "
"+ "1\n2\n3"+ "
test.xsdhttp://www.w3.org/2001/XMLSche"+ "ma-instance
"; assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+ finalStr+"]", expectedFinalStr.equals(finalStr)); } /** * Converting a JSON doc containing a named array to JSONObject, then * XML.toString() should result in valid XML. */ @Test public void shouldHandleArraytoString() { String expectedStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+ "\"something\":[1, 2, 3]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = "
"+ "123"+ "
test.xsdhttp://www.w3.org/2001/XMLSche"+ "ma-instance
"; assertTrue("Should handle expectedFinal: ["+expectedStr+"] final: ["+ finalStr+"]", expectedFinalStr.equals(finalStr)); } /** * Tests that the XML output for empty arrays is consistent. */ @Test public void shouldHandleEmptyArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("array",new Object[]{}); final JSONObject jo2 = new JSONObject(); jo2.put("array",new JSONArray()); final String expected = ""; String output1 = XML.toString(jo1,"jo"); assertTrue("Expected an empty root tag", expected.equals(output1)); String output2 = XML.toString(jo2,"jo"); assertTrue("Expected an empty root tag", expected.equals(output2)); } /** * Tests that the XML output for arrays is consistent when an internal array is empty. */ @Test public void shouldHandleEmptyMultiArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("arr",new Object[]{"One", new String[]{}, "Four"}); final JSONObject jo2 = new JSONObject(); jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{}), "Four"})); final String expected = "OneFour"; String output1 = XML.toString(jo1,"jo"); assertTrue("Expected a matching array", expected.equals(output1)); String output2 = XML.toString(jo2,"jo"); assertTrue("Expected a matching array", expected.equals(output2)); } /** * Tests that the XML output for arrays is consistent when arrays are not empty. */ @Test public void shouldHandleNonEmptyArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("arr",new String[]{"One", "Two", "Three"}); final JSONObject jo2 = new JSONObject(); jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"})); final String expected = "OneTwoThree"; String output1 = XML.toString(jo1,"jo"); assertTrue("Expected a non empty root tag", expected.equals(output1)); String output2 = XML.toString(jo2,"jo"); assertTrue("Expected a non empty root tag", expected.equals(output2)); } /** * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays. */ @Test public void shouldHandleMultiArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"}); final JSONObject jo2 = new JSONObject(); jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{"Two", "Three"}), "Four"})); final String expected = "OneTwoThreeFour"; String output1 = XML.toString(jo1,"jo"); assertTrue("Expected a matching array", expected.equals(output1)); String output2 = XML.toString(jo2,"jo"); assertTrue("Expected a matching array", expected.equals(output2)); } /** * Converting a JSON doc containing a named array of nested arrays to * JSONObject, then XML.toString() should result in valid XML. */ @Test public void shouldHandleNestedArraytoString() { String xmlStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+ "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject jsonObject = new JSONObject(xmlStr); String finalStr = XML.toString(jsonObject); JSONObject finalJsonObject = XML.toJSONObject(finalStr); String expectedStr = "
"+ "12"+ "3"+ "
test.xsdhttp://www.w3.org/2001/XMLSche"+ "ma-instance
"; JSONObject expectedJsonObject = XML.toJSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); } /** * Possible bug: * Illegal node-names must be converted to legal XML-node-names. * The given example shows 2 nodes which are valid for JSON, but not for XML. * Therefore illegal arguments should be converted to e.g. an underscore (_). */ @Test public void shouldHandleIllegalJSONNodeNames() { JSONObject inputJSON = new JSONObject(); inputJSON.append("123IllegalNode", "someValue1"); inputJSON.append("Illegal@node", "someValue2"); String result = XML.toString(inputJSON); /* * This is invalid XML. Names should not begin with digits or contain * certain values, including '@'. One possible solution is to replace * illegal chars with '_', in which case the expected output would be: * <___IllegalNode>someValue1someValue2 */ String expected = "<123IllegalNode>someValue1someValue2"; assertEquals(expected, result); } /** * JSONObject with NULL value, to XML.toString() */ @Test public void shouldHandleNullNodeValue() { JSONObject inputJSON = new JSONObject(); inputJSON.put("nullValue", JSONObject.NULL); // This is a possible preferred result // String expectedXML = ""; /** * This is the current behavior. JSONObject.NULL is emitted as * the string, "null". */ String actualXML = "null"; String resultXML = XML.toString(inputJSON); assertEquals(actualXML, resultXML); } /** * Investigate exactly how the "content" keyword works */ @Test public void contentOperations() { /* * When a standalone 0) then return]]>"; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("1. 3 items", 3 == jsonObject.length()); assertTrue("1. empty tag1", "".equals(jsonObject.get("tag1"))); assertTrue("1. empty tag2", "".equals(jsonObject.get("tag2"))); assertTrue("1. content found", "if (a < b && a > 0) then return".equals(jsonObject.get("content"))); // multiple consecutive standalone cdatas are accumulated into an array xmlStr = " 0) then return]]>"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("2. 3 items", 3 == jsonObject.length()); assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1"))); assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2"))); assertTrue("2. content array found", jsonObject.get("content") instanceof JSONArray); JSONArray jsonArray = jsonObject.getJSONArray("content"); assertTrue("2. array size", jsonArray.length() == 2); assertTrue("2. content array entry 0", "if (a < b && a > 0) then return".equals(jsonArray.get(0))); assertTrue("2. content array entry 1", "here is another cdata".equals(jsonArray.get(1))); /* * text content is accumulated in a "content" inside a local JSONObject. * If there is only one instance, it is saved in the context (a different JSONObject * from the calling code. and the content element is discarded. */ xmlStr = "value 1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("3. 2 items", 1 == jsonObject.length()); assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1"))); /* * array-style text content (multiple tags with the same name) is * accumulated in a local JSONObject with key="content" and value=JSONArray, * saved in the context, and then the local JSONObject is discarded. */ xmlStr = "value 12true"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("4. 1 item", 1 == jsonObject.length()); assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray); jsonArray = jsonObject.getJSONArray("tag1"); assertTrue("4. array size", jsonArray.length() == 3); assertTrue("4. content array entry 0", "value 1".equals(jsonArray.get(0))); assertTrue("4. content array entry 1", jsonArray.getInt(1) == 2); assertTrue("4. content array entry 2", jsonArray.getBoolean(2) == true); /* * Complex content is accumulated in a "content" field. For example, an element * may contain a mix of child elements and text. Each text segment is * accumulated to content. */ xmlStr = "val1val2"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("5. 1 item", 1 == jsonObject.length()); assertTrue("5. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); jsonObject = jsonObject.getJSONObject("tag1"); assertTrue("5. 2 contained items", 2 == jsonObject.length()); assertTrue("5. contained tag", "".equals(jsonObject.get("tag2"))); assertTrue("5. contained content jsonArray found", jsonObject.get("content") instanceof JSONArray); jsonArray = jsonObject.getJSONArray("content"); assertTrue("5. array size", jsonArray.length() == 2); assertTrue("5. content array entry 0", "val1".equals(jsonArray.get(0))); assertTrue("5. content array entry 1", "val2".equals(jsonArray.get(1))); /* * If there is only 1 complex text content, then it is accumulated in a * "content" field as a string. */ xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("6. 1 item", 1 == jsonObject.length()); assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); jsonObject = jsonObject.getJSONObject("tag1"); assertTrue("6. contained content found", "val1".equals(jsonObject.get("content"))); assertTrue("6. contained tag2", "".equals(jsonObject.get("tag2"))); /* * In this corner case, the content sibling happens to have key=content * We end up with an array within an array, and no content element. * This is probably a bug. */ xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("7. 1 item", 1 == jsonObject.length()); assertTrue("7. jsonArray found", jsonObject.get("tag1") instanceof JSONArray); jsonArray = jsonObject.getJSONArray("tag1"); assertTrue("array size 1", jsonArray.length() == 1); assertTrue("7. contained array found", jsonArray.get(0) instanceof JSONArray); jsonArray = jsonArray.getJSONArray(0); assertTrue("7. inner array size 2", jsonArray.length() == 2); assertTrue("7. inner array item 0", "val1".equals(jsonArray.get(0))); assertTrue("7. inner array item 1", "".equals(jsonArray.get(1))); /* * Confirm behavior of original issue */ String jsonStr = "{"+ "\"Profile\": {"+ "\"list\": {"+ "\"history\": {"+ "\"entries\": ["+ "{"+ "\"deviceId\": \"id\","+ "\"content\": {"+ "\"material\": ["+ "{"+ "\"stuff\": false"+ "}"+ "]"+ "}"+ "}"+ "]"+ "}"+ "}"+ "}"+ "}"; jsonObject = new JSONObject(jsonStr); xmlStr = XML.toString(jsonObject); /* * This is the created XML. Looks like content was mistaken for * complex (child node + text) XML. * * * * * id * {"material":[{"stuff":false}]} * * * * */ assertTrue("nothing to test here, see comment on created XML, above", true); } /** * Convenience method, given an input string and expected result, * convert to JSONObject and compare actual to expected result. * @param xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareStringToJSONObject(String xmlStr, String expectedStr) { JSONObject expectedJsonObject = new JSONObject(expectedStr); JSONObject jsonObject = XML.toJSONObject(xmlStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Convenience method, given an input string and expected result, * convert to JSONObject via reader and compare actual to expected result. * @param xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { /* * Commenting out this method until the JSON-java code is updated * to support XML.toJSONObject(reader) JSONObject expectedJsonObject = new JSONObject(expectedStr); Reader reader = new StringReader(xmlStr); JSONObject jsonObject = XML.toJSONObject(reader); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); */ } /** * Convenience method, given an input string and expected result, convert to * JSONObject via file and compare actual to expected result. * * @param xmlStr * the string to parse * @param expectedStr * the expected JSON string * @throws IOException */ private void compareFileToJSONObject(String xmlStr, String expectedStr) { /* * Commenting out this method until the JSON-java code is updated * to support XML.toJSONObject(reader) try { JSONObject expectedJsonObject = new JSONObject(expectedStr); File tempFile = testFolder.newFile("fileToJSONObject.xml"); FileWriter fileWriter = new FileWriter(tempFile); fileWriter.write(xmlStr); fileWriter.close(); Reader reader = new FileReader(tempFile); JSONObject jsonObject = XML.toJSONObject(reader); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } catch (IOException e) { assertTrue("file writer error: " +e.getMessage(), false); } */ } }