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

hazendaz / httpunit / 656

06 Dec 2025 09:11PM UTC coverage: 80.452% (+0.02%) from 80.435%
656

push

github

hazendaz
[maven-release-plugin] prepare for next development iteration

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8245 of 10137 relevant lines covered (81.34%)

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
 * MIT License
3
 *
4
 * Copyright 2011-2025 Russell Gold
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
8
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
9
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
12
 * of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
15
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
17
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
 * DEALINGS IN THE SOFTWARE.
19
 */
20
package com.meterware.httpunit;
21

22
import com.meterware.httpunit.dom.HTMLContainerElement;
23
import com.meterware.httpunit.dom.HTMLControl;
24
import com.meterware.httpunit.dom.HTMLDocumentImpl;
25
import com.meterware.httpunit.scripting.ScriptableDelegate;
26

27
import java.io.IOException;
28
import java.net.HttpURLConnection;
29
import java.net.URL;
30
import java.util.ArrayList;
31
import java.util.Arrays;
32
import java.util.HashMap;
33
import java.util.Iterator;
34
import java.util.List;
35
import java.util.Locale;
36
import java.util.StringTokenizer;
37

38
import org.w3c.dom.Document;
39
import org.w3c.dom.Element;
40
import org.w3c.dom.Node;
41
import org.w3c.dom.NodeList;
42
import org.w3c.dom.html.HTMLAppletElement;
43
import org.w3c.dom.html.HTMLCollection;
44
import org.w3c.dom.html.HTMLFormElement;
45
import org.w3c.dom.html.HTMLImageElement;
46
import org.w3c.dom.html.HTMLTableCellElement;
47
import org.w3c.dom.html.HTMLTableRowElement;
48

49
/**
50
 * The Class ParsedHTML.
51
 */
