/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.archetype.common.util;

/*
 * Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions, and the disclaimer that follows
 *    these conditions in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. The name "JDOM" must not be used to endorse or promote products
 *    derived from this software without prior written permission.  For
 *    written permission, please contact <request_AT_jdom_DOT_org>.
 *
 * 4. Products derived from this software may not be called "JDOM", nor
 *    may "JDOM" appear in their name, without prior written permission
 *    from the JDOM Project Management <request_AT_jdom_DOT_org>.
 *
 * In addition, we request (but do not require) that you include in the
 * end-user documentation provided with the redistribution and/or in the
 * software itself an acknowledgement equivalent to the following:
 *     "This product includes software developed by the
 *      JDOM Project (http://www.jdom.org/)."
 * Alternatively, the acknowledgment may be graphical using the logos
 * available at http://www.jdom.org/images/logos.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the JDOM Project and was originally
 * created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
 * Brett McLaughlin <brett_AT_jdom_DOT_org>.  For more information
 * on the JDOM Project, please see <http://www.jdom.org/>.
 */

import javax.xml.transform.Result;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;

import org.codehaus.plexus.util.StringUtils;
import org.jdom2.Attribute;
import org.jdom2.CDATA;
import org.jdom2.Comment;
import org.jdom2.DocType;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.EntityRef;
import org.jdom2.Namespace;
import org.jdom2.ProcessingInstruction;
import org.jdom2.Text;
import org.jdom2.output.EscapeStrategy;

/**
 * <p>This class is a fork from JDOM 1.0 modified to preserve CData sections and
 * comments.</p>
 *
 * <p>Outputs a JDOM document as a stream of bytes. The outputter can manage many
 * styles of document formatting, from untouched to pretty printed. The default
 * is to output the document content exactly as created, but this can be changed
 * by setting a new Format object. For pretty-print output, use
 * <code>{@link Format#getPrettyFormat()}</code>. For whitespace-normalized
 * output, use <code>{@link Format#getCompactFormat()}</code>.</p>
 *
 * <p>There are <code>{@link #output output(...)}</code> methods to print any of
 * the standard JDOM classes, including Document and Element, to either a Writer
 * or an OutputStream. <b>Warning</b>: When outputting to a Writer, make sure
 * the writer's encoding matches the encoding setting in the Format object. This
 * ensures the encoding in which the content is written (controlled by the
 * Writer configuration) matches the encoding placed in the document's XML
 * declaration (controlled by the XMLOutputter). Because a Writer cannot be
 * queried for its encoding, the information must be passed to the Format
 * manually in its constructor or via the
 * <code>{@link Format#setEncoding}</code> method. The default encoding is
 * UTF-8.</p>
 *
 * <p>The methods <code>{@link #outputString outputString(...)}</code> are for
 * convenience only; for top performance you should call one of the <code>{@link
 * #output output(...)}</code> methods and pass in your own Writer or
 * OutputStream if possible.</p>
 *
 * <p>XML declarations are always printed on their own line followed by a line
 * separator (this doesn't change the semantics of the document). To omit
 * printing of the declaration use
 * <code>{@link Format#setOmitDeclaration}</code>. To omit printing of the
 * encoding in the declaration use <code>{@link Format#setOmitEncoding}</code>.
 * Unfortunately there is currently no way to know the original encoding of the
 * document.</p>
 *
 * <p>Empty elements are by default printed as &lt;empty/&gt;, but this can be
 * configured with <code>{@link Format#setExpandEmptyElements}</code> to cause
 * them to be expanded to &lt;empty&gt;&lt;/empty&gt;.</p>
 *
 * @author Brett McLaughlin
 * @author Jason Hunter
 * @author Jason Reid
 * @author Wolfgang Werner
 * @author Elliotte Rusty Harold
 * @author David &amp; Will (from Post Tool Design)
 * @author Dan Schaffer
 * @author Alex Chaffee
 * @author Bradley S. Huffman
 */
public class XMLOutputter implements Cloneable {
    // For normal output
    private Format userFormat = Format.getRawFormat();

    // For xml:space="preserve"
    protected static final Format PRESERVE_FORMAT = Format.getRawFormat();

