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 if (parent_ != null && parent_.firstChild_ == this) {
1165 parent_.firstChild_ = nextSibling_;
1166 }
1167 else if (previousSibling_ != null && previousSibling_.nextSibling_ == this) {
1168 previousSibling_.nextSibling_ = nextSibling_;
1169 }
1170 if (nextSibling_ != null && nextSibling_.previousSibling_ == this) {
1171 nextSibling_.previousSibling_ = previousSibling_;
1172 }
1173 if (parent_ != null && this == parent_.getLastChild()) {
1174 parent_.firstChild_.previousSibling_ = previousSibling_;
1175 }
1176
1177 nextSibling_ = null;
1178 previousSibling_ = null;
1179 parent_ = null;
1180 attachedToPage_ = false;
1181 for (final DomNode descendant : getDescendants()) {
1182 descendant.attachedToPage_ = false;
1183 }
1184 }
1185
1186 private void fireRemoval(final DomNode exParent) {
1187 final SgmlPage page = getPage();
1188 if (page instanceof HtmlPage) {
1189
1190
1191 parent_ = exParent;
1192 ((HtmlPage) page).notifyNodeRemoved(this);
1193 parent_ = null;
1194 }
1195
1196 if (exParent != null && (page == null || page.isDomChangeListenerInUse())) {
1197 fireNodeDeleted(exParent, this);
1198
1199 exParent.fireNodeDeleted(exParent, this);
1200 }
1201 }
1202
1203
1204
1205
1206 @Override
1207 public Node replaceChild(final Node newChild, final Node oldChild) {
1208 if (oldChild.getParentNode() != this) {
1209 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1210 }
1211 ((DomNode) oldChild).replace((DomNode) newChild);
1212 return oldChild;
1213 }
1214
1215
1216
1217
1218
1219
1220 public void replace(final DomNode newNode) {
1221 if (newNode != this) {
1222 final DomNode exParent = parent_;
1223 final DomNode exNextSibling = nextSibling_;
1224
1225 remove();
1226
1227 exParent.insertBefore(newNode, exNextSibling);
1228 }
1229 }
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240 public void quietlyRemoveAndMoveChildrenTo(final DomNode destination) {
1241 if (destination.getPage() != getPage()) {
1242 throw new RuntimeException("Cannot perform quiet move on nodes from different pages.");
1243 }
1244 for (final DomNode child : getChildren()) {
1245 if (child != destination) {
1246 child.basicRemove();
1247 destination.basicAppend(child);
1248 }
1249 }
1250 basicRemove();
1251 }
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265 protected void checkChildHierarchy(final Node newChild) throws DOMException {
1266 Node parentNode = this;
1267 while (parentNode != null) {
1268 if (parentNode == newChild) {
1269 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Child node is already a parent.");
1270 }
1271 parentNode = parentNode.getParentNode();
1272 }
1273 final Document thisDocument = getOwnerDocument();
1274 final Document childDocument = newChild.getOwnerDocument();
1275 if (childDocument != thisDocument && childDocument != null) {
1276 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Child node " + newChild.getNodeName()
1277 + " is not in the same Document as this " + getNodeName() + ".");
1278 }
1279 }
1280
1281
1282
1283
1284
1285
1286
1287 protected void onAddedToPage() {
1288 if (firstChild_ != null) {
1289 for (final DomNode child : getChildren()) {
1290 child.onAddedToPage();
1291 }
1292 }
1293 }
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303 public void onAllChildrenAddedToPage(final boolean postponed) {
1304
1305 }
1306
1307
1308
1309
1310
1311
1312
1313 protected void onAddedToDocumentFragment() {
1314 if (firstChild_ != null) {
1315 for (final DomNode child : getChildren()) {
1316 child.onAddedToDocumentFragment();
1317 }
1318 }
1319 }
1320
1321
1322
1323
1324 public final Iterable<DomNode> getChildren() {
1325 return () -> new ChildIterator(firstChild_);
1326 }
1327
1328
1329
1330
1331 protected static class ChildIterator implements Iterator<DomNode> {
1332
1333 private DomNode nextNode_;
1334 private DomNode currentNode_;
1335
1336 public ChildIterator(final DomNode nextNode) {
1337 nextNode_ = nextNode;
1338 }
1339
1340
1341 @Override
1342 public boolean hasNext() {
1343 return nextNode_ != null;
1344 }
1345
1346
1347 @Override
1348 public DomNode next() {
1349 if (nextNode_ != null) {
1350 currentNode_ = nextNode_;
1351 nextNode_ = nextNode_.nextSibling_;
1352 return currentNode_;
1353 }
1354 throw new NoSuchElementException();
1355 }
1356
1357
1358 @Override
1359 public void remove() {
1360 if (currentNode_ == null) {
1361 throw new IllegalStateException();
1362 }
1363 currentNode_.remove();
1364 }
1365 }
1366
1367
1368
1369
1370
1371
1372
1373 public final Iterable<DomNode> getDescendants() {
1374 return () -> new DescendantDomNodesIterator();
1375 }
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385 public final Iterable<HtmlElement> getHtmlElementDescendants() {
1386 return () -> new DescendantHtmlElementsIterator();
1387 }
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397 public final Iterable<DomElement> getDomElementDescendants() {
1398 return () -> new DescendantDomElementsIterator();
1399 }
1400
1401
1402
1403
1404 protected final class DescendantDomNodesIterator implements Iterator<DomNode> {
1405 private DomNode currentNode_;
1406 private DomNode nextNode_;
1407
1408
1409
1410
1411 public DescendantDomNodesIterator() {
1412 nextNode_ = DomNode.this.getFirstChild();
1413 }
1414
1415
1416 @Override
1417 public boolean hasNext() {
1418 return nextNode_ != null;
1419 }
1420
1421
1422 @Override
1423 public DomNode next() {
1424 return nextNode();
1425 }
1426
1427
1428 @Override
1429 public void remove() {
1430 if (currentNode_ == null) {
1431 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1432 }
1433 final DomNode current = currentNode_;
1434 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1435 next();
1436 }
1437 current.remove();
1438 }
1439
1440
1441
1442
1443 public DomNode nextNode() {
1444 currentNode_ = nextNode_;
1445
1446 DomNode next = nextNode_.getFirstChild();
1447 if (next == null) {
1448 next = nextNode_.getNextSibling();
1449 }
1450 if (next == null) {
1451 next = getNextElementUpwards(nextNode_);
1452 }
1453 nextNode_ = next;
1454
1455 return currentNode_;
1456 }
1457
1458 private DomNode getNextElementUpwards(final DomNode startingNode) {
1459 if (startingNode == DomNode.this) {
1460 return null;
1461 }
1462
1463 DomNode parent = startingNode.getParentNode();
1464 while (parent != null && parent != DomNode.this) {
1465 final DomNode next = parent.getNextSibling();
1466 if (next != null) {
1467 return next;
1468 }
1469 parent = parent.getParentNode();
1470 }
1471 return null;
1472 }
1473 }
1474
1475
1476
1477
1478 protected final class DescendantDomElementsIterator implements Iterator<DomElement> {
1479 private DomNode currentNode_;
1480 private DomNode nextNode_;
1481
1482
1483
1484
1485 public DescendantDomElementsIterator() {
1486 nextNode_ = getFirstChildElement(DomNode.this);
1487 }
1488
1489
1490 @Override
1491 public boolean hasNext() {
1492 return nextNode_ != null;
1493 }
1494
1495
1496 @Override
1497 public DomElement next() {
1498 return nextNode();
1499 }
1500
1501
1502 @Override
1503 public void remove() {
1504 if (currentNode_ == null) {
1505 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1506 }
1507 final DomNode current = currentNode_;
1508 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1509 next();
1510 }
1511 current.remove();
1512 }
1513
1514
1515
1516
1517 public DomElement nextNode() {
1518 currentNode_ = nextNode_;
1519
1520 DomNode next = getFirstChildElement(nextNode_);
1521 if (next == null) {
1522 next = getNextDomSibling(nextNode_);
1523 }
1524 if (next == null) {
1525 next = getNextElementUpwards(nextNode_);
1526 }
1527 nextNode_ = next;
1528
1529 return (DomElement) currentNode_;
1530 }
1531
1532 private DomNode getNextElementUpwards(final DomNode startingNode) {
1533 if (startingNode == DomNode.this) {
1534 return null;
1535 }
1536
1537 DomNode parent = startingNode.getParentNode();
1538 while (parent != null && parent != DomNode.this) {
1539 DomNode next = parent.getNextSibling();
1540 while (next != null && !isAccepted(next)) {
1541 next = next.getNextSibling();
1542 }
1543 if (next != null) {
1544 return next;
1545 }
1546 parent = parent.getParentNode();
1547 }
1548 return null;
1549 }
1550
1551 private DomNode getFirstChildElement(final DomNode parent) {
1552 DomNode node = parent.getFirstChild();
1553 while (node != null && !isAccepted(node)) {
1554 node = node.getNextSibling();
1555 }
1556 return node;
1557 }
1558
1559
1560
1561
1562
1563
1564 private boolean isAccepted(final DomNode node) {
1565 return DomElement.class.isAssignableFrom(node.getClass());
1566 }
1567
1568 private DomNode getNextDomSibling(final DomNode element) {
1569 DomNode node = element.getNextSibling();
1570 while (node != null && !isAccepted(node)) {
1571 node = node.getNextSibling();
1572 }
1573 return node;
1574 }
1575 }
1576
1577
1578
1579
1580 protected final class DescendantHtmlElementsIterator implements Iterator<HtmlElement> {
1581 private DomNode currentNode_;
1582 private DomNode nextNode_;
1583
1584
1585
1586
1587 public DescendantHtmlElementsIterator() {
1588 nextNode_ = getFirstChildElement(DomNode.this);
1589 }
1590
1591
1592 @Override
1593 public boolean hasNext() {
1594 return nextNode_ != null;
1595 }
1596
1597
1598 @Override
1599 public HtmlElement next() {
1600 return nextNode();
1601 }
1602
1603
1604 @Override
1605 public void remove() {
1606 if (currentNode_ == null) {
1607 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1608 }
1609 final DomNode current = currentNode_;
1610 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1611 next();
1612 }
1613 current.remove();
1614 }
1615
1616
1617
1618
1619 public HtmlElement nextNode() {
1620 currentNode_ = nextNode_;
1621
1622 DomNode next = getFirstChildElement(nextNode_);
1623 if (next == null) {
1624 next = getNextDomSibling(nextNode_);
1625 }
1626 if (next == null) {
1627 next = getNextElementUpwards(nextNode_);
1628 }
1629 nextNode_ = next;
1630
1631 return (HtmlElement) currentNode_;
1632 }
1633
1634 private DomNode getNextElementUpwards(final DomNode startingNode) {
1635 if (startingNode == DomNode.this) {
1636 return null;
1637 }
1638
1639 DomNode parent = startingNode.getParentNode();
1640 while (parent != null && parent != DomNode.this) {
1641 DomNode next = parent.getNextSibling();
1642 while (next != null && !isAccepted(next)) {
1643 next = next.getNextSibling();
1644 }
1645 if (next != null) {
1646 return next;
1647 }
1648 parent = parent.getParentNode();
1649 }
1650 return null;
1651 }
1652
1653 private DomNode getFirstChildElement(final DomNode parent) {
1654 DomNode node = parent.getFirstChild();
1655 while (node != null && !isAccepted(node)) {
1656 node = node.getNextSibling();
1657 }
1658 return node;
1659 }
1660
1661
1662
1663
1664
1665
1666 private boolean isAccepted(final DomNode node) {
1667 return HtmlElement.class.isAssignableFrom(node.getClass());
1668 }
1669
1670 private DomNode getNextDomSibling(final DomNode element) {
1671 DomNode node = element.getNextSibling();
1672 while (node != null && !isAccepted(node)) {
1673 node = node.getNextSibling();
1674 }
1675 return node;
1676 }
1677 }
1678
1679
1680
1681
1682
1683 public String getReadyState() {
1684 return readyState_;
1685 }
1686
1687
1688
1689
1690
1691 public void setReadyState(final String state) {
1692 readyState_ = state;
1693 }
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709 public <T> List<T> getByXPath(final String xpathExpr) {
1710 return XPathHelper.getByXPath(this, xpathExpr, null);
1711 }
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722 public List<?> getByXPath(final String xpathExpr, final PrefixResolver resolver) {
1723 return XPathHelper.getByXPath(this, xpathExpr, resolver);
1724 }
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736 public <X> X getFirstByXPath(final String xpathExpr) {
1737 return getFirstByXPath(xpathExpr, null);
1738 }
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751 @SuppressWarnings("unchecked")
1752 public <X> X getFirstByXPath(final String xpathExpr, final PrefixResolver resolver) {
1753 final List<?> results = getByXPath(xpathExpr, resolver);
1754 if (results.isEmpty()) {
1755 return null;
1756 }
1757 return (X) results.get(0);
1758 }
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771 public String getCanonicalXPath() {
1772 throw new RuntimeException("Method getCanonicalXPath() not implemented for nodes of type " + getNodeType());
1773 }
1774
1775
1776
1777
1778
1779 protected void notifyIncorrectness(final String message) {
1780 final WebClient client = getPage().getEnclosingWindow().getWebClient();
1781 final IncorrectnessListener incorrectnessListener = client.getIncorrectnessListener();
1782 incorrectnessListener.notify(message, this);
1783 }
1784
1785
1786
1787
1788
1789
1790
1791
1792 public void addDomChangeListener(final DomChangeListener listener) {
1793 WebAssert.notNull("listener", listener);
1794
1795 synchronized (this) {
1796 if (domListeners_ == null) {
1797 domListeners_ = new ArrayList<>();
1798 }
1799 domListeners_.add(listener);
1800
1801 final SgmlPage page = getPage();
1802 if (page != null) {
1803 page.domChangeListenerAdded();
1804 }
1805 }
1806 }
1807
1808
1809
1810
1811
1812
1813
1814
1815 public void removeDomChangeListener(final DomChangeListener listener) {
1816 WebAssert.notNull("listener", listener);
1817
1818 synchronized (this) {
1819 if (domListeners_ != null) {
1820 domListeners_.remove(listener);
1821 }
1822 }
1823 }
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834 protected void fireNodeAdded(final DomNode parentNode, final DomNode addedNode) {
1835 DomChangeEvent event = null;
1836
1837 DomNode toInform = this;
1838 while (toInform != null) {
1839 if (toInform.domListeners_ != null) {
1840 final List<DomChangeListener> listeners;
1841 synchronized (toInform) {
1842 listeners = new ArrayList<>(toInform.domListeners_);
1843 }
1844
1845 if (event == null) {
1846 event = new DomChangeEvent(parentNode, addedNode);
1847 }
1848 for (final DomChangeListener domChangeListener : listeners) {
1849 domChangeListener.nodeAdded(event);
1850 }
1851 }
1852
1853 toInform = toInform.getParentNode();
1854 }
1855 }
1856
1857
1858
1859
1860
1861
1862
1863
1864 public void addCharacterDataChangeListener(final CharacterDataChangeListener listener) {
1865 WebAssert.notNull("listener", listener);
1866
1867 synchronized (this) {
1868 if (characterDataListeners_ == null) {
1869 characterDataListeners_ = new ArrayList<>();
1870 }
1871 characterDataListeners_.add(listener);
1872
1873 final SgmlPage page = getPage();
1874 if (page != null) {
1875 page.characterDataChangeListenerAdded();
1876 }
1877 }
1878 }
1879
1880
1881
1882
1883
1884
1885
1886
1887 public void removeCharacterDataChangeListener(final CharacterDataChangeListener listener) {
1888 WebAssert.notNull("listener", listener);
1889
1890 synchronized (this) {
1891 if (characterDataListeners_ != null) {
1892 characterDataListeners_.remove(listener);
1893 }
1894 }
1895 }
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905 protected void fireCharacterDataChanged(final DomCharacterData characterData, final String oldValue) {
1906 CharacterDataChangeEvent event = null;
1907
1908 DomNode toInform = this;
1909 while (toInform != null) {
1910 if (toInform.characterDataListeners_ != null) {
1911 final List<CharacterDataChangeListener> listeners;
1912 synchronized (toInform) {
1913 listeners = new ArrayList<>(toInform.characterDataListeners_);
1914 }
1915
1916 if (event == null) {
1917 event = new CharacterDataChangeEvent(characterData, oldValue);
1918 }
1919 for (final CharacterDataChangeListener domChangeListener : listeners) {
1920 domChangeListener.characterDataChanged(event);
1921 }
1922 }
1923
1924 toInform = toInform.getParentNode();
1925 }
1926 }
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937 protected void fireNodeDeleted(final DomNode parentNode, final DomNode deletedNode) {
1938 DomChangeEvent event = null;
1939
1940 DomNode toInform = this;
1941 while (toInform != null) {
1942 if (toInform.domListeners_ != null) {
1943 final List<DomChangeListener> listeners;
1944 synchronized (toInform) {
1945 listeners = new ArrayList<>(toInform.domListeners_);
1946 }
1947
1948 if (event == null) {
1949 event = new DomChangeEvent(parentNode, deletedNode);
1950 }
1951 for (final DomChangeListener domChangeListener : listeners) {
1952 domChangeListener.nodeDeleted(event);
1953 }
1954 }
1955
1956 toInform = toInform.getParentNode();
1957 }
1958 }
1959
1960
1961
1962
1963
1964
1965
1966 public DomNodeList<DomNode> querySelectorAll(final String selectors) {
1967 try {
1968 final WebClient webClient = getPage().getWebClient();
1969 final SelectorList selectorList = getSelectorList(selectors, webClient);
1970
1971 final List<DomNode> elements = new ArrayList<>();
1972 if (selectorList != null) {
1973 for (final DomElement child : getDomElementDescendants()) {
1974 for (final Selector selector : selectorList) {
1975 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, child, null, true, true)) {
1976 elements.add(child);
1977 break;
1978 }
1979 }
1980 }
1981 }
1982 return new StaticDomNodeList(elements);
1983 }
1984 catch (final IOException e) {
1985 throw new CSSException("Error parsing CSS selectors from '" + selectors + "': " + e.getMessage(), e);
1986 }
1987 }
1988
1989
1990
1991
1992
1993
1994
1995
1996 protected SelectorList getSelectorList(final String selectors, final WebClient webClient)
1997 throws IOException {
1998
1999
2000 try (PooledCSS3Parser pooledParser = webClient.getCSS3Parser()) {
2001 final CSSOMParser parser = new CSSOMParser(pooledParser);
2002 final CheckErrorHandler errorHandler = new CheckErrorHandler();
2003 parser.setErrorHandler(errorHandler);
2004
2005 final SelectorList selectorList = parser.parseSelectors(selectors);
2006
2007 if (errorHandler.error() != null) {
2008 throw new CSSException("Invalid selectors: '" + selectors + "'", errorHandler.error());
2009 }
2010
2011 if (selectorList != null) {
2012 CssStyleSheet.validateSelectors(selectorList, this);
2013
2014 }
2015 return selectorList;
2016 }
2017 }
2018
2019
2020
2021
2022
2023
2024
2025 @SuppressWarnings("unchecked")
2026 public <N extends DomNode> N querySelector(final String selectors) {
2027 final DomNodeList<DomNode> list = querySelectorAll(selectors);
2028 if (!list.isEmpty()) {
2029 return (N) list.get(0);
2030 }
2031 return null;
2032 }
2033
2034
2035
2036
2037
2038
2039
2040 public boolean isAttachedToPage() {
2041 return attachedToPage_;
2042 }
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053 public void processImportNode(final org.htmlunit.javascript.host.dom.Document doc) {
2054 page_ = (SgmlPage) doc.getDomNodeOrDie();
2055 }
2056
2057
2058
2059
2060
2061
2062
2063
2064 public boolean hasFeature(final BrowserVersionFeatures feature) {
2065 return getPage().getWebClient().getBrowserVersion().hasFeature(feature);
2066 }
2067
2068 private static final class CheckErrorHandler implements CSSErrorHandler {
2069 private CSSParseException error_;
2070
2071 CSSParseException error() {
2072 return error_;
2073 }
2074
2075 @Override
2076 public void warning(final CSSParseException exception) throws CSSException {
2077
2078 }
2079
2080 @Override
2081 public void fatalError(final CSSParseException exception) throws CSSException {
2082 error_ = exception;
2083 }
2084
2085 @Override
2086 public void error(final CSSParseException exception) throws CSSException {
2087 error_ = exception;
2088 }
2089 }
2090
2091
2092
2093
2094
2095
2096
2097 public boolean handles(final Event event) {
2098 return true;
2099 }
2100
2101
2102
2103
2104
2105
2106
2107 public DomElement getPreviousElementSibling() {
2108 DomNode node = getPreviousSibling();
2109 while (node != null && !(node instanceof DomElement)) {
2110 node = node.getPreviousSibling();
2111 }
2112 return (DomElement) node;
2113 }
2114
2115
2116
2117
2118
2119
2120
2121 public DomElement getNextElementSibling() {
2122 DomNode node = getNextSibling();
2123 while (node != null && !(node instanceof DomElement)) {
2124 node = node.getNextSibling();
2125 }
2126 return (DomElement) node;
2127 }
2128
2129
2130
2131
2132
2133 public DomElement closest(final String selectorString) {
2134 try {
2135 final WebClient webClient = getPage().getWebClient();
2136 final SelectorList selectorList = getSelectorList(selectorString, webClient);
2137
2138 DomNode current = this;
2139 if (selectorList != null) {
2140 do {
2141 for (final Selector selector : selectorList) {
2142 final DomElement elem = (DomElement) current;
2143 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, elem, null, true, true)) {
2144 return elem;
2145 }
2146 }
2147
2148 do {
2149 current = current.getParentNode();
2150 }
2151 while (current != null && !(current instanceof DomElement));
2152 }
2153 while (current != null);
2154 }
2155 return null;
2156 }
2157 catch (final IOException e) {
2158 throw new CSSException("Error parsing CSS selectors from '" + selectorString + "': " + e.getMessage(), e);
2159 }
2160 }
2161
2162
2163
2164
2165 private static final class ReadOnlyEmptyNamedNodeMapImpl implements NamedNodeMap, Serializable {
2166
2167
2168
2169
2170 @Override
2171 public int getLength() {
2172 return 0;
2173 }
2174
2175
2176
2177
2178 @Override
2179 public DomAttr getNamedItem(final String name) {
2180 return null;
2181 }
2182
2183
2184
2185
2186 @Override
2187 public Node getNamedItemNS(final String namespaceURI, final String localName) {
2188 return null;
2189 }
2190
2191
2192
2193
2194 @Override
2195 public Node item(final int index) {
2196 return null;
2197 }
2198
2199
2200
2201
2202 @Override
2203 public Node removeNamedItem(final String name) throws DOMException {
2204 return null;
2205 }
2206
2207
2208
2209
2210 @Override
2211 public Node removeNamedItemNS(final String namespaceURI, final String localName) {
2212 return null;
2213 }
2214
2215
2216
2217
2218 @Override
2219 public DomAttr setNamedItem(final Node node) {
2220 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItem");
2221 }
2222
2223
2224
2225
2226 @Override
2227 public Node setNamedItemNS(final Node node) throws DOMException {
2228 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItemNS");
2229 }
2230 }
2231 }