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