Skip to main content

pptx_rs/oxml/
parser.rs

1//! XML 解析与序列化基础。
2//!
3//! 我们的策略是 **不** 用 serde 派生(OOXML 命名空间混乱、属性多),而是用
4//! quick-xml 直接走 SAX 风格流式 API,专注读写一致性。
5//!
6//! 本文件提供:
7//!
8//! - 字符串 ↔ 数字 / 布尔 / 枚举 的转换;
9//! - 属性读取的便利函数;
10//! - XML 转义 / 反转义;
11//! - 简易的"开始/结束元素"过滤器。
12//!
13//! # 读取策略
14//!
15//! 读取大文件(如 `theme1.xml`)时**不**用 `Event::Start/End` 嵌套扫描完整
16//! 树形结构——一方面性能差,另一方面没必要。本库只对"有用属性"做精确匹配,
17//! 其余部分用 [`collect_inner_text`] 工具一次吞掉。
18//!
19//! # 写入策略
20//!
21//! 序列化时**不**经过本模块——所有写操作走 [`super::writer::XmlWriter`]。
22//! 这里的 `escape` / `render_attrs` / `render_start` / `render_empty` / `render_end`
23//! 是为兼容老路径与测试用例保留的"基础字符串工具"。
24
25use std::collections::HashMap;
26use std::str::FromStr;
27
28use quick_xml::events::BytesStart;
29use quick_xml::reader::Reader;
30
31use crate::units::Emu;
32
33/// XML 字符串属性转义。
34pub 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("&lt;"),
40            '>' => out.push_str("&gt;"),
41            '"' => out.push_str("&quot;"),
42            '\'' => out.push_str("&apos;"),
43            _ => out.push(c),
44        }
45    }
46    out
47}
48
49/// XML 字符串反转义(解析时用)。
50pub 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
79/// 元素属性集合:本地名(去前缀)→ 字符串。
80/// 使用 `Vec<u8>` 拥有键的所有权,避免借用迭代器临时值。
81pub type AttrMap = HashMap<Vec<u8>, String>;
82
83/// 从 `BytesStart` 提取属性集合(值自动反转义)。
84pub 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
97/// 取出某个属性,缺失返回 None。
98pub fn get_attr<'a>(m: &'a AttrMap, key: &[u8]) -> Option<&'a String> {
99    m.get(key)
100}
101
102/// 取出某个属性,缺失时用默认值。
103pub 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
107/// `i64` 解析。
108pub fn parse_i64(s: &str) -> Option<i64> {
109    s.parse().ok()
110}
111/// `u32` 解析。
112pub fn parse_u32(s: &str) -> Option<u32> {
113    s.parse().ok()
114}
115/// `u16` 解析。
116pub fn parse_u16(s: &str) -> Option<u16> {
117    s.parse().ok()
118}
119/// `u8` 解析。
120pub fn parse_u8(s: &str) -> Option<u8> {
121    s.parse().ok()
122}
123/// `f64` 解析(解析失败时返回 None 而不是 NaN)。
124pub fn parse_f64(s: &str) -> Option<f64> {
125    s.parse().ok()
126}
127/// 布尔解析:`true`/`1`/`on` 视为真;其余视为假(`0`/`false`/`off`/空)。
128pub fn parse_bool(s: &str) -> bool {
129    matches!(s, "1" | "true" | "on" | "True")
130}
131
132/// 任意 FromStr 类型的属性解析:缺失返回 None。
133pub fn parse_attr<T: FromStr>(m: &AttrMap, key: &[u8]) -> Option<T> {
134    m.get(key).and_then(|v| v.parse().ok())
135}
136
137/// 任意 FromStr 类型的属性解析:缺失时使用默认值。
138pub 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
142/// 把 i64 序列化为十进制字符串。
143pub fn i64_to_str(v: i64) -> String {
144    v.to_string()
145}
146
147/// 写一个 EMU 值(i64 → 十进制字符串)。
148pub fn emu_str(v: Emu) -> String {
149    v.value().to_string()
150}
151
152/// 写一个可选 EMU 值(不写就不输出)。
153pub fn emu_opt(v: Option<Emu>) -> String {
154    v.map(|x| x.value().to_string()).unwrap_or_default()
155}
156
157/// 把字符串集合渲染为 `"k1=\"v1\" k2=\"v2\""`(不带前后尖括号)。
158pub 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
170/// 把一个 `BytesStart` 渲染为完整的开标签(仅写 start 事件,不写子节点)。
171pub 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
185/// `render_start` 的自闭合版本。
186pub 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
200/// 写一个闭标签。
201pub 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
208/// 收集一个 XML Reader 中**当前开始标签到对应结束标签**之间的内容到字符串。
209/// 用于 `<a:rPr>...</a:rPr>` 这种"完整子元素"提取。
210pub 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                    // quick-xml 0.40 移除 `unescape()`,使用 `decode()`
240                    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&quot;y\"");
276    }
277}