From 2a4a9cf83dea46aff9fc97fc6da808948f5086e0 Mon Sep 17 00:00:00 2001 From: Jonathan Hedley Date: Wed, 25 Oct 2023 10:54:15 +1100 Subject: [PATCH] ImmediateParent evaluator should not match the context root Fixes #2018 Was a regression when ImmediateParent was reimplemented into ImmediateParentRun --- CHANGES | 4 +++ .../org/jsoup/select/StructuralEvaluator.java | 4 ++- .../java/org/jsoup/select/SelectorTest.java | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ce1b5b5f9d..1e84076053 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,10 @@ Release 1.17.1 [PENDING] be emitted as CDATA nodes, so that they can be parsed correctly by an XML parser. + * Bugfix: the Immediate Parent selector `>` could match elements above the root context element, causing incorrect + elements to be returned when used on elements other than the root document. + + Release 1.16.2 [20-Oct-2023] * Improvement: optimized the performance of complex CSS selectors, by adding a cost-based query planner. Evaluators are sorted by their relative execution cost, and executed in order of lower to higher cost. This speeds the diff --git a/src/main/java/org/jsoup/select/StructuralEvaluator.java b/src/main/java/org/jsoup/select/StructuralEvaluator.java index 96ff252e5b..560ffbcac6 100644 --- a/src/main/java/org/jsoup/select/StructuralEvaluator.java +++ b/src/main/java/org/jsoup/select/StructuralEvaluator.java @@ -189,7 +189,9 @@ void add(Evaluator evaluator) { @Override public boolean matches(Element root, Element element) { - // evaluate from last to first + if (element == root) + return false; // cannot match as the second eval (first parent test) would be above the root + for (int i = evaluators.size() -1; i >= 0; --i) { if (element == null) return false; diff --git a/src/test/java/org/jsoup/select/SelectorTest.java b/src/test/java/org/jsoup/select/SelectorTest.java index 3196dc2527..e941f9032b 100644 --- a/src/test/java/org/jsoup/select/SelectorTest.java +++ b/src/test/java/org/jsoup/select/SelectorTest.java @@ -1181,4 +1181,29 @@ public void wildcardNamespaceMatchesNoNamespace() { assertEquals("4", notEmpty.get(0).id()); assertEquals("5", notEmpty.get(1).id()); } + + @Test public void parentFromSpecifiedDescender() { + // https://github.com/jhy/jsoup/issues/2018 + String html = "
  • Foo
  • Bar
    • Baz
    • Qux
"; + Document doc = Jsoup.parse(html); + + Element ul = doc.expectFirst("#outer"); + assertEquals(2, ul.childrenSize()); + + Element li1 = ul.expectFirst("> li:nth-child(1)"); + assertEquals("Foo", li1.ownText()); + assertTrue(li1.select("ul").isEmpty()); + + Element li2 = ul.expectFirst("> li:nth-child(2)"); + assertEquals("Bar", li2.ownText()); + + // And now for the bug - li2 select was not restricted to the li2 context + Elements innerLis = li2.select("ul > li"); + assertEquals(2, innerLis.size()); + assertEquals("Baz", innerLis.first().ownText()); + + // Confirm that parent selector (" ") works same as immediate parent (">"); + Elements innerLisFromParent = li2.select("ul li"); + assertEquals(innerLis, innerLisFromParent); + } }