    // What's currently in use
    protected Format currentFormat = userFormat;

    /**
     * Whether output escaping is enabled for the being processed
     * Element - default is <code>true</code>
     */
    private boolean escapeOutput = true;

    // * * * * * * * * * * Constructors * * * * * * * * * *
    // * * * * * * * * * * Constructors * * * * * * * * * *

    /**
     * This will create an <code>XMLOutputter</code> with the default
     * {@link Format} matching {@link Format#getRawFormat}.
     */
    public XMLOutputter() {}

    /**
     * This will create an <code>XMLOutputter</code> with the specified
     * format characteristics.  Note the format object is cloned internally
     * before use.
     */
    public XMLOutputter(Format format) {
        userFormat = (Format) format.clone();
        currentFormat = userFormat;
    }

    /**
     * This will create an <code>XMLOutputter</code> with all the
     * options as set in the given <code>XMLOutputter</code>.  Note
     * that <code>XMLOutputter two = (XMLOutputter)one.clone();</code>
     * would work equally well.
     *
     * @param that the XMLOutputter to clone
     */
    public XMLOutputter(XMLOutputter that) {
        this.userFormat = (Format) that.userFormat.clone();
        currentFormat = userFormat;
    }

    // * * * * * * * * * * Set parameters methods * * * * * * * * * *
    // * * * * * * * * * * Set parameters methods * * * * * * * * * *

    /**
     * Sets the new format logic for the outputter.  Note the Format
     * object is cloned internally before use.
     *
     * @param newFormat the format to use for output
     */
    public void setFormat(Format newFormat) {
        this.userFormat = (Format) newFormat.clone();
        this.currentFormat = userFormat;
    }

    /**
     * Returns the current format in use by the outputter.  Note the
     * Format object returned is a clone of the one used internally.
     */
    public Format getFormat() {
        return (Format) userFormat.clone();
    }

    // * * * * * * * * * * Output to a OutputStream * * * * * * * * * *
    // * * * * * * * * * * Output to a OutputStream * * * * * * * * * *

    /**
     * This will print the <code>Document</code> to the given output stream.
     * The characters are printed using the encoding specified in the
     * constructor, or a default of UTF-8.
     *
     * @param doc <code>Document</code> to format
     * @param out <code>OutputStream</code> to use
     * @throws IOException if there's any problem writing
     */
    public void output(Document doc, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(doc, writer); // output() flushes
    }

    /**
     * Print out the <code>{@link DocType}</code>.
     *
     * @param doctype <code>DocType</code> to output
     * @param out     <code>OutputStream</code> to use
     */
    public void output(DocType doctype, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(doctype, writer); // output() flushes
    }

    /**
     * Print out an <code>{@link Element}</code>, including
     * its <code>{@link Attribute}</code>s, and all
     * contained (child) elements, etc.
     *
     * @param element <code>Element</code> to output
     * @param out     <code>Writer</code> to use
     */
    public void output(Element element, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(element, writer); // output() flushes
    }

    /**
     * This will handle printing out an <code>{@link
     * Element}</code>'s content only, not including its tag, and
     * attributes.  This can be useful for printing the content of an
     * element that contains HTML, like "&lt;description&gt;JDOM is
     * &lt;b&gt;fun&gt;!&lt;/description&gt;".
     *
     * @param element <code>Element</code> to output
     * @param out     <code>OutputStream</code> to use
     */
    public void outputElementContent(Element element, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        outputElementContent(element, writer); // output() flushes
    }

    /**
     * This will handle printing out a list of nodes.
     * This can be useful for printing the content of an element that
     * contains HTML, like "&lt;description&gt;JDOM is
     * &lt;b&gt;fun&gt;!&lt;/description&gt;".
     *
     * @param list <code>List</code> of nodes
     * @param out  <code>OutputStream</code> to use
     */
    public void output(List<?> list, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(list, writer); // output() flushes
    }

    /**
     * Print out a <code>{@link CDATA}</code> node.
     *
     * @param cdata <code>CDATA</code> to output
     * @param out   <code>OutputStream</code> to use
     */
    public void output(CDATA cdata, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(cdata, writer); // output() flushes
    }

