/* Stax2 API extension for Streaming Api for Xml processing (StAX).
*
* Copyright (c) 2006- Tatu Saloranta, tatu.saloranta@iki.fi
*
* 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 org.codehaus.stax2.ri;
import java.math.
BigDecimal;
import java.math.
BigInteger;
import javax.xml.namespace.
NamespaceContext;
import javax.xml.namespace.
QName;
import javax.xml.stream.*;
import org.codehaus.stax2.*;
import org.codehaus.stax2.ri.typed.
SimpleValueEncoder;
import org.codehaus.stax2.typed.
Base64Variant;
import org.codehaus.stax2.typed.
Base64Variants;
// Not from Stax 1.0, but Stax2 does provide it:
import org.codehaus.stax2.util.
StreamWriterDelegate;
import org.codehaus.stax2.validation.
ValidationProblemHandler;
import org.codehaus.stax2.validation.
XMLValidationSchema;
import org.codehaus.stax2.validation.
XMLValidator;
/**
* This adapter implements parts of {@link XMLStreamWriter2}, the
* extended stream writer defined by Stax2 extension, by wrapping
* a vanilla Stax 1.0 {@link XMLStreamReader} implementation.
*<p>
* Note: the implementation is incomplete as-is, since not all
* features needed are accessible via basic Stax 1.0 interface.
* As such, two main use cases for this wrapper are:
*<ul>
* <li>Serve as convenient base class for a complete implementation,
* which can use native accessors provided by the wrapped Stax
* implementation
* </li>
* <li>To be used for tasks that make limited use of Stax2 API, such
* that missing parts are not needed
* </li>
* </ul>
*/
public class
Stax2WriterAdapter
extends
StreamWriterDelegate
implements
XMLStreamWriter2 /* From Stax2 */
,
XMLStreamConstants
{
/**
* Encoding we have determined to be used, according to method
* calls (write start document etc.)
*/
protected
String mEncoding;
protected
SimpleValueEncoder mValueEncoder;
protected final boolean
mNsRepairing;
/*
////////////////////////////////////////////////////
// Life-cycle methods
////////////////////////////////////////////////////
*/
protected
Stax2WriterAdapter(
XMLStreamWriter sw)
{
super(
sw);
mDelegate =
sw;
Object value =
sw.
getProperty(
XMLOutputFactory.
IS_REPAIRING_NAMESPACES);
mNsRepairing = (
value instanceof
Boolean) && ((
Boolean)
value).
booleanValue();
}
/**
* Method that should be used to add dynamic support for
* {@link XMLStreamWriter2}. Method will check whether the
* stream reader passed happens to be a {@link XMLStreamWriter2};
* and if it is, return it properly cast. If not, it will create
* necessary wrapper to support features needed by StaxMate,
* using vanilla Stax 1.0 interface.
*/
public static
XMLStreamWriter2 wrapIfNecessary(
XMLStreamWriter sw)
{
if (
sw instanceof
XMLStreamWriter2) {
return (
XMLStreamWriter2)
sw;
}
return new
Stax2WriterAdapter(
sw);
}
/*
/////////////////////////////////////////////////
// TypedXMLStreamWriter2 implementation
// (Typed Access API, Stax v3.0)
/////////////////////////////////////////////////
*/
// // // Typed element content write methods
public void
writeBoolean(boolean
b) throws
XMLStreamException
{
mDelegate.
writeCharacters(
b ? "true" : "false");
}
public void
writeInt(int
value) throws
XMLStreamException
{
mDelegate.
writeCharacters(
String.
valueOf(
value));
}
public void
writeLong(long
value) throws
XMLStreamException
{
mDelegate.
writeCharacters(
String.
valueOf(
value));
}
public void
writeFloat(float
value) throws
XMLStreamException
{
mDelegate.
writeCharacters(
String.
valueOf(
value));
}
public void
writeDouble(double
value) throws
XMLStreamException
{
mDelegate.
writeCharacters(
String.
valueOf(
value));
}
public void
writeInteger(
BigInteger value) throws
XMLStreamException
{
mDelegate.
writeCharacters(
value.
toString());
}
public void
writeDecimal(
BigDecimal value) throws
XMLStreamException
{
mDelegate.
writeCharacters(
value.
toString());
}
public void
writeQName(
QName name) throws
XMLStreamException
{
mDelegate.
writeCharacters(
serializeQNameValue(
name));
}
public void
writeIntArray(int[]
value, int
from, int
length)
throws
XMLStreamException
{
mDelegate.
writeCharacters(
getValueEncoder().
encodeAsString(
value,
from,
length));
}
public void
writeLongArray(long[]
value, int
from, int
length)
throws
XMLStreamException
{
mDelegate.
writeCharacters(
getValueEncoder().
encodeAsString(
value,
from,
length));
}
public void
writeFloatArray(float[]
value, int
from, int
length)
throws
XMLStreamException
{
mDelegate.
writeCharacters(
getValueEncoder().
encodeAsString(
value,
from,
length));
}
public void
writeDoubleArray(double[]
value, int
from, int
length)
throws
XMLStreamException
{
mDelegate.
writeCharacters(
getValueEncoder().
encodeAsString(
value,
from,
length));
}
public void
writeBinary(
Base64Variant v, byte[]
value, int
from, int
length)
throws
XMLStreamException
{
mDelegate.
writeCharacters(
getValueEncoder().
encodeAsString(
v,
value,
from,
length));
}
public void
writeBinary(byte[]
value, int
from, int
length)
throws
XMLStreamException
{
writeBinary(
Base64Variants.
getDefaultVariant(),
value,
from,
length);
}
// // // Typed attribute value write methods
public void
writeBooleanAttribute(
String prefix,
String nsURI,
String localName, boolean
b) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
b ? "true" : "false");
}
public void
writeIntAttribute(
String prefix,
String nsURI,
String localName, int
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
String.
valueOf(
value));
}
public void
writeLongAttribute(
String prefix,
String nsURI,
String localName, long
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
String.
valueOf(
value));
}
public void
writeFloatAttribute(
String prefix,
String nsURI,
String localName, float
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
String.
valueOf(
value));
}
public void
writeDoubleAttribute(
String prefix,
String nsURI,
String localName, double
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
String.
valueOf(
value));
}
public void
writeIntegerAttribute(
String prefix,
String nsURI,
String localName,
BigInteger value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
value.
toString());
}
public void
writeDecimalAttribute(
String prefix,
String nsURI,
String localName,
BigDecimal value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
value.
toString());
}
public void
writeQNameAttribute(
String prefix,
String nsURI,
String localName,
QName name) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
serializeQNameValue(
name));
}
public void
writeIntArrayAttribute(
String prefix,
String nsURI,
String localName, int[]
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
getValueEncoder().
encodeAsString(
value, 0,
value.length));
}
public void
writeLongArrayAttribute(
String prefix,
String nsURI,
String localName, long[]
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
getValueEncoder().
encodeAsString(
value, 0,
value.length));
}
public void
writeFloatArrayAttribute(
String prefix,
String nsURI,
String localName, float[]
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
getValueEncoder().
encodeAsString(
value, 0,
value.length));
}
public void
writeDoubleArrayAttribute(
String prefix,
String nsURI,
String localName, double[]
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
getValueEncoder().
encodeAsString(
value, 0,
value.length));
}
public void
writeBinaryAttribute(
String prefix,
String nsURI,
String localName, byte[]
value) throws
XMLStreamException
{
writeBinaryAttribute(
Base64Variants.
getDefaultVariant(),
prefix,
nsURI,
localName,
value);
}
public void
writeBinaryAttribute(
Base64Variant v,
String prefix,
String nsURI,
String localName, byte[]
value) throws
XMLStreamException
{
mDelegate.
writeAttribute(
prefix,
nsURI,
localName,
getValueEncoder().
encodeAsString(
v,
value, 0,
value.length));
}
/*
////////////////////////////////////////////////////
// XMLStreamWriter2 (StAX2) implementation
////////////////////////////////////////////////////
*/
public boolean
isPropertySupported(
String name)
{
/* No real clean way to check this, so let's just fake by
* claiming nothing is supported
*/
return false;
}
public boolean
setProperty(
String name,
Object value)
{
throw new
IllegalArgumentException("No settable property '"+
name+"'");
}
public
XMLStreamLocation2 getLocation()
{
// No easy way to keep track of it, without impl support
return null;
}
public
String getEncoding()
{
// We may have been able to infer it... if so:
return
mEncoding;
}
public void
writeCData(char[]
text, int
start, int
len)
throws
XMLStreamException
{
writeCData(new
String(
text,
start,
len));
}
public void
writeDTD(
String rootName,
String systemId,
String publicId,
String internalSubset)
throws
XMLStreamException
{
/* This may or may not work... depending on how well underlying
* implementation follows stax 1.0 spec (it should work)
*/
StringBuffer sb = new
StringBuffer();
sb.
append("<!DOCTYPE");
sb.
append(
rootName);
if (
systemId != null) {
if (
publicId != null) {
sb.
append(" PUBLIC \"");
sb.
append(
publicId);
sb.
append("\" \"");
} else {
sb.
append(" SYSTEM \"");
}
sb.
append(
systemId);
sb.
append('"');
}
// Hmmh. Should we output empty internal subset?
if (
internalSubset != null &&
internalSubset.
length() > 0) {
sb.
append(" [");
sb.
append(
internalSubset);
sb.
append(']');
}
sb.
append('>');
writeDTD(
sb.
toString());
}
public void
writeFullEndElement()
throws
XMLStreamException
{
/* It may be possible to fake it, by pretending to write
* character output, which in turn should prevent writing of
* an empty element...
*/
mDelegate.
writeCharacters("");
mDelegate.
writeEndElement();
}
public void
writeSpace(
String text)
throws
XMLStreamException
{
/* Hmmh. Two choices: either try to write as regular characters,
* or output as is via raw calls. Latter would be safer, if we
* had access to it; former may escape incorrectly.
* While this may not be optimal, let's try former
*/
writeRaw(
text);
}
public void
writeSpace(char[]
text, int
offset, int
length)
throws
XMLStreamException
{
// See comments above...
writeRaw(
text,
offset,
length);
}
public void
writeStartDocument(
String version,
String encoding,
boolean
standAlone)
throws
XMLStreamException
{
// No good way to do it, so let's do what we can...
writeStartDocument(
encoding,
version);
}
/*
///////////////////////////////
// Stax2, Pass-through methods
///////////////////////////////
*/
public void
writeRaw(
String text)
throws
XMLStreamException
{
writeRaw(
text, 0,
text.
length());
}
public void
writeRaw(
String text, int
offset, int
len)
throws
XMLStreamException
{
// There is no clean way to implement this via Stax 1.0, alas...
throw new
UnsupportedOperationException("Not implemented");
}
public void
writeRaw(char[]
text, int
offset, int
length)
throws
XMLStreamException
{
writeRaw(new
String(
text,
offset,
length));
}
public void
copyEventFromReader(
XMLStreamReader2 sr, boolean
preserveEventData)
throws
XMLStreamException
{
switch (
sr.
getEventType()) {
case
START_DOCUMENT:
{
String version =
sr.
getVersion();
/* No real declaration? If so, we don't want to output
* anything, to replicate as closely as possible the
* source document
*/
if (
version == null ||
version.
length() == 0) {
; // no output if no real input
} else {
if (
sr.
standaloneSet()) {
writeStartDocument(
sr.
getVersion(),
sr.
getCharacterEncodingScheme(),
sr.
isStandalone());
} else {
writeStartDocument(
sr.
getCharacterEncodingScheme(),
sr.
getVersion());
}
}
}
return;
case
END_DOCUMENT:
writeEndDocument();
return;
// Element start/end events:
case
START_ELEMENT:
/* Start element is bit trickier to output since there
* may be differences between repairing/non-repairing
* writers. But let's try a generic handling here.
*/
copyStartElement(
sr);
return;
case
END_ELEMENT:
writeEndElement();
return;
case
SPACE:
writeSpace(
sr.
getTextCharacters(),
sr.
getTextStart(),
sr.
getTextLength());
return;
case
CDATA:
writeCData(
sr.
getTextCharacters(),
sr.
getTextStart(),
sr.
getTextLength());
return;
case
CHARACTERS:
writeCharacters(
sr.
getTextCharacters(),
sr.
getTextStart(),
sr.
getTextLength());
return;
case
COMMENT:
writeComment(
sr.
getText());
return;
case
PROCESSING_INSTRUCTION:
writeProcessingInstruction(
sr.
getPITarget(),
sr.
getPIData());
return;
case
DTD:
{
DTDInfo info =
sr.
getDTDInfo();
if (
info == null) {
/* Hmmmh. Can this happen for non-DTD-aware readers?
* And if so, what should we do?
*/
throw new
XMLStreamException("Current state DOCTYPE, but not DTDInfo Object returned -- reader doesn't support DTDs?");
}
writeDTD(
info.
getDTDRootName(),
info.
getDTDSystemId(),
info.
getDTDPublicId(),
info.
getDTDInternalSubset());
}
return;
case
ENTITY_REFERENCE:
writeEntityRef(
sr.
getLocalName());
return;
case
ATTRIBUTE:
case
NAMESPACE:
case
ENTITY_DECLARATION:
case
NOTATION_DECLARATION:
// Let's just fall back to throw the exception
}
throw new
XMLStreamException("Unrecognized event type ("
+
sr.
getEventType()+"); not sure how to copy");
}
/*
///////////////////////////////
// Stax2, output handling
///////////////////////////////
*/
public void
closeCompletely() throws
XMLStreamException
{
/* 06-Nov-2008, TSa: alas, there is no way to properly implement
* this. Should we throw an exception? For now, let's just call
* regular close; not quite the same, but better than nothing
*/
close();
}
/*
///////////////////////////////
// Stax2, validation
///////////////////////////////
*/
public
XMLValidator validateAgainst(
XMLValidationSchema schema)
throws
XMLStreamException
{
// !!! TODO: try to implement?
throw new
UnsupportedOperationException("Not yet implemented");
}
public
XMLValidator stopValidatingAgainst(
XMLValidationSchema schema)
throws
XMLStreamException
{
return null;
}
public
XMLValidator stopValidatingAgainst(
XMLValidator validator)
throws
XMLStreamException
{
return null;
}
public
ValidationProblemHandler setValidationProblemHandler(
ValidationProblemHandler h)
{
/* Not a real problem: although we can't do anything with it
* (without real validator integration)
*/
return null;
}
/*
///////////////////////////////
// Helper methods
///////////////////////////////
*/
protected void
copyStartElement(
XMLStreamReader sr)
throws
XMLStreamException
{
// Any namespace declarations/bindings?
int
nsCount =
sr.
getNamespaceCount();
if (
nsCount > 0) { // yup, got some...
/* First, need to (or at least, should?) add prefix bindings:
* (may not be 100% required, but probably a good thing to do,
* just so that app code has access to prefixes then)
*/
for (int
i = 0;
i <
nsCount; ++
i) {
String prefix =
sr.
getNamespacePrefix(
i);
String uri =
sr.
getNamespaceURI(
i);
if (
prefix == null ||
prefix.
length() == 0) { // default NS
setDefaultNamespace(
uri);
} else {
setPrefix(
prefix,
uri);
}
}
}
writeStartElement(
sr.
getPrefix(),
sr.
getLocalName(),
sr.
getNamespaceURI());
if (
nsCount > 0) {
// And then output actual namespace declarations:
for (int
i = 0;
i <
nsCount; ++
i) {
String prefix =
sr.
getNamespacePrefix(
i);
String uri =
sr.
getNamespaceURI(
i);
if (
prefix == null ||
prefix.
length() == 0) { // default NS
writeDefaultNamespace(
uri);
} else {
writeNamespace(
prefix,
uri);
}
}
}
/* And then let's just output attributes. But should we copy the
* implicit attributes (created via attribute defaulting?)
*/
int
attrCount =
sr.
getAttributeCount();
if (
attrCount > 0) {
for (int
i = 0;
i <
attrCount; ++
i) {
writeAttribute(
sr.
getAttributePrefix(
i),
sr.
getAttributeNamespace(
i),
sr.
getAttributeLocalName(
i),
sr.
getAttributeValue(
i));
}
}
}
/**
* Method called to serialize given qualified name into valid
* String serialization, taking into account existing namespace
* bindings.
*/
protected
String serializeQNameValue(
QName name)
throws
XMLStreamException
{
String prefix;
// Ok as is? In repairing mode need to ensure it's properly bound
if (
mNsRepairing) {
String uri =
name.
getNamespaceURI();
// First: let's see if a valid binding already exists:
NamespaceContext ctxt =
getNamespaceContext();
prefix = (
ctxt == null) ? null :
ctxt.
getPrefix(
uri);
if (
prefix == null) {
// nope: need to (try to) bind
String origPrefix =
name.
getPrefix();
if (
origPrefix == null ||
origPrefix.
length() == 0) {
prefix = "";
/* note: could cause a namespace conflict... but
* there is nothing we can do with just stax1 stream
* writer
*/
writeDefaultNamespace(
uri);
} else {
prefix =
origPrefix;
writeNamespace(
prefix,
uri);
}
}
} else { // in non-repairing, good as is
prefix =
name.
getPrefix();
}
String local =
name.
getLocalPart();
if (
prefix == null ||
prefix.
length() == 0) {
return
local;
}
// Not efficient... but should be ok
return
prefix + ":" +
local;
}
protected
SimpleValueEncoder getValueEncoder()
{
if (
mValueEncoder == null) {
mValueEncoder = new
SimpleValueEncoder();
}
return
mValueEncoder;
}
}