< 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 &lt;= 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 &lt;= 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 >