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}