/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.apache.commons.jxpath.ri.model.dom;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.jxpath.JXPathAbstractFactoryException;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathException;
import org.apache.commons.jxpath.Pointer;
import org.apache.commons.jxpath.ri.Compiler;
import org.apache.commons.jxpath.ri.NamespaceResolver;
import org.apache.commons.jxpath.ri.QName;
import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
import org.apache.commons.jxpath.ri.compiler.NodeTest;
import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
import org.apache.commons.jxpath.ri.compiler.ProcessingInstructionTest;
import org.apache.commons.jxpath.ri.model.NodeIterator;
import org.apache.commons.jxpath.ri.model.NodePointer;
import org.apache.commons.jxpath.ri.model.beans.NullPointer;
import org.apache.commons.jxpath.util.TypeUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
/**
* A Pointer that points to a DOM node. Because a DOM Node is not guaranteed Serializable,
* a DOMNodePointer instance may likewise not be properly Serializable.
*
* @author Dmitri Plotnikov
* @version $Revision: 668329 $ $Date: 2008-06-16 16:59:48 -0500 (Mon, 16 Jun 2008) $
*/
public class DOMNodePointer extends NodePointer {
private static final long serialVersionUID = -8751046933894857319L;
private Node node;
private Map namespaces;
private String defaultNamespace;
private String id;
private NamespaceResolver localNamespaceResolver;
/** XML namespace URI */
public static final String XML_NAMESPACE_URI =
"http://www.w3.org/XML/1998/namespace";
/** XMLNS namespace URI */
public static final String XMLNS_NAMESPACE_URI =
"http://www.w3.org/2000/xmlns/";
/**
* Create a new DOMNodePointer.
* @param node pointed at
* @param locale Locale
*/
public DOMNodePointer(Node node, Locale locale) {
super(null, locale);
this.node = node;
}
/**
* Create a new DOMNodePointer.
* @param node pointed at
* @param locale Locale
* @param id string id
*/
public DOMNodePointer(Node node, Locale locale, String id) {
super(null, locale);
this.node = node;
this.id = id;
}
/**
* Create a new DOMNodePointer.
* @param parent pointer
* @param node pointed
*/
public DOMNodePointer(NodePointer parent, Node node) {
super(parent);
this.node = node;
}
public boolean testNode(NodeTest test) {
return testNode(node, test);
}
/**
* Test a Node.
* @param node to test
* @param test to execute
* @return true if node passes test
*/
public static boolean testNode(Node node, NodeTest test) {
if (test == null) {
return true;
}
if (test instanceof NodeNameTest) {
if (node.getNodeType() != Node.ELEMENT_NODE) {
return false;
}
NodeNameTest nodeNameTest = (NodeNameTest) test;
QName testName = nodeNameTest.getNodeName();
String namespaceURI = nodeNameTest.getNamespaceURI();
boolean wildcard = nodeNameTest.isWildcard();
String testPrefix = testName.getPrefix();
if (wildcard && testPrefix == null) {
return true;
}
if (wildcard
|| testName.getName()
.equals(DOMNodePointer.getLocalName(node))) {
String nodeNS = DOMNodePointer.getNamespaceURI(node);
return equalStrings(namespaceURI, nodeNS) || nodeNS == null
&& equalStrings(testPrefix, getPrefix(node));
}
return false;
}
if (test instanceof NodeTypeTest) {
int nodeType = node.getNodeType();
switch (((NodeTypeTest) test).getNodeType()) {
case Compiler.NODE_TYPE_NODE :
return true;
case Compiler.NODE_TYPE_TEXT :
return nodeType == Node.CDATA_SECTION_NODE
|| nodeType == Node.TEXT_NODE;
case Compiler.NODE_TYPE_COMMENT :
return nodeType == Node.COMMENT_NODE;
case Compiler.NODE_TYPE_PI :
return nodeType == Node.PROCESSING_INSTRUCTION_NODE;
default:
return false;
}
}
if (test instanceof ProcessingInstructionTest
&& node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
String testPI = ((ProcessingInstructionTest) test).getTarget();
String nodePI = ((ProcessingInstruction) node).getTarget();
return testPI.equals(nodePI);
}
return false;
}
/**
* Test string equality.
* @param s1 String 1
* @param s2 String 2
* @return true if == or .equals()
*/
private static boolean equalStrings(String s1, String s2) {
if (s1 == s2) {
return true;
}
s1 = s1 == null ? "" : s1.trim();
s2 = s2 == null ? "" : s2.trim();
return s1.equals(s2);
}
public QName getName() {
String ln = null;
String ns = null;
int type = node.getNodeType();
if (type == Node.ELEMENT_NODE) {
ns = DOMNodePointer.getPrefix(node);
ln = DOMNodePointer.getLocalName(node);
}
else if (type == Node.PROCESSING_INSTRUCTION_NODE) {
ln = ((ProcessingInstruction) node).getTarget();
}
return new QName(ns, ln);
}
public String getNamespaceURI() {
return getNamespaceURI(node);
}
public NodeIterator childIterator(NodeTest test, boolean reverse,
NodePointer startWith) {
return new DOMNodeIterator(this, test, reverse, startWith);
}
public NodeIterator attributeIterator(QName name) {
return new DOMAttributeIterator(this, name);
}
public NodePointer namespacePointer(String prefix) {
return new NamespacePointer(this, prefix);
}
public NodeIterator namespaceIterator() {
return new DOMNamespaceIterator(this);
}
public synchronized NamespaceResolver getNamespaceResolver() {
if (localNamespaceResolver == null) {
localNamespaceResolver = new NamespaceResolver(super.getNamespaceResolver());
localNamespaceResolver.setNamespaceContextPointer(this);
}
return localNamespaceResolver;
}
public String getNamespaceURI(String prefix) {
if (prefix == null || prefix.equals("")) {
return getDefaultNamespaceURI();
}
if (prefix.equals("xml")) {
return XML_NAMESPACE_URI;
}
if (prefix.equals("xmlns")) {
return XMLNS_NAMESPACE_URI;
}
String namespace = null;
if (namespaces == null) {
namespaces = new HashMap();
}
else {
namespace = (String) namespaces.get(prefix);
}
if (namespace == null) {
String qname = "xmlns:" + prefix;
Node aNode = node;
if (aNode instanceof Document) {
aNode = ((Document) aNode).getDocumentElement();
}
while (aNode != null) {
if (aNode.getNodeType() == Node.ELEMENT_NODE) {
Attr attr = ((Element) aNode).getAttributeNode(qname);
if (attr != null) {
namespace = attr.getValue();
break;
}
}
aNode = aNode.getParentNode();
}
if (namespace == null || namespace.equals("")) {
namespace = NodePointer.UNKNOWN_NAMESPACE;
}
}
namespaces.put(prefix, namespace);
if (namespace == UNKNOWN_NAMESPACE) {
return null;
}
// TBD: We are supposed to resolve relative URIs to absolute ones.
return namespace;
}
public String getDefaultNamespaceURI() {
if (defaultNamespace == null) {
Node aNode = node;
if (aNode instanceof Document) {
aNode = ((Document) aNode).getDocumentElement();
}
while (aNode != null) {
if (aNode.getNodeType() == Node.ELEMENT_NODE) {
Attr attr = ((Element) aNode).getAttributeNode("xmlns");
if (attr != null) {
defaultNamespace = attr.getValue();
break;
}
}
aNode = aNode.getParentNode();
}
}
if (defaultNamespace == null) {
defaultNamespace = "";
}
// TBD: We are supposed to resolve relative URIs to absolute ones.
return defaultNamespace.equals("") ? null : defaultNamespace;
}
public Object getBaseValue() {
return node;
}
public Object getImmediateNode() {
return node;
}
public boolean isActual() {
return true;
}
public boolean isCollection() {
return false;
}
public int getLength() {
return 1;
}
public boolean isLeaf() {
return !node.hasChildNodes();
}
/**
* Returns true if the xml:lang attribute for the current node
* or its parent has the specified prefix <i>lang</i>.
* If no node has this prefix, calls <code>super.isLanguage(lang)</code>.
* @param lang ns to test
* @return boolean
*/
public boolean isLanguage(String lang) {
String current = getLanguage();
return current == null ? super.isLanguage(lang)
: current.toUpperCase(Locale.ENGLISH).startsWith(lang.toUpperCase(Locale.ENGLISH));
}
/**
* Find the nearest occurrence of the specified attribute
* on the specified and enclosing elements.
* @param n current node
* @param attrName attribute name
* @return attribute value
*/
protected static String findEnclosingAttribute(Node n, String attrName) {
while (n != null) {
if (n.getNodeType() == Node.ELEMENT_NODE) {
Element e = (Element) n;
String attr = e.getAttribute(attrName);
if (attr != null && !attr.equals("")) {
return attr;
}
}
n = n.getParentNode();
}
return null;
}
/**
* Get the language attribute for this node.
* @return String language name
*/
protected String getLanguage() {
return findEnclosingAttribute(node, "xml:lang");
}
/**
* Sets contents of the node to the specified value. If the value is
* a String, the contents of the node are replaced with this text.
* If the value is an Element or Document, the children of the
* node are replaced with the children of the passed node.
* @param value to set
*/
public void setValue(Object value) {
if (node.getNodeType() == Node.TEXT_NODE
|| node.getNodeType() == Node.CDATA_SECTION_NODE) {
String string = (String) TypeUtils.convert(value, String.class);
if (string != null && !string.equals("")) {
node.setNodeValue(string);
}
else {
node.getParentNode().removeChild(node);
}
}
else {
NodeList children = node.getChildNodes();
int count = children.getLength();
for (int i = count; --i >= 0;) {
Node child = children.item(i);
node.removeChild(child);
}
if (value instanceof Node) {
Node valueNode = (Node) value;
if (valueNode instanceof Element
|| valueNode instanceof Document) {
children = valueNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
node.appendChild(child.cloneNode(true));
}
}
else {
node.appendChild(valueNode.cloneNode(true));
}
}
else {
String string = (String) TypeUtils.convert(value, String.class);
if (string != null && !string.equals("")) {
Node textNode =
node.getOwnerDocument().createTextNode(string);
node.appendChild(textNode);
}
}
}
}
public NodePointer createChild(JXPathContext context, QName name, int index) {
if (index == WHOLE_COLLECTION) {
index = 0;
}
boolean success =
getAbstractFactory(context).createObject(
context,
this,
node,
name.toString(),
index);
if (success) {
NodeTest nodeTest;
String prefix = name.getPrefix();
String namespaceURI = prefix == null ? null : context
.getNamespaceURI(prefix);
nodeTest = new NodeNameTest(name, namespaceURI);
NodeIterator it = childIterator(nodeTest, false, null);
if (it != null && it.setPosition(index + 1)) {
return it.getNodePointer();
}
}
throw new JXPathAbstractFactoryException(
"Factory could not create a child node for path: " + asPath()
+ "/" + name + "[" + (index + 1) + "]");
}
public NodePointer createChild(JXPathContext context, QName name,
int index, Object value) {
NodePointer ptr = createChild(context, name, index);
ptr.setValue(value);
return ptr;
}
public NodePointer createAttribute(JXPathContext context, QName name) {
if (!(node instanceof Element)) {
return super.createAttribute(context, name);
}
Element element = (Element) node;
String prefix = name.getPrefix();
if (prefix != null) {
String ns = null;
NamespaceResolver nsr = getNamespaceResolver();
if (nsr != null) {
ns = nsr.getNamespaceURI(prefix);
}
if (ns == null) {
throw new JXPathException(
"Unknown namespace prefix: " + prefix);
}
element.setAttributeNS(ns, name.toString(), "");
}
else {
if (!element.hasAttribute(name.getName())) {
element.setAttribute(name.getName(), "");
}
}
NodeIterator it = attributeIterator(name);
it.setPosition(1);
return it.getNodePointer();
}
public void remove() {
Node parent = node.getParentNode();
if (parent == null) {
throw new JXPathException("Cannot remove root DOM node");
}
parent.removeChild(node);
}
public String asPath() {
if (id != null) {
return "id('" + escape(id) + "')";
}
StringBuffer buffer = new StringBuffer();
if (parent != null) {
buffer.append(parent.asPath());
}
switch (node.getNodeType()) {
case Node.ELEMENT_NODE :
// If the parent pointer is not a DOMNodePointer, it is
// the parent's responsibility to produce the node test part
// of the path
if (parent instanceof DOMNodePointer) {
if (buffer.length() == 0
|| buffer.charAt(buffer.length() - 1) != '/') {
buffer.append('/');
}
String ln = DOMNodePointer.getLocalName(node);
String nsURI = getNamespaceURI();
if (nsURI == null) {
buffer.append(ln);
buffer.append('[');
buffer.append(getRelativePositionByName()).append(']');
}
else {
String prefix = getNamespaceResolver().getPrefix(nsURI);
if (prefix != null) {
buffer.append(prefix);
buffer.append(':');
buffer.append(ln);
buffer.append('[');
buffer.append(getRelativePositionByName());
buffer.append(']');
}
else {
buffer.append("node()");
buffer.append('[');
buffer.append(getRelativePositionOfElement());
buffer.append(']');
}
}
}
break;
case Node.TEXT_NODE :
case Node.CDATA_SECTION_NODE :
buffer.append("/text()");
buffer.append('[');
buffer.append(getRelativePositionOfTextNode()).append(']');
break;
case Node.PROCESSING_INSTRUCTION_NODE :
buffer.append("/processing-instruction(\'");
buffer.append(((ProcessingInstruction) node).getTarget()).append("')");
buffer.append('[');
buffer.append(getRelativePositionOfPI()).append(']');
break;
case Node.DOCUMENT_NODE :
// That'll be empty
break;
default:
break;
}
return buffer.toString();
}
/**
* Get relative position of this among like-named siblings.
* @return 1..n
*/
private int getRelativePositionByName() {
int count = 1;
Node n = node.getPreviousSibling();
while (n != null) {
if (n.getNodeType() == Node.ELEMENT_NODE) {
String nm = n.getNodeName();
if (nm.equals(node.getNodeName())) {
count++;
}
}
n = n.getPreviousSibling();
}
return count;
}
/**
* Get relative position of this among all siblings.
* @return 1..n
*/
private int getRelativePositionOfElement() {
int count = 1;
Node n = node.getPreviousSibling();
while (n != null) {
if (n.getNodeType() == Node.ELEMENT_NODE) {
count++;
}
n = n.getPreviousSibling();
}
return count;
}
/**
* Get the relative position of this among sibling text nodes.
* @return 1..n
*/
private int getRelativePositionOfTextNode() {
int count = 1;
Node n = node.getPreviousSibling();
while (n != null) {
if (n.getNodeType() == Node.TEXT_NODE
|| n.getNodeType() == Node.CDATA_SECTION_NODE) {
count++;
}
n = n.getPreviousSibling();
}
return count;
}
/**
* Get the relative position of this among same-target processing instruction siblings.
* @return 1..n
*/
private int getRelativePositionOfPI() {
int count = 1;
String target = ((ProcessingInstruction) node).getTarget();
Node n = node.getPreviousSibling();
while (n != null) {
if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE
&& ((ProcessingInstruction) n).getTarget().equals(target)) {
count++;
}
n = n.getPreviousSibling();
}
return count;
}
public int hashCode() {
return node.hashCode();
}
public boolean equals(Object object) {
return object == this || object instanceof DOMNodePointer && node == ((DOMNodePointer) object).node;
}
/**
* Get any prefix from the specified node.
* @param node the node to check
* @return String xml prefix
*/
public static String getPrefix(Node node) {
String prefix = node.getPrefix();
if (prefix != null) {
return prefix;
}
String name = node.getNodeName();
int index = name.lastIndexOf(':');
return index < 0 ? null : name.substring(0, index);
}
/**
* Get the local name of the specified node.
* @param node node to check
* @return String local name
*/
public static String getLocalName(Node node) {
String localName = node.getLocalName();
if (localName != null) {
return localName;
}
String name = node.getNodeName();
int index = name.lastIndexOf(':');
return index < 0 ? name : name.substring(index + 1);
}
/**
* Get the ns uri of the specified node.
* @param node Node to check
* @return String ns uri
*/
public static String getNamespaceURI(Node node) {
if (node instanceof Document) {
node = ((Document) node).getDocumentElement();
}
Element element = (Element) node;
String uri = element.getNamespaceURI();
if (uri != null) {
return uri;
}
String prefix = getPrefix(node);
String qname = prefix == null ? "xmlns" : "xmlns:" + prefix;
Node aNode = node;
while (aNode != null) {
if (aNode.getNodeType() == Node.ELEMENT_NODE) {
Attr attr = ((Element) aNode).getAttributeNode(qname);
if (attr != null) {
return attr.getValue();
}
}
aNode = aNode.getParentNode();
}
return null;
}
public Object getValue() {
if (node.getNodeType() == Node.COMMENT_NODE) {
String text = ((Comment) node).getData();
return text == null ? "" : text.trim();
}
return stringValue(node);
}
/**
* Get the string value of the specified node.
* @param node Node to check
* @return String
*/
private String stringValue(Node node) {
int nodeType = node.getNodeType();
if (nodeType == Node.COMMENT_NODE) {
return "";
}
boolean trim = !"preserve".equals(findEnclosingAttribute(node, "xml:space"));
if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
String text = node.getNodeValue();
return text == null ? "" : trim ? text.trim() : text;
}
if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
String text = ((ProcessingInstruction) node).getData();
return text == null ? "" : trim ? text.trim() : text;
}
NodeList list = node.getChildNodes();
StringBuffer buf = new StringBuffer();
for (int i = 0; i < list.getLength(); i++) {
Node child = list.item(i);
buf.append(stringValue(child));
}
return buf.toString();
}
/**
* Locates a node by ID.
* @param context starting context
* @param id to find
* @return Pointer
*/
public Pointer getPointerByID(JXPathContext context, String id) {
Document document = node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node
: node.getOwnerDocument();
Element element = document.getElementById(id);
return element == null ? (Pointer) new NullPointer(getLocale(), id)
: new DOMNodePointer(element, getLocale(), id);
}
public int compareChildNodePointers(NodePointer pointer1,
NodePointer pointer2) {
Node node1 = (Node) pointer1.getBaseValue();
Node node2 = (Node) pointer2.getBaseValue();
if (node1 == node2) {
return 0;
}
int t1 = node1.getNodeType();
int t2 = node2.getNodeType();
if (t1 == Node.ATTRIBUTE_NODE && t2 != Node.ATTRIBUTE_NODE) {
return -1;
}
if (t1 != Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
return 1;
}
if (t1 == Node.ATTRIBUTE_NODE && t2 == Node.ATTRIBUTE_NODE) {
NamedNodeMap map = ((Node) getNode()).getAttributes();
int length = map.getLength();
for (int i = 0; i < length; i++) {
Node n = map.item(i);
if (n == node1) {
return -1;
}
if (n == node2) {
return 1;
}
}
return 0; // Should not happen
}
Node current = node.getFirstChild();
while (current != null) {
if (current == node1) {
return -1;
}
if (current == node2) {
return 1;
}
current = current.getNextSibling();
}
return 0;
}
}
|