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