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