From 49c3b14bf072b0e05e21df955e7b1f83d3a7ea8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher=20Andr=C3=A9s?= Date: Mon, 20 May 2024 17:51:09 +0200 Subject: [PATCH] UPDATE: Adds get/set views to the user model API (#69) --- base/src/model.rs | 3 + base/src/new_empty.rs | 41 +++-- base/src/test/user_model/mod.rs | 1 + base/src/test/user_model/test_view.rs | 216 ++++++++++++++++++++++++++ base/src/types.rs | 27 +++- base/src/user_model.rs | 174 ++++++++++++++++++++- bindings/wasm/src/lib.rs | 52 +++++++ xlsx/src/import/mod.rs | 12 +- xlsx/src/import/worksheets.rs | 70 +++++---- xlsx/tests/example.ic | Bin 4760 -> 4778 bytes xlsx/tests/test.rs | 33 ++-- 11 files changed, 562 insertions(+), 67 deletions(-) create mode 100644 base/src/test/user_model/test_view.rs diff --git a/base/src/model.rs b/base/src/model.rs index a50a1b0..a029acd 100644 --- a/base/src/model.rs +++ b/base/src/model.rs @@ -118,6 +118,8 @@ pub struct Model { pub(crate) language: Language, /// The timezone used to evaluate the model 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 @@ -886,6 +888,7 @@ impl Model { language, locale, tz, + view_id: 0, }; model.parse_formulas(); diff --git a/base/src/new_empty.rs b/base/src/new_empty.rs index 4ea2e74..8d61ce0 100644 --- a/base/src/new_empty.rs +++ b/base/src/new_empty.rs @@ -15,7 +15,9 @@ use crate::{ language::get_language, locale::get_locale, model::{get_milliseconds_since_epoch, Model, ParsedDefinedName}, - types::{Metadata, Selection, SheetState, Workbook, WorkbookSettings, Worksheet}, + types::{ + Metadata, SheetState, Workbook, WorkbookSettings, WorkbookView, Worksheet, WorksheetView, + }, utils::ParsedReference, }; @@ -35,7 +37,20 @@ fn is_valid_sheet_name(name: &str) -> bool { impl Model { /// 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) -> Worksheet { + fn new_empty_worksheet(name: &str, sheet_id: u32, view_ids: &[&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 { cols: vec![], rows: vec![], @@ -50,12 +65,7 @@ impl Model { color: Default::default(), frozen_columns: 0, frozen_rows: 0, - selection: Selection { - is_selected: false, - row: 1, - column: 1, - range: [1, 1, 1, 1], - }, + views, } } @@ -130,7 +140,7 @@ impl Model { 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) { self.parser .set_worksheets(self.workbook.get_worksheet_names()); @@ -161,7 +171,8 @@ impl Model { let sheet_name = format!("{}{}", base_name, index); // Now we need a sheet_id let sheet_id = self.get_new_sheet_id(); - let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id); + let view_ids: Vec<&u32> = self.workbook.views.keys().collect(); + let worksheet = Model::new_empty_worksheet(&sheet_name, sheet_id, &view_ids); self.workbook.worksheets.push(worksheet); self.reset_parsed_structures(); (sheet_name, self.workbook.worksheets.len() as u32 - 1) @@ -192,7 +203,8 @@ impl Model { Some(id) => id, None => self.get_new_sheet_id(), }; - let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id); + let view_ids: Vec<&u32> = self.workbook.views.keys().collect(); + let worksheet = Model::new_empty_worksheet(sheet_name, sheet_id, &view_ids); if sheet_index as usize > self.workbook.worksheets.len() { return Err("Sheet index out of range".to_string()); } @@ -339,11 +351,14 @@ impl Model { // "2020-08-06T21:20:53Z 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 let workbook = Workbook { shared_strings: vec![], defined_names: vec![], - worksheets: vec![Model::new_empty_worksheet("Sheet1", 1)], + worksheets: vec![Model::new_empty_worksheet("Sheet1", 1, &[&0])], styles: Default::default(), name: name.to_string(), settings: WorkbookSettings { @@ -359,6 +374,7 @@ impl Model { last_modified: now, }, tables: HashMap::new(), + views, }; let parsed_formulas = Vec::new(); let worksheets = &workbook.worksheets; @@ -379,6 +395,7 @@ impl Model { locale, language, tz, + view_id: 0, }; model.parse_formulas(); Ok(model) diff --git a/base/src/test/user_model/mod.rs b/base/src/test/user_model/mod.rs index ef0cddb..a190c0e 100644 --- a/base/src/test/user_model/mod.rs +++ b/base/src/test/user_model/mod.rs @@ -8,3 +8,4 @@ mod test_row_column; mod test_styles; mod test_to_from_bytes; mod test_undo_redo; +mod test_view; diff --git a/base/src/test/user_model/test_view.rs b/base/src/test/user_model/test_view.rs new file mode 100644 index 0000000..5a6dcd6 --- /dev/null +++ b/base/src/test/user_model/test_view.rs @@ -0,0 +1,216 @@ +#![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::(&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)); +} diff --git a/base/src/types.rs b/base/src/types.rs index 014b985..042896f 100644 --- a/base/src/types.rs +++ b/base/src/types.rs @@ -27,6 +27,14 @@ pub struct WorkbookSettings { pub tz: 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 #[derive(Encode, Decode, Debug, PartialEq, Clone)] pub struct Workbook { @@ -38,6 +46,7 @@ pub struct Workbook { pub settings: WorkbookSettings, pub metadata: Metadata, pub tables: HashMap, + pub views: HashMap, } /// A defined name. The `sheet_id` is the sheet index in case the name is local @@ -48,9 +57,6 @@ pub struct DefinedName { pub sheet_id: Option, } -// TODO: Move to worksheet.rs make frozen_rows/columns private and u32 -/// Internal representation of a worksheet Excel object - /// * state: /// 18.18.68 ST_SheetState (Sheet Visibility Types) /// hidden, veryHidden, visible @@ -71,12 +77,21 @@ 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 Selection { - pub is_selected: bool, +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 @@ -95,7 +110,7 @@ pub struct Worksheet { pub comments: Vec, pub frozen_rows: i32, pub frozen_columns: i32, - pub selection: Selection, + pub views: HashMap, } /// Internal representation of Excel's sheet_data diff --git a/base/src/user_model.rs b/base/src/user_model.rs index f002b2a..cc10259 100644 --- a/base/src/user_model.rs +++ b/base/src/user_model.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, fmt::Debug}; use bitcode::{Decode, Encode}; +use serde::{Deserialize, Serialize}; use crate::{ constants, @@ -18,6 +19,17 @@ use crate::{ 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)] struct RowData { row: Option, @@ -118,6 +130,7 @@ enum Diff { old_value: String, new_value: String, }, + // FIXME: we are missing SetViewDiffs } type DiffList = Vec; @@ -249,7 +262,7 @@ fn vertical(value: &str) -> Result { } /// # A wrapper around [`Model`] for a spreadsheet end user. -/// UserModel is a wrapper around Model with undo/redo history, _diffs_ and automatic evaluation. +/// UserModel is a wrapper around Model with undo/redo history, _diffs_, automatic evaluation and view management. /// /// A diff in this context (or more correctly a _user diff_) is a change created by a user. /// @@ -935,6 +948,165 @@ impl UserModel { 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_column_number(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(()) + } // **** Private methods ****** // fn push_diff_list(&mut self, diff_list: DiffList) { diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index a09aaec..f7ce52f 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -286,4 +286,56 @@ impl Model { pub fn get_worksheets_properties(&self) -> JsValue { 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 { + 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) + } } diff --git a/xlsx/src/import/mod.rs b/xlsx/src/import/mod.rs index 9996c1a..d130237 100644 --- a/xlsx/src/import/mod.rs +++ b/xlsx/src/import/mod.rs @@ -16,7 +16,7 @@ use std::{ use roxmltree::Node; use ironcalc_base::{ - types::{Metadata, Workbook, WorkbookSettings}, + types::{Metadata, Workbook, WorkbookSettings, WorkbookView}, Model, }; @@ -66,7 +66,7 @@ fn load_xlsx_from_reader( let workbook = load_workbook(&mut archive)?; let rels = load_relationships(&mut archive)?; let mut tables = HashMap::new(); - let worksheets = load_sheets( + let (worksheets, selected_sheet) = load_sheets( &mut archive, &rels, &workbook, @@ -88,6 +88,13 @@ fn load_xlsx_from_reader( } } }; + let mut views = HashMap::new(); + views.insert( + 0, + WorkbookView { + sheet: selected_sheet, + }, + ); Ok(Workbook { shared_strings, defined_names: workbook.defined_names, @@ -100,6 +107,7 @@ fn load_xlsx_from_reader( }, metadata, tables, + views, }) } diff --git a/xlsx/src/import/worksheets.rs b/xlsx/src/import/worksheets.rs index f723a98..3b291bb 100644 --- a/xlsx/src/import/worksheets.rs +++ b/xlsx/src/import/worksheets.rs @@ -8,7 +8,8 @@ use ironcalc_base::{ utils::{column_to_number, parse_reference_a1}, }, types::{ - Cell, Col, Comment, DefinedName, Row, Selection, SheetData, SheetState, Table, Worksheet, + Cell, Col, Comment, DefinedName, Row, SheetData, SheetState, Table, Worksheet, + WorksheetView, }, }; use roxmltree::Node; @@ -674,7 +675,7 @@ pub(super) fn load_sheet( worksheets: &[String], tables: &HashMap, shared_strings: &mut Vec, -) -> Result { +) -> Result<(Worksheet, bool), XlsxError> { let sheet_name = &settings.name; let sheet_id = settings.id; let state = &settings.state; @@ -952,27 +953,37 @@ pub(super) fn load_sheet( // pageSetup // - Ok(Worksheet { - 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, - selection: Selection { - is_selected: sheet_view.is_selected, + let mut views = HashMap::new(); + views.insert( + 0, + WorksheetView { row: sheet_view.selected_row, column: sheet_view.selected_column, range: sheet_view.range, + top_row: 1, + left_column: 1, }, - }) + ); + + Ok(( + Worksheet { + 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, + views, + }, + sheet_view.is_selected, + )) } pub(super) fn load_sheets( @@ -981,7 +992,7 @@ pub(super) fn load_sheets( workbook: &WorkbookXML, tables: &mut HashMap, shared_strings: &mut Vec, -) -> Result, XlsxError> { +) -> Result<(Vec, u32), XlsxError> { // load comments and tables let mut comments = HashMap::new(); for sheet in &workbook.worksheets { @@ -1003,6 +1014,8 @@ pub(super) fn load_sheets( // load all sheets let worksheets: &Vec = &workbook.worksheets.iter().map(|s| s.name.clone()).collect(); let mut sheets = Vec::new(); + let mut selected_sheet = 0; + let mut sheet_index = 0; for sheet in &workbook.worksheets { let sheet_name = &sheet.name; let rel_id = &sheet.id; @@ -1021,15 +1034,14 @@ pub(super) fn load_sheets( state: state.clone(), comments: comments.get(rel_id).expect("").to_vec(), }; - sheets.push(load_sheet( - archive, - &path, - settings, - worksheets, - tables, - shared_strings, - )?); + let (s, is_selected) = + load_sheet(archive, &path, settings, worksheets, tables, shared_strings)?; + if is_selected { + selected_sheet = sheet_index; + } + sheets.push(s); + sheet_index += 1; } } - Ok(sheets) + Ok((sheets, selected_sheet)) } diff --git a/xlsx/tests/example.ic b/xlsx/tests/example.ic index 4f86e9556913322c98bfdfdd3918ad91c4034870..7e99ea30d823e8922a4194e07a48cfa8aa5f9a36 100644 GIT binary patch delta 1453 zcmZ8hO^6&t6n^#7-PQj!GrjXayEDDLJNci?`je~>XSV4idQn6#f)~X_MU-Sn*jpx` zCq+DTScE_jk2x)@5D<~MM9`Doy?PPU6>qu3*V9#Q$OqkDy;tvj@71g6`D)|-#=$Ra zPP=B$AN1=skt;zp63t4x6E-ZT#;S%z85ee?W=g_+$7Y^s8NxLvBb;$UkxiEq%d;ca z78aE?bCp+nt&VGS8}*<<9Dk)57SRF>Mi@aDCMDEh98ek+=$vs%y?|1()ClLIrWWz_(p4D>SSMT6Tozu&{>X`rHVsGQdF*4?A6+q-bz? zrjm=1S)3X;H-ZN@a2lL4N(?^7T38>aQ}kODUJ5LerIZw(0<0wUwx#f@fwV0gf6Xy` zRtcpnDivvrUXAa3aA*Hr6L^+fP2kfQECA?#vY%h!9N=0yj43*xp8?E)_92N8Y0O~; zz^DB*P0}n$lO#!p!`W;$obBz+X7OH{rCF9G83tz=P8ubO2T|z%kLeVvxMMbUy=R`? ze&HfxaE6#Y}WEO&F&~V{{*5) zU3)3N_;9M~C_b9xcg@YYP-g$BZCJn(k@9yHYdfr_pyc23foS9hRx@8%B0n$e*Wz*mbmy0qt%aZNT%SxA(s{ab znYYDS{*xsKI#RcMoKfE9R(@TK^4r47zZT}4TkDP|@K+1ATQ!Xn!O5^P^qc+q>e`^! zYqvU$X0Nr{X|F`xTBF`eHehzg&XE}bB0KfnUk})pcMFAm%V}auc+qTg>Mi2mm zqavDH#!GO4JNPlbdhIv>bEnEud`{cSt*-0#73GhnpI@@P`=33Wyp!$UeedrFll(2a zx%=|r!M9)h`|ISJhri!y{P=^qOD%ph`OCAveB;kQCOWyae^zmFTH4ZMPHch$3 zuit$8;~O_WT7PAgE_wI{= SFV~OR;Uo8+iGLY5j{g8n2Au-{ delta 1388 zcmZ8h%WEV>7_Vnn_4_fMdC#QN>16huOnhXctkc`H3m){M2d}HF3Zk%*CAXb`H$_Am zf{U<--f~)qu;4*U;mbufFP;LX z+TH$Ol&oMe^h|>WQ5?F&3PQ$B(IOEwuoSMr9ctOklmt`8DZ>O4iVa&ZKXz=7uxK!* zBObQ99kV~Qq$^qm4wibXPCyJ}N-&~~Vr&pfIA(-{WzGSH5bO|-5bD>mCJtmUATZz) zLjRE$##%=>Y%&>gJlmJHG%ZRbGzw)XBbYHb1mn`NF=3SXj?F!j5@yULHmSsxX-O=p z0||!h)JJ2mq)*(yioDQvOu&y^+h@B8-xO2D2fHsY3PwC0G!Joit*zn>C~LO&DJ2N20}@z;dA>z*sDV za)j822M2H7d8^nh^E=lhDziOCCIlsUHeq?1rbU8)l6i^{K_yDkG(!nPNU*fXfXX5( ziefKatjPy^lXNdDv$BLAWS1p`=5?K#M8g()?LZ7o>6ALpJiBe!AWiM3p5A%xvOArg zJ9B>R;@J!9lWcW7Oh)Ol*B`7zo$gW`2JKcau3k5W>zgMOZ*Iow=2eN~StH22`r248 zR0j{<%^UMjaUB*YJ$TUgW2Gadr+M|f7+=>1(PphDDqQ}btMsn2>vEc_yyLr#f8&02 zD8~4=>N7E^F3Yg5+tBeEf0$PTF*ddPMMzuT)S3>~MRk+<%rdV>kG86ta--UpviedQ zb33946T@`mDz^j+&v#%ECkQ%`%^zi`4bw_w{uC^)fo1# z>A3u*qkG?c^3TKBpN{$IYma|b{EykkSA=u;(IfRS9M8Ub^!x4358ut2_@7Tcl9!vr zZ&l)t$`RyG0>t?n<-r$k|8-yaQ4Zb1LEuk7tiG~w_2#YX@9*Avuln2UEpNa2&W#(p h)0JZDRN7Itt6Mu2w&J5J)@_XUtB3XrN5Z*l{0oN2o~-}? diff --git a/xlsx/tests/test.rs b/xlsx/tests/test.rs index 96d2d19..7e69c3c 100644 --- a/xlsx/tests/test.rs +++ b/xlsx/tests/test.rs @@ -16,33 +16,32 @@ fn test_example() { let workbook = model.workbook; let ws = &workbook.worksheets; let expected_names = vec![ - ("Sheet1".to_string(), false), - ("Second".to_string(), false), - ("Sheet4".to_string(), false), - ("shared".to_string(), false), - ("Table".to_string(), false), - ("Sheet2".to_string(), false), - ("Created fourth".to_string(), false), - ("Frozen".to_string(), true), - ("Split".to_string(), false), - ("Hidden".to_string(), false), + "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, bool)> = ws - .iter() - .map(|s| (s.name.clone(), s.selection.is_selected)) - .collect(); + let names: Vec = 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].selection.row, 13); - assert_eq!(ws[0].selection.column, 5); - assert_eq!(ws[0].selection.range, [13, 5, 20, 14]); + 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 s = bitcode::encode(&model2.workbook);