Skip to main content

xlsbye_xml/
comments.rs

1use crate::shared_strings::write_text_node;
2use crate::writer::{Result, XmlWriter};
3use std::io::Write;
4use xlsbye_core::types::{Comment, ParsedComments};
5use xlsbye_core::xml_names::SPREADSHEET_NS;
6
7pub fn write_comments(writer: impl Write, comments: &ParsedComments) -> Result<()> {
8    let mut writer = XmlWriter::new(writer);
9    writer.write_xml_declaration()?;
10    writer.write_start_element_with_ns(
11        "comments",
12        [("", SPREADSHEET_NS)],
13        std::iter::empty::<(&str, &str)>(),
14    )?;
15
16    writer.write_start_element("authors", std::iter::empty::<(&str, &str)>())?;
17    for author in &comments.authors {
18        writer.write_text_element("author", std::iter::empty::<(&str, &str)>(), author)?;
19    }
20    writer.write_end_element("authors")?;
21
22    writer.write_start_element("commentList", std::iter::empty::<(&str, &str)>())?;
23    for comment in &comments.comments {
24        write_comment(&mut writer, comment)?;
25    }
26    writer.write_end_element("commentList")?;
27
28    writer.write_end_element("comments")?;
29    Ok(())
30}
31
32fn write_comment<W: Write>(writer: &mut XmlWriter<W>, comment: &Comment) -> Result<()> {
33    writer.write_start_element(
34        "comment",
35        [
36            (
37                "ref",
38                format!("{}{}", col_to_name(comment.cell_ref.col), comment.cell_ref.row),
39            ),
40            ("authorId", comment.author_index.to_string()),
41        ],
42    )?;
43
44    writer.write_start_element("text", std::iter::empty::<(&str, &str)>())?;
45    if comment.text.is_empty() {
46        writer.write_start_element("r", std::iter::empty::<(&str, &str)>())?;
47        writer.write_text_element("t", std::iter::empty::<(&str, &str)>(), "")?;
48        writer.write_end_element("r")?;
49    } else {
50        for run in &comment.text {
51            writer.write_start_element("r", std::iter::empty::<(&str, &str)>())?;
52            write_text_node(writer, "t", &run.text)?;
53            writer.write_end_element("r")?;
54        }
55    }
56    writer.write_end_element("text")?;
57
58    writer.write_end_element("comment")
59}
60
61fn col_to_name(mut col: u32) -> String {
62    let mut letters = Vec::new();
63    while col > 0 {
64        let rem = ((col - 1) % 26) as u8;
65        letters.push((b'A' + rem) as char);
66        col = (col - 1) / 26;
67    }
68    letters.iter().rev().collect()
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use xlsbye_core::types::{CellRef, RichTextRun};
75
76    #[test]
77    fn writes_comments_xml() {
78        let parsed = ParsedComments {
79            authors: vec!["Author Name".to_string()],
80            comments: vec![Comment {
81                cell_ref: CellRef { row: 1, col: 1 },
82                author_index: 0,
83                text: vec![RichTextRun {
84                    font_index: None,
85                    text: "Comment text".to_string(),
86                }],
87            }],
88        };
89
90        let mut out = Vec::new();
91        write_comments(&mut out, &parsed).expect("comments xml should be written");
92        let xml = String::from_utf8(out).expect("utf-8 xml");
93
94        assert!(xml.contains("<comments xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">"));
95        assert!(xml.contains("<author>Author Name</author>"));
96        assert!(xml.contains("<comment ref=\"A1\" authorId=\"0\">"));
97        assert!(xml.contains("<t>Comment text</t>"));
98    }
99}