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