1 | #![allow (clippy::useless_format)] |
2 | |
3 | use crate::*; |
4 | use roxmltree::Node; |
5 | |
6 | pub fn parse_config<'a>( |
7 | xml_doc: &'a roxmltree::Document, |
8 | ) -> Result<impl Iterator<Item = Result<ConfigPart>> + 'a> { |
9 | let fontconfig: Node<'_, '_> = xml_doc.root_element(); |
10 | |
11 | if fontconfig.tag_name().name() != "fontconfig" { |
12 | return Err(Error::NoFontconfig); |
13 | } |
14 | |
15 | Ok(fontconfigChildren<'_, '_> |
16 | .children() |
17 | .filter_map(|c: Node<'_, '_>| parse_config_part(child:c).transpose())) |
18 | } |
19 | |
20 | fn parse_config_part(child: Node) -> Result<Option<ConfigPart>> { |
21 | let part = match child.tag_name().name() { |
22 | "description" => ConfigPart::Description(try_text!(child).into()), |
23 | "alias" => { |
24 | let mut alias = Alias::default(); |
25 | |
26 | for child in child.children() { |
27 | let families = |
28 | child |
29 | .children() |
30 | .filter_map(|family| match family.tag_name().name() { |
31 | "family" => family.text().map(Into::into), |
32 | _ => None, |
33 | }); |
34 | |
35 | match child.tag_name().name() { |
36 | "family" => { |
37 | alias.alias = try_text!(child).into(); |
38 | } |
39 | "prefer" => { |
40 | alias.prefer.extend(families); |
41 | } |
42 | "accept" => { |
43 | alias.accept.extend(families); |
44 | } |
45 | "default" => { |
46 | alias.default.extend(families); |
47 | } |
48 | _ => {} |
49 | } |
50 | } |
51 | |
52 | ConfigPart::Alias(alias) |
53 | } |
54 | "dir" => { |
55 | let mut dir = Dir::default(); |
56 | |
57 | parse_attrs!(child, { |
58 | "prefix" => dir.prefix, |
59 | }, { |
60 | "salt" => dir.salt, |
61 | }); |
62 | |
63 | dir.path = try_text!(child).into(); |
64 | |
65 | ConfigPart::Dir(dir) |
66 | } |
67 | "reset-dirs" => ConfigPart::ResetDirs, |
68 | "remap-dir" => { |
69 | let mut dir = RemapDir::default(); |
70 | |
71 | parse_attrs!(child, { |
72 | "prefix" => dir.prefix, |
73 | }, { |
74 | "salt" => dir.salt, |
75 | "as-path" => dir.as_path, |
76 | }); |
77 | |
78 | dir.path = try_text!(child).into(); |
79 | |
80 | ConfigPart::RemapDir(dir) |
81 | } |
82 | "cachedir" => { |
83 | let mut dir = CacheDir::default(); |
84 | |
85 | parse_attrs!(child, { |
86 | "prefix" => dir.prefix, |
87 | }); |
88 | |
89 | dir.path = try_text!(child).into(); |
90 | |
91 | ConfigPart::CacheDir(dir) |
92 | } |
93 | "include" => { |
94 | let mut dir = Include::default(); |
95 | let mut ignore_missing = "" ; |
96 | |
97 | parse_attrs!(child, { |
98 | "prefix" => dir.prefix, |
99 | }, { |
100 | "ignore_missing" => ignore_missing, |
101 | }); |
102 | |
103 | dir.ignore_missing = matches!(ignore_missing, "yes" ); |
104 | dir.path = try_text!(child).into(); |
105 | |
106 | ConfigPart::Include(dir) |
107 | } |
108 | "config" => { |
109 | let mut config = Config::default(); |
110 | |
111 | for child in child.children() { |
112 | match child.tag_name().name() { |
113 | "rescan" => { |
114 | if let Some(int) = child.first_element_child() { |
115 | if int.tag_name().name() == "int" { |
116 | config.rescans.push(try_text!(int).parse()?); |
117 | } |
118 | } |
119 | } |
120 | "blank" => { |
121 | if let Some(child) = child.first_element_child() { |
122 | config.blanks.push(parse_int_or_range(child)?); |
123 | } |
124 | } |
125 | _ => {} |
126 | } |
127 | } |
128 | |
129 | ConfigPart::Config(config) |
130 | } |
131 | "selectfont" => { |
132 | let mut s = SelectFont::default(); |
133 | |
134 | for child in child.children() { |
135 | let matches = child.children().filter_map(|c| match c.tag_name().name() { |
136 | "pattern" => { |
137 | let patelts = c.children().filter_map(|patelt| { |
138 | if patelt.tag_name().name() == "patelt" { |
139 | let mut kind = PropertyKind::default(); |
140 | parse_attrs_opt!(patelt, { |
141 | "name" => kind, |
142 | }); |
143 | parse_expr(patelt.first_element_child()?) |
144 | .ok() |
145 | .map(|expr| kind.make_property(expr)) |
146 | } else { |
147 | None |
148 | } |
149 | }); |
150 | Some(FontMatch::Pattern(patelts.collect())) |
151 | } |
152 | "glob" => c.text().map(Into::into).map(FontMatch::Glob), |
153 | _ => None, |
154 | }); |
155 | |
156 | match child.tag_name().name() { |
157 | "acceptfont" => { |
158 | s.accepts.extend(matches); |
159 | } |
160 | "rejectfont" => { |
161 | s.rejects.extend(matches); |
162 | } |
163 | _ => {} |
164 | } |
165 | } |
166 | |
167 | ConfigPart::SelectFont(s) |
168 | } |
169 | "match" => { |
170 | let mut m = Match::default(); |
171 | |
172 | parse_attrs!(child, { |
173 | "target" => m.target, |
174 | }); |
175 | |
176 | for child in child.children() { |
177 | match child.tag_name().name() { |
178 | "test" => { |
179 | let mut t = Test::default(); |
180 | let mut kind = PropertyKind::default(); |
181 | |
182 | parse_attrs!(child, { |
183 | "name" => kind, |
184 | "qual" => t.qual, |
185 | "target" => t.target, |
186 | "compare" => t.compare, |
187 | }); |
188 | |
189 | t.value = kind.make_property(parse_expr( |
190 | child |
191 | .first_element_child() |
192 | .ok_or_else(|| Error::InvalidFormat(format!("Empty test value" )))?, |
193 | )?); |
194 | |
195 | m.tests.push(t); |
196 | } |
197 | |
198 | "edit" => { |
199 | let mut e = Edit::default(); |
200 | let mut kind = PropertyKind::default(); |
201 | |
202 | parse_attrs!(child, { |
203 | "name" => kind, |
204 | "mode" => e.mode, |
205 | "binding" => e.binding, |
206 | }); |
207 | |
208 | e.value = kind.make_property(parse_expr( |
209 | child |
210 | .first_element_child() |
211 | .ok_or_else(|| Error::InvalidFormat(format!("Empty edit value" )))?, |
212 | )?); |
213 | |
214 | m.edits.push(e); |
215 | } |
216 | _ => {} |
217 | } |
218 | } |
219 | |
220 | ConfigPart::Match(m) |
221 | } |
222 | _ => { |
223 | return Ok(None); |
224 | } |
225 | }; |
226 | |
227 | Ok(Some(part)) |
228 | } |
229 | |
230 | fn parse_int_or_range(node: Node) -> Result<IntOrRange> { |
231 | let mut texts: impl Iterator = get_texts(&node); |
232 | |
233 | match node.tag_name().name() { |
234 | "int" => Ok(IntOrRange::Int(try_text!(node).parse()?)), |
235 | "range" => Ok(IntOrRange::Range( |
236 | try_next!(texts, "Expect int" ).parse()?, |
237 | try_next!(texts, "Expect int" ).parse()?, |
238 | )), |
239 | _ => Err(Error::InvalidFormat(format!("Expect IntOrRange" ))), |
240 | } |
241 | } |
242 | |
243 | fn parse_expr(node: Node) -> Result<Expression> { |
244 | let mut exprs = get_exprs(&node); |
245 | let mut texts = get_texts(&node); |
246 | |
247 | macro_rules! next { |
248 | ($iter:expr) => { |
249 | try_next!($iter, "Expect expression" ) |
250 | }; |
251 | } |
252 | |
253 | match node.tag_name().name() { |
254 | "string" => Ok(Value::String(try_text!(node).into()).into()), |
255 | "langset" => Ok(Value::LangSet(try_text!(node).into()).into()), |
256 | "double" => Ok(Value::Double(try_text!(node).parse()?).into()), |
257 | "int" => Ok(Value::Int(try_text!(node).parse()?).into()), |
258 | "bool" => Ok(Value::Bool(try_text!(node).parse()?).into()), |
259 | "const" => Ok(Value::Constant(try_text!(node).parse()?).into()), |
260 | "matrix" => Ok(Expression::Matrix(Box::new([ |
261 | next!(exprs)?, |
262 | next!(exprs)?, |
263 | next!(exprs)?, |
264 | next!(exprs)?, |
265 | ]))), |
266 | "charset" => { |
267 | let charset = node |
268 | .children() |
269 | .filter_map(|c| parse_int_or_range(c).ok()) |
270 | .collect(); |
271 | |
272 | Ok(Value::CharSet(charset).into()) |
273 | } |
274 | "range" => Ok(Value::Range(next!(texts).parse()?, next!(texts).parse()?).into()), |
275 | "name" => { |
276 | let mut target = PropertyTarget::default(); |
277 | parse_attrs!(node, { |
278 | "target" => target, |
279 | }); |
280 | let kind = try_text!(node).parse()?; |
281 | |
282 | Ok(Value::Property(target, kind).into()) |
283 | } |
284 | name => { |
285 | if let Ok(list_op) = name.parse() { |
286 | Ok(Expression::List( |
287 | list_op, |
288 | exprs.collect::<Result<Vec<_>>>()?, |
289 | )) |
290 | } else if let Ok(unary_op) = name.parse() { |
291 | Ok(Expression::Unary(unary_op, Box::new(next!(exprs)?))) |
292 | } else if let Ok(binary_op) = name.parse() { |
293 | Ok(Expression::Binary( |
294 | binary_op, |
295 | Box::new([next!(exprs)?, next!(exprs)?]), |
296 | )) |
297 | } else if let Ok(ternary_op) = name.parse() { |
298 | Ok(Expression::Ternary( |
299 | ternary_op, |
300 | Box::new([next!(exprs)?, next!(exprs)?, next!(exprs)?]), |
301 | )) |
302 | } else { |
303 | Err(Error::InvalidFormat(format!( |
304 | "Unknown expression: {:?}" , |
305 | node.tag_name(), |
306 | ))) |
307 | } |
308 | } |
309 | } |
310 | } |
311 | |
312 | fn get_exprs<'a>(node: &'a Node) -> impl Iterator<Item = Result<Expression>> + 'a { |
313 | node.children().filter_map(|n: Node<'_, '_>| { |
314 | if n.is_element() { |
315 | Some(parse_expr(node:n)) |
316 | } else { |
317 | None |
318 | } |
319 | }) |
320 | } |
321 | |
322 | fn get_texts<'a>(node: &'a Node) -> impl Iterator<Item = &'a str> { |
323 | nodeChildren<'_, '_>.children() |
324 | .filter_map(|n: Node<'_, '_>| if n.is_element() { n.text() } else { None }) |
325 | } |
326 | |
327 | #[cfg (test)] |
328 | mod tests { |
329 | use super::*; |
330 | |
331 | macro_rules! make_parse_failed_test { |
332 | ($name:ident, $test_fn:ident, $text:expr,) => { |
333 | #[test] |
334 | #[should_panic] |
335 | fn $name() { |
336 | let doc = roxmltree::Document::parse($text).expect("Parsing xml" ); |
337 | let node = doc.root_element(); |
338 | $test_fn(node).expect("Run parse" ); |
339 | } |
340 | }; |
341 | } |
342 | |
343 | macro_rules! make_parse_test { |
344 | ($name:ident, $test_fn:ident, $text:expr, $value:expr,) => { |
345 | #[test] |
346 | fn $name() { |
347 | let doc = roxmltree::Document::parse($text).expect("Parsing xml" ); |
348 | let node = doc.root_element(); |
349 | let ret = $test_fn(node).expect("Run parse" ); |
350 | let expected = $value; |
351 | k9::assert_equal!(expected, ret); |
352 | } |
353 | }; |
354 | } |
355 | |
356 | make_parse_test!( |
357 | test_parse_charset, |
358 | parse_expr, |
359 | "<charset><range><int>0</int><int>123</int></range></charset>" , |
360 | Expression::from(vec![IntOrRange::Range(0, 123)]), |
361 | ); |
362 | |
363 | make_parse_test!( |
364 | test_parse_int, |
365 | parse_expr, |
366 | "<int>123</int>" , |
367 | Expression::from(123), |
368 | ); |
369 | |
370 | make_parse_failed_test!(test_parse_invalid_int, parse_expr, "<int>123f</int>" ,); |
371 | |
372 | make_parse_test!( |
373 | test_parse_range, |
374 | parse_expr, |
375 | "<range><int>0</int><int>10</int></range>" , |
376 | Expression::from(Value::Range(0, 10)), |
377 | ); |
378 | |
379 | make_parse_failed_test!( |
380 | test_parse_invalid_range, |
381 | parse_expr, |
382 | "<range>0<int>10</int></range>" , |
383 | ); |
384 | |
385 | make_parse_test!( |
386 | test_langset, |
387 | parse_expr, |
388 | "<langset>ko-KR</langset>" , |
389 | Expression::from(Value::LangSet("ko-KR" .into())), |
390 | ); |
391 | } |
392 | |