UPDATE: Parses selected row/column/range and selected sheet (#67)
* FIX: Update to Rust 1.78.0 * UPDATE: Parses selected row/column/range and selected sheet
This commit is contained in:
committed by
GitHub
parent
a78d5593f2
commit
f752c90058
@@ -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 = context.clone();
|
self.context.clone_from(context);
|
||||||
self.parse_expr()
|
self.parse_expr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ use crate::{
|
|||||||
calc_result::Range,
|
calc_result::Range,
|
||||||
expressions::{
|
expressions::{
|
||||||
lexer::LexerMode,
|
lexer::LexerMode,
|
||||||
parser::stringify::{rename_sheet_in_node, to_rc_format},
|
parser::{
|
||||||
parser::Parser,
|
stringify::{rename_sheet_in_node, to_rc_format},
|
||||||
|
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::{Metadata, SheetState, Workbook, WorkbookSettings, Worksheet},
|
types::{Metadata, Selection, SheetState, Workbook, WorkbookSettings, Worksheet},
|
||||||
utils::ParsedReference,
|
utils::ParsedReference,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,6 +50,12 @@ impl Model {
|
|||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
frozen_columns: 0,
|
frozen_columns: 0,
|
||||||
frozen_rows: 0,
|
frozen_rows: 0,
|
||||||
|
selection: Selection {
|
||||||
|
is_selected: false,
|
||||||
|
row: 1,
|
||||||
|
column: 1,
|
||||||
|
range: [1, 1, 1, 1],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,14 @@ impl Display for SheetState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode, Debug, PartialEq, Clone)]
|
||||||
|
pub struct Selection {
|
||||||
|
pub is_selected: bool,
|
||||||
|
pub row: i32,
|
||||||
|
pub column: i32,
|
||||||
|
pub range: [i32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 {
|
||||||
@@ -87,6 +95,7 @@ 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 selection: Selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal representation of Excel's sheet_data
|
/// Internal representation of Excel's sheet_data
|
||||||
|
|||||||
@@ -849,7 +849,7 @@ impl UserModel {
|
|||||||
style.fill.fg_color = color(value)?;
|
style.fill.fg_color = color(value)?;
|
||||||
}
|
}
|
||||||
"num_fmt" => {
|
"num_fmt" => {
|
||||||
style.num_fmt = value.to_owned();
|
value.clone_into(&mut style.num_fmt);
|
||||||
}
|
}
|
||||||
"border.left" => {
|
"border.left" => {
|
||||||
style.border.left = border(value)?;
|
style.border.left = border(value)?;
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ 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,
|
utils::{column_to_number, parse_reference_a1},
|
||||||
|
},
|
||||||
|
types::{
|
||||||
|
Cell, Col, Comment, DefinedName, Row, Selection, SheetData, SheetState, Table, Worksheet,
|
||||||
},
|
},
|
||||||
types::{Cell, Col, Comment, DefinedName, Row, SheetData, SheetState, Table, Worksheet},
|
|
||||||
};
|
};
|
||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -47,6 +49,50 @@ 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
|
||||||
@@ -490,7 +536,29 @@ fn load_sheet_rels<R: Read + std::io::Seek>(
|
|||||||
Ok(comments)
|
Ok(comments)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_frozen_rows_and_columns(ws: Node) -> (i32, i32) {
|
struct SheetView {
|
||||||
|
is_selected: bool,
|
||||||
|
selected_row: i32,
|
||||||
|
selected_column: i32,
|
||||||
|
frozen_columns: i32,
|
||||||
|
frozen_rows: i32,
|
||||||
|
range: [i32; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
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],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"/>
|
||||||
@@ -511,19 +579,20 @@ fn get_frozen_rows_and_columns(ws: Node) -> (i32, i32) {
|
|||||||
// 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
|
||||||
// Calc ignores all those.
|
// IronCalc ignores all those.
|
||||||
|
|
||||||
let mut frozen_rows = 0;
|
let mut frozen_rows = 0;
|
||||||
let mut frozen_columns = 0;
|
let mut frozen_columns = 0;
|
||||||
|
|
||||||
// In Calc there can only be one sheetView
|
// In IronCalc 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 (0, 0);
|
return SheetView::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
let sheet_view = sheet_views[0]
|
let sheet_view = sheet_views[0]
|
||||||
@@ -531,25 +600,64 @@ fn get_frozen_rows_and_columns(ws: Node) -> (i32, i32) {
|
|||||||
.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 (0, 0);
|
return SheetView::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
let pane = sheet_view[0]
|
let sheet_view = sheet_view[0];
|
||||||
|
let is_selected = sheet_view.attribute("tabSelected").unwrap_or("0") == "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 && pane[0].attribute("state").unwrap_or("split") == "frozen" {
|
if pane.len() == 1 {
|
||||||
// TODO: Should we assert that topLeft is consistent?
|
if let Some("frozen") = pane[0].attribute("state") {
|
||||||
// let top_left_cell = pane[0].attribute("topLeftCell").unwrap_or("A1").to_string();
|
// TODO: Should we assert that topLeft is consistent?
|
||||||
|
// 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,
|
||||||
|
range: [row1, column1, row2, column2],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SheetView::default()
|
||||||
}
|
}
|
||||||
(frozen_rows, frozen_columns)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct SheetSettings {
|
pub(super) struct SheetSettings {
|
||||||
@@ -583,7 +691,7 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
|||||||
|
|
||||||
let dimension = load_dimension(ws);
|
let dimension = load_dimension(ws);
|
||||||
|
|
||||||
let (frozen_rows, frozen_columns) = get_frozen_rows_and_columns(ws);
|
let sheet_view = get_sheet_view(ws);
|
||||||
|
|
||||||
let cols = load_columns(ws)?;
|
let cols = load_columns(ws)?;
|
||||||
let color = load_sheet_color(ws)?;
|
let color = load_sheet_color(ws)?;
|
||||||
@@ -856,8 +964,14 @@ pub(super) fn load_sheet<R: Read + std::io::Seek>(
|
|||||||
color,
|
color,
|
||||||
merge_cells,
|
merge_cells,
|
||||||
comments: settings.comments,
|
comments: settings.comments,
|
||||||
frozen_rows,
|
frozen_rows: sheet_view.frozen_rows,
|
||||||
frozen_columns,
|
frozen_columns: sheet_view.frozen_columns,
|
||||||
|
selection: Selection {
|
||||||
|
is_selected: sheet_view.is_selected,
|
||||||
|
row: sheet_view.selected_row,
|
||||||
|
column: sheet_view.selected_column,
|
||||||
|
range: sheet_view.range,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -12,9 +12,38 @@ 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;
|
||||||
assert_eq!(workbook.worksheets[0].frozen_rows, 0);
|
let ws = &workbook.worksheets;
|
||||||
assert_eq!(workbook.worksheets[0].frozen_columns, 0);
|
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),
|
||||||
|
];
|
||||||
|
let names: Vec<(String, bool)> = ws
|
||||||
|
.iter()
|
||||||
|
.map(|s| (s.name.clone(), s.selection.is_selected))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// One is not not imported and one is hidden
|
||||||
|
assert_eq!(expected_names, names);
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user