1use std::collections::HashMap;
26use std::str::FromStr;
27
28use quick_xml::events::BytesStart;
29use quick_xml::reader::Reader;
30
31use crate::units::Emu;
32
33pub fn escape(s: &str) -> String {
35 let mut out = String::with_capacity(s.len());
36 for c in s.chars() {
37 match c {
38 '&' => out.push_str("&"),
39 '<' => out.push_str("<"),
40 '>' => out.push_str(">"),
41 '"' => out.push_str("""),
42 '\'' => out.push_str("'"),
43 _ => out.push(c),
44 }
45 }
46 out
47}
48
49pub fn unescape(s: &str) -> String {
51 let mut out = String::with_capacity(s.len());
52 let mut i = 0;
53 let bytes = s.as_bytes();
54 while i < bytes.len() {
55 if bytes[i] == b'&' {
56 if let Some(end) = s[i..].find(';') {
57 let entity = &s[i + 1..i + end];
58 let replacement = match entity {
59 "amp" => Some('&'),
60 "lt" => Some('<'),
61 "gt" => Some('>'),
62 "quot" => Some('"'),
63 "apos" => Some('\''),
64 _ => None,
65 };
66 if let Some(c) = replacement {
67 out.push(c);
68 i += end + 1;
69 continue;
70 }
71 }
72 }
73 out.push(bytes[i] as char);
74 i += 1;
75 }
76 out
77}
78
79pub type AttrMap = HashMap<Vec<u8>, String>;
82
83pub fn attrs_of(e: &BytesStart<'_>) -> AttrMap {
85 let mut m = HashMap::new();
86 for a in e.attributes().flatten() {
87 let k = a.key.as_ref().to_vec();
88 let v = a
89 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
90 .map(|c| c.to_string())
91 .unwrap_or_default();
92 m.insert(k, v);
93 }
94 m
95}
96
97pub fn get_attr<'a>(m: &'a AttrMap, key: &[u8]) -> Option<&'a String> {
99 m.get(key)
100}
101
102pub fn get_attr_or<'a>(m: &'a AttrMap, key: &[u8], default: &'a str) -> &'a str {
104 m.get(key).map(|s| s.as_str()).unwrap_or(default)
105}
106
107pub fn parse_i64(s: &str) -> Option<i64> {
109 s.parse().ok()
110}
111pub fn parse_u32(s: &str) -> Option<u32> {
113 s.parse().ok()
114}
115pub fn parse_u16(s: &str) -> Option<u16> {
117 s.parse().ok()
118}
119pub fn parse_u8(s: &str) -> Option<u8> {
121 s.parse().ok()
122}
123pub fn parse_f64(s: &str) -> Option<f64> {
125 s.parse().ok()
126}
127pub fn parse_bool(s: &str) -> bool {
129 matches!(s, "1" | "true" | "on" | "True")
130}
131
132pub fn parse_attr<T: FromStr>(m: &AttrMap, key: &[u8]) -> Option<T> {
134 m.get(key).and_then(|v| v.parse().ok())
135}
136
137pub fn parse_attr_or<T: FromStr>(m: &AttrMap, key: &[u8], default: T) -> T {
139 m.get(key).and_then(|v| v.parse().ok()).unwrap_or(default)
140}
141
142pub fn i64_to_str(v: i64) -> String {
144 v.to_string()
145}
146
147pub fn emu_str(v: Emu) -> String {
149 v.value().to_string()
150}
151
152pub fn emu_opt(v: Option<Emu>) -> String {
154 v.map(|x| x.value().to_string()).unwrap_or_default()
155}
156
157pub fn render_attrs(pairs: &[(&str, &str)]) -> String {
159 let mut s = String::new();
160 for (k, v) in pairs {
161 s.push(' ');
162 s.push_str(k);
163 s.push_str("=\"");
164 s.push_str(&escape(v));
165 s.push('"');
166 }
167 s
168}
169
170pub fn render_start(name: &[u8], attrs: &[(&str, &str)]) -> String {
172 let mut s = String::from("<");
173 s.push_str(std::str::from_utf8(name).unwrap_or("?"));
174 for (k, v) in attrs {
175 s.push(' ');
176 s.push_str(k);
177 s.push_str("=\"");
178 s.push_str(&escape(v));
179 s.push('"');
180 }
181 s.push('>');
182 s
183}
184
185pub fn render_empty(name: &[u8], attrs: &[(&str, &str)]) -> String {
187 let mut s = String::from("<");
188 s.push_str(std::str::from_utf8(name).unwrap_or("?"));
189 for (k, v) in attrs {
190 s.push(' ');
191 s.push_str(k);
192 s.push_str("=\"");
193 s.push_str(&escape(v));
194 s.push('"');
195 }
196 s.push_str("/>");
197 s
198}
199
200pub fn render_end(name: &[u8]) -> String {
202 let mut s = String::from("</");
203 s.push_str(std::str::from_utf8(name).unwrap_or("?"));
204 s.push('>');
205 s
206}
207
208pub fn collect_inner_text<R: std::io::BufRead>(
211 rd: &mut Reader<R>,
212 name: &[u8],
213) -> crate::Result<String> {
214 use quick_xml::events::Event;
215 let mut depth = 0usize;
216 let mut out = String::new();
217 let mut buf = Vec::new();
218 loop {
219 match rd.read_event_into(&mut buf) {
220 Ok(Event::Start(e)) => {
221 if e.name().as_ref() == name {
222 depth = 1;
223 continue;
224 }
225 if depth > 0 {
226 depth += 1;
227 }
228 }
229 Ok(Event::End(_e)) => {
230 if depth > 0 {
231 depth -= 1;
232 if depth == 0 {
233 return Ok(out);
234 }
235 }
236 }
237 Ok(Event::Text(t)) => {
238 if depth > 0 {
239 out.push_str(&t.decode().unwrap_or_default());
241 }
242 }
243 Ok(Event::CData(c)) => {
244 if depth > 0 {
245 out.push_str(std::str::from_utf8(&c).unwrap_or(""));
246 }
247 }
248 Ok(Event::Eof) => break,
249 Err(e) => return Err(crate::Error::Xml(format!("xml: {e}"))),
250 _ => {}
251 }
252 buf.clear();
253 }
254 Err(crate::Error::oxml(format!(
255 "unexpected EOF when collecting <{}>",
256 std::str::from_utf8(name).unwrap_or("?")
257 )))
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn escape_unescape_round() {
266 let s = "a < b & \"c\" 'd' > z";
267 let e = escape(s);
268 let u = unescape(&e);
269 assert_eq!(u, s);
270 }
271
272 #[test]
273 fn render_attrs_works() {
274 let s = render_attrs(&[("a", "1"), ("b", "x\"y")]);
275 assert_eq!(s, " a=\"1\" b=\"x"y\"");
276 }
277}