52
public class ParsedHTML {
53

54
    /** The Constant NO_ELEMENTS. */
55
    static final private HTMLElement[] NO_ELEMENTS = {};
1✔
56

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

60
    /** The root node. */
61
    private Node _rootNode;
62

63
    /** The base URL. */
64
    private URL _baseURL;
65

66
    /** The frame. */
67
    private FrameSelector _frame;
68

69
    /** The base target. */
70
    private String _baseTarget;
71

72
    /** The character set. */
73
    private String _characterSet;
74

75
    /** The response. */
76
    private WebResponse _response;
77

78
    /** The update elements. */
79
    private boolean _updateElements = true;
1✔
80

81
    /** The enable no script nodes. */
82
    private boolean _enableNoScriptNodes;
83

84
    /** map of element IDs to elements. **/
85
    private HashMap _elementsByID = new HashMap<>();
1✔
86

87
    /** map of element names to lists of elements. **/
88
    private HashMap _elementsByName = new HashMap<>();
1✔
89

90
    /** map of element class to elements. **/
91
    private HashMap _elementsByClass = new HashMap<>();
1✔
92

93
    /** map of DOM elements to HTML elements *. */
94
    private ElementRegistry _registry = new ElementRegistry();
1✔
95

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

99
    /** The blocks. */
100
    private TextBlock[] _blocks;
101

102
    /** The table list. */
103
    private ArrayList _tableList = new ArrayList<>();
1✔
104

105
    /** The tables. */
106
    private WebTable[] _tables;
107

108
    /** The frame list. */
109
    private ArrayList _frameList = new ArrayList<>();
1✔
110

111
    /** The frames. */
112
    private WebFrame[] _frames;
113

114
    /**
115
     * Instantiates a new parsed HTML.
116
     *
117
     * @param response
118
     *            the response
119
     * @param frame
120
     *            the frame
121
     * @param baseURL
122
     *            the base URL
123
     * @param baseTarget
124
     *            the base target
125
     * @param rootNode
126
     *            the root node
127
     * @param characterSet
128
     *            the character set
129
     */
130
    ParsedHTML(WebResponse response, FrameSelector frame, URL baseURL, String baseTarget, Node rootNode,
131
            String characterSet) {
1✔
132
        _response = response;
1✔
133
        _frame = frame;
1✔
134
        _baseURL = baseURL;
1✔
135
        _baseTarget = baseTarget;
1✔
136
        _rootNode = rootNode;
1✔
137
        _characterSet = characterSet;
1✔
138
    }
1✔
139

140
    /**
141
     * Returns the forms found in the page in the order in which they appear.
142
     *
143
     * @return an array of objects representing the forms in the page or portion of a page.
144
     **/
145
    public WebForm[] getForms() {
146
        loadElements();
1✔
147
        HTMLCollection forms = ((HTMLContainerElement) _rootNode).getForms();
1✔
148
        WebForm[] result = new WebForm[forms.getLength()];
1✔
149
        for (int i = 0; i < result.length; i++) {
1✔
150
            result[i] = getWebForm(forms.item(i));
1✔
151
        }
152
        return result;
1✔
153
    }
154

155
    /**
156
     * Gets the web form.
157
     *
158
     * @param node
159
     *            the node
160
     *
161
     * @return the web form
162
     */
163
    private WebForm getWebForm(Node node) {
164
        WebForm webForm = (WebForm) _registry.getRegisteredElement(node);
1✔
165
        return webForm != null ? webForm : (WebForm) _registry.registerElement(node, toWebForm((Element) node));
1!
166
    }
167

168
    /**
169
     * Returns the links found in the page in the order in which they appear.
170
     *
171
     * @return the links
172
     */
173
    public WebLink[] getLinks() {
174
        loadElements();
1✔
175
        HTMLCollection links = ((HTMLContainerElement) _rootNode).getLinks();
1✔
176
        WebLink[] result = new WebLink[links.getLength()];
1✔
177
        for (int i = 0; i < result.length; i++) {
1✔
178
            result[i] = (WebLink) _registry.getRegisteredElement(links.item(i));
1✔
179
            if (result[i] == null) {
1!
180
                result[i] = new WebLink(_response, _baseURL, links.item(i), _frame, _baseTarget, _characterSet);
×
181
                _registry.registerElement(links.item(i), result[i]);
×
182
            }
183
        }
184
        return result;
1✔
185
    }
186

187
    /**
188
     * Returns a proxy for each applet found embedded in this page.
189
     *
190
     * @return the applets
191
     */
192
    public WebApplet[] getApplets() {
193
        loadElements();
1✔
194
        HTMLCollection applets = ((HTMLContainerElement) _rootNode).getApplets();
1✔
195
        WebApplet[] result = new WebApplet[applets.getLength()];
1✔
196
        for (int i = 0; i < result.length; i++) {
1✔
197
            result[i] = (WebApplet) _registry.getRegisteredElement(applets.item(i));
1✔
198
            if (result[i] == null) {
1!
199
                result[i] = new WebApplet(_response, (HTMLAppletElement) applets.item(i), _baseTarget);
×
200
                _registry.registerElement(applets.item(i), result[i]);
×
201
            }
202
        }
203
        return result;
1✔
204
    }
205

206
    /**
207
     * Returns the images found in the page in the order in which they appear.
208
     *
209
     * @return the images
210
     */
211
    public WebImage[] getImages() {
212
        loadElements();
1✔
213
        HTMLCollection images = ((HTMLContainerElement) _rootNode).getImages();
1✔
214
        WebImage[] result = new WebImage[images.getLength()];
1✔
215
        for (int i = 0; i < result.length; i++) {
1✔
216
            result[i] = (WebImage) _registry.getRegisteredElement(images.item(i));
1✔
217
            if (result[i] == null) {
1!
218
                result[i] = new WebImage(_response, this, _baseURL, (HTMLImageElement) images.item(i), _frame,
×
219
                        _baseTarget, _characterSet);
220
                _registry.registerElement(images.item(i), result[i]);
×
221
            }
222
        }
223
        return result;
1✔
224
    }
225

226
    /**
227
     * Returns the top-level block elements found in the page in the order in which they appear.
228
     *
229
     * @return the text blocks
230
     */
231
    public TextBlock[] getTextBlocks() {
232
        loadElements();
1✔
233
        if (_blocks == null) {
1✔
234
            loadElements();
1✔
235
            _blocks = (TextBlock[]) _blocksList.toArray(new TextBlock[_blocksList.size()]);
1✔
236
        }
237
        return _blocks;
1✔
238
    }
239

240
    /**
241
     * Returns the first text block found in the page which matches the specified predicate and value.
242
     *
243
     * @param predicate
244
     *            the predicate
245
     * @param criteria
246
     *            the criteria
247
     *
248
     * @return the first matching text block
249
     */
250
    public TextBlock getFirstMatchingTextBlock(HTMLElementPredicate predicate, Object criteria) {
251
        loadElements();
1✔
252
        TextBlock[] blocks = getTextBlocks();
1✔
253
        for (TextBlock block : blocks) {
1!
254
            if (predicate.matchesCriteria(block, criteria)) {
1✔
255
                return block;
1✔
256
            }
257
        }
258
        return null;
×
259
    }
260

261
    /**
262
     * get the next text block based on the given block.
263
     *
264
     * @param block
265
     *            the block
266
     *
267
     * @return - the next text block
268
     */
269
    public TextBlock getNextTextBlock(TextBlock block) {
270
        loadElements();
1✔
271
        int index = _blocksList.indexOf(block);
1✔
272
        if (index < 0 || index == _blocksList.size() - 1) {
1!
273
            return null;
×
274
        }
275
        return (TextBlock) _blocksList.get(index + 1);
1✔
276
    }
277

278
    /**
279
     * Returns the top-level tables found in the page in the order in which they appear.
280
     *
281
     * @return an array of tables
282
     **/
283
    public WebTable[] getTables() {
284
        loadElements();
1✔
285
        if (_tables == null) {
1✔
286
            _tables = (WebTable[]) _tableList.toArray(new WebTable[_tableList.size()]);
1✔
287
        }
288
        return _tables;
1✔
289
    }
290

291
    /**
292
     * Returns the HTMLElement with the specified ID.
293
     *
294
     * @param id
295
     *            - the id of the element to return
296
     *
297
     * @return the element looked for
298
     */
299
    public HTMLElement getElementWithID(String id) {
300
        return (HTMLElement) getElementWithID(id, HTMLElement.class);
1✔
301
    }
302

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

317
    /**
318
     * Returns the HTML elements with the specified class.
319
     *
320
     * @param className
321
     *            the class name
322
     *
323
     * @return the elements with class name
324
     */
325
    public HTMLElement[] getElementsWithClassName(String className) {
326
        loadElements();
1✔
327
        ArrayList elements = (ArrayList) _elementsByClass.get(className);
1✔
328
        return elements == null ? NO_ELEMENTS : (HTMLElement[]) elements.toArray(new HTMLElement[elements.size()]);
1!
329
    }
330

331
    /**
332
     * Returns the HTML elements with an attribute with the specified name and value.
333
     *
334
     * @param name
335
     *            - the name of the attribute to check
336
     * @param value
337
     *            - the value of the attribute to check
338
     *
339
     * @return the elements with attribute
340
     */
341
    public HTMLElement[] getElementsWithAttribute(String name, String value) {
342
        loadElements();
1✔
343
        ArrayList elements = new ArrayList<>();
1✔
344
        for (Iterator i = _registry.iterator(); i.hasNext();) {
1✔
345
            HTMLElement element = (HTMLElement) i.next();
1✔
346
            String aValue = element.getAttribute(name);
1✔
347
            if (value.equals(aValue)) {
1✔
348
                // System.err.println(element.getTagName()+"("+name+")="+aValue);
349
                elements.add(element);
1✔
350
            }
351
        }
1✔
352
        return (HTMLElement[]) elements.toArray(new HTMLElement[elements.size()]);
1✔
353
    }
354

355
    /**
356
     * Returns a list of HTML element names contained in this HTML section.
357
     *
358
     * @return the element names
359
     */
360
    public String[] getElementNames() {
361
        loadElements();
1✔
362
        return (String[]) _elementsByName.keySet().toArray(new String[_elementsByName.size()]);
1✔
363
    }
364

365
    /**
366
     * get the elements with the given tagname.
367
     *
368
     * @param dom
369
     *            - the node to start from
370
     * @param name
371
     *            - the name of the attribute to look for
372
     *
373
     * @return the elements by tag name
374
     */
375
    HTMLElement[] getElementsByTagName(Node dom, String name) {
376
        loadElements();
1✔
377
        if (dom instanceof Element) {
1✔
378
            return getElementsFromList(((Element) dom).getElementsByTagName(name));
1✔
379
        }
380
        return getElementsFromList(((Document) dom).getElementsByTagName(name));
1✔
381
    }
382

383
    /**
384
     * Gets the elements from list.
385
     *
386
     * @param nl
387
     *            the nl
388
     *
389
     * @return the elements from list
390
     */
391
    private HTMLElement[] getElementsFromList(NodeList nl) {
392
        HTMLElement[] elements = new HTMLElement[nl.getLength()];
1✔
393
        for (int i = 0; i < elements.length; i++) {
1✔
394
            Node node = nl.item(i);
1✔
395
            elements[i] = (HTMLElement) _registry.getRegisteredElement(node);
1✔
396
            if (elements[i] == null) {
1✔
397
                elements[i] = toDefaultElement((Element) node);
1✔
398
                _registry.registerElement(node, elements[i]);
1✔
399
            }
400
        }
401
        return elements;
1✔
402
    }
403

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

416
    /**
417
     * Returns the link found in the page with the specified ID.
418
     *
419
     * @param id
420
     *            the id
421
     *
422
     * @return the link with ID
423
     */
424
    public WebLink getLinkWithID(String id) {
425
        return (WebLink) getElementWithID(id, WebLink.class);
1✔
426

427
    }
428

429
    /**
430
     * Gets the element with ID.
431
     *
432
     * @param id
433
     *            the id
434
     * @param klass
435
     *            the klass
436
     *
437
     * @return the element with ID
438
     */
439
    private Object getElementWithID(String id, final Class klass) {
440
        loadElements();
1✔
441
        return whenCast(_elementsByID.get(id), klass);
1✔
442
    }
443

444
    /**
445
     * When cast.
446
     *
447
     * @param o
448
     *            the o
449
     * @param klass
450
     *            the klass
451
     *
452
     * @return the object
453
     */
454
    private Object whenCast(Object o, Class klass) {
455
        return klass.isInstance(o) ? o : null;
1✔
456
    }
457

458
    /**
459
     * Returns the first link found in the page matching the specified criteria.
460
     *
461
     * @param predicate
462
     *            the predicate
463
     * @param criteria
464
     *            the criteria
465
     *
466
     * @return the first matching form
467
     */
468
    public WebForm getFirstMatchingForm(HTMLElementPredicate predicate, Object criteria) {
469
        WebForm[] forms = getForms();
1✔
470
        for (WebForm form : forms) {
1✔
471
            if (predicate.matchesCriteria(form, criteria)) {
1✔
472
                return form;
1✔
473
            }
474
        }
475
        return null;
1✔
476
    }
477

478
    /**
479
     * Returns all links found in the page matching the specified criteria.
480
     *
481
     * @param predicate
482
     *            the predicate
483
     * @param criteria
484
     *            the criteria
485
     *
486
     * @return the matching forms
487
     */
488
    public WebForm[] getMatchingForms(HTMLElementPredicate predicate, Object criteria) {
489
        ArrayList matches = new ArrayList<>();
×
490
        WebForm[] forms = getForms();
×
491
        for (WebForm form : forms) {
×
492
            if (predicate.matchesCriteria(form, criteria)) {
×
493
                matches.add(form);
×
494
            }
495
        }
496
        return (WebForm[]) matches.toArray(new WebForm[matches.size()]);
×
497
    }
498

499
    /**
500
     * Returns the form found in the page with the specified name.
501
     *
502
     * @param name
503
     *            the name
504
     *
505
     * @return the form with name
506
     */
507
    public WebForm getFormWithName(String name) {
508
        return getFirstMatchingForm(WebForm.MATCH_NAME, name);
1✔
509
    }
510

511
    /**
512
     * interpret the given script element.
513
     *
514
     * @param element
515
     *            the element
516
     */
517
    void interpretScriptElement(Element element) {
518
        if (!HttpUnitOptions.isScriptingEnabled()) {
1✔
519
            _enableNoScriptNodes = true;
1✔
520
            return;
1✔
521
        }
522

523
        String script = getScript(element);
1✔
524
        if (script != null) {
1!
525
            try {
526
                _updateElements = false;
1✔
527
                String language = NodeUtils.getNodeAttribute(element, "language", null);
1✔
528
                if (!getResponse().getScriptingHandler().supportsScriptLanguage(language)) {
1!
529
                    _enableNoScriptNodes = true;
1✔
530
                }
531
                getResponse().getScriptingHandler().runScript(language, script);
1✔
532
            } finally {
533
                clearCaches();
1✔
534
            }
535
        }
536
    }
1✔
537

538
    /**
539
     * get the script for the given node.
540
     *
541
     * @param scriptNode
542
     *            the script node
543
     *
544
     * @return the script
545
     */
546
    private String getScript(Node scriptNode) {
547
        String scriptLocation = NodeUtils.getNodeAttribute(scriptNode, "src", null);
1✔
548
        if (scriptLocation == null) {
1!
549
            return NodeUtils.asText(scriptNode.getChildNodes());
1✔
550
        }
551
        try {
552
            return getIncludedScript(scriptLocation);
×
553
        } catch (Exception e) {
×
554
            throw new RuntimeException("Error loading included script: " + e);
×
555
        }
556
    }
557

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

593
    /**
594
     * If noscript node content is enabled, returns null - otherwise returns a concealing element.
595
     *
596
     * @param element
597
     *            the element
598
     *
599
     * @return the HTML element
600
     */
601
    private HTMLElement toNoscriptElement(Element element) {
602
        HTMLElement result = null;
1✔
603
        if (!_enableNoScriptNodes) {
1✔
604
            result = new NoScriptElement(element);
1✔
605
        }
606
        return result;
1✔
607
    }
608

609
    /**
610
     * The Class HtmlElementRecorder.
611
     */
612
    static class HtmlElementRecorder {
1✔
613

614
        /**
615
         * Record html element.
616
         *
617
         * @param pot
618
         *            the pot
619
         * @param node
620
         *            the node
621
         * @param htmlElement
622
         *            the html element
623
         */
624
        protected void recordHtmlElement(NodeUtils.PreOrderTraversal pot, Node node, HTMLElement htmlElement) {
625
            if (htmlElement != null) {
1✔
626
                addToMaps(pot, node, htmlElement);
1✔
627
                addToLists(pot, htmlElement);
1✔
628
            }
629
        }
1✔
630

631
        /**
632
         * Adds the to lists.
633
         *
634
         * @param pot
635
         *            the pot
636
         * @param htmlElement
637
         *            the html element
638
         */
639
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
640
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1✔
641
                Object o = i.next();
1✔
642
                if (o instanceof ParsedHTML) {
1✔
643
                    ((ParsedHTML) o).addToList(htmlElement);
1✔
644
                }
645
            }
1✔
646
        }
