diff --git a/star/src/lower/selector.rs b/star/src/lower/selector.rs index 0919a46..f8795a2 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()] @@ -298,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) { @@ -343,7 +359,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 +403,8 @@ mod tests { #[test] fn test_match() { - let svg = r#" - + let svg = r#" + @@ -415,6 +433,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),