UPDATE: Dump of initial files
This commit is contained in:
215
xlsx/src/import/tables.rs
Normal file
215
xlsx/src/import/tables.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use std::io::Read;
|
||||
|
||||
use ironcalc_base::types::{Table, TableColumn, TableStyleInfo};
|
||||
use roxmltree::Node;
|
||||
|
||||
use crate::error::XlsxError;
|
||||
|
||||
use super::util::{get_bool, get_bool_false};
|
||||
|
||||
// <table name="Table" displayName="Table" totalsRowCount ref="A1:D6">
|
||||
// <autoFilter ref="A1:D6">
|
||||
// <filterColumn colId="0">
|
||||
// <customFilters><customFilter operator="greaterThan" val=20></customFilter></customFilters>
|
||||
// </filterColumn>
|
||||
// </autoFilter>
|
||||
// <tableColumns count="5">
|
||||
// <tableColumn name="Monday" totalsRowFunction="sum" />
|
||||
// ...
|
||||
// </tableColumns>
|
||||
// <tableStyleInfo name="TableStyle5"/>
|
||||
// </table>
|
||||
|
||||
/// Reads a table in an Excel workbook
|
||||
pub(crate) fn load_table<R: Read + std::io::Seek>(
|
||||
archive: &mut zip::read::ZipArchive<R>,
|
||||
path: &str,
|
||||
sheet_name: &str,
|
||||
) -> Result<Table, XlsxError> {
|
||||
let mut file = archive.by_name(path)?;
|
||||
let mut text = String::new();
|
||||
file.read_to_string(&mut text)?;
|
||||
let document = roxmltree::Document::parse(&text)?;
|
||||
|
||||
// table
|
||||
let table = document
|
||||
.root()
|
||||
.first_child()
|
||||
.ok_or_else(|| XlsxError::Xml("Corrupt XML structure".to_string()))?;
|
||||
|
||||
// Name and display name are normally the same and are unique in a workbook
|
||||
// They also need to be different from any defined name
|
||||
let name = table
|
||||
.attribute("name")
|
||||
.expect("Missing table name")
|
||||
.to_string();
|
||||
|
||||
let display_name = table
|
||||
.attribute("name")
|
||||
.expect("Missing table display name")
|
||||
.to_string();
|
||||
|
||||
// Range of the table, including the totals if any and headers.
|
||||
let reference = table
|
||||
.attribute("ref")
|
||||
.expect("Missing table ref")
|
||||
.to_string();
|
||||
|
||||
// Either 0 or 1, indicates if the table has a formula for totals at the bottom of the table
|
||||
let totals_row_count = match table.attribute("totalsRowCount") {
|
||||
Some(s) => s.parse::<u32>().expect("Invalid totalsRowCount"),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// Either 0 or 1, indicates if the table has headers at the top of the table
|
||||
let header_row_count = match table.attribute("headerRowCount") {
|
||||
Some(s) => s.parse::<u32>().expect("Invalid headerRowCount"),
|
||||
None => 1,
|
||||
};
|
||||
|
||||
// style index of the header row of the table
|
||||
let header_row_dxf_id = if let Some(index_str) = table.attribute("headerRowDxfId") {
|
||||
match index_str.parse::<u32>() {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// style index of the header row of the table
|
||||
let data_dxf_id = if let Some(index_str) = table.attribute("headerRowDxfId") {
|
||||
match index_str.parse::<u32>() {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// style index of the totals row of the table
|
||||
let totals_row_dxf_id = if let Some(index_str) = table.attribute("totalsRowDxfId") {
|
||||
match index_str.parse::<u32>() {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Missing in Calc: styles can also be defined via a name:
|
||||
// headerRowCellStyle, dataCellStyle, totalsRowCellStyle
|
||||
|
||||
// Missing in Calc: styles can also be applied to the borders:
|
||||
// headerRowBorderDxfId, tableBorderDxfId, totalsRowBorderDxfId
|
||||
|
||||
// TODO: Conformant implementations should panic if header_row_dxf_id or data_dxf_id are out of bounds.
|
||||
|
||||
// Note that filters are non dynamic
|
||||
// The only thing important for us is whether or not it has filters
|
||||
let auto_filter = table
|
||||
.descendants()
|
||||
.filter(|n| n.has_tag_name("autoFilter"))
|
||||
.collect::<Vec<Node>>();
|
||||
|
||||
let has_filters = if let Some(filter) = auto_filter.get(0) {
|
||||
filter.children().count() > 0
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// tableColumn
|
||||
let table_column = table
|
||||
.descendants()
|
||||
.filter(|n| n.has_tag_name("tableColumn"))
|
||||
.collect::<Vec<Node>>();
|
||||
let mut columns = Vec::new();
|
||||
for table_column in table_column {
|
||||
let column_name = table_column.attribute("name").expect("Missing column name");
|
||||
let id = table_column.attribute("id").expect("Missing column id");
|
||||
let id = id.parse::<u32>().expect("Invalid id");
|
||||
|
||||
// style index of the header row of the table
|
||||
let header_row_dxf_id = if let Some(index_str) = table_column.attribute("headerRowDxfId") {
|
||||
match index_str.parse::<u32>() {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// style index of the header row of the table column
|
||||
let data_dxf_id = if let Some(index_str) = table_column.attribute("headerRowDxfId") {
|
||||
match index_str.parse::<u32>() {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// style index of the totals row of the table column
|
||||
let totals_row_dxf_id = if let Some(index_str) = table_column.attribute("totalsRowDxfId") {
|
||||
match index_str.parse::<u32>() {
|
||||
Ok(i) => Some(i),
|
||||
Err(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// NOTE: Same as before, we should panic if indices to differential formatting records are out of bounds
|
||||
// Missing in Calc: styles can also be defined via a name:
|
||||
// headerRowCellStyle, dataCellStyle, totalsRowCellStyle
|
||||
|
||||
columns.push(TableColumn {
|
||||
id,
|
||||
name: column_name.to_string(),
|
||||
totals_row_label: None,
|
||||
header_row_dxf_id,
|
||||
data_dxf_id,
|
||||
totals_row_function: None,
|
||||
totals_row_dxf_id,
|
||||
});
|
||||
}
|
||||
|
||||
// tableInfo
|
||||
let table_info = table
|
||||
.descendants()
|
||||
.filter(|n| n.has_tag_name("tableInfo"))
|
||||
.collect::<Vec<Node>>();
|
||||
let style_info = match table_info.get(0) {
|
||||
Some(node) => {
|
||||
let name = node.attribute("name").map(|s| s.to_string());
|
||||
TableStyleInfo {
|
||||
name,
|
||||
show_first_column: get_bool_false(*node, "showFirstColumn"),
|
||||
show_last_column: get_bool_false(*node, "showLastColumn"),
|
||||
show_row_stripes: get_bool(*node, "showRowStripes"),
|
||||
show_column_stripes: get_bool_false(*node, "showColumnStripes"),
|
||||
}
|
||||
}
|
||||
None => TableStyleInfo {
|
||||
name: None,
|
||||
show_first_column: false,
|
||||
show_last_column: false,
|
||||
show_row_stripes: true,
|
||||
show_column_stripes: false,
|
||||
},
|
||||
};
|
||||
Ok(Table {
|
||||
name,
|
||||
display_name,
|
||||
reference,
|
||||
totals_row_count,
|
||||
header_row_count,
|
||||
header_row_dxf_id,
|
||||
data_dxf_id,
|
||||
totals_row_dxf_id,
|
||||
columns,
|
||||
style_info,
|
||||
has_filters,
|
||||
sheet_name: sheet_name.to_string(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user