View Javadoc

1   /*
2    * Copyright 2002-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package net.sf.json.xml;
18  
19  import java.io.BufferedReader;
20  import java.io.ByteArrayOutputStream;
21  import java.io.File;
22  import java.io.FileInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.OutputStream;
27  import java.io.StringReader;
28  import java.io.UnsupportedEncodingException;
29  import java.util.Arrays;
30  import java.util.Iterator;
31  import java.util.Map;
32  import java.util.TreeMap;
33  
34  import net.sf.json.JSON;
35  import net.sf.json.JSONArray;
36  import net.sf.json.JSONException;
37  import net.sf.json.JSONFunction;
38  import net.sf.json.JSONNull;
39  import net.sf.json.JSONObject;
40  import net.sf.json.util.JSONUtils;
41  import nu.xom.Attribute;
42  import nu.xom.Builder;
43  import nu.xom.Document;
44  import nu.xom.Element;
45  import nu.xom.Elements;
46  import nu.xom.Node;
47  import nu.xom.Serializer;
48  import nu.xom.Text;
49  
50  import org.apache.commons.lang.ArrayUtils;
51  import org.apache.commons.lang.StringUtils;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  
55  /**
56   * Utility class for transforming JSON to XML an back.<br>
57   * When transforming JSONObject and JSONArray instances to XML, this class will
58   * add hints for converting back to JSON.<br>
59   * Examples:<br>
60   *
61   * <pre>
62   * JSONObject json = JSONObject.fromObject("{\"name\":\"json\",\"bool\":true,\"int\":1}");
63   * String xml = new XMLSerializer().write( json );
64   * <xmp><o class="object">
65   <name type="string">json</name>
66   <bool type="boolean">true</bool>
67   <int type="number">1</int>
68   </o></xmp>
69   * </pre><pre>
70   * JSONArray json = JSONArray.fromObject("[1,2,3]");
71   * String xml = new XMLSerializer().write( json );
72   * <xmp><a class="array">
73   <e type="number">1</e>
74   <e type="number">2</e>
75   <e type="number">3</e>
76   </a></xmp>
77   * </pre>
78   *
79   * @author Andres Almiray <aalmiray@users.sourceforge.net>
80   */
81  public class XMLSerializer {
82     private static final String[] EMPTY_ARRAY = new String[0];
83     private static final String JSON_PREFIX = "json_";
84     private static final Log log = LogFactory.getLog( XMLSerializer.class );
85  
86     /** the name for an JSONArray Element */
87     private String arrayName;
88     /** the name for an JSONArray's element Element */
89     private String elementName;
90     /** list of properties to be expanded from child to parent */
91     private String[] expandableProperties;
92     private boolean forceTopLevelObject;
93     /** flag to be tolerant for incomplete namespace prefixes */
94     private boolean namespaceLenient;
95     /** Map of namespaces per element */
96     private Map namespacesPerElement = new TreeMap();
97     /** the name for an JSONObject Element */
98     private String objectName;
99     /** flag for trimming namespace prefix from element name */
100    private boolean removeNamespacePrefixFromElements;
101    /** the name for the root Element */
102    private String rootName;
103    /** Map of namespaces for root element */
104    private Map rootNamespace = new TreeMap();
105    /** flag for skipping namespaces while reading */
106    private boolean skipNamespaces;
107    /** flag for skipping whitespace elements while reading */
108    private boolean skipWhitespace;
109    /** flag for trimming spaces from string values */
110    private boolean trimSpaces;
111    /** flag for type hints naming compatibility */
112    private boolean typeHintsCompatibility;
113    /** flag for adding JSON types hints as attributes */
114    private boolean typeHintsEnabled;
115 
116    /**
117     * Creates a new XMLSerializer with default options.<br>
118     * <ul>
119     * <li><code>objectName</code>: 'o'</li>
120     * <li><code>arrayName</code>: 'a'</li>
121     * <li><code>elementName</code>: 'e'</li>
122     * <li><code>typeHinstEnabled</code>: true</li>
123     * <li><code>typeHinstCompatibility</code>: true</li>
124     * <li><code>namespaceLenient</code>: false</li>
125     * <li><code>expandableProperties</code>: []</li>
126     * <li><code>skipNamespaces</code>: false</li>
127     * <li><code>removeNameSpacePrefixFromElement</code>: false</li>
128     * <li><code>trimSpaces</code>: false</li>
129     * </ul>
130     */
131    public XMLSerializer() {
132       setObjectName( "o" );
133       setArrayName( "a" );
134       setElementName( "e" );
135       setTypeHintsEnabled( true );
136       setTypeHintsCompatibility( true );
137       setNamespaceLenient( false );
138       setSkipNamespaces( false );
139       setRemoveNamespacePrefixFromElements( false );
140       setTrimSpaces( false );
141       setExpandableProperties( EMPTY_ARRAY );
142       setSkipNamespaces( false );
143    }
144 
145    /**
146     * Adds a namespace declaration to the root element.
147     *
148     * @param prefix namespace prefix
149     * @param uri namespace uri
150     */
151    public void addNamespace( String prefix, String uri ) {
152       addNamespace( prefix, uri, null );
153    }
154 
155    /**
156     * Adds a namespace declaration to an element.<br>
157     * If the elementName param is null or blank, the namespace declaration will
158     * be added to the root element.
159     *
160     * @param prefix namespace prefix
161     * @param uri namespace uri
162     * @param elementName name of target element
163     */
164    public void addNamespace( String prefix, String uri, String elementName ) {
165       if( StringUtils.isBlank( uri ) ){
166          return;
167       }
168       if( prefix == null ){
169          prefix = "";
170       }
171       if( StringUtils.isBlank( elementName ) ){
172          rootNamespace.put( prefix.trim(), uri.trim() );
173       }else{
174          Map nameSpaces = (Map) namespacesPerElement.get( elementName );
175          if( nameSpaces == null ){
176             nameSpaces = new TreeMap();
177             namespacesPerElement.put( elementName, nameSpaces );
178          }
179          nameSpaces.put( prefix, uri );
180       }
181    }
182 
183    /**
184     * Removes all namespaces declarations (from root an elements).
185     */
186    public void clearNamespaces() {
187       rootNamespace.clear();
188       namespacesPerElement.clear();
189    }
190 
191    /**
192     * Removes all namespace declarations from an element.<br>
193     * If the elementName param is null or blank, the declarations will be
194     * removed from the root element.
195     *
196     * @param elementName name of target element
197     */
198    public void clearNamespaces( String elementName ) {
199       if( StringUtils.isBlank( elementName ) ){
200          rootNamespace.clear();
201       }else{
202          namespacesPerElement.remove( elementName );
203       }
204    }
205 
206    /**
207     * Returns the name used for JSONArray.
208     */
209    public String getArrayName() {
210       return arrayName;
211    }
212 
213    /**
214     * Returns the name used for JSONArray elements.
215     */
216    public String getElementName() {
217       return elementName;
218    }
219 
220    /**
221     * Returns a list of properties to be expanded from child to parent.
222     */
223    public String[] getExpandableProperties() {
224       return expandableProperties;
225    }
226 
227    /**
228     * Returns the name used for JSONArray.
229     */
230    public String getObjectName() {
231       return objectName;
232    }
233 
234    /**
235     * Returns the name used for the root element.
236     */
237    public String getRootName() {
238       return rootName;
239    }
240 
241    public boolean isForceTopLevelObject() {
242       return forceTopLevelObject;
243    }
244 
245    /**
246     * Returns wether this serializer is tolerant to namespaces without URIs or
247     * not.
248     */
249    public boolean isNamespaceLenient() {
250       return namespaceLenient;
251    }
252 
253    /**
254     * Returns wether this serializer will remove namespace prefix from elements
255     * or not.
256     */
257    public boolean isRemoveNamespacePrefixFromElements() {
258       return removeNamespacePrefixFromElements;
259    }
260 
261    /**
262     * Returns wether this serializer will skip adding namespace declarations to
263     * elements or not.
264     */
265    public boolean isSkipNamespaces() {
266       return skipNamespaces;
267    }
268 
269    /**
270     * Returns wether this serializer will skip whitespace or not.
271     */
272    public boolean isSkipWhitespace() {
273       return skipWhitespace;
274    }
275 
276    /**
277     * Returns wether this serializer will trim leading and trealing whitespace
278     * from values or not.
279     */
280    public boolean isTrimSpaces() {
281       return trimSpaces;
282    }
283 
284    /**
285     * Returns true if types hints will have a 'json_' prefix or not.
286     */
287    public boolean isTypeHintsCompatibility() {
288       return typeHintsCompatibility;
289    }
290 
291    /**
292     * Returns true if JSON types will be included as attributes.
293     */
294    public boolean isTypeHintsEnabled() {
295       return typeHintsEnabled;
296    }
297 
298    /**
299     * Creates a JSON value from a XML string.
300     *
301     * @param xml A well-formed xml document in a String
302     * @return a JSONNull, JSONObject or JSONArray
303     * @throws JSONException if the conversion from XML to JSON can't be made for
304     *         I/O or format reasons.
305     */
306    public JSON read( String xml ) {
307       JSON json = null;
308       try{
309          Document doc = new Builder().build( new StringReader( xml ) );
310          Element root = doc.getRootElement();
311          if( isNullObject( root ) ){
312             return JSONNull.getInstance();
313          }
314          String defaultType = getType( root, JSONTypes.STRING );
315          if( isArray( root, true ) ){
316             json = processArrayElement( root, defaultType );
317             if( forceTopLevelObject ){
318                String key = removeNamespacePrefix( root.getQualifiedName() );
319                json = new JSONObject().element( key, json );
320             }
321          }else{ 
322             json = processObjectElement( root, defaultType );
323             if( forceTopLevelObject ) {
324                String key = removeNamespacePrefix( root.getQualifiedName() );
325                json = new JSONObject().element( key, json );
326             }
327          }
328       }catch( JSONException jsone ){
329          throw jsone;
330       }catch( Exception e ){
331          throw new JSONException( e );
332       }
333       return json;
334    }
335 
336    /**
337     * Creates a JSON value from a File.
338     *
339     * @param file
340     * @return a JSONNull, JSONObject or JSONArray
341     * @throws JSONException if the conversion from XML to JSON can't be made for
342     *         I/O or format reasons.
343     */
344    public JSON readFromFile( File file ) {
345       if( file == null ){
346          throw new JSONException( "File is null" );
347       }
348       if( !file.canRead() ){
349          throw new JSONException( "Can't read input file" );
350       }
351       if( file.isDirectory() ){
352          throw new JSONException( "File is a directory" );
353       }
354       try{
355          return readFromStream( new FileInputStream( file ) );
356       }catch( IOException ioe ){
357          throw new JSONException( ioe );
358       }
359    }
360 
361    /**
362     * Creates a JSON value from a File.
363     *
364     * @param path
365     * @return a JSONNull, JSONObject or JSONArray
366     * @throws JSONException if the conversion from XML to JSON can't be made for
367     *         I/O or format reasons.
368     */
369    public JSON readFromFile( String path ) {
370       return readFromStream( Thread.currentThread()
371             .getContextClassLoader()
372             .getResourceAsStream( path ) );
373    }
374 
375    /**
376     * Creates a JSON value from an input stream.
377     *
378     * @param stream
379     * @return a JSONNull, JSONObject or JSONArray
380     * @throws JSONException if the conversion from XML to JSON can't be made for
381     *         I/O or format reasons.
382     */
383    public JSON readFromStream( InputStream stream ) {
384       try{
385          StringBuffer xml = new StringBuffer();
386          BufferedReader in = new BufferedReader( new InputStreamReader( stream ) );
387          String line = null;
388          while( (line = in.readLine()) != null ){
389             xml.append( line );
390          }
391          return read( xml.toString() );
392       }catch( IOException ioe ){
393          throw new JSONException( ioe );
394       }
395    }
396 
397    /**
398     * Removes a namespace from the root element.
399     *
400     * @param prefix namespace prefix
401     */
402    public void removeNamespace( String prefix ) {
403       removeNamespace( prefix, null );
404    }
405 
406    /**
407     * Removes a namespace from the root element.<br>
408     * If the elementName is null or blank, the namespace will be removed from
409     * the root element.
410     *
411     * @param prefix namespace prefix
412     * @param elementName name of target element
413     */
414    public void removeNamespace( String prefix, String elementName ) {
415       if( prefix == null ){
416          prefix = "";
417       }
418       if( StringUtils.isBlank( elementName ) ){
419          rootNamespace.remove( prefix.trim() );
420       }else{
421          Map nameSpaces = (Map) namespacesPerElement.get( elementName );
422          nameSpaces.remove( prefix );
423       }
424    }
425 
426    /**
427     * Sets the name used for JSONArray.<br>
428     * Default is 'a'.
429     */
430    public void setArrayName( String arrayName ) {
431       this.arrayName = StringUtils.isBlank( arrayName ) ? "a" : arrayName;
432    }
433 
434    /**
435     * Sets the name used for JSONArray elements.<br>
436     * Default is 'e'.
437     */
438    public void setElementName( String elementName ) {
439       this.elementName = StringUtils.isBlank( elementName ) ? "e" : elementName;
440    }
441 
442    /**
443     * Sets the list of properties to be expanded from child to parent.
444     */
445    public void setExpandableProperties( String[] expandableProperties ) {
446       this.expandableProperties = expandableProperties == null ? EMPTY_ARRAY : expandableProperties;
447    }
448 
449    public void setForceTopLevelObject( boolean forceTopLevelObject ) {
450       this.forceTopLevelObject = forceTopLevelObject;
451    }
452 
453    /**
454     * Sets the namespace declaration to the root element.<br>
455     * Any previous values are discarded.
456     *
457     * @param prefix namespace prefix
458     * @param uri namespace uri
459     */
460    public void setNamespace( String prefix, String uri ) {
461       setNamespace( prefix, uri, null );
462    }
463 
464    /**
465     * Adds a namespace declaration to an element.<br>
466     * Any previous values are discarded. If the elementName param is null or
467     * blank, the namespace declaration will be added to the root element.
468     *
469     * @param prefix namespace prefix
470     * @param uri namespace uri
471     * @param elementName name of target element
472     */
473    public void setNamespace( String prefix, String uri, String elementName ) {
474       if( StringUtils.isBlank( uri ) ){
475          return;
476       }
477       if( prefix == null ){
478          prefix = "";
479       }
480       if( StringUtils.isBlank( elementName ) ){
481          rootNamespace.clear();
482          rootNamespace.put( prefix.trim(), uri.trim() );
483       }else{
484          Map nameSpaces = (Map) namespacesPerElement.get( elementName );
485          if( nameSpaces == null ){
486             nameSpaces = new TreeMap();
487             namespacesPerElement.put( elementName, nameSpaces );
488          }
489          nameSpaces.clear();
490          nameSpaces.put( prefix, uri );
491       }
492    }
493 
494    /**
495     * Sets wether this serializer is tolerant to namespaces without URIs or not.
496     */
497    public void setNamespaceLenient( boolean namespaceLenient ) {
498       this.namespaceLenient = namespaceLenient;
499    }
500 
501    /**
502     * Sets the name used for JSONObject.<br>
503     * Default is 'o'.
504     */
505    public void setObjectName( String objectName ) {
506       this.objectName = StringUtils.isBlank( objectName ) ? "o" : objectName;
507    }
508 
509    /**
510     * Sets if this serializer will remove namespace prefix from elements when
511     * reading.
512     */
513    public void setRemoveNamespacePrefixFromElements( boolean removeNamespacePrefixFromElements ) {
514       this.removeNamespacePrefixFromElements = removeNamespacePrefixFromElements;
515    }
516 
517    /**
518     * Sets the name used for the root element.
519     */
520    public void setRootName( String rootName ) {
521       this.rootName = StringUtils.isBlank( rootName ) ? null : rootName;
522    }
523 
524    /**
525     * Sets if this serializer will skip adding namespace declarations to
526     * elements when reading.
527     */
528    public void setSkipNamespaces( boolean skipNamespaces ) {
529       this.skipNamespaces = skipNamespaces;
530    }
531 
532    /**
533     * Sets if this serializer will skip whitespace when reading.
534     */
535    public void setSkipWhitespace( boolean skipWhitespace ) {
536       this.skipWhitespace = skipWhitespace;
537    }
538 
539    /**
540     * Sets if this serializer will trim leading and trealing whitespace from
541     * values when reading.
542     */
543    public void setTrimSpaces( boolean trimSpaces ) {
544       this.trimSpaces = trimSpaces;
545    }
546 
547    /**
548     * Sets wether types hints will have a 'json_' prefix or not.
549     */
550    public void setTypeHintsCompatibility( boolean typeHintsCompatibility ) {
551       this.typeHintsCompatibility = typeHintsCompatibility;
552    }
553 
554    /**
555     * Sets wether JSON types will be included as attributes.
556     */
557    public void setTypeHintsEnabled( boolean typeHintsEnabled ) {
558       this.typeHintsEnabled = typeHintsEnabled;
559    }
560 
561    /**
562     * Writes a JSON value into a XML string with UTF-8 encoding.<br>
563     *
564     * @param json The JSON value to transform
565     * @return a String representation of a well-formed xml document.
566     * @throws JSONException if the conversion from JSON to XML can't be made for
567     *         I/O reasons.
568     */
569    public String write( JSON json ) {
570       return write( json, null );
571    }
572 
573    /**
574     * Writes a JSON value into a XML string with an specific encoding.<br>
575     * If the encoding string is null it will use UTF-8.
576     *
577     * @param json The JSON value to transform
578     * @param encoding The xml encoding to use
579     * @return a String representation of a well-formed xml document.
580     * @throws JSONException if the conversion from JSON to XML can't be made for
581     *         I/O reasons or the encoding is not supported.
582     */
583    public String write( JSON json, String encoding ) {
584       if( JSONNull.getInstance()
585             .equals( json ) ){
586          Element root = null;
587          root = newElement( getRootName() == null ? getObjectName() : getRootName() );
588          root.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
589          Document doc = new Document( root );
590          return writeDocument( doc, encoding );
591       }else if( json instanceof JSONArray ){
592          JSONArray jsonArray = (JSONArray) json;
593          Element root = processJSONArray( jsonArray,
594                newElement( getRootName() == null ? getArrayName() : getRootName() ),
595                expandableProperties );
596          Document doc = new Document( root );
597          return writeDocument( doc, encoding );
598       }else{
599          JSONObject jsonObject = (JSONObject) json;
600          Element root = null;
601          if( jsonObject.isNullObject() ){
602             root = newElement( getObjectName() );
603             root.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
604          }else{
605             root = processJSONObject( jsonObject,
606                   newElement( getRootName() == null ? getObjectName() : getRootName() ),
607                   expandableProperties, true );
608          }
609          Document doc = new Document( root );
610          return writeDocument( doc, encoding );
611       }
612    }
613 
614    private String addJsonPrefix( String str ) {
615       if( !isTypeHintsCompatibility() ){
616          return JSON_PREFIX + str;
617       }
618       return str;
619    }
620 
621    private void addNameSpaceToElement( Element element ) {
622       String elementName = null;
623       if( element instanceof CustomElement ){
624          elementName = ((CustomElement) element).getQName();
625       }else{
626          elementName = element.getQualifiedName();
627       }
628       Map nameSpaces = (Map) namespacesPerElement.get( elementName );
629       if( nameSpaces != null && !nameSpaces.isEmpty() ){
630          setNamespaceLenient( true );
631          for( Iterator entries = nameSpaces.entrySet()
632                .iterator(); entries.hasNext(); ){
633             Map.Entry entry = (Map.Entry) entries.next();
634             String prefix = (String) entry.getKey();
635             String uri = (String) entry.getValue();
636             if( StringUtils.isBlank( prefix ) ){
637                element.setNamespaceURI( uri );
638             }else{
639                element.addNamespaceDeclaration( prefix, uri );
640             }
641          }
642       }
643    }
644 
645    private boolean checkChildElements( Element element, boolean isTopLevel ) {
646       int childCount = element.getChildCount();
647       Elements elements = element.getChildElements();
648       int elementCount = elements.size();
649 
650       if( childCount == 1 && element.getChild( 0 ) instanceof Text ){
651          return isTopLevel;
652       }
653 
654       if( childCount == elementCount ){
655          if( elementCount == 0 ){
656             return true;
657          }
658          if( elementCount == 1 ){
659             if( skipWhitespace || element.getChild( 0 ) instanceof Text ){
660                return true;
661             }else{
662                return false;
663             }
664          }
665       }
666 
667       if( childCount > elementCount ){
668          for( int i = 0; i < childCount; i++ ){
669             Node node = element.getChild( i );
670             if( node instanceof Text ){
671                Text text = (Text) node;
672                if( StringUtils.isNotBlank( StringUtils.strip( text.getValue() ) )
673                      && !skipWhitespace ){
674                   return false;
675                }
676             }
677          }
678       }
679 
680       String childName = elements.get( 0 )
681             .getQualifiedName();
682       for( int i = 1; i < elementCount; i++ ){
683          if( childName.compareTo( elements.get( i )
684                .getQualifiedName() ) != 0 ){
685             return false;
686          }
687       }
688 
689       return true;
690    }
691 
692    private String getClass( Element element ) {
693       Attribute attribute = element.getAttribute( addJsonPrefix( "class" ) );
694       String clazz = null;
695       if( attribute != null ){
696          String clazzText = attribute.getValue()
697                .trim();
698          if( JSONTypes.OBJECT.compareToIgnoreCase( clazzText ) == 0 ){
699             clazz = JSONTypes.OBJECT;
700          }else if( JSONTypes.ARRAY.compareToIgnoreCase( clazzText ) == 0 ){
701             clazz = JSONTypes.ARRAY;
702          }
703       }
704       return clazz;
705    }
706 
707    private String getType( Element element ) {
708       return getType( element, null );
709    }
710 
711    private String getType( Element element, String defaultType ) {
712       Attribute attribute = element.getAttribute( addJsonPrefix( "type" ) );
713       String type = null;
714       if( attribute != null ){
715          String typeText = attribute.getValue()
716                .trim();
717          if( JSONTypes.BOOLEAN.compareToIgnoreCase( typeText ) == 0 ){
718             type = JSONTypes.BOOLEAN;
719          }else if( JSONTypes.NUMBER.compareToIgnoreCase( typeText ) == 0 ){
720             type = JSONTypes.NUMBER;
721          }else if( JSONTypes.INTEGER.compareToIgnoreCase( typeText ) == 0 ){
722             type = JSONTypes.INTEGER;
723          }else if( JSONTypes.FLOAT.compareToIgnoreCase( typeText ) == 0 ){
724             type = JSONTypes.FLOAT;
725          }else if( JSONTypes.OBJECT.compareToIgnoreCase( typeText ) == 0 ){
726             type = JSONTypes.OBJECT;
727          }else if( JSONTypes.ARRAY.compareToIgnoreCase( typeText ) == 0 ){
728             type = JSONTypes.ARRAY;
729          }else if( JSONTypes.STRING.compareToIgnoreCase( typeText ) == 0 ){
730             type = JSONTypes.STRING;
731          }else if( JSONTypes.FUNCTION.compareToIgnoreCase( typeText ) == 0 ){
732             type = JSONTypes.FUNCTION;
733          }
734       }else{
735          if( defaultType != null ){
736             log.info( "Using default type " + defaultType );
737             type = defaultType;
738          }
739       }
740       return type;
741    }
742 
743    private boolean hasNamespaces( Element element ) {
744       int namespaces = 0;
745       for( int i = 0; i < element.getNamespaceDeclarationCount(); i++ ){
746          String prefix = element.getNamespacePrefix( i );
747          String uri = element.getNamespaceURI( prefix );
748          if( StringUtils.isBlank( uri ) ){
749             continue;
750          }
751          namespaces++;
752       }
753       return namespaces > 0;
754    }
755 
756    private boolean isArray( Element element, boolean isTopLevel ) {
757       boolean isArray = false;
758       String clazz = getClass( element );
759       if( clazz != null && clazz.equals( JSONTypes.ARRAY ) ){
760          isArray = true;
761       }else if( element.getAttributeCount() == 0 ){
762          isArray = checkChildElements( element, isTopLevel );
763       }else if( element.getAttributeCount() == 1
764             && (element.getAttribute( addJsonPrefix( "class" ) ) != null || element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
765          isArray = checkChildElements( element, isTopLevel );
766       }else if( element.getAttributeCount() == 2
767             && (element.getAttribute( addJsonPrefix( "class" ) ) != null && element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
768          isArray = checkChildElements( element, isTopLevel );
769       }
770 
771       if( isArray ){
772          // check namespace
773          for( int j = 0; j < element.getNamespaceDeclarationCount(); j++ ){
774             String prefix = element.getNamespacePrefix( j );
775             String uri = element.getNamespaceURI( prefix );
776             if( !StringUtils.isBlank( uri ) ){
777                return false;
778             }
779          }
780       }
781 
782       return isArray;
783    }
784 
785    private boolean isFunction( Element element ) {
786       int attrCount = element.getAttributeCount();
787       if( attrCount > 0 ){
788          Attribute typeAttr = element.getAttribute( addJsonPrefix( "type" ) );
789          Attribute paramsAttr = element.getAttribute( addJsonPrefix( "params" ) );
790          if( attrCount == 1 && paramsAttr != null ){
791             return true;
792          }
793          if( attrCount == 2 && paramsAttr != null && typeAttr != null && (typeAttr.getValue()
794                .compareToIgnoreCase( JSONTypes.STRING ) == 0 || typeAttr.getValue()
795                .compareToIgnoreCase( JSONTypes.FUNCTION ) == 0) ){
796             return true;
797          }
798       }
799       return false;
800    }
801 
802    private boolean isNullObject( Element element ) {
803       if( element.getChildCount() == 0 ){
804          if( element.getAttributeCount() == 0 ){
805             return true;
806          }else if( element.getAttribute( addJsonPrefix( "null" ) ) != null ){
807             return true;
808          }else if( element.getAttributeCount() == 1
809                && (element.getAttribute( addJsonPrefix( "class" ) ) != null || element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
810             return true;
811          }else if( element.getAttributeCount() == 2
812                && (element.getAttribute( addJsonPrefix( "class" ) ) != null && element.getAttribute( addJsonPrefix( "type" ) ) != null) ){
813             return true;
814          }
815       }
816       if( skipWhitespace && element.getChildCount() == 1 && element.getChild( 0 ) instanceof Text ){
817          return true;
818       }
819       return false;
820    }
821 
822    private boolean isObject( Element element, boolean isTopLevel ) {
823       boolean isObject = false;
824       if( !isArray( element, isTopLevel ) && !isFunction( element ) ){
825          if( hasNamespaces( element ) ){
826             return true;
827          }
828 
829          int attributeCount = element.getAttributeCount();
830          if( attributeCount > 0 ){
831             int attrs = element.getAttribute( addJsonPrefix( "null" )) == null ? 0 : 1;
832             attrs += element.getAttribute( addJsonPrefix( "class" )) == null ? 0: 1;
833             attrs += element.getAttribute( addJsonPrefix( "type" ))== null ? 0 : 1;
834             switch( attributeCount ){
835                case 1:
836                   if( attrs == 0){
837                      return true;
838                   }
839                   break;
840                case 2:
841                   if( attrs < 2 ){
842                      return true;
843                   }
844                   break;
845                case 3: 
846                   if(  attrs < 3 ){
847                      return true;
848                   }
849                   break;
850                default:
851                   return true;
852             }
853          }
854          
855          int childCount = element.getChildCount();
856          if( childCount == 1 && element.getChild( 0 ) instanceof Text ){
857             return isTopLevel;
858          }
859 
860          isObject = true;
861       }
862       return isObject;
863    }
864 
865    private Element newElement( String name ) {
866       if( name.indexOf( ':' ) != -1 ){
867          namespaceLenient = true;
868       }
869       return namespaceLenient ? new CustomElement( name ) : new Element( name );
870    }
871 
872    private JSON processArrayElement( Element element, String defaultType ) {
873       JSONArray jsonArray = new JSONArray();
874       // process children (including text)
875       int childCount = element.getChildCount();
876       for( int i = 0; i < childCount; i++ ){
877          Node child = element.getChild( i );
878          if( child instanceof Text ){
879             Text text = (Text) child;
880             if( StringUtils.isNotBlank( StringUtils.strip( text.getValue() ) ) ){
881                jsonArray.element( text.getValue() );
882             }
883          }else if( child instanceof Element ){
884             setValue( jsonArray, (Element) child, defaultType );
885          }
886       }
887       return jsonArray;
888    }
889 
890    private Object processElement( Element element, String type ) {
891       if( isNullObject( element ) ){
892          return JSONNull.getInstance();
893       }else if( isArray( element, false ) ){
894          return processArrayElement( element, type );
895       }else if( isObject( element, false ) ){
896          return processObjectElement( element, type );
897       }else{
898          return trimSpaceFromValue( element.getValue() );
899       }
900    }
901 
902    private Element processJSONArray( JSONArray array, Element root, String[] expandableProperties ) {
903       int l = array.size();
904       for( int i = 0; i < l; i++ ){
905          Object value = array.get( i );
906          Element element = processJSONValue( value, root, null, expandableProperties );
907          root.appendChild( element );
908       }
909       return root;
910    }
911 
912    private Element processJSONObject( JSONObject jsonObject, Element root,
913          String[] expandableProperties, boolean isRoot ) {
914       if( jsonObject.isNullObject() ){
915          root.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
916          return root;
917       }else if( jsonObject.isEmpty() ){
918          return root;
919       }
920 
921       if( isRoot ){
922          if( !rootNamespace.isEmpty() ){
923             setNamespaceLenient( true );
924             for( Iterator entries = rootNamespace.entrySet()
925                   .iterator(); entries.hasNext(); ){
926                Map.Entry entry = (Map.Entry) entries.next();
927                String prefix = (String) entry.getKey();
928                String uri = (String) entry.getValue();
929                if( StringUtils.isBlank( prefix ) ){
930                   root.setNamespaceURI( uri );
931                }else{
932                   root.addNamespaceDeclaration( prefix, uri );
933                }
934             }
935          }
936       }
937 
938       addNameSpaceToElement( root );
939 
940       Object[] names = jsonObject.names()
941             .toArray();
942       Arrays.sort( names );
943       Element element = null;
944       for( int i = 0; i < names.length; i++ ){
945          String name = (String) names[i];
946          Object value = jsonObject.get( name );
947          if( name.startsWith( "@xmlns" ) ){
948             setNamespaceLenient( true );
949             int colon = name.indexOf( ':' );
950             if( colon == -1 ){
951                // do not override if already defined by nameSpaceMaps
952                if( StringUtils.isBlank( root.getNamespaceURI() ) ){
953                   root.setNamespaceURI( String.valueOf( value ) );
954                }
955             }else{
956                String prefix = name.substring( colon + 1 );
957                if( StringUtils.isBlank( root.getNamespaceURI( prefix ) ) ){
958                   root.addNamespaceDeclaration( prefix, String.valueOf( value ) );
959                }
960             }
961          }else if( name.startsWith( "@" ) ){
962             root.addAttribute( new Attribute( name.substring( 1 ), String.valueOf( value ) ) );
963          }else if( name.equals( "#text" ) ){
964             if( value instanceof JSONArray ){
965                root.appendChild( ((JSONArray) value).join( "", true ) );
966             }else{
967                root.appendChild( String.valueOf( value ) );
968             }
969          }else if( value instanceof JSONArray
970                && (((JSONArray) value).isExpandElements() || ArrayUtils.contains(
971                      expandableProperties, name )) ){
972             JSONArray array = (JSONArray) value;
973             int l = array.size();
974             for( int j = 0; j < l; j++ ){
975                Object item = array.get( j );
976                element = newElement( name );
977                if( item instanceof JSONObject ){
978                   element = processJSONValue( (JSONObject) item, root, element,
979                         expandableProperties );
980                }else if( item instanceof JSONArray ){
981                   element = processJSONValue( (JSONArray) item, root, element, expandableProperties );
982                }else{
983                   element = processJSONValue( item, root, element, expandableProperties );
984                }
985                addNameSpaceToElement( element );
986                root.appendChild( element );
987             }
988          }else{
989             element = newElement( name );
990             element = processJSONValue( value, root, element, expandableProperties );
991             addNameSpaceToElement( element );
992             root.appendChild( element );
993          }
994       }
995       return root;
996    }
997 
998    private Element processJSONValue( Object value, Element root, Element target,
999          String[] expandableProperties ) {
1000       if( target == null ){
1001          target = newElement( getElementName() );
1002       }
1003       if( JSONUtils.isBoolean( value ) ){
1004          if( isTypeHintsEnabled() ){
1005             target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.BOOLEAN ) );
1006          }
1007          target.appendChild( value.toString() );
1008       }else if( JSONUtils.isNumber( value ) ){
1009          if( isTypeHintsEnabled() ){
1010             target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.NUMBER ) );
1011          }
1012          target.appendChild( value.toString() );
1013       }else if( JSONUtils.isFunction( value ) ){
1014          if( value instanceof String ){
1015             value = JSONFunction.parse( (String) value );
1016          }
1017          JSONFunction func = (JSONFunction) value;
1018          if( isTypeHintsEnabled() ){
1019             target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.FUNCTION ) );
1020          }
1021          String params = ArrayUtils.toString( func.getParams() );
1022          params = params.substring( 1 );
1023          params = params.substring( 0, params.length() - 1 );
1024          target.addAttribute( new Attribute( addJsonPrefix( "params" ), params ) );
1025          target.appendChild( new Text( "<![CDATA[" + func.getText() + "]]>" ) );
1026       }else if( JSONUtils.isString( value ) ){
1027          if( isTypeHintsEnabled() ){
1028             target.addAttribute( new Attribute( addJsonPrefix( "type" ), JSONTypes.STRING ) );
1029          }
1030          target.appendChild( value.toString() );
1031       }else if( value instanceof JSONArray ){
1032          if( isTypeHintsEnabled() ){
1033             target.addAttribute( new Attribute( addJsonPrefix( "class" ), JSONTypes.ARRAY ) );
1034          }
1035          target = processJSONArray( (JSONArray) value, target, expandableProperties );
1036       }else if( value instanceof JSONObject ){
1037          if( isTypeHintsEnabled() ){
1038             target.addAttribute( new Attribute( addJsonPrefix( "class" ), JSONTypes.OBJECT ) );
1039          }
1040          target = processJSONObject( (JSONObject) value, target, expandableProperties, false );
1041       }else if( JSONUtils.isNull( value ) ){
1042          if( isTypeHintsEnabled() ){
1043             target.addAttribute( new Attribute( addJsonPrefix( "class" ), JSONTypes.OBJECT ) );
1044          }
1045          target.addAttribute( new Attribute( addJsonPrefix( "null" ), "true" ) );
1046       }
1047       return target;
1048    }
1049 
1050    private JSON processObjectElement( Element element, String defaultType ) {
1051       if( isNullObject( element ) ){
1052          return JSONNull.getInstance();
1053       }
1054       JSONObject jsonObject = new JSONObject();
1055 
1056       if( !skipNamespaces ){
1057          for( int j = 0; j < element.getNamespaceDeclarationCount(); j++ ){
1058             String prefix = element.getNamespacePrefix( j );
1059             String uri = element.getNamespaceURI( prefix );
1060             if( StringUtils.isBlank( uri ) ){
1061                continue;
1062             }
1063             if( !StringUtils.isBlank( prefix ) ){
1064                prefix = ":" + prefix;
1065             }
1066             setOrAccumulate( jsonObject, "@xmlns" + prefix, trimSpaceFromValue( uri ) );
1067          }
1068       }
1069      
1070       // process attributes first
1071       int attrCount = element.getAttributeCount();
1072       for( int i = 0; i < attrCount; i++ ){
1073          Attribute attr = element.getAttribute( i );
1074          String attrname = attr.getQualifiedName();
1075          if( isTypeHintsEnabled()
1076                && (addJsonPrefix( "class" ).compareToIgnoreCase( attrname ) == 0 || addJsonPrefix(
1077                      "type" ).compareToIgnoreCase( attrname ) == 0) ){
1078             continue;
1079          }
1080          String attrvalue = attr.getValue();
1081          setOrAccumulate( jsonObject, "@" + removeNamespacePrefix( attrname ),
1082                trimSpaceFromValue( attrvalue ) );
1083       }
1084 
1085       // process children (including text)
1086       int childCount = element.getChildCount();
1087       for( int i = 0; i < childCount; i++ ){
1088          Node child = element.getChild( i );
1089          if( child instanceof Text ){
1090             Text text = (Text) child;
1091             if( StringUtils.isNotBlank( StringUtils.strip( text.getValue() ) ) ){
1092                setOrAccumulate( jsonObject, "#text", trimSpaceFromValue( text.getValue() ) );
1093             }
1094          }else if( child instanceof Element ){
1095             setValue( jsonObject, (Element) child, defaultType );
1096          }
1097       }
1098 
1099       return jsonObject;
1100    }
1101 
1102    private String removeNamespacePrefix( String name ) {
1103       if( isRemoveNamespacePrefixFromElements() ){
1104          int colon = name.indexOf( ':' );
1105          return colon != -1 ? name.substring( colon + 1 ) : name;
1106       }
1107       return name;
1108    }
1109 
1110    private void setOrAccumulate( JSONObject jsonObject, String key, Object value ) {
1111       if( jsonObject.has( key ) ){
1112          jsonObject.accumulate( key, value );
1113          Object val = jsonObject.get( key );
1114          if( val instanceof JSONArray ){
1115             ((JSONArray) val).setExpandElements( true );
1116          }
1117       }else{
1118          jsonObject.element( key, value );
1119       }
1120    }
1121 
1122    private void setValue( JSONArray jsonArray, Element element, String defaultType ) {
1123       String clazz = getClass( element );
1124       String type = getType( element );
1125       type = (type == null) ? defaultType : type;
1126 
1127       if( hasNamespaces( element ) && !skipNamespaces ){
1128          jsonArray.element( simplifyValue( null, processElement( element, type ) ) );
1129          return;
1130       }else if( element.getAttributeCount() > 0 ){
1131          if( isFunction( element ) ){
1132             Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
1133             String[] params = null;
1134             String text = element.getValue();
1135             params = StringUtils.split( paramsAttribute.getValue(), "," );
1136             jsonArray.element( new JSONFunction( params, text ) );
1137             return;
1138          }else{
1139             jsonArray.element( simplifyValue( null, processElement( element, type ) ) );
1140             return;
1141          }
1142       }
1143 
1144       boolean classProcessed = false;
1145       if( clazz != null ){
1146          if( clazz.compareToIgnoreCase( JSONTypes.ARRAY ) == 0 ){
1147             jsonArray.element( processArrayElement( element, type ) );
1148             classProcessed = true;
1149          }else if( clazz.compareToIgnoreCase( JSONTypes.OBJECT ) == 0 ){
1150             jsonArray.element( simplifyValue( null, processObjectElement( element, type ) ) );
1151             classProcessed = true;
1152          }
1153       }
1154       if( !classProcessed ){
1155          if( type.compareToIgnoreCase( JSONTypes.BOOLEAN ) == 0 ){
1156             jsonArray.element( Boolean.valueOf( element.getValue() ) );
1157          }else if( type.compareToIgnoreCase( JSONTypes.NUMBER ) == 0 ){
1158             // try integer first
1159             try{
1160                jsonArray.element( Integer.valueOf( element.getValue() ) );
1161             }catch( NumberFormatException e ){
1162                jsonArray.element( Double.valueOf( element.getValue() ) );
1163             }
1164          }else if( type.compareToIgnoreCase( JSONTypes.INTEGER ) == 0 ){
1165             jsonArray.element( Integer.valueOf( element.getValue() ) );
1166          }else if( type.compareToIgnoreCase( JSONTypes.FLOAT ) == 0 ){
1167             jsonArray.element( Double.valueOf( element.getValue() ) );
1168          }else if( type.compareToIgnoreCase( JSONTypes.FUNCTION ) == 0 ){
1169             String[] params = null;
1170             String text = element.getValue();
1171             Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
1172             if( paramsAttribute != null ){
1173                params = StringUtils.split( paramsAttribute.getValue(), "," );
1174             }
1175             jsonArray.element( new JSONFunction( params, text ) );
1176          }else if( type.compareToIgnoreCase( JSONTypes.STRING ) == 0 ){
1177             // see if by any chance has a 'params' attribute
1178             Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
1179             if( paramsAttribute != null ){
1180                String[] params = null;
1181                String text = element.getValue();
1182                params = StringUtils.split( paramsAttribute.getValue(), "," );
1183                jsonArray.element( new JSONFunction( params, text ) );
1184             }else{
1185                if( isArray( element, false ) ){
1186                   jsonArray.element( processArrayElement( element, defaultType ) );
1187                }else if( isObject( element, false ) ){
1188                   jsonArray.element( simplifyValue( null, processObjectElement( element,
1189                         defaultType ) ) );
1190                }else{
1191                   jsonArray.element( trimSpaceFromValue( element.getValue() ) );
1192                }
1193             }
1194          }
1195       }
1196    }
1197 
1198    private void setValue( JSONObject jsonObject, Element element, String defaultType ) {
1199       String clazz = getClass( element );
1200       String type = getType( element );
1201       type = (type == null) ? defaultType : type;
1202   
1203       
1204       
1205       String key = removeNamespacePrefix( element.getQualifiedName() );
1206       if( hasNamespaces( element ) && !skipNamespaces ){
1207          setOrAccumulate( jsonObject, key, simplifyValue( jsonObject,
1208                processElement( element, type ) ) );
1209          return;
1210       }else if( element.getAttributeCount() > 0 ){
1211          if( isFunction( element ) ){
1212             Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
1213             String text = element.getValue();
1214             String[] params = StringUtils.split( paramsAttribute.getValue(), "," );
1215             setOrAccumulate( jsonObject, key, new JSONFunction( params, text ) );
1216             return;
1217          }/*else{
1218             setOrAccumulate( jsonObject, key, simplifyValue( jsonObject, processElement( element,
1219                   type ) ) );
1220             return;
1221          }*/
1222       }
1223 
1224       boolean classProcessed = false;
1225       if( clazz != null ){
1226          if( clazz.compareToIgnoreCase( JSONTypes.ARRAY ) == 0 ){
1227             setOrAccumulate( jsonObject, key, processArrayElement( element, type ) );
1228             classProcessed = true;
1229          }else if( clazz.compareToIgnoreCase( JSONTypes.OBJECT ) == 0 ){
1230             setOrAccumulate( jsonObject, key, simplifyValue( jsonObject, processObjectElement(
1231                   element, type ) ) );
1232             classProcessed = true;
1233          }
1234       }
1235       if( !classProcessed ){
1236          if( type.compareToIgnoreCase( JSONTypes.BOOLEAN ) == 0 ){
1237             setOrAccumulate( jsonObject, key, Boolean.valueOf( element.getValue() ) );
1238          }else if( type.compareToIgnoreCase( JSONTypes.NUMBER ) == 0 ){
1239             // try integer first
1240             try{
1241                setOrAccumulate( jsonObject, key, Integer.valueOf( element.getValue() ) );
1242             }catch( NumberFormatException e ){
1243                setOrAccumulate( jsonObject, key, Double.valueOf( element.getValue() ) );
1244             }
1245          }else if( type.compareToIgnoreCase( JSONTypes.INTEGER ) == 0 ){
1246             setOrAccumulate( jsonObject, key, Integer.valueOf( element.getValue() ) );
1247          }else if( type.compareToIgnoreCase( JSONTypes.FLOAT ) == 0 ){
1248             setOrAccumulate( jsonObject, key, Double.valueOf( element.getValue() ) );
1249          }else if( type.compareToIgnoreCase( JSONTypes.FUNCTION ) == 0 ){
1250             String[] params = null;
1251             String text = element.getValue();
1252             Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
1253             if( paramsAttribute != null ){
1254                params = StringUtils.split( paramsAttribute.getValue(), "," );
1255             }
1256             setOrAccumulate( jsonObject, key, new JSONFunction( params, text ) );
1257          }else if( type.compareToIgnoreCase( JSONTypes.STRING ) == 0 ){
1258             // see if by any chance has a 'params' attribute
1259             Attribute paramsAttribute = element.getAttribute( addJsonPrefix( "params" ) );
1260             if( paramsAttribute != null ){
1261                String[] params = null;
1262                String text = element.getValue();
1263                params = StringUtils.split( paramsAttribute.getValue(), "," );
1264                setOrAccumulate( jsonObject, key, new JSONFunction( params, text ) );
1265             }else{
1266                if( isArray( element, false ) ){
1267                   setOrAccumulate( jsonObject, key, processArrayElement( element, defaultType ) );
1268                }else if( isObject( element, false ) ){
1269                   setOrAccumulate( jsonObject, key, simplifyValue( jsonObject,
1270                         processObjectElement( element, defaultType ) ) );
1271                }else{
1272                   setOrAccumulate( jsonObject, key, trimSpaceFromValue( element.getValue() ) );
1273                }
1274             }
1275          }
1276       }
1277    }
1278 
1279    private Object simplifyValue( JSONObject parent, Object json ) {
1280       if( json instanceof JSONObject ){
1281          JSONObject object = (JSONObject) json;
1282          if( parent != null ){
1283             // remove all duplicated @xmlns from child
1284             for( Iterator entries = parent.entrySet()
1285                   .iterator(); entries.hasNext(); ){
1286                Map.Entry entry = (Map.Entry) entries.next();
1287                String key = (String) entry.getKey();
1288                Object value = entry.getValue();
1289                if( key.startsWith( "@xmlns" ) && value.equals( object.opt( key ) ) ){
1290                   object.remove( key );
1291                }
1292             }
1293          }
1294          if( object.size() == 1 && object.has( "#text" ) ){
1295             return object.get( "#text" );
1296          }
1297       }
1298       return json;
1299    }
1300    private String trimSpaceFromValue( String value ) {
1301       if( isTrimSpaces() ){
1302          return value.trim();
1303       }
1304       return value;
1305    }
1306    private String writeDocument( Document doc, String encoding ) {
1307       ByteArrayOutputStream baos = new ByteArrayOutputStream();
1308       try{
1309          XomSerializer serializer = (encoding == null) ? new XomSerializer( baos )
1310                : new XomSerializer( baos, encoding );
1311          serializer.write( doc );
1312          encoding = serializer.getEncoding();
1313       }catch( IOException ioe ){
1314          throw new JSONException( ioe );
1315       }
1316 
1317       String str = null;
1318       try{
1319          str = baos.toString( encoding );
1320       }catch( UnsupportedEncodingException uee ){
1321          throw new JSONException( uee );
1322       }
1323       return str;
1324    }
1325 
1326    private static class CustomElement extends Element {
1327       private static String getName( String name ) {
1328          int colon = name.indexOf( ':' );
1329          if( colon != -1 ){
1330             return name.substring( colon + 1 );
1331          }
1332          return name;
1333       }
1334 
1335       private static String getPrefix( String name ) {
1336          int colon = name.indexOf( ':' );
1337          if( colon != -1 ){
1338             return name.substring( 0, colon );
1339          }
1340          return "";
1341       }
1342 
1343       private String prefix;
1344 
1345       public CustomElement( String name ) {
1346          super( CustomElement.getName( name ) );
1347          prefix = CustomElement.getPrefix( name );
1348       }
1349 
1350       public final String getQName() {
1351          if( prefix.length() == 0 ){
1352             return getLocalName();
1353          }else{
1354             return prefix + ":" + getLocalName();
1355          }
1356       }
1357    }
1358 
1359    private class XomSerializer extends Serializer {
1360       public XomSerializer( OutputStream out ) {
1361          super( out );
1362       }
1363 
1364       public XomSerializer( OutputStream out, String encoding ) throws UnsupportedEncodingException {
1365          super( out, encoding );
1366       }
1367 
1368       protected void write( Text text ) throws IOException {
1369          String value = text.getValue();
1370          if( value.startsWith( "<![CDATA[" ) && value.endsWith( "]]>" ) ){
1371             value = value.substring( 9 );
1372             value = value.substring( 0, value.length() - 3 );
1373             writeRaw( "<![CDATA[" );
1374             writeRaw( value );
1375             writeRaw( "]]>" );
1376          }else{
1377             super.write( text );
1378          }
1379       }
1380 
1381       protected void writeEmptyElementTag( Element element ) throws IOException {
1382          if( element instanceof CustomElement && isNamespaceLenient() ){
1383             writeTagBeginning( (CustomElement) element );
1384             writeRaw( "/>" );
1385          }else{
1386             super.writeEmptyElementTag( element );
1387          }
1388       }
1389 
1390       protected void writeEndTag( Element element ) throws IOException {
1391          if( element instanceof CustomElement && isNamespaceLenient() ){
1392             writeRaw( "</" );
1393             writeRaw( ((CustomElement) element).getQName() );
1394             writeRaw( ">" );
1395          }else{
1396             super.writeEndTag( element );
1397          }
1398       }
1399 
1400       protected void writeNamespaceDeclaration( String prefix, String uri ) throws IOException {
1401          if( !StringUtils.isBlank( uri ) ){
1402             super.writeNamespaceDeclaration( prefix, uri );
1403          }
1404       }
1405 
1406       protected void writeStartTag( Element element ) throws IOException {
1407          if( element instanceof CustomElement && isNamespaceLenient() ){
1408             writeTagBeginning( (CustomElement) element );
1409             writeRaw( ">" );
1410          }else{
1411             super.writeStartTag( element );
1412          }
1413       }
1414 
1415       private void writeTagBeginning( CustomElement element ) throws IOException {
1416          writeRaw( "<" );
1417          writeRaw( element.getQName() );
1418          writeAttributes( element );
1419          writeNamespaceDeclarations( element );
1420       }
1421    }
1422 }