1✔
647

648
        /**
649
         * Adds the to maps.
650
         *
651
         * @param pot
652
         *            the pot
653
         * @param node
654
         *            the node
655
         * @param htmlElement
656
         *            the html element
657
         */
658
        protected void addToMaps(NodeUtils.PreOrderTraversal pot, Node node, HTMLElement htmlElement) {
659
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1✔
660
                Object o = i.next();
1✔
661
                if (o instanceof ParsedHTML) {
1✔
662
                    ((ParsedHTML) o).addToMaps(node, htmlElement);
1✔
663
                }
664
            }
1✔
665
        }
1✔
666

667
    }
668

669
    /**
670
     * A factory for creating HTMLElement objects.
671
     */
672
    abstract static class HTMLElementFactory extends HtmlElementRecorder {
1✔
673

674
        /**
675
         * To HTML element.
676
         *
677
         * @param pot
678
         *            the pot
679
         * @param parsedHTML
680
         *            the parsed HTML
681
         * @param element
682
         *            the element
683
         *
684
         * @return the HTML element
685
         */
686
        abstract HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element);
687

688
        /**
689
         * Record element.
690
         *
691
         * @param pot
692
         *            the pot
693
         * @param element
694
         *            the element
695
         * @param parsedHTML
696
         *            the parsed HTML
697
         */
698
        void recordElement(NodeUtils.PreOrderTraversal pot, Element element, ParsedHTML parsedHTML) {
699
            HTMLElement htmlElement = toHTMLElement(pot, parsedHTML, element);
1✔
700
            recordHtmlElement(pot, element, htmlElement);
1✔
701
        }
1✔
702

703
        /**
704
         * Checks if is recognized.
705
         *
706
         * @param properties
707
         *            the properties
708
         *
709
         * @return true, if is recognized
710
         */
711
        protected boolean isRecognized(ClientProperties properties) {
712
            return true;
1✔
713
        }
714

715
        /**
716
         * Adds the to context.
717
         *
718
         * @return true, if successful
719
         */
720
        protected boolean addToContext() {
721
            return false;
1✔
722
        }
723

724
        /**
725
         * Gets the parsed HTML.
726
         *
727
         * @param pot
728
         *            the pot
729
         *
730
         * @return the parsed HTML
731
         */
732
        protected final ParsedHTML getParsedHTML(NodeUtils.PreOrderTraversal pot) {
733
            return (ParsedHTML) getClosestContext(pot, ParsedHTML.class);
×
734
        }
735

