< prev index next >
src/java.xml/share/classes/com/sun/org/apache/xml/internal/utils/DOM2Helper.java
Print this page
@@ -1,8 +1,7 @@
/*
- * reserved comment block
- * DO NOT REMOVE OR ALTER!
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
*/
/*
* 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.
@@ -16,300 +15,329 @@
* 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.sun.org.apache.xml.internal.utils;
-import java.io.IOException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
-
+import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeProxy;
import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
-import org.xml.sax.InputSource;
/**
- * @deprecated Since the introduction of the DTM, this class will be removed.
- * This class provides a DOM level 2 "helper", which provides services currently
- * not provided be the DOM standard.
+ * This class provides a DOM level 2 "helper", which provides several services.
+ *
+ * The original class extended DOMHelper that was deprecated and then removed.
*/
-public class DOM2Helper extends DOMHelper
-{
+public final class DOM2Helper {
/**
* Construct an instance.
*/
- public DOM2Helper(){}
+ private DOM2Helper() {
+ }
/**
- * Check node to see if it was created by a DOM implementation
- * that this helper is intended to support. This is currently
- * disabled, and assumes all nodes are acceptable rather than checking
- * that they implement com.sun.org.apache.xerces.internal.dom.NodeImpl.
- *
- * @param node The node to be tested.
- *
- * @throws TransformerException if the node is not one which this
- * DOM2Helper can support. If we return without throwing the exception,
- * the node is compatable.
- * @xsl.usage internal
+ * Returns the local name of the given node, as defined by the XML
+ * Namespaces specification. This is prepared to handle documents built
+ * using DOM Level 1 methods by falling back upon explicitly parsing the
+ * node name.
+ *
+ * @param n Node to be examined
+ *
+ * @return String containing the local name, or null if the node was not
+ * assigned a Namespace.
*/
- public void checkNode(Node node) throws TransformerException
- {
+ public static String getLocalNameOfNode(Node n) {
+
+ String name = n.getLocalName();
- // if(!(node instanceof com.sun.org.apache.xerces.internal.dom.NodeImpl))
- // throw new TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_XERCES_CANNOT_HANDLE_NODES, new Object[]{((Object)node).getClass()})); //"DOM2Helper can not handle nodes of type"
- //+((Object)node).getClass());
+ return (null == name) ? getLocalNameOfNodeFallback(n) : name;
}
/**
- * Returns true if the DOM implementation handled by this helper
- * supports the SAX ContentHandler interface.
+ * Returns the local name of the given node. If the node's name begins with
+ * a namespace prefix, this is the part after the colon; otherwise it's the
+ * full node name.
*
- * @return true (since Xerces does).
+ * This method is copied from
+ * com.sun.org.apache.xml.internal.utils.DOMHelper
+ *
+ * @param n the node to be examined.
+ *
+ * @return String containing the Local Name
*/
- public boolean supportsSAX()
- {
- return true;
- }
+ private static String getLocalNameOfNodeFallback(Node n) {
- /** Field m_doc: Document Node for the document this helper is currently
- * accessing or building
- * @see #setDocument
- * @see #getDocument
- * */
- private Document m_doc;
+ String qname = n.getNodeName();
+ int index = qname.indexOf(':');
- /**
- * Specify which document this helper is currently operating on.
- *
- * @param doc The DOM Document node for this document.
- * @see #getDocument
- */
- public void setDocument(Document doc)
- {
- m_doc = doc;
+ return (index < 0) ? qname : qname.substring(index + 1);
}
/**
- * Query which document this helper is currently operating on.
+ * Returns the Namespace Name (Namespace URI) for the given node. In a Level
+ * 2 DOM, you can ask the node itself. Note, however, that doing so
+ * conflicts with our decision in getLocalNameOfNode not to trust the that
+ * the DOM was indeed created using the Level 2 methods. If Level 1 methods
+ * were used, these two functions will disagree with each other.
+ * <p>
+ * TODO: Reconcile with getLocalNameOfNode.
+ *
+ * @param n Node to be examined
*
- * @return The DOM Document node for this document.
- * @see #setDocument
+ * @return String containing the Namespace URI bound to this DOM node at the
+ * time the Node was created.
*/
- public Document getDocument()
- {
- return m_doc;
+ public static String getNamespaceOfNode(Node n) {
+ return n.getNamespaceURI();
}
/**
- * Parse an XML document.
- *
- * <p>Right now the Xerces DOMParser class is used. This needs
- * fixing, either via jaxp, or via some other, standard method.</p>
- *
- * <p>The application can use this method to instruct the SAX parser
- * to begin parsing an XML document from any valid input
- * source (a character stream, a byte stream, or a URI).</p>
- *
- * <p>Applications may not invoke this method while a parse is in
- * progress (they should create a new Parser instead for each
- * additional XML document). Once a parse is complete, an
- * application may reuse the same Parser object, possibly with a
- * different input source.</p>
+ * Figure out whether node2 should be considered as being later in the
+ * document than node1, in Document Order as defined by the XPath model.
+ * This may not agree with the ordering defined by other XML applications.
+ * <p>
+ * There are some cases where ordering isn't defined, and neither are the
+ * results of this function -- though we'll generally return true.
*
- * @param source The input source for the top-level of the
- * XML document.
+ * @param node1 DOM Node to perform position comparison on.
+ * @param node2 DOM Node to perform position comparison on .
*
- * @throws TransformerException if any checked exception is thrown.
- * @xsl.usage internal
+ * @return false if node2 comes before node1, otherwise return true. You can
+ * think of this as
+ * {@code (node1.documentOrderPosition <= node2.documentOrderPosition)}.
*/
- public void parse(InputSource source) throws TransformerException
- {
+ public static boolean isNodeAfter(Node node1, Node node2) {
+ if (node1 == node2 || isNodeTheSame(node1, node2)) {
+ return true;
+ }
- try
- {
+ // Default return value, if there is no defined ordering
+ boolean isNodeAfter = true;
- // I guess I should use JAXP factory here... when it's legal.
- // com.sun.org.apache.xerces.internal.parsers.DOMParser parser
- // = new com.sun.org.apache.xerces.internal.parsers.DOMParser();
- DocumentBuilderFactory builderFactory =
- DocumentBuilderFactory.newInstance();
-
- builderFactory.setNamespaceAware(true);
- builderFactory.setValidating(true);
-
- DocumentBuilder parser = builderFactory.newDocumentBuilder();
-
- /*
- // domParser.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", getShouldExpandEntityRefs()? false : true);
- if(m_useDOM2getNamespaceURI)
- {
- parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true);
- parser.setFeature("http://xml.org/sax/features/namespaces", true);
- }
- else
+ Node parent1 = getParentOfNode(node1);
+ Node parent2 = getParentOfNode(node2);
+
+ // Optimize for most common case
+ if (parent1 == parent2 || isNodeTheSame(parent1, parent2)) // then we know they are siblings
{
- parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
+ if (null != parent1) {
+ isNodeAfter = isNodeAfterSibling(parent1, node1, node2);
}
+ } else {
+ // General strategy: Figure out the lengths of the two
+ // ancestor chains, reconcile the lengths, and look for
+ // the lowest common ancestor. If that ancestor is one of
+ // the nodes being compared, it comes before the other.
+ // Otherwise perform a sibling compare.
+ //
+ // NOTE: If no common ancestor is found, ordering is undefined
+ // and we return the default value of isNodeAfter.
+ // Count parents in each ancestor chain
+ int nParents1 = 2, nParents2 = 2; // include node & parent obtained above
- parser.setFeature("http://apache.org/xml/features/allow-java-encodings", true);
- */
+ while (parent1 != null) {
+ nParents1++;
- parser.setErrorHandler(
- new com.sun.org.apache.xml.internal.utils.DefaultErrorHandler());
+ parent1 = getParentOfNode(parent1);
+ }
- // if(null != m_entityResolver)
- // {
- // System.out.println("Setting the entity resolver.");
- // parser.setEntityResolver(m_entityResolver);
- // }
- setDocument(parser.parse(source));
+ while (parent2 != null) {
+ nParents2++;
+
+ parent2 = getParentOfNode(parent2);
}
- catch (org.xml.sax.SAXException se)
- {
- throw new TransformerException(se);
+
+ // Initially assume scan for common ancestor starts with
+ // the input nodes.
+ Node startNode1 = node1, startNode2 = node2;
+
+ // If one ancestor chain is longer, adjust its start point
+ // so we're comparing at the same depths
+ if (nParents1 < nParents2) {
+ // Adjust startNode2 to depth of startNode1
+ int adjust = nParents2 - nParents1;
+
+ for (int i = 0; i < adjust; i++) {
+ startNode2 = getParentOfNode(startNode2);
}
- catch (ParserConfigurationException pce)
- {
- throw new TransformerException(pce);
+ } else if (nParents1 > nParents2) {
+ // adjust startNode1 to depth of startNode2
+ int adjust = nParents1 - nParents2;
+
+ for (int i = 0; i < adjust; i++) {
+ startNode1 = getParentOfNode(startNode1);
}
- catch (IOException ioe)
- {
- throw new TransformerException(ioe);
}
- // setDocument(((com.sun.org.apache.xerces.internal.parsers.DOMParser)parser).getDocument());
- }
+ Node prevChild1 = null, prevChild2 = null; // so we can "back up"
- /**
- * Given an XML ID, return the element. This requires assistance from the
- * DOM and parser, and is meaningful only in the context of a DTD
- * or schema which declares attributes as being of type ID. This
- * information may or may not be available in all parsers, may or
- * may not be available for specific documents, and may or may not
- * be available when validation is not turned on.
- *
- * @param id The ID to search for, as a String.
- * @param doc The document to search within, as a DOM Document node.
- * @return DOM Element node with an attribute of type ID whose value
- * uniquely matches the requested id string, or null if there isn't
- * such an element or if the DOM can't answer the question for other
- * reasons.
- */
- public Element getElementByID(String id, Document doc)
+ // Loop up the ancestor chain looking for common parent
+ while (null != startNode1) {
+ if (startNode1 == startNode2 || isNodeTheSame(startNode1, startNode2)) // common parent?
{
- return doc.getElementById(id);
- }
-
- /**
- * Figure out whether node2 should be considered as being later
- * in the document than node1, in Document Order as defined
- * by the XPath model. This may not agree with the ordering defined
- * by other XML applications.
- * <p>
- * There are some cases where ordering isn't defined, and neither are
- * the results of this function -- though we'll generally return true.
- * <p>
- * TODO: Make sure this does the right thing with attribute nodes!!!
- *
- * @param node1 DOM Node to perform position comparison on.
- * @param node2 DOM Node to perform position comparison on .
- *
- * @return false if node2 comes before node1, otherwise return true.
- * You can think of this as
- * <code>(node1.documentOrderPosition <= node2.documentOrderPosition)</code>.
- */
- public static boolean isNodeAfter(Node node1, Node node2)
+ if (null == prevChild1) // first time in loop?
{
- // Assume first that the nodes are DTM nodes, since discovering node
- // order is massivly faster for the DTM.
- if(node1 instanceof DOMOrder && node2 instanceof DOMOrder)
- {
- int index1 = ((DOMOrder) node1).getUid();
- int index2 = ((DOMOrder) node2).getUid();
+ // Edge condition: one is the ancestor of the other.
+ isNodeAfter = (nParents1 < nParents2) ? true : false;
- return index1 <= index2;
+ break; // from while loop
+ } else {
+ // Compare ancestors below lowest-common as siblings
+ isNodeAfter = isNodeAfterSibling(startNode1, prevChild1,
+ prevChild2);
+
+ break; // from while loop
}
- else
- {
+ } // end if(startNode1 == startNode2)
- // isNodeAfter will return true if node is after countedNode
- // in document order. The base isNodeAfter is sloooow (relatively).
- return DOMHelper.isNodeAfter(node1, node2);
+ // Move up one level and try again
+ prevChild1 = startNode1;
+ startNode1 = getParentOfNode(startNode1);
+ prevChild2 = startNode2;
+ startNode2 = getParentOfNode(startNode2);
+ } // end while(parents exist to examine)
+ } // end big else (not immediate siblings)
+
+ return isNodeAfter;
+ } // end isNodeAfter(Node node1, Node node2)
+
+ /**
+ * Use DTMNodeProxy to determine whether two nodes are the same.
+ *
+ * @param node1 The first DOM node to compare.
+ * @param node2 The second DOM node to compare.
+ * @return true if the two nodes are the same.
+ */
+ public static boolean isNodeTheSame(Node node1, Node node2) {
+ if (node1 instanceof DTMNodeProxy && node2 instanceof DTMNodeProxy) {
+ return ((DTMNodeProxy) node1).equals((DTMNodeProxy) node2);
+ } else {
+ return (node1 == node2);
}
}
/**
- * Get the XPath-model parent of a node. This version takes advantage
- * of the DOM Level 2 Attr.ownerElement() method; the base version we
- * would otherwise inherit is prepared to fall back on exhaustively
- * walking the document to find an Attr's parent.
+ * Get the XPath-model parent of a node. This version takes advantage of the
+ * DOM Level 2 Attr.ownerElement() method; the base version we would
+ * otherwise inherit is prepared to fall back on exhaustively walking the
+ * document to find an Attr's parent.
*
* @param node Node to be examined
*
* @return the DOM parent of the input node, if there is one, or the
- * ownerElement if the input node is an Attr, or null if the node is
- * a Document, a DocumentFragment, or an orphan.
+ * ownerElement if the input node is an Attr, or null if the node is a
+ * Document, a DocumentFragment, or an orphan.
*/
- public static Node getParentOfNode(Node node)
- {
- Node parent=node.getParentNode();
- if(parent==null && (Node.ATTRIBUTE_NODE == node.getNodeType()) )
- parent=((Attr) node).getOwnerElement();
+ public static Node getParentOfNode(Node node) {
+ Node parent = node.getParentNode();
+ if (parent == null && (Node.ATTRIBUTE_NODE == node.getNodeType())) {
+ parent = ((Attr) node).getOwnerElement();
+ }
return parent;
}
/**
- * Returns the local name of the given node, as defined by the
- * XML Namespaces specification. This is prepared to handle documents
- * built using DOM Level 1 methods by falling back upon explicitly
- * parsing the node name.
- *
- * @param n Node to be examined
+ * Figure out if child2 is after child1 in document order.
+ * <p>
+ * Warning: Some aspects of "document order" are not well defined. For
+ * example, the order of attributes is considered meaningless in XML, and
+ * the order reported by our model will be consistent for a given invocation
+ * but may not match that of either the source file or the serialized
+ * output.
*
- * @return String containing the local name, or null if the node
- * was not assigned a Namespace.
+ * @param parent Must be the parent of both child1 and child2.
+ * @param child1 Must be the child of parent and not equal to child2.
+ * @param child2 Must be the child of parent and not equal to child1.
+ * @return true if child 2 is after child1 in document order.
*/
- public String getLocalNameOfNode(Node n)
- {
+ private static boolean isNodeAfterSibling(Node parent, Node child1,
+ Node child2) {
- String name = n.getLocalName();
+ boolean isNodeAfterSibling = false;
+ short child1type = child1.getNodeType();
+ short child2type = child2.getNodeType();
+
+ if ((Node.ATTRIBUTE_NODE != child1type)
+ && (Node.ATTRIBUTE_NODE == child2type)) {
+
+ // always sort attributes before non-attributes.
+ isNodeAfterSibling = false;
+ } else if ((Node.ATTRIBUTE_NODE == child1type)
+ && (Node.ATTRIBUTE_NODE != child2type)) {
+
+ // always sort attributes before non-attributes.
+ isNodeAfterSibling = true;
+ } else if (Node.ATTRIBUTE_NODE == child1type) {
+ NamedNodeMap children = parent.getAttributes();
+ int nNodes = children.getLength();
+ boolean found1 = false, found2 = false;
+
+ // Count from the start until we find one or the other.
+ for (int i = 0; i < nNodes; i++) {
+ Node child = children.item(i);
- return (null == name) ? super.getLocalNameOfNode(n) : name;
+ if (child1 == child || isNodeTheSame(child1, child)) {
+ if (found2) {
+ isNodeAfterSibling = false;
+
+ break;
}
- /**
- * Returns the Namespace Name (Namespace URI) for the given node.
- * In a Level 2 DOM, you can ask the node itself. Note, however, that
- * doing so conflicts with our decision in getLocalNameOfNode not
- * to trust the that the DOM was indeed created using the Level 2
- * methods. If Level 1 methods were used, these two functions will
- * disagree with each other.
- * <p>
- * TODO: Reconcile with getLocalNameOfNode.
- *
- * @param n Node to be examined
- *
- * @return String containing the Namespace URI bound to this DOM node
- * at the time the Node was created.
- */
- public String getNamespaceOfNode(Node n)
- {
- return n.getNamespaceURI();
+ found1 = true;
+ } else if (child2 == child || isNodeTheSame(child2, child)) {
+ if (found1) {
+ isNodeAfterSibling = true;
+
+ break;
+ }
+
+ found2 = true;
+ }
+ }
+ } else {
+ // TODO: Check performance of alternate solution:
+ // There are two choices here: Count from the start of
+ // the document until we find one or the other, or count
+ // from one until we find or fail to find the other.
+ // Either can wind up scanning all the siblings in the worst
+ // case, which on a wide document can be a lot of work but
+ // is more typically is a short list.
+ // Scanning from the start involves two tests per iteration,
+ // but it isn't clear that scanning from the middle doesn't
+ // yield more iterations on average.
+ // We should run some testcases.
+ Node child = parent.getFirstChild();
+ boolean found1 = false, found2 = false;
+
+ while (null != child) {
+
+ // Node child = children.item(i);
+ if (child1 == child || isNodeTheSame(child1, child)) {
+ if (found2) {
+ isNodeAfterSibling = false;
+
+ break;
+ }
+
+ found1 = true;
+ } else if (child2 == child || isNodeTheSame(child2, child)) {
+ if (found1) {
+ isNodeAfterSibling = true;
+
+ break;
+ }
+
+ found2 = true;
+ }
+
+ child = child.getNextSibling();
+ }
}
- /** Field m_useDOM2getNamespaceURI is a compile-time flag which
- * gates some of the parser options used to build a DOM -- but
- * that code is commented out at this time and nobody else
- * references it, so I've commented this out as well. */
- //private boolean m_useDOM2getNamespaceURI = false;
+ return isNodeAfterSibling;
+ } // end isNodeAfterSibling(Node parent, Node child1, Node child2)
}
< prev index next >