/*--

 Copyright (C) 2000-2012 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/>.

 */

package org.jdom2.output;

import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;

import org.jdom2.IllegalDataException;
import org.jdom2.Verifier;

/**
 * Class to encapsulate XMLOutputter format options.
 * Typically users adapt the standard format configurations obtained by
 * {@link #getRawFormat} (no whitespace changes),
 * {@link #getPrettyFormat} (whitespace beautification), and
 * {@link #getCompactFormat} (whitespace normalization).
 * <p>
 * Several modes are available to effect the way textual content is printed.
 * See the documentation for {@link TextMode} for details.
 * <p>
 * <b>Note about Line Separator:</b>
 * <p>
 * By default JDOM will always use the CRNL sequence "\r\n" for output. This
 * can be changed in a number of different ways. See the {@link LineSeparator}
 * enumeration for more information.
 * <p>
 * <b>Note about XML Character Escaping:</b>
 * <p>
 * JDOM will escape characters in the output based on the EscapeStrategy that
 * is specified by this Format. The Format will by default use a sensible
 * EscapeStrategy that is based on the character encoding of the output. If
 * the default escape mechanism is not producing the correct results you can
 * change the EscapeStrategy on the format to suit your own needs.  
 * 
 *
 * @see LineSeparator
 *
 * @author Jason Hunter
 * @author Rolf Lear
 */
public class Format implements Cloneable {

	/**
	 * An EscapeStrategy suitable for UTF-8 an UTF-16. We want the class to
	 * have its own name.
	 */
	private static final class EscapeStrategyUTF implements EscapeStrategy {
		@Override
		public final boolean shouldEscape(char ch) {
			return Verifier.isHighSurrogate(ch);
		}
	}

	/**
	 * An EscapeStrategy suitable for UTF-8 an UTF-16
	 */
	private static final EscapeStrategy UTFEscapeStrategy = new EscapeStrategyUTF();

	/**
	 * An EscapeStrategy suitable for 8-bit charsets. We want the class to have
	 * its own name.
	 */
	private static final class EscapeStrategy8Bits implements EscapeStrategy {
		@Override
		public boolean shouldEscape(final char ch) {
			return (ch >>> 8) != 0;
		}
	}

	/**
	 * An EscapeStrategy suitable for 8-bit charsets
	 */
	private static final EscapeStrategy Bits8EscapeStrategy = new EscapeStrategy8Bits();

	/**
	 * An EscapeStrategy suitable for 7-bit charsets. We want the class to
	 * have its own name.
	 */
	private static final class EscapeStrategy7Bits implements EscapeStrategy {
		@Override
		public boolean shouldEscape(final char ch) {
			return (ch >>> 7) != 0;
		}
	}
	
	/**
	 * An EscapeStrategy suitable for 7-bit charsets
	 */
	private static final EscapeStrategy Bits7EscapeStrategy = 
			new EscapeStrategy7Bits();
	
	/**
	 * An EscapeStrategy suitable for 'unknown' charsets
	 */
	private static final EscapeStrategy DefaultEscapeStrategy = new EscapeStrategy() {
		@Override
		public boolean shouldEscape(char ch) {
			if (Verifier.isHighSurrogate(ch)) {
				return true;  // Safer this way per http://unicode.org/faq/utf_bom.html#utf8-4
			}

			return false;
		}
	};
	
	/**
	 * Handles Charsets.
	 */
	private final static class DefaultCharsetEscapeStrategy implements EscapeStrategy {
		
		private final CharsetEncoder encoder;

		public DefaultCharsetEscapeStrategy(CharsetEncoder cse) {
			encoder = cse;
		}

		@Override
		public boolean shouldEscape(final char ch) {
			
			if (Verifier.isHighSurrogate(ch)) {
				return true;  // Safer this way per http://unicode.org/faq/utf_bom.html#utf8-4
			}

			return !encoder.canEncode(ch);
		}
		
	}
	
	/**
	 * Returns a new Format object that performs no whitespace changes, uses
	 * the UTF-8 encoding, doesn't expand empty elements, includes the
	 * declaration and encoding, and uses the default entity escape strategy.
	 * Tweaks can be made to the returned Format instance without affecting
	 * other instances.

	 * @return                     a Format with no whitespace changes
	 */
	public static Format getRawFormat() {
		return new Format();
	}

	/**
	 * Returns a new Format object that performs whitespace beautification with
	 * 2-space indents, uses the UTF-8 encoding, doesn't expand empty elements,
	 * includes the declaration and encoding, and uses the default entity
	 * escape strategy.
	 * Tweaks can be made to the returned Format instance without affecting
	 * other instances.
	 *
	 * @return                     a Format with whitespace beautification
	 */
	public static Format getPrettyFormat() {
		Format f = new Format();
		f.setIndent(STANDARD_INDENT);
		f.setTextMode(TextMode.TRIM);
		return f;
	}

	/**
	 * Returns a new Format object that performs whitespace normalization, uses
	 * the UTF-8 encoding, doesn't expand empty elements, includes the
	 * declaration and encoding, and uses the default entity escape strategy.
	 * Tweaks can be made to the returned Format instance without affecting
	 * other instances.
	 *
	 * @return                     a Format with whitespace normalization
	 */
	public static Format getCompactFormat() {
		Format f = new Format();
		f.setTextMode(TextMode.NORMALIZE);
		return f;
	}
	
	/**
	 * Use the XML Specification definition of whitespace to compact the
	 * input value. The value is trimmed, and any internal XML whitespace
	 * is replaced with a single ' ' space.
	 * @param str The value to compact.
	 * @return The compacted value
	 * @since JDOM2
	 */
	public static final String compact(String str) {
		int right = str.length() - 1;
		int left = 0;
		while (left <= right && 
				Verifier.isXMLWhitespace(str.charAt(left))) {
			left++;
		}
		while (right > left &&
				Verifier.isXMLWhitespace(str.charAt(right))) {
			right--;
		}
		
		if (left > right) {
			return "";
		}
		
		boolean space = true;
		final StringBuilder buffer = new StringBuilder(right - left + 1);
		while (left <= right) {
			final char c = str.charAt(left);
			if (Verifier.isXMLWhitespace(c)) {
				if (space) {
					buffer.append(' ');
					space = false;
				}
			} else {
				buffer.append(c);
				space = true;
			}
			left++;
		}
		return buffer.toString();
	}

	/**
	 * Use the XML Specification definition of whitespace to Right-trim the
	 * input value.
	 * @param str The value to trim.
	 * @return The value right-trimmed
	 * @since JDOM2
	 */
	public static final String trimRight(String str) {
		int right = str.length() - 1;
		while (right >= 0 && Verifier.isXMLWhitespace(str.charAt(right))) {
			right--;
		}
		if (right < 0) {
			return "";
		}
		return str.substring(0, right + 1);
	}

	/**
	 * Use the XML Specification definition of whitespace to Left-trim the
	 * input value.
	 * @param str The value to trim.
	 * @return The value left-trimmed
	 * @since JDOM2
	 */
	public static final String trimLeft(final String str) {
		final int right = str.length();
		int left = 0;
		while (left < right && Verifier.isXMLWhitespace(str.charAt(left))) {
			left++;
		}
		if (left >= right) {
			return "";
		}

		return str.substring(left);
	}

	/**
	 * Use the XML Specification definition of whitespace to trim the
	 * input value.
	 * @param str The value to trim.
	 * @return The value trimmed
	 * @since JDOM2
	 */
	public static final String trimBoth(final String str) {
		int right = str.length() - 1;
		while (right > 0 && Verifier.isXMLWhitespace(str.charAt(right))) {
			right--;
		}
		int left = 0;
		while (left <= right && Verifier.isXMLWhitespace(str.charAt(left))) {
			left++;
		}
		if (left > right) {
			return "";
		}
		return str.substring(left, right + 1);
	}

	
	/**
	 * This will take the three pre-defined entities in XML 1.0 ('&lt;', '&gt;',
	 * and '&amp;' - used specifically in XML elements) as well as CR/NL, tabs,
	 * and Quote characters which require escaping inside Attribute values and
	 * converts their character representation to the appropriate entity
	 * reference suitable for XML attribute content. Further, some special
	 * characters (e.g. characters that are not valid in the current encoding)
	 * are converted to escaped representations.
	 * <p>
	 * @param strategy 
	 *        The EscapeStrategy to query.
	 * @param value
	 *        <code>String</code> Attribute value to escape.
	 * @return The value appropriately escaped.
	 * @throws IllegalDataException
	 *         if an entity can not be escaped
	 */
	public static final String escapeAttribute(final EscapeStrategy strategy, 
			final String value) {
		final int len = value.length();
		int idx = 0;
		
		checkloop: while (idx < len) {
			final char ch = value.charAt(idx);
			if (ch == '<' || ch == '>' || ch == '&' || ch == '\r' || ch == '\n'
					|| ch == '"' || ch == '\t' || strategy.shouldEscape(ch)) {
				break checkloop;
			}
			idx++;
		}
		
		if (idx == len) {
			return value;
		}
		
		char highsurrogate = 0;
		final StringBuilder sb = new StringBuilder(len + 5);
		sb.append(value, 0, idx);
		while (idx < len) {
			final char ch = value.charAt(idx++);
			if (highsurrogate > 0) {
				if (!Verifier.isLowSurrogate(ch)) {
					throw new IllegalDataException(
							"Could not decode surrogate pair 0x" +
									Integer.toHexString(highsurrogate) + " / 0x"
									+ Integer.toHexString(ch));
				}
				int chp = Verifier.decodeSurrogatePair(highsurrogate, ch);
				sb.append("&#x");
				sb.append(Integer.toHexString(chp));
				sb.append(';');
				highsurrogate = 0;
				continue;
			}
			switch (ch) {
				case '<':
					sb.append("&lt;");
					break;
				case '>':
					sb.append("&gt;");
					break;
				case '&':
					sb.append("&amp;");
					break;
				case '\r':
					sb.append("&#xD;");
					break;
				case '"':
					sb.append("&quot;");
					break;
				case '\t':
					sb.append("&#x9;");
					break;
				case '\n':
					sb.append("&#xA;");
					break;
				default:

					if (strategy.shouldEscape(ch)) {
						// make sure what we are escaping is not the
						// beginning of a multi-byte character.
						if (Verifier.isHighSurrogate(ch)) {
							// this is a the high of a surrogate pair
							highsurrogate = ch;
						} else {
							sb.append("&#x");
							sb.append(Integer.toHexString(ch));
							sb.append(';');
						}
					} else {
						sb.append(ch);
					}
					break;
			}
		}
		if (highsurrogate > 0) {
			throw new IllegalDataException("Surrogate pair 0x" +
					Integer.toHexString(highsurrogate) + "truncated");
		}

		return sb.toString();
	}

	
	
	
	/**
	 * This will take the three pre-defined entities in XML 1.0 ('&lt;', '&gt;',
	 * and '&amp;' - used specifically in XML elements) and convert their
	 * character representation to the appropriate entity reference, suitable
	 * for XML element content. Further, some special characters (e.g.
	 * characters that are not valid in the current encoding) are converted to
	 * escaped representations. If the eol parameter is not null, then any
	 * internal newlines will be replaced with the specified eol sequence.
	 * 
	 * @param strategy
	 *        The EscapeStrategy
	 * @param eol
	 *        The End-Of-Line sequence to be used (may be null).
	 * @param value
	 *        The String to escape
	 * @return The input value escaped.
	 * @throws IllegalDataException
	 *         if an entity can not be escaped
	 * @since JDOM2
	 */
	public static final String escapeText(final EscapeStrategy strategy,
			final String eol, final String value) {
		final int right = value.length();
		int idx = 0;
		checkloop: while (idx < right) {
			final char ch = value.charAt(idx);
			if (ch == '<' || ch == '>' || ch == '&' || ch == '\r' || ch == '\n'
					|| strategy.shouldEscape(ch)) {
				break checkloop;
			}
			idx++;
		}
		
		if (idx == right) {
			// no escape needed.
			return value;
		}
		
		StringBuilder sb = new StringBuilder();
		if (idx > 0) {
			sb.append(value, 0, idx);
		}
		char highsurrogate = 0;
		while (idx < right) {
			final char ch = value.charAt(idx++);
			if (highsurrogate > 0) {
				if (!Verifier.isLowSurrogate(ch)) {
					throw new IllegalDataException(
							"Could not decode surrogate pair 0x" +
									Integer.toHexString(highsurrogate) + " / 0x"
									+ Integer.toHexString(ch));
				}
				int chp = Verifier.decodeSurrogatePair(highsurrogate, ch);
				sb.append("&#x" + Integer.toHexString(chp) + ";");
				highsurrogate = 0;
				continue;
			}
			switch (ch) {
				case '<':
					sb.append("&lt;");
					break;
				case '>':
					sb.append("&gt;");
					break;
				case '&':
					sb.append("&amp;");
					break;
				case '\r':
					sb.append("&#xD;");
					break;
				case '\n':
					if (eol != null) {
						sb.append(eol);
					} else {
						sb.append('\n');
					}
					break;
				default:

					if (strategy.shouldEscape(ch)) {
						// make sure what we are escaping is not the
						// beginning of a multi-byte character.
						if (Verifier.isHighSurrogate(ch)) {
							// this is a the high of a surrogate pair
							highsurrogate = ch;
						} else {
							sb.append("&#x" + Integer.toHexString(ch) + ";");
						}
					} else {
						sb.append(ch);
					}
					break;
			}
		}
		if (highsurrogate > 0) {
			throw new IllegalDataException("Surrogate pair 0x" +
					Integer.toHexString(highsurrogate) + "truncated");
		}
		
		return sb.toString();

	}
	
	
	private static final EscapeStrategy chooseStrategy(String encoding) {
		if ("UTF-8".equalsIgnoreCase(encoding) ||
				"UTF-16".equalsIgnoreCase(encoding)) {
			return UTFEscapeStrategy;
		}
		
		if (encoding.toUpperCase().startsWith("ISO-8859-") ||
				"Latin1".equalsIgnoreCase(encoding)) {
			return Bits8EscapeStrategy;
		}
		
		if ("US-ASCII".equalsIgnoreCase(encoding) ||
				"ASCII".equalsIgnoreCase(encoding)) {
			return Bits7EscapeStrategy;
		}

		try {
			final CharsetEncoder cse = Charset.forName(encoding).newEncoder();
			return new DefaultCharsetEscapeStrategy(cse);
		} catch (Exception e) {
			// swallow that... and assume false.
		}
		return DefaultEscapeStrategy;
	}
	

	/** standard value to indent by, if we are indenting */
	private static final String STANDARD_INDENT = "  ";

	/** standard string with which to end a line */
	private static final String STANDARD_LINE_SEPARATOR = LineSeparator.DEFAULT.value();

	/** standard encoding */
	private static final String STANDARD_ENCODING = "UTF-8";


	/** The default indent is no spaces (as original document) */
	String indent = null;

	/** New line separator */
	String lineSeparator = STANDARD_LINE_SEPARATOR;

	/** The encoding format */
	String encoding = STANDARD_ENCODING;

	/** Whether or not to output the XML declaration
	 * - default is <code>false</code> */
	boolean omitDeclaration = false;

	/** Whether or not to output the encoding in the XML declaration
	 * - default is <code>false</code> */
	boolean omitEncoding = false;
	
	/** Whether Attributes that are defaulted from the DTD or Schema
	 * are output. */
	boolean specifiedAttributesOnly = false;

	/** Whether or not to expand empty elements to
	 * &lt;tagName&gt;&lt;/tagName&gt; - default is <code>false</code> */
	boolean expandEmptyElements = false;

	/** Whether TrAX output escaping disabling/enabling PIs are ignored
	 * or processed - default is <code>false</code> */
	boolean ignoreTrAXEscapingPIs = false;

	/** text handling mode */
	TextMode mode = TextMode.PRESERVE;

	/** entity escape logic */
	EscapeStrategy escapeStrategy = DefaultEscapeStrategy;

	/**
	 * Creates a new Format instance with default (raw) behavior.
	 */
	private Format() {
		setEncoding(STANDARD_ENCODING);
	}

	/**
	 * Sets the {@link EscapeStrategy} to use for character escaping.
	 *
	 * @param strategy the EscapeStrategy to use
	 * @return a pointer to this Format for chaining
	 */
	public Format setEscapeStrategy(EscapeStrategy strategy) {
		escapeStrategy = strategy;
		return this;
	}

	/**
	 * Returns the current escape strategy
	 *
	 * @return the current escape strategy
	 */
	public EscapeStrategy getEscapeStrategy() {
		return escapeStrategy;
	}

