use crate::DataValidation;
use umya_spreadsheet as u;
#[derive(Debug)]
pub(super) struct CellValidation(pub(super) a1::Address, pub(super) DataValidation);
macro_rules! custom_validation {
($v:ident, $type:ident, $formula1:expr, $prompt:expr) => {
$v.set_formula1($formula1.to_string())
.set_show_input_message(true)
.set_show_error_message(true)
.set_type(u::DataValidationValues::$type)
.set_prompt($prompt)
};
($v:ident, $type:ident, $formula1:expr, $formula2:expr, $prompt:expr) => {
custom_validation!($v, $type, $formula1, $prompt).set_formula2($formula2.to_string())
};
}
macro_rules! validation {
($v:ident, $type:ident, $op:ident, $formula1:expr, $prompt:expr) => {
custom_validation!($v, $type, $formula1, $prompt)
.set_operator(u::DataValidationOperatorValues::$op)
};
($v:ident, $type:ident, $op:ident, $formula1:expr, $formula2:expr, $prompt:expr) => {
custom_validation!($v, $type, $formula1, $formula2, $prompt)
.set_operator(u::DataValidationOperatorValues::$op)
};
}
macro_rules! date_validation {
($v:ident, $op:ident, $formula1:ident, $prompt:expr) => {
validation!($v, Date, $op, $formula1.distance_from_epoch(), $prompt)
};
($v:ident, $op:ident, $formula1:ident, $formula2:ident, $prompt:expr) => {
validation!(
$v,
Date,
$op,
$formula1.distance_from_epoch(),
$formula2.distance_from_epoch(),
$prompt
)
};
}
impl From<CellValidation> for u::DataValidation {
#[allow(clippy::too_many_lines)]
fn from(CellValidation(position, dv): CellValidation) -> Self {
let mut sqref = u::SequenceOfReferences::default();
sqref.set_sqref(position.to_string());
let mut v = u::DataValidation::default();
v.set_sequence_of_references(sqref);
match dv {
DataValidation::Custom(c) => {
custom_validation!(v, Custom, c, format!("Custom formula: {c}"))
}
DataValidation::DateAfter(d) => {
date_validation!(v, GreaterThan, d, format!("Date after {d}"))
}
DataValidation::DateBefore(d) => {
date_validation!(v, LessThan, d, format!("Date before {d}"))
}
DataValidation::DateBetween(d1, d2) => {
date_validation!(v, Between, d1, d2, format!("Date between {d1} and {d2}"))
}
DataValidation::DateEqualTo(d) => {
date_validation!(v, Equal, d, format!("Date equal to {d}"))
}
DataValidation::DateIsValid => {
let f = format!("=ISNUMBER(DAY({position}))");
custom_validation!(v, Custom, f, "Date is valid")
}
DataValidation::DateNotBetween(d1, d2) => {
date_validation!(
v,
NotBetween,
d1,
d2,
format!("Date not between {d1} and {d2}")
)
}
DataValidation::DateOnOrAfter(d) => {
date_validation!(v, GreaterThanOrEqual, d, format!("Date on or after {d}"))
}
DataValidation::DateOnOrBefore(d) => {
date_validation!(v, LessThanOrEqual, d, format!("Date on or before {d}"))
}
DataValidation::NumberBetween(n1, n2) => {
validation!(
v,
Decimal,
Between,
n1,
n2,
format!("Number between {n1} and {n2}")
)
}
DataValidation::NumberEqualTo(n) => {
validation!(v, Decimal, Equal, n, format!("Number equal to {n}"))
}
DataValidation::NumberGreaterThan(n) => {
validation!(
v,
Decimal,
GreaterThan,
n,
format!("Number greater than {n}")
)
}
DataValidation::NumberGreaterThanOrEqualTo(n) => {
validation!(
v,
Decimal,
GreaterThanOrEqual,
n,
format!("Number greater than or equal to {n}")
)
}
DataValidation::NumberLessThan(n) => {
validation!(v, Decimal, LessThan, n, format!("Number less than {n}"))
}
DataValidation::NumberLessThanOrEqualTo(n) => {
validation!(
v,
Decimal,
LessThanOrEqual,
n,
format!("Number less than or equal to {n}")
)
}
DataValidation::NumberNotBetween(n1, n2) => {
validation!(
v,
Decimal,
NotBetween,
n1,
n2,
format!("Number not between {n1} and {n2}")
)
}
DataValidation::NumberNotEqualTo(n) => {
validation!(v, Decimal, NotEqual, n, format!("Number not equal to {n}"))
}
DataValidation::TextContains(t) => {
let f = format!("=ISNUMBER(SEARCH(\"{t}\", {position}))");
custom_validation!(v, Custom, f, format!("Text contains \"{t}\""))
}
DataValidation::TextDoesNotContain(t) => {
let f = format!("=NOT(ISNUMBER(SEARCH(\"{t}\", {position})))");
custom_validation!(v, Custom, f, format!("Text does not contain \"{t}\""))
}
DataValidation::TextEqualTo(t) => {
let f = format!("{position} = \"{t}\"");
custom_validation!(v, Custom, f, format!("Text equal to \"{t}\""))
}
DataValidation::TextIsValidEmail => {
let f = format!("=ISNUMBER(MATCH(\"*@*.?*\", {position}, 0))");
custom_validation!(v, Custom, f, "Text is valid email")
}
DataValidation::TextIsValidUrl => {
let f = format!("=RegExpMatch({position}, \"^http\")");
custom_validation!(v, Custom, f, "Text is valid URL")
}
DataValidation::ValueInList(values) => {
let list_as_string = format!(
"\"{}\"",
&values
.into_iter()
.map(|v| v.to_string().replace('"', ""))
.collect::<Vec<String>>()
.join(", ")
);
validation!(
v,
List,
Equal,
list_as_string,
format!("Value in list: {list_as_string}")
)
}
DataValidation::ValueInRange(a1) => {
validation!(v, List, Equal, a1, format!("Value in range {a1}"))
}
};
v
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::*;
#[test]
fn custom() {
let cv: CellValidation = CellValidation(
a1::Address::new(0, 0),
DataValidation::Custom("foo".to_string()),
);
let dv: u::DataValidation = cv.into();
assert!(dv.get_show_input_message());
assert_eq!(dv.get_prompt(), "Custom formula: foo");
assert_eq!(dv.get_formula1(), "foo");
}
#[test]
fn value_in_list() {
let cv: CellValidation = CellValidation(
a1::Address::new(0, 0),
DataValidation::ValueInList(vec![
Node::reference("foo").into(),
Node::reference("foo bar").into(),
]),
);
let dv: u::DataValidation = cv.into();
assert!(dv.get_show_input_message());
assert_eq!(dv.get_prompt(), "Value in list: \"foo, foo bar\"");
assert_eq!(dv.get_formula1(), "\"foo, foo bar\"");
}
}