    /**
     * Print out a <code>{@link Text}</code> node.  Perfoms
     * the necessary entity escaping and whitespace stripping.
     *
     * @param text <code>Text</code> to output
     * @param out  <code>OutputStream</code> to use
     */
    public void output(Text text, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(text, writer); // output() flushes
    }

    /**
     * Print out a <code>{@link Comment}</code>.
     *
     * @param comment <code>Comment</code> to output
     * @param out     <code>OutputStream</code> to use
     */
    public void output(Comment comment, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(comment, writer); // output() flushes
    }

    /**
     * Print out a <code>{@link ProcessingInstruction}</code>.
     *
     * @param pi  <code>ProcessingInstruction</code> to output
     * @param out <code>OutputStream</code> to use
     */
    public void output(ProcessingInstruction pi, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(pi, writer); // output() flushes
    }

    /**
     * Print out a <code>{@link EntityRef}</code>.
     *
     * @param entity <code>EntityRef</code> to output
     * @param out    <code>OutputStream</code> to use
     */
    public void output(EntityRef entity, OutputStream out) throws IOException {
        Writer writer = makeWriter(out);
        output(entity, writer); // output() flushes
    }

    /**
     * Get an OutputStreamWriter, using prefered encoding
     * (see {@link Format#setEncoding}).
     */
    private Writer makeWriter(OutputStream out) throws java.io.UnsupportedEncodingException {
        return makeWriter(out, userFormat.encoding);
    }

    /** Get an OutputStreamWriter, use specified encoding. */
    private static Writer makeWriter(OutputStream out, String enc) throws java.io.UnsupportedEncodingException {
        return new BufferedWriter((new OutputStreamWriter(new BufferedOutputStream(out), enc)));
    }

    // * * * * * * * * * * Output to a Writer * * * * * * * * * *
    // * * * * * * * * * * Output to a Writer * * * * * * * * * *

    /**
     * <p>This will print the <code>Document</code> to the given Writer.</p>
     *
     * <p>Warning: using your own Writer may cause the outputter's
     * preferred character encoding to be ignored.  If you use
     * encodings other than UTF-8, we recommend using the method that
     * takes an OutputStream instead.</p>
     *
     * @param doc <code>Document</code> to format
     * @param out <code>Writer</code> to use
     * @throws IOException if there's any problem writing
     */
    public void output(Document doc, Writer out) throws IOException {

        printDeclaration(out, doc, userFormat.encoding);

        // Print out root element, as well as any root level
        // comments and processing instructions,
        // starting with no indentation
        List<?> content = doc.getContent();
        int size = content.size();
        for (int i = 0; i < size; i++) {
            Object obj = content.get(i);

            if (obj instanceof Element) {
                printElement(out, doc.getRootElement(), 0, createNamespaceStack());
            } else if (obj instanceof Comment) {
                printComment(out, (Comment) obj);
            } else if (obj instanceof ProcessingInstruction) {
                printProcessingInstruction(out, (ProcessingInstruction) obj);
            } else if (obj instanceof DocType) {
                printDocType(out, doc.getDocType());
                // Always print line separator after declaration, helps the
                // output look better and is semantically inconsequential
                out.write(currentFormat.lineSeparator);
            } else {
                // XXX if we get here then we have a illegal content, for
                //     now we'll just ignore it
            }

            newline(out);
            indent(out, 0);
        }

        // Output final line separator
        // We output this no matter what the newline flags say
        out.write(currentFormat.lineSeparator);
    }

    /**
     * Print out the <code>{@link DocType}</code>.
     *
     * @param doctype <code>DocType</code> to output
     * @param out     <code>Writer</code> to use
     */
    public void output(DocType doctype, Writer out) throws IOException {
        printDocType(out, doctype);
    }

    /**
     * Print out an <code>{@link Element}</code>, including
     * its <code>{@link Attribute}</code>s, and all
     * contained (child) elements, etc.
     *
     * @param element <code>Element</code> to output
     * @param out     <code>Writer</code> to use
     */
    public void output(Element element, Writer out) throws IOException {
        // If this is the root element we could pre-initialize the
        // namespace stack with the namespaces
        printElement(out, element, 0, createNamespaceStack());
    }

