0001: // XMLWriter.java - serialize an XML document.
0002: // Written by David Megginson, david@megginson.com
0003: // NO WARRANTY! This class is in the public domain.
0004:
0005: package nu.xom.tests;
0006:
0007: import java.io.IOException;
0008: import java.io.OutputStreamWriter;
0009: import java.io.Writer;
0010: import java.util.Enumeration;
0011: import java.util.Hashtable;
0012:
0013: import org.xml.sax.Attributes;
0014: import org.xml.sax.SAXException;
0015: import org.xml.sax.ext.LexicalHandler;
0016: import org.xml.sax.helpers.AttributesImpl;
0017: import org.xml.sax.helpers.NamespaceSupport;
0018: import org.xml.sax.helpers.XMLFilterImpl;
0019:
0020: /**
0021: * <p>
0022: * The original version of this class was written and placed in the
0023: * public domain by David Megginson. Elliotte Rusty Harold added
0024: * <code>LexicalHandler</code> support. It is included here purely
0025: * for help with testing the <code>SAXConverter</code> class. It is
0026: * not part of the XOM API; nor is it used internally by XOM anywhere
0027: * except in the <code>SAXConverter</code> tests.
0028: * </p>
0029: *
0030: * <p>
0031: * This class does not properly preserve additional namespace
0032: * declarations in non-root elements. If you encounter that, the
0033: * bug is here, not in <code>SAXConverter</code>.
0034: * </p>
0035: *
0036: * @author David Megginson, Elliotte Rusty Harold
0037: * @version 1.0
0038: */
0039: class XMLWriter extends XMLFilterImpl implements LexicalHandler {
0040:
0041: ///////////////////////////////////////////////////////////////////
0042: // Constructors.
0043: ///////////////////////////////////////////////////////////////////
0044:
0045: /**
0046: * <p>
0047: * Create a new XML writer.
0048: * </p>
0049: *
0050: * <p>Write to standard output.</p>
0051: */
0052: public XMLWriter() {
0053: init(null);
0054: }
0055:
0056: /**
0057: * <p>
0058: * Create a new XML writer.
0059: * </p>
0060: *
0061: * <p>Write to the writer provided.</p>
0062: *
0063: * @param writer the output destination, or null to use standard
0064: * output
0065: */
0066: public XMLWriter(Writer writer) {
0067: init(writer);
0068: }
0069:
0070: /**
0071: * <p>
0072: * Internal initialization method.
0073: * </p>
0074: *
0075: * <p>All of the public constructors invoke this method.
0076: *
0077: * @param writer the output destination, or null to use
0078: * standard output.
0079: */
0080: private void init(Writer writer) {
0081: setOutput(writer);
0082: nsSupport = new NamespaceSupport();
0083: prefixTable = new Hashtable();
0084: forcedDeclTable = new Hashtable();
0085: doneDeclTable = new Hashtable();
0086: }
0087:
0088: ///////////////////////////////////////////////////////////////////
0089: // Public methods.
0090: ///////////////////////////////////////////////////////////////////
0091:
0092: /**
0093: * <p>
0094: * Reset the writer.
0095: * </p>
0096: *
0097: * <p>This method is especially useful if the writer throws an
0098: * exception before it is finished, and you want to reuse the
0099: * writer for a new document. It is usually a good idea to
0100: * invoke {@link #flush flush} before resetting the writer,
0101: * to make sure that no output is lost.</p>
0102: *
0103: * <p>This method is invoked automatically by the
0104: * {@link #startDocument startDocument} method before writing
0105: * a new document.</p>
0106: *
0107: * <p><strong>Note:</strong> this method will <em>not</em>
0108: * clear the prefix or URI information in the writer or
0109: * the selected output writer.</p>
0110: *
0111: * @see #flush
0112: */
0113: public void reset() {
0114: elementLevel = 0;
0115: prefixCounter = 0;
0116: nsSupport.reset();
0117: }
0118:
0119: /**
0120: * <p>
0121: * Flush the output.
0122: * </p>
0123: *
0124: * <p>This method flushes the output stream. It is especially useful
0125: * when you need to make certain that the entire document has
0126: * been written to output but do not want to close the output
0127: * stream.</p>
0128: *
0129: * <p>This method is invoked automatically by the
0130: * {@link #endDocument endDocument} method after writing a
0131: * document.</p>
0132: *
0133: * @throws IOException
0134: *
0135: * @see #reset
0136: *
0137: */
0138: public void flush() throws IOException {
0139: output.flush();
0140: }
0141:
0142: /**
0143: * <p>
0144: * Set a new output destination for the document.
0145: * </p>
0146: *
0147: * @param writer the output destination, or null to use
0148: * standard output
0149: *
0150: * @return the current output writer
0151: *
0152: * @see #flush
0153: */
0154: public void setOutput(Writer writer) {
0155: if (writer == null) {
0156: output = new OutputStreamWriter(System.out);
0157: } else {
0158: output = writer;
0159: }
0160: }
0161:
0162: /**
0163: * <p>
0164: * Specify a preferred prefix for a namespace URI.
0165: * </p>
0166: *
0167: * <p>Note that this method does not actually force the namespace
0168: * to be declared; to do that, use the {@link
0169: * #forceNSDecl(java.lang.String) forceNSDecl} method as well.</p>
0170: *
0171: * @param uri the namespace URI
0172: * @param prefix the preferred prefix, or "" to select
0173: * the default namespace
0174: *
0175: * @see #getPrefix
0176: * @see #forceNSDecl(java.lang.String)
0177: * @see #forceNSDecl(java.lang.String,java.lang.String)
0178: */
0179: public void setPrefix(String uri, String prefix) {
0180: prefixTable.put(uri, prefix);
0181: }
0182:
0183: /**
0184: * <p>
0185: * Get the current or preferred prefix for a namespace URI.
0186: * </p>
0187: *
0188: * @param uri the namespace URI
0189: *
0190: * @return the preferred prefix, or "" for the default namespace
0191: *
0192: * @see #setPrefix
0193: */
0194: public String getPrefix(String uri) {
0195: return (String) prefixTable.get(uri);
0196: }
0197:
0198: public void startPrefixMapping(String prefix, String uri) {
0199: this .forceNSDecl(uri, prefix);
0200: }
0201:
0202: /**
0203: * <p>
0204: * Force a namespace to be declared on the root element.
0205: * </p>
0206: *
0207: * <p>By default, the XMLWriter will declare only the namespaces
0208: * needed for an element; as a result, a namespace may be
0209: * declared many places in a document if it is not used on the
0210: * root element.</p>
0211: *
0212: * <p>This method forces a namespace to be declared on the root
0213: * element even if it is not used there, and reduces the number
0214: * of xmlns attributes in the document.</p>
0215: *
0216: * @param uri the namespace URI to declare
0217: *
0218: * @see #forceNSDecl(java.lang.String,java.lang.String)
0219: * @see #setPrefix
0220: */
0221: public void forceNSDecl(String uri) {
0222: forcedDeclTable.put(uri, Boolean.TRUE);
0223: }
0224:
0225: /**
0226: * <p>
0227: * Force a namespace declaration with a preferred prefix.
0228: * </p>
0229: *
0230: * <p>This is a convenience method that invokes {@link
0231: * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String)
0232: * forceNSDecl}.</p>
0233: *
0234: * @param uri the namespace URI to declare on the root element
0235: * @param prefix the preferred prefix for the namespace, or ""
0236: * for the default namespace
0237: *
0238: * @see #setPrefix
0239: * @see #forceNSDecl(java.lang.String)
0240: */
0241: public void forceNSDecl(String uri, String prefix) {
0242: setPrefix(uri, prefix);
0243: forceNSDecl(uri);
0244: }
0245:
0246: ///////////////////////////////////////////////////////////////////
0247: // Methods from org.xml.sax.ContentHandler.
0248: ///////////////////////////////////////////////////////////////////
0249:
0250: /**
0251: * <p>
0252: * Write the XML declaration at the beginning of the document.
0253: * </p>
0254: *
0255: * <p>
0256: * Pass the event on down the filter chain for further processing.
0257: * </p>
0258: *
0259: * @throws org.xml.sax.SAXException if there is an error
0260: * writing the XML declaration, or if a handler further
0261: * down the filter chain raises an exception
0262: *
0263: * @see org.xml.sax.ContentHandler#startDocument
0264: */
0265: public void startDocument() throws SAXException {
0266: reset();
0267: write("<?xml version=\"1.0\" standalone=\"yes\"?>\n\n");
0268: super .startDocument();
0269: }
0270:
0271: /**
0272: * <p>
0273: * Write a newline at the end of the document.
0274: * </p>
0275: *
0276: * <p>
0277: * Pass the event on down the filter chain for further processing.
0278: * </p>
0279: *
0280: * @throws org.xml.sax.SAXException if there is an error
0281: * writing the newline, or if a handler further down
0282: * the filter chain raises an exception
0283: *
0284: * @see org.xml.sax.ContentHandler#endDocument
0285: */
0286: public void endDocument() throws SAXException {
0287: write('\n');
0288: super .endDocument();
0289: try {
0290: flush();
0291: } catch (IOException ex) {
0292: throw new SAXException(ex);
0293: }
0294: }
0295:
0296: /**
0297: * <p>
0298: * Write a start-tag.
0299: * </p>
0300: *
0301: * <p>
0302: * Pass the event on down the filter chain for further processing.
0303: * </p>
0304: *
0305: * @param uri the namespace URI, or the empty string if none
0306: * is available
0307: * @param localName the element's local (unprefixed) name (required)
0308: * @param qualifiedName the element's qualified (prefixed) name,
0309: * or the empty string is none is available. This method
0310: * will use the qualified name as a template for generating
0311: * a prefix if necessary, but it is not guaranteed to use
0312: * the same qualified name.
0313: * @param atts the element's attribute list (must not be null)
0314: *
0315: * @throws org.xml.sax.SAXException if there is an error
0316: * writing the start-tag, or if a handler further down
0317: * the filter chain raises an exception
0318: *
0319: * @see org.xml.sax.ContentHandler#startElement
0320: */
0321: public void startElement(String uri, String localName,
0322: String qualifiedName, Attributes atts) throws SAXException {
0323: elementLevel++;
0324: nsSupport.pushContext();
0325: write('<');
0326: writeName(uri, localName, qualifiedName, true);
0327: writeAttributes(atts);
0328: if (elementLevel == 1) {
0329: forceNSDecls();
0330: }
0331: writeNSDecls();
0332: write('>');
0333: super .startElement(uri, localName, qualifiedName, atts);
0334: }
0335:
0336: /**
0337: * <p>
0338: * Write an end-tag.
0339: * </p>
0340: *
0341: * <p>
0342: * Pass the event on down the filter chain for further processing.
0343: * </p>
0344: *
0345: * @param uri the namespace URI, or the empty string if none
0346: * is available
0347: * @param localName the element's local (unprefixed) name (required)
0348: * @param qualifiedName the element's qualified (prefixed) name, or the
0349: * empty string is none is available. This method will
0350: * use the qName as a template for generating a prefix
0351: * if necessary, but it is not guaranteed to use the
0352: * same qualified name.
0353: *
0354: * @throws org.xml.sax.SAXException if there is an error
0355: * writing the end-tag, or if a handler further down
0356: * the filter chain raises an exception
0357: *
0358: * @see org.xml.sax.ContentHandler#endElement
0359: */
0360: public void endElement(String uri, String localName,
0361: String qualifiedName) throws SAXException {
0362: write("</");
0363: writeName(uri, localName, qualifiedName, true);
0364: write('>');
0365: if (elementLevel == 1) {
0366: write('\n');
0367: }
0368: super .endElement(uri, localName, qualifiedName);
0369: nsSupport.popContext();
0370: elementLevel--;
0371: }
0372:
0373: /**
0374: * <p>
0375: * Write character data.
0376: * </p>
0377: *
0378: * <p>
0379: * Pass the event on down the filter chain for further processing.
0380: * </p>
0381: *
0382: * @param ch the array of characters to write
0383: * @param start the starting position in the array
0384: * @param length the number of characters to write
0385: *
0386: * @throws org.xml.sax.SAXException if there is an error
0387: * writing the characters, or if a handler further down
0388: * the filter chain raises an exception
0389: *
0390: * @see org.xml.sax.ContentHandler#characters
0391: */
0392: public void characters(char[] ch, int start, int length)
0393: throws SAXException {
0394: writeEsc(ch, start, length, false);
0395: super .characters(ch, start, length);
0396: }
0397:
0398: /**
0399: * <p>
0400: * Write ignorable whitespace.
0401: * </p>
0402: *
0403: * <p>
0404: * Pass the event on down the filter chain for further processing.
0405: * </p>
0406: *
0407: * @param ch the array of characters to write
0408: * @param start the starting position in the array
0409: * @param length the number of characters to write
0410: *
0411: * @throws org.xml.sax.SAXException if there is an error
0412: * writing the whitespace, or if a handler further down
0413: * the filter chain raises an exception
0414: *
0415: * @see org.xml.sax.ContentHandler#ignorableWhitespace
0416: */
0417: public void ignorableWhitespace(char[] ch, int start, int length)
0418: throws SAXException {
0419: writeEsc(ch, start, length, false);
0420: super .ignorableWhitespace(ch, start, length);
0421: }
0422:
0423: /**
0424: * <p>
0425: * Write a processing instruction.
0426: * </p>
0427: *
0428: * <p>
0429: * Pass the event on down the filter chain for further processing.
0430: * </p>
0431: *
0432: * @param target the processing instruction target
0433: * @param data the processing instruction data
0434: *
0435: * @throws org.xml.sax.SAXException if there is an error
0436: * writing the PI, or if a handler further down
0437: * the filter chain raises an exception
0438: *
0439: * @see org.xml.sax.ContentHandler#processingInstruction
0440: */
0441: public void processingInstruction(String target, String data)
0442: throws SAXException {
0443: write("<?");
0444: write(target);
0445: write(' ');
0446: write(data);
0447: write("?>");
0448: if (elementLevel < 1) {
0449: write('\n');
0450: }
0451: super .processingInstruction(target, data);
0452: }
0453:
0454: ///////////////////////////////////////////////////////////////////
0455: // Additional markup.
0456: ///////////////////////////////////////////////////////////////////
0457:
0458: /**
0459: * <p>
0460: * Write an empty element.
0461: * </p>
0462: *
0463: * <p>
0464: * This method writes an empty-element tag rather than a start-tag
0465: * followed by an end-tag. Both a {@link #startElement
0466: * startElement} and an {@link #endElement endElement} event will
0467: * be passed on down the filter chain.
0468: * </p>
0469: *
0470: * @param uri the element's namespace URI, or the empty string
0471: * if the element has no namespace or if namespace
0472: * processing is not being performed
0473: * @param localName the element's local name (without prefix). This
0474: * parameter must be provided.
0475: * @param qualifiedName the element's qualified name (with prefix), or
0476: * the empty string if none is available. This parameter
0477: * is strictly advisory: the writer may or may not use
0478: * the prefix attached.
0479: * @param atts the element's attribute list
0480: *
0481: * @throws org.xml.sax.SAXException if there is an error
0482: * writing the empty tag, or if a handler further down
0483: * the filter chain raises an exception
0484: *
0485: * @see #startElement
0486: * @see #endElement
0487: */
0488: public void emptyElement(String uri, String localName,
0489: String qualifiedName, Attributes atts) throws SAXException {
0490: nsSupport.pushContext();
0491: write('<');
0492: writeName(uri, localName, qualifiedName, true);
0493: writeAttributes(atts);
0494: if (elementLevel == 1) {
0495: forceNSDecls();
0496: }
0497: writeNSDecls();
0498: write("/>");
0499: super .startElement(uri, localName, qualifiedName, atts);
0500: super .endElement(uri, localName, qualifiedName);
0501: }
0502:
0503: ///////////////////////////////////////////////////////////////////
0504: // Convenience methods.
0505: ///////////////////////////////////////////////////////////////////
0506:
0507: /**
0508: * <p>
0509: * Start a new element without a qualified name or attributes.
0510: * </p>
0511: *
0512: * <p>This method will provide a default empty attribute
0513: * list and an empty string for the qualified name.
0514: * It invokes {@link
0515: * #startElement(String, String, String, Attributes)}
0516: * directly.</p>
0517: *
0518: * @param uri the element's namespace URI
0519: * @param localName the element's local name
0520: *
0521: * @throws org.xml.sax.SAXException if there is an error
0522: * writing the start-tag, or if a handler further down
0523: * the filter chain raises an exception
0524: *
0525: * @see #startElement(String, String, String, Attributes)
0526: */
0527: public void startElement(String uri, String localName)
0528: throws SAXException {
0529: startElement(uri, localName, "", EMPTY_ATTS);
0530: }
0531:
0532: /**
0533: * <p>
0534: * Start a new element without a qualified name,
0535: * attributes or a namespace URI.</p>
0536: *
0537: * <p>This method will provide an empty string for the
0538: * namespace URI, and empty string for the qualified name,
0539: * and a default empty attribute list. It invokes
0540: * #startElement(String, String, String, Attributes)}
0541: * directly.</p>
0542: *
0543: * @param localName the element's local name
0544: *
0545: * @throws org.xml.sax.SAXException if there is an error
0546: * writing the start-tag, or if a handler further down
0547: * the filter chain raises an exception
0548: *
0549: * @see #startElement(String, String, String, Attributes)
0550: */
0551: public void startElement(String localName) throws SAXException {
0552: startElement("", localName, "", EMPTY_ATTS);
0553: }
0554:
0555: /**
0556: * <p>
0557: * End an element without a qualfied name.
0558: * </p>
0559: *
0560: * <p>This method will supply an empty string for the qName.
0561: * It invokes {@link #endElement(String, String, String)}
0562: * directly.</p>
0563: *
0564: * @param uri the element's namespace URI
0565: * @param localName the element's local name
0566: *
0567: * @throws org.xml.sax.SAXException if there is an error
0568: * writing the end-tag, or if a handler further down
0569: * the filter chain raises an exception
0570: *
0571: * @see #endElement(String, String, String)
0572: */
0573: public void endElement(String uri, String localName)
0574: throws SAXException {
0575: endElement(uri, localName, "");
0576: }
0577:
0578: /**
0579: * <p>
0580: * End an element without a namespace URI or qualfiied name.
0581: * </p>
0582: *
0583: * <p>This method will supply an empty string for the qName
0584: * and an empty string for the namespace URI.
0585: * It invokes {@link #endElement(String, String, String)}
0586: * directly.</p>
0587: *
0588: * @param localName the element's local name`
0589: *
0590: * @throws org.xml.sax.SAXException if there is an error
0591: * writing the end-tag, or if a handler further down
0592: * the filter chain raises an exception
0593: *
0594: * @see #endElement(String, String, String)
0595: */
0596: public void endElement(String localName) throws SAXException {
0597: endElement("", localName, "");
0598: }
0599:
0600: /**
0601: * <p>
0602: * Add an empty element without a qualified name or attributes.
0603: * </p>
0604: *
0605: * <p>This method will supply an empty string for the qualified name
0606: * and an empty attribute list. It invokes
0607: * {@link #emptyElement(String, String, String, Attributes)}
0608: * directly.</p>
0609: *
0610: * @param uri the element's namespace URI
0611: * @param localName the element's local name
0612: *
0613: * @throws org.xml.sax.SAXException if there is an error
0614: * writing the empty tag, or if a handler further down
0615: * the filter chain raises an exception
0616: *
0617: * @see #emptyElement(String, String, String, Attributes)
0618: */
0619: public void emptyElement(String uri, String localName)
0620: throws SAXException {
0621: emptyElement(uri, localName, "", EMPTY_ATTS);
0622: }
0623:
0624: /**
0625: * <p>
0626: * Add an empty element without a namespace URI, qualified
0627: * name or attributes.
0628: * </p>
0629: *
0630: * <p>This method will supply an empty string for the qualified
0631: * name, and empty string for the namespace URI, and an empty
0632: * attribute list. It invokes
0633: * {@link #emptyElement(String, String, String, Attributes)}
0634: * directly.</p>
0635: *
0636: * @param localName the element's local name
0637: *
0638: * @throws org.xml.sax.SAXException if there is an error
0639: * writing the empty tag, or if a handler further down
0640: * the filter chain raises an exception
0641: *
0642: * @see #emptyElement(String, String, String, Attributes)
0643: */
0644: public void emptyElement(String localName) throws SAXException {
0645: emptyElement("", localName, "", EMPTY_ATTS);
0646: }
0647:
0648: /**
0649: * <p>
0650: * Write an element with character data content.
0651: * </p>
0652: *
0653: * <p>This is a convenience method to write a complete element
0654: * with character data content, including the start-tag
0655: * and end-tag.</p>
0656: *
0657: * <p>This method invokes
0658: * {@link #startElement(String, String, String, Attributes)},
0659: * followed by
0660: * {@link #characters(String)}, followed by
0661: * {@link #endElement(String, String, String)}.</p>
0662: *
0663: * @param uri the element's namespace URI
0664: * @param localName the element's local name
0665: * @param qualifiedName the element's default qualified name
0666: * @param atts the element's attributes
0667: * @param content the character data content
0668: *
0669: * @throws org.xml.sax.SAXException if there is an error
0670: * writing the empty tag, or if a handler further down
0671: * the filter chain raises an exception
0672: *
0673: * @see #startElement(String, String, String, Attributes)
0674: * @see #characters(String)
0675: * @see #endElement(String, String, String)
0676: */
0677: public void dataElement(String uri, String localName,
0678: String qualifiedName, Attributes atts, String content)
0679: throws SAXException {
0680: startElement(uri, localName, qualifiedName, atts);
0681: characters(content);
0682: endElement(uri, localName, qualifiedName);
0683: }
0684:
0685: /**
0686: * <p>
0687: * Write an element with character data content but no attributes.
0688: * </p>
0689: *
0690: * <p>This is a convenience method to write a complete element
0691: * with character data content, including the start-tag
0692: * and end-tag. This method provides an empty string
0693: * for the qualified name and an empty attribute list.</p>
0694: *
0695: * <p>This method invokes
0696: * {@link #startElement(String, String, String, Attributes)},
0697: * followed by
0698: * {@link #characters(String)}, followed by
0699: * {@link #endElement(String, String, String)}.</p>
0700: *
0701: * @param uri the element's namespace URI
0702: * @param localName the element's local name
0703: * @param content the character data content
0704: *
0705: * @throws org.xml.sax.SAXException if there is an error
0706: * writing the empty tag, or if a handler further down
0707: * the filter chain raises an exception
0708: *
0709: * @see #startElement(String, String, String, Attributes)
0710: * @see #characters(String)
0711: * @see #endElement(String, String, String)
0712: */
0713: public void dataElement(String uri, String localName, String content)
0714: throws SAXException {
0715: dataElement(uri, localName, "", EMPTY_ATTS, content);
0716: }
0717:
0718: /**
0719: * <p>
0720: * Write an element with character data content but no attributes
0721: * or namespace URI.
0722: * </p>
0723: *
0724: * <p>This is a convenience method to write a complete element
0725: * with character data content, including the start-tag
0726: * and end-tag. The method provides an empty string for the
0727: * namespace URI, and empty string for the qualified name,
0728: * and an empty attribute list.</p>
0729: *
0730: * <p>This method invokes
0731: * {@link #startElement(String, String, String, Attributes)},
0732: * followed by
0733: * {@link #characters(String)}, followed by
0734: * {@link #endElement(String, String, String)}.</p>
0735: *
0736: * @param localName the element's local name
0737: * @param content the character data content
0738: *
0739: * @throws org.xml.sax.SAXException if there is an error
0740: * writing the empty tag, or if a handler further down
0741: * the filter chain raises an exception
0742: *
0743: * @see #startElement(String, String, String, Attributes)
0744: * @see #characters(String)
0745: * @see #endElement(String, String, String)
0746: */
0747: public void dataElement(String localName, String content)
0748: throws SAXException {
0749: dataElement("", localName, "", EMPTY_ATTS, content);
0750: }
0751:
0752: /**
0753: * <p>
0754: * Write a string of character data, with XML escaping.
0755: * </p>
0756: *
0757: * <p>This is a convenience method that takes an XML
0758: * String, converts it to a character array, then invokes
0759: * {@link #characters(char[], int, int)}.</p>
0760: *
0761: * @param data the character data
0762: * @throws org.xml.sax.SAXException if there is an error
0763: * writing the string, or if a handler further down
0764: * the filter chain raises an exception
0765: * @see #characters(char[], int, int)
0766: */
0767: public void characters(String data) throws SAXException {
0768: char[] ch = data.toCharArray();
0769: characters(ch, 0, ch.length);
0770: }
0771:
0772: ///////////////////////////////////////////////////////////////////
0773: // Internal methods.
0774: ///////////////////////////////////////////////////////////////////
0775:
0776: /**
0777: * <p>
0778: * Force all namespaces to be declared.
0779: * </p>
0780: *
0781: * <p>
0782: * This method is used on the root element to ensure that
0783: * the predeclared namespaces all appear.
0784: * </p>
0785: */
0786: private void forceNSDecls() {
0787: Enumeration prefixes = forcedDeclTable.keys();
0788: while (prefixes.hasMoreElements()) {
0789: String prefix = (String) prefixes.nextElement();
0790: doPrefix(prefix, null, true);
0791: }
0792: }
0793:
0794: /**
0795: * <p>
0796: * Determine the prefix for an element or attribute name.
0797: * </p>
0798: *
0799: * TODO: this method probably needs some cleanup.
0800: *
0801: * @param uri the namespace URI
0802: * @param qName the qualified name (optional); this will be used
0803: * to indicate the preferred prefix if none is currently
0804: * bound.
0805: * @param isElement true if this is an element name, false
0806: * if it is an attribute name (which cannot use the
0807: * default namespace).
0808: */
0809: private String doPrefix(String uri, String qName, boolean isElement) {
0810: String defaultNS = nsSupport.getURI("");
0811: if ("".equals(uri)) {
0812: if (isElement && defaultNS != null)
0813: nsSupport.declarePrefix("", "");
0814: return null;
0815: }
0816: String prefix;
0817: if (isElement && defaultNS != null && uri.equals(defaultNS)) {
0818: prefix = "";
0819: } else {
0820: prefix = nsSupport.getPrefix(uri);
0821: }
0822: if (prefix != null) {
0823: return prefix;
0824: }
0825: prefix = (String) doneDeclTable.get(uri);
0826: if (prefix != null
0827: && ((!isElement || defaultNS != null)
0828: && "".equals(prefix) || nsSupport
0829: .getURI(prefix) != null)) {
0830: prefix = null;
0831: }
0832: if (prefix == null) {
0833: prefix = (String) prefixTable.get(uri);
0834: if (prefix != null
0835: && ((!isElement || defaultNS != null)
0836: && "".equals(prefix) || nsSupport
0837: .getURI(prefix) != null)) {
0838: prefix = null;
0839: }
0840: }
0841: if (prefix == null && qName != null && !"".equals(qName)) {
0842: int i = qName.indexOf(':');
0843: if (i == -1) {
0844: if (isElement && defaultNS == null) {
0845: prefix = "";
0846: }
0847: } else {
0848: prefix = qName.substring(0, i);
0849: }
0850: }
0851: for (; prefix == null || nsSupport.getURI(prefix) != null; prefix = "__NS"
0852: + ++prefixCounter)
0853: ;
0854: nsSupport.declarePrefix(prefix, uri);
0855: doneDeclTable.put(uri, prefix);
0856: return prefix;
0857: }
0858:
0859: /**
0860: * <p>
0861: * Write a raw character.
0862: * </p>
0863: *
0864: * @param c the character to write
0865: *
0866: * @throws org.xml.sax.SAXException if there is an error writing
0867: * the character, this method will throw an IOException
0868: * wrapped in a SAXException
0869: */
0870: private void write(char c) throws SAXException {
0871: try {
0872: output.write(c);
0873: } catch (IOException ex) {
0874: throw new SAXException(ex);
0875: }
0876: }
0877:
0878: /**
0879: * <p>
0880: * Write a raw string.
0881: * </p>
0882: *
0883: * @param s
0884: *
0885: * @throws org.xml.sax.SAXException if there is an error writing
0886: * the string, this method will throw an IOException
0887: * wrapped in a SAXException
0888: */
0889: private void write(String s) throws SAXException {
0890: try {
0891: output.write(s);
0892: } catch (IOException e) {
0893: throw new SAXException(e);
0894: }
0895: }
0896:
0897: /**
0898: * <p>
0899: * Write out an attribute list, escaping values.
0900: *</p>
0901: *
0902: * <p>
0903: * The names will have prefixes added to them.
0904: * </p>
0905: *
0906: * @param atts the attribute list to write
0907: *
0908: * @throws org.xml.SAXException if there is an error writing
0909: * the attribute list, this method will throw an
0910: * IOException wrapped in a SAXException
0911: */
0912: private void writeAttributes(Attributes atts) throws SAXException {
0913: int len = atts.getLength();
0914: for (int i = 0; i < len; i++) {
0915: char[] ch = atts.getValue(i).toCharArray();
0916: write(' ');
0917: writeName(atts.getURI(i), atts.getLocalName(i), atts
0918: .getQName(i), false);
0919: write("=\"");
0920: writeEsc(ch, 0, ch.length, true);
0921: write('"');
0922: }
0923: }
0924:
0925: /**
0926: * <p>
0927: * Write an array of data characters with escaping.
0928: * </p>
0929: *
0930: * @param ch the array of characters
0931: * @param start the starting position
0932: * @param length the number of characters to use
0933: * @param isAttVal true if this is an attribute value literal
0934: *
0935: * @throws org.xml.SAXException if there is an error writing
0936: * the characters, this method will throw an
0937: * IOException wrapped in a SAXException
0938: */
0939: private void writeEsc(char[] ch, int start, int length,
0940: boolean isAttVal) throws SAXException {
0941: for (int i = start; i < start + length; i++) {
0942: switch (ch[i]) {
0943: case '&':
0944: write("&");
0945: break;
0946: case '<':
0947: write("<");
0948: break;
0949: case '>':
0950: write(">");
0951: break;
0952: case '\"':
0953: if (isAttVal) {
0954: write(""");
0955: } else {
0956: write('\"');
0957: }
0958: break;
0959: default:
0960: if (ch[i] > '\u007f') {
0961: write("&#");
0962: write(Integer.toString(ch[i]));
0963: write(';');
0964: } else {
0965: write(ch[i]);
0966: }
0967: }
0968: }
0969: }
0970:
0971: /**
0972: * <p>
0973: * Write an array of data characters without escaping.
0974: * </p>
0975: *
0976: * @param ch the array of characters
0977: * @param start the starting position
0978: * @param length the number of characters to use
0979: *
0980: * @throws org.xml.SAXException if there is an error writing
0981: * the characters, this method will throw an
0982: * IOException wrapped in a SAXException.
0983: */
0984: private void write(char[] ch, int start, int length)
0985: throws SAXException {
0986:
0987: try {
0988: output.write(ch, start, length);
0989: } catch (IOException e) {
0990: throw new SAXException(e);
0991: }
0992:
0993: }
0994:
0995: /**
0996: * <p>
0997: * Write out the list of namespace declarations.
0998: * </p>
0999: *
1000: * @throws org.xml.sax.SAXException This method will throw
1001: * an IOException wrapped in a SAXException if
1002: * there is an error writing the namespace
1003: * declarations
1004: */
1005: private void writeNSDecls() throws SAXException {
1006: Enumeration prefixes = nsSupport.getDeclaredPrefixes();
1007: while (prefixes.hasMoreElements()) {
1008: String prefix = (String) prefixes.nextElement();
1009: String uri = nsSupport.getURI(prefix);
1010: if (uri == null) {
1011: uri = "";
1012: }
1013: char[] ch = uri.toCharArray();
1014: write(' ');
1015: if ("".equals(prefix)) {
1016: write("xmlns=\"");
1017: } else {
1018: write("xmlns:");
1019: write(prefix);
1020: write("=\"");
1021: }
1022: writeEsc(ch, 0, ch.length, true);
1023: write('\"');
1024: }
1025: }
1026:
1027: /**
1028: * <p>
1029: * Write an element or attribute name.
1030: * </p>
1031: *
1032: * @param uri the namespace URI
1033: * @param localName the local name
1034: * @param qualifiedName the prefixed name, if available,
1035: * or the empty string.
1036: * @param isElement true if this is an element name, false if it
1037: * is an attribute name
1038: *
1039: * @throws org.xml.sax.SAXException this method will throw an
1040: * IOException wrapped in a SAXException if there is
1041: * an error writing the name
1042: */
1043: private void writeName(String uri, String localName,
1044: String qualifiedName, boolean isElement)
1045: throws SAXException {
1046: String prefix = doPrefix(uri, qualifiedName, isElement);
1047: if (prefix != null && !"".equals(prefix)) {
1048: write(prefix);
1049: write(':');
1050: }
1051: write(localName);
1052: }
1053:
1054: ////////////////////////////////////////////////////////////////////
1055: // Constants.
1056: ////////////////////////////////////////////////////////////////////
1057:
1058: private final Attributes EMPTY_ATTS = new AttributesImpl();
1059:
1060: ////////////////////////////////////////////////////////////////////
1061: // Internal state.
1062: ////////////////////////////////////////////////////////////////////
1063:
1064: private Hashtable prefixTable;
1065: private Hashtable forcedDeclTable;
1066: private Hashtable doneDeclTable;
1067: private int elementLevel = 0;
1068: private Writer output;
1069: private NamespaceSupport nsSupport;
1070: private int prefixCounter = 0;
1071:
1072: ///////////////////////////////////////////////////////////////////
1073: // LexicalHandler methods.
1074: ///////////////////////////////////////////////////////////////////
1075:
1076: public void endCDATA() {
1077: }
1078:
1079: public void endDTD() throws SAXException {
1080: write(">");
1081: }
1082:
1083: public void startCDATA() {
1084: }
1085:
1086: public void comment(char[] ch, int start, int length)
1087: throws SAXException {
1088: write("<!--");
1089: write(ch, start, length);
1090: write("-->");
1091: if (elementLevel < 1) {
1092: write('\n');
1093: }
1094: }
1095:
1096: public void endEntity(String name) {
1097: }
1098:
1099: public void startEntity(String name) {
1100: }
1101:
1102: public void startDTD(String name, String publicID, String systemID)
1103: throws SAXException {
1104: write("<!DOCTYPE ");
1105: write(name);
1106: if (systemID != null) {
1107: if (publicID != null) {
1108: write(" PUBLIC \"");
1109: write(publicID);
1110: write("\" \"");
1111: write(systemID);
1112: write("\"");
1113: } else {
1114: write(" SYSTEM \"");
1115: write(systemID);
1116: write("\"");
1117: }
1118: }
1119:
1120: }
1121:
1122: }
1123:
1124: // end of XMLWriter.java
|