001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: NodeTest.java 468655 2006-10-28 07:12:06Z minchau $
020 */
021 package org.apache.xpath.patterns;
022
023 import org.apache.xml.dtm.DTM;
024 import org.apache.xml.dtm.DTMFilter;
025 import org.apache.xpath.Expression;
026 import org.apache.xpath.ExpressionOwner;
027 import org.apache.xpath.XPath;
028 import org.apache.xpath.XPathContext;
029 import org.apache.xpath.XPathVisitor;
030 import org.apache.xpath.objects.XNumber;
031 import org.apache.xpath.objects.XObject;
032
033 /**
034 * This is the basic node test class for both match patterns and location path
035 * steps.
036 * @xsl.usage advanced
037 */
038 public class NodeTest extends Expression
039 {
040 static final long serialVersionUID = -5736721866747906182L;
041
042 /**
043 * The namespace or local name for node tests with a wildcard.
044 * @see <a href="http://www.w3.org/TR/xpath#NT-NameTest">the XPath NameTest production.</a>
045 */
046 public static final String WILD = "*";
047
048 /**
049 * The URL to pass to the Node#supports method, to see if the
050 * DOM has already been stripped of whitespace nodes.
051 */
052 public static final String SUPPORTS_PRE_STRIPPING =
053 "http://xml.apache.org/xpath/features/whitespace-pre-stripping";
054
055 /**
056 * This attribute determines which node types are accepted.
057 * @serial
058 */
059 protected int m_whatToShow;
060
061 /**
062 * Special bitmap for match patterns starting with a function.
063 * Make sure this does not conflict with {@link org.w3c.dom.traversal.NodeFilter}.
064 */
065 public static final int SHOW_BYFUNCTION = 0x00010000;
066
067 /**
068 * This attribute determines which node types are accepted.
069 * These constants are defined in the {@link org.w3c.dom.traversal.NodeFilter}
070 * interface.
071 *
072 * @return bitset mainly defined in {@link org.w3c.dom.traversal.NodeFilter}.
073 */
074 public int getWhatToShow()
075 {
076 return m_whatToShow;
077 }
078
079 /**
080 * This attribute determines which node types are accepted.
081 * These constants are defined in the {@link org.w3c.dom.traversal.NodeFilter}
082 * interface.
083 *
084 * @param what bitset mainly defined in {@link org.w3c.dom.traversal.NodeFilter}.
085 */
086 public void setWhatToShow(int what)
087 {
088 m_whatToShow = what;
089 }
090
091 /**
092 * The namespace to be tested for, which may be null.
093 * @serial
094 */
095 String m_namespace;
096
097 /**
098 * Return the namespace to be tested.
099 *
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 }