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}