excel_xmlwriter/
lib.rs

1//! # Excel_XMLWriter
2//!
3//! `excel_xmlwriter` is library for writing XML in the same format and with
4//!  the same escaping as used by Excel in xlsx xml files.
5//!
6//! This is a test crate for a future application and isn't currently
7//! very useful on its own.
8//!
9//!
10//! ```
11//! use std::fs::File;
12//! use excel_xmlwriter::XMLWriter;
13//!
14//! fn main() -> Result<(), std::io::Error> {
15//!     let xmlfile = File::create("test.xml")?;
16//!     let mut writer = XMLWriter::new(&xmlfile);
17//!
18//!     writer.xml_declaration();
19//!
20//!     let attributes = vec![("bar", "1")];
21//!     writer.xml_data_element("foo", "some text", &attributes);
22//!
23//!     Ok(())
24//! }
25//! ```
26//! Output in `test.xml`:
27//!
28//! ```xml
29//! <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
30//! <foo bar="1">some text</foo>
31//! ```
32// SPDX-License-Identifier: MIT
33// Copyright 2022, John McNamara, jmcnamara@cpan.org
34
35use std::fs::File;
36use std::io::Write;
37
38pub struct XMLWriter<'a> {
39    xmlfile: &'a File,
40}
41
42impl<'a> XMLWriter<'a> {
43    /// Create a new XMLWriter struct to write XML to a given filehandle.
44    /// ```
45    /// # use std::fs::File;
46    /// # use excel_xmlwriter::XMLWriter;
47    /// #
48    /// # fn main() -> Result<(), std::io::Error> {
49    /// let xmlfile = File::create("test.xml")?;
50    /// let mut writer = XMLWriter::new(&xmlfile);
51    /// #
52    /// # Ok(())
53    /// # }
54    /// ```
55    pub fn new(xmlfile: &File) -> XMLWriter {
56        XMLWriter { xmlfile }
57    }
58
59    /// Write an XML file declaration.
60    /// ```
61    /// # use std::fs::File;
62    /// # use excel_xmlwriter::XMLWriter;
63    /// #
64    /// # fn main() -> Result<(), std::io::Error> {
65    /// # let xmlfile = File::create("test.xml")?;
66    /// # let mut writer = XMLWriter::new(&xmlfile);
67    /// #
68    /// writer.xml_declaration();
69    /// // Output: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
70    /// #
71    /// # Ok(())
72    /// # }
73    ///
74    pub fn xml_declaration(&mut self) {
75        writeln!(
76            &mut self.xmlfile,
77            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#
78        )
79        .expect("Couldn't write to file");
80    }
81
82    /// Write an XML start tag with attributes.
83    /// ```
84    /// # use std::fs::File;
85    /// # use excel_xmlwriter::XMLWriter;
86    /// #
87    /// # fn main() -> Result<(), std::io::Error> {
88    /// # let xmlfile = File::create("test.xml")?;
89    /// # let mut writer = XMLWriter::new(&xmlfile);
90    /// #
91    /// let attributes = vec![("bar", "1")];
92    /// writer.xml_data_element("foo", "some text", &attributes);
93    /// // Output: <foo bar="1">some text</foo>
94    /// #
95    /// # Ok(())
96    /// # }
97    /// ```
98    pub fn xml_start_tag(&mut self, tag: &str, attributes: &Vec<(&str, &str)>) {
99        let mut attribute_str = String::from("");
100
101        for attribute in attributes {
102            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
103            attribute_str.push_str(&pair);
104        }
105
106        write!(&mut self.xmlfile, r"<{}{}>", tag, attribute_str).expect("Couldn't write to file");
107    }
108
109    /// Write an XML end tag.
110    /// ```
111    /// # use std::fs::File;
112    /// # use excel_xmlwriter::XMLWriter;
113    /// #
114    /// # fn main() -> Result<(), std::io::Error> {
115    /// # let xmlfile = File::create("test.xml")?;
116    /// # let mut writer = XMLWriter::new(&xmlfile);
117    /// #
118    /// writer.xml_end_tag("foo");
119    /// // Output: </foo>
120    /// // Output: <foo bar="1">some text</foo>
121    /// #
122    /// # Ok(())
123    /// # }
124    /// ```
125    pub fn xml_end_tag(&mut self, tag: &str) {
126        write!(&mut self.xmlfile, r"</{}>", tag).expect("Couldn't write to file");
127    }
128
129    /// Write an empty XML tag with attributes.
130    /// ```
131    /// # use std::fs::File;
132    /// # use excel_xmlwriter::XMLWriter;
133    /// #
134    /// # fn main() -> Result<(), std::io::Error> {
135    /// # let xmlfile = File::create("test.xml")?;
136    /// # let mut writer = XMLWriter::new(&xmlfile);
137    /// #
138    /// let attributes = vec![("bar", "1"), ("car", "y")];
139    /// writer.xml_empty_tag("foo", &attributes);
140    /// // Output: <foo bar="1" car="y"/>
141    /// #
142    /// # Ok(())
143    /// # }
144    /// ```
145    pub fn xml_empty_tag(&mut self, tag: &str, attributes: &Vec<(&str, &str)>) {
146        let mut attribute_str = String::from("");
147
148        for attribute in attributes {
149            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
150            attribute_str.push_str(&pair);
151        }
152
153        write!(&mut self.xmlfile, r"<{}{}/>", tag, attribute_str).expect("Couldn't write to file");
154    }
155
156    /// Write an XML element containing data with optional attributes.
157    /// ```
158    /// # use std::fs::File;
159    /// # use excel_xmlwriter::XMLWriter;
160    /// #
161    /// # fn main() -> Result<(), std::io::Error> {
162    /// # let xmlfile = File::create("test.xml")?;
163    /// # let mut writer = XMLWriter::new(&xmlfile);
164    /// #
165    /// let attributes = vec![("bar", "1")];
166    /// writer.xml_data_element("foo", "some text", &attributes);
167    /// // Output: <foo bar="1">some text</foo>
168    /// #
169    /// # Ok(())
170    /// # }
171    /// ```
172    pub fn xml_data_element(&mut self, tag: &str, data: &str, attributes: &Vec<(&str, &str)>) {
173        let mut attribute_str = String::from("");
174
175        for attribute in attributes {
176            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
177            attribute_str.push_str(&pair);
178        }
179
180        write!(
181            &mut self.xmlfile,
182            r"<{}{}>{}</{}>",
183            tag,
184            attribute_str,
185            escape_data(data),
186            tag
187        )
188        .expect("Couldn't write to file");
189    }
190
191    /// Optimized tag writer for `<c>` cell string elements in the inner loop.
192    pub fn xml_string_element(&mut self, index: u32, attributes: &Vec<(&str, &str)>) {
193        let mut attribute_str = String::from("");
194
195        for attribute in attributes {
196            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
197            attribute_str.push_str(&pair);
198        }
199
200        write!(
201            &mut self.xmlfile,
202            r#"<c{} t="s"><v>{}</v></c>"#,
203            attribute_str, index
204        )
205        .expect("Couldn't write to file");
206    }
207
208    /// Optimized tag writer for `<c>` cell number elements in the inner loop.
209    pub fn xml_number_element(&mut self, number: f64, attributes: &Vec<(&str, &str)>) {
210        // TODO: make this generic with the previous function.
211        let mut attribute_str = String::from("");
212
213        for attribute in attributes {
214            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
215            attribute_str.push_str(&pair);
216        }
217
218        write!(
219            &mut self.xmlfile,
220            r#"<c{} t="s"><v>{}</v></c>"#,
221            attribute_str, number
222        )
223        .expect("Couldn't write to file");
224    }
225
226    /// Optimized tag writer for `<c>` cell formula elements in the inner loop.
227    pub fn xml_formula_element(
228        &mut self,
229        formula: &str,
230        result: f64,
231        attributes: &Vec<(&str, &str)>,
232    ) {
233        let mut attribute_str = String::from("");
234
235        for attribute in attributes {
236            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
237            attribute_str.push_str(&pair);
238        }
239
240        write!(
241            &mut self.xmlfile,
242            r#"<c{}><f>{}</f><v>{}</v></c>"#,
243            attribute_str,
244            escape_data(formula),
245            result
246        )
247        .expect("Couldn't write to file");
248    }
249
250    /// Optimized tag writer for shared strings `<si>` elements.
251    pub fn xml_si_element(&mut self, string: &str, attributes: &Vec<(&str, &str)>) {
252        let mut attribute_str = String::from("");
253
254        for attribute in attributes {
255            let pair = format!(r#" {}="{}""#, attribute.0, escape_attributes(attribute.1));
256            attribute_str.push_str(&pair);
257        }
258
259        write!(
260            &mut self.xmlfile,
261            r#"<si><t{}>{}</t></si>"#,
262            attribute_str,
263            escape_data(string)
264        )
265        .expect("Couldn't write to file");
266    }
267
268    /// Optimized tag writer for shared strings <si> rich string elements.
269    pub fn xml_rich_si_element(&mut self, string: &str) {
270        write!(&mut self.xmlfile, r#"<si>{}</si>"#, string).expect("Couldn't write to file");
271    }
272}
273
274// Escape XML characters in attributes.
275fn escape_attributes(attribute: &str) -> String {
276    attribute
277        .replace('&', "&amp;")
278        .replace('"', "&quot;")
279        .replace('<', "&lt;")
280        .replace('>', "&gt;")
281        .replace('\n', "&#xA;")
282}
283
284// Escape XML characters in data sections of tags.  Note, this
285// is different from escape_attributes() because double quotes
286// and newline are not escaped by Excel.
287fn escape_data(attribute: &str) -> String {
288    attribute
289        .replace('&', "&amp;")
290        .replace('<', "&lt;")
291        .replace('>', "&gt;")
292}
293
294#[cfg(test)]
295mod tests {
296
297    use super::XMLWriter;
298    use std::fs::File;
299    use std::io::{Read, Seek, SeekFrom};
300    use tempfile::tempfile;
301
302    use pretty_assertions::assert_eq;
303
304    fn read_xmlfile_data(tempfile: &mut File) -> String {
305        let mut got = String::new();
306        tempfile.seek(SeekFrom::Start(0)).unwrap();
307        tempfile.read_to_string(&mut got).unwrap();
308        got
309    }
310
311    #[test]
312    fn test_xml_declaration() {
313        let expected = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
314
315        let mut tempfile = tempfile().unwrap();
316        let mut writer = XMLWriter::new(&tempfile);
317
318        writer.xml_declaration();
319
320        let got = read_xmlfile_data(&mut tempfile);
321        assert_eq!(got, expected);
322    }
323
324    #[test]
325    fn test_xml_start_tag() {
326        let expected = "<foo>";
327        let attributes = vec![];
328
329        let mut tempfile = tempfile().unwrap();
330        let mut writer = XMLWriter::new(&tempfile);
331
332        writer.xml_start_tag("foo", &attributes);
333
334        let got = read_xmlfile_data(&mut tempfile);
335        assert_eq!(got, expected);
336    }
337
338    #[test]
339    fn test_xml_start_tag_with_attributes() {
340        let expected = r#"<foo span="8" baz="7">"#;
341        let attributes = vec![("span", "8"), ("baz", "7")];
342
343        let mut tempfile = tempfile().unwrap();
344        let mut writer = XMLWriter::new(&tempfile);
345
346        writer.xml_start_tag("foo", &attributes);
347
348        let got = read_xmlfile_data(&mut tempfile);
349        assert_eq!(got, expected);
350    }
351
352    #[test]
353    fn test_xml_end_tag() {
354        let expected = "</foo>";
355
356        let mut tempfile = tempfile().unwrap();
357        let mut writer = XMLWriter::new(&tempfile);
358
359        writer.xml_end_tag("foo");
360
361        let got = read_xmlfile_data(&mut tempfile);
362        assert_eq!(got, expected);
363    }
364
365    #[test]
366    fn test_xml_empty_tag() {
367        let expected = "<foo/>";
368        let attributes = vec![];
369
370        let mut tempfile = tempfile().unwrap();
371        let mut writer = XMLWriter::new(&tempfile);
372
373        writer.xml_empty_tag("foo", &attributes);
374
375        let got = read_xmlfile_data(&mut tempfile);
376        assert_eq!(got, expected);
377    }
378
379    #[test]
380    fn test_xml_empty_tag_with_attributes() {
381        let expected = r#"<foo span="8"/>"#;
382        let attributes = vec![("span", "8")];
383
384        let mut tempfile = tempfile().unwrap();
385        let mut writer = XMLWriter::new(&tempfile);
386
387        writer.xml_empty_tag("foo", &attributes);
388
389        let got = read_xmlfile_data(&mut tempfile);
390        assert_eq!(got, expected);
391    }
392
393    #[test]
394    fn test_xml_data_element() {
395        let expected = r#"<foo>bar</foo>"#;
396        let attributes = vec![];
397
398        let mut tempfile = tempfile().unwrap();
399        let mut writer = XMLWriter::new(&tempfile);
400
401        writer.xml_data_element("foo", "bar", &attributes);
402
403        let got = read_xmlfile_data(&mut tempfile);
404        assert_eq!(got, expected);
405    }
406
407    #[test]
408    fn test_xml_data_element_with_attributes() {
409        let expected = r#"<foo span="8">bar</foo>"#;
410        let attributes = vec![("span", "8")];
411
412        let mut tempfile = tempfile().unwrap();
413        let mut writer = XMLWriter::new(&tempfile);
414
415        writer.xml_data_element("foo", "bar", &attributes);
416
417        let got = read_xmlfile_data(&mut tempfile);
418        assert_eq!(got, expected);
419    }
420
421    #[test]
422    fn test_xml_data_element_with_escapes() {
423        let expected = r#"<foo span="8">&amp;&lt;&gt;"</foo>"#;
424        let attributes = vec![("span", "8")];
425
426        let mut tempfile = tempfile().unwrap();
427        let mut writer = XMLWriter::new(&tempfile);
428
429        writer.xml_data_element("foo", "&<>\"", &attributes);
430
431        let got = read_xmlfile_data(&mut tempfile);
432        assert_eq!(got, expected);
433    }
434
435    #[test]
436    fn test_xml_string_element() {
437        let expected = r#"<c span="8" t="s"><v>99</v></c>"#;
438        let attributes = vec![("span", "8")];
439
440        let mut tempfile = tempfile().unwrap();
441        let mut writer = XMLWriter::new(&tempfile);
442
443        writer.xml_string_element(99, &attributes);
444
445        let got = read_xmlfile_data(&mut tempfile);
446        assert_eq!(got, expected);
447    }
448
449    #[test]
450    fn test_xml_number_element() {
451        let expected = r#"<c span="8" t="s"><v>99</v></c>"#;
452        let attributes = vec![("span", "8")];
453
454        let mut tempfile = tempfile().unwrap();
455        let mut writer = XMLWriter::new(&tempfile);
456
457        writer.xml_number_element(99.0, &attributes);
458
459        let got = read_xmlfile_data(&mut tempfile);
460        assert_eq!(got, expected);
461    }
462
463    #[test]
464    fn test_xml_formula_element() {
465        let expected = r#"<c span="8"><f>1+2</f><v>3</v></c>"#;
466        let attributes = vec![("span", "8")];
467
468        let mut tempfile = tempfile().unwrap();
469        let mut writer = XMLWriter::new(&tempfile);
470
471        writer.xml_formula_element("1+2", 3.0, &attributes);
472
473        let got = read_xmlfile_data(&mut tempfile);
474        assert_eq!(got, expected);
475    }
476
477    #[test]
478    fn test_xml_si_element() {
479        let expected = r#"<si><t span="8">foo</t></si>"#;
480        let attributes = vec![("span", "8")];
481
482        let mut tempfile = tempfile().unwrap();
483        let mut writer = XMLWriter::new(&tempfile);
484
485        writer.xml_si_element("foo", &attributes);
486
487        let got = read_xmlfile_data(&mut tempfile);
488        assert_eq!(got, expected);
489    }
490
491    #[test]
492    fn test_xml_rich_si_element() {
493        let expected = r#"<si>foo</si>"#;
494
495        let mut tempfile = tempfile().unwrap();
496        let mut writer = XMLWriter::new(&tempfile);
497
498        writer.xml_rich_si_element("foo");
499
500        let got = read_xmlfile_data(&mut tempfile);
501        assert_eq!(got, expected);
502    }
503}