736
        /**
737
         * Gets the closest context.
738
         *
739
         * @param pot
740
         *            the pot
741
         * @param aClass
742
         *            the a class
743
         *
744
         * @return the closest context
745
         */
746
        protected final Object getClosestContext(NodeUtils.PreOrderTraversal pot, Class aClass) {
747
            return pot.getClosestContext(aClass);
1✔
748
        }
749

750
        /**
751
         * Gets the root context.
752
         *
753
         * @param pot
754
         *            the pot
755
         *
756
         * @return the root context
757
         */
758
        protected ParsedHTML getRootContext(NodeUtils.PreOrderTraversal pot) {
759
            return (ParsedHTML) pot.getRootContext();
×
760
        }
761
    }
762

763
    /**
764
     * A factory for creating DefaultElement objects.
765
     */
766
    static class DefaultElementFactory extends HTMLElementFactory {
1✔
767

768
        @Override
769
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
770
            // [ 1531005 ] getElementsWithAttribute **FIX**
771
            // if (element.getAttribute( "id" ).equals( "" )) {
772
            // return null;
773
            // }
774
            return parsedHTML.toDefaultElement(element);
1✔
775
        }
776

777
        @Override
778
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
779
        }
1✔
780
    }
781

782
    /**
783
     * To default element.
784
     *
785
     * @param element
786
     *            the element
787
     *
788
     * @return the HTML element
789
     */
790
    private HTMLElement toDefaultElement(Element element) {
791
        return new HTMLElementBase(element) {
1✔
792
            @Override
793
            public ScriptableDelegate newScriptable() {
794
                return new HTMLElementScriptable(this);
1✔
795
            }
796

797
            @Override
798
            public ScriptableDelegate getParentDelegate() {
799
                return getResponse().getDocumentScriptable();
1✔
800
            }
801
        };
802
    }
803

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

814
    /**
815
     * A factory for creating WebLink objects.
816
     */
817
    static class WebLinkFactory extends HTMLElementFactory {
1✔
818
        @Override
819
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
820
            return parsedHTML.toLinkAnchor(element);
1✔
821
        }
822
    }
823

824
    /**
825
     * A factory for creating TextBlock objects.
826
     */
827
    static class TextBlockFactory extends HTMLElementFactory {
1✔
828
        @Override
829
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
830
            return parsedHTML.toTextBlock(element);
1✔
831
        }
832

833
        @Override
834
        protected boolean addToContext() {
835
            return true;
1✔
836
        }
837

838
        @Override
839
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
840
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1!
841
                Object o = i.next();
1✔
842
                if (!(o instanceof ParsedHTML)) {
1!
843
                    continue;
×
844
                }
845
                ((ParsedHTML) o).addToList(htmlElement);
1✔
846
                break;
1✔
847
            }
848
        }
1✔
849

850
    }
851

852
    /**
853
     * A factory for creating Script objects.
854
     */
855
    static class ScriptFactory extends HTMLElementFactory {
1✔
856

857
        @Override
858
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
859
            return null;
×
860
        }
861

862
        @Override
863
        void recordElement(NodeUtils.PreOrderTraversal pot, Element element, ParsedHTML parsedHTML) {
864
            parsedHTML.interpretScriptElement(element);
1✔
865
        }
1✔
866
    }
867

868
    /**
869
     * A factory for creating NoScript objects.
870
     */
871
    static class NoScriptFactory extends HTMLElementFactory {
1✔
872

873
        @Override
874
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
875
            return parsedHTML.toNoscriptElement(element);
1✔
876
        }
877

878
        @Override
879
        protected boolean addToContext() {
880
            return true;
1✔
881
        }
882
    }
883

884
    /**
885
     * A factory for creating WebFrame objects.
886
     */
887
    static class WebFrameFactory extends HTMLElementFactory {
1✔
888
        @Override
889
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
890
            return parsedHTML.toWebFrame(element);
1✔
891
        }
892
    }
893

894
    /**
895
     * A factory for creating WebIFrame objects.
896
     */
897
    static class WebIFrameFactory extends HTMLElementFactory {
1✔
898
        @Override
899
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
900
            return parsedHTML.toWebIFrame(element);
1✔
901
        }
902

903
        @Override
904
        protected boolean isRecognized(ClientProperties properties) {
905
            return properties.isIframeSupported();
1✔
906
        }
907

908
        @Override
909
        protected boolean addToContext() {
910
            return true;
1✔
911
        }
912
    }
913

914
    /**
915
     * A factory for creating WebImage objects.
916
     */
917
    static class WebImageFactory extends HTMLElementFactory {
1✔
918
        @Override
919
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
920
            return parsedHTML.toWebImage(element);
1✔
921
        }
922
    }
923

924
    /**
925
     * A factory for creating WebApplet objects.
926
     */
927
    static class WebAppletFactory extends HTMLElementFactory {
1✔
928
        @Override
929
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
930
            return parsedHTML.toWebApplet(element);
1✔
931
        }
932

933
        @Override
934
        protected boolean addToContext() {
935
            return true;
1✔
936
        }
937
    }
938

939
    /**
940
     * A factory for creating WebTable objects.
941
     */
942
    static class WebTableFactory extends HTMLElementFactory {
1✔
943
        @Override
944
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
945
            return parsedHTML.toWebTable(element);
1✔
946
        }
947

948
        @Override
949
        protected boolean addToContext() {
950
            return true;
1✔
951
        }
952

953
        @Override
954
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
955
            for (Iterator i = pot.getContexts(); i.hasNext();) {
1✔
956
                Object o = i.next();
1✔
957
                if (o instanceof ParsedHTML) {
1!
958
                    ((ParsedHTML) o).addToList(htmlElement);
1✔
959
                }
960
                if (o instanceof TableCell) {
1✔
961
                    break;
1✔
962
                }
963
            }
1✔
964
        }
1✔
965
    }
966

967
    /**
968
     * A factory for creating TableRow objects.
969
     */
970
    static class TableRowFactory extends HTMLElementFactory {
1✔
971
        @Override
972
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
973
            WebTable wt = getWebTable(pot);
1✔
974
            if (wt == null) {
1!
975
                return null;
×
976
            }
977
            return wt.newTableRow((HTMLTableRowElement) element);
1✔
978
        }
979

980
        /**
981
         * Gets the web table.
982
         *
983
         * @param pot
984
         *            the pot
985
         *
986
         * @return the web table
987
         */
988
        private WebTable getWebTable(NodeUtils.PreOrderTraversal pot) {
989
            return (WebTable) getClosestContext(pot, WebTable.class);
1✔
990
        }
991

992
        @Override
993
        protected boolean addToContext() {
994
            return true;
1✔
995
        }
996

997
        @Override
998
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
999
            getWebTable(pot).addRow((TableRow) htmlElement);
1✔
1000
        }
1✔
1001
    }
1002

1003
    /**
1004
     * A factory for creating TableCell objects.
1005
     */
1006
    static class TableCellFactory extends HTMLElementFactory {
1✔
1007
        @Override
1008
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1009
            TableRow tr = getTableRow(pot);
1✔
1010
            if (tr == null) {
1!
1011
                return null;
×
1012
            }
1013
            return tr.newTableCell((HTMLTableCellElement) element);
1✔
1014
        }
1015

