1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: XPath.java 468655 2006-10-28 07:12:06Z minchau $
20  */
21 package org.apache.xpath;
22 
23 import java.io.Serializable;
24 
25 import javax.xml.transform.ErrorListener;
26 import javax.xml.transform.SourceLocator;
27 import javax.xml.transform.TransformerException;
28 
29 import org.apache.xalan.res.XSLMessages;
30 import org.apache.xml.dtm.DTM;
31 import org.apache.xml.utils.PrefixResolver;
32 import org.apache.xml.utils.SAXSourceLocator;
33 import org.apache.xpath.compiler.Compiler;
34 import org.apache.xpath.compiler.FunctionTable;
35 import org.apache.xpath.compiler.XPathParser;
36 import org.apache.xpath.functions.Function;
37 import org.apache.xpath.objects.XObject;
38 import org.apache.xpath.res.XPATHErrorResources;
39 
40 /**
41  * The XPath class wraps an expression object and provides general services
42  * for execution of that expression.
43  * @xsl.usage advanced
44  */
45 public class XPath implements Serializable, ExpressionOwner
46 {
47     static final long serialVersionUID = 3976493477939110553L;
48 
49   /** The top of the expression tree.
50    *  @serial */
51   private Expression m_mainExp;
52 
53   /**
54    * The function table for xpath build-in functions
55    */
56   private transient FunctionTable m_funcTable = null;
57 
58   /**
59    * initial the function table
60    */
initFunctionTable()61   private void initFunctionTable(){
62   	      m_funcTable = new FunctionTable();
63   }
64 
65   /**
66    * Get the raw Expression object that this class wraps.
67    *
68    *
69    * @return the raw Expression object, which should not normally be null.
70    */
getExpression()71   public Expression getExpression()
72   {
73     return m_mainExp;
74   }
75 
76   /**
77    * This function is used to fixup variables from QNames to stack frame
78    * indexes at stylesheet build time.
79    * @param vars List of QNames that correspond to variables.  This list
80    * should be searched backwards for the first qualified name that
81    * corresponds to the variable reference qname.  The position of the
82    * QName in the vector from the start of the vector will be its position
83    * in the stack frame (but variables above the globalsTop value will need
84    * to be offset to the current stack frame).
85    */
fixupVariables(java.util.Vector vars, int globalsSize)86   public void fixupVariables(java.util.Vector vars, int globalsSize)
87   {
88     m_mainExp.fixupVariables(vars, globalsSize);
89   }
90 
91   /**
92    * Set the raw expression object for this object.
93    *
94    *
95    * @param exp the raw Expression object, which should not normally be null.
96    */
setExpression(Expression exp)97   public void setExpression(Expression exp)
98   {
99   	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    */
getLocator()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    */
getPatternString()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    */
XPath( String exprString, SourceLocator locator, PrefixResolver prefixResolver, int type, ErrorListener errorListener)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    */
XPath( String exprString, SourceLocator locator, PrefixResolver prefixResolver, int type, ErrorListener errorListener, FunctionTable aTable)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    */
XPath( String exprString, SourceLocator locator, PrefixResolver prefixResolver, int type)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    */
XPath(Expression expr)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    */
execute( XPathContext xctxt, org.w3c.dom.Node contextNode, PrefixResolver namespaceContext)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    */
execute( XPathContext xctxt, int contextNode, PrefixResolver namespaceContext)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    */
bool( XPathContext xctxt, int contextNode, PrefixResolver namespaceContext)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    */
getMatchScore(XPathContext xctxt, int context)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    */
warn( XPathContext xctxt, int sourceNode, String msg, Object[] args)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    */
assertion(boolean b, String msg)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    */
error( XPathContext xctxt, int sourceNode, String msg, Object[] args)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    */
callVisitors(ExpressionOwner owner, XPathVisitor visitor)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 }
644