fontconfig_parser/
parser.rs

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 = xml_doc.root_element();
10
11    if fontconfig.tag_name().name() != "fontconfig" {
12        return Err(Error::NoFontconfig);
13    }
14
15    Ok(fontconfig
16        .children()
17        .filter_map(|c| parse_config_part(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 = 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| {
314        if n.is_element() {
315            Some(parse_expr(n))
316        } else {
317            None
318        }
319    })
320}
321
322fn get_texts<'a>(node: &'a Node) -> impl Iterator<Item = &'a str> {
323    node.children()
324        .filter_map(|n| 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}