1 /*
2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3  */
4 /*
5  * Licensed to the Apache Software Foundation (ASF) under one or more
6  * contributor license agreements.  See the NOTICE file distributed with
7  * this work for additional information regarding copyright ownership.
8  * The ASF licenses this file to You under the Apache License, Version 2.0
9  * (the "License"); you may not use this file except in compliance with
10  * the License.  You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 
22 
23 // Sep 14, 2000:
24 //  Fixed problem with namespace handling. Contributed by
25 //  David Blondeau <blondeau@intalio.com>
26 // Sep 14, 2000:
27 //  Fixed serializer to report IO exception directly, instead at
28 //  the end of document processing.
29 //  Reported by Patrick Higgins <phiggins@transzap.com>
30 // Aug 21, 2000:
31 //  Fixed bug in startDocument not calling prepare.
32 //  Reported by Mikael Staldal <d96-mst-ingen-reklam@d.kth.se>
33 // Aug 21, 2000:
34 //  Added ability to omit DOCTYPE declaration.
35 
36 
37 package com.sun.org.apache.xml.internal.serialize;
38 
39 import com.sun.org.apache.xerces.internal.dom.DOMMessageFormatter;
40 import com.sun.org.apache.xerces.internal.util.NamespaceSupport;
41 import com.sun.org.apache.xerces.internal.util.SymbolTable;
42 import com.sun.org.apache.xerces.internal.util.XMLChar;
43 import com.sun.org.apache.xerces.internal.util.XMLSymbols;
44 import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
45 import java.io.IOException;
46 import java.io.OutputStream;
47 import java.io.Writer;
48 import java.util.Map;
49 import org.w3c.dom.Attr;
50 import org.w3c.dom.DOMError;
51 import org.w3c.dom.Element;
52 import org.w3c.dom.NamedNodeMap;
53 import org.w3c.dom.Node;
54 import org.w3c.dom.traversal.NodeFilter;
55 import org.xml.sax.AttributeList;
56 import org.xml.sax.Attributes;
57 import org.xml.sax.SAXException;
58 import org.xml.sax.helpers.AttributesImpl;
59 
60 /**
61  * Implements an XML serializer supporting both DOM and SAX pretty
62  * serializing. For usage instructions see {@link Serializer}.
63  * <p>
64  * If an output stream is used, the encoding is taken from the
65  * output format (defaults to <tt>UTF-8</tt>). If a writer is
66  * used, make sure the writer uses the same encoding (if applies)
67  * as specified in the output format.
68  * <p>
69  * The serializer supports both DOM and SAX. SAX serializing is done by firing
70  * SAX events and using the serializer as a document handler. DOM serializing is done
71  * by calling {@link #serialize(Document)} or by using DOM Level 3
72  * {@link org.w3c.dom.ls.DOMSerializer} and
73  * serializing with {@link org.w3c.dom.ls.DOMSerializer#write},
74  * {@link org.w3c.dom.ls.DOMSerializer#writeToString}.
75  * <p>
76  * If an I/O exception occurs while serializing, the serializer
77  * will not throw an exception directly, but only throw it
78  * at the end of serializing (either DOM or SAX's {@link
79  * org.xml.sax.DocumentHandler#endDocument}.
80  * <p>
81  * For elements that are not specified as whitespace preserving,
82  * the serializer will potentially break long text lines at space
83  * boundaries, indent lines, and serialize elements on separate
84  * lines. Line terminators will be regarded as spaces, and
85  * spaces at beginning of line will be stripped.
86  * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
87  * @author <a href="mailto:rahul.srivastava@sun.com">Rahul Srivastava</a>
88  * @author Elena Litani IBM
89  * @see Serializer
90  */
91 public class XMLSerializer
92 extends BaseMarkupSerializer {
93 
94     //
95     // constants
96     //
97 
98     protected static final boolean DEBUG = false;
99 
100     //
101     // data
102     //
103 
104     //
105     // DOM Level 3 implementation: variables intialized in DOMSerializerImpl
106     //
107 
108     /** stores namespaces in scope */
109     protected NamespaceSupport fNSBinder;
110 
111     /** stores all namespace bindings on the current element */
112     protected NamespaceSupport fLocalNSBinder;
113 
114     /** symbol table for serialization */
115     protected SymbolTable fSymbolTable;
116 
117     protected final static String PREFIX = "NS";
118 
119     /**
120      * Controls whether namespace fixup should be performed during
121      * the serialization.
122      * NOTE: if this field is set to true the following
123      * fields need to be initialized: fNSBinder, fLocalNSBinder, fSymbolTable,
124      * XMLSymbols.EMPTY_STRING, fXmlSymbol, fXmlnsSymbol
125      */
126     protected boolean fNamespaces = false;
127 
128     /**
129      * Controls whether namespace prefixes will be printed out during serialization
130      */
131     protected boolean fNamespacePrefixes = true;
132 
133 
134     private boolean fPreserveSpace;
135 
136 
137     /**
138      * Constructs a new serializer. The serializer cannot be used without
139      * calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
140      * first.
141      */
XMLSerializer()142     public XMLSerializer() {
143         super( new OutputFormat( Method.XML, null, false ) );
144     }
145 
146 
147     /**
148      * Constructs a new serializer. The serializer cannot be used without
149      * calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
150      * first.
151      */
XMLSerializer( OutputFormat format )152     public XMLSerializer( OutputFormat format ) {
153         super( format != null ? format : new OutputFormat( Method.XML, null, false ) );
154         _format.setMethod( Method.XML );
155     }
156 
157 
158     /**
159      * Constructs a new serializer that writes to the specified writer
160      * using the specified output format. If <tt>format</tt> is null,
161      * will use a default output format.
162      *
163      * @param writer The writer to use
164      * @param format The output format to use, null for the default
165      */
XMLSerializer( Writer writer, OutputFormat format )166     public XMLSerializer( Writer writer, OutputFormat format ) {
167         super( format != null ? format : new OutputFormat( Method.XML, null, false ) );
168         _format.setMethod( Method.XML );
169         setOutputCharStream( writer );
170     }
171 
172 
173     /**
174      * Constructs a new serializer that writes to the specified output
175      * stream using the specified output format. If <tt>format</tt>
176      * is null, will use a default output format.
177      *
178      * @param output The output stream to use
179      * @param format The output format to use, null for the default
180      */
XMLSerializer( OutputStream output, OutputFormat format )181     public XMLSerializer( OutputStream output, OutputFormat format ) {
182         super( format != null ? format : new OutputFormat( Method.XML, null, false ) );
183         _format.setMethod( Method.XML );
184         setOutputByteStream( output );
185     }
186 
187 
setOutputFormat( OutputFormat format )188     public void setOutputFormat( OutputFormat format ) {
189         super.setOutputFormat( format != null ? format : new OutputFormat( Method.XML, null, false ) );
190     }
191 
192 
193     /**
194      * This methods turns on namespace fixup algorithm during
195      * DOM serialization.
196      * @see org.w3c.dom.ls.DOMSerializer
197      *
198      * @param namespaces
199      */
setNamespaces(boolean namespaces)200     public void setNamespaces (boolean namespaces){
201         fNamespaces = namespaces;
202         if (fNSBinder == null) {
203             fNSBinder = new NamespaceSupport();
204             fLocalNSBinder = new NamespaceSupport();
205             fSymbolTable = new SymbolTable();
206         }
207     }
208 
209     //-----------------------------------------//
210     // SAX content handler serializing methods //
211     //-----------------------------------------//
212 
213 
startElement( String namespaceURI, String localName, String rawName, Attributes attrs )214     public void startElement( String namespaceURI, String localName,
215                               String rawName, Attributes attrs )
216     throws SAXException
217     {
218         int          i;
219         boolean      preserveSpace;
220         ElementState state;
221         String       name;
222         String       value;
223         boolean      addNSAttr = false;
224 
225         if (DEBUG) {
226             System.out.println("==>startElement("+namespaceURI+","+localName+
227                                ","+rawName+")");
228         }
229 
230         try {
231             if (_printer == null) {
232                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoWriterSupplied", null);
233                 throw new IllegalStateException(msg);
234             }
235 
236             state = getElementState();
237             if (isDocumentState()) {
238                 // If this is the root element handle it differently.
239                 // If the first root element in the document, serialize
240                 // the document's DOCTYPE. Space preserving defaults
241                 // to that of the output format.
242                 if (! _started)
243                     startDocument( ( localName == null || localName.length() == 0 ) ? rawName : localName );
244             } else {
245                 // For any other element, if first in parent, then
246                 // close parent's opening tag and use the parnet's
247                 // space preserving.
248                 if (state.empty)
249                     _printer.printText( '>' );
250                 // Must leave CData section first
251                 if (state.inCData) {
252                     _printer.printText( "]]>" );
253                     state.inCData = false;
254                 }
255                 // Indent this element on a new line if the first
256                 // content of the parent element or immediately
257                 // following an element or a comment
258                 if (_indenting && ! state.preserveSpace &&
259                     ( state.empty || state.afterElement || state.afterComment))
260                     _printer.breakLine();
261             }
262             preserveSpace = state.preserveSpace;
263 
264             //We remove the namespaces from the attributes list so that they will
265             //be in _prefixes
266             attrs = extractNamespaces(attrs);
267 
268             // Do not change the current element state yet.
269             // This only happens in endElement().
270             if (rawName == null || rawName.length() == 0) {
271                 if (localName == null) {
272                     String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoName", null);
273                     throw new SAXException(msg);
274                 }
275                 if (namespaceURI != null && ! namespaceURI.equals( "" )) {
276                     String prefix;
277                     prefix = getPrefix( namespaceURI );
278                     if (prefix != null && prefix.length() > 0)
279                         rawName = prefix + ":" + localName;
280                     else
281                         rawName = localName;
282                 } else
283                     rawName = localName;
284                 addNSAttr = true;
285             }
286 
287             _printer.printText( '<' );
288             _printer.printText( rawName );
289             _printer.indent();
290 
291             // For each attribute print it's name and value as one part,
292             // separated with a space so the element can be broken on
293             // multiple lines.
294             if (attrs != null) {
295                 for (i = 0 ; i < attrs.getLength() ; ++i) {
296                     _printer.printSpace();
297 
298                     name = attrs.getQName( i );
299                     if (name != null && name.length() == 0) {
300                         String prefix;
301                         String attrURI;
302 
303                         name = attrs.getLocalName( i );
304                         attrURI = attrs.getURI( i );
305                         if (( attrURI != null && attrURI.length() != 0 ) &&
306                             ( namespaceURI == null || namespaceURI.length() == 0 ||
307                               ! attrURI.equals( namespaceURI ) )) {
308                             prefix = getPrefix( attrURI );
309                             if (prefix != null && prefix.length() > 0)
310                                 name = prefix + ":" + name;
311                         }
312                     }
313 
314                     value = attrs.getValue( i );
315                     if (value == null)
316                         value = "";
317                     _printer.printText( name );
318                     _printer.printText( "=\"" );
319                     printEscaped( value );
320                     _printer.printText( '"' );
321 
322                     // If the attribute xml:space exists, determine whether
323                     // to preserve spaces in this and child nodes based on
324                     // its value.
325                     if (name.equals( "xml:space" )) {
326                         if (value.equals( "preserve" ))
327                             preserveSpace = true;
328                         else
329                             preserveSpace = _format.getPreserveSpace();
330                     }
331                 }
332             }
333 
334             if (_prefixes != null) {
335                 for (Map.Entry<String, String> entry : _prefixes.entrySet()) {
336                     _printer.printSpace();
337                     value = entry.getKey(); //The prefixes map uses the URI value as key.
338                     name = entry.getValue(); //and prefix name as value
339                     if (name.length() == 0) {
340                         _printer.printText( "xmlns=\"" );
341                         printEscaped( value );
342                         _printer.printText( '"' );
343                     } else {
344                         _printer.printText( "xmlns:" );
345                         _printer.printText( name );
346                         _printer.printText( "=\"" );
347                         printEscaped( value );
348                         _printer.printText( '"' );
349                     }
350                 }
351             }
352 
353             // Now it's time to enter a new element state
354             // with the tag name and space preserving.
355             // We still do not change the curent element state.
356             state = enterElementState( namespaceURI, localName, rawName, preserveSpace );
357             name = ( localName == null || localName.length() == 0 ) ? rawName : namespaceURI + "^" + localName;
358             state.doCData = _format.isCDataElement( name );
359             state.unescaped = _format.isNonEscapingElement( name );
360         } catch (IOException except) {
361             throw new SAXException( except );
362         }
363     }
364 
365 
endElement( String namespaceURI, String localName, String rawName )366     public void endElement( String namespaceURI, String localName,
367                             String rawName )
368     throws SAXException
369     {
370         try {
371             endElementIO( namespaceURI, localName, rawName );
372         } catch (IOException except) {
373             throw new SAXException( except );
374         }
375     }
376 
377 
endElementIO( String namespaceURI, String localName, String rawName )378     public void endElementIO( String namespaceURI, String localName,
379                               String rawName )
380     throws IOException
381     {
382         ElementState state;
383         if (DEBUG) {
384             System.out.println("==>endElement: " +rawName);
385         }
386         // Works much like content() with additions for closing
387         // an element. Note the different checks for the closed
388         // element's state and the parent element's state.
389         _printer.unindent();
390         state = getElementState();
391         if (state.empty) {
392             _printer.printText( "/>" );
393         } else {
394             // Must leave CData section first
395             if (state.inCData)
396                 _printer.printText( "]]>" );
397             // This element is not empty and that last content was
398             // another element, so print a line break before that
399             // last element and this element's closing tag.
400             if (_indenting && ! state.preserveSpace && (state.afterElement || state.afterComment))
401                 _printer.breakLine();
402             _printer.printText( "</" );
403             _printer.printText( state.rawName );
404             _printer.printText( '>' );
405         }
406         // Leave the element state and update that of the parent
407         // (if we're not root) to not empty and after element.
408         state = leaveElementState();
409         state.afterElement = true;
410         state.afterComment = false;
411         state.empty = false;
412         if (isDocumentState())
413             _printer.flush();
414     }
415 
416 
417     //------------------------------------------//
418     // SAX document handler serializing methods //
419     //------------------------------------------//
420 
421 
startElement( String tagName, AttributeList attrs )422     public void startElement( String tagName, AttributeList attrs )
423     throws SAXException
424     {
425         int          i;
426         boolean      preserveSpace;
427         ElementState state;
428         String       name;
429         String       value;
430 
431 
432         if (DEBUG) {
433             System.out.println("==>startElement("+tagName+")");
434         }
435 
436         try {
437             if (_printer == null) {
438                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoWriterSupplied", null);
439                 throw new IllegalStateException(msg);
440             }
441 
442             state = getElementState();
443             if (isDocumentState()) {
444                 // If this is the root element handle it differently.
445                 // If the first root element in the document, serialize
446                 // the document's DOCTYPE. Space preserving defaults
447                 // to that of the output format.
448                 if (! _started)
449                     startDocument( tagName );
450             } else {
451                 // For any other element, if first in parent, then
452                 // close parent's opening tag and use the parnet's
453                 // space preserving.
454                 if (state.empty)
455                     _printer.printText( '>' );
456                 // Must leave CData section first
457                 if (state.inCData) {
458                     _printer.printText( "]]>" );
459                     state.inCData = false;
460                 }
461                 // Indent this element on a new line if the first
462                 // content of the parent element or immediately
463                 // following an element.
464                 if (_indenting && ! state.preserveSpace &&
465                     ( state.empty || state.afterElement || state.afterComment))
466                     _printer.breakLine();
467             }
468             preserveSpace = state.preserveSpace;
469 
470             // Do not change the current element state yet.
471             // This only happens in endElement().
472 
473             _printer.printText( '<' );
474             _printer.printText( tagName );
475             _printer.indent();
476 
477             // For each attribute print it's name and value as one part,
478             // separated with a space so the element can be broken on
479             // multiple lines.
480             if (attrs != null) {
481                 for (i = 0 ; i < attrs.getLength() ; ++i) {
482                     _printer.printSpace();
483                     name = attrs.getName( i );
484                     value = attrs.getValue( i );
485                     if (value != null) {
486                         _printer.printText( name );
487                         _printer.printText( "=\"" );
488                         printEscaped( value );
489                         _printer.printText( '"' );
490                     }
491 
492                     // If the attribute xml:space exists, determine whether
493                     // to preserve spaces in this and child nodes based on
494                     // its value.
495                     if (name.equals( "xml:space" )) {
496                         if (value.equals( "preserve" ))
497                             preserveSpace = true;
498                         else
499                             preserveSpace = _format.getPreserveSpace();
500                     }
501                 }
502             }
503             // Now it's time to enter a new element state
504             // with the tag name and space preserving.
505             // We still do not change the curent element state.
506             state = enterElementState( null, null, tagName, preserveSpace );
507             state.doCData = _format.isCDataElement( tagName );
508             state.unescaped = _format.isNonEscapingElement( tagName );
509         } catch (IOException except) {
510             throw new SAXException( except );
511         }
512 
513     }
514 
515 
endElement( String tagName )516     public void endElement( String tagName )
517     throws SAXException
518     {
519         endElement( null, null, tagName );
520     }
521 
522 
523 
524     //------------------------------------------//
525     // Generic node serializing methods methods //
526     //------------------------------------------//
527 
528 
529     /**
530      * Called to serialize the document's DOCTYPE by the root element.
531      * The document type declaration must name the root element,
532      * but the root element is only known when that element is serialized,
533      * and not at the start of the document.
534      * <p>
535      * This method will check if it has not been called before ({@link #_started}),
536      * will serialize the document type declaration, and will serialize all
537      * pre-root comments and PIs that were accumulated in the document
538      * (see {@link #serializePreRoot}). Pre-root will be serialized even if
539      * this is not the first root element of the document.
540      */
startDocument( String rootTagName )541     protected void startDocument( String rootTagName )
542     throws IOException
543     {
544         int    i;
545         String dtd;
546 
547         dtd = _printer.leaveDTD();
548         if (! _started) {
549 
550             if (! _format.getOmitXMLDeclaration()) {
551                 StringBuffer    buffer;
552 
553                 // Serialize the document declaration appreaing at the head
554                 // of very XML document (unless asked not to).
555                 buffer = new StringBuffer( "<?xml version=\"" );
556                 if (_format.getVersion() != null)
557                     buffer.append( _format.getVersion() );
558                 else
559                     buffer.append( "1.0" );
560                 buffer.append( '"' );
561                 String format_encoding =  _format.getEncoding();
562                 if (format_encoding != null) {
563                     buffer.append( " encoding=\"" );
564                     buffer.append( format_encoding );
565                     buffer.append( '"' );
566                 }
567                 if (_format.getStandalone() && _docTypeSystemId == null &&
568                     _docTypePublicId == null)
569                     buffer.append( " standalone=\"yes\"" );
570                 buffer.append( "?>" );
571                 _printer.printText( buffer );
572                 _printer.breakLine();
573             }
574 
575             if (! _format.getOmitDocumentType()) {
576                 if (_docTypeSystemId != null) {
577                     // System identifier must be specified to print DOCTYPE.
578                     // If public identifier is specified print 'PUBLIC
579                     // <public> <system>', if not, print 'SYSTEM <system>'.
580                     _printer.printText( "<!DOCTYPE " );
581                     _printer.printText( rootTagName );
582                     if (_docTypePublicId != null) {
583                         _printer.printText( " PUBLIC " );
584                         printDoctypeURL( _docTypePublicId );
585                         if (_indenting) {
586                             _printer.breakLine();
587                             for (i = 0 ; i < 18 + rootTagName.length() ; ++i)
588                                 _printer.printText( " " );
589                         } else
590                             _printer.printText( " " );
591                         printDoctypeURL( _docTypeSystemId );
592                     } else {
593                         _printer.printText( " SYSTEM " );
594                         printDoctypeURL( _docTypeSystemId );
595                     }
596 
597                     // If we accumulated any DTD contents while printing.
598                     // this would be the place to print it.
599                     if (dtd != null && dtd.length() > 0) {
600                         _printer.printText( " [" );
601                         printText( dtd, true, true );
602                         _printer.printText( ']' );
603                     }
604 
605                     _printer.printText( ">" );
606                     _printer.breakLine();
607                 } else if (dtd != null && dtd.length() > 0) {
608                     _printer.printText( "<!DOCTYPE " );
609                     _printer.printText( rootTagName );
610                     _printer.printText( " [" );
611                     printText( dtd, true, true );
612                     _printer.printText( "]>" );
613                     _printer.breakLine();
614                 }
615             }
616         }
617         _started = true;
618         // Always serialize these, even if not te first root element.
619         serializePreRoot();
620     }
621 
622 
623     /**
624      * Called to serialize a DOM element. Equivalent to calling {@link
625      * #startElement}, {@link #endElement} and serializing everything
626      * inbetween, but better optimized.
627      */
serializeElement( Element elem )628     protected void serializeElement( Element elem )
629     throws IOException
630     {
631         Attr         attr;
632         NamedNodeMap attrMap;
633         int          i;
634         Node         child;
635         ElementState state;
636         String       name;
637         String       value;
638         String       tagName;
639 
640         String prefix, localUri;
641         String uri;
642         if (fNamespaces) {
643             // local binder stores namespace declaration
644             // that has been printed out during namespace fixup of
645             // the current element
646             fLocalNSBinder.reset();
647 
648             // add new namespace context
649             fNSBinder.pushContext();
650         }
651 
652         if (DEBUG) {
653             System.out.println("==>startElement: " +elem.getNodeName() +" ns="+elem.getNamespaceURI());
654         }
655         tagName = elem.getTagName();
656         state = getElementState();
657         if (isDocumentState()) {
658             // If this is the root element handle it differently.
659             // If the first root element in the document, serialize
660             // the document's DOCTYPE. Space preserving defaults
661             // to that of the output format.
662 
663             if (! _started) {
664                 startDocument( tagName);
665             }
666         } else {
667             // For any other element, if first in parent, then
668             // close parent's opening tag and use the parent's
669             // space preserving.
670             if (state.empty)
671                 _printer.printText( '>' );
672             // Must leave CData section first
673             if (state.inCData) {
674                 _printer.printText( "]]>" );
675                 state.inCData = false;
676             }
677             // Indent this element on a new line if the first
678             // content of the parent element or immediately
679             // following an element.
680             if (_indenting && ! state.preserveSpace &&
681                 ( state.empty || state.afterElement || state.afterComment))
682                 _printer.breakLine();
683         }
684 
685         // Do not change the current element state yet.
686         // This only happens in endElement().
687         fPreserveSpace = state.preserveSpace;
688 
689 
690         int length = 0;
691         attrMap = null;
692         // retrieve attributes
693         if (elem.hasAttributes()) {
694             attrMap = elem.getAttributes();
695             length = attrMap.getLength();
696         }
697 
698         if (!fNamespaces) { // no namespace fixup should be performed
699 
700             // serialize element name
701             _printer.printText( '<' );
702             _printer.printText( tagName );
703             _printer.indent();
704 
705             // For each attribute print it's name and value as one part,
706             // separated with a space so the element can be broken on
707             // multiple lines.
708             for ( i = 0 ; i < length ; ++i ) {
709                 attr = (Attr) attrMap.item( i );
710                 name = attr.getName();
711                 value = attr.getValue();
712                 if ( value == null )
713                     value = "";
714                 printAttribute (name, value, attr.getSpecified(), attr);
715             }
716         } else { // do namespace fixup
717 
718             // REVISIT: some optimization could probably be done to avoid traversing
719             //          attributes twice.
720             //
721 
722             // ---------------------------------------
723             // record all valid namespace declarations
724             // before attempting to fix element's namespace
725             // ---------------------------------------
726 
727             for (i = 0;i < length;i++) {
728 
729                 attr = (Attr) attrMap.item( i );
730                 uri = attr.getNamespaceURI();
731                 // check if attribute is a namespace decl
732                 if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
733 
734                     value = attr.getNodeValue();
735                     if (value == null) {
736                         value=XMLSymbols.EMPTY_STRING;
737                     }
738 
739                     if (value.equals(NamespaceContext.XMLNS_URI)) {
740                         if (fDOMErrorHandler != null) {
741                             String msg = DOMMessageFormatter.formatMessage(
742                                 DOMMessageFormatter.XML_DOMAIN,"CantBindXMLNS",null );
743                             modifyDOMError(msg,  DOMError.SEVERITY_ERROR, null, attr);
744                             boolean continueProcess = fDOMErrorHandler.handleError(fDOMError);
745                             if (!continueProcess) {
746                                 // stop the namespace fixup and validation
747                                 throw new RuntimeException(
748                                     DOMMessageFormatter.formatMessage(
749                                     DOMMessageFormatter.SERIALIZER_DOMAIN,
750                                     "SerializationStopped", null));
751                             }
752                         }
753                     } else {
754                         prefix = attr.getPrefix();
755                         prefix = (prefix == null ||
756                                   prefix.length() == 0) ? XMLSymbols.EMPTY_STRING :fSymbolTable.addSymbol(prefix);
757                         String localpart = fSymbolTable.addSymbol( attr.getLocalName());
758                         if (prefix == XMLSymbols.PREFIX_XMLNS) { //xmlns:prefix
759                             value = fSymbolTable.addSymbol(value);
760                             // record valid decl
761                             if (value.length() != 0) {
762                                 fNSBinder.declarePrefix(localpart, value);
763                             } else {
764                                 // REVISIT: issue error on invalid declarations
765                                 //          xmlns:foo = ""
766                             }
767                             continue;
768                         } else { // xmlns
769                             // empty prefix is always bound ("" or some string)
770 
771                             value = fSymbolTable.addSymbol(value);
772                             fNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, value);
773                             continue;
774                         }
775                     }  // end-else: valid declaration
776                 } // end-if: namespace declaration
777             }  // end-for
778 
779             //-----------------------
780             // get element uri/prefix
781             //-----------------------
782             uri = elem.getNamespaceURI();
783             prefix = elem.getPrefix();
784 
785             //----------------------
786             // output element name
787             //----------------------
788             // REVISIT: this could be removed if we always convert empty string to null
789             //          for the namespaces.
790             if ((uri !=null && prefix !=null ) && uri.length() == 0 && prefix.length()!=0) {
791                 // uri is an empty string and element has some prefix
792                 // the namespace alg later will fix up the namespace attributes
793                 // remove element prefix
794                 prefix = null;
795                 _printer.printText( '<' );
796                 _printer.printText( elem.getLocalName() );
797                 _printer.indent();
798             } else {
799                 _printer.printText( '<' );
800                 _printer.printText( tagName );
801                 _printer.indent();
802             }
803 
804 
805             // ---------------------------------------------------------
806             // Fix up namespaces for element: per DOM L3
807             // Need to consider the following cases:
808             //
809             // case 1: <foo:elem xmlns:ns1="myURI" xmlns="default"/>
810             // Assume "foo", "ns1" are declared on the parent. We should not miss
811             // redeclaration for both "ns1" and default namespace. To solve this
812             // we add a local binder that stores declaration only for current element.
813             // This way we avoid outputing duplicate declarations for the same element
814             // as well as we are not omitting redeclarations.
815             //
816             // case 2: <elem xmlns="" xmlns="default"/>
817             // We need to bind default namespace to empty string, to be able to
818             // omit duplicate declarations for the same element
819             //
820             // case 3: <xsl:stylesheet xmlns:xsl="http://xsl">
821             // We create another element body bound to the "http://xsl" namespace
822             // as well as namespace attribute rebounding xsl to another namespace.
823             // <xsl:body xmlns:xsl="http://another">
824             // Need to make sure that the new namespace decl value is changed to
825             // "http://xsl"
826             //
827             // ---------------------------------------------------------
828             // check if prefix/namespace is correct for current element
829             // ---------------------------------------------------------
830 
831 
832             if (uri != null) {  // Element has a namespace
833                 uri = fSymbolTable.addSymbol(uri);
834                 prefix = (prefix == null ||
835                           prefix.length() == 0) ? XMLSymbols.EMPTY_STRING :fSymbolTable.addSymbol(prefix);
836                 if (fNSBinder.getURI(prefix) == uri) {
837                     // The xmlns:prefix=namespace or xmlns="default" was declared at parent.
838                     // The binder always stores mapping of empty prefix to "".
839                     // (NOTE: local binder does not store this kind of binding!)
840                     // Thus the case where element was declared with uri="" (with or without a prefix)
841                     // will be covered here.
842 
843                 } else {
844                     // the prefix is either undeclared
845                     // or
846                     // conflict: the prefix is bound to another URI
847                     if (fNamespacePrefixes) {
848                         printNamespaceAttr(prefix, uri);
849                     }
850                     fLocalNSBinder.declarePrefix(prefix, uri);
851                     fNSBinder.declarePrefix(prefix, uri);
852                 }
853             } else { // Element has no namespace
854                 if (elem.getLocalName() == null) {
855                     //  DOM Level 1 node!
856                     if (fDOMErrorHandler != null) {
857                         String msg = DOMMessageFormatter.formatMessage(
858                             DOMMessageFormatter.DOM_DOMAIN, "NullLocalElementName",
859                             new Object[]{elem.getNodeName()});
860                         modifyDOMError(msg,DOMError.SEVERITY_ERROR, null, elem);
861                         boolean continueProcess = fDOMErrorHandler.handleError(fDOMError);
862                         // REVISIT: should we terminate upon request?
863                         if (!continueProcess) {
864                            throw new RuntimeException(
865                                DOMMessageFormatter.formatMessage(
866                                DOMMessageFormatter.SERIALIZER_DOMAIN,
867                                "SerializationStopped", null));
868                         }
869                     }
870                 } else { // uri=null and no colon (DOM L2 node)
871                     uri = fNSBinder.getURI(XMLSymbols.EMPTY_STRING);
872 
873                     if (uri !=null && uri.length() > 0) {
874                         // there is a default namespace decl that is bound to
875                         // non-zero length uri, output xmlns=""
876                         if (fNamespacePrefixes) {
877                             printNamespaceAttr(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
878                         }
879                         fLocalNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
880                         fNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
881                     }
882                 }
883             }
884 
885 
886             // -----------------------------------------
887             // Fix up namespaces for attributes: per DOM L3
888             // check if prefix/namespace is correct the attributes
889             // -----------------------------------------
890 
891             for (i = 0; i < length; i++) {
892 
893                 attr = (Attr) attrMap.item( i );
894                 value = attr.getValue();
895                 name = attr.getNodeName();
896 
897                 uri = attr.getNamespaceURI();
898 
899                 // Fix attribute that was declared with a prefix and namespace=""
900                 if (uri !=null && uri.length() == 0) {
901                     uri=null;
902                     // we must remove prefix for this attribute
903                     name=attr.getLocalName();
904                 }
905 
906                 if (DEBUG) {
907                     System.out.println("==>process attribute: "+attr.getNodeName());
908                 }
909                 // make sure that value is never null.
910                 if (value == null) {
911                     value=XMLSymbols.EMPTY_STRING;
912                 }
913 
914                 if (uri != null) {  // attribute has namespace !=null
915                     prefix = attr.getPrefix();
916                     prefix = prefix == null ? XMLSymbols.EMPTY_STRING :fSymbolTable.addSymbol(prefix);
917                     String localpart = fSymbolTable.addSymbol( attr.getLocalName());
918 
919 
920 
921                     // ---------------------------------------------------
922                     // print namespace declarations namespace declarations
923                     // ---------------------------------------------------
924                     if (uri != null && uri.equals(NamespaceContext.XMLNS_URI)) {
925                         // check if we need to output this declaration
926                         prefix = attr.getPrefix();
927                         prefix = (prefix == null ||
928                                   prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
929                         localpart = fSymbolTable.addSymbol( attr.getLocalName());
930                         if (prefix == XMLSymbols.PREFIX_XMLNS) { //xmlns:prefix
931                             localUri = fLocalNSBinder.getURI(localpart);  // local prefix mapping
932                             value = fSymbolTable.addSymbol(value);
933                             if (value.length() != 0 ) {
934                                 if (localUri == null) {
935                                     // declaration was not printed while fixing element namespace binding
936 
937                                     // If the DOM Level 3 namespace-prefixes feature is set to false
938                                     // do not print xmlns attributes
939                                     if (fNamespacePrefixes) {
940                                         printNamespaceAttr(localpart, value);
941                                     }
942 
943                                     // case 4: <elem xmlns:xx="foo" xx:attr=""/>
944                                     // where attribute is bound to "bar".
945                                     // If the xmlns:xx is output here first, later we should not
946                                     // redeclare "xx" prefix. Instead we would pick up different prefix
947                                     // for the attribute.
948                                     // final: <elem xmlns:xx="foo" NS1:attr="" xmlns:NS1="bar"/>
949                                     fLocalNSBinder.declarePrefix(localpart, value);
950                                 }
951                             } else {
952                                 // REVISIT: issue error on invalid declarations
953                                 //          xmlns:foo = ""
954                             }
955                             continue;
956                         } else { // xmlns
957                             // empty prefix is always bound ("" or some string)
958 
959                             uri = fNSBinder.getURI(XMLSymbols.EMPTY_STRING);
960                             localUri=fLocalNSBinder.getURI(XMLSymbols.EMPTY_STRING);
961                             value = fSymbolTable.addSymbol(value);
962                             if (localUri == null ){
963                                 // declaration was not printed while fixing element namespace binding
964                                 if (fNamespacePrefixes) {
965                                     printNamespaceAttr(XMLSymbols.EMPTY_STRING, value);
966                                 }
967                                 // case 4 does not apply here since attributes can't use
968                                 // default namespace
969                             }
970                             continue;
971                         }
972 
973                     }
974                     uri = fSymbolTable.addSymbol(uri);
975 
976                     // find if for this prefix a URI was already declared
977                     String declaredURI =  fNSBinder.getURI(prefix);
978 
979                     if (prefix == XMLSymbols.EMPTY_STRING || declaredURI != uri) {
980                         // attribute has no prefix (default namespace decl does not apply to attributes)
981                         // OR
982                         // attribute prefix is not declared
983                         // OR
984                         // conflict: attr URI does not match the prefix in scope
985 
986                         name  = attr.getNodeName();
987                         // Find if any prefix for attributes namespace URI is available
988                         // in the scope
989                         String declaredPrefix = fNSBinder.getPrefix(uri);
990 
991                         if (declaredPrefix !=null && declaredPrefix !=XMLSymbols.EMPTY_STRING) {
992                             // use the prefix that was found
993                             prefix = declaredPrefix;
994                             name=prefix+":"+localpart;
995                         } else {
996                             if (DEBUG) {
997                                 System.out.println("==> cound not find prefix for the attribute: " +prefix);
998                             }
999 
1000                             if (prefix != XMLSymbols.EMPTY_STRING && fLocalNSBinder.getURI(prefix) == null) {
1001                                 // the current prefix is not null and it has no in scope declaration
1002 
1003                                 // use this prefix
1004                             } else {
1005                                 // find a prefix following the pattern "NS" +index (starting at 1)
1006                                 // make sure this prefix is not declared in the current scope.
1007                                 int counter = 1;
1008                                 prefix = fSymbolTable.addSymbol(PREFIX + counter++);
1009                                 while (fLocalNSBinder.getURI(prefix)!=null) {
1010                                     prefix = fSymbolTable.addSymbol(PREFIX +counter++);
1011                                 }
1012                                 name=prefix+":"+localpart;
1013                             }
1014                             // add declaration for the new prefix
1015                             if (fNamespacePrefixes) {
1016                                 printNamespaceAttr(prefix, uri);
1017                             }
1018                             value = fSymbolTable.addSymbol(value);
1019                             fLocalNSBinder.declarePrefix(prefix, value);
1020                             fNSBinder.declarePrefix(prefix, uri);
1021                         }
1022 
1023                         // change prefix for this attribute
1024                     }
1025 
1026                     printAttribute (name, (value==null)?XMLSymbols.EMPTY_STRING:value, attr.getSpecified(), attr);
1027                 } else { // attribute uri == null
1028                     if (attr.getLocalName() == null) {
1029                         if (fDOMErrorHandler != null) {
1030                             String msg = DOMMessageFormatter.formatMessage(
1031                                 DOMMessageFormatter.DOM_DOMAIN,
1032                                 "NullLocalAttrName", new Object[]{attr.getNodeName()});
1033                             modifyDOMError(msg, DOMError.SEVERITY_ERROR, null, attr);
1034                             boolean continueProcess = fDOMErrorHandler.handleError(fDOMError);
1035                             if (!continueProcess) {
1036                                 // stop the namespace fixup and validation
1037                                 throw new RuntimeException(
1038                                    DOMMessageFormatter.formatMessage(
1039                                    DOMMessageFormatter.SERIALIZER_DOMAIN,
1040                                    "SerializationStopped", null));
1041                             }
1042                         }
1043                         printAttribute (name, value, attr.getSpecified(), attr);
1044                     } else { // uri=null and no colon
1045 
1046                         // no fix up is needed: default namespace decl does not
1047                         // apply to attributes
1048                         printAttribute (name, value, attr.getSpecified(), attr);
1049                     }
1050                 }
1051             } // end loop for attributes
1052 
1053         }// end namespace fixup algorithm
1054 
1055 
1056         // If element has children, then serialize them, otherwise
1057         // serialize en empty tag.
1058         if (elem.hasChildNodes()) {
1059             // Enter an element state, and serialize the children
1060             // one by one. Finally, end the element.
1061             state = enterElementState( null, null, tagName, fPreserveSpace );
1062             state.doCData = _format.isCDataElement( tagName );
1063             state.unescaped = _format.isNonEscapingElement( tagName );
1064             child = elem.getFirstChild();
1065             while (child != null) {
1066                 serializeNode( child );
1067                 child = child.getNextSibling();
1068             }
1069             if (fNamespaces) {
1070                 fNSBinder.popContext();
1071             }
1072             endElementIO( null, null, tagName );
1073         } else {
1074             if (DEBUG) {
1075                 System.out.println("==>endElement: " +elem.getNodeName());
1076             }
1077             if (fNamespaces) {
1078                 fNSBinder.popContext();
1079             }
1080             _printer.unindent();
1081             _printer.printText( "/>" );
1082             // After element but parent element is no longer empty.
1083             state.afterElement = true;
1084             state.afterComment = false;
1085             state.empty = false;
1086             if (isDocumentState())
1087                 _printer.flush();
1088         }
1089     }
1090 
1091 
1092 
1093     /**
1094      * Serializes a namespace attribute with the given prefix and value for URI.
1095      * In case prefix is empty will serialize default namespace declaration.
1096      *
1097      * @param prefix
1098      * @param uri
1099      * @exception IOException
1100      */
1101 
printNamespaceAttr(String prefix, String uri)1102     private void printNamespaceAttr(String prefix, String uri) throws IOException{
1103         _printer.printSpace();
1104         if (prefix == XMLSymbols.EMPTY_STRING) {
1105             if (DEBUG) {
1106                 System.out.println("=>add xmlns=\""+uri+"\" declaration");
1107             }
1108             _printer.printText( XMLSymbols.PREFIX_XMLNS );
1109         } else {
1110             if (DEBUG) {
1111                 System.out.println("=>add xmlns:"+prefix+"=\""+uri+"\" declaration");
1112             }
1113             _printer.printText( "xmlns:"+prefix );
1114         }
1115         _printer.printText( "=\"" );
1116         printEscaped( uri );
1117         _printer.printText( '"' );
1118     }
1119 
1120 
1121 
1122     /**
1123      * Prints attribute.
1124      * NOTE: xml:space attribute modifies output format
1125      *
1126      * @param name
1127      * @param value
1128      * @param isSpecified
1129      * @exception IOException
1130      */
printAttribute(String name, String value, boolean isSpecified, Attr attr)1131     private void printAttribute (String name, String value, boolean isSpecified, Attr attr) throws IOException{
1132 
1133         if (isSpecified || (features & DOMSerializerImpl.DISCARDDEFAULT) == 0) {
1134             if (fDOMFilter !=null &&
1135                 (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ATTRIBUTE)!= 0) {
1136                 short code = fDOMFilter.acceptNode(attr);
1137                 switch (code) {
1138                     case NodeFilter.FILTER_REJECT:
1139                     case NodeFilter.FILTER_SKIP: {
1140                         return;
1141                     }
1142                     default: {
1143                         // fall through
1144                     }
1145                 }
1146             }
1147             _printer.printSpace();
1148             _printer.printText( name );
1149             _printer.printText( "=\"" );
1150             printEscaped( value );
1151             _printer.printText( '"' );
1152         }
1153 
1154         // If the attribute xml:space exists, determine whether
1155         // to preserve spaces in this and child nodes based on
1156         // its value.
1157         if (name.equals( "xml:space" )) {
1158             if (value.equals( "preserve" ))
1159                 fPreserveSpace = true;
1160             else
1161                 fPreserveSpace = _format.getPreserveSpace();
1162         }
1163     }
1164 
getEntityRef( int ch )1165     protected String getEntityRef( int ch ) {
1166         // Encode special XML characters into the equivalent character references.
1167         // These five are defined by default for all XML documents.
1168         switch (ch) {
1169         case '<':
1170             return "lt";
1171         case '>':
1172             return "gt";
1173         case '"':
1174             return "quot";
1175         case '\'':
1176             return "apos";
1177         case '&':
1178             return "amp";
1179         }
1180         return null;
1181     }
1182 
1183 
1184     /** Retrieve and remove the namespaces declarations from the list of attributes.
1185      *
1186      */
extractNamespaces( Attributes attrs )1187     private Attributes extractNamespaces( Attributes attrs )
1188     throws SAXException
1189     {
1190         AttributesImpl attrsOnly;
1191         String         rawName;
1192         int            i;
1193         int            indexColon;
1194         String         prefix;
1195         int            length;
1196 
1197         if (attrs == null) {
1198             return null;
1199         }
1200         length = attrs.getLength();
1201         attrsOnly = new AttributesImpl( attrs );
1202 
1203         for (i = length - 1 ; i >= 0 ; --i) {
1204             rawName = attrsOnly.getQName( i );
1205 
1206             //We have to exclude the namespaces declarations from the attributes
1207             //Append only when the feature http://xml.org/sax/features/namespace-prefixes"
1208             //is TRUE
1209             if (rawName.startsWith( "xmlns" )) {
1210                 if (rawName.length() == 5) {
1211                     startPrefixMapping( "", attrs.getValue( i ) );
1212                     attrsOnly.removeAttribute( i );
1213                 } else if (rawName.charAt(5) == ':') {
1214                     startPrefixMapping(rawName.substring(6), attrs.getValue(i));
1215                     attrsOnly.removeAttribute( i );
1216                 }
1217             }
1218         }
1219         return attrsOnly;
1220     }
1221 
1222     //
1223     // Printing attribute value
1224     //
printEscaped(String source)1225     protected void printEscaped(String source) throws IOException {
1226         int length = source.length();
1227         for (int i = 0; i < length; ++i) {
1228             int ch = source.charAt(i);
1229             if (!XMLChar.isValid(ch)) {
1230                 if (++i < length) {
1231                     surrogates(ch, source.charAt(i));
1232                 } else {
1233                     fatalError("The character '" + (char) ch + "' is an invalid XML character");
1234                 }
1235                 continue;
1236             }
1237             // escape NL, CR, TAB
1238             if (ch == '\n' || ch == '\r' || ch == '\t') {
1239                 printHex(ch);
1240             } else if (ch == '<') {
1241                 _printer.printText("&lt;");
1242             } else if (ch == '&') {
1243                 _printer.printText("&amp;");
1244             } else if (ch == '"') {
1245                 _printer.printText("&quot;");
1246             } else if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch))) {
1247                 _printer.printText((char) ch);
1248             } else {
1249                 printHex(ch);
1250             }
1251         }
1252     }
1253 
1254     /** print text data */
printXMLChar( int ch)1255     protected void printXMLChar( int ch) throws IOException {
1256         if (ch == '\r') {
1257                         printHex(ch);
1258         } else if ( ch == '<') {
1259             _printer.printText("&lt;");
1260         } else if (ch == '&') {
1261             _printer.printText("&amp;");
1262         } else if (ch == '>'){
1263                 // character sequence "]]>" can't appear in content, therefore
1264                 // we should escape '>'
1265                         _printer.printText("&gt;");
1266         } else if ( ch == '\n' ||  ch == '\t' ||
1267                     ( ch >= ' ' && _encodingInfo.isPrintable((char)ch))) {
1268             _printer.printText((char)ch);
1269         } else {
1270                         printHex(ch);
1271         }
1272     }
1273 
printText( String text, boolean preserveSpace, boolean unescaped )1274     protected void printText( String text, boolean preserveSpace, boolean unescaped )
1275     throws IOException {
1276         int index;
1277         char ch;
1278         int length = text.length();
1279         if ( preserveSpace ) {
1280             // Preserving spaces: the text must print exactly as it is,
1281             // without breaking when spaces appear in the text and without
1282             // consolidating spaces. If a line terminator is used, a line
1283             // break will occur.
1284             for ( index = 0 ; index < length ; ++index ) {
1285                 ch = text.charAt( index );
1286                 if (!XMLChar.isValid(ch)) {
1287                     // check if it is surrogate
1288                     if (++index <length) {
1289                         surrogates(ch, text.charAt(index));
1290                     } else {
1291                         fatalError("The character '"+(char)ch+"' is an invalid XML character");
1292                     }
1293                     continue;
1294                 }
1295                 if ( unescaped ) {
1296                     _printer.printText( ch );
1297                 } else
1298                     printXMLChar( ch );
1299             }
1300         } else {
1301             // Not preserving spaces: print one part at a time, and
1302             // use spaces between parts to break them into different
1303             // lines. Spaces at beginning of line will be stripped
1304             // by printing mechanism. Line terminator is treated
1305             // no different than other text part.
1306             for ( index = 0 ; index < length ; ++index ) {
1307                 ch = text.charAt( index );
1308                 if (!XMLChar.isValid(ch)) {
1309                     // check if it is surrogate
1310                     if (++index <length) {
1311                         surrogates(ch, text.charAt(index));
1312                     } else {
1313                         fatalError("The character '"+(char)ch+"' is an invalid XML character");
1314                     }
1315                     continue;
1316                 }
1317 
1318                                 if ( unescaped )
1319                     _printer.printText( ch );
1320                 else
1321                     printXMLChar( ch);
1322             }
1323         }
1324     }
1325 
1326 
1327 
printText( char[] chars, int start, int length, boolean preserveSpace, boolean unescaped )1328     protected void printText( char[] chars, int start, int length,
1329                               boolean preserveSpace, boolean unescaped ) throws IOException {
1330         int index;
1331         char ch;
1332 
1333         if ( preserveSpace ) {
1334             // Preserving spaces: the text must print exactly as it is,
1335             // without breaking when spaces appear in the text and without
1336             // consolidating spaces. If a line terminator is used, a line
1337             // break will occur.
1338             while ( length-- > 0 ) {
1339                 ch = chars[start++];
1340                 if (!XMLChar.isValid(ch)) {
1341                     // check if it is surrogate
1342                     if ( length-- > 0 ) {
1343                         surrogates(ch, chars[start++]);
1344                     } else {
1345                         fatalError("The character '"+(char)ch+"' is an invalid XML character");
1346                     }
1347                     continue;
1348                 }
1349                 if ( unescaped )
1350                     _printer.printText( ch );
1351                 else
1352                     printXMLChar( ch );
1353             }
1354         } else {
1355             // Not preserving spaces: print one part at a time, and
1356             // use spaces between parts to break them into different
1357             // lines. Spaces at beginning of line will be stripped
1358             // by printing mechanism. Line terminator is treated
1359             // no different than other text part.
1360             while ( length-- > 0 ) {
1361                 ch = chars[start++];
1362                 if (!XMLChar.isValid(ch)) {
1363                     // check if it is surrogate
1364                     if ( length-- > 0 ) {
1365                         surrogates(ch, chars[start++]);
1366                     } else {
1367                         fatalError("The character '"+(char)ch+"' is an invalid XML character");
1368                     }
1369                     continue;
1370                 }
1371                 if ( unescaped )
1372                     _printer.printText( ch );
1373                 else
1374                     printXMLChar( ch );
1375             }
1376         }
1377     }
1378 
1379 
1380    /**
1381     * DOM Level 3:
1382     * Check a node to determine if it contains unbound namespace prefixes.
1383     *
1384     * @param node The node to check for unbound namespace prefices
1385     */
checkUnboundNamespacePrefixedNode(Node node)1386         protected void checkUnboundNamespacePrefixedNode (Node node) throws IOException{
1387 
1388                 if (fNamespaces) {
1389 
1390                         if (DEBUG) {
1391                             System.out.println("==>serializeNode("+node.getNodeName()+") [Entity Reference - Namespaces on]");
1392                                 System.out.println("==>Declared Prefix Count: " + fNSBinder.getDeclaredPrefixCount());
1393                                 System.out.println("==>Node Name: " + node.getNodeName());
1394                                 System.out.println("==>First Child Node Name: " + node.getFirstChild().getNodeName());
1395                                 System.out.println("==>First Child Node Prefix: " + node.getFirstChild().getPrefix());
1396                                 System.out.println("==>First Child Node NamespaceURI: " + node.getFirstChild().getNamespaceURI());
1397                         }
1398 
1399 
1400                         Node child, next;
1401                 for (child = node.getFirstChild(); child != null; child = next) {
1402                     next = child.getNextSibling();
1403                             if (DEBUG) {
1404                                 System.out.println("==>serializeNode("+child.getNodeName()+") [Child Node]");
1405                                 System.out.println("==>serializeNode("+child.getPrefix()+") [Child Node Prefix]");
1406                     }
1407 
1408                             //If a NamespaceURI is not declared for the current
1409                             //node's prefix, raise a fatal error.
1410                             String prefix = child.getPrefix();
1411                 prefix = (prefix == null ||
1412                         prefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(prefix);
1413                             if (fNSBinder.getURI(prefix) == null && prefix != null) {
1414                                         fatalError("The replacement text of the entity node '"
1415                                                                 + node.getNodeName()
1416                                                                 + "' contains an element node '"
1417                                                                 + child.getNodeName()
1418                                                                 + "' with an undeclared prefix '"
1419                                                                 + prefix + "'.");
1420                             }
1421 
1422                                 if (child.getNodeType() == Node.ELEMENT_NODE) {
1423 
1424                                         NamedNodeMap attrs = child.getAttributes();
1425 
1426                                         for (int i = 0; i< attrs.getLength(); i++ ) {
1427 
1428                                             String attrPrefix = attrs.item(i).getPrefix();
1429                         attrPrefix = (attrPrefix == null ||
1430                                 attrPrefix.length() == 0) ? XMLSymbols.EMPTY_STRING : fSymbolTable.addSymbol(attrPrefix);
1431                                             if (fNSBinder.getURI(attrPrefix) == null && attrPrefix != null) {
1432                                                         fatalError("The replacement text of the entity node '"
1433                                                                                 + node.getNodeName()
1434                                                                                 + "' contains an element node '"
1435                                                                                 + child.getNodeName()
1436                                                                                 + "' with an attribute '"
1437                                                                                 + attrs.item(i).getNodeName()
1438                                                                                 + "' an undeclared prefix '"
1439                                                                                 + attrPrefix + "'.");
1440                                             }
1441 
1442                                         }
1443 
1444                                 }
1445 
1446                                 if (child.hasChildNodes()) {
1447                                         checkUnboundNamespacePrefixedNode(child);
1448                                 }
1449                 }
1450                 }
1451         }
1452 
reset()1453     public boolean reset() {
1454         super.reset();
1455         if (fNSBinder != null){
1456             fNSBinder.reset();
1457             // during serialization always have a mapping to empty string
1458             // so we assume there is a declaration.
1459             fNSBinder.declarePrefix(XMLSymbols.EMPTY_STRING, XMLSymbols.EMPTY_STRING);
1460         }
1461         return true;
1462     }
1463 
1464 }
1465