001package org.jsoup.nodes; 002 003import ai.platon.pulsar.jsoup.ext.NodeExt; 004import org.jsoup.helper.Validate; 005import org.jsoup.internal.QuietAppendable; 006import org.jsoup.internal.StringUtil; 007import org.jsoup.parser.ParseSettings; 008import org.jsoup.select.NodeFilter; 009import org.jsoup.select.NodeVisitor; 010import org.jspecify.annotations.Nullable; 011 012import java.io.IOException; 013import java.util.ArrayList; 014import java.util.Arrays; 015import java.util.Collections; 016import java.util.Iterator; 017import java.util.LinkedList; 018import java.util.List; 019import java.util.function.Consumer; 020import java.util.stream.Stream; 021 022/** 023 The base, abstract Node model. {@link Element}, {@link Document}, {@link Comment}, {@link TextNode}, et al., 024 are instances of Node. 025 026 @author Jonathan Hedley, jonathan@hedley.net */ 027public abstract class Node implements Cloneable { 028 static final List<Node> EmptyNodes = Collections.emptyList(); 029 static final String EmptyString = ""; 030 @Nullable Element parentNode; // Nodes don't always have parents 031 int siblingIndex; 032 final NodeExt extension = new NodeExt(); 033 034 /** 035 * Default constructor. Doesn't set up base uri, children, or attributes; use with caution. 036 */ 037 protected Node() { 038 } 039 040 /** 041 Get the node name of this node. Use for debugging purposes and not logic switching (for that, use instanceof). 042 @return node name 043 */ 044 public abstract String nodeName(); 045 046 /** 047 Get the normalized name of this node. For node types other than Element, this is the same as {@link #nodeName()}. 048 For an Element, will be the lower-cased tag name. 049 @return normalized node name 050 @since 1.15.4. 051 */ 052 public String normalName() { 053 return nodeName(); 054 } 055 056 /** 057 Get the node's value. For a TextNode, the whole text; for a Comment, the comment data; for an Element, 058 wholeOwnText. Returns "" if there is no value. 059 @return the node's value 060 */ 061 public String nodeValue() { 062 return ""; 063 } 064 065 /** 066 Test if this node has the specified normalized name, in any namespace. 067 * @param normalName a normalized element name (e.g. {@code div}). 068 * @return true if the element's normal name matches exactly 069 * @since 1.17.2 070 */ 071 public boolean nameIs(String normalName) { 072 return normalName().equals(normalName); 073 } 074 075 /** 076 Test if this node's parent has the specified normalized name. 077 * @param normalName a normalized name (e.g. {@code div}). 078 * @return true if the parent element's normal name matches exactly 079 * @since 1.17.2 080 */ 081 public boolean parentNameIs(String normalName) { 082 return parentNode != null && parentNode.normalName().equals(normalName); 083 } 084 085 /** 086 Test if this node's parent is an Element with the specified normalized name and namespace. 087 * @param normalName a normalized element name (e.g. {@code div}). 088 * @param namespace the namespace 089 * @return true if the parent element's normal name matches exactly, and that element is in the specified namespace 090 * @since 1.17.2 091 */ 092 public boolean parentElementIs(String normalName, String namespace) { 093 return parentNode != null && parentNode instanceof Element 094 && ((Element) parentNode).elementIs(normalName, namespace); 095 } 096 097 /** 098 * Check if this Node has an actual Attributes object. 099 */ 100 protected abstract boolean hasAttributes(); 101 102 /** 103 Checks if this node has a parent. Nodes won't have parents if (e.g.) they are newly created and not added as a child 104 to an existing node, or if they are a {@link #shallowClone()}. In such cases, {@link #parent()} will return {@code null}. 105 @return if this node has a parent. 106 */ 107 public boolean hasParent() { 108 return parentNode != null; 109 } 110 111 /** 112 * Get an attribute's value by its key. <b>Case insensitive</b> 113 * <p> 114 * To get an absolute URL from an attribute that may be a relative URL, prefix the key with <code><b>abs:</b></code>, 115 * which is a shortcut to the {@link #absUrl} method. 116 * </p> 117 * E.g.: 118 * <blockquote><code>String url = a.attr("abs:href");</code></blockquote> 119 * 120 * @param attributeKey The attribute key. 121 * @return The attribute, or empty string if not present (to avoid nulls). 122 * @see #attributes() 123 * @see #hasAttr(String) 124 * @see #absUrl(String) 125 */ 126 public String attr(String attributeKey) { 127 Validate.notNull(attributeKey); 128 if (!hasAttributes()) 129 return EmptyString; 130 131 String val = attributes().getIgnoreCase(attributeKey); 132 if (val.length() > 0) 133 return val; 134 else if (attributeKey.startsWith("abs:")) 135 return absUrl(attributeKey.substring("abs:".length())); 136 else return ""; 137 } 138 139 /** 140 * Get each of the Element's attributes. 141 * @return attributes (which implements Iterable, with the same order as presented in the original HTML). 142 */ 143 public abstract Attributes attributes(); 144 145 /** 146 Get the number of attributes that this Node has. 147 @return the number of attributes 148 @since 1.14.2 149 */ 150 public int attributesSize() { 151 // added so that we can test how many attributes exist without implicitly creating the Attributes object 152 return hasAttributes() ? attributes().size() : 0; 153 } 154 155 /** 156 * Set an attribute (key=value). If the attribute already exists, it is replaced. The attribute key comparison is 157 * <b>case insensitive</b>. The key will be set with case sensitivity as set in the parser settings. 158 * @param attributeKey The attribute key. 159 * @param attributeValue The attribute value. 160 * @return this (for chaining) 161 */ 162 public Node attr(String attributeKey, String attributeValue) { 163 Document doc = ownerDocument(); 164 ParseSettings settings = doc != null ? doc.parser().settings() : ParseSettings.htmlDefault; 165 attributeKey = settings.normalizeAttribute(attributeKey); 166 attributes().putIgnoreCase(attributeKey, attributeValue); 167 return this; 168 } 169 170 /** 171 * Test if this Node has an attribute. <b>Case insensitive</b>. 172 * @param attributeKey The attribute key to check. 173 * @return true if the attribute exists, false if not. 174 */ 175 public boolean hasAttr(String attributeKey) { 176 Validate.notNull(attributeKey); 177 if (!hasAttributes()) 178 return false; 179 180 if (attributeKey.startsWith("abs:")) { 181 String key = attributeKey.substring("abs:".length()); 182 if (attributes().hasKeyIgnoreCase(key) && !absUrl(key).isEmpty()) 183 return true; 184 } 185 return attributes().hasKeyIgnoreCase(attributeKey); 186 } 187 188 /** 189 * Remove an attribute from this node. 190 * @param attributeKey The attribute to remove. 191 * @return this (for chaining) 192 */ 193 public Node removeAttr(String attributeKey) { 194 Validate.notNull(attributeKey); 195 if (hasAttributes()) 196 attributes().removeIgnoreCase(attributeKey); 197 return this; 198 } 199 200 /** 201 * Clear (remove) each of the attributes in this node. 202 * @return this, for chaining 203 */ 204 public Node clearAttributes() { 205 if (hasAttributes()) { 206 Iterator<Attribute> it = attributes().iterator(); 207 while (it.hasNext()) { 208 it.next(); 209 it.remove(); 210 } 211 } 212 return this; 213 } 214 215 /** 216 Get the base URI that applies to this node. Will return an empty string if not defined. Used to make relative links 217 absolute. 218 219 @return base URI 220 @see #absUrl 221 */ 222 public abstract String baseUri(); 223 224 /** 225 * Set the baseUri for just this node (not its descendants), if this Node tracks base URIs. 226 * @param baseUri new URI 227 */ 228 protected abstract void doSetBaseUri(String baseUri); 229 230 /** 231 Update the base URI of this node and all of its descendants. 232 @param baseUri base URI to set 233 */ 234 public void setBaseUri(final String baseUri) { 235 Validate.notNull(baseUri); 236 doSetBaseUri(baseUri); 237 } 238 239 /** 240 * Get an absolute URL from a URL attribute that may be relative (such as an <code><a href></code> or 241 * <code><img src></code>). 242 * <p> 243 * E.g.: <code>String absUrl = linkEl.absUrl("href");</code> 244 * </p> 245 * <p> 246 * If the attribute value is already absolute (i.e. it starts with a protocol, like 247 * <code>http://</code> or <code>https://</code> etc), and it successfully parses as a URL, the attribute is 248 * returned directly. Otherwise, it is treated as a URL relative to the element's {@link #baseUri}, and made 249 * absolute using that. 250 * </p> 251 * <p> 252 * As an alternate, you can use the {@link #attr} method with the <code>abs:</code> prefix, e.g.: 253 * <code>String absUrl = linkEl.attr("abs:href");</code> 254 * </p> 255 * 256 * @param attributeKey The attribute key 257 * @return An absolute URL if one could be made, or an empty string (not null) if the attribute was missing or 258 * could not be made successfully into a URL. 259 * @see #attr 260 * @see java.net.URL#URL(java.net.URL, String) 261 */ 262 public String absUrl(String attributeKey) { 263 Validate.notEmpty(attributeKey); 264 if (!(hasAttributes() && attributes().hasKeyIgnoreCase(attributeKey))) // not using hasAttr, so that we don't recurse down hasAttr->absUrl 265 return ""; 266 267 return StringUtil.resolve(baseUri(), attributes().getIgnoreCase(attributeKey)); 268 } 269 270 protected abstract List<Node> ensureChildNodes(); 271 272 /** 273 Get a child node by its 0-based index. 274 @param index index of child node 275 @return the child node at this index. 276 @throws IndexOutOfBoundsException if the index is out of bounds. 277 */ 278 public Node childNode(int index) { 279 return ensureChildNodes().get(index); 280 } 281 282 /** 283 Get this node's children. Presented as an unmodifiable list: new children can not be added, but the child nodes 284 themselves can be manipulated. 285 @return list of children. If no children, returns an empty list. 286 */ 287 public List<Node> childNodes() { 288 if (childNodeSize() == 0) 289 return EmptyNodes; 290 291 List<Node> children = ensureChildNodes(); 292 List<Node> rewrap = new ArrayList<>(children.size()); // wrapped so that looping and moving will not throw a CME as the source changes 293 rewrap.addAll(children); 294 return Collections.unmodifiableList(rewrap); 295 } 296 297 /** 298 * Returns a deep copy of this node's children. Changes made to these nodes will not be reflected in the original 299 * nodes 300 * @return a deep copy of this node's children 301 */ 302 public List<Node> childNodesCopy() { 303 final List<Node> nodes = ensureChildNodes(); 304 final ArrayList<Node> children = new ArrayList<>(nodes.size()); 305 for (Node node : nodes) { 306 children.add(node.clone()); 307 } 308 return children; 309 } 310 311 /** 312 * Get the number of child nodes that this node holds. 313 * @return the number of child nodes that this node holds. 314 */ 315 public abstract int childNodeSize(); 316 317 protected Node[] childNodesAsArray() { 318 return ensureChildNodes().toArray(new Node[0]); 319 } 320 321 /** 322 * Delete all this node's children. 323 * @return this node, for chaining 324 */ 325 public abstract Node empty(); 326 327 /** 328 Gets this node's parent node. This is always an Element. 329 @return parent node; or null if no parent. 330 @see #hasParent() 331 @see #parentElement(); 332 */ 333 public @Nullable Node parent() { 334 return parentNode; 335 } 336 337 /** 338 Gets this node's parent Element. 339 @return parent element; or null if this node has no parent. 340 @see #hasParent() 341 @since 1.21.1 342 */ 343 public @Nullable Element parentElement() { 344 return parentNode; 345 } 346 347 /** 348 Gets this node's parent node. Not overridable by extending classes, so useful if you really just need the Node type. 349 @return parent node; or null if no parent. 350 */ 351 public @Nullable final Node parentNode() { 352 return parentNode; 353 } 354 355 /** 356 * Get this node's root node; that is, its topmost ancestor. If this node is the top ancestor, returns {@code this}. 357 * @return topmost ancestor. 358 */ 359 public Node root() { 360 Node node = this; 361 while (node.parentNode != null) 362 node = node.parentNode; 363 return node; 364 } 365 366 /** 367 * Gets the Document associated with this Node. 368 * @return the Document associated with this Node, or null if there is no such Document. 369 */ 370 public @Nullable Document ownerDocument() { 371 Node node = this; 372 while (node != null) { 373 if (node instanceof Document) return (Document) node; 374 node = node.parentNode; 375 } 376 return null; 377 } 378 379 /** 380 * Remove (delete) this node from the DOM tree. If this node has children, they are also removed. If this node is 381 * an orphan, nothing happens. 382 */ 383 public void remove() { 384 if (parentNode != null) 385 parentNode.removeChild(this); 386 } 387 388 /** 389 * Insert the specified HTML into the DOM before this node (as a preceding sibling). 390 * @param html HTML to add before this node 391 * @return this node, for chaining 392 * @see #after(String) 393 */ 394 public Node before(String html) { 395 addSiblingHtml(siblingIndex(), html); 396 return this; 397 } 398 399 /** 400 * Insert the specified node into the DOM before this node (as a preceding sibling). 401 * @param node to add before this node 402 * @return this node, for chaining 403 * @see #after(Node) 404 */ 405 public Node before(Node node) { 406 Validate.notNull(node); 407 Validate.notNull(parentNode); 408 409 // if the incoming node is a sibling of this, remove it first so siblingIndex is correct on add 410 if (node.parentNode == parentNode) node.remove(); 411 412 parentNode.addChildren(siblingIndex(), node); 413 return this; 414 } 415 416 /** 417 * Insert the specified HTML into the DOM after this node (as a following sibling). 418 * @param html HTML to add after this node 419 * @return this node, for chaining 420 * @see #before(String) 421 */ 422 public Node after(String html) { 423 addSiblingHtml(siblingIndex() + 1, html); 424 return this; 425 } 426 427 /** 428 * Insert the specified node into the DOM after this node (as a following sibling). 429 * @param node to add after this node 430 * @return this node, for chaining 431 * @see #before(Node) 432 */ 433 public Node after(Node node) { 434 Validate.notNull(node); 435 Validate.notNull(parentNode); 436 437 // if the incoming node is a sibling of this, remove it first so siblingIndex is correct on add 438 if (node.parentNode == parentNode) node.remove(); 439 440 parentNode.addChildren(siblingIndex() + 1, node); 441 return this; 442 } 443 444 private void addSiblingHtml(int index, String html) { 445 Validate.notNull(html); 446 Validate.notNull(parentNode); 447 448 Element context = parentNode instanceof Element ? (Element) parentNode : null; 449 List<Node> nodes = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); 450 parentNode.addChildren(index, nodes.toArray(new Node[0])); 451 } 452 453 /** 454 Wrap the supplied HTML around this node. 455 456 @param html HTML to wrap around this node, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep. If 457 the input HTML does not parse to a result starting with an Element, this will be a no-op. 458 @return this node, for chaining. 459 */ 460 public Node wrap(String html) { 461 Validate.notEmpty(html); 462 463 // Parse context - parent (because wrapping), this, or null 464 Element context = 465 parentNode != null && parentNode instanceof Element ? (Element) parentNode : 466 this instanceof Element ? (Element) this : 467 null; 468 List<Node> wrapChildren = NodeUtils.parser(this).parseFragmentInput(html, context, baseUri()); 469 Node wrapNode = wrapChildren.get(0); 470 if (!(wrapNode instanceof Element)) // nothing to wrap with; noop 471 return this; 472 473 Element wrap = (Element) wrapNode; 474 Element deepest = getDeepChild(wrap); 475 if (parentNode != null) 476 parentNode.replaceChild(this, wrap); 477 deepest.addChildren(this); // side effect of tricking wrapChildren to lose first 478 479 // remainder (unbalanced wrap, like <div></div><p></p> -- The <p> is remainder 480 if (wrapChildren.size() > 0) { 481 //noinspection ForLoopReplaceableByForEach (beacause it allocates an Iterator which is wasteful here) 482 for (int i = 0; i < wrapChildren.size(); i++) { 483 Node remainder = wrapChildren.get(i); 484 // if no parent, this could be the wrap node, so skip 485 if (wrap == remainder) 486 continue; 487 488 if (remainder.parentNode != null) 489 remainder.parentNode.removeChild(remainder); 490 wrap.after(remainder); 491 } 492 } 493 return this; 494 } 495 496 /** 497 * Removes this node from the DOM, and moves its children up into the node's parent. This has the effect of dropping 498 * the node but keeping its children. 499 * <p> 500 * For example, with the input html: 501 * </p> 502 * <p>{@code <div>One <span>Two <b>Three</b></span></div>}</p> 503 * Calling {@code element.unwrap()} on the {@code span} element will result in the html: 504 * <p>{@code <div>One Two <b>Three</b></div>}</p> 505 * and the {@code "Two "} {@link TextNode} being returned. 506 * 507 * @return the first child of this node, after the node has been unwrapped. @{code Null} if the node had no children. 508 * @see #remove() 509 * @see #wrap(String) 510 */ 511 public @Nullable Node unwrap() { 512 Validate.notNull(parentNode); 513 Node firstChild = firstChild(); 514 parentNode.addChildren(siblingIndex(), this.childNodesAsArray()); 515 this.remove(); 516 517 return firstChild; 518 } 519 520 private static Element getDeepChild(Element el) { 521 Element child = el.firstElementChild(); 522 while (child != null) { 523 el = child; 524 child = child.firstElementChild(); 525 } 526 return el; 527 } 528 529 /** 530 * Replace this node in the DOM with the supplied node. 531 * @param in the node that will replace the existing node. 532 */ 533 public void replaceWith(Node in) { 534 Validate.notNull(in); 535 if (parentNode == null) parentNode = in.parentNode; // allows old to have been temp removed before replacing 536 Validate.notNull(parentNode); 537 parentNode.replaceChild(this, in); 538 } 539 540 protected void setParentNode(Node parentNode) { 541 Validate.notNull(parentNode); 542 if (this.parentNode != null) 543 this.parentNode.removeChild(this); 544 assert parentNode instanceof Element; 545 this.parentNode = (Element) parentNode; 546 } 547 548 protected void replaceChild(Node out, Node in) { 549 Validate.isTrue(out.parentNode == this); 550 Validate.notNull(in); 551 if (out == in) return; // no-op self replacement 552 553 if (in.parentNode != null) 554 in.parentNode.removeChild(in); 555 556 final int index = out.siblingIndex(); 557 ensureChildNodes().set(index, in); 558 in.parentNode = (Element) this; 559 in.setSiblingIndex(index); 560 out.parentNode = null; 561 562 ((Element) this).childNodes.incrementMod(); // as mod count not changed in set(), requires explicit update, to invalidate the child element cache 563 } 564 565 protected void removeChild(Node out) { 566 Validate.isTrue(out.parentNode == this); 567 Element el = (Element) this; 568 if (el.hasValidChildren()) // can remove by index 569 ensureChildNodes().remove(out.siblingIndex); 570 else 571 ensureChildNodes().remove(out); // iterates, but potentially not every one 572 573 el.invalidateChildren(); 574 out.parentNode = null; 575 } 576 577 protected void addChildren(Node... children) { 578 //most used. short circuit addChildren(int), which hits reindex children and array copy 579 final List<Node> nodes = ensureChildNodes(); 580 581 for (Node child: children) { 582 reparentChild(child); 583 nodes.add(child); 584 child.setSiblingIndex(nodes.size()-1); 585 } 586 } 587 588 protected void addChildren(int index, Node... children) { 589 // todo clean up all these and use the list, not the var array. just need to be careful when iterating the incoming (as we are removing as we go) 590 Validate.notNull(children); 591 if (children.length == 0) return; 592 final List<Node> nodes = ensureChildNodes(); 593 594 // fast path - if used as a wrap (index=0, children = child[0].parent.children - do inplace 595 final Node firstParent = children[0].parent(); 596 if (firstParent != null && firstParent.childNodeSize() == children.length) { 597 boolean sameList = true; 598 final List<Node> firstParentNodes = firstParent.ensureChildNodes(); 599 // identity check contents to see if same 600 int i = children.length; 601 while (i-- > 0) { 602 if (children[i] != firstParentNodes.get(i)) { 603 sameList = false; 604 break; 605 } 606 } 607 if (sameList) { // moving, so OK to empty firstParent and short-circuit 608 firstParent.empty(); 609 nodes.addAll(index, Arrays.asList(children)); 610 i = children.length; 611 assert this instanceof Element; 612 while (i-- > 0) { 613 children[i].parentNode = (Element) this; 614 } 615 ((Element) this).invalidateChildren(); 616 return; 617 } 618 } 619 620 Validate.noNullElements(children); 621 for (Node child : children) { 622 reparentChild(child); 623 } 624 nodes.addAll(index, Arrays.asList(children)); 625 ((Element) this).invalidateChildren(); 626 } 627 628 protected void reparentChild(Node child) { 629 child.setParentNode(this); 630 } 631 632 /** 633 Retrieves this node's sibling nodes. Similar to {@link #childNodes() node.parent.childNodes()}, but does not 634 include this node (a node is not a sibling of itself). 635 @return node siblings. If the node has no parent, returns an empty list. 636 */ 637 public List<Node> siblingNodes() { 638 if (parentNode == null) 639 return Collections.emptyList(); 640 641 List<Node> nodes = parentNode.ensureChildNodes(); 642 List<Node> siblings = new ArrayList<>(nodes.size() - 1); 643 for (Node node: nodes) 644 if (node != this) 645 siblings.add(node); 646 return siblings; 647 } 648 649 /** 650 Get this node's next sibling. 651 @return next sibling, or {@code null} if this is the last sibling 652 */ 653 public @Nullable Node nextSibling() { 654 if (parentNode == null) 655 return null; // root 656 657 final List<Node> siblings = parentNode.ensureChildNodes(); 658 final int index = siblingIndex() + 1; 659 if (siblings.size() > index) { 660 Node node = siblings.get(index); 661 assert (node.siblingIndex == index); // sanity test that invalidations haven't missed 662 return node; 663 } else 664 return null; 665 } 666 667 /** 668 Get this node's previous sibling. 669 @return the previous sibling, or @{code null} if this is the first sibling 670 */ 671 public @Nullable Node previousSibling() { 672 if (parentNode == null) 673 return null; // root 674 675 if (siblingIndex() > 0) 676 return parentNode.ensureChildNodes().get(siblingIndex-1); 677 else 678 return null; 679 } 680 681 /** 682 * Get the list index of this node in its node sibling list. E.g. if this is the first node 683 * sibling, returns 0. 684 * @return position in node sibling list 685 * @see org.jsoup.nodes.Element#elementSiblingIndex() 686 */ 687 public int siblingIndex() { 688 if (parentNode != null && !parentNode.childNodes.validChildren) 689 parentNode.reindexChildren(); 690 691 return siblingIndex; 692 } 693 694 protected void setSiblingIndex(int siblingIndex) { 695 this.siblingIndex = siblingIndex; 696 } 697 698 /** 699 Gets the first child node of this node, or {@code null} if there is none. This could be any Node type, such as an 700 Element, TextNode, Comment, etc. Use {@link Element#firstElementChild()} to get the first Element child. 701 @return the first child node, or null if there are no children. 702 @see Element#firstElementChild() 703 @see #lastChild() 704 @since 1.15.2 705 */ 706 public @Nullable Node firstChild() { 707 if (childNodeSize() == 0) return null; 708 return ensureChildNodes().get(0); 709 } 710 711 /** 712 Gets the last child node of this node, or {@code null} if there is none. 713 @return the last child node, or null if there are no children. 714 @see Element#lastElementChild() 715 @see #firstChild() 716 @since 1.15.2 717 */ 718 public @Nullable Node lastChild() { 719 final int size = childNodeSize(); 720 if (size == 0) return null; 721 List<Node> children = ensureChildNodes(); 722 return children.get(size - 1); 723 } 724 725 /** 726 Gets the first sibling of this node. That may be this node. 727 728 @return the first sibling node 729 @since 1.21.1 730 */ 731 public Node firstSibling() { 732 if (parentNode != null) { 733 //noinspection DataFlowIssue 734 return parentNode.firstChild(); 735 } else 736 return this; // orphan is its own first sibling 737 } 738 739 /** 740 Gets the last sibling of this node. That may be this node. 741 742 @return the last sibling (aka the parent's last child) 743 @since 1.21.1 744 */ 745 public Node lastSibling() { 746 if (parentNode != null) { 747 //noinspection DataFlowIssue (not nullable, would be this if no other sibs) 748 return parentNode.lastChild(); 749 } else 750 return this; 751 } 752 753 /** 754 Gets the next sibling Element of this node. E.g., if a {@code div} contains two {@code p}s, the 755 {@code nextElementSibling} of the first {@code p} is the second {@code p}. 756 <p>This is similar to {@link #nextSibling()}, but specifically finds only Elements.</p> 757 758 @return the next element, or null if there is no next element 759 @see #previousElementSibling() 760 */ 761 public @Nullable Element nextElementSibling() { 762 Node next = this; 763 while ((next = next.nextSibling()) != null) { 764 if (next instanceof Element) return (Element) next; 765 } 766 return null; 767 } 768 769 /** 770 Gets the previous Element sibling of this node. 771 772 @return the previous element, or null if there is no previous element 773 @see #nextElementSibling() 774 */ 775 public @Nullable Element previousElementSibling() { 776 Node prev = this; 777 while ((prev = prev.previousSibling()) != null) { 778 if (prev instanceof Element) return (Element) prev; 779 } 780 return null; 781 } 782 783 /** 784 * Perform a depth-first traversal through this node and its descendants. 785 * @param nodeVisitor the visitor callbacks to perform on each node 786 * @return this node, for chaining 787 */ 788 public Node traverse(NodeVisitor nodeVisitor) { 789 Validate.notNull(nodeVisitor); 790 nodeVisitor.traverse(this); 791 return this; 792 } 793 794 /** 795 Perform the supplied action on this Node and each of its descendants, during a depth-first traversal. Nodes may be 796 inspected, changed, added, replaced, or removed. 797 @param action the function to perform on the node 798 @return this Node, for chaining 799 @see Element#forEach(Consumer) 800 */ 801 public Node forEachNode(Consumer<? super Node> action) { 802 Validate.notNull(action); 803 nodeStream().forEach(action); 804 return this; 805 } 806 807 /** 808 * Perform a depth-first controllable traversal through this node and its descendants. 809 * @param nodeFilter the filter callbacks to perform on each node 810 * @return this node, for chaining 811 */ 812 public Node filter(NodeFilter nodeFilter) { 813 Validate.notNull(nodeFilter); 814 nodeFilter.traverse(this); 815 return this; 816 } 817 818 /** 819 Returns a Stream of this Node and all of its descendant Nodes. The stream has document order. 820 @return a stream of all nodes. 821 @see Element#stream() 822 @since 1.17.1 823 */ 824 public Stream<Node> nodeStream() { 825 return NodeUtils.stream(this, Node.class); 826 } 827 828 /** 829 Returns a Stream of this and descendant nodes, containing only nodes of the specified type. The stream has document 830 order. 831 @return a stream of nodes filtered by type. 832 @see Element#stream() 833 @since 1.17.1 834 */ 835 public <T extends Node> Stream<T> nodeStream(Class<T> type) { 836 return NodeUtils.stream(this, type); 837 } 838 839 /** 840 Get the outer HTML of this node. For example, on a {@code p} element, may return {@code <p>Para</p>}. 841 @return outer HTML 842 @see Element#html() 843 @see Element#text() 844 */ 845 public String outerHtml() { 846 StringBuilder sb = StringUtil.borrowBuilder(); 847 outerHtml(QuietAppendable.wrap(sb)); 848 return StringUtil.releaseBuilder(sb); 849 } 850 851 protected void outerHtml(Appendable accum) { 852 outerHtml(QuietAppendable.wrap(accum)); 853 } 854 855 protected void outerHtml(QuietAppendable accum) { 856 Printer printer = Printer.printerFor(this, accum); 857 printer.traverse(this); 858 } 859 860 /** 861 Get the outer HTML of this node. 862 863 @param accum accumulator to place HTML into 864 @param out 865 */ 866 abstract void outerHtmlHead(final QuietAppendable accum, final Document.OutputSettings out); 867 868 abstract void outerHtmlTail(final QuietAppendable accum, final Document.OutputSettings out); 869 870 /** 871 Write this node and its children to the given {@link Appendable}. 872 873 @param appendable the {@link Appendable} to write to. 874 @return the supplied {@link Appendable}, for chaining. 875 @throws org.jsoup.SerializationException if the appendable throws an IOException. 876 */ 877 public <T extends Appendable> T html(T appendable) { 878 outerHtml(appendable); 879 return appendable; 880 } 881 882 /** 883 Get the source range (start and end positions) in the original input source from which this node was parsed. 884 Position tracking must be enabled prior to parsing the content. For an Element, this will be the positions of the 885 start tag. 886 @return the range for the start of the node, or {@code untracked} if its range was not tracked. 887 @see org.jsoup.parser.Parser#setTrackPosition(boolean) 888 @see Range#isImplicit() 889 @see Element#endSourceRange() 890 @see Attributes#sourceRange(String name) 891 @since 1.15.2 892 */ 893 public Range sourceRange() { 894 return Range.ofStart(this); 895 } 896 897 /** 898 Gets the range spans, if source tracking was used. 899 */ 900 Range.@Nullable Spans spans() { 901 if (!hasAttributes()) return null; 902 return attributes().spans(); 903 } 904 905 /** 906 Gets or creates range spans for this node. 907 */ 908 Range.Spans ensureSpans() { 909 return attributes().ensureSpans(); 910 } 911 912 /** 913 * Gets this node's outer HTML. 914 * @return outer HTML. 915 * @see #outerHtml() 916 */ 917 @Override 918 public String toString() { 919 return outerHtml(); 920 } 921 922 /** @deprecated internal method moved into Printer; will be removed in jsoup 1.24.1. */ 923 @Deprecated 924 protected void indent(Appendable accum, int depth, Document.OutputSettings out) throws IOException { 925 accum.append('\n').append(StringUtil.padding(depth * out.indentAmount(), out.maxPaddingWidth())); 926 } 927 928 /** 929 * Check if this node is the same instance of another (object identity test). 930 * <p>For a node value equality check, see {@link #hasSameValue(Object)}</p> 931 * @param o other object to compare to 932 * @return true if the content of this node is the same as the other 933 * @see Node#hasSameValue(Object) 934 */ 935 @Override 936 public boolean equals(@Nullable Object o) { 937 // implemented just so that javadoc is clear this is an identity test 938 return this == o; 939 } 940 941 /** 942 Provides a hashCode for this Node, based on its object identity. Changes to the Node's content will not impact the 943 result. 944 @return an object identity based hashcode for this Node 945 */ 946 @Override 947 public int hashCode() { 948 // implemented so that javadoc and scanners are clear this is an identity test 949 return super.hashCode(); 950 } 951 952 /** 953 * Check if this node has the same content as another node. A node is considered the same if its name, attributes and content match the 954 * other node; particularly its position in the tree does not influence its similarity. 955 * @param o other object to compare to 956 * @return true if the content of this node is the same as the other 957 */ 958 public boolean hasSameValue(@Nullable Object o) { 959 if (this == o) return true; 960 if (o == null || getClass() != o.getClass()) return false; 961 962 return this.outerHtml().equals(((Node) o).outerHtml()); 963 } 964 965 /** 966 Create a stand-alone, deep copy of this node, and all of its children. The cloned node will have no siblings. 967 <p><ul> 968 <li>If this node is a {@link LeafNode}, the clone will have no parent.</li> 969 <li>If this node is an {@link Element}, the clone will have a simple owning {@link Document} to retain the 970 configured output settings and parser.</li> 971 </ul></p> 972 <p>The cloned node may be adopted into another Document or node structure using 973 {@link Element#appendChild(Node)}.</p> 974 975 @return a stand-alone cloned node, including clones of any children 976 @see #shallowClone() 977 */ 978 @SuppressWarnings("MethodDoesntCallSuperMethod") 979 // because it does call super.clone in doClone - analysis just isn't following 980 @Override 981 public Node clone() { 982 Node thisClone = doClone(null); // splits for orphan 983 984 // Queue up nodes that need their children cloned (BFS). 985 final LinkedList<Node> nodesToProcess = new LinkedList<>(); 986 nodesToProcess.add(thisClone); 987 988 while (!nodesToProcess.isEmpty()) { 989 Node currParent = nodesToProcess.remove(); 990 991 final int size = currParent.childNodeSize(); 992 for (int i = 0; i < size; i++) { 993 final List<Node> childNodes = currParent.ensureChildNodes(); 994 Node childClone = childNodes.get(i).doClone(currParent); 995 childNodes.set(i, childClone); 996 nodesToProcess.add(childClone); 997 } 998 } 999 1000 return thisClone; 1001 } 1002 1003 /** 1004 * Create a stand-alone, shallow copy of this node. None of its children (if any) will be cloned, and it will have 1005 * no parent or sibling nodes. 1006 * @return a single independent copy of this node 1007 * @see #clone() 1008 */ 1009 public Node shallowClone() { 1010 return doClone(null); 1011 } 1012 1013 /* 1014 * Return a clone of the node using the given parent (which can be null). 1015 * Not a deep copy of children. 1016 */ 1017 protected Node doClone(@Nullable Node parent) { 1018 assert parent == null || parent instanceof Element; 1019 Node clone; 1020 1021 try { 1022 clone = (Node) super.clone(); 1023 } catch (CloneNotSupportedException e) { 1024 throw new RuntimeException(e); 1025 } 1026 1027 clone.parentNode = (Element) parent; // can be null, to create an orphan split 1028 clone.siblingIndex = parent == null ? 0 : siblingIndex(); 1029 // if not keeping the parent, shallowClone the ownerDocument to preserve its settings 1030 if (parent == null && !(this instanceof Document)) { 1031 Document doc = ownerDocument(); 1032 if (doc != null) { 1033 Document docClone = doc.shallowClone(); 1034 clone.parentNode = docClone; 1035 docClone.ensureChildNodes().add(clone); 1036 } 1037 } 1038 1039 return clone; 1040 } 1041}