• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / httpunit / 755

14 Feb 2026 07:14PM UTC coverage: 80.526%. Remained the same
755

push

github

hazendaz
[ci] Fix badge

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8245 of 10124 relevant lines covered (81.44%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.16
/src/main/java/com/meterware/httpunit/ParsedHTML.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2000-2026 Russell Gold
6
 * Copyright 2021-2000 hazendaz
7
 */
8
package com.meterware.httpunit;
9

10
import com.meterware.httpunit.dom.HTMLContainerElement;
11
import com.meterware.httpunit.dom.HTMLControl;
12
import com.meterware.httpunit.dom.HTMLDocumentImpl;
13
import com.meterware.httpunit.scripting.ScriptableDelegate;
14

15
import java.io.IOException;
16
import java.net.HttpURLConnection;
17
import java.net.URL;
18
import java.util.ArrayList;
19
import java.util.Arrays;
20
import java.util.HashMap;
21
import java.util.Iterator;
22
import java.util.List;
23
import java.util.Locale;
24
import java.util.StringTokenizer;
25

26
import org.w3c.dom.Document;
27
import org.w3c.dom.Element;
28
import org.w3c.dom.Node;
29
import org.w3c.dom.NodeList;
30
import org.w3c.dom.html.HTMLAppletElement;
31
import org.w3c.dom.html.HTMLCollection;
32
import org.w3c.dom.html.HTMLFormElement;
33
import org.w3c.dom.html.HTMLImageElement;
34
import org.w3c.dom.html.HTMLTableCellElement;
35
import org.w3c.dom.html.HTMLTableRowElement;
36

37
/**
38
 * The Class ParsedHTML.
39
 */
40
public class ParsedHTML {
41

42
    /** The Constant NO_ELEMENTS. */
43
    static final private HTMLElement[] NO_ELEMENTS = {};
1✔
44

45
    /** The Constant TEXT_ELEMENTS. */
46
    static final private String[] TEXT_ELEMENTS = { "p", "h1", "h2", "h3", "h4", "h5", "h6" };
1✔
47

48
    /** The root node. */
49
    private Node _rootNode;
50

51
    /** The base URL. */
52
    private URL _baseURL;
53

54
    /** The frame. */
55
    private FrameSelector _frame;
56

57
    /** The base target. */
58
    private String _baseTarget;
59

60
    /** The character set. */
61
    private String _characterSet;
62

63
    /** The response. */
64
    private WebResponse _response;
65

66
    /** The update elements. */
67
    private boolean _updateElements = true;
1✔
68

69
    /** The enable no script nodes. */
70
    private boolean _enableNoScriptNodes;
71

72
    /** map of element IDs to elements. **/
73
    private HashMap _elementsByID = new HashMap<>();
1✔
74

75
    /** map of element names to lists of elements. **/
76
    private HashMap _elementsByName = new HashMap<>();
1✔
77

78
    /** map of element class to elements. **/
79
    private HashMap _elementsByClass = new HashMap<>();
1✔
80

81
    /** map of DOM elements to HTML elements *. */
82
    private ElementRegistry _registry = new ElementRegistry();
1✔
83

84
    /** The blocks list. */
85
    private ArrayList _blocksList = new ArrayList<>();
1✔
86

87
    /** The blocks. */
88
    private TextBlock[] _blocks;
89

90
    /** The table list. */
91
    private ArrayList _tableList = new ArrayList<>();
1✔
92

93
    /** The tables. */
94
    private WebTable[] _tables;
95

96
    /** The frame list. */
97
    private ArrayList _frameList = new ArrayList<>();
1✔
98

99
    /** The frames. */
100
    private WebFrame[] _frames;
101

102
    /**
103
     * Instantiates a new parsed HTML.
104
     *
105
     * @param response
106
     *            the response
107
     * @param frame
108
     *            the frame
109
     * @param baseURL
110
     *            the base URL
111
     * @param baseTarget
112
     *            the base target
113
     * @param rootNode
114
     *            the root node
115
     * @param characterSet
116
     *            the character set
117
     */
118
    ParsedHTML(WebResponse response, FrameSelector frame, URL baseURL, String baseTarget, Node rootNode,
119
            String characterSet) {
1✔
120
        _response = response;
1✔
121
        _frame = frame;
1✔
122
        _baseURL = baseURL;
1✔
123
        _baseTarget = baseTarget;
1✔
124
        _rootNode = rootNode;
1✔
125
        _characterSet = characterSet;
1✔
126
    }
1✔
127

128
    /**
129
     * Returns the forms found in the page in the order in which they appear.
130
     *
131
     * @return an array of objects representing the forms in the page or portion of a page.
132
     **/
133
    public WebForm[] getForms() {
134
        loadElements();
1✔
135
        HTMLCollection forms = ((HTMLContainerElement) _rootNode).getForms();
1✔
136
        WebForm[] result = new WebForm[forms.getLength()];
1✔
137
        for (int i = 0; i < result.length; i++) {
1✔
138
            result[i] = getWebForm(forms.item(i));
1✔
139
        }
140
        return result;
1✔
141
    }
142

143
    /**
144
     * Gets the web form.
145
     *
146
     * @param node
147
     *            the node
148
     *
149
     * @return the web form
150
     */
151
    private WebForm getWebForm(Node node) {
152
        WebForm webForm = (WebForm) _registry.getRegisteredElement(node);
1✔
153
        return webForm != null ? webForm : (WebForm) _registry.registerElement(node, toWebForm((Element) node));
1!
154
    }
155

156
    /**
157
     * Returns the links found in the page in the order in which they appear.
158
     *
159
     * @return the links
160
     */
161
    public WebLink[] getLinks() {
162
        loadElements();
1✔
163
        HTMLCollection links = ((HTMLContainerElement) _rootNode).getLinks();
1✔
164
        WebLink[] result = new WebLink[links.getLength()];
1✔
165
        for (int i = 0; i < result.length; i++) {
1✔
166
            result[i] = (WebLink) _registry.getRegisteredElement(links.item(i));
1✔
167
            if (result[i] == null) {
1!
168
                result[i] = new WebLink(_response, _baseURL, links.item(i), _frame, _baseTarget, _characterSet);
×
169
                _registry.registerElement(links.item(i), result[i]);
×
170
            }
171
        }
172
        return result;
1✔
173
    }
174

175
    /**
176
     * Returns a proxy for each applet found embedded in this page.
177
     *
178
     * @return the applets
179
     */
180
    public WebApplet[] getApplets() {
181
        loadElements();
1✔
182
        HTMLCollection applets = ((HTMLContainerElement) _rootNode).getApplets();
1✔
183
        WebApplet[] result = new WebApplet[applets.getLength()];
1✔
184
        for (int i = 0; i < result.length; i++) {
1✔
185
            result[i] = (WebApplet) _registry.getRegisteredElement(applets.item(i));
1✔
186
            if (result[i] == null) {
1!
187
                result[i] = new WebApplet(_response, (HTMLAppletElement) applets.item(i), _baseTarget);
×
188
                _registry.registerElement(applets.item(i), result[i]);
×
189
            }
190
        }
191
        return result;
1✔
192
    }
193

194
    /**
195
     * Returns the images found in the page in the order in which they appear.
196
     *
197
     * @return the images
198
     */
199
    public WebImage[] getImages() {
200
        loadElements();
1✔
201
        HTMLCollection images = ((HTMLContainerElement) _rootNode).getImages();
1✔
202
        WebImage[] result = new WebImage[images.getLength()];
1✔
203
        for (int i = 0; i < result.length; i++) {
1✔
204
            result[i] = (WebImage) _registry.getRegisteredElement(images.item(i));
1✔
205
            if (result[i] == null) {
1!
206
                result[i] = new WebImage(_response, this, _baseURL, (HTMLImageElement) images.item(i), _frame,
×
207
                        _baseTarget, _characterSet);
208
                _registry.registerElement(images.item(i), result[i]);
×
209
            }
210
        }
211
        return result;
1✔
212
    }
213

214
    /**
215
     * Returns the top-level block elements found in the page in the order in which they appear.
216
     *
217
     * @return the text blocks
218
     */
219
    public TextBlock[] getTextBlocks() {
220
        loadElements();
1✔
221
        if (_blocks == null) {
1✔
222
            loadElements();
1✔
223
            _blocks = (TextBlock[]) _blocksList.toArray(new TextBlock[_blocksList.size()]);
1✔
224
        }
225
        return _blocks;
1✔
226
    }
227

228
    /**
229
     * Returns the first text block found in the page which matches the specified predicate and value.
230
     *
231
     * @param predicate
232
     *            the predicate
233
     * @param criteria
234
     *            the criteria
235
     *
236
     * @return the first matching text block
237
     */
238
    public TextBlock getFirstMatchingTextBlock(HTMLElementPredicate predicate, Object criteria) {
239
        loadElements();
1✔
240
        TextBlock[] blocks = getTextBlocks();
1✔
241
        for (TextBlock block : blocks) {
1!
242
            if (predicate.matchesCriteria(block, criteria)) {
1✔
243
                return block;
1✔
244
            }
245
        }
246
        return null;
×
247
    }
248

249
    /**
250
     * get the next text block based on the given block.
251
     *
252
     * @param block
253
     *            the block
254
     *
255
     * @return - the next text block
256
     */
257
    public TextBlock getNextTextBlock(TextBlock block) {
258
        loadElements();
1✔
259
        int index = _blocksList.indexOf(block);
1✔
260
        if (index < 0 || index == _blocksList.size() - 1) {
1!
261
            return null;
×
262
        }
263
        return (TextBlock) _blocksList.get(index + 1);
1✔
264
    }
265

266
    /**
267
     * Returns the top-level tables found in the page in the order in which they appear.
268
     *
269
     * @return an array of tables
270
     **/
271
    public WebTable[] getTables() {
272
        loadElements();
1✔
273
        if (_tables == null) {
1✔
274
            _tables = (WebTable[]) _tableList.toArray(new WebTable[_tableList.size()]);
1✔
275
        }
276
        return _tables;
1✔
277
    }
278

279
    /**
280
     * Returns the HTMLElement with the specified ID.
281
     *
282
     * @param id
283
     *            - the id of the element to return
284
     *
285
     * @return the element looked for
286
     */
287
    public HTMLElement getElementWithID(String id) {
288
        return (HTMLElement) getElementWithID(id, HTMLElement.class);
1✔
289
    }
290

291
    /**
292
     * Returns the HTML elements with the specified name.
293
     *
294
     * @param name
295
     *            the name
296
     *
297
     * @return the elements with name
298
     */
299
    public HTMLElement[] getElementsWithName(String name) {
300
        loadElements();
1✔
301
        ArrayList elements = (ArrayList) _elementsByName.get(name);
1✔
302
        return elements == null ? NO_ELEMENTS : (HTMLElement[]) elements.toArray(new HTMLElement[elements.size()]);
1!
303
    }
304

305
    /**
306
     * Returns the HTML elements with the specified class.
307
     *
308
     * @param className
309
     *            the class name
310
     *
311
     * @return the elements with class name
312
     */
313
    public HTMLElement[] getElementsWithClassName(String className) {
314
        loadElements();
1✔
315
        ArrayList elements = (ArrayList) _elementsByClass.get(className);
1✔
316
        return elements == null ? NO_ELEMENTS : (HTMLElement[]) elements.toArray(new HTMLElement[elements.size()]);
1!
317
    }
318

319
    /**
320
     * Returns the HTML elements with an attribute with the specified name and value.
321
     *
322
     * @param name
323
     *            - the name of the attribute to check
324
     * @param value
325
     *            - the value of the attribute to check
326
     *
327
     * @return the elements with attribute
328
     */
329
    public HTMLElement[] getElementsWithAttribute(String name, String value) {
330
        loadElements();
1✔
331
        ArrayList elements = new ArrayList<>();
1✔
332
        for (Iterator i = _registry.iterator(); i.hasNext();) {
1✔
333
            HTMLElement element = (HTMLElement) i.next();
1✔
334
            String aValue = element.getAttribute(name);
1✔
335
            if (value.equals(aValue)) {
1✔
336
                // System.err.println(element.getTagName()+"("+name+")="+aValue);
337
                elements.add(element);
1✔
338
            }
339
        }
1✔
340
        return (HTMLElement[]) elements.toArray(new HTMLElement[elements.size()]);
1✔
341
    }
342

343
    /**
344
     * Returns a list of HTML element names contained in this HTML section.
345
     *
346
     * @return the element names
347
     */
348
    public String[] getElementNames() {
349
        loadElements();
1✔
350
        return (String[]) _elementsByName.keySet().toArray(new String[_elementsByName.size()]);
1✔
351
    }
352

353
    /**
354
     * get the elements with the given tagname.
355
     *
356
     * @param dom
357
     *            - the node to start from
358
     * @param name
359
     *            - the name of the attribute to look for
360
     *
361
     * @return the elements by tag name
362
     */
363
    HTMLElement[] getElementsByTagName(Node dom, String name) {
364
        loadElements();
1✔
365
        if (dom instanceof Element) {
1✔
366
            return getElementsFromList(((Element) dom).getElementsByTagName(name));
1✔
367
        }
368
        return getElementsFromList(((Document) dom).getElementsByTagName(name));
1✔
369
    }
370

371
    /**
372
     * Gets the elements from list.
373
     *
374
     * @param nl
375
     *            the nl
376
     *
377
     * @return the elements from list
378
     */
379
    private HTMLElement[] getElementsFromList(NodeList nl) {
380
        HTMLElement[] elements = new HTMLElement[nl.getLength()];
1✔
381
        for (int i = 0; i < elements.length; i++) {
1✔
382
            Node node = nl.item(i);
1✔
383
            elements[i] = (HTMLElement) _registry.getRegisteredElement(node);
1✔
384
            if (elements[i] == null) {
1✔
385
                elements[i] = toDefaultElement((Element) node);
1✔
386
                _registry.registerElement(node, elements[i]);
1✔
387
            }
388
        }
389
        return elements;
1✔
390
    }
391

392
    /**
393
     * Returns the form found in the page with the specified ID.
394
     *
395
     * @param id
396
     *            the id
397
     *
398
     * @return the form with ID
399
     */
400
    public WebForm getFormWithID(String id) {
401
        return (WebForm) getElementWithID(id, WebForm.class);
1✔
402
    }
403

404
    /**
405
     * Returns the link found in the page with the specified ID.
406
     *
407
     * @param id
408
     *            the id
409
     *
410
     * @return the link with ID
411
     */
412
    public WebLink getLinkWithID(String id) {
413
        return (WebLink) getElementWithID(id, WebLink.class);
1✔
414

415
    }
416

417
    /**
418
     * Gets the element with ID.
419
     *
420
     * @param id
421
     *            the id
422
     * @param klass
423
     *            the klass
424
     *
425
     * @return the element with ID
426
     */
427
    private Object getElementWithID(String id, final Class klass) {
428
        loadElements();
1✔
429
        return whenCast(_elementsByID.get(id), klass);
1✔
430
    }
431

432
    /**
433
     * When cast.
434
     *
435
     * @param o
436
     *            the o
437
     * @param klass
438
     *            the klass
439
     *
440
     * @return the object
441
     */
442
    private Object whenCast(Object o, Class klass) {
443
        return klass.isInstance(o) ? o : null;
1✔
444
    }
445

446
    /**
447
     * Returns the first link found in the page matching the specified criteria.
448
     *
449
     * @param predicate
450
     *            the predicate
451
     * @param criteria
452
     *            the criteria
453
     *
454
     * @return the first matching form
455
     */
456
    public WebForm getFirstMatchingForm(HTMLElementPredicate predicate, Object criteria) {
457
        WebForm[] forms = getForms();
1✔
458
        for (WebForm form : forms) {
1✔
459
            if (predicate.matchesCriteria(form, criteria)) {
1✔
460
                return form;
1✔
461
            }
462
        }
463
        return null;
1✔
464
    }
465

466
    /**
467
     * Returns all links found in the page matching the specified criteria.
468
     *
469
     * @param predicate
470
     *            the predicate
471
     * @param criteria
472
     *            the criteria
473
     *
474
     * @return the matching forms
475
     */
476
    public WebForm[] getMatchingForms(HTMLElementPredicate predicate, Object criteria) {
477
        ArrayList matches = new ArrayList<>();
×
478
        WebForm[] forms = getForms();
×
479
        for (WebForm form : forms) {
×
480
            if (predicate.matchesCriteria(form, criteria)) {
×
481
                matches.add(form);
×
482
            }
483
        }
484
        return (WebForm[]) matches.toArray(new WebForm[matches.size()]);
×
485
    }
486

487
    /**
488
     * Returns the form found in the page with the specified name.
489
     *
490
     * @param name
491
     *            the name
492
     *
493
     * @return the form with name
494
     */
495
    public WebForm getFormWithName(String name) {
496
        return getFirstMatchingForm(WebForm.MATCH_NAME, name);
1✔
497
    }
498

499
    /**
500
     * interpret the given script element.
501
     *
502
     * @param element
503
     *            the element
504
     */
505
    void interpretScriptElement(Element element) {
506
        if (!HttpUnitOptions.isScriptingEnabled()) {
1✔
507
            _enableNoScriptNodes = true;
1✔
508
            return;
1✔
509
        }
510

511
        String script = getScript(element);
1✔
512
        if (script != null) {
1!
513
            try {
514
                _updateElements = false;
1✔
515
                String language = NodeUtils.getNodeAttribute(element, "language", null);
1✔
516
                if (!getResponse().getScriptingHandler().supportsScriptLanguage(language)) {
1!
517
                    _enableNoScriptNodes = true;
1✔
518
                }
519
                getResponse().getScriptingHandler().runScript(language, script);
1✔
520
            } finally {
521
                clearCaches();
1✔
522
            }
523
        }
524
    }
1✔
525

526
    /**
527
     * get the script for the given node.
528
     *
529
     * @param scriptNode
530
     *            the script node
531
     *
532
     * @return the script
533
     */
534
    private String getScript(Node scriptNode) {
535
        String scriptLocation = NodeUtils.getNodeAttribute(scriptNode, "src", null);
1✔
536
        if (scriptLocation == null) {
1!
537
            return NodeUtils.asText(scriptNode.getChildNodes());
1✔
538
        }
539
        try {
540
            return getIncludedScript(scriptLocation);
×
541
        } catch (Exception e) {
×
542
            throw new RuntimeException("Error loading included script: " + e);
×
543
        }
544
    }
545

546
    /**
547
     * Returns the contents of an included script, given its src attribute.
548
     *
549
     * @param srcAttribute
550
     *            the location of the script.
551
     *
552
     * @return the contents of the script.
553
     *
554
     * @throws IOException
555
     *             if there is a problem retrieving the script
556
     */
557
    String getIncludedScript(String srcAttribute) throws IOException {
558
        WebRequest req = new GetMethodWebRequest(getBaseURL(), srcAttribute);
1✔
559
        WebWindow window = getResponse().getWindow();
1✔
560
        if (window == null) {
1!
561
            throw new IllegalStateException(
×
562
                    "Unable to retrieve script included by this response, since it was loaded by getResource(). Use getResponse() instead.");
563
        }
564
        WebResponse response = window.getResource(req);
1✔
565
        // check whether the Source is available
566
        int code = response.getResponseCode();
1✔
567
        // if everything is o.k.
568
        if (code <= HttpURLConnection.HTTP_BAD_REQUEST) {
1✔
569
            return response.getText();
1✔
570
        }
571
        // in this case the text would be an error message
572
        // we do not return it but set the
573
        ScriptException se = new ScriptException(
1✔
574
                "reponseCode " + code + " on getIncludedScript for src='" + srcAttribute + "'");
575
        String badScript = null;
1✔
576
        // let scripting engine decide what to do with this exception (throw it or remember it ...)
577
        HttpUnitOptions.getScriptingEngine().handleScriptException(se, badScript);
1✔
578
        return "";
1✔
579
    }
580

581
    /**
582
     * If noscript node content is enabled, returns null - otherwise returns a concealing element.
583
     *
584
     * @param element
585
     *            the element
586
     *
587
     * @return the HTML element
588
     */
589
    private HTMLElement toNoscriptElement(Element element) {
590
        HTMLElement result = null;
1✔
591
        if (!_enableNoScriptNodes) {
1✔
592
            result = new NoScriptElement(element);
1✔
593
        }
594
        return result;
1✔
595
    }
596

597
    /**
598
     * The Class HtmlElementRecorder.
599
     */
600
    static class HtmlElementRecorder {
1✔
601

602
        /**
603
         * Record html element.
604
         *
605
         * @param pot
606
         *            the pot
607
         * @param node
608
         *            the node
609
         * @param htmlElement
610
         *            the html element
611
         */
612
        protected void recordHtmlElement(NodeUtils.PreOrderTraversal pot, Node node, HTMLElement htmlElement) {
613
            if (htmlElement != null) {
1✔
614
                addToMaps(pot, node, htmlElement);
1✔
615
                addToLists(pot, htmlElement);
1✔
616
            }
617
        }
1✔
618

619
        /**
620
         * Adds the to lists.
621
         *
622
         * @param pot
623
         *            the pot
624
         * @param htmlElement
625
         *            the html element
626
         */
627
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
628
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1✔
629
                Object o = i.next();
1✔
630
                if (o instanceof ParsedHTML) {
1✔
631
                    ((ParsedHTML) o).addToList(htmlElement);
1✔
632
                }
633
            }
1✔
634
        }
1✔
635

636
        /**
637
         * Adds the to maps.
638
         *
639
         * @param pot
640
         *            the pot
641
         * @param node
642
         *            the node
643
         * @param htmlElement
644
         *            the html element
645
         */
646
        protected void addToMaps(NodeUtils.PreOrderTraversal pot, Node node, HTMLElement htmlElement) {
647
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1✔
648
                Object o = i.next();
1✔
649
                if (o instanceof ParsedHTML) {
1✔
650
                    ((ParsedHTML) o).addToMaps(node, htmlElement);
1✔
651
                }
652
            }
1✔
653
        }
