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 java.util.List;
18  
19  import org.htmlunit.html.DomAttr;
20  import org.htmlunit.html.DomNode;
21  import org.htmlunit.javascript.HtmlUnitScriptable;
22  import org.htmlunit.javascript.JavaScriptEngine;
23  import org.htmlunit.javascript.configuration.JsxClass;
24  import org.htmlunit.javascript.configuration.JsxConstant;
25  import org.htmlunit.javascript.configuration.JsxConstructor;
26  import org.htmlunit.javascript.configuration.JsxFunction;
27  import org.htmlunit.javascript.configuration.JsxGetter;
28  
29  /**
30   * A JavaScript object for {@code XPathResult}.
31   *
32   * @author Ahmed Ashour
33   * @author Chuck Dumont
34   * @author Ronald Brill
35   */
36  @JsxClass
37  public class XPathResult extends HtmlUnitScriptable {
38  
39      /**
40       * This code does not represent a specific type.
41       * An evaluation of an XPath expression will never produce this type. If this type is requested,
42       * then the evaluation returns whatever type naturally results from evaluation of the expression.
43       */
44      @JsxConstant
45      public static final int ANY_TYPE = 0;
46  
47      /**
48       * The result is a number.
49       */
50      @JsxConstant
51      public static final int NUMBER_TYPE = 1;
52  
53      /**
54       * The result is a string.
55       */
56      @JsxConstant
57      public static final int STRING_TYPE = 2;
58  
59      /**
60       * The result is a boolean.
61       */
62      @JsxConstant
63      public static final int BOOLEAN_TYPE = 3;
64  
65      /**
66       * The result is a node set that will be accessed iteratively, which may not produce nodes in a particular order.
67       * This is the default type returned if the result is a node set and {@link #ANY_TYPE} is requested.
68       */
69      @JsxConstant
70      public static final int UNORDERED_NODE_ITERATOR_TYPE = 4;
71  
72      /**
73       * The result is a node set that will be accessed iteratively, which will produce document-ordered nodes.
74       */
75      @JsxConstant
76      public static final int ORDERED_NODE_ITERATOR_TYPE = 5;
77  
78      /**
79       * The result is a node set that will be accessed as a snapshot list of nodes
80       * that may not be in a particular order.
81       */
82      @JsxConstant
83      public static final int UNORDERED_NODE_SNAPSHOT_TYPE = 6;
84  
85      /**
86       * The result is a node set that will be accessed as a snapshot list of nodes
87       * that will be in original document order.
88       */
89      @JsxConstant
90      public static final int ORDERED_NODE_SNAPSHOT_TYPE = 7;
91  
92      /**
93       * The result is a node set and will be accessed as a single node, which may be null if the node set is empty.
94       * If there is more than one node in the actual result,
95       * the single node returned might not be the first in document order.
96       */
97      @JsxConstant
98      public static final int ANY_UNORDERED_NODE_TYPE = 8;
99  
100     /**
101      * The result is a node set and will be accessed as a single node, which may be null if the node set is empty.
102      * If there are more than one node in the actual result,
103      * the single node returned will be the first in document order.
104      */
105     @JsxConstant
106     public static final int FIRST_ORDERED_NODE_TYPE = 9;
107 
108     private List<?> result_;
109     private int resultType_;
110 
111     /**
112      * The index of the next result.
113      */
114     private int iteratorIndex_;
115 
116     /**
117      * Creates an instance.
118      */
119     @JsxConstructor
120     public void jsConstructor() {
121         throw JavaScriptEngine.typeErrorIllegalConstructor();
122     }
123 
124     /**
125      * @param result the evaluation result
126      * @param type If a specific type is specified, then the result will be returned as the corresponding type
127      */
128     void init(final List<?> result, final int type) {
129         result_ = result;
130         resultType_ = type;
131 
132         if (type == ANY_TYPE) {
133             resultType_ = UNORDERED_NODE_ITERATOR_TYPE;
134 
135             if (result_.size() == 1) {
136                 final Object o = result_.get(0);
137                 if (o instanceof Number) {
138                     resultType_ = NUMBER_TYPE;
139                 }
140                 else if (o instanceof String) {
141                     resultType_ = STRING_TYPE;
142                 }
143                 else if (o instanceof Boolean) {
144                     resultType_ = BOOLEAN_TYPE;
145                 }
146             }
147         }
148 
149         iteratorIndex_ = 0;
150     }
151 
152     /**
153      * The code representing the type of this result, as defined by the type constants.
154      * @return the code representing the type of this result
155      */
156     @JsxGetter
157     public int getResultType() {
158         return resultType_;
159     }
160 
161     /**
162      * The number of nodes in the result snapshot.
163      * @return the number of nodes in the result snapshot
164      */
165     @JsxGetter
166     public int getSnapshotLength() {
167         if (resultType_ != UNORDERED_NODE_SNAPSHOT_TYPE && resultType_ != ORDERED_NODE_SNAPSHOT_TYPE) {
168             throw JavaScriptEngine.reportRuntimeError("Cannot get snapshotLength for type: " + resultType_);
169         }
170         return result_.size();
171     }
172 
173     /**
174      * The value of this single node result, which may be null.
175      * @return the value of this single node result, which may be null
176      */
177     @JsxGetter
178     public Node getSingleNodeValue() {
179         if (resultType_ != ANY_UNORDERED_NODE_TYPE && resultType_ != FIRST_ORDERED_NODE_TYPE) {
180             throw JavaScriptEngine.reportRuntimeError("Cannot get singleNodeValue for type: " + resultType_);
181         }
182         if (!result_.isEmpty()) {
183             return ((DomNode) result_.get(0)).getScriptableObject();
184         }
185         return null;
186     }
187 
188     /**
189      * @return signifies that the iterator has become invalid.
190      *         It is true if XPathResult.resultType is UNORDERED_NODE_ITERATOR_TYPE or
191      *         ORDERED_NODE_ITERATOR_TYPE and the document has been modified since this result was returned.
192      */
193     @JsxGetter
194     public boolean getInvalidIteratorState() {
195         return false;
196     }
197 
198     /**
199      * Iterates and returns the next node from the node set or {@code null} if there are no more nodes.
200      * @return the next node
201      */
202     @JsxFunction
203     public Node iterateNext() {
204         if (resultType_ != UNORDERED_NODE_ITERATOR_TYPE && resultType_ != ORDERED_NODE_ITERATOR_TYPE) {
205             throw JavaScriptEngine.reportRuntimeError("Cannot get iterateNext for type: " + resultType_);
206         }
207         if (iteratorIndex_ < result_.size()) {
208             return ((DomNode) result_.get(iteratorIndex_++)).getScriptableObject();
209         }
210         return null;
211     }
212 
213     /**
214      * Returns the index<sup>th</sup> item in the snapshot collection.
215      * If index is greater than or equal to the number of nodes in the list, this method returns null.
216      * @param index Index into the snapshot collection
217      * @return the node at the index<sup>th</sup> position in the NodeList, or null if that is not a valid index
218      */
219     @JsxFunction
220     public Node snapshotItem(final int index) {
221         if (resultType_ != UNORDERED_NODE_SNAPSHOT_TYPE && resultType_ != ORDERED_NODE_SNAPSHOT_TYPE) {
222             throw JavaScriptEngine.reportRuntimeError("Cannot get snapshotLength for type: " + resultType_);
223         }
224         if (index >= 0 && index < result_.size()) {
225             return ((DomNode) result_.get(index)).getScriptableObject();
226         }
227         return null;
228     }
229 
230     /**
231      * Returns the value of this number result.
232      * @return the value of this number result
233      */
234     @JsxGetter
235     public double getNumberValue() {
236         if (resultType_ != NUMBER_TYPE) {
237             throw JavaScriptEngine.reportRuntimeError("Cannot get numberValue for type: " + resultType_);
238         }
239 
240         if (result_.size() == 1) {
241             final Object o = result_.get(0);
242             if (o instanceof Number) {
243                 return ((Double) o).doubleValue();
244             }
245             if (o instanceof Boolean) {
246                 return ((Boolean) o).booleanValue() ? 1 : 0;
247             }
248         }
249 
250         final String asString = asString();
251         double answer;
252         try {
253             answer = Double.parseDouble(asString);
254         }
255         catch (final NumberFormatException e) {
256             answer = Double.NaN;
257         }
258         return answer;
259     }
260 
261     /**
262      * Returns the value of this boolean result.
263      * @return the value of this boolean result
264      */
265     @JsxGetter
266     public boolean getBooleanValue() {
267         if (resultType_ != BOOLEAN_TYPE) {
268             throw JavaScriptEngine.reportRuntimeError("Cannot get booleanValue for type: " + resultType_);
269         }
270 
271         if (result_.size() == 1) {
272             final Object o = result_.get(0);
273             if (o instanceof Number) {
274                 final double d = ((Double) o).doubleValue();
275                 if (Double.isNaN(d) || Double.isInfinite(d)) {
276                     return true;
277                 }
278 
279                 return 0.0 != d;
280             }
281             if (o instanceof String) {
282                 return !((String) o).isEmpty();
283             }
284             if (o instanceof Boolean) {
285                 return ((Boolean) o).booleanValue();
286             }
287         }
288 
289         return !result_.isEmpty();
290     }
291 
292     /**
293      * Returns the value of this string result.
294      * @return the value of this string result
295      */
296     @JsxGetter
297     public String getStringValue() {
298         if (resultType_ != STRING_TYPE) {
299             throw JavaScriptEngine.reportRuntimeError("Cannot get stringValue for type: " + resultType_);
300         }
301         return asString();
302     }
303 
304     private String asString() {
305         if (result_.isEmpty()) {
306             return "";
307         }
308 
309         final Object resultObj = result_.get(0);
310         if (resultObj instanceof DomAttr) {
311             return ((DomAttr) resultObj).getValue();
312         }
313         if (resultObj instanceof DomNode) {
314             return ((DomNode) resultObj).asNormalizedText();
315         }
316         return resultObj.toString();
317     }
318 }