0001: /*--
0002:
0003: Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
0004: All rights reserved.
0005:
0006: Redistribution and use in source and binary forms, with or without
0007: modification, are permitted provided that the following conditions
0008: are met:
0009:
0010: 1. Redistributions of source code must retain the above copyright
0011: notice, this list of conditions, and the following disclaimer.
0012:
0013: 2. Redistributions in binary form must reproduce the above copyright
0014: notice, this list of conditions, and the disclaimer that follows
0015: these conditions in the documentation and/or other materials
0016: provided with the distribution.
0017:
0018: 3. The name "JDOM" must not be used to endorse or promote products
0019: derived from this software without prior written permission. For
0020: written permission, please contact license@jdom.org.
0021:
0022: 4. Products derived from this software may not be called "JDOM", nor
0023: may "JDOM" appear in their name, without prior written permission
0024: from the JDOM Project Management (pm@jdom.org).
0025:
0026: In addition, we request (but do not require) that you include in the
0027: end-user documentation provided with the redistribution and/or in the
0028: software itself an acknowledgement equivalent to the following:
0029: "This product includes software developed by the
0030: JDOM Project (http://www.jdom.org/)."
0031: Alternatively, the acknowledgment may be graphical using the logos
0032: available at http://www.jdom.org/images/logos.
0033:
0034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0036: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0037: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
0038: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0039: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0040: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0041: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0042: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0043: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0044: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0045: SUCH DAMAGE.
0046:
0047: This software consists of voluntary contributions made by many
0048: individuals on behalf of the JDOM Project and was originally
0049: created by Brett McLaughlin <brett@jdom.org> and
0050: Jason Hunter <jhunter@jdom.org>. For more information on the
0051: JDOM Project, please see <http://www.jdom.org/>.
0052:
0053: */
0054: package sax;
0055:
0056: import java.io.IOException;
0057: import java.io.OutputStreamWriter;
0058: import java.io.Writer;
0059: import java.util.Enumeration;
0060: import java.util.HashMap;
0061: import java.util.Iterator;
0062: import java.util.Map;
0063:
0064: import org.xml.sax.Attributes;
0065: import org.xml.sax.InputSource;
0066: import org.xml.sax.SAXException;
0067: import org.xml.sax.XMLReader;
0068: import org.xml.sax.helpers.NamespaceSupport;
0069:
0070: /**
0071: * Filter to write an XML document from a SAX event stream.
0072: *
0073: * <i>Code and comments adapted from XMLWriter-0.2, written
0074: * by David Megginson and released into the public domain,
0075: * without warranty.</i>
0076: *
0077: * <p>This class can be used by itself or as part of a SAX event
0078: * stream: it takes as input a series of SAX2 ContentHandler
0079: * events and uses the information in those events to write
0080: * an XML document. Since this class is a filter, it can also
0081: * pass the events on down a filter chain for further processing
0082: * (you can use the XMLWriter to take a snapshot of the current
0083: * state at any point in a filter chain), and it can be
0084: * used directly as a ContentHandler for a SAX2 XMLReader.</p>
0085: *
0086: * <p>The client creates a document by invoking the methods for
0087: * standard SAX2 events, always beginning with the
0088: * {@link #startDocument startDocument} method and ending with
0089: * the {@link #endDocument endDocument} method.</p>
0090: *
0091: * <p>The following code will send a simple XML document to
0092: * standard output:</p>
0093: *
0094: * <pre>
0095: * XMLWriter w = new XMLWriter();
0096: *
0097: * w.startDocument();
0098: * w.dataElement("greeting", "Hello, world!");
0099: * w.endDocument();
0100: * </pre>
0101: *
0102: * <p>The resulting document will look like this:</p>
0103: *
0104: * <pre>
0105: * <?xml version="1.0"?>
0106: *
0107: * <greeting>Hello, world!</greeting>
0108: * </pre>
0109: *
0110: * <h2>Whitespace</h2>
0111: *
0112: * <p>According to the XML Recommendation, <em>all</em> whitespace
0113: * in an XML document is potentially significant to an application,
0114: * so this class never adds newlines or indentation. If you
0115: * insert three elements in a row, as in</p>
0116: *
0117: * <pre>
0118: * w.dataElement("item", "1");
0119: * w.dataElement("item", "2");
0120: * w.dataElement("item", "3");
0121: * </pre>
0122: *
0123: * <p>you will end up with</p>
0124: *
0125: * <pre>
0126: * <item>1</item><item>3</item><item>3</item>
0127: * </pre>
0128: *
0129: * <p>You need to invoke one of the <var>characters</var> methods
0130: * explicitly to add newlines or indentation. Alternatively, you
0131: * can use {@link samples.sax.DataFormatFilter DataFormatFilter}
0132: * add linebreaks and indentation (but does not support mixed content
0133: * properly).</p>
0134: *
0135: *
0136: * <h2>Namespace Support</h2>
0137: *
0138: * <p>The writer contains extensive support for XML Namespaces, so that
0139: * a client application does not have to keep track of prefixes and
0140: * supply <var>xmlns</var> attributes. By default, the XML writer will
0141: * generate Namespace declarations in the form _NS1, _NS2, etc., wherever
0142: * they are needed, as in the following example:</p>
0143: *
0144: * <pre>
0145: * w.startDocument();
0146: * w.emptyElement("http://www.foo.com/ns/", "foo");
0147: * w.endDocument();
0148: * </pre>
0149: *
0150: * <p>The resulting document will look like this:</p>
0151: *
0152: * <pre>
0153: * <?xml version="1.0"?>
0154: *
0155: * <_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
0156: * </pre>
0157: *
0158: * <p>In many cases, document authors will prefer to choose their
0159: * own prefixes rather than using the (ugly) default names. The
0160: * XML writer allows two methods for selecting prefixes:</p>
0161: *
0162: * <ol>
0163: * <li>the qualified name</li>
0164: * <li>the {@link #setPrefix setPrefix} method.</li>
0165: * </ol>
0166: *
0167: * <p>Whenever the XML writer finds a new Namespace URI, it checks
0168: * to see if a qualified (prefixed) name is also available; if so
0169: * it attempts to use the name's prefix (as long as the prefix is
0170: * not already in use for another Namespace URI).</p>
0171: *
0172: * <p>Before writing a document, the client can also pre-map a prefix
0173: * to a Namespace URI with the setPrefix method:</p>
0174: *
0175: * <pre>
0176: * w.setPrefix("http://www.foo.com/ns/", "foo");
0177: * w.startDocument();
0178: * w.emptyElement("http://www.foo.com/ns/", "foo");
0179: * w.endDocument();
0180: * </pre>
0181: *
0182: * <p>The resulting document will look like this:</p>
0183: *
0184: * <pre>
0185: * <?xml version="1.0"?>
0186: *
0187: * <foo:foo xmlns:foo="http://www.foo.com/ns/"/>
0188: * </pre>
0189: *
0190: * <p>The default Namespace simply uses an empty string as the prefix:</p>
0191: *
0192: * <pre>
0193: * w.setPrefix("http://www.foo.com/ns/", "");
0194: * w.startDocument();
0195: * w.emptyElement("http://www.foo.com/ns/", "foo");
0196: * w.endDocument();
0197: * </pre>
0198: *
0199: * <p>The resulting document will look like this:</p>
0200: *
0201: * <pre>
0202: * <?xml version="1.0"?>
0203: *
0204: * <foo xmlns="http://www.foo.com/ns/"/>
0205: * </pre>
0206: *
0207: * <p>By default, the XML writer will not declare a Namespace until
0208: * it is actually used. Sometimes, this approach will create
0209: * a large number of Namespace declarations, as in the following
0210: * example:</p>
0211: *
0212: * <pre>
0213: * <xml version="1.0"?>
0214: *
0215: * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
0216: * <rdf:Description about="http://www.foo.com/ids/books/12345">
0217: * <dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night</dc:title>
0218: * <dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith</dc:title>
0219: * <dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09</dc:title>
0220: * </rdf:Description>
0221: * </rdf:RDF>
0222: * </pre>
0223: *
0224: * <p>The "rdf" prefix is declared only once, because the RDF Namespace
0225: * is used by the root element and can be inherited by all of its
0226: * descendants; the "dc" prefix, on the other hand, is declared three
0227: * times, because no higher element uses the Namespace. To solve this
0228: * problem, you can instruct the XML writer to predeclare Namespaces
0229: * on the root element even if they are not used there:</p>
0230: *
0231: * <pre>
0232: * w.forceNSDecl("http://www.purl.org/dc/");
0233: * </pre>
0234: *
0235: * <p>Now, the "dc" prefix will be declared on the root element even
0236: * though it's not needed there, and can be inherited by its
0237: * descendants:</p>
0238: *
0239: * <pre>
0240: * <xml version="1.0"?>
0241: *
0242: * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
0243: * xmlns:dc="http://www.purl.org/dc/">
0244: * <rdf:Description about="http://www.foo.com/ids/books/12345">
0245: * <dc:title>A Dark Night</dc:title>
0246: * <dc:creator>Jane Smith</dc:title>
0247: * <dc:date>2000-09-09</dc:title>
0248: * </rdf:Description>
0249: * </rdf:RDF>
0250: * </pre>
0251: *
0252: * <p>This approach is also useful for declaring Namespace prefixes
0253: * that be used by qualified names appearing in attribute values or
0254: * character data.</p>
0255: *
0256: * @see XMLFilterBase
0257: */
0258: public class XMLWriter extends XMLFilterBase {
0259:
0260: ////////////////////////////////////////////////////////////////////
0261: // Constructors.
0262: ////////////////////////////////////////////////////////////////////
0263:
0264: /**
0265: * Create a new XML writer.
0266: *
0267: * <p>Write to standard output.</p>
0268: */
0269: public XMLWriter() {
0270: init(null);
0271: }
0272:
0273: /**
0274: * Create a new XML writer.
0275: *
0276: * <p>Write to the writer provided.</p>
0277: *
0278: * @param writer The output destination, or null to use standard
0279: * output.
0280: */
0281: public XMLWriter(Writer writer) {
0282: init(writer);
0283: }
0284:
0285: /**
0286: * Create a new XML writer.
0287: *
0288: * <p>Use the specified XML reader as the parent.</p>
0289: *
0290: * @param xmlreader The parent in the filter chain, or null
0291: * for no parent.
0292: */
0293: public XMLWriter(XMLReader xmlreader) {
0294: super (xmlreader);
0295: init(null);
0296: }
0297:
0298: /**
0299: * Create a new XML writer.
0300: *
0301: * <p>Use the specified XML reader as the parent, and write
0302: * to the specified writer.</p>
0303: *
0304: * @param xmlreader The parent in the filter chain, or null
0305: * for no parent.
0306: * @param writer The output destination, or null to use standard
0307: * output.
0308: */
0309: public XMLWriter(XMLReader xmlreader, Writer writer) {
0310: super (xmlreader);
0311: init(writer);
0312: }
0313:
0314: /**
0315: * Internal initialization method.
0316: *
0317: * <p>All of the public constructors invoke this method.
0318: *
0319: * @param writer The output destination, or null to use
0320: * standard output.
0321: */
0322: private void init(Writer writer) {
0323: setOutput(writer);
0324: nsSupport = new NamespaceSupport();
0325: prefixTable = new HashMap();
0326: forcedDeclTable = new HashMap();
0327: doneDeclTable = new HashMap();
0328: }
0329:
0330: ////////////////////////////////////////////////////////////////////
0331: // Public methods.
0332: ////////////////////////////////////////////////////////////////////
0333:
0334: /**
0335: * Reset the writer.
0336: *
0337: * <p>This method is especially useful if the writer throws an
0338: * exception before it is finished, and you want to reuse the
0339: * writer for a new document. It is usually a good idea to
0340: * invoke {@link #flush flush} before resetting the writer,
0341: * to make sure that no output is lost.</p>
0342: *
0343: * <p>This method is invoked automatically by the
0344: * {@link #startDocument startDocument} method before writing
0345: * a new document.</p>
0346: *
0347: * <p><strong>Note:</strong> this method will <em>not</em>
0348: * clear the prefix or URI information in the writer or
0349: * the selected output writer.</p>
0350: *
0351: * @see #flush
0352: */
0353: public void reset() {
0354: openElement = false;
0355: elementLevel = 0;
0356: prefixCounter = 0;
0357: nsSupport.reset();
0358: inDTD = false;
0359: }
0360:
0361: /**
0362: * Flush the output.
0363: *
0364: * <p>This method flushes the output stream. It is especially useful
0365: * when you need to make certain that the entire document has
0366: * been written to output but do not want to close the output
0367: * stream.</p>
0368: *
0369: * <p>This method is invoked automatically by the
0370: * {@link #endDocument endDocument} method after writing a
0371: * document.</p>
0372: *
0373: * @see #reset
0374: */
0375: public void flush() throws IOException {
0376: output.flush();
0377: }
0378:
0379: /**
0380: * Set a new output destination for the document.
0381: *
0382: * @param writer The output destination, or null to use
0383: * standard output.
0384: * @return The current output writer.
0385: * @see #flush
0386: */
0387: public void setOutput(Writer writer) {
0388: if (writer == null) {
0389: output = new OutputStreamWriter(System.out);
0390: } else {
0391: output = writer;
0392: }
0393: }
0394:
0395: /**
0396: * Specify a preferred prefix for a Namespace URI.
0397: *
0398: * <p>Note that this method does not actually force the Namespace
0399: * to be declared; to do that, use the {@link
0400: * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p>
0401: *
0402: * @param uri The Namespace URI.
0403: * @param prefix The preferred prefix, or "" to select
0404: * the default Namespace.
0405: * @see #getPrefix
0406: * @see #forceNSDecl(java.lang.String)
0407: * @see #forceNSDecl(java.lang.String,java.lang.String)
0408: */
0409: public void setPrefix(String uri, String prefix) {
0410: prefixTable.put(uri, prefix);
0411: }
0412:
0413: /**
0414: * Get the current or preferred prefix for a Namespace URI.
0415: *
0416: * @param uri The Namespace URI.
0417: * @return The preferred prefix, or "" for the default Namespace.
0418: * @see #setPrefix
0419: */
0420: public String getPrefix(String uri) {
0421: return (String) prefixTable.get(uri);
0422: }
0423:
0424: /**
0425: * Force a Namespace to be declared on the root element.
0426: *
0427: * <p>By default, the XMLWriter will declare only the Namespaces
0428: * needed for an element; as a result, a Namespace may be
0429: * declared many places in a document if it is not used on the
0430: * root element.</p>
0431: *
0432: * <p>This method forces a Namespace to be declared on the root
0433: * element even if it is not used there, and reduces the number
0434: * of xmlns attributes in the document.</p>
0435: *
0436: * @param uri The Namespace URI to declare.
0437: * @see #forceNSDecl(java.lang.String,java.lang.String)
0438: * @see #setPrefix
0439: */
0440: public void forceNSDecl(String uri) {
0441: forcedDeclTable.put(uri, Boolean.TRUE);
0442: }
0443:
0444: /**
0445: * Force a Namespace declaration with a preferred prefix.
0446: *
0447: * <p>This is a convenience method that invokes {@link
0448: * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String)
0449: * forceNSDecl}.</p>
0450: *
0451: * @param uri The Namespace URI to declare on the root element.
0452: * @param prefix The preferred prefix for the Namespace, or ""
0453: * for the default Namespace.
0454: * @see #setPrefix
0455: * @see #forceNSDecl(java.lang.String)
0456: */
0457: public void forceNSDecl(String uri, String prefix) {
0458: setPrefix(uri, prefix);
0459: forceNSDecl(uri);
0460: }
0461:
0462: ////////////////////////////////////////////////////////////////////
0463: // Methods from org.xml.sax.ContentHandler.
0464: ////////////////////////////////////////////////////////////////////
0465:
0466: /**
0467: * Write the XML declaration at the beginning of the document.
0468: *
0469: * Pass the event on down the filter chain for further processing.
0470: *
0471: * @exception org.xml.sax.SAXException If there is an error
0472: * writing the XML declaration, or if a handler further down
0473: * the filter chain raises an exception.
0474: * @see org.xml.sax.ContentHandler#startDocument
0475: */
0476: public void startDocument() throws SAXException {
0477: reset();
0478: //write("<?xml version=\"1.0\" standalone=\"yes\"?>\n\n");
0479: write("<?xml version=\"1.0\"?>\n\n");
0480: super .startDocument();
0481: }
0482:
0483: /**
0484: * Write a newline at the end of the document.
0485: *
0486: * Pass the event on down the filter chain for further processing.
0487: *
0488: * @exception org.xml.sax.SAXException If there is an error
0489: * writing the newline, or if a handler further down
0490: * the filter chain raises an exception.
0491: * @see org.xml.sax.ContentHandler#endDocument
0492: */
0493: public void endDocument() throws SAXException {
0494: closeElement();
0495: write('\n');
0496: super .endDocument();
0497: try {
0498: flush();
0499: } catch (IOException e) {
0500: throw new SAXException(e);
0501: }
0502: }
0503:
0504: /**
0505: * Write a start tag.
0506: *
0507: * Pass the event on down the filter chain for further processing.
0508: *
0509: * @param uri The Namespace URI, or the empty string if none
0510: * is available.
0511: * @param localName The element's local (unprefixed) name (required).
0512: * @param qName The element's qualified (prefixed) name, or the
0513: * empty string is none is available. This method will
0514: * use the qName as a template for generating a prefix
0515: * if necessary, but it is not guaranteed to use the
0516: * same qName.
0517: * @param atts The element's attribute list (must not be null).
0518: * @exception org.xml.sax.SAXException If there is an error
0519: * writing the start tag, or if a handler further down
0520: * the filter chain raises an exception.
0521: * @see org.xml.sax.ContentHandler#startElement
0522: */
0523: public void startElement(String uri, String localName,
0524: String qName, Attributes atts) throws SAXException {
0525: closeElement();
0526: elementLevel++;
0527: nsSupport.pushContext();
0528: write('<');
0529: writeName(uri, localName, qName, true);
0530: writeAttributes(atts);
0531: if (elementLevel == 1) {
0532: forceNSDecls();
0533: }
0534: writeNSDecls();
0535: openElement = true;
0536: super .startElement(uri, localName, qName, atts);
0537: }
0538:
0539: /**
0540: * Write an end tag.
0541: *
0542: * Pass the event on down the filter chain for further processing.
0543: *
0544: * @param uri The Namespace URI, or the empty string if none
0545: * is available.
0546: * @param localName The element's local (unprefixed) name (required).
0547: * @param qName The element's qualified (prefixed) name, or the
0548: * empty string is none is available. This method will
0549: * use the qName as a template for generating a prefix
0550: * if necessary, but it is not guaranteed to use the
0551: * same qName.
0552: * @exception org.xml.sax.SAXException If there is an error
0553: * writing the end tag, or if a handler further down
0554: * the filter chain raises an exception.
0555: * @see org.xml.sax.ContentHandler#endElement
0556: */
0557: public void endElement(String uri, String localName, String qName)
0558: throws SAXException {
0559: if (openElement) {
0560: write("/>");
0561: openElement = false;
0562: } else {
0563: write("</");
0564: writeName(uri, localName, qName, true);
0565: write('>');
0566: }
0567: if (elementLevel == 1) {
0568: write('\n');
0569: }
0570: super .endElement(uri, localName, qName);
0571: nsSupport.popContext();
0572: elementLevel--;
0573: }
0574:
0575: /**
0576: * Write character data.
0577: *
0578: * Pass the event on down the filter chain for further processing.
0579: *
0580: * @param ch The array of characters to write.
0581: * @param start The starting position in the array.
0582: * @param length The number of characters to write.
0583: * @exception org.xml.sax.SAXException If there is an error
0584: * writing the characters, or if a handler further down
0585: * the filter chain raises an exception.
0586: * @see org.xml.sax.ContentHandler#characters
0587: */
0588: public void characters(char ch[], int start, int len)
0589: throws SAXException {
0590: closeElement();
0591: writeEsc(ch, start, len, false);
0592: super .characters(ch, start, len);
0593: }
0594:
0595: /**
0596: * Write ignorable whitespace.
0597: *
0598: * Pass the event on down the filter chain for further processing.
0599: *
0600: * @param ch The array of characters to write.
0601: * @param start The starting position in the array.
0602: * @param length The number of characters to write.
0603: * @exception org.xml.sax.SAXException If there is an error
0604: * writing the whitespace, or if a handler further down
0605: * the filter chain raises an exception.
0606: * @see org.xml.sax.ContentHandler#ignorableWhitespace
0607: */
0608: public void ignorableWhitespace(char ch[], int start, int length)
0609: throws SAXException {
0610: closeElement();
0611: writeEsc(ch, start, length, false);
0612: super .ignorableWhitespace(ch, start, length);
0613: }
0614:
0615: /**
0616: * Write a processing instruction.
0617: *
0618: * Pass the event on down the filter chain for further processing.
0619: *
0620: * @param target The PI target.
0621: * @param data The PI data.
0622: * @exception org.xml.sax.SAXException If there is an error
0623: * writing the PI, or if a handler further down
0624: * the filter chain raises an exception.
0625: * @see org.xml.sax.ContentHandler#processingInstruction
0626: */
0627: public void processingInstruction(String target, String data)
0628: throws SAXException {
0629: closeElement();
0630: write("<?");
0631: write(target);
0632: write(' ');
0633: write(data);
0634: write("?>");
0635: if (elementLevel < 1) {
0636: write('\n');
0637: }
0638: super .processingInstruction(target, data);
0639: }
0640:
0641: ////////////////////////////////////////////////////////////////////
0642: // Methods from org.xml.sax.ext.LexicalHandler.
0643: ////////////////////////////////////////////////////////////////////
0644:
0645: /**
0646: * Write start of DOCTYPE declaration.
0647: *
0648: * Pass the event on down the filter chain for further processing.
0649: *
0650: * @param name The document type name.
0651: * @param publicId The declared public identifier for the
0652: * external DTD subset, or null if none was declared.
0653: * @param systemId The declared system identifier for the
0654: * external DTD subset, or null if none was declared.
0655: * @exception org.xml.sax.SAXException If a filter
0656: * further down the chain raises an exception.
0657: * @see org.xml.sax.ext.LexicalHandler#startDTD
0658: */
0659: public void startDTD(String name, String publicId, String systemId)
0660: throws SAXException {
0661: //closeElement();
0662: inDTD = true;
0663: write("<!DOCTYPE ");
0664: write(name);
0665: boolean hasPublic = publicId != null && !publicId.equals("");
0666: if (hasPublic) {
0667: write(" PUBLIC \"");
0668: write(publicId);
0669: write('\"');
0670: }
0671: if (systemId != null && !systemId.equals("")) {
0672: if (!hasPublic) {
0673: write(" SYSTEM");
0674: }
0675: write(" \"");
0676: write(systemId);
0677: write('\"');
0678: }
0679: write(">\n\n");
0680: super .startDTD(name, publicId, systemId);
0681: }
0682:
0683: /**
0684: * Write end of DOCTYPE declaration.
0685: *
0686: * Pass the event on down the filter chain for further processing.
0687: *
0688: * @exception org.xml.sax.SAXException If a filter
0689: * further down the chain raises an exception.
0690: * @see org.xml.sax.ext.LexicalHandler#endDTD
0691: */
0692: public void endDTD() throws SAXException {
0693: inDTD = false;
0694: super .endDTD();
0695: }
0696:
0697: /*
0698: * Write entity.
0699: *
0700: * Pass the event on down the filter chain for further processing.
0701: *
0702: * @param name The name of the entity. If it is a parameter
0703: * entity, the name will begin with '%', and if it is the
0704: * external DTD subset, it will be "[dtd]".
0705: * @exception org.xml.sax.SAXException If a filter
0706: * further down the chain raises an exception.
0707: * @see org.xml.sax.ext.LexicalHandler#startEntity
0708: */
0709: public void startEntity(String name) throws SAXException {
0710: closeElement();
0711: write('&');
0712: write(name);
0713: write(';');
0714: super .startEntity(name);
0715: }
0716:
0717: /*
0718: * Filter a end entity event.
0719: *
0720: * Pass the event on down the filter chain for further processing.
0721: *
0722: * @param name The name of the entity that is ending.
0723: * @exception org.xml.sax.SAXException If a filter
0724: * further down the chain raises an exception.
0725: * @see org.xml.sax.ext.LexicalHandler#endEntity
0726: */
0727: public void endEntity(String name) throws SAXException {
0728: super .endEntity(name);
0729: }
0730:
0731: /*
0732: * Write start of CDATA.
0733: *
0734: * Pass the event on down the filter chain for further processing.
0735: *
0736: * @exception org.xml.sax.SAXException If a filter
0737: * further down the chain raises an exception.
0738: * @see org.xml.sax.ext.LexicalHandler#startCDATA
0739: */
0740: public void startCDATA() throws SAXException {
0741: closeElement();
0742: write("<![CDATA[");
0743: super .startCDATA();
0744: }
0745:
0746: /*
0747: * Write end of CDATA.
0748: *
0749: * Pass the event on down the filter chain for further processing.
0750: *
0751: * @exception org.xml.sax.SAXException If a filter
0752: * further down the chain raises an exception.
0753: * @see org.xml.sax.ext.LexicalHandler#endCDATA
0754: */
0755: public void endCDATA() throws SAXException {
0756: write("]]>");
0757: super .endCDATA();
0758: }
0759:
0760: /*
0761: * Write a comment.
0762: *
0763: * Pass the event on down the filter chain for further processing.
0764: *
0765: * @param ch An array holding the characters in the comment.
0766: * @param start The starting position in the array.
0767: * @param length The number of characters to use from the array.
0768: * @exception org.xml.sax.SAXException If a filter
0769: * further down the chain raises an exception.
0770: * @see org.xml.sax.ext.LexicalHandler#comment
0771: */
0772: public void comment(char[] ch, int start, int length)
0773: throws SAXException {
0774: if (!inDTD) {
0775: closeElement();
0776: write("<!--");
0777: write(ch, start, length);
0778: write("-->");
0779: if (elementLevel < 1) {
0780: write('\n');
0781: }
0782: }
0783: super .comment(ch, start, length);
0784: }
0785:
0786: ////////////////////////////////////////////////////////////////////
0787: // Internal methods.
0788: ////////////////////////////////////////////////////////////////////
0789:
0790: /**
0791: * Force all Namespaces to be declared.
0792: *
0793: * This method is used on the root element to ensure that
0794: * the predeclared Namespaces all appear.
0795: */
0796: private void forceNSDecls() {
0797: Iterator prefixes = forcedDeclTable.keySet().iterator();
0798: while (prefixes.hasNext()) {
0799: String prefix = (String) prefixes.next();
0800: doPrefix(prefix, null, true);
0801: }
0802: }
0803:
0804: /**
0805: * Determine the prefix for an element or attribute name.
0806: *
0807: * TODO: this method probably needs some cleanup.
0808: *
0809: * @param uri The Namespace URI.
0810: * @param qName The qualified name (optional); this will be used
0811: * to indicate the preferred prefix if none is currently
0812: * bound.
0813: * @param isElement true if this is an element name, false
0814: * if it is an attribute name (which cannot use the
0815: * default Namespace).
0816: */
0817: private String doPrefix(String uri, String qName, boolean isElement) {
0818: String defaultNS = nsSupport.getURI("");
0819: if ("".equals(uri)) {
0820: if (isElement && defaultNS != null)
0821: nsSupport.declarePrefix("", "");
0822: return null;
0823: }
0824: String prefix;
0825: if (isElement && defaultNS != null && uri.equals(defaultNS)) {
0826: prefix = "";
0827: } else {
0828: prefix = nsSupport.getPrefix(uri);
0829: }
0830: if (prefix != null) {
0831: return prefix;
0832: }
0833: prefix = (String) doneDeclTable.get(uri);
0834: if (prefix != null
0835: && ((!isElement || defaultNS != null)
0836: && "".equals(prefix) || nsSupport
0837: .getURI(prefix) != null)) {
0838: prefix = null;
0839: }
0840: if (prefix == null) {
0841: prefix = (String) prefixTable.get(uri);
0842: if (prefix != null
0843: && ((!isElement || defaultNS != null)
0844: && "".equals(prefix) || nsSupport
0845: .getURI(prefix) != null)) {
0846: prefix = null;
0847: }
0848: }
0849: if (prefix == null && qName != null && !"".equals(qName)) {
0850: int i = qName.indexOf(':');
0851: if (i == -1) {
0852: if (isElement && defaultNS == null) {
0853: prefix = "";
0854: }
0855: } else {
0856: prefix = qName.substring(0, i);
0857: }
0858: }
0859: for (; prefix == null || nsSupport.getURI(prefix) != null; prefix = "__NS"
0860: + ++prefixCounter)
0861: ;
0862: nsSupport.declarePrefix(prefix, uri);
0863: doneDeclTable.put(uri, prefix);
0864: return prefix;
0865: }
0866:
0867: /**
0868: * Write a raw character.
0869: *
0870: * @param c The character to write.
0871: * @exception org.xml.sax.SAXException If there is an error writing
0872: * the character, this method will throw an IOException
0873: * wrapped in a SAXException.
0874: */
0875: private void write(char c) throws SAXException {
0876: try {
0877: output.write(c);
0878: } catch (IOException e) {
0879: throw new SAXException(e);
0880: }
0881: }
0882:
0883: /**
0884: * Write a portion of an array of characters.
0885: *
0886: * @param cbuf Array of characters.
0887: * @param off Offset from which to start writing characters.
0888: * @param len Number of characters to write.
0889: * @exception org.xml.sax.SAXException If there is an error writing
0890: * the character, this method will throw an IOException
0891: * wrapped in a SAXException.
0892: */
0893: private void write(char[] cbuf, int off, int len)
0894: throws SAXException {
0895: try {
0896: output.write(cbuf, off, len);
0897: } catch (IOException e) {
0898: throw new SAXException(e);
0899: }
0900: }
0901:
0902: /**
0903: * Write a raw string.
0904: *
0905: * @param s
0906: * @exception org.xml.sax.SAXException If there is an error writing
0907: * the string, this method will throw an IOException
0908: * wrapped in a SAXException
0909: */
0910: private void write(String s) throws SAXException {
0911: try {
0912: output.write(s);
0913: } catch (IOException e) {
0914: throw new SAXException(e);
0915: }
0916: }
0917:
0918: /**
0919: * Write out an attribute list, escaping values.
0920: *
0921: * The names will have prefixes added to them.
0922: *
0923: * @param atts The attribute list to write.
0924: * @exception org.xml.SAXException If there is an error writing
0925: * the attribute list, this method will throw an
0926: * IOException wrapped in a SAXException.
0927: */
0928: private void writeAttributes(Attributes atts) throws SAXException {
0929: int len = atts.getLength();
0930: for (int i = 0; i < len; i++) {
0931: char ch[] = atts.getValue(i).toCharArray();
0932: write(' ');
0933: writeName(atts.getURI(i), atts.getLocalName(i), atts
0934: .getQName(i), false);
0935: write("=\"");
0936: writeEsc(ch, 0, ch.length, true);
0937: write('"');
0938: }
0939: }
0940:
0941: /**
0942: * Write an array of data characters with escaping.
0943: *
0944: * @param ch The array of characters.
0945: * @param start The starting position.
0946: * @param length The number of characters to use.
0947: * @param isAttVal true if this is an attribute value literal.
0948: * @exception org.xml.SAXException If there is an error writing
0949: * the characters, this method will throw an
0950: * IOException wrapped in a SAXException.
0951: */
0952: private void writeEsc(char ch[], int start, int length,
0953: boolean isAttVal) throws SAXException {
0954: for (int i = start; i < start + length; i++) {
0955: switch (ch[i]) {
0956: case '&':
0957: write("&");
0958: break;
0959: case '<':
0960: write("<");
0961: break;
0962: case '>':
0963: write(">");
0964: break;
0965: case '\"':
0966: if (isAttVal) {
0967: write(""");
0968: } else {
0969: write('\"');
0970: }
0971: break;
0972: default:
0973: if (ch[i] > '\u007f') {
0974: write("&#");
0975: write(Integer.toString(ch[i]));
0976: write(';');
0977: } else {
0978: write(ch[i]);
0979: }
0980: }
0981: }
0982: }
0983:
0984: /**
0985: * Write out the list of Namespace declarations.
0986: *
0987: * @exception org.xml.sax.SAXException This method will throw
0988: * an IOException wrapped in a SAXException if
0989: * there is an error writing the Namespace
0990: * declarations.
0991: */
0992: private void writeNSDecls() throws SAXException {
0993: Enumeration prefixes = nsSupport.getDeclaredPrefixes();
0994: while (prefixes.hasMoreElements()) {
0995: String prefix = (String) prefixes.nextElement();
0996: String uri = nsSupport.getURI(prefix);
0997: if (uri == null) {
0998: uri = "";
0999: }
1000: char ch[] = uri.toCharArray();
1001: write(' ');
1002: if ("".equals(prefix)) {
1003: write("xmlns=\"");
1004: } else {
1005: write("xmlns:");
1006: write(prefix);
1007: write("=\"");
1008: }
1009: writeEsc(ch, 0, ch.length, true);
1010: write('\"');
1011: }
1012: }
1013:
1014: /**
1015: * Write an element or attribute name.
1016: *
1017: * @param uri The Namespace URI.
1018: * @param localName The local name.
1019: * @param qName The prefixed name, if available, or the empty string.
1020: * @param isElement true if this is an element name, false if it
1021: * is an attribute name.
1022: * @exception org.xml.sax.SAXException This method will throw an
1023: * IOException wrapped in a SAXException if there is
1024: * an error writing the name.
1025: */
1026: private void writeName(String uri, String localName, String qName,
1027: boolean isElement) throws SAXException {
1028: String prefix = doPrefix(uri, qName, isElement);
1029: if (prefix != null && !"".equals(prefix)) {
1030: write(prefix);
1031: write(':');
1032: }
1033: write(localName);
1034: }
1035:
1036: /**
1037: * If start element tag is still open, write closing bracket.
1038: */
1039: private void closeElement() throws SAXException {
1040: if (openElement) {
1041: write('>');
1042: openElement = false;
1043: }
1044: }
1045:
1046: ////////////////////////////////////////////////////////////////////
1047: // Internal state.
1048: ////////////////////////////////////////////////////////////////////
1049:
1050: private Map prefixTable;
1051: private Map forcedDeclTable;
1052: private Map doneDeclTable;
1053: private boolean openElement = false;
1054: private int elementLevel = 0;
1055: private Writer output;
1056: private NamespaceSupport nsSupport;
1057: private int prefixCounter = 0;
1058: private boolean inDTD = false;
1059:
1060: }
1061:
1062: // end of XMLWriter.java
|