package org.basex.query.util; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import java.util.EnumSet; import java.util.Stack; import org.basex.query.QueryException; import org.basex.query.item.ANode; import org.basex.query.item.Item; import org.basex.query.item.NodeType; import org.basex.query.item.QNm; import org.basex.query.item.Type; import org.basex.query.item.Value; import org.basex.query.item.map.Map; import org.basex.query.iter.AxisIter; import org.basex.query.iter.Iter; import org.basex.util.Atts; import org.basex.util.InputInfo; /** * Utility class for comparing XQuery values. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class Compare { /** Flags. */ public enum Flag { /** Compare all node types. */ ALLNODES, /** Compare namespaces. */ NAMESPACES, } /** Input info. */ private final InputInfo input; /** Flag values. */ private final EnumSet<Flag> flags = EnumSet.noneOf(Flag.class); /** * Constructor. * @param ii input info */ public Compare(final InputInfo ii) { input = ii; } /** * Sets the specified flag. * @param f flag * @return self reference */ public Compare set(final Flag f) { flags.add(f); return this; } /** * Checks items for deep equality. * @param val1 first value * @param val2 second value * @param input input info * @return result of check * @throws QueryException query exception */ public static boolean deep(final Value val1, final Value val2, final InputInfo input) throws QueryException { return new Compare(input).deep(val1.iter(), val2.iter()); } /** * Checks items for deep equality. * @param iter1 first iterator * @param iter2 second iterator * @param input input info * @return result of check * @throws QueryException query exception */ public static boolean deep(final Iter iter1, final Iter iter2, final InputInfo input) throws QueryException { return new Compare(input).deep(iter1, iter2); } /** * Checks values for deep equality. * @param iter1 first iterator * @param iter2 second iterator * @return result of check * @throws QueryException query exception */ public boolean deep(final Iter iter1, final Iter iter2) throws QueryException { while(true) { // check if one or both iterators are exhausted final Item it1 = iter1.next(), it2 = iter2.next(); if(it1 == null || it2 == null) return it1 == null && it2 == null; // check functions Type t1 = it1.type, t2 = it2.type; if(t1.isFunction() || t2.isFunction()) { // maps are functions but have a defined deep-equality if(t1.isMap() && t2.isMap()) { final Map map1 = (Map) it1, map2 = (Map) it2; if(!map1.deep(input, map2)) return false; continue; } FNCMP.thrw(input, t1.isFunction() ? it1 : it2); } // check atomic values if(!t1.isNode() && !t2.isNode()) { if(!it1.equiv(input, it2)) return false; continue; } // node types must be equal if(t1 != t2) return false; ANode s1 = (ANode) it1, s2 = (ANode) it2; AxisIter ch1 = s1.children(), ch2 = s2.children(); final Stack<AxisIter> stack = new Stack<AxisIter>(); stack.push(ch1); stack.push(ch2); boolean skip = false; do { t1 = s1 != null ? s1.type : null; t2 = s2 != null ? s2.type : null; // skip comparison of descendant comments and processing instructions if(skip) { if(t1 == NodeType.COM || t1 == NodeType.PI) { s1 = ch1.next(); continue; } if(t2 == NodeType.COM || t2 == NodeType.PI) { s2 = ch2.next(); continue; } } if(s1 == null || s2 == null) { if(s1 != s2) return false; ch2 = stack.pop(); ch1 = stack.pop(); } else { // ensure that nodes have same type if(t1 != t2) return false; // compare names QNm n1 = s1.qname(), n2 = s2.qname(); if(n1 != null && (!n1.eq(n2) || flags.contains(Flag.NAMESPACES) && !eq(n1.prefix(), n2.prefix()))) return false; if(t1 == NodeType.TXT || t1 == NodeType.ATT || t1 == NodeType.COM || t1 == NodeType.PI) { // compare string values if(!eq(s1.string(), s2.string())) return false; } else if(t1 == NodeType.ELM) { // compare attributes if(s1.attributes().value().size() != s2.attributes().value().size()) return false; // compare names, values and prefixes final AxisIter ai1 = s1.attributes(); LOOP: for(ANode a1; (a1 = ai1.next()) != null;) { n1 = a1.qname(); final AxisIter ai2 = s2.attributes(); for(ANode a2; (a2 = ai2.next()) != null;) { n2 = a2.qname(); if(!n1.eq(n2)) continue; if(flags.contains(Flag.NAMESPACES) && !eq(n1.prefix(), n2.prefix()) || !eq(a1.string(), a2.string())) return false; continue LOOP; } return false; } // compare namespaces if(flags.contains(Flag.NAMESPACES)) { final Atts ns1 = s1.namespaces(); final Atts ns2 = s2.namespaces(); if(ns1.size() != ns2.size()) return false; LOOP: for(int i1 = 0; i1 < ns1.size(); i1++) { for(int i2 = 0; i2 < ns2.size(); i2++) { if(!eq(ns1.name(i1), ns2.name(i2))) continue; if(!eq(ns1.string(i1), ns2.string(i2))) return false; continue LOOP; } return false; } } // check children stack.push(ch1); stack.push(ch2); ch1 = s1.children(); ch2 = s2.children(); } } // check next child s1 = ch1.next(); s2 = ch2.next(); skip = !flags.contains(Flag.ALLNODES); } while(!stack.isEmpty()); } } }