001package org.jsoup.nodes; 002 003import org.jsoup.helper.Validate; 004import org.jsoup.internal.Normalizer; 005import org.jsoup.internal.QuietAppendable; 006import org.jsoup.internal.StringUtil; 007import org.jsoup.parser.ParseSettings; 008import org.jsoup.parser.Parser; 009import org.jsoup.parser.Tag; 010import org.jsoup.parser.TokenQueue; 011import org.jsoup.select.Collector; 012import org.jsoup.select.Elements; 013import org.jsoup.select.Evaluator; 014import org.jsoup.select.NodeFilter; 015import org.jsoup.select.NodeVisitor; 016import org.jsoup.select.Nodes; 017import org.jsoup.select.Selector; 018import org.jspecify.annotations.Nullable; 019 020import java.lang.ref.WeakReference; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Iterator; 026import java.util.LinkedHashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.atomic.AtomicBoolean; 031import java.util.function.Consumer; 032import java.util.regex.Pattern; 033import java.util.regex.PatternSyntaxException; 034import java.util.stream.Collectors; 035import java.util.stream.Stream; 036 037import static org.jsoup.internal.Normalizer.normalize; 038import static org.jsoup.nodes.Document.OutputSettings.Syntax.xml; 039import static org.jsoup.nodes.TextNode.lastCharIsWhitespace; 040import static org.jsoup.parser.Parser.NamespaceHtml; 041import static org.jsoup.parser.TokenQueue.escapeCssIdentifier; 042import static org.jsoup.select.Selector.evaluatorOf; 043 044/** 045 An HTML Element consists of a tag name, attributes, and child nodes (including text nodes and other elements). 046 <p> 047 From an Element, you can extract data, traverse the node graph, and manipulate the HTML. 048*/ 049public class Element extends Node implements Iterable<Element> { 050 private static final List<Element> EmptyChildren = Collections.emptyList(); 051 private static final NodeList EmptyNodeList = new NodeList(0); 052 private static final Pattern ClassSplit = Pattern.compile("\\s+"); 053 static final String BaseUriKey = Attributes.internalKey("baseUri"); 054 Tag tag; 055 NodeList childNodes; 056 @Nullable Attributes attributes; // field is nullable but all methods for attributes are non-null 057 058 /** 059 * Create a new, standalone element, in the specified namespace. 060 * @param tag tag name 061 * @param namespace namespace for this element 062 */ 063 public Element(String tag, String namespace) { 064 this(Tag.valueOf(tag, namespace, ParseSettings.preserveCase), null); 065 } 066 067 /** 068 * Create a new, standalone element, in the HTML namespace. 069 * @param tag tag name 070 * @see #Element(String tag, String namespace) 071 */ 072 public Element(String tag) { 073 this(tag, Parser.NamespaceHtml); 074 } 075 076 /** 077 * Create a new, standalone Element. (Standalone in that it has no parent.) 078 * 079 * @param tag tag of this element 080 * @param baseUri the base URI (optional, may be null to inherit from parent, or "" to clear parent's) 081 * @param attributes initial attributes (optional, may be null) 082 * @see #appendChild(Node) 083 * @see #appendElement(String) 084 */ 085 public Element(Tag tag, @Nullable String baseUri, @Nullable Attributes attributes) { 086 Validate.notNull(tag); 087 childNodes = EmptyNodeList; 088 this.attributes = attributes; 089 this.tag = tag; 090 if (!StringUtil.isBlank(baseUri)) this.setBaseUri(baseUri); 091 } 092 093 /** 094 * Create a new Element from a Tag and a base URI. 095 * 096 * @param tag element tag 097 * @param baseUri the base URI of this element. Optional, and will inherit from its parent, if any. 098 * @see Tag#valueOf(String, ParseSettings) 099 */ 100 public Element(Tag tag, @Nullable String baseUri) { 101 this(tag, baseUri, null); 102 } 103 104 /** 105 Internal test to check if a nodelist object has been created. 106 */ 107 protected boolean hasChildNodes() { 108 return childNodes != EmptyNodeList; 109 } 110 111 @Override protected List<Node> ensureChildNodes() { 112 if (childNodes == EmptyNodeList) { 113 childNodes = new NodeList(4); 114 } 115 return childNodes; 116 } 117 118 @Override 119 protected boolean hasAttributes() { 120 return attributes != null; 121 } 122 123 @Override 124 public Attributes attributes() { 125 if (attributes == null) // not using hasAttributes, as doesn't clear warning 126 attributes = new Attributes(); 127 return attributes; 128 } 129 130 @Override 131 public String baseUri() { 132 String baseUri = searchUpForAttribute(this, BaseUriKey); 133 return baseUri != null ? baseUri : ""; 134 } 135 136 @Nullable 137 static String searchUpForAttribute(final Element start, final String key) { 138 Element el = start; 139 while (el != null) { 140 if (el.attributes != null && el.attributes.hasKey(key)) 141 return el.attributes.get(key); 142 el = el.parent(); 143 } 144 return null; 145 } 146 147 @Override 148 protected void doSetBaseUri(String baseUri) { 149 attributes().put(BaseUriKey, baseUri); 150 } 151 152 @Override 153 public int childNodeSize() { 154 return childNodes.size(); 155 } 156 157 @Override 158 public String nodeName() { 159 return tag.getName(); 160 } 161 162 /** 163 * Get the name of the tag for this element. E.g. {@code div}. If you are using {@link ParseSettings#preserveCase 164 * case preserving parsing}, this will return the source's original case. 165 * 166 * @return the tag name 167 */ 168 public String tagName() { 169 return tag.getName(); 170 } 171 172 /** 173 * Get the normalized name of this Element's tag. This will always be the lower-cased version of the tag, regardless 174 * of the tag case preserving setting of the parser. For e.g., {@code <DIV>} and {@code <div>} both have a 175 * normal name of {@code div}. 176 * @return normal name 177 */ 178 @Override 179 public String normalName() { 180 return tag.normalName(); 181 } 182 183 /** 184 Test if this Element has the specified normalized name, and is in the specified namespace. 185 * @param normalName a normalized element name (e.g. {@code div}). 186 * @param namespace the namespace 187 * @return true if the element's normal name matches exactly, and is in the specified namespace 188 * @since 1.17.2 189 */ 190 public boolean elementIs(String normalName, String namespace) { 191 return tag.normalName().equals(normalName) && tag.namespace().equals(namespace); 192 } 193 194 /** 195 * Change (rename) the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with 196 * {@code el.tagName("div");}. 197 * 198 * @param tagName new tag name for this element 199 * @return this element, for chaining 200 * @see Elements#tagName(String) 201 */ 202 public Element tagName(String tagName) { 203 return tagName(tagName, tag.namespace()); 204 } 205 206 /** 207 * Change (rename) the tag of this element. For example, convert a {@code <span>} to a {@code <div>} with 208 * {@code el.tagName("div");}. 209 * 210 * @param tagName new tag name for this element 211 * @param namespace the new namespace for this element 212 * @return this element, for chaining 213 * @see Elements#tagName(String) 214 */ 215 public Element tagName(String tagName, String namespace) { 216 Validate.notEmptyParam(tagName, "tagName"); 217 Validate.notEmptyParam(namespace, "namespace"); 218 Parser parser = NodeUtils.parser(this); 219 tag = parser.tagSet().valueOf(tagName, namespace, parser.settings()); // maintains the case option of the original parse 220 return this; 221 } 222 223 /** 224 * Get the Tag for this element. 225 * 226 * @return the tag object 227 */ 228 public Tag tag() { 229 return tag; 230 } 231 232 /** 233 Change the Tag of this element. 234 @param tag the new tag 235 @return this element, for chaining 236 @since 1.20.1 237 */ 238 public Element tag(Tag tag) { 239 Validate.notNull(tag); 240 this.tag = tag; 241 return this; 242 } 243 244 /** 245 * Test if this element is a block-level element. (E.g. {@code <div> == true} or an inline element 246 * {@code <span> == false}). 247 * 248 * @return true if block, false if not (and thus inline) 249 */ 250 public boolean isBlock() { 251 return tag.isBlock(); 252 } 253 254 /** 255 * Get the {@code id} attribute of this element. 256 * 257 * @return The id attribute, if present, or an empty string if not. 258 */ 259 public String id() { 260 return attributes != null ? attributes.getIgnoreCase("id") :""; 261 } 262 263 /** 264 Set the {@code id} attribute of this element. 265 @param id the ID value to use 266 @return this Element, for chaining 267 */ 268 public Element id(String id) { 269 Validate.notNull(id); 270 attr("id", id); 271 return this; 272 } 273 274 /** 275 * Set an attribute value on this element. If this element already has an attribute with the 276 * key, its value is updated; otherwise, a new attribute is added. 277 * 278 * @return this element 279 */ 280 @Override public Element attr(String attributeKey, String attributeValue) { 281 super.attr(attributeKey, attributeValue); 282 return this; 283 } 284 285 /** 286 * Set a boolean attribute value on this element. Setting to <code>true</code> sets the attribute value to "" and 287 * marks the attribute as boolean so no value is written out. Setting to <code>false</code> removes the attribute 288 * with the same key if it exists. 289 * 290 * @param attributeKey the attribute key 291 * @param attributeValue the attribute value 292 * 293 * @return this element 294 */ 295 public Element attr(String attributeKey, boolean attributeValue) { 296 attributes().put(attributeKey, attributeValue); 297 return this; 298 } 299 300 /** 301 Get an Attribute by key. Changes made via {@link Attribute#setKey(String)}, {@link Attribute#setValue(String)} etc 302 will cascade back to this Element. 303 @param key the (case-sensitive) attribute key 304 @return the Attribute for this key, or null if not present. 305 @since 1.17.2 306 */ 307 @Nullable public Attribute attribute(String key) { 308 return hasAttributes() ? attributes().attribute(key) : null; 309 } 310 311 /** 312 * Get this element's HTML5 custom data attributes. Each attribute in the element that has a key 313 * starting with "data-" is included the dataset. 314 * <p> 315 * E.g., the element {@code <div data-package="jsoup" data-language="Java" class="group">...} has the dataset 316 * {@code package=jsoup, language=java}. 317 * <p> 318 * This map is a filtered view of the element's attribute map. Changes to one map (add, remove, update) are reflected 319 * in the other map. 320 * <p> 321 * You can find elements that have data attributes using the {@code [^data-]} attribute key prefix selector. 322 * @return a map of {@code key=value} custom data attributes. 323 */ 324 public Map<String, String> dataset() { 325 return attributes().dataset(); 326 } 327 328 @Override @Nullable 329 public final Element parent() { 330 return (Element) parentNode; 331 } 332 333 /** 334 * Get this element's parent and ancestors, up to the document root. 335 * @return this element's stack of parents, starting with the closest first. 336 */ 337 public Elements parents() { 338 Elements parents = new Elements(); 339 Element parent = this.parent(); 340 while (parent != null && !parent.nameIs("#root")) { 341 parents.add(parent); 342 parent = parent.parent(); 343 } 344 return parents; 345 } 346 347 /** 348 * Get a child element of this element, by its 0-based index number. 349 * <p> 350 * Note that an element can have both mixed Nodes and Elements as children. This method inspects 351 * a filtered list of children that are elements, and the index is based on that filtered list. 352 * </p> 353 * 354 * @param index the index number of the element to retrieve 355 * @return the child element, if it exists, otherwise throws an {@code IndexOutOfBoundsException} 356 * @see #childNode(int) 357 */ 358 public Element child(int index) { 359 Validate.isTrue(index >= 0, "Index must be >= 0"); 360 List<Element> cached = cachedChildren(); 361 if (cached != null) return cached.get(index); 362 // otherwise, iter on elementChild; saves creating list 363 int size = childNodes.size(); 364 for (int i = 0, e = 0; i < size; i++) { // direct iter is faster than chasing firstElSib, nextElSibd 365 Node node = childNodes.get(i); 366 if (node instanceof Element) { 367 if (e++ == index) return (Element) node; 368 } 369 } 370 throw new IndexOutOfBoundsException("No child at index: " + index); 371 } 372 373 /** 374 * Get the number of child nodes of this element that are elements. 375 * <p> 376 * This method works on the same filtered list like {@link #child(int)}. Use {@link #childNodes()} and {@link 377 * #childNodeSize()} to get the unfiltered Nodes (e.g. includes TextNodes etc.) 378 * </p> 379 * 380 * @return the number of child nodes that are elements 381 * @see #children() 382 * @see #child(int) 383 */ 384 public int childrenSize() { 385 if (childNodeSize() == 0) return 0; 386 return childElementsList().size(); // gets children into cache; faster subsequent child(i) if unmodified 387 } 388 389 /** 390 * Get this element's child elements. 391 * <p> 392 * This is effectively a filter on {@link #childNodes()} to get Element nodes. 393 * </p> 394 * @return child elements. If this element has no children, returns an empty list. 395 * @see #childNodes() 396 */ 397 public Elements children() { 398 return new Elements(childElementsList()); 399 } 400 401 /** 402 * Maintains a shadow copy of this element's child elements. If the nodelist is changed, this cache is invalidated. 403 * @return a list of child elements 404 */ 405 List<Element> childElementsList() { 406 if (childNodeSize() == 0) return EmptyChildren; // short circuit creating empty 407 // set atomically, so works in multi-thread. Calling methods look like reads, so should be thread-safe 408 synchronized (childNodes) { // sync vs re-entrant lock, to save another field 409 List<Element> children = cachedChildren(); 410 if (children == null) { 411 children = filterNodes(Element.class); 412 stashChildren(children); 413 } 414 return children; 415 } 416 } 417 418 private static final String childElsKey = "jsoup.childEls"; 419 private static final String childElsMod = "jsoup.childElsMod"; 420 421 /** returns the cached child els, if they exist, and the modcount of our childnodes matches the stashed modcount */ 422 @Nullable List<Element> cachedChildren() { 423 if (attributes == null || !attributes.hasUserData()) return null; // don't create empty userdata 424 Map<String, Object> userData = attributes.userData(); 425 //noinspection unchecked 426 WeakReference<List<Element>> ref = (WeakReference<List<Element>>) userData.get(childElsKey); 427 if (ref != null) { 428 List<Element> els = ref.get(); 429 if (els != null) { 430 Integer modCount = (Integer) userData.get(childElsMod); 431 if (modCount != null && modCount == childNodes.modCount()) 432 return els; 433 } 434 } 435 return null; 436 } 437 438 /** caches the child els into the Attribute user data. */ 439 private void stashChildren(List<Element> els) { 440 Map<String, Object> userData = attributes().userData(); 441 WeakReference<List<Element>> ref = new WeakReference<>(els); 442 userData.put(childElsKey, ref); 443 userData.put(childElsMod, childNodes.modCount()); 444 } 445 446 /** 447 Returns a Stream of this Element and all of its descendant Elements. The stream has document order. 448 @return a stream of this element and its descendants. 449 @see #nodeStream() 450 @since 1.17.1 451 */ 452 public Stream<Element> stream() { 453 return NodeUtils.stream(this, Element.class); 454 } 455 456 private <T> List<T> filterNodes(Class<T> clazz) { 457 return childNodes.stream() 458 .filter(clazz::isInstance) 459 .map(clazz::cast) 460 .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)); 461 } 462 463 /** 464 * Get this element's child text nodes. The list is unmodifiable but the text nodes may be manipulated. 465 * <p> 466 * This is effectively a filter on {@link #childNodes()} to get Text nodes. 467 * @return child text nodes. If this element has no text nodes, returns an 468 * empty list. 469 * </p> 470 * For example, with the input HTML: {@code <p>One <span>Two</span> Three <br> Four</p>} with the {@code p} element selected: 471 * <ul> 472 * <li>{@code p.text()} = {@code "One Two Three Four"}</li> 473 * <li>{@code p.ownText()} = {@code "One Three Four"}</li> 474 * <li>{@code p.children()} = {@code Elements[<span>, <br>]}</li> 475 * <li>{@code p.childNodes()} = {@code List<Node>["One ", <span>, " Three ", <br>, " Four"]}</li> 476 * <li>{@code p.textNodes()} = {@code List<TextNode>["One ", " Three ", " Four"]}</li> 477 * </ul> 478 */ 479 public List<TextNode> textNodes() { 480 return filterNodes(TextNode.class); 481 } 482 483 /** 484 * Get this element's child data nodes. The list is unmodifiable but the data nodes may be manipulated. 485 * <p> 486 * This is effectively a filter on {@link #childNodes()} to get Data nodes. 487 * </p> 488 * @return child data nodes. If this element has no data nodes, returns an 489 * empty list. 490 * @see #data() 491 */ 492 public List<DataNode> dataNodes() { 493 return filterNodes(DataNode.class); 494 } 495 496 /** 497 * Find elements that match the {@link Selector} CSS query, with this element as the starting context. Matched elements 498 * may include this element, or any of its descendents. 499 * <p>If the query starts with a combinator (e.g. {@code *} or {@code >}), that will combine to this element.</p> 500 * <p>This method is generally more powerful to use than the DOM-type {@code getElementBy*} methods, because 501 * multiple filters can be combined, e.g.:</p> 502 * <ul> 503 * <li>{@code el.select("a[href]")} - finds links ({@code a} tags with {@code href} attributes)</li> 504 * <li>{@code el.select("a[href*=example.com]")} - finds links pointing to example.com (loosely)</li> 505 * <li>{@code el.select("* div")} - finds all divs that descend from this element (and excludes this element)</li> 506 * <li>{@code el.select("> div")} - finds all divs that are direct children of this element (and excludes this element)</li> 507 * </ul> 508 * <p>See the query syntax documentation in {@link org.jsoup.select.Selector}.</p> 509 * <p>Also known as {@code querySelectorAll()} in the Web DOM.</p> 510 * 511 * @param cssQuery a {@link Selector} CSS-like query 512 * @return an {@link Elements} list containing elements that match the query (empty if none match) 513 * @see Selector selector query syntax 514 * @see #select(Evaluator) 515 * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. 516 */ 517 public Elements select(String cssQuery) { 518 return Selector.select(cssQuery, this); 519 } 520 521 /** 522 * Find elements that match the supplied Evaluator. This has the same functionality as {@link #select(String)}, but 523 * may be useful if you are running the same query many times (on many documents) and want to save the overhead of 524 * repeatedly parsing the CSS query. 525 * @param evaluator an element evaluator 526 * @return an {@link Elements} list containing elements that match the query (empty if none match) 527 * @see Selector#evaluatorOf(String css) 528 */ 529 public Elements select(Evaluator evaluator) { 530 return Selector.select(evaluator, this); 531 } 532 533 /** 534 Selects elements from the given root that match the specified {@link Selector} CSS query, with this element as the 535 starting context, and returns them as a lazy Stream. Matched elements may include this element, or any of its 536 children. 537 <p> 538 Unlike {@link #select(String query)}, which returns a complete list of all matching elements, this method returns a 539 {@link Stream} that processes elements lazily as they are needed. The stream operates in a "pull" model — elements 540 are fetched from the root as the stream is traversed. You can use standard {@code Stream} operations such as 541 {@code filter}, {@code map}, or {@code findFirst} to process elements on demand. 542 </p> 543 544 @param cssQuery a {@link Selector} CSS-like query 545 @return a {@link Stream} containing elements that match the query (empty if none match) 546 @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. 547 @see Selector selector query syntax 548 @see #selectStream(Evaluator eval) 549 @since 1.19.1 550 */ 551 public Stream<Element> selectStream(String cssQuery) { 552 return Selector.selectStream(cssQuery, this); 553 } 554 555 /** 556 Find a Stream of elements that match the supplied Evaluator. 557 558 @param evaluator an element Evaluator 559 @return a {@link Stream} containing elements that match the query (empty if none match) 560 @see Selector#evaluatorOf(String css) 561 @since 1.19.1 562 */ 563 public Stream<Element> selectStream(Evaluator evaluator) { 564 return Selector.selectStream(evaluator, this); 565 } 566 567 /** 568 * Find the first Element that matches the {@link Selector} CSS query, with this element as the starting context. 569 * <p>This is effectively the same as calling {@code element.select(query).first()}, but is more efficient as query 570 * execution stops on the first hit.</p> 571 * <p>Also known as {@code querySelector()} in the Web DOM.</p> 572 * @param cssQuery cssQuery a {@link Selector} CSS-like query 573 * @return the first matching element, or <b>{@code null}</b> if there is no match. 574 * @see #expectFirst(String) 575 */ 576 public @Nullable Element selectFirst(String cssQuery) { 577 return Selector.selectFirst(cssQuery, this); 578 } 579 580 /** 581 * Finds the first Element that matches the supplied Evaluator, with this element as the starting context, or 582 * {@code null} if none match. 583 * 584 * @param evaluator an element evaluator 585 * @return the first matching element (walking down the tree, starting from this element), or {@code null} if none 586 * match. 587 */ 588 public @Nullable Element selectFirst(Evaluator evaluator) { 589 return Collector.findFirst(evaluator, this); 590 } 591 592 /** 593 Just like {@link #selectFirst(String)}, but if there is no match, throws an {@link IllegalArgumentException}. This 594 is useful if you want to simply abort processing on a failed match. 595 @param cssQuery a {@link Selector} CSS-like query 596 @return the first matching element 597 @throws IllegalArgumentException if no match is found 598 @since 1.15.2 599 */ 600 public Element expectFirst(String cssQuery) { 601 return Validate.expectNotNull( 602 Selector.selectFirst(cssQuery, this), 603 parent() != null ? 604 "No elements matched the query '%s' on element '%s'." : 605 "No elements matched the query '%s' in the document." 606 , cssQuery, this.tagName() 607 ); 608 } 609 610 /** 611 Find nodes that match the supplied {@link Evaluator}, with this element as the starting context. Matched 612 nodes may include this element, or any of its descendents. 613 614 @param evaluator an evaluator 615 @return a list of nodes that match the query (empty if none match) 616 @since 1.21.1 617 */ 618 public Nodes<Node> selectNodes(Evaluator evaluator) { 619 return selectNodes(evaluator, Node.class); 620 } 621 622 /** 623 Find nodes that match the supplied {@link Selector} CSS query, with this element as the starting context. Matched 624 nodes may include this element, or any of its descendents. 625 <p>To select leaf nodes, the query should specify the node type, e.g. {@code ::text}, 626 {@code ::comment}, {@code ::data}, {@code ::leafnode}.</p> 627 628 @param cssQuery a {@link Selector} CSS query 629 @return a list of nodes that match the query (empty if none match) 630 @since 1.21.1 631 */ 632 public Nodes<Node> selectNodes(String cssQuery) { 633 return selectNodes(cssQuery, Node.class); 634 } 635 636 /** 637 Find nodes that match the supplied Evaluator, with this element as the starting context. Matched 638 nodes may include this element, or any of its descendents. 639 640 @param evaluator an evaluator 641 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 642 @param <T> the type of node to collect 643 @return a list of nodes that match the query (empty if none match) 644 @since 1.21.1 645 */ 646 public <T extends Node> Nodes<T> selectNodes(Evaluator evaluator, Class<T> type) { 647 Validate.notNull(evaluator); 648 return Collector.collectNodes(evaluator, this, type); 649 } 650 651 /** 652 Find nodes that match the supplied {@link Selector} CSS query, with this element as the starting context. Matched 653 nodes may include this element, or any of its descendents. 654 <p>To select specific node types, use {@code ::text}, {@code ::comment}, {@code ::leafnode}, etc. For example, to 655 select all text nodes under {@code p} elements: </p> 656 <pre> Nodes<TextNode> textNodes = doc.selectNodes("p ::text", TextNode.class);</pre> 657 658 @param cssQuery a {@link Selector} CSS query 659 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 660 @param <T> the type of node to collect 661 @return a list of nodes that match the query (empty if none match) 662 @since 1.21.1 663 */ 664 public <T extends Node> Nodes<T> selectNodes(String cssQuery, Class<T> type) { 665 Validate.notEmpty(cssQuery); 666 return selectNodes(evaluatorOf(cssQuery), type); 667 } 668 669 /** 670 Find the first Node that matches the {@link Selector} CSS query, with this element as the starting context. 671 <p>This is effectively the same as calling {@code element.selectNodes(query).first()}, but is more efficient as 672 query 673 execution stops on the first hit.</p> 674 <p>Also known as {@code querySelector()} in the Web DOM.</p> 675 676 @param cssQuery cssQuery a {@link Selector} CSS-like query 677 @return the first matching node, or <b>{@code null}</b> if there is no match. 678 @since 1.21.1 679 @see #expectFirst(String) 680 */ 681 public @Nullable <T extends Node> T selectFirstNode(String cssQuery, Class<T> type) { 682 return selectFirstNode(evaluatorOf(cssQuery), type); 683 } 684 685 /** 686 Finds the first Node that matches the supplied Evaluator, with this element as the starting context, or 687 {@code null} if none match. 688 689 @param evaluator an element evaluator 690 @return the first matching node (walking down the tree, starting from this element), or {@code null} if none 691 match. 692 @since 1.21.1 693 */ 694 public @Nullable <T extends Node> T selectFirstNode(Evaluator evaluator, Class<T> type) { 695 return Collector.findFirstNode(evaluator, this, type); 696 } 697 698 /** 699 Just like {@link #selectFirstNode(String, Class)}, but if there is no match, throws an 700 {@link IllegalArgumentException}. This is useful if you want to simply abort processing on a failed match. 701 702 @param cssQuery a {@link Selector} CSS-like query 703 @return the first matching node 704 @throws IllegalArgumentException if no match is found 705 @since 1.21.1 706 */ 707 public <T extends Node> T expectFirstNode(String cssQuery, Class<T> type) { 708 return Validate.expectNotNull( 709 selectFirstNode(cssQuery, type), 710 parent() != null ? 711 "No nodes matched the query '%s' on element '%s'.": 712 "No nodes matched the query '%s' in the document." 713 , cssQuery, this.tagName() 714 ); 715 } 716 717 /** 718 * Checks if this element matches the given {@link Selector} CSS query. Also knows as {@code matches()} in the Web 719 * DOM. 720 * 721 * @param cssQuery a {@link Selector} CSS query 722 * @return if this element matches the query 723 */ 724 public boolean is(String cssQuery) { 725 return is(evaluatorOf(cssQuery)); 726 } 727 728 /** 729 * Check if this element matches the given evaluator. 730 * @param evaluator an element evaluator 731 * @return if this element matches 732 */ 733 public boolean is(Evaluator evaluator) { 734 return evaluator.matches(this.root(), this); 735 } 736 737 /** 738 * Find the closest element up the tree of parents that matches the specified CSS query. Will return itself, an 739 * ancestor, or {@code null} if there is no such matching element. 740 * @param cssQuery a {@link Selector} CSS query 741 * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not 742 * found. 743 */ 744 public @Nullable Element closest(String cssQuery) { 745 return closest(evaluatorOf(cssQuery)); 746 } 747 748 /** 749 * Find the closest element up the tree of parents that matches the specified evaluator. Will return itself, an 750 * ancestor, or {@code null} if there is no such matching element. 751 * @param evaluator a query evaluator 752 * @return the closest ancestor element (possibly itself) that matches the provided evaluator. {@code null} if not 753 * found. 754 */ 755 public @Nullable Element closest(Evaluator evaluator) { 756 Validate.notNull(evaluator); 757 Element el = this; 758 final Element root = root(); 759 do { 760 if (evaluator.matches(root, el)) 761 return el; 762 el = el.parent(); 763 } while (el != null); 764 return null; 765 } 766 767 /** 768 Find Elements that match the supplied {@index XPath} expression. 769 <p>Note that for convenience of writing the Xpath expression, namespaces are disabled, and queries can be 770 expressed using the element's local name only.</p> 771 <p>By default, XPath 1.0 expressions are supported. If you would to use XPath 2.0 or higher, you can provide an 772 alternate XPathFactory implementation:</p> 773 <ol> 774 <li>Add the implementation to your classpath. E.g. to use <a href="https://www.saxonica.com/products/products.xml">Saxon-HE</a>, add <a href="https://mvnrepository.com/artifact/net.sf.saxon/Saxon-HE">net.sf.saxon:Saxon-HE</a> to your build.</li> 775 <li>Set the system property <code>javax.xml.xpath.XPathFactory:jsoup</code> to the implementing classname. E.g.:<br> 776 <code>System.setProperty(W3CDom.XPathFactoryProperty, "net.sf.saxon.xpath.XPathFactoryImpl");</code> 777 </li> 778 </ol> 779 780 @param xpath XPath expression 781 @return matching elements, or an empty list if none match. 782 @see #selectXpath(String, Class) 783 @since 1.14.3 784 */ 785 public Elements selectXpath(String xpath) { 786 return new Elements(NodeUtils.selectXpath(xpath, this, Element.class)); 787 } 788 789 /** 790 Find Nodes that match the supplied XPath expression. 791 <p>For example, to select TextNodes under {@code p} elements: </p> 792 <pre>List<TextNode> textNodes = doc.selectXpath("//body//p//text()", TextNode.class);</pre> 793 <p>Note that in the jsoup DOM, Attribute objects are not Nodes. To directly select attribute values, do something 794 like:</p> 795 <pre>List<String> hrefs = doc.selectXpath("//a").eachAttr("href");</pre> 796 @param xpath XPath expression 797 @param nodeType the jsoup node type to return 798 @see #selectXpath(String) 799 @return a list of matching nodes 800 @since 1.14.3 801 */ 802 public <T extends Node> List<T> selectXpath(String xpath, Class<T> nodeType) { 803 return NodeUtils.selectXpath(xpath, this, nodeType); 804 } 805 806 /** 807 * Insert a node to the end of this Element's children. The incoming node will be re-parented. 808 * 809 * @param child node to add. 810 * @return this Element, for chaining 811 * @see #prependChild(Node) 812 * @see #insertChildren(int, Collection) 813 */ 814 public Element appendChild(Node child) { 815 Validate.notNull(child); 816 817 // was - Node#addChildren(child). short-circuits an array create and a loop. 818 reparentChild(child); 819 ensureChildNodes(); 820 childNodes.add(child); 821 child.setSiblingIndex(childNodes.size() - 1); 822 return this; 823 } 824 825 /** 826 Insert the given nodes to the end of this Element's children. 827 828 @param children nodes to add 829 @return this Element, for chaining 830 @see #insertChildren(int, Collection) 831 */ 832 public Element appendChildren(Collection<? extends Node> children) { 833 insertChildren(-1, children); 834 return this; 835 } 836 837 /** 838 * Add this element to the supplied parent element, as its next child. 839 * 840 * @param parent element to which this element will be appended 841 * @return this element, so that you can continue modifying the element 842 */ 843 public Element appendTo(Element parent) { 844 Validate.notNull(parent); 845 parent.appendChild(this); 846 return this; 847 } 848 849 /** 850 * Add a node to the start of this element's children. 851 * 852 * @param child node to add. 853 * @return this element, so that you can add more child nodes or elements. 854 */ 855 public Element prependChild(Node child) { 856 Validate.notNull(child); 857 858 addChildren(0, child); 859 return this; 860 } 861 862 /** 863 Insert the given nodes to the start of this Element's children. 864 865 @param children nodes to add 866 @return this Element, for chaining 867 @see #insertChildren(int, Collection) 868 */ 869 public Element prependChildren(Collection<? extends Node> children) { 870 insertChildren(0, children); 871 return this; 872 } 873 874 875 /** 876 * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the 877 * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first. 878 * 879 * @param index 0-based index to insert children at. Specify {@code 0} to insert at the start, {@code -1} at the 880 * end 881 * @param children child nodes to insert 882 * @return this element, for chaining. 883 */ 884 public Element insertChildren(int index, Collection<? extends Node> children) { 885 Validate.notNull(children, "Children collection to be inserted must not be null."); 886 int currentSize = childNodeSize(); 887 if (index < 0) index += currentSize +1; // roll around 888 Validate.isTrue(index >= 0 && index <= currentSize, "Insert position out of bounds."); 889 addChildren(index, children.toArray(new Node[0])); 890 return this; 891 } 892 893 /** 894 * Inserts the given child nodes into this element at the specified index. Current nodes will be shifted to the 895 * right. The inserted nodes will be moved from their current parent. To prevent moving, copy the nodes first. 896 * 897 * @param index 0-based index to insert children at. Specify {@code 0} to insert at the start, {@code -1} at the 898 * end 899 * @param children child nodes to insert 900 * @return this element, for chaining. 901 */ 902 public Element insertChildren(int index, Node... children) { 903 Validate.notNull(children, "Children collection to be inserted must not be null."); 904 int currentSize = childNodeSize(); 905 if (index < 0) index += currentSize +1; // roll around 906 Validate.isTrue(index >= 0 && index <= currentSize, "Insert position out of bounds."); 907 908 addChildren(index, children); 909 return this; 910 } 911 912 /** 913 * Create a new element by tag name, and add it as this Element's last child. 914 * 915 * @param tagName the name of the tag (e.g. {@code div}). 916 * @return the new element, to allow you to add content to it, e.g.: 917 * {@code parent.appendElement("h1").attr("id", "header").text("Welcome");} 918 */ 919 public Element appendElement(String tagName) { 920 return appendElement(tagName, tag.namespace()); 921 } 922 923 /** 924 * Create a new element by tag name and namespace, add it as this Element's last child. 925 * 926 * @param tagName the name of the tag (e.g. {@code div}). 927 * @param namespace the namespace of the tag (e.g. {@link Parser#NamespaceHtml}) 928 * @return the new element, in the specified namespace 929 */ 930 public Element appendElement(String tagName, String namespace) { 931 Parser parser = NodeUtils.parser(this); 932 Element child = new Element(parser.tagSet().valueOf(tagName, namespace, parser.settings()), baseUri()); 933 appendChild(child); 934 return child; 935 } 936 937 /** 938 * Create a new element by tag name, and add it as this Element's first child. 939 * 940 * @param tagName the name of the tag (e.g. {@code div}). 941 * @return the new element, to allow you to add content to it, e.g.: 942 * {@code parent.prependElement("h1").attr("id", "header").text("Welcome");} 943 */ 944 public Element prependElement(String tagName) { 945 return prependElement(tagName, tag.namespace()); 946 } 947 948 /** 949 * Create a new element by tag name and namespace, and add it as this Element's first child. 950 * 951 * @param tagName the name of the tag (e.g. {@code div}). 952 * @param namespace the namespace of the tag (e.g. {@link Parser#NamespaceHtml}) 953 * @return the new element, in the specified namespace 954 */ 955 public Element prependElement(String tagName, String namespace) { 956 Parser parser = NodeUtils.parser(this); 957 Element child = new Element(parser.tagSet().valueOf(tagName, namespace, parser.settings()), baseUri()); 958 prependChild(child); 959 return child; 960 } 961 962 /** 963 * Create and append a new TextNode to this element. 964 * 965 * @param text the (un-encoded) text to add 966 * @return this element 967 */ 968 public Element appendText(String text) { 969 Validate.notNull(text); 970 TextNode node = new TextNode(text); 971 appendChild(node); 972 return this; 973 } 974 975 /** 976 * Create and prepend a new TextNode to this element. 977 * 978 * @param text the decoded text to add 979 * @return this element 980 */ 981 public Element prependText(String text) { 982 Validate.notNull(text); 983 TextNode node = new TextNode(text); 984 prependChild(node); 985 return this; 986 } 987 988 /** 989 * Add inner HTML to this element. The supplied HTML will be parsed, and each node appended to the end of the children. 990 * @param html HTML to add inside this element, after the existing HTML 991 * @return this element 992 * @see #html(String) 993 */ 994 public Element append(String html) { 995 Validate.notNull(html); 996 List<Node> nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri()); 997 addChildren(nodes.toArray(new Node[0])); 998 return this; 999 } 1000 1001 /** 1002 * Add inner HTML into this element. The supplied HTML will be parsed, and each node prepended to the start of the element's children. 1003 * @param html HTML to add inside this element, before the existing HTML 1004 * @return this element 1005 * @see #html(String) 1006 */ 1007 public Element prepend(String html) { 1008 Validate.notNull(html); 1009 List<Node> nodes = NodeUtils.parser(this).parseFragmentInput(html, this, baseUri()); 1010 addChildren(0, nodes.toArray(new Node[0])); 1011 return this; 1012 } 1013 1014 /** 1015 * Insert the specified HTML into the DOM before this element (as a preceding sibling). 1016 * 1017 * @param html HTML to add before this element 1018 * @return this element, for chaining 1019 * @see #after(String) 1020 */ 1021 @Override 1022 public Element before(String html) { 1023 return (Element) super.before(html); 1024 } 1025 1026 /** 1027 * Insert the specified node into the DOM before this node (as a preceding sibling). 1028 * @param node to add before this element 1029 * @return this Element, for chaining 1030 * @see #after(Node) 1031 */ 1032 @Override 1033 public Element before(Node node) { 1034 return (Element) super.before(node); 1035 } 1036 1037 /** 1038 * Insert the specified HTML into the DOM after this element (as a following sibling). 1039 * 1040 * @param html HTML to add after this element 1041 * @return this element, for chaining 1042 * @see #before(String) 1043 */ 1044 @Override 1045 public Element after(String html) { 1046 return (Element) super.after(html); 1047 } 1048 1049 /** 1050 * Insert the specified node into the DOM after this node (as a following sibling). 1051 * @param node to add after this element 1052 * @return this element, for chaining 1053 * @see #before(Node) 1054 */ 1055 @Override 1056 public Element after(Node node) { 1057 return (Element) super.after(node); 1058 } 1059 1060 /** 1061 * Remove all the element's child nodes. Any attributes are left as-is. Each child node has its parent set to 1062 * {@code null}. 1063 * @return this element 1064 */ 1065 @Override 1066 public Element empty() { 1067 // Detach each of the children -> parent links: 1068 int size = childNodes.size(); 1069 for (int i = 0; i < size; i++) 1070 childNodes.get(i).parentNode = null; 1071 childNodes.clear(); 1072 return this; 1073 } 1074 1075 /** 1076 * Wrap the supplied HTML around this element. 1077 * 1078 * @param html HTML to wrap around this element, e.g. {@code <div class="head"></div>}. Can be arbitrarily deep. 1079 * @return this element, for chaining. 1080 */ 1081 @Override 1082 public Element wrap(String html) { 1083 return (Element) super.wrap(html); 1084 } 1085 1086 /** 1087 Gets an #id selector for this element, if it has a unique ID. Otherwise, returns an empty string. 1088 1089 @param ownerDoc the document that owns this element, if there is one 1090 */ 1091 private String uniqueIdSelector(@Nullable Document ownerDoc) { 1092 String id = id(); 1093 if (!id.isEmpty()) { // check if the ID is unique and matches this 1094 String idSel = "#" + escapeCssIdentifier(id); 1095 if (ownerDoc != null) { 1096 Elements els = ownerDoc.select(idSel); 1097 if (els.size() == 1 && els.get(0) == this) return idSel; 1098 } else { 1099 return idSel; 1100 } 1101 } 1102 return EmptyString; 1103 } 1104 1105 /** 1106 Get a CSS selector that will uniquely select this element. 1107 <p> 1108 If the element has an ID, returns #id; otherwise returns the parent (if any) CSS selector, followed by 1109 {@literal '>'}, followed by a unique selector for the element (tag.class.class:nth-child(n)). 1110 </p> 1111 1112 @return the CSS Path that can be used to retrieve the element in a selector. 1113 */ 1114 public String cssSelector() { 1115 Document ownerDoc = ownerDocument(); 1116 String idSel = uniqueIdSelector(ownerDoc); 1117 if (!idSel.isEmpty()) return idSel; 1118 1119 // No unique ID, work up the parent stack and find either a unique ID to hang from, or just a GP > Parent > Child chain 1120 StringBuilder selector = StringUtil.borrowBuilder(); 1121 Element el = this; 1122 while (el != null && !(el instanceof Document)) { 1123 idSel = el.uniqueIdSelector(ownerDoc); 1124 if (!idSel.isEmpty()) { 1125 selector.insert(0, idSel); 1126 break; // found a unique ID to use as ancestor; stop 1127 } 1128 selector.insert(0, el.cssSelectorComponent()); 1129 el = el.parent(); 1130 } 1131 return StringUtil.releaseBuilder(selector); 1132 } 1133 1134 private String cssSelectorComponent() { 1135 // Escape tagname, and translate HTML namespace ns:tag to CSS namespace syntax ns|tag 1136 String tagName = escapeCssIdentifier(tagName()).replace("\\:", "|"); 1137 StringBuilder selector = StringUtil.borrowBuilder().append(tagName); 1138 String classes = classNames().stream().map(TokenQueue::escapeCssIdentifier) 1139 .collect(StringUtil.joining(".")); 1140 if (!classes.isEmpty()) 1141 selector.append('.').append(classes); 1142 1143 if (parent() == null || parent() instanceof Document) // don't add Document to selector, as will always have a html node 1144 return StringUtil.releaseBuilder(selector); 1145 1146 selector.insert(0, " > "); 1147 if (parent().select(selector.toString()).size() > 1) 1148 selector.append(String.format( 1149 ":nth-child(%d)", elementSiblingIndex() + 1)); 1150 1151 return StringUtil.releaseBuilder(selector); 1152 } 1153 1154 /** 1155 * Get sibling elements. If the element has no sibling elements, returns an empty list. An element is not a sibling 1156 * of itself, so will not be included in the returned list. 1157 * @return sibling elements 1158 */ 1159 public Elements siblingElements() { 1160 if (parentNode == null) 1161 return new Elements(0); 1162 1163 List<Element> elements = parent().childElementsList(); 1164 Elements siblings = new Elements(elements.size() - 1); 1165 for (Element el: elements) 1166 if (el != this) 1167 siblings.add(el); 1168 return siblings; 1169 } 1170 1171 1172 1173 /** 1174 * Get each of the sibling elements that come after this element. 1175 * 1176 * @return each of the element siblings after this element, or an empty list if there are no next sibling elements 1177 */ 1178 public Elements nextElementSiblings() { 1179 return nextElementSiblings(true); 1180 } 1181 1182 /** 1183 * Get each of the element siblings before this element. 1184 * 1185 * @return the previous element siblings, or an empty list if there are none. 1186 */ 1187 public Elements previousElementSiblings() { 1188 return nextElementSiblings(false); 1189 } 1190 1191 private Elements nextElementSiblings(boolean next) { 1192 Elements els = new Elements(); 1193 if (parentNode == null) 1194 return els; 1195 els.add(this); 1196 return next ? els.nextAll() : els.prevAll(); 1197 } 1198 1199 /** 1200 * Gets the first Element sibling of this element. That may be this element. 1201 * @return the first sibling that is an element (aka the parent's first element child) 1202 */ 1203 public Element firstElementSibling() { 1204 if (parent() != null) { 1205 //noinspection DataFlowIssue (not nullable, would be this is no other sibs) 1206 return parent().firstElementChild(); 1207 } else 1208 return this; // orphan is its own first sibling 1209 } 1210 1211 /** 1212 * Get the list index of this element in its element sibling list. I.e. if this is the first element 1213 * sibling, returns 0. 1214 * @return position in element sibling list 1215 */ 1216 public int elementSiblingIndex() { 1217 if (parent() == null) return 0; 1218 return indexInList(this, parent().childElementsList()); 1219 } 1220 1221 /** 1222 * Gets the last element sibling of this element. That may be this element. 1223 * @return the last sibling that is an element (aka the parent's last element child) 1224 */ 1225 public Element lastElementSibling() { 1226 if (parent() != null) { 1227 //noinspection DataFlowIssue (not nullable, would be this if no other sibs) 1228 return parent().lastElementChild(); 1229 } else 1230 return this; 1231 } 1232 1233 private static <E extends Element> int indexInList(Element search, List<E> elements) { 1234 final int size = elements.size(); 1235 for (int i = 0; i < size; i++) { 1236 if (elements.get(i) == search) 1237 return i; 1238 } 1239 return 0; 1240 } 1241 1242 /** 1243 Gets the first child of this Element that is an Element, or {@code null} if there is none. 1244 @return the first Element child node, or null. 1245 @see #firstChild() 1246 @see #lastElementChild() 1247 @since 1.15.2 1248 */ 1249 public @Nullable Element firstElementChild() { 1250 int size = childNodes.size(); 1251 for (int i = 0; i < size; i++) { 1252 Node node = childNodes.get(i); 1253 if (node instanceof Element) return (Element) node; 1254 } 1255 return null; 1256 } 1257 1258 /** 1259 Gets the last child of this Element that is an Element, or @{code null} if there is none. 1260 @return the last Element child node, or null. 1261 @see #lastChild() 1262 @see #firstElementChild() 1263 @since 1.15.2 1264 */ 1265 public @Nullable Element lastElementChild() { 1266 for (int i = childNodes.size() - 1; i >= 0; i--) { 1267 Node node = childNodes.get(i); 1268 if (node instanceof Element) return (Element) node; 1269 } 1270 return null; 1271 } 1272 1273 // DOM type methods 1274 1275 /** 1276 * Finds elements, including and recursively under this element, with the specified tag name. 1277 * @param tagName The tag name to search for (case insensitively). 1278 * @return a matching unmodifiable list of elements. Will be empty if this element and none of its children match. 1279 */ 1280 public Elements getElementsByTag(String tagName) { 1281 Validate.notEmpty(tagName); 1282 tagName = normalize(tagName); 1283 1284 return Collector.collect(new Evaluator.Tag(tagName), this); 1285 } 1286 1287 /** 1288 * Find an element by ID, including or under this element. 1289 * <p> 1290 * Note that this finds the first matching ID, starting with this element. If you search down from a different 1291 * starting point, it is possible to find a different element by ID. For unique element by ID within a Document, 1292 * use {@link Document#getElementById(String)} 1293 * @param id The ID to search for. 1294 * @return The first matching element by ID, starting with this element, or null if none found. 1295 */ 1296 public @Nullable Element getElementById(String id) { 1297 Validate.notEmpty(id); 1298 return Collector.findFirst(new Evaluator.Id(id), this); 1299 } 1300 1301 /** 1302 * Find elements that have this class, including or under this element. Case-insensitive. 1303 * <p> 1304 * Elements can have multiple classes (e.g. {@code <div class="header round first">}). This method 1305 * checks each class, so you can find the above with {@code el.getElementsByClass("header");}. 1306 * 1307 * @param className the name of the class to search for. 1308 * @return elements with the supplied class name, empty if none 1309 * @see #hasClass(String) 1310 * @see #classNames() 1311 */ 1312 public Elements getElementsByClass(String className) { 1313 Validate.notEmpty(className); 1314 1315 return Collector.collect(new Evaluator.Class(className), this); 1316 } 1317 1318 /** 1319 * Find elements that have a named attribute set. Case-insensitive. 1320 * 1321 * @param key name of the attribute, e.g. {@code href} 1322 * @return elements that have this attribute, empty if none 1323 */ 1324 public Elements getElementsByAttribute(String key) { 1325 Validate.notEmpty(key); 1326 key = key.trim(); 1327 1328 return Collector.collect(new Evaluator.Attribute(key), this); 1329 } 1330 1331 /** 1332 * Find elements that have an attribute name starting with the supplied prefix. Use {@code data-} to find elements 1333 * that have HTML5 datasets. 1334 * @param keyPrefix name prefix of the attribute e.g. {@code data-} 1335 * @return elements that have attribute names that start with the prefix, empty if none. 1336 */ 1337 public Elements getElementsByAttributeStarting(String keyPrefix) { 1338 Validate.notEmpty(keyPrefix); 1339 keyPrefix = keyPrefix.trim(); 1340 1341 return Collector.collect(new Evaluator.AttributeStarting(keyPrefix), this); 1342 } 1343 1344 /** 1345 * Find elements that have an attribute with the specific value. Case-insensitive. 1346 * 1347 * @param key name of the attribute 1348 * @param value value of the attribute 1349 * @return elements that have this attribute with this value, empty if none 1350 */ 1351 public Elements getElementsByAttributeValue(String key, String value) { 1352 return Collector.collect(new Evaluator.AttributeWithValue(key, value), this); 1353 } 1354 1355 /** 1356 * Find elements that either do not have this attribute, or have it with a different value. Case-insensitive. 1357 * 1358 * @param key name of the attribute 1359 * @param value value of the attribute 1360 * @return elements that do not have a matching attribute 1361 */ 1362 public Elements getElementsByAttributeValueNot(String key, String value) { 1363 return Collector.collect(new Evaluator.AttributeWithValueNot(key, value), this); 1364 } 1365 1366 /** 1367 * Find elements that have attributes that start with the value prefix. Case-insensitive. 1368 * 1369 * @param key name of the attribute 1370 * @param valuePrefix start of attribute value 1371 * @return elements that have attributes that start with the value prefix 1372 */ 1373 public Elements getElementsByAttributeValueStarting(String key, String valuePrefix) { 1374 return Collector.collect(new Evaluator.AttributeWithValueStarting(key, valuePrefix), this); 1375 } 1376 1377 /** 1378 * Find elements that have attributes that end with the value suffix. Case-insensitive. 1379 * 1380 * @param key name of the attribute 1381 * @param valueSuffix end of the attribute value 1382 * @return elements that have attributes that end with the value suffix 1383 */ 1384 public Elements getElementsByAttributeValueEnding(String key, String valueSuffix) { 1385 return Collector.collect(new Evaluator.AttributeWithValueEnding(key, valueSuffix), this); 1386 } 1387 1388 /** 1389 * Find elements that have attributes whose value contains the match string. Case-insensitive. 1390 * 1391 * @param key name of the attribute 1392 * @param match substring of value to search for 1393 * @return elements that have attributes containing this text 1394 */ 1395 public Elements getElementsByAttributeValueContaining(String key, String match) { 1396 return Collector.collect(new Evaluator.AttributeWithValueContaining(key, match), this); 1397 } 1398 1399 /** 1400 * Find elements that have an attribute whose value matches the supplied regular expression. 1401 * @param key name of the attribute 1402 * @param pattern compiled regular expression to match against attribute values 1403 * @return elements that have attributes matching this regular expression 1404 */ 1405 public Elements getElementsByAttributeValueMatching(String key, Pattern pattern) { 1406 return Collector.collect(new Evaluator.AttributeWithValueMatching(key, pattern), this); 1407 1408 } 1409 1410 /** 1411 * Find elements that have attributes whose values match the supplied regular expression. 1412 * @param key name of the attribute 1413 * @param regex regular expression to match against attribute values. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as {@code (?i)} and {@code (?m)}) to control regex options. 1414 * @return elements that have attributes matching this regular expression 1415 */ 1416 public Elements getElementsByAttributeValueMatching(String key, String regex) { 1417 Pattern pattern; 1418 try { 1419 pattern = Pattern.compile(regex); 1420 } catch (PatternSyntaxException e) { 1421 throw new IllegalArgumentException("Pattern syntax error: " + regex, e); 1422 } 1423 return getElementsByAttributeValueMatching(key, pattern); 1424 } 1425 1426 /** 1427 * Find elements whose sibling index is less than the supplied index. 1428 * @param index 0-based index 1429 * @return elements less than index 1430 */ 1431 public Elements getElementsByIndexLessThan(int index) { 1432 return Collector.collect(new Evaluator.IndexLessThan(index), this); 1433 } 1434 1435 /** 1436 * Find elements whose sibling index is greater than the supplied index. 1437 * @param index 0-based index 1438 * @return elements greater than index 1439 */ 1440 public Elements getElementsByIndexGreaterThan(int index) { 1441 return Collector.collect(new Evaluator.IndexGreaterThan(index), this); 1442 } 1443 1444 /** 1445 * Find elements whose sibling index is equal to the supplied index. 1446 * @param index 0-based index 1447 * @return elements equal to index 1448 */ 1449 public Elements getElementsByIndexEquals(int index) { 1450 return Collector.collect(new Evaluator.IndexEquals(index), this); 1451 } 1452 1453 /** 1454 * Find elements that contain the specified string. The search is case-insensitive. The text may appear directly 1455 * in the element, or in any of its descendants. 1456 * @param searchText to look for in the element's text 1457 * @return elements that contain the string, case-insensitive. 1458 * @see Element#text() 1459 */ 1460 public Elements getElementsContainingText(String searchText) { 1461 return Collector.collect(new Evaluator.ContainsText(searchText), this); 1462 } 1463 1464 /** 1465 * Find elements that directly contain the specified string. The search is case-insensitive. The text must appear directly 1466 * in the element, not in any of its descendants. 1467 * @param searchText to look for in the element's own text 1468 * @return elements that contain the string, case-insensitive. 1469 * @see Element#ownText() 1470 */ 1471 public Elements getElementsContainingOwnText(String searchText) { 1472 return Collector.collect(new Evaluator.ContainsOwnText(searchText), this); 1473 } 1474 1475 /** 1476 * Find elements whose text matches the supplied regular expression. 1477 * @param pattern regular expression to match text against 1478 * @return elements matching the supplied regular expression. 1479 * @see Element#text() 1480 */ 1481 public Elements getElementsMatchingText(Pattern pattern) { 1482 return Collector.collect(new Evaluator.Matches(pattern), this); 1483 } 1484 1485 /** 1486 * Find elements whose text matches the supplied regular expression. 1487 * @param regex regular expression to match text against. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as {@code (?i)} and {@code (?m)}) to control regex options. 1488 * @return elements matching the supplied regular expression. 1489 * @see Element#text() 1490 */ 1491 public Elements getElementsMatchingText(String regex) { 1492 Pattern pattern; 1493 try { 1494 pattern = Pattern.compile(regex); 1495 } catch (PatternSyntaxException e) { 1496 throw new IllegalArgumentException("Pattern syntax error: " + regex, e); 1497 } 1498 return getElementsMatchingText(pattern); 1499 } 1500 1501 /** 1502 * Find elements whose own text matches the supplied regular expression. 1503 * @param pattern regular expression to match text against 1504 * @return elements matching the supplied regular expression. 1505 * @see Element#ownText() 1506 */ 1507 public Elements getElementsMatchingOwnText(Pattern pattern) { 1508 return Collector.collect(new Evaluator.MatchesOwn(pattern), this); 1509 } 1510 1511 /** 1512 * Find elements whose own text matches the supplied regular expression. 1513 * @param regex regular expression to match text against. You can use <a href="http://java.sun.com/docs/books/tutorial/essential/regex/pattern.html#embedded">embedded flags</a> (such as {@code (?i)} and {@code (?m)}) to control regex options. 1514 * @return elements matching the supplied regular expression. 1515 * @see Element#ownText() 1516 */ 1517 public Elements getElementsMatchingOwnText(String regex) { 1518 Pattern pattern; 1519 try { 1520 pattern = Pattern.compile(regex); 1521 } catch (PatternSyntaxException e) { 1522 throw new IllegalArgumentException("Pattern syntax error: " + regex, e); 1523 } 1524 return getElementsMatchingOwnText(pattern); 1525 } 1526 1527 /** 1528 * Find all elements under this element (including self, and children of children). 1529 * 1530 * @return all elements 1531 */ 1532 public Elements getAllElements() { 1533 return Collector.collect(new Evaluator.AllElements(), this); 1534 } 1535 1536 /** 1537 Gets the <b>normalized, combined text</b> of this element and all its children. Whitespace is normalized and 1538 trimmed. 1539 <p>For example, given HTML {@code <p>Hello <b>there</b> now! </p>}, {@code p.text()} returns {@code "Hello there 1540 now!"} 1541 <p>If you do not want normalized text, use {@link #wholeText()}. If you want just the text of this node (and not 1542 children), use {@link #ownText()} 1543 <p>Note that this method returns the textual content that would be presented to a reader. The contents of data 1544 nodes (such as {@code <script>} tags) are not considered text. Use {@link #data()} or {@link #html()} to retrieve 1545 that content. 1546 1547 @return decoded, normalized text, or empty string if none. 1548 @see #wholeText() 1549 @see #ownText() 1550 @see #textNodes() 1551 */ 1552 public String text() { 1553 final StringBuilder accum = StringUtil.borrowBuilder(); 1554 new TextAccumulator(accum).traverse(this); 1555 return StringUtil.releaseBuilder(accum).trim(); 1556 } 1557 1558 private static class TextAccumulator implements NodeVisitor { 1559 private final StringBuilder accum; 1560 1561 public TextAccumulator(StringBuilder accum) { 1562 this.accum = accum; 1563 } 1564 1565 @Override public void head(Node node, int depth) { 1566 if (node instanceof TextNode) { 1567 TextNode textNode = (TextNode) node; 1568 appendNormalisedText(accum, textNode); 1569 } else if (node instanceof Element) { 1570 Element element = (Element) node; 1571 if (accum.length() > 0 && 1572 (element.isBlock() || element.nameIs("br")) && 1573 !lastCharIsWhitespace(accum)) 1574 accum.append(' '); 1575 } 1576 } 1577 1578 @Override public void tail(Node node, int depth) { 1579 // make sure there is a space between block tags and immediately following text nodes or inline elements <div>One</div>Two should be "One Two". 1580 if (node instanceof Element) { 1581 Element element = (Element) node; 1582 Node next = node.nextSibling(); 1583 if (!element.tag.isInline() && (next instanceof TextNode || next instanceof Element && ((Element) next).tag.isInline()) && !lastCharIsWhitespace(accum)) 1584 accum.append(' '); 1585 } 1586 1587 } 1588 } 1589 1590 /** 1591 Get the non-normalized, decoded text of this element and its children, including only any newlines and spaces 1592 present in the original source. 1593 @return decoded, non-normalized text 1594 @see #text() 1595 @see #wholeOwnText() 1596 */ 1597 public String wholeText() { 1598 return wholeTextOf(nodeStream()); 1599 } 1600 1601 /** 1602 An Element's nodeValue is its whole own text. 1603 */ 1604 @Override 1605 public String nodeValue() { 1606 return wholeOwnText(); 1607 } 1608 1609 private static String wholeTextOf(Stream<Node> stream) { 1610 return stream.map(node -> { 1611 if (node instanceof TextNode) return ((TextNode) node).getWholeText(); 1612 if (node.nameIs("br")) return "\n"; 1613 return ""; 1614 }).collect(StringUtil.joining("")); 1615 } 1616 1617 /** 1618 Get the non-normalized, decoded text of this element, <b>not including</b> any child elements, including any 1619 newlines and spaces present in the original source. 1620 @return decoded, non-normalized text that is a direct child of this Element 1621 @see #text() 1622 @see #wholeText() 1623 @see #ownText() 1624 @since 1.15.1 1625 */ 1626 public String wholeOwnText() { 1627 return wholeTextOf(childNodes.stream()); 1628 } 1629 1630 /** 1631 * Gets the (normalized) text owned by this element only; does not get the combined text of all children. 1632 * <p> 1633 * For example, given HTML {@code <p>Hello <b>there</b> now!</p>}, {@code p.ownText()} returns {@code "Hello now!"}, 1634 * whereas {@code p.text()} returns {@code "Hello there now!"}. 1635 * Note that the text within the {@code b} element is not returned, as it is not a direct child of the {@code p} element. 1636 * 1637 * @return decoded text, or empty string if none. 1638 * @see #text() 1639 * @see #textNodes() 1640 */ 1641 public String ownText() { 1642 StringBuilder sb = StringUtil.borrowBuilder(); 1643 ownText(sb); 1644 return StringUtil.releaseBuilder(sb).trim(); 1645 } 1646 1647 private void ownText(StringBuilder accum) { 1648 for (int i = 0; i < childNodeSize(); i++) { 1649 Node child = childNodes.get(i); 1650 if (child instanceof TextNode) { 1651 TextNode textNode = (TextNode) child; 1652 appendNormalisedText(accum, textNode); 1653 } else if (child.nameIs("br") && !lastCharIsWhitespace(accum)) { 1654 accum.append(" "); 1655 } 1656 } 1657 } 1658 1659 private static void appendNormalisedText(StringBuilder accum, TextNode textNode) { 1660 String text = textNode.getWholeText(); 1661 if (preserveWhitespace(textNode.parentNode) || textNode instanceof CDataNode) 1662 accum.append(text); 1663 else 1664 StringUtil.appendNormalisedWhitespace(accum, text, lastCharIsWhitespace(accum)); 1665 } 1666 1667 static boolean preserveWhitespace(@Nullable Node node) { 1668 // looks only at this element and five levels up, to prevent recursion & needless stack searches 1669 if (node instanceof Element) { 1670 Element el = (Element) node; 1671 int i = 0; 1672 do { 1673 if (el.tag.preserveWhitespace()) 1674 return true; 1675 el = el.parent(); 1676 i++; 1677 } while (i < 6 && el != null); 1678 } 1679 return false; 1680 } 1681 1682 /** 1683 * Set the text of this element. Any existing contents (text or elements) will be cleared. 1684 * <p>As a special case, for {@code <script>} and {@code <style>} tags, the input text will be treated as data, 1685 * not visible text.</p> 1686 * @param text decoded text 1687 * @return this element 1688 */ 1689 public Element text(String text) { 1690 Validate.notNull(text); 1691 empty(); 1692 // special case for script/style in HTML (or customs): should be data node 1693 if (tag().is(Tag.Data)) 1694 appendChild(new DataNode(text)); 1695 else 1696 appendChild(new TextNode(text)); 1697 1698 return this; 1699 } 1700 1701 /** 1702 Checks if the current element or any of its child elements contain non-whitespace text. 1703 @return {@code true} if the element has non-blank text content, {@code false} otherwise. 1704 */ 1705 public boolean hasText() { 1706 AtomicBoolean hasText = new AtomicBoolean(false); 1707 filter((node, depth) -> { 1708 if (node instanceof TextNode) { 1709 TextNode textNode = (TextNode) node; 1710 if (!textNode.isBlank()) { 1711 hasText.set(true); 1712 return NodeFilter.FilterResult.STOP; 1713 } 1714 } 1715 return NodeFilter.FilterResult.CONTINUE; 1716 }); 1717 return hasText.get(); 1718 } 1719 1720 /** 1721 * Get the combined data of this element. Data is e.g. the inside of a {@code <script>} tag. Note that data is NOT the 1722 * text of the element. Use {@link #text()} to get the text that would be visible to a user, and {@code data()} 1723 * for the contents of scripts, comments, CSS styles, etc. 1724 * 1725 * @return the data, or empty string if none 1726 * 1727 * @see #dataNodes() 1728 */ 1729 public String data() { 1730 StringBuilder sb = StringUtil.borrowBuilder(); 1731 traverse((childNode, depth) -> { 1732 if (childNode instanceof DataNode) { 1733 DataNode data = (DataNode) childNode; 1734 sb.append(data.getWholeData()); 1735 } else if (childNode instanceof Comment) { 1736 Comment comment = (Comment) childNode; 1737 sb.append(comment.getData()); 1738 } else if (childNode instanceof CDataNode) { 1739 // this shouldn't really happen because the html parser won't see the cdata as anything special when parsing script. 1740 // but in case another type gets through. 1741 CDataNode cDataNode = (CDataNode) childNode; 1742 sb.append(cDataNode.getWholeText()); 1743 } 1744 }); 1745 return StringUtil.releaseBuilder(sb); 1746 } 1747 1748 /** 1749 * Gets the literal value of this element's "class" attribute, which may include multiple class names, space 1750 * separated. (E.g. on <code><div class="header gray"></code> returns, "<code>header gray</code>") 1751 * @return The literal class attribute, or <b>empty string</b> if no class attribute set. 1752 */ 1753 public String className() { 1754 return attr("class").trim(); 1755 } 1756 1757 /** 1758 * Get each of the element's class names. E.g. on element {@code <div class="header gray">}, 1759 * returns a set of two elements {@code "header", "gray"}. Note that modifications to this set are not pushed to 1760 * the backing {@code class} attribute; use the {@link #classNames(java.util.Set)} method to persist them. 1761 * @return set of classnames, empty if no class attribute 1762 */ 1763 public Set<String> classNames() { 1764 String[] names = ClassSplit.split(className()); 1765 Set<String> classNames = new LinkedHashSet<>(Arrays.asList(names)); 1766 classNames.remove(""); // if classNames() was empty, would include an empty class 1767 1768 return classNames; 1769 } 1770 1771 /** 1772 Set the element's {@code class} attribute to the supplied class names. 1773 @param classNames set of classes 1774 @return this element, for chaining 1775 */ 1776 public Element classNames(Set<String> classNames) { 1777 Validate.notNull(classNames); 1778 if (classNames.isEmpty()) { 1779 attributes().remove("class"); 1780 } else { 1781 attributes().put("class", StringUtil.join(classNames, " ")); 1782 } 1783 return this; 1784 } 1785 1786 /** 1787 * Tests if this element has a class. Case-insensitive. 1788 * @param className name of class to check for 1789 * @return true if it does, false if not 1790 */ 1791 // performance sensitive 1792 public boolean hasClass(String className) { 1793 if (attributes == null) 1794 return false; 1795 1796 final String classAttr = attributes.getIgnoreCase("class"); 1797 final int len = classAttr.length(); 1798 final int wantLen = className.length(); 1799 1800 if (len == 0 || len < wantLen) { 1801 return false; 1802 } 1803 1804 // if both lengths are equal, only need compare the className with the attribute 1805 if (len == wantLen) { 1806 return className.equalsIgnoreCase(classAttr); 1807 } 1808 1809 // otherwise, scan for whitespace and compare regions (with no string or arraylist allocations) 1810 boolean inClass = false; 1811 int start = 0; 1812 for (int i = 0; i < len; i++) { 1813 if (Character.isWhitespace(classAttr.charAt(i))) { 1814 if (inClass) { 1815 // white space ends a class name, compare it with the requested one, ignore case 1816 if (i - start == wantLen && classAttr.regionMatches(true, start, className, 0, wantLen)) { 1817 return true; 1818 } 1819 inClass = false; 1820 } 1821 } else { 1822 if (!inClass) { 1823 // we're in a class name : keep the start of the substring 1824 inClass = true; 1825 start = i; 1826 } 1827 } 1828 } 1829 1830 // check the last entry 1831 if (inClass && len - start == wantLen) { 1832 return classAttr.regionMatches(true, start, className, 0, wantLen); 1833 } 1834 1835 return false; 1836 } 1837 1838 /** 1839 Add a class name to this element's {@code class} attribute. 1840 @param className class name to add 1841 @return this element 1842 */ 1843 public Element addClass(String className) { 1844 Validate.notNull(className); 1845 1846 Set<String> classes = classNames(); 1847 classes.add(className); 1848 classNames(classes); 1849 1850 return this; 1851 } 1852 1853 /** 1854 Remove a class name from this element's {@code class} attribute. 1855 @param className class name to remove 1856 @return this element 1857 */ 1858 public Element removeClass(String className) { 1859 Validate.notNull(className); 1860 1861 Set<String> classes = classNames(); 1862 classes.remove(className); 1863 classNames(classes); 1864 1865 return this; 1866 } 1867 1868 /** 1869 Toggle a class name on this element's {@code class} attribute: if present, remove it; otherwise add it. 1870 @param className class name to toggle 1871 @return this element 1872 */ 1873 public Element toggleClass(String className) { 1874 Validate.notNull(className); 1875 1876 Set<String> classes = classNames(); 1877 if (classes.contains(className)) 1878 classes.remove(className); 1879 else 1880 classes.add(className); 1881 classNames(classes); 1882 1883 return this; 1884 } 1885 1886 /** 1887 * Get the value of a form element (input, textarea, etc). 1888 * @return the value of the form element, or empty string if not set. 1889 */ 1890 public String val() { 1891 if (elementIs("textarea", NamespaceHtml)) 1892 return text(); 1893 else 1894 return attr("value"); 1895 } 1896 1897 /** 1898 * Set the value of a form element (input, textarea, etc). 1899 * @param value value to set 1900 * @return this element (for chaining) 1901 */ 1902 public Element val(String value) { 1903 if (elementIs("textarea", NamespaceHtml)) 1904 text(value); 1905 else 1906 attr("value", value); 1907 return this; 1908 } 1909 1910 /** 1911 Get the source range (start and end positions) of the end (closing) tag for this Element. Position tracking must be 1912 enabled prior to parsing the content. 1913 @return the range of the closing tag for this element, or {@code untracked} if its range was not tracked. 1914 @see org.jsoup.parser.Parser#setTrackPosition(boolean) 1915 @see Node#sourceRange() 1916 @see Range#isImplicit() 1917 @since 1.15.2 1918 */ 1919 public Range endSourceRange() { 1920 return Range.of(this, false); 1921 } 1922 1923 @Override 1924 void outerHtmlHead(final QuietAppendable accum, Document.OutputSettings out) { 1925 String tagName = safeTagName(out.syntax()); 1926 accum.append('<').append(tagName); 1927 if (attributes != null) attributes.html(accum, out); 1928 1929 if (childNodes.isEmpty()) { 1930 boolean xmlMode = out.syntax() == xml || !tag.namespace().equals(NamespaceHtml); 1931 if (xmlMode && (tag.is(Tag.SeenSelfClose) || (tag.isKnownTag() && (tag.isEmpty() || tag.isSelfClosing())))) { 1932 accum.append(" />"); 1933 } else if (!xmlMode && tag.isEmpty()) { // html void element 1934 accum.append('>'); 1935 } else { 1936 accum.append("></").append(tagName).append('>'); 1937 } 1938 } else { 1939 accum.append('>'); 1940 } 1941 } 1942 1943 @Override 1944 void outerHtmlTail(QuietAppendable accum, Document.OutputSettings out) { 1945 if (!childNodes.isEmpty()) 1946 accum.append("</").append(safeTagName(out.syntax())).append('>'); 1947 // if empty, we have already closed in htmlHead 1948 } 1949 1950 /* If XML syntax, normalizes < to _ in tag name. */ 1951 @Nullable private String safeTagName(Document.OutputSettings.Syntax syntax) { 1952 return syntax == xml ? Normalizer.xmlSafeTagName(tagName()) : tagName(); 1953 } 1954 1955 /** 1956 * Retrieves the element's inner HTML. E.g. on a {@code <div>} with one empty {@code <p>}, would return 1957 * {@code <p></p>}. (Whereas {@link #outerHtml()} would return {@code <div><p></p></div>}.) 1958 * 1959 * @return String of HTML. 1960 * @see #outerHtml() 1961 */ 1962 public String html() { 1963 StringBuilder sb = StringUtil.borrowBuilder(); 1964 html(sb); 1965 String html = StringUtil.releaseBuilder(sb); 1966 return NodeUtils.outputSettings(this).prettyPrint() ? html.trim() : html; 1967 } 1968 1969 @Override 1970 public <T extends Appendable> T html(T accum) { 1971 Node child = firstChild(); 1972 if (child != null) { 1973 Printer printer = Printer.printerFor(child, QuietAppendable.wrap(accum)); 1974 while (child != null) { 1975 printer.traverse(child); 1976 child = child.nextSibling(); 1977 } 1978 } 1979 return accum; 1980 } 1981 1982 /** 1983 * Set this element's inner HTML. Clears the existing HTML first. 1984 * @param html HTML to parse and set into this element 1985 * @return this element 1986 * @see #append(String) 1987 */ 1988 public Element html(String html) { 1989 empty(); 1990 append(html); 1991 return this; 1992 } 1993 1994 @Override 1995 public Element clone() { 1996 return (Element) super.clone(); 1997 } 1998 1999 @Override 2000 public Element shallowClone() { 2001 // simpler than implementing a clone version with no child copy 2002 String baseUri = baseUri(); 2003 if (baseUri.isEmpty()) baseUri = null; // saves setting a blank internal attribute 2004 return new Element(tag, baseUri, attributes == null ? null : attributes.clone()); 2005 } 2006 2007 @Override 2008 protected Element doClone(@Nullable Node parent) { 2009 Element clone = (Element) super.doClone(parent); 2010 clone.childNodes = new NodeList(childNodes.size()); 2011 clone.childNodes.addAll(childNodes); // the children then get iterated and cloned in Node.clone 2012 if (attributes != null) { 2013 clone.attributes = attributes.clone(); 2014 // clear any cached children 2015 clone.attributes.userData(childElsKey, null); 2016 } 2017 2018 return clone; 2019 } 2020 2021 // overrides of Node for call chaining 2022 @Override 2023 public Element clearAttributes() { 2024 if (attributes != null) { 2025 super.clearAttributes(); // keeps internal attributes via iterator 2026 if (attributes.size == 0) 2027 attributes = null; // only remove entirely if no internal attributes 2028 } 2029 2030 return this; 2031 } 2032 2033 @Override 2034 public Element removeAttr(String attributeKey) { 2035 return (Element) super.removeAttr(attributeKey); 2036 } 2037 2038 @Override 2039 public Element root() { 2040 return (Element) super.root(); // probably a document, but always at least an element 2041 } 2042 2043 @Override 2044 public Element traverse(NodeVisitor nodeVisitor) { 2045 return (Element) super.traverse(nodeVisitor); 2046 } 2047 2048 @Override 2049 public Element forEachNode(Consumer<? super Node> action) { 2050 return (Element) super.forEachNode(action); 2051 } 2052 2053 /** 2054 Perform the supplied action on this Element and each of its descendant Elements, during a depth-first traversal. 2055 Elements may be inspected, changed, added, replaced, or removed. 2056 @param action the function to perform on the element 2057 @see Node#forEachNode(Consumer) 2058 */ 2059 @Override 2060 public void forEach(Consumer<? super Element> action) { 2061 stream().forEach(action); 2062 } 2063 2064 /** 2065 Returns an Iterator that iterates this Element and each of its descendant Elements, in document order. 2066 @return an Iterator 2067 */ 2068 @Override 2069 public Iterator<Element> iterator() { 2070 return new NodeIterator<>(this, Element.class); 2071 } 2072 2073 @Override 2074 public Element filter(NodeFilter nodeFilter) { 2075 return (Element) super.filter(nodeFilter); 2076 } 2077 2078 static final class NodeList extends ArrayList<Node> { 2079 /** Tracks if the children have valid sibling indices. We only need to reindex on siblingIndex() demand. */ 2080 boolean validChildren = true; 2081 2082 public NodeList(int size) { 2083 super(size); 2084 } 2085 2086 int modCount() { 2087 return this.modCount; 2088 } 2089 } 2090 2091 void reindexChildren() { 2092 final int size = childNodes.size(); 2093 for (int i = 0; i < size; i++) { 2094 childNodes.get(i).setSiblingIndex(i); 2095 } 2096 childNodes.validChildren = true; 2097 } 2098 2099 void invalidateChildren() { 2100 childNodes.validChildren = false; 2101 } 2102 2103 boolean hasValidChildren() { 2104 return childNodes.validChildren; 2105 } 2106}