[Practicalxml-commits] SF.net SVN: practicalxml:[144] branches/dev-1.1/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2009-09-22 18:05:30
|
Revision: 144 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=144&view=rev Author: kdgregory Date: 2009-09-22 17:32:02 +0000 (Tue, 22 Sep 2009) Log Message: ----------- xml->json: support arrays Modified Paths: -------------- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonConverter.java branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/package.html branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/json/TestXml2JsonConverter.java Modified: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonConverter.java =================================================================== --- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonConverter.java 2009-09-22 15:27:06 UTC (rev 143) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonConverter.java 2009-09-22 17:32:02 UTC (rev 144) @@ -14,9 +14,12 @@ package net.sf.practicalxml.converter.json; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import net.sf.practicalxml.DomUtil; @@ -43,16 +46,24 @@ //---------------------------------------------------------------------------- /** - * Appends the contents of the specified element to an existing buffer. + * Converts the subtree rooted at <code>elem</code> to a JSON string. + */ + public String convert(Element elem) + { + return convert(elem, new StringBuilder(256)).toString(); + } + + + /** + * Converts the subtree rooted at <code>elem</code> to a JSON string, + * appending to an existing buffer. This is useful when building a + * JSON assignment statment (eg: "var x = OBJECT"). + * <p> * Returns the buffer as a convenience. */ public StringBuilder convert(Element elem, StringBuilder buf) { - String text = DomUtil.getText(elem); - if (text != null) - return appendText(text, buf); - else - return appendChildren(elem, buf); + return append(buf, elem); } @@ -60,8 +71,22 @@ // Internals //---------------------------------------------------------------------------- - private StringBuilder appendText(String text, StringBuilder buf) + // yes, this method is just a restatement of convert(Element,StringBuilder) + // I want all the internal appenders named "append". + private StringBuilder append(StringBuilder buf, Element elem) { + List<Element> children = DomUtil.getChildren(elem); + String text = DomUtil.getText(elem); + + if ((children.size() > 0) || (text == null)) + return appendObject(buf, children); + else + return appendText(buf, text); + } + + + private StringBuilder appendText(StringBuilder buf, String text) + { buf.append('"') .append(JsonUtil.escape(text)) .append('"'); @@ -69,20 +94,74 @@ } - private StringBuilder appendChildren(Element elem, StringBuilder buf) + private StringBuilder appendObject(StringBuilder buf, List<Element> children) { + List<String> names = new ArrayList<String>(); + Map<String,List<Element>> arrays = new HashMap<String,List<Element>>(); + Map<String,Element> nonArrays = new HashMap<String,Element>(); + categorizeChildren(children, names, arrays, nonArrays); + buf.append("{"); - List<Element> children = DomUtil.getChildren(elem); - for (Iterator<Element> childItx = children.iterator() ; childItx.hasNext() ; ) + for (Iterator<String> itx = names.iterator() ; itx.hasNext() ; ) { - Element child = childItx.next(); - buf.append(DomUtil.getLocalName(child)) - .append(": "); - convert(child, buf); - if (childItx.hasNext()) + String name = itx.next(); + buf.append(name).append(": "); + if (arrays.containsKey(name)) + appendArray(buf, arrays.get(name)); + else + append(buf, nonArrays.get(name)); + if (itx.hasNext()) buf.append(", "); } buf.append("}"); return buf; } + + + private StringBuilder appendArray(StringBuilder buf, List<Element> values) + { + buf.append("["); + for (Iterator<Element> itx = values.iterator() ; itx.hasNext() ; ) + { + Element child = itx.next(); + append(buf, child); + if (itx.hasNext()) + buf.append(", "); + } + buf.append("]"); + return buf; + } + + + /** + * Examines the children of the passed element and categorizes them as + * "array" or "not array", while tracking the first appearance of the + * element name in document order. + */ + private void categorizeChildren( + List<Element> children, List<String> names, + Map<String,List<Element>> arrays, Map<String,Element> nonArrays) + { + for (Element child : children) + { + String name = DomUtil.getLocalName(child); + if (arrays.containsKey(name)) + { + arrays.get(name).add(child); + } + else if (nonArrays.containsKey(name)) + { + Element prev = nonArrays.remove(name); + List<Element> list = new ArrayList<Element>(2); + list.add(prev); + list.add(child); + arrays.put(name, list); + } + else + { + nonArrays.put(name, child); + names.add(name); + } + } + } } Modified: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/package.html =================================================================== --- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/package.html 2009-09-22 15:27:06 UTC (rev 143) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/package.html 2009-09-22 17:32:02 UTC (rev 144) @@ -2,36 +2,39 @@ <body> This package contains classes to convert JSON (JavaScript Object Notation) -strings (as defined <a href="http://www.json.org/">json.org</a> ) to and +strings (as defined <a href="http://www.json.org/">json.org</a> ) to and from an XML DOM. Although both XML and JSON are textual representations of hierarchical data, there are some peculiarities in the conversion: <dl> <dt> Container Element <dd> Although elements within a JSON object are named, the object itself is - not. XML requires a named root element, so the JSON->XML conversion - creates a root element named "data", and the XML->JSON conversion ignores - the passed root element. + not. XML requires a named root element, so the JSON to XML conversion + creates a root element named "data", and the XML to JSON conversion + ignores the passed root element. <dt> Document Order <dd> In XML, document order is important and may be validated; in JSON, the order of elements within an object is not important. To support arrays - (qv), the XML->JSON conversion intentionally breaks document order. And + the XML to JSON conversion intentionally breaks document order (qv). And as JSON has no intrinsic order, you'll need to explicitly re-arrange the - result of a JSON->XML conversion prior to any validation. + result of a JSON to XML conversion prior to any validation. <dt> Non-String Data <dd> JSON supports numbers and boolean literals in addition to quote-delimited - strings. The JSON->XML conversion will handle these values transparently. - The default XML->JSON conversion writes all content as quote-delimited + strings. The JSON to XML conversion will handle these values transparently. + The default XML to JSON conversion writes all content as quote-delimited strings, but there is a conversion option to recognize a limited set of - non-string elements based on the <code>xsi:type</code> attribute. -<dt> Arrays + non-string elements based on the <code>xsi:type</code> attribute. +<dt> Arrays, XML to JSON <dd> XML does not have a defined array construct, but may repeat elements; JSON has a defined array construct, and repeated elements will overwrite the - former definition. To avoid this problem, the XML->JSON conversion will - identify all repeated elements and create an array construct. The default - JSON->XML conversion creates a parent-children construct (which is more - palatable to subsequent XML->Java conversion), but a conversion option - allows conversion to a repeated element. + former definition. To avoid this problem, the XML to JSON converter will + create a JSON array construct from all siblings with the same localname. +<dt> Arrays, JSON to XML +<dd> By default, arrays are converted into a single parent element, with one + child named "data" for each element of the array; this is similar to the + Java to XML conversion. Optionally, you may convert an array into a series + of repeated elements (siblings), all with the same name. When using this + option, conversion will fail if presented with a top-level JSON array. </dl> </body> Modified: branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/json/TestXml2JsonConverter.java =================================================================== --- branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/json/TestXml2JsonConverter.java 2009-09-22 15:27:06 UTC (rev 143) +++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/json/TestXml2JsonConverter.java 2009-09-22 17:32:02 UTC (rev 144) @@ -37,9 +37,8 @@ public void convertAndAssert(String expected, ElementNode rootNode) { Element root = rootNode.toDOM().getDocumentElement(); - StringBuilder buf = new Xml2JsonConverter() - .convert(root, new StringBuilder(256)); - assertEquals(expected, buf.toString()); + String json = new Xml2JsonConverter().convert(root); + assertEquals(expected, json); } @@ -86,6 +85,53 @@ } + public void testArray() throws Exception + { + // note that "argle" elements are not adjacent, must become adjacent + convertAndAssert( + "{foo: \"bar\", argle: [\"bargle\", \"wargle\"], baz: \"bar\"}", + element("data", + element("foo", text("bar")), + element("argle", text("bargle")), + element("baz", text("bar")), + element("argle", text("wargle")))); + } + + + public void testArrayWithNestedObject() throws Exception + { + convertAndAssert( + "{foo: \"bar\", argle: [\"bargle\", {foo: \"bar\", bar: \"baz\"}]}", + element("data", + element("foo", text("bar")), + element("argle", text("bargle")), + element("argle", + element("foo", text("bar")), + element("bar", text("baz"))))); + } + + + // this covers documents parsed with "ignorable whitespace" + public void testMixedContentWithWhitespace() throws Exception + { + convertAndAssert( + "{foo: \"bar\"}", + element("data", + text(" "), + element("foo", text("bar")), + text("\n"))); + } + + + public void testWhitespace() throws Exception + { + convertAndAssert( + "{foo: \" \"}", + element("data", + element("foo", text(" ")))); + } + + public void testStringEscaping() throws Exception { convertAndAssert( This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |