blob: af88918879c055998e06ae075b52531f1df3f46e [file] [log] [blame]
// ***************************************************************************************************************************
// * 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.juneau.xml;
import java.io.*;
import java.net.*;
import org.apache.juneau.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.xml.annotation.*;
/**
* Specialized writer for serializing XML.
*
* <ul class='notes'>
* <li>
* This class is not intended for external use.
* </ul>
*/
public class XmlWriter extends SerializerWriter {
private String defaultNsPrefix;
private boolean enableNs;
/**
* Constructor.
*
* @param out The wrapped writer.
* @param useWhitespace If <jk>true</jk> XML elements will be indented.
* @param maxIndent The maximum indentation level.
* @param trimStrings If <jk>true</jk>, strings should be trimmed before they're serialized.
* @param quoteChar The quote character to use for attributes. Should be <js>'\''</js> or <js>'"'</js>.
* @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
* @param enableNs Flag to indicate if XML namespaces are enabled.
* @param defaultNamespace The default namespace if XML namespaces are enabled.
*/
public XmlWriter(Writer out, boolean useWhitespace, int maxIndent, boolean trimStrings, char quoteChar,
UriResolver uriResolver, boolean enableNs, Namespace defaultNamespace) {
super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver);
this.enableNs = enableNs;
this.defaultNsPrefix = defaultNamespace == null ? null : defaultNamespace.name;
}
/**
* Writes an opening tag to the output: <code><xt>&lt;ns:name</xt></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oTag(String ns, String name, boolean needsEncoding) throws IOException {
append('<');
if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
append(ns).append(':');
if (needsEncoding)
XmlUtils.encodeElementName(out, name);
else
append(name);
return this;
}
/**
* Shortcut for <code>oTag(ns, name, <jk>false</jk>);</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oTag(String ns, String name) throws IOException {
return oTag(ns, name, false);
}
/**
* Shortcut for <code>oTag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oTag(String name) throws IOException {
return oTag(null, name, false);
}
/**
* Shortcut for <c>i(indent).oTag(ns, name, needsEncoding);</c>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
return i(indent).oTag(ns, name, needsEncoding);
}
/**
* Shortcut for <code>i(indent).oTag(ns, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oTag(int indent, String ns, String name) throws IOException {
return i(indent).oTag(ns, name, false);
}
/**
* Shortcut for <code>i(indent).oTag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oTag(int indent, String name) throws IOException {
return i(indent).oTag(null, name, false);
}
/**
* Closes a tag.
*
* <p>
* Shortcut for <code>append(<js>'>'</js>);</code>
*
* @return This object (for method chaining).
* @throws IOException Thrown by underlying stream.
*/
public XmlWriter cTag() throws IOException {
append('>');
return this;
}
/**
* Closes an empty tag.
*
* <p>
* Shortcut for <code>append(<js>'/'</js>).append(<js>'>'</js>);</code>
*
* @return This object (for method chaining).
* @throws IOException Thrown by underlying stream.
*/
public XmlWriter ceTag() throws IOException {
append('/').append('>');
return this;
}
/**
* Writes a closed tag to the output: <code><xt>&lt;ns:name/&gt;</xt></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter tag(String ns, String name, boolean needsEncoding) throws IOException {
append('<');
if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
append(ns).append(':');
if (needsEncoding)
XmlUtils.encodeElementName(out, name);
else
append(name);
return append('/').append('>');
}
/**
* Shortcut for <code>tag(ns, name, <jk>false</jk>);</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter tag(String ns, String name) throws IOException {
return tag(ns, name, false);
}
/**
* Shortcut for <code>tag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter tag(String name) throws IOException {
return tag(null, name, false);
}
/**
* Shortcut for <code>i(indent).tag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter tag(int indent, String name) throws IOException {
return i(indent).tag(name);
}
/**
* Shortcut for <c>i(indent).tag(ns, name, needsEncoding);</c>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter tag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
return i(indent).tag(ns, name, needsEncoding);
}
/**
* Shortcut for <code>i(indent).tag(ns, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter tag(int indent, String ns, String name) throws IOException {
return i(indent).tag(ns, name);
}
/**
* Writes a start tag to the output: <code><xt>&lt;ns:name&gt;</xt></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter sTag(String ns, String name, boolean needsEncoding) throws IOException {
return oTag(ns, name, needsEncoding).append('>');
}
/**
* Shortcut for <code>sTag(ns, name, <jk>false</jk>);</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter sTag(String ns, String name) throws IOException {
return sTag(ns, name, false);
}
/**
* Shortcut for <code>sTag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter sTag(String name) throws IOException {
return sTag(null, name);
}
/**
* Shortcut for <c>i(indent).sTag(ns, name, needsEncoding);</c>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter sTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
return i(indent).sTag(ns, name, needsEncoding);
}
/**
* Shortcut for <code>i(indent).sTag(ns, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter sTag(int indent, String ns, String name) throws IOException {
return i(indent).sTag(ns, name, false);
}
/**
* Shortcut for <code>i(indent).sTag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter sTag(int indent, String name) throws IOException {
return i(indent).sTag(null, name, false);
}
/**
* Writes an end tag to the output: <code><xt>&lt;/ns:name&gt;</xt></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter eTag(String ns, String name, boolean needsEncoding) throws IOException {
append('<').append('/');
if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
append(ns).append(':');
if (needsEncoding)
XmlUtils.encodeElementName(out, name);
else
append(name);
return append('>');
}
/**
* Shortcut for <code>eTag(ns, name, <jk>false</jk>);</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter eTag(String ns, String name) throws IOException {
return eTag(ns, name, false);
}
/**
* Shortcut for <code>eTag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter eTag(String name) throws IOException {
return eTag(null, name);
}
/**
* Shortcut for <c>i(indent).eTag(ns, name, needsEncoding);</c>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @param needsEncoding If <jk>true</jk>, element name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter eTag(int indent, String ns, String name, boolean needsEncoding) throws IOException {
return i(indent).eTag(ns, name, needsEncoding);
}
/**
* Shortcut for <code>i(indent).eTag(ns, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter eTag(int indent, String ns, String name) throws IOException {
return i(indent).eTag(ns, name, false);
}
/**
* Shortcut for <code>i(indent).eTag(<jk>null</jk>, name, <jk>false</jk>);</code>
*
* @param indent The number of prefix tabs to add.
* @param name The element name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter eTag(int indent, String name) throws IOException {
return i(indent).eTag(name);
}
/**
* Writes an attribute to the output: <code><xa>ns:name</xa>=<xs>'value'</xs></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @param value The attribute value.
* @param valNeedsEncoding If <jk>true</jk>, attribute name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attr(String ns, String name, Object value, boolean valNeedsEncoding) throws IOException {
return oAttr(ns, name).q().attrValue(value, valNeedsEncoding).q();
}
/**
* Shortcut for <code>attr(<jk>null</jk>, name, value, <jk>false</jk>);</code>
*
* @param name The attribute name.
* @param value The attribute value.
* @param valNeedsEncoding If <jk>true</jk>, attribute name will be encoded.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attr(String name, Object value, boolean valNeedsEncoding) throws IOException {
return attr(null, name, value, valNeedsEncoding);
}
/**
* Shortcut for <code>attr(ns, name, value, <jk>false</jk>);</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @param value The attribute value.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attr(String ns, String name, Object value) throws IOException {
return oAttr(ns, name).q().attrValue(value, false).q();
}
/**
* Same as {@link #attr(String, String, Object)}, except pass in a {@link Namespace} object for the namespace.
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @param value The attribute value.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attr(Namespace ns, String name, Object value) throws IOException {
return oAttr(ns == null ? null : ns.name, name).q().attrValue(value, false).q();
}
/**
* Shortcut for <code>attr(<jk>null</jk>, name, value, <jk>false</jk>);</code>
*
* @param name The attribute name.
* @param value The attribute value.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attr(String name, Object value) throws IOException {
return attr((String)null, name, value);
}
/**
* Writes an open-ended attribute to the output: <code><xa>ns:name</xa>=</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oAttr(String ns, String name) throws IOException {
append(' ');
if (enableNs && ns != null && ! (ns.isEmpty() || ns.equals(defaultNsPrefix)))
append(ns).append(':');
append(name).append('=');
return this;
}
/**
* Writes an open-ended attribute to the output: <code><xa>ns:name</xa>=</code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter oAttr(Namespace ns, String name) throws IOException {
return oAttr(ns == null ? null : ns.name, name);
}
/**
* Writes an attribute with a URI value to the output: <code><xa>ns:name</xa>=<xs>'uri-value'</xs></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @param value The attribute value, convertible to a URI via <c>toString()</c>
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attrUri(Namespace ns, String name, Object value) throws IOException {
return attr(ns, name, uriResolver.resolve(value));
}
/**
* Writes an attribute with a URI value to the output: <code><xa>ns:name</xa>=<xs>'uri-value'</xs></code>
*
* @param ns The namespace. Can be <jk>null</jk>.
* @param name The attribute name.
* @param value The attribute value, convertible to a URI via <c>toString()</c>
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter attrUri(String ns, String name, Object value) throws IOException {
return attr(ns, name, uriResolver.resolve(value), true);
}
/**
* Append an attribute with a URI value.
*
* @param name The attribute name.
* @param value The attribute value. Can be any object whose <c>toString()</c> method returns a URI.
* @return This object (for method chaining);
* @throws IOException If a problem occurred.
*/
public XmlWriter attrUri(String name, Object value) throws IOException {
return attrUri((String)null, name, value);
}
/**
* Shortcut for calling <code>text(o, <jk>false</jk>);</code>
*
* @param o The object being serialized.
* @return This object (for method chaining).
* @throws IOException If a problem occurred.
*/
public XmlWriter text(Object o) throws IOException {
text(o, false);
return this;
}
/**
* Serializes and encodes the specified object as valid XML text.
*
* @param o The object being serialized.
* @param preserveWhitespace
* If <jk>true</jk>, then we're serializing {@link XmlFormat#MIXED_PWS} or {@link XmlFormat#TEXT_PWS} content.
* @return This object (for method chaining).
* @throws IOException Thrown by underlying stream.
*/
public XmlWriter text(Object o, boolean preserveWhitespace) throws IOException {
XmlUtils.encodeText(this, o, trimStrings, preserveWhitespace);
return this;
}
/**
* Same as {@link #text(Object)} but treats the value as a URL to resolved then serialized.
*
* @param o The object being serialized.
* @return This object (for method chaining).
* @throws IOException Thrown by underlying stream.
*/
public XmlWriter textUri(Object o) throws IOException {
text(uriResolver.resolve(o), false);
return this;
}
private XmlWriter attrValue(Object o, boolean needsEncoding) throws IOException {
if (needsEncoding)
XmlUtils.encodeAttrValue(out, o, this.trimStrings);
else if (o instanceof URI || o instanceof URL)
append(uriResolver.resolve(o));
else
append(o);
return this;
}
@Override /* SerializerWriter */
public XmlWriter cr(int depth) throws IOException {
super.cr(depth);
return this;
}
@Override /* SerializerWriter */
public XmlWriter cre(int depth) throws IOException {
super.cre(depth);
return this;
}
@Override /* SerializerWriter */
public XmlWriter appendln(int indent, String text) throws IOException {
super.appendln(indent, text);
return this;
}
@Override /* SerializerWriter */
public XmlWriter appendln(String text) throws IOException {
super.appendln(text);
return this;
}
@Override /* SerializerWriter */
public XmlWriter append(int indent, String text) throws IOException {
super.append(indent, text);
return this;
}
@Override /* SerializerWriter */
public XmlWriter append(int indent, char c) throws IOException {
super.append(indent, c);
return this;
}
@Override /* SerializerWriter */
public XmlWriter s() throws IOException {
super.s();
return this;
}
@Override /* SerializerWriter */
public XmlWriter q() throws IOException {
super.q();
return this;
}
@Override /* SerializerWriter */
public XmlWriter i(int indent) throws IOException {
super.i(indent);
return this;
}
@Override /* SerializerWriter */
public XmlWriter ie(int indent) throws IOException {
super.ie(indent);
return this;
}
@Override /* SerializerWriter */
public XmlWriter nl(int indent) throws IOException {
super.nl(indent);
return this;
}
@Override /* SerializerWriter */
public XmlWriter append(Object text) throws IOException {
super.append(text);
return this;
}
@Override /* SerializerWriter */
public XmlWriter append(String text) throws IOException {
super.append(text);
return this;
}
@Override /* SerializerWriter */
public XmlWriter append(char c) throws IOException {
out.write(c);
return this;
}
@Override /* Object */
public String toString() {
return out.toString();
}
}