UPDATE: Dump of initial files
This commit is contained in:
843
base/src/functions/lookup_and_reference.rs
Normal file
843
base/src/functions/lookup_and_reference.rs
Normal file
@@ -0,0 +1,843 @@
|
||||
use crate::constants::{LAST_COLUMN, LAST_ROW};
|
||||
use crate::{
|
||||
calc_result::{CalcResult, CellReference},
|
||||
expressions::parser::Node,
|
||||
expressions::token::Error,
|
||||
model::Model,
|
||||
utils::ParsedReference,
|
||||
};
|
||||
|
||||
use super::util::{compare_values, from_wildcard_to_regex, result_matches_regex, values_are_equal};
|
||||
|
||||
impl Model {
|
||||
pub(crate) fn fn_index(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
let row_num;
|
||||
let col_num;
|
||||
if args.len() == 3 {
|
||||
row_num = match self.get_number(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => {
|
||||
return s;
|
||||
}
|
||||
};
|
||||
if row_num < 1.0 {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Argument must be >= 1".to_string(),
|
||||
};
|
||||
}
|
||||
col_num = match self.get_number(&args[2], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => {
|
||||
return s;
|
||||
}
|
||||
};
|
||||
if col_num < 1.0 {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Argument must be >= 1".to_string(),
|
||||
};
|
||||
}
|
||||
} else if args.len() == 2 {
|
||||
row_num = match self.get_number(&args[1], cell) {
|
||||
Ok(f) => f,
|
||||
Err(s) => {
|
||||
return s;
|
||||
}
|
||||
};
|
||||
if row_num < 1.0 {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Argument must be >= 1".to_string(),
|
||||
};
|
||||
}
|
||||
col_num = -1.0;
|
||||
} else {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
match self.evaluate_node_in_context(&args[0], cell) {
|
||||
CalcResult::Range { left, right } => {
|
||||
let row;
|
||||
let column;
|
||||
if (col_num + 1.0).abs() < f64::EPSILON {
|
||||
if left.row == right.row {
|
||||
column = left.column + (row_num as i32) - 1;
|
||||
row = left.row;
|
||||
} else {
|
||||
column = left.column;
|
||||
row = left.row + (row_num as i32) - 1;
|
||||
}
|
||||
} else {
|
||||
row = left.row + (row_num as i32) - 1;
|
||||
column = left.column + (col_num as i32) - 1;
|
||||
}
|
||||
if row > right.row {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Wrong reference".to_string(),
|
||||
};
|
||||
}
|
||||
if column > right.column {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Wrong reference".to_string(),
|
||||
};
|
||||
}
|
||||
self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
})
|
||||
}
|
||||
error @ CalcResult::Error { .. } => error,
|
||||
_ => CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Expecting a Range".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MATCH(lookup_value, lookup_array, [match_type])
|
||||
// The MATCH function syntax has the following arguments:
|
||||
// * lookup_value Required. The value that you want to match in lookup_array.
|
||||
// The lookup_value argument can be a value (number, text, or logical value)
|
||||
// or a cell reference to a number, text, or logical value.
|
||||
// * lookup_array Required. The range of cells being searched.
|
||||
// * match_type Optional. The number -1, 0, or 1.
|
||||
// The match_type argument specifies how Excel matches lookup_value
|
||||
// with values in lookup_array. The default value for this argument is 1.
|
||||
// NOTE: Please read the caveat above in binary search
|
||||
pub(crate) fn fn_match(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 3 || args.len() < 2 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let target = self.evaluate_node_in_context(&args[0], cell);
|
||||
if target.is_error() {
|
||||
return target;
|
||||
}
|
||||
if matches!(target, CalcResult::EmptyCell) {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Cannot match empty cell".to_string(),
|
||||
};
|
||||
}
|
||||
let match_type = if args.len() == 3 {
|
||||
match self.get_number(&args[2], cell) {
|
||||
Ok(v) => v as i32,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let match_range = self.evaluate_node_in_context(&args[1], cell);
|
||||
|
||||
match match_range {
|
||||
CalcResult::Range { left, right } => {
|
||||
match match_type {
|
||||
-1 => {
|
||||
// We apply binary search leftmost for value in the range
|
||||
let is_row_vector;
|
||||
if left.row == right.row {
|
||||
is_row_vector = false;
|
||||
} else if left.column == right.column {
|
||||
is_row_vector = true;
|
||||
} else {
|
||||
// second argument must be a vector
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Argument must be a vector".to_string(),
|
||||
};
|
||||
}
|
||||
let n = if is_row_vector {
|
||||
right.row - left.row
|
||||
} else {
|
||||
right.column - left.column
|
||||
} + 1;
|
||||
let mut l = 0;
|
||||
let mut r = n;
|
||||
while l < r {
|
||||
let m = (l + r) / 2;
|
||||
let row;
|
||||
let column;
|
||||
if is_row_vector {
|
||||
row = left.row + m;
|
||||
column = left.column;
|
||||
} else {
|
||||
column = left.column + m;
|
||||
row = left.row;
|
||||
}
|
||||
let value = self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
});
|
||||
|
||||
if compare_values(&value, &target) >= 0 {
|
||||
l = m + 1;
|
||||
} else {
|
||||
r = m;
|
||||
}
|
||||
}
|
||||
// r is the number of elements less than target in the vector
|
||||
// If target is less than the minimum return #N/A
|
||||
if l == 0 {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
};
|
||||
}
|
||||
// Now l points to the leftmost element
|
||||
CalcResult::Number(l as f64)
|
||||
}
|
||||
0 => {
|
||||
// We apply linear search
|
||||
let is_row_vector;
|
||||
if left.row == right.row {
|
||||
is_row_vector = false;
|
||||
} else if left.column == right.column {
|
||||
is_row_vector = true;
|
||||
} else {
|
||||
// second argument must be a vector
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Argument must be a vector".to_string(),
|
||||
};
|
||||
}
|
||||
let n = if is_row_vector {
|
||||
right.row - left.row
|
||||
} else {
|
||||
right.column - left.column
|
||||
} + 1;
|
||||
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
|
||||
if let CalcResult::String(s) = &target {
|
||||
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
|
||||
Box::new(move |x| result_matches_regex(x, ®))
|
||||
} else {
|
||||
Box::new(move |_| false)
|
||||
}
|
||||
} else {
|
||||
Box::new(move |x| values_are_equal(x, &target))
|
||||
};
|
||||
for l in 0..n {
|
||||
let row;
|
||||
let column;
|
||||
if is_row_vector {
|
||||
row = left.row + l;
|
||||
column = left.column;
|
||||
} else {
|
||||
column = left.column + l;
|
||||
row = left.row;
|
||||
}
|
||||
let value = self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
});
|
||||
if result_matches(&value) {
|
||||
return CalcResult::Number(l as f64 + 1.0);
|
||||
}
|
||||
}
|
||||
CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// l is the number of elements less than target in the vector
|
||||
let is_row_vector;
|
||||
if left.row == right.row {
|
||||
is_row_vector = false;
|
||||
} else if left.column == right.column {
|
||||
is_row_vector = true;
|
||||
} else {
|
||||
// second argument must be a vector
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Argument must be a vector".to_string(),
|
||||
};
|
||||
}
|
||||
let l = self.binary_search(&target, &left, &right, is_row_vector);
|
||||
if l == -2 {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
CalcResult::Number(l as f64 + 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => error,
|
||||
_ => CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Invalid".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// HLOOKUP(lookup_value, table_array, row_index, [is_sorted])
|
||||
/// We look for `lookup_value` in the first row of table array
|
||||
/// We return the value in row `row_index` of the same column in `table_array`
|
||||
/// `is_sorted` is true by default and assumes that values in first row are ordered
|
||||
pub(crate) fn fn_hlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 4 || args.len() < 3 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
|
||||
if lookup_value.is_error() {
|
||||
return lookup_value;
|
||||
}
|
||||
let row_index = match self.get_number(&args[2], cell) {
|
||||
Ok(v) => v.floor() as i32,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let is_sorted = if args.len() == 4 {
|
||||
match self.get_boolean(&args[3], cell) {
|
||||
Ok(v) => v,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let range = self.evaluate_node_in_context(&args[1], cell);
|
||||
match range {
|
||||
CalcResult::Range { left, right } => {
|
||||
if is_sorted {
|
||||
// This assumes the values in row are in order
|
||||
let l = self.binary_search(&lookup_value, &left, &right, false);
|
||||
if l == -2 {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
};
|
||||
}
|
||||
let row = left.row + row_index - 1;
|
||||
let column = left.column + l;
|
||||
if row > right.row {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Invalid reference".to_string(),
|
||||
};
|
||||
}
|
||||
self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
})
|
||||
} else {
|
||||
// Linear search for exact match
|
||||
let n = right.column - left.column + 1;
|
||||
let row = left.row + row_index - 1;
|
||||
if row > right.row {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Invalid reference".to_string(),
|
||||
};
|
||||
}
|
||||
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
|
||||
if let CalcResult::String(s) = &lookup_value {
|
||||
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
|
||||
Box::new(move |x| result_matches_regex(x, ®))
|
||||
} else {
|
||||
Box::new(move |_| false)
|
||||
}
|
||||
} else {
|
||||
Box::new(move |x| compare_values(x, &lookup_value) == 0)
|
||||
};
|
||||
for l in 0..n {
|
||||
let value = self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row: left.row,
|
||||
column: left.column + l,
|
||||
});
|
||||
if result_matches(&value) {
|
||||
return self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column: left.column + l,
|
||||
});
|
||||
}
|
||||
}
|
||||
CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => error,
|
||||
CalcResult::String(_) => CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Range expected".to_string(),
|
||||
},
|
||||
_ => CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Range expected".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// VLOOKUP(lookup_value, table_array, row_index, [is_sorted])
|
||||
/// We look for `lookup_value` in the first column of table array
|
||||
/// We return the value in column `column_index` of the same row in `table_array`
|
||||
/// `is_sorted` is true by default and assumes that values in first column are ordered
|
||||
pub(crate) fn fn_vlookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 4 || args.len() < 3 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let lookup_value = self.evaluate_node_in_context(&args[0], cell);
|
||||
if lookup_value.is_error() {
|
||||
return lookup_value;
|
||||
}
|
||||
let column_index = match self.get_number(&args[2], cell) {
|
||||
Ok(v) => v.floor() as i32,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let is_sorted = if args.len() == 4 {
|
||||
match self.get_boolean(&args[3], cell) {
|
||||
Ok(v) => v,
|
||||
Err(s) => return s,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let range = self.evaluate_node_in_context(&args[1], cell);
|
||||
match range {
|
||||
CalcResult::Range { left, right } => {
|
||||
if is_sorted {
|
||||
// This assumes the values in column are in order
|
||||
let l = self.binary_search(&lookup_value, &left, &right, true);
|
||||
if l == -2 {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
};
|
||||
}
|
||||
let row = left.row + l;
|
||||
let column = left.column + column_index - 1;
|
||||
if column > right.column {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Invalid reference".to_string(),
|
||||
};
|
||||
}
|
||||
self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
})
|
||||
} else {
|
||||
// Linear search for exact match
|
||||
let n = right.row - left.row + 1;
|
||||
let column = left.column + column_index - 1;
|
||||
if column > right.column {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Invalid reference".to_string(),
|
||||
};
|
||||
}
|
||||
let result_matches: Box<dyn Fn(&CalcResult) -> bool> =
|
||||
if let CalcResult::String(s) = &lookup_value {
|
||||
if let Ok(reg) = from_wildcard_to_regex(&s.to_lowercase(), true) {
|
||||
Box::new(move |x| result_matches_regex(x, ®))
|
||||
} else {
|
||||
Box::new(move |_| false)
|
||||
}
|
||||
} else {
|
||||
Box::new(move |x| compare_values(x, &lookup_value) == 0)
|
||||
};
|
||||
for l in 0..n {
|
||||
let value = self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row: left.row + l,
|
||||
column: left.column,
|
||||
});
|
||||
if result_matches(&value) {
|
||||
return self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row: left.row + l,
|
||||
column,
|
||||
});
|
||||
}
|
||||
}
|
||||
CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => error,
|
||||
CalcResult::String(_) => CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Range expected".to_string(),
|
||||
},
|
||||
_ => CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Range expected".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// LOOKUP(lookup_value, lookup_vector, [result_vector])
|
||||
// Important: The values in lookup_vector must be placed in ascending order:
|
||||
// ..., -2, -1, 0, 1, 2, ..., A-Z, FALSE, TRUE;
|
||||
// otherwise, LOOKUP might not return the correct value.
|
||||
// Uppercase and lowercase text are equivalent.
|
||||
// TODO: Implement the other form of INDEX:
|
||||
// INDEX(reference, row_num, [column_num], [area_num])
|
||||
// NOTE: Please read the caveat above in binary search
|
||||
pub(crate) fn fn_lookup(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 3 || args.len() < 2 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let target = self.evaluate_node_in_context(&args[0], cell);
|
||||
if target.is_error() {
|
||||
return target;
|
||||
}
|
||||
let value = self.evaluate_node_in_context(&args[1], cell);
|
||||
match value {
|
||||
CalcResult::Range { left, right } => {
|
||||
let is_row_vector;
|
||||
if left.row == right.row {
|
||||
is_row_vector = false;
|
||||
} else if left.column == right.column {
|
||||
is_row_vector = true;
|
||||
} else {
|
||||
// second argument must be a vector
|
||||
return CalcResult::Error {
|
||||
error: Error::ERROR,
|
||||
origin: cell,
|
||||
message: "Second argument must be a vector".to_string(),
|
||||
};
|
||||
}
|
||||
let l = self.binary_search(&target, &left, &right, is_row_vector);
|
||||
if l == -2 {
|
||||
return CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Not found".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
if args.len() == 3 {
|
||||
let target_range = self.evaluate_node_in_context(&args[2], cell);
|
||||
match target_range {
|
||||
CalcResult::Range {
|
||||
left: l1,
|
||||
right: _r1,
|
||||
} => {
|
||||
let row;
|
||||
let column;
|
||||
if is_row_vector {
|
||||
row = l1.row + l;
|
||||
column = l1.column;
|
||||
} else {
|
||||
column = l1.column + l;
|
||||
row = l1.row;
|
||||
}
|
||||
self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
})
|
||||
}
|
||||
error @ CalcResult::Error { .. } => error,
|
||||
_ => CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Range expected".to_string(),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
let row;
|
||||
let column;
|
||||
if is_row_vector {
|
||||
row = left.row + l;
|
||||
column = left.column;
|
||||
} else {
|
||||
column = left.column + l;
|
||||
row = left.row;
|
||||
}
|
||||
self.evaluate_cell(CellReference {
|
||||
sheet: left.sheet,
|
||||
row,
|
||||
column,
|
||||
})
|
||||
}
|
||||
}
|
||||
error @ CalcResult::Error { .. } => error,
|
||||
_ => CalcResult::Error {
|
||||
error: Error::NA,
|
||||
origin: cell,
|
||||
message: "Range expected".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ROW([reference])
|
||||
// If reference is not present returns the row of the present cell.
|
||||
// Otherwise returns the row number of reference
|
||||
pub(crate) fn fn_row(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 1 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
if args.is_empty() {
|
||||
return CalcResult::Number(cell.row as f64);
|
||||
}
|
||||
match self.get_reference(&args[0], cell) {
|
||||
Ok(c) => CalcResult::Number(c.left.row as f64),
|
||||
Err(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
// ROWS(range)
|
||||
// Returns the number of rows in range
|
||||
pub(crate) fn fn_rows(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() != 1 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
match self.get_reference(&args[0], cell) {
|
||||
Ok(c) => CalcResult::Number((c.right.row - c.left.row + 1) as f64),
|
||||
Err(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
// COLUMN([reference])
|
||||
// If reference is not present returns the column of the present cell.
|
||||
// Otherwise returns the column number of reference
|
||||
pub(crate) fn fn_column(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 1 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
if args.is_empty() {
|
||||
return CalcResult::Number(cell.column as f64);
|
||||
}
|
||||
|
||||
match self.get_reference(&args[0], cell) {
|
||||
Ok(range) => CalcResult::Number(range.left.column as f64),
|
||||
Err(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
/// CHOOSE(index_num, value1, [value2], ...)
|
||||
/// Uses index_num to return a value from the list of value arguments.
|
||||
pub(crate) fn fn_choose(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() < 2 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
|
||||
let index_num = match self.get_number(&args[0], cell) {
|
||||
Ok(index_num) => index_num as usize,
|
||||
Err(calc_err) => return calc_err,
|
||||
};
|
||||
|
||||
if index_num < 1 || index_num > (args.len() - 1) {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "Invalid index".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
self.evaluate_node_with_reference(&args[index_num], cell)
|
||||
}
|
||||
|
||||
// COLUMNS(range)
|
||||
// Returns the number of columns in range
|
||||
pub(crate) fn fn_columns(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() != 1 {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
match self.get_reference(&args[0], cell) {
|
||||
Ok(c) => CalcResult::Number((c.right.column - c.left.column + 1) as f64),
|
||||
Err(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
// INDIRECT(ref_tex)
|
||||
// Returns the reference specified by 'ref_text'
|
||||
pub(crate) fn fn_indirect(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
if args.len() > 2 || args.is_empty() {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let value = self.get_string(&args[0], cell);
|
||||
match value {
|
||||
Ok(s) => {
|
||||
if args.len() == 2 {
|
||||
return CalcResult::Error {
|
||||
error: Error::NIMPL,
|
||||
origin: cell,
|
||||
message: "Not implemented".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let parsed_reference = ParsedReference::parse_reference_formula(
|
||||
Some(cell.sheet),
|
||||
&s,
|
||||
&self.locale,
|
||||
|name| self.get_sheet_index_by_name(name),
|
||||
);
|
||||
|
||||
let parsed_reference = match parsed_reference {
|
||||
Ok(reference) => reference,
|
||||
Err(message) => {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
match parsed_reference {
|
||||
ParsedReference::CellReference(reference) => CalcResult::Range {
|
||||
left: reference,
|
||||
right: reference,
|
||||
},
|
||||
ParsedReference::Range(left, right) => CalcResult::Range { left, right },
|
||||
}
|
||||
}
|
||||
Err(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
// OFFSET(reference, rows, cols, [height], [width])
|
||||
// Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
|
||||
// The reference that is returned can be a single cell or a range of cells.
|
||||
// You can specify the number of rows and the number of columns to be returned.
|
||||
pub(crate) fn fn_offset(&mut self, args: &[Node], cell: CellReference) -> CalcResult {
|
||||
let l = args.len();
|
||||
if !(3..=5).contains(&l) {
|
||||
return CalcResult::new_args_number_error(cell);
|
||||
}
|
||||
let reference = match self.get_reference(&args[0], cell) {
|
||||
Ok(c) => c,
|
||||
Err(s) => return s,
|
||||
};
|
||||
let rows = match self.get_number(&args[1], cell) {
|
||||
Ok(c) => {
|
||||
if c < 0.0 {
|
||||
c.ceil() as i32
|
||||
} else {
|
||||
c.floor() as i32
|
||||
}
|
||||
}
|
||||
Err(s) => return s,
|
||||
};
|
||||
let cols = match self.get_number(&args[2], cell) {
|
||||
Ok(c) => {
|
||||
if c < 0.0 {
|
||||
c.ceil() as i32
|
||||
} else {
|
||||
c.floor() as i32
|
||||
}
|
||||
}
|
||||
Err(s) => return s,
|
||||
};
|
||||
let row_start = reference.left.row + rows;
|
||||
let column_start = reference.left.column + cols;
|
||||
let width;
|
||||
let height;
|
||||
if l == 4 {
|
||||
height = match self.get_number(&args[3], cell) {
|
||||
Ok(c) => {
|
||||
if c < 1.0 {
|
||||
c.ceil() as i32 - 1
|
||||
} else {
|
||||
c.floor() as i32 - 1
|
||||
}
|
||||
}
|
||||
Err(s) => return s,
|
||||
};
|
||||
width = reference.right.column - reference.left.column;
|
||||
} else if l == 5 {
|
||||
height = match self.get_number(&args[3], cell) {
|
||||
Ok(c) => {
|
||||
if c < 1.0 {
|
||||
c.ceil() as i32 - 1
|
||||
} else {
|
||||
c.floor() as i32 - 1
|
||||
}
|
||||
}
|
||||
Err(s) => return s,
|
||||
};
|
||||
width = match self.get_number(&args[4], cell) {
|
||||
Ok(c) => {
|
||||
if c < 1.0 {
|
||||
c.ceil() as i32 - 1
|
||||
} else {
|
||||
c.floor() as i32 - 1
|
||||
}
|
||||
}
|
||||
Err(s) => return s,
|
||||
};
|
||||
} else {
|
||||
width = reference.right.column - reference.left.column;
|
||||
height = reference.right.row - reference.left.row;
|
||||
}
|
||||
// This is what Excel does
|
||||
if width == -1 || height == -1 {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Invalid reference".to_string(),
|
||||
};
|
||||
}
|
||||
// NB: Excel documentation says that negative values of width and height are not valid
|
||||
// but in practice they are valid. We follow the documentation and not Excel
|
||||
if width < -1 || height < -1 {
|
||||
return CalcResult::Error {
|
||||
error: Error::VALUE,
|
||||
origin: cell,
|
||||
message: "width and height cannot be negative".to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let column_end = column_start + width;
|
||||
let row_end = row_start + height;
|
||||
if row_start < 1 || row_end > LAST_ROW || column_start < 1 || column_end > LAST_COLUMN {
|
||||
return CalcResult::Error {
|
||||
error: Error::REF,
|
||||
origin: cell,
|
||||
message: "Invalid reference".to_string(),
|
||||
};
|
||||
}
|
||||
let left = CellReference {
|
||||
sheet: reference.left.sheet,
|
||||
row: row_start,
|
||||
column: column_start,
|
||||
};
|
||||
let right = CellReference {
|
||||
sheet: reference.right.sheet,
|
||||
row: row_end,
|
||||
column: column_end,
|
||||
};
|
||||
CalcResult::Range { left, right }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user