1✔
654

655
    }
656

657
    /**
658
     * A factory for creating HTMLElement objects.
659
     */
660
    abstract static class HTMLElementFactory extends HtmlElementRecorder {
1✔
661

662
        /**
663
         * To HTML element.
664
         *
665
         * @param pot
666
         *            the pot
667
         * @param parsedHTML
668
         *            the parsed HTML
669
         * @param element
670
         *            the element
671
         *
672
         * @return the HTML element
673
         */
674
        abstract HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element);
675

676
        /**
677
         * Record element.
678
         *
679
         * @param pot
680
         *            the pot
681
         * @param element
682
         *            the element
683
         * @param parsedHTML
684
         *            the parsed HTML
685
         */
686
        void recordElement(NodeUtils.PreOrderTraversal pot, Element element, ParsedHTML parsedHTML) {
687
            HTMLElement htmlElement = toHTMLElement(pot, parsedHTML, element);
1✔
688
            recordHtmlElement(pot, element, htmlElement);
1✔
689
        }
1✔
690

691
        /**
692
         * Checks if is recognized.
693
         *
694
         * @param properties
695
         *            the properties
696
         *
697
         * @return true, if is recognized
698
         */
699
        protected boolean isRecognized(ClientProperties properties) {
700
            return true;
1✔
701
        }
