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 | |