xlsbye-xml 0.1.0

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

pub fn write_shared_strings(
    writer: impl Write,
    sst: &SharedStringTable,
    count: u32,
    unique_count: u32,
) -> Result<()> {
    let mut writer = XmlWriter::new(writer);
    writer.write_xml_declaration()?;
    writer.write_start_element_with_ns(
        "sst",
        [("", SPREADSHEET_NS)],
        [
            ("count", count.to_string()),
            ("uniqueCount", unique_count.to_string()),
        ],
    )?;

    for entry in sst {
        writer.write_start_element("si", std::iter::empty::<(&str, &str)>())?;
        match entry {
            xlsbye_core::types::SharedStringEntry::Plain(text) => {
                write_text_node(&mut writer, "t", text)?;
            }
            xlsbye_core::types::SharedStringEntry::Rich(runs) => {
                for run in runs {
                    writer.write_start_element("r", std::iter::empty::<(&str, &str)>())?;
                    if let Some(font_index) = run.font_index {
                        writer.write_start_element("rPr", std::iter::empty::<(&str, &str)>())?;
                        writer.write_empty_element("rFont", [("val", format!("font{}", font_index))])?;
                        writer.write_end_element("rPr")?;
                    }
                    write_text_node(&mut writer, "t", &run.text)?;
                    writer.write_end_element("r")?;
                }
            }
        }
        writer.write_end_element("si")?;
    }

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

pub(crate) fn write_text_node<W: Write>(
    writer: &mut XmlWriter<W>,
    tag_name: &str,
    text: &str,
) -> Result<()> {
    if needs_space_preserve(text) {
        writer.write_text_element(tag_name, [("xml:space", "preserve")], text)
    } else {
        writer.write_text_element(tag_name, std::iter::empty::<(&str, &str)>(), text)
    }
}

fn needs_space_preserve(text: &str) -> bool {
    text.chars().next().is_some_and(char::is_whitespace)
        || text.chars().last().is_some_and(char::is_whitespace)
}