0001: /*
0002: * Copyright 2005-2007 Noelios Consulting.
0003: *
0004: * The contents of this file are subject to the terms of the Common Development
0005: * and Distribution License (the "License"). You may not use this file except in
0006: * compliance with the License.
0007: *
0008: * You can obtain a copy of the license at
0009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
0010: * language governing permissions and limitations under the License.
0011: *
0012: * When distributing Covered Code, include this CDDL HEADER in each file and
0013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
0014: * applicable, add the following below this CDDL HEADER, with the fields
0015: * enclosed by brackets "[]" replaced with your own identifying information:
0016: * Portions Copyright [yyyy] [name of copyright owner]
0017: */
0018:
0019: package org.restlet.util;
0020:
0021: import java.io.IOException;
0022: import java.io.OutputStream;
0023: import java.io.OutputStreamWriter;
0024: import java.io.UnsupportedEncodingException;
0025: import java.io.Writer;
0026: import java.nio.charset.Charset;
0027: import java.nio.charset.CharsetEncoder;
0028: import java.util.Enumeration;
0029: import java.util.Map;
0030: import java.util.Stack;
0031: import java.util.TreeMap;
0032:
0033: import org.xml.sax.Attributes;
0034: import org.xml.sax.SAXException;
0035: import org.xml.sax.XMLReader;
0036: import org.xml.sax.helpers.AttributesImpl;
0037: import org.xml.sax.helpers.NamespaceSupport;
0038: import org.xml.sax.helpers.XMLFilterImpl;
0039:
0040: /**
0041: * XML writer doing the opposite work of a SAX-based XML reader. The
0042: * implementation is based on the work of David Megginson, the creator of SAX
0043: * who placed the original code in the public domain.
0044: *
0045: * <p>
0046: * This class can be used by itself or as part of a SAX event stream: it takes
0047: * as input a series of SAX2 ContentHandler events and uses the information in
0048: * those events to write an XML document. Since this class is a filter, it can
0049: * also pass the events on down a filter chain for further processing (you can
0050: * use the XmlWriter to take a snapshot of the current state at any point in a
0051: * filter chain), and it can be used directly as a ContentHandler for a SAX2
0052: * XMLReader.
0053: * </p>
0054: *
0055: * <p>
0056: * The client creates a document by invoking the methods for standard SAX2
0057: * events, always beginning with the {@link #startDocument startDocument} method
0058: * and ending with the {@link #endDocument endDocument} method. There are
0059: * convenience methods provided so that clients to not have to create empty
0060: * attribute lists or provide empty strings as parameters; for example, the
0061: * method invocation
0062: * </p>
0063: *
0064: * <pre>
0065: * w.startElement("foo");
0066: * </pre>
0067: *
0068: * <p>
0069: * is equivalent to the regular SAX2 ContentHandler method
0070: * </p>
0071: *
0072: * <pre>
0073: * w.startElement("", "foo", "", new AttributesImpl());
0074: * </pre>
0075: *
0076: * <p>
0077: * Except that it is more efficient because it does not allocate a new empty
0078: * attribute list each time. The following code will send a simple XML document
0079: * to standard output:
0080: * </p>
0081: *
0082: * <pre>
0083: * XmlWriter w = new XmlWriter();
0084: *
0085: * w.startDocument();
0086: * w.startElement("greeting");
0087: * w.characters("Hello, world!");
0088: * w.endElement("greeting");
0089: * w.endDocument();
0090: * </pre>
0091: *
0092: * <p>
0093: * The resulting document will look like this:
0094: * </p>
0095: *
0096: * <pre>
0097: * <?xml version="1.0" standalone="yes"?>
0098: *
0099: * <greeting>Hello, world!</greeting>
0100: * </pre>
0101: *
0102: * <p>
0103: * In fact, there is an even simpler convenience method, <var>dataElement</var>,
0104: * designed for writing elements that contain only character data, so the code
0105: * to generate the document could be shortened to
0106: * </p>
0107: *
0108: * <pre>
0109: * XmlWriter w = new XmlWriter();
0110: *
0111: * w.startDocument();
0112: * w.dataElement("greeting", "Hello, world!");
0113: * w.endDocument();
0114: * </pre>
0115: *
0116: * <h2>Whitespace</h2>
0117: *
0118: * <p>
0119: * According to the XML Recommendation, <em>all</em> whitespace in an XML
0120: * document is potentially significant to an application, so this class never
0121: * adds newlines or indentation. If you insert three elements in a row, as in
0122: * </p>
0123: *
0124: * <pre>
0125: * w.dataElement("item", "1");
0126: * w.dataElement("item", "2");
0127: * w.dataElement("item", "3");
0128: * </pre>
0129: *
0130: * <p>
0131: * you will end up with
0132: * </p>
0133: *
0134: * <pre>
0135: * <item>1</item><item>3</item><item>3</item>
0136: * </pre>
0137: *
0138: * <p>
0139: * You need to invoke one of the <var>characters</var> methods explicitly to
0140: * add newlines or indentation. Alternatively, you can use the data format mode
0141: * (set the "dataFormat" property) which is optimized for writing purely
0142: * data-oriented (or field-oriented) XML, and does automatic linebreaks and
0143: * indentation (but does not support mixed content properly). See details below.
0144: * </p>
0145: *
0146: * <h2>Namespace Support</h2>
0147: *
0148: * <p>
0149: * The writer contains extensive support for XML Namespaces, so that a client
0150: * application does not have to keep track of prefixes and supply <var>xmlns</var>
0151: * attributes. By default, the XML writer will generate Namespace declarations
0152: * in the form _NS1, _NS2, etc., wherever they are needed, as in the following
0153: * example:
0154: * </p>
0155: *
0156: * <pre>
0157: * w.startDocument();
0158: * w.emptyElement("http://www.foo.com/ns/", "foo");
0159: * w.endDocument();
0160: * </pre>
0161: *
0162: * <p>
0163: * The resulting document will look like this:
0164: * </p>
0165: *
0166: * <pre>
0167: * <?xml version="1.0" standalone="yes"?>
0168: *
0169: * <_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
0170: * </pre>
0171: *
0172: * <p>
0173: * In many cases, document authors will prefer to choose their own prefixes
0174: * rather than using the (ugly) default names. The XML writer allows two methods
0175: * for selecting prefixes:
0176: * </p>
0177: *
0178: * <ol>
0179: * <li>the qualified name</li>
0180: * <li>the {@link #setPrefix setPrefix} method.</li>
0181: * </ol>
0182: *
0183: * <p>
0184: * Whenever the XML writer finds a new Namespace URI, it checks to see if a
0185: * qualified (prefixed) name is also available; if so it attempts to use the
0186: * name's prefix (as long as the prefix is not already in use for another
0187: * Namespace URI).
0188: * </p>
0189: *
0190: * <p>
0191: * Before writing a document, the client can also pre-map a prefix to a
0192: * Namespace URI with the setPrefix method:
0193: * </p>
0194: *
0195: * <pre>
0196: * w.setPrefix("http://www.foo.com/ns/", "foo");
0197: * w.startDocument();
0198: * w.emptyElement("http://www.foo.com/ns/", "foo");
0199: * w.endDocument();
0200: * </pre>
0201: *
0202: * <p>
0203: * The resulting document will look like this:
0204: * </p>
0205: *
0206: * <pre>
0207: * <?xml version="1.0" standalone="yes"?>
0208: *
0209: * <foo:foo xmlns:foo="http://www.foo.com/ns/"/>
0210: * </pre>
0211: *
0212: * <p>
0213: * The default Namespace simply uses an empty string as the prefix:
0214: * </p>
0215: *
0216: * <pre>
0217: * w.setPrefix("http://www.foo.com/ns/", "");
0218: * w.startDocument();
0219: * w.emptyElement("http://www.foo.com/ns/", "foo");
0220: * w.endDocument();
0221: * </pre>
0222: *
0223: * <p>
0224: * The resulting document will look like this:
0225: * </p>
0226: *
0227: * <pre>
0228: * <?xml version="1.0" standalone="yes"?>
0229: *
0230: * <foo xmlns="http://www.foo.com/ns/"/>
0231: * </pre>
0232: *
0233: * <p>
0234: * By default, the XML writer will not declare a Namespace until it is actually
0235: * used. Sometimes, this approach will create a large number of Namespace
0236: * declarations, as in the following example:
0237: * </p>
0238: *
0239: * <pre>
0240: * <xml version="1.0" standalone="yes"?>
0241: *
0242: * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
0243: * <rdf:Description about="http://www.foo.com/ids/books/12345">
0244: * <dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night</dc:title>
0245: * <dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith</dc:title>
0246: * <dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09</dc:title>
0247: * </rdf:Description>
0248: * </rdf:RDF>
0249: * </pre>
0250: *
0251: * <p>
0252: * The "rdf" prefix is declared only once, because the RDF Namespace is used by
0253: * the root element and can be inherited by all of its descendants; the "dc"
0254: * prefix, on the other hand, is declared three times, because no higher element
0255: * uses the Namespace. To solve this problem, you can instruct the XML writer to
0256: * predeclare Namespaces on the root element even if they are not used there:
0257: * </p>
0258: *
0259: * <pre>
0260: * w.forceNSDecl("http://www.purl.org/dc/");
0261: * </pre>
0262: *
0263: * <p>
0264: * Now, the "dc" prefix will be declared on the root element even though it's
0265: * not needed there, and can be inherited by its descendants:
0266: * </p>
0267: *
0268: * <pre>
0269: * <xml version="1.0" standalone="yes"?>
0270: *
0271: * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
0272: * xmlns:dc="http://www.purl.org/dc/">
0273: * <rdf:Description about="http://www.foo.com/ids/books/12345">
0274: * <dc:title>A Dark Night</dc:title>
0275: * <dc:creator>Jane Smith</dc:title>
0276: * <dc:date>2000-09-09</dc:title>
0277: * </rdf:Description>
0278: * </rdf:RDF>
0279: * </pre>
0280: *
0281: * <p>
0282: * This approach is also useful for declaring Namespace prefixes that be used by
0283: * qualified names appearing in attribute values or character data.
0284: * </p>
0285: *
0286: * <h2>Data Format</h2>
0287: *
0288: * <p>
0289: * This mode, enabled by the "dataFormat" property, pretty-prints field-oriented
0290: * XML without mixed content. All added indentation and newlines will be passed
0291: * on down the filter chain (if any).
0292: * </p>
0293: *
0294: * <p>
0295: * In general, all whitespace in an XML document is potentially significant, so
0296: * a general-purpose XML writing tool cannot add newlines or indentation.
0297: * </p>
0298: *
0299: * <p>
0300: * There is, however, a large class of XML documents where information is
0301: * strictly fielded: each element contains either character data or other
0302: * elements, but not both. For this special case, it is possible for a writing
0303: * tool to provide automatic indentation and newlines without requiring extra
0304: * work from the user. Note that this class will likely not yield appropriate
0305: * results for document-oriented XML like XHTML pages, which mix character data
0306: * and elements together.
0307: * </p>
0308: *
0309: * <p>
0310: * This writer mode will automatically place each start tag on a new line,
0311: * optionally indented if an indent step is provided (by default, there is no
0312: * indentation). If an element contains other elements, the end tag will also
0313: * appear on a new line with leading indentation. Consider, for example, the
0314: * following code:
0315: * </p>
0316: *
0317: * <pre>
0318: * XmlWriter w = new XmlWriter();
0319: * w.setDataFormat(true);
0320: * w.setIndentStep(2);
0321: * w.startDocument();
0322: * w.startElement("Person");
0323: * w.dataElement("name", "Jane Smith");
0324: * w.dataElement("date-of-birth", "1965-05-23");
0325: * w.dataElement("citizenship", "US");
0326: * w.endElement("Person");
0327: * w.endDocument();
0328: * </pre>
0329: *
0330: * <p>
0331: * This code will produce the following document:
0332: * </p>
0333: *
0334: * <pre>
0335: * <?xml version="1.0" standalone="yes"?>
0336: *
0337: * <Person>
0338: * <name>Jane Smith</name>
0339: * <date-of-birth>1965-05-23</date-of-birth>
0340: * <citizenship>US</citizenship>
0341: * </Person>
0342: * </pre>
0343: *
0344: * @see org.xml.sax.XMLFilter
0345: * @see org.xml.sax.ContentHandler
0346: * @author David Megginson, Jerome Louvel (contact@noelios.com)
0347: */
0348: public final class XmlWriter extends XMLFilterImpl {
0349: private static final Object SEEN_NOTHING = new Object();
0350:
0351: private static final Object SEEN_ELEMENT = new Object();
0352:
0353: private static final Object SEEN_DATA = new Object();
0354:
0355: /**
0356: * Constant representing empty attributes.
0357: */
0358: private final Attributes EMPTY_ATTS = new AttributesImpl();
0359:
0360: /**
0361: * The prefixes table.
0362: */
0363: private Map<String, String> prefixTable;
0364:
0365: /**
0366: * The forced declarations table.
0367: */
0368: private Map<String, Boolean> forcedDeclTable;
0369:
0370: /**
0371: * The document declarations table.
0372: */
0373: private Map<String, String> doneDeclTable;
0374:
0375: /**
0376: * The element level.
0377: */
0378: private int elementLevel = 0;
0379:
0380: /**
0381: * The namespace support.
0382: */
0383: private NamespaceSupport nsSupport;
0384:
0385: /**
0386: * The prefix counter.
0387: */
0388: private int prefixCounter = 0;
0389:
0390: /**
0391: * The underlying writer.
0392: */
0393: private Writer output;
0394:
0395: private Object state = SEEN_NOTHING;
0396:
0397: private Stack<Object> stateStack = new Stack<Object>();
0398:
0399: private boolean dataFormat = false;
0400:
0401: private int indentStep = 0;
0402:
0403: private int depth = 0;
0404:
0405: /**
0406: * Create a new XML writer.
0407: * <p>
0408: * Write to standard output.
0409: * </p>
0410: */
0411: public XmlWriter() {
0412: init(null);
0413: }
0414:
0415: /**
0416: * Constructor.
0417: *
0418: * @param out
0419: * The underlying output stream.
0420: */
0421: public XmlWriter(OutputStream out) {
0422: this (new OutputStreamWriter(out));
0423: }
0424:
0425: /**
0426: * Constructor.
0427: *
0428: * @param out
0429: * The underlying output stream.
0430: */
0431: public XmlWriter(OutputStream out, Charset cs) {
0432: this (new OutputStreamWriter(out, cs));
0433: }
0434:
0435: /**
0436: * Constructor.
0437: *
0438: * @param out
0439: * The underlying output stream.
0440: */
0441: public XmlWriter(OutputStream out, CharsetEncoder enc) {
0442: this (new OutputStreamWriter(out, enc));
0443: }
0444:
0445: /**
0446: * Constructor.
0447: *
0448: * @param out
0449: * The underlying output stream.
0450: */
0451: public XmlWriter(OutputStream out, String charsetName)
0452: throws UnsupportedEncodingException {
0453: this (new OutputStreamWriter(out, charsetName));
0454: }
0455:
0456: /**
0457: * Create a new XML writer.
0458: * <p>
0459: * Write to the writer provided.
0460: * </p>
0461: *
0462: * @param writer
0463: * The output destination, or null to use standard output.
0464: */
0465: public XmlWriter(Writer writer) {
0466: init(writer);
0467: }
0468:
0469: /**
0470: * Create a new XML writer.
0471: * <p>
0472: * Use the specified XML reader as the parent.
0473: * </p>
0474: *
0475: * @param xmlreader
0476: * The parent in the filter chain, or null for no parent.
0477: */
0478: public XmlWriter(XMLReader xmlreader) {
0479: super (xmlreader);
0480: init(null);
0481: }
0482:
0483: /**
0484: * Create a new XML writer.
0485: * <p>
0486: * Use the specified XML reader as the parent, and write to the specified
0487: * writer.
0488: * </p>
0489: *
0490: * @param xmlreader
0491: * The parent in the filter chain, or null for no parent.
0492: * @param writer
0493: * The output destination, or null to use standard output.
0494: */
0495: public XmlWriter(XMLReader xmlreader, Writer writer) {
0496: super (xmlreader);
0497: init(writer);
0498: }
0499:
0500: /**
0501: * Write character data. Pass the event on down the filter chain for further
0502: * processing.
0503: *
0504: * @param ch
0505: * The array of characters to write.
0506: * @param start
0507: * The starting position in the array.
0508: * @param len
0509: * The number of characters to write.
0510: * @exception org.xml.sax.SAXException
0511: * If there is an error writing the characters, or if a
0512: * restlet further down the filter chain raises an exception.
0513: * @see org.xml.sax.ContentHandler#characters
0514: */
0515: private void characters(boolean dataFormat, char ch[], int start,
0516: int len) throws SAXException {
0517: if (dataFormat) {
0518: state = SEEN_DATA;
0519: }
0520:
0521: writeEsc(ch, start, len, false);
0522: super .characters(ch, start, len);
0523: }
0524:
0525: // //////////////////////////////////////////////////////////////////
0526: // Public methods.
0527: // //////////////////////////////////////////////////////////////////
0528:
0529: /**
0530: * Write a string of character data, with XML escaping.
0531: * <p>
0532: * This is a convenience method that takes an XML String, converts it to a
0533: * character array, then invokes {@link #characters(char[], int, int)}.
0534: * </p>
0535: *
0536: * @param data
0537: * The character data.
0538: * @exception org.xml.sax.SAXException
0539: * If there is an error writing the string, or if a restlet
0540: * further down the filter chain raises an exception.
0541: * @see #characters(char[], int, int)
0542: */
0543: private void characters(boolean dataFormat, String data)
0544: throws SAXException {
0545: char ch[] = data.toCharArray();
0546: characters(dataFormat, ch, 0, ch.length);
0547: }
0548:
0549: /**
0550: * Write character data. Pass the event on down the filter chain for further
0551: * processing.
0552: *
0553: * @param ch
0554: * The array of characters to write.
0555: * @param start
0556: * The starting position in the array.
0557: * @param len
0558: * The number of characters to write.
0559: * @exception org.xml.sax.SAXException
0560: * If there is an error writing the characters, or if a
0561: * restlet further down the filter chain raises an exception.
0562: * @see org.xml.sax.ContentHandler#characters
0563: */
0564: public void characters(char ch[], int start, int len)
0565: throws SAXException {
0566: characters(isDataFormat(), ch, start, len);
0567: }
0568:
0569: /**
0570: * Write a string of character data, with XML escaping.
0571: * <p>
0572: * This is a convenience method that takes an XML String, converts it to a
0573: * character array, then invokes {@link #characters(char[], int, int)}.
0574: * </p>
0575: *
0576: * @param data
0577: * The character data.
0578: * @exception org.xml.sax.SAXException
0579: * If there is an error writing the string, or if a restlet
0580: * further down the filter chain raises an exception.
0581: * @see #characters(char[], int, int)
0582: */
0583: public void characters(String data) throws SAXException {
0584: characters(false, data);
0585: }
0586:
0587: /**
0588: * Write an element with character data content but no attributes or
0589: * Namespace URI.
0590: *
0591: * <p>
0592: * This is a convenience method to write a complete element with character
0593: * data content, including the start tag and end tag. The method provides an
0594: * empty string for the Namespace URI, and empty string for the qualified
0595: * name, and an empty attribute list.
0596: * </p>
0597: *
0598: * <p>
0599: * This method invokes
0600: * {@link #startElement(String, String, String, Attributes)}, followed by
0601: * {@link #characters(String)}, followed by
0602: * {@link #endElement(String, String, String)}.
0603: * </p>
0604: *
0605: * @param localName
0606: * The element's local name.
0607: * @param content
0608: * The character data content.
0609: * @exception org.xml.sax.SAXException
0610: * If there is an error writing the empty tag, or if a
0611: * restlet further down the filter chain raises an exception.
0612: * @see #startElement(String, String, String, Attributes)
0613: * @see #characters(String)
0614: * @see #endElement(String, String, String)
0615: */
0616: public void dataElement(String localName, String content)
0617: throws SAXException {
0618: dataElement("", localName, "", EMPTY_ATTS, content);
0619: }
0620:
0621: /**
0622: * Write an element with character data content but no attributes.
0623: *
0624: * <p>
0625: * This is a convenience method to write a complete element with character
0626: * data content, including the start tag and end tag. This method provides
0627: * an empty string for the qname and an empty attribute list.
0628: * </p>
0629: *
0630: * <p>
0631: * This method invokes
0632: * {@link #startElement(String, String, String, Attributes)}, followed by
0633: * {@link #characters(String)}, followed by
0634: * {@link #endElement(String, String, String)}.
0635: * </p>
0636: *
0637: * @param uri
0638: * The element's Namespace URI.
0639: * @param localName
0640: * The element's local name.
0641: * @param content
0642: * The character data content.
0643: * @exception org.xml.sax.SAXException
0644: * If there is an error writing the empty tag, or if a
0645: * restlet further down the filter chain raises an exception.
0646: * @see #startElement(String, String, String, Attributes)
0647: * @see #characters(String)
0648: * @see #endElement(String, String, String)
0649: */
0650: public void dataElement(String uri, String localName, String content)
0651: throws SAXException {
0652: dataElement(uri, localName, "", EMPTY_ATTS, content);
0653: }
0654:
0655: /**
0656: * Write an element with character data content.
0657: *
0658: * <p>
0659: * This is a convenience method to write a complete element with character
0660: * data content, including the start tag and end tag.
0661: * </p>
0662: *
0663: * <p>
0664: * This method invokes
0665: * {@link #startElement(String, String, String, Attributes)}, followed by
0666: * {@link #characters(String)}, followed by
0667: * {@link #endElement(String, String, String)}.
0668: * </p>
0669: *
0670: * @param uri
0671: * The element's Namespace URI.
0672: * @param localName
0673: * The element's local name.
0674: * @param qName
0675: * The element's default qualified name.
0676: * @param atts
0677: * The element's attributes.
0678: * @param content
0679: * The character data content.
0680: * @exception org.xml.sax.SAXException
0681: * If there is an error writing the empty tag, or if a
0682: * restlet further down the filter chain raises an exception.
0683: * @see #startElement(String, String, String, Attributes)
0684: * @see #characters(String)
0685: * @see #endElement(String, String, String)
0686: */
0687: public void dataElement(String uri, String localName, String qName,
0688: Attributes atts, String content) throws SAXException {
0689: startElement(uri, localName, qName, atts);
0690: characters(content);
0691: endElement(uri, localName, qName);
0692: }
0693:
0694: /**
0695: * Print indentation for the current level.
0696: *
0697: * @exception org.xml.sax.SAXException
0698: * If there is an error writing the indentation characters,
0699: * or if a filter further down the chain raises an exception.
0700: */
0701: private void doIndent() throws SAXException {
0702: if ((indentStep > 0) && (depth > 0)) {
0703: int n = indentStep * depth;
0704: char ch[] = new char[n];
0705: for (int i = 0; i < n; i++) {
0706: ch[i] = ' ';
0707: }
0708: characters(ch, 0, n);
0709: }
0710: }
0711:
0712: /**
0713: * Determine the prefix for an element or attribute name. TODO: this method
0714: * probably needs some cleanup.
0715: *
0716: * @param uri
0717: * The Namespace URI.
0718: * @param qName
0719: * The qualified name (optional); this will be used to indicate
0720: * the preferred prefix if none is currently bound.
0721: * @param isElement
0722: * true if this is an element name, false if it is an attribute
0723: * name (which cannot use the default Namespace).
0724: */
0725: private String doPrefix(String uri, String qName, boolean isElement) {
0726: String defaultNS = nsSupport.getURI("");
0727: if ("".equals(uri)) {
0728: if (isElement && (defaultNS != null))
0729: nsSupport.declarePrefix("", "");
0730: return null;
0731: }
0732: String prefix;
0733: if (isElement && (defaultNS != null) && uri.equals(defaultNS)) {
0734: prefix = "";
0735: } else {
0736: prefix = nsSupport.getPrefix(uri);
0737: }
0738: if (prefix != null) {
0739: return prefix;
0740: }
0741: prefix = doneDeclTable.get(uri);
0742: if ((prefix != null)
0743: && (((!isElement || (defaultNS != null)) && ""
0744: .equals(prefix)) || (nsSupport.getURI(prefix) != null))) {
0745: prefix = null;
0746: }
0747: if (prefix == null) {
0748: prefix = prefixTable.get(uri);
0749: if ((prefix != null)
0750: && (((!isElement || (defaultNS != null)) && ""
0751: .equals(prefix)) || (nsSupport
0752: .getURI(prefix) != null))) {
0753: prefix = null;
0754: }
0755: }
0756: if ((prefix == null) && (qName != null) && !"".equals(qName)) {
0757: int i = qName.indexOf(':');
0758: if (i == -1) {
0759: if (isElement && (defaultNS == null)) {
0760: prefix = "";
0761: }
0762: } else {
0763: prefix = qName.substring(0, i);
0764: }
0765: }
0766: for (; (prefix == null) || (nsSupport.getURI(prefix) != null); prefix = "__NS"
0767: + ++prefixCounter)
0768: ;
0769: nsSupport.declarePrefix(prefix, uri);
0770: doneDeclTable.put(uri, prefix);
0771: return prefix;
0772: }
0773:
0774: // //////////////////////////////////////////////////////////////////
0775: // Methods from org.xml.sax.ContentHandler.
0776: // //////////////////////////////////////////////////////////////////
0777:
0778: /**
0779: * Add an empty element without a Namespace URI, qname or attributes.
0780: *
0781: * <p>
0782: * This method will supply an empty string for the qname, and empty string
0783: * for the Namespace URI, and an empty attribute list. It invokes
0784: * {@link #emptyElement(String, String, String, Attributes)} directly.
0785: * </p>
0786: *
0787: * @param localName
0788: * The element's local name.
0789: * @exception org.xml.sax.SAXException
0790: * If there is an error writing the empty tag, or if a
0791: * restlet further down the filter chain raises an exception.
0792: * @see #emptyElement(String, String, String, Attributes)
0793: */
0794: public void emptyElement(String localName) throws SAXException {
0795: emptyElement("", localName, "", EMPTY_ATTS);
0796: }
0797:
0798: /**
0799: * Add an empty element without a qname or attributes.
0800: *
0801: * <p>
0802: * This method will supply an empty string for the qname and an empty
0803: * attribute list. It invokes
0804: * {@link #emptyElement(String, String, String, Attributes)} directly.
0805: * </p>
0806: *
0807: * @param uri
0808: * The element's Namespace URI.
0809: * @param localName
0810: * The element's local name.
0811: * @exception org.xml.sax.SAXException
0812: * If there is an error writing the empty tag, or if a
0813: * restlet further down the filter chain raises an exception.
0814: * @see #emptyElement(String, String, String, Attributes)
0815: */
0816: public void emptyElement(String uri, String localName)
0817: throws SAXException {
0818: emptyElement(uri, localName, "", EMPTY_ATTS);
0819: }
0820:
0821: /**
0822: * Write an empty element. This method writes an empty element tag rather
0823: * than a start tag followed by an end tag. Both a
0824: * {@link #startElement startElement} and an {@link #endElement endElement}
0825: * event will be passed on down the filter chain.
0826: *
0827: * @param uri
0828: * The element's Namespace URI, or the empty string if the
0829: * element has no Namespace or if Namespace processing is not
0830: * being performed.
0831: * @param localName
0832: * The element's local name (without prefix). This parameter must
0833: * be provided.
0834: * @param qName
0835: * The element's qualified name (with prefix), or the empty
0836: * string if none is available. This parameter is strictly
0837: * advisory: the writer may or may not use the prefix attached.
0838: * @param atts
0839: * The element's attribute list.
0840: * @exception org.xml.sax.SAXException
0841: * If there is an error writing the empty tag, or if a
0842: * restlet further down the filter chain raises an exception.
0843: * @see #startElement
0844: * @see #endElement
0845: */
0846: public void emptyElement(String uri, String localName,
0847: String qName, Attributes atts) throws SAXException {
0848: if (isDataFormat()) {
0849: state = SEEN_ELEMENT;
0850: if (depth > 0) {
0851: characters(false, "\n");
0852: }
0853: doIndent();
0854: }
0855:
0856: nsSupport.pushContext();
0857: write('<');
0858: writeName(uri, localName, qName, true);
0859: writeAttributes(atts);
0860: if (elementLevel == 1) {
0861: forceNSDecls();
0862: }
0863: writeNSDecls();
0864: write("/>");
0865: super .startElement(uri, localName, qName, atts);
0866: super .endElement(uri, localName, qName);
0867: }
0868:
0869: /**
0870: * Write a newline at the end of the document. Pass the event on down the
0871: * filter chain for further processing.
0872: *
0873: * @exception org.xml.sax.SAXException
0874: * If there is an error writing the newline, or if a restlet
0875: * further down the filter chain raises an exception.
0876: * @see org.xml.sax.ContentHandler#endDocument
0877: */
0878: public void endDocument() throws SAXException {
0879: write('\n');
0880: super .endDocument();
0881: try {
0882: flush();
0883: } catch (IOException e) {
0884: throw new SAXException(e);
0885: }
0886: }
0887:
0888: /**
0889: * End an element without a Namespace URI or qname.
0890: *
0891: * <p>
0892: * This method will supply an empty string for the qName and an empty string
0893: * for the Namespace URI. It invokes
0894: * {@link #endElement(String, String, String)} directly.
0895: * </p>
0896: *
0897: * @param localName
0898: * The element's local name.
0899: * @exception org.xml.sax.SAXException
0900: * If there is an error writing the end tag, or if a restlet
0901: * further down the filter chain raises an exception.
0902: * @see #endElement(String, String, String)
0903: */
0904: public void endElement(String localName) throws SAXException {
0905: endElement("", localName, "");
0906: }
0907:
0908: /**
0909: * End an element without a qname.
0910: *
0911: * <p>
0912: * This method will supply an empty string for the qName. It invokes
0913: * {@link #endElement(String, String, String)} directly.
0914: * </p>
0915: *
0916: * @param uri
0917: * The element's Namespace URI.
0918: * @param localName
0919: * The element's local name.
0920: * @exception org.xml.sax.SAXException
0921: * If there is an error writing the end tag, or if a restlet
0922: * further down the filter chain raises an exception.
0923: * @see #endElement(String, String, String)
0924: */
0925: public void endElement(String uri, String localName)
0926: throws SAXException {
0927: endElement(uri, localName, "");
0928: }
0929:
0930: /**
0931: * Write an end tag. Pass the event on down the filter chain for further
0932: * processing.
0933: *
0934: * @param uri
0935: * The Namespace URI, or the empty string if none is available.
0936: * @param localName
0937: * The element's local (unprefixed) name (required).
0938: * @param qName
0939: * The element's qualified (prefixed) name, or the empty string
0940: * is none is available. This method will use the qName as a
0941: * template for generating a prefix if necessary, but it is not
0942: * guaranteed to use the same qName.
0943: * @exception org.xml.sax.SAXException
0944: * If there is an error writing the end tag, or if a restlet
0945: * further down the filter chain raises an exception.
0946: * @see org.xml.sax.ContentHandler#endElement
0947: */
0948: public void endElement(String uri, String localName, String qName)
0949: throws SAXException {
0950: if (isDataFormat()) {
0951: depth--;
0952: if (state == SEEN_ELEMENT) {
0953: characters(false, "\n");
0954: doIndent();
0955: }
0956: }
0957:
0958: write("</");
0959: writeName(uri, localName, qName, true);
0960: write('>');
0961: if (elementLevel == 1) {
0962: write('\n');
0963: }
0964: super .endElement(uri, localName, qName);
0965: nsSupport.popContext();
0966: elementLevel--;
0967:
0968: if (isDataFormat()) {
0969: state = stateStack.pop();
0970: }
0971: }
0972:
0973: /**
0974: * Flush the output.
0975: * <p>
0976: * This method flushes the output stream. It is especially useful when you
0977: * need to make certain that the entire document has been written to output
0978: * but do not want to close the output stream.
0979: * </p>
0980: * <p>
0981: * This method is invoked automatically by the
0982: * {@link #endDocument endDocument} method after writing a document.
0983: * </p>
0984: *
0985: * @see #reset
0986: */
0987: public void flush() throws IOException {
0988: output.flush();
0989: }
0990:
0991: // //////////////////////////////////////////////////////////////////
0992: // Additional markup.
0993: // //////////////////////////////////////////////////////////////////
0994:
0995: /**
0996: * Force a Namespace to be declared on the root element.
0997: * <p>
0998: * By default, the XMLWriter will declare only the Namespaces needed for an
0999: * element; as a result, a Namespace may be declared many places in a
1000: * document if it is not used on the root element.
1001: * </p>
1002: * <p>
1003: * This method forces a Namespace to be declared on the root element even if
1004: * it is not used there, and reduces the number of xmlns attributes in the
1005: * document.
1006: * </p>
1007: *
1008: * @param uri
1009: * The Namespace URI to declare.
1010: * @see #forceNSDecl(java.lang.String,java.lang.String)
1011: * @see #setPrefix
1012: */
1013: public void forceNSDecl(String uri) {
1014: forcedDeclTable.put(uri, Boolean.TRUE);
1015: }
1016:
1017: // //////////////////////////////////////////////////////////////////
1018: // Convenience methods.
1019: // //////////////////////////////////////////////////////////////////
1020:
1021: /**
1022: * Force a Namespace declaration with a preferred prefix.
1023: * <p>
1024: * This is a convenience method that invokes {@link #setPrefix setPrefix}
1025: * then {@link #forceNSDecl(java.lang.String) forceNSDecl}.
1026: * </p>
1027: *
1028: * @param uri
1029: * The Namespace URI to declare on the root element.
1030: * @param prefix
1031: * The preferred prefix for the Namespace, or "" for the default
1032: * Namespace.
1033: * @see #setPrefix
1034: * @see #forceNSDecl(java.lang.String)
1035: */
1036: public void forceNSDecl(String uri, String prefix) {
1037: setPrefix(uri, prefix);
1038: forceNSDecl(uri);
1039: }
1040:
1041: /**
1042: * Force all Namespaces to be declared. This method is used on the root
1043: * element to ensure that the predeclared Namespaces all appear.
1044: */
1045: private void forceNSDecls() {
1046: for (String prefix : forcedDeclTable.keySet()) {
1047: doPrefix(prefix, null, true);
1048: }
1049: }
1050:
1051: /**
1052: * Return the current indent step.
1053: * <p>
1054: * Return the current indent step: each start tag will be indented by this
1055: * number of spaces times the number of ancestors that the element has.
1056: * </p>
1057: *
1058: * @return The number of spaces in each indentation step, or 0 or less for
1059: * no indentation.
1060: */
1061: public int getIndentStep() {
1062: return indentStep;
1063: }
1064:
1065: /**
1066: * Get the current or preferred prefix for a Namespace URI.
1067: *
1068: * @param uri
1069: * The Namespace URI.
1070: * @return The preferred prefix, or "" for the default Namespace.
1071: * @see #setPrefix
1072: */
1073: public String getPrefix(String uri) {
1074: return prefixTable.get(uri);
1075: }
1076:
1077: /**
1078: * Returns the underlying writer.
1079: *
1080: * @return The underlying writer.
1081: */
1082: public Writer getWriter() {
1083: return this .output;
1084: }
1085:
1086: /**
1087: * Write ignorable whitespace. Pass the event on down the filter chain for
1088: * further processing.
1089: *
1090: * @param ch
1091: * The array of characters to write.
1092: * @param start
1093: * The starting position in the array.
1094: * @param length
1095: * The number of characters to write.
1096: * @exception org.xml.sax.SAXException
1097: * If there is an error writing the whitespace, or if a
1098: * restlet further down the filter chain raises an exception.
1099: * @see org.xml.sax.ContentHandler#ignorableWhitespace
1100: */
1101: public void ignorableWhitespace(char ch[], int start, int length)
1102: throws SAXException {
1103: writeEsc(ch, start, length, false);
1104: super .ignorableWhitespace(ch, start, length);
1105: }
1106:
1107: /**
1108: * Internal initialization method.
1109: *
1110: * <p>
1111: * All of the public constructors invoke this method.
1112: *
1113: * @param writer
1114: * The output destination, or null to use standard output.
1115: */
1116: private void init(Writer writer) {
1117: setOutput(writer);
1118: nsSupport = new NamespaceSupport();
1119: prefixTable = new TreeMap<String, String>();
1120: forcedDeclTable = new TreeMap<String, Boolean>();
1121: doneDeclTable = new TreeMap<String, String>();
1122: }
1123:
1124: public boolean isDataFormat() {
1125: return this .dataFormat;
1126: }
1127:
1128: /**
1129: * Write a processing instruction. Pass the event on down the filter chain
1130: * for further processing.
1131: *
1132: * @param target
1133: * The PI target.
1134: * @param data
1135: * The PI data.
1136: * @exception org.xml.sax.SAXException
1137: * If there is an error writing the PI, or if a restlet
1138: * further down the filter chain raises an exception.
1139: * @see org.xml.sax.ContentHandler#processingInstruction
1140: */
1141: public void processingInstruction(String target, String data)
1142: throws SAXException {
1143: write("<?");
1144: write(target);
1145: write(' ');
1146: write(data);
1147: write("?>");
1148: if (elementLevel < 1) {
1149: write('\n');
1150: }
1151: super .processingInstruction(target, data);
1152: }
1153:
1154: /**
1155: * Reset the writer.
1156: *
1157: * <p>
1158: * This method is especially useful if the writer throws an exception before
1159: * it is finished, and you want to reuse the writer for a new document. It
1160: * is usually a good idea to invoke {@link #flush flush} before resetting
1161: * the writer, to make sure that no output is lost.
1162: * </p>
1163: *
1164: * <p>
1165: * This method is invoked automatically by the
1166: * {@link #startDocument startDocument} method before writing a new
1167: * document.
1168: * </p>
1169: *
1170: * <p>
1171: * <strong>Note:</strong> this method will <em>not</em> clear the prefix
1172: * or URI information in the writer or the selected output writer.
1173: * </p>
1174: *
1175: * @see #flush
1176: */
1177: public void reset() {
1178: if (isDataFormat()) {
1179: depth = 0;
1180: state = SEEN_NOTHING;
1181: stateStack = new Stack<Object>();
1182: }
1183:
1184: elementLevel = 0;
1185: prefixCounter = 0;
1186: nsSupport.reset();
1187: }
1188:
1189: public void setDataFormat(boolean dataFormat) {
1190: this .dataFormat = dataFormat;
1191: }
1192:
1193: // //////////////////////////////////////////////////////////////////
1194: // Internal methods.
1195: // //////////////////////////////////////////////////////////////////
1196:
1197: /**
1198: * Set the current indent step.
1199: *
1200: * @param indentStep
1201: * The new indent step (0 or less for no indentation).
1202: */
1203: public void setIndentStep(int indentStep) {
1204: this .indentStep = indentStep;
1205: }
1206:
1207: /**
1208: * Set a new output destination for the document.
1209: *
1210: * @param writer
1211: * The output destination, or null to use standard output.
1212: * @see #flush
1213: */
1214: public void setOutput(Writer writer) {
1215: if (writer == null) {
1216: output = new OutputStreamWriter(System.out);
1217: } else {
1218: output = writer;
1219: }
1220: }
1221:
1222: /**
1223: * Specify a preferred prefix for a Namespace URI.
1224: * <p>
1225: * Note that this method does not actually force the Namespace to be
1226: * declared; to do that, use the
1227: * {@link #forceNSDecl(java.lang.String) forceNSDecl} method as well.
1228: * </p>
1229: *
1230: * @param uri
1231: * The Namespace URI.
1232: * @param prefix
1233: * The preferred prefix, or "" to select the default Namespace.
1234: * @see #getPrefix
1235: * @see #forceNSDecl(java.lang.String)
1236: * @see #forceNSDecl(java.lang.String,java.lang.String)
1237: */
1238: public void setPrefix(String uri, String prefix) {
1239: prefixTable.put(uri, prefix);
1240: }
1241:
1242: /**
1243: * Write the XML declaration at the beginning of the document. Pass the
1244: * event on down the filter chain for further processing.
1245: *
1246: * @exception org.xml.sax.SAXException
1247: * If there is an error writing the XML declaration, or if a
1248: * restlet further down the filter chain raises an exception.
1249: * @see org.xml.sax.ContentHandler#startDocument
1250: */
1251: public void startDocument() throws SAXException {
1252: reset();
1253: write("<?xml version=\"1.0\" standalone=\"yes\"?>\n\n");
1254: super .startDocument();
1255: }
1256:
1257: /**
1258: * Start a new element without a qname, attributes or a Namespace URI.
1259: *
1260: * <p>
1261: * This method will provide an empty string for the Namespace URI, and empty
1262: * string for the qualified name, and a default empty attribute list. It
1263: * invokes #startElement(String, String, String, Attributes)} directly.
1264: * </p>
1265: *
1266: * @param localName
1267: * The element's local name.
1268: * @exception org.xml.sax.SAXException
1269: * If there is an error writing the start tag, or if a
1270: * restlet further down the filter chain raises an exception.
1271: * @see #startElement(String, String, String, Attributes)
1272: */
1273: public void startElement(String localName) throws SAXException {
1274: startElement("", localName, "", EMPTY_ATTS);
1275: }
1276:
1277: /**
1278: * Start a new element without a qname or attributes.
1279: *
1280: * <p>
1281: * This method will provide a default empty attribute list and an empty
1282: * string for the qualified name. It invokes
1283: * {@link #startElement(String, String, String, Attributes)} directly.
1284: * </p>
1285: *
1286: * @param uri
1287: * The element's Namespace URI.
1288: * @param localName
1289: * The element's local name.
1290: * @exception org.xml.sax.SAXException
1291: * If there is an error writing the start tag, or if a
1292: * restlet further down the filter chain raises an exception.
1293: * @see #startElement(String, String, String, Attributes)
1294: */
1295: public void startElement(String uri, String localName)
1296: throws SAXException {
1297: startElement(uri, localName, "", EMPTY_ATTS);
1298: }
1299:
1300: /**
1301: * Write a start tag. Pass the event on down the filter chain for further
1302: * processing.
1303: *
1304: * @param uri
1305: * The Namespace URI, or the empty string if none is available.
1306: * @param localName
1307: * The element's local (unprefixed) name (required).
1308: * @param qName
1309: * The element's qualified (prefixed) name, or the empty string
1310: * is none is available. This method will use the qName as a
1311: * template for generating a prefix if necessary, but it is not
1312: * guaranteed to use the same qName.
1313: * @param atts
1314: * The element's attribute list (must not be null).
1315: * @exception org.xml.sax.SAXException
1316: * If there is an error writing the start tag, or if a
1317: * restlet further down the filter chain raises an exception.
1318: * @see org.xml.sax.ContentHandler#startElement
1319: */
1320: public void startElement(String uri, String localName,
1321: String qName, Attributes atts) throws SAXException {
1322: if (isDataFormat()) {
1323: stateStack.push(SEEN_ELEMENT);
1324: state = SEEN_NOTHING;
1325: if (depth > 0) {
1326: characters("\n");
1327: }
1328: doIndent();
1329: }
1330:
1331: elementLevel++;
1332: nsSupport.pushContext();
1333: write('<');
1334: writeName(uri, localName, qName, true);
1335: writeAttributes(atts);
1336: if (elementLevel == 1) {
1337: forceNSDecls();
1338: }
1339: writeNSDecls();
1340: write('>');
1341: super .startElement(uri, localName, qName, atts);
1342:
1343: if (isDataFormat()) {
1344: depth++;
1345: }
1346: }
1347:
1348: /**
1349: * Write a raw character.
1350: *
1351: * @param c
1352: * The character to write.
1353: * @exception org.xml.sax.SAXException
1354: * If there is an error writing the character, this method
1355: * will throw an IOException wrapped in a SAXException.
1356: */
1357: private void write(char c) throws SAXException {
1358: try {
1359: output.write(c);
1360: } catch (IOException e) {
1361: throw new SAXException(e);
1362: }
1363: }
1364:
1365: /**
1366: * Write a raw string.
1367: *
1368: * @param s
1369: * @exception org.xml.sax.SAXException
1370: * If there is an error writing the string, this method will
1371: * throw an IOException wrapped in a SAXException
1372: */
1373: private void write(String s) throws SAXException {
1374: try {
1375: output.write(s);
1376: } catch (IOException e) {
1377: throw new SAXException(e);
1378: }
1379: }
1380:
1381: /**
1382: * Write out an attribute list, escaping values. The names will have
1383: * prefixes added to them.
1384: *
1385: * @param atts
1386: * The attribute list to write.
1387: * @exception org.xml.SAXException
1388: * If there is an error writing the attribute list, this
1389: * method will throw an IOException wrapped in a
1390: * SAXException.
1391: */
1392: private void writeAttributes(Attributes atts) throws SAXException {
1393: int len = atts.getLength();
1394: for (int i = 0; i < len; i++) {
1395: char ch[] = atts.getValue(i).toCharArray();
1396: write(' ');
1397: writeName(atts.getURI(i), atts.getLocalName(i), atts
1398: .getQName(i), false);
1399: write("=\"");
1400: writeEsc(ch, 0, ch.length, true);
1401: write('"');
1402: }
1403: }
1404:
1405: /**
1406: * Write an array of data characters with escaping.
1407: *
1408: * @param ch
1409: * The array of characters.
1410: * @param start
1411: * The starting position.
1412: * @param length
1413: * The number of characters to use.
1414: * @param isAttVal
1415: * true if this is an attribute value literal.
1416: * @exception org.xml.SAXException
1417: * If there is an error writing the characters, this method
1418: * will throw an IOException wrapped in a SAXException.
1419: */
1420: private void writeEsc(char ch[], int start, int length,
1421: boolean isAttVal) throws SAXException {
1422: for (int i = start; i < start + length; i++) {
1423: switch (ch[i]) {
1424: case '&':
1425: write("&");
1426: break;
1427: case '<':
1428: write("<");
1429: break;
1430: case '>':
1431: write(">");
1432: break;
1433: case '\"':
1434: if (isAttVal) {
1435: write(""");
1436: } else {
1437: write('\"');
1438: }
1439: break;
1440: default:
1441: if (ch[i] > '\u007f') {
1442: write("&#");
1443: write(Integer.toString(ch[i]));
1444: write(';');
1445: } else {
1446: write(ch[i]);
1447: }
1448: }
1449: }
1450: }
1451:
1452: /**
1453: * Write an element or attribute name.
1454: *
1455: * @param uri
1456: * The Namespace URI.
1457: * @param localName
1458: * The local name.
1459: * @param qName
1460: * The prefixed name, if available, or the empty string.
1461: * @param isElement
1462: * true if this is an element name, false if it is an attribute
1463: * name.
1464: * @exception org.xml.sax.SAXException
1465: * This method will throw an IOException wrapped in a
1466: * SAXException if there is an error writing the name.
1467: */
1468: private void writeName(String uri, String localName, String qName,
1469: boolean isElement) throws SAXException {
1470: String prefix = doPrefix(uri, qName, isElement);
1471: if ((prefix != null) && !"".equals(prefix)) {
1472: write(prefix);
1473: write(':');
1474: }
1475: write(localName);
1476: }
1477:
1478: /**
1479: * Write out the list of Namespace declarations.
1480: *
1481: * @exception org.xml.sax.SAXException
1482: * This method will throw an IOException wrapped in a
1483: * SAXException if there is an error writing the Namespace
1484: * declarations.
1485: */
1486: @SuppressWarnings("unchecked")
1487: private void writeNSDecls() throws SAXException {
1488: Enumeration<String> prefixes = nsSupport.getDeclaredPrefixes();
1489: while (prefixes.hasMoreElements()) {
1490: String prefix = prefixes.nextElement();
1491: String uri = nsSupport.getURI(prefix);
1492: if (uri == null) {
1493: uri = "";
1494: }
1495: char ch[] = uri.toCharArray();
1496: write(' ');
1497: if ("".equals(prefix)) {
1498: write("xmlns=\"");
1499: } else {
1500: write("xmlns:");
1501: write(prefix);
1502: write("=\"");
1503: }
1504: writeEsc(ch, 0, ch.length, true);
1505: write('\"');
1506: }
1507: }
1508:
1509: }
|