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 035 return stream.collect(toCollection(Elements::new)); 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 evaluator.reset(); 048 return root.stream().filter(evaluator.asPredicate(root)); 049 } 050 051 /** 052 Obtain a Stream of nodes, of the specified type, by visiting the root and every descendant of root and testing it 053 against the evaluator. 054 055 @param evaluator Evaluator to test elements against 056 @param root root of tree to descend 057 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 058 @param <T> the type of node to collect 059 @return A {@link Stream} of matches 060 @since 1.21.1 061 */ 062 public static <T extends Node> Stream<T> streamNodes(Evaluator evaluator, Element root, Class<T> type) { 063 evaluator.reset(); 064 return root.nodeStream(type).filter(evaluator.asNodePredicate(root)); 065 } 066 067 /** 068 Finds the first Element that matches the Evaluator that descends from the root, and stops the query once that first 069 match is found. 070 @param eval Evaluator to test elements against 071 @param root root of tree to descend 072 @return the first match; {@code null} if none 073 */ 074 public static @Nullable Element findFirst(Evaluator eval, Element root) { 075 return stream(eval, root).findFirst().orElse(null); 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 return streamNodes(eval, root, type).findFirst().orElse(null); 090 } 091 092 /** 093 Build a list of nodes that match the supplied criteria, by visiting the root and every descendant of root, and 094 testing it against the Evaluator. 095 096 @param evaluator Evaluator to test elements against 097 @param root root of tree to descend 098 @param type the type of node to collect (e.g. {@link Element}, {@link LeafNode}, {@link TextNode} etc) 099 @param <T> the type of node to collect 100 @return list of matches; empty if none 101 */ 102 public static <T extends Node> Nodes<T> collectNodes(Evaluator evaluator, Element root, Class<T> type) { 103 return streamNodes(evaluator, root, type).collect(toCollection(Nodes::new)); 104 } 105}