702

703
        /**
704
         * Adds the to context.
705
         *
706
         * @return true, if successful
707
         */
708
        protected boolean addToContext() {
709
            return false;
1✔
710
        }
711

712
        /**
713
         * Gets the parsed HTML.
714
         *
715
         * @param pot
716
         *            the pot
717
         *
718
         * @return the parsed HTML
719
         */
720
        protected final ParsedHTML getParsedHTML(NodeUtils.PreOrderTraversal pot) {
721
            return (ParsedHTML) getClosestContext(pot, ParsedHTML.class);
×
722
        }
723

724
        /**
725
         * Gets the closest context.
726
         *
727
         * @param pot
728
         *            the pot
729
         * @param aClass
730
         *            the a class
731
         *
732
         * @return the closest context
733
         */
734
        protected final Object getClosestContext(NodeUtils.PreOrderTraversal pot, Class aClass) {
735
            return pot.getClosestContext(aClass);
1✔
736
        }
737

738
        /**
739
         * Gets the root context.
740
         *
741
         * @param pot
742
         *            the pot
743
         *
744
         * @return the root context
745
         */
746
        protected ParsedHTML getRootContext(NodeUtils.PreOrderTraversal pot) {
747
            return (ParsedHTML) pot.getRootContext();
×
748
        }
