    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.jxpath.ri.axes;
    019
    020import java.util.ArrayList;
    021import java.util.Collections;
    022import java.util.List;
    023
    024import org.apache.commons.jxpath.JXPathException;
    025import org.apache.commons.jxpath.ri.Compiler;
    026import org.apache.commons.jxpath.ri.EvalContext;
    027import org.apache.commons.jxpath.ri.InfoSetUtil;
    028import org.apache.commons.jxpath.ri.QName;
    029import org.apache.commons.jxpath.ri.compiler.Expression;
    030import org.apache.commons.jxpath.ri.compiler.NameAttributeTest;
    031import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
    032import org.apache.commons.jxpath.ri.compiler.NodeTest;
    033import org.apache.commons.jxpath.ri.compiler.Step;
    034import org.apache.commons.jxpath.ri.model.NodeIterator;
    035import org.apache.commons.jxpath.ri.model.NodePointer;
    036import org.apache.commons.jxpath.ri.model.beans.LangAttributePointer;
    037import org.apache.commons.jxpath.ri.model.beans.NullElementPointer;
    038import org.apache.commons.jxpath.ri.model.beans.NullPropertyPointer;
    039import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer;
    040import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
    041
    042/**
    043 * An evaluation mechanism for simple XPaths, which is much faster than the usual process. It is only used for xpaths which have no context-dependent parts,
    044 * consist entirely of {@code child::name} and {@code self::node()} steps with predicates that either integer or have the form {@code [@name = ...]}.
    045 */
    046public class SimplePathInterpreter {
    047    // Because of the complexity caused by the variety of situations
    048    // that need to be addressed by this class, we attempt to break up
    049    // the class into individual methods addressing those situations
    050    // individually. The names of the methods are supposed to
    051    // give brief descriptions of those situations.
    052
    053    private static final QName QNAME_NAME = new QName(null, "name");
    054    private static final int PERFECT_MATCH = 1000;
    055    // Uncomment this variable and the PATH = ... lines in
    056    // the two following methods in order to be able to print the
    057    // currently evaluated path for debugging of this class
    058//    private static String PATH;       // Debugging
    059
    060    /**
    061     * For a pointer that matches an actual node, returns 0. For a pointer that does not match an actual node, but whose parent pointer does returns -1, etc.
    062     *
    063     * @param pointer input pointer
    064     * @return int match quality code
    065     */
    066    private static int computeQuality(NodePointer pointer) {
    067        int quality = PERFECT_MATCH;
    068        while (pointer != null && !pointer.isActual()) {
    069            quality--;
    070            pointer = pointer.getImmediateParentPointer();
    071        }
    072        return quality;
    073    }
    074
    075    /**
    076     * Create the child pointer for a given step.
    077     *
    078     * @param parentPointer parent pointer
    079     * @param step          associated step
    080     * @return NodePointer
    081     */
    082    private static NodePointer createChildPointerForStep(final PropertyOwnerPointer parentPointer, final Step step) {
    083        final int axis = step.getAxis();
    084        if (axis == Compiler.AXIS_CHILD || axis == Compiler.AXIS_ATTRIBUTE) {
    085            final QName qName = ((NodeNameTest) step.getNodeTest()).getNodeName();
    086            if (axis == Compiler.AXIS_ATTRIBUTE && isLangAttribute(qName)) {
    087                return new LangAttributePointer(parentPointer);
    088            }
    089            if (parentPointer.isValidProperty(qName)) {
    090                final NodePointer childPointer = parentPointer.getPropertyPointer();
    091                ((PropertyPointer) childPointer).setPropertyName(qName.toString());
    092                childPointer.setAttribute(axis == Compiler.AXIS_ATTRIBUTE);
    093                return childPointer;
    094            }
    095            // invalid property gets nothing, not even a NullPointer
    096            return null;
    097        }
    098        return parentPointer;
    099    }
    100
    101    /**
    102     * Creates a "null pointer" that a) represents the requested path and b) can be used for creation of missing nodes in the path.
    103     *
    104     * @param context     evaluation context
    105     * @param parent      parent pointer
    106     * @param steps       path steps
    107     * @param currentStep step number
    108     * @return NodePointer
    109     */
    110    public static NodePointer createNullPointer(final EvalContext context, NodePointer parent, final Step[] steps, final int currentStep) {
    111        if (currentStep == steps.length) {
    112            return parent;
    113        }
    114        parent = valuePointer(parent);
    115        final Step step = steps[currentStep];
    116        final int axis = step.getAxis();
    117        if (axis == Compiler.AXIS_CHILD || axis == Compiler.AXIS_ATTRIBUTE) {
    118            final NullPropertyPointer pointer = new NullPropertyPointer(parent);
    119            final QName qName = ((NodeNameTest) step.getNodeTest()).getNodeName();
    120            pointer.setPropertyName(qName.toString());
    121            pointer.setAttribute(axis == Compiler.AXIS_ATTRIBUTE);
    122            parent = pointer;
    123        }
    124        // else { it is self::node() }
    125        final Expression[] predicates = step.getPredicates();
    126        return createNullPointerForPredicates(context, parent, steps, currentStep, predicates, 0);
    127    }
    128
    129    /**
    130     * Creates a "null pointer" that starts with predicates.
    131     *
    132     * @param context          evaluation context
    133     * @param parent           parent pointer
    134     * @param steps            path steps
    135     * @param currentStep      step number
    136     * @param predicates       predicates
    137     * @param currentPredicate int predicate number
    138     * @return NodePointer
    139     */
    140    private static NodePointer createNullPointerForPredicates(final EvalContext context, NodePointer parent, final Step[] steps, final int currentStep,
    141            final Expression[] predicates, final int currentPredicate) {
    142        for (int i = currentPredicate; i < predicates.length; i++) {
    143            final Expression predicate = predicates[i];
    144            if (predicate instanceof NameAttributeTest) {
    145                final String key = keyFromPredicate(context, predicate);
    146                parent = valuePointer(parent);
    147                final NullPropertyPointer pointer = new NullPropertyPointer(parent);
    148                pointer.setNameAttributeValue(key);
    149                parent = pointer;
    150            } else {
    151                final int index = indexFromPredicate(context, predicate);
    152                if (parent instanceof NullPropertyPointer) {
    153                    parent.setIndex(index);
    154                } else {
    155                    parent = new NullElementPointer(parent, index);
    156                }
    157            }
    158        }
    159        // Proceed with the remaining steps
    160        return createNullPointer(context, parent, steps, currentStep + 1);
    161    }
    162
    163    /**
    164     * Evaluates predicates and proceeds with the subsequent steps of the path.
    165     *
    166     * @param context          evaluation context
    167     * @param parent           parent pointer
    168     * @param steps            path steps
    169     * @param currentStep      step number
    170     * @param predicates       predicate expressions
    171     * @param currentPredicate int predicate number
    172     * @return NodePointer
    173     */
    174    private static NodePointer doPredicate(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep,
    175            final Expression[] predicates, final int currentPredicate) {
    176        if (currentPredicate == predicates.length) {
    177            return doStep(context, parent, steps, currentStep + 1);
    178        }
    179        final Expression predicate = predicates[currentPredicate];
    180        if (predicate instanceof NameAttributeTest) { // [@name = key1]
    181            return doPredicateName(context, parent, steps, currentStep, predicates, currentPredicate);
    182        }
    183        // else [index]
    184        return doPredicateIndex(context, parent, steps, currentStep, predicates, currentPredicate);
    185    }
    186
    187    /**
    188     * Evaluate a subscript predicate: see if the node is a collection and if the index is inside the collection.
    189     *
    190     * @param context          evaluation context
    191     * @param parent           parent pointer
    192     * @param steps            path steps
    193     * @param currentStep      step number
    194     * @param predicates       predicates
    195     * @param currentPredicate int predicate number
    196     * @return NodePointer
    197     */
    198    private static NodePointer doPredicateIndex(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep,
    199            final Expression[] predicates, final int currentPredicate) {
    200        final Expression predicate = predicates[currentPredicate];
    201        final int index = indexFromPredicate(context, predicate);
    202        NodePointer pointer = parent;
    203        if (isCollectionElement(pointer, index)) {
    204            pointer = (NodePointer) pointer.clone();
    205            pointer.setIndex(index);
    206            return doPredicate(context, pointer, steps, currentStep, predicates, currentPredicate + 1);
    207        }
    208        return createNullPointerForPredicates(context, parent, steps, currentStep, predicates, currentPredicate);
    209    }
    210
    211    /**
    212     * Execute a NameAttributeTest predicate
    213     *
    214     * @param context          evaluation context
    215     * @param parent           parent pointer
    216     * @param steps            path steps
    217     * @param currentStep      int step number
    218     * @param predicates       predicates
    219     * @param currentPredicate int predicate number
    220     * @return NodePointer
    221     */
    222    private static NodePointer doPredicateName(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep,
    223            final Expression[] predicates, final int currentPredicate) {
    224        final Expression predicate = predicates[currentPredicate];
    225        final String key = keyFromPredicate(context, predicate);
    226        NodePointer child = valuePointer(parent);
    227        if (child instanceof PropertyOwnerPointer) {
    228            final PropertyPointer pointer = ((PropertyOwnerPointer) child).getPropertyPointer();
    229            pointer.setPropertyName(key);
    230            if (pointer.isActual()) {
    231                return doPredicate(context, pointer, steps, currentStep, predicates, currentPredicate + 1);
    232            }
    233        } else if (child.isCollection()) {
    234            // For each node in the collection, perform the following:
    235            // if the node is a property owner, apply this predicate to it;
    236            // if the node is a collection, apply this predicate to each elem.;
    237            // if the node is not a prop owner or a collection,
    238            // see if it has the attribute "name" with the right value,
    239            // if so - proceed to the next predicate
    240            NodePointer bestMatch = null;
    241            int bestQuality = 0;
    242            child = (NodePointer) child.clone();
    243            final int count = child.getLength();
    244            for (int i = 0; i < count; i++) {
    245                child.setIndex(i);
    246                final NodePointer valuePointer = valuePointer(child);
    247                NodePointer pointer;
    248                if (valuePointer instanceof PropertyOwnerPointer || valuePointer.isCollection()) {
    249                    pointer = doPredicateName(context, valuePointer, steps, currentStep, predicates, currentPredicate);
    250                } else if (isNameAttributeEqual(valuePointer, key)) {
    251                    pointer = doPredicate(context, valuePointer, steps, currentStep, predicates, currentPredicate + 1);
    252                } else {
    253                    pointer = null;
    254                }
    255                if (pointer != null) {
    256                    final int quality = computeQuality(pointer);
    257                    if (quality == PERFECT_MATCH) {
    258                        return pointer;
    259                    }
    260                    if (quality > bestQuality) {
    261                        bestMatch = (NodePointer) pointer.clone();
    262                        bestQuality = quality;
    263                    }
    264                }
    265            }
    266            if (bestMatch != null) {
    267                return bestMatch;
    268            }
    269        } else {
    270            // If the node is a standard InfoSet node (e.g. DOM Node),
    271            // employ doPredicates_standard, which will iterate through
    272            // the node's children and apply all predicates
    273            final NodePointer found = doPredicatesStandard(context, Collections.singletonList(child), steps, currentStep, predicates, currentPredicate);
    274            if (found != null) {
    275                return found;
    276            }
    277        }
    278        // If nothing worked - return a null pointer
    279        return createNullPointerForPredicates(context, child, steps, currentStep, predicates, currentPredicate);
    280    }
    281
    282    /**
    283     * Called exclusively for standard InfoSet nodes, e.g. DOM nodes to evaluate predicate sequences like [@name=...][@name=...][index].
    284     *
    285     * @param context          evaluation context
    286     * @param parents          List of parent pointers
    287     * @param steps            path steps
    288     * @param currentStep      step number
    289     * @param predicates       predicates
    290     * @param currentPredicate int predicate number
    291     * @return NodePointer
    292     */
    293    private static NodePointer doPredicatesStandard(final EvalContext context, final List<NodePointer> parents, final Step[] steps, final int currentStep,
    294            final Expression[] predicates, final int currentPredicate) {
    295        if (parents.isEmpty()) {
    296            return null;
    297        }
    298        // If all predicates have been processed, take the first
    299        // element from the list of results and proceed to the
    300        // remaining steps with that element.
    301        if (currentPredicate == predicates.length) {
    302            final NodePointer pointer = parents.get(0);
    303            return doStep(context, pointer, steps, currentStep + 1);
    304        }
    305        final Expression predicate = predicates[currentPredicate];
    306        if (predicate instanceof NameAttributeTest) {
    307            final String key = keyFromPredicate(context, predicate);
    308            final List<NodePointer> newList = new ArrayList<>();
    309            for (int i = 0; i < parents.size(); i++) {
    310                final NodePointer pointer = parents.get(i);
    311                if (isNameAttributeEqual(pointer, key)) {
    312                    newList.add(pointer);
    313                }
    314            }
    315            if (newList.isEmpty()) {
    316                return null;
    317            }
    318            return doPredicatesStandard(context, newList, steps, currentStep, predicates, currentPredicate + 1);
    319        }
    320        // For a subscript, simply take the corresponding
    321        // element from the list of results and
    322        // proceed to the remaining predicates with that element
    323        final int index = indexFromPredicate(context, predicate);
    324        if (index < 0 || index >= parents.size()) {
    325            return null;
    326        }
    327        final NodePointer ptr = parents.get(index);
    328        return doPredicate(context, ptr, steps, currentStep, predicates, currentPredicate + 1);
    329    }
    330
    331    /**
    332     * Recursive evaluation of a path. The general plan is: Look at the current step, find nodes that match it, iterate over those nodes and for each of them
    333     * call doStep again for subsequent steps.
    334     *
    335     * @param context     evaluation context
    336     * @param parent      parent pointer
    337     * @param steps       path steps
    338     * @param currentStep step number
    339     * @return NodePointer
    340     */
    341    private static NodePointer doStep(final EvalContext context, NodePointer parent, final Step[] steps, final int currentStep) {
    342        if (parent == null) {
    343            return null;
    344        }
    345        if (currentStep == steps.length) {
    346            // We have reached the end of the list of steps
    347            return parent;
    348        }
    349        // Open all containers
    350        parent = valuePointer(parent);
    351        final Step step = steps[currentStep];
    352        final Expression[] predicates = step.getPredicates();
    353        // Divide and conquer: the process is broken out into
    354        // four major use cases.
    355        // 1. Current step has no predicates and
    356        // the root is a property owner (e.g. bean or map)
    357        // 2. Current step has predicates and
    358        // the root is a property owner (e.g. bean or map)
    359        // 3. Current step has no predicates and
    360        // the root is an InfoSet standard node (e.g. DOM Node)
    361        // 4. Current step has predicates and
    362        // the root is an InfoSet standard node (e.g. DOM Node)
    363        if (parent instanceof PropertyOwnerPointer) {
    364            if (predicates == null || predicates.length == 0) {
    365                return doStepNoPredicatesPropertyOwner(context, (PropertyOwnerPointer) parent, steps, currentStep);
    366            }
    367            return doStepPredicatesPropertyOwner(context, (PropertyOwnerPointer) parent, steps, currentStep);
    368        }
    369        if (predicates == null || predicates.length == 0) {
    370            return doStepNoPredicatesStandard(context, parent, steps, currentStep);
    371        }
    372        return doStepPredicatesStandard(context, parent, steps, currentStep);
    373    }
    374
    375    /**
    376     * We have a step that starts with a property owner (bean, map, etc) and has no predicates. The name test of the step may map to a scalar property or to a
    377     * collection. If it is a collection, we should apply the tail of the path to each element until we find a match. If we don't find a perfect match, we
    378     * should return the "best quality" pointer, which has the longest chain of steps mapping to existing nodes and the shortes tail of Null* pointers.
    379     *
    380     * @param context       evaluation context
    381     * @param parentPointer property owner pointer
    382     * @param steps         path steps
    383     * @param currentStep   step number
    384     * @return NodePointer
    385     */
    386    private static NodePointer doStepNoPredicatesPropertyOwner(final EvalContext context, final PropertyOwnerPointer parentPointer, final Step[] steps,
    387            final int currentStep) {
    388        final Step step = steps[currentStep];
    389        NodePointer childPointer = createChildPointerForStep(parentPointer, step);
    390        if (childPointer == null) {
    391            return null;
    392        }
    393        if (!childPointer.isActual()) {
    394            // The property does not exist - create a null pointer.
    395            return createNullPointer(context, parentPointer, steps, currentStep);
    396        }
    397        if (currentStep == steps.length - 1) {
    398            // If this is the last step - we are done, we found it
    399            return childPointer;
    400        }
    401        if (childPointer.isCollection()) {
    402            // Iterate over all values and
    403            // execute remaining steps for each node,
    404            // looking for the best quality match
    405            int bestQuality = 0;
    406            childPointer = (NodePointer) childPointer.clone();
    407            NodePointer bestMatch = null;
    408            final int count = childPointer.getLength();
    409            for (int i = 0; i < count; i++) {
    410                childPointer.setIndex(i);
    411                final NodePointer pointer = doStep(context, childPointer, steps, currentStep + 1);
    412                final int quality = computeQuality(pointer);
    413                if (quality == PERFECT_MATCH) {
    414                    return pointer;
    415                }
    416                if (quality > bestQuality) {
    417                    bestQuality = quality;
    418                    bestMatch = (NodePointer) pointer.clone();
    419                }
    420            }
    421            if (bestMatch != null) {
    422                return bestMatch;
    423            }
    424            // This step did not find anything - return a null pointer
    425            return createNullPointer(context, childPointer, steps, currentStep);
    426        }
    427        // Evaluate subsequent steps
    428        return doStep(context, childPointer, steps, currentStep + 1);
    429    }
    430
    431    /**
    432     * A path that starts with a standard InfoSet node (e.g. DOM Node) and has no predicates. Get a child iterator and apply the tail of the path to each
    433     * element until we find a match. If we don't find a perfect match, we should return the "best quality" pointer, which has the longest chain of steps
    434     * mapping to existing nodes and the shortes tail of Null* pointers.
    435     *
    436     * @param context       evaluation context
    437     * @param parentPointer parent pointer
    438     * @param steps         path steps
    439     * @param currentStep   step number
    440     * @return NodePointer
    441     */
    442    private static NodePointer doStepNoPredicatesStandard(final EvalContext context, final NodePointer parentPointer, final Step[] steps,
    443            final int currentStep) {
    444        final Step step = steps[currentStep];
    445        if (step.getAxis() == Compiler.AXIS_SELF) {
    446            return doStep(context, parentPointer, steps, currentStep + 1);
    447        }
    448        int bestQuality = 0;
    449        NodePointer bestMatch = null;
    450        final NodeIterator it = getNodeIterator(context, parentPointer, step);
    451        if (it != null) {
    452            for (int i = 1; it.setPosition(i); i++) {
    453                final NodePointer childPointer = it.getNodePointer();
    454                if (steps.length == currentStep + 1) {
    455                    // If this is the last step - we are done, we found it
    456                    return childPointer;
    457                }
    458                final NodePointer pointer = doStep(context, childPointer, steps, currentStep + 1);
    459                final int quality = computeQuality(pointer);
    460                if (quality == PERFECT_MATCH) {
    461                    return pointer;
    462                }
    463                if (quality > bestQuality) {
    464                    bestQuality = quality;
    465                    bestMatch = (NodePointer) pointer.clone();
    466                }
    467            }
    468        }
    469        return bestMatch != null ? bestMatch : createNullPointer(context, parentPointer, steps, currentStep);
    470    }
    471
    472    /**
    473     * A path that starts with a property owner. The method evaluates the first predicate in a special way and then forwards to a general predicate processing
    474     * method.
    475     *
    476     * @param context       evaluation context
    477     * @param parentPointer parent pointer
    478     * @param steps         path steps
    479     * @param currentStep   step number
    480     * @return NodePointer
    481     */
    482    private static NodePointer doStepPredicatesPropertyOwner(final EvalContext context, final PropertyOwnerPointer parentPointer, final Step[] steps,
    483            final int currentStep) {
    484        final Step step = steps[currentStep];
    485        final Expression[] predicates = step.getPredicates();
    486        final NodePointer childPointer = createChildPointerForStep(parentPointer, step);
    487        if (!childPointer.isActual()) {
    488            // Property does not exist - return a null pointer
    489            return createNullPointer(context, parentPointer, steps, currentStep);
    490        }
    491        // Evaluate predicates
    492        return doPredicate(context, childPointer, steps, currentStep, predicates, 0);
    493    }
    494
    495    /**
    496     * A path that starts with a standard InfoSet node, e.g. a DOM Node. The method evaluates the first predicate in a special way and then forwards to a
    497     * general predicate processing method.
    498     *
    499     * @param context     evaluation context
    500     * @param parent      parent pointer
    501     * @param steps       path steps
    502     * @param currentStep step number
    503     * @return NodePointer
    504     */
    505    private static NodePointer doStepPredicatesStandard(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep) {
    506        final Step step = steps[currentStep];
    507        final Expression[] predicates = step.getPredicates();
    508        final int axis = step.getAxis();
    509        if (axis == Compiler.AXIS_SELF) {
    510            return doPredicate(context, parent, steps, currentStep, predicates, 0);
    511        }
    512        final Expression predicate = predicates[0];
    513        // Optimize for a single predicate to avoid building a list
    514        // and to allow the direct access to the index'th element
    515        // in the case of a simple subscript predecate
    516        // It is a very common use case, so it deserves individual
    517        // attention
    518        if (predicates.length == 1) {
    519            final NodeIterator it = getNodeIterator(context, parent, step);
    520            NodePointer pointer = null;
    521            if (it != null) {
    522                if (predicate instanceof NameAttributeTest) { // [@name = key]
    523                    final String key = keyFromPredicate(context, predicate);
    524                    for (int i = 1; it.setPosition(i); i++) {
    525                        final NodePointer ptr = it.getNodePointer();
    526                        if (isNameAttributeEqual(ptr, key)) {
    527                            pointer = ptr;
    528                            break;
    529                        }
    530                    }
    531                } else {
    532                    final int index = indexFromPredicate(context, predicate);
    533                    if (it.setPosition(index + 1)) {
    534                        pointer = it.getNodePointer();
    535                    }
    536                }
    537            }
    538            if (pointer != null) {
    539                return doStep(context, pointer, steps, currentStep + 1);
    540            }
    541        } else {
    542            final NodeIterator it = getNodeIterator(context, parent, step);
    543            if (it != null) {
    544                final List<NodePointer> list = new ArrayList<>();
    545                for (int i = 1; it.setPosition(i); i++) {
    546                    list.add(it.getNodePointer());
    547                }
    548                final NodePointer pointer = doPredicatesStandard(context, list, steps, currentStep, predicates, 0);
    549                if (pointer != null) {
    550                    return pointer;
    551                }
    552            }
    553        }
    554        return createNullPointer(context, parent, steps, currentStep);
    555    }
    556
    557    /**
    558     * Gets a NodeIterator.
    559     *
    560     * @param context evaluation context
    561     * @param pointer owning pointer
    562     * @param step    triggering step
    563     * @return NodeIterator
    564     */
    565    private static NodeIterator getNodeIterator(final EvalContext context, final NodePointer pointer, final Step step) {
    566        if (step.getAxis() == Compiler.AXIS_CHILD) {
    567            NodeTest nodeTest = step.getNodeTest();
    568            final QName qname = ((NodeNameTest) nodeTest).getNodeName();
    569            final String prefix = qname.getPrefix();
    570            if (prefix != null) {
    571                final String namespaceURI = context.getJXPathContext().getNamespaceURI(prefix);
    572                nodeTest = new NodeNameTest(qname, namespaceURI);
    573            }
    574            return pointer.childIterator(nodeTest, false, null);
    575        }
    576        // else Compiler.AXIS_ATTRIBUTE
    577        if (!(step.getNodeTest() instanceof NodeNameTest)) {
    578            throw new UnsupportedOperationException("Not supported node test for attributes: " + step.getNodeTest());
    579        }
    580        return pointer.attributeIterator(((NodeNameTest) step.getNodeTest()).getNodeName());
    581    }
    582
    583    /**
    584     * Extract an integer from a subscript predicate. The returned index starts with 0, even though the subscript starts with 1.
    585     *
    586     * @param context   evaluation context
    587     * @param predicate to evaluate
    588     * @return calculated index
    589     */
    590    private static int indexFromPredicate(final EvalContext context, final Expression predicate) {
    591        Object value = predicate.computeValue(context);
    592        if (value instanceof EvalContext) {
    593            value = ((EvalContext) value).getSingleNodePointer();
    594        }
    595        if (value instanceof NodePointer) {
    596            value = ((NodePointer) value).getValue();
    597        }
    598        if (value == null) {
    599            throw new JXPathException("Predicate value is null: " + predicate);
    600        }
    601        if (value instanceof Number) {
    602            final double round = 0.5;
    603            return (int) (InfoSetUtil.doubleValue(value) + round) - 1;
    604        }
    605        return InfoSetUtil.booleanValue(value) ? 0 : -1;
    606    }
    607
    608    /**
    609     * Interpret the steps of a simple expression path that starts with the given root, which is the result of evaluation of the root expression of the
    610     * expression path, applies the given predicates to it and then follows the given steps. All steps must have the axis "child::" or "attribute::" and a name
    611     * test. They can also optionally have predicates of type [@name=...] or simply [...] interpreted as an index.
    612     *
    613     * @param context    evaluation context
    614     * @param root       root pointer
    615     * @param predicates predicates corresponding to {@code steps}
    616     * @param steps      path steps
    617     * @return NodePointer
    618     */
    619    public static NodePointer interpretSimpleExpressionPath(final EvalContext context, final NodePointer root, final Expression[] predicates,
    620            final Step[] steps) {
    621//        PATH = createNullPointerForPredicates(context, root,
    622//                    steps, -1, predicates, 0).toString();  // Debugging
    623        final NodePointer pointer = doPredicate(context, root, steps, -1, predicates, 0);
    624//        return valuePointer(pointer);
    625        return pointer;
    626    }
    627
    628    /**
    629     * Interpret a simple path that starts with the given root and follows the given steps. All steps must have the axis "child::" and a name test. They can
    630     * also optionally have predicates of type [@name=expression] or simply [expression] interpreted as an index.
    631     *
    632     * @param context evaluation context
    633     * @param root    root pointer
    634     * @param steps   path steps
    635     * @return NodePointer
    636     */
    637    public static NodePointer interpretSimpleLocationPath(final EvalContext context, final NodePointer root, final Step[] steps) {
    638//        PATH = createNullPointer(context, root, steps, 0).toString();  // Dbg
    639        final NodePointer pointer = doStep(context, root, steps, 0);
    640//        return valuePointer(pointer);
    641        return pointer;
    642    }
    643
    644    /**
    645     * Returns true if the pointer is a collection and the index is withing the bounds of the collection.
    646     *
    647     * @param pointer input pointer
    648     * @param index   to check
    649     * @return boolean
    650     */
    651    private static boolean isCollectionElement(final NodePointer pointer, final int index) {
    652        return pointer.isActual() && (index == 0 || pointer.isCollection() && index >= 0 && index < pointer.getLength());
    653    }
    654
    655    /**
    656     * Tests whether {@code name} is a lang attribute.
    657     *
    658     * @param qName to compare
    659     * @return boolean
    660     */
    661    private static boolean isLangAttribute(final QName qName) {
    662        return qName.getPrefix() != null && qName.getPrefix().equals("xml") && qName.getName().equals("lang");
    663    }
    664
    665    /**
    666     * Returns true if the pointer has an attribute called "name" and its value is equal to the supplied string.
    667     *
    668     * @param pointer input pointer
    669     * @param name    name to check
    670     * @return boolean
    671     */
    672    private static boolean isNameAttributeEqual(final NodePointer pointer, final String name) {
    673        final NodeIterator it = pointer.attributeIterator(QNAME_NAME);
    674        return it != null && it.setPosition(1) && name.equals(it.getNodePointer().getValue());
    675    }
    676
    677    /**
    678     * Extracts the string value of the expression from a predicate like [@name=expression].
    679     *
    680     * @param context   evaluation context
    681     * @param predicate predicate to evaluate
    682     * @return String key extracted
    683     */
    684    private static String keyFromPredicate(final EvalContext context, final Expression predicate) {
    685        final Expression expr = ((NameAttributeTest) predicate).getNameTestExpression();
    686        return InfoSetUtil.stringValue(expr.computeValue(context));
    687    }
    688
    689    /**
    690     * For an intermediate pointer (e.g. PropertyPointer, ContainerPointer) returns a pointer for the contained value.
    691     *
    692     * @param pointer input pointer
    693     * @return NodePointer
    694     */
    695    private static NodePointer valuePointer(final NodePointer pointer) {
    696        return pointer == null ? null : pointer.getValuePointer();
    697    }
    698
    699    /**
    700     * Constructs a new instance.
    701     *
    702     * @deprecated Will be private in the next major version.
    703     */
    704    @Deprecated
    705    public SimplePathInterpreter() {
    706        // empty
    707    }
    708}