1016
        /**
1017
         * Gets the table row.
1018
         *
1019
         * @param pot
1020
         *            the pot
1021
         *
1022
         * @return the table row
1023
         */
1024
        private TableRow getTableRow(NodeUtils.PreOrderTraversal pot) {
1025
            return (TableRow) getClosestContext(pot, TableRow.class);
1✔
1026
        }
1027

1028
        @Override
1029
        protected boolean addToContext() {
1030
            return true;
1✔
1031
        }
1032

1033
        @Override
1034
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
1035
            getTableRow(pot).addTableCell((TableCell) htmlElement);
1✔
1036
        }
1✔
1037
    }
1038

1039
    /**
1040
     * A factory for creating FormControl objects.
1041
     */
1042
    static class FormControlFactory extends HTMLElementFactory {
1✔
1043

1044
        @Override
1045
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1046
            HTMLFormElement form = ((HTMLControl) element).getForm();
1✔
1047
            if (form == null) {
1✔
1048
                return newControlWithoutForm(parsedHTML, element);
1✔
1049
            }
1050
            return parsedHTML.getWebForm(form).newFormControl(element);
1✔
1051
        }
1052

1053
        /**
1054
         * New control without form.
1055
         *
1056
         * @param parsedHTML
1057
         *            the parsed HTML
1058
         * @param element
1059
         *            the element
1060
         *
1061
         * @return the HTML element
1062
         */
1063
        private HTMLElement newControlWithoutForm(ParsedHTML parsedHTML, Element element) {
1064
            if ((element.getNodeName().equalsIgnoreCase("button") || element.getNodeName().equalsIgnoreCase("input"))
1!
1065
                    && isValidNonFormButtonType(NodeUtils.getNodeAttribute(element, "type"))) {
1!
1066
                return parsedHTML.toButtonWithoutForm(element);
1✔
1067
            }
1068
            return null;
×
1069
        }
1070

1071
        /**
1072
         * Checks if is valid non form button type.
1073
         *
1074
         * @param buttonType
1075
         *            the button type
1076
         *
1077
         * @return true, if is valid non form button type
1078
         */
1079
        private boolean isValidNonFormButtonType(String buttonType) {
1080
            return buttonType.equals("") || buttonType.equalsIgnoreCase("button");
1!
1081
        }
1082
    }
1083

1084
    /**
1085
     * A factory for creating WebList objects.
1086
     */
1087
    static class WebListFactory extends HTMLElementFactory {
1✔
1088
        @Override
1089
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1090
            return parsedHTML.toOrderedList(element);
×
1091
        }
1092

1093
        @Override
1094
        protected boolean addToContext() {
1095
            return true;
×
1096
        }
1097

1098
        @Override
1099
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
1100
            TextBlock textBlock = getTextBlock(pot);
×
1101
            if (textBlock != null) {
×
1102
                textBlock.addList((WebList) htmlElement);
×
1103
            }
1104
        }
×
1105

1106
        /**
1107
         * Gets the text block.
1108
         *
1109
         * @param pot
1110
         *            the pot
1111
         *
1112
         * @return the text block
1113
         */
1114
        private TextBlock getTextBlock(NodeUtils.PreOrderTraversal pot) {
1115
            return (TextBlock) getClosestContext(pot, TextBlock.class);
×
1116
        }
1117
    }
1118

1119
    /**
1120
     * A factory for creating ListItem objects.
1121
     */
1122
    static class ListItemFactory extends HTMLElementFactory {
1✔
1123
        @Override
1124
        HTMLElement toHTMLElement(NodeUtils.PreOrderTraversal pot, ParsedHTML parsedHTML, Element element) {
1125
            WebList webList = getWebList(pot);
×
1126
            if (webList == null) {
×
1127
                return null;
×
1128
            }
1129
            return webList.addNewItem(element);
×
1130
        }
1131

1132
        /**
1133
         * Gets the web list.
1134
         *
1135
         * @param pot
1136
         *            the pot
1137
         *
1138
         * @return the web list
1139
         */
1140
        private WebList getWebList(NodeUtils.PreOrderTraversal pot) {
1141
            return (WebList) getClosestContext(pot, WebList.class);
×
1142
        }
1143

1144
        @Override
1145
        protected boolean addToContext() {
1146
            return true;
×
1147
        }
1148

1149
        @Override
1150
        protected void addToLists(NodeUtils.PreOrderTraversal pot, HTMLElement htmlElement) {
1151
        }
×
1152
    }
1153

1154
    /** The html factory classes. */
1155
    private static HashMap _htmlFactoryClasses = new HashMap<>();
1✔
1156

1157
    /** The default factory. */
1158
    private static HTMLElementFactory _defaultFactory = new DefaultElementFactory();
1✔
1159

1160
    static {
1161
        _htmlFactoryClasses.put("a", new WebLinkFactory());
1✔
1162
        _htmlFactoryClasses.put("area", new WebLinkFactory());
1✔
1163
        _htmlFactoryClasses.put("form", new WebFormFactory());
1✔
1164
        _htmlFactoryClasses.put("img", new WebImageFactory());
1✔
1165
        _htmlFactoryClasses.put("applet", new WebAppletFactory());
1✔
1166
        _htmlFactoryClasses.put("table", new WebTableFactory());
1✔
1167
        _htmlFactoryClasses.put("tr", new TableRowFactory());
1✔
1168
        _htmlFactoryClasses.put("td", new TableCellFactory());
1✔
1169
        _htmlFactoryClasses.put("th", new TableCellFactory());
1✔
1170
        _htmlFactoryClasses.put("frame", new WebFrameFactory());
1✔
1171
        _htmlFactoryClasses.put("iframe", new WebIFrameFactory());
1✔
1172
        _htmlFactoryClasses.put("script", new ScriptFactory());
1✔
1173
        _htmlFactoryClasses.put("noscript", new NoScriptFactory());
1✔
1174
        _htmlFactoryClasses.put("ol", new WebListFactory());
1✔
1175
        _htmlFactoryClasses.put("ul", new WebListFactory());
1✔
1176
        _htmlFactoryClasses.put("li", new ListItemFactory());
1✔
1177

1178
        for (String element : TEXT_ELEMENTS) {
1✔
1179
            _htmlFactoryClasses.put(element, new TextBlockFactory());
1✔
1180
        }
1181

1182
        for (Iterator i = Arrays.asList(FormControl.getControlElementTags()).iterator(); i.hasNext();) {
1✔
1183
            _htmlFactoryClasses.put(i.next(), new FormControlFactory());
1✔
1184
        }
1185
    }
1✔
1186

1187
    /**
1188
     * Gets the HTML element factory.
1189
     *
1190
     * @param tagName
1191
     *            the tag name
1192
     *
1193
     * @return the HTML element factory
1194
     */
1195
    private static HTMLElementFactory getHTMLElementFactory(String tagName) {
1196
        final HTMLElementFactory factory = (HTMLElementFactory) _htmlFactoryClasses.get(tagName);
1✔
1197
        return factory != null ? factory : _defaultFactory;
1✔
1198
    }
1199

1200
    /**
1201
     * Load elements.
1202
     */