	/**
	 * This will set the newline separator (<code>LineSeparator</code>).
	 * The default is <code>\r\n</code>.
	 * <p>
	 * Use the {@link #setLineSeparator(LineSeparator)} method to set
	 * standard separators in an easier way.
	 * <p>
	 * To make it output the system default line ending string, call
	 * <code>setLineSeparator(System.getProperty("line.separator"))</code>.
	 *
	 * <p>
	 * To output "UNIX-style" documents, call
	 * <code>setLineSeparator("\n")</code>.  To output "Mac-style"
	 * documents, call <code>setLineSeparator("\r")</code>.  DOS-style
	 * documents use CR-LF ("\r\n"), which is the default.
	 * </p>
	 *
	 * <p>
	 * Note that this only applies to newlines generated by the
	 * outputter.  All XML parsers are required to 'normalize' all the
	 * combinations of line seperators to just '\n'. As a result, if any JDOM
	 * component has an end-of-line-like value (e.g. '\r') in it then that value
	 * must be the result of an escaped value in the XML source document
	 * <code>&amp;#xD;</code> or a value explicitly set with one of the Text
	 * value setters. Values in JDOM content that were explicitly set to be
	 * '\r' will always be escaped on XML Output.
	 * <p>
	 * The actual newline separator itself though can be set with this method.
	 * Any internal newlines in Text output will be represented by this
	 * end-of-line sequence. For example, the following code:
	 * <p>
	 * <pre>
	 *   Text txt = new Text("\r\n");
	 *   XMLOutputter xout = new XMLOutputter();
	 *   String result = xout.outputString(txt);
	 * </pre>
	 * will produce the literal String sequence "&amp;#xD;\r\n" because the
	 * original \r is escaped to be <code>&amp;#xD;</code> and the original \n
	 * is replaced with the JDOM default Line Separator "\r\n".
	 *
	 * <p>
	 * If the format's "indent" property is null (as is the default
	 * for the Raw and Compact formats), then this value only effects the
	 * newlines written after the declaration and doctype, as well as any
	 * newlines embedded within existing text content. 
	 * </p>
	 * Setting the indent to be null will disable end-of-line processing
	 * for any formatting, but will not affect substitution of embedded \n.
	 * Setting this value to null or the empty string will disable all
	 * end-of-line modifications.
	 *
	 * @see #setTextMode
	 * @see #setLineSeparator(LineSeparator)
	 *
	 * @param separator <code>String</code> line separator to use.
	 * @return a pointer to this Format for chaining
	 */
	public Format setLineSeparator(String separator) {
		this.lineSeparator = "".equals(separator) ? null : separator;
		return this;
	}

	/**
	 * This will set the newline separator sequence.
	 * <p>
	 * This method differes from {@link #setLineSeparator(String)} slightly in
	 * that, to disable end-of-line processing you should call:
	 * <pre>
	 * Format.setLinewSeparator(LineSeparator.NONE);
	 * </pre>
	 * 
	 * @see #setLineSeparator(String) for comprehensive notes.
	 *
	 * @param separator {@link LineSeparator} line separator to us
	 * @return a pointer to this Format for chaining
	 * @since JDOM2
	 */
	public Format setLineSeparator(LineSeparator separator) {
		return setLineSeparator(separator == null ? 
				STANDARD_LINE_SEPARATOR : 
				separator.value());
	}
	
	/**
	 * Returns the current line separator.
	 *
	 * @return the current line separator
	 */
	public String getLineSeparator() {
		return lineSeparator;
	}

	/**
	 * This will set whether the XML declaration
	 * (<code>&lt;&#063;xml version="1&#046;0"
	 * encoding="UTF-8"&#063;&gt;</code>)
	 * includes the encoding of the document. It is common to omit
	 * this in uses such as WML and other wireless device protocols.
	 *
	 * @param omitEncoding <code>boolean</code> indicating whether or not
	 *        the XML declaration should indicate the document encoding.
	 * @return a pointer to this Format for chaining
	 */
	public Format setOmitEncoding(boolean omitEncoding) {
		this.omitEncoding = omitEncoding;
		return this;
	}

	/**
	 * Returns whether the XML declaration encoding will be omitted.
	 *
	 * @return whether the XML declaration encoding will be omitted
	 */
	public boolean getOmitEncoding() {
		return omitEncoding;
	}

