You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
13 KiB
342 lines
13 KiB
/*
|
|
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
|
*/
|
|
/*
|
|
* Copyright 1999-2004 The Apache Software Foundation.
|
|
*
|
|
* Licensed 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.
|
|
*/
|
|
/*
|
|
* $Id: DOM2Helper.java,v 1.2.4.1 2005/09/15 08:15:37 suresh_emailid Exp $
|
|
*/
|
|
package com.sun.org.apache.xml.internal.utils;
|
|
|
|
import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeProxy;
|
|
import org.w3c.dom.Attr;
|
|
import org.w3c.dom.NamedNodeMap;
|
|
import org.w3c.dom.Node;
|
|
|
|
|
|
/**
|
|
* This class provides a DOM level 2 "helper", which provides several services.
|
|
*
|
|
* The original class extended DOMHelper that was deprecated and then removed.
|
|
*/
|
|
public final class DOM2Helper {
|
|
|
|
/**
|
|
* Construct an instance.
|
|
*/
|
|
private DOM2Helper() {
|
|
}
|
|
|
|
/**
|
|
* 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 static String getLocalNameOfNode(Node n) {
|
|
String name = n.getLocalName();
|
|
return (null == name) ? getLocalNameOfNodeFallback(n) : name;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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
|
|
*/
|
|
private static String getLocalNameOfNodeFallback(Node n) {
|
|
String qname = n.getNodeName();
|
|
int index = qname.indexOf(':');
|
|
|
|
return (index < 0) ? qname : qname.substring(index + 1);
|
|
}
|
|
|
|
/**
|
|
* 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 static String getNamespaceOfNode(Node n) {
|
|
return n.getNamespaceURI();
|
|
}
|
|
|
|
/**
|
|
* 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 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)}.
|
|
*/
|
|
public static boolean isNodeAfter(Node node1, Node node2) {
|
|
if (node1 == node2 || isNodeTheSame(node1, node2)) {
|
|
return true;
|
|
}
|
|
|
|
// Default return value, if there is no defined ordering
|
|
boolean isNodeAfter = true;
|
|
|
|
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
|
|
{
|
|
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
|
|
|
|
while (parent1 != null) {
|
|
nParents1++;
|
|
parent1 = getParentOfNode(parent1);
|
|
}
|
|
|
|
while (parent2 != null) {
|
|
nParents2++;
|
|
|
|
parent2 = getParentOfNode(parent2);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
} else if (nParents1 > nParents2) {
|
|
// adjust startNode1 to depth of startNode2
|
|
int adjust = nParents1 - nParents2;
|
|
|
|
for (int i = 0; i < adjust; i++) {
|
|
startNode1 = getParentOfNode(startNode1);
|
|
}
|
|
}
|
|
|
|
Node prevChild1 = null, prevChild2 = null; // so we can "back up"
|
|
|
|
// Loop up the ancestor chain looking for common parent
|
|
while (null != startNode1) {
|
|
if (startNode1 == startNode2 || isNodeTheSame(startNode1, startNode2)) // common parent?
|
|
{
|
|
if (null == prevChild1) // first time in loop?
|
|
{
|
|
|
|
// Edge condition: one is the ancestor of the other.
|
|
isNodeAfter = (nParents1 < nParents2) ? true : false;
|
|
|
|
break; // from while loop
|
|
} else {
|
|
// Compare ancestors below lowest-common as siblings
|
|
isNodeAfter = isNodeAfterSibling(startNode1, prevChild1,
|
|
prevChild2);
|
|
|
|
break; // from while loop
|
|
}
|
|
} // end if(startNode1 == startNode2)
|
|
|
|
// 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.
|
|
*
|
|
* @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.
|
|
*/
|
|
public static Node getParentOfNode(Node node) {
|
|
Node parent = node.getParentNode();
|
|
if (parent == null && (Node.ATTRIBUTE_NODE == node.getNodeType())) {
|
|
parent = ((Attr) node).getOwnerElement();
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @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.
|
|
*/
|
|
private static boolean isNodeAfterSibling(Node parent, Node child1,
|
|
Node child2) {
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
}
|
|
} 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();
|
|
}
|
|
}
|
|
|
|
return isNodeAfterSibling;
|
|
} // end isNodeAfterSibling(Node parent, Node child1, Node child2)
|
|
}
|