use crate::error::{Error, Result};
use crate::pack;
use crate::style::{Style, StyleId, StyleManager};
use crate::worksheet::Worksheet;
use std::path::Path;
#[derive(Debug)]
pub struct Workbook {
pub(crate) sheets: Vec<Worksheet>,
pub(crate) active_sheet: usize,
pub(crate) style_manager: StyleManager,
pub(crate) title: Option<String>,
pub(crate) creator: Option<String>,
pub(crate) next_sheet_id: u32,
}
impl Default for Workbook {
fn default() -> Self {
let mut wb = Self {
sheets: Vec::new(),
active_sheet: 0,
style_manager: StyleManager::default(),
title: None,
creator: Some("openprxl".to_string()),
next_sheet_id: 1,
};
wb.create_sheet("Sheet").unwrap();
wb
}
}
impl Workbook {
pub fn new() -> Self {
Self::default()
}
pub fn empty() -> Self {
Self {
sheets: Vec::new(),
active_sheet: 0,
style_manager: StyleManager::default(),
title: None,
creator: Some("openprxl".to_string()),
next_sheet_id: 1,
}
}
pub fn register_style(&mut self, style: Style) -> StyleId {
self.style_manager.register(style)
}
pub fn style(&self, id: StyleId) -> Option<&Style> {
self.style_manager.get(id)
}
pub fn create_sheet<S: Into<String>>(&mut self, name: S) -> Result<&mut Worksheet> {
let name = name.into();
validate_sheet_name(&name)?;
if self.sheets.iter().any(|s| s.name == name) {
return Err(Error::InvalidWorksheetName(format!(
"worksheet name already exists: {name}"
)));
}
let id = self.next_sheet_id;
self.next_sheet_id += 1;
self.sheets.push(Worksheet::new(name, id));
let idx = self.sheets.len() - 1;
self.active_sheet = idx;
Ok(&mut self.sheets[idx])
}
pub fn sheet_count(&self) -> usize {
self.sheets.len()
}
pub fn sheet(&self, index: usize) -> Option<&Worksheet> {
self.sheets.get(index)
}
pub fn sheet_mut(&mut self, index: usize) -> Option<&mut Worksheet> {
self.sheets.get_mut(index)
}
pub fn sheet_by_name(&self, name: &str) -> Option<&Worksheet> {
self.sheets.iter().find(|s| s.name == name)
}
pub fn sheet_by_name_mut(&mut self, name: &str) -> Option<&mut Worksheet> {
self.sheets.iter_mut().find(|s| s.name == name)
}
pub fn active_sheet(&self) -> &Worksheet {
&self.sheets[self.active_sheet]
}
pub fn active_sheet_mut(&mut self) -> &mut Worksheet {
let idx = self.active_sheet;
&mut self.sheets[idx]
}
pub fn set_active_sheet(&mut self, index: usize) -> Result<()> {
if index >= self.sheets.len() {
return Err(Error::WorksheetNotFound(format!(
"index {index} out of range"
)));
}
self.active_sheet = index;
Ok(())
}
pub fn remove_sheet(&mut self, index: usize) -> Result<Worksheet> {
if index >= self.sheets.len() {
return Err(Error::WorksheetNotFound(format!(
"index {index} out of range"
)));
}
let sheet = self.sheets.remove(index);
if self.active_sheet >= self.sheets.len() && !self.sheets.is_empty() {
self.active_sheet = self.sheets.len() - 1;
}
Ok(sheet)
}
pub fn sheets(&self) -> &[Worksheet] {
&self.sheets
}
pub fn sheets_mut(&mut self) -> &mut [Worksheet] {
&mut self.sheets
}
pub fn set_title<S: Into<String>>(&mut self, title: S) {
self.title = Some(title.into());
}
pub fn title(&self) -> Option<&str> {
self.title.as_deref()
}
pub fn set_creator<S: Into<String>>(&mut self, creator: S) {
self.creator = Some(creator.into());
}
pub fn creator(&self) -> Option<&str> {
self.creator.as_deref()
}
pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
pack::write_workbook(self, path.as_ref())
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
pack::read_workbook(path.as_ref())
}
}
fn validate_sheet_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(Error::InvalidWorksheetName(
"worksheet name cannot be empty".to_string(),
));
}
if name.len() > 31 {
return Err(Error::InvalidWorksheetName(format!(
"worksheet name too long: {name}"
)));
}
let forbidden = ['/', '\\', '?', '*', ':', '[', ']'];
if name.chars().any(|c| forbidden.contains(&c)) {
return Err(Error::InvalidWorksheetName(format!(
"worksheet name contains invalid characters: {name}"
)));
}
Ok(())
}