1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html;
16
17 import java.io.IOException;
18 import java.io.PrintWriter;
19 import java.io.Serializable;
20 import java.io.StringWriter;
21 import java.nio.charset.Charset;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28
29 import org.htmlunit.BrowserVersionFeatures;
30 import org.htmlunit.IncorrectnessListener;
31 import org.htmlunit.Page;
32 import org.htmlunit.SgmlPage;
33 import org.htmlunit.WebAssert;
34 import org.htmlunit.WebClient;
35 import org.htmlunit.WebClient.PooledCSS3Parser;
36 import org.htmlunit.WebWindow;
37 import org.htmlunit.css.ComputedCssStyleDeclaration;
38 import org.htmlunit.css.CssStyleSheet;
39 import org.htmlunit.css.StyleAttributes;
40 import org.htmlunit.cssparser.parser.CSSErrorHandler;
41 import org.htmlunit.cssparser.parser.CSSException;
42 import org.htmlunit.cssparser.parser.CSSOMParser;
43 import org.htmlunit.cssparser.parser.CSSParseException;
44 import org.htmlunit.cssparser.parser.selector.Selector;
45 import org.htmlunit.cssparser.parser.selector.SelectorList;
46 import org.htmlunit.html.HtmlElement.DisplayStyle;
47 import org.htmlunit.html.serializer.HtmlSerializerNormalizedText;
48 import org.htmlunit.html.serializer.HtmlSerializerVisibleText;
49 import org.htmlunit.html.xpath.XPathHelper;
50 import org.htmlunit.javascript.HtmlUnitScriptable;
51 import org.htmlunit.javascript.host.event.Event;
52 import org.htmlunit.xpath.xml.utils.PrefixResolver;
53 import org.w3c.dom.DOMException;
54 import org.w3c.dom.Document;
55 import org.w3c.dom.NamedNodeMap;
56 import org.w3c.dom.Node;
57 import org.w3c.dom.UserDataHandler;
58 import org.xml.sax.SAXException;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 public abstract class DomNode implements Cloneable, Serializable, Node {
82
83
84 public static final String READY_STATE_UNINITIALIZED = "uninitialized";
85
86
87 public static final String READY_STATE_LOADING = "loading";
88
89
90 public static final String READY_STATE_LOADED = "loaded";
91
92
93 public static final String READY_STATE_INTERACTIVE = "interactive";
94
95
96 public static final String READY_STATE_COMPLETE = "complete";
97
98
99 public static final String PROPERTY_ELEMENT = "element";
100
101 private static final NamedNodeMap EMPTY_NAMED_NODE_MAP = new ReadOnlyEmptyNamedNodeMapImpl();
102
103
104 private SgmlPage page_;
105
106
107 private DomNode parent_;
108
109
110
111
112
113 private DomNode previousSibling_;
114
115
116
117
118 private DomNode nextSibling_;
119
120
121 private DomNode firstChild_;
122
123
124
125
126
127 private HtmlUnitScriptable scriptObject_;
128
129
130 private String readyState_;
131
132
133
134
135 private int startLineNumber_ = -1;
136
137
138
139
140 private int startColumnNumber_ = -1;
141
142
143
144
145 private int endLineNumber_ = -1;
146
147
148
149
150 private int endColumnNumber_ = -1;
151
152 private boolean attachedToPage_;
153
154
155 private List<CharacterDataChangeListener> characterDataListeners_;
156 private List<DomChangeListener> domListeners_;
157
158 private Map<String, Object> userData_;
159
160
161
162
163
164 protected DomNode(final SgmlPage page) {
165 readyState_ = READY_STATE_LOADING;
166 page_ = page;
167 }
168
169
170
171
172
173
174
175 public void setStartLocation(final int startLineNumber, final int startColumnNumber) {
176 startLineNumber_ = startLineNumber;
177 startColumnNumber_ = startColumnNumber;
178 }
179
180
181
182
183
184
185
186 public void setEndLocation(final int endLineNumber, final int endColumnNumber) {
187 endLineNumber_ = endLineNumber;
188 endColumnNumber_ = endColumnNumber;
189 }
190
191
192
193
194
195 public int getStartLineNumber() {
196 return startLineNumber_;
197 }
198
199
200
201
202
203 public int getStartColumnNumber() {
204 return startColumnNumber_;
205 }
206
207
208
209
210
211
212 public int getEndLineNumber() {
213 return endLineNumber_;
214 }
215
216
217
218
219
220
221 public int getEndColumnNumber() {
222 return endColumnNumber_;
223 }
224
225
226
227
228
229 public SgmlPage getPage() {
230 return page_;
231 }
232
233
234
235
236
237 public HtmlPage getHtmlPageOrNull() {
238 if (page_ == null || !page_.isHtmlPage()) {
239 return null;
240 }
241 return (HtmlPage) page_;
242 }
243
244
245
246
247 @Override
248 public Document getOwnerDocument() {
249 return getPage();
250 }
251
252
253
254
255
256
257
258
259
260 public void setScriptableObject(final HtmlUnitScriptable scriptObject) {
261 scriptObject_ = scriptObject;
262 }
263
264
265
266
267 @Override
268 public DomNode getLastChild() {
269 if (firstChild_ != null) {
270
271 return firstChild_.previousSibling_;
272 }
273 return null;
274 }
275
276
277
278
279 @Override
280 public DomNode getParentNode() {
281 return parent_;
282 }
283
284
285
286
287
288 protected void setParentNode(final DomNode parent) {
289 parent_ = parent;
290 }
291
292
293
294
295
296 public int getIndex() {
297 int index = 0;
298 for (DomNode n = previousSibling_; n != null && n.nextSibling_ != null; n = n.previousSibling_) {
299 index++;
300 }
301 return index;
302 }
303
304
305
306
307 @Override
308 public DomNode getPreviousSibling() {
309 if (parent_ == null || this == parent_.firstChild_) {
310
311 return null;
312 }
313 return previousSibling_;
314 }
315
316
317
318
319 @Override
320 public DomNode getNextSibling() {
321 return nextSibling_;
322 }
323
324
325
326
327 @Override
328 public DomNode getFirstChild() {
329 return firstChild_;
330 }
331
332
333
334
335
336
337
338 public boolean isAncestorOf(DomNode node) {
339 while (node != null) {
340 if (node == this) {
341 return true;
342 }
343 node = node.getParentNode();
344 }
345 return false;
346 }
347
348
349
350
351
352
353
354 public boolean isAncestorOfAny(final DomNode... nodes) {
355 for (final DomNode node : nodes) {
356 if (isAncestorOf(node)) {
357 return true;
358 }
359 }
360 return false;
361 }
362
363
364
365
366 @Override
367 public String getNamespaceURI() {
368 return null;
369 }
370
371
372
373
374 @Override
375 public String getLocalName() {
376 return null;
377 }
378
379
380
381
382 @Override
383 public String getPrefix() {
384 return null;
385 }
386
387
388
389
390 @Override
391 public boolean hasChildNodes() {
392 return firstChild_ != null;
393 }
394
395
396
397
398 @Override
399 public DomNodeList<DomNode> getChildNodes() {
400 return new SiblingDomNodeList(this);
401 }
402
403
404
405
406
407 @Override
408 public boolean isSupported(final String namespace, final String featureName) {
409 throw new UnsupportedOperationException("DomNode.isSupported is not yet implemented.");
410 }
411
412
413
414
415 @Override
416 public void normalize() {
417 for (DomNode child = getFirstChild(); child != null; child = child.getNextSibling()) {
418 if (child instanceof DomText) {
419 final StringBuilder dataBuilder = new StringBuilder();
420 DomNode toRemove = child;
421 DomText firstText = null;
422
423 while (toRemove instanceof DomText && !(toRemove instanceof DomCDataSection)) {
424 final DomNode nextChild = toRemove.getNextSibling();
425 dataBuilder.append(toRemove.getTextContent());
426 if (firstText != null) {
427 toRemove.remove();
428 }
429 if (firstText == null) {
430 firstText = (DomText) toRemove;
431 }
432 toRemove = nextChild;
433 }
434 if (firstText != null) {
435 firstText.setData(dataBuilder.toString());
436 }
437 }
438 }
439 }
440
441
442
443
444 @Override
445 public String getBaseURI() {
446 return getPage().getUrl().toExternalForm();
447 }
448
449
450
451
452 @Override
453 public short compareDocumentPosition(final Node other) {
454 if (other == this) {
455 return 0;
456 }
457
458
459 final List<Node> myAncestors = getAncestors();
460 final List<Node> otherAncestors = ((DomNode) other).getAncestors();
461
462 if (!myAncestors.get(0).equals(otherAncestors.get(0))) {
463
464
465
466
467
468
469 if (this.hashCode() < other.hashCode()) {
470 return DOCUMENT_POSITION_DISCONNECTED
471 | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
472 | DOCUMENT_POSITION_PRECEDING;
473 }
474
475 return DOCUMENT_POSITION_DISCONNECTED
476 | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
477 | DOCUMENT_POSITION_FOLLOWING;
478 }
479
480 final int max = Math.min(myAncestors.size(), otherAncestors.size());
481
482 int i = 1;
483 while (i < max && myAncestors.get(i) == otherAncestors.get(i)) {
484 i++;
485 }
486
487 if (i != 1 && i == max) {
488 if (myAncestors.size() == max) {
489 return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
490 }
491 return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
492 }
493
494 if (max == 1) {
495 if (myAncestors.contains(other)) {
496 return DOCUMENT_POSITION_CONTAINS;
497 }
498 if (otherAncestors.contains(this)) {
499 return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
500 }
501 return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
502 }
503
504
505 final Node myAncestor = myAncestors.get(i);
506 final Node otherAncestor = otherAncestors.get(i);
507 Node node = myAncestor;
508 while (node != otherAncestor && node != null) {
509 node = node.getPreviousSibling();
510 }
511 if (node == null) {
512 return DOCUMENT_POSITION_FOLLOWING;
513 }
514 return DOCUMENT_POSITION_PRECEDING;
515 }
516
517
518
519
520
521
522
523 public List<Node> getAncestors() {
524 final List<Node> list = new ArrayList<>();
525 list.add(this);
526
527 Node node = getParentNode();
528 while (node != null) {
529 list.add(0, node);
530 node = node.getParentNode();
531 }
532 return list;
533 }
534
535
536
537
538 @Override
539 public String getTextContent() {
540 switch (getNodeType()) {
541 case ELEMENT_NODE:
542 case ATTRIBUTE_NODE:
543 case ENTITY_NODE:
544 case ENTITY_REFERENCE_NODE:
545 case DOCUMENT_FRAGMENT_NODE:
546 final StringBuilder builder = new StringBuilder();
547 for (final DomNode child : getChildren()) {
548 final short childType = child.getNodeType();
549 if (childType != COMMENT_NODE && childType != PROCESSING_INSTRUCTION_NODE) {
550 builder.append(child.getTextContent());
551 }
552 }
553 return builder.toString();
554
555 case TEXT_NODE:
556 case CDATA_SECTION_NODE:
557 case COMMENT_NODE:
558 case PROCESSING_INSTRUCTION_NODE:
559 return getNodeValue();
560
561 default:
562 return null;
563 }
564 }
565
566
567
568
569 @Override
570 public void setTextContent(final String textContent) {
571 removeAllChildren();
572 if (textContent != null && !textContent.isEmpty()) {
573 appendChild(new DomText(getPage(), textContent));
574 }
575 }
576
577
578
579
580 @Override
581 public boolean isSameNode(final Node other) {
582 return other == this;
583 }
584
585
586
587
588
589 @Override
590 public String lookupPrefix(final String namespaceURI) {
591 throw new UnsupportedOperationException("DomNode.lookupPrefix is not yet implemented.");
592 }
593
594
595
596
597
598 @Override
599 public boolean isDefaultNamespace(final String namespaceURI) {
600 throw new UnsupportedOperationException("DomNode.isDefaultNamespace is not yet implemented.");
601 }
602
603
604
605
606
607 @Override
608 public String lookupNamespaceURI(final String prefix) {
609 throw new UnsupportedOperationException("DomNode.lookupNamespaceURI is not yet implemented.");
610 }
611
612
613
614
615
616 @Override
617 public boolean isEqualNode(final Node arg) {
618 throw new UnsupportedOperationException("DomNode.isEqualNode is not yet implemented.");
619 }
620
621
622
623
624
625 @Override
626 public Object getFeature(final String feature, final String version) {
627 throw new UnsupportedOperationException("DomNode.getFeature is not yet implemented.");
628 }
629
630
631
632
633 @Override
634 public Object getUserData(final String key) {
635 Object value = null;
636 if (userData_ != null) {
637 value = userData_.get(key);
638 }
639 return value;
640 }
641
642
643
644
645 @Override
646 public Object setUserData(final String key, final Object data, final UserDataHandler handler) {
647 if (userData_ == null) {
648 userData_ = new HashMap<>();
649 }
650 return userData_.put(key, data);
651 }
652
653
654
655
656 @Override
657 public boolean hasAttributes() {
658 return false;
659 }
660
661
662
663
664 @Override
665 public NamedNodeMap getAttributes() {
666 return EMPTY_NAMED_NODE_MAP;
667 }
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683 public boolean isDisplayed() {
684 if (!mayBeDisplayed()) {
685 return false;
686 }
687
688 final Page page = getPage();
689 final WebWindow window = page.getEnclosingWindow();
690 final WebClient webClient = window.getWebClient();
691 if (webClient.getOptions().isCssEnabled()) {
692
693
694 final List<Node> ancestors = getAncestors();
695 final ArrayList<ComputedCssStyleDeclaration> styles = new ArrayList<>(ancestors.size());
696
697 for (final Node node : ancestors) {
698 if (node instanceof HtmlElement) {
699 final HtmlElement elem = (HtmlElement) node;
700 if (elem.isHidden()) {
701 return false;
702 }
703
704 if (elem instanceof HtmlDialog) {
705 if (!((HtmlDialog) elem).isOpen()) {
706 return false;
707 }
708 }
709 else {
710 final ComputedCssStyleDeclaration style = window.getComputedStyle(elem, null);
711 if (DisplayStyle.NONE.value().equals(style.getDisplay())) {
712 return false;
713 }
714 styles.add(style);
715 }
716 }
717 }
718
719
720
721 for (int i = styles.size() - 1; i >= 0; i--) {
722 final ComputedCssStyleDeclaration style = styles.get(i);
723 final String visibility = style.getStyleAttribute(StyleAttributes.Definition.VISIBILITY, true);
724 if (visibility.length() > 5) {
725 if ("visible".equals(visibility)) {
726 return true;
727 }
728 if ("hidden".equals(visibility) || "collapse".equals(visibility)) {
729 return false;
730 }
731 }
732 }
733 }
734 return true;
735 }
736
737
738
739
740
741
742
743
744 public boolean mayBeDisplayed() {
745 return true;
746 }
747
748
749
750
751
752
753
754
755 public String asNormalizedText() {
756 final HtmlSerializerNormalizedText ser = new HtmlSerializerNormalizedText();
757 return ser.asText(this);
758 }
759
760
761
762
763
764
765
766
767
768
769
770 public String getVisibleText() {
771 final HtmlSerializerVisibleText ser = new HtmlSerializerVisibleText();
772 return ser.asText(this);
773 }
774
775
776
777
778
779
780
781
782
783 public String asXml() {
784 Charset charsetName = null;
785 final HtmlPage htmlPage = getHtmlPageOrNull();
786 if (htmlPage != null) {
787 charsetName = htmlPage.getCharset();
788 }
789
790 final StringWriter stringWriter = new StringWriter();
791 try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
792 boolean tag = false;
793 if (charsetName != null && this instanceof HtmlHtml) {
794 printWriter.print("<?xml version=\"1.0\" encoding=\"");
795 printWriter.print(charsetName);
796 printWriter.print("\"?>");
797 tag = true;
798 }
799 printXml("", tag, printWriter);
800 return stringWriter.toString();
801 }
802 }
803
804
805
806
807
808
809
810
811
812 protected boolean printXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
813 if (tagBefore) {
814 printWriter.print("\r\n");
815 printWriter.print(indent);
816 }
817 printWriter.print(this);
818 return printChildrenAsXml(indent, false, printWriter);
819 }
820
821
822
823
824
825
826
827
828
829 protected boolean printChildrenAsXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
830 DomNode child = getFirstChild();
831 boolean tag = tagBefore;
832 while (child != null) {
833 tag = child.printXml(indent + " ", tag, printWriter);
834 child = child.getNextSibling();
835 }
836 return tag;
837 }
838
839
840
841
842 @Override
843 public String getNodeValue() {
844 return null;
845 }
846
847
848
849
850 @Override
851 public DomNode cloneNode(final boolean deep) {
852 final DomNode newnode;
853 try {
854 newnode = (DomNode) clone();
855 }
856 catch (final CloneNotSupportedException e) {
857 throw new IllegalStateException("Clone not supported for node [" + this + "]", e);
858 }
859
860 newnode.parent_ = null;
861 newnode.nextSibling_ = null;
862 newnode.previousSibling_ = null;
863 newnode.scriptObject_ = null;
864 newnode.firstChild_ = null;
865 newnode.attachedToPage_ = false;
866
867
868 if (deep) {
869 for (DomNode child = firstChild_; child != null; child = child.nextSibling_) {
870 newnode.appendChild(child.cloneNode(true));
871 }
872 }
873
874 return newnode;
875 }
876
877
878
879
880
881
882
883
884
885
886
887
888 @SuppressWarnings("unchecked")
889 public <T extends HtmlUnitScriptable> T getScriptableObject() {
890 if (scriptObject_ == null) {
891 final SgmlPage page = getPage();
892 if (this == page) {
893 final StringBuilder msg = new StringBuilder("No script object associated with the Page.");
894
895 msg.append(" class: '")
896 .append(page.getClass().getName())
897 .append('\'');
898 try {
899 msg.append(" url: '")
900 .append(page.getUrl()).append("' content: ")
901 .append(page.getWebResponse().getContentAsString());
902 }
903 catch (final Exception e) {
904
905 msg.append(" no details: '").append(e).append('\'');
906 }
907 throw new IllegalStateException(msg.toString());
908 }
909 scriptObject_ = page.getScriptableObject().makeScriptableFor(this);
910 }
911 return (T) scriptObject_;
912 }
913
914
915
916
917 @Override
918 public DomNode appendChild(final Node node) {
919 if (node == this) {
920 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Can not add not to itself " + this);
921 }
922 final DomNode domNode = (DomNode) node;
923 if (domNode.isAncestorOf(this)) {
924 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Can not add (grand)parent to itself " + this);
925 }
926
927 if (domNode instanceof DomDocumentFragment) {
928 final DomDocumentFragment fragment = (DomDocumentFragment) domNode;
929 for (final DomNode child : fragment.getChildren()) {
930 appendChild(child);
931 }
932 }
933 else {
934
935 if (domNode.getParentNode() != null) {
936 domNode.detach();
937 }
938
939 basicAppend(domNode);
940
941 fireAddition(domNode);
942 }
943
944 return domNode;
945 }
946
947
948
949
950
951
952
953 private void basicAppend(final DomNode node) {
954
955
956 node.setPage(getPage());
957 node.parent_ = this;
958
959 if (firstChild_ == null) {
960 firstChild_ = node;
961 }
962 else {
963 final DomNode last = getLastChild();
964 node.previousSibling_ = last;
965 node.nextSibling_ = null;
966
967 last.nextSibling_ = node;
968 }
969 firstChild_.previousSibling_ = node;
970 }
971
972
973
974
975 @Override
976 public Node insertBefore(final Node newChild, final Node refChild) {
977 if (newChild instanceof DomDocumentFragment) {
978 final DomDocumentFragment fragment = (DomDocumentFragment) newChild;
979 for (final DomNode child : fragment.getChildren()) {
980 insertBefore(child, refChild);
981 }
982 return newChild;
983 }
984
985 if (refChild == null) {
986 appendChild(newChild);
987 return newChild;
988 }
989
990 if (refChild.getParentNode() != this) {
991 throw new DOMException(DOMException.NOT_FOUND_ERR, "Reference node is not a child of this node.");
992 }
993
994 ((DomNode) refChild).insertBefore((DomNode) newChild);
995 return newChild;
996 }
997
998
999
1000
1001
1002
1003
1004 public void insertBefore(final DomNode newNode) {
1005 if (previousSibling_ == null) {
1006 throw new IllegalStateException("Previous sibling for " + this + " is null.");
1007 }
1008
1009 if (newNode == this) {
1010 return;
1011 }
1012
1013
1014 if (newNode.getParentNode() != null) {
1015 newNode.detach();
1016 }
1017
1018 basicInsertBefore(newNode);
1019
1020 fireAddition(newNode);
1021 }
1022
1023
1024
1025
1026
1027
1028
1029 private void basicInsertBefore(final DomNode node) {
1030
1031
1032 node.setPage(page_);
1033 node.parent_ = parent_;
1034 node.previousSibling_ = previousSibling_;
1035 node.nextSibling_ = this;
1036
1037 if (parent_.firstChild_ == this) {
1038 parent_.firstChild_ = node;
1039 }
1040 else {
1041 previousSibling_.nextSibling_ = node;
1042 }
1043 previousSibling_ = node;
1044 }
1045
1046 private void fireAddition(final DomNode domNode) {
1047 final boolean wasAlreadyAttached = domNode.isAttachedToPage();
1048 domNode.attachedToPage_ = isAttachedToPage();
1049
1050 final SgmlPage page = getPage();
1051 if (domNode.attachedToPage_) {
1052
1053 if (null != page && page.isHtmlPage()) {
1054 ((HtmlPage) page).notifyNodeAdded(domNode);
1055 }
1056
1057
1058 if (!domNode.isBodyParsed() && !wasAlreadyAttached) {
1059 if (domNode.getFirstChild() != null) {
1060 for (final Iterator<DomNode> iterator =
1061 domNode.new DescendantDomNodesIterator(); iterator.hasNext();) {
1062 final DomNode child = iterator.next();
1063 child.attachedToPage_ = true;
1064 child.onAllChildrenAddedToPage(true);
1065 }
1066 }
1067 domNode.onAllChildrenAddedToPage(true);
1068 }
1069 }
1070
1071 if (this instanceof DomDocumentFragment) {
1072 onAddedToDocumentFragment();
1073 }
1074
1075 if (page == null || page.isDomChangeListenerInUse()) {
1076 fireNodeAdded(this, domNode);
1077 }
1078 }
1079
1080
1081
1082
1083
1084 private boolean isBodyParsed() {
1085 return getStartLineNumber() != -1 && getEndLineNumber() == -1;
1086 }
1087
1088
1089
1090
1091
1092 private void setPage(final SgmlPage newPage) {
1093 if (page_ == newPage) {
1094 return;
1095 }
1096
1097 page_ = newPage;
1098 for (final DomNode node : getChildren()) {
1099 node.setPage(newPage);
1100 }
1101 }
1102
1103
1104
1105
1106 @Override
1107 public Node removeChild(final Node child) {
1108 if (child.getParentNode() != this) {
1109 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1110 }
1111 ((DomNode) child).remove();
1112 return child;
1113 }
1114
1115
1116
1117
1118 public void removeAllChildren() {
1119 while (getFirstChild() != null) {
1120 getFirstChild().remove();
1121 }
1122 }
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132 public void parseHtmlSnippet(final String source) throws SAXException, IOException {
1133 final WebClient webClient = getPage().getWebClient();
1134 webClient.getPageCreator().getHtmlParser().parseFragment(webClient, this, this, source, false);
1135 }
1136
1137
1138
1139
1140 public void remove() {
1141
1142 detach();
1143 }
1144
1145
1146
1147
1148
1149
1150
1151 protected void detach() {
1152 final DomNode exParent = parent_;
1153
1154 basicRemove();
1155
1156 fireRemoval(exParent);
1157 }
1158
1159
1160
1161
1162 protected void basicRemove() {
1163 if (parent_ != null && parent_.firstChild_ == this) {
1164 parent_.firstChild_ = nextSibling_;
1165 }
1166 else if (previousSibling_ != null && previousSibling_.nextSibling_ == this) {
1167 previousSibling_.nextSibling_ = nextSibling_;
1168 }
1169 if (nextSibling_ != null && nextSibling_.previousSibling_ == this) {
1170 nextSibling_.previousSibling_ = previousSibling_;
1171 }
1172 if (parent_ != null && this == parent_.getLastChild()) {
1173 parent_.firstChild_.previousSibling_ = previousSibling_;
1174 }
1175
1176 nextSibling_ = null;
1177 previousSibling_ = null;
1178 parent_ = null;
1179 attachedToPage_ = false;
1180 for (final DomNode descendant : getDescendants()) {
1181 descendant.attachedToPage_ = false;
1182 }
1183 }
1184
1185 private void fireRemoval(final DomNode exParent) {
1186 final SgmlPage page = getPage();
1187 if (page instanceof HtmlPage) {
1188
1189
1190 parent_ = exParent;
1191 ((HtmlPage) page).notifyNodeRemoved(this);
1192 parent_ = null;
1193 }
1194
1195 if (exParent != null && (page == null || page.isDomChangeListenerInUse())) {
1196 fireNodeDeleted(exParent, this);
1197
1198 exParent.fireNodeDeleted(exParent, this);
1199 }
1200 }
1201
1202
1203
1204
1205 @Override
1206 public Node replaceChild(final Node newChild, final Node oldChild) {
1207 if (oldChild.getParentNode() != this) {
1208 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1209 }
1210 ((DomNode) oldChild).replace((DomNode) newChild);
1211 return oldChild;
1212 }
1213
1214
1215
1216
1217
1218
1219 public void replace(final DomNode newNode) {
1220 if (newNode != this) {
1221 final DomNode exParent = parent_;
1222 final DomNode exNextSibling = nextSibling_;
1223
1224 remove();
1225
1226 exParent.insertBefore(newNode, exNextSibling);
1227 }
1228 }
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239 public void quietlyRemoveAndMoveChildrenTo(final DomNode destination) {
1240 if (destination.getPage() != getPage()) {
1241 throw new RuntimeException("Cannot perform quiet move on nodes from different pages.");
1242 }
1243 for (final DomNode child : getChildren()) {
1244 if (child != destination) {
1245 child.basicRemove();
1246 destination.basicAppend(child);
1247 }
1248 }
1249 basicRemove();
1250 }
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264 protected void checkChildHierarchy(final Node newChild) throws DOMException {
1265 Node parentNode = this;
1266 while (parentNode != null) {
1267 if (parentNode == newChild) {
1268 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Child node is already a parent.");
1269 }
1270 parentNode = parentNode.getParentNode();
1271 }
1272 final Document thisDocument = getOwnerDocument();
1273 final Document childDocument = newChild.getOwnerDocument();
1274 if (childDocument != thisDocument && childDocument != null) {
1275 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Child node " + newChild.getNodeName()
1276 + " is not in the same Document as this " + getNodeName() + ".");
1277 }
1278 }
1279
1280
1281
1282
1283
1284
1285
1286 protected void onAddedToPage() {
1287 if (firstChild_ != null) {
1288 for (final DomNode child : getChildren()) {
1289 child.onAddedToPage();
1290 }
1291 }
1292 }
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302 public void onAllChildrenAddedToPage(final boolean postponed) {
1303
1304 }
1305
1306
1307
1308
1309
1310
1311
1312 protected void onAddedToDocumentFragment() {
1313 if (firstChild_ != null) {
1314 for (final DomNode child : getChildren()) {
1315 child.onAddedToDocumentFragment();
1316 }
1317 }
1318 }
1319
1320
1321
1322
1323 public final Iterable<DomNode> getChildren() {
1324 return () -> new ChildIterator(firstChild_);
1325 }
1326
1327
1328
1329
1330 protected static class ChildIterator implements Iterator<DomNode> {
1331
1332 private DomNode nextNode_;
1333 private DomNode currentNode_;
1334
1335 public ChildIterator(final DomNode nextNode) {
1336 nextNode_ = nextNode;
1337 }
1338
1339
1340 @Override
1341 public boolean hasNext() {
1342 return nextNode_ != null;
1343 }
1344
1345
1346 @Override
1347 public DomNode next() {
1348 if (nextNode_ != null) {
1349 currentNode_ = nextNode_;
1350 nextNode_ = nextNode_.nextSibling_;
1351 return currentNode_;
1352 }
1353 throw new NoSuchElementException();
1354 }
1355
1356
1357 @Override
1358 public void remove() {
1359 if (currentNode_ == null) {
1360 throw new IllegalStateException();
1361 }
1362 currentNode_.remove();
1363 }
1364 }
1365
1366
1367
1368
1369
1370
1371
1372 public final Iterable<DomNode> getDescendants() {
1373 return () -> new DescendantDomNodesIterator();
1374 }
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384 public final Iterable<HtmlElement> getHtmlElementDescendants() {
1385 return () -> new DescendantHtmlElementsIterator();
1386 }
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396 public final Iterable<DomElement> getDomElementDescendants() {
1397 return () -> new DescendantDomElementsIterator();
1398 }
1399
1400
1401
1402
1403
1404
1405
1406
1407 @Deprecated
1408 protected class DescendantElementsIterator<T extends DomNode> implements Iterator<T> {
1409
1410 private DomNode currentNode_;
1411 private DomNode nextNode_;
1412 private final Class<T> type_;
1413
1414
1415
1416
1417
1418 public DescendantElementsIterator(final Class<T> type) {
1419 type_ = type;
1420 nextNode_ = getFirstChildElement(DomNode.this);
1421 }
1422
1423
1424 @Override
1425 public boolean hasNext() {
1426 return nextNode_ != null;
1427 }
1428
1429
1430 @Override
1431 public T next() {
1432 return nextNode();
1433 }
1434
1435
1436 @Override
1437 public void remove() {
1438 if (currentNode_ == null) {
1439 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1440 }
1441 final DomNode current = currentNode_;
1442 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1443 next();
1444 }
1445 current.remove();
1446 }
1447
1448
1449
1450
1451 @SuppressWarnings("unchecked")
1452 public T nextNode() {
1453 currentNode_ = nextNode_;
1454 setNextElement();
1455 return (T) currentNode_;
1456 }
1457
1458 private void setNextElement() {
1459 DomNode next = getFirstChildElement(nextNode_);
1460 if (next == null) {
1461 next = getNextDomSibling(nextNode_);
1462 }
1463 if (next == null) {
1464 next = getNextElementUpwards(nextNode_);
1465 }
1466 nextNode_ = next;
1467 }
1468
1469 private DomNode getNextElementUpwards(final DomNode startingNode) {
1470 if (startingNode == DomNode.this) {
1471 return null;
1472 }
1473
1474 DomNode parent = startingNode.getParentNode();
1475 while (parent != null && parent != DomNode.this) {
1476 DomNode next = parent.getNextSibling();
1477 while (next != null && !isAccepted(next)) {
1478 next = next.getNextSibling();
1479 }
1480 if (next != null) {
1481 return next;
1482 }
1483 parent = parent.getParentNode();
1484 }
1485 return null;
1486 }
1487
1488 private DomNode getFirstChildElement(final DomNode parent) {
1489 DomNode node = parent.getFirstChild();
1490 while (node != null && !isAccepted(node)) {
1491 node = node.getNextSibling();
1492 }
1493 return node;
1494 }
1495
1496
1497
1498
1499
1500
1501 protected boolean isAccepted(final DomNode node) {
1502 return type_.isAssignableFrom(node.getClass());
1503 }
1504
1505 private DomNode getNextDomSibling(final DomNode element) {
1506 DomNode node = element.getNextSibling();
1507 while (node != null && !isAccepted(node)) {
1508 node = node.getNextSibling();
1509 }
1510 return node;
1511 }
1512 }
1513
1514
1515
1516
1517 protected final class DescendantDomNodesIterator implements Iterator<DomNode> {
1518 private DomNode currentNode_;
1519 private DomNode nextNode_;
1520
1521
1522
1523
1524 public DescendantDomNodesIterator() {
1525 nextNode_ = getFirstChildElement(DomNode.this);
1526 }
1527
1528
1529 @Override
1530 public boolean hasNext() {
1531 return nextNode_ != null;
1532 }
1533
1534
1535 @Override
1536 public DomNode next() {
1537 return nextNode();
1538 }
1539
1540
1541 @Override
1542 public void remove() {
1543 if (currentNode_ == null) {
1544 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1545 }
1546 final DomNode current = currentNode_;
1547 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1548 next();
1549 }
1550 current.remove();
1551 }
1552
1553
1554
1555
1556 @SuppressWarnings("unchecked")
1557 public DomNode nextNode() {
1558 currentNode_ = nextNode_;
1559
1560 DomNode next = getFirstChildElement(nextNode_);
1561 if (next == null) {
1562 next = getNextDomSibling(nextNode_);
1563 }
1564 if (next == null) {
1565 next = getNextElementUpwards(nextNode_);
1566 }
1567 nextNode_ = next;
1568
1569 return currentNode_;
1570 }
1571
1572 private DomNode getNextElementUpwards(final DomNode startingNode) {
1573 if (startingNode == DomNode.this) {
1574 return null;
1575 }
1576
1577 DomNode parent = startingNode.getParentNode();
1578 while (parent != null && parent != DomNode.this) {
1579 DomNode next = parent.getNextSibling();
1580 while (next != null && !isAccepted(next)) {
1581 next = next.getNextSibling();
1582 }
1583 if (next != null) {
1584 return next;
1585 }
1586 parent = parent.getParentNode();
1587 }
1588 return null;
1589 }
1590
1591 private DomNode getFirstChildElement(final DomNode parent) {
1592 DomNode node = parent.getFirstChild();
1593 while (node != null && !isAccepted(node)) {
1594 node = node.getNextSibling();
1595 }
1596 return node;
1597 }
1598
1599
1600
1601
1602
1603
1604 private boolean isAccepted(final DomNode node) {
1605 return DomNode.class.isAssignableFrom(node.getClass());
1606 }
1607
1608 private DomNode getNextDomSibling(final DomNode element) {
1609 DomNode node = element.getNextSibling();
1610 while (node != null && !isAccepted(node)) {
1611 node = node.getNextSibling();
1612 }
1613 return node;
1614 }
1615 }
1616
1617
1618
1619
1620 protected final class DescendantDomElementsIterator implements Iterator<DomElement> {
1621 private DomNode currentNode_;
1622 private DomNode nextNode_;
1623
1624
1625
1626
1627 public DescendantDomElementsIterator() {
1628 nextNode_ = getFirstChildElement(DomNode.this);
1629 }
1630
1631
1632 @Override
1633 public boolean hasNext() {
1634 return nextNode_ != null;
1635 }
1636
1637
1638 @Override
1639 public DomElement next() {
1640 return nextNode();
1641 }
1642
1643
1644 @Override
1645 public void remove() {
1646 if (currentNode_ == null) {
1647 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1648 }
1649 final DomNode current = currentNode_;
1650 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1651 next();
1652 }
1653 current.remove();
1654 }
1655
1656
1657
1658
1659 @SuppressWarnings("unchecked")
1660 public DomElement nextNode() {
1661 currentNode_ = nextNode_;
1662
1663 DomNode next = getFirstChildElement(nextNode_);
1664 if (next == null) {
1665 next = getNextDomSibling(nextNode_);
1666 }
1667 if (next == null) {
1668 next = getNextElementUpwards(nextNode_);
1669 }
1670 nextNode_ = next;
1671
1672 return (DomElement) currentNode_;
1673 }
1674
1675 private DomNode getNextElementUpwards(final DomNode startingNode) {
1676 if (startingNode == DomNode.this) {
1677 return null;
1678 }
1679
1680 DomNode parent = startingNode.getParentNode();
1681 while (parent != null && parent != DomNode.this) {
1682 DomNode next = parent.getNextSibling();
1683 while (next != null && !isAccepted(next)) {
1684 next = next.getNextSibling();
1685 }
1686 if (next != null) {
1687 return next;
1688 }
1689 parent = parent.getParentNode();
1690 }
1691 return null;
1692 }
1693
1694 private DomNode getFirstChildElement(final DomNode parent) {
1695 DomNode node = parent.getFirstChild();
1696 while (node != null && !isAccepted(node)) {
1697 node = node.getNextSibling();
1698 }
1699 return node;
1700 }
1701
1702
1703
1704
1705
1706
1707 private boolean isAccepted(final DomNode node) {
1708 return DomElement.class.isAssignableFrom(node.getClass());
1709 }
1710
1711 private DomNode getNextDomSibling(final DomNode element) {
1712 DomNode node = element.getNextSibling();
1713 while (node != null && !isAccepted(node)) {
1714 node = node.getNextSibling();
1715 }
1716 return node;
1717 }
1718 }
1719
1720
1721
1722
1723 protected final class DescendantHtmlElementsIterator implements Iterator<HtmlElement> {
1724 private DomNode currentNode_;
1725 private DomNode nextNode_;
1726
1727
1728
1729
1730 public DescendantHtmlElementsIterator() {
1731 nextNode_ = getFirstChildElement(DomNode.this);
1732 }
1733
1734
1735 @Override
1736 public boolean hasNext() {
1737 return nextNode_ != null;
1738 }
1739
1740
1741 @Override
1742 public HtmlElement next() {
1743 return nextNode();
1744 }
1745
1746
1747 @Override
1748 public void remove() {
1749 if (currentNode_ == null) {
1750 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1751 }
1752 final DomNode current = currentNode_;
1753 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1754 next();
1755 }
1756 current.remove();
1757 }
1758
1759
1760
1761
1762 @SuppressWarnings("unchecked")
1763 public HtmlElement nextNode() {
1764 currentNode_ = nextNode_;
1765
1766 DomNode next = getFirstChildElement(nextNode_);
1767 if (next == null) {
1768 next = getNextDomSibling(nextNode_);
1769 }
1770 if (next == null) {
1771 next = getNextElementUpwards(nextNode_);
1772 }
1773 nextNode_ = next;
1774
1775 return (HtmlElement) currentNode_;
1776 }
1777
1778 private DomNode getNextElementUpwards(final DomNode startingNode) {
1779 if (startingNode == DomNode.this) {
1780 return null;
1781 }
1782
1783 DomNode parent = startingNode.getParentNode();
1784 while (parent != null && parent != DomNode.this) {
1785 DomNode next = parent.getNextSibling();
1786 while (next != null && !isAccepted(next)) {
1787 next = next.getNextSibling();
1788 }
1789 if (next != null) {
1790 return next;
1791 }
1792 parent = parent.getParentNode();
1793 }
1794 return null;
1795 }
1796
1797 private DomNode getFirstChildElement(final DomNode parent) {
1798 DomNode node = parent.getFirstChild();
1799 while (node != null && !isAccepted(node)) {
1800 node = node.getNextSibling();
1801 }
1802 return node;
1803 }
1804
1805
1806
1807
1808
1809
1810 private boolean isAccepted(final DomNode node) {
1811 return HtmlElement.class.isAssignableFrom(node.getClass());
1812 }
1813
1814 private DomNode getNextDomSibling(final DomNode element) {
1815 DomNode node = element.getNextSibling();
1816 while (node != null && !isAccepted(node)) {
1817 node = node.getNextSibling();
1818 }
1819 return node;
1820 }
1821 }
1822
1823
1824
1825
1826
1827 public String getReadyState() {
1828 return readyState_;
1829 }
1830
1831
1832
1833
1834
1835 public void setReadyState(final String state) {
1836 readyState_ = state;
1837 }
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853 public <T> List<T> getByXPath(final String xpathExpr) {
1854 return XPathHelper.getByXPath(this, xpathExpr, null);
1855 }
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866 public List<?> getByXPath(final String xpathExpr, final PrefixResolver resolver) {
1867 return XPathHelper.getByXPath(this, xpathExpr, resolver);
1868 }
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880 public <X> X getFirstByXPath(final String xpathExpr) {
1881 return getFirstByXPath(xpathExpr, null);
1882 }
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895 @SuppressWarnings("unchecked")
1896 public <X> X getFirstByXPath(final String xpathExpr, final PrefixResolver resolver) {
1897 final List<?> results = getByXPath(xpathExpr, resolver);
1898 if (results.isEmpty()) {
1899 return null;
1900 }
1901 return (X) results.get(0);
1902 }
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915 public String getCanonicalXPath() {
1916 throw new RuntimeException("Method getCanonicalXPath() not implemented for nodes of type " + getNodeType());
1917 }
1918
1919
1920
1921
1922
1923 protected void notifyIncorrectness(final String message) {
1924 final WebClient client = getPage().getEnclosingWindow().getWebClient();
1925 final IncorrectnessListener incorrectnessListener = client.getIncorrectnessListener();
1926 incorrectnessListener.notify(message, this);
1927 }
1928
1929
1930
1931
1932
1933
1934
1935
1936 public void addDomChangeListener(final DomChangeListener listener) {
1937 WebAssert.notNull("listener", listener);
1938
1939 synchronized (this) {
1940 if (domListeners_ == null) {
1941 domListeners_ = new ArrayList<>();
1942 }
1943 domListeners_.add(listener);
1944
1945 final SgmlPage page = getPage();
1946 if (page != null) {
1947 page.domChangeListenerAdded();
1948 }
1949 }
1950 }
1951
1952
1953
1954
1955
1956
1957
1958
1959 public void removeDomChangeListener(final DomChangeListener listener) {
1960 WebAssert.notNull("listener", listener);
1961
1962 synchronized (this) {
1963 if (domListeners_ != null) {
1964 domListeners_.remove(listener);
1965 }
1966 }
1967 }
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978 protected void fireNodeAdded(final DomNode parentNode, final DomNode addedNode) {
1979 DomChangeEvent event = null;
1980
1981 DomNode toInform = this;
1982 while (toInform != null) {
1983 if (toInform.domListeners_ != null) {
1984 final List<DomChangeListener> listeners;
1985 synchronized (toInform) {
1986 listeners = new ArrayList<>(toInform.domListeners_);
1987 }
1988
1989 if (event == null) {
1990 event = new DomChangeEvent(parentNode, addedNode);
1991 }
1992 for (final DomChangeListener domChangeListener : listeners) {
1993 domChangeListener.nodeAdded(event);
1994 }
1995 }
1996
1997 toInform = toInform.getParentNode();
1998 }
1999 }
2000
2001
2002
2003
2004
2005
2006
2007
2008 public void addCharacterDataChangeListener(final CharacterDataChangeListener listener) {
2009 WebAssert.notNull("listener", listener);
2010
2011 synchronized (this) {
2012 if (characterDataListeners_ == null) {
2013 characterDataListeners_ = new ArrayList<>();
2014 }
2015 characterDataListeners_.add(listener);
2016
2017 final SgmlPage page = getPage();
2018 if (page != null) {
2019 page.characterDataChangeListenerAdded();
2020 }
2021 }
2022 }
2023
2024
2025
2026
2027
2028
2029
2030
2031 public void removeCharacterDataChangeListener(final CharacterDataChangeListener listener) {
2032 WebAssert.notNull("listener", listener);
2033
2034 synchronized (this) {
2035 if (characterDataListeners_ != null) {
2036 characterDataListeners_.remove(listener);
2037 }
2038 }
2039 }
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049 protected void fireCharacterDataChanged(final DomCharacterData characterData, final String oldValue) {
2050 CharacterDataChangeEvent event = null;
2051
2052 DomNode toInform = this;
2053 while (toInform != null) {
2054 if (toInform.characterDataListeners_ != null) {
2055 final List<CharacterDataChangeListener> listeners;
2056 synchronized (toInform) {
2057 listeners = new ArrayList<>(toInform.characterDataListeners_);
2058 }
2059
2060 if (event == null) {
2061 event = new CharacterDataChangeEvent(characterData, oldValue);
2062 }
2063 for (final CharacterDataChangeListener domChangeListener : listeners) {
2064 domChangeListener.characterDataChanged(event);
2065 }
2066 }
2067
2068 toInform = toInform.getParentNode();
2069 }
2070 }
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081 protected void fireNodeDeleted(final DomNode parentNode, final DomNode deletedNode) {
2082 DomChangeEvent event = null;
2083
2084 DomNode toInform = this;
2085 while (toInform != null) {
2086 if (toInform.domListeners_ != null) {
2087 final List<DomChangeListener> listeners;
2088 synchronized (toInform) {
2089 listeners = new ArrayList<>(toInform.domListeners_);
2090 }
2091
2092 if (event == null) {
2093 event = new DomChangeEvent(parentNode, deletedNode);
2094 }
2095 for (final DomChangeListener domChangeListener : listeners) {
2096 domChangeListener.nodeDeleted(event);
2097 }
2098 }
2099
2100 toInform = toInform.getParentNode();
2101 }
2102 }
2103
2104
2105
2106
2107
2108
2109
2110 public DomNodeList<DomNode> querySelectorAll(final String selectors) {
2111 try {
2112 final WebClient webClient = getPage().getWebClient();
2113 final SelectorList selectorList = getSelectorList(selectors, webClient);
2114
2115 final List<DomNode> elements = new ArrayList<>();
2116 if (selectorList != null) {
2117 for (final DomElement child : getDomElementDescendants()) {
2118 for (final Selector selector : selectorList) {
2119 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, child, null, true, true)) {
2120 elements.add(child);
2121 break;
2122 }
2123 }
2124 }
2125 }
2126 return new StaticDomNodeList(elements);
2127 }
2128 catch (final IOException e) {
2129 throw new CSSException("Error parsing CSS selectors from '" + selectors + "': " + e.getMessage(), e);
2130 }
2131 }
2132
2133
2134
2135
2136
2137
2138
2139
2140 protected SelectorList getSelectorList(final String selectors, final WebClient webClient)
2141 throws IOException {
2142
2143
2144 try (PooledCSS3Parser pooledParser = webClient.getCSS3Parser()) {
2145 final CSSOMParser parser = new CSSOMParser(pooledParser);
2146 final CheckErrorHandler errorHandler = new CheckErrorHandler();
2147 parser.setErrorHandler(errorHandler);
2148
2149 final SelectorList selectorList = parser.parseSelectors(selectors);
2150
2151 if (errorHandler.error() != null) {
2152 throw new CSSException("Invalid selectors: '" + selectors + "'", errorHandler.error());
2153 }
2154
2155 if (selectorList != null) {
2156 CssStyleSheet.validateSelectors(selectorList, this);
2157
2158 }
2159 return selectorList;
2160 }
2161 }
2162
2163
2164
2165
2166
2167
2168
2169 @SuppressWarnings("unchecked")
2170 public <N extends DomNode> N querySelector(final String selectors) {
2171 final DomNodeList<DomNode> list = querySelectorAll(selectors);
2172 if (!list.isEmpty()) {
2173 return (N) list.get(0);
2174 }
2175 return null;
2176 }
2177
2178
2179
2180
2181
2182
2183
2184 public boolean isAttachedToPage() {
2185 return attachedToPage_;
2186 }
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197 public void processImportNode(final org.htmlunit.javascript.host.dom.Document doc) {
2198 page_ = (SgmlPage) doc.getDomNodeOrDie();
2199 }
2200
2201
2202
2203
2204
2205
2206
2207
2208 public boolean hasFeature(final BrowserVersionFeatures feature) {
2209 return getPage().getWebClient().getBrowserVersion().hasFeature(feature);
2210 }
2211
2212 private static final class CheckErrorHandler implements CSSErrorHandler {
2213 private CSSParseException error_;
2214
2215 CSSParseException error() {
2216 return error_;
2217 }
2218
2219 @Override
2220 public void warning(final CSSParseException exception) throws CSSException {
2221
2222 }
2223
2224 @Override
2225 public void fatalError(final CSSParseException exception) throws CSSException {
2226 error_ = exception;
2227 }
2228
2229 @Override
2230 public void error(final CSSParseException exception) throws CSSException {
2231 error_ = exception;
2232 }
2233 }
2234
2235
2236
2237
2238
2239
2240
2241 public boolean handles(final Event event) {
2242 return true;
2243 }
2244
2245
2246
2247
2248
2249
2250
2251 public DomElement getPreviousElementSibling() {
2252 DomNode node = getPreviousSibling();
2253 while (node != null && !(node instanceof DomElement)) {
2254 node = node.getPreviousSibling();
2255 }
2256 return (DomElement) node;
2257 }
2258
2259
2260
2261
2262
2263
2264
2265 public DomElement getNextElementSibling() {
2266 DomNode node = getNextSibling();
2267 while (node != null && !(node instanceof DomElement)) {
2268 node = node.getNextSibling();
2269 }
2270 return (DomElement) node;
2271 }
2272
2273
2274
2275
2276
2277 public DomElement closest(final String selectorString) {
2278 try {
2279 final WebClient webClient = getPage().getWebClient();
2280 final SelectorList selectorList = getSelectorList(selectorString, webClient);
2281
2282 DomNode current = this;
2283 if (selectorList != null) {
2284 do {
2285 for (final Selector selector : selectorList) {
2286 final DomElement elem = (DomElement) current;
2287 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, elem, null, true, true)) {
2288 return elem;
2289 }
2290 }
2291
2292 do {
2293 current = current.getParentNode();
2294 }
2295 while (current != null && !(current instanceof DomElement));
2296 }
2297 while (current != null);
2298 }
2299 return null;
2300 }
2301 catch (final IOException e) {
2302 throw new CSSException("Error parsing CSS selectors from '" + selectorString + "': " + e.getMessage(), e);
2303 }
2304 }
2305
2306
2307
2308
2309 private static final class ReadOnlyEmptyNamedNodeMapImpl implements NamedNodeMap, Serializable {
2310
2311
2312
2313
2314 @Override
2315 public int getLength() {
2316 return 0;
2317 }
2318
2319
2320
2321
2322 @Override
2323 public DomAttr getNamedItem(final String name) {
2324 return null;
2325 }
2326
2327
2328
2329
2330 @Override
2331 public Node getNamedItemNS(final String namespaceURI, final String localName) {
2332 return null;
2333 }
2334
2335
2336
2337
2338 @Override
2339 public Node item(final int index) {
2340 return null;
2341 }
2342
2343
2344
2345
2346 @Override
2347 public Node removeNamedItem(final String name) throws DOMException {
2348 return null;
2349 }
2350
2351
2352
2353
2354 @Override
2355 public Node removeNamedItemNS(final String namespaceURI, final String localName) {
2356 return null;
2357 }
2358
2359
2360
2361
2362 @Override
2363 public DomAttr setNamedItem(final Node node) {
2364 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItem");
2365 }
2366
2367
2368
2369
2370 @Override
2371 public Node setNamedItemNS(final Node node) throws DOMException {
2372 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItemNS");
2373 }
2374 }
2375 }