View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.dom;
16  
17  import org.htmlunit.corejs.javascript.Context;
18  import org.htmlunit.corejs.javascript.Function;
19  import org.htmlunit.corejs.javascript.NativeFunction;
20  import org.htmlunit.corejs.javascript.Scriptable;
21  import org.htmlunit.html.DomNode;
22  import org.htmlunit.javascript.HtmlUnitScriptable;
23  import org.htmlunit.javascript.JavaScriptEngine;
24  import org.htmlunit.javascript.configuration.JsxClass;
25  import org.htmlunit.javascript.configuration.JsxConstructor;
26  import org.htmlunit.javascript.configuration.JsxFunction;
27  import org.htmlunit.javascript.host.NativeFunctionPrefixResolver;
28  import org.htmlunit.javascript.host.Window;
29  import org.htmlunit.xpath.xml.utils.PrefixResolver;
30  
31  /**
32   * A JavaScript object for {@code XPathEvaluator}.
33   *
34   * @author Marc Guillemot
35   * @author Chuck Dumont
36   * @author Ronald Brill
37   */
38  @JsxClass
39  public class XPathEvaluator extends HtmlUnitScriptable {
40  
41      /**
42       * JavaScript constructor.
43       */
44      @JsxConstructor
45      public void jsConstructor() {
46          // nothing to do
47      }
48  
49      /**
50       * Adapts any DOM node to resolve namespaces so that an XPath expression can be easily
51       * evaluated relative to the context of the node where it appeared within the document.
52       * @param nodeResolver the node to be used as a context for namespace resolution
53       * @return an XPathNSResolver which resolves namespaces with respect to the definitions
54       *         in scope for a specified node
55       */
56      @JsxFunction
57      public XPathNSResolver createNSResolver(final Node nodeResolver) {
58          final XPathNSResolver resolver = new XPathNSResolver();
59          resolver.setElement(nodeResolver);
60          resolver.setParentScope(getWindow());
61          resolver.setPrototype(getPrototype(resolver.getClass()));
62          return resolver;
63      }
64  
65      /**
66       * Evaluates an XPath expression string and returns a result of the specified type if possible.
67       * @param expression the XPath expression string to be parsed and evaluated
68       * @param contextNodeObj the context node for the evaluation of this XPath expression
69       * @param resolver the resolver permits translation of all prefixes, including the XML namespace prefix,
70       *        within the XPath expression into appropriate namespace URIs.
71       * @param type If a specific type is specified, then the result will be returned as the corresponding type
72       * @param result the result object which may be reused and returned by this method
73       * @return the result of the evaluation of the XPath expression
74       */
75      @JsxFunction
76      public XPathResult evaluate(final String expression, final Object contextNodeObj,
77              final Object resolver, final int type, final Object result) {
78          try {
79              // contextNodeObj can be either a node or an array with the node as the first element.
80              if (!(contextNodeObj instanceof Node)) {
81                  throw JavaScriptEngine.typeError("Illegal value for parameter 'context'");
82              }
83  
84              final Node contextNode = (Node) contextNodeObj;
85              PrefixResolver prefixResolver = null;
86              if (resolver instanceof PrefixResolver) {
87                  prefixResolver = (PrefixResolver) resolver;
88              }
89              else if (resolver instanceof NativeFunction) {
90                  prefixResolver = new NativeFunctionPrefixResolver(
91                                          (NativeFunction) resolver, contextNode.getParentScope());
92              }
93  
94              final XPathResult xPathResult;
95              if (result instanceof XPathResult) {
96                  xPathResult = (XPathResult) result;
97              }
98              else {
99                  xPathResult = new XPathResult();
100                 xPathResult.setParentScope(getParentScope());
101                 xPathResult.setPrototype(getPrototype(xPathResult.getClass()));
102             }
103 
104             xPathResult.init(contextNode.getDomNodeOrDie().getByXPath(expression, prefixResolver), type);
105             return xPathResult;
106         }
107         catch (final Exception e) {
108             throw JavaScriptEngine.typeError("Failed to execute 'evaluate': " + e.getMessage());
109         }
110     }
111 
112     /**
113      * Compiles an XPathExpression which can then be used for (repeated) evaluations of the XPath expression.
114      * @param context the context
115      * @param scope the scope
116      * @param thisObj this object
117      * @param args the arguments
118      * @param function the function
119      * @return a XPathExpression representing the compiled form of the XPath expression.
120      */
121     @JsxFunction
122     public static XPathExpression createExpression(final Context context, final Scriptable scope,
123             final Scriptable thisObj, final Object[] args, final Function function) {
124         if (args.length < 1) {
125             throw JavaScriptEngine.reportRuntimeError("Missing 'expression' parameter");
126         }
127 
128         PrefixResolver prefixResolver = null;
129         if (args.length > 1) {
130             final Object resolver = args[1];
131             if (resolver instanceof PrefixResolver) {
132                 prefixResolver = (PrefixResolver) resolver;
133             }
134             else if (resolver instanceof NativeFunction) {
135                 prefixResolver = new NativeFunctionPrefixResolver(
136                                         (NativeFunction) resolver, scope.getParentScope());
137             }
138         }
139 
140         final XPathEvaluator evaluator = (XPathEvaluator) thisObj;
141 
142         try {
143             final String xpath = JavaScriptEngine.toString(args[0]);
144             final DomNode doc = ((Window) scope).getDocument().getDocumentElement().getDomNodeOrDie();
145             final XPathExpression xPathExpression  = new XPathExpression(xpath, prefixResolver, doc);
146             xPathExpression.setParentScope(evaluator.getParentScope());
147             xPathExpression.setPrototype(evaluator.getPrototype(xPathExpression.getClass()));
148 
149             return xPathExpression;
150         }
151         catch (final Exception e) {
152             throw JavaScriptEngine.syntaxError(
153                     "Failed to compile xpath '" + args[0] + "' (" + e.getMessage() + ")");
154         }
155     }
156 }