EvaluatorInternals.java
package org.jsoup.select;
import org.jsoup.internal.CssMatching;
import org.jsoup.internal.QueryMode;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jspecify.annotations.Nullable;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
Shared query operations for {@link Element}, {@link Collector}, and {@link org.jsoup.parser.StreamParser}.
Public for access between jsoup packages; not a supported user API.
String-query methods report compatibility differences when in check mode.
*/
public final class EvaluatorInternals {
private EvaluatorInternals() {}
/**
Filters candidates using the evaluator's captured query mode, excluding {@code root} in Web mode.
*/
public static Stream<Element> stream(Evaluator evaluator, Element root, Stream<Element> candidates) {
return stream(evaluator, root, candidates, evaluator.asPredicate(root));
}
/**
Shares stream filtering between element and node queries.
*/
static <T extends Node> Stream<T> stream(Evaluator evaluator, Element root, Stream<T> candidates,
Predicate<? super T> predicate) {
evaluator.reset();
if (evaluator.web)
candidates = candidates.filter(node -> node != root);
return candidates.filter(documentPredicate(evaluator, root, predicate));
}
/**
Resolves the context document lazily and reuses it for Web matching.
*/
private static <T> Predicate<T> documentPredicate(Evaluator evaluator, Element root, Predicate<T> predicate) {
return evaluator.web ? CssMatching.withDocument(root, predicate) : predicate;
}
/**
Matches ID values exactly for {@link Element#getElementById(String)}, even in quirks mode.
*/
public static Evaluator byId(String id) {
return new Evaluator() {
@Override
public boolean matches(Element root, Element element) {
String actual = web ?
CssMatching.attributeValue(element, "id", CssMatching.isHtmlElement(root, element)) : element.id();
return id.equals(actual);
}
};
}
/**
Tests whether the supplied element matches the query.
*/
public static boolean matches(String query, Element element) {
QueryMode mode = QueryMode.current();
boolean result = matches(QueryParser.parse(query, mode == QueryMode.Web), element);
if (mode == QueryMode.Check)
QueryCompat.check(query, result, evaluator -> matches(evaluator, element));
return result;
}
/**
Returns the element if it matches the query, or its nearest matching ancestor.
*/
public static @Nullable Element closest(String query, Element element) {
QueryMode mode = QueryMode.current();
Element result = closest(QueryParser.parse(query, mode == QueryMode.Web), element);
if (mode == QueryMode.Check)
QueryCompat.check("closest", query, result, evaluator -> closest(evaluator, element));
return result;
}
/**
Tests the element itself, using it as {@code :scope} in Web mode.
*/
public static boolean matches(Evaluator evaluator, Element element) {
evaluator.reset();
Element root = evaluator.web ? element : element.root();
boolean matches = evaluator.web ?
CssMatching.<Element>withDocument(root, candidate -> evaluator.matches(root, candidate)).test(element) :
evaluator.matches(root, element);
evaluator.reset();
return matches;
}
/**
Returns the element if it matches the evaluator, or its nearest matching ancestor.
In Web mode, {@code :scope} remains the supplied element.
*/
public static @Nullable Element closest(Evaluator evaluator, Element element) {
evaluator.reset();
Element root = evaluator.web ? element : element.root();
Predicate<Element> predicate = documentPredicate(evaluator, root, candidate -> evaluator.matches(root, candidate));
Element candidate = element;
while (candidate != null && !predicate.test(candidate))
candidate = candidate.parent();
evaluator.reset();
return candidate;
}
}