All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.dom.NodeOverNodeInfo Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.dom;

import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.expr.sort.CodepointCollator;
import net.sf.saxon.expr.sort.GenericAtomicComparer;
import net.sf.saxon.functions.DeepEqual;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.AxisInfo;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.tree.iter.SingletonIterator;
import net.sf.saxon.type.Type;
import org.w3c.dom.*;

import java.util.ArrayList;
import java.util.List;


/**
 * This class implements the DOM Node interface as a wrapper around a Saxon NodeInfo object.
 * 

The class provides read-only access to the tree; methods that request updates all fail * with an UnsupportedOperationException.

*/ public abstract class NodeOverNodeInfo implements Node { protected NodeInfo node; /** * Get the Saxon NodeInfo object representing this node * * @return the Saxon NodeInfo object */ /*@Nullable*/ public NodeInfo getUnderlyingNodeInfo() { return node; } /** * Factory method to construct a DOM node that wraps an underlying Saxon NodeInfo * * @param node the Saxon NodeInfo object * @return the DOM wrapper node */ public static NodeOverNodeInfo wrap(NodeInfo node) { NodeOverNodeInfo n; if (node == null) { return null; } switch (node.getNodeKind()) { case Type.DOCUMENT: n = new DocumentOverNodeInfo(); break; case Type.ELEMENT: n = new ElementOverNodeInfo(); break; case Type.ATTRIBUTE: n = new AttrOverNodeInfo(); break; case Type.TEXT: case Type.COMMENT: n = new TextOverNodeInfo(); break; case Type.PROCESSING_INSTRUCTION: n = new PIOverNodeInfo(); break; case Type.NAMESPACE: n = new AttrOverNodeInfo(); break; default: return null; } n.node = node; return n; } /** * Determine whether this is the same node as another node. DOM Level 3 method. * * @return true if this Node object and the supplied Node object represent the * same node in the tree. */ @Override public final boolean isSameNode(Node other) { return other instanceof NodeOverNodeInfo && node.equals(((NodeOverNodeInfo) other).node); } /** * The equals() method returns true for two Node objects that represent the same * conceptual DOM Node. This is a concession to the Xalan IdentityTransformer, which relies * on equals() for DOM Nodes having this behaviour, even though it is not defined in the * specification * * @param obj the object to be compared * @return if this node and obj represent the same conceptual DOM node. That is, return * true if isSameNode((Node)obj) returns true */ @Override public boolean equals(Object obj) { return obj instanceof Node && isSameNode((Node) obj); } /** * Return a hashCode * * @return a hashCode such that two wrappers over the same underlying node have the same hashCode. */ @Override public int hashCode() { return node.hashCode(); } /** * Get the base URI for the node. Default implementation for child nodes gets * the base URI of the parent node. */ @Override public String getBaseURI() { return node.getBaseURI(); } /** * Get the name of this node, following the DOM rules * * @return The name of the node. For an element this is the element name, for an attribute * it is the attribute name, as a lexical QName. Other node types return conventional names such * as "#text" or "#comment" */ @Override public String getNodeName() { switch (node.getNodeKind()) { case Type.DOCUMENT: return "#document"; case Type.ELEMENT: return node.getDisplayName(); case Type.ATTRIBUTE: return node.getDisplayName(); case Type.TEXT: return "#text"; case Type.COMMENT: return "#comment"; case Type.PROCESSING_INSTRUCTION: return node.getLocalPart(); case Type.NAMESPACE: if (node.getLocalPart().isEmpty()) { return "xmlns"; } else { return "xmlns:" + node.getLocalPart(); } default: return "#unknown"; } } /** * Get the local name of this node, following the DOM rules * * @return The local name of the node. For an element this is the local part of the element name, * for an attribute it is the local part of the attribute name. Other node types return null. */ @Override public String getLocalName() { switch (node.getNodeKind()) { case Type.ELEMENT: case Type.ATTRIBUTE: return node.getLocalPart(); case Type.DOCUMENT: case Type.TEXT: case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: return null; case Type.NAMESPACE: if (node.getLocalPart().isEmpty()) { return "xmlns"; } else { return node.getLocalPart(); } default: return null; } } /** * Determine whether the node has any children. * * @return true if this node has any attributes, * false otherwise. */ @Override public boolean hasChildNodes() { return node.hasChildNodes(); } /** * Returns whether this node has any attributes. We treat the declaration of the XML namespace * as being present on every element, and since namespace declarations are treated as attributes, * every element has at least one attribute. This method therefore returns true. * * @return true if this node has any attributes, * false otherwise. * @since DOM Level 2 */ @Override public boolean hasAttributes() { return true; } /** * Get the type of this node (node kind, in XPath terminology). * Note, the numbers assigned to node kinds * in Saxon (see {@link Type}) are the same as those assigned in the DOM */ @Override public short getNodeType() { short kind = (short) node.getNodeKind(); if (kind == Type.NAMESPACE) { return Type.ATTRIBUTE; } else { return kind; } } /** * Find the parent node of this node. * * @return The Node object describing the containing element or root node. */ @Override public Node getParentNode() { return wrap(node.getParent()); } /** * Get the previous sibling of the node * * @return The previous sibling node. Returns null if the current node is the first * child of its parent. */ @Override public Node getPreviousSibling() { return wrap(node.iterateAxis(AxisInfo.PRECEDING_SIBLING).next()); } /** * Get next sibling node * * @return The next sibling node. Returns null if the current node is the last * child of its parent. */ @Override public Node getNextSibling() { return wrap(node.iterateAxis(AxisInfo.FOLLOWING_SIBLING).next()); } /** * Get first child * * @return the first child node of this node, or null if it has no children */ @Override public Node getFirstChild() { return wrap(node.iterateAxis(AxisInfo.CHILD).next()); } /** * Get last child * * @return last child of this node, or null if it has no children */ @Override public Node getLastChild() { AxisIterator children = node.iterateAxis(AxisInfo.CHILD); NodeInfo last = null; while (true) { NodeInfo next = children.next(); if (next == null) { return wrap(last); } else { last = next; } } } /** * Get the node value (as defined in the DOM). * This is not generally the same as the XPath string-value: in particular, the * node value of an element node is null. */ @Override public String getNodeValue() { switch (node.getNodeKind()) { case Type.DOCUMENT: case Type.ELEMENT: return null; case Type.ATTRIBUTE: case Type.TEXT: case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: case Type.NAMESPACE: return node.getStringValue(); default: return null; } } /** * Set the node value. Always fails */ @Override public void setNodeValue(String nodeValue) throws DOMException { disallowUpdate(); } /** * Return a NodeList that contains all children of this node. If * there are no children, this is a NodeList containing no * nodes. */ @Override public NodeList getChildNodes() { List nodes = new ArrayList<>(10); for (NodeInfo child : node.children()) { nodes.add(NodeOverNodeInfo.wrap(child)); } return new DOMNodeList(nodes); } /** * Return a NamedNodeMap containing the attributes of this node (if * it is an Element) or null otherwise. */ @Override public NamedNodeMap getAttributes() { return null; } /** * Return the Document object associated with this node. */ @Override public Document getOwnerDocument() { return (Document) wrap(node.getRoot()); } /** * Insert the node newChild before the existing child node * refChild. Always fails. * * @param newChild The node to insert. * @param refChild The reference node, i.e., the node before which the * new node must be inserted. * @return The node being inserted. * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Always raised. */ @Override public Node insertBefore(Node newChild, Node refChild) throws DOMException { disallowUpdate(); return null; } /** * Replace the child node oldChild with * newChild in the list of children, and returns the * oldChild node. Always fails. * * @param newChild The new node to put in the child list. * @param oldChild The node being replaced in the list. * @return The node replaced. * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Always raised. */ @Override public Node replaceChild(Node newChild, Node oldChild) throws DOMException { disallowUpdate(); return null; } /** * Remove the child node indicated by oldChild from the * list of children, and returns it. Always fails. * * @param oldChild The node being removed. * @return The node removed. * @throws org.w3c.dom.DOMException NO_MODIFICATION_ALLOWED_ERR: Always raised. */ @Override public Node removeChild(Node oldChild) throws DOMException { disallowUpdate(); return null; } /** * Adds the node newChild to the end of the list of children * of this node. Always fails. * * @param newChild The node to add. * @return The node added. * @throws org.w3c.dom.DOMException
NO_MODIFICATION_ALLOWED_ERR: Always raised. */ @Override public Node appendChild(Node newChild) throws DOMException { disallowUpdate(); return null; } /** * Returns a duplicate of this node, i.e., serves as a generic copy * constructor for nodes. Always fails. * * @param deep If true , recursively clone the subtree under * the specified node; if false , clone only the node * itself (and its attributes, if it is an Element ). * @return The duplicate node. */ @Override public Node cloneNode(boolean deep) { disallowUpdate(); return null; } /** * Puts all Text nodes in the full depth of the sub-tree * underneath this Node, including attribute nodes, into a * "normal" form where only structure (e.g., elements, comments, * processing instructions, CDATA sections, and entity references) * separates Text nodes, i.e., there are neither adjacent * Text nodes nor empty Text nodes. * * @since DOM Level 2 */ @Override public void normalize() { // null operation; nodes are always normalized } /** * Tests whether the DOM implementation implements a specific feature and * that feature is supported by this node. * * @param feature The name of the feature to test. This is the same name * which can be passed to the method hasFeature on * DOMImplementation . * @param version This is the version number of the feature to test. In * Level 2, version 1, this is the string "2.0". If the version is not * specified, supporting any version of the feature will cause the * method to return true . * @return Returns true if the specified feature is supported * on this node, false otherwise. * @since DOM Level 2 */ @Override public boolean isSupported(String feature, String version) { return (feature.equalsIgnoreCase("XML") || feature.equalsIgnoreCase("Core")) && (version == null || version.isEmpty() || version.equals("3.0") || version.equals("2.0") || version.equals("1.0")); } /** * The namespace URI of this node, or null if it is * unspecified. *
This is not a computed value that is the result of a namespace * lookup based on an examination of the namespace declarations in scope. * It is merely the namespace URI given at creation time. *
For nodes of any type other than ELEMENT_NODE and * ATTRIBUTE_NODE and nodes created with a DOM Level 1 * method, such as createElement from the * Document interface, this is always null . * Per the Namespaces in XML Specification an attribute does not * inherit its namespace from the element it is attached to. If an * attribute is not explicitly given a namespace, it simply has no * namespace. * * @since DOM Level 2 */ @Override public String getNamespaceURI() { if (node.getNodeKind() == Type.NAMESPACE) { return NamespaceConstant.XMLNS; } String uri = node.getURI(); return "".equals(uri) ? null : uri; } /** * The namespace prefix of this node, or null if it is * unspecified. *
For nodes of any type other than ELEMENT_NODE and * ATTRIBUTE_NODE and nodes created with a DOM Level 1 * method, such as createElement from the * Document interface, this is always null. * * @since DOM Level 2 */ @Override public String getPrefix() { if (node.getNodeKind() == Type.NAMESPACE) { if (node.getLocalPart().isEmpty()) { return null; } else { return "xmlns"; } } String p = node.getPrefix(); return "".equals(p) ? null : p; } /** * Set the namespace prefix of this node. Always fails. */ @Override public void setPrefix(String prefix) throws DOMException { disallowUpdate(); } /** * Compare the position of the (other) node in document order with the reference node (this node). * DOM Level 3 method. * * @param other the other node. * @return Returns how the node is positioned relatively to the reference * node. * @throws org.w3c.dom.DOMException if an error occurs */ @Override public short compareDocumentPosition(Node other) throws DOMException { final short DOCUMENT_POSITION_DISCONNECTED = 0x01; final short DOCUMENT_POSITION_PRECEDING = 0x02; final short DOCUMENT_POSITION_FOLLOWING = 0x04; final short DOCUMENT_POSITION_CONTAINS = 0x08; final short DOCUMENT_POSITION_CONTAINED_BY = 0x10; if (!(other instanceof NodeOverNodeInfo)) { return DOCUMENT_POSITION_DISCONNECTED; } int c = node.compareOrder(((NodeOverNodeInfo) other).node); if (c == 0) { return (short) 0; } else if (c == -1) { short result = DOCUMENT_POSITION_FOLLOWING; short d = compareDocumentPosition(other.getParentNode()); if (d == 0 || (d & DOCUMENT_POSITION_CONTAINED_BY) != 0) { result |= DOCUMENT_POSITION_CONTAINED_BY; } return result; } else if (c == +1) { short result = DOCUMENT_POSITION_PRECEDING; short d = getParentNode().compareDocumentPosition(other); if (d == 0 || (d & DOCUMENT_POSITION_CONTAINS) != 0) { result |= DOCUMENT_POSITION_CONTAINS; } return result; } else { throw new AssertionError(); } } /** * Get the text content of a node. This is a DOM Level 3 method. The definition * is the same as the definition of the string value of a node in XPath, except * in the case of document nodes. * * @return the string value of the node, or null in the case of document nodes. * @throws org.w3c.dom.DOMException if a dynamic error occurs */ @Override public String getTextContent() throws DOMException { if (node.getNodeKind() == Type.DOCUMENT) { return null; } else { return node.getStringValue(); } } /** * Set the text content of a node. Always fails. * * @param textContent the new text content of the node * @throws UnsupportedOperationException always */ @Override public void setTextContent(String textContent) throws UnsupportedOperationException { disallowUpdate(); } /** * Get the (first) prefix assigned to a specified namespace URI, or null * if the namespace is not in scope. DOM Level 3 method. * * @param namespaceURI the namespace whose prefix is required * @return the corresponding prefix, if there is one, or null if not. */ @Override public String lookupPrefix(String namespaceURI) { if (node.getNodeKind() == Type.DOCUMENT) { return null; } else if (node.getNodeKind() == Type.ELEMENT) { AxisIterator iter = node.iterateAxis(AxisInfo.NAMESPACE); NodeInfo ns; while ((ns = iter.next()) != null) { if (ns.getUnicodeStringValue().equals(namespaceURI)) { return ns.getLocalPart(); } } return null; } else { return getParentNode().lookupPrefix(namespaceURI); } } /** * Test whether a particular namespace is the default namespace. * DOM Level 3 method. * * @param namespaceURI the namespace to be tested * @return true if this is the default namespace */ @Override public boolean isDefaultNamespace(String namespaceURI) { return namespaceURI.equals(lookupNamespaceURI("")); } /** * Find the URI corresponding to a given in-scope prefix * * @param prefix The namespace prefix whose namespace URI is required. * @return the corresponding namespace URI, or null if the prefix is * not declared. */ @Override public String lookupNamespaceURI(String prefix) { if (node.getNodeKind() == Type.DOCUMENT) { return null; } else if (node.getNodeKind() == Type.ELEMENT) { AxisIterator iter = node.iterateAxis(AxisInfo.NAMESPACE); NodeInfo ns; while ((ns = iter.next()) != null) { if (ns.getLocalPart().equals(prefix)) { return ns.getStringValue(); } } return null; } else { return getParentNode().lookupNamespaceURI(prefix); } } /** * Compare whether two nodes have the same content. This is a DOM Level 3 method. * * @param arg The node to be compared. This must wrap a Saxon NodeInfo. * @return true if the two nodes are deep-equal. */ @Override public boolean isEqualNode(Node arg) { if (!(arg instanceof NodeOverNodeInfo)) { throw new IllegalArgumentException("Other Node must wrap a Saxon NodeInfo"); } try { XPathContext context = node.getConfiguration().getConversionContext(); return DeepEqual.deepEqual( SingletonIterator.makeIterator(node), SingletonIterator.makeIterator(((NodeOverNodeInfo) arg).node), new GenericAtomicComparer(CodepointCollator.getInstance(), context), context, DeepEqual.INCLUDE_PREFIXES | DeepEqual.INCLUDE_COMMENTS | DeepEqual.COMPARE_STRING_VALUES | DeepEqual.INCLUDE_PROCESSING_INSTRUCTIONS); } catch (XPathException err) { // can't happen return false; } } /** * Get a feature of this node. DOM Level 3 method, always returns null. * * @param feature the required feature * @param version the version of the required feature * @return the value of the feature. Always null in this implementation */ @Override public Object getFeature(String feature, String version) { return null; } /** * Set user data. Always throws UnsupportedOperationException in this implementation * * @param key name of the user data * @param data value of the user data * @param handler handler for the user data * @return This implementation always throws an exception */ @Override public Object setUserData(String key, Object data, UserDataHandler handler) { disallowUpdate(); return null; } /** * Get user data associated with this node. DOM Level 3 method, always returns * null in this implementation * * @param key identifies the user data required * @return always null in this implementation */ @Override public Object getUserData(String key) { return null; } /** * Internal method used to indicate that update operations are not allowed * * @throws org.w3c.dom.DOMException always, to indicate that update is not supported in this DOM implementation */ protected static void disallowUpdate() throws DOMException { throw new org.w3c.dom.DOMException( DOMException.NO_MODIFICATION_ALLOWED_ERR, "The Saxon DOM implementation cannot be updated"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy