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: XPath.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xpath;
022    
023    import java.io.Serializable;
024    
025    import javax.xml.transform.ErrorListener;
026    import javax.xml.transform.SourceLocator;
027    import javax.xml.transform.TransformerException;
028    
029    import org.apache.xalan.res.XSLMessages;
030    import org.apache.xml.dtm.DTM;
031    import org.apache.xml.utils.PrefixResolver;
032    import org.apache.xml.utils.SAXSourceLocator;
033    import org.apache.xpath.compiler.Compiler;
034    import org.apache.xpath.compiler.FunctionTable;
035    import org.apache.xpath.compiler.XPathParser;
036    import org.apache.xpath.functions.Function;
037    import org.apache.xpath.objects.XObject;
038    import org.apache.xpath.res.XPATHErrorResources;
039    
040    /**
041     * The XPath class wraps an expression object and provides general services 
042     * for execution of that expression.
043     * @xsl.usage advanced
044     */
045    public class XPath implements Serializable, ExpressionOwner
046    {
047        static final long serialVersionUID = 3976493477939110553L;
048    
049      /** The top of the expression tree. 
050       *  @serial */
051      private Expression m_mainExp;
052      
053      /**
054       * The function table for xpath build-in functions
055       */
056      private transient FunctionTable m_funcTable = null;
057    
058      /**
059       * initial the function table
060       */
061      private void initFunctionTable(){
062                  m_funcTable = new FunctionTable();
063      }
064    
065      /**
066       * Get the raw Expression object that this class wraps.
067       *
068       *
069       * @return the raw Expression object, which should not normally be null.
070       */
071      public Expression getExpression()
072      {
073        return m_mainExp;
074      }
075      
076      /**
077       * This function is used to fixup variables from QNames to stack frame 
078       * indexes at stylesheet build time.
079       * @param vars List of QNames that correspond to variables.  This list 
080       * should be searched backwards for the first qualified name that 
081       * corresponds to the variable reference qname.  The position of the 
082       * QName in the vector from the start of the vector will be its position 
083       * in the stack frame (but variables above the globalsTop value will need 
084       * to be offset to the current stack frame).
085       */
086      public void fixupVariables(java.util.Vector vars, int globalsSize)
087      {
088        m_mainExp.fixupVariables(vars, globalsSize);
089      }
090    
091      /**
092       * Set the raw expression object for this object.
093       *
094       *
095       * @param exp the raw Expression object, which should not normally be null.
096       */
097      public void setExpression(Expression exp)
098      {
099            if(null != m_mainExp)
100            exp.exprSetParent(m_mainExp.exprGetParent()); // a bit bogus
101        m_mainExp = exp;
102      }
103    
104      /**
105       * Get the SourceLocator on the expression object.
106       *
107       *
108       * @return the SourceLocator on the expression object, which may be null.
109       */
110      public SourceLocator getLocator()
111      {
112        return m_mainExp;
113      }
114    
115    //  /**
116    //   * Set the SourceLocator on the expression object.
117    //   *
118    //   *
119    //   * @param l the SourceLocator on the expression object, which may be null.
120    //   */
121    //  public void setLocator(SourceLocator l)
122    //  {
123    //    // Note potential hazards -- l may not be serializable, or may be changed
124    //      // after being assigned here.
125    //    m_mainExp.setSourceLocator(l);
126    //  }
127    
128      /** The pattern string, mainly kept around for diagnostic purposes.
129       *  @serial  */
130      String m_patternString;
131    
132      /**
133       * Return the XPath string associated with this object.
134       *
135       *
136       * @return the XPath string associated with this object.
137       */
138      public String getPatternString()
139      {
140        return m_patternString;
141      }
142    
143      /** Represents a select type expression. */
144      public static final int SELECT = 0;
145    
146      /** Represents a match type expression.  */
147      public static final int MATCH = 1;
148    
149      /**
150       * Construct an XPath object.  
151       *
152       * (Needs review -sc) This method initializes an XPathParser/
153       * Compiler and compiles the expression.
154       * @param exprString The XPath expression.
155       * @param locator The location of the expression, may be null.
156       * @param prefixResolver A prefix resolver to use to resolve prefixes to 
157       *                       namespace URIs.
158       * @param type one of {@link #SELECT} or {@link #MATCH}.
159       * @param errorListener The error listener, or null if default should be used.
160       *
161       * @throws javax.xml.transform.TransformerException if syntax or other error.
162       */
163      public XPath(
164              String exprString, SourceLocator locator, PrefixResolver prefixResolver, int type,
165              ErrorListener errorListener)
166                throws javax.xml.transform.TransformerException
167      { 
168        initFunctionTable();     
169        if(null == errorListener)
170          errorListener = new org.apache.xml.utils.DefaultErrorHandler();
171        
172        m_patternString = exprString;
173    
174        XPathParser parser = new XPathParser(errorListener, locator);
175        Compiler compiler = new Compiler(errorListener, locator, m_funcTable);
176    
177        if (SELECT == type)
178          parser.initXPath(compiler, exprString, prefixResolver);
179        else if (MATCH == type)
180          parser.initMatchPattern(compiler, exprString, prefixResolver);
181        else
182          throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANNOT_DEAL_XPATH_TYPE, new Object[]{Integer.toString(type)})); //"Can not deal with XPath type: " + type);
183    
184        // System.out.println("----------------");
185        Expression expr = compiler.compile(0);
186    
187        // System.out.println("expr: "+expr);
188        this.setExpression(expr);
189        
190        if((null != locator) && locator instanceof ExpressionNode)
191        {
192            expr.exprSetParent((ExpressionNode)locator);
193        }
194    
195      }
196    
197      /**
198       * Construct an XPath object.  
199       *
200       * (Needs review -sc) This method initializes an XPathParser/
201       * Compiler and compiles the expression.
202       * @param exprString The XPath expression.
203       * @param locator The location of the expression, may be null.
204       * @param prefixResolver A prefix resolver to use to resolve prefixes to 
205       *                       namespace URIs.
206       * @param type one of {@link #SELECT} or {@link #MATCH}.
207       * @param errorListener The error listener, or null if default should be used.
208       *
209       * @throws javax.xml.transform.TransformerException if syntax or other error.
210       */
211      public XPath(
212              String exprString, SourceLocator locator, 
213              PrefixResolver prefixResolver, int type,
214              ErrorListener errorListener, FunctionTable aTable)
215                throws javax.xml.transform.TransformerException
216      { 
217        m_funcTable = aTable;     
218        if(null == errorListener)
219          errorListener = new org.apache.xml.utils.DefaultErrorHandler();
220        
221        m_patternString = exprString;
222    
223        XPathParser parser = new XPathParser(errorListener, locator);
224        Compiler compiler = new Compiler(errorListener, locator, m_funcTable);
225    
226        if (SELECT == type)
227          parser.initXPath(compiler, exprString, prefixResolver);
228        else if (MATCH == type)
229          parser.initMatchPattern(compiler, exprString, prefixResolver);
230        else
231          throw new RuntimeException(XSLMessages.createXPATHMessage(
232                XPATHErrorResources.ER_CANNOT_DEAL_XPATH_TYPE, 
233                new Object[]{Integer.toString(type)})); 
234                //"Can not deal with XPath type: " + type);
235    
236        // System.out.println("----------------");
237        Expression expr = compiler.compile(0);
238    
239        // System.out.println("expr: "+expr);
240        this.setExpression(expr);
241        
242        if((null != locator) && locator instanceof ExpressionNode)
243        {
244            expr.exprSetParent((ExpressionNode)locator);
245        }
246    
247      }
248      
249      /**
250       * Construct an XPath object.  
251       *
252       * (Needs review -sc) This method initializes an XPathParser/
253       * Compiler and compiles the expression.
254       * @param exprString The XPath expression.
255       * @param locator The location of the expression, may be null.
256       * @param prefixResolver A prefix resolver to use to resolve prefixes to 
257       *                       namespace URIs.
258       * @param type one of {@link #SELECT} or {@link #MATCH}.
259       *
260       * @throws javax.xml.transform.TransformerException if syntax or other error.
261       */
262      public XPath(
263              String exprString, SourceLocator locator, PrefixResolver prefixResolver, int type)
264                throws javax.xml.transform.TransformerException
265      {  
266        this(exprString, locator, prefixResolver, type, null);    
267      }
268    
269      /**
270       * Construct an XPath object.
271       *
272       * @param expr The Expression object.
273       *
274       * @throws javax.xml.transform.TransformerException if syntax or other error.
275       */
276      public XPath(Expression expr)
277      {  
278        this.setExpression(expr);
279        initFunctionTable();   
280      }
281      
282      /**
283       * Given an expression and a context, evaluate the XPath
284       * and return the result.
285       * 
286       * @param xctxt The execution context.
287       * @param contextNode The node that "." expresses.
288       * @param namespaceContext The context in which namespaces in the
289       * XPath are supposed to be expanded.
290       *
291       * @return The result of the XPath or null if callbacks are used.
292       * @throws TransformerException thrown if
293       * the error condition is severe enough to halt processing.
294       *
295       * @throws javax.xml.transform.TransformerException
296       * @xsl.usage experimental
297       */
298      public XObject execute(
299              XPathContext xctxt, org.w3c.dom.Node contextNode, 
300              PrefixResolver namespaceContext)
301                throws javax.xml.transform.TransformerException
302      {
303        return execute(
304              xctxt, xctxt.getDTMHandleFromNode(contextNode), 
305              namespaceContext);
306      }
307      
308    
309      /**
310       * Given an expression and a context, evaluate the XPath
311       * and return the result.
312       * 
313       * @param xctxt The execution context.
314       * @param contextNode The node that "." expresses.
315       * @param namespaceContext The context in which namespaces in the
316       * XPath are supposed to be expanded.
317       * 
318       * @throws TransformerException thrown if the active ProblemListener decides
319       * the error condition is severe enough to halt processing.
320       *
321       * @throws javax.xml.transform.TransformerException
322       * @xsl.usage experimental
323       */
324      public XObject execute(
325              XPathContext xctxt, int contextNode, PrefixResolver namespaceContext)
326                throws javax.xml.transform.TransformerException
327      {
328    
329        xctxt.pushNamespaceContext(namespaceContext);
330    
331        xctxt.pushCurrentNodeAndExpression(contextNode, contextNode);
332    
333        XObject xobj = null;
334    
335        try
336        {
337          xobj = m_mainExp.execute(xctxt);
338        }
339        catch (TransformerException te)
340        {
341          te.setLocator(this.getLocator());
342          ErrorListener el = xctxt.getErrorListener();
343          if(null != el) // defensive, should never happen.
344          {
345            el.error(te);
346          }
347          else
348            throw te;
349        }
350        catch (Exception e)
351        {
352          while (e instanceof org.apache.xml.utils.WrappedRuntimeException)
353          {
354            e = ((org.apache.xml.utils.WrappedRuntimeException) e).getException();
355          }
356          // e.printStackTrace();
357    
358          String msg = e.getMessage();
359          
360          if (msg == null || msg.length() == 0) {
361               msg = XSLMessages.createXPATHMessage(
362                   XPATHErrorResources.ER_XPATH_ERROR, null);
363         
364          }  
365          TransformerException te = new TransformerException(msg,
366                  getLocator(), e);
367          ErrorListener el = xctxt.getErrorListener();
368          // te.printStackTrace();
369          if(null != el) // defensive, should never happen.
370          {
371            el.fatalError(te);
372          }
373          else
374            throw te;
375        }
376        finally
377        {
378          xctxt.popNamespaceContext();
379    
380          xctxt.popCurrentNodeAndExpression();
381        }
382    
383        return xobj;
384      }
385      
386      /**
387       * Given an expression and a context, evaluate the XPath
388       * and return the result.
389       * 
390       * @param xctxt The execution context.
391       * @param contextNode The node that "." expresses.
392       * @param namespaceContext The context in which namespaces in the
393       * XPath are supposed to be expanded.
394       * 
395       * @throws TransformerException thrown if the active ProblemListener decides
396       * the error condition is severe enough to halt processing.
397       *
398       * @throws javax.xml.transform.TransformerException
399       * @xsl.usage experimental
400       */
401      public boolean bool(
402              XPathContext xctxt, int contextNode, PrefixResolver namespaceContext)
403                throws javax.xml.transform.TransformerException
404      {
405    
406        xctxt.pushNamespaceContext(namespaceContext);
407    
408        xctxt.pushCurrentNodeAndExpression(contextNode, contextNode);
409    
410        try
411        {
412          return m_mainExp.bool(xctxt);
413        }
414        catch (TransformerException te)
415        {
416          te.setLocator(this.getLocator());
417          ErrorListener el = xctxt.getErrorListener();
418          if(null != el) // defensive, should never happen.
419          {
420            el.error(te);
421          }
422          else
423            throw te;
424        }
425        catch (Exception e)
426        {
427          while (e instanceof org.apache.xml.utils.WrappedRuntimeException)
428          {
429            e = ((org.apache.xml.utils.WrappedRuntimeException) e).getException();
430          }
431          // e.printStackTrace();
432    
433          String msg = e.getMessage();
434          
435          if (msg == null || msg.length() == 0) {
436               msg = XSLMessages.createXPATHMessage(
437                   XPATHErrorResources.ER_XPATH_ERROR, null);
438         
439          }        
440          
441          TransformerException te = new TransformerException(msg,
442                  getLocator(), e);
443          ErrorListener el = xctxt.getErrorListener();
444          // te.printStackTrace();
445          if(null != el) // defensive, should never happen.
446          {
447            el.fatalError(te);
448          }
449          else
450            throw te;
451        }
452        finally
453        {
454          xctxt.popNamespaceContext();
455    
456          xctxt.popCurrentNodeAndExpression();
457        }
458    
459        return false;
460      }
461    
462      /** Set to true to get diagnostic messages about the result of 
463       *  match pattern testing.  */
464      private static final boolean DEBUG_MATCHES = false;
465    
466      /**
467       * Get the match score of the given node.
468       *
469       * @param xctxt XPath runtime context.
470       * @param context The current source tree context node.
471       * 
472       * @return score, one of {@link #MATCH_SCORE_NODETEST},
473       * {@link #MATCH_SCORE_NONE}, {@link #MATCH_SCORE_OTHER}, 
474       * or {@link #MATCH_SCORE_QNAME}.
475       *
476       * @throws javax.xml.transform.TransformerException
477       */
478      public double getMatchScore(XPathContext xctxt, int context)
479              throws javax.xml.transform.TransformerException
480      {
481    
482        xctxt.pushCurrentNode(context);
483        xctxt.pushCurrentExpressionNode(context);
484    
485        try
486        {
487          XObject score = m_mainExp.execute(xctxt);
488    
489          if (DEBUG_MATCHES)
490          {
491            DTM dtm = xctxt.getDTM(context);
492            System.out.println("score: " + score.num() + " for "
493                               + dtm.getNodeName(context) + " for xpath "
494                               + this.getPatternString());
495          }
496    
497          return score.num();
498        }
499        finally
500        {
501          xctxt.popCurrentNode();
502          xctxt.popCurrentExpressionNode();
503        }
504    
505        // return XPath.MATCH_SCORE_NONE;
506      }
507    
508    
509      /**
510       * Warn the user of an problem.
511       *
512       * @param xctxt The XPath runtime context.
513       * @param sourceNode Not used.
514       * @param msg An error msgkey that corresponds to one of the constants found 
515       *            in {@link org.apache.xpath.res.XPATHErrorResources}, which is 
516       *            a key for a format string.
517       * @param args An array of arguments represented in the format string, which 
518       *             may be null.
519       *
520       * @throws TransformerException if the current ErrorListoner determines to 
521       *                              throw an exception.
522       */
523      public void warn(
524              XPathContext xctxt, int sourceNode, String msg, Object[] args)
525                throws javax.xml.transform.TransformerException
526      {
527    
528        String fmsg = XSLMessages.createXPATHWarning(msg, args);
529        ErrorListener ehandler = xctxt.getErrorListener();
530    
531        if (null != ehandler)
532        {
533    
534          // TO DO: Need to get stylesheet Locator from here.
535          ehandler.warning(new TransformerException(fmsg, (SAXSourceLocator)xctxt.getSAXLocator()));
536        }
537      }
538    
539      /**
540       * Tell the user of an assertion error, and probably throw an
541       * exception.
542       *
543       * @param b  If false, a runtime exception will be thrown.
544       * @param msg The assertion message, which should be informative.
545       * 
546       * @throws RuntimeException if the b argument is false.
547       */
548      public void assertion(boolean b, String msg)
549      {
550    
551        if (!b)
552        {
553          String fMsg = XSLMessages.createXPATHMessage(
554            XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION,
555            new Object[]{ msg });
556    
557          throw new RuntimeException(fMsg);
558        }
559      }
560    
561      /**
562       * Tell the user of an error, and probably throw an
563       * exception.
564       *
565       * @param xctxt The XPath runtime context.
566       * @param sourceNode Not used.
567       * @param msg An error msgkey that corresponds to one of the constants found 
568       *            in {@link org.apache.xpath.res.XPATHErrorResources}, which is 
569       *            a key for a format string.
570       * @param args An array of arguments represented in the format string, which 
571       *             may be null.
572       *
573       * @throws TransformerException if the current ErrorListoner determines to 
574       *                              throw an exception.
575       */
576      public void error(
577              XPathContext xctxt, int sourceNode, String msg, Object[] args)
578                throws javax.xml.transform.TransformerException
579      {
580    
581        String fmsg = XSLMessages.createXPATHMessage(msg, args);
582        ErrorListener ehandler = xctxt.getErrorListener();
583    
584        if (null != ehandler)
585        {
586          ehandler.fatalError(new TransformerException(fmsg,
587                                  (SAXSourceLocator)xctxt.getSAXLocator()));
588        }
589        else
590        {
591          SourceLocator slocator = xctxt.getSAXLocator();
592          System.out.println(fmsg + "; file " + slocator.getSystemId()
593                             + "; line " + slocator.getLineNumber() + "; column "
594                             + slocator.getColumnNumber());
595        }
596      }
597      
598      /**
599       * This will traverse the heararchy, calling the visitor for 
600       * each member.  If the called visitor method returns 
601       * false, the subtree should not be called.
602       * 
603       * @param owner The owner of the visitor, where that path may be 
604       *              rewritten if needed.
605       * @param visitor The visitor whose appropriate method will be called.
606       */
607      public void callVisitors(ExpressionOwner owner, XPathVisitor visitor)
608      {
609            m_mainExp.callVisitors(this, visitor);
610      }
611    
612      /**
613       * The match score if no match is made.
614       * @xsl.usage advanced
615       */
616      public static final double MATCH_SCORE_NONE = Double.NEGATIVE_INFINITY;
617    
618      /**
619       * The match score if the pattern has the form
620       * of a QName optionally preceded by an @ character.
621       * @xsl.usage advanced
622       */
623      public static final double MATCH_SCORE_QNAME = 0.0;
624    
625      /**
626       * The match score if the pattern pattern has the form NCName:*.
627       * @xsl.usage advanced
628       */
629      public static final double MATCH_SCORE_NSWILD = -0.25;
630    
631      /**
632       * The match score if the pattern consists of just a NodeTest.
633       * @xsl.usage advanced
634       */
635      public static final double MATCH_SCORE_NODETEST = -0.5;
636    
637      /**
638       * The match score if the pattern consists of something
639       * other than just a NodeTest or just a qname.
640       * @xsl.usage advanced
641       */
642      public static final double MATCH_SCORE_OTHER = 0.5;
643    }