UPDATE: Dump of initial files
This commit is contained in:
364
base/src/units.rs
Normal file
364
base/src/units.rs
Normal file
@@ -0,0 +1,364 @@
|
||||
use crate::{
|
||||
calc_result::CellReference,
|
||||
expressions::{parser::Node, token::OpProduct},
|
||||
formatter::parser::{ParsePart, Parser},
|
||||
functions::Function,
|
||||
model::Model,
|
||||
};
|
||||
|
||||
pub enum Units {
|
||||
Number {
|
||||
group_separator: bool,
|
||||
precision: i32,
|
||||
num_fmt: String,
|
||||
},
|
||||
Currency {
|
||||
group_separator: bool,
|
||||
precision: i32,
|
||||
num_fmt: String,
|
||||
currency: String,
|
||||
},
|
||||
Percentage {
|
||||
group_separator: bool,
|
||||
precision: i32,
|
||||
num_fmt: String,
|
||||
},
|
||||
Date(String),
|
||||
}
|
||||
|
||||
impl Units {
|
||||
pub fn get_num_fmt(&self) -> String {
|
||||
match self {
|
||||
Units::Number { num_fmt, .. } => num_fmt.to_string(),
|
||||
Units::Currency { num_fmt, .. } => num_fmt.to_string(),
|
||||
Units::Percentage { num_fmt, .. } => num_fmt.to_string(),
|
||||
Units::Date(num_fmt) => num_fmt.to_string(),
|
||||
}
|
||||
}
|
||||
pub fn get_precision(&self) -> i32 {
|
||||
match self {
|
||||
Units::Number { precision, .. } => *precision,
|
||||
Units::Currency { precision, .. } => *precision,
|
||||
Units::Percentage { precision, .. } => *precision,
|
||||
Units::Date(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_units_from_format_string(num_fmt: &str) -> Option<Units> {
|
||||
let mut parser = Parser::new(num_fmt);
|
||||
parser.parse();
|
||||
// We only care about the first part (positive number)
|
||||
match &parser.parts[0] {
|
||||
ParsePart::Number(part) => {
|
||||
if part.percent > 0 {
|
||||
Some(Units::Percentage {
|
||||
num_fmt: num_fmt.to_string(),
|
||||
group_separator: part.use_thousands,
|
||||
precision: part.precision,
|
||||
})
|
||||
} else if num_fmt.contains('$') {
|
||||
Some(Units::Currency {
|
||||
num_fmt: num_fmt.to_string(),
|
||||
group_separator: part.use_thousands,
|
||||
precision: part.precision,
|
||||
currency: "$".to_string(),
|
||||
})
|
||||
} else if num_fmt.contains('€') {
|
||||
Some(Units::Currency {
|
||||
num_fmt: num_fmt.to_string(),
|
||||
group_separator: part.use_thousands,
|
||||
precision: part.precision,
|
||||
currency: "€".to_string(),
|
||||
})
|
||||
} else {
|
||||
Some(Units::Number {
|
||||
num_fmt: num_fmt.to_string(),
|
||||
group_separator: part.use_thousands,
|
||||
precision: part.precision,
|
||||
})
|
||||
}
|
||||
}
|
||||
ParsePart::Date(_) => Some(Units::Date(num_fmt.to_string())),
|
||||
ParsePart::Error(_) => None,
|
||||
ParsePart::General(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn compute_cell_units(&self, cell_reference: &CellReference) -> Option<Units> {
|
||||
let style = &self.get_style_for_cell(
|
||||
cell_reference.sheet,
|
||||
cell_reference.row,
|
||||
cell_reference.column,
|
||||
);
|
||||
get_units_from_format_string(&style.num_fmt)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_node_units(&self, node: &Node, cell: &CellReference) -> Option<Units> {
|
||||
match node {
|
||||
Node::ReferenceKind {
|
||||
sheet_name: _,
|
||||
sheet_index,
|
||||
absolute_row,
|
||||
absolute_column,
|
||||
row,
|
||||
column,
|
||||
} => {
|
||||
let mut row1 = *row;
|
||||
let mut column1 = *column;
|
||||
if !absolute_row {
|
||||
row1 += cell.row;
|
||||
}
|
||||
if !absolute_column {
|
||||
column1 += cell.column;
|
||||
}
|
||||
self.compute_cell_units(&CellReference {
|
||||
sheet: *sheet_index,
|
||||
row: row1,
|
||||
column: column1,
|
||||
})
|
||||
}
|
||||
Node::RangeKind {
|
||||
sheet_name: _,
|
||||
sheet_index,
|
||||
absolute_row1,
|
||||
absolute_column1,
|
||||
row1,
|
||||
column1,
|
||||
absolute_row2: _,
|
||||
absolute_column2: _,
|
||||
row2: _,
|
||||
column2: _,
|
||||
} => {
|
||||
// We return the unit of the first element
|
||||
let mut row1 = *row1;
|
||||
let mut column1 = *column1;
|
||||
if !absolute_row1 {
|
||||
row1 += cell.row;
|
||||
}
|
||||
if !absolute_column1 {
|
||||
column1 += cell.column;
|
||||
}
|
||||
self.compute_cell_units(&CellReference {
|
||||
sheet: *sheet_index,
|
||||
row: row1,
|
||||
column: column1,
|
||||
})
|
||||
}
|
||||
Node::OpSumKind {
|
||||
kind: _,
|
||||
left,
|
||||
right,
|
||||
} => {
|
||||
let left_units = self.compute_node_units(left, cell);
|
||||
let right_units = self.compute_node_units(right, cell);
|
||||
match (&left_units, &right_units) {
|
||||
(Some(_), None) => left_units,
|
||||
(None, Some(_)) => right_units,
|
||||
(Some(l), Some(r)) => {
|
||||
if l.get_precision() < r.get_precision() {
|
||||
right_units
|
||||
} else {
|
||||
left_units
|
||||
}
|
||||
}
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
Node::OpProductKind { kind, left, right } => {
|
||||
let left_units = self.compute_node_units(left, cell);
|
||||
let right_units = self.compute_node_units(right, cell);
|
||||
match (&left_units, &right_units) {
|
||||
(
|
||||
Some(Units::Percentage { precision: l, .. }),
|
||||
Some(Units::Percentage { precision: r, .. }),
|
||||
) => {
|
||||
if l > r {
|
||||
left_units
|
||||
} else {
|
||||
if *r > 1 {
|
||||
return right_units;
|
||||
}
|
||||
// When multiplying percentage we want at least two decimal places
|
||||
Some(Units::Percentage {
|
||||
group_separator: false,
|
||||
precision: 2,
|
||||
num_fmt: "0.00%".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
(
|
||||
Some(Units::Currency {
|
||||
currency,
|
||||
precision,
|
||||
..
|
||||
}),
|
||||
Some(Units::Percentage { .. }),
|
||||
) => {
|
||||
match kind {
|
||||
OpProduct::Divide => None,
|
||||
OpProduct::Times => {
|
||||
if *precision > 1 {
|
||||
return left_units;
|
||||
}
|
||||
// This is tricky, we need at least 2 digit precision
|
||||
// but I do not want to mess with the num_fmt string
|
||||
Some(Units::Currency {
|
||||
currency: currency.to_string(),
|
||||
group_separator: true,
|
||||
precision: 2,
|
||||
num_fmt: format!("{currency}#,##0.00"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
(
|
||||
Some(Units::Percentage { .. }),
|
||||
Some(Units::Currency {
|
||||
precision,
|
||||
currency,
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
match kind {
|
||||
OpProduct::Divide => None,
|
||||
OpProduct::Times => {
|
||||
if *precision > 1 {
|
||||
return right_units;
|
||||
}
|
||||
// This is tricky, we need at least 2 digit precision
|
||||
// but I do not want to mess with the num_fmt string
|
||||
Some(Units::Currency {
|
||||
currency: currency.to_string(),
|
||||
group_separator: true,
|
||||
precision: 2,
|
||||
num_fmt: format!("{currency}#,##0.00"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(Units::Percentage { .. }), _) => right_units,
|
||||
(_, Some(Units::Percentage { .. })) => match kind {
|
||||
OpProduct::Divide => None,
|
||||
OpProduct::Times => left_units,
|
||||
},
|
||||
(None, _) => match kind {
|
||||
OpProduct::Divide => None,
|
||||
OpProduct::Times => right_units,
|
||||
},
|
||||
(_, None) => left_units,
|
||||
(
|
||||
Some(Units::Number { precision: l, .. }),
|
||||
Some(Units::Number { precision: r, .. }),
|
||||
) => {
|
||||
if l > r {
|
||||
left_units
|
||||
} else {
|
||||
right_units
|
||||
}
|
||||
}
|
||||
(Some(Units::Number { .. }), _) => match kind {
|
||||
OpProduct::Divide => None,
|
||||
OpProduct::Times => right_units,
|
||||
},
|
||||
(_, Some(Units::Number { .. })) => left_units,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Node::FunctionKind { kind, args } => self.compute_function_units(kind, args, cell),
|
||||
Node::UnaryKind { kind: _, right } => {
|
||||
// What happens if kind => OpUnary::Percentage?
|
||||
self.compute_node_units(right, cell)
|
||||
}
|
||||
// The rest of the nodes return None
|
||||
Node::BooleanKind(_) => None,
|
||||
Node::NumberKind(_) => None,
|
||||
Node::StringKind(_) => None,
|
||||
Node::WrongReferenceKind { .. } => None,
|
||||
Node::WrongRangeKind { .. } => None,
|
||||
Node::OpRangeKind { .. } => None,
|
||||
Node::OpConcatenateKind { .. } => None,
|
||||
Node::ErrorKind(_) => None,
|
||||
Node::ParseErrorKind { .. } => None,
|
||||
Node::EmptyArgKind => None,
|
||||
Node::InvalidFunctionKind { .. } => None,
|
||||
Node::ArrayKind(_) => None,
|
||||
Node::VariableKind(_) => None,
|
||||
Node::CompareKind { .. } => None,
|
||||
Node::OpPowerKind { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_function_units(
|
||||
&self,
|
||||
kind: &Function,
|
||||
args: &[Node],
|
||||
cell: &CellReference,
|
||||
) -> Option<Units> {
|
||||
match kind {
|
||||
Function::Sum => self.units_fn_sum_like(args, cell),
|
||||
Function::Average => self.units_fn_sum_like(args, cell),
|
||||
Function::Pmt => self.units_fn_currency(args, cell),
|
||||
Function::Nper => self.units_fn_currency(args, cell),
|
||||
Function::Npv => self.units_fn_currency(args, cell),
|
||||
Function::Irr => self.units_fn_percentage(args, cell),
|
||||
Function::Mirr => self.units_fn_percentage(args, cell),
|
||||
Function::Sln => self.units_fn_currency(args, cell),
|
||||
Function::Syd => self.units_fn_currency(args, cell),
|
||||
Function::Db => self.units_fn_currency(args, cell),
|
||||
Function::Ddb => self.units_fn_currency(args, cell),
|
||||
Function::Cumipmt => self.units_fn_currency(args, cell),
|
||||
Function::Cumprinc => self.units_fn_currency(args, cell),
|
||||
Function::Tbilleq => self.units_fn_percentage_2(args, cell),
|
||||
Function::Tbillprice => self.units_fn_currency(args, cell),
|
||||
Function::Tbillyield => self.units_fn_percentage_2(args, cell),
|
||||
Function::Date => self.units_fn_dates(args, cell),
|
||||
Function::Today => self.units_fn_dates(args, cell),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn units_fn_sum_like(&self, args: &[Node], cell: &CellReference) -> Option<Units> {
|
||||
// We return the unit of the first argument
|
||||
if !args.is_empty() {
|
||||
return self.compute_node_units(&args[0], cell);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn units_fn_currency(&self, _args: &[Node], _cell: &CellReference) -> Option<Units> {
|
||||
let currency_symbol = &self.locale.currency.symbol;
|
||||
let standard_format = &self.locale.numbers.currency_formats.standard;
|
||||
let num_fmt = standard_format.replace('¤', currency_symbol);
|
||||
// The "space" in the cldr is a weird space.
|
||||
let num_fmt = num_fmt.replace(' ', " ");
|
||||
Some(Units::Currency {
|
||||
num_fmt,
|
||||
group_separator: true,
|
||||
precision: 2,
|
||||
currency: currency_symbol.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn units_fn_percentage(&self, _args: &[Node], _cell: &CellReference) -> Option<Units> {
|
||||
Some(Units::Percentage {
|
||||
group_separator: false,
|
||||
precision: 0,
|
||||
num_fmt: "0%".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn units_fn_percentage_2(&self, _args: &[Node], _cell: &CellReference) -> Option<Units> {
|
||||
Some(Units::Percentage {
|
||||
group_separator: false,
|
||||
precision: 2,
|
||||
num_fmt: "0.00%".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn units_fn_dates(&self, _args: &[Node], _cell: &CellReference) -> Option<Units> {
|
||||
// TODO: update locale and use it here
|
||||
Some(Units::Date("dd/mm/yyyy".to_string()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user