749
    }
750

751
    /**
752
     * A factory for creating DefaultElement objects.
753
     */
754
    static class DefaultElementFactory extends HTMLElementFactory {
1✔
755

756
        @Override
757
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
758
            // [ 1531005 ] getElementsWithAttribute **FIX**
759
            // if (element.getAttribute( "id" ).equals( "" )) {
760
            // return null;
761
            // }
762
            return parsedHTML.toDefaultElement(element);
1✔
763
        }
764

765
        @Override
766
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
767
        }
1✔
768
    }
769

770
    /**
771
     * To default element.
772
     *
773
     * @param element
774
     *            the element
775
     *
776
     * @return the HTML element
777
     */
778
    private HTMLElement toDefaultElement(Element element) {
779
        return new HTMLElementBase(element) {
1✔
780
            @Override
781
            public ScriptableDelegate newScriptable() {
782
                return new HTMLElementScriptable(this);
1✔
783
            }
784

785
            @Override
786
            public ScriptableDelegate getParentDelegate() {
787
                return getResponse().getDocumentScriptable();
1✔
788
            }
789
        };
790
    }
791

792
    /**
793
     * A factory for creating WebForm objects.
794
     */
795
    static class WebFormFactory extends HTMLElementFactory {
1✔
796
        @Override
797
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
798
            return parsedHTML.toWebForm(element);
1✔
799
        }
800
    }
801

802
    /**
803
     * A factory for creating WebLink objects.
804
     */
805
    static class WebLinkFactory extends HTMLElementFactory {
1✔
806
        @Override
807
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
808
            return parsedHTML.toLinkAnchor(element);
1✔
809
        }
810
    }
811

812
    /**
813
     * A factory for creating TextBlock objects.
814
     */
815
    static class TextBlockFactory extends HTMLElementFactory {
1✔
816
        @Override
817
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
818
            return parsedHTML.toTextBlock(element);
1✔
819
        }
820

821
        @Override
822
        protected boolean addToContext() {
823
            return true;
1✔
824
        }
825

826
        @Override
827
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
828
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1!
829
                Object o = i.next();
1✔
830
                if (!(o instanceof ParsedHTML)) {
1!
831
                    continue;
×
832
                }
833
                ((ParsedHTML) o).addToList(htmlElement);
1✔
834
                break;
1✔
835
            }
836
        }
1✔
837

838
    }
839

840
    /**
841
     * A factory for creating Script objects.
842
     */
843
    static class ScriptFactory extends HTMLElementFactory {
1✔
844

845
        @Override
846
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
847
            return null;
×
848
        }
849

850
        @Override
851
        void recordElement(NodeUtils.PreOrderTraversal pot, Element element, ParsedHTML parsedHTML) {
852
            parsedHTML.interpretScriptElement(element);
1✔
853
        }
1✔
854
    }
855

856
    /**
857
     * A factory for creating NoScript objects.
858
     */
859
    static class NoScriptFactory extends HTMLElementFactory {
1✔
860

861
        @Override
862
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
863
            return parsedHTML.toNoscriptElement(element);
1✔
864
        }
865

866
        @Override
867
        protected boolean addToContext() {
868
            return true;
1✔
869
        }
870
    }
871

872
    /**
873
     * A factory for creating WebFrame objects.
874
     */
875
    static class WebFrameFactory extends HTMLElementFactory {
1✔
876
        @Override
877
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
878
            return parsedHTML.toWebFrame(element);
1✔
879
        }
880
    }
881

882
    /**
883
     * A factory for creating WebIFrame objects.
884
     */
885
    static class WebIFrameFactory extends HTMLElementFactory {
1✔
886
        @Override
887
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
888
            return parsedHTML.toWebIFrame(element);
1✔
889
        }
890

891
        @Override
892
        protected boolean isRecognized(ClientProperties properties) {
893
            return properties.isIframeSupported();
1✔
894
        }
895

896
        @Override
897
        protected boolean addToContext() {
898
            return true;
1✔
899
        }
900
    }
901

902
    /**
903
     * A factory for creating WebImage objects.
904
     */
905
    static class WebImageFactory extends HTMLElementFactory {
1✔
906
        @Override
907
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
908
            return parsedHTML.toWebImage(element);
1✔
909
        }
910
    }
911

912
    /**
913
     * A factory for creating WebApplet objects.
914
     */
915
    static class WebAppletFactory extends HTMLElementFactory {
1✔
916
        @Override
917
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
918
            return parsedHTML.toWebApplet(element);
1✔
919
        }
920

921
        @Override
922
        protected boolean addToContext() {
923
            return true;
1✔
924
        }
925
    }
926

927
    /**
928
     * A factory for creating WebTable objects.
929
     */
930
    static class WebTableFactory extends HTMLElementFactory {
1✔
931
        @Override
932
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
933
            return parsedHTML.toWebTable(element);
1✔
934
        }
935

936
        @Override
937
        protected boolean addToContext() {
938
            return true;
1✔
939
        }
940

941
        @Override
942
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
943
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1✔
944
                Object o = i.next();
1✔
945
                if (o instanceof ParsedHTML) {
1!
946
                    ((ParsedHTML) o).addToList(htmlElement);
1✔
947
                }
948
                if (o instanceof TableCell) {
1✔
949
                    break;
1✔
950
                }
951
            }
1✔
952
        }
1✔
953
    }
954

955
    /**
956
     * A factory for creating TableRow objects.
957
     */
958
    static class TableRowFactory extends HTMLElementFactory {
1✔
959
        @Override
960
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
961
            WebTable wt = getWebTable(pot);
1✔
962
            if (wt == null) {
1!
963
                return null;
×
964
            }
965
            return wt.newTableRow((HTMLTableRowElement) element);
1✔
966
        }
967

968
        /**
969
         * Gets the web table.
970
         *
971
         * @param pot
972
         *            the pot
973
         *
974
         * @return the web table
975
         */
976
        private WebTable getWebTable(NodeUtils.PreOrderTraversal pot) {
977
            return (WebTable) getClosestContext(pot, WebTable.class);
1✔
978
        }
979

980
        @Override
981
        protected boolean addToContext() {
982
            return true;
1✔
983
        }
984

985
        @Override
986
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
987
            getWebTable(pot).addRow((TableRow) htmlElement);
1✔
988
        }
1✔
989
    }
990

991
    /**
992
     * A factory for creating TableCell objects.
993
     */
994
    static class TableCellFactory extends HTMLElementFactory {
1✔
995
        @Override
996
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
997
            TableRow tr = getTableRow(pot);
1✔
998
            if (tr == null) {
1!
999
                return null;
×
1000
            }
1001
            return tr.newTableCell((HTMLTableCellElement) element);
1✔
1002
        }
1003

1004
        /**
1005
         * Gets the table row.
1006
         *
1007
         * @param pot
1008
         *            the pot
1009
         *
1010
         * @return the table row
1011
         */
1012
        private TableRow getTableRow(NodeUtils.PreOrderTraversal pot) {
1013
            return (TableRow) getClosestContext(pot, TableRow.class);
1✔
1014
        }
