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