1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
use anyhow::bail;
use quick_xml::events::Event;
use std::io::{Read, Seek};
use table_column::{load_table_columns, XlsxTableColumns};
use table_style_info::XlsxTableStyleInfo;
use zip::ZipArchive;
use crate::{
common_types::Dimension,
excel::xml_reader,
helper::{string_to_bool, string_to_unsignedint},
};
use super::filter::{auto_filter::XlsxAutoFilter, sort_state::XlsxSortState};
pub mod calculated_column_formula;
pub mod table_column;
pub mod table_style_info;
pub mod totals_row_formula;
pub mod xml_column_properties;
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.table?view=openxml-3.0.1
///
/// This element is the root element for a table that is not a single cell XML table.
///
/// Example:
/// ```
/// <table xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" id="1" name="financials"
/// displayName="financials" ref="A1:P701" totalsRowShown="0" headerRowDxfId="14" dataDxfId="13"
/// headerRowCellStyle="Currency" dataCellStyle="Currency">
/// <autoFilter ref="A1:P701" />
/// <tableColumns count="16">
/// <tableColumn id="1" name="Segment" />
/// <tableColumn id="2" name="Country" />
/// <tableColumn id="16" name="Product" dataDxfId="12" dataCellStyle="Currency" />
/// </tableColumns>
/// <tableStyleInfo name="TableStyleLight9" showFirstColumn="0" showLastColumn="0" showRowStripes="1" showColumnStripes="0" />
/// </table>
/// ```
///
/// table (Table)
#[derive(Debug, Clone, PartialEq)]
pub struct XlsxTable {
/// extLst (Future Feature Data Storage Area) Not supported
// Child Elements
/// autoFilter (AutoFilter Settings)
pub auto_filter: Option<XlsxAutoFilter>,
/// sortState (Sort State)
pub sort_state: Option<XlsxSortState>,
/// tableColumns (Table Columns)
pub table_columns: Option<XlsxTableColumns>,
/// tableStyleInfo (Table Style)
pub table_style_info: Option<XlsxTableStyleInfo>,
// Attributes Description
/// comment (Table Comment)
///
/// A string representing a textual comment about the table.
pub comment: Option<String>,
/// connectionId (Connection ID)
///
/// An integer representing an ID to indicate which connection from the connections collection is used by this table.
/// This shall only be used for tables that are based off of xml maps.
pub connection_id: Option<u64>,
/// dataCellStyle (Data Style Name)
///
/// A string representing the name of the cell style that is applied to the data area cells of the table.
/// If this string is missing or does not correspond to the name of a cell style, then the data cell style specified by the current table style should be applied.
pub data_cell_style: Option<String>,
/// dataDxfId (Data Area Format Id)
///
/// A zero based integer index into the differential formatting records <dxfs> in the styleSheet indicating which format to apply to the data area of this table.
/// The spreadsheet should fail to load if this index is out of bounds.
pub data_dxf_id: Option<u64>,
/// displayName (Table Name)
///
/// A string representing the name of the table.
/// This is the name that shall be used in formula references, and displayed in the UI to the spreadsheet user.
pub display_name: Option<String>,
/// headerRowBorderDxfId (Header Row Border Format Id)
///
/// A zero based integer index into the differential formatting records <dxfs> in the styleSheet indicating what border formatting to apply to the header row of this table. /// The spreadsheet should fail to load if this index is out of bounds.
pub header_row_border_dxf_id: Option<u64>,
/// headerRowCellStyle (Header Row Style)
///
/// A string representing the name of the cell style that is applied to the header row cells of the table.
/// If this string is missing or does not correspond to the name of a cell style, then the header row style specified by the current table style should be applied.
pub header_row_cell_style: Option<String>,
/// headerRowCount (Header Row Count)
///
/// An integer representing the number of header rows showing at the top of the table. 0 means that the header row is not shown.
/// It is up to the spreadsheet application to determine if numbers greater than 1 are allowed.
/// Unless the spreadsheet application has a feature where there might ever be more than one header row, this number should not be higher than 1.
pub header_row_count: Option<u64>,
/// headerRowDxfId (Header Row Format Id)
///
/// A zero based integer index into the differential formatting records *<dxfs>*in the styleSheet indicating which format to apply to the header row of this table.
/// The spreadsheet should fail to load if this index is out of bounds.
pub header_row_dxf_id: Option<u64>,
/// id (Table Id)
///
/// A non zero integer representing the unique identifier for this table.
/// Each table in the workbook shall have a unique id.
/// Ids can be used to refer to the specific table in the workbook.
pub id: Option<u64>,
/// insertRow (Insert Row Showing)
///
/// A Boolean value indicating whether the insert row is showing.
/// True when the insert row is showing, false otherwise.
///
/// The insert row should only be shown if the table has no data.
pub insert_row: Option<bool>,
/// insertRowShift (Insert Row Shift)
///
/// A Boolean that indicates whether cells in the sheet had to be inserted when the insert row was shown for this table.
/// True if the cells were shifted, false otherwise.
pub insert_row_shift: Option<bool>,
/// name (Name)
///
/// A string representing the name of the table that is used to reference the table programmatically through the spreadsheet applications object model.
/// This string shall be unique per table per sheet.
///
/// By default this should be the same as the table's displayName.
/// This name should also be kept in synch with the displayName when the displayName is updated in the UI by the spreadsheet user.
pub name: Option<String>,
/// published (Published)
///
/// A Boolean representing whether this table is marked as published for viewing by a server based spreadsheet application.
/// True if it should be viewed by the server spreadsheet application, false otherwise.
pub published: Option<bool>,
/// ref (Reference)
///
/// The range on the relevant sheet that the table occupies expressed using A1 style referencing.
/// The reference shall include the totals row if it is shown.
pub r#ref: Option<Dimension>,
/// tableBorderDxfId (Table Border Format Id)
///
/// A zero based integer index into the differential formatting records <dxfs> in the styleSheet indicating what border formatting to apply to the borders of this table.
pub table_border_dxf_id: Option<u64>,
/// tableType (Table Type)
///
/// An optional enumeration specifying the type or source of the table.
/// Indicates whether the table is based off of an external data query, data in a worksheet, or from an xml data mapped to a worksheet.
///
/// possible values: https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.tablevalues?view=openxml-3.0.1
pub table_type: Option<String>,
/// totalsRowBorderDxfId (Totals Row Border Format Id)
///
/// A zero based integer index into the differential formatting records <dxfs> in the styleSheet indicating what border formatting to apply to the totals row of this table.
pub totals_row_border_dxf_id: Option<u64>,
/// totalsRowCellStyle (Totals Row Style)
///
/// A string representing the name of the cell style that is applied to the totals row cells of the table.
/// If this string is missing or does not correspond to the name of a cell style, then the totals row style specified by the current table style should be applied.
pub totals_row_cell_style: Option<String>,
/// totalsRowCount (Totals Row Count)
///
/// An integer representing the number of totals rows that shall be shown at the bottom of the table.
/// 0 means that the totals row is not shown.
/// It is up to the spreadsheet application to determine if numbers greater than 1 are allowed. Unless the spreadsheet application has a feature where their might ever be more than one totals row, this number should not be higher than 1.
pub totals_row_count: Option<u64>,
/// totalsRowDxfId (Totals Row Format Id)
///
/// A zero based integer index into the differential formatting records <dxfs> in the styleSheet indicating which format to apply to the totals row of this table.
pub totals_row_dxf_id: Option<u64>,
/// totalsRowShown (Totals Row Shown)
///
/// A Boolean indicating whether the totals row has ever been shown in the past for this table.
/// True if the totals row has been shown, false otherwise.
pub totals_row_shown: Option<bool>,
}
impl XlsxTable {
pub(crate) fn load(zip: &mut ZipArchive<impl Read + Seek>, path: &str) -> anyhow::Result<Self> {
let mut table = Self {
auto_filter: None,
sort_state: None,
table_columns: None,
table_style_info: None,
comment: None,
connection_id: None,
data_cell_style: None,
data_dxf_id: None,
display_name: None,
header_row_border_dxf_id: None,
header_row_cell_style: None,
header_row_count: None,
header_row_dxf_id: None,
id: None,
insert_row: None,
insert_row_shift: None,
name: None,
published: None,
r#ref: None,
table_border_dxf_id: None,
table_type: None,
totals_row_border_dxf_id: None,
totals_row_cell_style: None,
totals_row_count: None,
totals_row_dxf_id: None,
totals_row_shown: None,
};
let Some(mut reader) = xml_reader(zip, path) else {
return Ok(table);
};
let mut buf = Vec::new();
loop {
buf.clear();
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) if e.local_name().as_ref() == b"extLst" => {
let _ = reader.read_to_end_into(e.to_end().to_owned().name(), &mut Vec::new());
}
Ok(Event::Start(ref e)) if e.local_name().as_ref() == b"table" => {
let attributes = e.attributes();
for a in attributes {
match a {
Ok(a) => {
let string_value = String::from_utf8(a.value.to_vec())?;
match a.key.local_name().as_ref() {
b"comment" => {
table.comment = Some(string_value);
}
b"connectionId" => {
table.connection_id = string_to_unsignedint(&string_value);
}
b"dataCellStyle" => {
table.data_cell_style = Some(string_value);
}
b"dataDxfId" => {
table.data_dxf_id = string_to_unsignedint(&string_value);
}
b"displayName" => {
table.display_name = Some(string_value);
}
b"headerRowBorderDxfId" => {
table.header_row_border_dxf_id =
string_to_unsignedint(&string_value);
}
b"headerRowCellStyle" => {
table.header_row_cell_style = Some(string_value);
}
b"headerRowCount" => {
table.header_row_count =
string_to_unsignedint(&string_value);
}
b"headerRowDxfId" => {
table.header_row_dxf_id =
string_to_unsignedint(&string_value);
}
b"id" => {
table.id = string_to_unsignedint(&string_value);
}
b"insertRow" => {
table.insert_row = string_to_bool(&string_value);
}
b"insertRowShift" => {
table.insert_row_shift = string_to_bool(&string_value);
}
b"name" => {
table.name = Some(string_value);
}
b"published" => {
table.published = string_to_bool(&string_value);
}
b"ref" => {
let value = a.value.as_ref();
table.r#ref = Dimension::from_a1(value);
}
b"tableBorderDxfId" => {
table.table_border_dxf_id =
string_to_unsignedint(&string_value);
}
b"tableType" => {
table.table_type = Some(string_value);
}
b"totalsRowBorderDxfId" => {
table.totals_row_border_dxf_id =
string_to_unsignedint(&string_value);
}
b"totalsRowCellStyle" => {
table.totals_row_cell_style = Some(string_value);
}
b"totalsRowCount" => {
table.totals_row_count =
string_to_unsignedint(&string_value);
}
b"totalsRowDxfId" => {
table.totals_row_dxf_id =
string_to_unsignedint(&string_value);
}
b"totalsRowShown" => {
table.totals_row_shown = string_to_bool(&string_value);
}
_ => {}
}
}
Err(error) => {
bail!(error.to_string())
}
}
}
}
Ok(Event::Start(ref e)) if e.local_name().as_ref() == b"autoFilter" => {
table.auto_filter = Some(XlsxAutoFilter::load(&mut reader, e)?);
}
Ok(Event::Start(ref e)) if e.local_name().as_ref() == b"sortState" => {
table.sort_state = Some(XlsxSortState::load(&mut reader, e)?);
}
Ok(Event::Start(ref e)) if e.local_name().as_ref() == b"tableColumns" => {
table.table_columns = Some(load_table_columns(&mut reader)?);
}
Ok(Event::Start(ref e)) if e.local_name().as_ref() == b"tableStyleInfo" => {
table.table_style_info = Some(XlsxTableStyleInfo::load(e)?);
}
Ok(Event::End(ref e)) if e.local_name().as_ref() == b"table" => break,
Ok(Event::Eof) => break,
Err(e) => bail!(e.to_string()),
_ => (),
}
}
return Ok(table);
}
}