[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.
|