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