1#![allow(clippy::useless_format)]
2
3use crate::*;
4use roxmltree::Node;
5
6pub 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
20fn 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
230fn 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
243fn 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
312fn 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
322fn 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)]
328mod 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