1015

1016
        @Override
1017
        protected boolean addToContext() {
1018
            return true;
1✔
1019
        }
1020

1021
        @Override
1022
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
1023
            getTableRow(pot).addTableCell((TableCell) htmlElement);
1✔
1024
        }
1✔
1025
    }
1026

1027
    /**
1028
     * A factory for creating FormControl objects.
1029
     */
1030
    static class FormControlFactory extends HTMLElementFactory {
1✔
1031

1032
        @Override
1033
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1034
            HTMLFormElement form = ((HTMLControl) element).getForm();
1✔
1035
            if (form == null) {
1✔
1036
                return newControlWithoutForm(parsedHTML, element);
1✔
1037
            }
1038
            return parsedHTML.getWebForm(form).newFormControl(element);
1✔
1039
        }
1040

1041
        /**
1042
         * New control without form.
1043
         *
1044
         * @param parsedHTML
1045
         *            the parsed HTML
1046
         * @param element
1047
         *            the element
1048
         *
1049
         * @return the HTML element
1050
         */
1051
        private HTMLElement newControlWithoutForm(ParsedHTML parsedHTML, Element element) {
1052
            if ((element.getNodeName().equalsIgnoreCase("button") || element.getNodeName().equalsIgnoreCase("input"))
1!
1053
                    && isValidNonFormButtonType(NodeUtils.getNodeAttribute(element, "type"))) {
1!
1054
                return parsedHTML.toButtonWithoutForm(element);
1✔
1055
            }
1056
            return null;
×
1057
        }
1058

1059
        /**
1060
         * Checks if is valid non form button type.
1061
         *
1062
         * @param buttonType
1063
         *            the button type
1064
         *
1065
         * @return true, if is valid non form button type
1066
         */
1067
        private boolean isValidNonFormButtonType(String buttonType) {
1068
            return buttonType.equals("") || buttonType.equalsIgnoreCase("button");
1!
1069
        }
1070
    }
1071

1072
    /**
1073
     * A factory for creating WebList objects.
1074
     */
1075
    static class WebListFactory extends HTMLElementFactory {
1✔
1076
        @Override
1077
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1078
            return parsedHTML.toOrderedList(element);
×
1079
        }
1080

1081
        @Override
1082
        protected boolean addToContext() {
1083
            return true;
×
1084
        }
1085

1086
        @Override
1087
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
1088
            TextBlock textBlock = getTextBlock(pot);
×
1089
            if (textBlock != null) {
×
1090
                textBlock.addList((WebList) htmlElement);
×
1091
            }
1092
        }
×
1093

1094
        /**
1095
         * Gets the text block.
1096
         *
1097
         * @param pot
1098
         *            the pot
1099
         *
1100
         * @return the text block
1101
         */
1102
        private TextBlock getTextBlock(NodeUtils.PreOrderTraversal pot) {
1103
            return (TextBlock) getClosestContext(pot, TextBlock.class);
×
1104
        }
1105
    }
1106

1107
    /**
1108
     * A factory for creating ListItem objects.
1109
     */
1110
    static class ListItemFactory extends HTMLElementFactory {
1✔
1111
        @Override
1112
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1113
            WebList webList = getWebList(pot);
×
1114
            if (webList == null) {
×
1115
                return null;
×
1116
            }
1117
            return webList.addNewItem(element);
×
1118
        }
1119

1120
        /**
1121
         * Gets the web list.
1122
         *
1123
         * @param pot
1124
         *            the pot
1125
         *
1126
         * @return the web list
1127
         */
1128
        private WebList getWebList(NodeUtils.PreOrderTraversal pot) {
1129
            return (WebList) getClosestContext(pot, WebList.class);
×
1130
        }
1131

1132
        @Override
1133
        protected boolean addToContext() {
1134
            return true;
×
1135
        }
1136

1137
        @Override
1138
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
1139
        }
×
1140
    }
1141

1142
    /** The html factory classes. */
1143
    private static HashMap _htmlFactoryClasses = new HashMap<>();
1✔
1144

1145
    /** The default factory. */
1146
    private static HTMLElementFactory _defaultFactory = new DefaultElementFactory();
1✔
1147

1148
    static {
1149
        _htmlFactoryClasses.put("a", new WebLinkFactory());
1✔
1150
        _htmlFactoryClasses.put("area", new WebLinkFactory());
1✔
1151
        _htmlFactoryClasses.put("form", new WebFormFactory());
1✔
1152
        _htmlFactoryClasses.put("img", new WebImageFactory());
1✔
1153
        _htmlFactoryClasses.put("applet", new WebAppletFactory());
1✔
1154
        _htmlFactoryClasses.put("table", new WebTableFactory());
1✔
1155
        _htmlFactoryClasses.put("tr", new TableRowFactory());
1✔
1156
        _htmlFactoryClasses.put("td", new TableCellFactory());
1✔
1157
        _htmlFactoryClasses.put("th", new TableCellFactory());
1✔
1158
        _htmlFactoryClasses.put("frame", new WebFrameFactory());
1✔
1159
        _htmlFactoryClasses.put("iframe", new WebIFrameFactory());
1✔
1160
        _htmlFactoryClasses.put("script", new ScriptFactory());
1✔
1161
        _htmlFactoryClasses.put("noscript", new NoScriptFactory());
1✔
1162
        _htmlFactoryClasses.put("ol", new WebListFactory());
1✔
1163
        _htmlFactoryClasses.put("ul", new WebListFactory());
1✔
1164
        _htmlFactoryClasses.put("li", new ListItemFactory());
1✔
1165

1166
        for (String element : TEXT_ELEMENTS) {
1✔
1167
            _htmlFactoryClasses.put(element, new TextBlockFactory());
1✔
1168
        }
1169

1170
        for (Iterator i = Arrays.asList(FormControl.getControlElementTags()).iterator(); i.hasNext();) {
1✔
1171
            _htmlFactoryClasses.put(i.next(), new FormControlFactory());
1✔
1172
        }
1173
    }
1✔
1174

1175
    /**
1176
     * Gets the HTML element factory.
1177
     *
1178
     * @param tagName
1179
     *            the tag name
1180
     *
1181
     * @return the HTML element factory
1182
     */
1183
    private static HTMLElementFactory getHTMLElementFactory(String tagName) {
1184
        final HTMLElementFactory factory = (HTMLElementFactory) _htmlFactoryClasses.get(tagName);
1✔
1185
        return factory != null ? factory : _defaultFactory;
1✔
1186
    }
1187

1188
    /**
1189
     * Load elements.
1190
     */
1191
    private void loadElements() {
1192
        if (!_updateElements) {
1✔
1193
            return;
1✔
1194
        }
1195

1196
        NodeUtils.NodeAction action = new NodeUtils.NodeAction() {
1✔
1197
            @Override
1198
            public boolean processElement(NodeUtils.PreOrderTraversal pot, Element element) {
1199
                HTMLElementFactory factory = getHTMLElementFactory(element.getNodeName().toLowerCase(Locale.ENGLISH));
1✔
1200
                if (factory == null || !factory.isRecognized(getClientProperties())
1!
1201
                        || pot.getClosestContext(ContentConcealer.class) != null) {
1✔
1202
                    return true;
1✔
1203
                }
1204

1205
                if (!_registry.hasNode(element)) {
1✔
1206
                    factory.recordElement(pot, element, ParsedHTML.this);
1✔
1207
                }
1208
                if (factory.addToContext()) {
1✔
1209
                    pot.pushContext(_registry.getRegisteredElement(element));
1✔
1210
                }
1211

1212
                return true;
1✔
1213
            }
1214

1215
            @Override
1216
            public void processTextNode(NodeUtils.PreOrderTraversal pot, Node textNode) {
1217
                if (textNode.getNodeValue().trim().isEmpty()) {
1✔
1218
                    return;
1✔
1219
                }
1220

1221
                Node parent = textNode.getParentNode();
1✔
1222
                if (!parent.getNodeName().equalsIgnoreCase("body")
1✔
1223
                        || pot.getClosestContext(ContentConcealer.class) != null) {
1!
1224
                    return;
1✔
1225
                }
1226
                new HtmlElementRecorder().recordHtmlElement(pot, textNode, newTextBlock(textNode));
1✔
1227
            }
1✔
1228
        };
1229
        NodeUtils.PreOrderTraversal nt = new NodeUtils.PreOrderTraversal(getRootNode());
1✔
1230
        nt.pushBaseContext(this);
1✔
1231
        nt.perform(action);
1✔
1232

1233
        _updateElements = false;
1✔
1234
    }
