pub mod translate;
use imbl::HashMap;
use logisheets_base::SheetId;
use logisheets_workbook::prelude::{CtDataValidation, CtDataValidations, StDataValidationType};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListValidation {
Inline(Vec<String>),
Reference(String),
}
pub fn list_validation(dv: &CtDataValidation) -> Option<ListValidation> {
if !matches!(dv.ty, StDataValidationType::List) {
return None;
}
let src = dv.formula1.as_ref()?.value.trim().to_string();
if src.len() >= 2 && src.starts_with('"') && src.ends_with('"') {
let inner = &src[1..src.len() - 1];
let items = inner
.split(',')
.map(|s| s.trim().replace("\"\"", "\""))
.collect();
Some(ListValidation::Inline(items))
} else {
Some(ListValidation::Reference(src))
}
}
#[derive(Debug, Clone, Default)]
pub struct DataValidationManager {
pub validations: HashMap<SheetId, CtDataValidations>,
}
impl DataValidationManager {
pub fn new() -> Self {
Self::default()
}
pub fn set_sheet(&mut self, sheet_id: SheetId, validations: CtDataValidations) {
self.validations.insert(sheet_id, validations);
}
pub fn get_sheet(&self, sheet_id: SheetId) -> Option<&CtDataValidations> {
self.validations.get(&sheet_id)
}
pub fn is_empty(&self) -> bool {
self.validations.is_empty()
}
}
#[derive(Debug, Clone, Copy)]
pub struct SqrefRange {
pub r0: usize,
pub c0: usize,
pub r1: usize,
pub c1: usize,
}
impl SqrefRange {
pub fn contains(&self, row: usize, col: usize) -> bool {
row >= self.r0 && row <= self.r1 && col >= self.c0 && col <= self.c1
}
}
pub fn parse_sqref(sqref: &str) -> Vec<SqrefRange> {
sqref
.split_whitespace()
.filter_map(parse_range_token)
.collect()
}
fn parse_range_token(tok: &str) -> Option<SqrefRange> {
let (start, end) = match tok.split_once(':') {
Some((a, b)) => (a, b),
None => (tok, tok),
};
let (r0, c0) = parse_a1(start, false);
let (r1, c1) = parse_a1(end, true);
if c0 > c1 || r0 > r1 {
return None;
}
Some(SqrefRange { r0, c0, r1, c1 })
}
fn parse_a1(s: &str, upper: bool) -> (usize, usize) {
let s = s.replace('$', "");
let letters: String = s.chars().take_while(|c| c.is_ascii_alphabetic()).collect();
let digits: String = s
.chars()
.skip_while(|c| c.is_ascii_alphabetic())
.filter(|c| c.is_ascii_digit())
.collect();
let col = if letters.is_empty() {
if upper { usize::MAX } else { 0 }
} else {
col_to_idx(&letters)
};
let row = match digits.parse::<usize>() {
Ok(r) if r >= 1 => r - 1,
_ => {
if upper {
usize::MAX
} else {
0
}
}
};
(row, col)
}
fn col_to_idx(letters: &str) -> usize {
let mut idx = 0usize;
for c in letters.chars() {
idx = idx * 26 + (c.to_ascii_uppercase() as usize - 'A' as usize + 1);
}
idx - 1
}