1203
    private void loadElements() {
1204
        if (!_updateElements) {
1✔
1205
            return;
1✔
1206
        }
1207

1208
        NodeUtils.NodeAction action = new NodeUtils.NodeAction() {
1✔
1209
            @Override
1210
            public boolean processElement(NodeUtils.PreOrderTraversal pot, Element element) {
1211
                HTMLElementFactory factory = getHTMLElementFactory(element.getNodeName().toLowerCase(Locale.ENGLISH));
1✔
1212
                if (factory == null || !factory.isRecognized(getClientProperties())
1!
1213
                        || pot.getClosestContext(ContentConcealer.class) != null) {
1✔
1214
                    return true;
1✔
1215
                }
1216

1217
                if (!_registry.hasNode(element)) {
1✔
1218
                    factory.recordElement(pot, element, ParsedHTML.this);
1✔
1219
                }
1220
                if (factory.addToContext()) {
1✔
1221
                    pot.pushContext(_registry.getRegisteredElement(element));
1✔
1222
                }
1223

1224
                return true;
1✔
1225
            }
1226

1227
            @Override
1228
            public void processTextNode(NodeUtils.PreOrderTraversal pot, Node textNode) {
1229
                if (textNode.getNodeValue().trim().isEmpty()) {
1✔
1230
                    return;
1✔
1231
                }
1232

1233
                Node parent = textNode.getParentNode();
1✔
1234
                if (!parent.getNodeName().equalsIgnoreCase("body")
1✔
1235
                        || pot.getClosestContext(ContentConcealer.class) != null) {
1!
1236
                    return;
1✔
1237
                }
1238
                new HtmlElementRecorder().recordHtmlElement(pot, textNode, newTextBlock(textNode));
1✔
1239
            }
1✔
1240
        };
1241
        NodeUtils.PreOrderTraversal nt = new NodeUtils.PreOrderTraversal(getRootNode());
1✔
1242
        nt.pushBaseContext(this);
1✔
1243
        nt.perform(action);
1✔
1244

1245
        _updateElements = false;
1✔
1246
    }
1✔
1247

1248
    /**
1249
     * Gets the client properties.
1250
     *
1251
     * @return the client properties
1252
     */
1253
    private ClientProperties getClientProperties() {
1254
        WebWindow window = _response.getWindow();
1✔
1255
        return window == null ? ClientProperties.getDefaultProperties() : window.getClient().getClientProperties();
1✔
1256
    }
1257

1258
    /**
1259
     * To button without form.
1260
     *
1261
     * @param element
1262
     *            the element
1263
     *
1264
     * @return the button
1265
     */
1266
    private Button toButtonWithoutForm(Element element) {
1267
        return new Button(_response, (HTMLControl) element);
1✔
1268
    }
1269

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

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

1294
    /**
1295
     * To web I frame.
1296
     *
1297
     * @param element
1298
     *            the element
1299
     *
1300
     * @return the web frame
1301
     */
1302
    private WebFrame toWebIFrame(Element element) {
1303
        return new WebIFrame(_baseURL, element, _frame);
1✔
1304
    }
1305

1306
    /**
1307
     * convert the given child to a link anchor.
1308
     *
1309
     * @param child
1310
     *            the child
1311
     *
1312
     * @return the web link
1313
     */
1314
    private WebLink toLinkAnchor(Element child) {
1315
        return !isWebLink(child) ? null : new WebLink(_response, _baseURL, child, _frame, _baseTarget, _characterSet);
1✔
1316
    }
1317

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

1343
    /**
1344
     * To web image.
1345
     *
1346
     * @param child
1347
     *            the child
1348
     *
1349
     * @return the web image
1350
     */
1351
    private WebImage toWebImage(Element child) {
1352
        return new WebImage(_response, this, _baseURL, (HTMLImageElement) child, _frame, _baseTarget, _characterSet);
1✔
1353
    }
1354

1355
    /**
1356
     * To web applet.
1357
     *
1358
     * @param element
1359
     *            the element
1360
     *
1361
     * @return the web applet
1362
     */
1363
    private WebApplet toWebApplet(Element element) {
1364
        return new WebApplet(_response, (HTMLAppletElement) element, _baseTarget);
1✔
1365
    }
1366

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

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

1391
    /**
1392
     * New text block.
1393
     *
1394
     * @param textNode
1395
     *            the text node
1396
     *
1397
     * @return the text block
1398
     */
1399
    private TextBlock newTextBlock(Node textNode) {
1400
        return new TextBlock(_response, _frame, _baseURL, _baseTarget, textNode, _characterSet);
1✔
1401
    }
1402

1403
    /**
1404
     * To ordered list.
1405
     *
1406
     * @param element
1407
     *            the element
1408
     *
1409
     * @return the web list
1410
     */
1411
    private WebList toOrderedList(Element element) {
1412
        return new WebList(_response, _frame, _baseURL, _baseTarget, element, _characterSet);
×
1413
    }
1414

1415
    /**
1416
     * Adds the to maps.
1417
     *
1418
     * @param node
1419
     *            the node
1420
     * @param htmlElement
1421
     *            the html element
1422
     */
1423
    private void addToMaps(Node node, HTMLElement htmlElement) {
1424
        _registry.registerElement(node, htmlElement);
1✔
1425
        if (htmlElement.getID() != null) {
1!
1426
            _elementsByID.put(htmlElement.getID(), htmlElement);
1✔
1427
        }
1428
        if (htmlElement.getName() != null) {
1✔
1429
            addNamedElement(htmlElement.getName(), htmlElement);
1✔
1430
        }
1431
        if (htmlElement.getClassName() != null) {
1!
1432
            StringTokenizer tokenizer = new StringTokenizer(htmlElement.getClassName());
1✔
1433
            String token;
1434

1435
            while (tokenizer.hasMoreElements()) {
1✔
1436
                token = tokenizer.nextToken();
1✔
1437

1438
                if (_elementsByClass.containsKey(token)) {
1✔
1439
                    ((ArrayList) _elementsByClass.get(token)).add(htmlElement);
1✔
1440
                } else {
1441
                    ArrayList arrayList = new ArrayList<>();
1✔
1442
                    arrayList.add(htmlElement);
1✔
1443
                    _elementsByClass.put(token, arrayList);
1✔
1444
                }
1✔
1445
            }
1446
        }
1447
    }
1✔
1448

1449
    /**
1450
     * Adds the named element.
1451
     *
1452
     * @param name
1453
     *            the name
1454
     * @param htmlElement
1455
     *            the html element
1456
     */
1457
    private void addNamedElement(String name, HTMLElement htmlElement) {
1458
        List list = (List) _elementsByName.get(name);
1✔
1459
        if (list == null) {
1✔
1460
            _elementsByName.put(name, list = new ArrayList<>());
1✔
1461
        }
1462
        list.add(htmlElement);
1✔
1463
    }
1✔
1464

1465
    /**
1466
     * Adds the to list.
1467
     *
1468
     * @param htmlElement
1469
     *            the html element
1470
     */
1471
    private void addToList(HTMLElement htmlElement) {
1472
        List list = getListForElement(htmlElement);
1✔
1473
        if (list != null) {
1✔
1474
            list.add(htmlElement);
1✔
1475
        }
1476
    }
1✔
1477

1478
    /**
1479
     * Gets the list for element.
1480
     *
1481
     * @param element
1482
     *            the element
1483
     *
1484
     * @return the list for element
1485
     */
