UPDATE: Dynamic arrays!

This commit is contained in:
Nicolás Hatcher
2025-03-23 01:51:17 +01:00
parent 8844b80c51
commit 889845b948
26 changed files with 1172 additions and 123 deletions

View File

@@ -22,7 +22,7 @@ use itertools::Itertools;
use ironcalc_base::{
expressions::{
parser::{stringify::to_excel_string, Node},
parser::{static_analysis::StaticResult, stringify::to_excel_string, Node},
types::CellReferenceRC,
utils::number_to_column,
},
@@ -56,7 +56,7 @@ fn get_formula_attribute(
pub(crate) fn get_worksheet_xml(
worksheet: &Worksheet,
parsed_formulas: &[Node],
parsed_formulas: &[(Node, StaticResult)],
dimension: &str,
is_sheet_selected: bool,
) -> String {
@@ -104,7 +104,7 @@ pub(crate) fn get_worksheet_xml(
let style = get_cell_style_attribute(*s);
row_data_str.push(format!("<c r=\"{cell_name}\"{style}/>"));
}
Cell::BooleanCell { v, s } => {
Cell::SpillBooleanCell { v, s, .. } | Cell::BooleanCell { v, s } => {
// <c r="A8" t="b" s="1">
// <v>1</v>
// </c>
@@ -114,7 +114,7 @@ pub(crate) fn get_worksheet_xml(
"<c r=\"{cell_name}\" t=\"b\"{style}><v>{b}</v></c>"
));
}
Cell::NumberCell { v, s } => {
Cell::SpillNumberCell { v, s, .. } | Cell::NumberCell { v, s } => {
// Normally the type number is left out. Example:
// <c r="C6" s="1">
// <v>3</v>
@@ -122,7 +122,7 @@ pub(crate) fn get_worksheet_xml(
let style = get_cell_style_attribute(*s);
row_data_str.push(format!("<c r=\"{cell_name}\"{style}><v>{v}</v></c>"));
}
Cell::ErrorCell { ei, s } => {
Cell::SpillErrorCell { ei, s, .. } | Cell::ErrorCell { ei, s } => {
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"e\"{style}><v>{ei}</v></c>"
@@ -153,7 +153,7 @@ pub(crate) fn get_worksheet_xml(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
&parsed_formulas[*f as usize].0,
);
let b = i32::from(*v);
@@ -172,7 +172,7 @@ pub(crate) fn get_worksheet_xml(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
&parsed_formulas[*f as usize].0,
);
let style = get_cell_style_attribute(*s);
@@ -189,14 +189,14 @@ pub(crate) fn get_worksheet_xml(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
&parsed_formulas[*f as usize].0,
);
let style = get_cell_style_attribute(*s);
let escaped_v = escape_xml(v);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"str\"{style}><f>{formula}</f><v>{escaped_v}</v></c>"
));
"<c r=\"{cell_name}\" t=\"str\"{style}><f>{formula}</f><v>{escaped_v}</v></c>"
));
}
Cell::CellFormulaError {
f,
@@ -213,13 +213,135 @@ pub(crate) fn get_worksheet_xml(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize],
&parsed_formulas[*f as usize].0,
);
let style = get_cell_style_attribute(*s);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"e\"{style}><f>{formula}</f><v>{ei}</v></c>"
));
}
Cell::SpillStringCell { v, s, .. } => {
// inline string
// <c r="A1" t="str">
let style = get_cell_style_attribute(*s);
let escaped_v = escape_xml(v);
row_data_str.push(format!(
"<c r=\"{cell_name}\" t=\"str\"{style}><v>{escaped_v}</v></c>"
));
}
Cell::DynamicCellFormula { .. } => {
panic!("Model needs to be evaluated before saving!");
}
Cell::DynamicCellFormulaBoolean { f, v, s, r, a: _ } => {
// <c r="A1" s="3" cm="1">
// <f t="array" ref="A1:A10">A1:A10</f>
// <v>1</v>
// </c>
let style = get_cell_style_attribute(*s);
let range = format!(
"{}{}:{}{}",
column_name,
row_index,
number_to_column(r.0 + column_index).unwrap(),
r.1 + row_index
);
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize].0,
);
let b = i32::from(*v);
row_data_str.push(format!(
r#"<c r="{cell_name}" t="b" s="{style}" cm="1"><f t="array" ref="{range}">{formula}</f><v>{b}</v></c>"#
));
}
Cell::DynamicCellFormulaNumber { f, v, s, r, a: _ } => {
// <c r="C4" s="3" cm="1">
// <f t="array" ref="C4:C10">C4:C10</f>
// <v>123</v>
// </c>
let style = get_cell_style_attribute(*s);
let range = format!(
"{}{}:{}{}",
column_name,
row_index,
number_to_column(r.0 + column_index).unwrap(),
r.1 + row_index
);
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize].0,
);
row_data_str.push(format!(
r#"<c r="{cell_name}" s="{style}" cm="1"><f t="array" ref="{range}">{formula}</f><v>{v}</v></c>"#
));
}
Cell::DynamicCellFormulaString { f, v, s, r, a: _ } => {
// <c r="C6" t="str" s="5" cm="1">
// <f t="array" ref="C6:C10">C6:C10</f>
// <v>Hello world!</v>
// </c>
let style = get_cell_style_attribute(*s);
let range = format!(
"{}{}:{}{}",
column_name,
row_index,
number_to_column(r.0 + column_index).unwrap(),
r.1 + row_index
);
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize].0,
);
let escaped_v = escape_xml(v);
row_data_str.push(format!(
r#"<c r="{cell_name}" t="str" s="{style}" cm="1"><f t="array" ref="{range}">{formula}</f><v>{escaped_v}</v></c>"#
));
}
Cell::DynamicCellFormulaError {
f,
ei,
s,
o: _,
m: _,
r,
a: _,
} => {
// <c r="C6" t="e" s="4" cm="1">
// <f t="array" ref="C6:C10">C6:C10</f>
// <v>#DIV/0!</v>
// </c>
let style = get_cell_style_attribute(*s);
let range = format!(
"{}{}:{}{}",
column_name,
row_index,
number_to_column(r.0 + column_index).unwrap(),
r.1 + row_index
);
let formula = get_formula_attribute(
worksheet.get_name(),
*row_index,
*column_index,
&parsed_formulas[*f as usize].0,
);
row_data_str.push(format!(
r#"<c r="{cell_name}" t="e" s="{style}" cm="1"><f t="array" ref="{range}">{formula}</f><v>{ei}</v></c>"#
));
}
}
}
let row_style_str = match row_style_dict.get(row_index) {

View File

@@ -303,13 +303,15 @@ fn from_a1_to_rc(
context: String,
tables: HashMap<String, Table>,
defined_names: Vec<DefinedNameS>,
is_array: bool,
) -> Result<String, XlsxError> {
let mut parser = Parser::new(worksheets.to_owned(), defined_names, tables);
let cell_reference =
parse_reference(&context).map_err(|error| XlsxError::Xml(error.to_string()))?;
let mut t = parser.parse(&formula, &cell_reference);
add_implicit_intersection(&mut t, true);
if !is_array {
add_implicit_intersection(&mut t, true);
}
Ok(to_rc_format(&t))
}
@@ -837,6 +839,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
};
let cell_metadata = cell.attribute("cm");
let is_dynamic_array = cell_metadata == Some("1");
// type, the default type being "n" for number
// If the cell does not have a value is an empty cell
@@ -903,6 +906,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
context,
tables.clone(),
defined_names.clone(),
is_dynamic_array,
)?;
match index_map.get(&si) {
Some(index) => {
@@ -951,7 +955,6 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
return Err(XlsxError::NotImplemented("data table formulas".to_string()));
}
"array" | "normal" => {
let is_dynamic_array = cell_metadata == Some("1");
if formula_type == "array" && !is_dynamic_array {
// Dynamic formulas in Excel are formulas of type array with the cm=1, those we support.
// On the other hand the old CSE formulas or array formulas are not supported in IronCalc for the time being
@@ -966,6 +969,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
context,
tables.clone(),
defined_names.clone(),
is_dynamic_array,
)?;
match get_formula_index(&formula, &shared_formulas) {

Binary file not shown.