diff --git a/CHANGES.md b/CHANGES.md index 0981c4f97d..ac3d710434 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,9 @@ untracked.[2175](https://github.com/jhy/jsoup/issues/2175) * When a document has no doctype, or a doctype not named `html`, it should be parsed in Quirks Mode. [2197](https://github.com/jhy/jsoup/issues/2197) +* With a selector like `div:has(span + a)`, the `has()` component was not working correctly, as the inner combining + query caused the evaluator to match those against the outer's siblings, not + children. [2187](https://github.com/jhy/jsoup/issues/2187) ## 1.18.1 (2024-Jul-10) diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index 1e01ed03f1..2fd77eaafc 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -69,16 +69,15 @@ public Has(Evaluator evaluator) { return true; } } - } else { - // otherwise we only want to match children (or below), and not the input element. And we want to minimize GCs so reusing the Iterator obj - NodeIterator it = ThreadElementIter.get(); - it.restart(element); - while (it.hasNext()) { - Element el = it.next(); - if (el == element) continue; // don't match self, only descendants - if (evaluator.matches(element, el)) - return true; - } + } + // otherwise we only want to match children (or below), and not the input element. And we want to minimize GCs so reusing the Iterator obj + NodeIterator it = ThreadElementIter.get(); + it.restart(element); + while (it.hasNext()) { + Element el = it.next(); + if (el == element) continue; // don't match self, only descendants + if (evaluator.matches(element, el)) + return true; } return false; } diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 7eca896793..3cdc0d2cfa 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1296,4 +1296,32 @@ public void emptyPseudo() { Elements emptyAttr = doc.select("p:not([*])"); assertSelectedOwnText(emptyAttr, "Three"); } + + @Test void divHasSpanPreceding() { + // https://github.com/jhy/jsoup/issues/2187 + String html = "
abcdef
"; + String q = "div:has(span + a)"; + + Document doc = Jsoup.parse(html); + Elements els = doc.select(q); + assertEquals(1, els.size()); + assertEquals("div", els.first().normalName()); + } + + @Test void divHasDivPreceding() { + // https://github.com/jhy/jsoup/issues/2131 , dupe of https://github.com/jhy/jsoup/issues/2187 + String html = "
\n" + + "
hello
\n" + + "
there
\n" + + "\n" + + "
"; + + String q = "div:has(>div + div)"; + + Document doc = Jsoup.parse(html); + Elements els = doc.select(q); + assertEquals(1, els.size()); + assertEquals("div", els.first().normalName()); + assertEquals("1", els.first().id()); + } }