1✔
1235

1236
    /**
1237
     * Gets the client properties.
1238
     *
1239
     * @return the client properties
1240
     */
1241
    private ClientProperties getClientProperties() {
1242
        WebWindow window = _response.getWindow();
1✔
1243
        return window == null ? ClientProperties.getDefaultProperties() : window.getClient().getClientProperties();
1✔
1244
    }
1245

1246
    /**
1247
     * To button without form.
1248
     *
1249
     * @param element
1250
     *            the element
1251
     *
1252
     * @return the button
1253
     */
1254
    private Button toButtonWithoutForm(Element element) {
1255
        return new Button(_response, (HTMLControl) element);
1✔
1256
    }
1257

1258
    /**
1259
     * To web form.
1260
     *
1261
     * @param element
1262
     *            the element
1263
     *
1264
     * @return the web form
1265
     */
1266
    private WebForm toWebForm(Element element) {
1267
        return new WebForm(_response, _baseURL, element, _frame, _baseTarget, _characterSet, _registry);
1✔
1268
    }
1269

1270
    /**
1271
     * To web frame.
1272
     *
1273
     * @param element
1274
     *            the element
1275
     *
1276
     * @return the web frame
1277
     */
1278
    private WebFrame toWebFrame(Element element) {
1279
        return new WebFrame(_response, _baseURL, element, _frame);
1✔
1280
    }
1281

1282
    /**
1283
     * To web I frame.
1284
     *
1285
     * @param element
1286
     *            the element
1287
     *
1288
     * @return the web frame
1289
     */
1290
    private WebFrame toWebIFrame(Element element) {
1291
        return new WebIFrame(_baseURL, element, _frame);
1✔
1292
    }
1293

1294
    /**
1295
     * convert the given child to a link anchor.
1296
     *
1297
     * @param child
1298
     *            the child
1299
     *
1300
     * @return the web link
1301
     */
1302
    private WebLink toLinkAnchor(Element child) {
1303
        return !isWebLink(child) ? null : new WebLink(_response, _baseURL, child, _frame, _baseTarget, _characterSet);
1✔
1304
    }
1305

1306
    /**
1307
     * check whether the given node is a Web link by checking that the node is of type "A".
1308
     *
1309
     * @param node
1310
     *            - the node to check
1311
     *
1312
     * @return whether the given node represents a web link
1313
     */
1314
    public static boolean isWebLink(Node node) {
1315
        /*
1316
         * Bug report [ 1156972 ] isWebLink doesn't recognize all anchor tags by fregienj claims this be changed to
1317
         * check whether the case-insensitive node name is "A" -> should be isAnchor method and getAnchor
1318
         */
1319
        boolean result = false;
1✔
1320
        String tagName = ((Element) node).getTagName();
1✔
1321
        if (!tagName.equalsIgnoreCase("area") && !tagName.equalsIgnoreCase("a")) {
1✔
1322
        } else {
1323
            // pre 1.7 code - still active
1324
            result = node.getAttributes().getNamedItem("href") != null;
1✔
1325
            // proposed patch - not activated
1326
            // result=true;
1327
        }
1328
        return result;
1✔
1329
    }
1330

1331
    /**
1332
     * To web image.
1333
     *
1334
     * @param child
1335
     *            the child
1336
     *
1337
     * @return the web image
1338
     */
1339
    private WebImage toWebImage(Element child) {
1340
        return new WebImage(_response, this, _baseURL, (HTMLImageElement) child, _frame, _baseTarget, _characterSet);
1✔
1341
    }
1342

1343
    /**
1344
     * To web applet.
1345
     *
1346
     * @param element
1347
     *            the element
1348
     *
1349
     * @return the web applet
1350
     */
1351
    private WebApplet toWebApplet(Element element) {
1352
        return new WebApplet(_response, (HTMLAppletElement) element, _baseTarget);
1✔
1353
    }
1354

1355
    /**
1356
     * To web table.
1357
     *
1358
     * @param element
1359
     *            the element
1360
     *
1361
     * @return the web table
1362
     */
1363
    private WebTable toWebTable(Element element) {
1364
        return new WebTable(_response, _frame, element, _baseURL, _baseTarget, _characterSet);
1✔
1365
    }
1366

1367
    /**
1368
     * To text block.
1369
     *
1370
     * @param element
1371
     *            the element
1372
     *
1373
     * @return the text block
1374
     */
1375
    private TextBlock toTextBlock(Element element) {
1376
        return new TextBlock(_response, _frame, _baseURL, _baseTarget, element, _characterSet);
1✔
1377
    }
1378

1379
    /**
1380
     * New text block.
1381
     *
1382
     * @param textNode
1383
     *            the text node
1384
     *
1385
     * @return the text block
1386
     */
1387
    private TextBlock newTextBlock(Node textNode) {
1388
        return new TextBlock(_response, _frame, _baseURL, _baseTarget, textNode, _characterSet);
1✔
1389
    }
1390

1391
    /**
1392
     * To ordered list.
1393
     *
1394
     * @param element
1395
     *            the element
1396
     *
1397
     * @return the web list
1398
     */
1399
    private WebList toOrderedList(Element element) {
1400
        return new WebList(_response, _frame, _baseURL, _baseTarget, element, _characterSet);
×
1401
    }
1402

1403
    /**
1404
     * Adds the to maps.
1405
     *
1406
     * @param node
1407
     *            the node
1408
     * @param htmlElement
1409
     *            the html element
1410
     */
1411
    private void addToMaps(Node node, HTMLElement htmlElement) {
1412
        _registry.registerElement(node, htmlElement);
1✔
1413
        if (htmlElement.getID() != null) {
1!
1414
            _elementsByID.put(htmlElement.getID(), htmlElement);
1✔
1415
        }
1416
        if (htmlElement.getName() != null) {
1✔
1417
            addNamedElement(htmlElement.getName(), htmlElement);
1✔
1418
        }
1419
        if (htmlElement.getClassName() != null) {
1!
1420
            StringTokenizer tokenizer = new StringTokenizer(htmlElement.getClassName());
1✔
1421
            String token;
1422

1423
            while (tokenizer.hasMoreElements()) {
1✔
1424
                token = tokenizer.nextToken();
1✔
1425

1426
                if (_elementsByClass.containsKey(token)) {
1✔
1427
                    ((ArrayList) _elementsByClass.get(token)).add(htmlElement);
1✔
1428
                } else {
1429
                    ArrayList arrayList = new ArrayList<>();
1✔
1430
                    arrayList.add(htmlElement);
1✔
1431
                    _elementsByClass.put(token, arrayList);
1✔
1432
                }
1✔
1433
            }
1434
        }
1435
    }
1✔
1436

1437
    /**
1438
     * Adds the named element.
1439
     *
1440
     * @param name
1441
     *            the name
1442
     * @param htmlElement
1443
     *            the html element
1444
     */
1445
    private void addNamedElement(String name, HTMLElement htmlElement) {
1446
        List list = (List) _elementsByName.get(name);
1✔
1447
        if (list == null) {
1✔
1448
            _elementsByName.put(name, list = new ArrayList<>());
1✔
1449
        }
1450
        list.add(htmlElement);
1✔
1451
    }
1✔
1452

1453
    /**
1454
     * Adds the to list.
1455
     *
1456
     * @param htmlElement
1457
     *            the html element
1458
     */
