Skip to main content

excelize_rs/xml/
common.rs

1//! Shared OpenXML data structures used across multiple package parts.
2//!
3//! These small types are referenced by worksheets, shared strings, comments,
4//! styles and many other parts. Keeping them in one module avoids circular
5//! dependencies between the XML model modules.
6
7use std::io::Cursor;
8
9use quick_xml::events::Event;
10use quick_xml::{Reader, Writer};
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
12use std::fmt;
13
14// ------------------------------------------------------------------
15// Attribute-value wrappers
16// ------------------------------------------------------------------
17
18/// Wrapper for a string value carried by a child element `attrValString`.
19#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct AttrValString {
21    #[serde(rename = "@val", default)]
22    pub val: Option<String>,
23}
24
25/// Wrapper for an integer value carried by a child element `attrValInt`.
26#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct AttrValInt {
28    #[serde(rename = "@val", default)]
29    pub val: Option<i64>,
30}
31
32/// Wrapper for a floating point value carried by a child element `attrValFloat`.
33#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
34pub struct AttrValFloat {
35    #[serde(rename = "@val", default)]
36    pub val: Option<f64>,
37}
38
39/// Wrapper for a boolean value carried by a child element `attrValBool`.
40#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct AttrValBool {
42    #[serde(
43        rename = "@val",
44        default,
45        serialize_with = "serialize_attr_val_bool",
46        deserialize_with = "deserialize_attr_val_bool"
47    )]
48    pub val: Option<bool>,
49}
50
51impl AttrValBool {
52    /// Return the boolean value or `false` when missing.
53    pub fn value(&self) -> bool {
54        self.val.unwrap_or(false)
55    }
56}
57
58fn serialize_attr_val_bool<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
59where
60    S: Serializer,
61{
62    match value {
63        Some(true) => serializer.serialize_str("1"),
64        _ => serializer.serialize_str("0"),
65    }
66}
67
68fn deserialize_attr_val_bool<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
69where
70    D: Deserializer<'de>,
71{
72    struct AttrValBoolVisitor;
73
74    impl<'de> Visitor<'de> for AttrValBoolVisitor {
75        type Value = Option<bool>;
76
77        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
78            formatter.write_str("a boolean attribute value (0, 1, true, false, or empty)")
79        }
80
81        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
82        where
83            E: serde::de::Error,
84        {
85            match value {
86                "" | "1" | "true" => Ok(Some(true)),
87                "0" | "false" => Ok(Some(false)),
88                _ => Err(E::custom(format!(
89                    "invalid boolean attribute value: {}",
90                    value
91                ))),
92            }
93        }
94
95        fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
96        where
97            E: serde::de::Error,
98        {
99            Ok(Some(value))
100        }
101    }
102
103    deserializer.deserialize_any(AttrValBoolVisitor)
104}
105
106impl AttrValString {
107    /// Return the string value or an empty string when missing.
108    pub fn value(&self) -> String {
109        self.val.clone().unwrap_or_default()
110    }
111}
112
113impl AttrValInt {
114    /// Return the integer value or `0` when missing.
115    pub fn value(&self) -> i64 {
116        self.val.unwrap_or(0)
117    }
118}
119
120impl AttrValFloat {
121    /// Return the float value or `0.0` when missing.
122    pub fn value(&self) -> f64 {
123        self.val.unwrap_or(0.0)
124    }
125}
126
127// ------------------------------------------------------------------
128// Color
129// ------------------------------------------------------------------
130
131/// Directly maps the color element.
132#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
133pub struct XlsxColor {
134    #[serde(rename = "@auto", default, skip_serializing_if = "Option::is_none")]
135    pub auto: Option<bool>,
136    #[serde(rename = "@indexed", default, skip_serializing_if = "Option::is_none")]
137    pub indexed: Option<i64>,
138    #[serde(rename = "@rgb", default, skip_serializing_if = "Option::is_none")]
139    pub rgb: Option<String>,
140    #[serde(rename = "@theme", default, skip_serializing_if = "Option::is_none")]
141    pub theme: Option<i64>,
142    #[serde(rename = "@tint", default, skip_serializing_if = "Option::is_none")]
143    pub tint: Option<f64>,
144}
145
146// ------------------------------------------------------------------
147// Rich text / phonetic primitives
148// ------------------------------------------------------------------
149
150/// Directly maps the `t` element in a run or shared string item.
151#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct XlsxT {
153    #[serde(rename = "@xml:space", default, skip_serializing_if = "Option::is_none")]
154    pub space: Option<String>,
155    #[serde(rename = "$value", default)]
156    pub val: String,
157}
158
159/// Represents a run of rich text.
160#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
161pub struct XlsxR {
162    #[serde(rename = "rPr", default)]
163    pub r_pr: Option<XlsxRPr>,
164    #[serde(rename = "t", default)]
165    pub t: Option<XlsxT>,
166}
167
168/// Run properties.
169#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
170pub struct XlsxRPr {
171    #[serde(rename = "rFont", default)]
172    pub r_font: Option<AttrValString>,
173    #[serde(rename = "charset", default)]
174    pub charset: Option<AttrValInt>,
175    #[serde(rename = "family", default)]
176    pub family: Option<AttrValInt>,
177    #[serde(rename = "b", default)]
178    pub b: Option<AttrValBool>,
179    #[serde(rename = "i", default)]
180    pub i: Option<AttrValBool>,
181    #[serde(rename = "strike", default)]
182    pub strike: Option<AttrValBool>,
183    #[serde(rename = "outline", default)]
184    pub outline: Option<AttrValBool>,
185    #[serde(rename = "shadow", default)]
186    pub shadow: Option<AttrValBool>,
187    #[serde(rename = "condense", default)]
188    pub condense: Option<AttrValBool>,
189    #[serde(rename = "extend", default)]
190    pub extend: Option<AttrValBool>,
191    #[serde(rename = "color", default)]
192    pub color: Option<XlsxColor>,
193    #[serde(rename = "sz", default)]
194    pub sz: Option<AttrValFloat>,
195    #[serde(rename = "u", default)]
196    pub u: Option<AttrValString>,
197    #[serde(rename = "vertAlign", default)]
198    pub vert_align: Option<AttrValString>,
199    #[serde(rename = "scheme", default)]
200    pub scheme: Option<AttrValString>,
201}
202
203/// A phonetic run.
204#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
205pub struct XlsxPhoneticRun {
206    #[serde(rename = "@sb", default)]
207    pub sb: u32,
208    #[serde(rename = "@eb", default)]
209    pub eb: u32,
210    #[serde(rename = "t", default)]
211    pub t: String,
212}
213
214/// Phonetic properties.
215#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
216pub struct XlsxPhoneticPr {
217    #[serde(rename = "@fontId", default, skip_serializing_if = "Option::is_none")]
218    pub font_id: Option<i64>,
219    #[serde(rename = "@type", default, skip_serializing_if = "Option::is_none")]
220    pub r#type: Option<String>,
221    #[serde(rename = "@alignment", default, skip_serializing_if = "Option::is_none")]
222    pub alignment: Option<String>,
223}
224
225/// Rich text run used in the public API.
226#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
227pub struct RichTextRun {
228    #[serde(default)]
229    pub font: Option<crate::styles::Font>,
230    #[serde(default)]
231    pub text: String,
232}
233
234// ------------------------------------------------------------------
235// Extension list
236// ------------------------------------------------------------------
237
238/// Directly maps the extLst element.
239#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
240pub struct XlsxExtLst {
241    #[serde(rename = "ext", default)]
242    pub ext: Vec<XlsxExt>,
243}
244
245/// Directly maps an ext element used in extension lists.
246#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
247pub struct XlsxExt {
248    #[serde(rename = "@uri", default)]
249    pub uri: Option<String>,
250    #[serde(
251        rename = "@xmlns:x14",
252        default,
253        skip_serializing_if = "Option::is_none"
254    )]
255    pub xmlns_x14: Option<String>,
256    #[serde(rename = "@xmlns:xm", default, skip_serializing_if = "Option::is_none")]
257    pub xmlns_xm: Option<String>,
258    #[serde(rename = "$value", default)]
259    pub content: String,
260}
261
262/// Parse the inner XML of an `<extLst>` element and preserve namespace
263/// declarations on each `<ext>` child.
264pub fn parse_ext_lst_content(xml: &str) -> Result<XlsxExtLst, quick_xml::Error> {
265    let wrapped = format!("<extLst>{xml}</extLst>");
266    let mut reader = Reader::from_str(&wrapped);
267    reader.config_mut().trim_text(true);
268    let mut buf = Vec::new();
269    let mut ext_lst = XlsxExtLst::default();
270    let mut current: Option<XlsxExt> = None;
271    let mut depth = 0;
272    let mut content_buf: Vec<u8> = Vec::new();
273    let mut writer: Option<Writer<Cursor<&mut Vec<u8>>>> = None;
274
275    loop {
276        match reader.read_event_into(&mut buf) {
277            Ok(Event::Start(e)) => {
278                if e.name().as_ref() == b"ext" && depth == 0 {
279                    let mut ext = XlsxExt::default();
280                    for attr in e.attributes() {
281                        let attr = attr?;
282                        let value = String::from_utf8_lossy(&attr.value).to_string();
283                        match attr.key.as_ref() {
284                            b"uri" => ext.uri = Some(value),
285                            b"xmlns:x14" => ext.xmlns_x14 = Some(value),
286                            b"xmlns:xm" => ext.xmlns_xm = Some(value),
287                            _ => {}
288                        }
289                    }
290                    current = Some(ext);
291                    depth = 1;
292                    content_buf.clear();
293                    writer = Some(Writer::new(Cursor::new(&mut content_buf)));
294                } else if depth > 0 {
295                    depth += 1;
296                    if let Some(ref mut w) = writer {
297                        w.write_event(Event::Start(e))?;
298                    }
299                }
300            }
301            Ok(Event::End(e)) => {
302                if e.name().as_ref() == b"ext" && depth == 1 {
303                    if let Some(mut ext) = current.take() {
304                        writer = None;
305                        ext.content = String::from_utf8_lossy(&content_buf).to_string();
306                        ext_lst.ext.push(ext);
307                    }
308                    depth = 0;
309                } else if depth > 0 {
310                    depth -= 1;
311                    if let Some(ref mut w) = writer {
312                        w.write_event(Event::End(e))?;
313                    }
314                }
315            }
316            Ok(Event::Empty(e)) => {
317                if depth > 0 {
318                    if let Some(ref mut w) = writer {
319                        w.write_event(Event::Empty(e))?;
320                    }
321                }
322            }
323            Ok(Event::Text(e)) => {
324                if depth > 0 {
325                    if let Some(ref mut w) = writer {
326                        w.write_event(Event::Text(e))?;
327                    }
328                }
329            }
330            Ok(Event::CData(e)) => {
331                if depth > 0 {
332                    if let Some(ref mut w) = writer {
333                        w.write_event(Event::CData(e))?;
334                    }
335                }
336            }
337            Ok(Event::Comment(e)) => {
338                if depth > 0 {
339                    if let Some(ref mut w) = writer {
340                        w.write_event(Event::Comment(e))?;
341                    }
342                }
343            }
344            Ok(Event::PI(e)) => {
345                if depth > 0 {
346                    if let Some(ref mut w) = writer {
347                        w.write_event(Event::PI(e))?;
348                    }
349                }
350            }
351            Ok(Event::DocType(e)) => {
352                if depth > 0 {
353                    if let Some(ref mut w) = writer {
354                        w.write_event(Event::DocType(e))?;
355                    }
356                }
357            }
358            Ok(Event::Decl(_)) => {}
359            Ok(Event::Eof) => break,
360            Err(e) => return Err(e),
361        }
362        buf.clear();
363    }
364    Ok(ext_lst)
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
372    struct TestBool {
373        #[serde(rename = "b", default)]
374        b: Option<AttrValBool>,
375    }
376
377    #[test]
378    fn test_attr_val_bool_serialization() {
379        let t = TestBool {
380            b: Some(AttrValBool { val: Some(true) }),
381        };
382        let xml = quick_xml::se::to_string(&t).unwrap();
383        assert!(xml.contains(r#"<b val="1"/>"#), "{}", xml);
384
385        let f = TestBool {
386            b: Some(AttrValBool { val: Some(false) }),
387        };
388        let xml = quick_xml::se::to_string(&f).unwrap();
389        assert!(xml.contains(r#"<b val="0"/>"#), "{}", xml);
390    }
391
392    #[test]
393    fn test_attr_val_bool_deserialization() {
394        let xml = r#"<TestBool><b val="1"/></TestBool>"#;
395        let t: TestBool = quick_xml::de::from_str(xml).unwrap();
396        assert_eq!(t.b.unwrap().val, Some(true));
397
398        let xml = r#"<TestBool><b val="0"/></TestBool>"#;
399        let t: TestBool = quick_xml::de::from_str(xml).unwrap();
400        assert_eq!(t.b.unwrap().val, Some(false));
401
402        let xml = r#"<TestBool/>"#;
403        let t: TestBool = quick_xml::de::from_str(xml).unwrap();
404        assert_eq!(t.b, None);
405    }
406}
407
408/// Serialize an `<extLst>` element to the inner XML that should be placed
409/// between the `<extLst>` and `</extLst>` tags.
410pub fn serialize_ext_lst(ext_lst: &XlsxExtLst) -> String {
411    let mut s = String::new();
412    for ext in &ext_lst.ext {
413        s.push_str("<ext");
414        if let Some(uri) = &ext.uri {
415            s.push_str(&format!(r#" uri="{}""#, uri));
416        }
417        if let Some(ns) = &ext.xmlns_x14 {
418            s.push_str(&format!(r#" xmlns:x14="{}""#, ns));
419        }
420        if let Some(ns) = &ext.xmlns_xm {
421            s.push_str(&format!(r#" xmlns:xm="{}""#, ns));
422        }
423        s.push('>');
424        s.push_str(&ext.content);
425        s.push_str("</ext>");
426    }
427    s
428}
429
430// ------------------------------------------------------------------
431// Inner XML / opaque content
432// ------------------------------------------------------------------
433
434/// Container used to preserve raw XML content that is not fully modeled.
435#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
436pub struct XlsxInnerXml {
437    #[serde(rename = "$value", default)]
438    pub content: String,
439}