    /**
     * This will handle printing out an <code>{@link
     * Element}</code>'s content only, not including its tag, and
     * attributes.  This can be useful for printing the content of an
     * element that contains HTML, like "&lt;description&gt;JDOM is
     * &lt;b&gt;fun&gt;!&lt;/description&gt;".
     *
     * @param element <code>Element</code> to output
     * @param out     <code>Writer</code> to use
     */
    public void outputElementContent(Element element, Writer out) throws IOException {
        List<?> content = element.getContent();
        printContentRange(out, content, 0, content.size(), 0, createNamespaceStack());
    }

    /**
     * This will handle printing out a list of nodes.
     * This can be useful for printing the content of an element that
     * contains HTML, like "&lt;description&gt;JDOM is
     * &lt;b&gt;fun&gt;!&lt;/description&gt;".
     *
     * @param list <code>List</code> of nodes
     * @param out  <code>Writer</code> to use
     */
    public void output(List<?> list, Writer out) throws IOException {
        printContentRange(out, list, 0, list.size(), 0, createNamespaceStack());
    }

    /**
     * Print out a <code>{@link CDATA}</code> node.
     *
     * @param cdata <code>CDATA</code> to output
     * @param out   <code>Writer</code> to use
     */
    public void output(CDATA cdata, Writer out) throws IOException {
        printCDATA(out, cdata);
    }

    /**
     * Print out a <code>{@link Text}</code> node.  Perfoms
     * the necessary entity escaping and whitespace stripping.
     *
     * @param text <code>Text</code> to output
     * @param out  <code>Writer</code> to use
     */
    public void output(Text text, Writer out) throws IOException {
        printText(out, text);
    }

    /**
     * Print out a <code>{@link Comment}</code>.
     *
     * @param comment <code>Comment</code> to output
     * @param out     <code>Writer</code> to use
     */
    public void output(Comment comment, Writer out) throws IOException {
        printComment(out, comment);
    }

    /**
     * Print out a <code>{@link ProcessingInstruction}</code>.
     *
     * @param pi  <code>ProcessingInstruction</code> to output
     * @param out <code>Writer</code> to use
     */
    public void output(ProcessingInstruction pi, Writer out) throws IOException {
        boolean currentEscapingPolicy = currentFormat.ignoreTrAXEscapingPIs;

        // Output PI verbatim, disregarding TrAX escaping PIs.
        currentFormat.setIgnoreTrAXEscapingPIs(true);
        printProcessingInstruction(out, pi);
        currentFormat.setIgnoreTrAXEscapingPIs(currentEscapingPolicy);
    }

    /**
     * Print out a <code>{@link EntityRef}</code>.
     *
     * @param entity <code>EntityRef</code> to output
     * @param out    <code>Writer</code> to use
     */
    public void output(EntityRef entity, Writer out) throws IOException {
        printEntityRef(out, entity);
    }

    // * * * * * * * * * * Output to a String * * * * * * * * * *
    // * * * * * * * * * * Output to a String * * * * * * * * * *

