1use serde::{Deserialize, Serialize};
8
9use crate::namespaces;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(rename = "table")]
14pub struct TableXml {
15 #[serde(rename = "@xmlns")]
16 pub xmlns: String,
17
18 #[serde(rename = "@id")]
19 pub id: u32,
20
21 #[serde(rename = "@name")]
22 pub name: String,
23
24 #[serde(rename = "@displayName")]
25 pub display_name: String,
26
27 #[serde(rename = "@ref")]
28 pub reference: String,
29
30 #[serde(rename = "@totalsRowCount", skip_serializing_if = "Option::is_none")]
31 pub totals_row_count: Option<u32>,
32
33 #[serde(rename = "@totalsRowShown", skip_serializing_if = "Option::is_none")]
34 pub totals_row_shown: Option<bool>,
35
36 #[serde(rename = "@headerRowCount", skip_serializing_if = "Option::is_none")]
37 pub header_row_count: Option<u32>,
38
39 #[serde(rename = "autoFilter", skip_serializing_if = "Option::is_none")]
40 pub auto_filter: Option<TableAutoFilter>,
41
42 #[serde(rename = "tableColumns")]
43 pub table_columns: TableColumnsXml,
44
45 #[serde(rename = "tableStyleInfo", skip_serializing_if = "Option::is_none")]
46 pub table_style_info: Option<TableStyleInfoXml>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51pub struct TableAutoFilter {
52 #[serde(rename = "@ref")]
53 pub reference: String,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub struct TableColumnsXml {
59 #[serde(rename = "@count")]
60 pub count: u32,
61
62 #[serde(rename = "tableColumn", default)]
63 pub columns: Vec<TableColumnXml>,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub struct TableColumnXml {
69 #[serde(rename = "@id")]
70 pub id: u32,
71
72 #[serde(rename = "@name")]
73 pub name: String,
74
75 #[serde(rename = "@totalsRowFunction", skip_serializing_if = "Option::is_none")]
76 pub totals_row_function: Option<String>,
77
78 #[serde(rename = "@totalsRowLabel", skip_serializing_if = "Option::is_none")]
79 pub totals_row_label: Option<String>,
80}
81
82#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct TableStyleInfoXml {
85 #[serde(rename = "@name", skip_serializing_if = "Option::is_none")]
86 pub name: Option<String>,
87
88 #[serde(rename = "@showFirstColumn", skip_serializing_if = "Option::is_none")]
89 pub show_first_column: Option<bool>,
90
91 #[serde(rename = "@showLastColumn", skip_serializing_if = "Option::is_none")]
92 pub show_last_column: Option<bool>,
93
94 #[serde(rename = "@showRowStripes", skip_serializing_if = "Option::is_none")]
95 pub show_row_stripes: Option<bool>,
96
97 #[serde(rename = "@showColumnStripes", skip_serializing_if = "Option::is_none")]
98 pub show_column_stripes: Option<bool>,
99}
100
101impl Default for TableXml {
102 fn default() -> Self {
103 Self {
104 xmlns: namespaces::SPREADSHEET_ML.to_string(),
105 id: 1,
106 name: "Table1".to_string(),
107 display_name: "Table1".to_string(),
108 reference: "A1:A1".to_string(),
109 totals_row_count: None,
110 totals_row_shown: None,
111 header_row_count: None,
112 auto_filter: None,
113 table_columns: TableColumnsXml {
114 count: 1,
115 columns: vec![TableColumnXml {
116 id: 1,
117 name: "Column1".to_string(),
118 totals_row_function: None,
119 totals_row_label: None,
120 }],
121 },
122 table_style_info: None,
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_table_xml_default() {
133 let table = TableXml::default();
134 assert_eq!(table.xmlns, namespaces::SPREADSHEET_ML);
135 assert_eq!(table.name, "Table1");
136 assert_eq!(table.display_name, "Table1");
137 assert_eq!(table.reference, "A1:A1");
138 assert_eq!(table.table_columns.count, 1);
139 assert_eq!(table.table_columns.columns.len(), 1);
140 }
141
142 #[test]
143 fn test_table_xml_serialize_roundtrip() {
144 let table = TableXml {
145 xmlns: namespaces::SPREADSHEET_ML.to_string(),
146 id: 1,
147 name: "SalesTable".to_string(),
148 display_name: "SalesTable".to_string(),
149 reference: "A1:D10".to_string(),
150 totals_row_count: None,
151 totals_row_shown: None,
152 header_row_count: None,
153 auto_filter: Some(TableAutoFilter {
154 reference: "A1:D10".to_string(),
155 }),
156 table_columns: TableColumnsXml {
157 count: 4,
158 columns: vec![
159 TableColumnXml {
160 id: 1,
161 name: "Name".to_string(),
162 totals_row_function: None,
163 totals_row_label: None,
164 },
165 TableColumnXml {
166 id: 2,
167 name: "Region".to_string(),
168 totals_row_function: None,
169 totals_row_label: None,
170 },
171 TableColumnXml {
172 id: 3,
173 name: "Sales".to_string(),
174 totals_row_function: None,
175 totals_row_label: None,
176 },
177 TableColumnXml {
178 id: 4,
179 name: "Profit".to_string(),
180 totals_row_function: None,
181 totals_row_label: None,
182 },
183 ],
184 },
185 table_style_info: Some(TableStyleInfoXml {
186 name: Some("TableStyleMedium2".to_string()),
187 show_first_column: Some(false),
188 show_last_column: Some(false),
189 show_row_stripes: Some(true),
190 show_column_stripes: Some(false),
191 }),
192 };
193
194 let xml = quick_xml::se::to_string(&table).unwrap();
195 assert!(xml.contains("SalesTable"));
196 assert!(xml.contains("A1:D10"));
197 assert!(xml.contains("autoFilter"));
198 assert!(xml.contains("TableStyleMedium2"));
199
200 let parsed: TableXml = quick_xml::de::from_str(&xml).unwrap();
201 assert_eq!(parsed.name, "SalesTable");
202 assert_eq!(parsed.display_name, "SalesTable");
203 assert_eq!(parsed.reference, "A1:D10");
204 assert_eq!(parsed.table_columns.count, 4);
205 assert_eq!(parsed.table_columns.columns.len(), 4);
206 assert_eq!(parsed.table_columns.columns[0].name, "Name");
207 assert!(parsed.auto_filter.is_some());
208 assert_eq!(parsed.auto_filter.unwrap().reference, "A1:D10");
209 let style = parsed.table_style_info.unwrap();
210 assert_eq!(style.name, Some("TableStyleMedium2".to_string()));
211 assert_eq!(style.show_row_stripes, Some(true));
212 }
213
214 #[test]
215 fn test_table_xml_without_optional_fields() {
216 let table = TableXml {
217 xmlns: namespaces::SPREADSHEET_ML.to_string(),
218 id: 2,
219 name: "Table2".to_string(),
220 display_name: "Table2".to_string(),
221 reference: "B1:C5".to_string(),
222 totals_row_count: None,
223 totals_row_shown: None,
224 header_row_count: None,
225 auto_filter: None,
226 table_columns: TableColumnsXml {
227 count: 2,
228 columns: vec![
229 TableColumnXml {
230 id: 1,
231 name: "Col1".to_string(),
232 totals_row_function: None,
233 totals_row_label: None,
234 },
235 TableColumnXml {
236 id: 2,
237 name: "Col2".to_string(),
238 totals_row_function: None,
239 totals_row_label: None,
240 },
241 ],
242 },
243 table_style_info: None,
244 };
245
246 let xml = quick_xml::se::to_string(&table).unwrap();
247 assert!(!xml.contains("autoFilter"));
248 assert!(!xml.contains("tableStyleInfo"));
249
250 let parsed: TableXml = quick_xml::de::from_str(&xml).unwrap();
251 assert_eq!(parsed.id, 2);
252 assert!(parsed.auto_filter.is_none());
253 assert!(parsed.table_style_info.is_none());
254 }
255
256 #[test]
257 fn test_table_xml_with_totals_row() {
258 let table = TableXml {
259 xmlns: namespaces::SPREADSHEET_ML.to_string(),
260 id: 3,
261 name: "Table3".to_string(),
262 display_name: "Table3".to_string(),
263 reference: "A1:B5".to_string(),
264 totals_row_count: Some(1),
265 totals_row_shown: Some(true),
266 header_row_count: None,
267 auto_filter: None,
268 table_columns: TableColumnsXml {
269 count: 2,
270 columns: vec![
271 TableColumnXml {
272 id: 1,
273 name: "Label".to_string(),
274 totals_row_function: None,
275 totals_row_label: Some("Total".to_string()),
276 },
277 TableColumnXml {
278 id: 2,
279 name: "Amount".to_string(),
280 totals_row_function: Some("sum".to_string()),
281 totals_row_label: None,
282 },
283 ],
284 },
285 table_style_info: None,
286 };
287
288 let xml = quick_xml::se::to_string(&table).unwrap();
289 assert!(xml.contains("totalsRowCount"));
290 assert!(xml.contains("totalsRowFunction"));
291 assert!(xml.contains("totalsRowLabel"));
292
293 let parsed: TableXml = quick_xml::de::from_str(&xml).unwrap();
294 assert_eq!(parsed.totals_row_count, Some(1));
295 assert_eq!(
296 parsed.table_columns.columns[0].totals_row_label,
297 Some("Total".to_string())
298 );
299 assert_eq!(
300 parsed.table_columns.columns[1].totals_row_function,
301 Some("sum".to_string())
302 );
303 }
304
305 #[test]
306 fn test_parse_real_excel_table_xml() {
307 let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
308<table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
309 id="1" name="Table1" displayName="Table1" ref="A1:C4">
310 <autoFilter ref="A1:C4"/>
311 <tableColumns count="3">
312 <tableColumn id="1" name="Name"/>
313 <tableColumn id="2" name="City"/>
314 <tableColumn id="3" name="Score"/>
315 </tableColumns>
316 <tableStyleInfo name="TableStyleMedium9" showFirstColumn="0" showLastColumn="0"
317 showRowStripes="1" showColumnStripes="0"/>
318</table>"#;
319
320 let parsed: TableXml = quick_xml::de::from_str(xml).unwrap();
321 assert_eq!(parsed.id, 1);
322 assert_eq!(parsed.name, "Table1");
323 assert_eq!(parsed.display_name, "Table1");
324 assert_eq!(parsed.reference, "A1:C4");
325 assert!(parsed.auto_filter.is_some());
326 assert_eq!(parsed.auto_filter.unwrap().reference, "A1:C4");
327 assert_eq!(parsed.table_columns.count, 3);
328 assert_eq!(parsed.table_columns.columns[0].name, "Name");
329 assert_eq!(parsed.table_columns.columns[1].name, "City");
330 assert_eq!(parsed.table_columns.columns[2].name, "Score");
331 let style = parsed.table_style_info.unwrap();
332 assert_eq!(style.name, Some("TableStyleMedium9".to_string()));
333 assert_eq!(style.show_first_column, Some(false));
334 assert_eq!(style.show_row_stripes, Some(true));
335 }
336}