Compare commits
1 Commits
feature/ni
...
feature/ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
736afb8a62 |
@@ -222,7 +222,7 @@ impl Parser {
|
|||||||
|
|
||||||
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
pub fn parse(&mut self, formula: &str, context: &Option<CellReferenceRC>) -> Node {
|
||||||
self.lexer.set_formula(formula);
|
self.lexer.set_formula(formula);
|
||||||
self.context.clone_from(context);
|
self.context = context.clone();
|
||||||
self.parse_expr()
|
self.parse_expr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,8 +118,6 @@ pub struct Model {
|
|||||||
pub(crate) language: Language,
|
pub(crate) language: Language,
|
||||||
/// The timezone used to evaluate the model
|
/// The timezone used to evaluate the model
|
||||||
pub(crate) tz: Tz,
|
pub(crate) tz: Tz,
|
||||||
/// The view id. A view consist of a selected sheet and ranges.
|
|
||||||
pub(crate) view_id: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Maybe this should be the same as CellReference
|
// FIXME: Maybe this should be the same as CellReference
|
||||||
@@ -683,13 +681,6 @@ impl Model {
|
|||||||
Err(format!("Invalid color: {}", color))
|
Err(format!("Invalid color: {}", color))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Makes the grid lines in the sheet visible (`true`) or hidden (`false`)
|
|
||||||
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
|
|
||||||
let worksheet = self.workbook.worksheet_mut(sheet)?;
|
|
||||||
worksheet.show_grid_lines = show_grid_lines;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cell_value(&self, cell: &Cell, cell_reference: CellReferenceIndex) -> CalcResult {
|
fn get_cell_value(&self, cell: &Cell, cell_reference: CellReferenceIndex) -> CalcResult {
|
||||||
use Cell::*;
|
use Cell::*;
|
||||||
match cell {
|
match cell {
|
||||||
@@ -895,7 +886,6 @@ impl Model {
|
|||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
tz,
|
tz,
|
||||||
view_id: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
model.parse_formulas();
|
model.parse_formulas();
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ use crate::{
|
|||||||
calc_result::Range,
|
calc_result::Range,
|
||||||
expressions::{
|
expressions::{
|
||||||
lexer::LexerMode,
|
lexer::LexerMode,
|
||||||
parser::{
|
parser::stringify::{rename_sheet_in_node, to_rc_format},
|
||||||
stringify::{rename_sheet_in_node, to_rc_format},
|
parser::Parser,
|
||||||
Parser,
|
|
||||||
},
|
|
||||||
types::CellReferenceRC,
|
types::CellReferenceRC,
|
||||||
},
|
},
|
||||||
language::get_language,
|
language::get_language,
|
||||||
locale::get_locale,
|
locale::get_locale,
|
||||||
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
model::{get_milliseconds_since_epoch, Model, ParsedDefinedName},
|
||||||
types::{
|
types::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
|
||||||
Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView,
|
|
||||||
},
|
|
||||||
utils::ParsedReference,
|
utils::ParsedReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,20 +33,7 @@ fn is_valid_sheet_name(name: &str) -> bool {
|
|||||||
|
|
||||||
impl Model {
|
impl Model {
|
||||||
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
/// Creates a new worksheet. Note that it does not check if the name or the sheet_id exists
|
||||||
fn new_empty_worksheet(name: &str, sheet_id: u32, view_ids: &[&u32]) -> Worksheet {
|
fn new_empty_worksheet(name: &str, sheet_id: u32) -> Worksheet {
|
||||||
let mut views = HashMap::new();
|
|
||||||
for id in view_ids {
|
|
||||||
views.insert(
|
|
||||||
**id,
|
|
||||||
WorksheetView {
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Worksheet {
|
Worksheet {
|
||||||
cols: vec![],
|
cols: vec![],
|
||||||
rows: vec![],
|
rows: vec![],
|
||||||
@@ -65,8 +48,6 @@ impl Model {
|
|||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
frozen_columns: 0,
|
frozen_columns: 0,
|
||||||
frozen_rows: 0,
|
frozen_rows: 0,
|
||||||
show_grid_lines: true,
|
|
||||||
views,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +122,7 @@ impl Model {
|
|||||||
self.parsed_defined_names = parsed_defined_names;
|
self.parsed_defined_names = parsed_defined_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reparses all formulas and defined names
|
// Reparses all formulas and defined names
|
||||||
pub(crate) fn reset_parsed_structures(&mut self) {
|
pub(crate) fn reset_parsed_structures(&mut self) {
|
||||||
self.parser
|
self.parser
|
||||||
.set_worksheets(self.workbook.get_worksheet_names());
|
.set_worksheets(self.workbook.get_worksheet_names());
|
||||||
@@ -172,8 +153,7 @@ impl Model {
|
|||||||
let sheet_name = format!("{}{}", base_name, index);
|
let sheet_name = format!("{}{}", base_name, index);
|
||||||
// Now we need a sheet_id
|
// Now we need a sheet_id
|
||||||
let sheet_id = self.get_new_sheet_id();
|
let sheet_id = self.get_new_sheet_id();
|
||||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id);
|
||||||
let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id, &view_ids);
|
|
||||||
self.workbook.worksheets.push(worksheet);
|
self.workbook.worksheets.push(worksheet);
|
||||||
self.reset_parsed_structures();
|
self.reset_parsed_structures();
|
||||||
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
(sheet_name, self.workbook.worksheets.len() as u32 - 1)
|
||||||
@@ -204,8 +184,7 @@ impl Model {
|
|||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => self.get_new_sheet_id(),
|
None => self.get_new_sheet_id(),
|
||||||
};
|
};
|
||||||
let view_ids: Vec<&u32> = self.workbook.views.keys().collect();
|
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id);
|
||||||
let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id, &view_ids);
|
|
||||||
if sheet_index as usize > self.workbook.worksheets.len() {
|
if sheet_index as usize > self.workbook.worksheets.len() {
|
||||||
return Err("Sheet index out of range".to_string());
|
return Err("Sheet index out of range".to_string());
|
||||||
}
|
}
|
||||||
@@ -352,14 +331,11 @@ impl Model {
|
|||||||
// "2020-08-06T21:20:53Z
|
// "2020-08-06T21:20:53Z
|
||||||
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
let now = dt.format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
||||||
|
|
||||||
let mut views = HashMap::new();
|
|
||||||
views.insert(0, WorkbookView { sheet: 0 });
|
|
||||||
|
|
||||||
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
// String versions of the locale are added here to simplify the serialize/deserialize logic
|
||||||
let workbook = Workbook {
|
let workbook = Workbook {
|
||||||
shared_strings: vec![],
|
shared_strings: vec![],
|
||||||
defined_names: vec![],
|
defined_names: vec![],
|
||||||
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1, &[&0])],
|
worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)],
|
||||||
styles: Default::default(),
|
styles: Default::default(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
settings: WorkbookSettings {
|
settings: WorkbookSettings {
|
||||||
@@ -375,7 +351,6 @@ impl Model {
|
|||||||
last_modified: now,
|
last_modified: now,
|
||||||
},
|
},
|
||||||
tables: HashMap::new(),
|
tables: HashMap::new(),
|
||||||
views,
|
|
||||||
};
|
};
|
||||||
let parsed_formulas = Vec::new();
|
let parsed_formulas = Vec::new();
|
||||||
let worksheets = &workbook.worksheets;
|
let worksheets = &workbook.worksheets;
|
||||||
@@ -396,7 +371,6 @@ impl Model {
|
|||||||
locale,
|
locale,
|
||||||
language,
|
language,
|
||||||
tz,
|
tz,
|
||||||
view_id: 0,
|
|
||||||
};
|
};
|
||||||
model.parse_formulas();
|
model.parse_formulas();
|
||||||
Ok(model)
|
Ok(model)
|
||||||
|
|||||||
@@ -76,16 +76,10 @@ fn fn_imconjugate() {
|
|||||||
fn fn_imcos() {
|
fn fn_imcos() {
|
||||||
let mut model = new_empty_model();
|
let mut model = new_empty_model();
|
||||||
model._set("A1", r#"=IMCOS("4+3i")"#);
|
model._set("A1", r#"=IMCOS("4+3i")"#);
|
||||||
// In macos non intel this is "-6.58066304055116+7.58155274274655i"
|
|
||||||
model._set("A2", r#"=COMPLEX(-6.58066304055116, 7.58155274274654)"#);
|
|
||||||
model._set("A3", r#"=IMABS(IMSUB(A1, A2)) < G1"#);
|
|
||||||
|
|
||||||
// small number
|
|
||||||
model._set("G1", "0.0000001");
|
|
||||||
|
|
||||||
model.evaluate();
|
model.evaluate();
|
||||||
|
|
||||||
assert_eq!(model._get_text("A3"), "TRUE");
|
assert_eq!(model._get_text("A1"), "-6.58066304055116+7.58155274274654i");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
mod test_add_delete_sheets;
|
mod test_add_delete_sheets;
|
||||||
mod test_autofill_columns;
|
|
||||||
mod test_autofill_rows;
|
|
||||||
mod test_clear_cells;
|
mod test_clear_cells;
|
||||||
mod test_diff_queue;
|
mod test_diff_queue;
|
||||||
mod test_evaluation;
|
mod test_evaluation;
|
||||||
mod test_general;
|
mod test_general;
|
||||||
mod test_grid_lines;
|
|
||||||
mod test_rename_sheet;
|
mod test_rename_sheet;
|
||||||
mod test_row_column;
|
mod test_row_column;
|
||||||
mod test_styles;
|
mod test_styles;
|
||||||
mod test_to_from_bytes;
|
mod test_to_from_bytes;
|
||||||
mod test_undo_redo;
|
mod test_undo_redo;
|
||||||
mod test_view;
|
|
||||||
|
|||||||
@@ -1,404 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
|
||||||
use crate::expressions::types::Area;
|
|
||||||
use crate::test::util::new_empty_model;
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_tests() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// This is cell A3
|
|
||||||
model.set_user_input(0, 3, 1, "alpha").unwrap();
|
|
||||||
// We autofill from A3 to C3
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 3,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
// B3
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 3, 2),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
// C3
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 3, 3),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn one_cell_right() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
// B1
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 2),
|
|
||||||
Ok("23".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alpha_beta_gamma() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:B3
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap(); // A1
|
|
||||||
model.set_user_input(0, 1, 2, "Bethe").unwrap(); // B1
|
|
||||||
model.set_user_input(0, 1, 3, "Gamow").unwrap(); // C1
|
|
||||||
model.set_user_input(0, 2, 1, "=A1").unwrap(); // A2
|
|
||||||
model.set_user_input(0, 2, 2, "=B1").unwrap(); // B2
|
|
||||||
model.set_user_input(0, 2, 3, "=C1").unwrap(); // C2
|
|
||||||
|
|
||||||
// We autofill from A1:C2 to I2
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 3,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// D1
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 4),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 5),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 6),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 7),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 8),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 9),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 4),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 5),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 6),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 7),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 8),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 9),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 2, 4), Ok("=D1".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn styles() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:C1
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 3, 1, "Gamow").unwrap();
|
|
||||||
|
|
||||||
let b1 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 2,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let c1 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 3,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model.update_range_style(&b1, "font.i", "true").unwrap();
|
|
||||||
model
|
|
||||||
.update_range_style(&c1, "fill.bg_color", "#334455")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 3,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Check that cell E1 has B1 style
|
|
||||||
let style = model.get_cell_style(0, 1, 5).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 1, 6).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 1, 4), Ok("".to_string()));
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 1, 5).unwrap();
|
|
||||||
assert!(!style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 1, 6).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, None);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 4),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 1, 5).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 1, 6).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn left() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A12
|
|
||||||
model.set_user_input(0, 1, 10, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 1, 11, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 1, 12, "Gamow").unwrap();
|
|
||||||
|
|
||||||
// We fill upwards to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 10,
|
|
||||||
width: 3,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 9),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 8),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 7),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn left_4() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A13
|
|
||||||
model.set_user_input(0, 1, 10, "Margaret Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 1, 11, "Geoffrey Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 1, 12, "Willy Fowler").unwrap();
|
|
||||||
model.set_user_input(0, 1, 13, "Fred Hoyle").unwrap();
|
|
||||||
|
|
||||||
// We fill left to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 10,
|
|
||||||
width: 4,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 9),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 8),
|
|
||||||
Ok("Willy Fowler".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 1, 5),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
|
|
||||||
model.set_user_input(0, 1, 4, "Margaret Burbidge").unwrap();
|
|
||||||
|
|
||||||
// Invalid sheet
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 3,
|
|
||||||
row: 1,
|
|
||||||
column: 4,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid worksheet index: '3'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid column
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: -1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '-1'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid column
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: LAST_COLUMN - 1,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '16392'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: LAST_ROW + 1,
|
|
||||||
column: 1,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '1048577'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: LAST_ROW - 2,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '1048583'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 5,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
-10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '-10'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_parameters() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_columns(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 2,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Err("Invalid parameters for autofill".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
|
||||||
use crate::expressions::types::Area;
|
|
||||||
use crate::test::util::new_empty_model;
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_tests() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// This is cell A3
|
|
||||||
model.set_user_input(0, 3, 1, "alpha").unwrap();
|
|
||||||
// We autofill from A3 to A5
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 3,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 1),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("alpha".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn one_cell_down() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 2, 1),
|
|
||||||
Ok("23".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn alpha_beta_gamma() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:B3
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 3, 1, "Gamow").unwrap();
|
|
||||||
model.set_user_input(0, 1, 2, "=A1").unwrap();
|
|
||||||
model.set_user_input(0, 2, 2, "=A2").unwrap();
|
|
||||||
model.set_user_input(0, 3, 2, "=A3").unwrap();
|
|
||||||
// We autofill from A1:B3 to A9
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 2,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 6, 1),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 1),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 1),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 2),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 2),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 6, 2),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 2),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 2),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 2),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 2), Ok("=A4".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn styles() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A1:B3
|
|
||||||
model.set_user_input(0, 1, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 2, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 3, 1, "Gamow").unwrap();
|
|
||||||
|
|
||||||
let a2 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 2,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let a3 = Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 3,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
model.update_range_style(&a2, "font.i", "true").unwrap();
|
|
||||||
model
|
|
||||||
.update_range_style(&a3, "fill.bg_color", "#334455")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
9,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 5, 1).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 6, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_cell_content(0, 4, 1), Ok("".to_string()));
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 5, 1).unwrap();
|
|
||||||
assert!(!style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 6, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, None);
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 4, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
// Check that cell A5 has A2 style
|
|
||||||
let style = model.get_cell_style(0, 5, 1).unwrap();
|
|
||||||
assert!(style.font.i);
|
|
||||||
// A6 would have the style of A3
|
|
||||||
let style = model.get_cell_style(0, 6, 1).unwrap();
|
|
||||||
assert_eq!(style.fill.bg_color, Some("#334455".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn upwards() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A12
|
|
||||||
model.set_user_input(0, 10, 1, "Alpher").unwrap();
|
|
||||||
model.set_user_input(0, 11, 1, "Bethe").unwrap();
|
|
||||||
model.set_user_input(0, 12, 1, "Gamow").unwrap();
|
|
||||||
|
|
||||||
// We fill upwards to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 10,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 3,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 1),
|
|
||||||
Ok("Gamow".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 1),
|
|
||||||
Ok("Bethe".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 7, 1),
|
|
||||||
Ok("Alpher".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn upwards_4() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A13
|
|
||||||
model.set_user_input(0, 10, 1, "Margaret Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 11, 1, "Geoffrey Burbidge").unwrap();
|
|
||||||
model.set_user_input(0, 12, 1, "Willy Fowler").unwrap();
|
|
||||||
model.set_user_input(0, 13, 1, "Fred Hoyle").unwrap();
|
|
||||||
|
|
||||||
// We fill upwards to row 5
|
|
||||||
model
|
|
||||||
.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 10,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 4,
|
|
||||||
},
|
|
||||||
5,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 9, 1),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 8, 1),
|
|
||||||
Ok("Willy Fowler".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.get_formatted_cell_value(0, 5, 1),
|
|
||||||
Ok("Fred Hoyle".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// cells A10:A13
|
|
||||||
model.set_user_input(0, 4, 1, "Margaret Burbidge").unwrap();
|
|
||||||
|
|
||||||
// Invalid sheet
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 3,
|
|
||||||
row: 4,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid worksheet index: '3'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid row
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: -1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '-1'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// invalid row
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: LAST_ROW - 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '1048584'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: LAST_COLUMN + 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '16385'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: LAST_COLUMN - 2,
|
|
||||||
width: 10,
|
|
||||||
height: 1,
|
|
||||||
},
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
Err("Invalid column: '16391'".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 10,
|
|
||||||
},
|
|
||||||
-10,
|
|
||||||
),
|
|
||||||
Err("Invalid row: '-10'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_parameters() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_user_input(0, 1, 1, "23").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
model.auto_fill_rows(
|
|
||||||
&Area {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
width: 1,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
Err("Invalid parameters for autofill".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use crate::test::util::new_empty_model;
|
|
||||||
use crate::UserModel;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic_tests() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.new_sheet();
|
|
||||||
|
|
||||||
// default sheet has show_grid_lines = true
|
|
||||||
assert_eq!(model.get_show_grid_lines(0), Ok(true));
|
|
||||||
|
|
||||||
// default new sheet has show_grid_lines = true
|
|
||||||
assert_eq!(model.get_show_grid_lines(1), Ok(true));
|
|
||||||
|
|
||||||
// wrong sheet number
|
|
||||||
assert_eq!(
|
|
||||||
model.get_show_grid_lines(2),
|
|
||||||
Err("Invalid sheet index".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// we can set it
|
|
||||||
model.set_show_grid_lines(1, false).unwrap();
|
|
||||||
assert_eq!(model.get_show_grid_lines(1), Ok(false));
|
|
||||||
assert_eq!(model.get_show_grid_lines(0), Ok(true));
|
|
||||||
|
|
||||||
model.undo().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model.get_show_grid_lines(1), Ok(true));
|
|
||||||
assert_eq!(model.get_show_grid_lines(0), Ok(true));
|
|
||||||
|
|
||||||
model.redo().unwrap();
|
|
||||||
|
|
||||||
let send_queue = model.flush_send_queue();
|
|
||||||
let mut model2 = UserModel::from_model(new_empty_model());
|
|
||||||
model2.apply_external_diffs(&send_queue).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(model2.get_show_grid_lines(1), Ok(false));
|
|
||||||
assert_eq!(model2.get_show_grid_lines(0), Ok(true));
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
constants::{LAST_COLUMN, LAST_ROW},
|
|
||||||
test::util::new_empty_model,
|
|
||||||
user_model::SelectedView,
|
|
||||||
UserModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn initial_view() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let model = UserModel::from_model(model);
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_the_cell_sets_the_range() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_selected_cell(5, 4).unwrap();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 5,
|
|
||||||
column: 4,
|
|
||||||
range: [5, 4, 5, 4],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_the_range_does_not_set_the_cell() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_selected_range(5, 4, 10, 6).unwrap();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [5, 4, 10, 6],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_new_sheet_and_back() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.new_sheet();
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
model.set_selected_cell(5, 4).unwrap();
|
|
||||||
model.set_selected_sheet(1).unwrap();
|
|
||||||
assert_eq!(model.get_selected_cell(), (1, 1, 1));
|
|
||||||
model.set_selected_sheet(0).unwrap();
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 5, 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_selected_cell_errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_cell(-5, 4),
|
|
||||||
Err("Invalid row: '-5'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_cell(5, -4),
|
|
||||||
Err("Invalid column: '-4'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(-1, 1, 1, 1),
|
|
||||||
Err("Invalid row: '-1'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(1, 0, 1, 1),
|
|
||||||
Err("Invalid column: '0'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(1, 1, LAST_ROW + 1, 1),
|
|
||||||
Err("Invalid row: '1048577'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(1, 1, 1, LAST_COLUMN + 1),
|
|
||||||
Err("Invalid column: '16385'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_selected_cell_errors_wrong_sheet() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// forcefully set a wrong index
|
|
||||||
model.workbook.views.get_mut(&0).unwrap().sheet = 2;
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// It's returning the wrong number
|
|
||||||
assert_eq!(model.get_selected_sheet(), 2);
|
|
||||||
|
|
||||||
// But we can't set the selected cell anymore
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_cell(3, 4),
|
|
||||||
Err("Invalid worksheet index 2".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_selected_range(3, 4, 5, 6),
|
|
||||||
Err("Invalid worksheet index 2".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.set_top_left_visible_cell(3, 4),
|
|
||||||
Err("Invalid worksheet index 2".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
// we can fix it by setting the right cell
|
|
||||||
model.set_selected_sheet(0).unwrap();
|
|
||||||
model.set_selected_cell(3, 4).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_visible_cell() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
model.set_top_left_visible_cell(100, 12).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 100,
|
|
||||||
left_column: 12
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let s = serde_json::to_string(&model.get_selected_view()).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::from_str::<SelectedView>(&s).unwrap(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 100,
|
|
||||||
left_column: 12
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn set_visible_cell_errors() {
|
|
||||||
let model = new_empty_model();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_top_left_visible_cell(-100, 12),
|
|
||||||
Err("Invalid row: '-100'".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
model.set_top_left_visible_cell(100, -12),
|
|
||||||
Err("Invalid column: '-12'".to_string())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors_no_views() {
|
|
||||||
let mut model = new_empty_model();
|
|
||||||
// forcefully remove the view
|
|
||||||
model.workbook.views = HashMap::new();
|
|
||||||
// also in the sheet
|
|
||||||
model.workbook.worksheets[0].views = HashMap::new();
|
|
||||||
let mut model = UserModel::from_model(model);
|
|
||||||
// get methods will return defaults
|
|
||||||
assert_eq!(model.get_selected_sheet(), 0);
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
assert_eq!(
|
|
||||||
model.get_selected_view(),
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// set methods won't complain. but won't work either
|
|
||||||
model.set_selected_sheet(0).unwrap();
|
|
||||||
model.set_selected_cell(5, 6).unwrap();
|
|
||||||
assert_eq!(model.get_selected_cell(), (0, 1, 1));
|
|
||||||
}
|
|
||||||
@@ -27,14 +27,6 @@ pub struct WorkbookSettings {
|
|||||||
pub tz: String,
|
pub tz: String,
|
||||||
pub locale: String,
|
pub locale: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Workbook View tracks of the selected sheet for each view
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
|
||||||
pub struct WorkbookView {
|
|
||||||
/// The index of the currently selected sheet.
|
|
||||||
pub sheet: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An internal representation of an IronCalc Workbook
|
/// An internal representation of an IronCalc Workbook
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
pub struct Workbook {
|
pub struct Workbook {
|
||||||
@@ -46,7 +38,6 @@ pub struct Workbook {
|
|||||||
pub settings: WorkbookSettings,
|
pub settings: WorkbookSettings,
|
||||||
pub metadata: Metadata,
|
pub metadata: Metadata,
|
||||||
pub tables: HashMap<String, Table>,
|
pub tables: HashMap<String, Table>,
|
||||||
pub views: HashMap<u32, WorkbookView>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
/// A defined name. The `sheet_id` is the sheet index in case the name is local
|
||||||
@@ -57,6 +48,9 @@ pub struct DefinedName {
|
|||||||
pub sheet_id: Option<u32>,
|
pub sheet_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move to worksheet.rs make frozen_rows/columns private and u32
|
||||||
|
/// Internal representation of a worksheet Excel object
|
||||||
|
|
||||||
/// * state:
|
/// * state:
|
||||||
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
/// 18.18.68 ST_SheetState (Sheet Visibility Types)
|
||||||
/// hidden, veryHidden, visible
|
/// hidden, veryHidden, visible
|
||||||
@@ -77,23 +71,6 @@ impl Display for SheetState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the state of the worksheet as seen by the user. This includes
|
|
||||||
/// details such as the currently selected cell, the visible range, and the
|
|
||||||
/// position of the viewport.
|
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
|
||||||
pub struct WorksheetView {
|
|
||||||
/// The row index of the currently selected cell.
|
|
||||||
pub row: i32,
|
|
||||||
/// The column index of the currently selected cell.
|
|
||||||
pub column: i32,
|
|
||||||
/// The selected range in the worksheet, specified as [start_row, start_column, end_row, end_column].
|
|
||||||
pub range: [i32; 4],
|
|
||||||
/// The row index of the topmost visible cell in the worksheet view.
|
|
||||||
pub top_row: i32,
|
|
||||||
/// The column index of the leftmost visible cell in the worksheet view.
|
|
||||||
pub left_column: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal representation of a worksheet Excel object
|
/// Internal representation of a worksheet Excel object
|
||||||
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
pub struct Worksheet {
|
pub struct Worksheet {
|
||||||
@@ -110,9 +87,6 @@ pub struct Worksheet {
|
|||||||
pub comments: Vec<Comment>,
|
pub comments: Vec<Comment>,
|
||||||
pub frozen_rows: i32,
|
pub frozen_rows: i32,
|
||||||
pub frozen_columns: i32,
|
pub frozen_columns: i32,
|
||||||
pub views: HashMap<u32, WorksheetView>,
|
|
||||||
/// Whether or not to show the grid lines in the worksheet
|
|
||||||
pub show_grid_lines: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal representation of Excel's sheet_data
|
/// Internal representation of Excel's sheet_data
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
use std::{collections::HashMap, fmt::Debug};
|
use std::{collections::HashMap, fmt::Debug};
|
||||||
|
|
||||||
use bitcode::{Decode, Encode};
|
use bitcode::{Decode, Encode};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants,
|
constants,
|
||||||
@@ -19,17 +18,6 @@ use crate::{
|
|||||||
utils::is_valid_hex_color,
|
utils::is_valid_hex_color,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
|
||||||
pub struct SelectedView {
|
|
||||||
pub sheet: u32,
|
|
||||||
pub row: i32,
|
|
||||||
pub column: i32,
|
|
||||||
pub range: [i32; 4],
|
|
||||||
pub top_row: i32,
|
|
||||||
pub left_column: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Encode, Decode)]
|
#[derive(Clone, Encode, Decode)]
|
||||||
struct RowData {
|
struct RowData {
|
||||||
row: Option<Row>,
|
row: Option<Row>,
|
||||||
@@ -130,11 +118,6 @@ enum Diff {
|
|||||||
old_value: String,
|
old_value: String,
|
||||||
new_value: String,
|
new_value: String,
|
||||||
},
|
},
|
||||||
SetShowGridLines {
|
|
||||||
sheet: u32,
|
|
||||||
old_value: bool,
|
|
||||||
new_value: bool,
|
|
||||||
}, // FIXME: we are missing SetViewDiffs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiffList = Vec<Diff>;
|
type DiffList = Vec<Diff>;
|
||||||
@@ -266,7 +249,7 @@ fn vertical(value: &str) -> Result<VerticalAlignment, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// # A wrapper around [`Model`] for a spreadsheet end user.
|
/// # A wrapper around [`Model`] for a spreadsheet end user.
|
||||||
/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management.
|
/// UserModel is a wrapper around Model with undo/redo history, _diffs_ and automatic evaluation.
|
||||||
///
|
///
|
||||||
/// A diff in this context (or more correctly a _user diff_) is a change created by a user.
|
/// A diff in this context (or more correctly a _user diff_) is a change created by a user.
|
||||||
///
|
///
|
||||||
@@ -877,7 +860,7 @@ impl UserModel {
|
|||||||
style.fill.fg_color = color(value)?;
|
style.fill.fg_color = color(value)?;
|
||||||
}
|
}
|
||||||
"num_fmt" => {
|
"num_fmt" => {
|
||||||
value.clone_into(&mut style.num_fmt);
|
style.num_fmt = value.to_owned();
|
||||||
}
|
}
|
||||||
"border.left" => {
|
"border.left" => {
|
||||||
style.border.left = border(value)?;
|
style.border.left = border(value)?;
|
||||||
@@ -938,7 +921,7 @@ impl UserModel {
|
|||||||
column,
|
column,
|
||||||
old_value: Box::new(old_value),
|
old_value: Box::new(old_value),
|
||||||
new_value: Box::new(style),
|
new_value: Box::new(style),
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.push_diff_list(diff_list);
|
self.push_diff_list(diff_list);
|
||||||
@@ -954,208 +937,6 @@ impl UserModel {
|
|||||||
Ok(self.model.get_style_for_cell(sheet, row, column))
|
Ok(self.model.get_style_for_cell(sheet, row, column))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fills the cells from `source_area` until `to_row`.
|
|
||||||
/// This simulates the user clicking on the cell outline handle and dragging it downwards (or upwards)
|
|
||||||
pub fn auto_fill_rows(&mut self, source_area: &Area, to_row: i32) -> Result<(), String> {
|
|
||||||
let mut diff_list = Vec::new();
|
|
||||||
let sheet = source_area.sheet;
|
|
||||||
let row1 = source_area.row;
|
|
||||||
let column1 = source_area.column;
|
|
||||||
let width1 = source_area.width;
|
|
||||||
let height1 = source_area.height;
|
|
||||||
|
|
||||||
// Check first all parameters are valid
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index: '{sheet}'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_valid_column_number(column1) {
|
|
||||||
return Err(format!("Invalid column: '{column1}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(row1) {
|
|
||||||
return Err(format!("Invalid row: '{row1}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_column_number(column1 + width1 - 1) {
|
|
||||||
return Err(format!("Invalid column: '{}'", column1 + width1 - 1));
|
|
||||||
}
|
|
||||||
if !is_valid_row(row1 + height1 - 1) {
|
|
||||||
return Err(format!("Invalid row: '{}'", row1 + height1 - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_valid_row(to_row) {
|
|
||||||
return Err(format!("Invalid row: '{to_row}'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// anchor_row is the first row that repeats in each case.
|
|
||||||
let anchor_row;
|
|
||||||
let sign;
|
|
||||||
// this is the range of rows we are going to fill
|
|
||||||
let row_range: Vec<i32>;
|
|
||||||
|
|
||||||
if to_row >= row1 + height1 {
|
|
||||||
// we go downwards, we start from `row1 + height1` to `to_row`,
|
|
||||||
anchor_row = row1;
|
|
||||||
sign = 1;
|
|
||||||
row_range = (row1 + height1..to_row + 1).collect();
|
|
||||||
} else if to_row < row1 {
|
|
||||||
// we go upwards, starting from `row1 - `` all the way to `to_row`
|
|
||||||
anchor_row = row1 + height1 - 1;
|
|
||||||
sign = -1;
|
|
||||||
row_range = (to_row..row1).rev().collect();
|
|
||||||
} else {
|
|
||||||
return Err("Invalid parameters for autofill".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
for column in column1..column1 + width1 {
|
|
||||||
let mut index = 0;
|
|
||||||
for row_ref in &row_range {
|
|
||||||
// Save value and style first
|
|
||||||
let row = *row_ref;
|
|
||||||
let old_value = self
|
|
||||||
.model
|
|
||||||
.workbook
|
|
||||||
.worksheet(sheet)?
|
|
||||||
.cell(row, column)
|
|
||||||
.cloned();
|
|
||||||
let old_style = self.model.get_style_for_cell(sheet, row, column);
|
|
||||||
|
|
||||||
// compute the new value and set it
|
|
||||||
let source_row = anchor_row + index;
|
|
||||||
let target_value = self
|
|
||||||
.model
|
|
||||||
.extend_to(sheet, source_row, column, row, column)?;
|
|
||||||
self.model
|
|
||||||
.set_user_input(sheet, row, column, target_value.to_string());
|
|
||||||
|
|
||||||
// Compute the new style and set it
|
|
||||||
let new_style = self.model.get_style_for_cell(sheet, source_row, column);
|
|
||||||
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
|
||||||
|
|
||||||
// Add the diffs
|
|
||||||
diff_list.push(Diff::SetCellStyle {
|
|
||||||
sheet,
|
|
||||||
row,
|
|
||||||
column,
|
|
||||||
old_value: Box::new(old_style),
|
|
||||||
new_value: Box::new(new_style),
|
|
||||||
});
|
|
||||||
diff_list.push(Diff::SetCellValue {
|
|
||||||
sheet,
|
|
||||||
row,
|
|
||||||
column,
|
|
||||||
new_value: target_value.to_string(),
|
|
||||||
old_value: Box::new(old_value),
|
|
||||||
});
|
|
||||||
|
|
||||||
index = (index + sign) % height1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.push_diff_list(diff_list);
|
|
||||||
self.evaluate();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fills the cells from `source_area` until `to_column`.
|
|
||||||
/// This simulates the user clicking on the cell outline handle and dragging it to the right (or to the left)
|
|
||||||
pub fn auto_fill_columns(&mut self, source_area: &Area, to_column: i32) -> Result<(), String> {
|
|
||||||
let mut diff_list = Vec::new();
|
|
||||||
let sheet = source_area.sheet;
|
|
||||||
let row1 = source_area.row;
|
|
||||||
let column1 = source_area.column;
|
|
||||||
let width1 = source_area.width;
|
|
||||||
let height1 = source_area.height;
|
|
||||||
|
|
||||||
// Check first all parameters are valid
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index: '{sheet}'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_valid_column_number(column1) {
|
|
||||||
return Err(format!("Invalid column: '{column1}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(row1) {
|
|
||||||
return Err(format!("Invalid row: '{row1}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_column_number(column1 + width1 - 1) {
|
|
||||||
return Err(format!("Invalid column: '{}'", column1 + width1 - 1));
|
|
||||||
}
|
|
||||||
if !is_valid_row(row1 + height1 - 1) {
|
|
||||||
return Err(format!("Invalid row: '{}'", row1 + height1 - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_valid_row(to_column) {
|
|
||||||
return Err(format!("Invalid row: '{to_column}'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// anchor_column is the first column that repeats in each case.
|
|
||||||
let anchor_column;
|
|
||||||
let sign;
|
|
||||||
// this is the range of columns we are going to fill
|
|
||||||
let column_range: Vec<i32>;
|
|
||||||
|
|
||||||
if to_column >= column1 + width1 {
|
|
||||||
// we go right, we start from `1 + width` to `to_column`,
|
|
||||||
anchor_column = column1;
|
|
||||||
sign = 1;
|
|
||||||
column_range = (column1 + width1..to_column + 1).collect();
|
|
||||||
} else if to_column < column1 {
|
|
||||||
// we go left, starting from `column1 - `` all the way to `to_column`
|
|
||||||
anchor_column = column1 + width1 - 1;
|
|
||||||
sign = -1;
|
|
||||||
column_range = (to_column..column1).rev().collect();
|
|
||||||
} else {
|
|
||||||
return Err("Invalid parameters for autofill".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
for row in row1..row1 + height1 {
|
|
||||||
let mut index = 0;
|
|
||||||
for column_ref in &column_range {
|
|
||||||
let column = *column_ref;
|
|
||||||
// Save value and style first
|
|
||||||
let old_value = self
|
|
||||||
.model
|
|
||||||
.workbook
|
|
||||||
.worksheet(sheet)?
|
|
||||||
.cell(row, column)
|
|
||||||
.cloned();
|
|
||||||
let old_style = self.model.get_style_for_cell(sheet, row, column);
|
|
||||||
|
|
||||||
// compute the new value and set it
|
|
||||||
let source_column = anchor_column + index;
|
|
||||||
let target_value = self
|
|
||||||
.model
|
|
||||||
.extend_to(sheet, row, source_column, row, column)?;
|
|
||||||
self.model
|
|
||||||
.set_user_input(sheet, row, column, target_value.to_string());
|
|
||||||
|
|
||||||
// Compute the new style and set it
|
|
||||||
let new_style = self.model.get_style_for_cell(sheet, row, source_column);
|
|
||||||
self.model.set_cell_style(sheet, row, column, &new_style)?;
|
|
||||||
|
|
||||||
// Add the diffs
|
|
||||||
diff_list.push(Diff::SetCellStyle {
|
|
||||||
sheet,
|
|
||||||
row,
|
|
||||||
column,
|
|
||||||
old_value: Box::new(old_style),
|
|
||||||
new_value: Box::new(new_style),
|
|
||||||
});
|
|
||||||
diff_list.push(Diff::SetCellValue {
|
|
||||||
sheet,
|
|
||||||
row,
|
|
||||||
column,
|
|
||||||
new_value: target_value.to_string(),
|
|
||||||
old_value: Box::new(old_value),
|
|
||||||
});
|
|
||||||
|
|
||||||
index = (index + sign) % width1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.push_diff_list(diff_list);
|
|
||||||
self.evaluate();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns information about the sheets
|
/// Returns information about the sheets
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
@@ -1165,184 +946,6 @@ impl UserModel {
|
|||||||
self.model.get_worksheets_properties()
|
self.model.get_worksheets_properties()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the selected sheet index
|
|
||||||
pub fn get_selected_sheet(&self) -> u32 {
|
|
||||||
if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the selected cell
|
|
||||||
pub fn get_selected_cell(&self) -> (u32, i32, i32) {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
|
||||||
return (sheet, view.row, view.column);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return a safe default
|
|
||||||
(0, 1, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns selected view
|
|
||||||
pub fn get_selected_view(&self) -> SelectedView {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get(&self.model.view_id) {
|
|
||||||
return SelectedView {
|
|
||||||
sheet,
|
|
||||||
row: view.row,
|
|
||||||
column: view.column,
|
|
||||||
range: view.range,
|
|
||||||
top_row: view.top_row,
|
|
||||||
left_column: view.left_column,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// return a safe default
|
|
||||||
SelectedView {
|
|
||||||
sheet: 0,
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
top_row: 1,
|
|
||||||
left_column: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the the selected sheet
|
|
||||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), String> {
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Some(view) = self.model.workbook.views.get_mut(&0) {
|
|
||||||
view.sheet = sheet;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the selected cell
|
|
||||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
if !is_valid_column_number(column) {
|
|
||||||
return Err(format!("Invalid column: '{column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_row(row) {
|
|
||||||
return Err(format!("Invalid row: '{row}'"));
|
|
||||||
}
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
|
||||||
view.row = row;
|
|
||||||
view.column = column;
|
|
||||||
view.range = [row, column, row, column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the selected range
|
|
||||||
pub fn set_selected_range(
|
|
||||||
&mut self,
|
|
||||||
start_row: i32,
|
|
||||||
start_column: i32,
|
|
||||||
end_row: i32,
|
|
||||||
end_column: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_valid_column_number(start_column) {
|
|
||||||
return Err(format!("Invalid column: '{start_column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_column_number(start_row) {
|
|
||||||
return Err(format!("Invalid row: '{start_row}'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_valid_column_number(end_column) {
|
|
||||||
return Err(format!("Invalid column: '{end_column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_column_number(end_row) {
|
|
||||||
return Err(format!("Invalid row: '{end_row}'"));
|
|
||||||
}
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
|
||||||
view.range = [start_row, start_column, end_row, end_column];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the value of the first visible cell
|
|
||||||
pub fn set_top_left_visible_cell(
|
|
||||||
&mut self,
|
|
||||||
top_row: i32,
|
|
||||||
left_column: i32,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
let sheet = if let Some(view) = self.model.workbook.views.get(&self.model.view_id) {
|
|
||||||
view.sheet
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
if !is_valid_column_number(left_column) {
|
|
||||||
return Err(format!("Invalid column: '{left_column}'"));
|
|
||||||
}
|
|
||||||
if !is_valid_column_number(top_row) {
|
|
||||||
return Err(format!("Invalid row: '{top_row}'"));
|
|
||||||
}
|
|
||||||
if self.model.workbook.worksheet(sheet).is_err() {
|
|
||||||
return Err(format!("Invalid worksheet index {}", sheet));
|
|
||||||
}
|
|
||||||
if let Ok(worksheet) = self.model.workbook.worksheet_mut(sheet) {
|
|
||||||
if let Some(view) = worksheet.views.get_mut(&0) {
|
|
||||||
view.top_row = top_row;
|
|
||||||
view.left_column = left_column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the gid lines in the worksheet to visible (`true`) or hidden (`false`)
|
|
||||||
pub fn set_show_grid_lines(&mut self, sheet: u32, show_grid_lines: bool) -> Result<(), String> {
|
|
||||||
let old_value = self.model.workbook.worksheet(sheet)?.show_grid_lines;
|
|
||||||
self.model.set_show_grid_lines(sheet, show_grid_lines)?;
|
|
||||||
|
|
||||||
self.push_diff_list(vec![Diff::SetShowGridLines {
|
|
||||||
sheet,
|
|
||||||
new_value: show_grid_lines,
|
|
||||||
old_value,
|
|
||||||
}]);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true in the grid lines for
|
|
||||||
pub fn get_show_grid_lines(&self, sheet: u32) -> Result<bool, String> {
|
|
||||||
Ok(self.model.workbook.worksheet(sheet)?.show_grid_lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
// **** Private methods ****** //
|
// **** Private methods ****** //
|
||||||
|
|
||||||
fn push_diff_list(&mut self, diff_list: DiffList) {
|
fn push_diff_list(&mut self, diff_list: DiffList) {
|
||||||
@@ -1506,13 +1109,6 @@ impl UserModel {
|
|||||||
} => {
|
} => {
|
||||||
self.model.set_sheet_color(*index, old_value)?;
|
self.model.set_sheet_color(*index, old_value)?;
|
||||||
}
|
}
|
||||||
Diff::SetShowGridLines {
|
|
||||||
sheet,
|
|
||||||
old_value,
|
|
||||||
new_value: _,
|
|
||||||
} => {
|
|
||||||
self.model.set_show_grid_lines(*sheet, *old_value)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if needs_evaluation {
|
if needs_evaluation {
|
||||||
@@ -1633,13 +1229,6 @@ impl UserModel {
|
|||||||
} => {
|
} => {
|
||||||
self.model.set_sheet_color(*index, new_value)?;
|
self.model.set_sheet_color(*index, new_value)?;
|
||||||
}
|
}
|
||||||
Diff::SetShowGridLines {
|
|
||||||
sheet,
|
|
||||||
old_value: _,
|
|
||||||
new_value,
|
|
||||||
} => {
|
|
||||||
self.model.set_show_grid_lines(*sheet, *new_value)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,65 +63,15 @@ style_types = r"""
|
|||||||
getCellStyle(sheet: number, row: number, column: number): CellStyle;
|
getCellStyle(sheet: number, row: number, column: number): CellStyle;
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
view = r"""
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
getSelectedView(): any;
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
view_types = r"""
|
|
||||||
* @returns {CellStyle}
|
|
||||||
*/
|
|
||||||
getSelectedView(): SelectedView;
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
autofill_rows = r"""
|
|
||||||
/**
|
|
||||||
* @param {any} source_area
|
|
||||||
* @param {number} to_row
|
|
||||||
*/
|
|
||||||
autoFillRows(source_area: any, to_row: number): void;
|
|
||||||
"""
|
|
||||||
|
|
||||||
autofill_rows_types = r"""
|
|
||||||
/**
|
|
||||||
* @param {Area} source_area
|
|
||||||
* @param {number} to_row
|
|
||||||
*/
|
|
||||||
autoFillRows(source_area: Area, to_row: number): void;
|
|
||||||
"""
|
|
||||||
|
|
||||||
autofill_columns = r"""
|
|
||||||
/**
|
|
||||||
* @param {any} source_area
|
|
||||||
* @param {number} to_column
|
|
||||||
*/
|
|
||||||
autoFillColumns(source_area: any, to_column: number): void;
|
|
||||||
"""
|
|
||||||
|
|
||||||
autofill_columns_types = r"""
|
|
||||||
/**
|
|
||||||
* @param {Area} source_area
|
|
||||||
* @param {number} to_column
|
|
||||||
*/
|
|
||||||
autoFillColumns(source_area: Area, to_column: number): void;
|
|
||||||
"""
|
|
||||||
|
|
||||||
def fix_types(text):
|
def fix_types(text):
|
||||||
text = text.replace(get_tokens_str, get_tokens_str_types)
|
text = text.replace(get_tokens_str, get_tokens_str_types)
|
||||||
text = text.replace(update_style_str, update_style_str_types)
|
text = text.replace(update_style_str, update_style_str_types)
|
||||||
text = text.replace(properties, properties_types)
|
text = text.replace(properties, properties_types)
|
||||||
text = text.replace(style, style_types)
|
text = text.replace(style, style_types)
|
||||||
text = text.replace(view, view_types)
|
|
||||||
text = text.replace(autofill_rows, autofill_rows_types)
|
|
||||||
text = text.replace(autofill_columns, autofill_columns_types)
|
|
||||||
with open("types.ts") as f:
|
with open("types.ts") as f:
|
||||||
types_str = f.read()
|
types_str = f.read()
|
||||||
header_types = "{}\n\n{}".format(header, types_str)
|
header_types = "{}\n\n{}".format(header, types_str)
|
||||||
text = text.replace(header, header_types)
|
text = text.replace(header, header_types)
|
||||||
if text.find("any") != -1:
|
|
||||||
print("There are 'unfixed' types. Please check.")
|
|
||||||
exit(1)
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -286,94 +286,4 @@ impl Model {
|
|||||||
pub fn get_worksheets_properties(&self) -> JsValue {
|
pub fn get_worksheets_properties(&self) -> JsValue {
|
||||||
serde_wasm_bindgen::to_value(&self.model.get_worksheets_properties()).unwrap()
|
serde_wasm_bindgen::to_value(&self.model.get_worksheets_properties()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "getSelectedSheet")]
|
|
||||||
pub fn get_selected_sheet(&self) -> u32 {
|
|
||||||
self.model.get_selected_sheet()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "getSelectedCell")]
|
|
||||||
pub fn get_selected_cell(&self) -> Vec<i32> {
|
|
||||||
let (sheet, row, column) = self.model.get_selected_cell();
|
|
||||||
vec![sheet as i32, row, column]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "getSelectedView")]
|
|
||||||
pub fn get_selected_view(&self) -> JsValue {
|
|
||||||
serde_wasm_bindgen::to_value(&self.model.get_selected_view()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "setSelectedSheet")]
|
|
||||||
pub fn set_selected_sheet(&mut self, sheet: u32) -> Result<(), JsError> {
|
|
||||||
self.model.set_selected_sheet(sheet).map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "setSelectedCell")]
|
|
||||||
pub fn set_selected_cell(&mut self, row: i32, column: i32) -> Result<(), JsError> {
|
|
||||||
self.model
|
|
||||||
.set_selected_cell(row, column)
|
|
||||||
.map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "setSelectedRange")]
|
|
||||||
pub fn set_selected_range(
|
|
||||||
&mut self,
|
|
||||||
start_row: i32,
|
|
||||||
start_column: i32,
|
|
||||||
end_row: i32,
|
|
||||||
end_column: i32,
|
|
||||||
) -> Result<(), JsError> {
|
|
||||||
self.model
|
|
||||||
.set_selected_range(start_row, start_column, end_row, end_column)
|
|
||||||
.map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "setTopLeftVisibleCell")]
|
|
||||||
pub fn set_top_left_visible_cell(
|
|
||||||
&mut self,
|
|
||||||
top_row: i32,
|
|
||||||
top_column: i32,
|
|
||||||
) -> Result<(), JsError> {
|
|
||||||
self.model
|
|
||||||
.set_top_left_visible_cell(top_row, top_column)
|
|
||||||
.map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "setShowGridLines")]
|
|
||||||
pub fn set_show_grid_lines(
|
|
||||||
&mut self,
|
|
||||||
sheet: u32,
|
|
||||||
show_grid_lines: bool,
|
|
||||||
) -> Result<(), JsError> {
|
|
||||||
self.model
|
|
||||||
.set_show_grid_lines(sheet, show_grid_lines)
|
|
||||||
.map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "getShowGridLines")]
|
|
||||||
pub fn get_show_grid_lines(&mut self, sheet: u32) -> Result<bool, JsError> {
|
|
||||||
self.model.get_show_grid_lines(sheet).map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "autoFillRows")]
|
|
||||||
pub fn auto_fill_rows(&mut self, source_area: JsValue, to_row: i32) -> Result<(), JsError> {
|
|
||||||
let area: Area =
|
|
||||||
serde_wasm_bindgen::from_value(source_area).map_err(|e| to_js_error(e.to_string()))?;
|
|
||||||
self.model
|
|
||||||
.auto_fill_rows(&area, to_row)
|
|
||||||
.map_err(to_js_error)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "autoFillColumns")]
|
|
||||||
pub fn auto_fill_columns(
|
|
||||||
&mut self,
|
|
||||||
source_area: JsValue,
|
|
||||||
to_column: i32,
|
|
||||||
) -> Result<(), JsError> {
|
|
||||||
let area: Area =
|
|
||||||
serde_wasm_bindgen::from_value(source_area).map_err(|e| to_js_error(e.to_string()))?;
|
|
||||||
self.model
|
|
||||||
.auto_fill_columns(&area, to_column)
|
|
||||||
.map_err(to_js_error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,14 +119,5 @@ test("floating column numbers get truncated", () => {
|
|||||||
assert.strictEqual(model.getRowHeight(0, 5), 100.5);
|
assert.strictEqual(model.getRowHeight(0, 5), 100.5);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("autofill", () => {
|
|
||||||
const model = new Model('en', 'UTC');
|
|
||||||
model.setUserInput(0, 1, 1, "23");
|
|
||||||
model.autoFillRows({sheet: 0, row: 1, column: 1, width: 1, height: 1}, 2);
|
|
||||||
|
|
||||||
const result = model.getFormattedCellValue(0, 2, 1);
|
|
||||||
assert.strictEqual(result, "23");
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,110 +35,6 @@ enum CursorMode {
|
|||||||
Popup,
|
Popup,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SelectedRange {
|
|
||||||
sheet: u32,
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
min_row: i32,
|
|
||||||
min_column: i32,
|
|
||||||
max_row: i32,
|
|
||||||
max_column: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SheetState {
|
|
||||||
row: i32,
|
|
||||||
column: i32,
|
|
||||||
min_row: i32,
|
|
||||||
min_column: i32,
|
|
||||||
max_row: i32,
|
|
||||||
max_column: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ModelState {
|
|
||||||
selected_sheet: u32,
|
|
||||||
sheet_states: Vec<SheetState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModelState {
|
|
||||||
pub fn new(sheet_count: usize) -> ModelState {
|
|
||||||
let mut sheet_states = vec![];
|
|
||||||
for _ in 0..sheet_count {
|
|
||||||
sheet_states.push(SheetState {
|
|
||||||
row: 1,
|
|
||||||
column: 1,
|
|
||||||
min_row: 1,
|
|
||||||
min_column: 1,
|
|
||||||
max_row: 1,
|
|
||||||
max_column: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ModelState {
|
|
||||||
selected_sheet: 0,
|
|
||||||
sheet_states,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_selected_range(&self) -> SelectedRange {
|
|
||||||
let sheet = self.selected_sheet;
|
|
||||||
let sheet_state = self.sheet_states.get(sheet as usize).unwrap();
|
|
||||||
|
|
||||||
SelectedRange {
|
|
||||||
sheet,
|
|
||||||
row: sheet_state.row,
|
|
||||||
column: sheet_state.column,
|
|
||||||
min_column: sheet_state.min_column,
|
|
||||||
min_row: sheet_state.min_row,
|
|
||||||
max_column: sheet_state.max_column,
|
|
||||||
max_row: sheet_state.max_row,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_selected_sheet(&mut self, selected_sheet: u32) {
|
|
||||||
self.selected_sheet = selected_sheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_selected_sheet(&self) -> u32 {
|
|
||||||
self.selected_sheet
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_up(&mut self) {
|
|
||||||
let sheet = self.selected_sheet;
|
|
||||||
let mut sheet_state = &mut self.sheet_states.get(sheet as usize).unwrap();
|
|
||||||
sheet_state.column -= 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_down(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_left(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_right(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_shift_up(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_shift_down(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_shift_left(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_shift_right(&mut self) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
|
||||||
@@ -155,10 +51,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Model::new_empty(file_name, "en", "UTC").unwrap()
|
Model::new_empty(file_name, "en", "UTC").unwrap()
|
||||||
};
|
};
|
||||||
let mut user_model = UserModel::from_model(model);
|
let mut user_model = UserModel::from_model(model);
|
||||||
let mut state = ModelState::new(user_model.get_worksheets_properties().len());
|
let mut selected_sheet = 0;
|
||||||
// let mut selected_sheet = 0;
|
let mut selected_row_index = 1;
|
||||||
// let mut selected_row_index = 1;
|
let mut selected_column_index = 1;
|
||||||
// let mut selected_column_index = 1;
|
|
||||||
let mut minimum_row_index = 1;
|
let mut minimum_row_index = 1;
|
||||||
let mut minimum_column_index = 1;
|
let mut minimum_column_index = 1;
|
||||||
let sheet_list_width = 20;
|
let sheet_list_width = 20;
|
||||||
@@ -226,7 +121,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
(0..sheet_properties.len()).for_each(|sheet_index| {
|
(0..sheet_properties.len()).for_each(|sheet_index| {
|
||||||
let sheet_name = &sheet_properties[sheet_index].name;
|
let sheet_name = &sheet_properties[sheet_index].name;
|
||||||
let style = if sheet_index == state.get_selected_sheet() {
|
let style = if sheet_index == selected_sheet {
|
||||||
selected_sheet_style
|
selected_sheet_style
|
||||||
} else {
|
} else {
|
||||||
non_selected_sheet_style
|
non_selected_sheet_style
|
||||||
|
|||||||
@@ -68,10 +68,6 @@ pub fn save_to_xlsx(model: &Model, file_name: &str) -> Result<(), XlsxError> {
|
|||||||
|
|
||||||
pub fn save_xlsx_to_writer<W: Write + Seek>(model: &Model, writer: W) -> Result<W, XlsxError> {
|
pub fn save_xlsx_to_writer<W: Write + Seek>(model: &Model, writer: W) -> Result<W, XlsxError> {
|
||||||
let workbook = &model.workbook;
|
let workbook = &model.workbook;
|
||||||
let selected_sheet = match workbook.views.get(&0) {
|
|
||||||
Some(view) => view.sheet,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
let mut zip = zip::ZipWriter::new(writer);
|
let mut zip = zip::ZipWriter::new(writer);
|
||||||
|
|
||||||
let options = zip::write::FileOptions::default();
|
let options = zip::write::FileOptions::default();
|
||||||
@@ -98,7 +94,7 @@ pub fn save_xlsx_to_writer<W: Write + Seek>(model: &Model, writer: W) -> Result<
|
|||||||
zip.start_file("xl/styles.xml", options)?;
|
zip.start_file("xl/styles.xml", options)?;
|
||||||
zip.write_all(styles::get_styles_xml(workbook).as_bytes())?;
|
zip.write_all(styles::get_styles_xml(workbook).as_bytes())?;
|
||||||
zip.start_file("xl/workbook.xml", options)?;
|
zip.start_file("xl/workbook.xml", options)?;
|
||||||
zip.write_all(workbook::get_workbook_xml(workbook, selected_sheet).as_bytes())?;
|
zip.write_all(workbook::get_workbook_xml(workbook).as_bytes())?;
|
||||||
|
|
||||||
zip.add_directory("xl/_rels", options)?;
|
zip.add_directory("xl/_rels", options)?;
|
||||||
zip.start_file("xl/_rels/workbook.xml.rels", options)?;
|
zip.start_file("xl/_rels/workbook.xml.rels", options)?;
|
||||||
@@ -118,13 +114,11 @@ pub fn save_xlsx_to_writer<W: Write + Seek>(model: &Model, writer: W) -> Result<
|
|||||||
let min_row = dimension.min_row;
|
let min_row = dimension.min_row;
|
||||||
let max_row = dimension.max_row;
|
let max_row = dimension.max_row;
|
||||||
let sheet_dimension_str = &format!("{column_min_str}{min_row}:{column_max_str}{max_row}");
|
let sheet_dimension_str = &format!("{column_min_str}{min_row}:{column_max_str}{max_row}");
|
||||||
let is_sheet_selected = selected_sheet as usize == sheet_index;
|
|
||||||
zip.write_all(
|
zip.write_all(
|
||||||
worksheets::get_worksheet_xml(
|
worksheets::get_worksheet_xml(
|
||||||
worksheet,
|
worksheet,
|
||||||
&model.parsed_formulas[sheet_index],
|
&model.parsed_formulas[sheet_index],
|
||||||
sheet_dimension_str,
|
sheet_dimension_str,
|
||||||
is_sheet_selected,
|
|
||||||
)
|
)
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ use ironcalc_base::types::{SheetState, Workbook};
|
|||||||
use super::escape::escape_xml;
|
use super::escape::escape_xml;
|
||||||
use super::xml_constants::XML_DECLARATION;
|
use super::xml_constants::XML_DECLARATION;
|
||||||
|
|
||||||
pub(crate) fn get_workbook_xml(workbook: &Workbook, selected_sheet: u32) -> String {
|
pub(crate) fn get_workbook_xml(workbook: &Workbook) -> String {
|
||||||
// sheets
|
// sheets
|
||||||
// <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
|
// <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
|
||||||
let mut sheets_str: Vec<String> = vec![];
|
let mut sheets_str: Vec<String> = vec![];
|
||||||
@@ -80,9 +80,6 @@ pub(crate) fn get_workbook_xml(workbook: &Workbook, selected_sheet: u32) -> Stri
|
|||||||
let defined_names = defined_names_str.join("");
|
let defined_names = defined_names_str.join("");
|
||||||
format!("{XML_DECLARATION}\n\
|
format!("{XML_DECLARATION}\n\
|
||||||
<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\
|
<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\
|
||||||
<bookViews>
|
|
||||||
<workbookView activeTab=\"{selected_sheet}\"/>\
|
|
||||||
</bookViews>
|
|
||||||
<sheets>\
|
<sheets>\
|
||||||
{sheets}\
|
{sheets}\
|
||||||
</sheets>\
|
</sheets>\
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ pub(crate) fn get_worksheet_xml(
|
|||||||
worksheet: &Worksheet,
|
worksheet: &Worksheet,
|
||||||
parsed_formulas: &[Node],
|
parsed_formulas: &[Node],
|
||||||
dimension: &str,
|
dimension: &str,
|
||||||
is_sheet_selected: bool,
|
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut sheet_data_str: Vec<String> = vec![];
|
let mut sheet_data_str: Vec<String> = vec![];
|
||||||
let mut cols_str: Vec<String> = vec![];
|
let mut cols_str: Vec<String> = vec![];
|
||||||
@@ -248,38 +247,6 @@ pub(crate) fn get_worksheet_xml(
|
|||||||
format!("<cols>{cols}</cols>")
|
format!("<cols>{cols}</cols>")
|
||||||
};
|
};
|
||||||
|
|
||||||
let tab_selected = if is_sheet_selected {
|
|
||||||
" tabSelected=\"1\""
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let show_grid_lines = if !worksheet.show_grid_lines {
|
|
||||||
" showGridLines=\"0\""
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut active_cell = "A1".to_string();
|
|
||||||
let mut sqref = "A1".to_string();
|
|
||||||
|
|
||||||
let views = &worksheet.views;
|
|
||||||
if let Some(view) = views.get(&0) {
|
|
||||||
let range = view.range;
|
|
||||||
let row = view.row;
|
|
||||||
let column = view.column;
|
|
||||||
let column_name = number_to_column(column).unwrap_or("A".to_string());
|
|
||||||
active_cell = format!("{column_name}{row}");
|
|
||||||
|
|
||||||
let column_start = number_to_column(range[1]).unwrap_or("A".to_string());
|
|
||||||
let column_end = number_to_column(range[3]).unwrap_or("A".to_string());
|
|
||||||
if range[0] == range[2] && range[1] == range[3] {
|
|
||||||
sqref = format!("{column_start}{}", range[0]);
|
|
||||||
} else {
|
|
||||||
sqref = format!("{}{}:{}{}", column_start, range[0], column_end, range[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"{XML_DECLARATION}
|
"{XML_DECLARATION}
|
||||||
<worksheet \
|
<worksheet \
|
||||||
@@ -287,8 +254,8 @@ xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" \
|
|||||||
xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\
|
xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\
|
||||||
<dimension ref=\"{dimension}\"/>\
|
<dimension ref=\"{dimension}\"/>\
|
||||||
<sheetViews>\
|
<sheetViews>\
|
||||||
<sheetView workbookViewId=\"0\"{show_grid_lines}{tab_selected}>\
|
<sheetView workbookViewId=\"0\">\
|
||||||
<selection activeCell=\"{active_cell}\" sqref=\"{sqref}\"/>\
|
<selection activeCell=\"A1\" sqref=\"A1\"/>\
|
||||||
</sheetView>\
|
</sheetView>\
|
||||||
</sheetViews>\
|
</sheetViews>\
|
||||||
{cols}\
|
{cols}\
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use std::{
|
|||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
|
|
||||||
use ironcalc_base::{
|
use ironcalc_base::{
|
||||||
types::{Metadata, Workbook, WorkbookSettings, WorkbookView},
|
types::{Metadata, Workbook, WorkbookSettings},
|
||||||
Model,
|
Model,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
|||||||
let workbook = load_workbook(&mut archive)?;
|
let workbook = load_workbook(&mut archive)?;
|
||||||
let rels = load_relationships(&mut archive)?;
|
let rels = load_relationships(&mut archive)?;
|
||||||
let mut tables = HashMap::new();
|
let mut tables = HashMap::new();
|
||||||
let (worksheets, selected_sheet) = load_sheets(
|
let worksheets = load_sheets(
|
||||||
&mut archive,
|
&mut archive,
|
||||||
&rels,
|
&rels,
|
||||||
&workbook,
|
&workbook,
|
||||||
@@ -88,13 +88,6 @@ fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut views = HashMap::new();
|
|
||||||
views.insert(
|
|
||||||
0,
|
|
||||||
WorkbookView {
|
|
||||||
sheet: selected_sheet,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(Workbook {
|
Ok(Workbook {
|
||||||
shared_strings,
|
shared_strings,
|
||||||
defined_names: workbook.defined_names,
|
defined_names: workbook.defined_names,
|
||||||
@@ -107,7 +100,6 @@ fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
|||||||
},
|
},
|
||||||
metadata,
|
metadata,
|
||||||
tables,
|
tables,
|
||||||
views,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ use ironcalc_base::{
|
|||||||
parser::{stringify::to_rc_format, Parser},
|
parser::{stringify::to_rc_format, Parser},
|
||||||
token::{get_error_by_english_name, Error},
|
token::{get_error_by_english_name, Error},
|
||||||
types::CellReferenceRC,
|
types::CellReferenceRC,
|
||||||
utils::{column_to_number, parse_reference_a1},
|
utils::column_to_number,
|
||||||
},
|
|
||||||
types::{
|
|
||||||
Cell, Col, Comment, DefinedName, Row, SheetData, SheetState, Table, Worksheet,
|
|
||||||
WorksheetView,
|
|
||||||
},
|
},
|
||||||
|
types::{Cell, Col, Comment, DefinedName, Row, SheetData, SheetState, Table, Worksheet},
|
||||||
};
|
};
|
||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -50,50 +47,6 @@ fn get_column_from_ref(s: &str) -> String {
|
|||||||
column.into_iter().collect()
|
column.into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_cell_reference(cell: &str) -> Result<(i32, i32), String> {
|
|
||||||
if let Some(r) = parse_reference_a1(cell) {
|
|
||||||
Ok((r.row, r.column))
|
|
||||||
} else {
|
|
||||||
Err(format!("Invalid cell reference: '{}'", cell))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_range(range: &str) -> Result<(i32, i32, i32, i32), String> {
|
|
||||||
let parts: Vec<&str> = range.split(':').collect();
|
|
||||||
if parts.len() == 1 {
|
|
||||||
if let Some(r) = parse_reference_a1(parts[0]) {
|
|
||||||
Ok((r.row, r.column, r.row, r.column))
|
|
||||||
} else {
|
|
||||||
Err(format!("Invalid range: '{}'", range))
|
|
||||||
}
|
|
||||||
} else if parts.len() == 2 {
|
|
||||||
match (parse_reference_a1(parts[0]), parse_reference_a1(parts[1])) {
|
|
||||||
(Some(left), Some(right)) => {
|
|
||||||
return Ok((left.row, left.column, right.row, right.column));
|
|
||||||
}
|
|
||||||
_ => return Err(format!("Invalid range: '{}'", range)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(format!("Invalid range: '{}'", range));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::import::worksheets::parse_range;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_range() {
|
|
||||||
assert!(parse_range("3Aw").is_err());
|
|
||||||
assert_eq!(parse_range("A1"), Ok((1, 1, 1, 1)));
|
|
||||||
assert_eq!(parse_range("B5:C6"), Ok((5, 2, 6, 3)));
|
|
||||||
assert!(parse_range("A1:A2:A3").is_err());
|
|
||||||
assert!(parse_range("A1:34").is_err());
|
|
||||||
assert!(parse_range("A").is_err());
|
|
||||||
assert!(parse_range("12").is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_dimension(ws: Node) -> String {
|
fn load_dimension(ws: Node) -> String {
|
||||||
// <dimension ref="A1:O18"/>
|
// <dimension ref="A1:O18"/>
|
||||||
let application_nodes = ws
|
let application_nodes = ws
|
||||||
@@ -537,31 +490,7 @@ fn load_sheet_rels<R: Read + std::io::Seek>(
|
|||||||
Ok(comments)
|
Ok(comments)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SheetView {
|
fn get_frozen_rows_and_columns(ws: Node) -> (i32, i32) {
|
||||||
is_selected: bool,
|
|
||||||
selected_row: i32,
|
|
||||||
selected_column: i32,
|
|
||||||
frozen_columns: i32,
|
|
||||||
frozen_rows: i32,
|
|
||||||
range: [i32; 4],
|
|
||||||
show_grid_lines: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SheetView {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
is_selected: false,
|
|
||||||
selected_row: 1,
|
|
||||||
selected_column: 1,
|
|
||||||
frozen_rows: 0,
|
|
||||||
frozen_columns: 0,
|
|
||||||
range: [1, 1, 1, 1],
|
|
||||||
show_grid_lines: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sheet_view(ws: Node) -> SheetView {
|
|
||||||
// <sheetViews>
|
// <sheetViews>
|
||||||
// <sheetView workbookViewId="0">
|
// <sheetView workbookViewId="0">
|
||||||
// <selection activeCell="E10" sqref="E10"/>
|
// <selection activeCell="E10" sqref="E10"/>
|
||||||
@@ -582,20 +511,19 @@ fn get_sheet_view(ws: Node) -> SheetView {
|
|||||||
// bottomLeft, bottomRight, topLeft, topRight
|
// bottomLeft, bottomRight, topLeft, topRight
|
||||||
|
|
||||||
// NB: bottomLeft is used when only rows are frozen, etc
|
// NB: bottomLeft is used when only rows are frozen, etc
|
||||||
// IronCalc ignores all those.
|
// Calc ignores all those.
|
||||||
|
|
||||||
let mut frozen_rows = 0;
|
let mut frozen_rows = 0;
|
||||||
let mut frozen_columns = 0;
|
let mut frozen_columns = 0;
|
||||||
|
|
||||||
// In IronCalc there can only be one sheetView
|
// In Calc there can only be one sheetView
|
||||||
let sheet_views = ws
|
let sheet_views = ws
|
||||||
.children()
|
.children()
|
||||||
.filter(|n| n.has_tag_name("sheetViews"))
|
.filter(|n| n.has_tag_name("sheetViews"))
|
||||||
.collect::<Vec<Node>>();
|
.collect::<Vec<Node>>();
|
||||||
|
|
||||||
// We are only expecting one `sheetViews` element. Otherwise return a default
|
|
||||||
if sheet_views.len() != 1 {
|
if sheet_views.len() != 1 {
|
||||||
return SheetView::default();
|
return (0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sheet_view = sheet_views[0]
|
let sheet_view = sheet_views[0]
|
||||||
@@ -603,66 +531,25 @@ fn get_sheet_view(ws: Node) -> SheetView {
|
|||||||
.filter(|n| n.has_tag_name("sheetView"))
|
.filter(|n| n.has_tag_name("sheetView"))
|
||||||
.collect::<Vec<Node>>();
|
.collect::<Vec<Node>>();
|
||||||
|
|
||||||
// We are only expecting one `sheetView` element. Otherwise return a default
|
|
||||||
if sheet_view.len() != 1 {
|
if sheet_view.len() != 1 {
|
||||||
return SheetView::default();
|
return (0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sheet_view = sheet_view[0];
|
let pane = sheet_view[0]
|
||||||
let is_selected = sheet_view.attribute("tabSelected").unwrap_or("0") == "1";
|
|
||||||
let show_grid_lines = sheet_view.attribute("showGridLines").unwrap_or("1") == "1";
|
|
||||||
|
|
||||||
let pane = sheet_view
|
|
||||||
.children()
|
.children()
|
||||||
.filter(|n| n.has_tag_name("pane"))
|
.filter(|n| n.has_tag_name("pane"))
|
||||||
.collect::<Vec<Node>>();
|
.collect::<Vec<Node>>();
|
||||||
|
|
||||||
// 18.18.53 ST_PaneState (Pane State)
|
// 18.18.53 ST_PaneState (Pane State)
|
||||||
// frozen, frozenSplit, split
|
// frozen, frozenSplit, split
|
||||||
if pane.len() == 1 {
|
if pane.len() == 1 && pane[0].attribute("state").unwrap_or("split") == "frozen" {
|
||||||
if let Some("frozen") = pane[0].attribute("state") {
|
// TODO: Should we assert that topLeft is consistent?
|
||||||
// TODO: Should we assert that topLeft is consistent?
|
// let top_left_cell = pane[0].attribute("topLeftCell").unwrap_or("A1").to_string();
|
||||||
// let top_left_cell = pane[0].attribute("topLeftCell").unwrap_or("A1").to_string();
|
|
||||||
|
|
||||||
frozen_columns = get_number(pane[0], "xSplit");
|
frozen_columns = get_number(pane[0], "xSplit");
|
||||||
frozen_rows = get_number(pane[0], "ySplit");
|
frozen_rows = get_number(pane[0], "ySplit");
|
||||||
}
|
|
||||||
}
|
|
||||||
let selections = sheet_view
|
|
||||||
.children()
|
|
||||||
.filter(|n| n.has_tag_name("selection"))
|
|
||||||
.collect::<Vec<Node>>();
|
|
||||||
|
|
||||||
if let Some(selection) = selections.last() {
|
|
||||||
let active_cell = match selection.attribute("activeCell").map(parse_cell_reference) {
|
|
||||||
Some(Ok(s)) => Some(s),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let sqref = match selection.attribute("sqref").map(parse_range) {
|
|
||||||
Some(Ok(s)) => Some(s),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (selected_row, selected_column, row1, column1, row2, column2) =
|
|
||||||
match (active_cell, sqref) {
|
|
||||||
(Some(cell), Some(range)) => (cell.0, cell.1, range.0, range.1, range.2, range.3),
|
|
||||||
(Some(cell), None) => (cell.0, cell.1, cell.0, cell.1, cell.0, cell.1),
|
|
||||||
(None, Some(range)) => (range.0, range.1, range.0, range.1, range.2, range.3),
|
|
||||||
_ => (1, 1, 1, 1, 1, 1),
|
|
||||||
};
|
|
||||||
|
|
||||||
SheetView {
|
|
||||||
frozen_rows,
|
|
||||||
frozen_columns,
|
|
||||||
selected_row,
|
|
||||||
selected_column,
|
|
||||||
is_selected,
|
|
||||||
show_grid_lines,
|
|
||||||
range: [row1, column1, row2, column2],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
SheetView::default()
|
|
||||||
}
|
}
|
||||||
|
(frozen_rows, frozen_columns)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct SheetSettings {
|
pub(super) struct SheetSettings {
|
||||||
@@ -679,7 +566,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
|||||||
worksheets: &[String],
|
worksheets: &[String],
|
||||||
tables: &HashMap<String, Table>,
|
tables: &HashMap<String, Table>,
|
||||||
shared_strings: &mut Vec<String>,
|
shared_strings: &mut Vec<String>,
|
||||||
) -> Result<(Worksheet, bool), XlsxError> {
|
) -> Result<Worksheet, XlsxError> {
|
||||||
let sheet_name = &settings.name;
|
let sheet_name = &settings.name;
|
||||||
let sheet_id = settings.id;
|
let sheet_id = settings.id;
|
||||||
let state = &settings.state;
|
let state = &settings.state;
|
||||||
@@ -696,7 +583,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
|||||||
|
|
||||||
let dimension = load_dimension(ws);
|
let dimension = load_dimension(ws);
|
||||||
|
|
||||||
let sheet_view = get_sheet_view(ws);
|
let (frozen_rows, frozen_columns) = get_frozen_rows_and_columns(ws);
|
||||||
|
|
||||||
let cols = load_columns(ws)?;
|
let cols = load_columns(ws)?;
|
||||||
let color = load_sheet_color(ws)?;
|
let color = load_sheet_color(ws)?;
|
||||||
@@ -957,38 +844,21 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
|||||||
// pageSetup
|
// pageSetup
|
||||||
// <pageSetup orientation="portrait" r:id="rId1"/>
|
// <pageSetup orientation="portrait" r:id="rId1"/>
|
||||||
|
|
||||||
let mut views = HashMap::new();
|
Ok(Worksheet {
|
||||||
views.insert(
|
dimension,
|
||||||
0,
|
cols,
|
||||||
WorksheetView {
|
rows,
|
||||||
row: sheet_view.selected_row,
|
shared_formulas,
|
||||||
column: sheet_view.selected_column,
|
sheet_data,
|
||||||
range: sheet_view.range,
|
name: sheet_name.to_string(),
|
||||||
top_row: 1,
|
sheet_id,
|
||||||
left_column: 1,
|
state: state.to_owned(),
|
||||||
},
|
color,
|
||||||
);
|
merge_cells,
|
||||||
|
comments: settings.comments,
|
||||||
Ok((
|
frozen_rows,
|
||||||
Worksheet {
|
frozen_columns,
|
||||||
dimension,
|
})
|
||||||
cols,
|
|
||||||
rows,
|
|
||||||
shared_formulas,
|
|
||||||
sheet_data,
|
|
||||||
name: sheet_name.to_string(),
|
|
||||||
sheet_id,
|
|
||||||
state: state.to_owned(),
|
|
||||||
color,
|
|
||||||
merge_cells,
|
|
||||||
comments: settings.comments,
|
|
||||||
frozen_rows: sheet_view.frozen_rows,
|
|
||||||
frozen_columns: sheet_view.frozen_columns,
|
|
||||||
show_grid_lines: sheet_view.show_grid_lines,
|
|
||||||
views,
|
|
||||||
},
|
|
||||||
sheet_view.is_selected,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
||||||
@@ -997,7 +867,7 @@ pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
|||||||
workbook: &WorkbookXML,
|
workbook: &WorkbookXML,
|
||||||
tables: &mut HashMap<String, Table>,
|
tables: &mut HashMap<String, Table>,
|
||||||
shared_strings: &mut Vec<String>,
|
shared_strings: &mut Vec<String>,
|
||||||
) -> Result<(Vec<Worksheet>, u32), XlsxError> {
|
) -> Result<Vec<Worksheet>, XlsxError> {
|
||||||
// load comments and tables
|
// load comments and tables
|
||||||
let mut comments = HashMap::new();
|
let mut comments = HashMap::new();
|
||||||
for sheet in &workbook.worksheets {
|
for sheet in &workbook.worksheets {
|
||||||
@@ -1019,8 +889,6 @@ pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
|||||||
// load all sheets
|
// load all sheets
|
||||||
let worksheets: &Vec<String> = &workbook.worksheets.iter().map(|s| s.name.clone()).collect();
|
let worksheets: &Vec<String> = &workbook.worksheets.iter().map(|s| s.name.clone()).collect();
|
||||||
let mut sheets = Vec::new();
|
let mut sheets = Vec::new();
|
||||||
let mut selected_sheet = 0;
|
|
||||||
let mut sheet_index = 0;
|
|
||||||
for sheet in &workbook.worksheets {
|
for sheet in &workbook.worksheets {
|
||||||
let sheet_name = &sheet.name;
|
let sheet_name = &sheet.name;
|
||||||
let rel_id = &sheet.id;
|
let rel_id = &sheet.id;
|
||||||
@@ -1039,14 +907,15 @@ pub(super) fn load_sheets<R: Read + std::io::Seek>(
|
|||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
comments: comments.get(rel_id).expect("").to_vec(),
|
comments: comments.get(rel_id).expect("").to_vec(),
|
||||||
};
|
};
|
||||||
let (s, is_selected) =
|
sheets.push(load_sheet(
|
||||||
load_sheet(archive, &path, settings, worksheets, tables, shared_strings)?;
|
archive,
|
||||||
if is_selected {
|
&path,
|
||||||
selected_sheet = sheet_index;
|
settings,
|
||||||
}
|
worksheets,
|
||||||
sheets.push(s);
|
tables,
|
||||||
sheet_index += 1;
|
shared_strings,
|
||||||
|
)?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((sheets, selected_sheet))
|
Ok(sheets)
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,88 +12,14 @@ use ironcalc_base::Model;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_example() {
|
fn test_example() {
|
||||||
let model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
|
let model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
|
||||||
// We should use the API once it is in place
|
|
||||||
let workbook = model.workbook;
|
let workbook = model.workbook;
|
||||||
let ws = &workbook.worksheets;
|
assert_eq!(workbook.worksheets[0].frozen_rows, 0);
|
||||||
let expected_names = vec![
|
assert_eq!(workbook.worksheets[0].frozen_columns, 0);
|
||||||
"Sheet1".to_string(),
|
|
||||||
"Second".to_string(),
|
|
||||||
"Sheet4".to_string(),
|
|
||||||
"shared".to_string(),
|
|
||||||
"Table".to_string(),
|
|
||||||
"Sheet2".to_string(),
|
|
||||||
"Created fourth".to_string(),
|
|
||||||
"Frozen".to_string(),
|
|
||||||
"Split".to_string(),
|
|
||||||
"Hidden".to_string(),
|
|
||||||
];
|
|
||||||
let names: Vec<String> = ws.iter().map(|s| s.name.clone()).collect();
|
|
||||||
|
|
||||||
// One is not not imported and one is hidden
|
|
||||||
assert_eq!(expected_names, names);
|
|
||||||
|
|
||||||
assert_eq!(workbook.views[&0].sheet, 7);
|
|
||||||
|
|
||||||
// Test selection:
|
|
||||||
// First sheet (Sheet1)
|
|
||||||
// E13 and E13:N20
|
|
||||||
assert_eq!(ws[0].frozen_rows, 0);
|
|
||||||
assert_eq!(ws[0].frozen_columns, 0);
|
|
||||||
assert_eq!(ws[0].views[&0].row, 13);
|
|
||||||
assert_eq!(ws[0].views[&0].column, 5);
|
|
||||||
assert_eq!(ws[0].views[&0].range, [13, 5, 20, 14]);
|
|
||||||
|
|
||||||
let model2 = load_from_icalc("tests/example.ic").unwrap();
|
let model2 = load_from_icalc("tests/example.ic").unwrap();
|
||||||
let s = bitcode::encode(&model2.workbook);
|
let s = bitcode::encode(&model2.workbook);
|
||||||
assert_eq!(workbook, model2.workbook, "{:?}", s);
|
assert_eq!(workbook, model2.workbook, "{:?}", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_grid() {
|
|
||||||
let model = load_from_xlsx("tests/NoGrid.xlsx", "en", "UTC").unwrap();
|
|
||||||
{
|
|
||||||
let workbook = &model.workbook;
|
|
||||||
let ws = &workbook.worksheets;
|
|
||||||
|
|
||||||
// NoGrid does not show grid lines
|
|
||||||
let no_grid_sheet = &ws[0];
|
|
||||||
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
||||||
assert!(!no_grid_sheet.show_grid_lines);
|
|
||||||
|
|
||||||
let sheet2 = &ws[1];
|
|
||||||
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
||||||
assert!(sheet2.show_grid_lines);
|
|
||||||
|
|
||||||
let no_grid_no_headers_sheet = &ws[2];
|
|
||||||
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
||||||
// There is also no headers
|
|
||||||
assert!(!no_grid_no_headers_sheet.show_grid_lines);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
// save it and check again
|
|
||||||
let temp_file_name = "temp_file_no_grid.xlsx";
|
|
||||||
save_to_xlsx(&model, temp_file_name).unwrap();
|
|
||||||
let model = load_from_xlsx(temp_file_name, "en", "UTC").unwrap();
|
|
||||||
let workbook = &model.workbook;
|
|
||||||
let ws = &workbook.worksheets;
|
|
||||||
|
|
||||||
// NoGrid does not show grid lines
|
|
||||||
let no_grid_sheet = &ws[0];
|
|
||||||
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
||||||
assert!(!no_grid_sheet.show_grid_lines);
|
|
||||||
|
|
||||||
let sheet2 = &ws[1];
|
|
||||||
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
||||||
assert!(sheet2.show_grid_lines);
|
|
||||||
|
|
||||||
let no_grid_no_headers_sheet = &ws[2];
|
|
||||||
assert_eq!(no_grid_sheet.name, "NoGrid".to_string());
|
|
||||||
// There is also no headers
|
|
||||||
assert!(!no_grid_no_headers_sheet.show_grid_lines);
|
|
||||||
fs::remove_file(temp_file_name).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_save_to_xlsx() {
|
fn test_save_to_xlsx() {
|
||||||
let mut model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
|
let mut model = load_from_xlsx("tests/example.xlsx", "en", "UTC").unwrap();
|
||||||
@@ -107,20 +33,6 @@ fn test_save_to_xlsx() {
|
|||||||
assert_eq!(metadata.application, "IronCalc Sheets");
|
assert_eq!(metadata.application, "IronCalc Sheets");
|
||||||
// FIXME: This will need to be updated once we fix versioning
|
// FIXME: This will need to be updated once we fix versioning
|
||||||
assert_eq!(metadata.app_version, "10.0000");
|
assert_eq!(metadata.app_version, "10.0000");
|
||||||
|
|
||||||
let workbook = model.workbook;
|
|
||||||
let ws = &workbook.worksheets;
|
|
||||||
|
|
||||||
assert_eq!(workbook.views[&0].sheet, 7);
|
|
||||||
|
|
||||||
// Test selection:
|
|
||||||
// First sheet (Sheet1)
|
|
||||||
// E13 and E13:N20
|
|
||||||
assert_eq!(ws[0].frozen_rows, 0);
|
|
||||||
assert_eq!(ws[0].frozen_columns, 0);
|
|
||||||
assert_eq!(ws[0].views[&0].row, 13);
|
|
||||||
assert_eq!(ws[0].views[&0].column, 5);
|
|
||||||
assert_eq!(ws[0].views[&0].range, [13, 5, 20, 14]);
|
|
||||||
// TODO: can we show it is the 'same' model?
|
// TODO: can we show it is the 'same' model?
|
||||||
fs::remove_file(temp_file_name).unwrap();
|
fs::remove_file(temp_file_name).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user