use crate::DecimalSeparator;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CellAnalysis {
Point,
Comma,
Either,
None,
}
pub fn analyze_cell(txt: &str) -> CellAnalysis {
let has_space = txt.contains(' ');
let has_apostrophe = txt.contains('\'');
let has_middle_dot = txt.contains('·');
let num_dots = txt.matches('.').count();
let num_commas = txt.matches(',').count();
match (
num_dots,
num_commas,
has_space,
has_apostrophe,
has_middle_dot,
) {
(0, 1, true, _, _) => CellAnalysis::Comma, (1, 0, _, true, _) => CellAnalysis::Point, (1, 0, _, _, true) => CellAnalysis::Point,
(0, 0, _, _, _) => CellAnalysis::None,
(d, 0, _, _, _) if d > 1 => CellAnalysis::Point, (0, c, _, _, _) if c > 1 => CellAnalysis::Comma,
(d, c, _, _, _) if d > 0 && c > 0 => {
if let (Some(dot_pos), Some(comma_pos)) = (txt.find('.'), txt.find(',')) {
if dot_pos < comma_pos {
CellAnalysis::Comma } else {
CellAnalysis::Point }
} else {
CellAnalysis::Either
}
}
(1, 0, _, _, _) => digits_after_separator(txt, '.'),
(0, 1, _, _, _) => digits_after_separator(txt, ','),
_ => CellAnalysis::Either,
}
}
fn digits_after_separator(txt: &str, sep: char) -> CellAnalysis {
if let Some(pos) = txt.rfind(sep) {
let after = txt.len() - pos - 1;
match after {
0 => CellAnalysis::Either, 1 | 2 => {
if sep == ',' {
CellAnalysis::Comma
} else {
CellAnalysis::Point
}
}
3 => CellAnalysis::Either, 4.. => {
if sep == ',' {
CellAnalysis::Comma
} else {
CellAnalysis::Point
}
}
}
} else {
CellAnalysis::Either
}
}
pub fn detect_column_format(cells: &[&str], max_scan: usize) -> DecimalSeparator {
let mut point_votes = 0;
let mut comma_votes = 0;
for cell in cells.iter().take(max_scan) {
match analyze_cell(cell) {
CellAnalysis::Point => {
point_votes += 1;
if comma_votes == 0 && point_votes >= 3 {
return DecimalSeparator::Point;
}
}
CellAnalysis::Comma => {
comma_votes += 1;
if point_votes == 0 && comma_votes >= 3 {
return DecimalSeparator::Comma;
}
}
CellAnalysis::Either | CellAnalysis::None => {} }
}
match (comma_votes, point_votes) {
(c, p) if c > p => DecimalSeparator::Comma,
(c, p) if p > c => DecimalSeparator::Point,
_ => DecimalSeparator::Auto,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analyze_cell_clear_comma() {
assert_eq!(analyze_cell("1 234,56"), CellAnalysis::Comma); assert_eq!(analyze_cell("1234,56"), CellAnalysis::Comma); }
#[test]
fn test_analyze_cell_clear_point() {
assert_eq!(analyze_cell("1'234.56"), CellAnalysis::Point); assert_eq!(analyze_cell("1234.56"), CellAnalysis::Point); }
#[test]
fn test_analyze_cell_ambiguous() {
assert_eq!(analyze_cell("123,456"), CellAnalysis::Either); assert_eq!(analyze_cell("1,234"), CellAnalysis::Either); }
#[test]
fn test_analyze_cell_multiple_separators() {
assert_eq!(analyze_cell("1.234.567"), CellAnalysis::Point); assert_eq!(analyze_cell("1,234,567"), CellAnalysis::Comma); assert_eq!(analyze_cell("1.234,56"), CellAnalysis::Comma); assert_eq!(analyze_cell("1,234.56"), CellAnalysis::Point); }
#[test]
fn test_detect_column_format_clear_comma() {
let cells = vec!["1 234,56", "2 345,78", "3 456,90"];
assert_eq!(detect_column_format(&cells, 10), DecimalSeparator::Comma);
}
#[test]
fn test_detect_column_format_clear_point() {
let cells = vec!["1'234.56", "2'345.78", "3'456.90"];
assert_eq!(detect_column_format(&cells, 10), DecimalSeparator::Point);
}
#[test]
fn test_detect_column_format_with_ambiguous() {
let cells = vec!["1 234,56", "123,456", "2 345,78"];
assert_eq!(detect_column_format(&cells, 10), DecimalSeparator::Comma);
}
#[test]
fn test_detect_column_format_stops_early() {
let cells = vec!["1 234,56", "2 345,78", "999"]; assert_eq!(detect_column_format(&cells, 10), DecimalSeparator::Comma);
}
}