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}