From 31d299af56141be0338186fa3e38113846dc9863 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 28 Dec 2023 13:27:27 +0530 Subject: [PATCH] Add select methods returning element streams --- src/main/java/org/jsoup/nodes/Element.java | 35 +++++++++++++++++++ src/main/java/org/jsoup/select/Collector.java | 25 ++++++++----- src/main/java/org/jsoup/select/Selector.java | 27 ++++++++++++++ 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jsoup/nodes/Element.java b/src/main/java/org/jsoup/nodes/Element.java index 0a63d73bf8..c161967dad 100644 --- a/src/main/java/org/jsoup/nodes/Element.java +++ b/src/main/java/org/jsoup/nodes/Element.java @@ -485,6 +485,41 @@ public Elements select(Evaluator evaluator) { return Selector.select(evaluator, this); } + /** + * Find elements that match the {@link Selector} CSS query, with this element as the starting context. Matched elements + * may include this element, or any of its children. + *

This method is generally more powerful to use than the DOM-type {@code getElementBy*} methods, because + * multiple filters can be combined, e.g.:

+ * + *

See the query syntax documentation in {@link org.jsoup.select.Selector}.

+ *

Also known as {@code querySelectorAll()} in the Web DOM.

+ * + * @param cssQuery a {@link Selector} CSS-like query + * @return a {@link Stream} containing elements that match the query (empty if none match) + * @see Selector selector query syntax + * @see QueryParser#parse(String) + * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. + * @since 1.17.2 + */ + public Stream selectAsStream(String cssQuery) { + return Selector.selectAsStream(cssQuery, this); + } + + /** + * Find elements that match the supplied Evaluator. This has the same functionality as {@link #select(String)}, but + * may be useful if you are running the same query many times (on many documents) and want to save the overhead of + * repeatedly parsing the CSS query. + * @param evaluator an element evaluator + * @return a {@link Stream} containing elements that match the query (empty if none match) + * @since 1.17.2 + */ + public Stream selectAsStream(Evaluator evaluator) { + return Selector.selectAsStream(evaluator, this); + } + /** * Find the first Element that matches the {@link Selector} CSS query, with this element as the starting context. *

This is effectively the same as calling {@code element.select(query).first()}, but is more efficient as query diff --git a/src/main/java/org/jsoup/select/Collector.java b/src/main/java/org/jsoup/select/Collector.java index 02b0528384..6638f5ef6b 100644 --- a/src/main/java/org/jsoup/select/Collector.java +++ b/src/main/java/org/jsoup/select/Collector.java @@ -3,8 +3,8 @@ import org.jsoup.nodes.Element; import org.jspecify.annotations.Nullable; -import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Collects a list of elements that match the supplied criteria. @@ -21,12 +21,22 @@ private Collector() {} @param root root of tree to descend @return list of matches; empty if none */ - public static Elements collect (Evaluator eval, Element root) { - eval.reset(); + public static Elements collect(Evaluator eval, Element root) { + return stream(eval, root).collect(Collectors.toCollection(Elements::new)); + } + + /** + * Obtain a stream of elements by visiting root and every descendant of root and testing it + * against the evaluator. + * @param evaluator Evaluator to test elements against + * @param root root of tree to descend + * @return A {@link Stream} of matches + */ + public static Stream stream(Evaluator evaluator, Element root) { + evaluator.reset(); return root.stream() - .filter(eval.asPredicate(root)) - .collect(Collectors.toCollection(Elements::new)); + .filter(evaluator.asPredicate(root)); } /** @@ -37,9 +47,6 @@ public static Elements collect (Evaluator eval, Element root) { @return the first match; {@code null} if none */ public static @Nullable Element findFirst(Evaluator eval, Element root) { - eval.reset(); - - Optional first = root.stream().filter(eval.asPredicate(root)).findFirst(); - return first.orElse(null); + return stream(eval, root).findFirst().orElse(null); } } diff --git a/src/main/java/org/jsoup/select/Selector.java b/src/main/java/org/jsoup/select/Selector.java index 322cfa07bc..6c5bf99140 100644 --- a/src/main/java/org/jsoup/select/Selector.java +++ b/src/main/java/org/jsoup/select/Selector.java @@ -6,6 +6,7 @@ import java.util.Collection; import java.util.IdentityHashMap; +import java.util.stream.Stream; /** * CSS-like element selector, that finds elements matching a query. @@ -114,6 +115,32 @@ public static Elements select(Evaluator evaluator, Element root) { return Collector.collect(evaluator, root); } + /** + * Find elements matching selector. + * + * @param query CSS selector + * @param root root element to descend into + * @return matching elements, empty if none + * @throws Selector.SelectorParseException (unchecked) on an invalid CSS query. + */ + public static Stream selectAsStream(String query, Element root) { + Validate.notEmpty(query); + return selectAsStream(QueryParser.parse(query), root); + } + + /** + * Find elements matching selector. + * + * @param evaluator CSS selector + * @param root root element to descend into + * @return matching elements, empty if none + */ + public static Stream selectAsStream(Evaluator evaluator, Element root) { + Validate.notNull(evaluator); + Validate.notNull(root); + return Collector.stream(evaluator, root); + } + /** * Find elements matching selector. *