View Javadoc

    1   /*
    2    * Copyright 2002,2004 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16
    17  package org.apache.commons.jelly;
    18
    19  import java.io.IOException;
    20  import java.io.OutputStream;
    21  import java.io.UnsupportedEncodingException;
    22  import java.io.Writer;
    23
    24  import org.apache.commons.logging.Log;
    25  import org.apache.commons.logging.LogFactory;
    26  import org.dom4j.io.XMLWriter;
    27  import org.xml.sax.Attributes;
    28  import org.xml.sax.ContentHandler;
    29  import org.xml.sax.Locator;
    30  import org.xml.sax.SAXException;
    31  import org.xml.sax.XMLReader;
    32  import org.xml.sax.ext.LexicalHandler;
    33  import org.xml.sax.helpers.AttributesImpl;
    34  import org.xml.sax.helpers.DefaultHandler;
    35
    36  /*** <p><code>XMLOutput</code> is used to output XML events
    37    * in a SAX-like manner. This also allows pipelining to be done
    38    * such as in the <a href="http://xml.apache.org/cocoon/">Cocoon</a> project.</p>
    39    *
    40    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
    41    * @version $Revision: 155420 $
    42    */
    43
    44  public class XMLOutput implements ContentHandler, LexicalHandler {
    45
    46      protected static final String[] LEXICAL_HANDLER_NAMES =
    47          {
    48              "http://xml.org/sax/properties/lexical-handler",
    49              "http://xml.org/sax/handlers/LexicalHandler" };
    50
    51      /*** empty attributes */
    52      private static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl();
    53
    54      /*** The Log to which logging calls will be made. */
    55      private static final Log log = LogFactory.getLog(XMLOutput.class);
    56
    57      /*** the default for escaping of text */
    58      private static final boolean DEFAULT_ESCAPE_TEXT = false;
    59
    60      /*** The SAX ContentHandler that output goes to */
    61      private ContentHandler contentHandler;
    62
    63      /*** The SAX LexicalHandler that output goes to */
    64      private LexicalHandler lexicalHandler;
    65
    66
    67      public XMLOutput() {
    68      }
    69
    70      public XMLOutput(ContentHandler contentHandler) {
    71          this.contentHandler = contentHandler;
    72          // often classes will implement LexicalHandler as well
    73          if (contentHandler instanceof LexicalHandler) {
    74              this.lexicalHandler = (LexicalHandler) contentHandler;
    75          }
    76      }
    77
    78      public XMLOutput(
    79          ContentHandler contentHandler,
    80          LexicalHandler lexicalHandler) {
    81          this.contentHandler = contentHandler;
    82          this.lexicalHandler = lexicalHandler;
    83      }
    84
    85      public String toString() {
    86          return super.toString()
    87              + "[contentHandler="
    88              + contentHandler
    89              + ";lexicalHandler="
    90              + lexicalHandler
    91              + "]";
    92      }
    93
    94      /***
    95       * Provides a useful hook that implementations can use to close the
    96       * underlying OutputStream or Writer
    97       */
    98      public void close() throws IOException {
    99      }
    100
    101     public void flush() throws IOException {
    102         if( contentHandler instanceof XMLWriter )
    103         {
    104             ((XMLWriter)contentHandler).flush();
    105         }
    106     }
    107
    108     // Static helper methods
    109     //-------------------------------------------------------------------------
    110
    111     /***
    112      * Creates an XMLOutput from an existing SAX XMLReader
    113      */
    114     public static XMLOutput createXMLOutput(XMLReader xmlReader) {
    115         XMLOutput output = new XMLOutput(xmlReader.getContentHandler());
    116
    117         // isn't it lovely what we've got to do to find the LexicalHandler... ;-)
    118         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
    119             try {
    120                 Object value = xmlReader.getProperty(LEXICAL_HANDLER_NAMES[i]);
    121                 if (value instanceof LexicalHandler) {
    122                     output.setLexicalHandler((LexicalHandler) value);
    123                     break;
    124                 }
    125             }
    126             catch (Exception e) {
    127                 // ignore any unsupported-operation exceptions
    128                 if (log.isDebugEnabled()) log.debug("error setting lexical handler properties", e);
    129             }
    130         }
    131         return output;
    132     }
    133
    134     /***
    135      * Creates a text based XMLOutput which converts all XML events into
    136      * text and writes to the underlying Writer.
    137      */
    138     public static XMLOutput createXMLOutput(Writer writer) {
    139         return createXMLOutput(writer, DEFAULT_ESCAPE_TEXT);
    140     }
    141
    142     /***
    143      * Creates a text based XMLOutput which converts all XML events into
    144      * text and writes to the underlying Writer.
    145      *
    146      * @param writer is the writer to output to
    147      * @param escapeText is whether or not text output will be escaped. This must be true
    148      * if the underlying output is XML or could be false if the underlying output is textual.
    149      */
    150     public static XMLOutput createXMLOutput(Writer writer, boolean escapeText)
    151     {
    152         XMLWriter xmlWriter = new XMLWriter(writer);
    153         xmlWriter.setEscapeText(escapeText);
    154         return createXMLOutput(xmlWriter);
    155     }
    156
    157     /***
    158      * Creates a text based XMLOutput which converts all XML events into
    159      * text and writes to the underlying OutputStream.
    160      */
    161     public static XMLOutput createXMLOutput(OutputStream out) throws UnsupportedEncodingException {
    162         return createXMLOutput(out, DEFAULT_ESCAPE_TEXT);
    163     }
    164
    165     /***
    166      * Creates a text based XMLOutput which converts all XML events into
    167      * text and writes to the underlying OutputStream.
    168      *
    169      * @param out is the output stream to write
    170      * @param escapeText is whether or not text output will be escaped. This must be true
    171      * if the underlying output is XML or could be false if the underlying output is textual.
    172      */
    173     public static XMLOutput createXMLOutput(OutputStream out, boolean escapeText) throws UnsupportedEncodingException {
    174         XMLWriter xmlWriter = new XMLWriter(out);
    175         xmlWriter.setEscapeText(escapeText);
    176         return createXMLOutput(xmlWriter);
    177     }
    178
    179     /***
    180      * returns an XMLOutput object that will discard all
    181      * tag-generated XML events.  Useful when tag output is not expected
    182      * or not significant.
    183      *
    184      * @return a no-op XMLOutput
    185      */
    186     public static XMLOutput createDummyXMLOutput() {
    187         return new XMLOutput(new DefaultHandler());
    188     }
    189
    190     // Extra helper methods provided for tag authors
    191     //-------------------------------------------------------------------------
    192
    193     /***
    194      * Outputs the given String as a piece of valid text in the
    195      * XML event stream.
    196      * Any special XML characters should be properly escaped.
    197      */
    198     public void write(String text) throws SAXException {
    199         char[] ch = text.toCharArray();
    200         characters(ch, 0, ch.length);
    201     }
    202
    203     /***
    204      * Outputs the given String as a piece of CDATA in the
    205      * XML event stream.
    206      */
    207     public void writeCDATA(String text) throws SAXException {
    208         startCDATA();
    209         char[] ch = text.toCharArray();
    210         characters(ch, 0, ch.length);
    211         endCDATA();
    212     }
    213
    214     /***
    215      * Outputs a comment to the XML stream
    216      */
    217     public void writeComment(String text) throws SAXException {
    218         char[] ch = text.toCharArray();
    219         comment(ch, 0, ch.length);
    220     }
    221
    222     /***
    223      * Helper method for outputting a start element event for an element in no namespace
    224      */
    225     public void startElement(String localName) throws SAXException {
    226         startElement("", localName, localName, EMPTY_ATTRIBUTES);
    227     }
    228
    229     /***
    230      * Helper method for outputting a start element event for an element in no namespace
    231      */
    232     public void startElement(String localName, Attributes attributes) throws SAXException {
    233         startElement("", localName, localName, attributes);
    234     }
    235
    236     /***
    237      * Helper method for outputting an end element event for an element in no namespace
    238      */
    239     public void endElement(String localName) throws SAXException {
    240         endElement("", localName, localName);
    241     }
    242
    243
    244     // ContentHandler interface
    245     //-------------------------------------------------------------------------
    246
    247     /***
    248      * Receive an object for locating the origin of SAX document events.
    249      *
    250      * <p>SAX parsers are strongly encouraged (though not absolutely
    251      * required) to supply a locator: if it does so, it must supply
    252      * the locator to the application by invoking this method before
    253      * invoking any of the other methods in the ContentHandler
    254      * interface.</p>
    255      *
    256      * <p>The locator allows the application to determine the end
    257      * position of any document-related event, even if the parser is
    258      * not reporting an error.  Typically, the application will
    259      * use this information for reporting its own errors (such as
    260      * character content that does not match an application's
    261      * business rules).  The information returned by the locator
    262      * is probably not sufficient for use with a search engine.</p>
    263      *
    264      * <p>Note that the locator will return correct information only
    265      * during the invocation of the events in this interface.  The
    266      * application should not attempt to use it at any other time.</p>
    267      *
    268      * @param locator An object that can return the location of
    269      *                any SAX document event.
    270      * @see org.xml.sax.Locator
    271      */
    272     public void setDocumentLocator(Locator locator) {
    273         contentHandler.setDocumentLocator(locator);
    274     }
    275
    276     /***
    277      * Receive notification of the beginning of a document.
    278      *
    279      * <p>The SAX parser will invoke this method only once, before any
    280      * other event callbacks (except for {@link #setDocumentLocator
    281      * setDocumentLocator}).</p>
    282      *
    283      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    284      *            wrapping another exception.
    285      * @see #endDocument
    286      */
    287     public void startDocument() throws SAXException {
    288         contentHandler.startDocument();
    289     }
    290
    291     /***
    292      * Receive notification of the end of a document.
    293      *
    294      * <p>The SAX parser will invoke this method only once, and it will
    295      * be the last method invoked during the parse.  The parser shall
    296      * not invoke this method until it has either abandoned parsing
    297      * (because of an unrecoverable error) or reached the end of
    298      * input.</p>
    299      *
    300      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    301      *            wrapping another exception.
    302      * @see #startDocument
    303      */
    304     public void endDocument() throws SAXException {
    305         contentHandler.endDocument();
    306     }
    307
    308     /***
    309      * Begin the scope of a prefix-URI Namespace mapping.
    310      *
    311      * <p>The information from this event is not necessary for
    312      * normal Namespace processing: the SAX XML reader will
    313      * automatically replace prefixes for element and attribute
    314      * names when the <code>http://xml.org/sax/features/namespaces</code>
    315      * feature is <var>true</var> (the default).</p>
    316      *
    317      * <p>There are cases, however, when applications need to
    318      * use prefixes in character data or in attribute values,
    319      * where they cannot safely be expanded automatically; the
    320      * start/endPrefixMapping event supplies the information
    321      * to the application to expand prefixes in those contexts
    322      * itself, if necessary.</p>
    323      *
    324      * <p>Note that start/endPrefixMapping events are not
    325      * guaranteed to be properly nested relative to each other:
    326      * all startPrefixMapping events will occur immediately before the
    327      * corresponding {@link #startElement startElement} event,
    328      * and all {@link #endPrefixMapping endPrefixMapping}
    329      * events will occur immediately after the corresponding
    330      * {@link #endElement endElement} event,
    331      * but their order is not otherwise
    332      * guaranteed.</p>
    333      *
    334      * <p>There should never be start/endPrefixMapping events for the
    335      * "xml" prefix, since it is predeclared and immutable.</p>
    336      *
    337      * @param prefix The Namespace prefix being declared.
    338      *  An empty string is used for the default element namespace,
    339      *  which has no prefix.
    340      * @param uri The Namespace URI the prefix is mapped to.
    341      * @exception org.xml.sax.SAXException The client may throw
    342      *            an exception during processing.
    343      * @see #endPrefixMapping
    344      * @see #startElement
    345      */
    346     public void startPrefixMapping(String prefix, String uri) throws SAXException {
    347         contentHandler.startPrefixMapping(prefix, uri);
    348     }
    349
    350     /***
    351      * End the scope of a prefix-URI mapping.
    352      *
    353      * <p>See {@link #startPrefixMapping startPrefixMapping} for
    354      * details.  These events will always occur immediately after the
    355      * corresponding {@link #endElement endElement} event, but the order of
    356      * {@link #endPrefixMapping endPrefixMapping} events is not otherwise
    357      * guaranteed.</p>
    358      *
    359      * @param prefix The prefix that was being mapped.
    360      *  This is the empty string when a default mapping scope ends.
    361      * @exception org.xml.sax.SAXException The client may throw
    362      *            an exception during processing.
    363      * @see #startPrefixMapping
    364      * @see #endElement
    365      */
    366     public void endPrefixMapping(String prefix) throws SAXException {
    367         contentHandler.endPrefixMapping(prefix);
    368     }
    369
    370     /***
    371      * Receive notification of the beginning of an element.
    372      *
    373      * <p>The Parser will invoke this method at the beginning of every
    374      * element in the XML document; there will be a corresponding
    375      * {@link #endElement endElement} event for every startElement event
    376      * (even when the element is empty). All of the element's content will be
    377      * reported, in order, before the corresponding endElement
    378      * event.</p>
    379      *
    380      * <p>This event allows up to three name components for each
    381      * element:</p>
    382      *
    383      * <ol>
    384      * <li>the Namespace URI;</li>
    385      * <li>the local name; and</li>
    386      * <li>the qualified (prefixed) name.</li>
    387      * </ol>
    388      *
    389      * <p>Any or all of these may be provided, depending on the
    390      * values of the <var>http://xml.org/sax/features/namespaces</var>
    391      * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
    392      * properties:</p>
    393      *
    394      * <ul>
    395      * <li>the Namespace URI and local name are required when
    396      * the namespaces property is <var>true</var> (the default), and are
    397      * optional when the namespaces property is <var>false</var> (if one is
    398      * specified, both must be);</li>
    399      * <li>the qualified name is required when the namespace-prefixes property
    400      * is <var>true</var>, and is optional when the namespace-prefixes property
    401      * is <var>false</var> (the default).</li>
    402      * </ul>
    403      *
    404      * <p>Note that the attribute list provided will contain only
    405      * attributes with explicit values (specified or defaulted):
    406      * #IMPLIED attributes will be omitted.  The attribute list
    407      * will contain attributes used for Namespace declarations
    408      * (xmlns* attributes) only if the
    409      * <code>http://xml.org/sax/features/namespace-prefixes</code>
    410      * property is true (it is false by default, and support for a
    411      * true value is optional).</p>
    412      *
    413      * <p>Like {@link #characters characters()}, attribute values may have
    414      * characters that need more than one <code>char</code> value.  </p>
    415      *
    416      * @param uri The Namespace URI, or the empty string if the
    417      *        element has no Namespace URI or if Namespace
    418      *        processing is not being performed.
    419      * @param localName The local name (without prefix), or the
    420      *        empty string if Namespace processing is not being
    421      *        performed.
    422      * @param qName The qualified name (with prefix), or the
    423      *        empty string if qualified names are not available.
    424      * @param atts The attributes attached to the element.  If
    425      *        there are no attributes, it shall be an empty
    426      *        Attributes object.
    427      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    428      *            wrapping another exception.
    429      * @see #endElement
    430      * @see org.xml.sax.Attributes
    431      */
    432     public void startElement(
    433         String uri,
    434         String localName,
    435         String qName,
    436         Attributes atts)
    437         throws SAXException {
    438         contentHandler.startElement(uri, localName, qName, atts);
    439     }
    440
    441     /***
    442      * Receive notification of the end of an element.
    443      *
    444      * <p>The SAX parser will invoke this method at the end of every
    445      * element in the XML document; there will be a corresponding
    446      * {@link #startElement startElement} event for every endElement
    447      * event (even when the element is empty).</p>
    448      *
    449      * <p>For information on the names, see startElement.</p>
    450      *
    451      * @param uri The Namespace URI, or the empty string if the
    452      *        element has no Namespace URI or if Namespace
    453      *        processing is not being performed.
    454      * @param localName The local name (without prefix), or the
    455      *        empty string if Namespace processing is not being
    456      *        performed.
    457      * @param qName The qualified XML 1.0 name (with prefix), or the
    458      *        empty string if qualified names are not available.
    459      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    460      *            wrapping another exception.
    461      */
    462     public void endElement(String uri, String localName, String qName)
    463         throws SAXException {
    464         contentHandler.endElement(uri, localName, qName);
    465     }
    466
    467     /***
    468      * Receive notification of character data.
    469      *
    470      * <p>The Parser will call this method to report each chunk of
    471      * character data.  SAX parsers may return all contiguous character
    472      * data in a single chunk, or they may split it into several
    473      * chunks; however, all of the characters in any single event
    474      * must come from the same external entity so that the Locator
    475      * provides useful information.</p>
    476      *
    477      * <p>The application must not attempt to read from the array
    478      * outside of the specified range.</p>
    479      *
    480      * <p>Individual characters may consist of more than one Java
    481      * <code>char</code> value.  There are two important cases where this
    482      * happens, because characters can't be represented in just sixteen bits.
    483      * In one case, characters are represented in a <em>Surrogate Pair</em>,
    484      * using two special Unicode values. Such characters are in the so-called
    485      * "Astral Planes", with a code point above U+FFFF.  A second case involves
    486      * composite characters, such as a base character combining with one or
    487      * more accent characters. </p>
    488      *
    489      * <p> Your code should not assume that algorithms using
    490      * <code>char</code>-at-a-time idioms will be working in character
    491      * units; in some cases they will split characters.  This is relevant
    492      * wherever XML permits arbitrary characters, such as attribute values,
    493      * processing instruction data, and comments as well as in data reported
    494      * from this method.  It's also generally relevant whenever Java code
    495      * manipulates internationalized text; the issue isn't unique to XML.</p>
    496      *
    497      * <p>Note that some parsers will report whitespace in element
    498      * content using the {@link #ignorableWhitespace ignorableWhitespace}
    499      * method rather than this one (validating parsers <em>must</em>
    500      * do so).</p>
    501      *
    502      * @param ch The characters from the XML document.
    503      * @param start The start position in the array.
    504      * @param length The number of characters to read from the array.
    505      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    506      *            wrapping another exception.
    507      * @see #ignorableWhitespace
    508      * @see org.xml.sax.Locator
    509      */
    510     public void characters(char ch[], int start, int length) throws SAXException {
    511         contentHandler.characters(ch, start, length);
    512     }
    513
    514     /***
    515      * Receive notification of ignorable whitespace in element content.
    516      *
    517      * <p>Validating Parsers must use this method to report each chunk
    518      * of whitespace in element content (see the W3C XML 1.0 recommendation,
    519      * section 2.10): non-validating parsers may also use this method
    520      * if they are capable of parsing and using content models.</p>
    521      *
    522      * <p>SAX parsers may return all contiguous whitespace in a single
    523      * chunk, or they may split it into several chunks; however, all of
    524      * the characters in any single event must come from the same
    525      * external entity, so that the Locator provides useful
    526      * information.</p>
    527      *
    528      * <p>The application must not attempt to read from the array
    529      * outside of the specified range.</p>
    530      *
    531      * @param ch The characters from the XML document.
    532      * @param start The start position in the array.
    533      * @param length The number of characters to read from the array.
    534      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    535      *            wrapping another exception.
    536      * @see #characters
    537      */
    538     public void ignorableWhitespace(char ch[], int start, int length)
    539         throws SAXException {
    540         contentHandler.ignorableWhitespace(ch, start, length);
    541     }
    542
    543     /***
    544      * Receive notification of a processing instruction.
    545      *
    546      * <p>The Parser will invoke this method once for each processing
    547      * instruction found: note that processing instructions may occur
    548      * before or after the main document element.</p>
    549      *
    550      * <p>A SAX parser must never report an XML declaration (XML 1.0,
    551      * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
    552      * using this method.</p>
    553      *
    554      * <p>Like {@link #characters characters()}, processing instruction
    555      * data may have characters that need more than one <code>char</code>
    556      * value. </p>
    557      *
    558      * @param target The processing instruction target.
    559      * @param data The processing instruction data, or null if
    560      *        none was supplied.  The data does not include any
    561      *        whitespace separating it from the target.
    562      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    563      *            wrapping another exception.
    564      */
    565     public void processingInstruction(String target, String data)
    566         throws SAXException {
    567         contentHandler.processingInstruction(target, data);
    568     }
    569
    570     /***
    571      * Receive notification of a skipped entity.
    572      * This is not called for entity references within markup constructs
    573      * such as element start tags or markup declarations.  (The XML
    574      * recommendation requires reporting skipped external entities.
    575      * SAX also reports internal entity expansion/non-expansion, except
    576      * within markup constructs.)
    577      *
    578      * <p>The Parser will invoke this method each time the entity is
    579      * skipped.  Non-validating processors may skip entities if they
    580      * have not seen the declarations (because, for example, the
    581      * entity was declared in an external DTD subset).  All processors
    582      * may skip external entities, depending on the values of the
    583      * <code>http://xml.org/sax/features/external-general-entities</code>
    584      * and the
    585      * <code>http://xml.org/sax/features/external-parameter-entities</code>
    586      * properties.</p>
    587      *
    588      * @param name The name of the skipped entity.  If it is a
    589      *        parameter entity, the name will begin with '%', and if
    590      *        it is the external DTD subset, it will be the string
    591      *        "[dtd]".
    592      * @exception org.xml.sax.SAXException Any SAX exception, possibly
    593      *            wrapping another exception.
    594      */
    595     public void skippedEntity(String name) throws SAXException {
    596         contentHandler.skippedEntity(name);
    597     }
    598
    599
    600     // Lexical Handler interface
    601     //-------------------------------------------------------------------------
    602
    603     /***
    604      * Report the start of DTD declarations, if any.
    605      *
    606      * <p>This method is intended to report the beginning of the
    607      * DOCTYPE declaration; if the document has no DOCTYPE declaration,
    608      * this method will not be invoked.</p>
    609      *
    610      * <p>All declarations reported through
    611      * {@link org.xml.sax.DTDHandler DTDHandler} or
    612      * {@link org.xml.sax.ext.DeclHandler DeclHandler} events must appear
    613      * between the startDTD and {@link #endDTD endDTD} events.
    614      * Declarations are assumed to belong to the internal DTD subset
    615      * unless they appear between {@link #startEntity startEntity}
    616      * and {@link #endEntity endEntity} events.  Comments and
    617      * processing instructions from the DTD should also be reported
    618      * between the startDTD and endDTD events, in their original
    619      * order of (logical) occurrence; they are not required to
    620      * appear in their correct locations relative to DTDHandler
    621      * or DeclHandler events, however.</p>
    622      *
    623      * <p>Note that the start/endDTD events will appear within
    624      * the start/endDocument events from ContentHandler and
    625      * before the first
    626      * {@link org.xml.sax.ContentHandler#startElement startElement}
    627      * event.</p>
    628      *
    629      * @param name The document type name.
    630      * @param publicId The declared public identifier for the
    631      *        external DTD subset, or null if none was declared.
    632      * @param systemId The declared system identifier for the
    633      *        external DTD subset, or null if none was declared.
    634      *        (Note that this is not resolved against the document
    635      *        base URI.)
    636      * @exception SAXException The application may raise an
    637      *            exception.
    638      * @see #endDTD
    639      * @see #startEntity
    640      */
    641     public void startDTD(String name, String publicId, String systemId)
    642         throws SAXException {
    643         if (lexicalHandler != null) {
    644             lexicalHandler.startDTD(name, publicId, systemId);
    645         }
    646     }
    647
    648     /***
    649      * Report the end of DTD declarations.
    650      *
    651      * <p>This method is intended to report the end of the
    652      * DOCTYPE declaration; if the document has no DOCTYPE declaration,
    653      * this method will not be invoked.</p>
    654      *
    655      * @exception SAXException The application may raise an exception.
    656      * @see #startDTD
    657      */
    658     public void endDTD() throws SAXException {
    659         if (lexicalHandler != null) {
    660             lexicalHandler.endDTD();
    661         }
    662     }
    663
    664     /***
    665      * Report the beginning of some internal and external XML entities.
    666      *
    667      * <p>The reporting of parameter entities (including
    668      * the external DTD subset) is optional, and SAX2 drivers that
    669      * report LexicalHandler events may not implement it; you can use the
    670      * <code
    671      * >http://xml.org/sax/features/lexical-handler/parameter-entities</code>
    672      * feature to query or control the reporting of parameter entities.</p>
    673      *
    674      * <p>General entities are reported with their regular names,
    675      * parameter entities have '%' prepended to their names, and
    676      * the external DTD subset has the pseudo-entity name "[dtd]".</p>
    677      *
    678      * <p>When a SAX2 driver is providing these events, all other
    679      * events must be properly nested within start/end entity
    680      * events.  There is no additional requirement that events from
    681      * {@link org.xml.sax.ext.DeclHandler DeclHandler} or
    682      * {@link org.xml.sax.DTDHandler DTDHandler} be properly ordered.</p>
    683      *
    684      * <p>Note that skipped entities will be reported through the
    685      * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
    686      * event, which is part of the ContentHandler interface.</p>
    687      *
    688      * <p>Because of the streaming event model that SAX uses, some
    689      * entity boundaries cannot be reported under any
    690      * circumstances:</p>
    691      *
    692      * <ul>
    693      * <li>general entities within attribute values</li>
    694      * <li>parameter entities within declarations</li>
    695      * </ul>
    696      *
    697      * <p>These will be silently expanded, with no indication of where
    698      * the original entity boundaries were.</p>
    699      *
    700      * <p>Note also that the boundaries of character references (which
    701      * are not really entities anyway) are not reported.</p>
    702      *
    703      * <p>All start/endEntity events must be properly nested.
    704      *
    705      * @param name The name of the entity.  If it is a parameter
    706      *        entity, the name will begin with '%', and if it is the
    707      *        external DTD subset, it will be "[dtd]".
    708      * @exception SAXException The application may raise an exception.
    709      * @see #endEntity
    710      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
    711      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
    712      */
    713     public void startEntity(String name) throws SAXException {
    714         if (lexicalHandler != null) {
    715             lexicalHandler.startEntity(name);
    716         }
    717     }
    718
    719     /***
    720      * Report the end of an entity.
    721      *
    722      * @param name The name of the entity that is ending.
    723      * @exception SAXException The application may raise an exception.
    724      * @see #startEntity
    725      */
    726     public void endEntity(String name) throws SAXException {
    727         if (lexicalHandler != null) {
    728             lexicalHandler.endEntity(name);
    729         }
    730     }
    731
    732     /***
    733      * Report the start of a CDATA section.
    734      *
    735      * <p>The contents of the CDATA section will be reported through
    736      * the regular {@link org.xml.sax.ContentHandler#characters
    737      * characters} event; this event is intended only to report
    738      * the boundary.</p>
    739      *
    740      * @exception SAXException The application may raise an exception.
    741      * @see #endCDATA
    742      */
    743     public void startCDATA() throws SAXException {
    744         if (lexicalHandler != null) {
    745             lexicalHandler.startCDATA();
    746         }
    747     }
    748
    749     /***
    750      * Report the end of a CDATA section.
    751      *
    752      * @exception SAXException The application may raise an exception.
    753      * @see #startCDATA
    754      */
    755     public void endCDATA() throws SAXException {
    756         if (lexicalHandler != null) {
    757             lexicalHandler.endCDATA();
    758         }
    759     }
    760
    761     /***
    762      * Report an XML comment anywhere in the document.
    763      *
    764      * <p>This callback will be used for comments inside or outside the
    765      * document element, including comments in the external DTD
    766      * subset (if read).  Comments in the DTD must be properly
    767      * nested inside start/endDTD and start/endEntity events (if
    768      * used).</p>
    769      *
    770      * @param ch An array holding the characters in the comment.
    771      * @param start The starting position in the array.
    772      * @param length The number of characters to use from the array.
    773      * @exception SAXException The application may raise an exception.
    774      */
    775     public void comment(char ch[], int start, int length) throws SAXException {
    776         if (lexicalHandler != null) {
    777             lexicalHandler.comment(ch, start, length);
    778         }
    779     }
    780
    781     /*** Pass data through the pipline.
    782       * By default, this call is ignored.
    783       * Subclasses are invited to use this as a way for children tags to
    784       * pass data to their parent.
    785       *
    786       * @param object the data to pass
    787       * @exception SAXException The application may raise an exception.
    788       */
    789     public void objectData(Object object) throws SAXException {
    790         if(contentHandler instanceof XMLOutput)
    791             ((XMLOutput) contentHandler).objectData(object);
    792         else {
    793             if(object!=null) {
    794                 String output=object.toString();
    795                 write(output);
    796             } else {
    797                 // we could have a "configurable null-toString"...
    798                 write("null");
    799             }
    800         }
    801     }
    802
    803     // Properties
    804     //-------------------------------------------------------------------------
    805     /***
    806      * @return the SAX ContentHandler to use to pipe SAX events into
    807      */
    808     public ContentHandler getContentHandler() {
    809         return contentHandler;
    810     }
    811
    812     /***
    813      * Sets the SAX ContentHandler to pipe SAX events into
    814      *
    815      * @param contentHandler is the new ContentHandler to use.
    816      *      This value cannot be null.
    817      */
    818     public void setContentHandler(ContentHandler contentHandler) {
    819         if (contentHandler == null) {
    820             throw new NullPointerException("ContentHandler cannot be null!");
    821         }
    822         this.contentHandler = contentHandler;
    823     }
    824
    825     /***
    826      * @return the SAX LexicalHandler to use to pipe SAX events into
    827      */
    828     public LexicalHandler getLexicalHandler() {
    829         return lexicalHandler;
    830     }
    831
    832     /***
    833      * Sets the SAX LexicalHandler to pipe SAX events into
    834      *
    835      * @param lexicalHandler is the new LexicalHandler to use.
    836      *      This value can be null.
    837      */
    838     public void setLexicalHandler(LexicalHandler lexicalHandler) {
    839         this.lexicalHandler = lexicalHandler;
    840     }
    841
    842     // Implementation methods
    843     //-------------------------------------------------------------------------
    844     /***
    845      * Factory method to create a new XMLOutput from an XMLWriter
    846      */
    847     protected static XMLOutput createXMLOutput(final XMLWriter xmlWriter) {
    848         XMLOutput answer = new XMLOutput() {
    849             public void close() throws IOException {
    850                 xmlWriter.close();
    851             }
    852         };
    853         answer.setContentHandler(xmlWriter);
    854         answer.setLexicalHandler(xmlWriter);
    855         return answer;
    856     }
    857
    858 }