	/**
	 * This will set whether the XML declaration
	 * (<code>&lt;&#063;xml version="1&#046;0"&#063;&gt;</code>)
	 * will be omitted or not. It is common to omit this in uses such
	 * as SOAP and XML-RPC calls.
	 *
	 * @param omitDeclaration <code>boolean</code> indicating whether or not
	 *        the XML declaration should be omitted.
	 * @return a pointer to this Format for chaining
	 */
	public Format setOmitDeclaration(boolean omitDeclaration) {
		this.omitDeclaration = omitDeclaration;
		return this;
	}

	/**
	 * Returns whether the XML declaration will be omitted.
	 *
	 * @return whether the XML declaration will be omitted
	 */
	public boolean getOmitDeclaration() {
		return omitDeclaration;
	}

	/**
	 * This will set whether empty elements are expanded from
	 * <code>&lt;tagName/&gt;</code> to
	 * <code>&lt;tagName&gt;&lt;/tagName&gt;</code>.
	 *
	 * @param expandEmptyElements <code>boolean</code> indicating whether or not
	 *        empty elements should be expanded.
	 * @return a pointer to this Format for chaining
	 */
	public Format setExpandEmptyElements(boolean expandEmptyElements) {
		this.expandEmptyElements = expandEmptyElements;
		return this;
	}

	/**
	 * Returns whether empty elements are expanded.
	 *
	 * @return whether empty elements are expanded
	 */
	public boolean getExpandEmptyElements() {
		return expandEmptyElements;
	}

	/**
	 * This will set whether JAXP TrAX processing instructions for
	 * disabling/enabling output escaping are ignored.  Disabling
	 * output escaping allows using XML text as element content and
	 * outputing it verbatim, i&#46;e&#46; as element children would be.
	 * <p>
	 * When processed, these processing instructions are removed from
	 * the generated XML text and control whether the element text
	 * content is output verbatim or with escaping of the pre-defined
	 * entities in XML 1.0.  The text to be output verbatim shall be
	 * surrounded by the
	 * <code>&lt;?javax.xml.transform.disable-output-escaping ?&gt;</code>
	 * and <code>&lt;?javax.xml.transform.enable-output-escaping ?&gt;</code>
	 * PIs.</p>
	 * <p>
	 * When ignored, the processing instructions are present in the
	 * generated XML text and the pre-defined entities in XML 1.0 are
	 * escaped.
	 * <p>
	 * Default: <code>false</code>.</p>
	 *
	 * @param ignoreTrAXEscapingPIs <code>boolean</code> indicating
	 *        whether or not TrAX ouput escaping PIs are ignored.
	 *
	 * @see javax.xml.transform.Result#PI_ENABLE_OUTPUT_ESCAPING
	 * @see javax.xml.transform.Result#PI_DISABLE_OUTPUT_ESCAPING
	 */
	public void setIgnoreTrAXEscapingPIs(boolean ignoreTrAXEscapingPIs) {
		this.ignoreTrAXEscapingPIs = ignoreTrAXEscapingPIs;
	}

	/**
	 * Returns whether JAXP TrAX processing instructions for
	 * disabling/enabling output escaping are ignored.
	 *
	 * @return whether or not TrAX ouput escaping PIs are ignored.
	 */
	public boolean getIgnoreTrAXEscapingPIs() {
		return ignoreTrAXEscapingPIs;
	}

	/**
	 * This sets the text output style.  Options are available as static
	 * {@link TextMode} instances.  The default is {@link TextMode#PRESERVE}.
	 * 
	 * @param mode The TextMode to set.
	 * @return a pointer to this Format for chaining
	 */
	public Format setTextMode(Format.TextMode mode) {
		this.mode = mode;
		return this;
	}

	/**
	 * Returns the current text output style.
	 *
	 * @return the current text output style
	 */
	public Format.TextMode getTextMode() {
		return mode;
	}

	/**
	 * This will set the indent <code>String</code> to use; this
	 * is usually a <code>String</code> of empty spaces. If you pass
	 * the empty string (""), then no indentation will happen but newlines
	 * will still be generated.  Passing null will result in no indentation
	 * and no newlines generated.  Default: none (null)
	 *
	 * @param indent <code>String</code> to use for indentation.
	 * @return a pointer to this Format for chaining
	 */
	public Format setIndent(String indent) {
		this.indent = indent;
		return this;
	}

	/**
	 * Returns the indent string in use.
	 *
	 * @return the indent string in use
	 */
	public String getIndent() {
		return indent;
	}

