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