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