com.fasterxml.aalto.out.OutputElement Maven / Gradle / Ivy
Show all versions of aalto-xml Show documentation
/* 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.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import com.fasterxml.aalto.util.EmptyIterator;
/**
* Simple container for information regarding an open element within
* stream writer output.
*
* Note: these elements are designed to be reused within context of
* a single document output, ie. they are owned by the stream writer,
* and can be recycled by it, as necessary.
*/
final class OutputElement
{
final static byte BYTE_LT = (byte) '<';
final static byte BYTE_GT = (byte) '>';
final static byte BYTE_SLASH = (byte) '/';
public enum PrefixState {
UNBOUND, OK, MISBOUND
};
/**
* Reference to either the parent (enclosing element) of this
* element, when part of active output context; or link to next
* reusable unused element after this one (if not part of active
* context).
*/
OutputElement _parent;
/**
* Prefixed name used for serialization.
*/
WName _name;
/**
* Namespace of the element, whatever prefix part of {@link #_name}
* maps to. Non-final to allow reuse.
*/
String _uri;
/*
////////////////////////////////////////////
// Namespace binding/mapping information
////////////////////////////////////////////
*/
/**
* Namespace context end application may have supplied, and that
* (if given) should be used to augment explicitly defined bindings.
*/
//NamespaceContext _rootNsContext;
String _defaultNsURI = "";
NsBinder _nsBinder = null;
/*
////////////////////////////////////////////
// Life-cycle
////////////////////////////////////////////
*/
private OutputElement()
{
_parent = null;
_name = null;
_uri = null;
_nsBinder = null;
_defaultNsURI = "";
}
private OutputElement(OutputElement parent,
WName name, String uri,
NsBinder binder)
{
_parent = parent;
_name = name;
_uri = uri;
_nsBinder = binder;
_defaultNsURI = parent._defaultNsURI;
}
protected static OutputElement createRoot()
{
return new OutputElement();
}
/**
* Simplest factory method, which gets called when non-namespace
* element output method is called. It is, then, assumed to
* use the default namespce.
*/
protected OutputElement createChild(WName name)
{
return new OutputElement(this, name, _defaultNsURI, _nsBinder);
}
/**
* Full factory method, used for 'normal' namespace qualified output
* methods.
*/
protected OutputElement createChild(WName name, String uri)
{
return new OutputElement(this, name, uri, _nsBinder);
}
/*
////////////////////////////////////////////
// Instance reuse support
////////////////////////////////////////////
*/
/**
* @return New head of the recycle pool
*/
protected OutputElement reuseAsChild(OutputElement parent, WName name)
{
OutputElement poolHead = _parent;
relink(parent, name, _defaultNsURI);
return poolHead;
}
protected OutputElement reuseAsChild(OutputElement parent, WName name, String nsURI)
{
OutputElement poolHead = _parent;
relink(parent, name, nsURI);
return poolHead;
}
/**
* Method called to reuse a recycled instance, as is, with same
* name.
*/
public void relink(OutputElement parent)
{
_parent = parent;
_nsBinder = parent._nsBinder;
_defaultNsURI = parent._defaultNsURI;
}
/**
* Method called to reuse a pooled instance, but with different
* name
*/
private void relink(OutputElement parent, WName name, String uri)
{
_parent = parent;
_name = name;
_uri = uri;
_nsBinder = parent._nsBinder;
_defaultNsURI = parent._defaultNsURI;
}
/**
* Method called to temporarily link this instance to a pool, to
* allow reusing of instances with the same reader.
*/
protected void addToPool(OutputElement poolHead)
{
_parent = poolHead;
}
/*
////////////////////////////////////////////
// Public API, accessors
////////////////////////////////////////////
*/
public OutputElement getParent() {
return _parent;
}
public boolean isRoot() {
// (Virtual) Root element has no parent...
return (_parent == null);
}
public WName getName() { return _name; }
public String getLocalName() { return _name.getLocalName(); }
public String getNonNullPrefix() {
String p = _name.getPrefix();
return (p == null) ? "" : null;
}
public boolean hasPrefix() { return _name.hasPrefix(); }
/**
* @return String presentation of the fully-qualified name, in
* "prefix:localName" format (no URI). Useful for error and
* debugging messages.
*/
public String getNameDesc() {
return _name.toString();
}
public String getNamespaceURI() {
return _uri;
}
public String getNonNullNamespaceURI() {
return (_uri == null) ? "" : _uri;
}
public String getDefaultNsURI() {
return _defaultNsURI;
}
public boolean hasEmptyDefaultNs() {
return (_defaultNsURI == null) || (_defaultNsURI.length() == 0);
}
public QName getQName() {
return new QName(_uri, _name.getLocalName(), _name.getPrefix());
}
/*
////////////////////////////////////////////
// Public API, mutators
////////////////////////////////////////////
*/
public void setDefaultNsURI(String uri) {
_defaultNsURI = uri;
}
public String generatePrefix(NamespaceContext rootNsContext, String prefixBase, int[] seqArr)
{
// Didn't have a mapping yet? Need to create one...
if (_nsBinder == null) {
_nsBinder = NsBinder.createEmpty();
}
// no need to share though, won't be adding anything quite yet
return _nsBinder.generatePrefix(prefixBase, rootNsContext, seqArr);
}
public void addPrefix(String prefix, String uri)
{
if (_nsBinder == null) {
// Didn't have a mapping yet? Need to create one...
_nsBinder = NsBinder.createEmpty();
} else {
if (_parent != null && _parent._nsBinder == _nsBinder) {
// Shared with parent(s)? Need to branch off to be able to modify
_nsBinder = _nsBinder.createChild();
}
}
_nsBinder.addMapping(prefix, uri);
}
/*
//////////////////////////////////////////////////
// NamespaceContext implementation, other ns funcs
//////////////////////////////////////////////////
*/
public String getNamespaceURI(String prefix)
{
if (prefix.length() == 0) {
return _defaultNsURI;
}
if (_nsBinder != null) {
String uri = _nsBinder.findUriByPrefix(prefix);
if (uri != null) {
return uri;
}
}
return null;
}
public String getPrefix(String uri)
{
if (_defaultNsURI.equals(uri)) {
return "";
}
if (_nsBinder != null) {
String prefix = _nsBinder.findPrefixByUri(uri);
if (prefix != null) {
return prefix;
}
}
return null;
}
@SuppressWarnings("unchecked")
public Iterator getPrefixes(String uri, NamespaceContext rootNsContext)
{
List l = null;
if (_defaultNsURI.equals(uri)) {
l = new ArrayList();
l.add("");
}
if (_nsBinder != null) {
l = _nsBinder.getPrefixesBoundToUri(uri, l);
}
// How about the root namespace context? (if any)
// Note: it's quite difficult to properly resolve masking
if (rootNsContext != null) {
Iterator it = (Iterator)rootNsContext.getPrefixes(uri);
while (it.hasNext()) {
String prefix = it.next();
if (prefix.length() == 0) { // default NS already checked
continue;
}
// slow check... but what the heck
if (l == null) {
l = new ArrayList();
} else if (l.contains(prefix)) { // double-defined...
continue;
}
l.add(prefix);
}
}
if (l == null) {
return EmptyIterator.getInstance();
}
return l.iterator();
}
/**
* Method similar to {@link #getPrefix}, but one that will not accept
* the default namespace, only an explicit one. Usually used when
* trying to find a prefix for attributes.
*/
public String getExplicitPrefix(String uri, NamespaceContext rootNsContext)
{
if (_nsBinder != null) {
String prefix = _nsBinder.findPrefixByUri(uri);
if (prefix != null) {
return prefix;
}
}
if (rootNsContext != null) {
String prefix = rootNsContext.getPrefix(uri);
if (prefix != null) {
// Hmmh... still can't use the default NS:
if (prefix.length() > 0) {
return prefix;
}
// ... should we try to find an explicit one?
}
}
return null;
}
/**
* Method that verifies that passed-in non-empty prefix indeed maps
* to specified non-empty namespace URI; and depending on how it goes
* returns a status for caller.
*
* @return OK, if passed-in prefix matches matched-in namespace URI
* in current scope; UNBOUND if it's not bound to anything,
* and MISBOUND if it's bound to another URI.
*/
public PrefixState checkPrefixValidity(String prefix, String nsURI, NamespaceContext rootNsContext)
{
/* First thing is to see if specified prefix is bound to a namespace;
* and if so, verify it matches with data passed in:
*/
/* Need to handle 'xml' prefix and its associated
* URI; they are always declared by default
*/
if (prefix.equals("xml")) {
return nsURI.equals(XMLConstants.XML_NS_URI) ?
PrefixState.OK : PrefixState.MISBOUND;
}
// Nope checking some other namespace
String act = (_nsBinder == null) ? null : _nsBinder.findUriByPrefix(prefix);
if (act == null && rootNsContext != null) {
act = rootNsContext.getNamespaceURI(prefix);
}
// Not (yet) bound...
if (act == null) {
return PrefixState.UNBOUND;
}
return (act == nsURI || act.equals(nsURI)) ?
PrefixState.OK : PrefixState.MISBOUND;
}
public boolean isPrefixBoundTo(String prefix, String nsURI, NamespaceContext rootNsContext)
{
// First: test default namespace
if (prefix == null || prefix.length() == 0) {
return _defaultNsURI.equals(nsURI);
}
/* Need to handle 'xml' prefix and its associated
* URI; they are always declared by default
*/
if ("xml".equals(prefix)) {
return nsURI.equals(XMLConstants.XML_NS_URI);
}
// Nope checking some other namespace
String act = (_nsBinder == null) ? null : _nsBinder.findUriByPrefix(prefix);
if (act == null && rootNsContext != null) {
act = rootNsContext.getNamespaceURI(prefix);
}
// Not (yet) bound...
return (act != null) && (act == nsURI || act.equals(nsURI));
}
public boolean isPrefixUnbound(String prefix, NamespaceContext rootNsContext)
{
// First: if an explict binding is found, can't be unbound
String act = (_nsBinder == null) ? null : _nsBinder.findUriByPrefix(prefix);
if (act != null && act.length() != 0) {
return false;
}
if (prefix.equals("xml")) { // "xml" is always bound as well
return false;
}
if (rootNsContext != null) { // or maybe root context has a binding?
act = rootNsContext.getNamespaceURI(prefix);
if (act != null && act.length() != 0) {
return false;
}
}
// Must be unbound
return true;
}
/*
////////////////////////////////////////////
// Comparison (etc) methods
////////////////////////////////////////////
*/
@Override
public int hashCode()
{
return _name.hashCode();
}
}