Skip to main content

ooxml_core/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 字符串属性转义。
34///
35/// 实际实现委托给 [`crate::escape::xml_escape`],此处保留函数名 `escape`
36/// 以维持向后兼容(writer.rs 通过 `super::parser::escape` 引用)。
37pub fn escape(s: &str) -> String {
38    crate::escape::xml_escape(s)
39}
40
41/// XML 字符串反转义(解析时用)。
42pub 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
71/// 元素属性集合:本地名(去前缀)→ 字符串。
72/// 使用 `Vec<u8>` 拥有键的所有权,避免借用迭代器临时值。
73pub type AttrMap = HashMap<Vec<u8>, String>;
74
75/// 从 `BytesStart` 提取属性集合(值自动反转义)。
76pub 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
89/// 取出某个属性,缺失返回 None。
90pub fn get_attr<'a>(m: &'a AttrMap, key: &[u8]) -> Option<&'a String> {
91    m.get(key)
92}
93
94/// 取出某个属性,缺失时用默认值。
95pub 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
99/// `i64` 解析。
100pub fn parse_i64(s: &str) -> Option<i64> {
101    s.parse().ok()
102}
103/// `u32` 解析。
104pub fn parse_u32(s: &str) -> Option<u32> {
105    s.parse().ok()
106}
107/// `u16` 解析。
108pub fn parse_u16(s: &str) -> Option<u16> {
109    s.parse().ok()
110}
111/// `u8` 解析。
112pub fn parse_u8(s: &str) -> Option<u8> {
113    s.parse().ok()
114}
115/// `f64` 解析(解析失败时返回 None 而不是 NaN)。
116pub fn parse_f64(s: &str) -> Option<f64> {
117    s.parse().ok()
118}
119/// 布尔解析:`true`/`1`/`on` 视为真;其余视为假(`0`/`false`/`off`/空)。
120pub fn parse_bool(s: &str) -> bool {
121    matches!(s, "1" | "true" | "on" | "True")
122}
123
124/// 任意 FromStr 类型的属性解析:缺失返回 None。
125pub fn parse_attr<T: FromStr>(m: &AttrMap, key: &[u8]) -> Option<T> {
126    m.get(key).and_then(|v| v.parse().ok())
127}
128
129/// 任意 FromStr 类型的属性解析:缺失时使用默认值。
130pub 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
134/// 把 i64 序列化为十进制字符串。
135pub fn i64_to_str(v: i64) -> String {
136    v.to_string()
137}
138
139/// 写一个 EMU 值(i64 → 十进制字符串)。
140pub fn emu_str(v: Emu) -> String {
141    v.value().to_string()
142}
143
144/// 写一个可选 EMU 值(不写就不输出)。
145pub fn emu_opt(v: Option<Emu>) -> String {
146    v.map(|x| x.value().to_string()).unwrap_or_default()
147}
148
149/// 把字符串集合渲染为 `"k1=\"v1\" k2=\"v2\""`(不带前后尖括号)。
150pub 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
162/// 把一个 `BytesStart` 渲染为完整的开标签(仅写 start 事件,不写子节点)。
163pub 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
177/// `render_start` 的自闭合版本。
178pub 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
192/// 写一个闭标签。
193pub 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
200/// 收集一个 XML Reader 中**当前开始标签到对应结束标签**之间的内容到字符串。
201/// 用于 `<a:rPr>...</a:rPr>` 这种"完整子元素"提取。
202pub 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                    // quick-xml 0.40 移除 `unescape()`,使用 `decode()`
232                    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&quot;y\"");
268    }
269}