1use std::collections::HashMap;
7
8use yog_ids::sep::TYPED;
9use yog_ids::{yui, yui_attr};
10
11pub struct Entry<'a> {
13 pub kind: &'a str,
14 pub line: usize,
15 pub attrs: Vec<(&'a str, &'a str)>,
16}
17
18impl Entry<'_> {
19 fn attr(&self, key: &str) -> Option<&str> {
20 self.attrs.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
21 }
22}
23
24#[derive(Default)]
26pub struct Resources {
27 pub values: HashMap<String, String>,
28 pub styles: HashMap<String, Vec<(String, String)>>,
29 pub problems: Vec<(usize, String)>,
30}
31
32pub fn local_name(v: &str) -> Option<&str> {
35 v.strip_prefix(TYPED)
36 .filter(|n| !n.is_empty() && !n.starts_with(TYPED) && !n.contains('.'))
37}
38
39const STYLE_ONLY: [&str; 3] = [yui_attr::X_KEY, yui_attr::TARGET, yui_attr::BASED_ON];
41
42impl Resources {
43 pub fn read(entries: &[Entry]) -> Resources {
46 let mut out = Resources::default();
47 let mut pending: Vec<&Entry> = Vec::new();
48 for r in entries {
49 let Some(key) = r.attr(yui_attr::X_KEY) else {
50 out.problems
51 .push((r.line, format!("<{}> needs an x:Key", r.kind)));
52 continue;
53 };
54 if r.kind == yui::STYLE {
55 pending.push(r);
56 } else if let Some(v) = r.attr(yui_attr::VALUE) {
57 out.values.insert(key.to_string(), v.to_string());
58 } else {
59 out.problems.push((
60 r.line,
61 format!(
62 "<{}> needs a value (e.g. <Color x:Key=\"Accent\" value=\"#FFD700\"/>)",
63 r.kind
64 ),
65 ));
66 }
67 }
68 while !pending.is_empty() {
69 let before = pending.len();
70 pending.retain(|s| !out.style(s));
71 if pending.len() == before {
72 out.problems.push((
73 pending[0].line,
74 "<Style> based_on names an unknown (or circular) style".into(),
75 ));
76 break;
77 }
78 }
79 out
80 }
81
82 fn style(&mut self, s: &Entry) -> bool {
84 let mut attrs = match s
85 .attr(yui_attr::BASED_ON)
86 .map(|b| b.trim().trim_start_matches(TYPED))
87 {
88 Some(b) => match self.styles.get(b) {
89 Some(a) => a.clone(),
90 None => return false,
91 },
92 None => Vec::new(),
93 };
94 for (k, v) in s.attrs.iter().filter(|(k, _)| !STYLE_ONLY.contains(k)) {
95 let v = match local_name(v) {
96 Some(n) => match self.values.get(n) {
97 Some(r) => r.clone(),
98 None => {
99 self.problems
100 .push((s.line, format!("<Style> {k}=\"{v}\": no resource `{n}`")));
101 v.to_string()
102 }
103 },
104 None => v.to_string(),
105 };
106 attrs.retain(|(ek, _)| ek != k);
107 attrs.push((k.to_string(), v));
108 }
109 let key = s.attr(yui_attr::X_KEY).unwrap_or_default().to_string();
110 self.styles.insert(key, attrs);
111 true
112 }
113}