src.java.com.ctc.wstx.dom.DOMWrappingReader Maven / Gradle / Ivy
/* Woodstox XML processor
*
* Copyright (c) 2004- 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.ctc.wstx.dom;
import java.io.IOException;
import java.io.Writer;
import java.text.MessageFormat;
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.XMLInputFactory2;
import org.codehaus.stax2.XMLStreamLocation2;
import org.codehaus.stax2.XMLStreamReader2;
import org.codehaus.stax2.validation.DTDValidationSchema;
import org.codehaus.stax2.validation.ValidationProblemHandler;
import org.codehaus.stax2.validation.XMLValidationSchema;
import org.codehaus.stax2.validation.XMLValidator;
import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.exc.WstxParsingException;
import com.ctc.wstx.io.WstxInputLocation;
import com.ctc.wstx.util.EmptyIterator;
import com.ctc.wstx.util.EmptyNamespaceContext;
import com.ctc.wstx.util.SingletonIterator;
import com.ctc.wstx.util.TextAccumulator;
/**
* This is an adapter class that presents a DOM document as if it was
* a regular {@link XMLStreamReader}.
*
* Note that the implementation is only to be used for use with
* javax.xml.transform.dom.DOMSource
. It can however be
* used for both full documents, and single element root fragments,
* depending on what node is passed as the argument.
*
* Some notes regarding missing/incomplete functionality:
*
* - 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).
*
* - No location info is provided, since (you guessed it!) DOM
* does not provide that info.
*
*
*/
public class DOMWrappingReader
implements XMLStreamReader2,
DTDInfo, LocationInfo, NamespaceContext,
XMLStreamConstants
{
// // // 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_ELEMENT_TEXT =
(1 << CHARACTERS) | (1 << CDATA) | (1 << SPACE)
| (1 << ENTITY_REFERENCE);
// // // Configuration:
protected final ReaderConfig mConfig;
protected final String mSystemId;
protected final Node mRootNode;
protected final boolean mNsAware;
/**
* Whether stream reader is to coalesce adjacent textual
* (CHARACTERS, SPACE, CDATA) events (as per property
* {@link XMLInputFactory#IS_COALESCING}) or not
*/
protected final boolean mCoalescing;
// // // State:
protected int mCurrEvent = START_DOCUMENT;
/**
* Current node is the DOM node that contains information
* regarding the current event.
*/
protected Node mCurrNode;
protected int mDepth = 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 mCoalescedText;
/**
* Helper object used for combining segments of text as needed
*/
protected TextAccumulator mTextBuffer = new TextAccumulator();
// // // 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.
*
*/
protected List mAttrList = 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 mNsDeclList = null;
/*
////////////////////////////////////////////////////
// Construction
////////////////////////////////////////////////////
*/
/**
* @param cfg Configuration of this reader
* @param treeRoot Node that is the tree of the DOM document, or
* fragment.
*/
private DOMWrappingReader(ReaderConfig cfg, Node treeRoot, String sysId)
throws XMLStreamException
{
if (treeRoot == null) {
throw new IllegalArgumentException("Can not pass null Node for constructing a DOM-based XMLStreamReader");
}
mConfig = cfg;
mNsAware = cfg.willSupportNamespaces();
mCoalescing = cfg.willCoalesceText();
mSystemId = sysId;
/* 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());
}
mRootNode = mCurrNode = treeRoot;
}
public static DOMWrappingReader createFrom(ReaderConfig cfg, DOMSource src)
throws XMLStreamException
{
Node rootNode = src.getNode();
String systemId = src.getSystemId();
return new DOMWrappingReader(cfg, rootNode, systemId);
}
/*
////////////////////////////////////////////////////
// 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 Object getProperty(String name)
{
if (name.equals("javax.xml.stream.entities")) {
// !!! TBI
return Collections.EMPTY_LIST;
}
if (name.equals("javax.xml.stream.notations")) {
// !!! TBI
return Collections.EMPTY_LIST;
}
// [WSTX-162]: no way to cleanly enable name/nsURI interning
if (XMLInputFactory2.P_INTERN_NAMES.equals(name)
|| XMLInputFactory2.P_INTERN_NS_URIS.equals(name)) {
return Boolean.FALSE;
}
return mConfig.getProperty(name);
}
/*
////////////////////////////////////////////////////
// XMLStreamReader, current state
////////////////////////////////////////////////////
*/
// // // Attribute access:
public int getAttributeCount()
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
return mAttrList.size();
}
public String getAttributeLocalName(int index)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
if (index >= mAttrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) mAttrList.get(index);
return safeGetLocalName(attr);
}
public QName getAttributeName(int index)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
if (index >= mAttrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) mAttrList.get(index);
return constructQName(attr.getNamespaceURI(), safeGetLocalName(attr),
attr.getPrefix());
}
public String getAttributeNamespace(int index)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
if (index >= mAttrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) mAttrList.get(index);
return attr.getNamespaceURI();
}
public String getAttributePrefix(int index)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
if (index >= mAttrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) mAttrList.get(index);
return attr.getPrefix();
}
public String getAttributeType(int index)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
if (index >= mAttrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
//Attr attr = (Attr) mAttrList.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 (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
if (mAttrList == null) {
calcNsAndAttrLists(true);
}
if (index >= mAttrList.size() || index < 0) {
handleIllegalAttrIndex(index);
return null;
}
Attr attr = (Attr) mAttrList.get(index);
return attr.getValue();
}
public String getAttributeValue(String nsURI, String localName)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
Element elem = (Element) mCurrNode;
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:
*
* 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.
*
Precondition: the current event is START_ELEMENT.
*
Postcondition: the current event is the corresponding END_ELEMENT.
*
*/
public String getElementText()
throws XMLStreamException
{
if (mCurrEvent != START_ELEMENT) {
throwParseError(ErrorConsts.ERR_STATE_NOT_STELEM);
}
/**
* 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) {
throwParseError("Expected a text token, got "+ErrorConsts.tokenTypeDesc(type)+".");
}
mTextBuffer.addText(getText());
}
return mTextBuffer.getAndClear();
}
/**
* Returns type of the last event returned; or START_DOCUMENT before
* any events has been explicitly returned.
*/
public int getEventType()
{
return mCurrEvent;
}
public String getLocalName()
{
if (mCurrEvent == START_ELEMENT || mCurrEvent == END_ELEMENT) {
return safeGetLocalName(mCurrNode);
}
if (mCurrEvent == ENTITY_REFERENCE) {
return mCurrNode.getNodeName();
}
throw new IllegalStateException("Current state ("+ErrorConsts.tokenTypeDesc(mCurrEvent)+") not START_ELEMENT, END_ELEMENT or ENTITY_REFERENCE");
}
// // // getLocation() defined in StreamScanner
public QName getName()
{
if (mCurrEvent != START_ELEMENT && mCurrEvent != END_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM);
}
return constructQName(mCurrNode.getNamespaceURI(), safeGetLocalName(mCurrNode), mCurrNode.getPrefix());
}
// // // Namespace access
public NamespaceContext getNamespaceContext() {
return this;
}
public int getNamespaceCount() {
if (mCurrEvent != START_ELEMENT && mCurrEvent != END_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM);
}
if (mNsDeclList == null) {
if (!mNsAware) {
return 0;
}
calcNsAndAttrLists(mCurrEvent == START_ELEMENT);
}
return mNsDeclList.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 (mCurrEvent != START_ELEMENT && mCurrEvent != END_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM);
}
if (mNsDeclList == null) {
if (!mNsAware) {
handleIllegalNsIndex(index);
}
calcNsAndAttrLists(mCurrEvent == START_ELEMENT);
}
if (index < 0 || (index + index) >= mNsDeclList.size()) {
handleIllegalNsIndex(index);
}
return (String) mNsDeclList.get(index + index);
}
public String getNamespaceURI() {
if (mCurrEvent != START_ELEMENT && mCurrEvent != END_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM);
}
return mCurrNode.getNamespaceURI();
}
public String getNamespaceURI(int index) {
if (mCurrEvent != START_ELEMENT && mCurrEvent != END_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM);
}
if (mNsDeclList == null) {
if (!mNsAware) {
handleIllegalNsIndex(index);
}
calcNsAndAttrLists(mCurrEvent == START_ELEMENT);
}
if (index < 0 || (index + index) >= mNsDeclList.size()) {
handleIllegalNsIndex(index);
}
return (String) mNsDeclList.get(index + index + 1);
}
// Note: implemented as part of NamespaceContext
//public String getNamespaceURI(String prefix)
public String getPIData() {
if (mCurrEvent != PROCESSING_INSTRUCTION) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_PI);
}
return mCurrNode.getNodeValue();
}
public String getPITarget() {
if (mCurrEvent != PROCESSING_INSTRUCTION) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_PI);
}
return mCurrNode.getNodeName();
}
public String getPrefix() {
if (mCurrEvent != START_ELEMENT && mCurrEvent != END_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_ELEM);
}
return mCurrNode.getPrefix();
}
public String getText()
{
if (mCoalescedText != null) {
return mCoalescedText;
}
if (((1 << mCurrEvent) & MASK_GET_TEXT) == 0) {
throwNotTextual(mCurrEvent);
}
return mCurrNode.getNodeValue();
}
public char[] getTextCharacters()
{
String text = getText();
return text.toCharArray();
}
public int getTextCharacters(int sourceStart, char[] target, int targetStart, int len)
{
if (((1 << mCurrEvent) & MASK_GET_TEXT) == 0) {
throwNotTextual(mCurrEvent);
}
String text = getText();
if (len > text.length()) {
len = text.length();
}
text.getChars(sourceStart, sourceStart+len, target, targetStart);
return len;
}
public int getTextLength()
{
if (((1 << mCurrEvent) & MASK_GET_TEXT) == 0) {
throwNotTextual(mCurrEvent);
}
return getText().length();
}
public int getTextStart()
{
if (((1 << mCurrEvent) & MASK_GET_TEXT) == 0) {
throwNotTextual(mCurrEvent);
}
return 0;
}
public boolean hasName() {
return (mCurrEvent == START_ELEMENT) || (mCurrEvent == END_ELEMENT);
}
public boolean hasNext() {
return (mCurrEvent != END_DOCUMENT);
}
public boolean hasText() {
return (((1 << mCurrEvent) & MASK_GET_TEXT) != 0);
}
public boolean isAttributeSpecified(int index)
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
Element elem = (Element) mCurrNode;
Attr attr = (Attr) elem.getAttributes().item(index);
if (attr == null) {
handleIllegalAttrIndex(index);
return false;
}
return attr.getSpecified();
}
public boolean isCharacters()
{
return (mCurrEvent == CHARACTERS);
}
public boolean isEndElement() {
return (mCurrEvent == END_ELEMENT);
}
public boolean isStartElement() {
return (mCurrEvent == START_ELEMENT);
}
/**
*
* 05-Apr-2004, TSa: Could try to determine status when text is actually
* read. That'd prevent double reads... but would it slow down that
* one reading so that net effect would be negative?
*/
public boolean isWhiteSpace()
{
if (mCurrEvent == CHARACTERS || mCurrEvent == 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) > 0x0020) {
return false;
}
}
return true;
}
return (mCurrEvent == SPACE);
}
public void require(int type, String nsUri, String localName)
throws XMLStreamException
{
int curr = mCurrEvent;
/* 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) {
throwParseError("Expected type "+ErrorConsts.tokenTypeDesc(type)
+", current type "
+ErrorConsts.tokenTypeDesc(curr));
}
if (localName != null) {
if (curr != START_ELEMENT && curr != END_ELEMENT
&& curr != ENTITY_REFERENCE) {
throwParseError("Expected non-null local name, but current token not a START_ELEMENT, END_ELEMENT or ENTITY_REFERENCE (was "+ErrorConsts.tokenTypeDesc(mCurrEvent)+")");
}
String n = getLocalName();
if (n != localName && !n.equals(localName)) {
throwParseError("Expected local name '"+localName+"'; current local name '"+n+"'.");
}
}
if (nsUri != null) {
if (curr != START_ELEMENT && curr != END_ELEMENT) {
throwParseError("Expected non-null NS URI, but current token not a START_ELEMENT or END_ELEMENT (was "+ErrorConsts.tokenTypeDesc(curr)+")");
}
String uri = getNamespaceURI();
// No namespace?
if (nsUri.length() == 0) {
if (uri != null && uri.length() > 0) {
throwParseError("Expected empty namespace, instead have '"+uri+"'.");
}
} else {
if ((nsUri != uri) && !nsUri.equals(uri)) {
throwParseError("Expected namespace '"+nsUri+"'; have '"
+uri+"'.");
}
}
}
// Ok, fine, all's good
}
/*
////////////////////////////////////////////////////
// XMLStreamReader, iterating
////////////////////////////////////////////////////
*/
public int next()
throws XMLStreamException
{
mCoalescedText = 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 (mCurrEvent) {
case START_DOCUMENT: // initial state
/* What to do here depends on what kind of node we started
* with...
*/
switch (mCurrNode.getNodeType()) {
case Node.DOCUMENT_NODE:
case Node.DOCUMENT_FRAGMENT_NODE:
// For doc, fragment, need to find first child
mCurrNode = mCurrNode.getFirstChild();
break;
case Node.ELEMENT_NODE:
// For element, curr node is fine:
return (mCurrEvent = START_ELEMENT);
default:
throw new XMLStreamException("Internal error: unexpected DOM root node type "+mCurrNode.getNodeType()+" for node '"+mCurrNode+"'");
}
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
++mDepth;
mAttrList = null; // so it will not get reused accidentally
{
Node firstChild = mCurrNode.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 (mCurrEvent = END_ELEMENT);
}
mNsDeclList = null;
/* non-empty is easy: let's just swap curr node, and
* fall through to regular handling
*/
mCurrNode = firstChild;
break;
}
case END_ELEMENT:
--mDepth;
// Need to clear these lists
mAttrList = null;
mNsDeclList = 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 (mCurrNode == mRootNode) {
return (mCurrEvent = 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 = mCurrNode.getNextSibling();
// If sibling, let's just assign and fall through
if (next != null) {
mCurrNode = 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)
*/
mCurrNode = mCurrNode.getParentNode();
int type = mCurrNode.getNodeType();
if (type == Node.ELEMENT_NODE) {
return (mCurrEvent = END_ELEMENT);
}
// Let's do sanity check; should really be Doc/DocFragment
if (mCurrNode != mRootNode ||
(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 (mCurrEvent = END_DOCUMENT);
}
}
// Ok, need to determine current node type:
switch (mCurrNode.getNodeType()) {
case Node.CDATA_SECTION_NODE:
if (mCoalescing) {
coalesceText(CDATA);
} else {
mCurrEvent = CDATA;
}
break;
case Node.COMMENT_NODE:
mCurrEvent = COMMENT;
break;
case Node.DOCUMENT_TYPE_NODE:
mCurrEvent = DTD;
break;
case Node.ELEMENT_NODE:
mCurrEvent = START_ELEMENT;
break;
case Node.ENTITY_REFERENCE_NODE:
mCurrEvent = ENTITY_REFERENCE;
break;
case Node.PROCESSING_INSTRUCTION_NODE:
mCurrEvent = PROCESSING_INSTRUCTION;
break;
case Node.TEXT_NODE:
if (mCoalescing) {
coalesceText(CHARACTERS);
} else {
mCurrEvent = 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 "+mCurrNode.getNodeType()+" (attr/entity/notation?), for node '"+mCurrNode+"'");
default:
throw new XMLStreamException("Internal error: unrecognized DOM node type "+mCurrNode.getNodeType()+", for node '"+mCurrNode+"'");
}
return mCurrEvent;
}
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;
}
throwParseError("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;
}
throwParseError("Received event "+ErrorConsts.tokenTypeDesc(next)
+", instead of START_ELEMENT or END_ELEMENT.");
}
}
/**
*
* 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 mCurrNode.lookupNamespaceURI(null);
}
return mCurrNode.lookupNamespaceURI(prefix);
*/
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 = mCurrNode.lookupPrefix(namespaceURI);
if (prefix == null) { // maybe default NS?
String defURI = mCurrNode.lookupNamespaceURI(null);
if (defURI != null && defURI.equals(namespaceURI)) {
return "";
}
}
return prefix;
*/
return null;
}
public Iterator getPrefixes(String namespaceURI)
{
String prefix = getPrefix(namespaceURI);
if (prefix == null) {
return EmptyIterator.getInstance();
}
return new SingletonIterator(prefix);
}
/*
////////////////////////////////////////////////////
// XMLStreamReader2 (StAX2) implementation
////////////////////////////////////////////////////
*/
// // // StAX2, per-reader configuration
public Object getFeature(String name)
{
// No readable features defined yet...
throw new IllegalArgumentException(MessageFormat.format(ErrorConsts.ERR_UNKNOWN_FEATURE, new Object[] { name }));
}
public void setFeature(String name, Object value)
{
// Base-class has no settable features at this point.
throw new IllegalArgumentException(MessageFormat.format(ErrorConsts.ERR_UNKNOWN_FEATURE, new Object[] { name }));
}
// NOTE: getProperty() defined in Stax 1.0 interface
public boolean isPropertySupported(String name) {
// !!! TBI: not all these properties are really supported
return mConfig.isPropertySupported(name);
}
/**
* @param name Name of the property to set
* @param value Value to set property to.
*
* @return True, if the specified property was succesfully
* set to specified value; false if its value was not changed
*/
public boolean setProperty(String name, Object value)
{
/* Note: can not call local method, since it'll return false for
* recognized but non-mutable properties
*/
if (XMLInputFactory2.P_INTERN_NAMES.equals(name)
|| XMLInputFactory2.P_INTERN_NS_URIS.equals(name)) {
/* [WTSX-162]: Name/Namespace URI interning seemingly enabled,
* isn't. Alas, not easy to enable it, so let's force it to
* always be disabled
*/
if (!(value instanceof Boolean) || ((Boolean) value).booleanValue()) {
throw new IllegalArgumentException("DOM-based reader does not support interning of names or namespace URIs");
}
return true;
}
return mConfig.setProperty(name, value);
}
// // // StAX2, additional traversal methods
public void skipElement() throws XMLStreamException
{
if (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
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 (mCurrEvent != START_ELEMENT) {
throw new IllegalStateException(ErrorConsts.ERR_STATE_NOT_STELEM);
}
// !!! TBI
return null;
}
// // // StAX2, Additional DTD access
/**
* Since this class implements {@link DTDInfo}, method can just
* return this
.
*/
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 (mCurrEvent != 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.
*
* 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 getText
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 getText
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
{
if (((1 << mCurrEvent) & MASK_GET_TEXT) == 0) {
throwNotTextual(mCurrEvent);
}
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 mDepth;
}
/**
* @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 (mCurrEvent) {
case START_ELEMENT:
case END_ELEMENT:
{
String prefix = getPrefix();
String ln = getLocalName();
if (prefix == null) {
return ln;
}
StringBuffer sb = new StringBuffer(ln.length() + 1 + prefix.length());
sb.append(prefix);
sb.append(':');
sb.append(ln);
return sb.toString();
}
case ENTITY_REFERENCE:
return getLocalName();
case PROCESSING_INSTRUCTION:
return getPITarget();
case DTD:
return getDTDRootName();
}
throw new IllegalStateException("Current state ("+ErrorConsts.tokenTypeDesc(mCurrEvent)+") 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 (mCurrEvent == DTD) {
return ((DocumentType) mCurrNode).getName();
}
return null;
}
public String getDTDPublicId() {
if (mCurrEvent == DTD) {
return ((DocumentType) mCurrNode).getPublicId();
}
return null;
}
public String getDTDSystemId() {
if (mCurrEvent == DTD) {
return ((DocumentType) mCurrNode).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 final Location getLocation() {
return getStartLocation();
}
public XMLStreamLocation2 getStartLocation()
{
// !!! TBI
return null;
}
public XMLStreamLocation2 getCurrentLocation()
{
// !!! TBI
return null;
}
public final XMLStreamLocation2 getEndLocation()
throws XMLStreamException
{
// !!! TBI
return null;
}
/*
////////////////////////////////////////////////////
// Stax2 validation
////////////////////////////////////////////////////
*/
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
////////////////////////////////////////////
*/
protected void coalesceText(int initialType)
{
mTextBuffer.addText(mCurrNode.getNodeValue());
Node n;
while ((n = mCurrNode.getNextSibling()) != null) {
int type = n.getNodeType();
if (type != Node.TEXT_NODE && type != Node.CDATA_SECTION_NODE) {
break;
}
mCurrNode = n;
mTextBuffer.addText(mCurrNode.getNodeValue());
}
mCoalescedText = mTextBuffer.getAndClear();
// Either way, type gets always set to be CHARACTERS
mCurrEvent = CHARACTERS;
}
/*
////////////////////////////////////////////
// Internal methods
////////////////////////////////////////////
*/
private QName constructQName(String uri, String ln, String prefix)
{
// Stupid QName impls barf on nulls...
return new QName((uri == null) ? "" : uri, ln,
(prefix == null) ? "" : prefix);
}
/**
* @param attrsToo Whether to include actual attributes too, or
* just namespace declarations
*/
private void calcNsAndAttrLists(boolean attrsToo)
{
NamedNodeMap attrsIn = mCurrNode.getAttributes();
// A common case: neither attrs nor ns decls, can use short-cut
int len = attrsIn.getLength();
if (len == 0) {
mAttrList = mNsDeclList = Collections.EMPTY_LIST;
return;
}
if (!mNsAware) {
mAttrList = new ArrayList(len);
for (int i = 0; i < len; ++i) {
mAttrList.add(attrsIn.item(i));
}
mNsDeclList = Collections.EMPTY_LIST;
return;
}
// most should be attributes... and possibly no ns decls:
ArrayList attrsOut = null;
ArrayList 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(len - i);
}
attrsOut.add(attr);
}
continue;
}
prefix = "";
} else { // explicit ns decl?
if (!"xmlns".equals(prefix)) { // nope
if (attrsToo) {
if (attrsOut == null) {
attrsOut = new ArrayList(len - i);
}
attrsOut.add(attr);
}
continue;
}
prefix = attr.getLocalName();
}
if (nsOut == null) {
nsOut = new ArrayList((len - i) * 2);
}
nsOut.add(prefix);
nsOut.add(attr.getNodeValue());
}
mAttrList = (attrsOut == null) ? Collections.EMPTY_LIST : attrsOut;
mNsDeclList = (nsOut == null) ? Collections.EMPTY_LIST : nsOut;
}
/**
* Method that returns location of the last character returned by this
* reader; that is, location "one less" than the currently pointed to
* location.
*/
protected WstxInputLocation getLastCharLocation()
{
// !!! TBI
return null;
}
private void throwNotTextual(int type)
{
throw new IllegalStateException("Not a textual event ("
+ErrorConsts.tokenTypeDesc(mCurrEvent)+")");
}
private void throwParseError(String msg)
throws WstxParsingException
{
throw new WstxParsingException(msg, getLastCharLocation());
}
/*
private void throwParseError(String format, Object arg)
throws WstxParsingException
{
String msg = MessageFormat.format(format, new Object[] { arg });
throw new WstxParsingException(msg, getLastCharLocation());
}
*/
private void handleIllegalAttrIndex(int index)
{
Element elem = (Element) mCurrNode;
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;
}
}