/* * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.org.jvnet.staxex.util; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import static org.w3c.dom.Node.*; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.stream.Location; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.util.Collections; import java.util.Iterator; /** * Create an {@link XMLStreamReader} on top of a DOM tree. * *
* Since various libraries as well as users often create "incorrect" DOM node, * this class spends a lot of efforts making sure that broken DOM trees are * nevertheless interpreted correctly. * *
* For example, if a DOM level * 1 tree is passed, each method will attempt to return the correct value * by using {@link Node#getNodeName()}. * *
* Similarly, if DOM is missing explicit namespace declarations, * this class attempts to emulate necessary declarations. * * * @author Santiago.PericasGeertsen@sun.com * @author Kohsuke Kawaguchi */ public class DOMStreamReader implements XMLStreamReader, NamespaceContext { /** * Current DOM node being traversed. */ protected Node _current; /** * Starting node of the subtree being traversed. */ private Node _start; /** * Named mapping for attributes and NS decls for the current node. */ private NamedNodeMap _namedNodeMap; /** * If the reader points at {@link #CHARACTERS the text node}, * its whole value. * *
* This is simply a cache of {@link Text#getWholeText()} of {@link #_current},
* but when a large binary data sent as base64 text, this could get very much
* non-trivial.
*/
protected String wholeText;
/**
* List of attributes extracted from
* Makes sure that the namespace URI/prefix used in the given node is available,
* and if not, declare it on the current scope to "fix" it.
*
* It's often common to create DOM trees without putting namespace declarations,
* and this makes sure that such DOM tree will be properly marshalled.
*/
private void ensureNs(Node n) {
String prefix = fixNull(n.getPrefix());
String uri = fixNull(n.getNamespaceURI());
Scope scope = scopes[depth];
String currentUri = scope.getNamespaceURI(prefix);
if(prefix.length()==0) {
currentUri = fixNull(currentUri);
if(currentUri.equals(uri))
return; // declared correctly
} else {
if(currentUri!=null && currentUri.equals(uri))
return; // declared correctly
}
if(prefix.equals("xml") || prefix.equals("xmlns"))
return; // implicitly declared namespaces
// needs to be declared
scope.additionalNamespaces.add(prefix);
scope.additionalNamespaces.add(uri);
}
/**
* Allocate new {@link Scope} for {@link #splitAttributes()}.
*/
private Scope allocateScope() {
if(scopes.length==++depth) {
Scope[] newBuf = new Scope[scopes.length*2];
System.arraycopy(scopes,0,newBuf,0,scopes.length);
scopes = newBuf;
}
Scope scope = scopes[depth];
if(scope==null) {
scope = scopes[depth] = new Scope(scopes[depth-1]);
} else {
scope.reset();
}
return scope;
}
public int getAttributeCount() {
if (_state == START_ELEMENT)
return _currentAttributes.size();
throw new IllegalStateException("DOMStreamReader: getAttributeCount() called in illegal state");
}
/**
* Return an attribute's local name. Handle the case of DOM level 1 nodes.
*/
public String getAttributeLocalName(int index) {
if (_state == START_ELEMENT) {
String localName = _currentAttributes.get(index).getLocalName();
return (localName != null) ? localName :
QName.valueOf(_currentAttributes.get(index).getNodeName()).getLocalPart();
}
throw new IllegalStateException("DOMStreamReader: getAttributeLocalName() called in illegal state");
}
/**
* Return an attribute's qname. Handle the case of DOM level 1 nodes.
*/
public QName getAttributeName(int index) {
if (_state == START_ELEMENT) {
Node attr = _currentAttributes.get(index);
String localName = attr.getLocalName();
if (localName != null) {
String prefix = attr.getPrefix();
String uri = attr.getNamespaceURI();
return new QName(fixNull(uri), localName, fixNull(prefix));
}
else {
return QName.valueOf(attr.getNodeName());
}
}
throw new IllegalStateException("DOMStreamReader: getAttributeName() called in illegal state");
}
public String getAttributeNamespace(int index) {
if (_state == START_ELEMENT) {
String uri = _currentAttributes.get(index).getNamespaceURI();
return fixNull(uri);
}
throw new IllegalStateException("DOMStreamReader: getAttributeNamespace() called in illegal state");
}
public String getAttributePrefix(int index) {
if (_state == START_ELEMENT) {
String prefix = _currentAttributes.get(index).getPrefix();
return fixNull(prefix);
}
throw new IllegalStateException("DOMStreamReader: getAttributePrefix() called in illegal state");
}
public String getAttributeType(int index) {
if (_state == START_ELEMENT) {
return "CDATA";
}
throw new IllegalStateException("DOMStreamReader: getAttributeType() called in illegal state");
}
public String getAttributeValue(int index) {
if (_state == START_ELEMENT) {
return _currentAttributes.get(index).getNodeValue();
}
throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
}
public String getAttributeValue(String namespaceURI, String localName) {
if (_state == START_ELEMENT) {
if (_namedNodeMap != null) {
Node attr = _namedNodeMap.getNamedItemNS(namespaceURI, localName);
return attr != null ? attr.getNodeValue() : null;
}
return null;
}
throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
}
public String getCharacterEncodingScheme() {
return null;
}
public String getElementText() throws javax.xml.stream.XMLStreamException {
throw new RuntimeException("DOMStreamReader: getElementText() not implemented");
}
public String getEncoding() {
return null;
}
public int getEventType() {
return _state;
}
/**
* Return an element's local name. Handle the case of DOM level 1 nodes.
*/
public String getLocalName() {
if (_state == START_ELEMENT || _state == END_ELEMENT) {
String localName = _current.getLocalName();
return localName != null ? localName :
QName.valueOf(_current.getNodeName()).getLocalPart();
}
else if (_state == ENTITY_REFERENCE) {
return _current.getNodeName();
}
throw new IllegalStateException("DOMStreamReader: getAttributeValue() called in illegal state");
}
public Location getLocation() {
return DummyLocation.INSTANCE;
}
/**
* Return an element's qname. Handle the case of DOM level 1 nodes.
*/
public javax.xml.namespace.QName getName() {
if (_state == START_ELEMENT || _state == END_ELEMENT) {
String localName = _current.getLocalName();
if (localName != null) {
String prefix = _current.getPrefix();
String uri = _current.getNamespaceURI();
return new QName(fixNull(uri), localName, fixNull(prefix));
}
else {
return QName.valueOf(_current.getNodeName());
}
}
throw new IllegalStateException("DOMStreamReader: getName() called in illegal state");
}
public NamespaceContext getNamespaceContext() {
return this;
}
/**
* Verifies the current state to see if we can return the scope, and do so
* if appropriate.
*
* Used to implement a bunch of StAX API methods that have the same usage restriction.
*/
private Scope getCheckedScope() {
if (_state == START_ELEMENT || _state == END_ELEMENT) {
return scopes[depth];
}
throw new IllegalStateException("DOMStreamReader: neither on START_ELEMENT nor END_ELEMENT");
}
public int getNamespaceCount() {
return getCheckedScope().getNamespaceCount();
}
public String getNamespacePrefix(int index) {
return getCheckedScope().getNamespacePrefix(index);
}
public String getNamespaceURI(int index) {
return getCheckedScope().getNamespaceURI(index);
}
public String getNamespaceURI() {
if (_state == START_ELEMENT || _state == END_ELEMENT) {
String uri = _current.getNamespaceURI();
return fixNull(uri);
}
return null;
}
/**
* This method is not particularly fast, but shouldn't be called very
* often. If we start to use it more, we should keep track of the
* NS declarations using a NamespaceContext implementation instead.
*/
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new IllegalArgumentException("DOMStreamReader: getNamespaceURI(String) call with a null prefix");
}
else if (prefix.equals("xml")) {
return "http://www.w3.org/XML/1998/namespace";
}
else if (prefix.equals("xmlns")) {
return "http://www.w3.org/2000/xmlns/";
}
// check scopes
String nsUri = scopes[depth].getNamespaceURI(prefix);
if(nsUri!=null) return nsUri;
// then ancestors above start node
Node node = findRootElement();
String nsDeclName = prefix.length()==0 ? "xmlns" : "xmlns:"+prefix;
while (node.getNodeType() != DOCUMENT_NODE) {
// Is ns declaration on this element?
NamedNodeMap namedNodeMap = node.getAttributes();
Attr attr = (Attr) namedNodeMap.getNamedItem(nsDeclName);
if (attr != null)
return attr.getValue();
node = node.getParentNode();
}
return null;
}
public String getPrefix(String nsUri) {
if (nsUri == null) {
throw new IllegalArgumentException("DOMStreamReader: getPrefix(String) call with a null namespace URI");
}
else if (nsUri.equals("http://www.w3.org/XML/1998/namespace")) {
return "xml";
}
else if (nsUri.equals("http://www.w3.org/2000/xmlns/")) {
return "xmlns";
}
// check scopes
String prefix = scopes[depth].getPrefix(nsUri);
if(prefix!=null) return prefix;
// then ancestors above start node
Node node = findRootElement();
while (node.getNodeType() != DOCUMENT_NODE) {
// Is ns declaration on this element?
NamedNodeMap namedNodeMap = node.getAttributes();
for( int i=namedNodeMap.getLength()-1; i>=0; i-- ) {
Attr attr = (Attr)namedNodeMap.item(i);
prefix = getPrefixForAttr(attr,nsUri);
if(prefix!=null)
return prefix;
}
node = node.getParentNode();
}
return null;
}
/**
* Finds the root element node of the traversal.
*/
private Node findRootElement() {
int type;
Node node = _start;
while ((type = node.getNodeType()) != DOCUMENT_NODE
&& type != ELEMENT_NODE) {
node = node.getParentNode();
}
return node;
}
/**
* If the given attribute is a namespace declaration for the given namespace URI,
* return its prefix. Otherwise null.
*/
private static String getPrefixForAttr(Attr attr, String nsUri) {
String attrName = attr.getNodeName();
if (!attrName.startsWith("xmlns:") && !attrName.equals("xmlns"))
return null; // not nsdecl
if(attr.getValue().equals(nsUri)) {
if(attrName.equals("xmlns"))
return "";
String localName = attr.getLocalName();
return (localName != null) ? localName :
QName.valueOf(attrName).getLocalPart();
}
return null;
}
public Iterator getPrefixes(String nsUri) {
// This is an incorrect implementation,
// but AFAIK it's not used in the JAX-WS runtime
String prefix = getPrefix(nsUri);
if(prefix==null) return Collections.emptyList().iterator();
else return Collections.singletonList(prefix).iterator();
}
public String getPIData() {
if (_state == PROCESSING_INSTRUCTION) {
return ((ProcessingInstruction) _current).getData();
}
return null;
}
public String getPITarget() {
if (_state == PROCESSING_INSTRUCTION) {
return ((ProcessingInstruction) _current).getTarget();
}
return null;
}
public String getPrefix() {
if (_state == START_ELEMENT || _state == END_ELEMENT) {
String prefix = _current.getPrefix();
return fixNull(prefix);
}
return null;
}
public Object getProperty(String str) throws IllegalArgumentException {
return null;
}
public String getText() {
if (_state == CHARACTERS)
return wholeText;
if(_state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE)
return _current.getNodeValue();
throw new IllegalStateException("DOMStreamReader: getTextLength() called in illegal state");
}
public char[] getTextCharacters() {
return getText().toCharArray();
}
public int getTextCharacters(int sourceStart, char[] target, int targetStart,
int targetLength) throws XMLStreamException {
String text = getText();
int copiedSize = Math.min(targetLength, text.length() - sourceStart);
text.getChars(sourceStart, sourceStart + copiedSize, target, targetStart);
return copiedSize;
}
public int getTextLength() {
return getText().length();
}
public int getTextStart() {
if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) {
return 0;
}
throw new IllegalStateException("DOMStreamReader: getTextStart() called in illegal state");
}
public String getVersion() {
return null;
}
public boolean hasName() {
return (_state == START_ELEMENT || _state == END_ELEMENT);
}
public boolean hasNext() throws javax.xml.stream.XMLStreamException {
return (_state != END_DOCUMENT);
}
public boolean hasText() {
if (_state == CHARACTERS || _state == CDATA || _state == COMMENT || _state == ENTITY_REFERENCE) {
return getText().trim().length() > 0;
}
return false;
}
public boolean isAttributeSpecified(int param) {
return false;
}
public boolean isCharacters() {
return (_state == CHARACTERS);
}
public boolean isEndElement() {
return (_state == END_ELEMENT);
}
public boolean isStandalone() {
return true;
}
public boolean isStartElement() {
return (_state == START_ELEMENT);
}
public boolean isWhiteSpace() {
if (_state == CHARACTERS || _state == CDATA)
return getText().trim().length()==0;
return false;
}
private static int mapNodeTypeToState(int nodetype) {
switch (nodetype) {
case CDATA_SECTION_NODE:
return CDATA;
case COMMENT_NODE:
return COMMENT;
case ELEMENT_NODE:
return START_ELEMENT;
case ENTITY_NODE:
return ENTITY_DECLARATION;
case ENTITY_REFERENCE_NODE:
return ENTITY_REFERENCE;
case NOTATION_NODE:
return NOTATION_DECLARATION;
case PROCESSING_INSTRUCTION_NODE:
return PROCESSING_INSTRUCTION;
case TEXT_NODE:
return CHARACTERS;
default:
throw new RuntimeException("DOMStreamReader: Unexpected node type");
}
}
public int next() throws XMLStreamException {
while(true) {
int r = _next();
switch (r) {
case CHARACTERS:
// if we are currently at text node, make sure that this is a meaningful text node.
Node prev = _current.getPreviousSibling();
if(prev!=null && prev.getNodeType()==Node.TEXT_NODE)
continue; // nope. this is just a continuation of previous text that should be invisible
Text t = (Text)_current;
wholeText = t.getWholeText();
if(wholeText.length()==0)
continue; // nope. this is empty text.
return CHARACTERS;
case START_ELEMENT:
splitAttributes();
return START_ELEMENT;
default:
return r;
}
}
}
protected int _next() throws XMLStreamException {
Node child;
switch (_state) {
case END_DOCUMENT:
throw new IllegalStateException("DOMStreamReader: Calling next() at END_DOCUMENT");
case START_DOCUMENT:
// Don't skip document element if this is a fragment
if (_current.getNodeType() == ELEMENT_NODE) {
return (_state = START_ELEMENT);
}
child = _current.getFirstChild();
if (child == null) {
return (_state = END_DOCUMENT);
}
else {
_current = child;
return (_state = mapNodeTypeToState(_current.getNodeType()));
}
case START_ELEMENT:
child = _current.getFirstChild();
if (child == null) {
return (_state = END_ELEMENT);
}
else {
_current = child;
return (_state = mapNodeTypeToState(_current.getNodeType()));
}
case END_ELEMENT:
case CHARACTERS:
case COMMENT:
case CDATA:
case ENTITY_REFERENCE:
case PROCESSING_INSTRUCTION:
if (_state == END_ELEMENT) depth--;
// If at the end of this fragment, then terminate traversal
if (_current == _start) {
return (_state = END_DOCUMENT);
}
Node sibling = _current.getNextSibling();
if (sibling == null) {
_current = _current.getParentNode();
// getParentNode() returns null for fragments
_state = (_current == null || _current.getNodeType() == DOCUMENT_NODE) ?
END_DOCUMENT : END_ELEMENT;
return _state;
}
else {
_current = sibling;
return (_state = mapNodeTypeToState(_current.getNodeType()));
}
case DTD:
case ATTRIBUTE:
case NAMESPACE:
default:
throw new RuntimeException("DOMStreamReader: Unexpected internal state");
}
}
public int nextTag() throws javax.xml.stream.XMLStreamException {
int eventType = next();
while (eventType == CHARACTERS && isWhiteSpace()
|| eventType == CDATA && isWhiteSpace()
|| eventType == SPACE
|| eventType == PROCESSING_INSTRUCTION
|| eventType == COMMENT)
{
eventType = next();
}
if (eventType != START_ELEMENT && eventType != END_ELEMENT) {
throw new XMLStreamException("DOMStreamReader: Expected start or end tag");
}
return eventType;
}
public void require(int type, String namespaceURI, String localName)
throws javax.xml.stream.XMLStreamException
{
if (type != _state) {
throw new XMLStreamException("DOMStreamReader: Required event type not found");
}
if (namespaceURI != null && !namespaceURI.equals(getNamespaceURI())) {
throw new XMLStreamException("DOMStreamReader: Required namespaceURI not found");
}
if (localName != null && !localName.equals(getLocalName())) {
throw new XMLStreamException("DOMStreamReader: Required localName not found");
}
}
public boolean standaloneSet() {
return true;
}
// -- Debugging ------------------------------------------------------
/*
private static void displayDOM(Node node, java.io.OutputStream ostream) {
try {
System.out.println("\n====\n");
XmlUtil.newTransformer().transform(
new DOMSource(node), new StreamResult(ostream));
System.out.println("\n====\n");
}
catch (Exception e) {
e.printStackTrace();
}
}
private static void verifyDOMIntegrity(Node node) {
switch (node.getNodeType()) {
case ELEMENT_NODE:
case ATTRIBUTE_NODE:
// DOM level 1?
if (node.getLocalName() == null) {
System.out.println("WARNING: DOM level 1 node found");
System.out.println(" -> node.getNodeName() = " + node.getNodeName());
System.out.println(" -> node.getNamespaceURI() = " + node.getNamespaceURI());
System.out.println(" -> node.getLocalName() = " + node.getLocalName());
System.out.println(" -> node.getPrefix() = " + node.getPrefix());
}
if (node.getNodeType() == ATTRIBUTE_NODE) return;
NamedNodeMap attrs = node.getAttributes();
for (int i = 0; i < attrs.getLength(); i++) {
verifyDOMIntegrity(attrs.item(i));
}
case DOCUMENT_NODE:
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
verifyDOMIntegrity(children.item(i));
}
}
}
*/
private static String fixNull(String s) {
if(s==null) return "";
else return s;
}
}
_namedNodeMap
.
*/
private final FinalArrayList_namedNodeMap
*/
final FinalArrayList