From b49a1a366b2709ffaaa8d3379748ec77aa8db0b3 Mon Sep 17 00:00:00 2001 From: Sameer Puri Date: Fri, 22 May 2026 15:25:09 -0700 Subject: [PATCH 1/2] CSS selector character escaping with backslash --- star/src/lower/selector.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/star/src/lower/selector.rs b/star/src/lower/selector.rs index 0919a46..2e3611f 100644 --- a/star/src/lower/selector.rs +++ b/star/src/lower/selector.rs @@ -106,11 +106,17 @@ peg::parser!(grammar selector_parser() for str { rule _() = whitespace()* rule s() = whitespace()+ + rule escape() -> char = "\\" c:[_] { c } + + rule ident_char() -> char + = escape() + / c:['a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_'] { c } + // Tag names, class names, and IDs - rule ident() -> String = s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_']+) { s.to_string() } + rule ident() -> String = chars:ident_char()+ { chars.into_iter().collect() } // Attribute names - rule attr_name() -> String = s:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | ':']+) { s.to_string() } + rule attr_name() -> String = chars:ident_char()+ { chars.into_iter().collect() } // Attribute value rule unquoted() -> char = [c if c != ']' && !c.is_ascii_whitespace()] @@ -343,7 +349,9 @@ mod tests { ("[id=foo]", true), ("[id=\"foo bar\"]", true), ("[style*=\"display:none\"]", true), - ("[inkscape:label=\"Layer 1\"]", true), + ("[inkscape\\:label=\"Layer 1\"]", true), + ("[inkscape:label=\"Layer 1\"]", false), + ("g\\#layer1", true), // combinators ("g path", true), ("g > path", true), @@ -385,8 +393,8 @@ mod tests { #[test] fn test_match() { - let svg = r#" - + let svg = r#" + @@ -415,6 +423,8 @@ mod tests { // attribute selector ("[style*=\"display:none\"]", "p-outer", true), ("[style*=\"display:none\"]", "r-draft", false), + ("[inkscape\\:label=\"Layer 1\"]", "layer1", true), + ("[inkscape\\:label=\"Layer 1\"]", "p-cut", false), // descendant combinator — p-cut is inside layer1, p-outer is not ("#layer1 path", "p-cut", true), ("#layer1 path", "p-nested", true), From 395f8ceae752b89b08a3b8f99d58706dceb59db8 Mon Sep 17 00:00:00 2001 From: Sameer Puri Date: Fri, 29 May 2026 16:00:30 -0700 Subject: [PATCH 2/2] Fix namespacing lookup bug --- star/src/lower/selector.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/star/src/lower/selector.rs b/star/src/lower/selector.rs index 2e3611f..f8795a2 100644 --- a/star/src/lower/selector.rs +++ b/star/src/lower/selector.rs @@ -304,7 +304,17 @@ impl AttributeSelector { // like `inkscape:label` which roxmltree may expose under a namespace. let value = node .attributes() - .find(|a| a.name() == self.name) + .find(|a| { + if let Some((prefix, local)) = self.name.split_once(':') { + if let Some(ns_uri) = node.lookup_namespace_uri(Some(prefix)) { + a.namespace() == Some(ns_uri) && a.name() == local + } else { + false + } + } else { + a.name() == self.name + } + }) .map(|a| a.value()); match (&self.op, value) {