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 if (charsetName != null && this instanceof HtmlHtml) {
793 printWriter.print("<?xml version=\"1.0\" encoding=\"");
794 printWriter.print(charsetName);
795 printWriter.print("\"?>\r\n");
796 }
797 printXml("", printWriter);
798 return stringWriter.toString();
799 }
800 }
801
802
803
804
805
806
807
808 protected void printXml(final String indent, final PrintWriter printWriter) {
809 printWriter.print(indent);
810 printWriter.print(this);
811 printWriter.print("\r\n");
812 printChildrenAsXml(indent, printWriter);
813 }
814
815
816
817
818
819
820
821 protected void printChildrenAsXml(final String indent, final PrintWriter printWriter) {
822 DomNode child = getFirstChild();
823 while (child != null) {
824 child.printXml(indent + " ", printWriter);
825 child = child.getNextSibling();
826 }
827 }
828
829
830
831
832 @Override
833 public String getNodeValue() {
834 return null;
835 }
836
837
838
839
840 @Override
841 public DomNode cloneNode(final boolean deep) {
842 final DomNode newnode;
843 try {
844 newnode = (DomNode) clone();
845 }
846 catch (final CloneNotSupportedException e) {
847 throw new IllegalStateException("Clone not supported for node [" + this + "]", e);
848 }
849
850 newnode.parent_ = null;
851 newnode.nextSibling_ = null;
852 newnode.previousSibling_ = null;
853 newnode.scriptObject_ = null;
854 newnode.firstChild_ = null;
855 newnode.attachedToPage_ = false;
856
857
858 if (deep) {
859 for (DomNode child = firstChild_; child != null; child = child.nextSibling_) {
860 newnode.appendChild(child.cloneNode(true));
861 }
862 }
863
864 return newnode;
865 }
866
867
868
869
870
871
872
873
874
875
876
877
878 @SuppressWarnings("unchecked")
879 public <T extends HtmlUnitScriptable> T getScriptableObject() {
880 if (scriptObject_ == null) {
881 final SgmlPage page = getPage();
882 if (this == page) {
883 final StringBuilder msg = new StringBuilder("No script object associated with the Page.");
884
885 msg.append(" class: '")
886 .append(page.getClass().getName())
887 .append('\'');
888 try {
889 msg.append(" url: '")
890 .append(page.getUrl()).append("' content: ")
891 .append(page.getWebResponse().getContentAsString());
892 }
893 catch (final Exception e) {
894
895 msg.append(" no details: '").append(e).append('\'');
896 }
897 throw new IllegalStateException(msg.toString());
898 }
899 scriptObject_ = page.getScriptableObject().makeScriptableFor(this);
900 }
901 return (T) scriptObject_;
902 }
903
904
905
906
907 @Override
908 public DomNode appendChild(final Node node) {
909 if (node == this) {
910 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Can not add not to itself " + this);
911 }
912 final DomNode domNode = (DomNode) node;
913 if (domNode.isAncestorOf(this)) {
914 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Can not add (grand)parent to itself " + this);
915 }
916
917 if (domNode instanceof DomDocumentFragment) {
918 final DomDocumentFragment fragment = (DomDocumentFragment) domNode;
919 for (final DomNode child : fragment.getChildren()) {
920 appendChild(child);
921 }
922 }
923 else {
924
925 if (domNode.getParentNode() != null) {
926 domNode.detach();
927 }
928
929 basicAppend(domNode);
930
931 fireAddition(domNode);
932 }
933
934 return domNode;
935 }
936
937
938
939
940
941
942
943 private void basicAppend(final DomNode node) {
944
945
946 node.setPage(getPage());
947 node.parent_ = this;
948
949 if (firstChild_ == null) {
950 firstChild_ = node;
951 }
952 else {
953 final DomNode last = getLastChild();
954 node.previousSibling_ = last;
955 node.nextSibling_ = null;
956
957 last.nextSibling_ = node;
958 }
959 firstChild_.previousSibling_ = node;
960 }
961
962
963
964
965 @Override
966 public Node insertBefore(final Node newChild, final Node refChild) {
967 if (newChild instanceof DomDocumentFragment) {
968 final DomDocumentFragment fragment = (DomDocumentFragment) newChild;
969 for (final DomNode child : fragment.getChildren()) {
970 insertBefore(child, refChild);
971 }
972 return newChild;
973 }
974
975 if (refChild == null) {
976 appendChild(newChild);
977 return newChild;
978 }
979
980 if (refChild.getParentNode() != this) {
981 throw new DOMException(DOMException.NOT_FOUND_ERR, "Reference node is not a child of this node.");
982 }
983
984 ((DomNode) refChild).insertBefore((DomNode) newChild);
985 return newChild;
986 }
987
988
989
990
991
992
993
994 public void insertBefore(final DomNode newNode) {
995 if (previousSibling_ == null) {
996 throw new IllegalStateException("Previous sibling for " + this + " is null.");
997 }
998
999 if (newNode == this) {
1000 return;
1001 }
1002
1003
1004 if (newNode.getParentNode() != null) {
1005 newNode.detach();
1006 }
1007
1008 basicInsertBefore(newNode);
1009
1010 fireAddition(newNode);
1011 }
1012
1013
1014
1015
1016
1017
1018
1019 private void basicInsertBefore(final DomNode node) {
1020
1021
1022 node.setPage(page_);
1023 node.parent_ = parent_;
1024 node.previousSibling_ = previousSibling_;
1025 node.nextSibling_ = this;
1026
1027 if (parent_.firstChild_ == this) {
1028 parent_.firstChild_ = node;
1029 }
1030 else {
1031 previousSibling_.nextSibling_ = node;
1032 }
1033 previousSibling_ = node;
1034 }
1035
1036 private void fireAddition(final DomNode domNode) {
1037 final boolean wasAlreadyAttached = domNode.isAttachedToPage();
1038 domNode.attachedToPage_ = isAttachedToPage();
1039
1040 final SgmlPage page = getPage();
1041 if (domNode.attachedToPage_) {
1042
1043 if (null != page && page.isHtmlPage()) {
1044 ((HtmlPage) page).notifyNodeAdded(domNode);
1045 }
1046
1047
1048 if (!domNode.isBodyParsed() && !wasAlreadyAttached) {
1049 if (domNode.getFirstChild() != null) {
1050 for (final Iterator<DomNode> iterator =
1051 domNode.new DescendantDomNodesIterator(); iterator.hasNext();) {
1052 final DomNode child = iterator.next();
1053 child.attachedToPage_ = true;
1054 child.onAllChildrenAddedToPage(true);
1055 }
1056 }
1057 domNode.onAllChildrenAddedToPage(true);
1058 }
1059 }
1060
1061 if (this instanceof DomDocumentFragment) {
1062 onAddedToDocumentFragment();
1063 }
1064
1065 if (page == null || page.isDomChangeListenerInUse()) {
1066 fireNodeAdded(this, domNode);
1067 }
1068 }
1069
1070
1071
1072
1073
1074 private boolean isBodyParsed() {
1075 return getStartLineNumber() != -1 && getEndLineNumber() == -1;
1076 }
1077
1078
1079
1080
1081
1082 private void setPage(final SgmlPage newPage) {
1083 if (page_ == newPage) {
1084 return;
1085 }
1086
1087 page_ = newPage;
1088 for (final DomNode node : getChildren()) {
1089 node.setPage(newPage);
1090 }
1091 }
1092
1093
1094
1095
1096 @Override
1097 public Node removeChild(final Node child) {
1098 if (child.getParentNode() != this) {
1099 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1100 }
1101 ((DomNode) child).remove();
1102 return child;
1103 }
1104
1105
1106
1107
1108 public void removeAllChildren() {
1109 while (getFirstChild() != null) {
1110 getFirstChild().remove();
1111 }
1112 }
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122 public void parseHtmlSnippet(final String source) throws SAXException, IOException {
1123 getPage().getWebClient().getPageCreator().getHtmlParser().parseFragment(this, source);
1124 }
1125
1126
1127
1128
1129 public void remove() {
1130
1131 detach();
1132 }
1133
1134
1135
1136
1137
1138
1139
1140 protected void detach() {
1141 final DomNode exParent = parent_;
1142
1143 basicRemove();
1144
1145 fireRemoval(exParent);
1146 }
1147
1148
1149
1150
1151 protected void basicRemove() {
1152 if (parent_ != null && parent_.firstChild_ == this) {
1153 parent_.firstChild_ = nextSibling_;
1154 }
1155 else if (previousSibling_ != null && previousSibling_.nextSibling_ == this) {
1156 previousSibling_.nextSibling_ = nextSibling_;
1157 }
1158 if (nextSibling_ != null && nextSibling_.previousSibling_ == this) {
1159 nextSibling_.previousSibling_ = previousSibling_;
1160 }
1161 if (parent_ != null && this == parent_.getLastChild()) {
1162 parent_.firstChild_.previousSibling_ = previousSibling_;
1163 }
1164
1165 nextSibling_ = null;
1166 previousSibling_ = null;
1167 parent_ = null;
1168 attachedToPage_ = false;
1169 for (final DomNode descendant : getDescendants()) {
1170 descendant.attachedToPage_ = false;
1171 }
1172 }
1173
1174 private void fireRemoval(final DomNode exParent) {
1175 final SgmlPage page = getPage();
1176 if (page != null && page instanceof HtmlPage) {
1177
1178
1179 parent_ = exParent;
1180 ((HtmlPage) page).notifyNodeRemoved(this);
1181 parent_ = null;
1182 }
1183
1184 if (exParent != null && (page == null || page.isDomChangeListenerInUse())) {
1185 fireNodeDeleted(exParent, this);
1186
1187 exParent.fireNodeDeleted(exParent, this);
1188 }
1189 }
1190
1191
1192
1193
1194 @Override
1195 public Node replaceChild(final Node newChild, final Node oldChild) {
1196 if (oldChild.getParentNode() != this) {
1197 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1198 }
1199 ((DomNode) oldChild).replace((DomNode) newChild);
1200 return oldChild;
1201 }
1202
1203
1204
1205
1206
1207
1208 public void replace(final DomNode newNode) {
1209 if (newNode != this) {
1210 final DomNode exParent = parent_;
1211 final DomNode exNextSibling = nextSibling_;
1212
1213 remove();
1214
1215 exParent.insertBefore(newNode, exNextSibling);
1216 }
1217 }
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228 public void quietlyRemoveAndMoveChildrenTo(final DomNode destination) {
1229 if (destination.getPage() != getPage()) {
1230 throw new RuntimeException("Cannot perform quiet move on nodes from different pages.");
1231 }
1232 for (final DomNode child : getChildren()) {
1233 if (child != destination) {
1234 child.basicRemove();
1235 destination.basicAppend(child);
1236 }
1237 }
1238 basicRemove();
1239 }
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253 protected void checkChildHierarchy(final Node newChild) throws DOMException {
1254 Node parentNode = this;
1255 while (parentNode != null) {
1256 if (parentNode == newChild) {
1257 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Child node is already a parent.");
1258 }
1259 parentNode = parentNode.getParentNode();
1260 }
1261 final Document thisDocument = getOwnerDocument();
1262 final Document childDocument = newChild.getOwnerDocument();
1263 if (childDocument != thisDocument && childDocument != null) {
1264 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Child node " + newChild.getNodeName()
1265 + " is not in the same Document as this " + getNodeName() + ".");
1266 }
1267 }
1268
1269
1270
1271
1272
1273
1274
1275 protected void onAddedToPage() {
1276 if (firstChild_ != null) {
1277 for (final DomNode child : getChildren()) {
1278 child.onAddedToPage();
1279 }
1280 }
1281 }
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291 public void onAllChildrenAddedToPage(final boolean postponed) {
1292
1293 }
1294
1295
1296
1297
1298
1299
1300
1301 protected void onAddedToDocumentFragment() {
1302 if (firstChild_ != null) {
1303 for (final DomNode child : getChildren()) {
1304 child.onAddedToDocumentFragment();
1305 }
1306 }
1307 }
1308
1309
1310
1311
1312 public final Iterable<DomNode> getChildren() {
1313 return () -> new ChildIterator(firstChild_);
1314 }
1315
1316
1317
1318
1319 protected static class ChildIterator implements Iterator<DomNode> {
1320
1321 private DomNode nextNode_;
1322 private DomNode currentNode_;
1323
1324 public ChildIterator(final DomNode nextNode) {
1325 nextNode_ = nextNode;
1326 }
1327
1328
1329 @Override
1330 public boolean hasNext() {
1331 return nextNode_ != null;
1332 }
1333
1334
1335 @Override
1336 public DomNode next() {
1337 if (nextNode_ != null) {
1338 currentNode_ = nextNode_;
1339 nextNode_ = nextNode_.nextSibling_;
1340 return currentNode_;
1341 }
1342 throw new NoSuchElementException();
1343 }
1344
1345
1346 @Override
1347 public void remove() {
1348 if (currentNode_ == null) {
1349 throw new IllegalStateException();
1350 }
1351 currentNode_.remove();
1352 }
1353 }
1354
1355
1356
1357
1358
1359
1360
1361 public final Iterable<DomNode> getDescendants() {
1362 return () -> new DescendantDomNodesIterator();
1363 }
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373 public final Iterable<HtmlElement> getHtmlElementDescendants() {
1374 return () -> new DescendantHtmlElementsIterator();
1375 }
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385 public final Iterable<DomElement> getDomElementDescendants() {
1386 return () -> new DescendantDomElementsIterator();
1387 }
1388
1389
1390
1391
1392
1393
1394
1395
1396 @Deprecated
1397 protected class DescendantElementsIterator<T extends DomNode> implements Iterator<T> {
1398
1399 private DomNode currentNode_;
1400 private DomNode nextNode_;
1401 private final Class<T> type_;
1402
1403
1404
1405
1406
1407 public DescendantElementsIterator(final Class<T> type) {
1408 type_ = type;
1409 nextNode_ = getFirstChildElement(DomNode.this);
1410 }
1411
1412
1413 @Override
1414 public boolean hasNext() {
1415 return nextNode_ != null;
1416 }
1417
1418
1419 @Override
1420 public T next() {
1421 return nextNode();
1422 }
1423
1424
1425 @Override
1426 public void remove() {
1427 if (currentNode_ == null) {
1428 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1429 }
1430 final DomNode current = currentNode_;
1431 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1432 next();
1433 }
1434 current.remove();
1435 }
1436
1437
1438
1439
1440 @SuppressWarnings("unchecked")
1441 public T nextNode() {
1442 currentNode_ = nextNode_;
1443 setNextElement();
1444 return (T) currentNode_;
1445 }
1446
1447 private void setNextElement() {
1448 DomNode next = getFirstChildElement(nextNode_);
1449 if (next == null) {
1450 next = getNextDomSibling(nextNode_);
1451 }
1452 if (next == null) {
1453 next = getNextElementUpwards(nextNode_);
1454 }
1455 nextNode_ = next;
1456 }
1457
1458 private DomNode getNextElementUpwards(final DomNode startingNode) {
1459 if (startingNode == DomNode.this) {
1460 return null;
1461 }
1462
1463 DomNode parent = startingNode.getParentNode();
1464 while (parent != null && parent != DomNode.this) {
1465 DomNode next = parent.getNextSibling();
1466 while (next != null && !isAccepted(next)) {
1467 next = next.getNextSibling();
1468 }
1469 if (next != null) {
1470 return next;
1471 }
1472 parent = parent.getParentNode();
1473 }
1474 return null;
1475 }
1476
1477 private DomNode getFirstChildElement(final DomNode parent) {
1478 DomNode node = parent.getFirstChild();
1479 while (node != null && !isAccepted(node)) {
1480 node = node.getNextSibling();
1481 }
1482 return node;
1483 }
1484
1485
1486
1487
1488
1489
1490 protected boolean isAccepted(final DomNode node) {
1491 return type_.isAssignableFrom(node.getClass());
1492 }
1493
1494 private DomNode getNextDomSibling(final DomNode element) {
1495 DomNode node = element.getNextSibling();
1496 while (node != null && !isAccepted(node)) {
1497 node = node.getNextSibling();
1498 }
1499 return node;
1500 }
1501 }
1502
1503
1504
1505
1506 protected final class DescendantDomNodesIterator implements Iterator<DomNode> {
1507 private DomNode currentNode_;
1508 private DomNode nextNode_;
1509
1510
1511
1512
1513 public DescendantDomNodesIterator() {
1514 nextNode_ = getFirstChildElement(DomNode.this);
1515 }
1516
1517
1518 @Override
1519 public boolean hasNext() {
1520 return nextNode_ != null;
1521 }
1522
1523
1524 @Override
1525 public DomNode next() {
1526 return nextNode();
1527 }
1528
1529
1530 @Override
1531 public void remove() {
1532 if (currentNode_ == null) {
1533 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1534 }
1535 final DomNode current = currentNode_;
1536 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1537 next();
1538 }
1539 current.remove();
1540 }
1541
1542
1543
1544
1545 @SuppressWarnings("unchecked")
1546 public DomNode nextNode() {
1547 currentNode_ = nextNode_;
1548
1549 DomNode next = getFirstChildElement(nextNode_);
1550 if (next == null) {
1551 next = getNextDomSibling(nextNode_);
1552 }
1553 if (next == null) {
1554 next = getNextElementUpwards(nextNode_);
1555 }
1556 nextNode_ = next;
1557
1558 return currentNode_;
1559 }
1560
1561 private DomNode getNextElementUpwards(final DomNode startingNode) {
1562 if (startingNode == DomNode.this) {
1563 return null;
1564 }
1565
1566 DomNode parent = startingNode.getParentNode();
1567 while (parent != null && parent != DomNode.this) {
1568 DomNode next = parent.getNextSibling();
1569 while (next != null && !isAccepted(next)) {
1570 next = next.getNextSibling();
1571 }
1572 if (next != null) {
1573 return next;
1574 }
1575 parent = parent.getParentNode();
1576 }
1577 return null;
1578 }
1579
1580 private DomNode getFirstChildElement(final DomNode parent) {
1581 DomNode node = parent.getFirstChild();
1582 while (node != null && !isAccepted(node)) {
1583 node = node.getNextSibling();
1584 }
1585 return node;
1586 }
1587
1588
1589
1590
1591
1592
1593 private boolean isAccepted(final DomNode node) {
1594 return DomNode.class.isAssignableFrom(node.getClass());
1595 }
1596
1597 private DomNode getNextDomSibling(final DomNode element) {
1598 DomNode node = element.getNextSibling();
1599 while (node != null && !isAccepted(node)) {
1600 node = node.getNextSibling();
1601 }
1602 return node;
1603 }
1604 }
1605
1606
1607
1608
1609 protected final class DescendantDomElementsIterator implements Iterator<DomElement> {
1610 private DomNode currentNode_;
1611 private DomNode nextNode_;
1612
1613
1614
1615
1616 public DescendantDomElementsIterator() {
1617 nextNode_ = getFirstChildElement(DomNode.this);
1618 }
1619
1620
1621 @Override
1622 public boolean hasNext() {
1623 return nextNode_ != null;
1624 }
1625
1626
1627 @Override
1628 public DomElement next() {
1629 return nextNode();
1630 }
1631
1632
1633 @Override
1634 public void remove() {
1635 if (currentNode_ == null) {
1636 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1637 }
1638 final DomNode current = currentNode_;
1639 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1640 next();
1641 }
1642 current.remove();
1643 }
1644
1645
1646
1647
1648 @SuppressWarnings("unchecked")
1649 public DomElement nextNode() {
1650 currentNode_ = nextNode_;
1651
1652 DomNode next = getFirstChildElement(nextNode_);
1653 if (next == null) {
1654 next = getNextDomSibling(nextNode_);
1655 }
1656 if (next == null) {
1657 next = getNextElementUpwards(nextNode_);
1658 }
1659 nextNode_ = next;
1660
1661 return (DomElement) currentNode_;
1662 }
1663
1664 private DomNode getNextElementUpwards(final DomNode startingNode) {
1665 if (startingNode == DomNode.this) {
1666 return null;
1667 }
1668
1669 DomNode parent = startingNode.getParentNode();
1670 while (parent != null && parent != DomNode.this) {
1671 DomNode next = parent.getNextSibling();
1672 while (next != null && !isAccepted(next)) {
1673 next = next.getNextSibling();
1674 }
1675 if (next != null) {
1676 return next;
1677 }
1678 parent = parent.getParentNode();
1679 }
1680 return null;
1681 }
1682
1683 private DomNode getFirstChildElement(final DomNode parent) {
1684 DomNode node = parent.getFirstChild();
1685 while (node != null && !isAccepted(node)) {
1686 node = node.getNextSibling();
1687 }
1688 return node;
1689 }
1690
1691
1692
1693
1694
1695
1696 private boolean isAccepted(final DomNode node) {
1697 return DomElement.class.isAssignableFrom(node.getClass());
1698 }
1699
1700 private DomNode getNextDomSibling(final DomNode element) {
1701 DomNode node = element.getNextSibling();
1702 while (node != null && !isAccepted(node)) {
1703 node = node.getNextSibling();
1704 }
1705 return node;
1706 }
1707 }
1708
1709
1710
1711
1712 protected final class DescendantHtmlElementsIterator implements Iterator<HtmlElement> {
1713 private DomNode currentNode_;
1714 private DomNode nextNode_;
1715
1716
1717
1718
1719 public DescendantHtmlElementsIterator() {
1720 nextNode_ = getFirstChildElement(DomNode.this);
1721 }
1722
1723
1724 @Override
1725 public boolean hasNext() {
1726 return nextNode_ != null;
1727 }
1728
1729
1730 @Override
1731 public HtmlElement next() {
1732 return nextNode();
1733 }
1734
1735
1736 @Override
1737 public void remove() {
1738 if (currentNode_ == null) {
1739 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1740 }
1741 final DomNode current = currentNode_;
1742 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1743 next();
1744 }
1745 current.remove();
1746 }
1747
1748
1749
1750
1751 @SuppressWarnings("unchecked")
1752 public HtmlElement nextNode() {
1753 currentNode_ = nextNode_;
1754
1755 DomNode next = getFirstChildElement(nextNode_);
1756 if (next == null) {
1757 next = getNextDomSibling(nextNode_);
1758 }
1759 if (next == null) {
1760 next = getNextElementUpwards(nextNode_);
1761 }
1762 nextNode_ = next;
1763
1764 return (HtmlElement) currentNode_;
1765 }
1766
1767 private DomNode getNextElementUpwards(final DomNode startingNode) {
1768 if (startingNode == DomNode.this) {
1769 return null;
1770 }
1771
1772 DomNode parent = startingNode.getParentNode();
1773 while (parent != null && parent != DomNode.this) {
1774 DomNode next = parent.getNextSibling();
1775 while (next != null && !isAccepted(next)) {
1776 next = next.getNextSibling();
1777 }
1778 if (next != null) {
1779 return next;
1780 }
1781 parent = parent.getParentNode();
1782 }
1783 return null;
1784 }
1785
1786 private DomNode getFirstChildElement(final DomNode parent) {
1787 DomNode node = parent.getFirstChild();
1788 while (node != null && !isAccepted(node)) {
1789 node = node.getNextSibling();
1790 }
1791 return node;
1792 }
1793
1794
1795
1796
1797
1798
1799 private boolean isAccepted(final DomNode node) {
1800 return HtmlElement.class.isAssignableFrom(node.getClass());
1801 }
1802
1803 private DomNode getNextDomSibling(final DomNode element) {
1804 DomNode node = element.getNextSibling();
1805 while (node != null && !isAccepted(node)) {
1806 node = node.getNextSibling();
1807 }
1808 return node;
1809 }
1810 }
1811
1812
1813
1814
1815
1816 public String getReadyState() {
1817 return readyState_;
1818 }
1819
1820
1821
1822
1823
1824 public void setReadyState(final String state) {
1825 readyState_ = state;
1826 }
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842 public <T> List<T> getByXPath(final String xpathExpr) {
1843 return XPathHelper.getByXPath(this, xpathExpr, null);
1844 }
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855 public List<?> getByXPath(final String xpathExpr, final PrefixResolver resolver) {
1856 return XPathHelper.getByXPath(this, xpathExpr, resolver);
1857 }
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869 public <X> X getFirstByXPath(final String xpathExpr) {
1870 return getFirstByXPath(xpathExpr, null);
1871 }
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884 @SuppressWarnings("unchecked")
1885 public <X> X getFirstByXPath(final String xpathExpr, final PrefixResolver resolver) {
1886 final List<?> results = getByXPath(xpathExpr, resolver);
1887 if (results.isEmpty()) {
1888 return null;
1889 }
1890 return (X) results.get(0);
1891 }
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904 public String getCanonicalXPath() {
1905 throw new RuntimeException("Method getCanonicalXPath() not implemented for nodes of type " + getNodeType());
1906 }
1907
1908
1909
1910
1911
1912 protected void notifyIncorrectness(final String message) {
1913 final WebClient client = getPage().getEnclosingWindow().getWebClient();
1914 final IncorrectnessListener incorrectnessListener = client.getIncorrectnessListener();
1915 incorrectnessListener.notify(message, this);
1916 }
1917
1918
1919
1920
1921
1922
1923
1924
1925 public void addDomChangeListener(final DomChangeListener listener) {
1926 WebAssert.notNull("listener", listener);
1927
1928 synchronized (this) {
1929 if (domListeners_ == null) {
1930 domListeners_ = new ArrayList<>();
1931 }
1932 domListeners_.add(listener);
1933
1934 final SgmlPage page = getPage();
1935 if (page != null) {
1936 page.domChangeListenerAdded();
1937 }
1938 }
1939 }
1940
1941
1942
1943
1944
1945
1946
1947
1948 public void removeDomChangeListener(final DomChangeListener listener) {
1949 WebAssert.notNull("listener", listener);
1950
1951 synchronized (this) {
1952 if (domListeners_ != null) {
1953 domListeners_.remove(listener);
1954 }
1955 }
1956 }
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967 protected void fireNodeAdded(final DomNode parentNode, final DomNode addedNode) {
1968 DomChangeEvent event = null;
1969
1970 DomNode toInform = this;
1971 while (toInform != null) {
1972 if (toInform.domListeners_ != null) {
1973 final List<DomChangeListener> listeners;
1974 synchronized (toInform) {
1975 listeners = new ArrayList<>(toInform.domListeners_);
1976 }
1977
1978 if (event == null) {
1979 event = new DomChangeEvent(parentNode, addedNode);
1980 }
1981 for (final DomChangeListener domChangeListener : listeners) {
1982 domChangeListener.nodeAdded(event);
1983 }
1984 }
1985
1986 toInform = toInform.getParentNode();
1987 }
1988 }
1989
1990
1991
1992
1993
1994
1995
1996
1997 public void addCharacterDataChangeListener(final CharacterDataChangeListener listener) {
1998 WebAssert.notNull("listener", listener);
1999
2000 synchronized (this) {
2001 if (characterDataListeners_ == null) {
2002 characterDataListeners_ = new ArrayList<>();
2003 }
2004 characterDataListeners_.add(listener);
2005
2006 final SgmlPage page = getPage();
2007 if (page != null) {
2008 page.characterDataChangeListenerAdded();
2009 }
2010 }
2011 }
2012
2013
2014
2015
2016
2017
2018
2019
2020 public void removeCharacterDataChangeListener(final CharacterDataChangeListener listener) {
2021 WebAssert.notNull("listener", listener);
2022
2023 synchronized (this) {
2024 if (characterDataListeners_ != null) {
2025 characterDataListeners_.remove(listener);
2026 }
2027 }
2028 }
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038 protected void fireCharacterDataChanged(final DomCharacterData characterData, final String oldValue) {
2039 CharacterDataChangeEvent event = null;
2040
2041 DomNode toInform = this;
2042 while (toInform != null) {
2043 if (toInform.characterDataListeners_ != null) {
2044 final List<CharacterDataChangeListener> listeners;
2045 synchronized (toInform) {
2046 listeners = new ArrayList<>(toInform.characterDataListeners_);
2047 }
2048
2049 if (event == null) {
2050 event = new CharacterDataChangeEvent(characterData, oldValue);
2051 }
2052 for (final CharacterDataChangeListener domChangeListener : listeners) {
2053 domChangeListener.characterDataChanged(event);
2054 }
2055 }
2056
2057 toInform = toInform.getParentNode();
2058 }
2059 }
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070 protected void fireNodeDeleted(final DomNode parentNode, final DomNode deletedNode) {
2071 DomChangeEvent event = null;
2072
2073 DomNode toInform = this;
2074 while (toInform != null) {
2075 if (toInform.domListeners_ != null) {
2076 final List<DomChangeListener> listeners;
2077 synchronized (toInform) {
2078 listeners = new ArrayList<>(toInform.domListeners_);
2079 }
2080
2081 if (event == null) {
2082 event = new DomChangeEvent(parentNode, deletedNode);
2083 }
2084 for (final DomChangeListener domChangeListener : listeners) {
2085 domChangeListener.nodeDeleted(event);
2086 }
2087 }
2088
2089 toInform = toInform.getParentNode();
2090 }
2091 }
2092
2093
2094
2095
2096
2097
2098
2099 public DomNodeList<DomNode> querySelectorAll(final String selectors) {
2100 try {
2101 final WebClient webClient = getPage().getWebClient();
2102 final SelectorList selectorList = getSelectorList(selectors, webClient);
2103
2104 final List<DomNode> elements = new ArrayList<>();
2105 if (selectorList != null) {
2106 for (final DomElement child : getDomElementDescendants()) {
2107 for (final Selector selector : selectorList) {
2108 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, child, null, true, true)) {
2109 elements.add(child);
2110 break;
2111 }
2112 }
2113 }
2114 }
2115 return new StaticDomNodeList(elements);
2116 }
2117 catch (final IOException e) {
2118 throw new CSSException("Error parsing CSS selectors from '" + selectors + "': " + e.getMessage(), e);
2119 }
2120 }
2121
2122
2123
2124
2125
2126
2127
2128
2129 protected SelectorList getSelectorList(final String selectors, final WebClient webClient)
2130 throws IOException {
2131
2132
2133 try (PooledCSS3Parser pooledParser = webClient.getCSS3Parser()) {
2134 final CSSOMParser parser = new CSSOMParser(pooledParser);
2135 final CheckErrorHandler errorHandler = new CheckErrorHandler();
2136 parser.setErrorHandler(errorHandler);
2137
2138 final SelectorList selectorList = parser.parseSelectors(selectors);
2139
2140 if (errorHandler.error() != null) {
2141 throw new CSSException("Invalid selectors: '" + selectors + "'", errorHandler.error());
2142 }
2143
2144 if (selectorList != null) {
2145 CssStyleSheet.validateSelectors(selectorList, this);
2146
2147 }
2148 return selectorList;
2149 }
2150 }
2151
2152
2153
2154
2155
2156
2157
2158 @SuppressWarnings("unchecked")
2159 public <N extends DomNode> N querySelector(final String selectors) {
2160 final DomNodeList<DomNode> list = querySelectorAll(selectors);
2161 if (!list.isEmpty()) {
2162 return (N) list.get(0);
2163 }
2164 return null;
2165 }
2166
2167
2168
2169
2170
2171
2172
2173 public boolean isAttachedToPage() {
2174 return attachedToPage_;
2175 }
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186 public void processImportNode(final org.htmlunit.javascript.host.dom.Document doc) {
2187 page_ = (SgmlPage) doc.getDomNodeOrDie();
2188 }
2189
2190
2191
2192
2193
2194
2195
2196
2197 public boolean hasFeature(final BrowserVersionFeatures feature) {
2198 return getPage().getWebClient().getBrowserVersion().hasFeature(feature);
2199 }
2200
2201 private static final class CheckErrorHandler implements CSSErrorHandler {
2202 private CSSParseException error_;
2203
2204 CSSParseException error() {
2205 return error_;
2206 }
2207
2208 @Override
2209 public void warning(final CSSParseException exception) throws CSSException {
2210
2211 }
2212
2213 @Override
2214 public void fatalError(final CSSParseException exception) throws CSSException {
2215 error_ = exception;
2216 }
2217
2218 @Override
2219 public void error(final CSSParseException exception) throws CSSException {
2220 error_ = exception;
2221 }
2222 }
2223
2224
2225
2226
2227
2228
2229
2230 public boolean handles(final Event event) {
2231 return true;
2232 }
2233
2234
2235
2236
2237
2238
2239
2240 public DomElement getPreviousElementSibling() {
2241 DomNode node = getPreviousSibling();
2242 while (node != null && !(node instanceof DomElement)) {
2243 node = node.getPreviousSibling();
2244 }
2245 return (DomElement) node;
2246 }
2247
2248
2249
2250
2251
2252
2253
2254 public DomElement getNextElementSibling() {
2255 DomNode node = getNextSibling();
2256 while (node != null && !(node instanceof DomElement)) {
2257 node = node.getNextSibling();
2258 }
2259 return (DomElement) node;
2260 }
2261
2262
2263
2264
2265
2266 public DomElement closest(final String selectorString) {
2267 try {
2268 final WebClient webClient = getPage().getWebClient();
2269 final SelectorList selectorList = getSelectorList(selectorString, webClient);
2270
2271 DomNode current = this;
2272 if (selectorList != null) {
2273 do {
2274 for (final Selector selector : selectorList) {
2275 final DomElement elem = (DomElement) current;
2276 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, elem, null, true, true)) {
2277 return elem;
2278 }
2279 }
2280
2281 do {
2282 current = current.getParentNode();
2283 }
2284 while (current != null && !(current instanceof DomElement));
2285 }
2286 while (current != null);
2287 }
2288 return null;
2289 }
2290 catch (final IOException e) {
2291 throw new CSSException("Error parsing CSS selectors from '" + selectorString + "': " + e.getMessage(), e);
2292 }
2293 }
2294
2295
2296
2297
2298 private static final class ReadOnlyEmptyNamedNodeMapImpl implements NamedNodeMap, Serializable {
2299 private ReadOnlyEmptyNamedNodeMapImpl() {
2300 super();
2301 }
2302
2303
2304
2305
2306 @Override
2307 public int getLength() {
2308 return 0;
2309 }
2310
2311
2312
2313
2314 @Override
2315 public DomAttr getNamedItem(final String name) {
2316 return null;
2317 }
2318
2319
2320
2321
2322 @Override
2323 public Node getNamedItemNS(final String namespaceURI, final String localName) {
2324 return null;
2325 }
2326
2327
2328
2329
2330 @Override
2331 public Node item(final int index) {
2332 return null;
2333 }
2334
2335
2336
2337
2338 @Override
2339 public Node removeNamedItem(final String name) throws DOMException {
2340 return null;
2341 }
2342
2343
2344
2345
2346 @Override
2347 public Node removeNamedItemNS(final String namespaceURI, final String localName) {
2348 return null;
2349 }
2350
2351
2352
2353
2354 @Override
2355 public DomAttr setNamedItem(final Node node) {
2356 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItem");
2357 }
2358
2359
2360
2361
2362 @Override
2363 public Node setNamedItemNS(final Node node) throws DOMException {
2364 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItemNS");
2365 }
2366 }
2367 }