1486
    private List getListForElement(HTMLElement element) {
1487
        if (element instanceof WebTable) {
1✔
1488
            return _tableList;
1✔
1489
        }
1490
        if (element instanceof WebFrame) {
1✔
1491
            return _frameList;
1✔
1492
        }
1493
        if (element instanceof BlockElement) {
1✔
1494
            return _blocksList;
1✔
1495
        }
1496
        return null;
1✔
1497
    }
1498

1499
    /**
1500
     * Returns the first link which contains the specified text.
1501
     *
1502
     * @param text
1503
     *            the text
1504
     *
1505
     * @return the link with
1506
     */
1507
    public WebLink getLinkWith(String text) {
1508
        return getFirstMatchingLink(WebLink.MATCH_CONTAINED_TEXT, text);
1✔
1509
    }
1510

1511
    /**
1512
     * Returns the link which contains the first image with the specified text as its 'alt' attribute.
1513
     *
1514
     * @param text
1515
     *            the text
1516
     *
1517
     * @return the link with image text
1518
     */
1519
    public WebLink getLinkWithImageText(String text) {
1520
        WebImage image = getImageWithAltText(text);
1✔
1521
        return image == null ? null : image.getLink();
1!
1522
    }
1523

1524
    /**
1525
     * Returns the link found in the page with the specified name.
1526
     *
1527
     * @param name
1528
     *            the name
1529
     *
1530
     * @return the link with name
1531
     */
1532
    public WebLink getLinkWithName(String name) {
1533
        return getFirstMatchingLink(WebLink.MATCH_NAME, name);
1✔
1534
    }
1535

1536
    /**
1537
     * Returns the first link found in the page matching the specified criteria.
1538
     *
1539
     * @param predicate
1540
     *            the predicate
1541
     * @param criteria
1542
     *            the criteria
1543
     *
1544
     * @return the first matching link
1545
     */
1546
    public WebLink getFirstMatchingLink(HTMLElementPredicate predicate, Object criteria) {
1547
        WebLink[] links = getLinks();
1✔
1548
        for (WebLink link : links) {
1✔
1549
            if (predicate.matchesCriteria(link, criteria)) {
1✔
1550
                return link;
1✔
1551
            }
1552
        }
1553
        return null;
1✔
1554
    }
1555

1556
    /**
1557
     * Returns all links found in the page matching the specified criteria.
1558
     *
1559
     * @param predicate
1560
     *            the predicate
1561
     * @param criteria
1562
     *            the criteria
1563
     *
1564
     * @return the matching links
1565
     */
1566
    public WebLink[] getMatchingLinks(HTMLElementPredicate predicate, Object criteria) {
1567
        ArrayList matches = new ArrayList<>();
1✔
1568
        WebLink[] links = getLinks();
1✔
1569
        for (WebLink link : links) {
1✔
1570
            if (predicate.matchesCriteria(link, criteria)) {
1✔
1571
                matches.add(link);
1✔
1572
            }
1573
        }
1574
        return (WebLink[]) matches.toArray(new WebLink[matches.size()]);
1✔
1575
    }
1576

1577
    /**
1578
     * Returns the image found in the page with the specified name.
1579
     *
1580
     * @param name
1581
     *            the name
1582
     *
1583
     * @return the image with name
1584
     */
1585
    public WebImage getImageWithName(String name) {
1586
        WebImage[] images = getImages();
1✔
1587
        for (WebImage image : images) {
1!
1588
            if (HttpUnitUtils.matches(name, image.getName())) {
1!
1589
                return image;
1✔
1590
            }
1591
        }
1592
        return null;
×
1593
    }
1594

1595
    /**
1596
     * Returns the first image found in the page with the specified src attribute.
1597
     *
1598
     * @param source
1599
     *            the source
1600
     *
1601
     * @return the image with source
1602
     */
1603
    public WebImage getImageWithSource(String source) {
1604
        WebImage[] images = getImages();
1✔
1605
        for (WebImage image : images) {
1✔
1606
            if (HttpUnitUtils.matches(source, image.getSource())) {
1✔
1607
                return image;
1✔
1608
            }
1609
        }
1610
        return null;
1✔
1611
    }
1612

1613
    /**
1614
     * Returns the first image found in the page with the specified alt attribute.
1615
     *
1616
     * @param altText
1617
     *            the alt text
1618
     *
1619
     * @return the image with alt text
1620
     */
1621
    public WebImage getImageWithAltText(String altText) {
1622
        WebImage[] images = getImages();
1✔
1623
        for (WebImage image : images) {
1!
1624
            if (HttpUnitUtils.matches(altText, image.getAltText())) {
1✔
1625
                return image;
1✔
1626
            }
1627
        }
1628
        return null;
×
1629
    }
1630

1631
    /**
1632
     * Returns the first table in the response which matches the specified predicate and value. Will recurse into any
1633
     * nested tables, as needed.
1634
     *
1635
     * @param predicate
1636
     *            the predicate
1637
     * @param criteria
1638
     *            the criteria
1639
     *
1640
     * @return the selected table, or null if none is found
1641
     */
1642
    public WebTable getFirstMatchingTable(HTMLElementPredicate predicate, Object criteria) {
1643
        return getTableSatisfyingPredicate(getTables(), predicate, criteria);
1✔
1644
    }
1645

1646
    /**
1647
     * Returns the tables in the response which match the specified predicate and value. Will recurse into any nested
1648
     * tables, as needed.
1649
     *
1650
     * @param predicate
1651
     *            the predicate
1652
     * @param criteria
1653
     *            the criteria
1654
     *
1655
     * @return the selected tables, or null if none are found
1656
     */
1657
    public WebTable[] getMatchingTables(HTMLElementPredicate predicate, Object criteria) {
1658
        return getTablesSatisfyingPredicate(getTables(), predicate, criteria);
×
1659
    }
1660

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

1674
    /**
1675
     * Returns the first table in the response which has the specified text as a prefix of the text in its first
1676
     * non-blank row and non-blank column. Will recurse into any nested tables, as needed.
1677
     *
1678
     * @param text
1679
     *            the text
1680
     *
1681
     * @return the selected table, or null if none is found
1682
     */
1683
    public WebTable getTableStartingWithPrefix(String text) {
1684
        return getFirstMatchingTable(WebTable.MATCH_FIRST_NONBLANK_CELL_PREFIX, text);
1✔
1685
    }
1686

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

1700
    /**
1701
     * Returns the first table in the response which has the specified text as its ID attribute. Will recurse into any
1702
     * nested tables, as needed.
1703
     *
1704
     * @param ID
1705
     *            the id
1706
     *
1707
     * @return the selected table, or null if none is found
1708
     */
1709
    public WebTable getTableWithID(String ID) {
1710
        return getFirstMatchingTable(WebTable.MATCH_ID, ID);
1✔
1711
    }
1712

1713
    /**
1714
     * Returns a copy of the domain object model associated with this page.
1715
     *
1716
     * @return the dom
1717
     */
1718
    public Node getDOM() {
1719
        return getRootNode().cloneNode( /* deep */ true);
1✔
1720
    }
1721

1722
    // ---------------------------------- Object methods --------------------------------
1723

1724
    @Override
1725
    public String toString() {
1726
        return _baseURL.toExternalForm() + System.lineSeparator() + _rootNode;
×
1727
    }
1728

1729
    // ---------------------------------- package members --------------------------------
