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