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