/* 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 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 org.codehaus.stax2.ri.dom;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import org.w3c.dom.*;
import org.codehaus.stax2.AttributeInfo;
import org.codehaus.stax2.DTDInfo;
import org.codehaus.stax2.LocationInfo;
import org.codehaus.stax2.XMLStreamLocation2;
import org.codehaus.stax2.XMLStreamReader2;
import org.codehaus.stax2.ri.EmptyIterator;
import org.codehaus.stax2.ri.EmptyNamespaceContext;
import org.codehaus.stax2.ri.SingletonIterator;
import org.codehaus.stax2.ri.Stax2Util;
import org.codehaus.stax2.ri.typed.StringBase64Decoder;
import org.codehaus.stax2.ri.typed.ValueDecoderFactory;
import org.codehaus.stax2.typed.Base64Variant;
import org.codehaus.stax2.typed.Base64Variants;
import org.codehaus.stax2.typed.TypedArrayDecoder;
import org.codehaus.stax2.typed.TypedValueDecoder;
import org.codehaus.stax2.typed.TypedXMLStreamException;
import org.codehaus.stax2.validation.DTDValidationSchema;
import org.codehaus.stax2.validation.ValidationProblemHandler;
import org.codehaus.stax2.validation.XMLValidationSchema;
import org.codehaus.stax2.validation.XMLValidator;
/**
* This is an adapter class that presents a DOM document as if it was
* a regular {@link XMLStreamReader}. This is mostly useful for
* inter-operability purposes, and should only be used when the
* input has to come as a DOM object and the original xml content
* is not available as a stream.
*<p>
* Note that the implementation is only to be used for use with
* <code>javax.xml.transform.dom.DOMSource</code>. It can however be
* used for both full documents, and single element root fragments,
* depending on what node is passed as the argument.
*<p>
* Some notes regarding missing/incomplete functionality:
* <ul>
* <li>DOM does not seem to have access to information from the XML
* declaration (although Document node can be viewed as representing
* it). Consequently, all accessors return no information (version,
* encoding, standalone).
* </li>
* <li>No location info is provided, since (you guessed it!) DOM
* does not provide that info.
* </li>
* </ul>
*/
public abstract class DOMWrappingReader
implements XMLStreamReader2,
AttributeInfo, DTDInfo, LocationInfo,
NamespaceContext,
XMLStreamConstants
{
protected final static int INT_SPACE = 0x0020;
// // // Bit masks used for quick type comparisons
final private static int MASK_GET_TEXT =
(1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
| (1 << COMMENT) | (1 << DTD) | (1 << ENTITY_REFERENCE);
final private static int MASK_GET_TEXT_XXX =
(1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE) | (1 << COMMENT);
final private static int MASK_GET_ELEMENT_TEXT =
(1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
| (1 << ENTITY_REFERENCE);
final protected static int MASK_TYPED_ACCESS_BINARY =
(1 << START_ELEMENT) // note: END_ELEMENT handled separately
| (1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
;
// // // Enumerated error case ids
/**
* Current state not START_ELEMENT, should be
*/
protected final static int ERR_STATE_NOT_START_ELEM = 1;
/**
* Current state not START_ELEMENT or END_ELEMENT, should be
*/
protected final static int ERR_STATE_NOT_ELEM = 2;
/**
* Current state not PROCESSING_INSTRUCTION
*/
protected final static int ERR_STATE_NOT_PI = 3;
/**
* Current state not one where getText() can be used
*/
protected final static int ERR_STATE_NOT_TEXTUAL = 4;
/**
* Current state not one where getTextXxx() can be used
*/
protected final static int ERR_STATE_NOT_TEXTUAL_XXX = 5;
protected final static int ERR_STATE_NOT_TEXTUAL_OR_ELEM = 6;
protected final static int ERR_STATE_NO_LOCALNAME = 7;
// // // Configuration:
protected final String _systemId;
protected final Node _rootNode;
/**
* Whether stream reader is to be namespace aware (as per property
* {@link XMLInputFactory#IS_NAMESPACE_AWARE}) or not
*/
protected final boolean _cfgNsAware;
/**
* Whether stream reader is to coalesce adjacent textual
* (CHARACTERS, SPACE, CDATA) events (as per property
* {@link XMLInputFactory#IS_COALESCING}) or not
*/
protected final boolean _coalescing;
/**
* By default we do not force interning of names: can be
* reset by sub-classes.
*/
protected boolean _cfgInternNames = false;
/**
* By default we do not force interning of namespace URIs: can be
* reset by sub-classes.
*/
protected boolean _cfgInternNsURIs = false;
// // // State:
protected int _currEvent = START_DOCUMENT;
/**
* Current node is the DOM node that contains information
* regarding the current event.
*/
protected Node _currNode;
protected int _depth = 0;
/**
* In coalescing mode, we may need to combine textual content
* from multiple adjacent nodes. Since we shouldn't be modifying
* the underlying DOM tree, need to accumulate it into a temporary
* variable
*/
protected String _coalescedText;
/**
* Helper object used for combining segments of text as needed
*/
protected Stax2Util.TextBuffer _textBuffer = new Stax2Util.TextBuffer();
// // // Attribute/namespace declaration state
/* DOM, alas, does not distinguish between namespace declarations
* and attributes (due to its roots prior to XML namespaces?).
* Because of this, two lists need to be separated. Since this
* information is often not needed, it will be lazily generated.
*/
/**
* Lazily instantiated List of all actual attributes for the
* current (start) element, NOT including namespace declarations.
* As such, elements are {@link org.w3c.dom.Attr} instances.
*<p>
*/
protected List<Node> _attrList = null;
/**
* Lazily instantiated String pairs of all namespace declarations for the
* current (start/end) element. String pair means that for each
* declarations there are two Strings in the list: first one is prefix
* (empty String for the default namespace declaration), and second
* URI it is bound to.
*/
protected List<String> _nsDeclList = null;
/**
* Factory used for constructing decoders we need for typed access
*/
protected ValueDecoderFactory _decoderFactory;
/**
* Lazily-constructed decoder object for decoding base64 encoded
* binary content.
*/
protected StringBase64Decoder _base64Decoder = null;
/*
////////////////////////////////////////////////////
// Construction, configuration
////////////////////////////////////////////////////
*/
/**
* @param src Node that is the tree of the DOM document, or fragment.
* @param nsAware Whether resulting reader should operate in namespace
* aware mode or not. Note that this should be compatible with
* settings for the DOM builder that produced DOM tree or fragment
* being operated on, otherwise results are not defined.
* @param coalescing Whether resulting reader should coalesce adjacent
* text events or not
*/
protected DOMWrappingReader(DOMSource src, boolean nsAware, boolean coalescing)
throws XMLStreamException
{
Node treeRoot = src.getNode();
if (treeRoot == null) {
throw new IllegalArgumentException("Can not pass null Node for constructing a DOM-based XMLStreamReader");
}
_cfgNsAware = nsAware;
_coalescing = coalescing;
_systemId = src.getSystemId();
/* Ok; we need a document node; or an element node; or a document
* fragment node.
*/
switch (treeRoot.getNodeType()) {
case Node.DOCUMENT_NODE: // fine
/* Should try to find encoding, version and stand-alone
* settings... but is there a standard way of doing that?
*/
case Node.ELEMENT_NODE: // can make sub-tree... ok
// But should we skip START/END_DOCUMENT? For now, let's not
case Node.DOCUMENT_FRAGMENT_NODE: // as with element...
// Above types are fine
break;
default: // other Nodes not usable
throw new XMLStreamException("Can not create an XMLStreamReader for a DOM node of type "+treeRoot.getClass());
}
_rootNode = _currNode = treeRoot;
}
protected void setInternNames(boolean state) { _cfgInternNames = state; }
protected void setInternNsURIs(boolean state) { _cfgInternNsURIs = state; }
/*
////////////////////////////////////////////////////
// Abstract methods for sub-classes to implement
////////////////////////////////////////////////////
*/
protected abstract void throwStreamException(String msg, Location loc)
throws XMLStreamException;
/*
////////////////////////////////////////////////////
// XMLStreamReader, document info
////////////////////////////////////////////////////
*/
/**
* As per Stax (1.0) specs, needs to return whatever xml declaration
* claimed encoding is, if any; or null if no xml declaration found.
*/
public String getCharacterEncodingScheme() {
/* No standard way to figure it out from a DOM Document node;
* have to return null
*/
return null;
}
/**
* As per Stax (1.0) specs, needs to return whatever parser determined
* the encoding was, if it was able to figure it out. If not (there are
* cases where this can not be found; specifically when being passed a
* {@link java.io.Reader}), it should return null.
*/
public String getEncoding() {
/* We have no information regarding underlying stream/Reader, so
* best we can do is to see if we know xml declaration encoding.
*/
return getCharacterEncodingScheme();
}
public String getVersion()
{
/* No standard way to figure it out from a DOM Document node;
* have to return null
*/
return null;
}
public boolean isStandalone() {
/* No standard way to figure it out from a DOM Document node;
* have to return false
*/
return false;
}
public boolean standaloneSet() {
/* No standard way to figure it out from a DOM Document node;
* have to return false
*/
return false;
}
/*
////////////////////////////////////////////////////
// Public API, configuration
////////////////////////////////////////////////////
*/
public abstract Object getProperty(String name);
// NOTE: getProperty() defined in Stax 1.0 interface
public abstract boolean isPropertySupported(String name);
/**
* @param name Name of the property to set
* @param value Value to set property to.
*
* @return True, if the specified property was <b>succesfully</b>
* set to specified value; false if its value was not changed
*/
public abstract boolean setProperty(String name, Object value);
/*
////////////////////////////////////////////////////
// XMLStreamReader, current state
////////////////////////////////////////////////////
*/
// // // Attribute access:
public int getAttributeCount()
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
return _attrList.size();
}
public String getAttributeLocalName(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
if (index >= _attrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) _attrList.get(index);
return _internName(_safeGetLocalName(attr));
}
public QName getAttributeName(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
if (index >= _attrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) _attrList.get(index);
return _constructQName(attr.getNamespaceURI(), _safeGetLocalName(attr), attr.getPrefix());
}
public String getAttributeNamespace(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
if (index >= _attrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) _attrList.get(index);
return _internNsURI(attr.getNamespaceURI());
}
public String getAttributePrefix(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
if (index >= _attrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) _attrList.get(index);
return _internName(attr.getPrefix());
}
public String getAttributeType(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
if (index >= _attrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
//Attr attr = (Attr) _attrList.get(index);
// First, a special case, ID... since it's potentially most useful
/* 26-Apr-2006, TSa: Turns out that following methods are
* DOM Level3, and as such not available in JDK 1.4 and prior.
* Thus, let's not yet use them (could use dynamic discovery
* for graceful downgrade)
*/
/*
if (attr.isId()) {
return "ID";
}
TypeInfo schemaType = attr.getSchemaTypeInfo();
return (schemaType == null) ? "CDATA" : schemaType.getTypeName();
*/
return "CDATA";
}
public String getAttributeValue(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
if (_attrList == null) {
_calcNsAndAttrLists(true);
}
if (index >= _attrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) _attrList.get(index);
return attr.getValue();
}
public String getAttributeValue(String nsURI, String localName)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
Element elem = (Element) _currNode;
NamedNodeMap attrs = elem.getAttributes();
/* Hmmh. DOM javadocs claim "Per [XML Namespaces], applications
* must use the value null as the namespaceURI parameter for methods
* if they wish to have no namespace.".
* Not sure how true that is, but:
*/
if (nsURI != null && nsURI.length() == 0) {
nsURI = null;
}
Attr attr = (Attr) attrs.getNamedItemNS(nsURI, localName);
return (attr == null) ? null : attr.getValue();
}
/**
* From StAX specs:
*<blockquote>
* Reads the content of a text-only element, an exception is thrown if
* this is not a text-only element.
* Regardless of value of javax.xml.stream.isCoalescing this method always
* returns coalesced content.
*<br/>Precondition: the current event is START_ELEMENT.
*<br/>Postcondition: the current event is the corresponding END_ELEMENT.
*</blockquote>
*/
public String getElementText()
throws XMLStreamException
{
if (_currEvent != START_ELEMENT) {
/* Quite illogical: this is not an IllegalStateException
* like other similar ones, but rather an XMLStreamException.
* But that's how Stax JavaDocs outline how it should be.
*/
reportParseProblem(ERR_STATE_NOT_START_ELEM);
}
// As per [WSTX-244], handling of coalescing, regular differ a lot, so:
if (_coalescing) {
String text = null;
// Need to loop to get rid of PIs, comments
while (true) {
int type = next();
if (type == END_ELEMENT) {
break;
}
if (type == COMMENT || type == PROCESSING_INSTRUCTION) {
continue;
}
if (((1 << type) & MASK_GET_ELEMENT_TEXT) == 0) {
reportParseProblem(ERR_STATE_NOT_TEXTUAL);
}
if (text == null) {
text = getText();
} else { // uncommon but possible (with comments, PIs):
text = text + getText();
}
}
return (text == null) ? "" : text;
}
_textBuffer.reset();
// Need to loop to get rid of PIs, comments
while (true) {
int type = next();
if (type == END_ELEMENT) {
break;
}
if (type == COMMENT || type == PROCESSING_INSTRUCTION) {
continue;
}
if (((1 << type) & MASK_GET_ELEMENT_TEXT) == 0) {
reportParseProblem(ERR_STATE_NOT_TEXTUAL);
}
_textBuffer.append(getText());
}
return _textBuffer.get();
}
/**
* Returns type of the last event returned; or START_DOCUMENT before
* any events has been explicitly returned.
*/
public int getEventType()
{
return _currEvent;
}
public String getLocalName()
{
if (_currEvent == START_ELEMENT || _currEvent == END_ELEMENT) {
return _internName(_safeGetLocalName(_currNode));
}
if (_currEvent != ENTITY_REFERENCE) {
reportWrongState(ERR_STATE_NO_LOCALNAME);
}
return _internName(_currNode.getNodeName());
}
public final Location getLocation() {
return getStartLocation();
}
public QName getName()
{
if (_currEvent != START_ELEMENT && _currEvent != END_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
return _constructQName(_currNode.getNamespaceURI(), _safeGetLocalName(_currNode), _currNode.getPrefix());
}
// // // Namespace access
public NamespaceContext getNamespaceContext() {
return this;
}
public int getNamespaceCount()
{
if (_currEvent != START_ELEMENT && _currEvent != END_ELEMENT) {
reportWrongState(ERR_STATE_NOT_ELEM);
}
if (_nsDeclList == null) {
if (!_cfgNsAware) {
return 0;
}
_calcNsAndAttrLists(_currEvent == START_ELEMENT);
}
return _nsDeclList.size() / 2;
}
/**
* Alas, DOM does not expose any of information necessary for
* determining actual declarations. Thus, have to indicate that
* there are no declarations.
*/
public String getNamespacePrefix(int index) {
if (_currEvent != START_ELEMENT && _currEvent != END_ELEMENT) {
reportWrongState(ERR_STATE_NOT_ELEM);
}
if (_nsDeclList == null) {
if (!_cfgNsAware) {
handleIllegalNsIndex(index);
}
_calcNsAndAttrLists(_currEvent == START_ELEMENT);
}
if (index < 0 || (index + index) >= _nsDeclList.size()) {
handleIllegalNsIndex(index);
}
// Note: _nsDeclList entries have been appropriately intern()ed if need be
return _nsDeclList.get(index + index);
}
public String getNamespaceURI() {
if (_currEvent != START_ELEMENT && _currEvent != END_ELEMENT) {
reportWrongState(ERR_STATE_NOT_ELEM);
}
return _internNsURI(_currNode.getNamespaceURI());
}
public String getNamespaceURI(int index) {
if (_currEvent != START_ELEMENT && _currEvent != END_ELEMENT) {
reportWrongState(ERR_STATE_NOT_ELEM);
}
if (_nsDeclList == null) {
if (!_cfgNsAware) {
handleIllegalNsIndex(index);
}
_calcNsAndAttrLists(_currEvent == START_ELEMENT);
}
if (index < 0 || (index + index) >= _nsDeclList.size()) {
handleIllegalNsIndex(index);
}
// Note: _nsDeclList entries have been appropriately intern()ed if need be
return _nsDeclList.get(index + index + 1);
}
// Note: implemented as part of NamespaceContext
//public String getNamespaceURI(String prefix)
public String getPIData() {
if (_currEvent != PROCESSING_INSTRUCTION) {
reportWrongState(ERR_STATE_NOT_PI);
}
return _currNode.getNodeValue();
}
public String getPITarget() {
if (_currEvent != PROCESSING_INSTRUCTION) {
reportWrongState(ERR_STATE_NOT_PI);
}
return _internName(_currNode.getNodeName());
}
public String getPrefix() {
if (_currEvent != START_ELEMENT && _currEvent != END_ELEMENT) {
reportWrongState(ERR_STATE_NOT_ELEM);
}
return _internName(_currNode.getPrefix());
}
public String getText()
{
if (_coalescedText != null) {
return _coalescedText;
}
if (((1 << _currEvent) & MASK_GET_TEXT) == 0) {
reportWrongState(ERR_STATE_NOT_TEXTUAL);
}
return _currNode.getNodeValue();
}
public char[] getTextCharacters()
{
String text = getText();
return text.toCharArray();
}
public int getTextCharacters(int sourceStart, char[] target, int targetStart, int len)
{
if (((1 << _currEvent) & MASK_GET_TEXT_XXX) == 0) {
reportWrongState(ERR_STATE_NOT_TEXTUAL_XXX);
}
String text = getText();
if (len > text.length()) {
len = text.length();
}
text.getChars(sourceStart, sourceStart+len, target, targetStart);
return len;
}
public int getTextLength()
{
if (((1 << _currEvent) & MASK_GET_TEXT_XXX) == 0) {
reportWrongState(ERR_STATE_NOT_TEXTUAL_XXX);
}
return getText().length();
}
public int getTextStart()
{
if (((1 << _currEvent) & MASK_GET_TEXT_XXX) == 0) {
reportWrongState(ERR_STATE_NOT_TEXTUAL_XXX);
}
return 0;
}
public boolean hasName() {
return (_currEvent == START_ELEMENT) || (_currEvent == END_ELEMENT);
}
public boolean hasNext() {
return (_currEvent != END_DOCUMENT);
}
public boolean hasText() {
return (((1 << _currEvent) & MASK_GET_TEXT) != 0);
}
public boolean isAttributeSpecified(int index)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
Element elem = (Element) _currNode;
Attr attr = (Attr) elem.getAttributes().item(index);
if (attr == null) {
handleIllegalAttrIndex(index);
return false;
}
return attr.getSpecified();
}
public boolean isCharacters()
{
return (_currEvent == CHARACTERS);
}
public boolean isEndElement() {
return (_currEvent == END_ELEMENT);
}
public boolean isStartElement() {
return (_currEvent == START_ELEMENT);
}
public boolean isWhiteSpace()
{
if (_currEvent == CHARACTERS || _currEvent == CDATA) {
String text = getText();
for (int i = 0, len = text.length(); i < len; ++i) {
/* !!! If xml 1.1 was to be handled, should check for
* LSEP and NEL too?
*/
if (text.charAt(i) > INT_SPACE) {
return false;
}
}
return true;
}
return (_currEvent == SPACE);
}
public void require(int type, String nsUri, String localName)
throws XMLStreamException
{
int curr = _currEvent;
/* There are some special cases; specifically, SPACE and CDATA
* are sometimes reported as CHARACTERS. Let's be lenient by
* allowing both 'real' and reported types, for now.
*/
if (curr != type) {
if (curr == CDATA) {
curr = CHARACTERS;
} else if (curr == SPACE) {
curr = CHARACTERS;
}
}
if (type != curr) {
throwStreamException("Required type "+Stax2Util.eventTypeDesc(type)
+", current type "
+Stax2Util.eventTypeDesc(curr));
}
if (localName != null) {
if (curr != START_ELEMENT && curr != END_ELEMENT
&& curr != ENTITY_REFERENCE) {
throwStreamException("Required a non-null local name, but current token not a START_ELEMENT, END_ELEMENT or ENTITY_REFERENCE (was "+Stax2Util.eventTypeDesc(_currEvent)+")");
}
String n = getLocalName();
if (n != localName && !n.equals(localName)) {
throwStreamException("Required local name '"+localName+"'; current local name '"+n+"'.");
}
}
if (nsUri != null) {
if (curr != START_ELEMENT && curr != END_ELEMENT) {
throwStreamException("Required non-null NS URI, but current token not a START_ELEMENT or END_ELEMENT (was "+Stax2Util.eventTypeDesc(curr)+")");
}
String uri = getNamespaceURI();
// No namespace?
if (nsUri.length() == 0) {
if (uri != null && uri.length() > 0) {
throwStreamException("Required empty namespace, instead have '"+uri+"'.");
}
} else {
if ((nsUri != uri) && !nsUri.equals(uri)) {
throwStreamException("Required namespace '"+nsUri+"'; have '"
+uri+"'.");
}
}
}
// Ok, fine, all's good
}
/*
////////////////////////////////////////////////////
// XMLStreamReader, iterating
////////////////////////////////////////////////////
*/
public int next()
throws XMLStreamException
{
_coalescedText = null;
/* For most events, we just need to find the next sibling; and
* that failing, close the parent element. But there are couple
* of special cases, which are handled first:
*/
switch (_currEvent) {
case START_DOCUMENT: // initial state
/* What to do here depends on what kind of node we started
* with...
*/
switch (_currNode.getNodeType()) {
case Node.DOCUMENT_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
// For doc, fragment, need to find first child
_currNode = _currNode.getFirstChild();
// as per [WSTX-259], need to handle degenerate case of empty fragment, too
if (_currNode == null) {
return (_currEvent = END_DOCUMENT);
}
break;
case Node.ELEMENT_NODE:
// For element, curr node is fine:
return (_currEvent = START_ELEMENT);
default:
throw new XMLStreamException("Internal error: unexpected DOM root node type "+_currNode.getNodeType()+" for node '"+_currNode+"'");
}
break;
case END_DOCUMENT: // end reached: should not call!
throw new java.util.NoSuchElementException("Can not call next() after receiving END_DOCUMENT");
case START_ELEMENT: // element returned, need to traverse children, if any
++_depth;
_attrList = null; // so it will not get reused accidentally
{
Node firstChild = _currNode.getFirstChild();
if (firstChild == null) { // empty? need to return virtual END_ELEMENT
/* Note: need not clear namespace declarations, because
* it'll be the same as for the start elem!
*/
return (_currEvent = END_ELEMENT);
}
_nsDeclList = null;
/* non-empty is easy: let's just swap curr node, and
* fall through to regular handling
*/
_currNode = firstChild;
break;
}
case END_ELEMENT:
--_depth;
// Need to clear these lists
_attrList = null;
_nsDeclList = null;
/* One special case: if we hit the end of children of
* the root element (when tree constructed with Element,
* instead of Document or DocumentFragment). If so, it'll
* be END_DOCUMENT:
*/
if (_currNode == _rootNode) {
return (_currEvent = END_DOCUMENT);
}
// Otherwise need to fall through to default handling:
default:
/* For anything else, we can and should just get the
* following sibling.
*/
{
Node next = _currNode.getNextSibling();
// If sibling, let's just assign and fall through
if (next != null) {
_currNode = next;
break;
}
/* Otherwise, need to climb up _the stack and either
* return END_ELEMENT (if parent is element) or
* END_DOCUMENT (if not; needs to be root, then)
*/
_currNode = _currNode.getParentNode();
int type = _currNode.getNodeType();
if (type == Node.ELEMENT_NODE) {
return (_currEvent = END_ELEMENT);
}
// Let's do sanity check; should really be Doc/DocFragment
if (_currNode != _rootNode ||
(type != Node.DOCUMENT_NODE && type != Node.DOCUMENT_FRAGMENT_NODE)) {
throw new XMLStreamException("Internal error: non-element parent node ("+type+") that is not the initial root node");
}
return (_currEvent = END_DOCUMENT);
}
}
// Ok, need to determine current node type:
switch (_currNode.getNodeType()) {
case Node.CDATA_SECTION_NODE:
if (_coalescing) {
coalesceText(CDATA);
} else {
_currEvent = CDATA;
}
break;
case Node.COMMENT_NODE:
_currEvent = COMMENT;
break;
case Node.DOCUMENT_TYPE_NODE:
_currEvent = DTD;
break;
case Node.ELEMENT_NODE:
_currEvent = START_ELEMENT;
break;
case Node.ENTITY_REFERENCE_NODE:
_currEvent = ENTITY_REFERENCE;
break;
case Node.PROCESSING_INSTRUCTION_NODE:
_currEvent = PROCESSING_INSTRUCTION;
break;
case Node.TEXT_NODE:
if (_coalescing) {
coalesceText(CHARACTERS);
} else {
_currEvent = CHARACTERS;
}
break;
// Should not get other nodes (notation/entity decl., attr)
case Node.ATTRIBUTE_NODE:
case Node.ENTITY_NODE:
case Node.NOTATION_NODE:
throw new XMLStreamException("Internal error: unexpected DOM node type "+_currNode.getNodeType()+" (attr/entity/notation?), for node '"+_currNode+"'");
default:
throw new XMLStreamException("Internal error: unrecognized DOM node type "+_currNode.getNodeType()+", for node '"+_currNode+"'");
}
return _currEvent;
}
public int nextTag()
throws XMLStreamException
{
while (true) {
int next = next();
switch (next) {
case SPACE:
case COMMENT:
case PROCESSING_INSTRUCTION:
continue;
case CDATA:
case CHARACTERS:
if (isWhiteSpace()) {
continue;
}
throwStreamException("Received non-all-whitespace CHARACTERS or CDATA event in nextTag().");
break; // never gets here, but jikes complains without
case START_ELEMENT:
case END_ELEMENT:
return next;
}
throwStreamException("Received event "+Stax2Util.eventTypeDesc(next)
+", instead of START_ELEMENT or END_ELEMENT.");
}
}
/**
*<p>
* Note: as per StAX 1.0 specs, this method does NOT close the underlying
* input reader. That is, unless the new StAX2 property
* {@link org.codehaus.stax2.XMLInputFactory2#P_AUTO_CLOSE_INPUT} is
* set to true.
*/
public void close()
throws XMLStreamException
{
// Since DOM tree has no real input source, nothing to do
}
/*
////////////////////////////////////////////////////
// NamespaceContext
////////////////////////////////////////////////////
*/
public String getNamespaceURI(String prefix)
{
/* !!! 26-Apr-2006, TSa: Alas, these methods are DOM Level 3,
* i.e. require JDK 1.5 or higher
*/
/*
if (prefix.length() == 0) { // def NS
return _currNode.lookupNamespaceURI(null);
}
return _currNode.lookupNamespaceURI(prefix);
*/
Node n = _currNode;
boolean defaultNs = (prefix == null) || (prefix.length() == 0);
while (n != null) {
NamedNodeMap attrs = n.getAttributes();
if (attrs != null){
for (int i = 0, len = attrs.getLength(); i < len; ++i) {
Node attr = attrs.item(i);
String thisPrefix = attr.getPrefix();
if (thisPrefix == null || thisPrefix.length() == 0) { // nope
if (defaultNs && "xmlns".equals(attr.getLocalName())) {
return attr.getNodeValue();
}
} else if (!defaultNs && "xmlns".equals(thisPrefix)) {
if (prefix.equals(attr.getLocalName())) {
return attr.getNodeValue();
}
}
}
}
n = n.getParentNode();
}
return null;
}
public String getPrefix(String namespaceURI)
{
/* !!! 26-Apr-2006, TSa: Alas, these methods are DOM Level 3,
* i.e. require JDK 1.5 or higher
*/
/*
String prefix = _currNode.lookupPrefix(namespaceURI);
if (prefix == null) { // maybe default NS?
String defURI = _currNode.lookupNamespaceURI(null);
if (defURI != null && defURI.equals(namespaceURI)) {
return "";
}
}
return prefix;
*/
Node n = _currNode;
if (namespaceURI == null) { // not sure if this is even legal but...
namespaceURI = "";
}
while (n != null) {
NamedNodeMap attrs = n.getAttributes();
for (int i = 0, len = attrs.getLength(); i < len; ++i) {
Node attr = attrs.item(i);
String thisPrefix = attr.getPrefix();
if (thisPrefix == null || thisPrefix.length() == 0) {
if ("xmlns".equals(attr.getLocalName()) && namespaceURI.equals(attr.getNodeValue())) {
return "";
}
} else if ("xmlns".equals(thisPrefix)) {
if (namespaceURI.equals(attr.getNodeValue())) {
return attr.getLocalName();
}
}
}
n = n.getParentNode();
}
return null;
}
public Iterator<String> getPrefixes(String namespaceURI)
{
String prefix = getPrefix(namespaceURI);
if (prefix == null) {
return EmptyIterator.getInstance();
}
return new SingletonIterator<String>(prefix);
}
/*
/////////////////////////////////////////////////
// TypedXMLStreamReader2 implementation, element
/////////////////////////////////////////////////
*/
public boolean getElementAsBoolean() throws XMLStreamException
{
ValueDecoderFactory.BooleanDecoder dec = _decoderFactory().getBooleanDecoder();
getElementAs(dec);
return dec.getValue();
}
public int getElementAsInt() throws XMLStreamException
{
ValueDecoderFactory.IntDecoder dec = _decoderFactory().getIntDecoder();
getElementAs(dec);
return dec.getValue();
}
public long getElementAsLong() throws XMLStreamException
{
ValueDecoderFactory.LongDecoder dec = _decoderFactory().getLongDecoder();
getElementAs(dec);
return dec.getValue();
}
public float getElementAsFloat() throws XMLStreamException
{
ValueDecoderFactory.FloatDecoder dec = _decoderFactory().getFloatDecoder();
getElementAs(dec);
return dec.getValue();
}
public double getElementAsDouble() throws XMLStreamException
{
ValueDecoderFactory.DoubleDecoder dec = _decoderFactory().getDoubleDecoder();
getElementAs(dec);
return dec.getValue();
}
public BigInteger getElementAsInteger() throws XMLStreamException
{
ValueDecoderFactory.IntegerDecoder dec = _decoderFactory().getIntegerDecoder();
getElementAs(dec);
return dec.getValue();
}
public BigDecimal getElementAsDecimal() throws XMLStreamException
{
ValueDecoderFactory.DecimalDecoder dec = _decoderFactory().getDecimalDecoder();
getElementAs(dec);
return dec.getValue();
}
public QName getElementAsQName() throws XMLStreamException
{
ValueDecoderFactory.QNameDecoder dec = _decoderFactory().getQNameDecoder(getNamespaceContext());
getElementAs(dec);
return dec.getValue();
}
public byte[] getElementAsBinary() throws XMLStreamException
{
return getElementAsBinary(Base64Variants.getDefaultVariant());
}
public byte[] getElementAsBinary(Base64Variant v) throws XMLStreamException
{
// note: code here is similar to Base64DecoderBase.aggregateAll(), see comments there
Stax2Util.ByteAggregator aggr = _base64Decoder().getByteAggregator();
byte[] buffer = aggr.startAggregation();
while (true) {
int offset = 0;
int len = buffer.length;
do {
int readCount = readElementAsBinary(buffer, offset, len, v);
if (readCount < 1) { // all done!
return aggr.aggregateAll(buffer, offset);
}
offset += readCount;
len -= readCount;
} while (len > 0);
buffer = aggr.addFullBlock(buffer);
}
}
public void getElementAs(TypedValueDecoder tvd) throws XMLStreamException
{
String value = getElementText();
value = Stax2Util.trimSpaces(value);
try {
if (value == null) {
tvd.handleEmptyValue();
} else {
tvd.decode(value);
}
} catch (IllegalArgumentException iae) {
throw _constructTypeException(iae, value);
}
}
public int readElementAsIntArray(int[] value, int from, int length) throws XMLStreamException
{
return readElementAsArray(_decoderFactory().getIntArrayDecoder(value, from, length));
}
public int readElementAsLongArray(long[] value, int from, int length) throws XMLStreamException
{
return readElementAsArray(_decoderFactory().getLongArrayDecoder(value, from, length));
}
public int readElementAsFloatArray(float[] value, int from, int length) throws XMLStreamException
{
return readElementAsArray(_decoderFactory().getFloatArrayDecoder(value, from, length));
}
public int readElementAsDoubleArray(double[] value, int from, int length) throws XMLStreamException
{
return readElementAsArray(_decoderFactory().getDoubleArrayDecoder(value, from, length));
}
public int readElementAsArray(TypedArrayDecoder tad) throws XMLStreamException
{
/* Otherwise either we are just starting (START_ELEMENT), or
* have collected all the stuff into _textBuffer.
*/
if (_currEvent == START_ELEMENT) {
// One special case, no children:
Node fc = _currNode.getFirstChild();
if (fc == null) {
_currEvent = END_ELEMENT;
return -1;
}
_coalescedText = coalesceTypedText(fc);
_currEvent = CHARACTERS;
_currNode = _currNode.getLastChild();
} else {
if (_currEvent != CHARACTERS && _currEvent != CDATA) {
// Maybe we are already done?
if (_currEvent == END_ELEMENT) {
return -1;
}
reportWrongState(ERR_STATE_NOT_TEXTUAL_OR_ELEM);
}
/* One more thing: do we have the data? It is possible
* that caller has advanced to this text node by itself.
* We could handle this mostly ok; but that is not a supported
* use case as per Typed Access API definition (as it can not
* be reliably supported by all implementations), so:
*/
if (_coalescedText == null) {
throw new IllegalStateException("First call to readElementAsArray() must be for a START_ELEMENT, not directly for a textual event");
}
}
/* Otherwise, need to move pointer to point to the last
* child node, and fake that it was a textual node
*/
// Ok, so what do we have left?
String input = _coalescedText;
final int end = input.length();
int ptr = 0;
int count = 0;
String value = null;
try {
decode_loop:
while (ptr < end) {
// First, any space to skip?
while (input.charAt(ptr) <= INT_SPACE) {
if (++ptr >= end) {
break decode_loop;
}
}
// Then let's figure out non-space char (token)
int start = ptr;
++ptr;
while (ptr < end && input.charAt(ptr) > INT_SPACE) {
++ptr;
}
++count;
// And there we have it
value = input.substring(start, ptr);
// Plus, can skip trailing space (or at end, just beyond it)
++ptr;
if (tad.decodeValue(value)) {
break;
}
}
} catch (IllegalArgumentException iae) {
// Need to convert to a checked stream exception
/* Hmmh. This is not an accurate location... but it's
* about the best we can do
*/
Location loc = getLocation();
throw new TypedXMLStreamException(value, iae.getMessage(), loc, iae);
} finally {
int len = end-ptr;
_coalescedText = (len < 1) ? "" : input.substring(ptr);
}
if (count < 1) { // end
_currEvent = END_ELEMENT;
_currNode = _currNode.getParentNode();
return -1;
}
return count;
}
private String coalesceTypedText(Node firstNode)
throws XMLStreamException
{
/* This is a bit tricky as we have to collect all the
* text up end tag, but can not advance to END_ELEMENT
* event itself (except if there is no content)
*/
_textBuffer.reset();
_attrList = null; // so it will not get reused accidentally
for (Node n = firstNode; n != null; n = n.getNextSibling()) {
switch (n.getNodeType()) {
case Node.ELEMENT_NODE:
// Illegal to have child elements...
throwStreamException("Element content can not contain child START_ELEMENT when using Typed Access methods");
case Node.CDATA_SECTION_NODE:
case Node.TEXT_NODE:
_textBuffer.append(n.getNodeValue());
break;
case Node.COMMENT_NODE:
case Node.PROCESSING_INSTRUCTION_NODE:
break;
default:
// Otherwise... do we care? For now, let's do
throwStreamException("Unexpected DOM node type ("+n.getNodeType()+") when trying to decode Typed content");
}
}
return _textBuffer.get();
}
/*
////////////////////////////////////////////////////////
// TypedXMLStreamReader2 implementation, binary data
////////////////////////////////////////////////////////
*/
public int readElementAsBinary(byte[] resultBuffer, int offset, int maxLength)
throws XMLStreamException
{
return readElementAsBinary(resultBuffer, offset, maxLength, Base64Variants.getDefaultVariant());
}
public int readElementAsBinary(byte[] resultBuffer, int offset, int maxLength,
Base64Variant v)
throws XMLStreamException
{
if (resultBuffer == null) {
throw new IllegalArgumentException("resultBuffer is null");
}
if (offset < 0) {
throw new IllegalArgumentException("Illegal offset ("+offset+"), must be [0, "+resultBuffer.length+"[");
}
if (maxLength < 1 || (offset + maxLength) > resultBuffer.length) {
if (maxLength == 0) { // special case, allowed, but won't do anything
return 0;
}
throw new IllegalArgumentException("Illegal maxLength ("+maxLength+"), has to be positive number, and offset+maxLength can not exceed"+resultBuffer.length);
}
final StringBase64Decoder dec = _base64Decoder();
int type = _currEvent;
// First things first: must be acceptable start state:
if (((1 << type) & MASK_TYPED_ACCESS_BINARY) == 0) {
if (type == END_ELEMENT) {
// Minor complication: may have unflushed stuff (non-padded versions)
if (!dec.hasData()) {
return -1;
}
} else {
reportWrongState(ERR_STATE_NOT_TEXTUAL_OR_ELEM);
}
}
// Are we just starting (START_ELEMENT)?
if (type == START_ELEMENT) {
// Just need to locate the first text segment (or reach END_ELEMENT)
while (true) {
type = next();
if (type == END_ELEMENT) {
// Simple... no textual content
return -1;
}
if (type == COMMENT || type == PROCESSING_INSTRUCTION) {
continue;
}
if (((1 << type) & MASK_GET_ELEMENT_TEXT) == 0) {
reportParseProblem(ERR_STATE_NOT_TEXTUAL);
}
dec.init(v, true, getText());
break;
}
}
int totalCount = 0;
main_loop:
while (true) {
// Ok, decode:
int count;
try {
count = dec.decode(resultBuffer, offset, maxLength);
} catch (IllegalArgumentException iae) {
throw _constructTypeException(iae, "");
}
offset += count;
totalCount += count;
maxLength -= count;
/* And if we filled the buffer we are done. Or, an edge
* case: reached END_ELEMENT (for non-padded variant)
*/
if (maxLength < 1 || _currEvent == END_ELEMENT) {
break;
}
// Otherwise need to advance to the next event
while (true) {
type = next();
if (type == COMMENT || type == PROCESSING_INSTRUCTION
|| type == SPACE) { // space is ignorable too
continue;
}
if (type == END_ELEMENT) {
/* Just need to verify we don't have partial stuff
* (missing one to three characters of a full quartet
* that encodes 1 - 3 bytes). Also: non-padding
* variants can be in incomplete state, from which
* data may need to be flushed...
*/
int left = dec.endOfContent();
if (left < 0) { // incomplete, error
throw _constructTypeException("Incomplete base64 triplet at the end of decoded content", "");
} else if (left > 0) { // 1 or 2 more bytes of data, loop some more
continue main_loop;
}
// Otherwise, no more data, we are done
break main_loop;
}
if (((1 << type) & MASK_GET_ELEMENT_TEXT) == 0) {
reportParseProblem(ERR_STATE_NOT_TEXTUAL);
}
dec.init(v, false, getText());
break;
}
}
// If nothing was found, needs to be indicated via -1, not 0
return (totalCount > 0) ? totalCount : -1;
}
/*
/////////////////////////////////////////////////
// TypedXMLStreamReader2 implementation, attribute
/////////////////////////////////////////////////
*/
public int getAttributeIndex(String namespaceURI, String localName)
{
return findAttributeIndex(namespaceURI, localName);
}
public boolean getAttributeAsBoolean(int index) throws XMLStreamException
{
ValueDecoderFactory.BooleanDecoder dec = _decoderFactory().getBooleanDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public int getAttributeAsInt(int index) throws XMLStreamException
{
ValueDecoderFactory.IntDecoder dec = _decoderFactory().getIntDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public long getAttributeAsLong(int index) throws XMLStreamException
{
ValueDecoderFactory.LongDecoder dec = _decoderFactory().getLongDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public float getAttributeAsFloat(int index) throws XMLStreamException
{
ValueDecoderFactory.FloatDecoder dec = _decoderFactory().getFloatDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public double getAttributeAsDouble(int index) throws XMLStreamException
{
ValueDecoderFactory.DoubleDecoder dec = _decoderFactory().getDoubleDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public BigInteger getAttributeAsInteger(int index) throws XMLStreamException
{
ValueDecoderFactory.IntegerDecoder dec = _decoderFactory().getIntegerDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public BigDecimal getAttributeAsDecimal(int index) throws XMLStreamException
{
ValueDecoderFactory.DecimalDecoder dec = _decoderFactory().getDecimalDecoder();
getAttributeAs(index, dec);
return dec.getValue();
}
public QName getAttributeAsQName(int index) throws XMLStreamException
{
ValueDecoderFactory.QNameDecoder dec = _decoderFactory().getQNameDecoder(getNamespaceContext());
getAttributeAs(index, dec);
return dec.getValue();
}
public final void getAttributeAs(int index, TypedValueDecoder tvd) throws XMLStreamException
{
String value = getAttributeValue(index);
value = Stax2Util.trimSpaces(value);
try {
if (value == null) {
tvd.handleEmptyValue();
} else {
tvd.decode(value);
}
} catch (IllegalArgumentException iae) {
throw _constructTypeException(iae, value);
}
}
public int[] getAttributeAsIntArray(int index) throws XMLStreamException
{
ValueDecoderFactory.IntArrayDecoder dec = _decoderFactory().getIntArrayDecoder();
_getAttributeAsArray(dec, getAttributeValue(index));
return dec.getValues();
}
public long[] getAttributeAsLongArray(int index) throws XMLStreamException
{
ValueDecoderFactory.LongArrayDecoder dec = _decoderFactory().getLongArrayDecoder();
_getAttributeAsArray(dec, getAttributeValue(index));
return dec.getValues();
}
public float[] getAttributeAsFloatArray(int index) throws XMLStreamException
{
ValueDecoderFactory.FloatArrayDecoder dec = _decoderFactory().getFloatArrayDecoder();
_getAttributeAsArray(dec, getAttributeValue(index));
return dec.getValues();
}
public double[] getAttributeAsDoubleArray(int index) throws XMLStreamException
{
ValueDecoderFactory.DoubleArrayDecoder dec = _decoderFactory().getDoubleArrayDecoder();
_getAttributeAsArray(dec, getAttributeValue(index));
return dec.getValues();
}
public int getAttributeAsArray(int index, TypedArrayDecoder tad) throws XMLStreamException
{
return _getAttributeAsArray(tad, getAttributeValue(index));
}
protected int _getAttributeAsArray(TypedArrayDecoder tad, String attrValue) throws XMLStreamException
{
int ptr = 0;
int start = 0;
final int end = attrValue.length();
String lexical = null;
int count = 0;
try {
decode_loop:
while (ptr < end) {
// First, any space to skip?
while (attrValue.charAt(ptr) <= INT_SPACE) {
if (++ptr >= end) {
break decode_loop;
}
}
// Then let's figure out non-space char (token)
start = ptr;
++ptr;
while (ptr < end && attrValue.charAt(ptr) > INT_SPACE) {
++ptr;
}
int tokenEnd = ptr;
++ptr; // to skip trailing space (or, beyond end)
// And there we have it
lexical = attrValue.substring(start, tokenEnd);
++count;
if (tad.decodeValue(lexical)) {
if (!checkExpand(tad)) {
break;
}
}
}
} catch (IllegalArgumentException iae) {
// Need to convert to a checked stream exception
Location loc = getLocation();
throw new TypedXMLStreamException(lexical, iae.getMessage(), loc, iae);
}
return count;
}
/**
* Internal method used to see if we can expand the buffer that
* the array decoder has. Bit messy, but simpler than having
* separately typed instances; and called rarely so that performance
* downside of instanceof is irrelevant.
*/
private final boolean checkExpand(TypedArrayDecoder tad)
{
if (tad instanceof ValueDecoderFactory.BaseArrayDecoder) {
((ValueDecoderFactory.BaseArrayDecoder) tad).expand();
return true;
}
return false;
}
public byte[] getAttributeAsBinary(int index) throws XMLStreamException
{
return getAttributeAsBinary(index, Base64Variants.getDefaultVariant());
}
public byte[] getAttributeAsBinary(int index, Base64Variant v) throws XMLStreamException
{
String lexical = getAttributeValue(index);
final StringBase64Decoder dec = _base64Decoder();
dec.init(v, true, lexical);
try {
return dec.decodeCompletely();
} catch (IllegalArgumentException iae) {
throw _constructTypeException(iae, lexical);
}
}
/*
////////////////////////////////////////////////////
// XMLStreamReader2 (StAX2) implementation
////////////////////////////////////////////////////
*/
// // // StAX2, additional traversal methods
public void skipElement() throws XMLStreamException
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
int nesting = 1; // need one more end elements than start elements
while (true) {
int type = next();
if (type == START_ELEMENT) {
++nesting;
} else if (type == END_ELEMENT) {
if (--nesting == 0) {
break;
}
}
}
}
// // // StAX2, additional attribute access
public AttributeInfo getAttributeInfo() throws XMLStreamException
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
return this;
}
// AttributeInfo impl:
//public int getAttributeCount()
public int findAttributeIndex(String nsURI, String localName)
{
if (_currEvent != START_ELEMENT) {
reportWrongState(ERR_STATE_NOT_START_ELEM);
}
Element elem = (Element) _currNode;
NamedNodeMap attrs = elem.getAttributes();
if (nsURI != null && nsURI.length() == 0) {
nsURI = null;
}
// Ugh. Horrible clumsy code. But has to do...
for (int i = 0, len = attrs.getLength(); i < len; ++i) {
Node attr = attrs.item(i);
String ln = _safeGetLocalName(attr);
if (localName.equals(ln)) {
String thisUri = attr.getNamespaceURI();
boolean isEmpty = (thisUri == null) || thisUri.length() == 0;
if (nsURI == null) {
if (isEmpty) {
return i;
}
} else {
if (!isEmpty && nsURI.equals(thisUri)) {
return i;
}
}
}
}
return -1;
}
public int getIdAttributeIndex()
{
// !!! TBI
// Note: will need Dom3 level support (JDK 1.5)
return -1;
}
public int getNotationAttributeIndex()
{
// !!! TBI
// Note: will need Dom3 level support (JDK 1.5)
return -1;
}
// // // StAX2, Additional DTD access
/**
* Since this class implements {@link DTDInfo}, method can just
* return <code>this</code>.
*/
public DTDInfo getDTDInfo() throws XMLStreamException
{
/* Let's not allow it to be accessed during other events -- that
* way callers won't count on it being available afterwards.
*/
if (_currEvent != DTD) {
return null;
}
return this;
}
// // // StAX2, Additional location information
/**
* Location information is always accessible, for this reader.
*/
public final LocationInfo getLocationInfo() {
return this;
}
// // // StAX2, Pass-through text accessors
/**
* Method similar to {@link #getText()}, except
* that it just uses provided Writer to write all textual content.
* For further optimization, it may also be allowed to do true
* pass-through, thus possibly avoiding one temporary copy of the
* data.
*<p>
* TODO: try to optimize to allow completely streaming pass-through:
* currently will still read all data in memory buffers before
* outputting
*
* @param w Writer to use for writing textual contents
* @param preserveContents If true, reader has to preserve contents
* so that further calls to <code>getText</code> will return
* proper conntets. If false, reader is allowed to skip creation
* of such copies: this can improve performance, but it also means
* that further calls to <code>getText</code> is not guaranteed to
* return meaningful data.
*
* @return Number of characters written to the reader
*/
public int getText(Writer w, boolean preserveContents)
throws IOException, XMLStreamException
{
String text = getText();
w.write(text);
return text.length();
}
// // // StAX 2, Other accessors
/**
* @return Number of open elements in the stack; 0 when parser is in
* prolog/epilog, 1 inside root element and so on.
*/
public int getDepth() {
return _depth;
}
/**
* @return True, if cursor points to a start or end element that is
* constructed from 'empty' element (ends with '/>');
* false otherwise.
*/
public boolean isEmptyElement() throws XMLStreamException
{
// No way to really figure it out via DOM is there?
return false;
}
public NamespaceContext getNonTransientNamespaceContext()
{
/* Since DOM does not expose enough functionality to figure
* out complete declaration stack, can not implement.
* Can either return null, or a dummy instance. For now, let's
* do latter:
*/
return EmptyNamespaceContext.getInstance();
}
public String getPrefixedName()
{
switch (_currEvent) {
case START_ELEMENT:
case END_ELEMENT:
{
String prefix = _currNode.getPrefix();
String ln = _safeGetLocalName(_currNode);
if (prefix == null) {
return _internName(ln);
}
StringBuilder sb = new StringBuilder(ln.length() + 1 + prefix.length());
sb.append(prefix);
sb.append(':');
sb.append(ln);
return _internName(sb.toString());
}
case ENTITY_REFERENCE:
return getLocalName();
case PROCESSING_INSTRUCTION:
return getPITarget();
case DTD:
return getDTDRootName();
}
throw new IllegalStateException("Current state ("+Stax2Util.eventTypeDesc(_currEvent)+") not START_ELEMENT, END_ELEMENT, ENTITY_REFERENCE, PROCESSING_INSTRUCTION or DTD");
}
public void closeCompletely() throws XMLStreamException
{
// Nothing special to do...
}
/*
////////////////////////////////////////////////////
// DTDInfo implementation (StAX 2)
////////////////////////////////////////////////////
*/
public Object getProcessedDTD() {
return null;
}
public String getDTDRootName() {
if (_currEvent == DTD) {
return _internName(((DocumentType) _currNode).getName());
}
return null;
}
public String getDTDPublicId() {
if (_currEvent == DTD) {
return ((DocumentType) _currNode).getPublicId();
}
return null;
}
public String getDTDSystemId() {
if (_currEvent == DTD) {
return ((DocumentType) _currNode).getSystemId();
}
return null;
}
/**
* @return Internal subset portion of the DOCTYPE declaration, if any;
* empty String if none
*/
public String getDTDInternalSubset() {
/* DOM (level 3) doesn't expose anything extra; would need to
* synthetize subset... which would only contain some of the
* entity and notation declarations.
*/
return null;
}
// // StAX2, v2.0
public DTDValidationSchema getProcessedDTDSchema() {
return null;
}
/*
////////////////////////////////////////////////////
// LocationInfo implementation (StAX 2)
////////////////////////////////////////////////////
*/
// // // First, the "raw" offset accessors:
public long getStartingByteOffset() {
// !!! TBI
return -1L;
}
public long getStartingCharOffset() {
// !!! TBI
return 0;
}
public long getEndingByteOffset() throws XMLStreamException
{
// !!! TBI
return -1;
}
public long getEndingCharOffset() throws XMLStreamException
{
// !!! TBI
return -1;
}
// // // and then the object-based access methods:
public XMLStreamLocation2 getStartLocation()
{
return XMLStreamLocation2.NOT_AVAILABLE;
}
public XMLStreamLocation2 getCurrentLocation()
{
return XMLStreamLocation2.NOT_AVAILABLE;
}
public final XMLStreamLocation2 getEndLocation()
throws XMLStreamException
{
return XMLStreamLocation2.NOT_AVAILABLE;
}
/*
////////////////////////////////////////////////////
// Stax2 validation: !!! TODO
////////////////////////////////////////////////////
*/
public XMLValidator validateAgainst(XMLValidationSchema schema)
throws XMLStreamException
{
// Not implemented by the basic reader:
return null;
}
public XMLValidator stopValidatingAgainst(XMLValidationSchema schema)
throws XMLStreamException
{
// Not implemented by the basic reader:
return null;
}
public XMLValidator stopValidatingAgainst(XMLValidator validator)
throws XMLStreamException
{
// Not implemented by the basic reader:
return null;
}
public ValidationProblemHandler setValidationProblemHandler(ValidationProblemHandler h)
{
// Not implemented by the basic reader
return null;
}
/*
////////////////////////////////////////////
// Internal methods, text gathering
////////////////////////////////////////////
*/
/**
* @param initialType Type of the first textual event
*/
protected void coalesceText(int initialType)
{
_textBuffer.reset();
_textBuffer.append(_currNode.getNodeValue());
Node n;
while ((n = _currNode.getNextSibling()) != null) {
int type = n.getNodeType();
if (type != Node.TEXT_NODE && type != Node.CDATA_SECTION_NODE) {
break;
}
_currNode = n;
_textBuffer.append(_currNode.getNodeValue());
}
_coalescedText = _textBuffer.get();
// Either way, type gets always set to be CHARACTERS
_currEvent = CHARACTERS;
}
/*
////////////////////////////////////////////
// Internal methods, namespace support
////////////////////////////////////////////
*/
private QName _constructQName(String uri, String ln, String prefix)
{
// Stupid QName impls barf on nulls...
return new QName(_internNsURI(uri), _internName(ln), _internName(prefix));
}
/**
* @param attrsToo Whether to include actual attributes too, or
* just namespace declarations
*/
private void _calcNsAndAttrLists(boolean attrsToo)
{
NamedNodeMap attrsIn = _currNode.getAttributes();
// A common case: neither attrs nor ns decls, can use short-cut
int len = attrsIn.getLength();
if (len == 0) {
_attrList = Collections.emptyList();
_nsDeclList = Collections.emptyList();
return;
}
if (!_cfgNsAware) {
_attrList = new ArrayList<Node>(len);
for (int i = 0; i < len; ++i) {
_attrList.add(attrsIn.item(i));
}
_nsDeclList = Collections.emptyList();
return;
}
// most should be attributes... and possibly no ns decls:
ArrayList<Node> attrsOut = null;
ArrayList<String> nsOut = null;
for (int i = 0; i < len; ++i) {
Node attr = attrsIn.item(i);
String prefix = attr.getPrefix();
// Prefix?
if (prefix == null || prefix.length() == 0) { // nope
// default ns decl?
if (!"xmlns".equals(attr.getLocalName())) { // nope
if (attrsToo) {
if (attrsOut == null) {
attrsOut = new ArrayList<Node>(len - i);
}
attrsOut.add(attr);
}
continue;
}
prefix = null;
} else { // explicit ns decl?
if (!"xmlns".equals(prefix)) { // nope
if (attrsToo) {
if (attrsOut == null) {
attrsOut = new ArrayList<Node>(len - i);
}
attrsOut.add(attr);
}
continue;
}
prefix = attr.getLocalName();
}
if (nsOut == null) {
nsOut = new ArrayList<String>((len - i) * 2);
}
nsOut.add(_internName(prefix));
nsOut.add(_internNsURI(attr.getNodeValue()));
}
if (attrsOut == null) {
_attrList = Collections.emptyList();
} else {
_attrList = attrsOut;
}
if (nsOut == null) {
_nsDeclList = Collections.emptyList();
} else {
_nsDeclList= nsOut;
}
}
private void handleIllegalAttrIndex(int index)
{
Element elem = (Element) _currNode;
NamedNodeMap attrs = elem.getAttributes();
int len = attrs.getLength();
String msg = "Illegal attribute index "+index+"; element <"+elem.getNodeName()+"> has "+((len == 0) ? "no" : String.valueOf(len))+" attributes";
throw new IllegalArgumentException(msg);
}
private void handleIllegalNsIndex(int index)
{
String msg = "Illegal namespace declaration index "+index+" (has "+getNamespaceCount()+" ns declarations)";
throw new IllegalArgumentException(msg);
}
/**
* Due to differences in how namespace-aware and non-namespace modes
* work in DOM, different methods are needed. We may or may not be
* able to detect namespace-awareness mode of the source Nodes
* directly; but at any rate, should contain some logic for handling
* problem cases.
*/
private String _safeGetLocalName(Node n)
{
String ln = n.getLocalName();
if (ln == null) {
ln = n.getNodeName();
}
return ln;
}
/*
///////////////////////////////////////////////
// Overridable error reporting methods
///////////////////////////////////////////////
*/
protected void reportWrongState(int errorType)
{
throw new IllegalStateException(findErrorDesc(errorType, _currEvent));
}
protected void reportParseProblem(int errorType)
throws XMLStreamException
{
throwStreamException(findErrorDesc(errorType, _currEvent));
}
protected void throwStreamException(String msg)
throws XMLStreamException
{
throwStreamException(msg, getErrorLocation());
}
protected Location getErrorLocation()
{
Location loc = getCurrentLocation();
if (loc == null) {
loc = getLocation();
}
return loc;
}
/**
* Method called to wrap or convert given conversion-fail exception
* into a full {@link TypedXMLStreamException},
*
* @param iae Problem as reported by converter
* @param lexicalValue Lexical value (element content, attribute value)
* that could not be converted succesfully.
*/
protected TypedXMLStreamException _constructTypeException(IllegalArgumentException iae, String lexicalValue)
{
String msg = iae.getMessage();
if (msg == null) {
msg = "";
}
Location loc = getStartLocation();
if (loc == null) {
return new TypedXMLStreamException(lexicalValue, msg, iae);
}
return new TypedXMLStreamException(lexicalValue, msg, loc);
}
protected TypedXMLStreamException _constructTypeException(String msg, String lexicalValue)
{
Location loc = getStartLocation();
if (loc == null) {
return new TypedXMLStreamException(lexicalValue, msg);
}
return new TypedXMLStreamException(lexicalValue, msg, loc);
}
/*
///////////////////////////////////////////////
// Other internal methods
///////////////////////////////////////////////
*/
protected ValueDecoderFactory _decoderFactory()
{
if (_decoderFactory == null) {
_decoderFactory = new ValueDecoderFactory();
}
return _decoderFactory;
}
protected StringBase64Decoder _base64Decoder()
{
if (_base64Decoder == null) {
_base64Decoder = new StringBase64Decoder();
}
return _base64Decoder;
}
/**
* Method used to locate error message description to use.
* Calls sub-classes <code>findErrorDesc()</code> first, and only
* if no message found, uses default messages defined here.
*/
protected String findErrorDesc(int errorType, int currEvent)
{
String evtDesc = Stax2Util.eventTypeDesc(currEvent);
switch (errorType) {
case ERR_STATE_NOT_START_ELEM:
return "Current event "+evtDesc+", needs to be START_ELEMENT";
case ERR_STATE_NOT_ELEM:
return "Current event "+evtDesc+", needs to be START_ELEMENT or END_ELEMENT";
case ERR_STATE_NO_LOCALNAME:
return "Current event ("+evtDesc+") has no local name";
case ERR_STATE_NOT_PI:
return "Current event ("+evtDesc+") needs to be PROCESSING_INSTRUCTION";
case ERR_STATE_NOT_TEXTUAL:
return "Current event ("+evtDesc+") not a textual event";
case ERR_STATE_NOT_TEXTUAL_OR_ELEM:
return "Current event ("+evtDesc+" not START_ELEMENT, END_ELEMENT, CHARACTERS or CDATA";
case ERR_STATE_NOT_TEXTUAL_XXX:
return "Current event "+evtDesc+", needs to be one of CHARACTERS, CDATA, SPACE or COMMENT";
}
// should never happen, but it'd be bad to throw another exception...
return "Internal error (unrecognized error type: "+errorType+")";
}
/**
* Method called to do additional intern()ing for a name, if and as
* necessary
*/
protected String _internName(String name)
{
if (name == null) {
return "";
}
return _cfgInternNames ? name.intern() : name;
}
protected String _internNsURI(String uri)
{
if (uri == null) {
return "";
}
return _cfgInternNsURIs ? uri.intern() : uri;
}
}