View Javadoc

    1   /*
    2    * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
    3    *
    4    * This software is open source.
    5    * See the bottom of this file for the licence.
    6    */
    7
    8   package org.dom4j.io;
    9
    10  import java.io.BufferedWriter;
    11  import java.io.IOException;
    12  import java.io.OutputStream;
    13  import java.io.OutputStreamWriter;
    14  import java.io.UnsupportedEncodingException;
    15  import java.io.Writer;
    16  import java.util.HashMap;
    17  import java.util.Iterator;
    18  import java.util.List;
    19  import java.util.Map;
    20  import java.util.StringTokenizer;
    21
    22  import org.dom4j.Attribute;
    23  import org.dom4j.CDATA;
    24  import org.dom4j.Comment;
    25  import org.dom4j.Document;
    26  import org.dom4j.DocumentType;
    27  import org.dom4j.Element;
    28  import org.dom4j.Entity;
    29  import org.dom4j.Namespace;
    30  import org.dom4j.Node;
    31  import org.dom4j.ProcessingInstruction;
    32  import org.dom4j.Text;
    33  import org.dom4j.tree.NamespaceStack;
    34
    35  import org.xml.sax.Attributes;
    36  import org.xml.sax.InputSource;
    37  import org.xml.sax.Locator;
    38  import org.xml.sax.SAXException;
    39  import org.xml.sax.SAXNotRecognizedException;
    40  import org.xml.sax.SAXNotSupportedException;
    41  import org.xml.sax.XMLReader;
    42  import org.xml.sax.ext.LexicalHandler;
    43  import org.xml.sax.helpers.XMLFilterImpl;
    44
    45  /***
    46   * <p>
    47   * <code>XMLWriter</code> takes a DOM4J tree and formats it to a stream as
    48   * XML. It can also take SAX events too so can be used by SAX clients as this
    49   * object implements the {@link org.xml.sax.ContentHandler}and {@link
    50   * LexicalHandler} interfaces. as well. This formatter performs typical document
    51   * formatting. The XML declaration and processing instructions are always on
    52   * their own lines. An {@link OutputFormat}object can be used to define how
    53   * whitespace is handled when printing and allows various configuration options,
    54   * such as to allow suppression of the XML declaration, the encoding declaration
    55   * or whether empty documents are collapsed.
    56   * </p>
    57   *
    58   * <p>
    59   * There are <code>write(...)</code> methods to print any of the standard
    60   * DOM4J classes, including <code>Document</code> and <code>Element</code>,
    61   * to either a <code>Writer</code> or an <code>OutputStream</code>.
    62   * Warning: using your own <code>Writer</code> may cause the writer's
    63   * preferred character encoding to be ignored. If you use encodings other than
    64   * UTF8, we recommend using the method that takes an OutputStream instead.
    65   * </p>
    66   *
    67   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
    68   * @author Joseph Bowbeer
    69   * @version $Revision: 1.83.2.2 $
    70   */
    71  public class XMLWriter extends XMLFilterImpl implements LexicalHandler {
    72      private static final String PAD_TEXT = " ";
    73
    74      protected static final String[] LEXICAL_HANDLER_NAMES = {
    75              "http://xml.org/sax/properties/lexical-handler",
    76              "http://xml.org/sax/handlers/LexicalHandler"};
    77
    78      protected static final OutputFormat DEFAULT_FORMAT = new OutputFormat();
    79
    80      /*** Should entityRefs by resolved when writing ? */
    81      private boolean resolveEntityRefs = true;
    82
    83      /***
    84       * Stores the last type of node written so algorithms can refer to the
    85       * previous node type
    86       */
    87      protected int lastOutputNodeType;
    88
    89      /***
    90       * Stores if the last written element node was a closing tag or an opening
    91       * tag.
    92       */
    93      private boolean lastElementClosed = false;
    94
    95      /*** Stores the xml:space attribute value of preserve for whitespace flag */
    96      protected boolean preserve = false;
    97
    98      /*** The Writer used to output to */
    99      protected Writer writer;
    100
    101     /*** The Stack of namespaceStack written so far */
    102     private NamespaceStack namespaceStack = new NamespaceStack();
    103
    104     /*** The format used by this writer */
    105     private OutputFormat format;
    106
    107     /*** whether we should escape text */
    108     private boolean escapeText = true;
    109
    110     /***
    111      * The initial number of indentations (so you can print a whole document
    112      * indented, if you like)
    113      */
    114     private int indentLevel = 0;
    115
    116     /*** buffer used when escaping strings */
    117     private StringBuffer buffer = new StringBuffer();
    118
    119     /***
    120      * whether we have added characters before from the same chunk of characters
    121      */
    122     private boolean charsAdded = false;
    123
    124     private char lastChar;
    125
    126     /*** Whether a flush should occur after writing a document */
    127     private boolean autoFlush;
    128
    129     /*** Lexical handler we should delegate to */
    130     private LexicalHandler lexicalHandler;
    131
    132     /***
    133      * Whether comments should appear inside DTD declarations - defaults to
    134      * false
    135      */
    136     private boolean showCommentsInDTDs;
    137
    138     /*** Is the writer curerntly inside a DTD definition? */
    139     private boolean inDTD;
    140
    141     /*** The namespaces used for the current element when consuming SAX events */
    142     private Map namespacesMap;
    143
    144     /***
    145      * what is the maximum allowed character code such as 127 in US-ASCII (7
    146      * bit) or 255 in ISO- (8 bit) or -1 to not escape any characters (other
    147      * than the special XML characters like &lt; &gt; &amp;)
    148      */
    149     private int maximumAllowedCharacter;
    150
    151     public XMLWriter(Writer writer) {
    152         this(writer, DEFAULT_FORMAT);
    153     }
    154
    155     public XMLWriter(Writer writer, OutputFormat format) {
    156         this.writer = writer;
    157         this.format = format;
    158         namespaceStack.push(Namespace.NO_NAMESPACE);
    159     }
    160
    161     public XMLWriter() {
    162         this.format = DEFAULT_FORMAT;
    163         this.writer = new BufferedWriter(new OutputStreamWriter(System.out));
    164         this.autoFlush = true;
    165         namespaceStack.push(Namespace.NO_NAMESPACE);
    166     }
    167
    168     public XMLWriter(OutputStream out) throws UnsupportedEncodingException {
    169         this.format = DEFAULT_FORMAT;
    170         this.writer = createWriter(out, format.getEncoding());
    171         this.autoFlush = true;
    172         namespaceStack.push(Namespace.NO_NAMESPACE);
    173     }
    174
    175     public XMLWriter(OutputStream out, OutputFormat format)
    176             throws UnsupportedEncodingException {
    177         this.format = format;
    178         this.writer = createWriter(out, format.getEncoding());
    179         this.autoFlush = true;
    180         namespaceStack.push(Namespace.NO_NAMESPACE);
    181     }
    182
    183     public XMLWriter(OutputFormat format) throws UnsupportedEncodingException {
    184         this.format = format;
    185         this.writer = createWriter(System.out, format.getEncoding());
    186         this.autoFlush = true;
    187         namespaceStack.push(Namespace.NO_NAMESPACE);
    188     }
    189
    190     public void setWriter(Writer writer) {
    191         this.writer = writer;
    192         this.autoFlush = false;
    193     }
    194
    195     public void setOutputStream(OutputStream out)
    196             throws UnsupportedEncodingException {
    197         this.writer = createWriter(out, format.getEncoding());
    198         this.autoFlush = true;
    199     }
    200
    201     /***
    202      * DOCUMENT ME!
    203      *
    204      * @return true if text thats output should be escaped. This is enabled by
    205      *         default. It could be disabled if the output format is textual,
    206      *         like in XSLT where we can have xml, html or text output.
    207      */
    208     public boolean isEscapeText() {
    209         return escapeText;
    210     }
    211
    212     /***
    213      * Sets whether text output should be escaped or not. This is enabled by
    214      * default. It could be disabled if the output format is textual, like in
    215      * XSLT where we can have xml, html or text output.
    216      *
    217      * @param escapeText
    218      *            DOCUMENT ME!
    219      */
    220     public void setEscapeText(boolean escapeText) {
    221         this.escapeText = escapeText;
    222     }
    223
    224     /***
    225      * Set the initial indentation level. This can be used to output a document
    226      * (or, more likely, an element) starting at a given indent level, so it's
    227      * not always flush against the left margin. Default: 0
    228      *
    229      * @param indentLevel
    230      *            the number of indents to start with
    231      */
    232     public void setIndentLevel(int indentLevel) {
    233         this.indentLevel = indentLevel;
    234     }
    235
    236     /***
    237      * Returns the maximum allowed character code that should be allowed
    238      * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
    239      * bit).
    240      *
    241      * @return DOCUMENT ME!
    242      */
    243     public int getMaximumAllowedCharacter() {
    244         if (maximumAllowedCharacter == 0) {
    245             maximumAllowedCharacter = defaultMaximumAllowedCharacter();
    246         }
    247
    248         return maximumAllowedCharacter;
    249     }
    250
    251     /***
    252      * Sets the maximum allowed character code that should be allowed unescaped
    253      * such as 127 in US-ASCII (7 bit) or 255 in ISO- (8 bit) or -1 to not
    254      * escape any characters (other than the special XML characters like &lt;
    255      * &gt; &amp;) If this is not explicitly set then it is defaulted from the
    256      * encoding.
    257      *
    258      * @param maximumAllowedCharacter
    259      *            The maximumAllowedCharacter to set
    260      */
    261     public void setMaximumAllowedCharacter(int maximumAllowedCharacter) {
    262         this.maximumAllowedCharacter = maximumAllowedCharacter;
    263     }
    264
    265     /***
    266      * Flushes the underlying Writer
    267      *
    268      * @throws IOException
    269      *             DOCUMENT ME!
    270      */
    271     public void flush() throws IOException {
    272         writer.flush();
    273     }
    274
    275     /***
    276      * Closes the underlying Writer
    277      *
    278      * @throws IOException
    279      *             DOCUMENT ME!
    280      */
    281     public void close() throws IOException {
    282         writer.close();
    283     }
    284
    285     /***
    286      * Writes the new line text to the underlying Writer
    287      *
    288      * @throws IOException
    289      *             DOCUMENT ME!
    290      */
    291     public void println() throws IOException {
    292         writer.write(format.getLineSeparator());
    293     }
    294
    295     /***
    296      * Writes the given {@link Attribute}.
    297      *
    298      * @param attribute
    299      *            <code>Attribute</code> to output.
    300      *
    301      * @throws IOException
    302      *             DOCUMENT ME!
    303      */
    304     public void write(Attribute attribute) throws IOException {
    305         writeAttribute(attribute);
    306
    307         if (autoFlush) {
    308             flush();
    309         }
    310     }
    311
    312     /***
    313      * <p>
    314      * This will print the <code>Document</code> to the current Writer.
    315      * </p>
    316      *
    317      * <p>
    318      * Warning: using your own Writer may cause the writer's preferred character
    319      * encoding to be ignored. If you use encodings other than UTF8, we
    320      * recommend using the method that takes an OutputStream instead.
    321      * </p>
    322      *
    323      * <p>
    324      * Note: as with all Writers, you may need to flush() yours after this
    325      * method returns.
    326      * </p>
    327      *
    328      * @param doc
    329      *            <code>Document</code> to format.
    330      *
    331      * @throws IOException
    332      *             if there's any problem writing.
    333      */
    334     public void write(Document doc) throws IOException {
    335         writeDeclaration();
    336
    337         if (doc.getDocType() != null) {
    338             indent();
    339             writeDocType(doc.getDocType());
    340         }
    341
    342         for (int i = 0, size = doc.nodeCount(); i < size; i++) {
    343             Node node = doc.node(i);
    344             writeNode(node);
    345         }
    346
    347         writePrintln();
    348
    349         if (autoFlush) {
    350             flush();
    351         }
    352     }
    353
    354     /***
    355      * <p>
    356      * Writes the <code>{@link Element}</code>, including its <code>{@link
    357      * Attribute}</code>
    358      * s, and its value, and all its content (child nodes) to the current
    359      * Writer.
    360      * </p>
    361      *
    362      * @param element
    363      *            <code>Element</code> to output.
    364      *
    365      * @throws IOException
    366      *             DOCUMENT ME!
    367      */
    368     public void write(Element element) throws IOException {
    369         writeElement(element);
    370
    371         if (autoFlush) {
    372             flush();
    373         }
    374     }
    375
    376     /***
    377      * Writes the given {@link CDATA}.
    378      *
    379      * @param cdata
    380      *            <code>CDATA</code> to output.
    381      *
    382      * @throws IOException
    383      *             DOCUMENT ME!
    384      */
    385     public void write(CDATA cdata) throws IOException {
    386         writeCDATA(cdata.getText());
    387
    388         if (autoFlush) {
    389             flush();
    390         }
    391     }
    392
    393     /***
    394      * Writes the given {@link Comment}.
    395      *
    396      * @param comment
    397      *            <code>Comment</code> to output.
    398      *
    399      * @throws IOException
    400      *             DOCUMENT ME!
    401      */
    402     public void write(Comment comment) throws IOException {
    403         writeComment(comment.getText());
    404
    405         if (autoFlush) {
    406             flush();
    407         }
    408     }
    409
    410     /***
    411      * Writes the given {@link DocumentType}.
    412      *
    413      * @param docType
    414      *            <code>DocumentType</code> to output.
    415      *
    416      * @throws IOException
    417      *             DOCUMENT ME!
    418      */
    419     public void write(DocumentType docType) throws IOException {
    420         writeDocType(docType);
    421
    422         if (autoFlush) {
    423             flush();
    424         }
    425     }
    426
    427     /***
    428      * Writes the given {@link Entity}.
    429      *
    430      * @param entity
    431      *            <code>Entity</code> to output.
    432      *
    433      * @throws IOException
    434      *             DOCUMENT ME!
    435      */
    436     public void write(Entity entity) throws IOException {
    437         writeEntity(entity);
    438
    439         if (autoFlush) {
    440             flush();
    441         }
    442     }
    443
    444     /***
    445      * Writes the given {@link Namespace}.
    446      *
    447      * @param namespace
    448      *            <code>Namespace</code> to output.
    449      *
    450      * @throws IOException
    451      *             DOCUMENT ME!
    452      */
    453     public void write(Namespace namespace) throws IOException {
    454         writeNamespace(namespace);
    455
    456         if (autoFlush) {
    457             flush();
    458         }
    459     }
    460
    461     /***
    462      * Writes the given {@link ProcessingInstruction}.
    463      *
    464      * @param processingInstruction
    465      *            <code>ProcessingInstruction</code> to output.
    466      *
    467      * @throws IOException
    468      *             DOCUMENT ME!
    469      */
    470     public void write(ProcessingInstruction processingInstruction)
    471             throws IOException {
    472         writeProcessingInstruction(processingInstruction);
    473
    474         if (autoFlush) {
    475             flush();
    476         }
    477     }
    478
    479     /***
    480      * <p>
    481      * Print out a {@link String}, Perfoms the necessary entity escaping and
    482      * whitespace stripping.
    483      * </p>
    484      *
    485      * @param text
    486      *            is the text to output
    487      *
    488      * @throws IOException
    489      *             DOCUMENT ME!
    490      */
    491     public void write(String text) throws IOException {
    492         writeString(text);
    493
    494         if (autoFlush) {
    495             flush();
    496         }
    497     }
    498
    499     /***
    500      * Writes the given {@link Text}.
    501      *
    502      * @param text
    503      *            <code>Text</code> to output.
    504      *
    505      * @throws IOException
    506      *             DOCUMENT ME!
    507      */
    508     public void write(Text text) throws IOException {
    509         writeString(text.getText());
    510
    511         if (autoFlush) {
    512             flush();
    513         }
    514     }
    515
    516     /***
    517      * Writes the given {@link Node}.
    518      *
    519      * @param node
    520      *            <code>Node</code> to output.
    521      *
    522      * @throws IOException
    523      *             DOCUMENT ME!
    524      */
    525     public void write(Node node) throws IOException {
    526         writeNode(node);
    527
    528         if (autoFlush) {
    529             flush();
    530         }
    531     }
    532
    533     /***
    534      * Writes the given object which should be a String, a Node or a List of
    535      * Nodes.
    536      *
    537      * @param object
    538      *            is the object to output.
    539      *
    540      * @throws IOException
    541      *             DOCUMENT ME!
    542      */
    543     public void write(Object object) throws IOException {
    544         if (object instanceof Node) {
    545             write((Node) object);
    546         } else if (object instanceof String) {
    547             write((String) object);
    548         } else if (object instanceof List) {
    549             List list = (List) object;
    550
    551             for (int i = 0, size = list.size(); i < size; i++) {
    552                 write(list.get(i));
    553             }
    554         } else if (object != null) {
    555             throw new IOException("Invalid object: " + object);
    556         }
    557     }
    558
    559     /***
    560      * <p>
    561      * Writes the opening tag of an {@link Element}, including its {@link
    562      * Attribute}s but without its content.
    563      * </p>
    564      *
    565      * @param element
    566      *            <code>Element</code> to output.
    567      *
    568      * @throws IOException
    569      *             DOCUMENT ME!
    570      */
    571     public void writeOpen(Element element) throws IOException {
    572         writer.write("<");
    573         writer.write(element.getQualifiedName());
    574         writeAttributes(element);
    575         writer.write(">");
    576     }
    577
    578     /***
    579      * <p>
    580      * Writes the closing tag of an {@link Element}
    581      * </p>
    582      *
    583      * @param element
    584      *            <code>Element</code> to output.
    585      *
    586      * @throws IOException
    587      *             DOCUMENT ME!
    588      */
    589     public void writeClose(Element element) throws IOException {
    590         writeClose(element.getQualifiedName());
    591     }
    592
    593     // XMLFilterImpl methods
    594     // -------------------------------------------------------------------------
    595     public void parse(InputSource source) throws IOException, SAXException {
    596         installLexicalHandler();
    597         super.parse(source);
    598     }
    599
    600     public void setProperty(String name, Object value)
    601             throws SAXNotRecognizedException, SAXNotSupportedException {
    602         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
    603             if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
    604                 setLexicalHandler((LexicalHandler) value);
    605
    606                 return;
    607             }
    608         }
    609
    610         super.setProperty(name, value);
    611     }
    612
    613     public Object getProperty(String name) throws SAXNotRecognizedException,
    614             SAXNotSupportedException {
    615         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
    616             if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
    617                 return getLexicalHandler();
    618             }
    619         }
    620
    621         return super.getProperty(name);
    622     }
    623
    624     public void setLexicalHandler(LexicalHandler handler) {
    625         if (handler == null) {
    626             throw new NullPointerException("Null lexical handler");
    627         } else {
    628             this.lexicalHandler = handler;
    629         }
    630     }
    631
    632     public LexicalHandler getLexicalHandler() {
    633         return lexicalHandler;
    634     }
    635
    636     // ContentHandler interface
    637     // -------------------------------------------------------------------------
    638     public void setDocumentLocator(Locator locator) {
    639         super.setDocumentLocator(locator);
    640     }
    641
    642     public void startDocument() throws SAXException {
    643         try {
    644             writeDeclaration();
    645             super.startDocument();
    646         } catch (IOException e) {
    647             handleException(e);
    648         }
    649     }
    650
    651     public void endDocument() throws SAXException {
    652         super.endDocument();
    653
    654         if (autoFlush) {
    655             try {
    656                 flush();
    657             } catch (IOException e) {
    658             }
    659         }
    660     }
    661
    662     public void startPrefixMapping(String prefix, String uri)
    663             throws SAXException {
    664         if (namespacesMap == null) {
    665             namespacesMap = new HashMap();
    666         }
    667
    668         namespacesMap.put(prefix, uri);
    669         super.startPrefixMapping(prefix, uri);
    670     }
    671
    672     public void endPrefixMapping(String prefix) throws SAXException {
    673         super.endPrefixMapping(prefix);
    674     }
    675
    676     public void startElement(String namespaceURI, String localName,
    677             String qName, Attributes attributes) throws SAXException {
    678         try {
    679             charsAdded = false;
    680
    681             writePrintln();
    682             indent();
    683             writer.write("<");
    684             writer.write(qName);
    685             writeNamespaces();
    686             writeAttributes(attributes);
    687             writer.write(">");
    688             ++indentLevel;
    689             lastOutputNodeType = Node.ELEMENT_NODE;
    690             lastElementClosed = false;
    691
    692             super.startElement(namespaceURI, localName, qName, attributes);
    693         } catch (IOException e) {
    694             handleException(e);
    695         }
    696     }
    697
    698     public void endElement(String namespaceURI, String localName, String qName)
    699             throws SAXException {
    700         try {
    701             charsAdded = false;
    702             --indentLevel;
    703
    704             if (lastElementClosed) {
    705                 writePrintln();
    706                 indent();
    707             }
    708
    709             // XXXX: need to determine this using a stack and checking for
    710             // content / children
    711             boolean hadContent = true;
    712
    713             if (hadContent) {
    714                 writeClose(qName);
    715             } else {
    716                 writeEmptyElementClose(qName);
    717             }
    718
    719             lastOutputNodeType = Node.ELEMENT_NODE;
    720             lastElementClosed = true;
    721
    722             super.endElement(namespaceURI, localName, qName);
    723         } catch (IOException e) {
    724             handleException(e);
    725         }
    726     }
    727
    728     public void characters(char[] ch, int start, int length)
    729             throws SAXException {
    730         if ((ch == null) || (ch.length == 0) || (length <= 0)) {
    731             return;
    732         }
    733
    734         try {
    735             /*
    736              * we can't use the writeString method here because it's possible we
    737              * don't receive all characters at once and calling writeString
    738              * would cause unwanted spaces to be added in between these chunks
    739              * of character arrays.
    740              */
    741             String string = String.valueOf(ch, start, length);
    742
    743             if (escapeText) {
    744                 string = escapeElementEntities(string);
    745             }
    746
    747             if (format.isTrimText()) {
    748                 if ((lastOutputNodeType == Node.TEXT_NODE) && !charsAdded) {
    749                     writer.write(' ');
    750                 } else if (charsAdded && Character.isWhitespace(lastChar)) {
    751                     writer.write(' ');
    752                 } else if (lastOutputNodeType == Node.ELEMENT_NODE
    753                         && format.isPadText() && lastElementClosed
    754                         && Character.isWhitespace(ch[0])) {
    755                     writer.write(PAD_TEXT);
    756                 }
    757
    758                 String delim = "";
    759                 StringTokenizer tokens = new StringTokenizer(string);
    760
    761                 while (tokens.hasMoreTokens()) {
    762                     writer.write(delim);
    763                     writer.write(tokens.nextToken());
    764                     delim = " ";
    765                 }
    766             } else {
    767                 writer.write(string);
    768             }
    769
    770             charsAdded = true;
    771             lastChar = ch[(start + length) - 1];
    772             lastOutputNodeType = Node.TEXT_NODE;
    773
    774             super.characters(ch, start, length);
    775         } catch (IOException e) {
    776             handleException(e);
    777         }
    778     }
    779
    780     public void ignorableWhitespace(char[] ch, int start, int length)
    781             throws SAXException {
    782         super.ignorableWhitespace(ch, start, length);
    783     }
    784
    785     public void processingInstruction(String target, String data)
    786             throws SAXException {
    787         try {
    788             indent();
    789             writer.write("<?");
    790             writer.write(target);
    791             writer.write(" ");
    792             writer.write(data);
    793             writer.write("?>");
    794             writePrintln();
    795             lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
    796
    797             super.processingInstruction(target, data);
    798         } catch (IOException e) {
    799             handleException(e);
    800         }
    801     }
    802
    803     // DTDHandler interface
    804     // -------------------------------------------------------------------------
    805     public void notationDecl(String name, String publicID, String systemID)
    806             throws SAXException {
    807         super.notationDecl(name, publicID, systemID);
    808     }
    809
    810     public void unparsedEntityDecl(String name, String publicID,
    811             String systemID, String notationName) throws SAXException {
    812         super.unparsedEntityDecl(name, publicID, systemID, notationName);
    813     }
    814
    815     // LexicalHandler interface
    816     // -------------------------------------------------------------------------
    817     public void startDTD(String name, String publicID, String systemID)
    818             throws SAXException {
    819         inDTD = true;
    820
    821         try {
    822             writeDocType(name, publicID, systemID);
    823         } catch (IOException e) {
    824             handleException(e);
    825         }
    826
    827         if (lexicalHandler != null) {
    828             lexicalHandler.startDTD(name, publicID, systemID);
    829         }
    830     }
    831
    832     public void endDTD() throws SAXException {
    833         inDTD = false;
    834
    835         if (lexicalHandler != null) {
    836             lexicalHandler.endDTD();
    837         }
    838     }
    839
    840     public void startCDATA() throws SAXException {
    841         try {
    842             writer.write("<![CDATA[");
    843         } catch (IOException e) {
    844             handleException(e);
    845         }
    846
    847         if (lexicalHandler != null) {
    848             lexicalHandler.startCDATA();
    849         }
    850     }
    851
    852     public void endCDATA() throws SAXException {
    853         try {
    854             writer.write("]]>");
    855         } catch (IOException e) {
    856             handleException(e);
    857         }
    858
    859         if (lexicalHandler != null) {
    860             lexicalHandler.endCDATA();
    861         }
    862     }
    863
    864     public void startEntity(String name) throws SAXException {
    865         try {
    866             writeEntityRef(name);
    867         } catch (IOException e) {
    868             handleException(e);
    869         }
    870
    871         if (lexicalHandler != null) {
    872             lexicalHandler.startEntity(name);
    873         }
    874     }
    875
    876     public void endEntity(String name) throws SAXException {
    877         if (lexicalHandler != null) {
    878             lexicalHandler.endEntity(name);
    879         }
    880     }
    881
    882     public void comment(char[] ch, int start, int length) throws SAXException {
    883         if (showCommentsInDTDs || !inDTD) {
    884             try {
    885                 charsAdded = false;
    886                 writeComment(new String(ch, start, length));
    887             } catch (IOException e) {
    888                 handleException(e);
    889             }
    890         }
    891
    892         if (lexicalHandler != null) {
    893             lexicalHandler.comment(ch, start, length);
    894         }
    895     }
    896
    897     // Implementation methods
    898     // -------------------------------------------------------------------------
    899     protected void writeElement(Element element) throws IOException {
    900         int size = element.nodeCount();
    901         String qualifiedName = element.getQualifiedName();
    902
    903         writePrintln();
    904         indent();
    905
    906         writer.write("<");
    907         writer.write(qualifiedName);
    908
    909         int previouslyDeclaredNamespaces = namespaceStack.size();
    910         Namespace ns = element.getNamespace();
    911
    912         if (isNamespaceDeclaration(ns)) {
    913             namespaceStack.push(ns);
    914             writeNamespace(ns);
    915         }
    916
    917         // Print out additional namespace declarations
    918         boolean textOnly = true;
    919
    920         for (int i = 0; i < size; i++) {
    921             Node node = element.node(i);
    922
    923             if (node instanceof Namespace) {
    924                 Namespace additional = (Namespace) node;
    925
    926                 if (isNamespaceDeclaration(additional)) {
    927                     namespaceStack.push(additional);
    928                     writeNamespace(additional);
    929                 }
    930             } else if (node instanceof Element) {
    931                 textOnly = false;
    932             } else if (node instanceof Comment) {
    933                 textOnly = false;
    934             }
    935         }
    936
    937         writeAttributes(element);
    938
    939         lastOutputNodeType = Node.ELEMENT_NODE;
    940
    941         if (size <= 0) {
    942             writeEmptyElementClose(qualifiedName);
    943         } else {
    944             writer.write(">");
    945
    946             if (textOnly) {
    947                 // we have at least one text node so lets assume
    948                 // that its non-empty
    949                 writeElementContent(element);
    950             } else {
    951                 // we know it's not null or empty from above
    952                 ++indentLevel;
    953
    954                 writeElementContent(element);
    955
    956                 --indentLevel;
    957
    958                 writePrintln();
    959                 indent();
    960             }
    961
    962             writer.write("</");
    963             writer.write(qualifiedName);
    964             writer.write(">");
    965         }
    966
    967         // remove declared namespaceStack from stack
    968         while (namespaceStack.size() > previouslyDeclaredNamespaces) {
    969             namespaceStack.pop();
    970         }
    971
    972         lastOutputNodeType = Node.ELEMENT_NODE;
    973     }
    974
    975     /***
    976      * Determines if element is a special case of XML elements where it contains
    977      * an xml:space attribute of "preserve". If it does, then retain whitespace.
    978      *
    979      * @param element
    980      *            DOCUMENT ME!
    981      *
    982      * @return DOCUMENT ME!
    983      */
    984     protected final boolean isElementSpacePreserved(Element element) {
    985         final Attribute attr = (Attribute) element.attribute("space");
    986         boolean preserveFound = preserve; // default to global state
    987
    988         if (attr != null) {
    989             if ("xml".equals(attr.getNamespacePrefix())
    990                     && "preserve".equals(attr.getText())) {
    991                 preserveFound = true;
    992             } else {
    993                 preserveFound = false;
    994             }
    995         }
    996
    997         return preserveFound;
    998     }
    999
    1000     /***
    1001      * Outputs the content of the given element. If whitespace trimming is
    1002      * enabled then all adjacent text nodes are appended together before the
    1003      * whitespace trimming occurs to avoid problems with multiple text nodes
    1004      * being created due to text content that spans parser buffers in a SAX
    1005      * parser.
    1006      *
    1007      * @param element
    1008      *            DOCUMENT ME!
    1009      *
    1010      * @throws IOException
    1011      *             DOCUMENT ME!
    1012      */
    1013     protected void writeElementContent(Element element) throws IOException {
    1014         boolean trim = format.isTrimText();
    1015         boolean oldPreserve = preserve;
    1016
    1017         if (trim) { // verify we have to before more expensive test
    1018             preserve = isElementSpacePreserved(element);
    1019             trim = !preserve;
    1020         }
    1021
    1022         if (trim) {
    1023             // concatenate adjacent text nodes together
    1024             // so that whitespace trimming works properly
    1025             Text lastTextNode = null;
    1026             StringBuffer buff = null;
    1027             boolean textOnly = true;
    1028
    1029             for (int i = 0, size = element.nodeCount(); i < size; i++) {
    1030                 Node node = element.node(i);
    1031
    1032                 if (node instanceof Text) {
    1033                     if (lastTextNode == null) {
    1034                         lastTextNode = (Text) node;
    1035                     } else {
    1036                         if (buff == null) {
    1037                             buff = new StringBuffer(lastTextNode.getText());
    1038                         }
    1039
    1040                         buff.append(((Text) node).getText());
    1041                     }
    1042                 } else {
    1043                     if (!textOnly && format.isPadText()) {
    1044                         // only add the PAD_TEXT if the text itself starts with
    1045                         // whitespace
    1046                         char firstChar = 'a';
    1047                         if (buff != null) {
    1048                             firstChar = buff.charAt(0);
    1049                         } else if (lastTextNode != null) {
    1050                             firstChar = lastTextNode.getText().charAt(0);
    1051                         }
    1052
    1053                         if (Character.isWhitespace(firstChar)) {
    1054                             writer.write(PAD_TEXT);
    1055                         }
    1056                     }
    1057
    1058                     if (lastTextNode != null) {
    1059                         if (buff != null) {
    1060                             writeString(buff.toString());
    1061                             buff = null;
    1062                         } else {
    1063                             writeString(lastTextNode.getText());
    1064                         }
    1065
    1066                         if (format.isPadText()) {
    1067                             // only add the PAD_TEXT if the text itself ends
    1068                             // with whitespace
    1069                             char lastTextChar = 'a';
    1070                             if (buff != null) {
    1071                                 lastTextChar = buff.charAt(buff.length() - 1);
    1072                             } else if (lastTextNode != null) {
    1073                                 String txt = lastTextNode.getText();
    1074                                 lastTextChar = txt.charAt(txt.length() - 1);
    1075                             }
    1076
    1077                             if (Character.isWhitespace(lastTextChar)) {
    1078                                 writer.write(PAD_TEXT);
    1079                             }
    1080                         }
    1081
    1082                         lastTextNode = null;
    1083                     }
    1084
    1085                     textOnly = false;
    1086                     writeNode(node);
    1087                 }
    1088             }
    1089
    1090             if (lastTextNode != null) {
    1091                 if (!textOnly && format.isPadText()) {
    1092                     // only add the PAD_TEXT if the text itself starts with
    1093                     // whitespace
    1094                     char firstChar = 'a';
    1095                     if (buff != null) {
    1096                         firstChar = buff.charAt(0);
    1097                     } else {
    1098                         firstChar = lastTextNode.getText().charAt(0);
    1099                     }
    1100
    1101                     if (Character.isWhitespace(firstChar)) {
    1102                         writer.write(PAD_TEXT);
    1103                     }
    1104                 }
    1105
    1106                 if (buff != null) {
    1107                     writeString(buff.toString());
    1108                     buff = null;
    1109                 } else {
    1110                     writeString(lastTextNode.getText());
    1111                 }
    1112
    1113                 lastTextNode = null;
    1114             }
    1115         } else {
    1116             Node lastTextNode = null;
    1117
    1118             for (int i = 0, size = element.nodeCount(); i < size; i++) {
    1119                 Node node = element.node(i);
    1120
    1121                 if (node instanceof Text) {
    1122                     writeNode(node);
    1123                     lastTextNode = node;
    1124                 } else {
    1125                     if ((lastTextNode != null) && format.isPadText()) {
    1126                         // only add the PAD_TEXT if the text itself ends with
    1127                         // whitespace
    1128                         String txt = lastTextNode.getText();
    1129                         char lastTextChar = txt.charAt(txt.length() - 1);
    1130
    1131                         if (Character.isWhitespace(lastTextChar)) {
    1132                             writer.write(PAD_TEXT);
    1133                         }
    1134                     }
    1135
    1136                     writeNode(node);
    1137
    1138                     // if ((lastTextNode != null) && format.isPadText()) {
    1139                     // writer.write(PAD_TEXT);
    1140                     // }
    1141
    1142                     lastTextNode = null;
    1143                 }
    1144             }
    1145         }
    1146
    1147         preserve = oldPreserve;
    1148     }
    1149
    1150     protected void writeCDATA(String text) throws IOException {
    1151         writer.write("<![CDATA[");
    1152
    1153         if (text != null) {
    1154             writer.write(text);
    1155         }
    1156
    1157         writer.write("]]>");
    1158
    1159         lastOutputNodeType = Node.CDATA_SECTION_NODE;
    1160     }
    1161
    1162     protected void writeDocType(DocumentType docType) throws IOException {
    1163         if (docType != null) {
    1164             docType.write(writer);
    1165             writePrintln();
    1166         }
    1167     }
    1168
    1169     protected void writeNamespace(Namespace namespace) throws IOException {
    1170         if (namespace != null) {
    1171             writeNamespace(namespace.getPrefix(), namespace.getURI());
    1172         }
    1173     }
    1174
    1175     /***
    1176      * Writes the SAX namepsaces
    1177      *
    1178      * @throws IOException
    1179      *             DOCUMENT ME!
    1180      */
    1181     protected void writeNamespaces() throws IOException {
    1182         if (namespacesMap != null) {
    1183             for (Iterator iter = namespacesMap.entrySet().iterator(); iter
    1184                     .hasNext();) {
    1185                 Map.Entry entry = (Map.Entry) iter.next();
    1186                 String prefix = (String) entry.getKey();
    1187                 String uri = (String) entry.getValue();
    1188                 writeNamespace(prefix, uri);
    1189             }
    1190
    1191             namespacesMap = null;
    1192         }
    1193     }
    1194
    1195     /***
    1196      * Writes the SAX namepsaces
    1197      *
    1198      * @param prefix
    1199      *            the prefix
    1200      * @param uri
    1201      *            the namespace uri
    1202      *
    1203      * @throws IOException
    1204      */
    1205     protected void writeNamespace(String prefix, String uri)
    1206             throws IOException {
    1207         if ((prefix != null) && (prefix.length() > 0)) {
    1208             writer.write(" xmlns:");
    1209             writer.write(prefix);
    1210             writer.write("=\"");
    1211         } else {
    1212             writer.write(" xmlns=\"");
    1213         }
    1214
    1215         writer.write(uri);
    1216         writer.write("\"");
    1217     }
    1218
    1219     protected void writeProcessingInstruction(ProcessingInstruction pi)
    1220             throws IOException {
    1221         // indent();
    1222         writer.write("<?");
    1223         writer.write(pi.getName());
    1224         writer.write(" ");
    1225         writer.write(pi.getText());
    1226         writer.write("?>");
    1227         writePrintln();
    1228
    1229         lastOutputNodeType = Node.PROCESSING_INSTRUCTION_NODE;
    1230     }
    1231
    1232     protected void writeString(String text) throws IOException {
    1233         if ((text != null) && (text.length() > 0)) {
    1234             if (escapeText) {
    1235                 text = escapeElementEntities(text);
    1236             }
    1237
    1238             // if (format.isPadText()) {
    1239             // if (lastOutputNodeType == Node.ELEMENT_NODE) {
    1240             // writer.write(PAD_TEXT);
    1241             // }
    1242             // }
    1243             if (format.isTrimText()) {
    1244                 boolean first = true;
    1245                 StringTokenizer tokenizer = new StringTokenizer(text);
    1246
    1247                 while (tokenizer.hasMoreTokens()) {
    1248                     String token = tokenizer.nextToken();
    1249
    1250                     if (first) {
    1251                         first = false;
    1252
    1253                         if (lastOutputNodeType == Node.TEXT_NODE) {
    1254                             writer.write(" ");
    1255                         }
    1256                     } else {
    1257                         writer.write(" ");
    1258                     }
    1259
    1260                     writer.write(token);
    1261                     lastOutputNodeType = Node.TEXT_NODE;
    1262                     lastChar = token.charAt(token.length() - 1);
    1263                 }
    1264             } else {
    1265                 lastOutputNodeType = Node.TEXT_NODE;
    1266                 writer.write(text);
    1267                 lastChar = text.charAt(text.length() - 1);
    1268             }
    1269         }
    1270     }
    1271
    1272     /***
    1273      * This method is used to write out Nodes that contain text and still allow
    1274      * for xml:space to be handled properly.
    1275      *
    1276      * @param node
    1277      *            DOCUMENT ME!
    1278      *
    1279      * @throws IOException
    1280      *             DOCUMENT ME!
    1281      */
    1282     protected void writeNodeText(Node node) throws IOException {
    1283         String text = node.getText();
    1284
    1285         if ((text != null) && (text.length() > 0)) {
    1286             if (escapeText) {
    1287                 text = escapeElementEntities(text);
    1288             }
    1289
    1290             lastOutputNodeType = Node.TEXT_NODE;
    1291             writer.write(text);
    1292             lastChar = text.charAt(text.length() - 1);
    1293         }
    1294     }
    1295
    1296     protected void writeNode(Node node) throws IOException {
    1297         int nodeType = node.getNodeType();
    1298
    1299         switch (nodeType) {
    1300             case Node.ELEMENT_NODE:
    1301                 writeElement((Element) node);
    1302
    1303                 break;
    1304
    1305             case Node.ATTRIBUTE_NODE:
    1306                 writeAttribute((Attribute) node);
    1307
    1308                 break;
    1309
    1310             case Node.TEXT_NODE:
    1311                 writeNodeText(node);
    1312
    1313                 // write((Text) node);
    1314                 break;
    1315
    1316             case Node.CDATA_SECTION_NODE:
    1317                 writeCDATA(node.getText());
    1318
    1319                 break;
    1320
    1321             case Node.ENTITY_REFERENCE_NODE:
    1322                 writeEntity((Entity) node);
    1323
    1324                 break;
    1325
    1326             case Node.PROCESSING_INSTRUCTION_NODE:
    1327                 writeProcessingInstruction((ProcessingInstruction) node);
    1328
    1329                 break;
    1330
    1331             case Node.COMMENT_NODE:
    1332                 writeComment(node.getText());
    1333
    1334                 break;
    1335
    1336             case Node.DOCUMENT_NODE:
    1337                 write((Document) node);
    1338
    1339                 break;
    1340
    1341             case Node.DOCUMENT_TYPE_NODE:
    1342                 writeDocType((DocumentType) node);
    1343
    1344                 break;
    1345
    1346             case Node.NAMESPACE_NODE:
    1347
    1348                 // Will be output with attributes
    1349                 // write((Namespace) node);
    1350                 break;
    1351
    1352             default:
    1353                 throw new IOException("Invalid node type: " + node);
    1354         }
    1355     }
    1356
    1357     protected void installLexicalHandler() {
    1358         XMLReader parent = getParent();
    1359
    1360         if (parent == null) {
    1361             throw new NullPointerException("No parent for filter");
    1362         }
    1363
    1364         // try to register for lexical events
    1365         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
    1366             try {
    1367                 parent.setProperty(LEXICAL_HANDLER_NAMES[i], this);
    1368
    1369                 break;
    1370             } catch (SAXNotRecognizedException ex) {
    1371                 // ignore
    1372             } catch (SAXNotSupportedException ex) {
    1373                 // ignore
    1374             }
    1375         }
    1376     }
    1377
    1378     protected void writeDocType(String name, String publicID, String systemID)
    1379             throws IOException {
    1380         boolean hasPublic = false;
    1381
    1382         writer.write("<!DOCTYPE ");
    1383         writer.write(name);
    1384
    1385         if ((publicID != null) && (!publicID.equals(""))) {
    1386             writer.write(" PUBLIC \"");
    1387             writer.write(publicID);
    1388             writer.write("\"");
    1389             hasPublic = true;
    1390         }
    1391
    1392         if ((systemID != null) && (!systemID.equals(""))) {
    1393             if (!hasPublic) {
    1394                 writer.write(" SYSTEM");
    1395             }
    1396
    1397             writer.write(" \"");
    1398             writer.write(systemID);
    1399             writer.write("\"");
    1400         }
    1401
    1402         writer.write(">");
    1403         writePrintln();
    1404     }
    1405
    1406     protected void writeEntity(Entity entity) throws IOException {
    1407         if (!resolveEntityRefs()) {
    1408             writeEntityRef(entity.getName());
    1409         } else {
    1410             writer.write(entity.getText());
    1411         }
    1412     }
    1413
    1414     protected void writeEntityRef(String name) throws IOException {
    1415         writer.write("&");
    1416         writer.write(name);
    1417         writer.write(";");
    1418
    1419         lastOutputNodeType = Node.ENTITY_REFERENCE_NODE;
    1420     }
    1421
    1422     protected void writeComment(String text) throws IOException {
    1423         if (format.isNewlines()) {
    1424             println();
    1425             indent();
    1426         }
    1427
    1428         writer.write("<!--");
    1429         writer.write(text);
    1430         writer.write("-->");
    1431
    1432         lastOutputNodeType = Node.COMMENT_NODE;
    1433     }
    1434
    1435     /***
    1436      * Writes the attributes of the given element
    1437      *
    1438      * @param element
    1439      *            DOCUMENT ME!
    1440      *
    1441      * @throws IOException
    1442      *             DOCUMENT ME!
    1443      */
    1444     protected void writeAttributes(Element element) throws IOException {
    1445         // I do not yet handle the case where the same prefix maps to
    1446         // two different URIs. For attributes on the same element
    1447         // this is illegal; but as yet we don't throw an exception
    1448         // if someone tries to do this
    1449         for (int i = 0, size = element.attributeCount(); i < size; i++) {
    1450             Attribute attribute = element.attribute(i);
    1451             Namespace ns = attribute.getNamespace();
    1452
    1453             if ((ns != null) && (ns != Namespace.NO_NAMESPACE)
    1454                     && (ns != Namespace.XML_NAMESPACE)) {
    1455                 String prefix = ns.getPrefix();
    1456                 String uri = namespaceStack.getURI(prefix);
    1457
    1458                 if (!ns.getURI().equals(uri)) {
    1459                     writeNamespace(ns);
    1460                     namespaceStack.push(ns);
    1461                 }
    1462             }
    1463
    1464             // If the attribute is a namespace declaration, check if we have
    1465             // already written that declaration elsewhere (if that's the case,
    1466             // it must be in the namespace stack
    1467             String attName = attribute.getName();
    1468
    1469             if (attName.startsWith("xmlns:")) {
    1470                 String prefix = attName.substring(6);
    1471
    1472                 if (namespaceStack.getNamespaceForPrefix(prefix) == null) {
    1473                     String uri = attribute.getValue();
    1474                     namespaceStack.push(prefix, uri);
    1475                     writeNamespace(prefix, uri);
    1476                 }
    1477             } else if (attName.equals("xmlns")) {
    1478                 if (namespaceStack.getDefaultNamespace() == null) {
    1479                     String uri = attribute.getValue();
    1480                     namespaceStack.push(null, uri);
    1481                     writeNamespace(null, uri);
    1482                 }
    1483             } else {
    1484                 char quote = format.getAttributeQuoteCharacter();
    1485                 writer.write(" ");
    1486                 writer.write(attribute.getQualifiedName());
    1487                 writer.write("=");
    1488                 writer.write(quote);
    1489                 writeEscapeAttributeEntities(attribute.getValue());
    1490                 writer.write(quote);
    1491             }
    1492         }
    1493     }
    1494
    1495     protected void writeAttribute(Attribute attribute) throws IOException {
    1496         writer.write(" ");
    1497         writer.write(attribute.getQualifiedName());
    1498         writer.write("=");
    1499
    1500         char quote = format.getAttributeQuoteCharacter();
    1501         writer.write(quote);
    1502
    1503         writeEscapeAttributeEntities(attribute.getValue());
    1504
    1505         writer.write(quote);
    1506         lastOutputNodeType = Node.ATTRIBUTE_NODE;
    1507     }
    1508
    1509     protected void writeAttributes(Attributes attributes) throws IOException {
    1510         for (int i = 0, size = attributes.getLength(); i < size; i++) {
    1511             writeAttribute(attributes, i);
    1512         }
    1513     }
    1514
    1515     protected void writeAttribute(Attributes attributes, int index)
    1516             throws IOException {
    1517         char quote = format.getAttributeQuoteCharacter();
    1518         writer.write(" ");
    1519         writer.write(attributes.getQName(index));
    1520         writer.write("=");
    1521         writer.write(quote);
    1522         writeEscapeAttributeEntities(attributes.getValue(index));
    1523         writer.write(quote);
    1524     }
    1525
    1526     protected void indent() throws IOException {
    1527         String indent = format.getIndent();
    1528
    1529         if ((indent != null) && (indent.length() > 0)) {
    1530             for (int i = 0; i < indentLevel; i++) {
    1531                 writer.write(indent);
    1532             }
    1533         }
    1534     }
    1535
    1536     /***
    1537      * <p>
    1538      * This will print a new line only if the newlines flag was set to true
    1539      * </p>
    1540      *
    1541      * @throws IOException
    1542      *             DOCUMENT ME!
    1543      */
    1544     protected void writePrintln() throws IOException {
    1545         if (format.isNewlines()) {
    1546             String seperator = format.getLineSeparator();
    1547             if (lastChar != seperator.charAt(seperator.length() - 1)) {
    1548                 writer.write(format.getLineSeparator());
    1549             }
    1550         }
    1551     }
    1552
    1553     /***
    1554      * Get an OutputStreamWriter, use preferred encoding.
    1555      *
    1556      * @param outStream
    1557      *            DOCUMENT ME!
    1558      * @param encoding
    1559      *            DOCUMENT ME!
    1560      *
    1561      * @return DOCUMENT ME!
    1562      *
    1563      * @throws UnsupportedEncodingException
    1564      *             DOCUMENT ME!
    1565      */
    1566     protected Writer createWriter(OutputStream outStream, String encoding)
    1567             throws UnsupportedEncodingException {
    1568         return new BufferedWriter(new OutputStreamWriter(outStream, encoding));
    1569     }
    1570
    1571     /***
    1572      * <p>
    1573      * This will write the declaration to the given Writer. Assumes XML version
    1574      * 1.0 since we don't directly know.
    1575      * </p>
    1576      *
    1577      * @throws IOException
    1578      *             DOCUMENT ME!
    1579      */
    1580     protected void writeDeclaration() throws IOException {
    1581         String encoding = format.getEncoding();
    1582
    1583         // Only print of declaration is not suppressed
    1584         if (!format.isSuppressDeclaration()) {
    1585             // Assume 1.0 version
    1586             if (encoding.equals("UTF8")) {
    1587                 writer.write("<?xml version=\"1.0\"");
    1588
    1589                 if (!format.isOmitEncoding()) {
    1590                     writer.write(" encoding=\"UTF-8\"");
    1591                 }
    1592
    1593                 writer.write("?>");
    1594             } else {
    1595                 writer.write("<?xml version=\"1.0\"");
    1596
    1597                 if (!format.isOmitEncoding()) {
    1598                     writer.write(" encoding=\"" + encoding + "\"");
    1599                 }
    1600
    1601                 writer.write("?>");
    1602             }
    1603
    1604             if (format.isNewLineAfterDeclaration()) {
    1605                 println();
    1606             }
    1607         }
    1608     }
    1609
    1610     protected void writeClose(String qualifiedName) throws IOException {
    1611         writer.write("</");
    1612         writer.write(qualifiedName);
    1613         writer.write(">");
    1614     }
    1615
    1616     protected void writeEmptyElementClose(String qualifiedName)
    1617             throws IOException {
    1618         // Simply close up
    1619         if (!format.isExpandEmptyElements()) {
    1620             writer.write("/>");
    1621         } else {
    1622             writer.write("></");
    1623             writer.write(qualifiedName);
    1624             writer.write(">");
    1625         }
    1626     }
    1627
    1628     protected boolean isExpandEmptyElements() {
    1629         return format.isExpandEmptyElements();
    1630     }
    1631
    1632     /***
    1633      * This will take the pre-defined entities in XML 1.0 and convert their
    1634      * character representation to the appropriate entity reference, suitable
    1635      * for XML attributes.
    1636      *
    1637      * @param text
    1638      *            DOCUMENT ME!
    1639      *
    1640      * @return DOCUMENT ME!
    1641      */
    1642     protected String escapeElementEntities(String text) {
    1643         char[] block = null;
    1644         int i;
    1645         int last = 0;
    1646         int size = text.length();
    1647
    1648         for (i = 0; i < size; i++) {
    1649             String entity = null;
    1650             char c = text.charAt(i);
    1651
    1652             switch (c) {
    1653                 case '<':
    1654                     entity = "&lt;";
    1655
    1656                     break;
    1657
    1658                 case '>':
    1659                     entity = "&gt;";
    1660
    1661                     break;
    1662
    1663                 case '&':
    1664                     entity = "&amp;";
    1665
    1666                     break;
    1667
    1668                 case '\t':
    1669                 case '\n':
    1670                 case '\r':
    1671
    1672                     // don't encode standard whitespace characters
    1673                     if (preserve) {
    1674                         entity = String.valueOf(c);
    1675                     }
    1676
    1677                     break;
    1678
    1679                 default:
    1680
    1681                     if ((c < 32) || shouldEncodeChar(c)) {
    1682                         entity = "&#" + (int) c + ";";
    1683                     }
    1684
    1685                     break;
    1686             }
    1687
    1688             if (entity != null) {
    1689                 if (block == null) {
    1690                     block = text.toCharArray();
    1691                 }
    1692
    1693                 buffer.append(block, last, i - last);
    1694                 buffer.append(entity);
    1695                 last = i + 1;
    1696             }
    1697         }
    1698
    1699         if (last == 0) {
    1700             return text;
    1701         }
    1702
    1703         if (last < size) {
    1704             if (block == null) {
    1705                 block = text.toCharArray();
    1706             }
    1707
    1708             buffer.append(block, last, i - last);
    1709         }
    1710
    1711         String answer = buffer.toString();
    1712         buffer.setLength(0);
    1713
    1714         return answer;
    1715     }
    1716
    1717     protected void writeEscapeAttributeEntities(String txt) throws IOException {
    1718         if (txt != null) {
    1719             String escapedText = escapeAttributeEntities(txt);
    1720             writer.write(escapedText);
    1721         }
    1722     }
    1723
    1724     /***
    1725      * This will take the pre-defined entities in XML 1.0 and convert their
    1726      * character representation to the appropriate entity reference, suitable
    1727      * for XML attributes.
    1728      *
    1729      * @param text
    1730      *            DOCUMENT ME!
    1731      *
    1732      * @return DOCUMENT ME!
    1733      */
    1734     protected String escapeAttributeEntities(String text) {
    1735         char quote = format.getAttributeQuoteCharacter();
    1736
    1737         char[] block = null;
    1738         int i;
    1739         int last = 0;
    1740         int size = text.length();
    1741
    1742         for (i = 0; i < size; i++) {
    1743             String entity = null;
    1744             char c = text.charAt(i);
    1745
    1746             switch (c) {
    1747                 case '<':
    1748                     entity = "&lt;";
    1749
    1750                     break;
    1751
    1752                 case '>':
    1753                     entity = "&gt;";
    1754
    1755                     break;
    1756
    1757                 case '\'':
    1758
    1759                     if (quote == '\'') {
    1760                         entity = "&apos;";
    1761                     }
    1762
    1763                     break;
    1764
    1765                 case '\"':
    1766
    1767                     if (quote == '\"') {
    1768                         entity = "&quot;";
    1769                     }
    1770
    1771                     break;
    1772
    1773                 case '&':
    1774                     entity = "&amp;";
    1775
    1776                     break;
    1777
    1778                 case '\t':
    1779                 case '\n':
    1780                 case '\r':
    1781
    1782                     // don't encode standard whitespace characters
    1783                     break;
    1784
    1785                 default:
    1786
    1787                     if ((c < 32) || shouldEncodeChar(c)) {
    1788                         entity = "&#" + (int) c + ";";
    1789                     }
    1790
    1791                     break;
    1792             }
    1793
    1794             if (entity != null) {
    1795                 if (block == null) {
    1796                     block = text.toCharArray();
    1797                 }
    1798
    1799                 buffer.append(block, last, i - last);
    1800                 buffer.append(entity);
    1801                 last = i + 1;
    1802             }
    1803         }
    1804
    1805         if (last == 0) {
    1806             return text;
    1807         }
    1808
    1809         if (last < size) {
    1810             if (block == null) {
    1811                 block = text.toCharArray();
    1812             }
    1813
    1814             buffer.append(block, last, i - last);
    1815         }
    1816
    1817         String answer = buffer.toString();
    1818         buffer.setLength(0);
    1819
    1820         return answer;
    1821     }
    1822
    1823     /***
    1824      * Should the given character be escaped. This depends on the encoding of
    1825      * the document.
    1826      *
    1827      * @param c
    1828      *            DOCUMENT ME!
    1829      *
    1830      * @return boolean
    1831      */
    1832     protected boolean shouldEncodeChar(char c) {
    1833         int max = getMaximumAllowedCharacter();
    1834
    1835         return (max > 0) && (c > max);
    1836     }
    1837
    1838     /***
    1839      * Returns the maximum allowed character code that should be allowed
    1840      * unescaped which defaults to 127 in US-ASCII (7 bit) or 255 in ISO- (8
    1841      * bit).
    1842      *
    1843      * @return DOCUMENT ME!
    1844      */
    1845     protected int defaultMaximumAllowedCharacter() {
    1846         String encoding = format.getEncoding();
    1847
    1848         if (encoding != null) {
    1849             if (encoding.equals("US-ASCII")) {
    1850                 return 127;
    1851             }
    1852         }
    1853
    1854         // no encoding for things like ISO-*, UTF-8 or UTF-16
    1855         return -1;
    1856     }
    1857
    1858     protected boolean isNamespaceDeclaration(Namespace ns) {
    1859         if ((ns != null) && (ns != Namespace.XML_NAMESPACE)) {
    1860             String uri = ns.getURI();
    1861
    1862             if (uri != null) {
    1863                 if (!namespaceStack.contains(ns)) {
    1864                     return true;
    1865                 }
    1866             }
    1867         }
    1868
    1869         return false;
    1870     }
    1871
    1872     protected void handleException(IOException e) throws SAXException {
    1873         throw new SAXException(e);
    1874     }
    1875
    1876     // Laramie Crocker 4/8/2002 10:38AM
    1877
    1878     /***
    1879      * Lets subclasses get at the current format object, so they can call
    1880      * setTrimText, setNewLines, etc. Put in to support the HTMLWriter, in the
    1881      * way that it pushes the current newline/trim state onto a stack and
    1882      * overrides the state within preformatted tags.
    1883      *
    1884      * @return DOCUMENT ME!
    1885      */
    1886     protected OutputFormat getOutputFormat() {
    1887         return format;
    1888     }
    1889
    1890     public boolean resolveEntityRefs() {
    1891         return resolveEntityRefs;
    1892     }
    1893
    1894     public void setResolveEntityRefs(boolean resolve) {
    1895         this.resolveEntityRefs = resolve;
    1896     }
    1897 }
    1898
    1899 /*
    1900  * Redistribution and use of this software and associated documentation
    1901  * ("Software"), with or without modification, are permitted provided that the
    1902  * following conditions are met:
    1903  *
    1904  * 1. Redistributions of source code must retain copyright statements and
    1905  * notices. Redistributions must also contain a copy of this document.
    1906  *
    1907  * 2. Redistributions in binary form must reproduce the above copyright notice,
    1908  * this list of conditions and the following disclaimer in the documentation
    1909  * and/or other materials provided with the distribution.
    1910  *
    1911  * 3. The name "DOM4J" must not be used to endorse or promote products derived
    1912  * from this Software without prior written permission of MetaStuff, Ltd. For
    1913  * written permission, please contact dom4j-info@metastuff.com.
    1914  *
    1915  * 4. Products derived from this Software may not be called "DOM4J" nor may
    1916  * "DOM4J" appear in their names without prior written permission of MetaStuff,
    1917  * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
    1918  *
    1919  * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
    1920  *
    1921  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
    1922  * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    1923  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    1924  * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
    1925  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    1926  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    1927  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    1928  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    1929  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    1930  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    1931  * POSSIBILITY OF SUCH DAMAGE.
    1932  *
    1933  * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
    1934  */