rama_http/protocols/html/selector/
display.rs1use std::fmt::{self, Write as _};
9
10use super::ast::{
11 AttributeOperator, AttributeSelector, CaseSensitivity, Combinator, ComplexSelector, Compound,
12 Nth, Selector,
13};
14
15impl fmt::Display for Selector {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 for (i, complex) in self.selectors.iter().enumerate() {
18 if i != 0 {
19 f.write_str(", ")?;
20 }
21 write_complex(f, complex)?;
22 }
23 Ok(())
24 }
25}
26
27fn write_complex(f: &mut fmt::Formatter<'_>, complex: &ComplexSelector) -> fmt::Result {
28 for part in &complex.parts {
29 match part.combinator {
30 None => {}
31 Some(Combinator::Descendant) => f.write_str(" ")?,
32 Some(Combinator::Child) => f.write_str(" > ")?,
33 }
34 write_compound(f, &part.compound)?;
35 }
36 Ok(())
37}
38
39fn write_compound(f: &mut fmt::Formatter<'_>, compound: &Compound) -> fmt::Result {
40 if let Some(name) = &compound.name {
41 write_ident(f, name.as_str())?;
42 } else if compound.explicit_universal {
43 f.write_str("*")?;
44 }
45 if let Some(id) = &compound.id {
46 f.write_str("#")?;
47 write_ident(f, id)?;
48 }
49 for class in &compound.classes {
50 f.write_str(".")?;
51 write_ident(f, class)?;
52 }
53 for attr in &compound.attributes {
54 write_attribute(f, attr)?;
55 }
56 for nth in &compound.nth {
57 write_nth(f, *nth)?;
58 }
59 for negation in &compound.negations {
60 f.write_str(":not(")?;
61 write_compound(f, negation)?;
62 f.write_str(")")?;
63 }
64 Ok(())
65}
66
67fn write_attribute(f: &mut fmt::Formatter<'_>, attr: &AttributeSelector) -> fmt::Result {
68 f.write_str("[")?;
69 write_ident(f, &attr.name)?;
70 if let Some(operator) = attr.operator {
71 f.write_str(match operator {
72 AttributeOperator::Equals => "=",
73 AttributeOperator::Includes => "~=",
74 AttributeOperator::DashMatch => "|=",
75 AttributeOperator::Prefix => "^=",
76 AttributeOperator::Suffix => "$=",
77 AttributeOperator::Substring => "*=",
78 })?;
79 write_string(f, &attr.value)?;
80 if matches!(attr.case, CaseSensitivity::AsciiCaseInsensitive) {
81 f.write_str(" i")?;
82 }
83 }
84 f.write_str("]")
85}
86
87fn write_nth(f: &mut fmt::Formatter<'_>, nth: Nth) -> fmt::Result {
88 let pseudo = match nth.ty {
89 super::ast::NthType::Child => "nth-child",
90 super::ast::NthType::OfType => "nth-of-type",
91 };
92 write!(f, ":{pseudo}(")?;
93 if nth.a == 0 {
94 write!(f, "{}", nth.b)?;
95 } else {
96 match nth.a {
97 1 => f.write_str("n")?,
98 -1 => f.write_str("-n")?,
99 a => write!(f, "{a}n")?,
100 }
101 match nth.b {
102 0 => {}
103 b if b > 0 => write!(f, "+{b}")?,
104 b => write!(f, "{b}")?,
105 }
106 }
107 f.write_str(")")
108}
109
110fn write_ident(f: &mut fmt::Formatter<'_>, ident: &str) -> fmt::Result {
112 for (i, c) in ident.chars().enumerate() {
113 match c {
114 '\0' => f.write_str("\u{FFFD}")?,
115 c if is_control(c) => write_codepoint_escape(f, c)?,
116 c if c.is_ascii_digit() && i == 0 => write_codepoint_escape(f, c)?,
117 c if c.is_ascii_digit() && i == 1 && ident.starts_with('-') => {
118 write_codepoint_escape(f, c)?;
119 }
120 '-' if i == 0 && ident.len() == 1 => f.write_str("\\-")?,
121 c if c == '-' || c == '_' || c.is_ascii_alphanumeric() || !c.is_ascii() => {
122 f.write_char(c)?;
123 }
124 c => write!(f, "\\{c}")?,
125 }
126 }
127 Ok(())
128}
129
130fn write_string(f: &mut fmt::Formatter<'_>, value: &str) -> fmt::Result {
132 f.write_str("\"")?;
133 for c in value.chars() {
134 match c {
135 '\0' => f.write_str("\u{FFFD}")?,
136 c if is_control(c) => write_codepoint_escape(f, c)?,
137 '"' => f.write_str("\\\"")?,
138 '\\' => f.write_str("\\\\")?,
139 c => f.write_char(c)?,
140 }
141 }
142 f.write_str("\"")
143}
144
145fn write_codepoint_escape(f: &mut fmt::Formatter<'_>, c: char) -> fmt::Result {
146 write!(f, "\\{:x} ", c as u32)
147}
148
149fn is_control(c: char) -> bool {
150 let n = c as u32;
151 (0x1..=0x1F).contains(&n) || n == 0x7F
152}