0001: /*--
0002:
0003: $Id: XMLOutputter.java,v 1.2 2005/05/03 07:02:04 wittek Exp $
0004:
0005: Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
0006: All rights reserved.
0007:
0008: Redistribution and use in source and binary forms, with or without
0009: modification, are permitted provided that the following conditions
0010: are met:
0011:
0012: 1. Redistributions of source code must retain the above copyright
0013: notice, this list of conditions, and the following disclaimer.
0014:
0015: 2. Redistributions in binary form must reproduce the above copyright
0016: notice, this list of conditions, and the disclaimer that follows
0017: these conditions in the documentation and/or other materials
0018: provided with the distribution.
0019:
0020: 3. The name "JDOM" must not be used to endorse or promote products
0021: derived from this software without prior written permission. For
0022: written permission, please contact <request_AT_jdom_DOT_org>.
0023:
0024: 4. Products derived from this software may not be called "JDOM", nor
0025: may "JDOM" appear in their name, without prior written permission
0026: from the JDOM Project Management <request_AT_jdom_DOT_org>.
0027:
0028: In addition, we request (but do not require) that you include in the
0029: end-user documentation provided with the redistribution and/or in the
0030: software itself an acknowledgement equivalent to the following:
0031: "This product includes software developed by the
0032: JDOM Project (http://www.jdom.org/)."
0033: Alternatively, the acknowledgment may be graphical using the logos
0034: available at http://www.jdom.org/images/logos.
0035:
0036: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0037: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0038: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0039: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
0040: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0041: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0042: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0043: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0044: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0045: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0046: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0047: SUCH DAMAGE.
0048:
0049: This software consists of voluntary contributions made by many
0050: individuals on behalf of the JDOM Project and was originally
0051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
0052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
0053: on the JDOM Project, please see <http://www.jdom.org/>.
0054:
0055: */
0056:
0057: package org.jdom.output;
0058:
0059: import java.io.*;
0060: import java.util.*;
0061:
0062: import javax.xml.transform.Result;
0063:
0064: import org.jdom.*;
0065:
0066: /**
0067: * Outputs a JDOM document as a stream of bytes. The outputter can manage many
0068: * styles of document formatting, from untouched to pretty printed. The default
0069: * is to output the document content exactly as created, but this can be changed
0070: * by setting a new Format object. For pretty-print output, use
0071: * <code>{@link Format#getPrettyFormat()}</code>. For whitespace-normalized
0072: * output, use <code>{@link Format#getCompactFormat()}</code>.
0073: * <p>
0074: * There are <code>{@link #output output(...)}</code> methods to print any of
0075: * the standard JDOM classes, including Document and Element, to either a Writer
0076: * or an OutputStream. <b>Warning</b>: When outputting to a Writer, make sure
0077: * the writer's encoding matches the encoding setting in the Format object. This
0078: * ensures the encoding in which the content is written (controlled by the
0079: * Writer configuration) matches the encoding placed in the document's XML
0080: * declaration (controlled by the XMLOutputter). Because a Writer cannot be
0081: * queried for its encoding, the information must be passed to the Format
0082: * manually in its constructor or via the
0083: * <code>{@link Format#setEncoding}</code> method. The default encoding is
0084: * UTF-8.
0085: * <p>
0086: * The methods <code>{@link #outputString outputString(...)}</code> are for
0087: * convenience only; for top performance you should call one of the <code>{@link
0088: * #output output(...)}</code> methods and pass in your own Writer or
0089: * OutputStream if possible.
0090: * <p>
0091: * XML declarations are always printed on their own line followed by a line
0092: * seperator (this doesn't change the semantics of the document). To omit
0093: * printing of the declaration use
0094: * <code>{@link Format#setOmitDeclaration}</code>. To omit printing of the
0095: * encoding in the declaration use <code>{@link Format#setOmitEncoding}</code>.
0096: * Unfortunatly there is currently no way to know the original encoding of the
0097: * document.
0098: * <p>
0099: * Empty elements are by default printed as <empty/>, but this can be
0100: * configured with <code>{@link Format#setExpandEmptyElements}</code> to cause
0101: * them to be expanded to <empty></empty>.
0102: *
0103: * @version $Revision: 1.2 $, $Date: 2005/05/03 07:02:04 $
0104: * @author Brett McLaughlin
0105: * @author Jason Hunter
0106: * @author Jason Reid
0107: * @author Wolfgang Werner
0108: * @author Elliotte Rusty Harold
0109: * @author David & Will (from Post Tool Design)
0110: * @author Dan Schaffer
0111: * @author Alex Chaffee
0112: * @author Bradley S. Huffman
0113: */
0114:
0115: public class XMLOutputter implements Cloneable {
0116:
0117: private static final String CVS_ID = "@(#) $RCSfile: XMLOutputter.java,v $ $Revision: 1.2 $ $Date: 2005/05/03 07:02:04 $ $Name: $";
0118:
0119: // For normal output
0120: private Format userFormat = Format.getRawFormat();
0121:
0122: // For xml:space="preserve"
0123: protected static final Format preserveFormat = Format
0124: .getRawFormat();
0125:
0126: // What's currently in use
0127: protected Format currentFormat = userFormat;
0128:
0129: /** Whether output escaping is enabled for the being processed
0130: * Element - default is <code>true</code> */
0131: private boolean escapeOutput = true;
0132:
0133: // * * * * * * * * * * Constructors * * * * * * * * * *
0134: // * * * * * * * * * * Constructors * * * * * * * * * *
0135:
0136: /**
0137: * This will create an <code>XMLOutputter</code> with the default
0138: * {@link Format} matching {@link Format#getRawFormat}.
0139: */
0140: public XMLOutputter() {
0141: }
0142:
0143: /**
0144: * This will create an <code>XMLOutputter</code> with the specified
0145: * format characteristics. Note the format object is cloned internally
0146: * before use.
0147: */
0148: public XMLOutputter(Format format) {
0149: userFormat = (Format) format.clone();
0150: currentFormat = userFormat;
0151: }
0152:
0153: /**
0154: * This will create an <code>XMLOutputter</code> with all the
0155: * options as set in the given <code>XMLOutputter</code>. Note
0156: * that <code>XMLOutputter two = (XMLOutputter)one.clone();</code>
0157: * would work equally well.
0158: *
0159: * @param that the XMLOutputter to clone
0160: */
0161: public XMLOutputter(XMLOutputter that) {
0162: this .userFormat = (Format) that.userFormat.clone();
0163: currentFormat = userFormat;
0164: }
0165:
0166: // * * * * * * * * * * Set parameters methods * * * * * * * * * *
0167: // * * * * * * * * * * Set parameters methods * * * * * * * * * *
0168:
0169: /**
0170: * Sets the new format logic for the outputter. Note the Format
0171: * object is cloned internally before use.
0172: *
0173: * @param newFormat the format to use for output
0174: */
0175: public void setFormat(Format newFormat) {
0176: this .userFormat = (Format) newFormat.clone();
0177: this .currentFormat = userFormat;
0178: }
0179:
0180: /**
0181: * Returns the current format in use by the outputter. Note the
0182: * Format object returned is a clone of the one used internally.
0183: */
0184: public Format getFormat() {
0185: return (Format) userFormat.clone();
0186: }
0187:
0188: // * * * * * * * * * * Output to a OutputStream * * * * * * * * * *
0189: // * * * * * * * * * * Output to a OutputStream * * * * * * * * * *
0190:
0191: /**
0192: * This will print the <code>Document</code> to the given output stream.
0193: * The characters are printed using the encoding specified in the
0194: * constructor, or a default of UTF-8.
0195: *
0196: * @param doc <code>Document</code> to format.
0197: * @param out <code>OutputStream</code> to use.
0198: * @throws IOException - if there's any problem writing.
0199: */
0200: public void output(Document doc, OutputStream out)
0201: throws IOException {
0202: Writer writer = makeWriter(out);
0203: output(doc, writer); // output() flushes
0204: }
0205:
0206: /**
0207: * Print out the <code>{@link DocType}</code>.
0208: *
0209: * @param doctype <code>DocType</code> to output.
0210: * @param out <code>OutputStream</code> to use.
0211: */
0212: public void output(DocType doctype, OutputStream out)
0213: throws IOException {
0214: Writer writer = makeWriter(out);
0215: output(doctype, writer); // output() flushes
0216: }
0217:
0218: /**
0219: * Print out an <code>{@link Element}</code>, including
0220: * its <code>{@link Attribute}</code>s, and all
0221: * contained (child) elements, etc.
0222: *
0223: * @param element <code>Element</code> to output.
0224: * @param out <code>Writer</code> to use.
0225: */
0226: public void output(Element element, OutputStream out)
0227: throws IOException {
0228: Writer writer = makeWriter(out);
0229: output(element, writer); // output() flushes
0230: }
0231:
0232: /**
0233: * This will handle printing out an <code>{@link
0234: * Element}</code>'s content only, not including its tag, and
0235: * attributes. This can be useful for printing the content of an
0236: * element that contains HTML, like "<description>JDOM is
0237: * <b>fun>!</description>".
0238: *
0239: * @param element <code>Element</code> to output.
0240: * @param out <code>OutputStream</code> to use.
0241: */
0242: public void outputElementContent(Element element, OutputStream out)
0243: throws IOException {
0244: Writer writer = makeWriter(out);
0245: outputElementContent(element, writer); // output() flushes
0246: }
0247:
0248: /**
0249: * This will handle printing out a list of nodes.
0250: * This can be useful for printing the content of an element that
0251: * contains HTML, like "<description>JDOM is
0252: * <b>fun>!</description>".
0253: *
0254: * @param list <code>List</code> of nodes.
0255: * @param out <code>OutputStream</code> to use.
0256: */
0257: public void output(List list, OutputStream out) throws IOException {
0258: Writer writer = makeWriter(out);
0259: output(list, writer); // output() flushes
0260: }
0261:
0262: /**
0263: * Print out a <code>{@link CDATA}</code> node.
0264: *
0265: * @param cdata <code>CDATA</code> to output.
0266: * @param out <code>OutputStream</code> to use.
0267: */
0268: public void output(CDATA cdata, OutputStream out)
0269: throws IOException {
0270: Writer writer = makeWriter(out);
0271: output(cdata, writer); // output() flushes
0272: }
0273:
0274: /**
0275: * Print out a <code>{@link Text}</code> node. Perfoms
0276: * the necessary entity escaping and whitespace stripping.
0277: *
0278: * @param text <code>Text</code> to output.
0279: * @param out <code>OutputStream</code> to use.
0280: */
0281: public void output(Text text, OutputStream out) throws IOException {
0282: Writer writer = makeWriter(out);
0283: output(text, writer); // output() flushes
0284: }
0285:
0286: /**
0287: * Print out a <code>{@link Comment}</code>.
0288: *
0289: * @param comment <code>Comment</code> to output.
0290: * @param out <code>OutputStream</code> to use.
0291: */
0292: public void output(Comment comment, OutputStream out)
0293: throws IOException {
0294: Writer writer = makeWriter(out);
0295: output(comment, writer); // output() flushes
0296: }
0297:
0298: /**
0299: * Print out a <code>{@link ProcessingInstruction}</code>.
0300: *
0301: * @param pi <code>ProcessingInstruction</code> to output.
0302: * @param out <code>OutputStream</code> to use.
0303: */
0304: public void output(ProcessingInstruction pi, OutputStream out)
0305: throws IOException {
0306: Writer writer = makeWriter(out);
0307: output(pi, writer); // output() flushes
0308: }
0309:
0310: /**
0311: * Print out a <code>{@link EntityRef}</code>.
0312: *
0313: * @param entity <code>EntityRef</code> to output.
0314: * @param out <code>OutputStream</code> to use.
0315: */
0316: public void output(EntityRef entity, OutputStream out)
0317: throws IOException {
0318: Writer writer = makeWriter(out);
0319: output(entity, writer); // output() flushes
0320: }
0321:
0322: /**
0323: * Get an OutputStreamWriter, using prefered encoding
0324: * (see {@link Format#setEncoding}).
0325: */
0326: private Writer makeWriter(OutputStream out)
0327: throws java.io.UnsupportedEncodingException {
0328: return makeWriter(out, userFormat.encoding);
0329: }
0330:
0331: /**
0332: * Get an OutputStreamWriter, use specified encoding.
0333: */
0334: private static Writer makeWriter(OutputStream out, String enc)
0335: throws java.io.UnsupportedEncodingException {
0336: // "UTF-8" is not recognized before JDK 1.1.6, so we'll translate
0337: // into "UTF8" which works with all JDKs.
0338: if ("UTF-8".equals(enc)) {
0339: enc = "UTF8";
0340: }
0341:
0342: Writer writer = new BufferedWriter((new OutputStreamWriter(
0343: new BufferedOutputStream(out), enc)));
0344: return writer;
0345: }
0346:
0347: // * * * * * * * * * * Output to a Writer * * * * * * * * * *
0348: // * * * * * * * * * * Output to a Writer * * * * * * * * * *
0349:
0350: /**
0351: * This will print the <code>Document</code> to the given Writer.
0352: *
0353: * <p>
0354: * Warning: using your own Writer may cause the outputter's
0355: * preferred character encoding to be ignored. If you use
0356: * encodings other than UTF-8, we recommend using the method that
0357: * takes an OutputStream instead.
0358: * </p>
0359: *
0360: * @param doc <code>Document</code> to format.
0361: * @param out <code>Writer</code> to use.
0362: * @throws IOException - if there's any problem writing.
0363: */
0364: public void output(Document doc, Writer out) throws IOException {
0365:
0366: printDeclaration(out, doc, userFormat.encoding);
0367:
0368: // Print out root element, as well as any root level
0369: // comments and processing instructions,
0370: // starting with no indentation
0371: List content = doc.getContent();
0372: int size = content.size();
0373: for (int i = 0; i < size; i++) {
0374: Object obj = content.get(i);
0375:
0376: if (obj instanceof Element) {
0377: printElement(out, doc.getRootElement(), 0,
0378: createNamespaceStack());
0379: } else if (obj instanceof Comment) {
0380: printComment(out, (Comment) obj);
0381: } else if (obj instanceof ProcessingInstruction) {
0382: printProcessingInstruction(out,
0383: (ProcessingInstruction) obj);
0384: } else if (obj instanceof DocType) {
0385: printDocType(out, doc.getDocType());
0386: // Always print line separator after declaration, helps the
0387: // output look better and is semantically inconsequential
0388: out.write(currentFormat.lineSeparator);
0389: } else {
0390: // XXX if we get here then we have a illegal content, for
0391: // now we'll just ignore it
0392: }
0393:
0394: newline(out);
0395: indent(out, 0);
0396: }
0397:
0398: // Output final line separator
0399: // We output this no matter what the newline flags say
0400: out.write(currentFormat.lineSeparator);
0401:
0402: out.flush();
0403: }
0404:
0405: /**
0406: * Print out the <code>{@link DocType}</code>.
0407: *
0408: * @param doctype <code>DocType</code> to output.
0409: * @param out <code>Writer</code> to use.
0410: */
0411: public void output(DocType doctype, Writer out) throws IOException {
0412: printDocType(out, doctype);
0413: out.flush();
0414: }
0415:
0416: /**
0417: * Print out an <code>{@link Element}</code>, including
0418: * its <code>{@link Attribute}</code>s, and all
0419: * contained (child) elements, etc.
0420: *
0421: * @param element <code>Element</code> to output.
0422: * @param out <code>Writer</code> to use.
0423: */
0424: public void output(Element element, Writer out) throws IOException {
0425: // If this is the root element we could pre-initialize the
0426: // namespace stack with the namespaces
0427: printElement(out, element, 0, createNamespaceStack());
0428: out.flush();
0429: }
0430:
0431: /**
0432: * This will handle printing out an <code>{@link
0433: * Element}</code>'s content only, not including its tag, and
0434: * attributes. This can be useful for printing the content of an
0435: * element that contains HTML, like "<description>JDOM is
0436: * <b>fun>!</description>".
0437: *
0438: * @param element <code>Element</code> to output.
0439: * @param out <code>Writer</code> to use.
0440: */
0441: public void outputElementContent(Element element, Writer out)
0442: throws IOException {
0443: List content = element.getContent();
0444: printContentRange(out, content, 0, content.size(), 0,
0445: createNamespaceStack());
0446: out.flush();
0447: }
0448:
0449: /**
0450: * This will handle printing out a list of nodes.
0451: * This can be useful for printing the content of an element that
0452: * contains HTML, like "<description>JDOM is
0453: * <b>fun>!</description>".
0454: *
0455: * @param list <code>List</code> of nodes.
0456: * @param out <code>Writer</code> to use.
0457: */
0458: public void output(List list, Writer out) throws IOException {
0459: printContentRange(out, list, 0, list.size(), 0,
0460: createNamespaceStack());
0461: out.flush();
0462: }
0463:
0464: /**
0465: * Print out a <code>{@link CDATA}</code> node.
0466: *
0467: * @param cdata <code>CDATA</code> to output.
0468: * @param out <code>Writer</code> to use.
0469: */
0470: public void output(CDATA cdata, Writer out) throws IOException {
0471: printCDATA(out, cdata);
0472: out.flush();
0473: }
0474:
0475: /**
0476: * Print out a <code>{@link Text}</code> node. Perfoms
0477: * the necessary entity escaping and whitespace stripping.
0478: *
0479: * @param text <code>Text</code> to output.
0480: * @param out <code>Writer</code> to use.
0481: */
0482: public void output(Text text, Writer out) throws IOException {
0483: printText(out, text);
0484: out.flush();
0485: }
0486:
0487: /**
0488: * Print out a <code>{@link Comment}</code>.
0489: *
0490: * @param comment <code>Comment</code> to output.
0491: * @param out <code>Writer</code> to use.
0492: */
0493: public void output(Comment comment, Writer out) throws IOException {
0494: printComment(out, comment);
0495: out.flush();
0496: }
0497:
0498: /**
0499: * Print out a <code>{@link ProcessingInstruction}</code>.
0500: *
0501: * @param pi <code>ProcessingInstruction</code> to output.
0502: * @param out <code>Writer</code> to use.
0503: */
0504: public void output(ProcessingInstruction pi, Writer out)
0505: throws IOException {
0506: boolean currentEscapingPolicy = currentFormat.ignoreTrAXEscapingPIs;
0507:
0508: // Output PI verbatim, disregarding TrAX escaping PIs.
0509: currentFormat.setIgnoreTrAXEscapingPIs(true);
0510: printProcessingInstruction(out, pi);
0511: currentFormat.setIgnoreTrAXEscapingPIs(currentEscapingPolicy);
0512:
0513: out.flush();
0514: }
0515:
0516: /**
0517: * Print out a <code>{@link EntityRef}</code>.
0518: *
0519: * @param entity <code>EntityRef</code> to output.
0520: * @param out <code>Writer</code> to use.
0521: */
0522: public void output(EntityRef entity, Writer out) throws IOException {
0523: printEntityRef(out, entity);
0524: out.flush();
0525: }
0526:
0527: // * * * * * * * * * * Output to a String * * * * * * * * * *
0528: // * * * * * * * * * * Output to a String * * * * * * * * * *
0529:
0530: /**
0531: * Return a string representing a document. Uses an internal
0532: * StringWriter. Warning: a String is Unicode, which may not match
0533: * the outputter's specified encoding.
0534: *
0535: * @param doc <code>Document</code> to format.
0536: */
0537: public String outputString(Document doc) {
0538: StringWriter out = new StringWriter();
0539: try {
0540: output(doc, out); // output() flushes
0541: } catch (IOException e) {
0542: }
0543: return out.toString();
0544: }
0545:
0546: /**
0547: * Return a string representing a DocType. Warning: a String is
0548: * Unicode, which may not match the outputter's specified
0549: * encoding.
0550: *
0551: * @param doctype <code>DocType</code> to format.
0552: */
0553: public String outputString(DocType doctype) {
0554: StringWriter out = new StringWriter();
0555: try {
0556: output(doctype, out); // output() flushes
0557: } catch (IOException e) {
0558: }
0559: return out.toString();
0560: }
0561:
0562: /**
0563: * Return a string representing an element. Warning: a String is
0564: * Unicode, which may not match the outputter's specified
0565: * encoding.
0566: *
0567: * @param element <code>Element</code> to format.
0568: */
0569: public String outputString(Element element) {
0570: StringWriter out = new StringWriter();
0571: try {
0572: output(element, out); // output() flushes
0573: } catch (IOException e) {
0574: }
0575: return out.toString();
0576: }
0577:
0578: /**
0579: * Return a string representing a list of nodes. The list is
0580: * assumed to contain legal JDOM nodes.
0581: *
0582: * @param list <code>List</code> to format.
0583: */
0584: public String outputString(List list) {
0585: StringWriter out = new StringWriter();
0586: try {
0587: output(list, out); // output() flushes
0588: } catch (IOException e) {
0589: }
0590: return out.toString();
0591: }
0592:
0593: /**
0594: * Return a string representing a CDATA node. Warning: a String is
0595: * Unicode, which may not match the outputter's specified
0596: * encoding.
0597: *
0598: * @param cdata <code>CDATA</code> to format.
0599: */
0600: public String outputString(CDATA cdata) {
0601: StringWriter out = new StringWriter();
0602: try {
0603: output(cdata, out); // output() flushes
0604: } catch (IOException e) {
0605: }
0606: return out.toString();
0607: }
0608:
0609: /**
0610: * Return a string representing a Text node. Warning: a String is
0611: * Unicode, which may not match the outputter's specified
0612: * encoding.
0613: *
0614: * @param text <code>Text</code> to format.
0615: */
0616: public String outputString(Text text) {
0617: StringWriter out = new StringWriter();
0618: try {
0619: output(text, out); // output() flushes
0620: } catch (IOException e) {
0621: }
0622: return out.toString();
0623: }
0624:
0625: /**
0626: * Return a string representing a comment. Warning: a String is
0627: * Unicode, which may not match the outputter's specified
0628: * encoding.
0629: *
0630: * @param comment <code>Comment</code> to format.
0631: */
0632: public String outputString(Comment comment) {
0633: StringWriter out = new StringWriter();
0634: try {
0635: output(comment, out); // output() flushes
0636: } catch (IOException e) {
0637: }
0638: return out.toString();
0639: }
0640:
0641: /**
0642: * Return a string representing a PI. Warning: a String is
0643: * Unicode, which may not match the outputter's specified
0644: * encoding.
0645: *
0646: * @param pi <code>ProcessingInstruction</code> to format.
0647: */
0648: public String outputString(ProcessingInstruction pi) {
0649: StringWriter out = new StringWriter();
0650: try {
0651: output(pi, out); // output() flushes
0652: } catch (IOException e) {
0653: }
0654: return out.toString();
0655: }
0656:
0657: /**
0658: * Return a string representing an entity. Warning: a String is
0659: * Unicode, which may not match the outputter's specified
0660: * encoding.
0661: *
0662: * @param entity <code>EntityRef</code> to format.
0663: */
0664: public String outputString(EntityRef entity) {
0665: StringWriter out = new StringWriter();
0666: try {
0667: output(entity, out); // output() flushes
0668: } catch (IOException e) {
0669: }
0670: return out.toString();
0671: }
0672:
0673: // * * * * * * * * * * Internal printing methods * * * * * * * * * *
0674: // * * * * * * * * * * Internal printing methods * * * * * * * * * *
0675:
0676: /**
0677: * This will handle printing of the declaration.
0678: * Assumes XML version 1.0 since we don't directly know.
0679: *
0680: * @param doc <code>Document</code> whose declaration to write.
0681: * @param out <code>Writer</code> to use.
0682: * @param encoding The encoding to add to the declaration
0683: */
0684: protected void printDeclaration(Writer out, Document doc,
0685: String encoding) throws IOException {
0686:
0687: // Only print the declaration if it's not being omitted
0688: if (!userFormat.omitDeclaration) {
0689: // Assume 1.0 version
0690: out.write("<?xml version=\"1.0\"");
0691: if (!userFormat.omitEncoding) {
0692: out.write(" encoding=\"" + encoding + "\"");
0693: }
0694: out.write("?>");
0695:
0696: // Print new line after decl always, even if no other new lines
0697: // Helps the output look better and is semantically
0698: // inconsequential
0699: out.write(currentFormat.lineSeparator);
0700: }
0701: }
0702:
0703: /**
0704: * This handle printing the DOCTYPE declaration if one exists.
0705: *
0706: * @param docType <code>Document</code> whose declaration to write.
0707: * @param out <code>Writer</code> to use.
0708: */
0709: protected void printDocType(Writer out, DocType docType)
0710: throws IOException {
0711:
0712: String publicID = docType.getPublicID();
0713: String systemID = docType.getSystemID();
0714: String internalSubset = docType.getInternalSubset();
0715: boolean hasPublic = false;
0716:
0717: out.write("<!DOCTYPE ");
0718: out.write(docType.getElementName());
0719: if (publicID != null) {
0720: out.write(" PUBLIC \"");
0721: out.write(publicID);
0722: out.write("\"");
0723: hasPublic = true;
0724: }
0725: if (systemID != null) {
0726: if (!hasPublic) {
0727: out.write(" SYSTEM");
0728: }
0729: out.write(" \"");
0730: out.write(systemID);
0731: out.write("\"");
0732: }
0733: if ((internalSubset != null) && (!internalSubset.equals(""))) {
0734: out.write(" [");
0735: out.write(currentFormat.lineSeparator);
0736: out.write(docType.getInternalSubset());
0737: out.write("]");
0738: }
0739: out.write(">");
0740: }
0741:
0742: /**
0743: * This will handle printing of comments.
0744: *
0745: * @param comment <code>Comment</code> to write.
0746: * @param out <code>Writer</code> to use.
0747: */
0748: protected void printComment(Writer out, Comment comment)
0749: throws IOException {
0750: out.write("<!--");
0751: out.write(comment.getText());
0752: out.write("-->");
0753: }
0754:
0755: /**
0756: * This will handle printing of processing instructions.
0757: *
0758: * @param pi <code>ProcessingInstruction</code> to write.
0759: * @param out <code>Writer</code> to use.
0760: */
0761: protected void printProcessingInstruction(Writer out,
0762: ProcessingInstruction pi) throws IOException {
0763: String target = pi.getTarget();
0764: boolean piProcessed = false;
0765:
0766: if (currentFormat.ignoreTrAXEscapingPIs == false) {
0767: if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) {
0768: escapeOutput = false;
0769: piProcessed = true;
0770: } else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) {
0771: escapeOutput = true;
0772: piProcessed = true;
0773: }
0774: }
0775: if (piProcessed == false) {
0776: String rawData = pi.getData();
0777:
0778: // Write <?target data?> or if no data then just <?target?>
0779: if (!"".equals(rawData)) {
0780: out.write("<?");
0781: out.write(target);
0782: out.write(" ");
0783: out.write(rawData);
0784: out.write("?>");
0785: } else {
0786: out.write("<?");
0787: out.write(target);
0788: out.write("?>");
0789: }
0790: }
0791: }
0792:
0793: /**
0794: * This will handle printing a <code>{@link EntityRef}</code>.
0795: * Only the entity reference such as <code>&entity;</code>
0796: * will be printed. However, subclasses are free to override
0797: * this method to print the contents of the entity instead.
0798: *
0799: * @param entity <code>EntityRef</code> to output.
0800: * @param out <code>Writer</code> to use. */
0801: protected void printEntityRef(Writer out, EntityRef entity)
0802: throws IOException {
0803: out.write("&");
0804: out.write(entity.getName());
0805: out.write(";");
0806: }
0807:
0808: /**
0809: * This will handle printing of <code>{@link CDATA}</code> text.
0810: *
0811: * @param cdata <code>CDATA</code> to output.
0812: * @param out <code>Writer</code> to use.
0813: */
0814: protected void printCDATA(Writer out, CDATA cdata)
0815: throws IOException {
0816: String str = (currentFormat.mode == Format.TextMode.NORMALIZE) ? cdata
0817: .getTextNormalize()
0818: : ((currentFormat.mode == Format.TextMode.TRIM) ? cdata
0819: .getText().trim() : cdata.getText());
0820: out.write("<![CDATA[");
0821: out.write(str);
0822: out.write("]]>");
0823: }
0824:
0825: /**
0826: * This will handle printing of <code>{@link Text}</code> strings.
0827: *
0828: * @param text <code>Text</code> to write.
0829: * @param out <code>Writer</code> to use.
0830: */
0831: protected void printText(Writer out, Text text) throws IOException {
0832: String str = (currentFormat.mode == Format.TextMode.NORMALIZE) ? text
0833: .getTextNormalize()
0834: : ((currentFormat.mode == Format.TextMode.TRIM) ? text
0835: .getText().trim() : text.getText());
0836: out.write(escapeElementEntities(str));
0837: }
0838:
0839: /**
0840: * This will handle printing a string. Escapes the element entities,
0841: * trims interior whitespace, etc. if necessary.
0842: */
0843: private void printString(Writer out, String str) throws IOException {
0844: if (currentFormat.mode == Format.TextMode.NORMALIZE) {
0845: str = Text.normalizeString(str);
0846: } else if (currentFormat.mode == Format.TextMode.TRIM) {
0847: str = str.trim();
0848: }
0849: out.write(escapeElementEntities(str));
0850: }
0851:
0852: /**
0853: * This will handle printing of a <code>{@link Element}</code>,
0854: * its <code>{@link Attribute}</code>s, and all contained (child)
0855: * elements, etc.
0856: *
0857: * @param element <code>Element</code> to output.
0858: * @param out <code>Writer</code> to use.
0859: * @param level <code>int</code> level of indention.
0860: * @param namespaces <code>List</code> stack of Namespaces in scope.
0861: */
0862: protected void printElement(Writer out, Element element, int level,
0863: NamespaceStack namespaces) throws IOException {
0864:
0865: List attributes = element.getAttributes();
0866: List content = element.getContent();
0867:
0868: // Check for xml:space and adjust format settings
0869: String space = null;
0870: if (attributes != null) {
0871: space = element.getAttributeValue("space",
0872: Namespace.XML_NAMESPACE);
0873: }
0874:
0875: Format previousFormat = currentFormat;
0876:
0877: if ("default".equals(space)) {
0878: currentFormat = userFormat;
0879: } else if ("preserve".equals(space)) {
0880: currentFormat = preserveFormat;
0881: }
0882:
0883: // Print the beginning of the tag plus attributes and any
0884: // necessary namespace declarations
0885: out.write("<");
0886: printQualifiedName(out, element);
0887:
0888: // Mark our namespace starting point
0889: int previouslyDeclaredNamespaces = namespaces.size();
0890:
0891: // Print the element's namespace, if appropriate
0892: printElementNamespace(out, element, namespaces);
0893:
0894: // Print out additional namespace declarations
0895: printAdditionalNamespaces(out, element, namespaces);
0896:
0897: // Print out attributes
0898: if (attributes != null)
0899: printAttributes(out, attributes, element, namespaces);
0900:
0901: // Depending on the settings (newlines, textNormalize, etc), we may
0902: // or may not want to print all of the content, so determine the
0903: // index of the start of the content we're interested
0904: // in based on the current settings.
0905:
0906: int start = skipLeadingWhite(content, 0);
0907: int size = content.size();
0908: if (start >= size) {
0909: // Case content is empty or all insignificant whitespace
0910: if (currentFormat.expandEmptyElements) {
0911: out.write("></");
0912: printQualifiedName(out, element);
0913: out.write(">");
0914: } else {
0915: out.write(" />");
0916: }
0917: } else {
0918: out.write(">");
0919:
0920: // For a special case where the content is only CDATA
0921: // or Text we don't want to indent after the start or
0922: // before the end tag.
0923:
0924: if (nextNonText(content, start) < size) {
0925: // Case Mixed Content - normal indentation
0926: newline(out);
0927: printContentRange(out, content, start, size, level + 1,
0928: namespaces);
0929: newline(out);
0930: indent(out, level);
0931: } else {
0932: // Case all CDATA or Text - no indentation
0933: printTextRange(out, content, start, size);
0934: }
0935: out.write("</");
0936: printQualifiedName(out, element);
0937: out.write(">");
0938: }
0939:
0940: // remove declared namespaces from stack
0941: while (namespaces.size() > previouslyDeclaredNamespaces) {
0942: namespaces.pop();
0943: }
0944:
0945: // Restore our format settings
0946: currentFormat = previousFormat;
0947: }
0948:
0949: /**
0950: * This will handle printing of content within a given range.
0951: * The range to print is specified in typical Java fashion; the
0952: * starting index is inclusive, while the ending index is
0953: * exclusive.
0954: *
0955: * @param content <code>List</code> of content to output
0956: * @param start index of first content node (inclusive.
0957: * @param end index of last content node (exclusive).
0958: * @param out <code>Writer</code> to use.
0959: * @param level <code>int</code> level of indentation.
0960: * @param namespaces <code>List</code> stack of Namespaces in scope.
0961: */
0962: private void printContentRange(Writer out, List content, int start,
0963: int end, int level, NamespaceStack namespaces)
0964: throws IOException {
0965: boolean firstNode; // Flag for 1st node in content
0966: Object next; // Node we're about to print
0967: int first, index; // Indexes into the list of content
0968:
0969: index = start;
0970: while (index < end) {
0971: firstNode = (index == start) ? true : false;
0972: next = content.get(index);
0973:
0974: //
0975: // Handle consecutive CDATA, Text, and EntityRef nodes all at once
0976: //
0977: if ((next instanceof Text) || (next instanceof EntityRef)) {
0978: first = skipLeadingWhite(content, index);
0979: // Set index to next node for loop
0980: index = nextNonText(content, first);
0981:
0982: // If it's not all whitespace - print it!
0983: if (first < index) {
0984: if (!firstNode)
0985: newline(out);
0986: indent(out, level);
0987: printTextRange(out, content, first, index);
0988: }
0989: continue;
0990: }
0991:
0992: //
0993: // Handle other nodes
0994: //
0995: if (!firstNode) {
0996: newline(out);
0997: }
0998:
0999: indent(out, level);
1000:
1001: if (next instanceof Comment) {
1002: printComment(out, (Comment) next);
1003: } else if (next instanceof Element) {
1004: printElement(out, (Element) next, level, namespaces);
1005: } else if (next instanceof ProcessingInstruction) {
1006: printProcessingInstruction(out,
1007: (ProcessingInstruction) next);
1008: } else {
1009: // XXX if we get here then we have a illegal content, for
1010: // now we'll just ignore it (probably should throw
1011: // a exception)
1012: }
1013:
1014: index++;
1015: } /* while */
1016: }
1017:
1018: /**
1019: * This will handle printing of a sequence of <code>{@link CDATA}</code>
1020: * or <code>{@link Text}</code> nodes. It is an error to have any other
1021: * pass this method any other type of node.
1022: *
1023: * @param content <code>List</code> of content to output
1024: * @param start index of first content node (inclusive).
1025: * @param end index of last content node (exclusive).
1026: * @param out <code>Writer</code> to use.
1027: */
1028: private void printTextRange(Writer out, List content, int start,
1029: int end) throws IOException {
1030: String previous; // Previous text printed
1031: Object node; // Next node to print
1032: String next; // Next text to print
1033:
1034: previous = null;
1035:
1036: // Remove leading whitespace-only nodes
1037: start = skipLeadingWhite(content, start);
1038:
1039: int size = content.size();
1040: if (start < size) {
1041: // And remove trialing whitespace-only nodes
1042: end = skipTrailingWhite(content, end);
1043:
1044: for (int i = start; i < end; i++) {
1045: node = content.get(i);
1046:
1047: // Get the unmangled version of the text
1048: // we are about to print
1049: if (node instanceof Text) {
1050: next = ((Text) node).getText();
1051: } else if (node instanceof EntityRef) {
1052: next = "&" + ((EntityRef) node).getValue() + ";";
1053: } else {
1054: throw new IllegalStateException("Should see only "
1055: + "CDATA, Text, or EntityRef");
1056: }
1057:
1058: // This may save a little time
1059: if (next == null || "".equals(next)) {
1060: continue;
1061: }
1062:
1063: // Determine if we need to pad the output (padding is
1064: // only need in trim or normalizing mode)
1065: if (previous != null) { // NotElement 1st node
1066: if (currentFormat.mode == Format.TextMode.NORMALIZE
1067: || currentFormat.mode == Format.TextMode.TRIM) {
1068: if ((endsWithWhite(previous))
1069: || (startsWithWhite(next))) {
1070: out.write(" ");
1071: }
1072: }
1073: }
1074:
1075: // Print the node
1076: if (node instanceof CDATA) {
1077: printCDATA(out, (CDATA) node);
1078: } else if (node instanceof EntityRef) {
1079: printEntityRef(out, (EntityRef) node);
1080: } else {
1081: printString(out, next);
1082: }
1083:
1084: previous = next;
1085: }
1086: }
1087: }
1088:
1089: /**
1090: * This will handle printing of any needed <code>{@link Namespace}</code>
1091: * declarations.
1092: *
1093: * @param ns <code>Namespace</code> to print definition of
1094: * @param out <code>Writer</code> to use.
1095: */
1096: private void printNamespace(Writer out, Namespace ns,
1097: NamespaceStack namespaces) throws IOException {
1098: String prefix = ns.getPrefix();
1099: String uri = ns.getURI();
1100:
1101: // Already printed namespace decl?
1102: if (uri.equals(namespaces.getURI(prefix))) {
1103: return;
1104: }
1105:
1106: out.write(" xmlns");
1107: if (!prefix.equals("")) {
1108: out.write(":");
1109: out.write(prefix);
1110: }
1111: out.write("=\"");
1112: out.write(uri);
1113: out.write("\"");
1114: namespaces.push(ns);
1115: }
1116:
1117: /**
1118: * This will handle printing of a <code>{@link Attribute}</code> list.
1119: *
1120: * @param attributes <code>List</code> of Attribute objcts
1121: * @param out <code>Writer</code> to use
1122: */
1123: protected void printAttributes(Writer out, List attributes,
1124: Element parent, NamespaceStack namespaces)
1125: throws IOException {
1126:
1127: // I do not yet handle the case where the same prefix maps to
1128: // two different URIs. For attributes on the same element
1129: // this is illegal; but as yet we don't throw an exception
1130: // if someone tries to do this
1131: // Set prefixes = new HashSet();
1132: for (int i = 0; i < attributes.size(); i++) {
1133: Attribute attribute = (Attribute) attributes.get(i);
1134: Namespace ns = attribute.getNamespace();
1135: if ((ns != Namespace.NO_NAMESPACE)
1136: && (ns != Namespace.XML_NAMESPACE)) {
1137: printNamespace(out, ns, namespaces);
1138: }
1139:
1140: out.write(" ");
1141: printQualifiedName(out, attribute);
1142: out.write("=");
1143:
1144: out.write("\"");
1145: out.write(escapeAttributeEntities(attribute.getValue()));
1146: out.write("\"");
1147: }
1148: }
1149:
1150: private void printElementNamespace(Writer out, Element element,
1151: NamespaceStack namespaces) throws IOException {
1152: // Add namespace decl only if it's not the XML namespace and it's
1153: // not the NO_NAMESPACE with the prefix "" not yet mapped
1154: // (we do output xmlns="" if the "" prefix was already used and we
1155: // need to reclaim it for the NO_NAMESPACE)
1156: Namespace ns = element.getNamespace();
1157: if (ns == Namespace.XML_NAMESPACE) {
1158: return;
1159: }
1160: if (!((ns == Namespace.NO_NAMESPACE) && (namespaces.getURI("") == null))) {
1161: printNamespace(out, ns, namespaces);
1162: }
1163: }
1164:
1165: private void printAdditionalNamespaces(Writer out, Element element,
1166: NamespaceStack namespaces) throws IOException {
1167: List list = element.getAdditionalNamespaces();
1168: if (list != null) {
1169: for (int i = 0; i < list.size(); i++) {
1170: Namespace additional = (Namespace) list.get(i);
1171: printNamespace(out, additional, namespaces);
1172: }
1173: }
1174: }
1175:
1176: // * * * * * * * * * * Support methods * * * * * * * * * *
1177: // * * * * * * * * * * Support methods * * * * * * * * * *
1178:
1179: /**
1180: * This will print a new line only if the newlines flag was set to
1181: * true.
1182: *
1183: * @param out <code>Writer</code> to use
1184: */
1185: private void newline(Writer out) throws IOException {
1186: if (currentFormat.indent != null) {
1187: out.write(currentFormat.lineSeparator);
1188: }
1189: }
1190:
1191: /**
1192: * This will print indents (only if the newlines flag was
1193: * set to <code>true</code>, and indent is non-null).
1194: *
1195: * @param out <code>Writer</code> to use
1196: * @param level current indent level (number of tabs)
1197: */
1198: private void indent(Writer out, int level) throws IOException {
1199: if (currentFormat.indent == null
1200: || currentFormat.indent.equals("")) {
1201: return;
1202: }
1203:
1204: for (int i = 0; i < level; i++) {
1205: out.write(currentFormat.indent);
1206: }
1207: }
1208:
1209: // Returns the index of the first non-all-whitespace CDATA or Text,
1210: // index = content.size() is returned if content contains
1211: // all whitespace.
1212: // @param start index to begin search (inclusive)
1213: private int skipLeadingWhite(List content, int start) {
1214: if (start < 0) {
1215: start = 0;
1216: }
1217:
1218: int index = start;
1219: int size = content.size();
1220: if (currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE
1221: || currentFormat.mode == Format.TextMode.NORMALIZE
1222: || currentFormat.mode == Format.TextMode.TRIM) {
1223: while (index < size) {
1224: if (!isAllWhitespace(content.get(index))) {
1225: return index;
1226: }
1227: index++;
1228: }
1229: }
1230: return index;
1231: }
1232:
1233: // Return the index + 1 of the last non-all-whitespace CDATA or
1234: // Text node, index < 0 is returned
1235: // if content contains all whitespace.
1236: // @param start index to begin search (exclusive)
1237: private int skipTrailingWhite(List content, int start) {
1238: int size = content.size();
1239: if (start > size) {
1240: start = size;
1241: }
1242:
1243: int index = start;
1244: if (currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE
1245: || currentFormat.mode == Format.TextMode.NORMALIZE
1246: || currentFormat.mode == Format.TextMode.TRIM) {
1247: while (index >= 0) {
1248: if (!isAllWhitespace(content.get(index - 1)))
1249: break;
1250: --index;
1251: }
1252: }
1253: return index;
1254: }
1255:
1256: // Return the next non-CDATA, non-Text, or non-EntityRef node,
1257: // index = content.size() is returned if there is no more non-CDATA,
1258: // non-Text, or non-EntiryRef nodes
1259: // @param start index to begin search (inclusive)
1260: private static int nextNonText(List content, int start) {
1261: if (start < 0) {
1262: start = 0;
1263: }
1264:
1265: int index = start;
1266: int size = content.size();
1267: while (index < size) {
1268: Object node = content.get(index);
1269: if (!((node instanceof Text) || (node instanceof EntityRef))) {
1270: return index;
1271: }
1272: index++;
1273: }
1274: return size;
1275: }
1276:
1277: // Determine if a Object is all whitespace
1278: private boolean isAllWhitespace(Object obj) {
1279: String str = null;
1280:
1281: if (obj instanceof String) {
1282: str = (String) obj;
1283: } else if (obj instanceof Text) {
1284: str = ((Text) obj).getText();
1285: } else if (obj instanceof EntityRef) {
1286: return false;
1287: } else {
1288: return false;
1289: }
1290:
1291: for (int i = 0; i < str.length(); i++) {
1292: if (!isWhitespace(str.charAt(i)))
1293: return false;
1294: }
1295: return true;
1296: }
1297:
1298: // Determine if a string starts with a XML whitespace.
1299: private boolean startsWithWhite(String str) {
1300: if ((str != null) && (str.length() > 0)
1301: && isWhitespace(str.charAt(0))) {
1302: return true;
1303: }
1304: return false;
1305: }
1306:
1307: // Determine if a string ends with a XML whitespace.
1308: private boolean endsWithWhite(String str) {
1309: if ((str != null) && (str.length() > 0)
1310: && isWhitespace(str.charAt(str.length() - 1))) {
1311: return true;
1312: }
1313: return false;
1314: }
1315:
1316: // Determine if a character is a XML whitespace.
1317: // XXX should this method be in Verifier
1318: private static boolean isWhitespace(char c) {
1319: if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
1320: return true;
1321: }
1322: return false;
1323: }
1324:
1325: /**
1326: * This will take the pre-defined entities in XML 1.0 and
1327: * convert their character representation to the appropriate
1328: * entity reference, suitable for XML attributes. It does not convert
1329: * the single quote (') because it's not necessary as the outputter
1330: * writes attributes surrounded by double-quotes.
1331: *
1332: * @param str <code>String</code> input to escape.
1333: * @return <code>String</code> with escaped content.
1334: */
1335: public String escapeAttributeEntities(String str) {
1336: StringBuffer buffer;
1337: char ch;
1338: String entity;
1339: EscapeStrategy strategy = currentFormat.escapeStrategy;
1340:
1341: buffer = null;
1342: for (int i = 0; i < str.length(); i++) {
1343: ch = str.charAt(i);
1344: switch (ch) {
1345: case '<':
1346: entity = "<";
1347: break;
1348: case '>':
1349: entity = ">";
1350: break;
1351: /*
1352: case '\'' :
1353: entity = "'";
1354: break;
1355: */
1356: case '\"':
1357: entity = """;
1358: break;
1359: case '&':
1360: entity = "&";
1361: break;
1362: case '\r':
1363: entity = "
";
1364: break;
1365: case '\t':
1366: entity = "	";
1367: break;
1368: case '\n':
1369: entity = "
";
1370: break;
1371: default:
1372: if (strategy.shouldEscape(ch)) {
1373: entity = "&#x" + Integer.toHexString(ch) + ";";
1374: } else {
1375: entity = null;
1376: }
1377: break;
1378: }
1379: if (buffer == null) {
1380: if (entity != null) {
1381: // An entity occurred, so we'll have to use StringBuffer
1382: // (allocate room for it plus a few more entities).
1383: buffer = new StringBuffer(str.length() + 20);
1384: // Copy previous skipped characters and fall through
1385: // to pickup current character
1386: buffer.append(str.substring(0, i));
1387: buffer.append(entity);
1388: }
1389: } else {
1390: if (entity == null) {
1391: buffer.append(ch);
1392: } else {
1393: buffer.append(entity);
1394: }
1395: }
1396: }
1397:
1398: // If there were any entities, return the escaped characters
1399: // that we put in the StringBuffer. Otherwise, just return
1400: // the unmodified input string.
1401: return (buffer == null) ? str : buffer.toString();
1402: }
1403:
1404: /**
1405: * This will take the three pre-defined entities in XML 1.0
1406: * (used specifically in XML elements) and convert their character
1407: * representation to the appropriate entity reference, suitable for
1408: * XML element content.
1409: *
1410: * @param str <code>String</code> input to escape.
1411: * @return <code>String</code> with escaped content.
1412: */
1413: public String escapeElementEntities(String str) {
1414: if (escapeOutput == false)
1415: return str;
1416:
1417: StringBuffer buffer;
1418: char ch;
1419: String entity;
1420: EscapeStrategy strategy = currentFormat.escapeStrategy;
1421:
1422: buffer = null;
1423: for (int i = 0; i < str.length(); i++) {
1424: ch = str.charAt(i);
1425: switch (ch) {
1426: case '<':
1427: entity = "<";
1428: break;
1429: case '>':
1430: entity = ">";
1431: break;
1432: case '&':
1433: entity = "&";
1434: break;
1435: case '\r':
1436: entity = "
";
1437: break;
1438: case '\n':
1439: entity = currentFormat.lineSeparator;
1440: break;
1441: default:
1442: if (strategy.shouldEscape(ch)) {
1443: entity = "&#x" + Integer.toHexString(ch) + ";";
1444: } else {
1445: entity = null;
1446: }
1447: break;
1448: }
1449: if (buffer == null) {
1450: if (entity != null) {
1451: // An entity occurred, so we'll have to use StringBuffer
1452: // (allocate room for it plus a few more entities).
1453: buffer = new StringBuffer(str.length() + 20);
1454: // Copy previous skipped characters and fall through
1455: // to pickup current character
1456: buffer.append(str.substring(0, i));
1457: buffer.append(entity);
1458: }
1459: } else {
1460: if (entity == null) {
1461: buffer.append(ch);
1462: } else {
1463: buffer.append(entity);
1464: }
1465: }
1466: }
1467:
1468: // If there were any entities, return the escaped characters
1469: // that we put in the StringBuffer. Otherwise, just return
1470: // the unmodified input string.
1471: return (buffer == null) ? str : buffer.toString();
1472: }
1473:
1474: /**
1475: * Returns a copy of this XMLOutputter.
1476: */
1477: public Object clone() {
1478: // Implementation notes: Since all state of an XMLOutputter is
1479: // embodied in simple private instance variables, Object.clone
1480: // can be used. Note that since Object.clone is totally
1481: // broken, we must catch an exception that will never be
1482: // thrown.
1483: try {
1484: return super .clone();
1485: } catch (java.lang.CloneNotSupportedException e) {
1486: // even though this should never ever happen, it's still
1487: // possible to fool Java into throwing a
1488: // CloneNotSupportedException. If that happens, we
1489: // shouldn't swallow it.
1490: throw new RuntimeException(e.toString());
1491: }
1492: }
1493:
1494: /**
1495: * Return a string listing of the settings for this
1496: * XMLOutputter instance.
1497: *
1498: * @return a string listing the settings for this XMLOutputter instance
1499: */
1500: public String toString() {
1501: StringBuffer buffer = new StringBuffer();
1502: for (int i = 0; i < userFormat.lineSeparator.length(); i++) {
1503: char ch = userFormat.lineSeparator.charAt(i);
1504: switch (ch) {
1505: case '\r':
1506: buffer.append("\\r");
1507: break;
1508: case '\n':
1509: buffer.append("\\n");
1510: break;
1511: case '\t':
1512: buffer.append("\\t");
1513: break;
1514: default:
1515: buffer.append("[" + ((int) ch) + "]");
1516: break;
1517: }
1518: }
1519:
1520: return ("XMLOutputter[omitDeclaration = "
1521: + userFormat.omitDeclaration + ", " + "encoding = "
1522: + userFormat.encoding + ", " + "omitEncoding = "
1523: + userFormat.omitEncoding + ", " + "indent = '"
1524: + userFormat.indent + "'" + ", "
1525: + "expandEmptyElements = "
1526: + userFormat.expandEmptyElements + ", "
1527: + "lineSeparator = '" + buffer.toString() + "', "
1528: + "textMode = " + userFormat.mode + "]");
1529: }
1530:
1531: /**
1532: * Factory for making new NamespaceStack objects. The NamespaceStack
1533: * created is actually an inner class extending the package protected
1534: * NamespaceStack, as a way to make NamespaceStack "friendly" toward
1535: * subclassers.
1536: */
1537: private NamespaceStack createNamespaceStack() {
1538: // actually returns a XMLOutputter.NamespaceStack (see below)
1539: return new NamespaceStack();
1540: }
1541:
1542: /**
1543: * Our own null subclass of NamespaceStack. This plays a little
1544: * trick with Java access protection. We want subclasses of
1545: * XMLOutputter to be able to override protected methods that
1546: * declare a NamespaceStack parameter, but we don't want to
1547: * declare the parent NamespaceStack class as public.
1548: */
1549: protected class NamespaceStack extends
1550: org.jdom.output.NamespaceStack {
1551: }
1552:
1553: // Support method to print a name without using elt.getQualifiedName()
1554: // and thus avoiding a StringBuffer creation and memory churn
1555: private void printQualifiedName(Writer out, Element e)
1556: throws IOException {
1557: if (e.getNamespace().getPrefix().length() == 0) {
1558: out.write(e.getName());
1559: } else {
1560: out.write(e.getNamespace().getPrefix());
1561: out.write(':');
1562: out.write(e.getName());
1563: }
1564: }
1565:
1566: // Support method to print a name without using att.getQualifiedName()
1567: // and thus avoiding a StringBuffer creation and memory churn
1568: private void printQualifiedName(Writer out, Attribute a)
1569: throws IOException {
1570: String prefix = a.getNamespace().getPrefix();
1571: if ((prefix != null) && (!prefix.equals(""))) {
1572: out.write(prefix);
1573: out.write(':');
1574: out.write(a.getName());
1575: } else {
1576: out.write(a.getName());
1577: }
1578: }
1579:
1580: // * * * * * * * * * * Deprecated methods * * * * * * * * * *
1581:
1582: /* The methods below here are deprecations of protected methods. We
1583: * don't usually deprecate protected methods, so they're commented out.
1584: * They're left here in case this mass deprecation causes people trouble.
1585: * Since we're getting close to 1.0 it's actually better for people to
1586: * raise issues early though.
1587: */
1588:
1589: }
|