net.sf.json.xml.XMLSerializer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of json-lib Show documentation
Show all versions of json-lib Show documentation
Java library for transforming beans, maps, collections, java
arrays and XML to JSON.
The newest version!
/*
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.json.xml;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import net.sf.json.JSON;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONFunction;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;
import nu.xom.Attribute;
import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.Node;
import nu.xom.Serializer;
import nu.xom.Text;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class for transforming JSON to XML an back.
* When transforming JSONObject and JSONArray instances to XML, this class will
* add hints for converting back to JSON.
* Examples:
*
*
* JSONObject json = JSONObject.fromObject("{\"name\":\"json\",\"bool\":true,\"int\":1}");
* String xml = new XMLSerializer().write( json );
*
json
true
1
*
* JSONArray json = JSONArray.fromObject("[1,2,3]");
* String xml = new XMLSerializer().write( json );
*
1
2
3
*
*
* @author Andres Almiray
*/
public class XMLSerializer {
private static final String[] EMPTY_ARRAY = new String[0];
private static final String JSON_PREFIX = "json_";
private static final Log log = LogFactory.getLog( XMLSerializer.class );
/** the name for an JSONArray Element */
private String arrayName;
/** the name for an JSONArray's element Element */
private String elementName;
/** list of properties to be expanded from child to parent */
private String[] expandableProperties;
private boolean forceTopLevelObject;
/** flag to be tolerant for incomplete namespace prefixes */
private boolean namespaceLenient;
/** Map of namespaces per element */
private Map namespacesPerElement = new TreeMap();
/** the name for an JSONObject Element */
private String objectName;
/** flag for trimming namespace prefix from element name */
private boolean removeNamespacePrefixFromElements;
/** the name for the root Element */
private String rootName;
/** Map of namespaces for root element */
private Map rootNamespace = new TreeMap();
/** flag for skipping namespaces while reading */
private boolean skipNamespaces;
/** flag for skipping whitespace elements while reading */
private boolean skipWhitespace;
/** flag for trimming spaces from string values */
private boolean trimSpaces;
/** flag for type hints naming compatibility */
private boolean typeHintsCompatibility;
/** flag for adding JSON types hints as attributes */
private boolean typeHintsEnabled;
/**
* Creates a new XMLSerializer with default options.
*
* objectName
: 'o'
* arrayName
: 'a'
* elementName
: 'e'
* typeHinstEnabled
: true
* typeHinstCompatibility
: true
* namespaceLenient
: false
* expandableProperties
: []
* skipNamespaces
: false
* removeNameSpacePrefixFromElement
: false
* trimSpaces
: false
*
*/
public XMLSerializer() {
setObjectName( "o" );
setArrayName( "a" );
setElementName( "e" );
setTypeHintsEnabled( true );
setTypeHintsCompatibility( true );
setNamespaceLenient( false );
setSkipNamespaces( false );
setRemoveNamespacePrefixFromElements( false );
setTrimSpaces( false );
setExpandableProperties( EMPTY_ARRAY );
setSkipNamespaces( false );
}
/**
* Adds a namespace declaration to the root element.
*
* @param prefix namespace prefix
* @param uri namespace uri
*/
public void addNamespace( String prefix, String uri ) {
addNamespace( prefix, uri, null );
}
/**
* Adds a namespace declaration to an element.
* If the elementName param is null or blank, the namespace declaration will
* be added to the root element.
*
* @param prefix namespace prefix
* @param uri namespace uri
* @param elementName name of target element
*/
public void addNamespace( String prefix, String uri, String elementName ) {
if( StringUtils.isBlank( uri ) ){
return;
}
if( prefix == null ){
prefix = "";
}
if( StringUtils.isBlank( elementName ) ){
rootNamespace.put( prefix.trim(), uri.trim() );
}else{
Map nameSpaces = (Map) namespacesPerElement.get( elementName );
if( nameSpaces == null ){
nameSpaces = new TreeMap();
namespacesPerElement.put( elementName, nameSpaces );
}
nameSpaces.put( prefix, uri );
}
}
/**
* Removes all namespaces declarations (from root an elements).
*/
public void clearNamespaces() {
rootNamespace.clear();
namespacesPerElement.clear();
}
/**
* Removes all namespace declarations from an element.
* If the elementName param is null or blank, the declarations will be
* removed from the root element.
*
* @param elementName name of target element
*/
public void clearNamespaces( String elementName ) {
if( StringUtils.isBlank( elementName ) ){
rootNamespace.clear();
}else{
namespacesPerElement.remove( elementName );
}
}
/**
* Returns the name used for JSONArray.
*/
public String getArrayName() {
return arrayName;
}
/**
* Returns the name used for JSONArray elements.
*/
public String getElementName() {
return elementName;
}
/**
* Returns a list of properties to be expanded from child to parent.
*/
public String[] getExpandableProperties() {
return expandableProperties;
}
/**
* Returns the name used for JSONArray.
*/
public String getObjectName() {
return objectName;
}
/**
* Returns the name used for the root element.
*/
public String getRootName() {
return rootName;
}
public boolean isForceTopLevelObject() {
return forceTopLevelObject;
}
/**
* Returns wether this serializer is tolerant to namespaces without URIs or
* not.
*/
public boolean isNamespaceLenient() {
return namespaceLenient;
}
/**
* Returns wether this serializer will remove namespace prefix from elements
* or not.
*/
public boolean isRemoveNamespacePrefixFromElements() {
return removeNamespacePrefixFromElements;
}
/**
* Returns wether this serializer will skip adding namespace declarations to
* elements or not.
*/
public boolean isSkipNamespaces() {
return skipNamespaces;
}
/**
* Returns wether this serializer will skip whitespace or not.
*/
public boolean isSkipWhitespace() {
return skipWhitespace;
}
/**
* Returns wether this serializer will trim leading and trealing whitespace
* from values or not.
*/
public boolean isTrimSpaces() {
return trimSpaces;
}
/**
* Returns true if types hints will have a 'json_' prefix or not.
*/
public boolean isTypeHintsCompatibility() {
return typeHintsCompatibility;
}
/**
* Returns true if JSON types will be included as attributes.
*/
public boolean isTypeHintsEnabled() {
return typeHintsEnabled;
}
/**
* Creates a JSON value from a XML string.
*
* @param xml A well-formed xml document in a String
* @return a JSONNull, JSONObject or JSONArray
* @throws JSONException if the conversion from XML to JSON can't be made for
* I/O or format reasons.
*/
public JSON read( String xml ) {
JSON json = null;
try{
Document doc = new Builder().build( new StringReader( xml ) );
Element root = doc.getRootElement();
if( isNullObject( root ) ){
return JSONNull.getInstance();
}
String defaultType = getType( root, JSONTypes.STRING );
if( isArray( root, true ) ){
json = processArrayElement( root, defaultType );
if( forceTopLevelObject ){
String key = removeNamespacePrefix( root.getQualifiedName() );
json = new JSONObject().element( key, json );
}
}else{
json = processObjectElement( root, defaultType );
if( forceTopLevelObject ) {
String key = removeNamespacePrefix( root.getQualifiedName() );
json = new JSONObject().element( key, json );
}
}
}catch( JSONException jsone ){
throw jsone;
}catch( Exception e ){
throw new JSONException( e );
}
return json;
}
/**
* Creates a JSON value from a File.
*
* @param file
* @return a JSONNull, JSONObject or JSONArray
* @throws JSONException if the conversion from XML to JSON can't be made for
* I/O or format reasons.
*/
public JSON readFromFile( File file ) {
if( file == null ){
throw new JSONException( "File is null" );
}
if( !file.canRead() ){
throw new JSONException( "Can't read input file" );
}
if( file.isDirectory() ){
throw new JSONException( "File is a directory" );
}
try{
return readFromStream( new FileInputStream( file ) );
}catch( IOException ioe ){
throw new JSONException( ioe );
}
}
/**
* Creates a JSON value from a File.
*
* @param path
* @return a JSONNull, JSONObject or JSONArray
* @throws JSONException if the conversion from XML to JSON can't be made for
* I/O or format reasons.
*/
public JSON readFromFile( String path ) {
return readFromStream( Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream( path ) );
}
/**
* Creates a JSON value from an input stream.
*
* @param stream
* @return a JSONNull, JSONObject or JSONArray
* @throws JSONException if the conversion from XML to JSON can't be made for
* I/O or format reasons.
*/
public JSON readFromStream( InputStream stream ) {
try{
StringBuffer xml = new StringBuffer();
BufferedReader in = new BufferedReader( new InputStreamReader( stream ) );
String line = null;
while( (line = in.readLine()) != null ){
xml.append( line );
}
return read( xml.toString() );
}catch( IOException ioe ){
throw new JSONException( ioe );
}
}
/**
* Removes a namespace from the root element.
*
* @param prefix namespace prefix
*/
public void removeNamespace( String prefix ) {
removeNamespace( prefix, null );
}
/**
* Removes a namespace from the root element.
* If the elementName is null or blank, the namespace will be removed from
* the root element.
*
* @param prefix namespace prefix
* @param elementName name of target element
*/
public void removeNamespace( String prefix, String elementName ) {
if( prefix == null ){
prefix = "";
}
if( StringUtils.isBlank( elementName ) ){
rootNamespace.remove( prefix.trim() );
}else{
Map nameSpaces = (Map) namespacesPerElement.get( elementName );
nameSpaces.remove( prefix );
}
}
/**
* Sets the name used for JSONArray.
* Default is 'a'.
*/
public void setArrayName( String arrayName ) {
this.arrayName = StringUtils.isBlank( arrayName ) ? "a" : arrayName;
}
/**
* Sets the name used for JSONArray elements.
* Default is 'e'.
*/
public void setElementName( String elementName ) {
this.elementName = StringUtils.isBlank( elementName ) ? "e" : elementName;
}
/**
* Sets the list of properties to be expanded from child to parent.
*/
public void setExpandableProperties( String[] expandableProperties ) {
this.expandableProperties = expandableProperties == null ? EMPTY_ARRAY : expandableProperties;
}
public void setForceTopLevelObject( boolean forceTopLevelObject ) {
this.forceTopLevelObject = forceTopLevelObject;
}
/**
* Sets the namespace declaration to the root element.
* Any previous values are discarded.
*
* @param prefix namespace prefix
* @param uri namespace uri
*/
public void setNamespace( String prefix, String uri ) {
setNamespace( prefix, uri, null );
}
/**
* Adds a namespace declaration to an element.
* Any previous values are discarded. If the elementName param is null or
* blank, the namespace declaration will be added to the root element.
*
* @param prefix namespace prefix
* @param uri namespace uri
* @param elementName name of target element
*/
public void setNamespace( String prefix, String uri, String elementName ) {
if( StringUtils.isBlank( uri ) ){
return;
}
if( prefix == null ){
prefix = "";
}
if( StringUtils.isBlank( elementName ) ){
rootNamespace.clear();
rootNamespace.put( prefix.trim(), uri.trim() );
}else{
Map nameSpaces = (Map) namespacesPerElement.get( elementName );
if( nameSpaces == null ){
nameSpaces = new TreeMap();
namespacesPerElement.put( elementName, nameSpaces );
}
nameSpaces.clear();
nameSpaces.put( prefix, uri );
}
}
/**
* Sets wether this serializer is tolerant to namespaces without URIs or not.
*/
public void setNamespaceLenient( boolean namespaceLenient ) {
this.namespaceLenient = namespaceLenient;
}
/**
* Sets the name used for JSONObject.
* Default is 'o'.
*/
public void setObjectName( String objectName ) {
this.objectName = StringUtils.isBlank( objectName ) ? "o" : objectName;
}
/**
* Sets if this serializer will remove namespace prefix from elements when
* reading.
*/
public void setRemoveNamespacePrefixFromElements( boolean removeNamespacePrefixFromElements ) {
this.removeNamespacePrefixFromElements = removeNamespacePrefixFromElements;
}
/**
* Sets the name used for the root element.
*/
public void setRootName( String rootName ) {
this.rootName = StringUtils.isBlank( rootName ) ? null : rootName;
}
/**
* Sets if this serializer will skip adding namespace declarations to
* elements when reading.
*/
public void setSkipNamespaces( boolean skipNamespaces ) {
this.skipNamespaces = skipNamespaces;
}
/**
* Sets if this serializer will skip whitespace when reading.
*/
public void setSkipWhitespace( boolean skipWhitespace ) {
this.skipWhitespace = skipWhitespace;
}
/**
* Sets if this serializer will trim leading and trealing whitespace from
* values when reading.
*/
public void setTrimSpaces( boolean trimSpaces ) {
this.trimSpaces = trimSpaces;
}
/**
* Sets wether types hints will have a 'json_' prefix or not.
*/
public void setTypeHintsCompatibility( boolean typeHintsCompatibility ) {
this.typeHintsCompatibility = typeHintsCompatibility;
}
/**
* Sets wether JSON types will be included as attributes.
*/
public void setTypeHintsEnabled( boolean typeHintsEnabled ) {
this.typeHintsEnabled = typeHintsEnabled;
}
/**
* Writes a JSON value into a XML string with UTF-8 encoding.
*
* @param json The JSON value to transform
* @return a String representation of a well-formed xml document.
* @throws JSONException if the conversion from JSON to XML can't be made for
* I/O reasons.
*/
public String write( JSON json ) {
return write( json, null );
}
/**
* Writes a JSON value into a XML string with an specific encoding.
* If the encoding string is null it will use UTF-8.
*
* @param json The JSON value to transform
* @param encoding The xml encoding to use
* @return a String representation of a well-formed xml document.
* @throws JSONException if the conversion from JSON to XML can't be made for
* I/O reasons or the encoding is not supported.
*/
public String write( JSON json, String encoding ) {
if( JSONNull.getInstance()
.equals( json ) ){
Element root = null;
root = newElement( getRootName() == null ? getObjectName() : getRootName() );
root.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
Document doc = new Document( root );
return writeDocument( doc, encoding );
}else if( json instanceof JSONArray ){
JSONArray jsonArray = (JSONArray) json;
Element root = processJSONArray( jsonArray,
newElement( getRootName() == null ? getArrayName() : getRootName() ),
expandableProperties );
Document doc = new Document( root );
return writeDocument( doc, encoding );
}else{
JSONObject jsonObject = (JSONObject) json;
Element root = null;
if( jsonObject.isNullObject() ){
root = newElement( getObjectName() );
root.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
}else{
root = processJSONObject( jsonObject,
newElement( getRootName() == null ? getObjectName() : getRootName() ),
expandableProperties, true );
}
Document doc = new Document( root );
return writeDocument( doc, encoding );
}
}
private String addJsonPrefix( String str ) {
if( !isTypeHintsCompatibility() ){
return JSON_PREFIX + str;
}
return str;
}
private void addNameSpaceToElement( Element element ) {
String elementName = null;
if( element instanceof CustomElement ){
elementName = ((CustomElement) element).getQName();
}else{
elementName = element.getQualifiedName();
}
Map nameSpaces = (Map) namespacesPerElement.get( elementName );
if( nameSpaces != null && !nameSpaces.isEmpty() ){
setNamespaceLenient( true );
for( Iterator entries = nameSpaces.entrySet()
.iterator(); entries.hasNext(); ){
Map.Entry entry = (Map.Entry) entries.next();
String prefix = (String) entry.getKey();
String uri = (String) entry.getValue();
if( StringUtils.isBlank( prefix ) ){
element.setNamespaceURI( uri );
}else{
element.addNamespaceDeclaration( prefix, uri );
}
}
}
}
private boolean checkChildElements( Element element, boolean isTopLevel ) {
int childCount = element.getChildCount();
Elements elements = element.getChildElements();
int elementCount = elements.size();
if( childCount == 1 && element.getChild( 0 ) instanceof Text ){
return isTopLevel;
}
if( childCount == elementCount ){
if( elementCount == 0 ){
return true;
}
if( elementCount == 1 ){
if( skipWhitespace || element.getChild( 0 ) instanceof Text ){
return true;
}else{
return false;
}
}
}
if( childCount > elementCount ){
for( int i = 0; i < childCount; i++ ){
Node node = element.getChild( i );
if( node instanceof Text ){
Text text = (Text) node;
if( StringUtils.isNotBlank( StringUtils.strip( text.getValue() ) )
&& !skipWhitespace ){
return false;
}
}
}
}
String childName = elements.get( 0 )
.getQualifiedName();
for( int i = 1; i < elementCount; i++ ){
if( childName.compareTo( elements.get( i )
.getQualifiedName() ) != 0 ){
return false;
}
}
return true;
}
private String getClass( Element element ) {
Attribute attribute = element.getAttribute( addJsonPrefix( "class" ) );
String clazz = null;
if( attribute != null ){
String clazzText = attribute.getValue()
.trim();
if( JSONTypes.OBJECT.compareToIgnoreCase( clazzText ) == 0 ){
clazz = JSONTypes.OBJECT;
}else if( JSONTypes.ARRAY.compareToIgnoreCase( clazzText ) == 0 ){
clazz = JSONTypes.ARRAY;
}
}
return clazz;
}
private String getType( Element element ) {
return getType( element, null );
}
private String getType( Element element, String defaultType ) {
Attribute attribute = element.getAttribute( addJsonPrefix( "type" ) );
String type = null;
if( attribute != null ){
String typeText = attribute.getValue()
.trim();
if( JSONTypes.BOOLEAN.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.BOOLEAN;
}else if( JSONTypes.NUMBER.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.NUMBER;
}else if( JSONTypes.INTEGER.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.INTEGER;
}else if( JSONTypes.FLOAT.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.FLOAT;
}else if( JSONTypes.OBJECT.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.OBJECT;
}else if( JSONTypes.ARRAY.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.ARRAY;
}else if( JSONTypes.STRING.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.STRING;
}else if( JSONTypes.FUNCTION.compareToIgnoreCase( typeText ) == 0 ){
type = JSONTypes.FUNCTION;
}
}else{
if( defaultType != null ){
log.info( "Using default type " + defaultType );
type = defaultType;
}
}
return type;
}
private boolean hasNamespaces( Element element ) {
int namespaces = 0;
for( int i = 0; i < element.getNamespaceDeclarationCount(); i++ ){
String prefix = element.getNamespacePrefix( i );
String uri = element.getNamespaceURI( prefix );
if( StringUtils.isBlank( uri ) ){
continue;
}
namespaces++;
}
return namespaces > 0;
}
private boolean isArray( Element element, boolean isTopLevel ) {
boolean isArray = false;
String clazz = getClass( element );
if( clazz != null && clazz.equals( JSONTypes.ARRAY ) ){
isArray = true;
}else if( element.getAttributeCount() == 0 ){
isArray = checkChildElements( element, isTopLevel );
}else if( element.getAttributeCount() == 1
&& (element.getAttribute( addJsonPrefix( "class" ) ) != null || element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
isArray = checkChildElements( element, isTopLevel );
}else if( element.getAttributeCount() == 2
&& (element.getAttribute( addJsonPrefix( "class" ) ) != null && element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
isArray = checkChildElements( element, isTopLevel );
}
if( isArray ){
// check namespace
for( int j = 0; j < element.getNamespaceDeclarationCount(); j++ ){
String prefix = element.getNamespacePrefix( j );
String uri = element.getNamespaceURI( prefix );
if( !StringUtils.isBlank( uri ) ){
return false;
}
}
}
return isArray;
}
private boolean isFunction( Element element ) {
int attrCount = element.getAttributeCount();
if( attrCount > 0 ){
Attribute typeAttr = element.getAttribute( addJsonPrefix( "type" ) );
Attribute paramsAttr = element.getAttribute( addJsonPrefix( "params" ) );
if( attrCount == 1 && paramsAttr != null ){
return true;
}
if( attrCount == 2 && paramsAttr != null && typeAttr != null && (typeAttr.getValue()
.compareToIgnoreCase( JSONTypes.STRING ) == 0 || typeAttr.getValue()
.compareToIgnoreCase( JSONTypes.FUNCTION ) == 0) ){
return true;
}
}
return false;
}
private boolean isNullObject( Element element ) {
if( element.getChildCount() == 0 ){
if( element.getAttributeCount() == 0 ){
return true;
}else if( element.getAttribute( addJsonPrefix( "null" ) ) != null ){
return true;
}else if( element.getAttributeCount() == 1
&& (element.getAttribute( addJsonPrefix( "class" ) ) != null || element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
return true;
}else if( element.getAttributeCount() == 2
&& (element.getAttribute( addJsonPrefix( "class" ) ) != null && element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
return true;
}
}
if( skipWhitespace && element.getChildCount() == 1 && element.getChild( 0 ) instanceof Text ){
return true;
}
return false;
}
private boolean isObject( Element element, boolean isTopLevel ) {
boolean isObject = false;
if( !isArray( element, isTopLevel ) && !isFunction( element ) ){
if( hasNamespaces( element ) ){
return true;
}
int attributeCount = element.getAttributeCount();
if( attributeCount > 0 ){
int attrs = element.getAttribute( addJsonPrefix( "null" )) == null ? 0 : 1;
attrs += element.getAttribute( addJsonPrefix( "class" )) == null ? 0: 1;
attrs += element.getAttribute( addJsonPrefix( "type" ))== null ? 0 : 1;
switch( attributeCount ){
case 1:
if( attrs == 0){
return true;
}
break;
case 2:
if( attrs < 2 ){
return true;
}
break;
case 3:
if( attrs < 3 ){
return true;
}
break;
default:
return true;
}
}
int childCount = element.getChildCount();
if( childCount == 1 && element.getChild( 0 ) instanceof Text ){
return isTopLevel;
}
isObject = true;
}
return isObject;
}
private Element newElement( String name ) {
if( name.indexOf( ':' ) != -1 ){
namespaceLenient = true;
}
return namespaceLenient ? new CustomElement( name ) : new Element( name );
}
private JSON processArrayElement( Element element, String defaultType ) {
JSONArray jsonArray = new JSONArray();
// process children (including text)
int childCount = element.getChildCount();
for( int i = 0; i < childCount; i++ ){
Node child = element.getChild( i );
if( child instanceof Text ){
Text text = (Text) child;
if( StringUtils.isNotBlank( StringUtils.strip( text.getValue() ) ) ){
jsonArray.element( text.getValue() );
}
}else if( child instanceof Element ){
setValue( jsonArray, (Element) child, defaultType );
}
}
return jsonArray;
}
private Object processElement( Element element, String type ) {
if( isNullObject( element ) ){
return JSONNull.getInstance();
}else if( isArray( element, false ) ){
return processArrayElement( element, type );
}else if( isObject( element, false ) ){
return processObjectElement( element, type );
}else{
return trimSpaceFromValue( element.getValue() );
}
}
private Element processJSONArray( JSONArray array, Element root, String[] expandableProperties ) {
int l = array.size();
for( int i = 0; i < l; i++ ){
Object value = array.get( i );
Element element = processJSONValue( value, root, null, expandableProperties );
root.appendChild( element );
}
return root;
}
private Element processJSONObject( JSONObject jsonObject, Element root,
String[] expandableProperties, boolean isRoot ) {
if( jsonObject.isNullObject() ){
root.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
return root;
}else if( jsonObject.isEmpty() ){
return root;
}
if( isRoot ){
if( !rootNamespace.isEmpty() ){
setNamespaceLenient( true );
for( Iterator entries = rootNamespace.entrySet()
.iterator(); entries.hasNext(); ){
Map.Entry entry = (Map.Entry) entries.next();
String prefix = (String) entry.getKey();
String uri = (String) entry.getValue();
if( StringUtils.isBlank( prefix ) ){
root.setNamespaceURI( uri );
}else{
root.addNamespaceDeclaration( prefix, uri );
}
}
}
}
addNameSpaceToElement( root );
Object[] names = jsonObject.names()
.toArray();
Arrays.sort( names );
Element element = null;
for( int i = 0; i < names.length; i++ ){
String name = (String) names[i];
Object value = jsonObject.get( name );
if( name.startsWith( "@xmlns" ) ){
setNamespaceLenient( true );
int colon = name.indexOf( ':' );
if( colon == -1 ){
// do not override if already defined by nameSpaceMaps
if( StringUtils.isBlank( root.getNamespaceURI() ) ){
root.setNamespaceURI( String.valueOf( value ) );
}
}else{
String prefix = name.substring( colon + 1 );
if( StringUtils.isBlank( root.getNamespaceURI( prefix ) ) ){
root.addNamespaceDeclaration( prefix, String.valueOf( value ) );
}
}
}else if( name.startsWith( "@" ) ){
root.addAttribute( new Attribute( name.substring( 1 ), String.valueOf( value ) ) );
}else if( name.equals( "#text" ) ){
if( value instanceof JSONArray ){
root.appendChild( ((JSONArray) value).join( "", true ) );
}else{
root.appendChild( String.valueOf( value ) );
}
}else if( value instanceof JSONArray
&& (((JSONArray) value).isExpandElements() || ArrayUtils.contains(
expandableProperties, name )) ){
JSONArray array = (JSONArray) value;
int l = array.size();
for( int j = 0; j < l; j++ ){
Object item = array.get( j );
element = newElement( name );
if( item instanceof JSONObject ){
element = processJSONValue( (JSONObject) item, root, element,
expandableProperties );
}else if( item instanceof JSONArray ){
element = processJSONValue( (JSONArray) item, root, element, expandableProperties );
}else{
element = processJSONValue( item, root, element, expandableProperties );
}
addNameSpaceToElement( element );
root.appendChild( element );
}
}else{
element = newElement( name );
element = processJSONValue( value, root, element, expandableProperties );
addNameSpaceToElement( element );
root.appendChild( element );
}
}
return root;
}
private Element processJSONValue( Object value, Element root, Element target,
String[] expandableProperties ) {
if( target == null ){
target = newElement( getElementName() );
}
if( JSONUtils.isBoolean( value ) ){
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.BOOLEAN ) );
}
target.appendChild( value.toString() );
}else if( JSONUtils.isNumber( value ) ){
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.NUMBER ) );
}
target.appendChild( value.toString() );
}else if( JSONUtils.isFunction( value ) ){
if( value instanceof String ){
value = JSONFunction.parse( (String) value );
}
JSONFunction func = (JSONFunction) value;
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.FUNCTION ) );
}
String params = ArrayUtils.toString( func.getParams() );
params = params.substring( 1 );
params = params.substring( 0, params.length() - 1 );
target.addAttribute( new Attribute( addJsonPrefix( "params" ), params ) );
target.appendChild( new Text( "" ) );
}else if( JSONUtils.isString( value ) ){
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.STRING ) );
}
target.appendChild( value.toString() );
}else if( value instanceof JSONArray ){
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "class" ), JSONTypes.ARRAY ) );
}
target = processJSONArray( (JSONArray) value, target, expandableProperties );
}else if( value instanceof JSONObject ){
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "class" ), JSONTypes.OBJECT ) );
}
target = processJSONObject( (JSONObject) value, target, expandableProperties, false );
}else if( JSONUtils.isNull( value ) ){
if( isTypeHintsEnabled() ){
target.addAttribute( new Attribute( addJsonPrefix( "class" ), JSONTypes.OBJECT ) );
}
target.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
}
return target;
}
private JSON processObjectElement( Element element, String defaultType ) {
if( isNullObject( element ) ){
return JSONNull.getInstance();
}
JSONObject jsonObject = new JSONObject();
if( !skipNamespaces ){
for( int j = 0; j < element.getNamespaceDeclarationCount(); j++ ){
String prefix = element.getNamespacePrefix( j );
String uri = element.getNamespaceURI( prefix );
if( StringUtils.isBlank( uri ) ){
continue;
}
if( !StringUtils.isBlank( prefix ) ){
prefix = ":" + prefix;
}
setOrAccumulate( jsonObject, "@xmlns" + prefix, trimSpaceFromValue( uri ) );
}
}
// process attributes first
int attrCount = element.getAttributeCount();
for( int i = 0; i < attrCount; i++ ){
Attribute attr = element.getAttribute( i );
String attrname = attr.getQualifiedName();
if( isTypeHintsEnabled()
&& (addJsonPrefix( "class" ).compareToIgnoreCase( attrname ) == 0 || addJsonPrefix(
"type" ).compareToIgnoreCase( attrname ) == 0) ){
continue;
}
String attrvalue = attr.getValue();
setOrAccumulate( jsonObject, "@" + removeNamespacePrefix( attrname ),
trimSpaceFromValue( attrvalue ) );
}
// process children (including text)
int childCount = element.getChildCount();
for( int i = 0; i < childCount; i++ ){
Node child = element.getChild( i );
if( child instanceof Text ){
Text text = (Text) child;
if( StringUtils.isNotBlank( StringUtils.strip( text.getValue() ) ) ){
setOrAccumulate( jsonObject, "#text", trimSpaceFromValue( text.getValue() ) );
}
}else if( child instanceof Element ){
setValue( jsonObject, (Element) child, defaultType );
}
}
return jsonObject;
}
private String removeNamespacePrefix( String name ) {
if( isRemoveNamespacePrefixFromElements() ){
int colon = name.indexOf( ':' );
return colon != -1 ? name.substring( colon + 1 ) : name;
}
return name;
}
private void setOrAccumulate( JSONObject jsonObject, String key, Object value ) {
if( jsonObject.has( key ) ){
jsonObject.accumulate( key, value );
Object val = jsonObject.get( key );
if( val instanceof JSONArray ){
((JSONArray) val).setExpandElements( true );
}
}else{
jsonObject.element( key, value );
}
}
private void setValue( JSONArray jsonArray, Element element, String defaultType ) {
String clazz = getClass( element );
String type = getType( element );
type = (type == null) ? defaultType : type;
if( hasNamespaces( element ) && !skipNamespaces ){
jsonArray.element( simplifyValue( null, processElement( element, type ) ) );
return;
}else if( element.getAttributeCount() > 0 ){
if( isFunction( element ) ){
Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
String[] params = null;
String text = element.getValue();
params = StringUtils.split( paramsAttribute.getValue(), "," );
jsonArray.element( new JSONFunction( params, text ) );
return;
}else{
jsonArray.element( simplifyValue( null, processElement( element, type ) ) );
return;
}
}
boolean classProcessed = false;
if( clazz != null ){
if( clazz.compareToIgnoreCase( JSONTypes.ARRAY ) == 0 ){
jsonArray.element( processArrayElement( element, type ) );
classProcessed = true;
}else if( clazz.compareToIgnoreCase( JSONTypes.OBJECT ) == 0 ){
jsonArray.element( simplifyValue( null, processObjectElement( element, type ) ) );
classProcessed = true;
}
}
if( !classProcessed ){
if( type.compareToIgnoreCase( JSONTypes.BOOLEAN ) == 0 ){
jsonArray.element( Boolean.valueOf( element.getValue() ) );
}else if( type.compareToIgnoreCase( JSONTypes.NUMBER ) == 0 ){
// try integer first
try{
jsonArray.element( Integer.valueOf( element.getValue() ) );
}catch( NumberFormatException e ){
jsonArray.element( Double.valueOf( element.getValue() ) );
}
}else if( type.compareToIgnoreCase( JSONTypes.INTEGER ) == 0 ){
jsonArray.element( Integer.valueOf( element.getValue() ) );
}else if( type.compareToIgnoreCase( JSONTypes.FLOAT ) == 0 ){
jsonArray.element( Double.valueOf( element.getValue() ) );
}else if( type.compareToIgnoreCase( JSONTypes.FUNCTION ) == 0 ){
String[] params = null;
String text = element.getValue();
Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
if( paramsAttribute != null ){
params = StringUtils.split( paramsAttribute.getValue(), "," );
}
jsonArray.element( new JSONFunction( params, text ) );
}else if( type.compareToIgnoreCase( JSONTypes.STRING ) == 0 ){
// see if by any chance has a 'params' attribute
Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
if( paramsAttribute != null ){
String[] params = null;
String text = element.getValue();
params = StringUtils.split( paramsAttribute.getValue(), "," );
jsonArray.element( new JSONFunction( params, text ) );
}else{
if( isArray( element, false ) ){
jsonArray.element( processArrayElement( element, defaultType ) );
}else if( isObject( element, false ) ){
jsonArray.element( simplifyValue( null, processObjectElement( element,
defaultType ) ) );
}else{
jsonArray.element( trimSpaceFromValue( element.getValue() ) );
}
}
}
}
}
private void setValue( JSONObject jsonObject, Element element, String defaultType ) {
String clazz = getClass( element );
String type = getType( element );
type = (type == null) ? defaultType : type;
String key = removeNamespacePrefix( element.getQualifiedName() );
if( hasNamespaces( element ) && !skipNamespaces ){
setOrAccumulate( jsonObject, key, simplifyValue( jsonObject,
processElement( element, type ) ) );
return;
}else if( element.getAttributeCount() > 0 ){
if( isFunction( element ) ){
Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
String text = element.getValue();
String[] params = StringUtils.split( paramsAttribute.getValue(), "," );
setOrAccumulate( jsonObject, key, new JSONFunction( params, text ) );
return;
}/*else{
setOrAccumulate( jsonObject, key, simplifyValue( jsonObject, processElement( element,
type ) ) );
return;
}*/
}
boolean classProcessed = false;
if( clazz != null ){
if( clazz.compareToIgnoreCase( JSONTypes.ARRAY ) == 0 ){
setOrAccumulate( jsonObject, key, processArrayElement( element, type ) );
classProcessed = true;
}else if( clazz.compareToIgnoreCase( JSONTypes.OBJECT ) == 0 ){
setOrAccumulate( jsonObject, key, simplifyValue( jsonObject, processObjectElement(
element, type ) ) );
classProcessed = true;
}
}
if( !classProcessed ){
if( type.compareToIgnoreCase( JSONTypes.BOOLEAN ) == 0 ){
setOrAccumulate( jsonObject, key, Boolean.valueOf( element.getValue() ) );
}else if( type.compareToIgnoreCase( JSONTypes.NUMBER ) == 0 ){
// try integer first
try{
setOrAccumulate( jsonObject, key, Integer.valueOf( element.getValue() ) );
}catch( NumberFormatException e ){
setOrAccumulate( jsonObject, key, Double.valueOf( element.getValue() ) );
}
}else if( type.compareToIgnoreCase( JSONTypes.INTEGER ) == 0 ){
setOrAccumulate( jsonObject, key, Integer.valueOf( element.getValue() ) );
}else if( type.compareToIgnoreCase( JSONTypes.FLOAT ) == 0 ){
setOrAccumulate( jsonObject, key, Double.valueOf( element.getValue() ) );
}else if( type.compareToIgnoreCase( JSONTypes.FUNCTION ) == 0 ){
String[] params = null;
String text = element.getValue();
Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
if( paramsAttribute != null ){
params = StringUtils.split( paramsAttribute.getValue(), "," );
}
setOrAccumulate( jsonObject, key, new JSONFunction( params, text ) );
}else if( type.compareToIgnoreCase( JSONTypes.STRING ) == 0 ){
// see if by any chance has a 'params' attribute
Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
if( paramsAttribute != null ){
String[] params = null;
String text = element.getValue();
params = StringUtils.split( paramsAttribute.getValue(), "," );
setOrAccumulate( jsonObject, key, new JSONFunction( params, text ) );
}else{
if( isArray( element, false ) ){
setOrAccumulate( jsonObject, key, processArrayElement( element, defaultType ) );
}else if( isObject( element, false ) ){
setOrAccumulate( jsonObject, key, simplifyValue( jsonObject,
processObjectElement( element, defaultType ) ) );
}else{
setOrAccumulate( jsonObject, key, trimSpaceFromValue( element.getValue() ) );
}
}
}
}
}
private Object simplifyValue( JSONObject parent, Object json ) {
if( json instanceof JSONObject ){
JSONObject object = (JSONObject) json;
if( parent != null ){
// remove all duplicated @xmlns from child
for( Iterator entries = parent.entrySet()
.iterator(); entries.hasNext(); ){
Map.Entry entry = (Map.Entry) entries.next();
String key = (String) entry.getKey();
Object value = entry.getValue();
if( key.startsWith( "@xmlns" ) && value.equals( object.opt( key ) ) ){
object.remove( key );
}
}
}
if( object.size() == 1 && object.has( "#text" ) ){
return object.get( "#text" );
}
}
return json;
}
private String trimSpaceFromValue( String value ) {
if( isTrimSpaces() ){
return value.trim();
}
return value;
}
private String writeDocument( Document doc, String encoding ) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try{
XomSerializer serializer = (encoding == null) ? new XomSerializer( baos )
: new XomSerializer( baos, encoding );
serializer.write( doc );
encoding = serializer.getEncoding();
}catch( IOException ioe ){
throw new JSONException( ioe );
}
String str = null;
try{
str = baos.toString( encoding );
}catch( UnsupportedEncodingException uee ){
throw new JSONException( uee );
}
return str;
}
private static class CustomElement extends Element {
private static String getName( String name ) {
int colon = name.indexOf( ':' );
if( colon != -1 ){
return name.substring( colon + 1 );
}
return name;
}
private static String getPrefix( String name ) {
int colon = name.indexOf( ':' );
if( colon != -1 ){
return name.substring( 0, colon );
}
return "";
}
private String prefix;
public CustomElement( String name ) {
super( CustomElement.getName( name ) );
prefix = CustomElement.getPrefix( name );
}
public final String getQName() {
if( prefix.length() == 0 ){
return getLocalName();
}else{
return prefix + ":" + getLocalName();
}
}
}
private class XomSerializer extends Serializer {
public XomSerializer( OutputStream out ) {
super( out );
}
public XomSerializer( OutputStream out, String encoding ) throws UnsupportedEncodingException {
super( out, encoding );
}
protected void write( Text text ) throws IOException {
String value = text.getValue();
if( value.startsWith( "" ) ){
value = value.substring( 9 );
value = value.substring( 0, value.length() - 3 );
writeRaw( "" );
}else{
super.write( text );
}
}
protected void writeEmptyElementTag( Element element ) throws IOException {
if( element instanceof CustomElement && isNamespaceLenient() ){
writeTagBeginning( (CustomElement) element );
writeRaw( "/>" );
}else{
super.writeEmptyElementTag( element );
}
}
protected void writeEndTag( Element element ) throws IOException {
if( element instanceof CustomElement && isNamespaceLenient() ){
writeRaw( "" );
writeRaw( ((CustomElement) element).getQName() );
writeRaw( ">" );
}else{
super.writeEndTag( element );
}
}
protected void writeNamespaceDeclaration( String prefix, String uri ) throws IOException {
if( !StringUtils.isBlank( uri ) ){
super.writeNamespaceDeclaration( prefix, uri );
}
}
protected void writeStartTag( Element element ) throws IOException {
if( element instanceof CustomElement && isNamespaceLenient() ){
writeTagBeginning( (CustomElement) element );
writeRaw( ">" );
}else{
super.writeStartTag( element );
}
}
private void writeTagBeginning( CustomElement element ) throws IOException {
writeRaw( "<" );
writeRaw( element.getQName() );
writeAttributes( element );
writeNamespaceDeclarations( element );
}
}
}