#![warn(missing_docs)]
mod tests;
#[cfg(feature = "chrono")]
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
use crate::{ExcelDateTime, Formula, IntoExcelDateTime, XlsxError};
use std::fmt;
#[derive(Clone)]
pub struct DataValidation {
pub(crate) validation_type: DataValidationType,
pub(crate) rule: DataValidationRuleInternal,
pub(crate) ignore_blank: bool,
pub(crate) show_input_message: bool,
pub(crate) show_error_message: bool,
pub(crate) show_dropdown: bool,
pub(crate) multi_range: String,
pub(crate) input_title: String,
pub(crate) error_title: String,
pub(crate) input_message: String,
pub(crate) error_message: String,
pub(crate) error_style: DataValidationErrorStyle,
}
impl DataValidation {
#[allow(clippy::new_without_default)]
pub fn new() -> DataValidation {
DataValidation {
validation_type: DataValidationType::Any,
rule: DataValidationRuleInternal::EqualTo(String::new()),
ignore_blank: true,
show_input_message: true,
show_error_message: true,
show_dropdown: true,
multi_range: String::new(),
input_title: String::new(),
error_title: String::new(),
input_message: String::new(),
error_message: String::new(),
error_style: DataValidationErrorStyle::Stop,
}
}
pub fn allow_whole_number(mut self, rule: DataValidationRule<i32>) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Whole;
self
}
pub fn allow_whole_number_formula(
mut self,
rule: DataValidationRule<Formula>,
) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Whole;
self
}
pub fn allow_decimal_number(mut self, rule: DataValidationRule<f64>) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Decimal;
self
}
pub fn allow_decimal_number_formula(
mut self,
rule: DataValidationRule<Formula>,
) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Decimal;
self
}
pub fn allow_list_strings(
mut self,
list: &[impl AsRef<str>],
) -> Result<DataValidation, XlsxError> {
let joined_list = list
.iter()
.map(|s| s.as_ref().to_string().replace('"', "\"\""))
.collect::<Vec<String>>()
.join(",");
let length = joined_list.chars().count();
if length > 255 {
return Err(XlsxError::DataValidationError(
format!("Validation list length '{length}' including commas is greater than Excel's limit of 255 characters: {joined_list}")
));
}
let joined_list = format!("\"{joined_list}\"");
self.rule = DataValidationRuleInternal::ListSource(joined_list);
self.validation_type = DataValidationType::List;
Ok(self)
}
pub fn allow_list_formula(mut self, list: Formula) -> DataValidation {
let formula = list.formula_string.clone();
self.rule = DataValidationRuleInternal::ListSource(formula);
self.validation_type = DataValidationType::List;
self
}
pub fn allow_date(
mut self,
rule: DataValidationRule<impl IntoExcelDateTime + IntoDataValidationValue>,
) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Date;
self
}
pub fn allow_date_formula(mut self, rule: DataValidationRule<Formula>) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Date;
self
}
pub fn allow_time(
mut self,
rule: DataValidationRule<impl IntoExcelDateTime + IntoDataValidationValue>,
) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Time;
self
}
pub fn allow_time_formula(mut self, rule: DataValidationRule<Formula>) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::Time;
self
}
pub fn allow_text_length(mut self, rule: DataValidationRule<u32>) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::TextLength;
self
}
pub fn allow_text_length_formula(
mut self,
rule: DataValidationRule<Formula>,
) -> DataValidation {
self.rule = rule.to_internal_rule();
self.validation_type = DataValidationType::TextLength;
self
}
pub fn allow_custom(mut self, rule: Formula) -> DataValidation {
let formula = rule.formula_string.clone();
self.rule = DataValidationRuleInternal::CustomFormula(formula);
self.validation_type = DataValidationType::Custom;
self
}
pub fn allow_any_value(mut self) -> DataValidation {
self.rule = DataValidationRuleInternal::EqualTo(String::new());
self.validation_type = DataValidationType::Any;
self
}
pub fn ignore_blank(mut self, enable: bool) -> DataValidation {
self.ignore_blank = enable;
self
}
pub fn show_dropdown(mut self, enable: bool) -> DataValidation {
self.show_dropdown = enable;
self
}
pub fn show_input_message(mut self, enable: bool) -> DataValidation {
self.show_input_message = enable;
self
}
pub fn set_input_title(mut self, text: impl Into<String>) -> Result<DataValidation, XlsxError> {
let text = text.into();
let length = text.chars().count();
if length > 32 {
return Err(XlsxError::DataValidationError(format!(
"Validation title length '{length}' greater than Excel's limit of 32 characters."
)));
}
self.input_title = text;
Ok(self)
}
pub fn set_input_message(
mut self,
text: impl Into<String>,
) -> Result<DataValidation, XlsxError> {
let text = text.into();
let length = text.chars().count();
if length > 255 {
return Err(XlsxError::DataValidationError(format!(
"Validation message length '{length}' greater than Excel's limit of 255 characters."
)));
}
self.input_message = text;
Ok(self)
}
pub fn show_error_message(mut self, enable: bool) -> DataValidation {
self.show_error_message = enable;
self
}
pub fn set_error_title(mut self, text: impl Into<String>) -> Result<DataValidation, XlsxError> {
let text = text.into();
let length = text.chars().count();
if length > 32 {
return Err(XlsxError::DataValidationError(format!(
"Validation title length '{length}' greater than Excel's limit of 32 characters."
)));
}
self.error_title = text;
Ok(self)
}
pub fn set_error_message(
mut self,
text: impl Into<String>,
) -> Result<DataValidation, XlsxError> {
let text = text.into();
let length = text.chars().count();
if length > 255 {
return Err(XlsxError::DataValidationError(format!(
"Validation message length '{length}' greater than Excel's limit of 255 characters."
)));
}
self.error_message = text;
Ok(self)
}
pub fn set_error_style(mut self, error_style: DataValidationErrorStyle) -> DataValidation {
self.error_style = error_style;
self
}
pub fn set_multi_range(mut self, range: impl Into<String>) -> DataValidation {
self.multi_range = range.into().replace('$', "").replace(',', " ");
self
}
pub(crate) fn is_invalid_any(&mut self) -> bool {
self.validation_type == DataValidationType::Any
&& self.input_title.is_empty()
&& self.input_message.is_empty()
&& self.error_title.is_empty()
&& self.error_message.is_empty()
}
}
pub trait IntoDataValidationValue {
fn to_string_value(&self) -> String;
}
impl IntoDataValidationValue for i32 {
fn to_string_value(&self) -> String {
self.to_string()
}
}
impl IntoDataValidationValue for u32 {
fn to_string_value(&self) -> String {
self.to_string()
}
}
impl IntoDataValidationValue for f64 {
fn to_string_value(&self) -> String {
self.to_string()
}
}
impl IntoDataValidationValue for Formula {
fn to_string_value(&self) -> String {
self.formula_string.clone()
}
}
impl IntoDataValidationValue for ExcelDateTime {
fn to_string_value(&self) -> String {
self.to_excel().to_string()
}
}
impl IntoDataValidationValue for &ExcelDateTime {
fn to_string_value(&self) -> String {
self.to_excel().to_string()
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoDataValidationValue for NaiveDateTime {
fn to_string_value(&self) -> String {
ExcelDateTime::chrono_datetime_to_excel(self).to_string()
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoDataValidationValue for &NaiveDateTime {
fn to_string_value(&self) -> String {
ExcelDateTime::chrono_datetime_to_excel(self).to_string()
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoDataValidationValue for NaiveDate {
fn to_string_value(&self) -> String {
ExcelDateTime::chrono_date_to_excel(self).to_string()
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoDataValidationValue for &NaiveDate {
fn to_string_value(&self) -> String {
ExcelDateTime::chrono_date_to_excel(self).to_string()
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoDataValidationValue for NaiveTime {
fn to_string_value(&self) -> String {
ExcelDateTime::chrono_time_to_excel(self).to_string()
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoDataValidationValue for &NaiveTime {
fn to_string_value(&self) -> String {
ExcelDateTime::chrono_time_to_excel(self).to_string()
}
}
#[cfg(feature = "jiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl IntoDataValidationValue for jiff::civil::DateTime {
fn to_string_value(&self) -> String {
ExcelDateTime::jiff_datetime_to_excel(self).to_string()
}
}
#[cfg(feature = "jiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl IntoDataValidationValue for jiff::civil::Date {
fn to_string_value(&self) -> String {
ExcelDateTime::jiff_date_to_excel(self).to_string()
}
}
#[cfg(feature = "jiff")]
#[cfg_attr(docsrs, doc(cfg(feature = "jiff")))]
impl IntoDataValidationValue for jiff::civil::Time {
fn to_string_value(&self) -> String {
ExcelDateTime::jiff_time_to_excel(self).to_string()
}
}
#[derive(Clone, Eq, PartialEq)]
pub(crate) enum DataValidationType {
Whole,
Decimal,
Date,
Time,
TextLength,
Custom,
List,
Any,
}
impl fmt::Display for DataValidationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Any => write!(f, "any"),
Self::Date => write!(f, "date"),
Self::List => write!(f, "list"),
Self::Time => write!(f, "time"),
Self::Whole => write!(f, "whole"),
Self::Custom => write!(f, "custom"),
Self::Decimal => write!(f, "decimal"),
Self::TextLength => write!(f, "textLength"),
}
}
}
#[derive(Clone)]
pub enum DataValidationRule<T: IntoDataValidationValue> {
EqualTo(T),
NotEqualTo(T),
GreaterThan(T),
GreaterThanOrEqualTo(T),
LessThan(T),
LessThanOrEqualTo(T),
Between(T, T),
NotBetween(T, T),
}
impl<T: IntoDataValidationValue> DataValidationRule<T> {
fn to_internal_rule(&self) -> DataValidationRuleInternal {
match &self {
DataValidationRule::EqualTo(value) => {
DataValidationRuleInternal::EqualTo(value.to_string_value())
}
DataValidationRule::NotEqualTo(value) => {
DataValidationRuleInternal::NotEqualTo(value.to_string_value())
}
DataValidationRule::GreaterThan(value) => {
DataValidationRuleInternal::GreaterThan(value.to_string_value())
}
DataValidationRule::GreaterThanOrEqualTo(value) => {
DataValidationRuleInternal::GreaterThanOrEqualTo(value.to_string_value())
}
DataValidationRule::LessThan(value) => {
DataValidationRuleInternal::LessThan(value.to_string_value())
}
DataValidationRule::LessThanOrEqualTo(value) => {
DataValidationRuleInternal::LessThanOrEqualTo(value.to_string_value())
}
DataValidationRule::Between(min, max) => {
DataValidationRuleInternal::Between(min.to_string_value(), max.to_string_value())
}
DataValidationRule::NotBetween(min, max) => {
DataValidationRuleInternal::NotBetween(min.to_string_value(), max.to_string_value())
}
}
}
}
#[derive(Clone)]
pub(crate) enum DataValidationRuleInternal {
EqualTo(String),
NotEqualTo(String),
GreaterThan(String),
GreaterThanOrEqualTo(String),
LessThan(String),
LessThanOrEqualTo(String),
Between(String, String),
NotBetween(String, String),
CustomFormula(String),
ListSource(String),
}
impl fmt::Display for DataValidationRuleInternal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EqualTo(_) => write!(f, "equal"),
Self::LessThan(_) => write!(f, "lessThan"),
Self::Between(_, _) => write!(f, "between"),
Self::ListSource(_) => write!(f, "list"),
Self::NotEqualTo(_) => write!(f, "notEqual"),
Self::GreaterThan(_) => write!(f, "greaterThan"),
Self::CustomFormula(_) => write!(f, ""),
Self::NotBetween(_, _) => write!(f, "notBetween"),
Self::LessThanOrEqualTo(_) => write!(f, "lessThanOrEqual"),
Self::GreaterThanOrEqualTo(_) => write!(f, "greaterThanOrEqual"),
}
}
}
#[derive(Clone)]
pub enum DataValidationErrorStyle {
Stop,
Warning,
Information,
}
impl fmt::Display for DataValidationErrorStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Stop => write!(f, "stop"),
Self::Warning => write!(f, "warning"),
Self::Information => write!(f, "information"),
}
}
}