1730

1731
    /**
1732
     * Specifies the root node for this HTML fragment. It will be either an HTMLDocument or an HTMLElement representing
1733
     * a page fragment.
1734
     *
1735
     * @param rootNode
1736
     *            the new root node
1737
     */
1738
    void setRootNode(Node rootNode) {
1739
        if (_rootNode != null && rootNode != _rootNode) {
1!
1740
            throw new IllegalStateException("The root node has already been defined as " + _rootNode
×
1741
                    + " and cannot be redefined as " + rootNode);
1742
        }
1743
        _rootNode = rootNode;
1✔
1744
        if (rootNode instanceof HTMLDocumentImpl) {
1!
1745
            ((HTMLDocumentImpl) rootNode).setIFramesEnabled(getClientProperties().isIframeSupported());
1✔
1746
        }
1747
        clearCaches();
1✔
1748
    }
1✔
1749

1750
    /**
1751
     * Clear caches.
1752
     */
1753
    private void clearCaches() {
1754
        _tables = null;
1✔
1755
        _frames = null;
1✔
1756
        _blocks = null;
1✔
1757
        _updateElements = true;
1✔
1758
    }
1✔
1759

1760
    /**
1761
     * Returns the base URL for this HTML segment.
1762
     *
1763
     * @return the base URL
1764
     */
1765
    URL getBaseURL() {
1766
        return _baseURL;
1✔
1767
    }
1768

1769
    /**
1770
     * Gets the response.
1771
     *
1772
     * @return the response
1773
     */
1774
    WebResponse getResponse() {
1775
        return _response;
1✔
1776
    }
1777

1778
    /**
1779
     * Returns the domain object model associated with this page, to be used internally.
1780
     *
1781
     * @return the original DOM
1782
     */
1783
    Node getOriginalDOM() {
1784
        return getRootNode();
1✔
1785
    }
1786

1787
    /**
1788
     * Gets the element.
1789
     *
1790
     * @param node
1791
     *            the node
1792
     *
1793
     * @return the element
1794
     */
1795
    HTMLElement getElement(Node node) {
1796
        return (HTMLElement) _registry.getRegisteredElement(node);
1✔
1797
    }
1798

1799
    /**
1800
     * Returns the frames found in the page in the order in which they appear.
1801
     *
1802
     * @return the frames
1803
     */
1804
    public WebFrame[] getFrames() {
1805
        if (_frames == null) {
1✔
1806
            loadElements();
1✔
1807
            _frames = (WebFrame[]) _frameList.toArray(new WebFrame[_frameList.size()]);
1✔
1808
        }
1809
        return _frames;
1✔
1810
    }
1811

1812
    // ---------------------------------- private members --------------------------------
1813

1814
    /**
1815
     * Gets the root node.
1816
     *
1817
     * @return the root node
1818
     */
1819
    Node getRootNode() {
1820
        if (_rootNode == null) {
1!
1821
            throw new IllegalStateException("The root node has not been specified");
×
1822
        }
1823
        return _rootNode;
1✔
1824
    }
1825

1826
    /**
1827
     * Returns the table with the specified text in its summary attribute.
1828
     *
1829
     * @param tables
1830
     *            the tables
1831
     * @param predicate
1832
     *            the predicate
1833
     * @param value
1834
     *            the value
1835
     *
1836
     * @return the table satisfying predicate
1837
     */
1838
    private WebTable getTableSatisfyingPredicate(WebTable[] tables, HTMLElementPredicate predicate, Object value) {
1839
        for (WebTable table : tables) {
1✔
1840
            if (predicate.matchesCriteria(table, value)) {
1✔
1841
                return table;
1✔
1842
            }
1843
            for (int j = 0; j < table.getRowCount(); j++) {
1✔
1844
                for (int k = 0; k < table.getColumnCount(); k++) {
1✔
1845
                    TableCell cell = table.getTableCell(j, k);
1✔
1846
                    if (cell != null) {
1!
1847
                        WebTable[] innerTables = cell.getTables();
1✔
1848
                        if (innerTables.length != 0) {
1✔
1849
                            WebTable result = getTableSatisfyingPredicate(innerTables, predicate, value);
1✔
1850
                            if (result != null) {
1✔
1851
                                return result;
1✔
1852
                            }
1853
                        }
1854
                    }
1855
                }
1856
            }
1857
        }
1858
        return null;
1✔
1859
    }
1860

1861
    /**
1862
     * Returns the tables which match the specified criteria.
1863
     *
1864
     * @param tables
1865
     *            the tables
1866
     * @param predicate
1867
     *            the predicate
1868
     * @param value
1869
     *            the value
1870
     *
1871
     * @return the tables satisfying predicate
1872
     */
1873
    private WebTable[] getTablesSatisfyingPredicate(WebTable[] tables, HTMLElementPredicate predicate, Object value) {
1874
        ArrayList matches = new ArrayList<>();
×
1875
        for (WebTable table : tables) {
×
1876
            if (predicate.matchesCriteria(table, value)) {
×
1877
                matches.add(table);
×
1878
            }
1879
            for (int j = 0; j < table.getRowCount(); j++) {
×
1880
                for (int k = 0; k < table.getColumnCount(); k++) {
×
1881
                    TableCell cell = table.getTableCell(j, k);
×
1882
                    if (cell != null) {
×
1883
                        WebTable[] innerTables = cell.getTables();
×
1884
                        if (innerTables.length != 0) {
×
1885
                            WebTable[] result = getTablesSatisfyingPredicate(innerTables, predicate, value);
×
1886
                            if (result != null && result.length > 0) {
×
1887
                                matches.addAll(Arrays.asList(result));
×
1888
                            }
1889
                        }
1890
                    }
1891
                }
1892
            }
1893
        }
1894
        if (matches.size() > 0) {
×
1895
            return (WebTable[]) matches.toArray(new WebTable[matches.size()]);
×
1896
        }
1897
        return null;
×
1898
    }
1899

1900
    /**
1901
     * The Class WebIFrame.
1902
     */
1903
    class WebIFrame extends WebFrame implements ContentConcealer {
1904

1905
        /**
1906
         * Instantiates a new web I frame.
1907
         *
1908
         * @param baseURL
1909
         *            the base URL
1910
         * @param frameNode
1911
         *            the frame node
1912
         * @param parentFrame
1913
         *            the parent frame
1914
         */
1915
        public WebIFrame(URL baseURL, Node frameNode, FrameSelector parentFrame) {
1✔
1916
            super(_response, baseURL, frameNode, parentFrame);
1✔
1917
        }
1✔
1918
    }
1919

1920
    /**
1921
     * NoScript Element.
1922
     */
1923
    class NoScriptElement extends HTMLElementBase implements ContentConcealer {
1924

1925
        /**
1926
         * Instantiates a new no script element.
1927
         *
1928
         * @param node
1929
         *            the node
1930
         */
1931
        public NoScriptElement(Node node) {
1✔
1932
            super(node);
1✔
1933
        }
1✔
1934

1935
        @Override
1936
        public ScriptableDelegate newScriptable() {
1937
            return null;
×
1938
        }
1939

1940
        @Override
1941
        public ScriptableDelegate getParentDelegate() {
1942
            return null;
×
1943
        }
1944
    }
1945

1946
}
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