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