use std::iter::successors;
use fancy_regex::Regex;
fn index_to_alpha(index: u32) -> String {
assert!(index >= 1, "Index cannot be less than one.");
const BASE_CHAR_CODE: u32 = 'A' as u32;
successors(Some(index - 1), |index| match index / 26u32 {
0 => None,
n => Some(n - 1),
})
.map(|v| BASE_CHAR_CODE + (v % 26))
.collect::<Vec<u32>>()
.into_iter()
.rev()
.map(|v| char::from_u32(v).unwrap())
.collect()
}
fn alpha_to_index<S>(alpha: S) -> u32
where
S: AsRef<str>,
{
const BASE_CHAR_CODE: u32 = 'A' as u32;
const POSITIONAL_CONSTANTS: [u32; 3] = [1, 26, 676];
alpha
.as_ref()
.to_uppercase()
.chars()
.rev()
.enumerate()
.map(|(index, v)| {
let vn = (v as u32 - BASE_CHAR_CODE) + 1;
POSITIONAL_CONSTANTS[index] * vn
})
.sum::<u32>()
}
#[inline]
pub fn column_index_from_string<S: AsRef<str>>(column: S) -> u32 {
let column_c = column.as_ref();
if column_c == "0" {
return 0;
}
alpha_to_index(column_c)
}
#[inline]
pub fn string_from_column_index(column_index: &u32) -> String {
assert!(column_index >= &1u32, "Column number starts from 1.");
index_to_alpha(*column_index)
}
pub fn index_from_coordinate<T>(coordinate: T) -> CellIndex
where
T: AsRef<str>,
{
lazy_static! {
static ref RE: Regex = Regex::new(r"((\$)?([A-Z]{1,3}))?((\$)?([0-9]+))?").unwrap();
}
let caps = RE.captures(coordinate.as_ref()).ok().flatten();
caps.map(|v| {
let col = v.get(3).map(|v| alpha_to_index(v.as_str())); let row = v.get(6).and_then(|v| v.as_str().parse::<u32>().ok());
let col_lock_flg = col.map(|_col| {
v.get(2).is_some() });
let row_lock_flg = row.map(|_row| {
v.get(5).is_some() });
(col, row, col_lock_flg, row_lock_flg)
})
.unwrap_or_default()
}
#[inline]
pub fn coordinate_from_index(col: &u32, row: &u32) -> String {
format!("{}{}", string_from_column_index(col), row)
}
pub fn coordinate_from_index_with_lock(
col: &u32,
row: &u32,
is_lock_col: &bool,
is_lock_row: &bool,
) -> String {
format!(
"{}{}{}{}",
if *is_lock_col { "$" } else { "" },
string_from_column_index(col),
if *is_lock_row { "$" } else { "" },
row
)
}
#[inline]
pub(crate) fn adjustment_insert_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> u32 {
if (num >= root_num && offset_num != &0) {
num + offset_num
} else {
*num
}
}
#[inline]
pub(crate) fn adjustment_remove_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> u32 {
if (num >= root_num && offset_num != &0) {
num - offset_num
} else {
*num
}
}
#[inline]
pub(crate) fn is_remove_coordinate(num: &u32, root_num: &u32, offset_num: &u32) -> bool {
if root_num != &0 && offset_num != &0 {
return num >= root_num && num < &(root_num + offset_num);
}
false
}
pub type CellIndex = (Option<u32>, Option<u32>, Option<bool>, Option<bool>);
#[derive(Clone, Debug)]
pub struct CellCoordinates {
pub row: u32,
pub col: u32,
}
impl CellCoordinates {
#[inline]
fn new(col: u32, row: u32) -> Self {
CellCoordinates { row, col }
}
}
impl From<(u32, u32)> for CellCoordinates {
#[inline]
fn from(value: (u32, u32)) -> Self {
CellCoordinates::new(value.0, value.1)
}
}
impl From<(&u32, &u32)> for CellCoordinates {
#[inline]
fn from(value: (&u32, &u32)) -> Self {
CellCoordinates::new(*value.0, *value.1)
}
}
impl From<String> for CellCoordinates {
#[inline]
fn from(value: String) -> Self {
let str_ref: &str = value.as_ref();
str_ref.into()
}
}
impl From<&str> for CellCoordinates {
#[inline]
fn from(value: &str) -> Self {
let (col, row, ..) = index_from_coordinate(value.to_uppercase());
CellCoordinates::new(col.unwrap(), row.unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn column_index_from_string_1() {
assert_eq!(column_index_from_string("A"), 1);
assert_eq!(column_index_from_string("B"), 2);
assert_eq!(column_index_from_string("Z"), 26);
assert_eq!(column_index_from_string("AA"), 27);
assert_eq!(column_index_from_string("AB"), 28);
assert_eq!(column_index_from_string("BA"), 53);
assert_eq!(column_index_from_string("ZZ"), 702);
assert_eq!(column_index_from_string("AAA"), 703);
assert_eq!(column_index_from_string("LAV"), 8160);
assert_eq!(column_index_from_string("XFD"), 16384); }
#[test]
fn string_from_column_index_1() {
assert_eq!(string_from_column_index(&1), String::from("A"));
assert_eq!(string_from_column_index(&26), String::from("Z"));
assert_eq!(string_from_column_index(&27), String::from("AA"));
assert_eq!(string_from_column_index(&28), String::from("AB"));
assert_eq!(string_from_column_index(&53), String::from("BA"));
assert_eq!(string_from_column_index(&702), String::from("ZZ"));
assert_eq!(string_from_column_index(&703), String::from("AAA"));
assert_eq!(string_from_column_index(&8160), String::from("LAV"));
assert_eq!(string_from_column_index(&16384), String::from("XFD"));
}
#[test]
fn index_from_coordinate_1() {
assert_eq!(
index_from_coordinate("$A$4"),
(Some(1), Some(4), Some(true), Some(true))
);
assert_eq!(
index_from_coordinate("$A4"),
(Some(1), Some(4), Some(true), Some(false))
);
assert_eq!(
index_from_coordinate("A4"),
(Some(1), Some(4), Some(false), Some(false))
);
assert_eq!(
index_from_coordinate("Z91"),
(Some(26), Some(91), Some(false), Some(false))
);
assert_eq!(
index_from_coordinate("AA91"),
(Some(27), Some(91), Some(false), Some(false))
);
assert_eq!(
index_from_coordinate("AA$91"),
(Some(27), Some(91), Some(false), Some(true))
);
assert_eq!(
index_from_coordinate("$AA91"),
(Some(27), Some(91), Some(true), Some(false))
);
assert_eq!(
index_from_coordinate("$AA$91"),
(Some(27), Some(91), Some(true), Some(true))
);
assert_eq!(
index_from_coordinate("A"),
(Some(1), None, Some(false), None)
);
assert_eq!(
index_from_coordinate("$A"),
(Some(1), None, Some(true), None)
);
assert_eq!(
index_from_coordinate("5"),
(None, Some(5), None, Some(false))
);
assert_eq!(
index_from_coordinate("$5"),
(None, Some(5), None, Some(true))
);
}
}