1 /*
   2  * Copyright (c) 2007, 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 package com.sun.org.apache.xml.internal.serializer;
  22 
  23 import java.io.IOException;
  24 import java.io.OutputStream;
  25 import java.io.Writer;
  26 import java.util.ArrayList;
  27 import java.util.List;
  28 import java.util.Properties;
  29 import javax.xml.transform.SourceLocator;
  30 import javax.xml.transform.Transformer;
  31 import org.w3c.dom.Node;
  32 import org.xml.sax.Attributes;
  33 import org.xml.sax.ContentHandler;
  34 import org.xml.sax.Locator;
  35 import org.xml.sax.SAXException;
  36 
  37 /**
  38  *This class wraps another SerializationHandler. The wrapped object will either
  39  * handler XML or HTML, which is not known until a little later when the first XML
  40  * tag is seen.  If the first tag is <html> then the wrapped object is an HTML
  41  * handler, otherwise it is an XML handler.
  42  *
  43  * This class effectively caches the first few calls to it then passes them
  44  * on to the wrapped handler (once it exists).  After that subsequent calls a
  45  * simply passed directly to the wrapped handler.
  46  *
  47  * The user of this class doesn't know if the output is ultimatley XML or HTML.
  48  *
  49  * This class is not a public API, it is public because it is used within Xalan.
  50  * @xsl.usage internal
  51  * @LastModified: Oct 2017
  52  */
  53 public final class ToUnknownStream extends SerializerBase
  54 {
  55     /**
  56      * The wrapped handler, initially XML but possibly switched to HTML
  57      */
  58     private SerializationHandler m_handler;
  59 
  60     /**
  61      * A String with no characters
  62      */
  63     private static final String EMPTYSTRING = "";
  64 
  65     /**
  66      * true if the underlying handler (XML or HTML) is fully initialized
  67      */
  68     private boolean m_wrapped_handler_not_initialized = false;
  69 
  70     /**
  71      * the prefix of the very first tag in the document
  72      */
  73     private String m_firstElementPrefix;
  74 
  75     /**
  76      * the element name (including any prefix) of the very first tag in the document
  77      */
  78     private String m_firstElementName;
  79 
  80     /**
  81      * the namespace URI associated with the first element
  82      */
  83     private String m_firstElementURI;
  84 
  85     /**
  86      * the local name (no prefix) associated with the first element
  87      */
  88     private String m_firstElementLocalName = null;
  89 
  90     /**
  91      * true if the first tag has been emitted to the wrapped handler
  92      */
  93     private boolean m_firstTagNotEmitted = true;
  94 
  95     /**
  96      * A collection of namespace URI's (only for first element).
  97      * _namespacePrefix has the matching prefix for these URI's
  98      */
  99     private List<String> m_namespaceURI = null;
 100 
 101     /**
 102      * A collection of namespace Prefix (only for first element)
 103      * _namespaceURI has the matching URIs for these prefix'
 104      */
 105     private List<String> m_namespacePrefix = null;
 106 
 107     /**
 108      * true if startDocument() was called before the underlying handler
 109      * was initialized
 110      */
 111     private boolean m_needToCallStartDocument = false;
 112 
 113     /**
 114      * Default constructor.
 115      * Initially this object wraps an XML Stream object, so _handler is never null.
 116      * That may change later to an HTML Stream object.
 117      */
 118     public ToUnknownStream() {
 119         m_handler = new ToXMLStream();
 120     }
 121 
 122     /**
 123      * @see Serializer#asContentHandler()
 124      * @return the wrapped XML or HTML handler
 125      */
 126     public ContentHandler asContentHandler() throws IOException {
 127         /* don't return the real handler ( m_handler ) because
 128          * that would expose the real handler to the outside.
 129          * Keep m_handler private so it can be internally swapped
 130          * to an HTML handler.
 131          */
 132         return this;
 133     }
 134 
 135     /**
 136      * @see SerializationHandler#close()
 137      */
 138     public void close() {
 139         m_handler.close();
 140     }
 141 
 142     /**
 143      * @see Serializer#getOutputFormat()
 144      * @return the properties of the underlying handler
 145      */
 146     public Properties getOutputFormat() {
 147         return m_handler.getOutputFormat();
 148     }
 149 
 150     /**
 151      * @see Serializer#getOutputStream()
 152      * @return the OutputStream of the underlying XML or HTML handler
 153      */
 154     public OutputStream getOutputStream() {
 155         return m_handler.getOutputStream();
 156     }
 157 
 158     /**
 159      * @see Serializer#getWriter()
 160      * @return the Writer of the underlying XML or HTML handler
 161      */
 162     public Writer getWriter() {
 163         return m_handler.getWriter();
 164     }
 165 
 166     /**
 167      * passes the call on to the underlying HTML or XML handler
 168      * @see Serializer#reset()
 169      * @return ???
 170      */
 171     public boolean reset() {
 172         return m_handler.reset();
 173     }
 174 
 175     /**
 176      * Converts the DOM node to output
 177      * @param node the DOM node to transform to output
 178      * @see DOMSerializer#serialize(Node)
 179      *
 180      */
 181     public void serialize(Node node) throws IOException {
 182         if (m_firstTagNotEmitted) {
 183             flush();
 184         }
 185         m_handler.serialize(node);
 186     }
 187 
 188     /**
 189      * @see SerializationHandler#setEscaping(boolean)
 190      */
 191     public boolean setEscaping(boolean escape) throws SAXException {
 192         return m_handler.setEscaping(escape);
 193     }
 194 
 195     /**
 196      * Set the properties of the handler
 197      * @param format the output properties to set
 198      * @see Serializer#setOutputFormat(Properties)
 199      */
 200     public void setOutputFormat(Properties format) {
 201         m_handler.setOutputFormat(format);
 202     }
 203 
 204     /**
 205      * Sets the output stream to write to
 206      * @param output the OutputStream to write to
 207      * @see Serializer#setOutputStream(OutputStream)
 208      */
 209     public void setOutputStream(OutputStream output) {
 210         m_handler.setOutputStream(output);
 211     }
 212 
 213     /**
 214      * Sets the writer to write to
 215      * @param writer the writer to write to
 216      * @see Serializer#setWriter(Writer)
 217      */
 218     public void setWriter(Writer writer) {
 219         m_handler.setWriter(writer);
 220     }
 221 
 222     /**
 223      * Adds an attribute to the currenly open tag
 224      * @param uri the URI of a namespace
 225      * @param localName the attribute name, without prefix
 226      * @param rawName the attribute name, with prefix (if any)
 227      * @param type the type of the attribute, typically "CDATA"
 228      * @param value the value of the parameter
 229      * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
 230      * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
 231      */
 232     public void addAttribute(String uri, String localName, String rawName,
 233                              String type, String value)
 234         throws SAXException
 235     {
 236         addAttribute(uri, localName, rawName, type, value, false);
 237     }
 238 
 239     /**
 240      * Adds an attribute to the currenly open tag
 241      * @param uri the URI of a namespace
 242      * @param localName the attribute name, without prefix
 243      * @param rawName the attribute name, with prefix (if any)
 244      * @param type the type of the attribute, typically "CDATA"
 245      * @param value the value of the parameter
 246      * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
 247      * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
 248      */
 249     public void addAttribute(String uri, String localName, String rawName,
 250                              String type, String value, boolean XSLAttribute)
 251         throws SAXException
 252     {
 253         if (m_firstTagNotEmitted) {
 254             flush();
 255         }
 256         m_handler.addAttribute(uri, localName, rawName, type, value, XSLAttribute);
 257     }
 258 
 259     /**
 260      * Adds an attribute to the currenly open tag
 261      * @param rawName the attribute name, with prefix (if any)
 262      * @param value the value of the parameter
 263      * @see ExtendedContentHandler#addAttribute(String, String)
 264      */
 265     public void addAttribute(String rawName, String value) {
 266         if (m_firstTagNotEmitted) {
 267             flush();
 268         }
 269         m_handler.addAttribute(rawName, value);
 270     }
 271 
 272     /**
 273      * Adds a unique attribute to the currenly open tag
 274      */
 275     public void addUniqueAttribute(String rawName, String value, int flags)
 276         throws SAXException
 277     {
 278         if (m_firstTagNotEmitted) {
 279             flush();
 280         }
 281         m_handler.addUniqueAttribute(rawName, value, flags);
 282     }
 283 
 284     /**
 285      * Converts the String to a character array and calls the SAX method
 286      * characters(char[],int,int);
 287      *
 288      * @param chars The string of characters to process.
 289      *
 290      * @throws org.xml.sax.SAXException
 291      *
 292      * @see ExtendedContentHandler#characters(String)
 293      */
 294     public void characters(String chars) throws SAXException {
 295         final int len = (chars == null) ? 0 : chars.length();
 296         if (len > m_charsBuff.length) {
 297             m_charsBuff = new char[len * 2 + 1];
 298         }
 299         if (len > 0) {
 300             chars.getChars(0, len, m_charsBuff, 0);
 301         }
 302         this.characters(m_charsBuff, 0, len);
 303     }
 304 
 305     /**
 306      * Pass the call on to the underlying handler
 307      * @see ExtendedContentHandler#endElement(String)
 308      */
 309     public void endElement(String elementName) throws SAXException {
 310         if (m_firstTagNotEmitted) {
 311             flush();
 312         }
 313         m_handler.endElement(elementName);
 314     }
 315 
 316     /**
 317      * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
 318      * @param prefix The prefix that maps to the URI
 319      * @param uri The URI for the namespace
 320      */
 321     public void startPrefixMapping(String prefix, String uri) throws SAXException {
 322         this.startPrefixMapping(prefix,uri, true);
 323     }
 324 
 325     /**
 326      * This method is used when a prefix/uri namespace mapping
 327      * is indicated after the element was started with a
 328      * startElement() and before and endElement().
 329      * startPrefixMapping(prefix,uri) would be used before the
 330      * startElement() call.
 331      * @param uri the URI of the namespace
 332      * @param prefix the prefix associated with the given URI.
 333      *
 334      * @see ExtendedContentHandler#namespaceAfterStartElement(String, String)
 335      */
 336     public void namespaceAfterStartElement(String prefix, String uri)
 337         throws SAXException
 338     {
 339         // hack for XSLTC with finding URI for default namespace
 340         if (m_firstTagNotEmitted &&
 341             m_firstElementURI == null &&
 342             m_firstElementName != null)
 343         {
 344             String prefix1 = getPrefixPart(m_firstElementName);
 345             if (prefix1 == null && EMPTYSTRING.equals(prefix)) {
 346                 // the elements URI is not known yet, and it
 347                 // doesn't have a prefix, and we are currently
 348                 // setting the uri for prefix "", so we have
 349                 // the uri for the element... lets remember it
 350                 m_firstElementURI = uri;
 351             }
 352         }
 353         startPrefixMapping(prefix, uri, false);
 354     }
 355 
 356     public boolean startPrefixMapping(String prefix, String uri, boolean shouldFlush)
 357         throws SAXException
 358     {
 359         boolean pushed = false;
 360         if (m_firstTagNotEmitted) {
 361             if (m_firstElementName != null && shouldFlush) {
 362                 /* we've already seen a startElement, and this is a prefix mapping
 363                  * for the up coming element, so flush the old element
 364                  * then send this event on its way.
 365                  */
 366                 flush();
 367                 pushed = m_handler.startPrefixMapping(prefix, uri, shouldFlush);
 368             } else {
 369                 if (m_namespacePrefix == null) {
 370                     m_namespacePrefix = new ArrayList<>();
 371                     m_namespaceURI = new ArrayList<>();
 372                 }
 373                 m_namespacePrefix.add(prefix);
 374                 m_namespaceURI.add(uri);
 375 
 376                 if (m_firstElementURI == null) {
 377                     if (prefix.equals(m_firstElementPrefix))
 378                         m_firstElementURI = uri;
 379                 }
 380             }
 381         } else {
 382             pushed = m_handler.startPrefixMapping(prefix, uri, shouldFlush);
 383         }
 384         return pushed;
 385     }
 386 
 387     /**
 388       * This method cannot be cached because default is different in
 389       * HTML and XML (we need more than a boolean).
 390       */
 391     public void setVersion(String version) {
 392         m_handler.setVersion(version);
 393     }
 394 
 395     /**
 396      * @see org.xml.sax.ContentHandler#startDocument()
 397      */
 398     public void startDocument() throws SAXException {
 399         m_needToCallStartDocument = true;
 400     }
 401 
 402     public void startElement(String qName) throws SAXException {
 403         this.startElement(null, null, qName, null);
 404     }
 405 
 406     public void startElement(String namespaceURI, String localName,
 407                              String qName) throws SAXException {
 408         this.startElement(namespaceURI, localName, qName, null);
 409     }
 410 
 411     public void startElement(String namespaceURI, String localName,
 412                              String elementName, Attributes atts)
 413         throws SAXException
 414     {
 415         if (m_needToCallSetDocumentInfo) {
 416             super.setDocumentInfo();
 417             m_needToCallSetDocumentInfo = false;
 418         }
 419 
 420         /* we are notified of the start of an element */
 421         if (m_firstTagNotEmitted) {
 422             /* we have not yet sent the first element on its way */
 423             if (m_firstElementName != null) {
 424                 /* this is not the first element, but a later one.
 425                  * But we have the old element pending, so flush it out,
 426                  * then send this one on its way.
 427                  */
 428                 flush();
 429                 m_handler.startElement(namespaceURI, localName, elementName,  atts);
 430             }
 431             else
 432             {
 433                 /* this is the very first element that we have seen,
 434                  * so save it for flushing later.  We may yet get to know its
 435                  * URI due to added attributes.
 436                  */
 437 
 438                 m_wrapped_handler_not_initialized = true;
 439                 m_firstElementName = elementName;
 440 
 441                 // null if not known
 442                 m_firstElementPrefix = getPrefixPartUnknown(elementName);
 443 
 444                 // null if not known
 445                 m_firstElementURI = namespaceURI;
 446 
 447                 // null if not known
 448                 m_firstElementLocalName = localName;
 449 
 450                 if (m_tracer != null)
 451                     firePseudoElement(elementName);
 452 
 453                 /* we don't want to call our own addAttributes, which
 454                  * merely delegates to the wrapped handler, but we want to
 455                  * add these attributes to m_attributes. So me must call super.
 456                  * addAttributes() In this case m_attributes is only used for the
 457                  * first element, after that this class totally delegates to the
 458                  * wrapped handler which is either XML or HTML.
 459                  */
 460                 if (atts != null)
 461                     super.addAttributes(atts);
 462 
 463                 // if there are attributes, then lets make the flush()
 464                 // call the startElement on the handler and send the
 465                 // attributes on their way.
 466                 if (atts != null)
 467                     flush();
 468 
 469             }
 470         }
 471         else
 472         {
 473             // this is not the first element, but a later one, so just
 474             // send it on its way.
 475             m_handler.startElement(namespaceURI, localName, elementName,  atts);
 476         }
 477     }
 478 
 479     /**
 480      * Pass the call on to the underlying handler
 481      * @see ExtendedLexicalHandler#comment(String)
 482      */
 483     public void comment(String comment) throws SAXException
 484     {
 485         if (m_firstTagNotEmitted && m_firstElementName != null)
 486         {
 487             emitFirstTag();
 488         }
 489         else if (m_needToCallStartDocument)
 490         {
 491             m_handler.startDocument();
 492             m_needToCallStartDocument = false;
 493         }
 494 
 495         m_handler.comment(comment);
 496     }
 497 
 498     /**
 499      * Pass the call on to the underlying handler
 500      * @see XSLOutputAttributes#getDoctypePublic()
 501      */
 502     public String getDoctypePublic()
 503     {
 504 
 505         return m_handler.getDoctypePublic();
 506     }
 507 
 508     /**
 509      * Pass the call on to the underlying handler
 510      * @see XSLOutputAttributes#getDoctypeSystem()
 511      */
 512     public String getDoctypeSystem()
 513     {
 514         return m_handler.getDoctypeSystem();
 515     }
 516 
 517     /**
 518      * Pass the call on to the underlying handler
 519      * @see XSLOutputAttributes#getEncoding()
 520      */
 521     public String getEncoding()
 522     {
 523         return m_handler.getEncoding();
 524     }
 525 
 526     /**
 527      * Pass the call on to the underlying handler
 528      * @see XSLOutputAttributes#getIndent()
 529      */
 530     public boolean getIndent()
 531     {
 532         return m_handler.getIndent();
 533     }
 534 
 535     /**
 536      * Pass the call on to the underlying handler
 537      * @see XSLOutputAttributes#getIndentAmount()
 538      */
 539     public int getIndentAmount()
 540     {
 541         return m_handler.getIndentAmount();
 542     }
 543 
 544     /**
 545      * Pass the call on to the underlying handler
 546      * @see XSLOutputAttributes#getMediaType()
 547      */
 548     public String getMediaType()
 549     {
 550         return m_handler.getMediaType();
 551     }
 552 
 553     /**
 554      * Pass the call on to the underlying handler
 555      * @see XSLOutputAttributes#getOmitXMLDeclaration()
 556      */
 557     public boolean getOmitXMLDeclaration()
 558     {
 559         return m_handler.getOmitXMLDeclaration();
 560     }
 561 
 562     /**
 563      * Pass the call on to the underlying handler
 564      * @see XSLOutputAttributes#getStandalone()
 565      */
 566     public String getStandalone()
 567     {
 568         return m_handler.getStandalone();
 569     }
 570 
 571     /**
 572      * Pass the call on to the underlying handler
 573      * @see XSLOutputAttributes#getVersion()
 574      */
 575     public String getVersion() {
 576         return m_handler.getVersion();
 577     }
 578 
 579     /**
 580      * @see XSLOutputAttributes#setDoctype(String, String)
 581      */
 582     public void setDoctype(String system, String pub) {
 583         m_handler.setDoctypePublic(pub);
 584         m_handler.setDoctypeSystem(system);
 585     }
 586 
 587     /**
 588      * Set the doctype in the underlying XML handler. Remember that this method
 589      * was called, just in case we need to transfer this doctype to an HTML handler
 590      * @param doctype the public doctype to set
 591      * @see XSLOutputAttributes#setDoctypePublic(String)
 592      */
 593     public void setDoctypePublic(String doctype) {
 594         m_handler.setDoctypePublic(doctype);
 595     }
 596 
 597     /**
 598      * Set the doctype in the underlying XML handler. Remember that this method
 599      * was called, just in case we need to transfer this doctype to an HTML handler
 600      * @param doctype the system doctype to set
 601      * @see XSLOutputAttributes#setDoctypeSystem(String)
 602      */
 603     public void setDoctypeSystem(String doctype) {
 604         m_handler.setDoctypeSystem(doctype);
 605     }
 606 
 607     /**
 608      * Pass the call on to the underlying handler
 609      * @see XSLOutputAttributes#setEncoding(String)
 610      */
 611     public void setEncoding(String encoding) {
 612         m_handler.setEncoding(encoding);
 613     }
 614 
 615     /**
 616      * Pass the call on to the underlying handler
 617      * @see XSLOutputAttributes#setIndent(boolean)
 618      */
 619     public void setIndent(boolean indent) {
 620         m_handler.setIndent(indent);
 621     }
 622 
 623     /**
 624      * Pass the call on to the underlying handler
 625      */
 626     public void setIndentAmount(int value) {
 627         m_handler.setIndentAmount(value);
 628     }
 629 
 630     /**
 631      * @see XSLOutputAttributes#setMediaType(String)
 632      */
 633     public void setMediaType(String mediaType) {
 634         m_handler.setMediaType(mediaType);
 635     }
 636 
 637     /**
 638      * Pass the call on to the underlying handler
 639      * @see XSLOutputAttributes#setOmitXMLDeclaration(boolean)
 640      */
 641     public void setOmitXMLDeclaration(boolean b) {
 642         m_handler.setOmitXMLDeclaration(b);
 643     }
 644 
 645     /**
 646      * Pass the call on to the underlying handler
 647      * @see XSLOutputAttributes#setStandalone(String)
 648      */
 649     public void setStandalone(String standalone) {
 650         m_handler.setStandalone(standalone);
 651     }
 652 
 653     /**
 654      * Pass the call on to the underlying handler
 655      * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
 656      */
 657     public void attributeDecl(String arg0, String arg1, String arg2,
 658                               String arg3, String arg4) throws SAXException {
 659         m_handler.attributeDecl(arg0, arg1, arg2, arg3, arg4);
 660     }
 661 
 662     /**
 663      * Pass the call on to the underlying handler
 664      * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
 665      */
 666     public void elementDecl(String arg0, String arg1) throws SAXException
 667     {
 668         if (m_firstTagNotEmitted) {
 669             emitFirstTag();
 670         }
 671         m_handler.elementDecl(arg0, arg1);
 672     }
 673 
 674     /**
 675      * Pass the call on to the underlying handler
 676      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
 677      */
 678     public void externalEntityDecl(
 679         String name,
 680         String publicId,
 681         String systemId)
 682         throws SAXException
 683     {
 684         if (m_firstTagNotEmitted) {
 685             flush();
 686         }
 687         m_handler.externalEntityDecl(name, publicId, systemId);
 688     }
 689 
 690     /**
 691      * Pass the call on to the underlying handler
 692      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
 693      */
 694     public void internalEntityDecl(String arg0, String arg1)
 695         throws SAXException
 696     {
 697         if (m_firstTagNotEmitted) {
 698             flush();
 699         }
 700         m_handler.internalEntityDecl(arg0, arg1);
 701     }
 702 
 703     /**
 704      * Pass the call on to the underlying handler
 705      * @see org.xml.sax.ContentHandler#characters(char[], int, int)
 706      */
 707     public void characters(char[] characters, int offset, int length)
 708         throws SAXException
 709     {
 710         if (m_firstTagNotEmitted) {
 711             flush();
 712         }
 713         m_handler.characters(characters, offset, length);
 714     }
 715 
 716     /**
 717      * Pass the call on to the underlying handler
 718      * @see org.xml.sax.ContentHandler#endDocument()
 719      */
 720     public void endDocument() throws SAXException {
 721         if (m_firstTagNotEmitted) {
 722             flush();
 723         }
 724         m_handler.endDocument();
 725     }
 726 
 727     /**
 728      * Pass the call on to the underlying handler
 729      * @see org.xml.sax.ContentHandler#endElement(String, String, String)
 730      */
 731     public void endElement(String namespaceURI, String localName, String qName)
 732         throws SAXException
 733     {
 734         if (m_firstTagNotEmitted) {
 735             flush();
 736             if (namespaceURI == null && m_firstElementURI != null)
 737                 namespaceURI = m_firstElementURI;
 738 
 739             if (localName == null && m_firstElementLocalName != null)
 740                 localName = m_firstElementLocalName;
 741         }
 742         m_handler.endElement(namespaceURI, localName, qName);
 743     }
 744 
 745     /**
 746      * Pass the call on to the underlying handler
 747      * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
 748      */
 749     public void endPrefixMapping(String prefix) throws SAXException {
 750         m_handler.endPrefixMapping(prefix);
 751     }
 752 
 753     /**
 754      * Pass the call on to the underlying handler
 755      * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
 756      */
 757     public void ignorableWhitespace(char[] ch, int start, int length)
 758         throws SAXException
 759     {
 760         if (m_firstTagNotEmitted)
 761         {
 762             flush();
 763         }
 764         m_handler.ignorableWhitespace(ch, start, length);
 765     }
 766 
 767     /**
 768      * Pass the call on to the underlying handler
 769      * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
 770      */
 771     public void processingInstruction(String target, String data)
 772         throws SAXException
 773     {
 774           if (m_firstTagNotEmitted)
 775         {
 776             flush();
 777         }
 778 
 779         m_handler.processingInstruction(target, data);
 780     }
 781 
 782     /**
 783      * Pass the call on to the underlying handler
 784      * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
 785      */
 786     public void setDocumentLocator(Locator locator)
 787     {
 788         super.setDocumentLocator(locator);
 789         m_handler.setDocumentLocator(locator);
 790     }
 791 
 792     /**
 793      * Pass the call on to the underlying handler
 794      * @see org.xml.sax.ContentHandler#skippedEntity(String)
 795      */
 796     public void skippedEntity(String name) throws SAXException
 797     {
 798         m_handler.skippedEntity(name);
 799     }
 800 
 801 
 802 
 803     /**
 804      * Pass the call on to the underlying handler
 805      * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
 806      */
 807     public void comment(char[] ch, int start, int length) throws SAXException
 808     {
 809         if (m_firstTagNotEmitted)
 810         {
 811             flush();
 812         }
 813 
 814         m_handler.comment(ch, start, length);
 815     }
 816 
 817     /**
 818      * Pass the call on to the underlying handler
 819      * @see org.xml.sax.ext.LexicalHandler#endCDATA()
 820      */
 821     public void endCDATA() throws SAXException
 822     {
 823 
 824         m_handler.endCDATA();
 825     }
 826 
 827     /**
 828      * Pass the call on to the underlying handler
 829      * @see org.xml.sax.ext.LexicalHandler#endDTD()
 830      */
 831     public void endDTD() throws SAXException
 832     {
 833 
 834         m_handler.endDTD();
 835     }
 836 
 837     /**
 838      * Pass the call on to the underlying handler
 839      * @see org.xml.sax.ext.LexicalHandler#endEntity(String)
 840      */
 841     public void endEntity(String name) throws SAXException
 842     {
 843         if (m_firstTagNotEmitted)
 844         {
 845             emitFirstTag();
 846         }
 847         m_handler.endEntity(name);
 848     }
 849 
 850     /**
 851      * Pass the call on to the underlying handler
 852      * @see org.xml.sax.ext.LexicalHandler#startCDATA()
 853      */
 854     public void startCDATA() throws SAXException
 855     {
 856         m_handler.startCDATA();
 857     }
 858 
 859     /**
 860      * Pass the call on to the underlying handler
 861      * @see org.xml.sax.ext.LexicalHandler#startDTD(String, String, String)
 862      */
 863     public void startDTD(String name, String publicId, String systemId)
 864         throws SAXException
 865     {
 866         m_handler.startDTD(name, publicId, systemId);
 867     }
 868 
 869     /**
 870      * Pass the call on to the underlying handler
 871      * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
 872      */
 873     public void startEntity(String name) throws SAXException
 874     {
 875         m_handler.startEntity(name);
 876     }
 877 
 878     /**
 879      * Initialize the wrapped output stream (XML or HTML).
 880      * If the stream handler should be HTML, then replace the XML handler with
 881      * an HTML handler. After than send the starting method calls that were cached
 882      * to the wrapped handler.
 883      *
 884      */
 885     private void initStreamOutput() throws SAXException
 886     {
 887 
 888         // Try to rule out if this is an not to be an HTML document based on prefix
 889         boolean firstElementIsHTML = isFirstElemHTML();
 890 
 891         if (firstElementIsHTML)
 892         {
 893             // create an HTML output handler, and initialize it
 894 
 895             // keep a reference to the old handler, ... it will soon be gone
 896             SerializationHandler oldHandler = m_handler;
 897 
 898             /* We have to make sure we get an output properties with the proper
 899              * defaults for the HTML method.  The easiest way to do this is to
 900              * have the OutputProperties class do it.
 901              */
 902 
 903             Properties htmlProperties =
 904                 OutputPropertiesFactory.getDefaultMethodProperties(Method.HTML);
 905             Serializer serializer =
 906                 SerializerFactory.getSerializer(htmlProperties);
 907 
 908             // The factory should be returning a ToStream
 909             // Don't know what to do if it doesn't
 910             // i.e. the user has over-ridden the content-handler property
 911             // for html
 912             m_handler = (SerializationHandler) serializer;
 913             //m_handler = new ToHTMLStream();
 914 
 915             Writer writer = oldHandler.getWriter();
 916 
 917             if (null != writer)
 918                 m_handler.setWriter(writer);
 919             else
 920             {
 921                 OutputStream os = oldHandler.getOutputStream();
 922 
 923                 if (null != os)
 924                     m_handler.setOutputStream(os);
 925             }
 926 
 927             // need to copy things from the old handler to the new one here
 928 
 929             //            if (_setVersion_called)
 930             //            {
 931             m_handler.setVersion(oldHandler.getVersion());
 932             //            }
 933             //            if (_setDoctypeSystem_called)
 934             //            {
 935             m_handler.setDoctypeSystem(oldHandler.getDoctypeSystem());
 936             //            }
 937             //            if (_setDoctypePublic_called)
 938             //            {
 939             m_handler.setDoctypePublic(oldHandler.getDoctypePublic());
 940             //            }
 941             //            if (_setMediaType_called)
 942             //            {
 943             m_handler.setMediaType(oldHandler.getMediaType());
 944             //            }
 945 
 946             m_handler.setTransformer(oldHandler.getTransformer());
 947         }
 948 
 949         /* Now that we have a real wrapped handler (XML or HTML) lets
 950          * pass any cached calls to it
 951          */
 952         // Call startDocument() if necessary
 953         if (m_needToCallStartDocument)
 954         {
 955             m_handler.startDocument();
 956             m_needToCallStartDocument = false;
 957         }
 958 
 959         // the wrapped handler is now fully initialized
 960         m_wrapped_handler_not_initialized = false;
 961     }
 962 
 963     private void emitFirstTag() throws SAXException {
 964         if (m_firstElementName != null) {
 965             if (m_wrapped_handler_not_initialized) {
 966                 initStreamOutput();
 967                 m_wrapped_handler_not_initialized = false;
 968             }
 969             // Output first tag
 970             m_handler.startElement(m_firstElementURI, null, m_firstElementName, m_attributes);
 971             // don't need the collected attributes of the first element anymore.
 972             m_attributes = null;
 973 
 974             // Output namespaces of first tag
 975             if (m_namespacePrefix != null) {
 976                 final int n = m_namespacePrefix.size();
 977                 for (int i = 0; i < n; i++) {
 978                     final String prefix = m_namespacePrefix.get(i);
 979                     final String uri = m_namespaceURI.get(i);
 980                     m_handler.startPrefixMapping(prefix, uri, false);
 981                 }
 982                 m_namespacePrefix = null;
 983                 m_namespaceURI = null;
 984             }
 985             m_firstTagNotEmitted = false;
 986         }
 987     }
 988 
 989     /**
 990      * Utility function for calls to local-name().
 991      *
 992      * Don't want to override static function on SerializerBase
 993      * So added Unknown suffix to method name.
 994      */
 995     private String getLocalNameUnknown(String value) {
 996         int idx = value.lastIndexOf(':');
 997         if (idx >= 0)
 998             value = value.substring(idx + 1);
 999         idx = value.lastIndexOf('@');
1000         if (idx >= 0)
1001             value = value.substring(idx + 1);
1002         return (value);
1003     }
1004 
1005     /**
1006      * Utility function to return prefix
1007      *
1008      * Don't want to override static function on SerializerBase
1009      * So added Unknown suffix to method name.
1010      */
1011     private String getPrefixPartUnknown(String qname) {
1012         final int index = qname.indexOf(':');
1013         return (index > 0) ? qname.substring(0, index) : EMPTYSTRING;
1014     }
1015 
1016     /**
1017      * Determine if the firts element in the document is <html> or <HTML>
1018      * This uses the cached first element name, first element prefix and the
1019      * cached namespaces from previous method calls
1020      *
1021      * @return true if the first element is an opening <html> tag
1022      */
1023     private boolean isFirstElemHTML() {
1024         boolean isHTML;
1025 
1026         // is the first tag html, not considering the prefix ?
1027         isHTML =
1028             getLocalNameUnknown(m_firstElementName).equalsIgnoreCase("html");
1029 
1030         // Try to rule out if this is not to be an HTML document based on URI
1031         if (isHTML &&
1032             m_firstElementURI != null &&
1033             !EMPTYSTRING.equals(m_firstElementURI))
1034         {
1035             // the <html> element has a non-trivial namespace
1036             isHTML = false;
1037         }
1038         // Try to rule out if this is an not to be an HTML document based on prefix
1039         if (isHTML && m_namespacePrefix != null) {
1040             /* the first element has a name of "html", but lets check the prefix.
1041              * If the prefix points to a namespace with a URL that is not ""
1042              * then the doecument doesn't start with an <html> tag, and isn't html
1043              */
1044             final int max = m_namespacePrefix.size();
1045             for (int i = 0; i < max; i++) {
1046                 final String prefix = m_namespacePrefix.get(i);
1047                 final String uri = m_namespaceURI.get(i);
1048 
1049                 if (m_firstElementPrefix != null &&
1050                     m_firstElementPrefix.equals(prefix) &&
1051                     !EMPTYSTRING.equals(uri))
1052                 {
1053                     // The first element has a prefix, so it can't be <html>
1054                     isHTML = false;
1055                     break;
1056                 }
1057             }
1058 
1059         }
1060         return isHTML;
1061     }
1062 
1063     /**
1064      * @see Serializer#asDOMSerializer()
1065      */
1066     public DOMSerializer asDOMSerializer() throws IOException {
1067         return m_handler.asDOMSerializer();
1068     }
1069 
1070     /**
1071      * @param URI_and_localNames a list of pairs of URI/localName
1072      * specified in the cdata-section-elements attribute.
1073      * @see SerializationHandler#setCdataSectionElements(List)
1074      */
1075     public void setCdataSectionElements(List<String> URI_and_localNames) {
1076         m_handler.setCdataSectionElements(URI_and_localNames);
1077     }
1078 
1079     /**
1080      * @see ExtendedContentHandler#addAttributes(org.xml.sax.Attributes)
1081      */
1082     public void addAttributes(Attributes atts) throws SAXException {
1083         m_handler.addAttributes(atts);
1084     }
1085 
1086     /**
1087      * Get the current namespace mappings.
1088      * Simply returns the mappings of the wrapped handler.
1089      * @see ExtendedContentHandler#getNamespaceMappings()
1090      */
1091     public NamespaceMappings getNamespaceMappings() {
1092         NamespaceMappings mappings = null;
1093         if (m_handler != null) {
1094             mappings = m_handler.getNamespaceMappings();
1095         }
1096         return mappings;
1097     }
1098 
1099     /**
1100      * @see SerializationHandler#flushPending()
1101      */
1102     public void flushPending() throws SAXException {
1103         flush();
1104         m_handler.flushPending();
1105     }
1106 
1107     private void flush() {
1108         try {
1109             if (m_firstTagNotEmitted) {
1110                 emitFirstTag();
1111             }
1112             if (m_needToCallStartDocument) {
1113                 m_handler.startDocument();
1114                 m_needToCallStartDocument = false;
1115             }
1116         } catch(SAXException e) {
1117             throw new RuntimeException(e.toString());
1118         }
1119     }
1120 
1121     /**
1122      * @see ExtendedContentHandler#getPrefix
1123      */
1124     public String getPrefix(String namespaceURI) {
1125         return m_handler.getPrefix(namespaceURI);
1126     }
1127 
1128     /**
1129      * @see ExtendedContentHandler#entityReference(java.lang.String)
1130      */
1131     public void entityReference(String entityName) throws SAXException {
1132         m_handler.entityReference(entityName);
1133     }
1134 
1135     /**
1136      * @see ExtendedContentHandler#getNamespaceURI(java.lang.String, boolean)
1137      */
1138     public String getNamespaceURI(String qname, boolean isElement) {
1139         return m_handler.getNamespaceURI(qname, isElement);
1140     }
1141 
1142     public String getNamespaceURIFromPrefix(String prefix) {
1143         return m_handler.getNamespaceURIFromPrefix(prefix);
1144     }
1145 
1146     public void setTransformer(Transformer t) {
1147         m_handler.setTransformer(t);
1148         if ((t instanceof SerializerTrace) &&
1149             (((SerializerTrace) t).hasTraceListeners()))
1150         {
1151             m_tracer = (SerializerTrace) t;
1152         } else {
1153             m_tracer = null;
1154         }
1155     }
1156 
1157     public Transformer getTransformer() {
1158         return m_handler.getTransformer();
1159     }
1160 
1161     /**
1162      * @see SerializationHandler#setContentHandler(org.xml.sax.ContentHandler)
1163      */
1164     public void setContentHandler(ContentHandler ch) {
1165         m_handler.setContentHandler(ch);
1166     }
1167 
1168     /**
1169      * This method is used to set the source locator, which might be used to
1170      * generated an error message.
1171      * @param locator the source locator
1172      *
1173      * @see ExtendedContentHandler#setSourceLocator(javax.xml.transform.SourceLocator)
1174      */
1175     public void setSourceLocator(SourceLocator locator) {
1176         m_handler.setSourceLocator(locator);
1177     }
1178 
1179     protected void firePseudoElement(String elementName) {
1180         if (m_tracer != null) {
1181             StringBuffer sb = new StringBuffer();
1182 
1183             sb.append('<');
1184             sb.append(elementName);
1185 
1186             // convert the StringBuffer to a char array and
1187             // emit the trace event that these characters "might"
1188             // be written
1189             char ch[] = sb.toString().toCharArray();
1190             m_tracer.fireGenerateEvent(
1191                 SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
1192                 ch,
1193                 0,
1194                 ch.length);
1195         }
1196     }
1197 
1198     /**
1199      * @see org.apache.xml.serializer.Serializer#asDOM3Serializer()
1200      */
1201     public Object asDOM3Serializer() throws IOException
1202     {
1203         return m_handler.asDOM3Serializer();
1204     }
1205 }