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