diff --git a/CHANGES.md b/CHANGES.md
index d06bda6a9b..3758b9a417 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -40,6 +40,7 @@
e.g.: `h1:has(+h2)`). [2137](https://github.com/jhy/jsoup/issues/2137)
* The `:empty` selector incorrectly matched elements that started with a blank text node and were followed by
non-empty nodes, due to an incorrect short-circuit. [2130](https://github.com/jhy/jsoup/issues/2130)
+* `Element.cssSelector()` would fail with "Did not find balanced marker" when building a selector for elements that had a `(` or `[` in their class names. And selectors with those characters escaped would not match as expected. [2146](https://github.com/jhy/jsoup/issues/2146)
* Fuzz: a Stack Overflow exception could occur when resolving a crafted `` URL, in the normalizing regex.
[2165](https://github.com/jhy/jsoup/issues/2165)
diff --git a/src/main/java/org/jsoup/select/QueryParser.java b/src/main/java/org/jsoup/select/QueryParser.java
index a8c263a122..eaa1c0fe0f 100644
--- a/src/main/java/org/jsoup/select/QueryParser.java
+++ b/src/main/java/org/jsoup/select/QueryParser.java
@@ -158,7 +158,10 @@ private String consumeSubQuery() {
sq.append("(").append(tq.chompBalanced('(', ')')).append(")");
else if (tq.matches("["))
sq.append("[").append(tq.chompBalanced('[', ']')).append("]");
- else
+ else if (tq.matches("\\")) { // bounce over escapes
+ sq.append(tq.consume());
+ if (!tq.isEmpty()) sq.append(tq.consume());
+ } else
sq.append(tq.consume());
}
return StringUtil.releaseBuilder(sq);
diff --git a/src/test/java/org/jsoup/nodes/ElementTest.java b/src/test/java/org/jsoup/nodes/ElementTest.java
index f0e1f4627e..ef0b22543a 100644
--- a/src/test/java/org/jsoup/nodes/ElementTest.java
+++ b/src/test/java/org/jsoup/nodes/ElementTest.java
@@ -2612,6 +2612,30 @@ void prettySerializationRoundTrips(Document.OutputSettings settings) {
assertEquals(element, elements.first());
}
+ @Test void cssSelectorWithBracket() {
+ // https://github.com/jhy/jsoup/issues/2146
+ Document doc = Jsoup.parse("
One
Two
");
+ Element div = doc.expectFirst("div");
+ String selector = div.cssSelector();
+ assertEquals("html > body > div.a\\[foo\\]", selector); // would fail with "Did not find balanced marker", consumeSubquery was not handling escapes
+
+ Elements selected = doc.select(selector);
+ assertEquals(1, selected.size());
+ assertEquals(selected.first(), div);
+ }
+
+ @Test void cssSelectorUnbalanced() {
+ // https://github.com/jhy/jsoup/issues/2146
+ Document doc = Jsoup.parse("One
Two
");
+ Element div = doc.expectFirst("div");
+ String selector = div.cssSelector();
+ assertEquals("html > body > div.a\\(foo", selector);
+
+ Elements selected = doc.select(selector);
+ assertEquals(1, selected.size());
+ assertEquals(selected.first(), div);
+ }
+
@Test void orphanSiblings() {
Element el = new Element("div");
assertEquals(0, el.siblingElements().size());