1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18/* 19 * $Id: NodeTest.java 468655 2006-10-28 07:12:06Z minchau $ 20 */ 21package org.apache.xpath.patterns; 22 23import org.apache.xml.dtm.DTM; 24import org.apache.xml.dtm.DTMFilter; 25import org.apache.xpath.Expression; 26import org.apache.xpath.ExpressionOwner; 27import org.apache.xpath.XPath; 28import org.apache.xpath.XPathContext; 29import org.apache.xpath.XPathVisitor; 30import org.apache.xpath.objects.XNumber; 31import org.apache.xpath.objects.XObject; 32 33/** 34 * This is the basic node test class for both match patterns and location path 35 * steps. 36 * @xsl.usage advanced 37 */ 38public class NodeTest extends Expression 39{ 40 static final long serialVersionUID = -5736721866747906182L; 41 42 /** 43 * The namespace or local name for node tests with a wildcard. 44 * @see <a href="http://www.w3.org/TR/xpath#NT-NameTest">the XPath NameTest production.</a> 45 */ 46 public static final String WILD = "*"; 47 48 /** 49 * The URL to pass to the Node#supports method, to see if the 50 * DOM has already been stripped of whitespace nodes. 51 */ 52 public static final String SUPPORTS_PRE_STRIPPING = 53 "http://xml.apache.org/xpath/features/whitespace-pre-stripping"; 54 55 /** 56 * This attribute determines which node types are accepted. 57 * @serial 58 */ 59 protected int m_whatToShow; 60 61 /** 62 * Special bitmap for match patterns starting with a function. 63 * Make sure this does not conflict with {@link org.w3c.dom.traversal.NodeFilter}. 64 */ 65 public static final int SHOW_BYFUNCTION = 0x00010000; 66 67 /** 68 * This attribute determines which node types are accepted. 69 * These constants are defined in the {@link org.w3c.dom.traversal.NodeFilter} 70 * interface. 71 * 72 * @return bitset mainly defined in {@link org.w3c.dom.traversal.NodeFilter}. 73 */ 74 public int getWhatToShow() 75 { 76 return m_whatToShow; 77 } 78 79 /** 80 * This attribute determines which node types are accepted. 81 * These constants are defined in the {@link org.w3c.dom.traversal.NodeFilter} 82 * interface. 83 * 84 * @param what bitset mainly defined in {@link org.w3c.dom.traversal.NodeFilter}. 85 */ 86 public void setWhatToShow(int what) 87 { 88 m_whatToShow = what; 89 } 90 91 /** 92 * The namespace to be tested for, which may be null. 93 * @serial 94 */ 95 String m_namespace; 96 97 /** 98 * Return the namespace to be tested. 99 * 100 * @return The namespace to be tested for, or {@link #WILD}, or null. 101 */ 102 public String getNamespace() 103 { 104 return m_namespace; 105 } 106 107 /** 108 * Set the namespace to be tested. 109 * 110 * @param ns The namespace to be tested for, or {@link #WILD}, or null. 111 */ 112 public void setNamespace(String ns) 113 { 114 m_namespace = ns; 115 } 116 117 /** 118 * The local name to be tested for. 119 * @serial 120 */ 121 protected String m_name; 122 123 /** 124 * Return the local name to be tested. 125 * 126 * @return the local name to be tested, or {@link #WILD}, or an empty string. 127 */ 128 public String getLocalName() 129 { 130 return (null == m_name) ? "" : m_name; 131 } 132 133 /** 134 * Set the local name to be tested. 135 * 136 * @param name the local name to be tested, or {@link #WILD}, or an empty string. 137 */ 138 public void setLocalName(String name) 139 { 140 m_name = name; 141 } 142 143 /** 144 * Statically calculated score for this test. One of 145 * {@link #SCORE_NODETEST}, 146 * {@link #SCORE_NONE}, 147 * {@link #SCORE_NSWILD}, 148 * {@link #SCORE_QNAME}, or 149 * {@link #SCORE_OTHER}. 150 * @serial 151 */ 152 XNumber m_score; 153 154 /** 155 * The match score if the pattern consists of just a NodeTest. 156 * @see <a href="http://www.w3.org/TR/xslt#conflict">XSLT Specification - 5.5 Conflict Resolution for Template Rules</a> 157 */ 158 public static final XNumber SCORE_NODETEST = 159 new XNumber(XPath.MATCH_SCORE_NODETEST); 160 161 /** 162 * The match score if the pattern pattern has the form NCName:*. 163 * @see <a href="http://www.w3.org/TR/xslt#conflict">XSLT Specification - 5.5 Conflict Resolution for Template Rules</a> 164 */ 165 public static final XNumber SCORE_NSWILD = 166 new XNumber(XPath.MATCH_SCORE_NSWILD); 167 168 /** 169 * The match score if the pattern has the form 170 * of a QName optionally preceded by an @ character. 171 * @see <a href="http://www.w3.org/TR/xslt#conflict">XSLT Specification - 5.5 Conflict Resolution for Template Rules</a> 172 */ 173 public static final XNumber SCORE_QNAME = 174 new XNumber(XPath.MATCH_SCORE_QNAME); 175 176 /** 177 * The match score if the pattern consists of something 178 * other than just a NodeTest or just a qname. 179 * @see <a href="http://www.w3.org/TR/xslt#conflict">XSLT Specification - 5.5 Conflict Resolution for Template Rules</a> 180 */ 181 public static final XNumber SCORE_OTHER = 182 new XNumber(XPath.MATCH_SCORE_OTHER); 183 184 /** 185 * The match score if no match is made. 186 * @see <a href="http://www.w3.org/TR/xslt#conflict">XSLT Specification - 5.5 Conflict Resolution for Template Rules</a> 187 */ 188 public static final XNumber SCORE_NONE = 189 new XNumber(XPath.MATCH_SCORE_NONE); 190 191 /** 192 * Construct an NodeTest that tests for namespaces and node names. 193 * 194 * 195 * @param whatToShow Bit set defined mainly by {@link org.w3c.dom.traversal.NodeFilter}. 196 * @param namespace The namespace to be tested. 197 * @param name The local name to be tested. 198 */ 199 public NodeTest(int whatToShow, String namespace, String name) 200 { 201 initNodeTest(whatToShow, namespace, name); 202 } 203 204 /** 205 * Construct an NodeTest that doesn't test for node names. 206 * 207 * 208 * @param whatToShow Bit set defined mainly by {@link org.w3c.dom.traversal.NodeFilter}. 209 */ 210 public NodeTest(int whatToShow) 211 { 212 initNodeTest(whatToShow); 213 } 214 215 /** 216 * @see Expression#deepEquals(Expression) 217 */ 218 public boolean deepEquals(Expression expr) 219 { 220 if(!isSameClass(expr)) 221 return false; 222 223 NodeTest nt = (NodeTest)expr; 224 225 if(null != nt.m_name) 226 { 227 if(null == m_name) 228 return false; 229 else if(!nt.m_name.equals(m_name)) 230 return false; 231 } 232 else if(null != m_name) 233 return false; 234 235 if(null != nt.m_namespace) 236 { 237 if(null == m_namespace) 238 return false; 239 else if(!nt.m_namespace.equals(m_namespace)) 240 return false; 241 } 242 else if(null != m_namespace) 243 return false; 244 245 if(m_whatToShow != nt.m_whatToShow) 246 return false; 247 248 if(m_isTotallyWild != nt.m_isTotallyWild) 249 return false; 250 251 return true; 252 } 253 254 /** 255 * Null argument constructor. 256 */ 257 public NodeTest(){} 258 259 /** 260 * Initialize this node test by setting the whatToShow property, and 261 * calculating the score that this test will return if a test succeeds. 262 * 263 * 264 * @param whatToShow Bit set defined mainly by {@link org.w3c.dom.traversal.NodeFilter}. 265 */ 266 public void initNodeTest(int whatToShow) 267 { 268 269 m_whatToShow = whatToShow; 270 271 calcScore(); 272 } 273 274 /** 275 * Initialize this node test by setting the whatToShow property and the 276 * namespace and local name, and 277 * calculating the score that this test will return if a test succeeds. 278 * 279 * 280 * @param whatToShow Bit set defined mainly by {@link org.w3c.dom.traversal.NodeFilter}. 281 * @param namespace The namespace to be tested. 282 * @param name The local name to be tested. 283 */ 284 public void initNodeTest(int whatToShow, String namespace, String name) 285 { 286 287 m_whatToShow = whatToShow; 288 m_namespace = namespace; 289 m_name = name; 290 291 calcScore(); 292 } 293 294 /** 295 * True if this test has a null namespace and a local name of {@link #WILD}. 296 * @serial 297 */ 298 private boolean m_isTotallyWild; 299 300 /** 301 * Get the static score for this node test. 302 * @return Should be one of the SCORE_XXX constants. 303 */ 304 public XNumber getStaticScore() 305 { 306 return m_score; 307 } 308 309 /** 310 * Set the static score for this node test. 311 * @param score Should be one of the SCORE_XXX constants. 312 */ 313 public void setStaticScore(XNumber score) 314 { 315 m_score = score; 316 } 317 318 /** 319 * Static calc of match score. 320 */ 321 protected void calcScore() 322 { 323 324 if ((m_namespace == null) && (m_name == null)) 325 m_score = SCORE_NODETEST; 326 else if (((m_namespace == WILD) || (m_namespace == null)) 327 && (m_name == WILD)) 328 m_score = SCORE_NODETEST; 329 else if ((m_namespace != WILD) && (m_name == WILD)) 330 m_score = SCORE_NSWILD; 331 else 332 m_score = SCORE_QNAME; 333 334 m_isTotallyWild = (m_namespace == null && m_name == WILD); 335 } 336 337 /** 338 * Get the score that this test will return if a test succeeds. 339 * 340 * 341 * @return the score that this test will return if a test succeeds. 342 */ 343 public double getDefaultScore() 344 { 345 return m_score.num(); 346 } 347 348 /** 349 * Tell what node type to test, if not DTMFilter.SHOW_ALL. 350 * 351 * @param whatToShow Bit set defined mainly by 352 * {@link org.apache.xml.dtm.DTMFilter}. 353 * @return the node type for the whatToShow. Since whatToShow can specify 354 * multiple types, it will return the first bit tested that is on, 355 * so the caller of this function should take care that this is 356 * the function they really want to call. If none of the known bits 357 * are set, this function will return zero. 358 */ 359 public static int getNodeTypeTest(int whatToShow) 360 { 361 // %REVIEW% Is there a better way? 362 if (0 != (whatToShow & DTMFilter.SHOW_ELEMENT)) 363 return DTM.ELEMENT_NODE; 364 365 if (0 != (whatToShow & DTMFilter.SHOW_ATTRIBUTE)) 366 return DTM.ATTRIBUTE_NODE; 367 368 if (0 != (whatToShow & DTMFilter.SHOW_TEXT)) 369 return DTM.TEXT_NODE; 370 371 if (0 != (whatToShow & DTMFilter.SHOW_DOCUMENT)) 372 return DTM.DOCUMENT_NODE; 373 374 if (0 != (whatToShow & DTMFilter.SHOW_DOCUMENT_FRAGMENT)) 375 return DTM.DOCUMENT_FRAGMENT_NODE; 376 377 if (0 != (whatToShow & DTMFilter.SHOW_NAMESPACE)) 378 return DTM.NAMESPACE_NODE; 379 380 if (0 != (whatToShow & DTMFilter.SHOW_COMMENT)) 381 return DTM.COMMENT_NODE; 382 383 if (0 != (whatToShow & DTMFilter.SHOW_PROCESSING_INSTRUCTION)) 384 return DTM.PROCESSING_INSTRUCTION_NODE; 385 386 if (0 != (whatToShow & DTMFilter.SHOW_DOCUMENT_TYPE)) 387 return DTM.DOCUMENT_TYPE_NODE; 388 389 if (0 != (whatToShow & DTMFilter.SHOW_ENTITY)) 390 return DTM.ENTITY_NODE; 391 392 if (0 != (whatToShow & DTMFilter.SHOW_ENTITY_REFERENCE)) 393 return DTM.ENTITY_REFERENCE_NODE; 394 395 if (0 != (whatToShow & DTMFilter.SHOW_NOTATION)) 396 return DTM.NOTATION_NODE; 397 398 if (0 != (whatToShow & DTMFilter.SHOW_CDATA_SECTION)) 399 return DTM.CDATA_SECTION_NODE; 400 401 402 return 0; 403 } 404 405 406 /** 407 * Do a diagnostics dump of a whatToShow bit set. 408 * 409 * 410 * @param whatToShow Bit set defined mainly by 411 * {@link org.apache.xml.dtm.DTMFilter}. 412 */ 413 public static void debugWhatToShow(int whatToShow) 414 { 415 416 java.util.Vector v = new java.util.Vector(); 417 418 if (0 != (whatToShow & DTMFilter.SHOW_ATTRIBUTE)) 419 v.addElement("SHOW_ATTRIBUTE"); 420 421 if (0 != (whatToShow & DTMFilter.SHOW_NAMESPACE)) 422 v.addElement("SHOW_NAMESPACE"); 423 424 if (0 != (whatToShow & DTMFilter.SHOW_CDATA_SECTION)) 425 v.addElement("SHOW_CDATA_SECTION"); 426 427 if (0 != (whatToShow & DTMFilter.SHOW_COMMENT)) 428 v.addElement("SHOW_COMMENT"); 429 430 if (0 != (whatToShow & DTMFilter.SHOW_DOCUMENT)) 431 v.addElement("SHOW_DOCUMENT"); 432 433 if (0 != (whatToShow & DTMFilter.SHOW_DOCUMENT_FRAGMENT)) 434 v.addElement("SHOW_DOCUMENT_FRAGMENT"); 435 436 if (0 != (whatToShow & DTMFilter.SHOW_DOCUMENT_TYPE)) 437 v.addElement("SHOW_DOCUMENT_TYPE"); 438 439 if (0 != (whatToShow & DTMFilter.SHOW_ELEMENT)) 440 v.addElement("SHOW_ELEMENT"); 441 442 if (0 != (whatToShow & DTMFilter.SHOW_ENTITY)) 443 v.addElement("SHOW_ENTITY"); 444 445 if (0 != (whatToShow & DTMFilter.SHOW_ENTITY_REFERENCE)) 446 v.addElement("SHOW_ENTITY_REFERENCE"); 447 448 if (0 != (whatToShow & DTMFilter.SHOW_NOTATION)) 449 v.addElement("SHOW_NOTATION"); 450 451 if (0 != (whatToShow & DTMFilter.SHOW_PROCESSING_INSTRUCTION)) 452 v.addElement("SHOW_PROCESSING_INSTRUCTION"); 453 454 if (0 != (whatToShow & DTMFilter.SHOW_TEXT)) 455 v.addElement("SHOW_TEXT"); 456 457 int n = v.size(); 458 459 for (int i = 0; i < n; i++) 460 { 461 if (i > 0) 462 System.out.print(" | "); 463 464 System.out.print(v.elementAt(i)); 465 } 466 467 if (0 == n) 468 System.out.print("empty whatToShow: " + whatToShow); 469 470 System.out.println(); 471 } 472 473 /** 474 * Two names are equal if they and either both are null or 475 * the name t is wild and the name p is non-null, or the two 476 * strings are equal. 477 * 478 * @param p part string from the node. 479 * @param t target string, which may be {@link #WILD}. 480 * 481 * @return true if the strings match according to the rules of this method. 482 */ 483 private static final boolean subPartMatch(String p, String t) 484 { 485 486 // boolean b = (p == t) || ((null != p) && ((t == WILD) || p.equals(t))); 487 // System.out.println("subPartMatch - p: "+p+", t: "+t+", result: "+b); 488 return (p == t) || ((null != p) && ((t == WILD) || p.equals(t))); 489 } 490 491 /** 492 * This is temporary to patch over Xerces issue with representing DOM 493 * namespaces as "". 494 * 495 * @param p part string from the node, which may represent the null namespace 496 * as null or as "". 497 * @param t target string, which may be {@link #WILD}. 498 * 499 * @return true if the strings match according to the rules of this method. 500 */ 501 private static final boolean subPartMatchNS(String p, String t) 502 { 503 504 return (p == t) 505 || ((null != p) 506 && ((p.length() > 0) 507 ? ((t == WILD) || p.equals(t)) : null == t)); 508 } 509 510 /** 511 * Tell what the test score is for the given node. 512 * 513 * 514 * @param xctxt XPath runtime context. 515 * @param context The node being tested. 516 * 517 * @return {@link org.apache.xpath.patterns.NodeTest#SCORE_NODETEST}, 518 * {@link org.apache.xpath.patterns.NodeTest#SCORE_NONE}, 519 * {@link org.apache.xpath.patterns.NodeTest#SCORE_NSWILD}, 520 * {@link org.apache.xpath.patterns.NodeTest#SCORE_QNAME}, or 521 * {@link org.apache.xpath.patterns.NodeTest#SCORE_OTHER}. 522 * 523 * @throws javax.xml.transform.TransformerException 524 */ 525 public XObject execute(XPathContext xctxt, int context) 526 throws javax.xml.transform.TransformerException 527 { 528 529 DTM dtm = xctxt.getDTM(context); 530 short nodeType = dtm.getNodeType(context); 531 532 if (m_whatToShow == DTMFilter.SHOW_ALL) 533 return m_score; 534 535 int nodeBit = (m_whatToShow & (0x00000001 << (nodeType - 1))); 536 537 switch (nodeBit) 538 { 539 case DTMFilter.SHOW_DOCUMENT_FRAGMENT : 540 case DTMFilter.SHOW_DOCUMENT : 541 return SCORE_OTHER; 542 case DTMFilter.SHOW_COMMENT : 543 return m_score; 544 case DTMFilter.SHOW_CDATA_SECTION : 545 case DTMFilter.SHOW_TEXT : 546 547 // was: 548 // return (!xctxt.getDOMHelper().shouldStripSourceNode(context)) 549 // ? m_score : SCORE_NONE; 550 return m_score; 551 case DTMFilter.SHOW_PROCESSING_INSTRUCTION : 552 return subPartMatch(dtm.getNodeName(context), m_name) 553 ? m_score : SCORE_NONE; 554 555 // From the draft: "Two expanded names are equal if they 556 // have the same local part, and either both have no URI or 557 // both have the same URI." 558 // "A node test * is true for any node of the principal node type. 559 // For example, child::* will select all element children of the 560 // context node, and attribute::* will select all attributes of 561 // the context node." 562 // "A node test can have the form NCName:*. In this case, the prefix 563 // is expanded in the same way as with a QName using the context 564 // namespace declarations. The node test will be true for any node 565 // of the principal type whose expanded name has the URI to which 566 // the prefix expands, regardless of the local part of the name." 567 case DTMFilter.SHOW_NAMESPACE : 568 { 569 String ns = dtm.getLocalName(context); 570 571 return (subPartMatch(ns, m_name)) ? m_score : SCORE_NONE; 572 } 573 case DTMFilter.SHOW_ATTRIBUTE : 574 case DTMFilter.SHOW_ELEMENT : 575 { 576 return (m_isTotallyWild || (subPartMatchNS(dtm.getNamespaceURI(context), m_namespace) && subPartMatch(dtm.getLocalName(context), m_name))) 577 ? m_score : SCORE_NONE; 578 } 579 default : 580 return SCORE_NONE; 581 } // end switch(testType) 582 } 583 584 /** 585 * Tell what the test score is for the given node. 586 * 587 * 588 * @param xctxt XPath runtime context. 589 * @param context The node being tested. 590 * 591 * @return {@link org.apache.xpath.patterns.NodeTest#SCORE_NODETEST}, 592 * {@link org.apache.xpath.patterns.NodeTest#SCORE_NONE}, 593 * {@link org.apache.xpath.patterns.NodeTest#SCORE_NSWILD}, 594 * {@link org.apache.xpath.patterns.NodeTest#SCORE_QNAME}, or 595 * {@link org.apache.xpath.patterns.NodeTest#SCORE_OTHER}. 596 * 597 * @throws javax.xml.transform.TransformerException 598 */ 599 public XObject execute(XPathContext xctxt, int context, 600 DTM dtm, int expType) 601 throws javax.xml.transform.TransformerException 602 { 603 604 if (m_whatToShow == DTMFilter.SHOW_ALL) 605 return m_score; 606 607 int nodeBit = (m_whatToShow & (0x00000001 608 << ((dtm.getNodeType(context)) - 1))); 609 610 switch (nodeBit) 611 { 612 case DTMFilter.SHOW_DOCUMENT_FRAGMENT : 613 case DTMFilter.SHOW_DOCUMENT : 614 return SCORE_OTHER; 615 case DTMFilter.SHOW_COMMENT : 616 return m_score; 617 case DTMFilter.SHOW_CDATA_SECTION : 618 case DTMFilter.SHOW_TEXT : 619 620 // was: 621 // return (!xctxt.getDOMHelper().shouldStripSourceNode(context)) 622 // ? m_score : SCORE_NONE; 623 return m_score; 624 case DTMFilter.SHOW_PROCESSING_INSTRUCTION : 625 return subPartMatch(dtm.getNodeName(context), m_name) 626 ? m_score : SCORE_NONE; 627 628 // From the draft: "Two expanded names are equal if they 629 // have the same local part, and either both have no URI or 630 // both have the same URI." 631 // "A node test * is true for any node of the principal node type. 632 // For example, child::* will select all element children of the 633 // context node, and attribute::* will select all attributes of 634 // the context node." 635 // "A node test can have the form NCName:*. In this case, the prefix 636 // is expanded in the same way as with a QName using the context 637 // namespace declarations. The node test will be true for any node 638 // of the principal type whose expanded name has the URI to which 639 // the prefix expands, regardless of the local part of the name." 640 case DTMFilter.SHOW_NAMESPACE : 641 { 642 String ns = dtm.getLocalName(context); 643 644 return (subPartMatch(ns, m_name)) ? m_score : SCORE_NONE; 645 } 646 case DTMFilter.SHOW_ATTRIBUTE : 647 case DTMFilter.SHOW_ELEMENT : 648 { 649 return (m_isTotallyWild || (subPartMatchNS(dtm.getNamespaceURI(context), m_namespace) && subPartMatch(dtm.getLocalName(context), m_name))) 650 ? m_score : SCORE_NONE; 651 } 652 default : 653 return SCORE_NONE; 654 } // end switch(testType) 655 } 656 657 /** 658 * Test the current node to see if it matches the given node test. 659 * 660 * @param xctxt XPath runtime context. 661 * 662 * @return {@link org.apache.xpath.patterns.NodeTest#SCORE_NODETEST}, 663 * {@link org.apache.xpath.patterns.NodeTest#SCORE_NONE}, 664 * {@link org.apache.xpath.patterns.NodeTest#SCORE_NSWILD}, 665 * {@link org.apache.xpath.patterns.NodeTest#SCORE_QNAME}, or 666 * {@link org.apache.xpath.patterns.NodeTest#SCORE_OTHER}. 667 * 668 * @throws javax.xml.transform.TransformerException 669 */ 670 public XObject execute(XPathContext xctxt) 671 throws javax.xml.transform.TransformerException 672 { 673 return execute(xctxt, xctxt.getCurrentNode()); 674 } 675 676 /** 677 * Node tests by themselves do not need to fix up variables. 678 */ 679 public void fixupVariables(java.util.Vector vars, int globalsSize) 680 { 681 // no-op 682 } 683 684 /** 685 * @see org.apache.xpath.XPathVisitable#callVisitors(ExpressionOwner, XPathVisitor) 686 */ 687 public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) 688 { 689 assertion(false, "callVisitors should not be called for this object!!!"); 690 } 691 692} 693