	/**
	 * Sets the output encoding.  The name should be an accepted XML
	 * encoding.
	 *
	 * @param encoding the encoding format.  Use XML-style names like
	 *                 "UTF-8" or "ISO-8859-1" or "US-ASCII"
	 * @return a pointer to this Format for chaining
	 */
	public Format setEncoding(String encoding) {
		this.encoding = encoding;
		escapeStrategy = chooseStrategy(encoding);
		return this;
	}

	/**
	 * Returns the configured output encoding.
	 *
	 * @return the output encoding
	 */
	public String getEncoding() {
		return encoding;
	}
	
	
	/**
	 * Will Attributes defaulted from the DTD or XMLSchema
	 * be output
	 * @return true if the defaulted Attributes will be output
	 */
	public boolean isSpecifiedAttributesOnly() {
		return specifiedAttributesOnly;
	}

	/**
	 * Set whether only those Attributes specified in the input XML should 
	 * be output. Other Attributes (those defaulted or 'fixed' in the DTD
	 * or XMLSchema) should be ignored.
	 * @param specifiedAttributesOnly true if the defaulted
	 * Attributes should be ignored, false if they should be output
	 */
	public void setSpecifiedAttributesOnly(boolean specifiedAttributesOnly) {
		this.specifiedAttributesOnly = specifiedAttributesOnly;
	}

	@Override
	public Format clone() {
		Format format = null;

		try {
			format = (Format) super.clone();
		}
		catch (CloneNotSupportedException ce) {
			// swallow.
		}

		return format;
	}

	/**
	 * Class to signify how text should be handled on output.  The following
	 * table provides details.
	 *
	 * <table>
	 *   <tr>
	 *     <th align="left">
	 *       Text Mode
	 *     </th>
	 *     <th>
	 *       Resulting behavior.
	 *     </th>
	 *   </tr>
	 *
	 *   <tr valign="top">
	 *     <td>
	 *       <i>PRESERVE (Default)</i>
	 *     </td>
	 *     <td>
	 *       All content is printed in the format it was created, no whitespace
	 *       or line separators are are added or removed.
	 *     </td>
	 *   </tr>
	 *
	 *   <tr valign="top">
	 *     <td>
	 *       TRIM_FULL_WHITE
	 *     </td>
	 *     <td>
	 *       Content between tags consisting of all whitespace is not printed.
	 *       If the content contains even one non-whitespace character, it is
	 *       all printed verbatim, whitespace and all.
	 *     </td>
	 *   </tr>
	 *
	 *   <tr valign="top">
	 *     <td>
	 *       TRIM
	 *     </td>
	 *     <td>
	 *       All leading and trailing whitespace is trimmed.
	 *     </td>
	 *   </tr>
	 *
	 *   <tr valign="top">
	 *     <td>
	 *       NORMALIZE
	 *     </td>
	 *     <td>
	 *       Leading and trailing whitespace is trimmed, and any 'internal'
	 *       whitespace is compressed to a single space.
	 *     </td>
	 *   </tr>
	 * </table>
	 *
	 * In most cases textual content is aligned with the surrounding tags
	 * (after the appropriate text mode is applied). In the case where the only
	 * content between the start and end tags is textual, the start tag, text,
	 * and end tag are all printed on the same line. If the document being
	 * output already has whitespace, it's wise to turn on TRIM mode so the
	 * pre-existing whitespace can be trimmed before adding new whitespace.
	 * <p>
	 * When an element has a xml:space attribute with the value of "preserve",
	 * all formating is turned off (actually, the TextMode is set to
	 * {@link #PRESERVE} until the element and its contents have been printed.
	 * If a nested element contains another xml:space with the value "default"
	 * formatting is turned back on  for the child element and then off for the
	 * remainder of the parent element.
	 * 
	 * @since JDOM2
	 */
	public static enum TextMode {
		/**
		 * Mode for literal text preservation.
		 */
		PRESERVE,

		/**
		 * Mode for text trimming (left and right trim).
		 */
		TRIM,

		/**
		 * Mode for text normalization (left and right trim plus internal
		 * whitespace is normalized to a single space.
		 * @see org.jdom2.Element#getTextNormalize
		 */
		NORMALIZE,

		/**
		 * Mode for text trimming of content consisting of nothing but
		 * whitespace but otherwise not changing output.
		 */
		TRIM_FULL_WHITE;

	}
}
