// *************************************************************************************************************************** | |
// * 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><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><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 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><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 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></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 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(); | |
} | |
} |