[Practicalxml-commits] SF.net SVN: practicalxml:[170] branches/dev-1.1/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2009-10-20 20:54:09
|
Revision: 170 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=170&view=rev Author: kdgregory Date: 2009-10-20 20:54:00 +0000 (Tue, 20 Oct 2009) Log Message: ----------- add Xml2JsonOptions.USE_XSI_TYPE 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/Xml2JsonOptions.java 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-10-15 20:49:36 UTC (rev 169) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonConverter.java 2009-10-20 20:54:00 UTC (rev 170) @@ -17,13 +17,19 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import javax.xml.XMLConstants; + import net.sf.practicalxml.DomUtil; +import net.sf.practicalxml.internal.StringUtils; import org.w3c.dom.Element; +import org.w3c.dom.Node; /** @@ -31,6 +37,38 @@ */ public class Xml2JsonConverter { + /** + * Lookup table for XSD types that can potentially be unquoted. + */ + private static Set<String> _unquotedXsd = new HashSet<String>(); + static + { + _unquotedXsd.add("xsd:boolean"); + _unquotedXsd.add("xsd:byte"); + _unquotedXsd.add("xsd:decimal"); + _unquotedXsd.add("xsd:double"); + _unquotedXsd.add("xsd:float"); + _unquotedXsd.add("xsd:int"); + _unquotedXsd.add("xsd:integer"); + _unquotedXsd.add("xsd:long"); + _unquotedXsd.add("xsd:negativeInteger"); + _unquotedXsd.add("xsd:nonNegativeInteger"); + _unquotedXsd.add("xsd:nonPositiveInteger"); + _unquotedXsd.add("xsd:positiveInteger"); + _unquotedXsd.add("xsd:short"); + _unquotedXsd.add("xsd:unsignedByte"); + _unquotedXsd.add("xsd:unsignedInt"); + _unquotedXsd.add("xsd:unsignedLong"); + _unquotedXsd.add("xsd:unsignedShort"); + } + + +//---------------------------------------------------------------------------- +// Instance variables and constructors +//---------------------------------------------------------------------------- + + + private EnumSet<Xml2JsonOptions> _options = EnumSet.noneOf(Xml2JsonOptions.class); @@ -66,11 +104,11 @@ if (_options.contains(Xml2JsonOptions.WRAP_WITH_PARENS)) { buf.append("("); - append(buf, elem); + appendObject(buf, elem); buf.append(")"); } else - append(buf, elem); + appendObject(buf, elem); return buf; } @@ -79,26 +117,36 @@ // Internals //---------------------------------------------------------------------------- - // 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); + if (isSimple(elem)) + return appendText(buf, elem); + + return appendObject(buf, elem); + } + + + private StringBuilder appendText(StringBuilder buf, Element elem) + { String text = DomUtil.getText(elem); + String type = elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type"); + String quote = "\""; + if (_options.contains(Xml2JsonOptions.USE_XSI_TYPE) && _unquotedXsd.contains(type)) + quote = ""; - if ((children.size() > 0) || (text == null)) - return appendObject(buf, children); - else - return appendText(buf, text); + buf.append(quote) + .append(JsonUtil.escape(text)) + .append(quote); + return buf; } - private StringBuilder appendObject(StringBuilder buf, List<Element> children) + private StringBuilder appendObject(StringBuilder buf, Element elem) { 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); + categorizeChildren(elem, names, arrays, nonArrays); buf.append("{"); for (Iterator<String> itx = names.iterator() ; itx.hasNext() ; ) @@ -132,62 +180,100 @@ } + private StringBuilder appendFieldName(StringBuilder buf, String name) + { + if (_options.contains(Xml2JsonOptions.UNQUOTED_FIELD_NAMES)) + { + buf.append(name); + } + else + { + buf.append('"') + .append(name) + .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) + Element elem, + List<String> names, + Map<String,List<Element>> arrays, + Map<String,Element> nonArrays) { - for (Element child : children) + for (Element child : DomUtil.getChildren(elem)) { String name = DomUtil.getLocalName(child); + if (!arrays.containsKey(name) && !nonArrays.containsKey(name)) + names.add(name); + if (arrays.containsKey(name)) { - arrays.get(name).add(child); + getArray(name, arrays).add(child); } else if (nonArrays.containsKey(name)) { + List<Element> array = getArray(name, arrays); Element prev = nonArrays.remove(name); - List<Element> list = new ArrayList<Element>(2); - list.add(prev); - list.add(child); - arrays.put(name, list); + array.add(prev); + array.add(child); } + else if (isArrayParent(child)) + { + List<Element> array = getArray(name, arrays); + for (Element grandchild : DomUtil.getChildren(child)) + array.add(grandchild); + } else { nonArrays.put(name, child); - names.add(name); } } } - private StringBuilder appendFieldName(StringBuilder buf, String name) + private List<Element> getArray(String name, Map<String,List<Element>> arrays) { - if (_options.contains(Xml2JsonOptions.UNQUOTED_FIELD_NAMES)) + List<Element> array = arrays.get(name); + if (array == null) { - buf.append(name); + array = new ArrayList<Element>(); + arrays.put(name, array); } - else + return array; + } + + + private boolean isSimple(Element elem) + { + for (Node child = elem.getFirstChild() ; child != null ; child = child.getNextSibling()) { - buf.append('"') - .append(name) - .append('"'); + if (child instanceof Element) + return false; } - - buf.append(": "); - return buf; + return true; } - private StringBuilder appendText(StringBuilder buf, String text) + private boolean isArrayParent(Element elem) { - buf.append('"') - .append(JsonUtil.escape(text)) - .append('"'); - return buf; + if (!_options.contains(Xml2JsonOptions.USE_XSI_TYPE)) + return false; + + String type = elem.getAttributeNS(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "type"); + if (StringUtils.isEmpty(type)) + return false; + if (type.startsWith("java:[")) + return true; + + return false; } } Modified: branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonOptions.java =================================================================== --- branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonOptions.java 2009-10-15 20:49:36 UTC (rev 169) +++ branches/dev-1.1/src/main/java/net/sf/practicalxml/converter/json/Xml2JsonOptions.java 2009-10-20 20:54:00 UTC (rev 170) @@ -16,7 +16,7 @@ /** - * Options to control conversion from XML documents to JSON strings + * Options to control conversion from XML documents to JSON strings. */ public enum Xml2JsonOptions { @@ -32,6 +32,25 @@ UNQUOTED_FIELD_NAMES, /** + * If enabled, the converter will look for an <code>xsi:type</code> + * attribute (<code>type</code> in XML Schema Instance namespace), and + * apply the following rules: + * <ul> + * <li> If the attribute value begins with "xsd:", and the portion after + * the ":" is one of the XSD primitive numeric or boolean types, the + * element's value will be emitted without quotes. + * <li> If the attribute value begins with "java:", and the portion + * after the ":" corresponds to a Java array type or standard + * collection type, then the element's content will be emitted + * as a JSON array (assumes zero or more sub-elements). + * </ul> + * These rules are designed to allow use of XML produced by {@link + * net.sf.practicalxml.converter.BeanConverter}, preserving knowledge + * about the bean structure. + */ + USE_XSI_TYPE, + + /** * If enabled, the entire string is wrapped by parentheses. This is * needed for strings that will be passed to <code>eval()</code>. * Note that the resulting string is not acceptable to {@link 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-10-15 20:49:36 UTC (rev 169) +++ branches/dev-1.1/src/test/java/net/sf/practicalxml/converter/json/TestXml2JsonConverter.java 2009-10-20 20:54:00 UTC (rev 170) @@ -87,8 +87,24 @@ } - public void testArray() throws Exception + public void testPrimitivesWithXsiType() throws Exception { + // unquoted field names for readability + convertAndAssert( + "{int: 123, boolean: true, decimal: 1234567890.1234567890, " + + "intWithoutType: \"123456\"}", + element("data", + element("int", text("123"), xsiType("xsd:int")), + element("boolean", text("true"), xsiType("xsd:boolean")), + element("decimal", text("1234567890.1234567890"), xsiType("xsd:decimal")), + element("intWithoutType", text("123456"))), + Xml2JsonOptions.UNQUOTED_FIELD_NAMES, + Xml2JsonOptions.USE_XSI_TYPE); + } + + + public void testArrayAsRepeatedElement() throws Exception + { // note that "argle" elements are not adjacent, must become adjacent convertAndAssert( "{\"foo\": \"bar\", \"argle\": [\"bargle\", \"wargle\"], \"baz\": \"bar\"}", @@ -100,6 +116,30 @@ } + public void testArrayPerXsiType() throws Exception + { + // note: using xsi:type implies that numbers won't be quoted + // also: array member name is ignored + convertAndAssert( + "{\"value\": [123, 456]}", + element("data", + element("value", xsiType("java:" + int[].class.getName()), + element("foo", text("123"), xsiType("xsd:int")), + element("bar", text("456"), xsiType("xsd:int")))), + Xml2JsonOptions.USE_XSI_TYPE); + } + + + public void testEmptyArrayPerXsiType() throws Exception + { + convertAndAssert( + "{\"value\": []}", + element("data", + element("value", xsiType("java:" + int[].class.getName()))), + Xml2JsonOptions.USE_XSI_TYPE); + } + + public void testArrayWithNestedObject() throws Exception { convertAndAssert( This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |