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