xlsbye-xml 0.1.0

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

pub fn write_table(writer: impl Write, table: &ParsedTable) -> Result<()> {
    let mut writer = XmlWriter::new(writer);
    writer.write_xml_declaration()?;

    let mut attrs = vec![
        ("id".to_string(), table.id.to_string()),
        ("name".to_string(), table.name.clone()),
        ("displayName".to_string(), table.display_name.clone()),
        ("ref".to_string(), range_ref_to_a1(&table.range)),
        (
            "totalsRowShown".to_string(),
            if table.totals_row_count > 0 {
                "1".to_string()
            } else {
                "0".to_string()
            },
        ),
    ];
    if table.header_row_count != 1 {
        attrs.push((
            "headerRowCount".to_string(),
            table.header_row_count.to_string(),
        ));
    }

    writer.write_start_element_with_ns("table", [("", SPREADSHEET_NS)], attrs)?;

    if table.has_auto_filter {
        writer.write_empty_element("autoFilter", [("ref", range_ref_to_a1(&table.range))])?;
    }

    writer.write_start_element(
        "tableColumns",
        [("count", table.columns.len().to_string())],
    )?;
    for column in &table.columns {
        writer.write_empty_element(
            "tableColumn",
            [
                ("id", column.id.to_string()),
                ("name", column.name.clone()),
            ],
        )?;
    }
    writer.write_end_element("tableColumns")?;

    if let Some(style_name) = &table.style_name {
        writer.write_empty_element(
            "tableStyleInfo",
            [
                ("name", style_name.clone()),
                ("showFirstColumn", "0".to_string()),
                ("showLastColumn", "0".to_string()),
                ("showRowStripes", "1".to_string()),
                ("showColumnStripes", "0".to_string()),
            ],
        )?;
    }

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

fn range_ref_to_a1(range: &RangeRef) -> String {
    format!(
        "{}:{}",
        cell_ref_to_a1(range.first_row, range.first_col),
        cell_ref_to_a1(range.last_row, range.last_col)
    )
}

fn cell_ref_to_a1(row: u32, col: u32) -> String {
    format!("{}{}", col_to_name(col), row)
}

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::TableColumn;

    #[test]
    fn writes_table_xml() {
        let table = ParsedTable {
            id: 1,
            name: "Table1".to_string(),
            display_name: "Table1".to_string(),
            range: RangeRef {
                first_row: 1,
                last_row: 10,
                first_col: 1,
                last_col: 4,
            },
            columns: vec![
                TableColumn {
                    id: 1,
                    name: "Header1".to_string(),
                },
                TableColumn {
                    id: 2,
                    name: "Header2".to_string(),
                },
            ],
            has_auto_filter: true,
            style_name: Some("TableStyleMedium2".to_string()),
            header_row_count: 1,
            totals_row_count: 0,
        };

        let mut out = Vec::new();
        write_table(&mut out, &table).expect("table xml should be written");
        let xml = String::from_utf8(out).expect("utf-8 xml");
        assert!(xml.contains("<table xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" id=\"1\" name=\"Table1\" displayName=\"Table1\" ref=\"A1:D10\" totalsRowShown=\"0\""));
        assert!(xml.contains("<autoFilter ref=\"A1:D10\"/>"));
        assert!(xml.contains("<tableColumns count=\"2\">"));
        assert!(xml.contains("<tableColumn id=\"1\" name=\"Header1\"/>"));
        assert!(xml.contains("<tableStyleInfo name=\"TableStyleMedium2\""));
    }
}