use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CellStyle {
Default = 0,
HeaderBold = 1,
NumberInteger = 2,
NumberDecimal = 3,
NumberCurrency = 4,
NumberPercentage = 5,
DateDefault = 6,
DateTimestamp = 7,
TextBold = 8,
TextItalic = 9,
HighlightYellow = 10,
HighlightGreen = 11,
HighlightRed = 12,
BorderThin = 13,
DateTimeShort = 14,
}
impl CellStyle {
pub fn index(&self) -> u32 {
*self as u32
}
}
#[derive(Debug, Clone)]
pub struct StyledCell {
pub value: CellValue,
pub style: CellStyle,
}
impl StyledCell {
pub fn new(value: CellValue, style: CellStyle) -> Self {
StyledCell { value, style }
}
pub fn default_style(value: CellValue) -> Self {
StyledCell {
value,
style: CellStyle::Default,
}
}
}
impl From<CellValue> for StyledCell {
fn from(value: CellValue) -> Self {
StyledCell::default_style(value)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum CellValue {
Empty,
String(String),
Int(i64),
Float(f64),
Bool(bool),
DateTime(f64),
Error(String),
Formula(String),
}
impl CellValue {
pub fn as_string(&self) -> String {
match self {
CellValue::Empty => String::new(),
CellValue::String(s) => s.clone(),
CellValue::Int(i) => i.to_string(),
CellValue::Float(f) => f.to_string(),
CellValue::Bool(b) => b.to_string(),
CellValue::DateTime(d) => d.to_string(),
CellValue::Error(e) => format!("ERROR: {}", e),
CellValue::Formula(f) => f.clone(),
}
}
pub fn is_empty(&self) -> bool {
matches!(self, CellValue::Empty)
}
pub fn as_i64(&self) -> Option<i64> {
match self {
CellValue::Int(i) => Some(*i),
CellValue::Float(f) => Some(*f as i64),
CellValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
CellValue::Float(f) => Some(*f),
CellValue::Int(i) => Some(*i as f64),
CellValue::DateTime(d) => Some(*d),
CellValue::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
CellValue::Bool(b) => Some(*b),
CellValue::Int(i) => Some(*i != 0),
CellValue::String(s) => match s.to_lowercase().as_str() {
"true" | "yes" | "1" => Some(true),
"false" | "no" | "0" => Some(false),
_ => None,
},
_ => None,
}
}
}
impl fmt::Display for CellValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_string())
}
}
impl From<&str> for CellValue {
fn from(s: &str) -> Self {
CellValue::String(s.to_string())
}
}
impl From<String> for CellValue {
fn from(s: String) -> Self {
CellValue::String(s)
}
}
impl From<i64> for CellValue {
fn from(i: i64) -> Self {
CellValue::Int(i)
}
}
impl From<f64> for CellValue {
fn from(f: f64) -> Self {
CellValue::Float(f)
}
}
impl From<bool> for CellValue {
fn from(b: bool) -> Self {
CellValue::Bool(b)
}
}
#[derive(Debug, Clone)]
pub struct Cell {
pub row: u32,
pub col: u32,
pub value: CellValue,
}
impl Cell {
pub fn new(row: u32, col: u32, value: CellValue) -> Self {
Cell { row, col, value }
}
pub fn reference(&self) -> String {
format!("{}{}", Self::col_to_letter(self.col), self.row + 1)
}
fn col_to_letter(col: u32) -> String {
let mut result = String::new();
let mut col = col + 1;
while col > 0 {
col -= 1;
result.insert(0, (b'A' + (col % 26) as u8) as char);
col /= 26;
}
result
}
}
#[derive(Debug, Clone)]
pub struct Row {
pub index: u32,
pub cells: Vec<CellValue>,
}
impl Row {
pub fn new(index: u32, cells: Vec<CellValue>) -> Self {
Row { index, cells }
}
pub fn get(&self, col: usize) -> Option<&CellValue> {
self.cells.get(col)
}
pub fn len(&self) -> usize {
self.cells.len()
}
pub fn is_empty(&self) -> bool {
self.cells.is_empty() || self.cells.iter().all(|c| c.is_empty())
}
pub fn to_strings(&self) -> Vec<String> {
self.cells.iter().map(|c| c.as_string()).collect()
}
}
#[derive(Debug, Clone)]
pub struct ProtectionOptions {
pub password_hash: Option<String>,
pub select_locked_cells: bool,
pub select_unlocked_cells: bool,
pub format_cells: bool,
pub format_columns: bool,
pub format_rows: bool,
pub insert_columns: bool,
pub insert_rows: bool,
pub delete_columns: bool,
pub delete_rows: bool,
pub sort: bool,
pub auto_filter: bool,
}
impl Default for ProtectionOptions {
fn default() -> Self {
ProtectionOptions {
password_hash: None,
select_locked_cells: true,
select_unlocked_cells: true,
format_cells: false,
format_columns: false,
format_rows: false,
insert_columns: false,
insert_rows: false,
delete_columns: false,
delete_rows: false,
sort: false,
auto_filter: false,
}
}
}
impl ProtectionOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_password(mut self, password: &str) -> Self {
self.password_hash = Some(Self::hash_password(password));
self
}
pub fn allow_select_locked_cells(mut self, allow: bool) -> Self {
self.select_locked_cells = allow;
self
}
pub fn allow_select_unlocked_cells(mut self, allow: bool) -> Self {
self.select_unlocked_cells = allow;
self
}
pub fn allow_format_cells(mut self, allow: bool) -> Self {
self.format_cells = allow;
self
}
pub fn allow_format_columns(mut self, allow: bool) -> Self {
self.format_columns = allow;
self
}
pub fn allow_format_rows(mut self, allow: bool) -> Self {
self.format_rows = allow;
self
}
pub fn allow_insert_columns(mut self, allow: bool) -> Self {
self.insert_columns = allow;
self
}
pub fn allow_insert_rows(mut self, allow: bool) -> Self {
self.insert_rows = allow;
self
}
pub fn allow_delete_columns(mut self, allow: bool) -> Self {
self.delete_columns = allow;
self
}
pub fn allow_delete_rows(mut self, allow: bool) -> Self {
self.delete_rows = allow;
self
}
pub fn allow_sort(mut self, allow: bool) -> Self {
self.sort = allow;
self
}
pub fn allow_auto_filter(mut self, allow: bool) -> Self {
self.auto_filter = allow;
self
}
fn hash_password(password: &str) -> String {
let mut hash: u16 = 0;
for ch in password.chars().rev() {
let val = (ch as u16).rotate_left(1);
hash ^= val;
}
hash ^= password.len() as u16;
hash ^= 0xCE4B;
format!("{:04X}", hash)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cell_reference() {
let cell = Cell::new(0, 0, CellValue::Empty);
assert_eq!(cell.reference(), "A1");
let cell = Cell::new(0, 25, CellValue::Empty);
assert_eq!(cell.reference(), "Z1");
let cell = Cell::new(0, 26, CellValue::Empty);
assert_eq!(cell.reference(), "AA1");
}
#[test]
fn test_cell_value_conversions() {
let val = CellValue::Int(42);
assert_eq!(val.as_i64(), Some(42));
assert_eq!(val.as_f64(), Some(42.0));
let val = CellValue::String("true".to_string());
assert_eq!(val.as_bool(), Some(true));
}
}