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}