Java tutorial
/* * Copyright 2002,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.jelly; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.io.Writer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.io.XMLWriter; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.DefaultHandler; /** <p><code>XMLOutput</code> is used to output XML events * in a SAX-like manner. This also allows pipelining to be done * such as in the <a href="http://xml.apache.org/cocoon/">Cocoon</a> project.</p> * * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> * @version $Revision: 1.21 $ */ public class XMLOutput implements ContentHandler, LexicalHandler { protected static final String[] LEXICAL_HANDLER_NAMES = { "http://xml.org/sax/properties/lexical-handler", "http://xml.org/sax/handlers/LexicalHandler" }; /** empty attributes */ private static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl(); /** The Log to which logging calls will be made. */ private static final Log log = LogFactory.getLog(XMLOutput.class); /** the default for escaping of text */ private static final boolean DEFAULT_ESCAPE_TEXT = false; /** The SAX ContentHandler that output goes to */ private ContentHandler contentHandler; /** The SAX LexicalHandler that output goes to */ private LexicalHandler lexicalHandler; public XMLOutput() { } public XMLOutput(ContentHandler contentHandler) { this.contentHandler = contentHandler; // often classes will implement LexicalHandler as well if (contentHandler instanceof LexicalHandler) { this.lexicalHandler = (LexicalHandler) contentHandler; } } public XMLOutput(ContentHandler contentHandler, LexicalHandler lexicalHandler) { this.contentHandler = contentHandler; this.lexicalHandler = lexicalHandler; } public String toString() { return super.toString() + "[contentHandler=" + contentHandler + ";lexicalHandler=" + lexicalHandler + "]"; } /** * Provides a useful hook that implementations can use to close the * underlying OutputStream or Writer */ public void close() throws IOException { } public void flush() throws IOException { if (contentHandler instanceof XMLWriter) { ((XMLWriter) contentHandler).flush(); } } // Static helper methods //------------------------------------------------------------------------- /** * Creates an XMLOutput from an existing SAX XMLReader */ public static XMLOutput createXMLOutput(XMLReader xmlReader) { XMLOutput output = new XMLOutput(xmlReader.getContentHandler()); // isn't it lovely what we've got to do to find the LexicalHandler... ;-) for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { try { Object value = xmlReader.getProperty(LEXICAL_HANDLER_NAMES[i]); if (value instanceof LexicalHandler) { output.setLexicalHandler((LexicalHandler) value); break; } } catch (Exception e) { // ignore any unsupported-operation exceptions if (log.isDebugEnabled()) log.debug("error setting lexical handler properties", e); } } return output; } /** * Creates a text based XMLOutput which converts all XML events into * text and writes to the underlying Writer. */ public static XMLOutput createXMLOutput(Writer writer) { return createXMLOutput(writer, DEFAULT_ESCAPE_TEXT); } /** * Creates a text based XMLOutput which converts all XML events into * text and writes to the underlying Writer. * * @param writer is the writer to output to * @param escapeText is whether or not text output will be escaped. This must be true * if the underlying output is XML or could be false if the underlying output is textual. */ public static XMLOutput createXMLOutput(Writer writer, boolean escapeText) { XMLWriter xmlWriter = new XMLWriter(writer); xmlWriter.setEscapeText(escapeText); return createXMLOutput(xmlWriter); } /** * Creates a text based XMLOutput which converts all XML events into * text and writes to the underlying OutputStream. */ public static XMLOutput createXMLOutput(OutputStream out) throws UnsupportedEncodingException { return createXMLOutput(out, DEFAULT_ESCAPE_TEXT); } /** * Creates a text based XMLOutput which converts all XML events into * text and writes to the underlying OutputStream. * * @param out is the output stream to write * @param escapeText is whether or not text output will be escaped. This must be true * if the underlying output is XML or could be false if the underlying output is textual. */ public static XMLOutput createXMLOutput(OutputStream out, boolean escapeText) throws UnsupportedEncodingException { XMLWriter xmlWriter = new XMLWriter(out); xmlWriter.setEscapeText(escapeText); return createXMLOutput(xmlWriter); } /** * returns an XMLOutput object that will discard all * tag-generated XML events. Useful when tag output is not expected * or not significant. * * @return a no-op XMLOutput */ public static XMLOutput createDummyXMLOutput() { return new XMLOutput(new DefaultHandler()); } // Extra helper methods provided for tag authors //------------------------------------------------------------------------- /** * Outputs the given String as a piece of valid text in the * XML event stream. * Any special XML characters should be properly escaped. */ public void write(String text) throws SAXException { char[] ch = text.toCharArray(); characters(ch, 0, ch.length); } /** * Outputs the given String as a piece of CDATA in the * XML event stream. */ public void writeCDATA(String text) throws SAXException { startCDATA(); char[] ch = text.toCharArray(); characters(ch, 0, ch.length); endCDATA(); } /** * Outputs a comment to the XML stream */ public void writeComment(String text) throws SAXException { char[] ch = text.toCharArray(); comment(ch, 0, ch.length); } /** * Helper method for outputting a start element event for an element in no namespace */ public void startElement(String localName) throws SAXException { startElement("", localName, localName, EMPTY_ATTRIBUTES); } /** * Helper method for outputting a start element event for an element in no namespace */ public void startElement(String localName, Attributes attributes) throws SAXException { startElement("", localName, localName, attributes); } /** * Helper method for outputting an end element event for an element in no namespace */ public void endElement(String localName) throws SAXException { endElement("", localName, localName); } // ContentHandler interface //------------------------------------------------------------------------- /** * Receive an object for locating the origin of SAX document events. * * <p>SAX parsers are strongly encouraged (though not absolutely * required) to supply a locator: if it does so, it must supply * the locator to the application by invoking this method before * invoking any of the other methods in the ContentHandler * interface.</p> * * <p>The locator allows the application to determine the end * position of any document-related event, even if the parser is * not reporting an error. Typically, the application will * use this information for reporting its own errors (such as * character content that does not match an application's * business rules). The information returned by the locator * is probably not sufficient for use with a search engine.</p> * * <p>Note that the locator will return correct information only * during the invocation of the events in this interface. The * application should not attempt to use it at any other time.</p> * * @param locator An object that can return the location of * any SAX document event. * @see org.xml.sax.Locator */ public void setDocumentLocator(Locator locator) { contentHandler.setDocumentLocator(locator); } /** * Receive notification of the beginning of a document. * * <p>The SAX parser will invoke this method only once, before any * other event callbacks (except for {@link #setDocumentLocator * setDocumentLocator}).</p> * * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see #endDocument */ public void startDocument() throws SAXException { contentHandler.startDocument(); } /** * Receive notification of the end of a document. * * <p>The SAX parser will invoke this method only once, and it will * be the last method invoked during the parse. The parser shall * not invoke this method until it has either abandoned parsing * (because of an unrecoverable error) or reached the end of * input.</p> * * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see #startDocument */ public void endDocument() throws SAXException { contentHandler.endDocument(); } /** * Begin the scope of a prefix-URI Namespace mapping. * * <p>The information from this event is not necessary for * normal Namespace processing: the SAX XML reader will * automatically replace prefixes for element and attribute * names when the <code>http://xml.org/sax/features/namespaces</code> * feature is <var>true</var> (the default).</p> * * <p>There are cases, however, when applications need to * use prefixes in character data or in attribute values, * where they cannot safely be expanded automatically; the * start/endPrefixMapping event supplies the information * to the application to expand prefixes in those contexts * itself, if necessary.</p> * * <p>Note that start/endPrefixMapping events are not * guaranteed to be properly nested relative to each other: * all startPrefixMapping events will occur immediately before the * corresponding {@link #startElement startElement} event, * and all {@link #endPrefixMapping endPrefixMapping} * events will occur immediately after the corresponding * {@link #endElement endElement} event, * but their order is not otherwise * guaranteed.</p> * * <p>There should never be start/endPrefixMapping events for the * "xml" prefix, since it is predeclared and immutable.</p> * * @param prefix The Namespace prefix being declared. * An empty string is used for the default element namespace, * which has no prefix. * @param uri The Namespace URI the prefix is mapped to. * @exception org.xml.sax.SAXException The client may throw * an exception during processing. * @see #endPrefixMapping * @see #startElement */ public void startPrefixMapping(String prefix, String uri) throws SAXException { contentHandler.startPrefixMapping(prefix, uri); } /** * End the scope of a prefix-URI mapping. * * <p>See {@link #startPrefixMapping startPrefixMapping} for * details. These events will always occur immediately after the * corresponding {@link #endElement endElement} event, but the order of * {@link #endPrefixMapping endPrefixMapping} events is not otherwise * guaranteed.</p> * * @param prefix The prefix that was being mapped. * This is the empty string when a default mapping scope ends. * @exception org.xml.sax.SAXException The client may throw * an exception during processing. * @see #startPrefixMapping * @see #endElement */ public void endPrefixMapping(String prefix) throws SAXException { contentHandler.endPrefixMapping(prefix); } /** * Receive notification of the beginning of an element. * * <p>The Parser will invoke this method at the beginning of every * element in the XML document; there will be a corresponding * {@link #endElement endElement} event for every startElement event * (even when the element is empty). All of the element's content will be * reported, in order, before the corresponding endElement * event.</p> * * <p>This event allows up to three name components for each * element:</p> * * <ol> * <li>the Namespace URI;</li> * <li>the local name; and</li> * <li>the qualified (prefixed) name.</li> * </ol> * * <p>Any or all of these may be provided, depending on the * values of the <var>http://xml.org/sax/features/namespaces</var> * and the <var>http://xml.org/sax/features/namespace-prefixes</var> * properties:</p> * * <ul> * <li>the Namespace URI and local name are required when * the namespaces property is <var>true</var> (the default), and are * optional when the namespaces property is <var>false</var> (if one is * specified, both must be);</li> * <li>the qualified name is required when the namespace-prefixes property * is <var>true</var>, and is optional when the namespace-prefixes property * is <var>false</var> (the default).</li> * </ul> * * <p>Note that the attribute list provided will contain only * attributes with explicit values (specified or defaulted): * #IMPLIED attributes will be omitted. The attribute list * will contain attributes used for Namespace declarations * (xmlns* attributes) only if the * <code>http://xml.org/sax/features/namespace-prefixes</code> * property is true (it is false by default, and support for a * true value is optional).</p> * * <p>Like {@link #characters characters()}, attribute values may have * characters that need more than one <code>char</code> value. </p> * * @param uri The Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName The local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qName The qualified name (with prefix), or the * empty string if qualified names are not available. * @param atts The attributes attached to the element. If * there are no attributes, it shall be an empty * Attributes object. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see #endElement * @see org.xml.sax.Attributes */ public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { contentHandler.startElement(uri, localName, qName, atts); } /** * Receive notification of the end of an element. * * <p>The SAX parser will invoke this method at the end of every * element in the XML document; there will be a corresponding * {@link #startElement startElement} event for every endElement * event (even when the element is empty).</p> * * <p>For information on the names, see startElement.</p> * * @param uri The Namespace URI, or the empty string if the * element has no Namespace URI or if Namespace * processing is not being performed. * @param localName The local name (without prefix), or the * empty string if Namespace processing is not being * performed. * @param qName The qualified XML 1.0 name (with prefix), or the * empty string if qualified names are not available. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void endElement(String uri, String localName, String qName) throws SAXException { contentHandler.endElement(uri, localName, qName); } /** * Receive notification of character data. * * <p>The Parser will call this method to report each chunk of * character data. SAX parsers may return all contiguous character * data in a single chunk, or they may split it into several * chunks; however, all of the characters in any single event * must come from the same external entity so that the Locator * provides useful information.</p> * * <p>The application must not attempt to read from the array * outside of the specified range.</p> * * <p>Individual characters may consist of more than one Java * <code>char</code> value. There are two important cases where this * happens, because characters can't be represented in just sixteen bits. * In one case, characters are represented in a <em>Surrogate Pair</em>, * using two special Unicode values. Such characters are in the so-called * "Astral Planes", with a code point above U+FFFF. A second case involves * composite characters, such as a base character combining with one or * more accent characters. </p> * * <p> Your code should not assume that algorithms using * <code>char</code>-at-a-time idioms will be working in character * units; in some cases they will split characters. This is relevant * wherever XML permits arbitrary characters, such as attribute values, * processing instruction data, and comments as well as in data reported * from this method. It's also generally relevant whenever Java code * manipulates internationalized text; the issue isn't unique to XML.</p> * * <p>Note that some parsers will report whitespace in element * content using the {@link #ignorableWhitespace ignorableWhitespace} * method rather than this one (validating parsers <em>must</em> * do so).</p> * * @param ch The characters from the XML document. * @param start The start position in the array. * @param length The number of characters to read from the array. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see #ignorableWhitespace * @see org.xml.sax.Locator */ public void characters(char ch[], int start, int length) throws SAXException { contentHandler.characters(ch, start, length); } /** * Receive notification of ignorable whitespace in element content. * * <p>Validating Parsers must use this method to report each chunk * of whitespace in element content (see the W3C XML 1.0 recommendation, * section 2.10): non-validating parsers may also use this method * if they are capable of parsing and using content models.</p> * * <p>SAX parsers may return all contiguous whitespace in a single * chunk, or they may split it into several chunks; however, all of * the characters in any single event must come from the same * external entity, so that the Locator provides useful * information.</p> * * <p>The application must not attempt to read from the array * outside of the specified range.</p> * * @param ch The characters from the XML document. * @param start The start position in the array. * @param length The number of characters to read from the array. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. * @see #characters */ public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { contentHandler.ignorableWhitespace(ch, start, length); } /** * Receive notification of a processing instruction. * * <p>The Parser will invoke this method once for each processing * instruction found: note that processing instructions may occur * before or after the main document element.</p> * * <p>A SAX parser must never report an XML declaration (XML 1.0, * section 2.8) or a text declaration (XML 1.0, section 4.3.1) * using this method.</p> * * <p>Like {@link #characters characters()}, processing instruction * data may have characters that need more than one <code>char</code> * value. </p> * * @param target The processing instruction target. * @param data The processing instruction data, or null if * none was supplied. The data does not include any * whitespace separating it from the target. * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void processingInstruction(String target, String data) throws SAXException { contentHandler.processingInstruction(target, data); } /** * Receive notification of a skipped entity. * This is not called for entity references within markup constructs * such as element start tags or markup declarations. (The XML * recommendation requires reporting skipped external entities. * SAX also reports internal entity expansion/non-expansion, except * within markup constructs.) * * <p>The Parser will invoke this method each time the entity is * skipped. Non-validating processors may skip entities if they * have not seen the declarations (because, for example, the * entity was declared in an external DTD subset). All processors * may skip external entities, depending on the values of the * <code>http://xml.org/sax/features/external-general-entities</code> * and the * <code>http://xml.org/sax/features/external-parameter-entities</code> * properties.</p> * * @param name The name of the skipped entity. If it is a * parameter entity, the name will begin with '%', and if * it is the external DTD subset, it will be the string * "[dtd]". * @exception org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void skippedEntity(String name) throws SAXException { contentHandler.skippedEntity(name); } // Lexical Handler interface //------------------------------------------------------------------------- /** * Report the start of DTD declarations, if any. * * <p>This method is intended to report the beginning of the * DOCTYPE declaration; if the document has no DOCTYPE declaration, * this method will not be invoked.</p> * * <p>All declarations reported through * {@link org.xml.sax.DTDHandler DTDHandler} or * {@link org.xml.sax.ext.DeclHandler DeclHandler} events must appear * between the startDTD and {@link #endDTD endDTD} events. * Declarations are assumed to belong to the internal DTD subset * unless they appear between {@link #startEntity startEntity} * and {@link #endEntity endEntity} events. Comments and * processing instructions from the DTD should also be reported * between the startDTD and endDTD events, in their original * order of (logical) occurrence; they are not required to * appear in their correct locations relative to DTDHandler * or DeclHandler events, however.</p> * * <p>Note that the start/endDTD events will appear within * the start/endDocument events from ContentHandler and * before the first * {@link org.xml.sax.ContentHandler#startElement startElement} * event.</p> * * @param name The document type name. * @param publicId The declared public identifier for the * external DTD subset, or null if none was declared. * @param systemId The declared system identifier for the * external DTD subset, or null if none was declared. * (Note that this is not resolved against the document * base URI.) * @exception SAXException The application may raise an * exception. * @see #endDTD * @see #startEntity */ public void startDTD(String name, String publicId, String systemId) throws SAXException { if (lexicalHandler != null) { lexicalHandler.startDTD(name, publicId, systemId); } } /** * Report the end of DTD declarations. * * <p>This method is intended to report the end of the * DOCTYPE declaration; if the document has no DOCTYPE declaration, * this method will not be invoked.</p> * * @exception SAXException The application may raise an exception. * @see #startDTD */ public void endDTD() throws SAXException { if (lexicalHandler != null) { lexicalHandler.endDTD(); } } /** * Report the beginning of some internal and external XML entities. * * <p>The reporting of parameter entities (including * the external DTD subset) is optional, and SAX2 drivers that * report LexicalHandler events may not implement it; you can use the * <code * >http://xml.org/sax/features/lexical-handler/parameter-entities</code> * feature to query or control the reporting of parameter entities.</p> * * <p>General entities are reported with their regular names, * parameter entities have '%' prepended to their names, and * the external DTD subset has the pseudo-entity name "[dtd]".</p> * * <p>When a SAX2 driver is providing these events, all other * events must be properly nested within start/end entity * events. There is no additional requirement that events from * {@link org.xml.sax.ext.DeclHandler DeclHandler} or * {@link org.xml.sax.DTDHandler DTDHandler} be properly ordered.</p> * * <p>Note that skipped entities will be reported through the * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity} * event, which is part of the ContentHandler interface.</p> * * <p>Because of the streaming event model that SAX uses, some * entity boundaries cannot be reported under any * circumstances:</p> * * <ul> * <li>general entities within attribute values</li> * <li>parameter entities within declarations</li> * </ul> * * <p>These will be silently expanded, with no indication of where * the original entity boundaries were.</p> * * <p>Note also that the boundaries of character references (which * are not really entities anyway) are not reported.</p> * * <p>All start/endEntity events must be properly nested. * * @param name The name of the entity. If it is a parameter * entity, the name will begin with '%', and if it is the * external DTD subset, it will be "[dtd]". * @exception SAXException The application may raise an exception. * @see #endEntity * @see org.xml.sax.ext.DeclHandler#internalEntityDecl * @see org.xml.sax.ext.DeclHandler#externalEntityDecl */ public void startEntity(String name) throws SAXException { if (lexicalHandler != null) { lexicalHandler.startEntity(name); } } /** * Report the end of an entity. * * @param name The name of the entity that is ending. * @exception SAXException The application may raise an exception. * @see #startEntity */ public void endEntity(String name) throws SAXException { if (lexicalHandler != null) { lexicalHandler.endEntity(name); } } /** * Report the start of a CDATA section. * * <p>The contents of the CDATA section will be reported through * the regular {@link org.xml.sax.ContentHandler#characters * characters} event; this event is intended only to report * the boundary.</p> * * @exception SAXException The application may raise an exception. * @see #endCDATA */ public void startCDATA() throws SAXException { if (lexicalHandler != null) { lexicalHandler.startCDATA(); } } /** * Report the end of a CDATA section. * * @exception SAXException The application may raise an exception. * @see #startCDATA */ public void endCDATA() throws SAXException { if (lexicalHandler != null) { lexicalHandler.endCDATA(); } } /** * Report an XML comment anywhere in the document. * * <p>This callback will be used for comments inside or outside the * document element, including comments in the external DTD * subset (if read). Comments in the DTD must be properly * nested inside start/endDTD and start/endEntity events (if * used).</p> * * @param ch An array holding the characters in the comment. * @param start The starting position in the array. * @param length The number of characters to use from the array. * @exception SAXException The application may raise an exception. */ public void comment(char ch[], int start, int length) throws SAXException { if (lexicalHandler != null) { lexicalHandler.comment(ch, start, length); } } /** Pass data through the pipline. * By default, this call is ignored. * Subclasses are invited to use this as a way for children tags to * pass data to their parent. * * @param object the data to pass * @exception SAXException The application may raise an exception. */ public void objectData(Object object) throws SAXException { if (contentHandler instanceof XMLOutput) ((XMLOutput) contentHandler).objectData(object); else { if (object != null) { String output = object.toString(); write(output); } else { // we could have a "configurable null-toString"... write("null"); } } } // Properties //------------------------------------------------------------------------- /** * @return the SAX ContentHandler to use to pipe SAX events into */ public ContentHandler getContentHandler() { return contentHandler; } /** * Sets the SAX ContentHandler to pipe SAX events into * * @param contentHandler is the new ContentHandler to use. * This value cannot be null. */ public void setContentHandler(ContentHandler contentHandler) { if (contentHandler == null) { throw new NullPointerException("ContentHandler cannot be null!"); } this.contentHandler = contentHandler; } /** * @return the SAX LexicalHandler to use to pipe SAX events into */ public LexicalHandler getLexicalHandler() { return lexicalHandler; } /** * Sets the SAX LexicalHandler to pipe SAX events into * * @param lexicalHandler is the new LexicalHandler to use. * This value can be null. */ public void setLexicalHandler(LexicalHandler lexicalHandler) { this.lexicalHandler = lexicalHandler; } // Implementation methods //------------------------------------------------------------------------- /** * Factory method to create a new XMLOutput from an XMLWriter */ protected static XMLOutput createXMLOutput(final XMLWriter xmlWriter) { XMLOutput answer = new XMLOutput() { public void close() throws IOException { xmlWriter.close(); } }; answer.setContentHandler(xmlWriter); answer.setLexicalHandler(xmlWriter); return answer; } }