Open Source Repository

Home /json/json-lib-2.4-jdk15 | Repository Home



net/sf/json/xml/XMLSerializer.java
/*
 * 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.<br>
 * When transforming JSONObject and JSONArray instances to XML, this class will
 * add hints for converting back to JSON.<br>
 * Examples:<br>
 *
 <pre>
 * JSONObject json = JSONObject.fromObject("{\"name\":\"json\",\"bool\":true,\"int\":1}");
 * String xml = new XMLSerializer().write( json );
 <xmp><o class="object">
 <name type="string">json</name>
 <bool type="boolean">true</bool>
 <int type="number">1</int>
 </o></xmp>
 </pre><pre>
 * JSONArray json = JSONArray.fromObject("[1,2,3]");
 * String xml = new XMLSerializer().write( json );
 <xmp><a class="array">
 <e type="number">1</e>
 <e type="number">2</e>
 <e type="number">3</e>
 </a></xmp>
 </pre>
 *
 @author Andres Almiray <[email protected]>
 */
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.getLogXMLSerializer.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.<br>
    <ul>
    <li><code>objectName</code>: 'o'</li>
    <li><code>arrayName</code>: 'a'</li>
    <li><code>elementName</code>: 'e'</li>
    <li><code>typeHinstEnabled</code>: true</li>
    <li><code>typeHinstCompatibility</code>: true</li>
    <li><code>namespaceLenient</code>: false</li>
    <li><code>expandableProperties</code>: []</li>
    <li><code>skipNamespaces</code>: false</li>
    <li><code>removeNameSpacePrefixFromElement</code>: false</li>
    <li><code>trimSpaces</code>: false</li>
    </ul>
    */
   public XMLSerializer() {
      setObjectName"o" );
      setArrayName"a" );
      setElementName"e" );
      setTypeHintsEnabledtrue );
      setTypeHintsCompatibilitytrue );
      setNamespaceLenientfalse );
      setSkipNamespacesfalse );
      setRemoveNamespacePrefixFromElementsfalse );
      setTrimSpacesfalse );
      setExpandablePropertiesEMPTY_ARRAY );
      setSkipNamespacesfalse );
   }

   /**
    * Adds a namespace declaration to the root element.
    *
    @param prefix namespace prefix
    @param uri namespace uri
    */
   public void addNamespaceString prefix, String uri ) {
      addNamespaceprefix, uri, null );
   }

   /**
    * Adds a namespace declaration to an element.<br>
    * 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 addNamespaceString prefix, String uri, String elementName ) {
      ifStringUtils.isBlankuri ) ){
         return;
      }
      ifprefix == null ){
         prefix = "";
      }
      ifStringUtils.isBlankelementName ) ){
         rootNamespace.putprefix.trim(), uri.trim() );
      }else{
         Map nameSpaces = (MapnamespacesPerElement.getelementName );
         ifnameSpaces == null ){
            nameSpaces = new TreeMap();
            namespacesPerElement.putelementName, nameSpaces );
         }
         nameSpaces.putprefix, uri );
      }
   }

   /**
    * Removes all namespaces declarations (from root an elements).
    */
   public void clearNamespaces() {
      rootNamespace.clear();
      namespacesPerElement.clear();
   }

   /**
    * Removes all namespace declarations from an element.<br>
    * 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 clearNamespacesString elementName ) {
      ifStringUtils.isBlankelementName ) ){
         rootNamespace.clear();
      }else{
         namespacesPerElement.removeelementName );
      }
   }

   /**
    * 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 readString xml ) {
      JSON json = null;
      try{
         Document doc = new Builder().buildnew StringReaderxml ) );
         Element root = doc.getRootElement();
         ifisNullObjectroot ) ){
            return JSONNull.getInstance();
         }
         String defaultType = getTyperoot, JSONTypes.STRING );
         ifisArrayroot, true ) ){
            json = processArrayElementroot, defaultType );
            ifforceTopLevelObject ){
               String key = removeNamespacePrefixroot.getQualifiedName() );
               json = new JSONObject().elementkey, json );
            }
         }else
            json = processObjectElementroot, defaultType );
            ifforceTopLevelObject ) {
               String key = removeNamespacePrefixroot.getQualifiedName() );
               json = new JSONObject().elementkey, json );
            }
         }
      }catchJSONException jsone ){
         throw jsone;
      }catchException e ){
         throw new JSONException);
      }
      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 readFromFileFile file ) {
      iffile == null ){
         throw new JSONException"File is null" );
      }
      if!file.canRead() ){
         throw new JSONException"Can't read input file" );
      }
      iffile.isDirectory() ){
         throw new JSONException"File is a directory" );
      }
      try{
         return readFromStreamnew FileInputStreamfile ) );
      }catchIOException ioe ){
         throw new JSONExceptionioe );
      }
   }

   /**
    * 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 readFromFileString path ) {
      return readFromStreamThread.currentThread()
            .getContextClassLoader()
            .getResourceAsStreampath ) );
   }

   /**
    * 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 readFromStreamInputStream stream ) {
      try{
         StringBuffer xml = new StringBuffer();
         BufferedReader in = new BufferedReadernew InputStreamReaderstream ) );
         String line = null;
         while( (line = in.readLine()) != null ){
            xml.appendline );
         }
         return readxml.toString() );
      }catchIOException ioe ){
         throw new JSONExceptionioe );
      }
   }

   /**
    * Removes a namespace from the root element.
    *
    @param prefix namespace prefix
    */
   public void removeNamespaceString prefix ) {
      removeNamespaceprefix, null );
   }

   /**
    * Removes a namespace from the root element.<br>
    * 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 removeNamespaceString prefix, String elementName ) {
      ifprefix == null ){
         prefix = "";
      }
      ifStringUtils.isBlankelementName ) ){
         rootNamespace.removeprefix.trim() );
      }else{
         Map nameSpaces = (MapnamespacesPerElement.getelementName );
         nameSpaces.removeprefix );
      }
   }

   /**
    * Sets the name used for JSONArray.<br>
    * Default is 'a'.
    */
   public void setArrayNameString arrayName ) {
      this.arrayName = StringUtils.isBlankarrayName "a" : arrayName;
   }

   /**
    * Sets the name used for JSONArray elements.<br>
    * Default is 'e'.
    */
   public void setElementNameString elementName ) {
      this.elementName = StringUtils.isBlankelementName "e" : elementName;
   }

   /**
    * Sets the list of properties to be expanded from child to parent.
    */
   public void setExpandablePropertiesString[] expandableProperties ) {
      this.expandableProperties = expandableProperties == null ? EMPTY_ARRAY : expandableProperties;
   }

   public void setForceTopLevelObjectboolean forceTopLevelObject ) {
      this.forceTopLevelObject = forceTopLevelObject;
   }

   /**
    * Sets the namespace declaration to the root element.<br>
    * Any previous values are discarded.
    *
    @param prefix namespace prefix
    @param uri namespace uri
    */
   public void setNamespaceString prefix, String uri ) {
      setNamespaceprefix, uri, null );
   }

   /**
    * Adds a namespace declaration to an element.<br>
    * 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 setNamespaceString prefix, String uri, String elementName ) {
      ifStringUtils.isBlankuri ) ){
         return;
      }
      ifprefix == null ){
         prefix = "";
      }
      ifStringUtils.isBlankelementName ) ){
         rootNamespace.clear();
         rootNamespace.putprefix.trim(), uri.trim() );
      }else{
         Map nameSpaces = (MapnamespacesPerElement.getelementName );
         ifnameSpaces == null ){
            nameSpaces = new TreeMap();
            namespacesPerElement.putelementName, nameSpaces );
         }
         nameSpaces.clear();
         nameSpaces.putprefix, uri );
      }
   }

   /**
    * Sets wether this serializer is tolerant to namespaces without URIs or not.
    */
   public void setNamespaceLenientboolean namespaceLenient ) {
      this.namespaceLenient = namespaceLenient;
   }

   /**
    * Sets the name used for JSONObject.<br>
    * Default is 'o'.
    */
   public void setObjectNameString objectName ) {
      this.objectName = StringUtils.isBlankobjectName "o" : objectName;
   }

   /**
    * Sets if this serializer will remove namespace prefix from elements when
    * reading.
    */
   public void setRemoveNamespacePrefixFromElementsboolean removeNamespacePrefixFromElements ) {
      this.removeNamespacePrefixFromElements = removeNamespacePrefixFromElements;
   }

   /**
    * Sets the name used for the root element.
    */
   public void setRootNameString rootName ) {
      this.rootName = StringUtils.isBlankrootName null : rootName;
   }

   /**
    * Sets if this serializer will skip adding namespace declarations to
    * elements when reading.
    */
   public void setSkipNamespacesboolean skipNamespaces ) {
      this.skipNamespaces = skipNamespaces;
   }

   /**
    * Sets if this serializer will skip whitespace when reading.
    */
   public void setSkipWhitespaceboolean skipWhitespace ) {
      this.skipWhitespace = skipWhitespace;
   }

   /**
    * Sets if this serializer will trim leading and trealing whitespace from
    * values when reading.
    */
   public void setTrimSpacesboolean trimSpaces ) {
      this.trimSpaces = trimSpaces;
   }

   /**
    * Sets wether types hints will have a 'json_' prefix or not.
    */
   public void setTypeHintsCompatibilityboolean typeHintsCompatibility ) {
      this.typeHintsCompatibility = typeHintsCompatibility;
   }

   /**
    * Sets wether JSON types will be included as attributes.
    */
   public void setTypeHintsEnabledboolean typeHintsEnabled ) {
      this.typeHintsEnabled = typeHintsEnabled;
   }

   /**
    * Writes a JSON value into a XML string with UTF-8 encoding.<br>
    *
    @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 writeJSON json ) {
      return writejson, null );
   }

   /**
    * Writes a JSON value into a XML string with an specific encoding.<br>
    * 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 writeJSON json, String encoding ) {
      ifJSONNull.getInstance()
            .equalsjson ) ){
         Element root = null;
         root = newElementgetRootName() == null ? getObjectName() : getRootName() );
         root.addAttributenew AttributeaddJsonPrefix"null" )"true" ) );
         Document doc = new Documentroot );
         return writeDocumentdoc, encoding );
      }else ifjson instanceof JSONArray ){
         JSONArray jsonArray = (JSONArrayjson;
         Element root = processJSONArrayjsonArray,
               newElementgetRootName() == null ? getArrayName() : getRootName() ),
               expandableProperties );
         Document doc = new Documentroot );
         return writeDocumentdoc, encoding );
      }else{
         JSONObject jsonObject = (JSONObjectjson;
         Element root = null;
         ifjsonObject.isNullObject() ){
            root = newElementgetObjectName() );
            root.addAttributenew AttributeaddJsonPrefix"null" )"true" ) );
         }else{
            root = processJSONObjectjsonObject,
                  newElementgetRootName() == null ? getObjectName() : getRootName() ),
                  expandableProperties, true );
         }
         Document doc = new Documentroot );
         return writeDocumentdoc, encoding );
      }
   }

   private String addJsonPrefixString str ) {
      if!isTypeHintsCompatibility() ){
         return JSON_PREFIX + str;
      }
      return str;
   }

   private void addNameSpaceToElementElement element ) {
      String elementName = null;
      ifelement instanceof CustomElement ){
         elementName = ((CustomElementelement).getQName();
      }else{
         elementName = element.getQualifiedName();
      }
      Map nameSpaces = (MapnamespacesPerElement.getelementName );
      ifnameSpaces != null && !nameSpaces.isEmpty() ){
         setNamespaceLenienttrue );
         forIterator entries = nameSpaces.entrySet()
               .iterator(); entries.hasNext()){
            Map.Entry entry = (Map.Entryentries.next();
            String prefix = (Stringentry.getKey();
            String uri = (Stringentry.getValue();
            ifStringUtils.isBlankprefix ) ){
               element.setNamespaceURIuri );
            }else{
               element.addNamespaceDeclarationprefix, uri );
            }
         }
      }
   }

   private boolean checkChildElementsElement element, boolean isTopLevel ) {
      int childCount = element.getChildCount();
      Elements elements = element.getChildElements();
      int elementCount = elements.size();

      ifchildCount == && element.getChildinstanceof Text ){
         return isTopLevel;
      }

      ifchildCount == elementCount ){
         ifelementCount == ){
            return true;
         }
         ifelementCount == ){
            ifskipWhitespace || element.getChildinstanceof Text ){
               return true;
            }else{
               return false;
            }
         }
      }

      ifchildCount > elementCount ){
         forint i = 0; i < childCount; i++ ){
            Node node = element.getChild);
            ifnode instanceof Text ){
               Text text = (Textnode;
               ifStringUtils.isNotBlankStringUtils.striptext.getValue() ) )
                     && !skipWhitespace ){
                  return false;
               }
            }
         }
      }

      String childName = elements.get)
            .getQualifiedName();
      forint i = 1; i < elementCount; i++ ){
         ifchildName.compareToelements.get)
               .getQualifiedName() ) != ){
            return false;
         }
      }

      return true;
   }

   private String getClassElement element ) {
      Attribute attribute = element.getAttributeaddJsonPrefix"class" ) );
      String clazz = null;
      ifattribute != null ){
         String clazzText = attribute.getValue()
               .trim();
         ifJSONTypes.OBJECT.compareToIgnoreCaseclazzText == ){
            clazz = JSONTypes.OBJECT;
         }else ifJSONTypes.ARRAY.compareToIgnoreCaseclazzText == ){
            clazz = JSONTypes.ARRAY;
         }
      }
      return clazz;
   }

   private String getTypeElement element ) {
      return getTypeelement, null );
   }

   private String getTypeElement element, String defaultType ) {
      Attribute attribute = element.getAttributeaddJsonPrefix"type" ) );
      String type = null;
      ifattribute != null ){
         String typeText = attribute.getValue()
               .trim();
         ifJSONTypes.BOOLEAN.compareToIgnoreCasetypeText == ){
            type = JSONTypes.BOOLEAN;
         }else ifJSONTypes.NUMBER.compareToIgnoreCasetypeText == ){
            type = JSONTypes.NUMBER;
         }else ifJSONTypes.INTEGER.compareToIgnoreCasetypeText == ){
            type = JSONTypes.INTEGER;
         }else ifJSONTypes.FLOAT.compareToIgnoreCasetypeText == ){
            type = JSONTypes.FLOAT;
         }else ifJSONTypes.OBJECT.compareToIgnoreCasetypeText == ){
            type = JSONTypes.OBJECT;
         }else ifJSONTypes.ARRAY.compareToIgnoreCasetypeText == ){
            type = JSONTypes.ARRAY;
         }else ifJSONTypes.STRING.compareToIgnoreCasetypeText == ){
            type = JSONTypes.STRING;
         }else ifJSONTypes.FUNCTION.compareToIgnoreCasetypeText == ){
            type = JSONTypes.FUNCTION;
         }
      }else{
         ifdefaultType != null ){
            log.info"Using default type " + defaultType );
            type = defaultType;
         }
      }
      return type;
   }

   private boolean hasNamespacesElement element ) {
      int namespaces = 0;
      forint i = 0; i < element.getNamespaceDeclarationCount(); i++ ){
         String prefix = element.getNamespacePrefix);
         String uri = element.getNamespaceURIprefix );
         ifStringUtils.isBlankuri ) ){
            continue;
         }
         namespaces++;
      }
      return namespaces > 0;
   }

   private boolean isArrayElement element, boolean isTopLevel ) {
      boolean isArray = false;
      String clazz = getClasselement );
      ifclazz != null && clazz.equalsJSONTypes.ARRAY ) ){
         isArray = true;
      }else ifelement.getAttributeCount() == ){
         isArray = checkChildElementselement, isTopLevel );
      }else ifelement.getAttributeCount() == 1
            && (element.getAttributeaddJsonPrefix"class" ) ) != null || element.getAttributeaddJsonPrefix"type" ) ) != null) ){
         isArray = checkChildElementselement, isTopLevel );
      }else ifelement.getAttributeCount() == 2
            && (element.getAttributeaddJsonPrefix"class" ) ) != null && element.getAttributeaddJsonPrefix"type" ) ) != null) ){
         isArray = checkChildElementselement, isTopLevel );
      }

      ifisArray ){
         // check namespace
         forint j = 0; j < element.getNamespaceDeclarationCount(); j++ ){
            String prefix = element.getNamespacePrefix);
            String uri = element.getNamespaceURIprefix );
            if!StringUtils.isBlankuri ) ){
               return false;
            }
         }
      }

      return isArray;
   }

   private boolean isFunctionElement element ) {
      int attrCount = element.getAttributeCount();
      ifattrCount > ){
         Attribute typeAttr = element.getAttributeaddJsonPrefix"type" ) );
         Attribute paramsAttr = element.getAttributeaddJsonPrefix"params" ) );
         ifattrCount == && paramsAttr != null ){
            return true;
         }
         ifattrCount == && paramsAttr != null && typeAttr != null && (typeAttr.getValue()
               .compareToIgnoreCaseJSONTypes.STRING == || typeAttr.getValue()
               .compareToIgnoreCaseJSONTypes.FUNCTION == 0) ){
            return true;
         }
      }
      return false;
   }

   private boolean isNullObjectElement element ) {
      ifelement.getChildCount() == ){
         ifelement.getAttributeCount() == ){
            return true;
         }else ifelement.getAttributeaddJsonPrefix"null" ) ) != null ){
            return true;
         }else ifelement.getAttributeCount() == 1
               && (element.getAttributeaddJsonPrefix"class" ) ) != null || element.getAttributeaddJsonPrefix"type" ) ) != null) ){
            return true;
         }else ifelement.getAttributeCount() == 2
               && (element.getAttributeaddJsonPrefix"class" ) ) != null && element.getAttributeaddJsonPrefix"type" ) ) != null) ){
            return true;
         }
      }
      ifskipWhitespace && element.getChildCount() == && element.getChildinstanceof Text ){
         return true;
      }
      return false;
   }

   private boolean isObjectElement element, boolean isTopLevel ) {
      boolean isObject = false;
      if!isArrayelement, isTopLevel && !isFunctionelement ) ){
         ifhasNamespaceselement ) ){
            return true;
         }

         int attributeCount = element.getAttributeCount();
         ifattributeCount > ){
            int attrs = element.getAttributeaddJsonPrefix"null" )) == null 1;
            attrs += element.getAttributeaddJsonPrefix"class" )) == null 01;
            attrs += element.getAttributeaddJsonPrefix"type" ))== null 1;
            switchattributeCount ){
               case 1:
                  ifattrs == 0){
                     return true;
                  }
                  break;
               case 2:
                  ifattrs < ){
                     return true;
                  }
                  break;
               case 3
                  if(  attrs < ){
                     return true;
                  }
                  break;
               default:
                  return true;
            }
         }
         
         int childCount = element.getChildCount();
         ifchildCount == && element.getChildinstanceof Text ){
            return isTopLevel;
         }

         isObject = true;
      }
      return isObject;
   }

   private Element newElementString name ) {
      ifname.indexOf':' != -){
         namespaceLenient = true;
      }
      return namespaceLenient ? new CustomElementname new Elementname );
   }

   private JSON processArrayElementElement element, String defaultType ) {
      JSONArray jsonArray = new JSONArray();
      // process children (including text)
      int childCount = element.getChildCount();
      forint i = 0; i < childCount; i++ ){
         Node child = element.getChild);
         ifchild instanceof Text ){
            Text text = (Textchild;
            ifStringUtils.isNotBlankStringUtils.striptext.getValue() ) ) ){
               jsonArray.elementtext.getValue() );
            }
         }else ifchild instanceof Element ){
            setValuejsonArray, (Elementchild, defaultType );
         }
      }
      return jsonArray;
   }

   private Object processElementElement element, String type ) {
      ifisNullObjectelement ) ){
         return JSONNull.getInstance();
      }else ifisArrayelement, false ) ){
         return processArrayElementelement, type );
      }else ifisObjectelement, false ) ){
         return processObjectElementelement, type );
      }else{
         return trimSpaceFromValueelement.getValue() );
      }
   }

   private Element processJSONArrayJSONArray array, Element root, String[] expandableProperties ) {
      int l = array.size();
      forint i = 0; i < l; i++ ){
         Object value = array.get);
         Element element = processJSONValuevalue, root, null, expandableProperties );
         root.appendChildelement );
      }
      return root;
   }

   private Element processJSONObjectJSONObject jsonObject, Element root,
         String[] expandableProperties, boolean isRoot ) {
      ifjsonObject.isNullObject() ){
         root.addAttributenew AttributeaddJsonPrefix"null" )"true" ) );
         return root;
      }else ifjsonObject.isEmpty() ){
         return root;
      }

      ifisRoot ){
         if!rootNamespace.isEmpty() ){
            setNamespaceLenienttrue );
            forIterator entries = rootNamespace.entrySet()
                  .iterator(); entries.hasNext()){
               Map.Entry entry = (Map.Entryentries.next();
               String prefix = (Stringentry.getKey();
               String uri = (Stringentry.getValue();
               ifStringUtils.isBlankprefix ) ){
                  root.setNamespaceURIuri );
               }else{
                  root.addNamespaceDeclarationprefix, uri );
               }
            }
         }
      }

      addNameSpaceToElementroot );

      Object[] names = jsonObject.names()
            .toArray();
      Arrays.sortnames );
      Element element = null;
      forint i = 0; i < names.length; i++ ){
         String name = (Stringnames[i];
         Object value = jsonObject.getname );
         ifname.startsWith"@xmlns" ) ){
            setNamespaceLenienttrue );
            int colon = name.indexOf':' );
            ifcolon == -){
               // do not override if already defined by nameSpaceMaps
               ifStringUtils.isBlankroot.getNamespaceURI() ) ){
                  root.setNamespaceURIString.valueOfvalue ) );
               }
            }else{
               String prefix = name.substringcolon + );
               ifStringUtils.isBlankroot.getNamespaceURIprefix ) ) ){
                  root.addNamespaceDeclarationprefix, String.valueOfvalue ) );
               }
            }
         }else ifname.startsWith"@" ) ){
            root.addAttributenew Attributename.substring), String.valueOfvalue ) ) );
         }else ifname.equals"#text" ) ){
            ifvalue instanceof JSONArray ){
               root.appendChild( ((JSONArrayvalue).join""true ) );
            }else{
               root.appendChildString.valueOfvalue ) );
            }
         }else ifvalue instanceof JSONArray
               && (((JSONArrayvalue).isExpandElements() || ArrayUtils.contains(
                     expandableProperties, name )) ){
            JSONArray array = (JSONArrayvalue;
            int l = array.size();
            forint j = 0; j < l; j++ ){
               Object item = array.get);
               element = newElementname );
               ifitem instanceof JSONObject ){
                  element = processJSONValue( (JSONObjectitem, root, element,
                        expandableProperties );
               }else ifitem instanceof JSONArray ){
                  element = processJSONValue( (JSONArrayitem, root, element, expandableProperties );
               }else{
                  element = processJSONValueitem, root, element, expandableProperties );
               }
               addNameSpaceToElementelement );
               root.appendChildelement );
            }
         }else{
            element = newElementname );
            element = processJSONValuevalue, root, element, expandableProperties );
            addNameSpaceToElementelement );
            root.appendChildelement );
         }
      }
      return root;
   }

   private Element processJSONValueObject value, Element root, Element target,
         String[] expandableProperties ) {
      iftarget == null ){
         target = newElementgetElementName() );
      }
      ifJSONUtils.isBooleanvalue ) ){
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"type" ), JSONTypes.BOOLEAN ) );
         }
         target.appendChildvalue.toString() );
      }else ifJSONUtils.isNumbervalue ) ){
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"type" ), JSONTypes.NUMBER ) );
         }
         target.appendChildvalue.toString() );
      }else ifJSONUtils.isFunctionvalue ) ){
         ifvalue instanceof String ){
            value = JSONFunction.parse( (Stringvalue );
         }
         JSONFunction func = (JSONFunctionvalue;
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"type" ), JSONTypes.FUNCTION ) );
         }
         String params = ArrayUtils.toStringfunc.getParams() );
         params = params.substring);
         params = params.substring0, params.length() );
         target.addAttributenew AttributeaddJsonPrefix"params" ), params ) );
         target.appendChildnew Text"<![CDATA[" + func.getText() "]]>" ) );
      }else ifJSONUtils.isStringvalue ) ){
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"type" ), JSONTypes.STRING ) );
         }
         target.appendChildvalue.toString() );
      }else ifvalue instanceof JSONArray ){
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"class" ), JSONTypes.ARRAY ) );
         }
         target = processJSONArray( (JSONArrayvalue, target, expandableProperties );
      }else ifvalue instanceof JSONObject ){
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"class" ), JSONTypes.OBJECT ) );
         }
         target = processJSONObject( (JSONObjectvalue, target, expandableProperties, false );
      }else ifJSONUtils.isNullvalue ) ){
         ifisTypeHintsEnabled() ){
            target.addAttributenew AttributeaddJsonPrefix"class" ), JSONTypes.OBJECT ) );
         }
         target.addAttributenew AttributeaddJsonPrefix"null" )"true" ) );
      }
      return target;
   }

   private JSON processObjectElementElement element, String defaultType ) {
      ifisNullObjectelement ) ){
         return JSONNull.getInstance();
      }
      JSONObject jsonObject = new JSONObject();

      if!skipNamespaces ){
         forint j = 0; j < element.getNamespaceDeclarationCount(); j++ ){
            String prefix = element.getNamespacePrefix);
            String uri = element.getNamespaceURIprefix );
            ifStringUtils.isBlankuri ) ){
               continue;
            }
            if!StringUtils.isBlankprefix ) ){
               prefix = ":" + prefix;
            }
            setOrAccumulatejsonObject, "@xmlns" + prefix, trimSpaceFromValueuri ) );
         }
      }
     
      // process attributes first
      int attrCount = element.getAttributeCount();
      forint i = 0; i < attrCount; i++ ){
         Attribute attr = element.getAttribute);
         String attrname = attr.getQualifiedName();
         ifisTypeHintsEnabled()
               && (addJsonPrefix"class" ).compareToIgnoreCaseattrname == || addJsonPrefix(
                     "type" ).compareToIgnoreCaseattrname == 0) ){
            continue;
         }
         String attrvalue = attr.getValue();
         setOrAccumulatejsonObject, "@" + removeNamespacePrefixattrname ),
               trimSpaceFromValueattrvalue ) );
      }

      // process children (including text)
      int childCount = element.getChildCount();
      forint i = 0; i < childCount; i++ ){
         Node child = element.getChild);
         ifchild instanceof Text ){
            Text text = (Textchild;
            ifStringUtils.isNotBlankStringUtils.striptext.getValue() ) ) ){
               setOrAccumulatejsonObject, "#text", trimSpaceFromValuetext.getValue() ) );
            }
         }else ifchild instanceof Element ){
            setValuejsonObject, (Elementchild, defaultType );
         }
      }

      return jsonObject;
   }

   private String removeNamespacePrefixString name ) {
      ifisRemoveNamespacePrefixFromElements() ){
         int colon = name.indexOf':' );
         return colon != -? name.substringcolon + : name;
      }
      return name;
   }

   private void setOrAccumulateJSONObject jsonObject, String key, Object value ) {
      ifjsonObject.haskey ) ){
         jsonObject.accumulatekey, value );
         Object val = jsonObject.getkey );
         ifval instanceof JSONArray ){
            ((JSONArrayval).setExpandElementstrue );
         }
      }else{
         jsonObject.elementkey, value );
      }
   }

   private void setValueJSONArray jsonArray, Element element, String defaultType ) {
      String clazz = getClasselement );
      String type = getTypeelement );
      type = (type == null? defaultType : type;

      ifhasNamespaceselement && !skipNamespaces ){
         jsonArray.elementsimplifyValuenull, processElementelement, type ) ) );
         return;
      }else ifelement.getAttributeCount() ){
         ifisFunctionelement ) ){
            Attribute paramsAttribute = element.getAttributeaddJsonPrefix"params" ) );
            String[] params = null;
            String text = element.getValue();
            params = StringUtils.splitparamsAttribute.getValue()"," );
            jsonArray.elementnew JSONFunctionparams, text ) );
            return;
         }else{
            jsonArray.elementsimplifyValuenull, processElementelement, type ) ) );
            return;
         }
      }

      boolean classProcessed = false;
      ifclazz != null ){
         ifclazz.compareToIgnoreCaseJSONTypes.ARRAY == ){
            jsonArray.elementprocessArrayElementelement, type ) );
            classProcessed = true;
         }else ifclazz.compareToIgnoreCaseJSONTypes.OBJECT == ){
            jsonArray.elementsimplifyValuenull, processObjectElementelement, type ) ) );
            classProcessed = true;
         }
      }
      if!classProcessed ){
         iftype.compareToIgnoreCaseJSONTypes.BOOLEAN == ){
            jsonArray.elementBoolean.valueOfelement.getValue() ) );
         }else iftype.compareToIgnoreCaseJSONTypes.NUMBER == ){
            // try integer first
            try{
               jsonArray.elementInteger.valueOfelement.getValue() ) );
            }catchNumberFormatException e ){
               jsonArray.elementDouble.valueOfelement.getValue() ) );
            }
         }else iftype.compareToIgnoreCaseJSONTypes.INTEGER == ){
            jsonArray.elementInteger.valueOfelement.getValue() ) );
         }else iftype.compareToIgnoreCaseJSONTypes.FLOAT == ){
            jsonArray.elementDouble.valueOfelement.getValue() ) );
         }else iftype.compareToIgnoreCaseJSONTypes.FUNCTION == ){
            String[] params = null;
            String text = element.getValue();
            Attribute paramsAttribute = element.getAttributeaddJsonPrefix"params" ) );
            ifparamsAttribute != null ){
               params = StringUtils.splitparamsAttribute.getValue()"," );
            }
            jsonArray.elementnew JSONFunctionparams, text ) );
         }else iftype.compareToIgnoreCaseJSONTypes.STRING == ){
            // see if by any chance has a 'params' attribute
            Attribute paramsAttribute = element.getAttributeaddJsonPrefix"params" ) );
            ifparamsAttribute != null ){
               String[] params = null;
               String text = element.getValue();
               params = StringUtils.splitparamsAttribute.getValue()"," );
               jsonArray.elementnew JSONFunctionparams, text ) );
            }else{
               ifisArrayelement, false ) ){
                  jsonArray.elementprocessArrayElementelement, defaultType ) );
               }else ifisObjectelement, false ) ){
                  jsonArray.elementsimplifyValuenull, processObjectElementelement,
                        defaultType ) ) );
               }else{
                  jsonArray.elementtrimSpaceFromValueelement.getValue() ) );
               }
            }
         }
      }
   }

   private void setValueJSONObject jsonObject, Element element, String defaultType ) {
      String clazz = getClasselement );
      String type = getTypeelement );
      type = (type == null? defaultType : type;
  
      
      
      String key = removeNamespacePrefixelement.getQualifiedName() );
      ifhasNamespaceselement && !skipNamespaces ){
         setOrAccumulatejsonObject, key, simplifyValuejsonObject,
               processElementelement, type ) ) );
         return;
      }else ifelement.getAttributeCount() ){
         ifisFunctionelement ) ){
            Attribute paramsAttribute = element.getAttributeaddJsonPrefix"params" ) );
            String text = element.getValue();
            String[] params = StringUtils.splitparamsAttribute.getValue()"," );
            setOrAccumulatejsonObject, key, new JSONFunctionparams, text ) );
            return;
         }/*else{
            setOrAccumulate( jsonObject, key, simplifyValue( jsonObject, processElement( element,
                  type ) ) );
            return;
         }*/
      }

      boolean classProcessed = false;
      ifclazz != null ){
         ifclazz.compareToIgnoreCaseJSONTypes.ARRAY == ){
            setOrAccumulatejsonObject, key, processArrayElementelement, type ) );
            classProcessed = true;
         }else ifclazz.compareToIgnoreCaseJSONTypes.OBJECT == ){
            setOrAccumulatejsonObject, key, simplifyValuejsonObject, processObjectElement(
                  element, type ) ) );
            classProcessed = true;
         }
      }
      if!classProcessed ){
         iftype.compareToIgnoreCaseJSONTypes.BOOLEAN == ){
            setOrAccumulatejsonObject, key, Boolean.valueOfelement.getValue() ) );
         }else iftype.compareToIgnoreCaseJSONTypes.NUMBER == ){
            // try integer first
            try{
               setOrAccumulatejsonObject, key, Integer.valueOfelement.getValue() ) );
            }catchNumberFormatException e ){
               setOrAccumulatejsonObject, key, Double.valueOfelement.getValue() ) );
            }
         }else iftype.compareToIgnoreCaseJSONTypes.INTEGER == ){
            setOrAccumulatejsonObject, key, Integer.valueOfelement.getValue() ) );
         }else iftype.compareToIgnoreCaseJSONTypes.FLOAT == ){
            setOrAccumulatejsonObject, key, Double.valueOfelement.getValue() ) );
         }else iftype.compareToIgnoreCaseJSONTypes.FUNCTION == ){
            String[] params = null;
            String text = element.getValue();
            Attribute paramsAttribute = element.getAttributeaddJsonPrefix"params" ) );
            ifparamsAttribute != null ){
               params = StringUtils.splitparamsAttribute.getValue()"," );
            }
            setOrAccumulatejsonObject, key, new JSONFunctionparams, text ) );
         }else iftype.compareToIgnoreCaseJSONTypes.STRING == ){
            // see if by any chance has a 'params' attribute
            Attribute paramsAttribute = element.getAttributeaddJsonPrefix"params" ) );
            ifparamsAttribute != null ){
               String[] params = null;
               String text = element.getValue();
               params = StringUtils.splitparamsAttribute.getValue()"," );
               setOrAccumulatejsonObject, key, new JSONFunctionparams, text ) );
            }else{
               ifisArrayelement, false ) ){
                  setOrAccumulatejsonObject, key, processArrayElementelement, defaultType ) );
               }else ifisObjectelement, false ) ){
                  setOrAccumulatejsonObject, key, simplifyValuejsonObject,
                        processObjectElementelement, defaultType ) ) );
               }else{
                  setOrAccumulatejsonObject, key, trimSpaceFromValueelement.getValue() ) );
               }
            }
         }
      }
   }

   private Object simplifyValueJSONObject parent, Object json ) {
      ifjson instanceof JSONObject ){
         JSONObject object = (JSONObjectjson;
         ifparent != null ){
            // remove all duplicated @xmlns from child
            forIterator entries = parent.entrySet()
                  .iterator(); entries.hasNext()){
               Map.Entry entry = (Map.Entryentries.next();
               String key = (Stringentry.getKey();
               Object value = entry.getValue();
               ifkey.startsWith"@xmlns" && value.equalsobject.optkey ) ) ){
                  object.removekey );
               }
            }
         }
         ifobject.size() == && object.has"#text" ) ){
            return object.get"#text" );
         }
      }
      return json;
   }
   private String trimSpaceFromValueString value ) {
      ifisTrimSpaces() ){
         return value.trim();
      }
      return value;
   }
   private String writeDocumentDocument doc, String encoding ) {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      try{
         XomSerializer serializer = (encoding == nullnew XomSerializerbaos )
               new XomSerializerbaos, encoding );
         serializer.writedoc );
         encoding = serializer.getEncoding();
      }catchIOException ioe ){
         throw new JSONExceptionioe );
      }

      String str = null;
      try{
         str = baos.toStringencoding );
      }catchUnsupportedEncodingException uee ){
         throw new JSONExceptionuee );
      }
      return str;
   }

   private static class CustomElement extends Element {
      private static String getNameString name ) {
         int colon = name.indexOf':' );
         ifcolon != -){
            return name.substringcolon + );
         }
         return name;
      }

      private static String getPrefixString name ) {
         int colon = name.indexOf':' );
         ifcolon != -){
            return name.substring0, colon );
         }
         return "";
      }

      private String prefix;

      public CustomElementString name ) {
         superCustomElement.getNamename ) );
         prefix = CustomElement.getPrefixname );
      }

      public final String getQName() {
         ifprefix.length() == ){
            return getLocalName();
         }else{
            return prefix + ":" + getLocalName();
         }
      }
   }

   private class XomSerializer extends Serializer {
      public XomSerializerOutputStream out ) {
         superout );
      }

      public XomSerializerOutputStream out, String encoding throws UnsupportedEncodingException {
         superout, encoding );
      }

      protected void writeText text throws IOException {
         String value = text.getValue();
         ifvalue.startsWith"<![CDATA[" && value.endsWith"]]>" ) ){
            value = value.substring);
            value = value.substring0, value.length() );
            writeRaw"<![CDATA[" );
            writeRawvalue );
            writeRaw"]]>" );
         }else{
            super.writetext );
         }
      }

      protected void writeEmptyElementTagElement element throws IOException {
         ifelement instanceof CustomElement && isNamespaceLenient() ){
            writeTagBeginning( (CustomElementelement );
            writeRaw"/>" );
         }else{
            super.writeEmptyElementTagelement );
         }
      }

      protected void writeEndTagElement element throws IOException {
         ifelement instanceof CustomElement && isNamespaceLenient() ){
            writeRaw"</" );
            writeRaw( ((CustomElementelement).getQName() );
            writeRaw">" );
         }else{
            super.writeEndTagelement );
         }
      }

      protected void writeNamespaceDeclarationString prefix, String uri throws IOException {
         if!StringUtils.isBlankuri ) ){
            super.writeNamespaceDeclarationprefix, uri );
         }
      }

      protected void writeStartTagElement element throws IOException {
         ifelement instanceof CustomElement && isNamespaceLenient() ){
            writeTagBeginning( (CustomElementelement );
            writeRaw">" );
         }else{
            super.writeStartTagelement );
         }
      }

      private void writeTagBeginningCustomElement element throws IOException {
         writeRaw"<" );
         writeRawelement.getQName() );
         writeAttributeselement );
         writeNamespaceDeclarationselement );
      }
   }
}