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    }