package client.net.sf.saxon.ce.functions;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.expr.Expression;
import client.net.sf.saxon.ce.expr.ExpressionVisitor;
import client.net.sf.saxon.ce.expr.XPathContext;
import client.net.sf.saxon.ce.expr.sort.GenericAtomicComparer;
import client.net.sf.saxon.ce.lib.NamespaceConstant;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.pattern.NameTest;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.iter.AxisIterator;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.value.AtomicValue;
import client.net.sf.saxon.ce.value.BooleanValue;
/**
* XSLT 2.0 deep-equal() function.
* Supports deep comparison of two sequences (of nodes and/or atomic values)
* optionally using a collation
*/
public class DeepEqual extends CollatingFunction {
public DeepEqual newInstance() {
return new DeepEqual();
}
private transient Configuration config = null;
/**
* preEvaluate: if all arguments are known statically, evaluate early
* @param visitor an expression visitor
*/
public Expression preEvaluate(ExpressionVisitor visitor) throws XPathException {
config = visitor.getConfiguration();
return super.preEvaluate(visitor);
}
/**
* Evaluate the expression
*/
public Item evaluateItem(XPathContext context) throws XPathException {
GenericAtomicComparer collator = getAtomicComparer(2, context);
SequenceIterator op1 = argument[0].iterate(context);
SequenceIterator op2 = argument[1].iterate(context);
Configuration config =
(this.config!=null ? this.config : context.getConfiguration());
try {
return BooleanValue.get(deepEquals(op1, op2, collator, config));
} catch (XPathException e) {
e.maybeSetLocation(getSourceLocator());
e.maybeSetContext(context);
throw e;
}
}
/**
* Determine when two sequences are deep-equal
* @param op1 the first sequence
* @param op2 the second sequence
* @param collator the collator to be used
* @param config the configuration (gives access to the NamePool)
* @return true if the sequences are deep-equal
* @throws XPathException if either sequence contains a function item
*/
private static boolean deepEquals(SequenceIterator op1, SequenceIterator op2,
GenericAtomicComparer collator, Configuration config)
throws XPathException {
boolean result = true;
try {
while (true) {
Item item1 = op1.next();
Item item2 = op2.next();
if (item1 == null && item2 == null) {
break;
}
if (item1 == null || item2 == null) {
result = false;
break;
}
if (item1 instanceof NodeInfo) {
if (item2 instanceof NodeInfo) {
if (!deepEquals((NodeInfo)item1, (NodeInfo)item2, collator, config)) {
result = false;
break;
}
} else {
result = false;
break;
}
} else {
if (item2 instanceof NodeInfo) {
result = false;
break;
} else {
AtomicValue av1 = ((AtomicValue)item1);
AtomicValue av2 = ((AtomicValue)item2);
if (av1.isNaN() && av2.isNaN()) {
// treat as equal, no action
} else if (!collator.comparesEqual(av1, av2)) {
result = false;
break;
}
}
}
} // end while
} catch (ClassCastException err) {
// this will happen if the sequences contain non-comparable values
// comparison errors are masked
result = false;
} catch (XPathException err) {
// comparison errors are masked
if ("FOTY0015".equals(err.getErrorCodeLocalPart()) && NamespaceConstant.ERR.equals(err.getErrorCodeNamespace())) {
throw err;
}
result = false;
}
return result;
}
/**
* Determine whether two nodes are deep-equal
*/
private static boolean deepEquals(NodeInfo n1, NodeInfo n2,
GenericAtomicComparer collator, Configuration config)
throws XPathException {
// shortcut: a node is always deep-equal to itself
if (n1.isSameNodeInfo(n2)) return true;
if (n1.getNodeKind() != n2.getNodeKind()) {
return false;
}
final NamePool pool = config.getNamePool();
switch (n1.getNodeKind()) {
case Type.ELEMENT:
if (n1.getFingerprint() != n2.getFingerprint()) {
return false;
}
AxisIterator a1 = n1.iterateAxis(Axis.ATTRIBUTE);
AxisIterator a2 = n2.iterateAxis(Axis.ATTRIBUTE);
if (Count.count(a1.getAnother()) != Count.count(a2)) {
return false;
}
while (true) {
NodeInfo att1 = (NodeInfo)a1.next();
if (att1 == null) break;
AxisIterator a2iter = n2.iterateAxis(Axis.ATTRIBUTE,
new NameTest(Type.ATTRIBUTE, att1.getFingerprint(), pool));
NodeInfo att2 = (NodeInfo)a2iter.next();
if (att2==null) {
return false;
}
if (!deepEquals(att1, att2, collator, config)) {
return false;
}
}
// fall through
case Type.DOCUMENT:
AxisIterator c1 = n1.iterateAxis(Axis.CHILD);
AxisIterator c2 = n2.iterateAxis(Axis.CHILD);
while (true) {
NodeInfo d1 = (NodeInfo)c1.next();
while (d1 != null && isIgnorable(d1)) {
d1 = (NodeInfo)c1.next();
}
NodeInfo d2 = (NodeInfo)c2.next();
while (d2 != null && isIgnorable(d2)) {
d2 = (NodeInfo)c2.next();
}
if (d1 == null || d2 == null) {
return (d1 == d2);
}
if (!deepEquals(d1, d2, collator, config)) {
return false;
}
}
case Type.ATTRIBUTE:
case Type.PROCESSING_INSTRUCTION:
case Type.NAMESPACE:
case Type.TEXT:
case Type.COMMENT:
return (n1.getFingerprint() == n2.getFingerprint() &&
collator.comparesEqual(n1.getTypedValue(), n2.getTypedValue()));
default:
throw new IllegalArgumentException("Unknown node type");
}
}
private static boolean isIgnorable(NodeInfo node) {
final int kind = node.getNodeKind();
if (kind == Type.COMMENT) {
return true;
} else if (kind == Type.PROCESSING_INSTRUCTION) {
return true;
}
return false;
}
}
// 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.