cadmus_core/document/html/
style.rs1use super::css::{AttributeOperator, Combinator, PseudoClass};
2use super::css::{CssParser, Rule, Selector, SimpleSelector};
3use super::dom::NodeRef;
4use fxhash::FxHashMap;
5
6pub type PropertyMap = FxHashMap<String, String>;
7
8#[derive(Debug, Clone)]
9pub struct StyleSheet {
10 pub rules: Vec<Rule>,
11}
12
13impl StyleSheet {
14 pub fn new() -> Self {
15 StyleSheet { rules: Vec::new() }
16 }
17
18 pub fn append(&mut self, other: &mut Self, sort: bool) {
19 if sort {
20 other.sort();
21 }
22 self.rules.append(&mut other.rules);
23 }
24
25 pub fn sort(&mut self) {
26 self.rules
27 .sort_by_cached_key(|rule| rule.selector.specificity());
28 }
29}
30
31pub fn specified_values(node: NodeRef, stylesheet: &StyleSheet) -> PropertyMap {
32 let mut props = FxHashMap::default();
33 let mut important = Vec::new();
34
35 for rule in stylesheet
36 .rules
37 .iter()
38 .filter(|rule| rule.selector.matches(node))
39 {
40 for declaration in &rule.declarations {
41 if declaration.important {
42 important.push([&declaration.name, &declaration.value]);
43 } else {
44 expand_and_insert(&declaration.name, &declaration.value, &mut props);
45 }
46 }
47 }
48
49 let local_declarations = node
50 .attribute("style")
51 .map(|text| CssParser::new(text).parse_declarations())
52 .unwrap_or_default();
53
54 for declaration in &local_declarations {
55 expand_and_insert(&declaration.name, &declaration.value, &mut props);
56 }
57
58 for [name, value] in important {
59 expand_and_insert(name, value, &mut props);
60 }
61
62 props
63}
64
65impl Selector {
66 fn matches(&self, node: NodeRef) -> bool {
67 let index = self.simple_selectors.len().saturating_sub(1);
68 self.matches_rec(node, index)
69 }
70
71 fn matches_rec(&self, node: NodeRef, index: usize) -> bool {
72 let comb = self.combinators[index];
73 let selec = &self.simple_selectors[index];
74
75 if !selec.matches(node) {
76 return false;
77 }
78
79 match comb {
80 Combinator::Child => {
81 if let Some(parent) = node.parent_element() {
82 if self.matches_rec(parent, index - 1) {
83 return true;
84 }
85 }
86 false
87 }
88 Combinator::Descendant => {
89 for anc in node.ancestor_elements() {
90 if self.matches_rec(anc, index - 1) {
91 return true;
92 }
93 }
94 false
95 }
96 Combinator::NextSibling => {
97 if let Some(nsib) = node.previous_sibling_element() {
98 if self.matches_rec(nsib, index - 1) {
99 return true;
100 }
101 }
102 false
103 }
104 Combinator::SubsequentSibling => {
105 for sib in node.previous_sibling_elements() {
106 if self.matches_rec(sib, index - 1) {
107 return true;
108 }
109 }
110 false
111 }
112 Combinator::None => true,
113 }
114 }
115}
116
117impl SimpleSelector {
118 fn matches(&self, node: NodeRef) -> bool {
119 if self
120 .tag_name
121 .iter()
122 .any(|name| node.tag_name() != Some(name))
123 {
124 return false;
125 }
126
127 if self
128 .classes
129 .iter()
130 .any(|class| node.classes().all(|c| c != class))
131 {
132 return false;
133 }
134
135 if self.id.iter().any(|id| node.id() != Some(id)) {
136 return false;
137 }
138
139 if self.attributes.iter().any(|attr| {
140 node.attribute(&attr.name)
141 .map(|value| attr.operator.matches(value))
142 != Some(true)
143 }) {
144 return false;
145 }
146
147 if self.pseudo_classes.iter().any(|pc| !pc.matches(node)) {
148 return false;
149 }
150
151 true
152 }
153}
154
155impl AttributeOperator {
156 fn matches(&self, value: &str) -> bool {
157 match self {
158 AttributeOperator::Exists => true,
159 AttributeOperator::Matches(v) => v == value,
160 AttributeOperator::Contains(v) => value.split_whitespace().any(|value| value == v),
161 AttributeOperator::StartsWith(v) => {
162 v == value || (value.starts_with(v) && value[v.len()..].starts_with('-'))
163 }
164 }
165 }
166}
167
168impl PseudoClass {
169 fn matches(&self, node: NodeRef) -> bool {
170 match self {
171 PseudoClass::FirstChild => node.previous_sibling_element().is_none(),
172 PseudoClass::LastChild => node.next_sibling_element().is_none(),
173 }
174 }
175}
176
177fn expand_and_insert(name: &str, value: &str, props: &mut PropertyMap) {
178 match name {
179 "margin" | "padding" => {
180 let values = value.split_whitespace().collect::<Vec<&str>>();
181 match values.len() {
182 1 => {
183 props.insert(format!("{}-top", name), value.to_string());
184 props.insert(format!("{}-right", name), value.to_string());
185 props.insert(format!("{}-bottom", name), value.to_string());
186 props.insert(format!("{}-left", name), value.to_string());
187 }
188 2 => {
189 let vertical = values[0];
190 let horizontal = values[1];
191 props.insert(format!("{}-top", name), vertical.to_string());
192 props.insert(format!("{}-right", name), horizontal.to_string());
193 props.insert(format!("{}-bottom", name), vertical.to_string());
194 props.insert(format!("{}-left", name), horizontal.to_string());
195 }
196 3 => {
197 let top = values[0];
198 let horizontal = values[1];
199 let bottom = values[2];
200 props.insert(format!("{}-top", name), top.to_string());
201 props.insert(format!("{}-right", name), horizontal.to_string());
202 props.insert(format!("{}-bottom", name), bottom.to_string());
203 props.insert(format!("{}-left", name), horizontal.to_string());
204 }
205 4 => {
206 let top = values[0];
207 let right = values[1];
208 let bottom = values[2];
209 let left = values[3];
210 props.insert(format!("{}-top", name), top.to_string());
211 props.insert(format!("{}-right", name), right.to_string());
212 props.insert(format!("{}-bottom", name), bottom.to_string());
213 props.insert(format!("{}-left", name), left.to_string());
214 }
215 _ => (),
216 }
217 }
218 _ => {
222 props.insert(name.to_string(), value.to_string());
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::super::css::CssParser;
230 use super::super::xml::XmlParser;
231 use super::specified_values;
232
233 #[test]
234 fn simple_style() {
235 let xml1 = XmlParser::new("<a class='c x y' style='c: 7'/>").parse();
236 let xml2 = XmlParser::new("<a id='e' class='x y'/>").parse();
237 let mut css = CssParser::new(
238 "a { b: 23 }\
239 .c.x.y { b: 6; c: 3 }\
240 #e { b: 5 }\
241 .y { b: 2 }",
242 )
243 .parse();
244 css.sort();
245 let n1 = xml1.root().first_child().unwrap();
246 let n2 = xml2.root().first_child().unwrap();
247 assert_eq!(
248 specified_values(n1, &css),
249 [
250 ("b".to_string(), "6".to_string()),
251 ("c".to_string(), "7".to_string())
252 ]
253 .iter()
254 .cloned()
255 .collect()
256 );
257 assert_eq!(
258 specified_values(n2, &css),
259 [("b".to_string(), "5".to_string())]
260 .iter()
261 .cloned()
262 .collect()
263 );
264 }
265}