package org.basex.query.path; import static org.basex.query.QueryText.*; import static org.basex.query.util.Err.*; import java.io.IOException; import org.basex.data.Data; import org.basex.index.Names; import org.basex.index.Stats; import org.basex.index.path.PathNode; import org.basex.io.serial.Serializer; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.expr.Expr; import org.basex.query.expr.Preds; import org.basex.query.item.ANode; import org.basex.query.item.Empty; import org.basex.query.item.Item; import org.basex.query.item.NodeType; import org.basex.query.item.SeqType; import org.basex.query.item.Type; import org.basex.query.item.Value; import org.basex.query.iter.AxisIter; import org.basex.query.iter.NodeCache; import org.basex.query.iter.NodeIter; import org.basex.query.path.Test.Name; import org.basex.util.Array; import org.basex.util.InputInfo; import org.basex.util.Token; import org.basex.util.list.ObjList; /** * Location Step expression. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public class AxisStep extends Preds { /** Axis. */ Axis axis; /** Node test. */ public Test test; /** * This method creates a copy from the specified step. * @param s step to be copied * @return step */ public static AxisStep get(final AxisStep s) { return get(s.input, s.axis, s.test, s.preds); } /** * This method creates a step instance. * @param ii input info * @param a axis * @param t node test * @param p predicates * @return step */ public static AxisStep get(final InputInfo ii, final Axis a, final Test t, final Expr... p) { boolean num = false; for(final Expr pr : p) num |= pr.type().mayBeNum() || pr.uses(Use.POS); return num ? new AxisStep(ii, a, t, p) : new IterStep(ii, a, t, p); } /** * Constructor. * @param ii input info * @param a axis * @param t node test * @param p predicates */ AxisStep(final InputInfo ii, final Axis a, final Test t, final Expr... p) { super(ii, p); axis = a; test = t; type = SeqType.NOD_ZM; } @Override public final Expr comp(final QueryContext ctx) throws QueryException { if(!test.comp(ctx)) return Empty.SEQ; // leaf flag indicates that a context node can be replaced by a text() step final Data data = ctx.data(); ctx.leaf = data != null && test.test == Name.NAME && test.type != NodeType.ATT && axis.down && data.meta.uptodate && data.nspaces.size() == 0; if(ctx.leaf) { final Stats s = data.tagindex.stat(data.tagindex.id(((NameTest) test).ln)); ctx.leaf = s != null && s.isLeaf(); } // as predicates will not necessarily start from the document node, // the context item type is temporarily generalized final Type ct = ctx.value != null ? ctx.value.type : null; if(ct == NodeType.DOC) ctx.value.type = NodeType.NOD; final Expr e = super.comp(ctx); if(ct == NodeType.DOC) ctx.value.type = ct; ctx.leaf = false; // return optimized step / don't re-optimize step if(e != this || e instanceof IterStep) return e; // no numeric predicates.. use simple iterator if(!uses(Use.POS)) return new IterStep(input, axis, test, preds); // don't re-optimize step if(this instanceof IterPosStep) return this; // use iterator for simple numeric predicate return useIterator() ? new IterPosStep(this) : this; } @Override public NodeIter iter(final QueryContext ctx) throws QueryException { final Value v = checkCtx(ctx); if(!v.type.isNode()) NODESPATH.thrw(input, this, v.type); final AxisIter ai = axis.iter((ANode) v); final NodeCache nc = new NodeCache(); for(ANode n; (n = ai.next()) != null;) if(test.eval(n)) nc.add(n.finish()); // evaluate predicates for(final Expr p : preds) { ctx.size = nc.size(); ctx.pos = 1; int c = 0; for(int n = 0; n < nc.size(); ++n) { ctx.value = nc.get(n); final Item i = p.test(ctx, input); if(i != null) { // assign score value nc.get(n).score(i.score()); nc.item[c++] = nc.get(n); } ctx.pos++; } nc.size(c); } return nc; } /** * Checks if this is a simple axis without predicates. * @param ax axis to be checked * @param name name/node test * @return result of check */ public final boolean simple(final Axis ax, final boolean name) { return axis == ax && preds.length == 0 && (name ? test.test == Name.NAME : test == Test.NOD); } /** * Returns the path nodes that are the result of this step. * @param nodes initial path nodes * @param data data reference * @return resulting path nodes, or {@code null} if nodes cannot be evaluated */ final ObjList<PathNode> nodes(final ObjList<PathNode> nodes, final Data data) { // skip steps with predicates or different namespaces if(preds.length != 0 || data.nspaces.globalNS() == null) return null; // check restrictions on node type int kind = -1, name = 0; if(test.type != null) { kind = ANode.kind(test.type); if(kind == Data.PI) return null; if(test.test == Name.NAME) { // element/attribute test (*:ln) final Names names = kind == Data.ATTR ? data.atnindex : data.tagindex; name = names.id(((NameTest) test).ln); } else if(test.test != null && test.test != Name.ALL) { // skip namespace and standard tests return null; } } // skip axes other than descendant, child, and attribute if(axis != Axis.ATTR && axis != Axis.CHILD && axis != Axis.DESC && axis != Axis.DESCORSELF && axis != Axis.SELF) return null; final ObjList<PathNode> tmp = new ObjList<PathNode>(); for(final PathNode n : nodes) { if(axis == Axis.SELF || axis == Axis.DESCORSELF) { if(kind == -1 || kind == n.kind && (name == 0 || name == n.name)) { if(!tmp.contains(n)) tmp.add(n); } } if(axis != Axis.SELF) add(n, tmp, name, kind); } return tmp; } /** * Adds path nodes to the list if they comply with the given test conditions. * @param node root node * @param nodes output nodes * @param name name id, or {@code 0} as wildcard * @param kind node kind, or {@code -1} for all types */ private void add(final PathNode node, final ObjList<PathNode> nodes, final int name, final int kind) { for(final PathNode n : node.ch) { if(axis == Axis.DESC || axis == Axis.DESCORSELF) { add(n, nodes, name, kind); } if(kind == -1 && n.kind != Data.ATTR ^ axis == Axis.ATTR || kind == n.kind && (name == 0 || name == n.name)) { if(!nodes.contains(n)) nodes.add(n); } } } /** * Adds predicates to the step. * @param prds predicates to be added * @return resulting step instance */ final AxisStep addPreds(final Expr... prds) { for(final Expr p : prds) preds = Array.add(preds, p); return get(input, axis, test, preds); } @Override public final boolean sameAs(final Expr cmp) { if(!(cmp instanceof AxisStep)) return false; final AxisStep st = (AxisStep) cmp; if(preds.length != st.preds.length || axis != st.axis || !test.sameAs(st.test)) return false; for(int p = 0; p < preds.length; ++p) { if(!preds[p].sameAs(st.preds[p])) return false; } return true; } @Override public final void plan(final Serializer ser) throws IOException { ser.openElement(this); ser.attribute(AXIS, Token.token(axis.name)); ser.attribute(TEST, Token.token(test.toString())); super.plan(ser); ser.closeElement(); } @Override public final String toString() { final StringBuilder sb = new StringBuilder(); if(test == Test.NOD) { if(axis == Axis.PARENT) sb.append(".."); if(axis == Axis.SELF) sb.append('.'); } if(sb.length() == 0) { if(axis == Axis.ATTR && test != Test.NOD) sb.append('@'); else if(axis != Axis.CHILD) sb.append(axis).append("::"); sb.append(test); } return sb.append(super.toString()).toString(); } }