1459
    private void addToList(HTMLElement htmlElement) {
1460
        List list = getListForElement(htmlElement);
1✔
1461
        if (list != null) {
1✔
1462
            list.add(htmlElement);
1✔
1463
        }
1464
    }
1✔
1465

1466
    /**
1467
     * Gets the list for element.
1468
     *
1469
     * @param element
1470
     *            the element
1471
     *
1472
     * @return the list for element
1473
     */
1474
    private List getListForElement(HTMLElement element) {
1475
        if (element instanceof WebTable) {
1✔
1476
            return _tableList;
1✔
1477
        }
1478
        if (element instanceof WebFrame) {
1✔
1479
            return _frameList;
1✔
1480
        }
1481
        if (element instanceof BlockElement) {
1✔
1482
            return _blocksList;
1✔
1483
        }
1484
        return null;
1✔
1485
    }
1486

1487
    /**
1488
     * Returns the first link which contains the specified text.
1489
     *
1490
     * @param text
1491
     *            the text
1492
     *
1493
     * @return the link with
1494
     */
1495
    public WebLink getLinkWith(String text) {
1496
        return getFirstMatchingLink(WebLink.MATCH_CONTAINED_TEXT, text);
1✔
1497
    }
1498

1499
    /**
1500
     * Returns the link which contains the first image with the specified text as its 'alt' attribute.
1501
     *
1502
     * @param text
1503
     *            the text
1504
     *
1505
     * @return the link with image text
1506
     */
1507
    public WebLink getLinkWithImageText(String text) {
1508
        WebImage image = getImageWithAltText(text);
1✔
1509
        return image == null ? null : image.getLink();
1!
1510
    }
1511

1512
    /**
1513
     * Returns the link found in the page with the specified name.
1514
     *
1515
     * @param name
1516
     *            the name
1517
     *
1518
     * @return the link with name
1519
     */
1520
    public WebLink getLinkWithName(String name) {
1521
        return getFirstMatchingLink(WebLink.MATCH_NAME, name);
1✔
1522
    }
1523

1524
    /**
1525
     * Returns the first link found in the page matching the specified criteria.
1526
     *
1527
     * @param predicate
1528
     *            the predicate
1529
     * @param criteria
1530
     *            the criteria
1531
     *
1532
     * @return the first matching link
1533
     */
1534
    public WebLink getFirstMatchingLink(HTMLElementPredicate predicate, Object criteria) {
1535
        WebLink[] links = getLinks();
1✔
1536
        for (WebLink link : links) {
1✔
1537
            if (predicate.matchesCriteria(link, criteria)) {
1✔
1538
                return link;
1✔
1539
            }
1540
        }
1541
        return null;
1✔
1542
    }
1543

1544
    /**
1545
     * Returns all links found in the page matching the specified criteria.
1546
     *
1547
     * @param predicate
1548
     *            the predicate
1549
     * @param criteria
1550
     *            the criteria
1551
     *
1552
     * @return the matching links
1553
     */
1554
    public WebLink[] getMatchingLinks(HTMLElementPredicate predicate, Object criteria) {
1555
        ArrayList matches = new ArrayList<>();
1✔
1556
        WebLink[] links = getLinks();
1✔
1557
        for (WebLink link : links) {
1✔
1558
            if (predicate.matchesCriteria(link, criteria)) {
1✔
1559
                matches.add(link);
1✔
1560
            }
1561
        }
1562
        return (WebLink[]) matches.toArray(new WebLink[matches.size()]);
1✔
1563
    }
1564

1565
    /**
1566
     * Returns the image found in the page with the specified name.
1567
     *
1568
     * @param name
1569
     *            the name
1570
     *
1571
     * @return the image with name
1572
     */
1573
    public WebImage getImageWithName(String name) {
1574
        WebImage[] images = getImages();
1✔
1575
        for (WebImage image : images) {
1!
1576
            if (HttpUnitUtils.matches(name, image.getName())) {
1!
1577
                return image;
1✔
1578
            }
1579
        }
1580
        return null;
×
1581
    }
1582

1583
    /**
1584
     * Returns the first image found in the page with the specified src attribute.
1585
     *
1586
     * @param source
1587
     *            the source
1588
     *
1589
     * @return the image with source
1590
     */
1591
    public WebImage getImageWithSource(String source) {
1592
        WebImage[] images = getImages();
1✔
1593
        for (WebImage image : images) {
1✔
1594
            if (HttpUnitUtils.matches(source, image.getSource())) {
1✔
1595
                return image;
1✔
1596
            }
1597
        }
1598
        return null;
1✔
1599
    }
1600

1601
    /**
1602
     * Returns the first image found in the page with the specified alt attribute.
1603
     *
1604
     * @param altText
1605
     *            the alt text
1606
     *
1607
     * @return the image with alt text
1608
     */
1609
    public WebImage getImageWithAltText(String altText) {
1610
        WebImage[] images = getImages();
1✔
1611
        for (WebImage image : images) {
1!
1612
            if (HttpUnitUtils.matches(altText, image.getAltText())) {
1✔
1613
                return image;
1✔
1614
            }
1615
        }
1616
        return null;
×
1617
    }
1618

1619
    /**
1620
     * Returns the first table in the response which matches the specified predicate and value. Will recurse into any
1621
     * nested tables, as needed.
1622
     *
1623
     * @param predicate
1624
     *            the predicate
1625
     * @param criteria
1626
     *            the criteria
1627
     *
1628
     * @return the selected table, or null if none is found
1629
     */
1630
    public WebTable getFirstMatchingTable(HTMLElementPredicate predicate, Object criteria) {
1631
        return getTableSatisfyingPredicate(getTables(), predicate, criteria);
1✔
1632
    }
1633

1634
    /**
1635
     * Returns the tables in the response which match the specified predicate and value. Will recurse into any nested
1636
     * tables, as needed.
1637
     *
1638
     * @param predicate
1639
     *            the predicate
1640
     * @param criteria
1641
     *            the criteria
1642
     *
1643
     * @return the selected tables, or null if none are found
1644
     */
1645
    public WebTable[] getMatchingTables(HTMLElementPredicate predicate, Object criteria) {
1646
        return getTablesSatisfyingPredicate(getTables(), predicate, criteria);
×
1647
    }
1648

1649
    /**
1650
     * Returns the first table in the response which has the specified text as the full text of its first non-blank row
1651
     * and non-blank column. Will recurse into any nested tables, as needed.
1652
     *
1653
     * @param text
1654
     *            the text
1655
     *
1656
     * @return the selected table, or null if none is found
1657
     */
1658
    public WebTable getTableStartingWith(String text) {
1659
        return getFirstMatchingTable(WebTable.MATCH_FIRST_NONBLANK_CELL, text);
1✔
1660
    }
1661

1662
    /**
1663
     * Returns the first table in the response which has the specified text as a prefix of the text in its first
1664
     * non-blank row and non-blank column. Will recurse into any nested tables, as needed.
1665
     *
1666
     * @param text
1667
     *            the text
1668
     *
1669
     * @return the selected table, or null if none is found
1670
     */
1671
    public WebTable getTableStartingWithPrefix(String text) {
1672
        return getFirstMatchingTable(WebTable.MATCH_FIRST_NONBLANK_CELL_PREFIX, text);
1✔
1673
    }
1674

1675
    /**
1676
     * Returns the first table in the response which has the specified text as its summary attribute. Will recurse into
1677
     * any nested tables, as needed.
1678
     *
1679
     * @param summary
1680
     *            the summary
1681
     *
1682
     * @return the selected table, or null if none is found
1683
     */
1684
    public WebTable getTableWithSummary(String summary) {
1685
        return getFirstMatchingTable(WebTable.MATCH_SUMMARY, summary);
1✔
1686
    }
1687

1688
    /**
1689
     * Returns the first table in the response which has the specified text as its ID attribute. Will recurse into any
1690
     * nested tables, as needed.
1691
     *
1692
     * @param ID
1693
     *            the id
1694
     *
1695
     * @return the selected table, or null if none is found
1696
     */
