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.html.xpath;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.BrowserRunner;
19  import org.htmlunit.junit.annotation.Alerts;
20  import org.junit.Test;
21  import org.junit.runner.RunWith;
22  
23  /**
24   * Tests for XPath evaluation on HtmlUnit DOM.
25   *
26   * @author Marc Guillemot
27   * @author Ahmed Ashour
28   * @author Ronald Brill
29   */
30  @RunWith(BrowserRunner.class)
31  public class HtmlUnitXPath2Test extends WebDriverTestCase {
32  
33      /**
34       * {@inheritDoc}
35       */
36      @Override
37      protected boolean isWebClientCached() {
38          return true;
39      }
40  
41      /**
42       * @throws Exception if the test fails
43       */
44      @Test
45      @Alerts({"4", "null"})
46      public void xPathNull() throws Exception {
47          final String content = DOCTYPE_HTML
48              + "<html><head>\n"
49              + "<script>\n"
50              + LOG_TITLE_FUNCTION
51              + "function test() {\n"
52              + "  try {\n"
53              + "    var node = '';"
54              + "    var expr = null;\n"
55              + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
56              + "    node = result.iterateNext();\n"
57              + "    log(result.resultType);\n"
58              + "    log(node);\n"
59              + "  } catch(e) { logEx(e) }\n"
60              + "}\n"
61              + "</script></head>\n"
62              + "<body onload='test()'>\n"
63              + "  <select name='test'><option value='1'>foo&nbsp;and&nbsp;foo</option></select>\n"
64              + "</body></html>";
65  
66          loadPageVerifyTitle2(content);
67      }
68  
69      /**
70       * @throws Exception if the test fails
71       */
72      @Test
73      @Alerts({"4", "null"})
74      public void xPathUndefined() throws Exception {
75          final String content = DOCTYPE_HTML
76              + "<html><head>\n"
77              + "<script>\n"
78              + LOG_TITLE_FUNCTION
79              + "function test() {\n"
80              + "  try {\n"
81              + "    var node = '';"
82              + "    var expr = undefined;\n"
83              + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
84              + "    node = result.iterateNext();\n"
85              + "    log(result.resultType);\n"
86              + "    log(node);\n"
87              + "  } catch(e) { logEx(e) }\n"
88              + "}\n"
89              + "</script></head>\n"
90              + "<body onload='test()'>\n"
91              + "  <select name='test'><option value='1'>foo&nbsp;and&nbsp;foo</option></select>\n"
92              + "</body></html>";
93  
94          loadPageVerifyTitle2(content);
95      }
96  
97      /**
98       * @throws Exception if the test fails
99       */
100     @Test
101     @Alerts({"102", "111", "111", "160", "97", "110", "100", "160", "102", "111", "111"})
102     public void optionText() throws Exception {
103         final String content = DOCTYPE_HTML
104             + "<html><head>\n"
105             + "<script>\n"
106             + LOG_TITLE_FUNCTION
107             + "function test() {\n"
108             + "  try {\n"
109             + "    var expr = 'string(//option)';\n"
110             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
111             + "    var value = result.stringValue;\n"
112             + "    for (var i = 0; i < value.length; i++) {\n"
113             + "      log(value.charCodeAt(i));\n"
114             + "    }\n"
115             + "  } catch(e) { logEx(e) }\n"
116             + "}\n"
117             + "</script></head>\n"
118             + "<body onload='test()'>\n"
119             + "  <select name='test'><option value='1'>foo&nbsp;and&nbsp;foo</option></select>\n"
120             + "</body></html>";
121 
122         loadPageVerifyTitle2(content);
123     }
124 
125     /**
126      * @throws Exception if the test fails
127      */
128     @Test
129     @Alerts("[object HTMLParagraphElement][object HTMLDivElement]")
130     public void pipe() throws Exception {
131         final String content = DOCTYPE_HTML
132             + "<html>\n"
133             + "<head>\n"
134             + "<script>\n"
135             + LOG_TITLE_FUNCTION
136             + "function test() {\n"
137             + "  try {\n"
138             + "    var res = '';"
139             + "    var expr = '//p | //div';\n"
140             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
141             + "    while (node = result.iterateNext()) {\n"
142             + "      res += node;\n"
143             + "    }\n"
144             + "    log(res);\n"
145             + "  } catch(e) { logEx(e) }\n"
146             + "}\n"
147             + "</script></head>\n"
148             + "<body onload='test()'>\n"
149             + "  <p></p>\n"
150             + "  <div></div>\n"
151             + "</body></html>";
152 
153         loadPageVerifyTitle2(content);
154     }
155 
156     /**
157      * @throws Exception if the test fails
158      */
159     @Test
160     @Alerts("a")
161     public void math() throws Exception {
162         final String content = DOCTYPE_HTML
163             + "<html>\n"
164             + "<head>\n"
165             + "<script>\n"
166             + LOG_TITLE_FUNCTION
167             + "function test() {\n"
168             + "  try {\n"
169             + "    var res = '';"
170             + "    var expr = '//p[position()=(1+5-(2*2))div 2]';\n"
171             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
172             + "    while (node = result.iterateNext()) {\n"
173             + "      res += node.id;\n"
174             + "    }\n"
175             + "    log(res);\n"
176             + "  } catch(e) { logEx(e) }\n"
177             + "}\n"
178             + "</script></head>\n"
179             + "<body onload='test()'>\n"
180             + "  <div>\n"
181             + "    <p id='a'></p>\n"
182             + "    <p id='b'></p>\n"
183             + "  </div>\n"
184             + "</body></html>";
185 
186         loadPageVerifyTitle2(content);
187     }
188 
189     /**
190      * @throws Exception if the test fails
191      */
192     @Test
193     @Alerts("b")
194     public void gt() throws Exception {
195         compare("//p[position()>1]");
196     }
197 
198     /**
199      * @throws Exception if the test fails
200      */
201     @Test
202     @Alerts("ab")
203     public void gte() throws Exception {
204         compare("//p[position()>=1]");
205     }
206 
207     /**
208      * @throws Exception if the test fails
209      */
210     @Test
211     @Alerts("a")
212     public void lt() throws Exception {
213         compare("//p[position()<2]");
214     }
215 
216     /**
217      * @throws Exception if the test fails
218      */
219     @Test
220     @Alerts("ab")
221     public void lte() throws Exception {
222         compare("//p[position()<=2]");
223     }
224 
225     /**
226      * @throws Exception if the test fails
227      */
228     @Test
229     @Alerts("b")
230     public void eq() throws Exception {
231         compare("//p[position()=2]");
232     }
233 
234     /**
235      * @throws Exception if the test fails
236      */
237     @Test
238     @Alerts("b")
239     public void neq() throws Exception {
240         compare("//p[position()!=1]");
241     }
242 
243     /**
244      * @throws Exception if the test fails
245      */
246     @Test
247     @Alerts("a")
248     public void and() throws Exception {
249         compare("//p[@x>= 0 and @y=7]");
250     }
251 
252     /**
253      * @throws Exception if the test fails
254      */
255     @Test
256     @Alerts("ab")
257     public void or() throws Exception {
258         compare("//p[@x>= 0 or @y>4]");
259     }
260 
261     /**
262      * @throws Exception if the test fails
263      */
264     @Test
265     @Alerts("ab")
266     public void mod() throws Exception {
267         compare("//p[@y mod 6 = 1]");
268     }
269 
270     /**
271      * @throws Exception if the test fails
272      */
273     @Test
274     @Alerts("'adc'")
275     public void translate() throws Exception {
276         compareStringValue("translate(\"abc\", \"b\", \"d\")");
277     }
278 
279     /**
280      * @throws Exception if the test fails
281      */
282     @Test
283     @Alerts("true")
284     public void trueTest() throws Exception {
285         compareBooleanValue("true()");
286     }
287 
288     /**
289      * @throws Exception if the test fails
290      */
291     @Test
292     @Alerts("false")
293     public void falseTest() throws Exception {
294         compareBooleanValue("false()");
295     }
296 
297     /**
298      * @throws Exception if the test fails
299      */
300     @Test
301     @Alerts("true")
302     public void booleanTest() throws Exception {
303         compareBooleanValue("boolean(7)");
304     }
305 
306     /**
307      * @throws Exception if the test fails
308      */
309     @Test
310     @Alerts("false")
311     public void booleanTestFalse() throws Exception {
312         compareBooleanValue("boolean(0)");
313     }
314 
315     /**
316      * @throws Exception if the test fails
317      */
318     @Test
319     @Alerts("b")
320     public void number() throws Exception {
321         compare("//p[@y=number(\"  13\t \")]");
322     }
323 
324     /**
325      * @throws Exception if the test fails
326      */
327     @Test
328     @Alerts("false")
329     public void startsWith() throws Exception {
330         compareBooleanValue("starts-with(\"haystack\", \"needle\")");
331     }
332 
333     /**
334      * @throws Exception if the test fails
335      */
336     @Test
337     @Alerts("true")
338     public void startsWithFound() throws Exception {
339         compareBooleanValue("starts-with(\"haystack\", \"hay\")");
340     }
341 
342     /**
343      * @throws Exception if the test fails
344      */
345     @Test
346     @Alerts("true")
347     public void startsWithWhole() throws Exception {
348         compareBooleanValue("starts-with(\"haystack\", \"haystack\")");
349     }
350 
351     /**
352      * @throws Exception if the test fails
353      */
354     @Test
355     @Alerts("true")
356     public void startsWithEmpty() throws Exception {
357         compareBooleanValue("starts-with(\"haystack\", \"\")");
358     }
359 
360     /**
361      * @throws Exception if the test fails
362      */
363     @Test
364     @Alerts("true")
365     public void startsWithEmptyEmpty() throws Exception {
366         compareBooleanValue("starts-with(\"\", \"\")");
367     }
368 
369     /**
370      * @throws Exception if the test fails
371      */
372     @Test
373     @Alerts("'tml'")
374     public void substring() throws Exception {
375         compareStringValue("substring(\"HtmlUnit\", 2, 3)");
376     }
377 
378     /**
379      * @throws Exception if the test fails
380      */
381     @Test
382     @Alerts("''")
383     public void substringWithNegativeLength() throws Exception {
384         compareStringValue("substring(\"HtmlUnit\", 2, -1)");
385     }
386 
387     /**
388      * @throws Exception in case of problems
389      */
390     @Test
391     @Alerts("''")
392     public void substringNegativeStartWithLength() throws Exception {
393         compareStringValue("substring(\"HtmlUnit\", -50, 20)");
394     }
395 
396     /**
397      * @throws Exception if the test fails
398      */
399     @Test
400     @Alerts("'Unit'")
401     public void substringAfter() throws Exception {
402         compareStringValue("substring-after(\"HtmlUnit\", \"tml\")");
403     }
404 
405     /**
406      * @throws Exception if the test fails
407      */
408     @Test
409     @Alerts("'Html'")
410     public void substringBefore() throws Exception {
411         compareStringValue("substring-before(\"HtmlUnit\", \"Uni\")");
412     }
413 
414     /**
415      * @throws Exception if the test fails
416      */
417     @Test
418     @Alerts("false")
419     public void not() throws Exception {
420         compareBooleanValue("not(\"HtmlUnit\")");
421     }
422 
423     /**
424      * @throws Exception if the test fails
425      */
426     @Test
427     @Alerts("a")
428     public void attrib() throws Exception {
429         compare("//p[@x=1]");
430     }
431 
432     /**
433      * @throws Exception if the test fails
434      */
435     @Test
436     @Alerts("SyntaxError/DOMException")
437     public void lowerCaseNotSupported() throws Exception {
438         compareError("//*[lower-case(@id) = \"a\"]");
439     }
440 
441     /**
442      * @throws Exception if the test fails
443      */
444     @Test
445     @Alerts("SyntaxError/DOMException")
446     public void upperCaseNotSupported() throws Exception {
447         compareError("//*[upper-case(@id) = \"A\"]");
448     }
449 
450     /**
451      * @throws Exception if the test fails
452      */
453     @Test
454     @Alerts("SyntaxError/DOMException")
455     public void endsWithNotSupported() throws Exception {
456         compareError("ends-with(\"haystack\", \"haystack\")");
457     }
458 
459     private void compare(final String xpath) throws Exception {
460         final String content = DOCTYPE_HTML
461             + "<html>\n"
462             + "<head>\n"
463             + "<script>\n"
464             + LOG_TITLE_FUNCTION
465             + "function test() {\n"
466             + "  try {\n"
467             + "    var res = '';\n"
468             + "    var expr = '" + xpath + "';\n"
469             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
470             + "    while (node = result.iterateNext()) {\n"
471             + "      res += node.id;\n"
472             + "    }\n"
473             + "    log(res);\n"
474             + "  } catch(e) { logEx(e) }\n"
475             + "}\n"
476             + "</script></head>\n"
477             + "<body onload='test()'>\n"
478             + "  <div>\n"
479             + "    <p id='a' x='1' y='7'></p>\n"
480             + "    <p id='b' y='13'></p>\n"
481             + "  </div>\n"
482             + "</body></html>";
483 
484         loadPageVerifyTitle2(content);
485     }
486 
487     private void compareStringValue(final String xpath) throws Exception {
488         final String content = DOCTYPE_HTML
489             + "<html>\n"
490             + "<head>\n"
491             + "<script>\n"
492             + LOG_TITLE_FUNCTION
493             + "function test() {\n"
494             + "  try {\n"
495             + "    var res = '';\n"
496             + "    var expr = '" + xpath + "';\n"
497             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
498             + "    log(\"'\" + result.stringValue + \"'\");\n"
499             + "  } catch(e) { logEx(e) }\n"
500             + "}\n"
501             + "</script></head>\n"
502             + "<body onload='test()'>\n"
503             + "  <div>\n"
504             + "    <p id='a' x='1' y='7'></p>\n"
505             + "    <p id='b' y='13'></p>\n"
506             + "  </div>\n"
507             + "</body></html>";
508 
509         loadPageVerifyTitle2(content);
510     }
511 
512     private void compareBooleanValue(final String xpath) throws Exception {
513         final String content = DOCTYPE_HTML
514             + "<html>\n"
515             + "<head>\n"
516             + "<script>\n"
517             + LOG_TITLE_FUNCTION
518             + "function test() {\n"
519             + "  try {\n"
520             + "    var res = '';\n"
521             + "    var expr = '" + xpath + "';\n"
522             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
523             + "    log(result.booleanValue);\n"
524             + "  } catch(e) { logEx(e) }\n"
525             + "}\n"
526             + "</script></head>\n"
527             + "<body onload='test()'>\n"
528             + "  <div>\n"
529             + "    <p id='a' x='1' y='7'></p>\n"
530             + "    <p id='b' y='13'></p>\n"
531             + "  </div>\n"
532             + "</body></html>";
533 
534         loadPageVerifyTitle2(content);
535     }
536 
537     private void compareError(final String xpath) throws Exception {
538         final String content = DOCTYPE_HTML
539             + "<html>\n"
540             + "<head>\n"
541             + "<script>\n"
542             + LOG_TITLE_FUNCTION
543             + "function test() {\n"
544             + "  try {\n"
545             + "    var expr = '" + xpath + "';\n"
546             + "    var result = document.evaluate(expr, document.documentElement, null, XPathResult.ANY_TYPE, null);\n"
547             + "    log('error expected');\n"
548             + "  } catch(e) { logEx(e) }\n"
549             + "}\n"
550             + "</script></head>\n"
551             + "<body onload='test()'>\n"
552             + "</body></html>";
553 
554         loadPageVerifyTitle2(content);
555     }
556 
557     /**
558      * @throws Exception if the test fails
559      */
560     @Test
561     @Alerts("mySpan")
562     public void minimalParameters() throws Exception {
563         final String content = DOCTYPE_HTML
564             + "<html>\n"
565             + "<head>\n"
566             + "<script>\n"
567             + LOG_TITLE_FUNCTION
568             + "function test() {\n"
569             + "  try {\n"
570             + "    var res = '';\n"
571             + "    var result = document.evaluate('//span', document.documentElement);\n"
572             + "    while (node = result.iterateNext()) {\n"
573             + "      res += node.id;\n"
574             + "    }\n"
575             + "    log(res);\n"
576             + "  } catch(e) { logEx(e) }\n"
577             + "}\n"
578             + "</script></head>\n"
579             + "<body onload='test()'>\n"
580             + "  <div id='myDiv' attr='false'>false</span>\n"
581             + "  <span id='mySpan' attr='true'>true</span>\n"
582             + "</body></html>";
583 
584         loadPageVerifyTitle2(content);
585     }
586 
587     /**
588      * @throws Exception if the test fails
589      */
590     @Test
591     @Alerts("mySpan")
592     public void undefinedResult() throws Exception {
593         final String content = DOCTYPE_HTML
594             + "<html>\n"
595             + "<head>\n"
596             + "<script>\n"
597             + LOG_TITLE_FUNCTION
598             + "function test() {\n"
599             + "  try {\n"
600             + "    var res = '';\n"
601             + "    var result = document.evaluate('//span', "
602                         + "document.documentElement, null, XPathResult.ANY_TYPE, undefined);\n"
603             + "    while (node = result.iterateNext()) {\n"
604             + "      res += node.id;\n"
605             + "    }\n"
606             + "    log(res);\n"
607             + "  } catch(e) { logEx(e) }\n"
608             + "}\n"
609             + "</script></head>\n"
610             + "<body onload='test()'>\n"
611             + "  <div id='myDiv' attr='false'>false</span>\n"
612             + "  <span id='mySpan' attr='true'>true</span>\n"
613             + "</body></html>";
614 
615         loadPageVerifyTitle2(content);
616     }
617 
618     /**
619      * @throws Exception if the test fails
620      */
621     @Test
622     @Alerts("TypeError")
623     public void stringResult() throws Exception {
624         final String content = DOCTYPE_HTML
625             + "<html>\n"
626             + "<head>\n"
627             + "<script>\n"
628             + LOG_TITLE_FUNCTION
629             + "function test() {\n"
630             + "  try {\n"
631             + "    var res = '';\n"
632             + "    var result = document.evaluate('//span', "
633                         + "document.documentElement, null, XPathResult.ANY_TYPE, 'abcd');\n"
634             + "    while (node = result.iterateNext()) {\n"
635             + "      res += node.id;\n"
636             + "    }\n"
637             + "    log(res);\n"
638             + "  } catch(e) { logEx(e) }\n"
639             + "}\n"
640             + "</script></head>\n"
641             + "<body onload='test()'>\n"
642             + "  <div id='myDiv' attr='false'>false</span>\n"
643             + "  <span id='mySpan' attr='true'>true</span>\n"
644             + "</body></html>";
645 
646         loadPageVerifyTitle2(content);
647     }
648 
649     /**
650      * @throws Exception if the test fails
651      */
652     @Test
653     @Alerts("mySpan")
654     public void objectResult() throws Exception {
655         final String content = DOCTYPE_HTML
656             + "<html>\n"
657             + "<head>\n"
658             + "<script>\n"
659             + LOG_TITLE_FUNCTION
660             + "function test() {\n"
661             + "  try {\n"
662             + "    var res = '';\n"
663             + "    var result = document.evaluate('//span', "
664                         + "document.documentElement, null, XPathResult.ANY_TYPE, {});\n"
665             + "    while (node = result.iterateNext()) {\n"
666             + "      res += node.id;\n"
667             + "    }\n"
668             + "    log(res);\n"
669             + "  } catch(e) { logEx(e) }\n"
670             + "}\n"
671             + "</script></head>\n"
672             + "<body onload='test()'>\n"
673             + "  <div id='myDiv' attr='false'>false</span>\n"
674             + "  <span id='mySpan' attr='true'>true</span>\n"
675             + "</body></html>";
676 
677         loadPageVerifyTitle2(content);
678     }
679 
680     /**
681      * @throws Exception if the test fails
682      */
683     @Test
684     @Alerts(DEFAULT = "mySpan - - myDiv",
685             FF = "mySpan - myDiv - ",
686             FF_ESR = "mySpan - myDiv - ")
687     public void reuseResult() throws Exception {
688         final String content = DOCTYPE_HTML
689             + "<html>\n"
690             + "<head>\n"
691             + "<script>\n"
692             + LOG_TITLE_FUNCTION
693             + "function test() {\n"
694             + "  try {\n"
695             + "    var res = '';\n"
696             + "    var result = document.evaluate('//span', "
697                         + "document.documentElement, null, XPathResult.ANY_TYPE);\n"
698             + "    while (node = result.iterateNext()) {\n"
699             + "      res += node.id;\n"
700             + "    }\n"
701             + "    res += ' - ';\n"
702 
703             + "    var result2 = document.evaluate('//div', "
704                         + "document.documentElement, null, XPathResult.ANY_TYPE, result);\n"
705             + "    while (node = result.iterateNext()) {\n"
706             + "      res += node.id;\n"
707             + "    }\n"
708             + "    res += ' - ';\n"
709 
710             + "    while (node = result2.iterateNext()) {\n"
711             + "      res += node.id;\n"
712             + "    }\n"
713 
714             + "    log(res);\n"
715             + "  } catch(e) { logEx(e) }\n"
716             + "}\n"
717             + "</script></head>\n"
718             + "<body onload='test()'>\n"
719             + "  <div id='myDiv' attr='false'>false</span>\n"
720             + "  <span id='mySpan' attr='true'>true</span>\n"
721             + "</body></html>";
722 
723         loadPageVerifyTitle2(content);
724     }
725 
726     /**
727      * @throws Exception if the test fails
728      */
729     @Test
730     @Alerts("myDiv1")
731     public void documentEvaluateFirst() throws Exception {
732         final String content = DOCTYPE_HTML
733             + "<html>\n"
734             + "<head>\n"
735             + "<script>\n"
736             + LOG_TITLE_FUNCTION
737             + "function test() {\n"
738             + "  try {\n"
739             + "    var res = '';\n"
740             + "    var result = document.evaluate('//div', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE);\n"
741             + "    log(result.singleNodeValue.id);\n"
742             + "  } catch(e) { logEx(e) }\n"
743             + "}\n"
744             + "</script></head>\n"
745             + "<body onload='test()'>\n"
746             + "  <div id='myDiv1'>div1</div>\n"
747             + "  <div id='myDiv2'>div1</div>\n"
748             + "</body></html>";
749 
750         loadPageVerifyTitle2(content);
751     }
752 }