com.fasterxml.aalto.out.RepairingStreamWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aalto-xml Show documentation
Show all versions of aalto-xml Show documentation
Ultra-high performance non-blocking XML processor (Stax/Stax2, SAX/SAX2)
/* Aalto XML processor
*
* Copyright (c) 2006- Tatu Saloranta, [email protected]
*
* Licensed under the License specified in the file LICENSE which is
* 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.fasterxml.aalto.out;
import java.util.*;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import org.codehaus.stax2.ri.typed.AsciiValueEncoder;
import com.fasterxml.aalto.impl.ErrorConsts;
/**
* Concrete implementation of {@link StreamWriterBase}, which
* implements the "namespace repairing" mode of operation.
* This means that the writer ensures correctness and validity
* of namespace bindings, as based on namespace URIs caller
* passes, by adding necessary namespace declarations and using
* prefixes as required to obtain expected results.
*/
public final class RepairingStreamWriter
extends StreamWriterBase
{
/*
/////////////////////////////////////////////////////
// Prefix generation settings
/////////////////////////////////////////////////////
*/
// // // Additional specific config flags base class doesn't have
final String _cfgAutomaticNsPrefix;
/*
////////////////////////////////////////////////////
// Additional state
////////////////////////////////////////////////////
*/
/**
* Sequence number used for generating dynamic namespace prefixes.
* Array used as a wrapper to allow for easy sharing of the sequence
* number.
*/
int[] _autoNsSeq = null;
String _suggestedDefNs = null;
/**
* Map that contains URI-to-prefix entries that point out suggested
* prefixes for URIs. These are populated by calls to
* {@link #setPrefix}, and they are only used as hints for binding;
* if there are conflicts, repairing writer can just use some other
* prefix.
*/
HashMap _suggestedPrefixes = null;
/*
/////////////////////////////////////////////////////
// Construction, init
/////////////////////////////////////////////////////
*/
public RepairingStreamWriter(WriterConfig cfg, XmlWriter writer,
WNameTable symbols)
{
super(cfg, writer, symbols);
_cfgAutomaticNsPrefix = cfg.getAutomaticNsPrefix();
}
/*
/////////////////////////////////////////////////////
// Implementations of abstract methods from base class,
// Stax 1.0 methods
/////////////////////////////////////////////////////
*/
/**
* With repairing writer, this is only taken as a suggestion as to how
* the caller would prefer prefixes to be mapped.
*/
@Override
public void setDefaultNamespace(String uri)
throws XMLStreamException
{
_suggestedDefNs = (uri == null || uri.length() == 0) ? null : uri;
}
@Override
public void _setPrefix(String prefix, String uri)
{
// note: sub-class has verified that prefix != null, uri != null
/* Ok; let's assume that passing an empty String as
* the URI means that we don't want passed prefix to be preferred
* for any URI (since there's no way to map a prefix to the default
* namespace)
*/
if (uri == null || uri.length() == 0) {
if (_suggestedPrefixes != null) {
for (Iterator> it =
_suggestedPrefixes.entrySet().iterator(); it.hasNext(); ) {
Map.Entry en = it.next();
if (en.getValue().equals(prefix)) {
it.remove();
}
}
}
} else {
if (_suggestedPrefixes == null) {
_suggestedPrefixes = new HashMap(16);
}
_suggestedPrefixes.put(uri, prefix);
}
}
//public void writeAttribute(String localName, String value)
@Override
public void writeAttribute(String nsURI, String localName, String value)
throws XMLStreamException
{
if (!_stateStartElementOpen) {
throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM);
}
// no URI? No prefix. Otherwise, need to find or bind:
WName name = (nsURI == null || nsURI.length() == 0)
? _symbols.findSymbol(localName)
: _generateAttrName(null, localName, nsURI);
_writeAttribute(name, value);
}
@Override
public void writeAttribute(String prefix, String nsURI,
String localName, String value)
throws XMLStreamException
{
if (!_stateStartElementOpen) {
throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM);
}
// no URI? No prefix. Otherwise, need to find or bind:
WName name = (nsURI == null || nsURI.length() == 0)
? _symbols.findSymbol(localName)
: _generateAttrName(prefix, localName, nsURI);
_writeAttribute(name, value);
}
@Override
public void writeDefaultNamespace(String nsURI)
throws XMLStreamException
{
if (!_stateStartElementOpen) {
throwOutputError(ErrorConsts.WERR_NS_NO_ELEM);
}
/* ... We have one complication though: if the current element
* uses default namespace, can not change it (attributes don't
* matter -- they never use the default namespace, but either don't
* belong to a namespace, or belong to one using explicit prefix)
*/
if (!_currElem.hasPrefix()) {
_currElem.setDefaultNsURI(nsURI);
_writeDefaultNamespace(nsURI);
}
_writeDefaultNamespace(nsURI);
}
//public void writeDTD(String dtd)
//public void writeEmptyElement(String localName)
@Override
public void writeEmptyElement(String nsURI, String localName)
throws XMLStreamException
{
_writeStartOrEmpty(null, localName, nsURI, true);
}
@Override
public void writeEmptyElement(String prefix, String localName, String nsURI)
throws XMLStreamException
{
_writeStartOrEmpty(prefix, localName, nsURI, true);
}
//public void writeEndElement()
@Override
public void writeNamespace(String prefix, String nsURI)
throws XMLStreamException
{
if (prefix == null || prefix.length() == 0) {
writeDefaultNamespace(nsURI);
return;
}
if (!_stateStartElementOpen) {
throwOutputError(ErrorConsts.WERR_NS_NO_ELEM);
}
/* Let's only add the declaration if the prefix
* is as of yet unbound. If we have to re-bind things in future,
* so be it -- for now, this should suffice (and if we have to
* add re-binding, must verify that no attribute, nor element
* itself, is using overridden prefix)
*/
if (_currElem.isPrefixUnbound(prefix, _rootNsContext)) {
_currElem.addPrefix(prefix, nsURI);
_writeNamespace(prefix, nsURI);
}
}
//public void writeStartElement(String localName)
@Override
public void writeStartElement(String nsURI, String localName)
throws XMLStreamException
{
_writeStartOrEmpty(null, localName, nsURI, false);
}
@Override
public void writeStartElement(String prefix, String localName, String nsURI)
throws XMLStreamException
{
_writeStartOrEmpty(prefix, localName, nsURI, false);
}
/*
/////////////////////////////////////////////////////
// Implementations of abstract methods from base class,
// Stax2 Typed API Access (v3.0)
/////////////////////////////////////////////////////
*/
@Override
public void writeTypedAttribute(String prefix, String nsURI, String localName,
AsciiValueEncoder enc)
throws XMLStreamException
{
if (!_stateStartElementOpen) {
throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM);
}
WName name = (prefix == null || prefix.length() == 0)
? _symbols.findSymbol(localName)
: _symbols.findSymbol(prefix, localName);
_writeAttribute(name, enc);
}
@Override
protected String _serializeQName(QName name) throws XMLStreamException
{
/* Gets bit more complicated: we need to ensure that given URI
* is properly bound...
*/
String uri = name.getNamespaceURI();
String prefix = name.getPrefix();
String local = name.getLocalPart();
// Perhaps prefix is fine as is?
if (_currElem.isPrefixBoundTo(prefix, uri, _rootNsContext)) {
if (prefix == null || prefix.length() == 0) {
return local;
}
return prefix + ":" + local;
}
/* Nope. Need to find or generate a new one... let's treat like an
* attribute (i.e. get an explicit prefix, not use default ns).
* Not optimal, need not be; not a hot spot method.
*/
WName newName = _generateAttrName(prefix, local, uri);
return newName.getPrefixedName();
}
/*
/////////////////////////////////////////////////////
// Internal methods
/////////////////////////////////////////////////////
*/
/**
* @param uri Non-empty namespace URI that will be used for the
* attribute
*/
protected WName _generateAttrName(String suggPrefix, String localName, String uri)
throws XMLStreamException
{
if (suggPrefix != null && suggPrefix.length() > 0) { // prefer this prefix
switch (_currElem.checkPrefixValidity(suggPrefix, uri, _rootNsContext)) {
case UNBOUND: // ok, need to bind
_writeNamespace(suggPrefix, uri);
_currElem.addPrefix(suggPrefix, uri);
// fall through:
case OK: // fine as is
return _symbols.findSymbol(suggPrefix, localName);
case MISBOUND: // can't bind, need another prefix
// fall through
}
}
/* Had no suggested prefix, or one given didn't work. Either way,
* need to find a match to URI, or generate new one.
*/
String prefix = _currElem.getExplicitPrefix(uri, _rootNsContext);
if (prefix == null) { // none found, generate
if (_autoNsSeq == null) {
_autoNsSeq = new int[1];
_autoNsSeq[0] = 1;
}
prefix = _currElem.generatePrefix(_rootNsContext, _cfgAutomaticNsPrefix, _autoNsSeq);
_writeNamespace(prefix, uri);
_currElem.addPrefix(prefix, uri);
}
return _symbols.findSymbol(prefix, localName);
}
public void _writeStartOrEmpty(String prefix, String localName, String nsURI,
boolean isEmpty)
throws XMLStreamException
{
// In repairing mode, better ensure validity.
// First: do we want the "no namespace"? Separate, distinct handling
if (nsURI == null || nsURI.length() == 0) {
// either way, can not have non-empty prefix
_verifyStartElement(null, localName);
// must output the start-tag-start to add xmlns decl (if needed)
WName name = _symbols.findSymbol(localName);
_writeStartTag(name, isEmpty, null);
if (!_currElem.hasEmptyDefaultNs()) { // is bound, need to rebind...
_writeDefaultNamespace(nsURI);
// also: changes default ns of curr elem etc:
_currElem.setDefaultNsURI("");
}
if (_validator != null) {
_validator.validateElementStart(localName, "", "");
}
return;
}
// Otherwise a non-empty namespace. Do we have a suggested prefix?
if (prefix == null) { // nope, anything goes
prefix = _currElem.getPrefix(nsURI);
// And we do have something bound already!
if (prefix != null) {
_writeStartAndVerify(prefix, localName, nsURI, isEmpty);
} else {
// nothing suitable bound, let's generate
prefix = _generateElemPrefix(nsURI);
if (_writeStartAndVerify(prefix, localName, nsURI, isEmpty)) { // default ns
_writeDefaultNamespace(nsURI);
_currElem.setDefaultNsURI(nsURI);
} else { // non-default, explicit prefix
_writeNamespace(prefix, nsURI);
_currElem.addPrefix(prefix, nsURI);
}
}
} else {
boolean isDef = _writeStartAndVerify(prefix, localName, nsURI, isEmpty);
// Is the prefix already properly bound?
if (!_currElem.isPrefixBoundTo(prefix, nsURI, _rootNsContext)) { // yup
// Nope, need to rebind
if (isDef) {
_writeDefaultNamespace(nsURI);
_currElem.setDefaultNsURI(nsURI);
} else { // explicit prefix
_writeNamespace(prefix, nsURI);
_currElem.addPrefix(prefix, nsURI);
}
}
}
// And after all that, validation?
if (_validator != null) {
_validator.validateElementStart(localName, "",
((prefix == null) ? "" : prefix));
}
return;
}
/**
* @return True, if prefix indicates default namespace (is null or empty);
* false otherwise
*/
private final boolean _writeStartAndVerify(String prefix, String localName, String nsURI,
boolean isEmpty)
throws XMLStreamException
{
if (prefix == null || prefix.length() == 0) { // default ns
_verifyStartElement(null, localName);
_writeStartTag(_symbols.findSymbol(localName), isEmpty, nsURI);
return true;
}
// non-default, i.e. real prefix
_verifyStartElement(prefix, localName);
_writeStartTag(_symbols.findSymbol(prefix, localName), isEmpty, nsURI);
return false;
}
/**
* Method called if given URI is not yet bound, and no suggested prefix
* is given (or one given can't be used). If so, methods is
* to create a not-yet-bound-prefix for the namespace.
*/
protected final String _generateElemPrefix(String uri)
throws XMLStreamException
{
// First: is it the 'recommended' default ns?
if (_suggestedDefNs != null && _suggestedDefNs.equals(uri)) {
return null;
}
// If not, maybe one of other 'recommended' bindings has it?
if (_suggestedPrefixes != null) {
String prefix = _suggestedPrefixes.get(uri);
if (prefix != null) {
return prefix;
}
}
// Otherwise, generate a new unbound prefix
/* We have 2 choices here, essentially;
* could make elements always try to override the def
* ns... or can just generate new one. Let's do latter
* for now.
*/
if (_autoNsSeq == null) {
_autoNsSeq = new int[1];
_autoNsSeq[0] = 1;
}
return _currElem.generatePrefix(_rootNsContext, _cfgAutomaticNsPrefix, _autoNsSeq);
}
}