001package org.jsoup.select; 002 003import org.jsoup.nodes.Element; 004import org.jsoup.nodes.LeafNode; 005import org.jsoup.nodes.Node; 006import org.jsoup.nodes.TextNode; 007import org.jspecify.annotations.Nullable; 008 009import java.util.stream.Stream; 010 011import static java.util.stream.Collectors.toCollection; 012 013/** 014 Collects elements and nodes that match an evaluator. 015 <p>The evaluator determines whether searches include the starting element. See 016 <a href="{@docRoot}/org/jsoup/select/Selector.html#query-compatibility">Query compatibility changes</a>.</p> 017 018 @author Jonathan Hedley */ 019public class Collector { 020 021 private Collector() {} 022 023 /** 024 Build a list of elements, by visiting the root and every descendant of root, and testing it against the Evaluator. 025 @param eval Evaluator to test elements against 026 @param root root of tree to descend 027 @return list of matches; empty if none 028 */ 029 public static Elements collect(Evaluator eval, Element root) { 030 Stream<Element> stream = eval.wantsNodes() ? 031 streamNodes(eval, root, Element.class) : 032 stream(eval, root); 033 Elements els = stream.collect(toCollection(Elements::new)); 034 eval.reset(); // drops any held memos 035 return els; 036 } 037 038 /** 039 Obtain a Stream of elements by visiting the root and every descendant of root and testing it against the evaluator. 040 041 @param evaluator Evaluator to test elements against 042 @param root root of tree to descend 043 @return A {@link Stream} of matches 044 @since 1.19.1 045 */ 046 public static Stream<Element> stream(Evaluator evaluator, Element root) { 047 return EvaluatorInternals.stream(evaluator, root, root.stream()); 048 } 049 050 /** 051 Obtain a Stream of nodes, of the specified type, by visiting the root and every descendant of root and testing it 052 against the evaluator. 053 054 @param evaluator Evaluator to test elements against 055 @param root root of tree to descend 056 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 057 @param <T> the type of node to collect 058 @return A {@link Stream} of matches 059 @since 1.21.1 060 */ 061 public static <T extends Node> Stream<T> streamNodes(Evaluator evaluator, Element root, Class<T> type) { 062 return EvaluatorInternals.stream(evaluator, root, root.nodeStream(type), evaluator.asNodePredicate(root)); 063 } 064 065 /** 066 Finds the first Element that matches the Evaluator that descends from the root, and stops the query once that first 067 match is found. 068 @param eval Evaluator to test elements against 069 @param root root of tree to descend 070 @return the first match; {@code null} if none 071 */ 072 public static @Nullable Element findFirst(Evaluator eval, Element root) { 073 Element el = stream(eval, root).findFirst().orElse(null); 074 eval.reset(); 075 return el; 076 } 077 078 /** 079 Finds the first Node that matches the Evaluator that descends from the root, and stops the query once that first 080 match is found. 081 082 @param eval Evaluator to test elements against 083 @param root root of tree to descend 084 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 085 @return the first match; {@code null} if none 086 @since 1.21.1 087 */ 088 public static <T extends Node> @Nullable T findFirstNode(Evaluator eval, Element root, Class<T> type) { 089 T node = streamNodes(eval, root, type).findFirst().orElse(null); 090 eval.reset(); 091 return node; 092 } 093 094 /** 095 Build a list of nodes that match the supplied criteria, by visiting the root and every descendant of root, and 096 testing it against the Evaluator. 097 098 @param evaluator Evaluator to test elements against 099 @param root root of tree to descend 100 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 101 @param <T> the type of node to collect 102 @return list of matches; empty if none 103 */ 104 public static <T extends Node> Nodes<T> collectNodes(Evaluator evaluator, Element root, Class<T> type) { 105 return streamNodes(evaluator, root, type).collect(toCollection(Nodes::new)); 106 } 107}