From 80aa598d2cfc5a0ea10cc4b27a1b1423358b0d8c Mon Sep 17 00:00:00 2001 From: Siraj saheb Date: Fri, 16 Oct 2026 21:40:22 +0530 Subject: [PATCH 1/2] Keep namespace wildcards in canonical() of combined selectors and :is() CombinedSelector, Matching and SpecificityAdjustment dropped a redundant universal selector from their subselector with lstrip("*"), which also stripped the "*" of a "*|" namespace wildcard: "foo *|bar" became "foo |bar", turning "any namespace" into "no namespace". Only strip a "*" followed by "#", ".", "[" or ":", as Selector and Negation do. --- cssselect/parser.py | 12 ++++++------ tests/test_cssselect.py | 5 +++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cssselect/parser.py b/cssselect/parser.py index a63246b..daeb731 100644 --- a/cssselect/parser.py +++ b/cssselect/parser.py @@ -331,8 +331,8 @@ def canonical(self) -> str: selector_arguments = [] for s in self.selector_list: selarg = s.canonical() - if len(selarg) > 1: - selarg = selarg.lstrip("*") + if len(selarg) > 1 and selarg[0] == "*" and selarg[1] in "#.[:": + selarg = selarg[1:] selector_arguments.append(selarg) args_str = ", ".join(selector_arguments) return f"{self.selector.canonical()}:is({args_str})" @@ -361,8 +361,8 @@ def canonical(self) -> str: selector_arguments = [] for s in self.selector_list: selarg = s.canonical() - if len(selarg) > 1: - selarg = selarg.lstrip("*") + if len(selarg) > 1 and selarg[0] == "*" and selarg[1] in "#.[:": + selarg = selarg[1:] selector_arguments.append(selarg) args_str = ", ".join(selector_arguments) return f"{self.selector.canonical()}:where({args_str})" @@ -521,8 +521,8 @@ def __repr__(self) -> str: def canonical(self) -> str: subsel = self.subselector.canonical() - if len(subsel) > 1: - subsel = subsel.lstrip("*") + if len(subsel) > 1 and subsel[0] == "*" and subsel[1] in "#.[:": + subsel = subsel[1:] combinator = " " if self.combinator == " " else f" {self.combinator} " return f"{self.selector.canonical()}{combinator}{subsel}" diff --git a/tests/test_cssselect.py b/tests/test_cssselect.py index 07bf58b..95122eb 100644 --- a/tests/test_cssselect.py +++ b/tests/test_cssselect.py @@ -467,6 +467,11 @@ def css2css(css: str, res: str | None = None) -> None: css2css("div:is(*)") css2css(":is(*, .foo)") css2css(":where(*)") + # a namespace wildcard is not a redundant universal selector + css2css("foo *|bar") + css2css("foo > *|*") + css2css(":is(*|foo, bar)") + css2css(":where(*|foo)") css2css("foo:empty") css2css("foo::before") css2css("foo:empty::before") From ba2a7042bad2120f96039d543397b637d4ff3a84 Mon Sep 17 00:00:00 2001 From: Siraj saheb Date: Sat, 17 Oct 2026 12:37:06 +0530 Subject: [PATCH 2/2] Test stripping a redundant universal selector after a combinator --- tests/test_cssselect.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_cssselect.py b/tests/test_cssselect.py index 95122eb..5d3efd0 100644 --- a/tests/test_cssselect.py +++ b/tests/test_cssselect.py @@ -478,6 +478,8 @@ def css2css(css: str, res: str | None = None) -> None: css2css('::name(arg + "val" - 3)', "::name(arg+'val'-3)") css2css("#lorem + foo#ipsum:first-child > bar::first-line") css2css("foo > *") + css2css("foo *.bar", "foo .bar") + css2css("foo > *[bar]", "foo > [bar]") # a leading universal selector is only redundant in a compound # selector css2css("* > foo")