src.java.com.ctc.wstx.sw.XmlWriter Maven / Gradle / Ivy
/* Woodstox XML processor
*
* Copyright (c) 2004- Tatu Saloranta, [email protected]
*
* Licensed under the License specified in file LICENSE, included with
* the source code.
* You may not use this file except in compliance with the License.
*
* 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 com.ctc.wstx.sw;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.text.MessageFormat;
import javax.xml.stream.XMLStreamException;
import org.codehaus.stax2.XMLStreamReader2;
import org.codehaus.stax2.io.EscapingWriterFactory;
import com.ctc.wstx.api.WriterConfig;
import com.ctc.wstx.api.WstxOutputProperties;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.cfg.OutputConfigFlags;
import com.ctc.wstx.cfg.XmlConsts;
import com.ctc.wstx.exc.WstxIOException;
import com.ctc.wstx.io.TextEscaper;
import com.ctc.wstx.io.WstxInputData;
/**
* This is the base class for actual physical xml outputters. These
* instances will only handle actual writing (possibly including
* encoding) of the serialized textual xml, and will in general
* not verify content being output. The exception are the
* character-by-character checks that are most efficiently done
* at encoding level (such as character escaping, and checks for
* illegal character combinations), which are handled at this
* level.
*
* Note that implementations can have different operating modes:
* specifically, when dealing with illegal content (such as "--"
* in a comment, "?>" in processing instruction, or "]]>" within
* CDATA section), implementations can do one of 3 things:
*
* - Fix the problem, by splitting the section (which can be done
* for CDATA sections, and to some degree, comments)
*
* - Stop outputting, and return an index to the illegal piece
* of data (if there is no easy way to fix the problem: for
* example, for processing instruction)
*
* - Just output content even though it will not result in
* well-formed output. This should only be done if the calling
* application has specifically requested verifications to be
* disabled.
*
*
*/
public abstract class XmlWriter
{
protected final static int SURR1_FIRST = 0xD800;
protected final static int SURR1_LAST = 0xDBFF;
protected final static int SURR2_FIRST = 0xDC00;
protected final static int SURR2_LAST = 0xDFFF;
protected final static char DEFAULT_QUOTE_CHAR = '"';
protected final WriterConfig mConfig;
protected final String mEncoding;
// // // Operating mode: base class needs to know whether
// // // namespaces are support (for entity/PI target validation)
protected final boolean mNsAware;
protected final boolean mCheckStructure;
protected final boolean mCheckContent;
protected final boolean mCheckNames;
protected final boolean mFixContent;
/**
* Whether to escape CR (\r) character.
*/
final boolean mEscapeCR;
/**
* Flag that defines whether close() on this writer should call
* close on the underlying output object (stream, writer)
*/
protected final boolean mAutoCloseOutput;
/**
* Optional escaping writer used for escaping characters like '<'
* '&' and '>' in textual content.
* Constructed if calling code has
* installed a special escaping writer factory for text content.
* Null if the default escaper is to be used.
*/
protected Writer mTextWriter;
/**
* Optional escaping writer used for escaping characters like '"'
* '&' and '<' in attribute values.
* Constructed if calling code has
* installed a special escaping writer factory for text content.
* Null if the default escaper is to be used.
*/
protected Writer mAttrValueWriter;
/**
* Indicates whether output is to be compliant; if false, is to be
* xml 1.0 compliant, if true, xml 1.1 compliant.
*/
protected boolean mXml11 = false;
/**
* Lazy-constructed wrapper object, which will route all calls to
* Writer API, to matching writeRaw
methods of this
* XmlWriter instance.
*/
protected XmlWriterWrapper mRawWrapper = null;
/**
* Lazy-constructed wrapper object, which will route all calls to
* Writer API, to matching writeCharacters
methods of this
* XmlWriter instance.
*/
protected XmlWriterWrapper mTextWrapper = null;
/*
///////////////////////////////////////////////////////
// Output location info
///////////////////////////////////////////////////////
*/
/**
* Number of characters output prior to currently buffered output
*/
protected int mLocPastChars = 0;
protected int mLocRowNr = 1;
/**
* Offset of the first character on this line. May be negative, if
* the offset was in a buffer that has been flushed out.
*/
protected int mLocRowStartOffset = 0;
/*
///////////////////////////////////////////////////////
// Life-cycle
///////////////////////////////////////////////////////
*/
protected XmlWriter(WriterConfig cfg, String encoding, boolean autoclose)
throws IOException
{
mConfig = cfg;
mEncoding = encoding;
mAutoCloseOutput = autoclose;
int flags = cfg.getConfigFlags();
mNsAware = (flags & OutputConfigFlags.CFG_ENABLE_NS) != 0;
mCheckStructure = (flags & OutputConfigFlags.CFG_VALIDATE_STRUCTURE) != 0;
mCheckContent = (flags & OutputConfigFlags.CFG_VALIDATE_CONTENT) != 0;
mCheckNames = (flags & OutputConfigFlags.CFG_VALIDATE_NAMES) != 0;
mFixContent = (flags & OutputConfigFlags.CFG_FIX_CONTENT) != 0;
mEscapeCR = (flags & OutputConfigFlags.CFG_ESCAPE_CR) != 0;
// Has caller requested any custom text or attr value escaping?
EscapingWriterFactory f = mConfig.getTextEscaperFactory();
if (f == null) {
mTextWriter = null;
} else {
String enc = (mEncoding == null || mEncoding.length() == 0) ?
WstxOutputProperties.DEFAULT_OUTPUT_ENCODING : mEncoding;
mTextWriter = f.createEscapingWriterFor(wrapAsRawWriter(), enc);
}
f = mConfig.getAttrValueEscaperFactory();
if (f == null) {
mAttrValueWriter = null;
} else {
String enc = (mEncoding == null || mEncoding.length() == 0) ?
WstxOutputProperties.DEFAULT_OUTPUT_ENCODING : mEncoding;
mAttrValueWriter = f.createEscapingWriterFor(wrapAsRawWriter(), enc);
}
}
/*
////////////////////////////////////////////////////
// Extra configuration
////////////////////////////////////////////////////
*/
public void enableXml11() {
mXml11 = true;
}
/*
////////////////////////////////////////////////////
// Access to underlying physical output destinations
////////////////////////////////////////////////////
*/
/**
* @return Underlying OutputStream used for physical output,
* if the writer was constructed using one
*/
protected abstract OutputStream getOutputStream();
/**
* @return Underlying Writer used for physical output,
* if the writer was constructed with one, or one was
* created to be used with an OutputStream.
*/
protected abstract Writer getWriter();
/*
////////////////////////////////////////////////////
// Basic methods for communicating with underlying
// stream or writer
////////////////////////////////////////////////////
*/
/**
* Method called to flush the buffer(s), and close the output
* sink (stream or writer).
*/
public abstract void close() throws IOException;
public abstract void flush()
throws IOException;
public abstract void writeRaw(String str, int offset, int len)
throws IOException;
public void writeRaw(String str)
throws IOException
{
writeRaw(str, 0, str.length());
}
public abstract void writeRaw(char[] cbuf, int offset, int len)
throws IOException;
/*
////////////////////////////////////////////////////
// Raw, non-verifying write methods; used when
// directly copying trusted content
////////////////////////////////////////////////////
*/
public abstract void writeCDataStart()
throws IOException;
public abstract void writeCDataEnd()
throws IOException;
public abstract void writeCommentStart()
throws IOException;
public abstract void writeCommentEnd()
throws IOException;
public abstract void writePIStart(String target, boolean addSpace)
throws IOException;
public abstract void writePIEnd()
throws IOException;
/*
////////////////////////////////////////////////////
// Write methods, non-elem/attr
////////////////////////////////////////////////////
*/
/**
* @param data Contents of the CDATA section to write out
* @return offset of the (first) illegal content segment ("]]>") in
* passed content, if not in repairing mode; or -1 if none
*/
public abstract int writeCData(String data)
throws IOException, XMLStreamException;
public abstract int writeCData(char[] cbuf, int offset, int len)
throws IOException, XMLStreamException;
public abstract void writeCharacters(String data)
throws IOException;
public abstract void writeCharacters(char[] cbuf, int offset, int len)
throws IOException;
/**
* Method that will try to output the content as specified. If
* the content passed in has embedded "--" in it, it will either
* add an intervening space between consequtive hyphens (if content
* fixing is enabled), or return the offset of the first hyphen in
* multi-hyphen sequence.
*/
public abstract int writeComment(String data)
throws IOException, XMLStreamException;
/**
* Older "legacy" output method for outputting DOCTYPE declaration.
* Assumes that the passed-in String contains a complete DOCTYPE
* declaration properly quoted.
*/
public abstract void writeDTD(String data)
throws IOException, XMLStreamException;
public abstract void writeDTD(String rootName,
String systemId, String publicId,
String internalSubset)
throws IOException, XMLStreamException;
public abstract void writeEntityReference(String name)
throws IOException, XMLStreamException;
public abstract int writePI(String target, String data)
throws IOException, XMLStreamException;
public abstract void writeXmlDeclaration(String version, String enc, String standalone)
throws IOException;
/*
////////////////////////////////////////////////////
// Write methods, elements
////////////////////////////////////////////////////
*/
/**
*
* Note: can throw XMLStreamException, if name checking is enabled,
* and name is invalid (name check has to be in this writer, not
* caller, since it depends not only on xml limitations, but also
* on encoding limitations)
*/
public abstract void writeStartTagStart(String localName)
throws IOException, XMLStreamException;
/**
*
* Note: can throw XMLStreamException, if name checking is enabled,
* and name is invalid (name check has to be in this writer, not
* caller, since it depends not only on xml limitations, but also
* on encoding limitations)
*/
public abstract void writeStartTagStart(String prefix, String localName)
throws IOException, XMLStreamException;
public abstract void writeStartTagEnd()
throws IOException;
public abstract void writeStartTagEmptyEnd()
throws IOException;
public abstract void writeEndTag(String localName)
throws IOException;
public abstract void writeEndTag(String prefix, String localName)
throws IOException;
/*
////////////////////////////////////////////////////
// Write methods, attributes/ns
////////////////////////////////////////////////////
*/
/**
*
* Note: can throw XMLStreamException, if name checking is enabled,
* and name is invalid (name check has to be in this writer, not
* caller, since it depends not only on xml limitations, but also
* on encoding limitations)
*/
public abstract void writeAttribute(String localName, String value)
throws IOException, XMLStreamException;
public abstract void writeAttribute(String localName, char[] value, int offset, int len)
throws IOException, XMLStreamException;
/**
*
* Note: can throw XMLStreamException, if name checking is enabled,
* and name is invalid (name check has to be in this writer, not
* caller, since it depends not only on xml limitations, but also
* on encoding limitations)
*/
public abstract void writeAttribute(String prefix, String localName, String value)
throws IOException, XMLStreamException;
public abstract void writeAttribute(String prefix, String localName, char[] value, int offset, int len)
throws IOException, XMLStreamException;
/*
////////////////////////////////////////////////////
// Location information
////////////////////////////////////////////////////
*/
protected abstract int getOutputPtr();
public int getRow() {
return mLocRowNr;
}
public int getColumn() {
return (getOutputPtr() - mLocRowStartOffset) + 1;
}
public int getAbsOffset() {
return mLocPastChars +getOutputPtr();
}
/*
////////////////////////////////////////////////////
// Wrapper methods, semi-public
////////////////////////////////////////////////////
*/
/**
* Method that can be called to get a wrapper instance that
* can be used to essentially call the writeRaw
* method.
*/
public final Writer wrapAsRawWriter()
{
if (mRawWrapper == null) {
mRawWrapper = XmlWriterWrapper.wrapWriteRaw(this);
}
return mRawWrapper;
}
public final Writer wrapAsTextWriter()
{
if (mTextWrapper == null) {
mTextWrapper = XmlWriterWrapper.wrapWriteCharacters(this);
}
return mTextWrapper;
}
/*
////////////////////////////////////////////////////
// Helper methods for sub-classes
////////////////////////////////////////////////////
*/
/**
* Method called to verify that the name is a legal XML name.
*/
public final void verifyNameValidity(String name, boolean checkNs)
throws XMLStreamException
{
/* No empty names... caller must have dealt with optional arguments
* prior to calling this method
*/
if (name == null || name.length() == 0) {
reportNwfName(ErrorConsts.WERR_NAME_EMPTY);
}
int illegalIx = WstxInputData.findIllegalNameChar(name, checkNs, mXml11);
if (illegalIx >= 0) {
if (illegalIx == 0) {
reportNwfName(ErrorConsts.WERR_NAME_ILLEGAL_FIRST_CHAR,
WstxInputData.getCharDesc(name.charAt(0)));
}
reportNwfName(ErrorConsts.WERR_NAME_ILLEGAL_CHAR,
WstxInputData.getCharDesc(name.charAt(illegalIx)));
}
}
/**
* This is the method called when an output method call violates
* name well-formedness checks
* and {@link WstxOutputProperties#P_OUTPUT_VALIDATE_NAMES} is
* is enabled.
*/
protected void reportNwfName(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
protected void reportNwfName(String msg, Object arg)
throws XMLStreamException
{
throwOutputError(msg, arg);
}
protected void reportNwfContent(String msg)
throws XMLStreamException
{
throwOutputError(msg);
}
protected void throwOutputError(String msg)
throws XMLStreamException
{
// First, let's flush any output we may have, to make debugging easier
try {
flush();
} catch (IOException ioe) {
throw new WstxIOException(ioe);
}
throw new XMLStreamException(msg);
}
protected void throwOutputError(String format, Object arg)
throws XMLStreamException
{
String msg = MessageFormat.format(format, new Object[] { arg });
throwOutputError(msg);
}
protected void throwInvalidChar(int c)
throws IOException
{
// First, let's flush any output we may have, to make debugging easier
flush();
/* 17-May-2006, TSa: Would really be useful if we could throw
* XMLStreamExceptions; esp. to indicate actual output location.
* However, this causes problem with methods that call us and
* can only throw IOExceptions (when invoked via Writer proxy).
* Need to figure out how to resolve this.
*/
if (c == 0) {
throw new IOException("Invalid null character in text to output");
}
if (c < ' ' || (c >= 0x7F && c <= 0x9F)) {
String msg = "Invalid white space character (0x"+Integer.toHexString(c)+") in text to output";
if (mXml11) {
msg += " (can only be output using character entity)";
}
throw new IOException(msg);
}
if (c > 0x10FFFF) {
throw new IOException("Illegal unicode character point (0x"+Integer.toHexString(c)+") to output; max is 0x10FFFF as per RFC 3629");
}
/* Surrogate pair in non-quotable (not text or attribute value)
* content, and non-unicode encoding (ISO-8859-x, Ascii)?
*/
if (c >= SURR1_FIRST && c <= SURR2_LAST) {
throw new IOException("Illegal surrogate pair -- can only be output via character entities, which are not allowed in this content");
}
throw new IOException("Invalid XML character (0x"+Integer.toHexString(c)+") in text to output");
}
}