    /**
     * Return a string representing a document.  Uses an internal
     * StringWriter. Warning: a String is Unicode, which may not match
     * the outputter's specified encoding.
     *
     * @param doc <code>Document</code> to format
     */
    public String outputString(Document doc) {
        StringWriter out = new StringWriter();
        try {
            output(doc, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing a DocType. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param doctype <code>DocType</code> to format
     */
    public String outputString(DocType doctype) {
        StringWriter out = new StringWriter();
        try {
            output(doctype, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing an element. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param element <code>Element</code> to format
     */
    public String outputString(Element element) {
        StringWriter out = new StringWriter();
        try {
            output(element, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing a list of nodes.  The list is
     * assumed to contain legal JDOM nodes.
     *
     * @param list <code>List</code> to format
     */
    public String outputString(List<?> list) {
        StringWriter out = new StringWriter();
        try {
            output(list, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing a CDATA node. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param cdata <code>CDATA</code> to format
     */
    public String outputString(CDATA cdata) {
        StringWriter out = new StringWriter();
        try {
            output(cdata, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing a Text node. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param text <code>Text</code> to format
     */
    public String outputString(Text text) {
        StringWriter out = new StringWriter();
        try {
            output(text, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing a comment. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param comment <code>Comment</code> to format
     */
    public String outputString(Comment comment) {
        StringWriter out = new StringWriter();
        try {
            output(comment, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing a PI. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param pi <code>ProcessingInstruction</code> to format
     */
    public String outputString(ProcessingInstruction pi) {
        StringWriter out = new StringWriter();
        try {
            output(pi, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    /**
     * Return a string representing an entity. Warning: a String is
     * Unicode, which may not match the outputter's specified
     * encoding.
     *
     * @param entity <code>EntityRef</code> to format
     */
    public String outputString(EntityRef entity) {
        StringWriter out = new StringWriter();
        try {
            output(entity, out); // output() flushes
        } catch (IOException e) {
        }
        return out.toString();
    }

    // * * * * * * * * * * Internal printing methods * * * * * * * * * *
    // * * * * * * * * * * Internal printing methods * * * * * * * * * *

    /**
     * This will handle printing of the declaration.
     * Assumes XML version 1.0 since we don't directly know.
     *
     * @param doc      <code>Document</code> whose declaration to write
     * @param out      <code>Writer</code> to use
     * @param encoding the encoding to add to the declaration
     */
    protected void printDeclaration(Writer out, Document doc, String encoding) throws IOException {

        // Only print the declaration if it's not being omitted
        if (!userFormat.omitDeclaration) {
            // Assume 1.0 version
            out.write("<?xml version=\"1.0\"");
            if (!userFormat.omitEncoding) {
                out.write(" encoding=\"" + encoding + "\"");
            }
            out.write("?>");

            // Print new line after decl always, even if no other new lines
            // Helps the output look better and is semantically
            // inconsequential
            out.write(currentFormat.lineSeparator);
        }
    }

    /**
     * This handle printing the DOCTYPE declaration if one exists.
     *
     * @param docType <code>Document</code> whose declaration to write
     * @param out     <code>Writer</code> to use
     */
    protected void printDocType(Writer out, DocType docType) throws IOException {

        String publicID = docType.getPublicID();
        String systemID = docType.getSystemID();
        String internalSubset = docType.getInternalSubset();
        boolean hasPublic = false;

        out.write("<!DOCTYPE ");
        out.write(docType.getElementName());
        if (publicID != null) {
            out.write(" PUBLIC \"");
            out.write(publicID);
            out.write("\"");
            hasPublic = true;
        }
        if (systemID != null) {
            if (!hasPublic) {
                out.write(" SYSTEM");
            }
            out.write(" \"");
            out.write(systemID);
            out.write("\"");
        }
        if ((internalSubset != null) && (!internalSubset.equals(""))) {
            out.write(" [");
            out.write(currentFormat.lineSeparator);
            out.write(docType.getInternalSubset());
            out.write("]");
        }
        out.write(">");
    }

    /**
     * This will handle printing of comments.
     *
     * @param comment <code>Comment</code> to write
     * @param out     <code>Writer</code> to use
     */
    protected void printComment(Writer out, Comment comment) throws IOException {
        out.write("<!--");
        out.write(StringUtils.unifyLineSeparators(comment.getText(), currentFormat.lineSeparator));
        out.write("-->");
    }

    /**
     * This will handle printing of processing instructions.
     *
     * @param pi  <code>ProcessingInstruction</code> to write
     * @param out <code>Writer</code> to use
     */
    protected void printProcessingInstruction(Writer out, ProcessingInstruction pi) throws IOException {
        String target = pi.getTarget();
        boolean piProcessed = false;

        if (!currentFormat.ignoreTrAXEscapingPIs) {
            if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) {
                escapeOutput = false;
                piProcessed = true;
            } else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) {
                escapeOutput = true;
                piProcessed = true;
            }
        }
        if (!piProcessed) {
            String rawData = pi.getData();

            // Write <?target data?> or if no data then just <?target?>
            if (!"".equals(rawData)) {
                out.write("<?");
                out.write(target);
                out.write(" ");
                out.write(rawData);
                out.write("?>");
            } else {
                out.write("<?");
                out.write(target);
                out.write("?>");
            }
        }
    }

    /**
     * This will handle printing a <code>{@link EntityRef}</code>.
     * Only the entity reference such as <code>&amp;entity;</code>
     * will be printed. However, subclasses are free to override
     * this method to print the contents of the entity instead.
     *
     * @param entity <code>EntityRef</code> to output
     * @param out    <code>Writer</code> to use
     */
    protected void printEntityRef(Writer out, EntityRef entity) throws IOException {
        out.write("&");
        out.write(entity.getName());
        out.write(";");
    }

    /**
     * This will handle printing of <code>{@link CDATA}</code> text.
     *
     * @param cdata <code>CDATA</code> to output
     * @param out   <code>Writer</code> to use
     */
    protected void printCDATA(Writer out, CDATA cdata) throws IOException {
        String str = (currentFormat.mode == Format.TextMode.NORMALIZE)
                ? cdata.getTextNormalize()
                : ((currentFormat.mode == Format.TextMode.TRIM)
                        ? cdata.getText().trim()
                        : cdata.getText());
        out.write("<![CDATA[");
        out.write(str);
        out.write("]]>");
    }

    /**
     * This will handle printing of <code>{@link Text}</code> strings.
     *
     * @param text <code>Text</code> to write
     * @param out  <code>Writer</code> to use
     */
    protected void printText(Writer out, Text text) throws IOException {
        String str = (currentFormat.mode == Format.TextMode.NORMALIZE)
                ? text.getTextNormalize()
                : ((currentFormat.mode == Format.TextMode.TRIM) ? text.getText().trim() : text.getText());
        out.write(escapeElementEntities(str));
    }

    /**
     * This will handle printing a string.  Escapes the element entities,
     * trims interior whitespace, etc. if necessary.
     */
    private void printString(Writer out, String str) throws IOException {
        if (currentFormat.mode == Format.TextMode.NORMALIZE) {
            str = Text.normalizeString(str);
        } else if (currentFormat.mode == Format.TextMode.TRIM) {
            str = str.trim();
        }
        out.write(escapeElementEntities(str));
    }

    /**
     * This will handle printing of a <code>{@link Element}</code>,
     * its <code>{@link Attribute}</code>s, and all contained (child)
     * elements, etc.
     *
     * @param element    <code>Element</code> to output
     * @param out        <code>Writer</code> to use
     * @param level      <code>int</code> level of indention
     * @param namespaces <code>List</code> stack of Namespaces in scope
     */
    protected void printElement(Writer out, Element element, int level, NamespaceStack namespaces) throws IOException {

        List<?> attributes = element.getAttributes();
        List<?> content = element.getContent();

        // Check for xml:space and adjust format settings
        String space = null;
        if (attributes != null) {
            space = element.getAttributeValue("space", Namespace.XML_NAMESPACE);
        }

        Format previousFormat = currentFormat;

        if ("default".equals(space)) {
            currentFormat = userFormat;
        } else if ("preserve".equals(space)) {
            currentFormat = PRESERVE_FORMAT;
        }

        // Print the beginning of the tag plus attributes and any
        // necessary namespace declarations
        out.write("<");
        printQualifiedName(out, element);

        // Mark our namespace starting point
        int previouslyDeclaredNamespaces = namespaces.size();

        // Print the element's namespace, if appropriate
        printElementNamespace(out, element, namespaces);

        // Print out additional namespace declarations
        printAdditionalNamespaces(out, element, namespaces);

        // Print out attributes
        if (attributes != null) {
            printAttributes(out, attributes, element, namespaces);
        }

        // Depending on the settings (newlines, textNormalize, etc), we may
        // or may not want to print all of the content, so determine the
        // index of the start of the content we're interested
        // in based on the current settings.

        int start = skipLeadingWhite(content, 0);
        int size = content.size();
        if (start >= size) {
            // Case content is empty or all insignificant whitespace
            if (currentFormat.expandEmptyElements) {
                out.write("></");
                printQualifiedName(out, element);
