xlsbye-xml 0.1.0

SpreadsheetML XML writer for xlsbye
Documentation
use crate::shared_strings::write_text_node;
use crate::writer::{Result, XmlWriter};
use std::io::Write;
use xlsbye_core::types::{Comment, ParsedComments};
use xlsbye_core::xml_names::SPREADSHEET_NS;

pub fn write_comments(writer: impl Write, comments: &ParsedComments) -> Result<()> {
    let mut writer = XmlWriter::new(writer);
    writer.write_xml_declaration()?;
    writer.write_start_element_with_ns(
        "comments",
        [("", SPREADSHEET_NS)],
        std::iter::empty::<(&str, &str)>(),
    )?;

    writer.write_start_element("authors", std::iter::empty::<(&str, &str)>())?;
    for author in &comments.authors {
        writer.write_text_element("author", std::iter::empty::<(&str, &str)>(), author)?;
    }
    writer.write_end_element("authors")?;

    writer.write_start_element("commentList", std::iter::empty::<(&str, &str)>())?;
    for comment in &comments.comments {
        write_comment(&mut writer, comment)?;
    }
    writer.write_end_element("commentList")?;

    writer.write_end_element("comments")?;
    Ok(())
}

fn write_comment<W: Write>(writer: &mut XmlWriter<W>, comment: &Comment) -> Result<()> {
    writer.write_start_element(
        "comment",
        [
            (
                "ref",
                format!("{}{}", col_to_name(comment.cell_ref.col), comment.cell_ref.row),
            ),
            ("authorId", comment.author_index.to_string()),
        ],
    )?;

    writer.write_start_element("text", std::iter::empty::<(&str, &str)>())?;
    if comment.text.is_empty() {
        writer.write_start_element("r", std::iter::empty::<(&str, &str)>())?;
        writer.write_text_element("t", std::iter::empty::<(&str, &str)>(), "")?;
        writer.write_end_element("r")?;
    } else {
        for run in &comment.text {
            writer.write_start_element("r", std::iter::empty::<(&str, &str)>())?;
            write_text_node(writer, "t", &run.text)?;
            writer.write_end_element("r")?;
        }
    }
    writer.write_end_element("text")?;

    writer.write_end_element("comment")
}

fn col_to_name(mut col: u32) -> String {
    let mut letters = Vec::new();
    while col > 0 {
        let rem = ((col - 1) % 26) as u8;
        letters.push((b'A' + rem) as char);
        col = (col - 1) / 26;
    }
    letters.iter().rev().collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use xlsbye_core::types::{CellRef, RichTextRun};

    #[test]
    fn writes_comments_xml() {
        let parsed = ParsedComments {
            authors: vec!["Author Name".to_string()],
            comments: vec![Comment {
                cell_ref: CellRef { row: 1, col: 1 },
                author_index: 0,
                text: vec![RichTextRun {
                    font_index: None,
                    text: "Comment text".to_string(),
                }],
            }],
        };

        let mut out = Vec::new();
        write_comments(&mut out, &parsed).expect("comments xml should be written");
        let xml = String::from_utf8(out).expect("utf-8 xml");

        assert!(xml.contains("<comments xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">"));
        assert!(xml.contains("<author>Author Name</author>"));
        assert!(xml.contains("<comment ref=\"A1\" authorId=\"0\">"));
        assert!(xml.contains("<t>Comment text</t>"));
    }
}