1697
    public WebTable getTableWithID(String ID) {
1698
        return getFirstMatchingTable(WebTable.MATCH_ID, ID);
1✔
1699
    }
1700

1701
    /**
1702
     * Returns a copy of the domain object model associated with this page.
1703
     *
1704
     * @return the dom
1705
     */
1706
    public Node getDOM() {
1707
        return getRootNode().cloneNode( /* deep */ true);
1✔
1708
    }
1709

1710
    // ---------------------------------- Object methods --------------------------------
1711

1712
    @Override
1713
    public String toString() {
1714
        return _baseURL.toExternalForm() + System.lineSeparator() + _rootNode;
×
1715
    }
1716

1717
    // ---------------------------------- package members --------------------------------
1718

1719
    /**
1720
     * Specifies the root node for this HTML fragment. It will be either an HTMLDocument or an HTMLElement representing
1721
     * a page fragment.
1722
     *
1723
     * @param rootNode
1724
     *            the new root node
1725
     */
1726
    void setRootNode(Node rootNode) {
1727
        if (_rootNode != null && rootNode != _rootNode) {
1!
1728
            throw new IllegalStateException("The root node has already been defined as " + _rootNode
×
1729
                    + " and cannot be redefined as " + rootNode);
1730
        }
1731
        _rootNode = rootNode;
1✔
1732
        if (rootNode instanceof HTMLDocumentImpl) {
1!
1733
            ((HTMLDocumentImpl) rootNode).setIFramesEnabled(getClientProperties().isIframeSupported());
1✔
1734
        }
1735
        clearCaches();
1✔
1736
    }
1✔
1737

1738
    /**
1739
     * Clear caches.
1740
     */
1741
    private void clearCaches() {
1742
        _tables = null;
1✔
1743
        _frames = null;
1✔
1744
        _blocks = null;
1✔
1745
        _updateElements = true;
1✔
1746
    }
1✔
1747

1748
    /**
1749
     * Returns the base URL for this HTML segment.
1750
     *
1751
     * @return the base URL
1752
     */
1753
    URL getBaseURL() {
1754
        return _baseURL;
1✔
1755
    }
1756

1757
    /**
1758
     * Gets the response.
1759
     *
1760
     * @return the response
1761
     */
1762
    WebResponse getResponse() {
1763
        return _response;
1✔
1764
    }
1765

1766
    /**
1767
     * Returns the domain object model associated with this page, to be used internally.
1768
     *
1769
     * @return the original DOM
1770
     */
1771
    Node getOriginalDOM() {
1772
        return getRootNode();
1✔
1773
    }
1774

1775
    /**
1776
     * Gets the element.
1777
     *
1778
     * @param node
1779
     *            the node
1780
     *
1781
     * @return the element
1782
     */
1783
    HTMLElement getElement(Node node) {
1784
        return (HTMLElement) _registry.getRegisteredElement(node);
1✔
1785
    }
1786

1787
    /**
1788
     * Returns the frames found in the page in the order in which they appear.
1789
     *
1790
     * @return the frames
1791
     */
1792
    public WebFrame[] getFrames() {
1793
        if (_frames == null) {
1✔
1794
            loadElements();
1✔
1795
            _frames = (WebFrame[]) _frameList.toArray(new WebFrame[_frameList.size()]);
1✔
1796
        }
1797
        return _frames;
1✔
1798
    }
1799

1800
    // ---------------------------------- private members --------------------------------
1801

1802
    /**
1803
     * Gets the root node.
1804
     *
1805
     * @return the root node
1806
     */
1807
    Node getRootNode() {
1808
        if (_rootNode == null) {
1!
1809
            throw new IllegalStateException("The root node has not been specified");
×
1810
        }
1811
        return _rootNode;
1✔
1812
    }
1813

1814
    /**
1815
     * Returns the table with the specified text in its summary attribute.
1816
     *
1817
     * @param tables
1818
     *            the tables
1819
     * @param predicate
1820
     *            the predicate
1821
     * @param value
1822
     *            the value
1823
     *
1824
     * @return the table satisfying predicate
1825
     */
1826
    private WebTable getTableSatisfyingPredicate(WebTable[] tables, HTMLElementPredicate predicate, Object value) {
1827
        for (WebTable table : tables) {
1✔
1828
            if (predicate.matchesCriteria(table, value)) {
1✔
1829
                return table;
1✔
1830
            }
1831
            for (int j = 0; j < table.getRowCount(); j++) {
1✔
1832
                for (int k = 0; k < table.getColumnCount(); k++) {
1✔
1833
                    TableCell cell = table.getTableCell(j, k);
1✔
1834
                    if (cell != null) {
1!
1835
                        WebTable[] innerTables = cell.getTables();
1✔
1836
                        if (innerTables.length != 0) {
1✔
1837
                            WebTable result = getTableSatisfyingPredicate(innerTables, predicate, value);
1✔
1838
                            if (result != null) {
1✔
1839
                                return result;
1✔
1840
                            }
1841
                        }
1842
                    }
1843
                }
1844
            }
1845
        }
1846
        return null;
1✔
1847
    }
1848

1849
    /**
1850
     * Returns the tables which match the specified criteria.
1851
     *
1852
     * @param tables
1853
     *            the tables
1854
     * @param predicate
1855
     *            the predicate
1856
     * @param value
1857
     *            the value
1858
     *
1859
     * @return the tables satisfying predicate
1860
     */
1861
    private WebTable[] getTablesSatisfyingPredicate(WebTable[] tables, HTMLElementPredicate predicate, Object value) {
1862
        ArrayList matches = new ArrayList<>();
×
1863
        for (WebTable table : tables) {
×
1864
            if (predicate.matchesCriteria(table, value)) {
×
1865
                matches.add(table);
×
1866
            }
1867
            for (int j = 0; j < table.getRowCount(); j++) {
×
1868
                for (int k = 0; k < table.getColumnCount(); k++) {
×
1869
                    TableCell cell = table.getTableCell(j, k);
×
1870
                    if (cell != null) {
×
1871
                        WebTable[] innerTables = cell.getTables();
×
1872
                        if (innerTables.length != 0) {
×
1873
                            WebTable[] result = getTablesSatisfyingPredicate(innerTables, predicate, value);
×
1874
                            if (result != null && result.length > 0) {
×
1875
                                matches.addAll(Arrays.asList(result));
×
1876
                            }
1877
                        }
1878
                    }
1879
                }
1880
            }
1881
        }
1882
        if (matches.size() > 0) {
×
1883
            return (WebTable[]) matches.toArray(new WebTable[matches.size()]);
×
1884
        }
1885
        return null;
×
1886
    }
1887

1888
    /**
1889
     * The Class WebIFrame.
1890
     */
1891
    class WebIFrame extends WebFrame implements ContentConcealer {
1892

1893
        /**
1894
         * Instantiates a new web I frame.
1895
         *
1896
         * @param baseURL
1897
         *            the base URL
1898
         * @param frameNode
1899
         *            the frame node
1900
         * @param parentFrame
1901
         *            the parent frame
1902
         */
1903
        public WebIFrame(URL baseURL, Node frameNode, FrameSelector parentFrame) {
1✔
1904
            super(_response, baseURL, frameNode, parentFrame);
1✔
1905
        }
1✔
1906
    }
1907

1908
    /**
1909
     * NoScript Element.
1910
     */
1911
    class NoScriptElement extends HTMLElementBase implements ContentConcealer {
1912

1913
        /**
1914
         * Instantiates a new no script element.
1915
         *
1916
         * @param node
1917
         *            the node
1918
         */
1919
        public NoScriptElement(Node node) {
1✔
1920
            super(node);
1✔
1921
        }
1✔
1922

1923
        @Override
1924
        public ScriptableDelegate newScriptable() {
1925
            return null;
×
1926
        }
1927

1928
        @Override
1929
        public ScriptableDelegate getParentDelegate() {
1930
            return null;
×
1931
        }
1932
    }
1933

1934
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc