From 9fd1740368dc9d760c7a5faf7a4d2cabb3c47b54 Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Mon, 29 Jan 2024 18:53:02 -0800 Subject: [PATCH 01/16] These files are what I have so far for milestone 2. There is work that needs to be done on the first method, but what is included here is a testing file for the methods and the modified XML class used to create the methods --- src/main/java/org/json/XML.java | 758 ++++++++++++++++++++++++++- src/test/java/org/json/XMLTest2.java | 248 +++++++++ 2 files changed, 1000 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/json/XMLTest2.java diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index a94c3fc4b..fabd87615 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -8,7 +8,11 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; +import java.util.Arrays; import java.util.Iterator; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; import static org.json.NumberConversionUtil.potentialNumber; import static org.json.NumberConversionUtil.stringToNumber; @@ -225,7 +229,7 @@ public static void noSpace(String string) throws JSONException { /** * Scan the content following the named tag, attaching it to the context. - * + * * @param x * The XMLTokener containing the source string. * @param context @@ -241,6 +245,7 @@ public static void noSpace(String string) throws JSONException { */ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth) throws JSONException { + char c; int i; JSONObject jsonObject = null; @@ -249,6 +254,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP Object token; XMLXsiTypeConverter xmlXsiTypeConverter; + // Test for and skip past these forms: // // @@ -258,7 +264,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP // <> // <= // << - + token = x.nextToken(); // "); return false; } else if (token == SLASH) { - + //1748 // Close tag 0) { if(xmlXsiTypeConverter != null) { jsonObject.accumulate(config.getcDataTagName(), @@ -406,14 +425,288 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP config.isKeepStrings() ? string : stringToValue(string)); } } - + /** + * where RECURSION TO FIND NEW SUBOBJECTS BEGINS + */ } else if (token == LT) { // Nested element if (currentNestingDepth == config.getMaxNestingDepth()) { throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); } - + if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) { + + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.append(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.append(tagName, jsonObject); + } + } else { + if (jsonObject.length() == 0) { + context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + + context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + private static boolean parsed(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, + int currentNestingDepth, String [] path_tracker, JSONObject ReturnObj, AtomicBoolean closeTagFound, String LAST_PATH) + throws JSONException { + + char c; + int i; + JSONObject jsonObject = null; + String string; + String tagName; + Object token; + XMLXsiTypeConverter xmlXsiTypeConverter; + + + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + // "); + return true; + } + + if (token == BANG) { + c = x.next(); + if (c == '-') { + if (x.next() == '-') { + x.skipPast("-->"); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate(config.getcDataTagName(), string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag "); + // return true; + // } + // ALL FIRST TAGS ARE PROCESSED HERE + + token = null; + jsonObject = new JSONObject(); + boolean nilAttributeFound = false; + xmlXsiTypeConverter = null; + for (;;) { + if (token == null) { + token = x.nextToken(); + } + // attribute = value + if (token instanceof String) { + string = (String) token; + token = x.nextToken(); + if (token == EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + + if (config.isConvertNilAttributeToNull() + && NULL_ATTR.equals(string) + && Boolean.parseBoolean((String) token)) { + nilAttributeFound = true; + } else if (config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty() + && TYPE_ATTR.equals(string)) { + xmlXsiTypeConverter = config.getXsiTypeMap().get(token); + } else if (!nilAttributeFound) { + + jsonObject.accumulate(string, + config.isKeepStrings() + ? ((String) token) + : stringToValue((String) token)); + } + token = null; + } else { + + jsonObject.accumulate(string, ""); + } + + } else if (token == SLASH) { + + // Empty tag <.../> + if (x.nextToken() != GT) { + + throw x.syntaxError("Misshaped tag"); + } + + if (config.getForceList().contains(tagName)) { + + // Force the value to be an array + if (nilAttributeFound) { + context.append(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.append(tagName, jsonObject); + } else { + context.put(tagName, new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(tagName, jsonObject); + } else { + context.accumulate(tagName, ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + token = x.nextContent(); + + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } + // WHERE STRING VALS ARE PROCESSED + else if (token instanceof String) { + string = (String) token; + + if (string.length() > 0) { + if (xmlXsiTypeConverter != null) { + jsonObject.accumulate(config.getcDataTagName(), + stringToValue(string, xmlXsiTypeConverter)); + } else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); + } + } + /** + * where RECURSION TO FIND NEW SUBOBJECTS BEGINS + */ + } else if (token == LT) { + // Nested element + if (currentNestingDepth == config.getMaxNestingDepth()) { + throw x.syntaxError( + "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); + } + + if(path_tracker.length > 0 && tagName.equals(path_tracker[0])){ + String [] newArray = path_tracker.length > 1 ? Arrays.copyOfRange(path_tracker, 1, path_tracker.length) : new String[0]; + path_tracker = newArray; + } + if(path_tracker.length == 1){ + + LAST_PATH = path_tracker[0].trim(); + } + + + if (parsed(x, jsonObject, tagName, config, currentNestingDepth + 1, path_tracker,ReturnObj,closeTagFound, LAST_PATH)) { + if(closeTagFound.get()){ + + // while(x.more()){ + // x.skipPast(">"); + // } + } if (config.getForceList().contains(tagName)) { // Force the value to be an array if (jsonObject.length() == 0) { @@ -423,14 +716,27 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP context.append(tagName, jsonObject.opt(config.getcDataTagName())); } else { context.append(tagName, jsonObject); + if(path_tracker.length==1 && tagName.equals(path_tracker[0])){ + ReturnObj.append(tagName, jsonObject); + } + } } else { if (jsonObject.length() == 0) { context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 && jsonObject.opt(config.getcDataTagName()) != null) { context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + if(path_tracker.length==0){ + + ReturnObj.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + + } + // where subObjects are processed } else { + // accumulates first subObject + context.accumulate(tagName, jsonObject); } } @@ -440,6 +746,302 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } } } else { + + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + private static boolean parsed2(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, + int currentNestingDepth, String[] path_tracker, JSONObject replacement, AtomicBoolean openTagFound, + String LAST_PATH, String NewTagName) + throws JSONException { + + char c; + int i; + JSONObject jsonObject = null; + String string; + String tagName; + Object token; + XMLXsiTypeConverter xmlXsiTypeConverter; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + + + // "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate(config.getcDataTagName(), string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag + if (x.nextToken() != GT) { + + throw x.syntaxError("Misshaped tag"); + } + + if (config.getForceList().contains(tagName)) { + + // Force the value to be an array + if (nilAttributeFound) { + context.append(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.append(tagName, jsonObject); + } else { + context.put(tagName, new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(tagName, jsonObject); + } else { + context.accumulate(tagName, ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + token = x.nextContent(); + + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } + // WHERE STRING VALS ARE PROCESSED + else if (token instanceof String) { + string = (String) token; + + if (string.length() > 0) { + if (xmlXsiTypeConverter != null) { + jsonObject.accumulate(config.getcDataTagName(), + stringToValue(string, xmlXsiTypeConverter)); + } else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); + } + } + /** + * where RECURSION TO FIND NEW SUBOBJECTS BEGINS + */ + } else if (token == LT) { + // Nested element + if (currentNestingDepth == config.getMaxNestingDepth()) { + throw x.syntaxError( + "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); + } + + if (path_tracker.length > 0 && tagName.equals(path_tracker[0])) { + String[] newArray = path_tracker.length > 1 + ? Arrays.copyOfRange(path_tracker, 1, path_tracker.length) + : new String[0]; + path_tracker = newArray; + } + if (path_tracker.length == 1) { + + LAST_PATH = path_tracker[0].trim(); + } + + if (parsed2(x, jsonObject, tagName, config, currentNestingDepth + 1, path_tracker, replacement, + openTagFound, LAST_PATH, NewTagName)) { + // if (openTagFound.get()) { + + // context.put(NewTagName, replacement); + // return false; + // } + // if(tagName == LAST_PATH){ + // return true; + // } + if(tagName.equals(NewTagName)){ + + + + + for (String key : replacement.keySet()) { + System.out.println(key); + System.out.println(replacement.get(key)); + jsonObject.put(key, replacement.get(key)); + } + + context.put(tagName, jsonObject); + return false; + } + else{ + + + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + + context.append(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.append(tagName, jsonObject); + } + } else { + if (jsonObject.length() == 0) { + context.accumulate(tagName, ""); + + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + + // where subObjects are processed + } else { + // accumulates first subObject + context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); } } @@ -654,6 +1256,150 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws public static JSONObject toJSONObject(String string, XMLParserConfiguration config) throws JSONException { return toJSONObject(new StringReader(string), config); } + // /** + // * working model + // * @param reader + // * @param path + // * @return + // */ + // public static JSONObject toJSONObject(Reader reader, JSONPointer path) { + // String thePath = path.toString(); + // XMLParserConfiguration config = new XMLParserConfiguration(); + // JSONObject jo = new JSONObject(); + // XMLTokener x = new XMLTokener(reader); + + // String currentPath = ""; + // while (x.more()) { + // x.skipPast("<"); + // if (x.more()) { + // JSONObject subObject = parse(x, jo, null, config, currentPath, thePath); + // if (subObject != null) { + // return subObject; // Path found, return sub-object + // } + // } + // } + // return jo; // Path not found, return full JSON + // } + /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 1738 + * + * This is our implementation of a toJSONObject that takes a reader and JSONPointer and parses through + * + * + * + */ + public static JSONObject toJSONObject(Reader reader, JSONPointer path) { + String the_path = path.toString(); + + String[] json_pointer = the_path.startsWith("/") ? the_path.substring(1).split("/") : the_path.split("/"); + + + XMLParserConfiguration config = new XMLParserConfiguration(); + JSONObject returnVal = new JSONObject(); + AtomicBoolean closeTagFound = new AtomicBoolean(false); + + String LAST_PATH = json_pointer[json_pointer.length-1]; + + + + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + + if (closeTagFound.get() == true) { + + + + } + if(x.more()) { + + parsed(x, jo, null, XMLParserConfiguration.ORIGINAL, 0,json_pointer,returnVal, closeTagFound,LAST_PATH); + } + + } + + + return returnVal; + + + + + + + + // Read the XML content from the reader + // Convert XML content to JSONObject + + // JSONObject fullJson = XML.toJSONObject(reader); + + // Object subObject = path.queryFrom(fullJson); + + // if (subObject == null) { + // // Handle the case where the JSONPointer path does not exist + // throw new JSONException("JSONPointer path '" + path + "' does not exist in the JSON object."); + // } + + // if (subObject instanceof JSONObject) { + // return (JSONObject) subObject; + // } else { + // throw new JSONException("JSONPointer path '" + path + "' does not point to a JSONObject."); + // } + } + + /** + * SECOND METHOD INPLEMENTATION FOR MILESTONE 2 + * @param reader - reads the XML String + * @param path - path to SubObject that we wish to replace + * @param replacement - the actual JSON Object that we want to use to replace the object at end of path + * @return - Modified JSON Object that has the aspect desired subObject replaced + * + * This method as it currently exists essentially takes the replacement object apart, puts its parent key to a + * new String val, at which point the real subOBject is extracted from the replacement and it is thrown into an overloaded + * parse method called parsed2. It essentially is able to use the inner library mechanics to find the subobject we want to + * replace, and then manually updating the opening and closing tags of said object, whereby the subObject is essentially used + * to overwrite the object that we need to replace with what we provided. + */ + + public static JSONObject toJSONObject(Reader reader, JSONPointer path, JSONObject replacement) { + String the_path = path.toString(); + + String[] json_pointer = the_path.startsWith("/") ? the_path.substring(1).split("/") : the_path.split("/"); + + XMLParserConfiguration config = new XMLParserConfiguration(); + + AtomicBoolean openTagFound = new AtomicBoolean(false); + + String LAST_PATH = json_pointer[json_pointer.length - 1]; + + String [] arr = JSONObject.getNames(replacement); + // get the tagName of the overarching key value of the Replacement Object + String NewTagName = arr[0]; + // quickly going to extract the subObject of the replacement + JSONPointer getter = new JSONPointer("/" + NewTagName); + // actually procured the subObject of the replacement + JSONObject actualReplacement = (JSONObject) getter.queryFrom(replacement); + + + + + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + + if (x.more()) { + + parsed2(x, jo, null, XMLParserConfiguration.ORIGINAL, 0, json_pointer, actualReplacement, openTagFound, + LAST_PATH, NewTagName); + } + + } + + return jo; + + + } /** * Convert a JSONObject into a well-formed, element-normal XML string. diff --git a/src/test/java/org/json/XMLTest2.java b/src/test/java/org/json/XMLTest2.java new file mode 100644 index 000000000..c58497a7f --- /dev/null +++ b/src/test/java/org/json/XMLTest2.java @@ -0,0 +1,248 @@ +package org.json; +import org.junit.Test; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.json.*; + +public class XMLTest2{ + + + /** + * This method tests the efficacy of the milestone 2 method 2 + * As you can see the initial xml object is taken and a replacement is + * named. The method is sucessfully able to replace the subobject (the entire + * adress body including nick, name, street, and zipcode) with the new + * object (jsonReplacement). + * + * The return object from the function call is not null and the resulting + * object matches the expected JSON Object output + */ + @Test + public void testReplacement() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " \n" + + " Crista \n" + + " Crista Lopes\n" + + " \n" + + " 92614\n" + + "
\n" + + "
"; + + String jsonReplacement = + " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + String expected = "{\"contact\":{\"place\":{\"zipcode\":92626,\"street\":\"Muholland Drive\"}}}"; + + + Reader reader = new StringReader(xmlString); + JSONPointer pointer = new JSONPointer("/contact/address"); + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + + JSONObject result = XML.toJSONObject(reader, pointer,replacement); + System.out.println(result); + assertNotNull("Resulting JSONObject should not be null", result); + assertEquals(expected, result.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + /** + * Further test efficacy of milestone 2 method 2 implementation by checking a different + * XMLString for a subObject. The structure of this one is different and therefore allows + * for a different means by which to verify the working order of this method. + * it checks to see if the subObject key has been placed within the xmlString and + * then it further checks if these values are able to be accessed. + */ + @Test + public void testReplacement2() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer to locate the subObject that needs to be extracted + JSONPointer pointer = new JSONPointer("/contact/address"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + // getting the answer + JSONObject result = XML.toJSONObject(reader, pointer, replacement); + + + JSONObject address = result.getJSONObject("contact").getJSONObject("place"); + assert result.getJSONObject("contact").has("place"); + assert address.getString("street").equals("Muholland Drive"); + assert address.getInt("zipcode") == (92626); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + /** + * What happens if we try to use a pointer to a subObject that doesn't exist? + * We would expect that nothing happens and the same object is returned + */ + @Test + public void testReplacementNull() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer that looks for subObject that doesn't exist + JSONPointer pointer = new JSONPointer("/contact/dog"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + // expected answer + String expected = XML.toJSONObject(xmlString).toString(); + + // getting the answer + JSONObject actual = XML.toJSONObject(reader, pointer, replacement); + + assertEquals(expected, actual.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + + public static void main(String[] args) { + + + try { + + String xmlString = "\n"+ + "\n"+ + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + // String xmlString = "\n" + + // "\n" + + // "
\n" + + // " \n" + + // " Crista \n" + + // " Crista Lopes\n" + + // " \n" + + // " 92614\n" + + // "
\n" + + + // "
"; + + + + Reader reader = new StringReader(xmlString); + JSONPointer pointer = new JSONPointer("/contact/address"); + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + JSONObject result = XML.toJSONObject(reader, pointer,replacement); + + + + + + + System.out.println(result); + String [] x = JSONObject.getNames(result); + + for(String z:x){ + System.out.println(z); + } + assertNotNull("Resulting JSONObject should not be null", result); + // assertEquals(replacement, result.optString("place")); + } catch (JSONException e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + + } +} + +// if(parsed2(x,jsonObject,tagName,config,currentNestingDepth+1,path_tracker,replacement,openTagFound,LAST_PATH,NewTagName)){ +// // if (openTagFound.get()) { + +// // context.put(NewTagName, replacement); +// // return false; +// // } +// // if(tagName == LAST_PATH){ +// // return true; +// // } +// if(tagName.equals(NewTagName)){ + +// }if(config.getForceList().contains(tagName)){ +// // Force the value to be an array +// if(jsonObject.length()==0){context.put(tagName,new JSONArray());}else if(jsonObject.length()==1&&jsonObject.opt(config.getcDataTagName())!=null){ + +// context.append(tagName,jsonObject.opt(config.getcDataTagName()));}else{ + +// if(tagName.equals(NewTagName)){context.append(tagName,replacement);}else{context.append(tagName,jsonObject);} + +// }}else{if(jsonObject.length()==0){context.accumulate(tagName,""); + +// }else if(jsonObject.length()==1&&jsonObject.opt(config.getcDataTagName())!=null){System.out.println("pingu");if(tagName.equals(NewTagName)){context.accumulate(tagName,replacement.opt(config.getcDataTagName()));}else{context.accumulate(tagName,jsonObject.opt(config.getcDataTagName()));} + +// // where subObjects are processed +// }else{ +// // accumulates first subObject + +// if(tagName==NewTagName){context.accumulate(tagName,replacement);}else{context.accumulate(tagName,jsonObject);} + +// }} + +// return false;} From c22222778263542f984c0ae352dcbd4ecaf71493 Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Mon, 29 Jan 2024 19:56:05 -0800 Subject: [PATCH 02/16] Adding the tests I made to the proper Maven location for tests so that they run upon compilation. --- src/test/java/org/json/junit/XMLTest.java | 131 ++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 823a06591..018e98bba 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -1323,6 +1324,136 @@ public void testMaxNestingDepthWithValidFittingXML() { "parameter of the XMLParserConfiguration used"); } } + /** + * This method tests the efficacy of the milestone 2 method 2 + * As you can see the initial xml object is taken and a replacement is + * named. The method is sucessfully able to replace the subobject (the entire + * adress body including nick, name, street, and zipcode) with the new + * object (jsonReplacement). + * + * The return object from the function call is not null and the resulting + * object matches the expected JSON Object output + */ + @Test + public void testReplacement() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " \n" + + " Crista \n" + + " Crista Lopes\n" + + " \n" + + " 92614\n" + + "
\n" + + "
"; + + String jsonReplacement = + " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + String expected = "{\"contact\":{\"place\":{\"zipcode\":92626,\"street\":\"Muholland Drive\"}}}"; + + + Reader reader = new StringReader(xmlString); + JSONPointer pointer = new JSONPointer("/contact/address"); + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + + JSONObject result = XML.toJSONObject(reader, pointer,replacement); + System.out.println(result); + assertNotNull("Resulting JSONObject should not be null", result); + assertEquals(expected, result.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + /** + * Further test efficacy of milestone 2 method 2 implementation by checking a different + * XMLString for a subObject. The structure of this one is different and therefore allows + * for a different means by which to verify the working order of this method. + * it checks to see if the subObject key has been placed within the xmlString and + * then it further checks if these values are able to be accessed. + */ + @Test + public void testReplacement2() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer to locate the subObject that needs to be extracted + JSONPointer pointer = new JSONPointer("/contact/address"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + + // getting the answer + JSONObject result = XML.toJSONObject(reader, pointer, replacement); + + + JSONObject address = result.getJSONObject("contact").getJSONObject("place"); + assert result.getJSONObject("contact").has("place"); + assert address.getString("street").equals("Muholland Drive"); + assert address.getInt("zipcode") == (92626); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } + /** + * What happens if we try to use a pointer to a subObject that doesn't exist? + * We would expect that nothing happens and the same object is returned + */ + @Test + public void testReplacementNull() { + try { + String xmlString = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; + + // reader for XML String + Reader reader = new StringReader(xmlString); + // pointer that looks for subObject that doesn't exist + JSONPointer pointer = new JSONPointer("/contact/dog"); + // turning XML String of replacement into a JSON object + JSONObject replacement = XML.toJSONObject(jsonReplacement); + // expected answer + String expected = XML.toJSONObject(xmlString).toString(); + + // getting the answer + JSONObject actual = XML.toJSONObject(reader, pointer, replacement); + + assertEquals(expected, actual.toString()); + } catch (Error e) { + fail("JSONException should definitively not be thrown: " + e.getMessage()); + } + } } From fd82b2de72b7e8ca6253cf69e382696033445687 Mon Sep 17 00:00:00 2001 From: theneelshah Date: Tue, 30 Jan 2024 10:40:18 -0800 Subject: [PATCH 03/16] Add the required overloaded method + Overload the toJSONObject method to take the pointer. + Call the overloaded parse method. --- src/main/java/org/json/XML.java | 355 ++++++++++++++++++++++++-------- 1 file changed, 274 insertions(+), 81 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index fabd87615..027dca09d 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -469,6 +469,272 @@ else if (token instanceof String) { } } + /** + * Scan the content following the named tag, attaching it to the context. + * + * @param x + * The XMLTokener containing the source string. + * @param context + * The JSONObject that will include the new material. + * @param name + * The tag name. + * @param config + * The XML parser configuration. + * @param currentNestingDepth + * The current nesting depth. + * @param path + * The path which is to be found + * @param requiredObj + * The object where the json object with the required + * path is saved + * @param constPath + * The path which won't be updated, but passed + * through every recursive function + * @return true if the close tag is processed. + * @throws JSONException Thrown if any parsing error occurs. + */ + private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, + int currentNestingDepth, String[] path, JSONObject requiredObj, final String[] constPath) + throws JSONException { + char c; + int i; + JSONObject jsonObject = null; + String string; + String tagName; + Object token; + XMLXsiTypeConverter xmlXsiTypeConverter; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + if (!(token == BANG || token == QUEST)) { + if (token.equals(SLASH)) { + String s = stack.pop(); + + if (s.equals(constPath[constPath.length - 1])) { + while (stack.size() > 0) { + String abc = stack.pop(); + + x.skipPast(""); + } + + return true; + } + + } else + stack.push(token.toString()); + } + + // "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate(config.getcDataTagName(), string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (nilAttributeFound) { + context.append(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.append(tagName, jsonObject); + } else { + context.put(tagName, new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(tagName, jsonObject); + } else { + context.accumulate(tagName, ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + if (!isPresent(constPath, tagName)) + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + if (xmlXsiTypeConverter != null) { + jsonObject.accumulate(config.getcDataTagName(), + stringToValue(string, xmlXsiTypeConverter)); + } else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); + } + } + + } else if (token == LT) { + // Nested element + if (currentNestingDepth == config.getMaxNestingDepth()) { + throw x.syntaxError( + "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); + } + + String[] newP = path; + + if (path.length > 0 && tagName.equals(path[0])) { + newP = path.length > 0 ? Arrays.copyOfRange(path, 1, path.length) : path; + } + + if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1, newP, requiredObj, + constPath)) { + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.append(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.append(tagName, jsonObject); + } + } else { + + if (jsonObject.length() == 0) { + context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + + if (path.length == 0) { + requiredObj.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + } + } else { + context.accumulate(tagName, jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + private static boolean parsed(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth, String [] path_tracker, JSONObject ReturnObj, AtomicBoolean closeTagFound, String LAST_PATH) throws JSONException { @@ -1256,95 +1522,22 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws public static JSONObject toJSONObject(String string, XMLParserConfiguration config) throws JSONException { return toJSONObject(new StringReader(string), config); } - // /** - // * working model - // * @param reader - // * @param path - // * @return - // */ - // public static JSONObject toJSONObject(Reader reader, JSONPointer path) { - // String thePath = path.toString(); - // XMLParserConfiguration config = new XMLParserConfiguration(); - // JSONObject jo = new JSONObject(); - // XMLTokener x = new XMLTokener(reader); - - // String currentPath = ""; - // while (x.more()) { - // x.skipPast("<"); - // if (x.more()) { - // JSONObject subObject = parse(x, jo, null, config, currentPath, thePath); - // if (subObject != null) { - // return subObject; // Path found, return sub-object - // } - // } - // } - // return jo; // Path not found, return full JSON - // } - /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * 1738 - * - * This is our implementation of a toJSONObject that takes a reader and JSONPointer and parses through - * - * - * - */ - public static JSONObject toJSONObject(Reader reader, JSONPointer path) { - String the_path = path.toString(); - - String[] json_pointer = the_path.startsWith("/") ? the_path.substring(1).split("/") : the_path.split("/"); - - - XMLParserConfiguration config = new XMLParserConfiguration(); - JSONObject returnVal = new JSONObject(); - AtomicBoolean closeTagFound = new AtomicBoolean(false); - - String LAST_PATH = json_pointer[json_pointer.length-1]; - - + public static JSONObject toJSONObject(Reader reader, JSONPointer path) throws JSONException { + String[] tempPath = path.toString().split("/"); + tempPath = Arrays.copyOfRange(tempPath, 1, tempPath.length); JSONObject jo = new JSONObject(); + JSONObject requiredObj = new JSONObject(); XMLTokener x = new XMLTokener(reader); + while (x.more()) { x.skipPast("<"); - - if (closeTagFound.get() == true) { - - - - } - if(x.more()) { - - parsed(x, jo, null, XMLParserConfiguration.ORIGINAL, 0,json_pointer,returnVal, closeTagFound,LAST_PATH); + if (x.more()) { + parse(x, jo, null, XMLParserConfiguration.ORIGINAL, 0, tempPath, requiredObj, tempPath); } - } - - - return returnVal; - - - - - - - - // Read the XML content from the reader - // Convert XML content to JSONObject - - // JSONObject fullJson = XML.toJSONObject(reader); - - // Object subObject = path.queryFrom(fullJson); - - // if (subObject == null) { - // // Handle the case where the JSONPointer path does not exist - // throw new JSONException("JSONPointer path '" + path + "' does not exist in the JSON object."); - // } - // if (subObject instanceof JSONObject) { - // return (JSONObject) subObject; - // } else { - // throw new JSONException("JSONPointer path '" + path + "' does not point to a JSONObject."); - // } + return requiredObj; } /** From aec5e645ed95f00eed58242d243a81f4dccae2bd Mon Sep 17 00:00:00 2001 From: theneelshah Date: Tue, 30 Jan 2024 10:43:28 -0800 Subject: [PATCH 04/16] Add the helper function and global variable + Add the helper function to see if the tag name is present inside the given path array. + Add the stack to know the current depth wrto the required path. --- src/main/java/org/json/XML.java | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 027dca09d..322f2ce1e 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -469,6 +469,27 @@ else if (token instanceof String) { } } + + private static Stack stack = new Stack(); + + /** + * Search if the tag name is present in the path. + * + * @param str - The string array with the paths + * @param tagName - The required tag name to find + * @return - true if the required tagname is found in the paths + */ + private static boolean isPresent(String[] str, String tagName) { + str = Arrays.copyOf(str, str.length - 1); + + for (int i = 0; i < str.length; i += 1) { + if (tagName.equals(str[i])) + return true; + } + + return false; + } + /** * Scan the content following the named tag, attaching it to the context. * From 6ec79981752db9dce9bc0685e942435021ba36c2 Mon Sep 17 00:00:00 2001 From: theneelshah Date: Tue, 30 Jan 2024 10:45:35 -0800 Subject: [PATCH 05/16] Add the test cases for task 1 --- src/test/java/org/json/junit/XMLTest.java | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 018e98bba..ee1ef6705 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1454,6 +1454,48 @@ public void testReplacementNull() { fail("JSONException should definitively not be thrown: " + e.getMessage()); } } + + @Test + public void customXmlPathExample1() { + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String json = "{\"zipcode\":92614,\"street\":\"Ave of Nowhere\"}\n"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), new JSONPointer("/contact/address")); + JSONObject expected = new JSONObject(json); + + assertTrue(expected.similar(actual)); + } + + @Test + public void customXmlPathExample2() { + String xml = "\n" + + "\n" + + "uhsafouash\n" + + "
\n" + + " \n" + + " Crista \n" + + " Crista Lopes\n" + + "\n" + + " 92614\n" + + "
\n" + + "shah\n" + + "
"; + String json = "{\"nick\":\"Crista\",\"name\":\"Crista Lopes\"}"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), new JSONPointer("/contact/address/street")); + JSONObject expected = new JSONObject(json); + + assertTrue(expected.similar(actual)); + + } + } From 76ce47faba4360991466fca77f8f559fa22abadb Mon Sep 17 00:00:00 2001 From: theneelshah Date: Tue, 30 Jan 2024 12:27:04 -0800 Subject: [PATCH 06/16] Add comment for the toJSONObject method --- src/main/java/org/json/XML.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 322f2ce1e..decd0a968 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -1543,6 +1543,14 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws public static JSONObject toJSONObject(String string, XMLParserConfiguration config) throws JSONException { return toJSONObject(new StringReader(string), config); } + + /** + * + * @param reader - The reader object for the xml string + * @param path - The path that is needed to be found + * @return - The object under the required path + * @throws JSONException + */ public static JSONObject toJSONObject(Reader reader, JSONPointer path) throws JSONException { String[] tempPath = path.toString().split("/"); tempPath = Arrays.copyOfRange(tempPath, 1, tempPath.length); From 75c5bf43c2afe1579c4399d829e71327335ae010 Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Tue, 30 Jan 2024 12:47:05 -0800 Subject: [PATCH 07/16] Updated files to reflect working order. Mainly just deleted my mistakes and unnecessary files --- src/main/java/org/json/XML.java | 301 ++------------------------- src/test/java/org/json/XMLTest2.java | 90 -------- 2 files changed, 16 insertions(+), 375 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index decd0a968..f0d88cb10 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -754,292 +754,23 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } } } - - - private static boolean parsed(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, - int currentNestingDepth, String [] path_tracker, JSONObject ReturnObj, AtomicBoolean closeTagFound, String LAST_PATH) - throws JSONException { - - char c; - int i; - JSONObject jsonObject = null; - String string; - String tagName; - Object token; - XMLXsiTypeConverter xmlXsiTypeConverter; - - - - // Test for and skip past these forms: - // - // - // - // - // Report errors for these forms: - // <> - // <= - // << - - token = x.nextToken(); - - // "); - return true; - } - - if (token == BANG) { - c = x.next(); - if (c == '-') { - if (x.next() == '-') { - x.skipPast("-->"); - return false; - } - x.back(); - } else if (c == '[') { - token = x.nextToken(); - if ("CDATA".equals(token)) { - if (x.next() == '[') { - string = x.nextCDATA(); - if (string.length() > 0) { - context.accumulate(config.getcDataTagName(), string); - } - return false; - } - } - throw x.syntaxError("Expected 'CDATA['"); - } - i = 1; - do { - token = x.nextMeta(); - if (token == null) { - throw x.syntaxError("Missing '>' after ' 0); - return false; - } else if (token == QUEST) { - - // "); - return false; - } else if (token == SLASH) { - - // Close tag "); - // return true; - // } - // ALL FIRST TAGS ARE PROCESSED HERE - - token = null; - jsonObject = new JSONObject(); - boolean nilAttributeFound = false; - xmlXsiTypeConverter = null; - for (;;) { - if (token == null) { - token = x.nextToken(); - } - // attribute = value - if (token instanceof String) { - string = (String) token; - token = x.nextToken(); - if (token == EQ) { - token = x.nextToken(); - if (!(token instanceof String)) { - throw x.syntaxError("Missing value"); - } - - if (config.isConvertNilAttributeToNull() - && NULL_ATTR.equals(string) - && Boolean.parseBoolean((String) token)) { - nilAttributeFound = true; - } else if (config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty() - && TYPE_ATTR.equals(string)) { - xmlXsiTypeConverter = config.getXsiTypeMap().get(token); - } else if (!nilAttributeFound) { - - jsonObject.accumulate(string, - config.isKeepStrings() - ? ((String) token) - : stringToValue((String) token)); - } - token = null; - } else { - - jsonObject.accumulate(string, ""); - } - - } else if (token == SLASH) { - - // Empty tag <.../> - if (x.nextToken() != GT) { - - throw x.syntaxError("Misshaped tag"); - } - - if (config.getForceList().contains(tagName)) { - - // Force the value to be an array - if (nilAttributeFound) { - context.append(tagName, JSONObject.NULL); - } else if (jsonObject.length() > 0) { - context.append(tagName, jsonObject); - } else { - context.put(tagName, new JSONArray()); - } - } else { - if (nilAttributeFound) { - context.accumulate(tagName, JSONObject.NULL); - } else if (jsonObject.length() > 0) { - context.accumulate(tagName, jsonObject); - } else { - context.accumulate(tagName, ""); - } - } - return false; - - } else if (token == GT) { - // Content, between <...> and - for (;;) { - token = x.nextContent(); - - if (token == null) { - if (tagName != null) { - throw x.syntaxError("Unclosed tag " + tagName); - } - return false; - } - // WHERE STRING VALS ARE PROCESSED - else if (token instanceof String) { - string = (String) token; - - if (string.length() > 0) { - if (xmlXsiTypeConverter != null) { - jsonObject.accumulate(config.getcDataTagName(), - stringToValue(string, xmlXsiTypeConverter)); - } else { - jsonObject.accumulate(config.getcDataTagName(), - config.isKeepStrings() ? string : stringToValue(string)); - } - } - /** - * where RECURSION TO FIND NEW SUBOBJECTS BEGINS - */ - } else if (token == LT) { - // Nested element - if (currentNestingDepth == config.getMaxNestingDepth()) { - throw x.syntaxError( - "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); - } - - if(path_tracker.length > 0 && tagName.equals(path_tracker[0])){ - String [] newArray = path_tracker.length > 1 ? Arrays.copyOfRange(path_tracker, 1, path_tracker.length) : new String[0]; - path_tracker = newArray; - } - if(path_tracker.length == 1){ - - LAST_PATH = path_tracker[0].trim(); - } - - - if (parsed(x, jsonObject, tagName, config, currentNestingDepth + 1, path_tracker,ReturnObj,closeTagFound, LAST_PATH)) { - if(closeTagFound.get()){ - - // while(x.more()){ - // x.skipPast(">"); - // } - } - if (config.getForceList().contains(tagName)) { - // Force the value to be an array - if (jsonObject.length() == 0) { - context.put(tagName, new JSONArray()); - } else if (jsonObject.length() == 1 - && jsonObject.opt(config.getcDataTagName()) != null) { - context.append(tagName, jsonObject.opt(config.getcDataTagName())); - } else { - context.append(tagName, jsonObject); - if(path_tracker.length==1 && tagName.equals(path_tracker[0])){ - ReturnObj.append(tagName, jsonObject); - } - - } - } else { - if (jsonObject.length() == 0) { - context.accumulate(tagName, ""); - - } else if (jsonObject.length() == 1 - && jsonObject.opt(config.getcDataTagName()) != null) { - context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); - if(path_tracker.length==0){ - - ReturnObj.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); - - } - // where subObjects are processed - } else { - // accumulates first subObject - - context.accumulate(tagName, jsonObject); - } - } - - return false; - } - } - } - } else { - - throw x.syntaxError("Misshaped tag"); - } - } - } - } + /** + * PARSE METHOD USED FOR MILESTONE 2 METHOD 2 + * + * @param x - XML Tokener used to parse XML String to JSON + * @param context - JSONObject that is being built from the XML String + * @param name - The tag name. + * @param config - XML configuration setting + * @param currentNestingDepth - The recorded level of recurssion that we are currently on + * @param path_tracker - This string holds the tags of the JSONPointer in order as they are processed + * @param replacement - this is the JSON Object that we want to use to replace the subObject + * @param openTagFound - atomic boolean used to determine if or when an open tag is found + * @param LAST_PATH - string allocation of tagname of the subObject that needs to be replaced + * @param NewTagName - This is the tagName associated with the replacement subObeject. + * @return - boolean value that measures success or failure in parsing XML to JSON + * @throws JSONException + */ private static boolean parsed2(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth, String[] path_tracker, JSONObject replacement, AtomicBoolean openTagFound, String LAST_PATH, String NewTagName) diff --git a/src/test/java/org/json/XMLTest2.java b/src/test/java/org/json/XMLTest2.java index c58497a7f..09034b012 100644 --- a/src/test/java/org/json/XMLTest2.java +++ b/src/test/java/org/json/XMLTest2.java @@ -154,95 +154,5 @@ public void testReplacementNull() { fail("JSONException should definitively not be thrown: " + e.getMessage()); } } - - public static void main(String[] args) { - - - try { - - String xmlString = "\n"+ - "\n"+ - "
\n" + - " Ave of Nowhere\n" + - " 92614\n" + - "
\n" + - " Crista \n" + - " Crista Lopes\n" + - "
"; - - String jsonReplacement = " \n" + - " Muholland Drive\n" + - " 92626\n" + - " \n"; - // String xmlString = "\n" + - // "\n" + - // "
\n" + - // " \n" + - // " Crista \n" + - // " Crista Lopes\n" + - // " \n" + - // " 92614\n" + - // "
\n" + - - // "
"; - - - - Reader reader = new StringReader(xmlString); - JSONPointer pointer = new JSONPointer("/contact/address"); - JSONObject replacement = XML.toJSONObject(jsonReplacement); - - JSONObject result = XML.toJSONObject(reader, pointer,replacement); - - - - - - - System.out.println(result); - String [] x = JSONObject.getNames(result); - - for(String z:x){ - System.out.println(z); - } - assertNotNull("Resulting JSONObject should not be null", result); - // assertEquals(replacement, result.optString("place")); - } catch (JSONException e) { - fail("JSONException should definitively not be thrown: " + e.getMessage()); - } - - } } -// if(parsed2(x,jsonObject,tagName,config,currentNestingDepth+1,path_tracker,replacement,openTagFound,LAST_PATH,NewTagName)){ -// // if (openTagFound.get()) { - -// // context.put(NewTagName, replacement); -// // return false; -// // } -// // if(tagName == LAST_PATH){ -// // return true; -// // } -// if(tagName.equals(NewTagName)){ - -// }if(config.getForceList().contains(tagName)){ -// // Force the value to be an array -// if(jsonObject.length()==0){context.put(tagName,new JSONArray());}else if(jsonObject.length()==1&&jsonObject.opt(config.getcDataTagName())!=null){ - -// context.append(tagName,jsonObject.opt(config.getcDataTagName()));}else{ - -// if(tagName.equals(NewTagName)){context.append(tagName,replacement);}else{context.append(tagName,jsonObject);} - -// }}else{if(jsonObject.length()==0){context.accumulate(tagName,""); - -// }else if(jsonObject.length()==1&&jsonObject.opt(config.getcDataTagName())!=null){System.out.println("pingu");if(tagName.equals(NewTagName)){context.accumulate(tagName,replacement.opt(config.getcDataTagName()));}else{context.accumulate(tagName,jsonObject.opt(config.getcDataTagName()));} - -// // where subObjects are processed -// }else{ -// // accumulates first subObject - -// if(tagName==NewTagName){context.accumulate(tagName,replacement);}else{context.accumulate(tagName,jsonObject);} - -// }} - -// return false;} From 5d594cb62289fa4d5773fb21e5ac6691e0a3bf28 Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Tue, 30 Jan 2024 13:00:28 -0800 Subject: [PATCH 08/16] Add .DS_Store to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b78af4db7..0374660b6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ build /gradlew /gradlew.bat .gitmodules +.DS_Store From 1cc7277e80bdc82ea041218c4e5f0ebdcc73c0ab Mon Sep 17 00:00:00 2001 From: theneelshah Date: Fri, 2 Feb 2024 19:55:30 -0800 Subject: [PATCH 09/16] Fix the case where key without sub obj is used + Add test case to verify. --- src/main/java/org/json/XML.java | 6 +++++- src/test/java/org/json/junit/XMLTest.java | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index f0d88cb10..df7fa3ee2 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -700,6 +700,9 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } else { jsonObject.accumulate(config.getcDataTagName(), config.isKeepStrings() ? string : stringToValue(string)); + if (tagName.equals(constPath[constPath.length - 1])) + requiredObj.accumulate(tagName, + config.isKeepStrings() ? string : stringToValue(string)); } } @@ -740,7 +743,8 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP requiredObj.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); } } else { - context.accumulate(tagName, jsonObject); + if (tagName.equals(constPath[constPath.length - 1])) + context.accumulate(tagName, jsonObject); } } diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index ee1ef6705..e59f65c31 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1496,6 +1496,25 @@ public void customXmlPathExample2() { } + @Test + public void customXmlPathExample3() { + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String json = "{\"street\":\"Ave of Nowhere\"}\n"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), new JSONPointer("/contact/address/street")); + JSONObject expected = new JSONObject(json); + + assertTrue(expected.similar(actual)); + } + } From f58c977d6745ba1d10aa102c10b2371c246614da Mon Sep 17 00:00:00 2001 From: theneelshah Date: Mon, 5 Feb 2024 01:05:03 -0800 Subject: [PATCH 10/16] Add milestone 3 changes --- src/main/java/org/json/XML.java | 221 +++++++ src/test/java/org/json/junit/XMLTest.java | 715 +++++++++++----------- 2 files changed, 582 insertions(+), 354 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index df7fa3ee2..2c89a503e 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -13,6 +13,7 @@ import java.util.Iterator; import java.util.Stack; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import static org.json.NumberConversionUtil.potentialNumber; import static org.json.NumberConversionUtil.stringToNumber; @@ -759,6 +760,212 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } } + + private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, Function keyTransformer) + throws JSONException { + char c; + int i; + JSONObject jsonObject = null; + String string; + String tagName; + Object token; + XMLXsiTypeConverter xmlXsiTypeConverter; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + // "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate(config.getcDataTagName(), string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (config.getForceList().contains(keyTransformer.apply(tagName))) { + // Force the value to be an array + if (nilAttributeFound) { + context.append(keyTransformer.apply(tagName), JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.append(keyTransformer.apply(tagName), jsonObject); + } else { + context.put(keyTransformer.apply(tagName), new JSONArray()); + } + } else { + if (nilAttributeFound) { + context.accumulate(keyTransformer.apply(tagName), JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(keyTransformer.apply(tagName), jsonObject); + } else { + context.accumulate(keyTransformer.apply(tagName), ""); + } + } + return false; + + } else if (token == GT) { + // Content, between <...> and + for (;;) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + if(xmlXsiTypeConverter != null) { + jsonObject.accumulate(config.getcDataTagName(), + stringToValue(string, xmlXsiTypeConverter)); + } else { + jsonObject.accumulate(config.getcDataTagName(), + config.isKeepStrings() ? string : stringToValue(string)); + } + } + + } else if (token == LT) { + // Nested element + if (parse(x, jsonObject, tagName, config, keyTransformer)) { + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.append(keyTransformer.apply(tagName), jsonObject.opt(config.getcDataTagName())); + } else { + context.append(keyTransformer.apply(tagName), jsonObject); + } + } else { + if (jsonObject.length() == 0) { + context.accumulate(keyTransformer.apply(tagName), ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(keyTransformer.apply(tagName), jsonObject.opt(config.getcDataTagName())); + } else { + context.accumulate(keyTransformer.apply(tagName), jsonObject); + } + } + + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + /** * PARSE METHOD USED FOR MILESTONE 2 METHOD 2 * @@ -1304,6 +1511,20 @@ public static JSONObject toJSONObject(Reader reader, JSONPointer path) throws JS return requiredObj; } + public static JSONObject toJSONObject(Reader reader, Function keyTransformer) throws JSONException { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + + while (x.more()) { + x.skipPast("<"); + if (x.more()) { + parse(x, jo, null, XMLParserConfiguration.ORIGINAL, keyTransformer); + } + } + + return jo; + } + /** * SECOND METHOD INPLEMENTATION FOR MILESTONE 2 * @param reader - reads the XML String diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index e59f65c31..d74770293 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -22,31 +22,31 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import org.json.*; 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/ + * 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) + @Test(expected = NullPointerException.class) public void shouldHandleNullXML() { String xmlStr = null; JSONObject jsonObject = XML.toJSONObject(xmlStr); @@ -80,15 +80,14 @@ public void shouldHandleNonXML() { */ @Test public void shouldHandleInvalidSlashInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " abc street\n"+ - "
\n"+ - "
"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " abc street\n" + + "
\n" + + "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -105,15 +104,14 @@ public void shouldHandleInvalidSlashInTag() { */ @Test public void shouldHandleInvalidBangInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " \n"+ - "
\n"+ - "
"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -130,15 +128,14 @@ public void shouldHandleInvalidBangInTag() { */ @Test public void shouldHandleInvalidBangNoCloseInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " \n"+ - ""; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + ""; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -155,15 +152,14 @@ public void shouldHandleInvalidBangNoCloseInTag() { */ @Test public void shouldHandleNoCloseStartTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " \n"+ - " \n"+ - ""; + String xmlStr = "\n" + + "\n" + + "
\n" + + " \n" + + " \n" + + ""; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -180,15 +176,14 @@ public void shouldHandleNoCloseStartTag() { */ @Test public void shouldHandleInvalidCDATABangInTag() { - String xmlStr = - "\n"+ - "\n"+ - "
\n"+ - " Joe Tester\n"+ - " \n"+ - "
\n"+ - "
"; + String xmlStr = "\n" + + "\n" + + "
\n" + + " Joe Tester\n" + + " \n" + + "
\n" + + "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); @@ -204,9 +199,9 @@ public void shouldHandleInvalidCDATABangInTag() { */ @Test public void shouldHandleNullJSONXML() { - JSONObject jsonObject= null; - String actualXml=XML.toString(jsonObject); - assertEquals("generated XML does not equal expected XML","\"null\"",actualXml); + JSONObject jsonObject = null; + String actualXml = XML.toString(jsonObject); + assertEquals("generated XML does not equal expected XML", "\"null\"", actualXml); } /** @@ -214,7 +209,7 @@ public void shouldHandleNullJSONXML() { */ @Test public void shouldHandleEmptyJSONXML() { - JSONObject jsonObject= new JSONObject(); + JSONObject jsonObject = new JSONObject(); String xmlStr = XML.toString(jsonObject); assertTrue("xml string should be empty", xmlStr.isEmpty()); } @@ -224,22 +219,20 @@ public void shouldHandleEmptyJSONXML() { */ @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\"}}"; + 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); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** @@ -247,34 +240,32 @@ public void shouldHandleNoStartTag() { */ @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\"}}"; + 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); @@ -285,44 +276,42 @@ public void shouldHandleSimpleXML() { * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testXmlEscapeToJson(){ - String xmlStr = - "\n"+ - ""+ - "\""+ - "A €33"+ - "A €22€"+ - "some text ©"+ - "" " & ' < >"+ - "𝄢 𐅥" + - ""; - String expectedStr = - "{\"root\":{" + - "\"rawQuote\":\"\\\"\"," + - "\"euro\":\"A €33\"," + - "\"euroX\":\"A €22€\"," + - "\"unknown\":\"some text ©\"," + - "\"known\":\"\\\" \\\" & ' < >\"," + - "\"high\":\"𝄢 𐅥\""+ - "}}"; - + public void testXmlEscapeToJson() { + String xmlStr = "\n" + + "" + + "\"" + + "A €33" + + "A €22€" + + "some text ©" + + "" " & ' < >" + + "𝄢 𐅥" + + ""; + String expectedStr = "{\"root\":{" + + "\"rawQuote\":\"\\\"\"," + + "\"euro\":\"A €33\"," + + "\"euroX\":\"A €22€\"," + + "\"unknown\":\"some text ©\"," + + "\"known\":\"\\\" \\\" & ' < >\"," + + "\"high\":\"𝄢 𐅥\"" + + "}}"; + compareStringToJSONObject(xmlStr, expectedStr); compareReaderToJSONObject(xmlStr, expectedStr); compareFileToJSONObject(xmlStr, expectedStr); } - + /** * Tests that control characters are escaped. */ @Test - public void testJsonToXmlEscape(){ + public void testJsonToXmlEscape() { final String jsonSrc = "{\"amount\":\"10,00 €\"," + "\"description\":\"Ação Válida\u0085\"," + "\"xmlEntities\":\"\\\" ' & < >\"" + "}"; JSONObject json = new JSONObject(jsonSrc); String xml = XML.toString(json); - //test control character not existing + // test control character not existing assertFalse("Escaping \u0085 failed. Found in XML output.", xml.contains("\u0085")); assertTrue("Escaping \u0085 failed. Entity not found in XML output.", xml.contains("…")); // test normal unicode existing @@ -344,24 +333,23 @@ public void testJsonToXmlEscape(){ @Test public void shouldHandleCommentsInXML() { - String xmlStr = - "\n"+ - "\n"+ - "\n"+ - "
\n"+ - " comment ]]>\n"+ - " Joe Tester\n"+ - " \n"+ - " Baker street 5\n"+ - "
\n"+ + 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 -- "+ + String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker " + + "street 5\",\"name\":\"Joe Tester\",\"content\":\" this is -- " + " comment \"}}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** @@ -369,31 +357,29 @@ public void shouldHandleCommentsInXML() { */ @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/"+ + 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); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); } /** @@ -406,8 +392,8 @@ public void shouldHandleContentNoArraytoString() { JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = ">"; - assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ - finalStr+"]", expectedFinalStr, finalStr); + assertEquals("Should handle expectedFinal: [" + expectedStr + "] final: [" + + finalStr + "]", expectedFinalStr, finalStr); } /** @@ -417,15 +403,14 @@ public void shouldHandleContentNoArraytoString() { */ @Test public void shouldHandleContentArraytoString() { - String expectedStr = - "{\"addresses\":{" + - "\"content\":[1, 2, 3]}}"; + String expectedStr = "{\"addresses\":{" + + "\"content\":[1, 2, 3]}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); - String expectedFinalStr = ""+ + String expectedFinalStr = "" + "1\n2\n3"; - assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ - finalStr+"]", expectedFinalStr, finalStr); + assertEquals("Should handle expectedFinal: [" + expectedStr + "] final: [" + + finalStr + "]", expectedFinalStr, finalStr); } /** @@ -434,83 +419,84 @@ public void shouldHandleContentArraytoString() { */ @Test public void shouldHandleArraytoString() { - String expectedStr = - "{\"addresses\":{"+ - "\"something\":[1, 2, 3]}}"; + String expectedStr = "{\"addresses\":{" + + "\"something\":[1, 2, 3]}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); - String expectedFinalStr = ""+ - "123"+ + String expectedFinalStr = "" + + "123" + ""; - assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ - finalStr+"]", expectedFinalStr, finalStr); + assertEquals("Should handle expectedFinal: [" + expectedStr + "] final: [" + + finalStr + "]", expectedFinalStr, finalStr); } - + /** * Tests that the XML output for empty arrays is consistent. */ @Test - public void shouldHandleEmptyArray(){ + public void shouldHandleEmptyArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("array",new Object[]{}); + jo1.put("array", new Object[] {}); final JSONObject jo2 = new JSONObject(); - jo2.put("array",new JSONArray()); + jo2.put("array", new JSONArray()); final String expected = ""; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected an empty root tag", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected an empty root tag", expected, output2); } - + /** - * Tests that the XML output for arrays is consistent when an internal array is empty. + * Tests that the XML output for arrays is consistent when an internal array is + * empty. */ @Test - public void shouldHandleEmptyMultiArray(){ + public void shouldHandleEmptyMultiArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("arr",new Object[]{"One", new String[]{}, "Four"}); + 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"})); + jo2.put("arr", new JSONArray(new Object[] { "One", new JSONArray(new String[] {}), "Four" })); final String expected = "OneFour"; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected a matching array", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected a matching array", expected, output2); } - + /** * Tests that the XML output for arrays is consistent when arrays are not empty. */ @Test - public void shouldHandleNonEmptyArray(){ + public void shouldHandleNonEmptyArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("arr",new String[]{"One", "Two", "Three"}); + jo1.put("arr", new String[] { "One", "Two", "Three" }); final JSONObject jo2 = new JSONObject(); - jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"})); + jo2.put("arr", new JSONArray(new String[] { "One", "Two", "Three" })); final String expected = "OneTwoThree"; - String output1 = XML.toString(jo1,"jo"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected a non empty root tag", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected a non empty root tag", expected, output2); } /** - * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays. + * Tests that the XML output for arrays is consistent when arrays are not empty + * and contain internal arrays. */ @Test - public void shouldHandleMultiArray(){ + public void shouldHandleMultiArray() { final JSONObject jo1 = new JSONObject(); - jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"}); + 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"})); + 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"); + String output1 = XML.toString(jo1, "jo"); assertEquals("Expected a matching array", expected, output1); - String output2 = XML.toString(jo2,"jo"); + String output2 = XML.toString(jo2, "jo"); assertEquals("Expected a matching array", expected, output2); } @@ -520,33 +506,30 @@ public void shouldHandleMultiArray(){ */ @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\"}}"; + 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"+ + String expectedStr = "
" + + "12" + + "3" + + "
test.xsdhttp://www.w3.org/2001/XMLSche" + "ma-instance
"; JSONObject expectedJsonObject = XML.toJSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject); } - /** - * Possible bug: + * 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() - { + public void shouldHandleIllegalJSONNodeNames() { JSONObject inputJSON = new JSONObject(); inputJSON.append("123IllegalNode", "someValue1"); inputJSON.append("Illegal@node", "someValue2"); @@ -557,27 +540,27 @@ public void shouldHandleIllegalJSONNodeNames() * 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 + * <___IllegalNode>someValue1someValue2 */ String expected = "<123IllegalNode>someValue1someValue2"; - assertEquals("length",expected.length(), result.length()); - assertTrue("123IllegalNode",result.contains("<123IllegalNode>someValue1")); - assertTrue("Illegal@node",result.contains("someValue2")); + assertEquals("length", expected.length(), result.length()); + assertTrue("123IllegalNode", result.contains("<123IllegalNode>someValue1")); + assertTrue("Illegal@node", result.contains("someValue2")); } /** * JSONObject with NULL value, to XML.toString() */ @Test - public void shouldHandleNullNodeValue() - { + 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 + * This is the current behavior. JSONObject.NULL is emitted as * the string, "null". */ String actualXML = "null"; @@ -615,20 +598,21 @@ public void contentOperations() { /* * 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. + * 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"; + 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 + * 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"; + xmlStr = "value 12true"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("4. 1 item", 1 == jsonObject.length()); assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray); @@ -640,10 +624,10 @@ public void contentOperations() { /* * 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. + * may contain a mix of child elements and text. Each text segment is + * accumulated to content. */ - xmlStr = "val1val2"; + xmlStr = "val1val2"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("5. 1 item", 1 == jsonObject.length()); assertTrue("5. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); @@ -657,10 +641,10 @@ public void contentOperations() { 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 + * If there is only 1 complex text content, then it is accumulated in a * "content" field as a string. */ - xmlStr = "val1"; + xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("6. 1 item", 1 == jsonObject.length()); assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); @@ -671,9 +655,9 @@ public void contentOperations() { /* * 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. + * This is probably a bug. */ - xmlStr = "val1"; + xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("7. 1 item", 1 == jsonObject.length()); assertTrue("7. jsonArray found", jsonObject.get("tag1") instanceof JSONArray); @@ -688,42 +672,41 @@ public void contentOperations() { /* * Confirm behavior of original issue */ - String jsonStr = - "{"+ - "\"Profile\": {"+ - "\"list\": {"+ - "\"history\": {"+ - "\"entries\": ["+ - "{"+ - "\"deviceId\": \"id\","+ - "\"content\": {"+ - "\"material\": ["+ - "{"+ - "\"stuff\": false"+ - "}"+ - "]"+ - "}"+ - "}"+ - "]"+ - "}"+ - "}"+ - "}"+ + 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}]} - * - * - * - * + * complex (child node + text) XML. + * + * + * + * + * id + * {"material":[{"stuff":false}]} + * + * + * + * */ assertTrue("nothing to test here, see comment on created XML, above", true); } @@ -731,26 +714,28 @@ public void contentOperations() { /** * 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 xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareStringToJSONObject(String xmlStr, String expectedStr) { JSONObject jsonObject = XML.toJSONObject(xmlStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + 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 xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { JSONObject expectedJsonObject = new JSONObject(expectedStr); Reader reader = new StringReader(xmlStr); JSONObject jsonObject = XML.toJSONObject(reader); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } /** @@ -758,9 +743,9 @@ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { * JSONObject via file and compare actual to expected result. * * @param xmlStr - * the string to parse + * the string to parse * @param expectedStr - * the expected JSON string + * the expected JSON string * @throws IOException */ private void compareFileToJSONObject(String xmlStr, String expectedStr) { @@ -777,12 +762,12 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) { Reader reader = new FileReader(tempFile); try { JSONObject jsonObject = XML.toJSONObject(reader); - Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); + Util.compareActualVsExpectedJsonObjects(jsonObject, expectedJsonObject); } finally { reader.close(); } } catch (IOException e) { - fail("Error: " +e.getMessage()); + fail("Error: " + e.getMessage()); } } @@ -792,10 +777,11 @@ private void compareFileToJSONObject(String xmlStr, String expectedStr) { @Test public void testToJSONArray_jsonOutput() { final String originalXml = "011000True"; - final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}"); + final JSONObject expectedJson = new JSONObject( + "{\"root\":{\"item\":{\"id\":1},\"id\":[1,1,0,0],\"title\":true}}"); final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false); - Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJsonOutput, expectedJson); } /** @@ -815,23 +801,24 @@ public void testToJSONArray_reversibility() { @Test public void testToJsonXML() { final String originalXml = "011000True"; - final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"); + final JSONObject expected = new JSONObject( + "{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"); + + final JSONObject actual = XML.toJSONObject(originalXml, true); - final JSONObject actual = XML.toJSONObject(originalXml,true); - Util.compareActualVsExpectedJsonObjects(actual, expected); - + final String reverseXml = XML.toString(actual); // this reversal isn't exactly the same. use JSONML for an exact reversal // the order of the elements may be differnet as well. final String expectedReverseXml = "01011000True"; - assertEquals("length",expectedReverseXml.length(), reverseXml.length()); + assertEquals("length", expectedReverseXml.length(), reverseXml.length()); assertTrue("array contents", reverseXml.contains("011000")); assertTrue("item contents", reverseXml.contains("01")); assertTrue("title contents", reverseXml.contains("True")); } - + /** * test to validate certain conditions of XML unescaping. */ @@ -858,15 +845,16 @@ public void testUnescape() { assertEquals("{\"xml\":\"Can cope <\"}", XML.toJSONObject("Can cope &lt; ").toString()); assertEquals("Can cope < ", XML.unescape("Can cope &lt; ")); - + assertEquals("{\"xml\":\"Can cope 4\"}", XML.toJSONObject("Can cope &#x34; ").toString()); assertEquals("Can cope 4 ", XML.unescape("Can cope &#x34; ")); - } + } /** - * test passes when xsi:nil="true" converting to null (JSON specification-like nil conversion enabled) + * test passes when xsi:nil="true" converting to null (JSON specification-like + * nil conversion enabled) */ @Test public void testToJsonWithNullWhenNilConversionEnabled() { @@ -875,14 +863,15 @@ public void testToJsonWithNullWhenNilConversionEnabled() { final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration() - .withKeepStrings(false) - .withcDataTagName("content") - .withConvertNilAttributeToNull(true)); + .withKeepStrings(false) + .withcDataTagName("content") + .withConvertNilAttributeToNull(true)); assertEquals(expectedJsonString, json.toString()); } /** - * test passes when xsi:nil="true" not converting to null (JSON specification-like nil conversion disabled) + * test passes when xsi:nil="true" not converting to null (JSON + * specification-like nil conversion disabled) */ @Test public void testToJsonWithNullWhenNilConversionDisabled() { @@ -897,12 +886,10 @@ public void testToJsonWithNullWhenNilConversionDisabled() { * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testIssue537CaseSensitiveHexEscapeMinimal(){ - String xmlStr = - "\n"+ - "Neutrophils.Hypersegmented | Bld-Ser-Plas"; - String expectedStr = - "{\"root\":\"Neutrophils.Hypersegmented | Bld-Ser-Plas\"}"; + public void testIssue537CaseSensitiveHexEscapeMinimal() { + String xmlStr = "\n" + + "Neutrophils.Hypersegmented | Bld-Ser-Plas"; + String expectedStr = "{\"root\":\"Neutrophils.Hypersegmented | Bld-Ser-Plas\"}"; JSONObject xmlJSONObj = XML.toJSONObject(xmlStr, true); JSONObject expected = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(xmlJSONObj, expected); @@ -912,7 +899,7 @@ public void testIssue537CaseSensitiveHexEscapeMinimal(){ * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testIssue537CaseSensitiveHexEscapeFullFile(){ + public void testIssue537CaseSensitiveHexEscapeFullFile() { try { InputStream xmlStream = null; try { @@ -923,7 +910,7 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){ try { jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.json"); final JSONObject expected = new JSONObject(new JSONTokener(jsonStream)); - Util.compareActualVsExpectedJsonObjects(actual,expected); + Util.compareActualVsExpectedJsonObjects(actual, expected); } finally { if (jsonStream != null) { jsonStream.close(); @@ -935,7 +922,7 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){ } } } catch (IOException e) { - fail("file writer error: " +e.getMessage()); + fail("file writer error: " + e.getMessage()); } } @@ -943,14 +930,12 @@ public void testIssue537CaseSensitiveHexEscapeFullFile(){ * Tests to verify that supported escapes in XML are converted to actual values. */ @Test - public void testIssue537CaseSensitiveHexUnEscapeDirect(){ - String origStr = - "Neutrophils.Hypersegmented | Bld-Ser-Plas"; - String expectedStr = - "Neutrophils.Hypersegmented | Bld-Ser-Plas"; + public void testIssue537CaseSensitiveHexUnEscapeDirect() { + String origStr = "Neutrophils.Hypersegmented | Bld-Ser-Plas"; + String expectedStr = "Neutrophils.Hypersegmented | Bld-Ser-Plas"; String actualStr = XML.unescape(origStr); - - assertEquals("Case insensitive Entity unescape", expectedStr, actualStr); + + assertEquals("Case insensitive Entity unescape", expectedStr, actualStr); } /** @@ -962,7 +947,7 @@ public void testToJsonWithTypeWhenTypeConversionDisabled() { String expectedJsonString = "{\"root\":{\"id\":{\"xsi:type\":\"string\",\"content\":1234}}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration()); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } /** @@ -976,17 +961,19 @@ public void testToJsonWithTypeWhenTypeConversionEnabled() { JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); xsiTypeMap.put("integer", new XMLXsiTypeConverter() { - @Override public Integer convert(final String value) { + @Override + public Integer convert(final String value) { return Integer.valueOf(value); } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } @Test @@ -997,17 +984,19 @@ public void testToJsonWithXSITypeWhenTypeConversionEnabled() { JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); xsiTypeMap.put("integer", new XMLXsiTypeConverter() { - @Override public Integer convert(final String value) { + @Override + public Integer convert(final String value) { return Integer.valueOf(value); } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } @Test @@ -1017,12 +1006,13 @@ public void testToJsonWithXSITypeWhenTypeConversionNotEnabledOnOne() { JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); - Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); + Util.compareActualVsExpectedJsonObjects(actualJson, expectedJson); } @Test @@ -1030,7 +1020,8 @@ public void testXSITypeMapNotModifiable() { Map> xsiTypeMap = new HashMap>(); XMLParserConfiguration config = new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap); xsiTypeMap.put("string", new XMLXsiTypeConverter() { - @Override public String convert(final String value) { + @Override + public String convert(final String value) { return value; } }); @@ -1038,16 +1029,18 @@ public void testXSITypeMapNotModifiable() { try { config.getXsiTypeMap().put("boolean", new XMLXsiTypeConverter() { - @Override public Boolean convert(final String value) { + @Override + public Boolean convert(final String value) { return Boolean.valueOf(value); } }); fail("Expected to be unable to modify the config"); - } catch (Exception ignored) { } + } catch (Exception ignored) { + } } @Test - public void testIndentComplicatedJsonObject(){ + public void testIndentComplicatedJsonObject() { String str = "{\n" + " \"success\": true,\n" + " \"error\": null,\n" + @@ -1110,7 +1103,7 @@ public void testIndentComplicatedJsonObject(){ " }\n" + " }\n" + " ]\n" + - "}" ; + "}"; JSONObject jsonObject = new JSONObject(str); String actualIndentedXmlString = XML.toString(jsonObject, 1); JSONObject actualJsonObject = XML.toJSONObject(actualIndentedXmlString); @@ -1176,34 +1169,32 @@ public void testIndentComplicatedJsonObject(){ JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); - } @Test - public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured(){ + public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured() { String jsonString = "{outer:{innerOne:\"\", innerTwo:\"two\"}}"; JSONObject jsonObject = new JSONObject(jsonString); String expectedXmlString = "two"; - String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(true)); + String xmlForm = XML.toString(jsonObject, "encloser", new XMLParserConfiguration().withCloseEmptyTag(true)); JSONObject actualJsonObject = XML.toJSONObject(xmlForm); JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test - public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured(){ + public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured() { String jsonString = "{outer:{innerOne:\"\", innerTwo:\"two\"}}"; JSONObject jsonObject = new JSONObject(jsonString); String expectedXmlString = "two"; - String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(false)); + String xmlForm = XML.toString(jsonObject, "encloser", new XMLParserConfiguration().withCloseEmptyTag(false)); JSONObject actualJsonObject = XML.toJSONObject(xmlForm); JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString); assertTrue(expectedJsonObject.similar(actualJsonObject)); } - @Test - public void testIndentSimpleJsonObject(){ + public void testIndentSimpleJsonObject() { String str = "{ \"employee\": { \n" + " \"name\": \"sonoo\", \n" + " \"salary\": 56000, \n" + @@ -1224,7 +1215,7 @@ public void testIndentSimpleJsonObject(){ } @Test - public void testIndentSimpleJsonArray(){ + public void testIndentSimpleJsonArray() { String str = "[ \n" + " {\"name\":\"Ram\", \"email\":\"Ram@gmail.com\"}, \n" + " {\"name\":\"Bob\", \"email\":\"bob32@gmail.com\"} \n" + @@ -1243,11 +1234,10 @@ public void testIndentSimpleJsonArray(){ JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); - } @Test - public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ + public void testIndentComplicatedJsonObjectWithArrayAndWithConfig() { try (InputStream jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.json")) { final JSONObject object = new JSONObject(new JSONTokener(jsonStream)); String actualString = XML.toString(object, null, XMLParserConfiguration.KEEP_STRINGS, 2); @@ -1256,13 +1246,13 @@ public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ char[] buffer = new char[bufferSize]; StringBuilder expected = new StringBuilder(); Reader in = new InputStreamReader(xmlStream, "UTF-8"); - for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) { + for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0;) { expected.append(buffer, 0, numRead); } assertTrue(XML.toJSONObject(expected.toString()).similar(XML.toJSONObject(actualString))); } } catch (IOException e) { - fail("file writer error: " +e.getMessage()); + fail("file writer error: " + e.getMessage()); } } @@ -1273,24 +1263,25 @@ public void testMaxNestingDepthOf42IsRespected() { final int maxNestingDepth = 42; try { - XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); + XML.toJSONObject(wayTooLongMalformedXML, + XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); fail("Expecting a JSONException"); } catch (JSONException e) { assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", - e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); + e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); } } @Test public void testMaxNestingDepthIsRespectedWithValidXML() { final String perfectlyFineXML = "\n" + - " \n" + - " sonoo\n" + - " 56000\n" + - " true\n" + - " \n" + - "\n"; + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\n"; final int maxNestingDepth = 1; @@ -1300,19 +1291,19 @@ public void testMaxNestingDepthIsRespectedWithValidXML() { fail("Expecting a JSONException"); } catch (JSONException e) { assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", - e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); + e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); } } @Test public void testMaxNestingDepthWithValidFittingXML() { final String perfectlyFineXML = "\n" + - " \n" + - " sonoo\n" + - " 56000\n" + - " true\n" + - " \n" + - "\n"; + " \n" + + " sonoo\n" + + " 56000\n" + + " true\n" + + " \n" + + "\n"; final int maxNestingDepth = 3; @@ -1321,9 +1312,10 @@ public void testMaxNestingDepthWithValidFittingXML() { } catch (JSONException e) { e.printStackTrace(); fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " + - "parameter of the XMLParserConfiguration used"); + "parameter of the XMLParserConfiguration used"); } } + /** * This method tests the efficacy of the milestone 2 method 2 * As you can see the initial xml object is taken and a replacement is @@ -1348,21 +1340,18 @@ public void testReplacement() { "
\n" + ""; - String jsonReplacement = - " \n" + - " Muholland Drive\n" + - " 92626\n" + - " \n"; + String jsonReplacement = " \n" + + " Muholland Drive\n" + + " 92626\n" + + " \n"; String expected = "{\"contact\":{\"place\":{\"zipcode\":92626,\"street\":\"Muholland Drive\"}}}"; - Reader reader = new StringReader(xmlString); JSONPointer pointer = new JSONPointer("/contact/address"); JSONObject replacement = XML.toJSONObject(jsonReplacement); - - JSONObject result = XML.toJSONObject(reader, pointer,replacement); + JSONObject result = XML.toJSONObject(reader, pointer, replacement); System.out.println(result); assertNotNull("Resulting JSONObject should not be null", result); assertEquals(expected, result.toString()); @@ -1370,11 +1359,15 @@ public void testReplacement() { fail("JSONException should definitively not be thrown: " + e.getMessage()); } } + /** - * Further test efficacy of milestone 2 method 2 implementation by checking a different - * XMLString for a subObject. The structure of this one is different and therefore allows + * Further test efficacy of milestone 2 method 2 implementation by checking a + * different + * XMLString for a subObject. The structure of this one is different and + * therefore allows * for a different means by which to verify the working order of this method. - * it checks to see if the subObject key has been placed within the xmlString and + * it checks to see if the subObject key has been placed within the xmlString + * and * then it further checks if these values are able to be accessed. */ @Test @@ -1395,7 +1388,6 @@ public void testReplacement2() { " 92626\n" + " \n"; - // reader for XML String Reader reader = new StringReader(xmlString); // pointer to locate the subObject that needs to be extracted @@ -1403,10 +1395,9 @@ public void testReplacement2() { // turning XML String of replacement into a JSON object JSONObject replacement = XML.toJSONObject(jsonReplacement); - // getting the answer + // getting the answer JSONObject result = XML.toJSONObject(reader, pointer, replacement); - JSONObject address = result.getJSONObject("contact").getJSONObject("place"); assert result.getJSONObject("contact").has("place"); assert address.getString("street").equals("Muholland Drive"); @@ -1415,6 +1406,7 @@ public void testReplacement2() { fail("JSONException should definitively not be thrown: " + e.getMessage()); } } + /** * What happens if we try to use a pointer to a subObject that doesn't exist? * We would expect that nothing happens and the same object is returned @@ -1515,7 +1507,22 @@ public void customXmlPathExample3() { assertTrue(expected.similar(actual)); } -} + @Test + public void updateKeys() { + Function addPrefix = (str) -> "swe262_" + str.toUpperCase(); + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + JSONObject actual = XML.toJSONObject(new StringReader(xml), addPrefix); + System.out.println(actual.toString()); + } +} From 2e420610b1aeef7b554f8811e19027d504aa2319 Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Mon, 5 Feb 2024 16:54:52 -0800 Subject: [PATCH 11/16] added some tests to the tester file and wrote the UCI_Project_README.md for reference on milestone III --- UCI_Project_README.md | 22 +++++ src/test/java/org/json/junit/XMLTest.java | 113 +++++++++++++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 UCI_Project_README.md diff --git a/UCI_Project_README.md b/UCI_Project_README.md new file mode 100644 index 000000000..b6f9a3ea1 --- /dev/null +++ b/UCI_Project_README.md @@ -0,0 +1,22 @@ +# Milestone II Changes + +All edits for Milestone II can be found within the `XMLTest.java` and `XML.java` files. We introduced two new overloaded parse methods, in addition to the two methods we were required to implement. These implementations are used to facilitate both of the required methods. Testing methods for these changes are located at the bottom of the `XMLTest.java` file. + +# Milestone III Changes + +For Milestone III, modifications were made within the `XMLTest.java` and `XML.java` files. All tests related to these changes are located at the bottom of the XML testing file. Within `XML.java`, we added two new methods: an overloaded parse method that accepts a function parameter and the `toJSONObject` method, which utilizes the new parse method for its operation and takes in a function parameter. + +# Milestone III Discussion + +In this phase, we introduced an additional parse method that accepts a Java `Function` object. This function is designed to take in a key as an object and return a modified key as an object. It integrates seamlessly with the parse method, allowing for real-time modification of keys during the XML to JSON conversion process. This functionality enables the dynamic alteration of keys to reflect the desired changes specified by the input function parameter. + +# Increased Efficiency with the Changes + +The recent enhancements to the parse method offer significant efficiency improvements over the initial Milestone I implementation, for several reasons: + +- **Milestone I Implementation:** + - Converted the entire XML object into a JSON string. + - Employed a recursive method to locate each object key and nested object key, replacing the value with a prefix. + +- **New Implementation:** + - Directly replaces each key encountered during the JSON conversion process with a new key, applying the provided function to modify the key. diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index d74770293..d4eb9a016 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1506,7 +1506,14 @@ public void customXmlPathExample3() { assertTrue(expected.similar(actual)); } - + + /** + * MILESTONE III TEST + * + * Determines working order of new toJSONObject method + * successfully transforms all keys based upon input function + * parameters + */ @Test public void updateKeys() { Function addPrefix = (str) -> "swe262_" + str.toUpperCase(); @@ -1519,10 +1526,114 @@ public void updateKeys() { " Crista \n" + " Crista Lopes\n" + ""; + + String expected = "{\"swe262_CONTACT\":{\"swe262_NAME\":\"Crista Lopes\",\"swe262_NICK\":\"Crista\",\"swe262_ADDRESS\":{\"swe262_STREET\":\"Ave of Nowhere\",\"swe262_ZIPCODE\":92614}}}"; JSONObject actual = XML.toJSONObject(new StringReader(xml), addPrefix); + assertEquals(expected, actual.toString()); + } + + /* + * MILESTONE III TEST + * + * Deletes part of the key + */ + @Test + public void updateKeys2() { + Function change = (str) -> str.substring(0, str.length() - 2); + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String expected = "{\"conta\":{\"addre\":{\"stre\":\"Ave of Nowhere\",\"zipco\":92614},\"na\":\"Crista Lopes\",\"ni\":\"Crista\"}}"; + + JSONObject actual = XML.toJSONObject(new StringReader(xml), change); + assertEquals(expected, actual.toString()); + } + + /* + * MILESTONE III TEST + * + * removes key + */ + @Test + public void updateKeys3() { + Function change = (str) -> " "; + String xml = "\n" + + "\n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + + "
"; + + String expected = "{\" \":{\" \":[{\" \":[\"Ave of Nowhere\",92614]},\"Crista\",\"Crista Lopes\"]}}"; + + + JSONObject actual = XML.toJSONObject(new StringReader(xml), change); System.out.println(actual.toString()); + assertEquals(expected, actual.toString()); + } + + /* + * MILESTONE III TEST + * + * test a more complex structure to verify working order + */ + @Test + public void updateKeys4() { + Function change = (str) -> "good morning"; + String xml = "\n" + + "\n" + + " \n" + + " Crista Lopes\n" + + " Crista\n" + + " clopes@example.com\n" + + " \n" + + "
\n" + + " Ave of Nowhere\n" + + " Unknown City\n" + + " CA\n" + + " 92614\n" + + " USA\n" + + "
\n" + + " \n" + + " 123-456-7890\n" + + " 098-765-4321\n" + + " \n" + + " \n" + + " University of Somewhere\n" + + " Professor\n" + + " Computer Science\n" + + " \n" + + " Engineering Hall\n" + + " 1234\n" + + " \n" + + " \n" + + " \n" + + " @crista\n" + + " linkedin.com/in/cristalopes\n" + + " \n" + + "
"; + + String expected = "{\"good morning\":{\"good morning\":[{\"good morning\":" + + "[\"Crista Lopes\",\"Crista\",\"clopes@example.com\"]},{\"good morning\":" + + "[\"Ave of Nowhere\",\"Unknown City\",\"CA\",92614,\"USA\"]},{\"good morning\":" + + "[\"123-456-7890\",\"098-765-4321\"]},{\"good morning\":[\"University of Somewhere\"," + + "\"Professor\",\"Computer Science\",{\"good morning\":[\"Engineering Hall\",1234]}]}," + + "{\"good morning\":[\"@crista\",\"linkedin.com/in/cristalopes\"]}]}}"; + + JSONObject actual = XML.toJSONObject(new StringReader(xml), change); + assertEquals(expected, actual.toString()); } } From 1c833a7539bf390cf098f2f2714c4c4203e6c146 Mon Sep 17 00:00:00 2001 From: theneelshah Date: Thu, 22 Feb 2024 19:22:12 -0800 Subject: [PATCH 12/16] Add the logic to convert JSON to stream of nodes --- src/main/java/org/json/JSONObject.java | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 039f136de..11c8073a5 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -24,9 +24,15 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.Stack; import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import static org.json.NumberConversionUtil.potentialNumber; import static org.json.NumberConversionUtil.stringToNumber; @@ -2884,5 +2890,63 @@ private static JSONException recursivelyDefinedObjectException(String key) { ); } + public Stream toStream() { + return StreamSupport + .stream(Spliterators.spliteratorUnknownSize(new JSONObjectIterator(this), Spliterator.ORDERED), false); + } + + private static class JSONObjectIterator implements Iterator { + private final Stack stack; + + JSONObjectIterator(JSONObject jsonObject) { + this.stack = new Stack<>(); + stack.push(jsonObject); + } + + @Override + public boolean hasNext() { + return !stack.isEmpty(); + } + + @Override + public JSONObject next() { + if (!hasNext()) + throw new NoSuchElementException(); + + Object current = stack.pop(); + + if (current instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) current; + + for (String key : jsonObject.keySet()) { + Object value = jsonObject.get(key); + + if (value instanceof JSONObject || value instanceof JSONArray) { + stack.push(value); + } + } + + return jsonObject; + } else if (current instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) current; + + for (int i = jsonArray.length() - 1; i >= 0; i--) { + Object value = jsonArray.get(i); + + if (value instanceof JSONObject || value instanceof JSONArray) { + stack.push(value); + } + } + + return null; // Null indicates that we're iterating over a JSONArray + } else { + throw new UnsupportedOperationException("JSONObjectStream only supports JSONObject and JSONArray"); + } + } + } + + public static Iterable stream(JSONObject jsonObject) { + return () -> new JSONObjectIterator(jsonObject); + } } From a6bca85062b7ce31e257533edced57a9d652c809 Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Fri, 23 Feb 2024 13:28:08 -0800 Subject: [PATCH 13/16] some testing files added for milestone 4 --- .../java/org/json/junit/JSONObjectTest.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 053f17a91..515c0899b 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -26,6 +26,7 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.json.CDL; import org.json.JSONArray; @@ -57,6 +58,7 @@ import org.json.junit.data.Singleton; import org.json.junit.data.SingletonEnum; import org.json.junit.data.WeirdList; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -3817,4 +3819,60 @@ public static HashMap buildNestedMap(int maxDepth) { return nestedMap; } + /** + * Test to determine validy of the stream method. + */ + @Test + public void streamTest() { + JSONObject obj = XML.toJSONObject( + "AAAASmithBBBBSmith"); + + JSONObject[] allNodesExpected = new JSONObject[] { + new JSONObject( + "{\"Books\":{\"book\":[{\"author\":\"ASmith\",\"title\":\"AAA\"},{\"author\":\"BSmith\",\"title\":\"BBB\"}]}}"), + new JSONObject( + "{\"book\":[{\"author\":\"ASmith\",\"title\":\"AAA\"},{\"author\":\"BSmith\",\"title\":\"BBB\"}]}"), + new JSONObject("{\"author\":\"ASmith\",\"title\":\"AAA\"}"), + new JSONObject("{\"author\":\"BSmith\",\"title\":\"BBB\"}") }; + + // Get All the nodes + JSONObject[] allNodesActual = obj.toStream().filter(node -> node != null).toArray(JSONObject[]::new); + + for (int i = 0; i < allNodesActual.length; i += 1) { + assertEquals(allNodesActual[i].toString(), allNodesExpected[i].toString()); + } + } + + @Test + // TEST TO SEE WE CAN GET TITLES + public void streamTest2(){ + JSONObject obj = XML.toJSONObject( + "AAAASmithBBBBSmith"); + + List titles = obj.toStream() + .filter(node -> node != null) + .map(node -> node.optString("title")) + .filter(x -> x.length() > 0) + .collect(Collectors.toList()); + + String expectedTitles[] = { "AAA", "BBB" }; + + for (int i = 0; i < titles.size(); i += 1) { + assertEquals(expectedTitles[i], titles.get(i)); + } + } + @Test + // TEST TO SEE IF WE CAN GET A PARTICULAR VALUE + public void streamTest3() { + + JSONObject obj = XML.toJSONObject( + "AAAASmithBBBBSmith"); + + JSONObject[] expected = obj.toStream().filter(node -> node != null) + .filter(node -> node.has("author") && + "ASmith".equals(node.optString("author"))) + .toArray(JSONObject[]::new); + + assertEquals(expected[0].toString(), "{\"author\":\"ASmith\",\"title\":\"AAA\"}"); + } } From 1e0b73dcee5189dd43a62796bea958b4960d1630 Mon Sep 17 00:00:00 2001 From: theneelshah Date: Thu, 7 Mar 2024 20:00:19 -0800 Subject: [PATCH 14/16] Add milestone5 changes + Use an ExecutorSevice to run the JSON conversion asynchronously. --- src/main/java/org/json/XML.java | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 2c89a503e..d24e60dcc 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -12,7 +12,11 @@ import java.util.Arrays; import java.util.Iterator; import java.util.Stack; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.function.Function; import static org.json.NumberConversionUtil.potentialNumber; @@ -1355,6 +1359,26 @@ public static JSONObject toJSONObject(String string) throws JSONException { return toJSONObject(string, XMLParserConfiguration.ORIGINAL); } + public static void toJsonObject(Reader reader, Consumer callback, + Consumer exceptionCallback) { + AtomicReference converted = new AtomicReference<>(new JSONObject()); + ExecutorService executor = Executors.newSingleThreadExecutor(); + + executor.submit(() -> { + try { + JSONObject jsonObject = toJSONObject(reader); + converted.set(jsonObject); + + callback.accept(jsonObject); + } catch (Exception e) { + exceptionCallback.accept(e); + } + }); + + executor.shutdown(); + } + + /** * Convert a well-formed (but not necessarily valid) XML into a * JSONObject. Some information may be lost in this transformation because From 4528728ad5c2ed53e420436989409284af9a715c Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Fri, 8 Mar 2024 20:53:32 -0800 Subject: [PATCH 15/16] added some tests for milestone V --- src/main/java/org/json/XML.java | 14 ++- src/test/java/org/json/junit/XMLTest.java | 131 +++++++++++++++------- 2 files changed, 102 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index d24e60dcc..59c73d2ee 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -1358,7 +1358,19 @@ public static Object stringToValue(String string) { public static JSONObject toJSONObject(String string) throws JSONException { return toJSONObject(string, XMLParserConfiguration.ORIGINAL); } - + /** + * Milestone V Method + * This method takes in a Reader Object as well as consumer functions that operate on the + * jsonObject after the executor thread has properly parsed the reader xml to JSON. This works + * with the use of an atomic reference that is thread safe and after which the callback passed + * within the method is utilized on the jsonObject that is parsed. if the jsonObject was unable + * to be created, then the exception consumer is passed on the error to be forwaded to the method + * user + * + * @param reader + * @param callback + * @param exceptionCallback + */ public static void toJsonObject(Reader reader, Consumer callback, Consumer exceptionCallback) { AtomicReference converted = new AtomicReference<>(new JSONObject()); diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index d4eb9a016..0195273be 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -8,6 +8,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -22,6 +23,10 @@ import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import org.json.*; @@ -1585,55 +1590,97 @@ public void updateKeys3() { } /* - * MILESTONE III TEST + * MILESTONE V TEST * - * test a more complex structure to verify working order + * tests the xml to json conversion capabilities on simple object */ @Test - public void updateKeys4() { - Function change = (str) -> "good morning"; + public void testV() throws InterruptedException { + String xml = "Value"; + CountDownLatch latch = new CountDownLatch(1); // To wait for the async operation + AtomicReference resultRef = new AtomicReference<>(); + + XML.toJsonObject( + new StringReader(xml), + jsonObject -> { + resultRef.set(jsonObject); + latch.countDown(); // Signal completion of async operation + }, + exception -> fail("Exception should not occur")); + + latch.await(); // Wait for the async operation to complete + JSONObject expected = new JSONObject("{\"root\":{\"element\":\"Value\"}}"); + assertEquals(expected.toString(), resultRef.get().toString()); + } + + /** + * Test async XML to JSON error handling capacities + * @throws InterruptedException + */ + @Test + public void testV2() throws InterruptedException { + String invalidXml = "Value"; // Missing closing tag + CountDownLatch latch = new CountDownLatch(1); + AtomicReference exceptionRef = new AtomicReference<>(); + + XML.toJsonObject( + new StringReader(invalidXml), + jsonObject -> fail("Success callback should not be called for invalid XML"), + exception -> { + exceptionRef.set(exception); + latch.countDown(); + }); + + latch.await(); + assertNotNull(exceptionRef.get()); + assertTrue(exceptionRef.get() instanceof JSONException); + } + /** + * Want to test if XML is being parsed properly on larger object + */ + @Test + public void testV3() { + String xml = "\n" + "\n" + - " \n" + - " Crista Lopes\n" + - " Crista\n" + - " clopes@example.com\n" + - " \n" + - "
\n" + - " Ave of Nowhere\n" + - " Unknown City\n" + - " CA\n" + - " 92614\n" + - " USA\n" + - "
\n" + - " \n" + - " 123-456-7890\n" + - " 098-765-4321\n" + - " \n" + - " \n" + - " University of Somewhere\n" + - " Professor\n" + - " Computer Science\n" + - " \n" + - " Engineering Hall\n" + - " 1234\n" + - " \n" + - " \n" + - " \n" + - " @crista\n" + - " linkedin.com/in/cristalopes\n" + - " \n" + + "
\n" + + " Ave of Nowhere\n" + + " 92614\n" + + "
\n" + + " Crista \n" + + " Crista Lopes\n" + "
"; - String expected = "{\"good morning\":{\"good morning\":[{\"good morning\":" - + "[\"Crista Lopes\",\"Crista\",\"clopes@example.com\"]},{\"good morning\":" - + "[\"Ave of Nowhere\",\"Unknown City\",\"CA\",92614,\"USA\"]},{\"good morning\":" - + "[\"123-456-7890\",\"098-765-4321\"]},{\"good morning\":[\"University of Somewhere\"," - + "\"Professor\",\"Computer Science\",{\"good morning\":[\"Engineering Hall\",1234]}]}," - + "{\"good morning\":[\"@crista\",\"linkedin.com/in/cristalopes\"]}]}}"; + String jsonString = "{\"contact\":{\"nick\":\"Crista\",\"address\":{\"zipcode\":92614,\"street\":\"Ave of Nowhere\"},\"name\":\"Crista Lopes\"}}"; + AtomicBoolean isJsonAsExpected = new AtomicBoolean(false); + AtomicReference exceptionRef = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + + XML.toJsonObject( + new StringReader(xml), + jsonObject -> { + System.out.println(jsonObject.toString()); + if (jsonObject != null && jsonObject.toString().equals(jsonString)) { + isJsonAsExpected.set(true); + System.out.println(jsonObject.toString()); + } + latch.countDown(); + }, + exception -> { + exceptionRef.set(exception); + latch.countDown(); + }); - JSONObject actual = XML.toJSONObject(new StringReader(xml), change); - assertEquals(expected, actual.toString()); + try { + boolean completed = latch.await(2, TimeUnit.SECONDS); + assertTrue("Asynchronous task did complete in time", completed); + + assertNull("Exception occurred during processing", exceptionRef.get()); + assertTrue("JSON as expected", isJsonAsExpected.get()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // Restore interrupted status + fail("Test interrupted"); + } } - + } From 25868d927f7385b8416f297284572b6c53e8d18e Mon Sep 17 00:00:00 2001 From: Alain Courtines Date: Sun, 10 Mar 2024 15:40:46 -0700 Subject: [PATCH 16/16] added some method headers --- src/main/java/org/json/XML.java | 74 +++++++++++++++++------ src/test/java/org/json/junit/XMLTest.java | 19 ++++-- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index 59c73d2ee..1921ab09a 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -496,6 +496,8 @@ private static boolean isPresent(String[] str, String tagName) { } /** + * parse method for METHOD 1 MILESTONE 2 + * * Scan the content following the named tag, attaching it to the context. * * @param x @@ -540,6 +542,17 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP // <= // << + + /** + * this lets us jump from the closing tag of the thing we wanted + * all the way to the end of the XML document we are parsing. This + * is done by checking every closing tag with the target tagName we + * wish to replace. We utilize a stack to keep track of all tags as they + * come in. If the closing tag is equal to the target, we have found what + * we wanted, already extracted the subObject and therefore no longer need + * to be in the method. We skip past the remaining tags and get out of the + * method much faster + */ token = x.nextToken(); if (!(token == BANG || token == QUEST)) { @@ -684,6 +697,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP context.accumulate(tagName, ""); } } + // exiting the recursive method early in parsing return false; } else if (token == GT) { @@ -718,6 +732,13 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); } + /** + * this part of the method is where we enter the recursive call. + * before we do that, we want to adjust the path. we saved the + * name of the tag currently being processed, and check it with + * our array path. if it matches, we decrement our path by the first + * element to keep track of the method's recursive call. + */ String[] newP = path; if (path.length > 0 && tagName.equals(path[0])) { @@ -743,7 +764,11 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } else if (jsonObject.length() == 1 && jsonObject.opt(config.getcDataTagName()) != null) { context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); - + /** + * if the path arr is empty, we know that we + * can extract the subObject and save it to a + * method variable + */ if (path.length == 0) { requiredObj.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); } @@ -1066,7 +1091,8 @@ private static boolean parsed2(XMLTokener x, JSONObject context, String name, XM token = x.nextToken(); - + // tag is equal to target, we replace its tag + // with the subObject we made's top tag if (token.equals((Object)LAST_PATH)) { token = NewTagName; } @@ -1093,7 +1119,11 @@ private static boolean parsed2(XMLTokener x, JSONObject context, String name, XM } else { - + /** + * open tag is found and processed here. + * we replace the curr subObjects opening tag + * with the opening tag of the subObject + */ tagName = (String) token; if(tagName.equals(LAST_PATH)){ openTagFound.set(true); @@ -1203,13 +1233,18 @@ else if (token instanceof String) { throw x.syntaxError( "Maximum nesting depth of " + config.getMaxNestingDepth() + " reached"); } - + /** + * adjust path as we go deeper into object + */ if (path_tracker.length > 0 && tagName.equals(path_tracker[0])) { String[] newArray = path_tracker.length > 1 ? Arrays.copyOfRange(path_tracker, 1, path_tracker.length) : new String[0]; path_tracker = newArray; } + /** + * we have come to the tag of the object we want to replace + */ if (path_tracker.length == 1) { LAST_PATH = path_tracker[0].trim(); @@ -1217,22 +1252,15 @@ else if (token instanceof String) { if (parsed2(x, jsonObject, tagName, config, currentNestingDepth + 1, path_tracker, replacement, openTagFound, LAST_PATH, NewTagName)) { - // if (openTagFound.get()) { - - // context.put(NewTagName, replacement); - // return false; - // } - // if(tagName == LAST_PATH){ - // return true; - // } + + /** + * check to see if we have found the replacement tags + * we inserted. if so, we replace the object with our + * own object + */ if(tagName.equals(NewTagName)){ - - - for (String key : replacement.keySet()) { - System.out.println(key); - System.out.println(replacement.get(key)); jsonObject.put(key, replacement.get(key)); } @@ -1359,6 +1387,7 @@ public static JSONObject toJSONObject(String string) throws JSONException { return toJSONObject(string, XMLParserConfiguration.ORIGINAL); } /** + * MILESTONE 5 * Milestone V Method * This method takes in a Reader Object as well as consumer functions that operate on the * jsonObject after the executor thread has properly parsed the reader xml to JSON. This works @@ -1523,6 +1552,15 @@ public static JSONObject toJSONObject(String string, XMLParserConfiguration conf } /** + * METHOD 1 MILESTONE 2 + * + * essentially use an array to parse through the different levels of the JSON structure + * as we get deeper. once we find the deepest level associated with the pointer, we can + * extract that subobject that is passed through an overloaded parse method along with + * every method call. We essentialluy add an empty json Object and once we find the subObject + * we are looking for, we save it to an empty JSONObject and return that value the second we find + * it thereby speeding up the process of finding a subObject without parsing the whole XML File + * * * @param reader - The reader object for the xml string * @param path - The path that is needed to be found @@ -1562,7 +1600,7 @@ public static JSONObject toJSONObject(Reader reader, Function ke } /** - * SECOND METHOD INPLEMENTATION FOR MILESTONE 2 + * METHOD 2 MILESTONE 2 * @param reader - reads the XML String * @param path - path to SubObject that we wish to replace * @param replacement - the actual JSON Object that we want to use to replace the object at end of path diff --git a/src/test/java/org/json/junit/XMLTest.java b/src/test/java/org/json/junit/XMLTest.java index 0195273be..bd3700231 100644 --- a/src/test/java/org/json/junit/XMLTest.java +++ b/src/test/java/org/json/junit/XMLTest.java @@ -1322,7 +1322,7 @@ public void testMaxNestingDepthWithValidFittingXML() { } /** - * This method tests the efficacy of the milestone 2 method 2 + * This method tests the efficacy of the MILESTONE 2 METHOD 2 * As you can see the initial xml object is taken and a replacement is * named. The method is sucessfully able to replace the subobject (the entire * adress body including nick, name, street, and zipcode) with the new @@ -1366,7 +1366,7 @@ public void testReplacement() { } /** - * Further test efficacy of milestone 2 method 2 implementation by checking a + * Further test efficacy of MILESTONE 2 METHOD 2 implementation by checking a * different * XMLString for a subObject. The structure of this one is different and * therefore allows @@ -1413,6 +1413,8 @@ public void testReplacement2() { } /** + * MILESTONE 2 METHOD 2 + * * What happens if we try to use a pointer to a subObject that doesn't exist? * We would expect that nothing happens and the same object is returned */ @@ -1452,6 +1454,9 @@ public void testReplacementNull() { } } + /** + * MILESTONE 2 METHOD 1 + */ @Test public void customXmlPathExample1() { String xml = "\n" + @@ -1470,7 +1475,10 @@ public void customXmlPathExample1() { assertTrue(expected.similar(actual)); } - + + /** + * MILESTONE 2 METHOD 1 + */ @Test public void customXmlPathExample2() { String xml = "\n" + @@ -1492,7 +1500,10 @@ public void customXmlPathExample2() { assertTrue(expected.similar(actual)); } - + + /** + * MILESTONE 2 METHOD 1 + */ @Test public void customXmlPathExample3() { String xml = "\n" +