Skip to main content

xlsbye_xml/
tables.rs

1use crate::writer::{Result, XmlWriter};
2use std::io::Write;
3use xlsbye_core::types::{ParsedTable, RangeRef};
4use xlsbye_core::xml_names::SPREADSHEET_NS;
5
6pub fn write_table(writer: impl Write, table: &ParsedTable) -> Result<()> {
7    let mut writer = XmlWriter::new(writer);
8    writer.write_xml_declaration()?;
9
10    let mut attrs = vec![
11        ("id".to_string(), table.id.to_string()),
12        ("name".to_string(), table.name.clone()),
13        ("displayName".to_string(), table.display_name.clone()),
14        ("ref".to_string(), range_ref_to_a1(&table.range)),
15        (
16            "totalsRowShown".to_string(),
17            if table.totals_row_count > 0 {
18                "1".to_string()
19            } else {
20                "0".to_string()
21            },
22        ),
23    ];
24    if table.header_row_count != 1 {
25        attrs.push((
26            "headerRowCount".to_string(),
27            table.header_row_count.to_string(),
28        ));
29    }
30
31    writer.write_start_element_with_ns("table", [("", SPREADSHEET_NS)], attrs)?;
32
33    if table.has_auto_filter {
34        writer.write_empty_element("autoFilter", [("ref", range_ref_to_a1(&table.range))])?;
35    }
36
37    writer.write_start_element(
38        "tableColumns",
39        [("count", table.columns.len().to_string())],
40    )?;
41    for column in &table.columns {
42        writer.write_empty_element(
43            "tableColumn",
44            [
45                ("id", column.id.to_string()),
46                ("name", column.name.clone()),
47            ],
48        )?;
49    }
50    writer.write_end_element("tableColumns")?;
51
52    if let Some(style_name) = &table.style_name {
53        writer.write_empty_element(
54            "tableStyleInfo",
55            [
56                ("name", style_name.clone()),
57                ("showFirstColumn", "0".to_string()),
58                ("showLastColumn", "0".to_string()),
59                ("showRowStripes", "1".to_string()),
60                ("showColumnStripes", "0".to_string()),
61            ],
62        )?;
63    }
64
65    writer.write_end_element("table")?;
66    Ok(())
67}
68
69fn range_ref_to_a1(range: &RangeRef) -> String {
70    format!(
71        "{}:{}",
72        cell_ref_to_a1(range.first_row, range.first_col),
73        cell_ref_to_a1(range.last_row, range.last_col)
74    )
75}
76
77fn cell_ref_to_a1(row: u32, col: u32) -> String {
78    format!("{}{}", col_to_name(col), row)
79}
80
81fn col_to_name(mut col: u32) -> String {
82    let mut letters = Vec::new();
83    while col > 0 {
84        let rem = ((col - 1) % 26) as u8;
85        letters.push((b'A' + rem) as char);
86        col = (col - 1) / 26;
87    }
88    letters.iter().rev().collect()
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use xlsbye_core::types::TableColumn;
95
96    #[test]
97    fn writes_table_xml() {
98        let table = ParsedTable {
99            id: 1,
100            name: "Table1".to_string(),
101            display_name: "Table1".to_string(),
102            range: RangeRef {
103                first_row: 1,
104                last_row: 10,
105                first_col: 1,
106                last_col: 4,
107            },
108            columns: vec![
109                TableColumn {
110                    id: 1,
111                    name: "Header1".to_string(),
112                },
113                TableColumn {
114                    id: 2,
115                    name: "Header2".to_string(),
116                },
117            ],
118            has_auto_filter: true,
119            style_name: Some("TableStyleMedium2".to_string()),
120            header_row_count: 1,
121            totals_row_count: 0,
122        };
123
124        let mut out = Vec::new();
125        write_table(&mut out, &table).expect("table xml should be written");
126        let xml = String::from_utf8(out).expect("utf-8 xml");
127        assert!(xml.contains("<table xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" id=\"1\" name=\"Table1\" displayName=\"Table1\" ref=\"A1:D10\" totalsRowShown=\"0\""));
128        assert!(xml.contains("<autoFilter ref=\"A1:D10\"/>"));
129        assert!(xml.contains("<tableColumns count=\"2\">"));
130        assert!(xml.contains("<tableColumn id=\"1\" name=\"Header1\"/>"));
131        assert!(xml.contains("<tableStyleInfo name=\"TableStyleMedium2\""));
132    }
133}