use crate::error::{ OfficeError, Result, XlsxError };
use crate::xlsx::worksheet::Worksheet;
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct WorkbookProperties {
pub title: Option<String>,
pub author: Option<String>,
pub company: Option<String>,
pub created: Option<String>,
pub modified: Option<String>,
pub version: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Workbook {
pub properties: WorkbookProperties,
worksheets: HashMap<String, Worksheet>,
worksheet_order: Vec<String>,
active_worksheet: Option<String>,
shared_strings: Vec<String>,
}
impl WorkbookProperties {
pub fn new() -> Self {
Self {
title: None,
author: None,
company: None,
created: None,
modified: None,
version: None,
}
}
pub fn with_title(mut self, title: String) -> Self {
self.title = Some(title);
self
}
pub fn with_author(mut self, author: String) -> Self {
self.author = Some(author);
self
}
pub fn with_company(mut self, company: String) -> Self {
self.company = Some(company);
self
}
pub fn with_version(mut self, version: String) -> Self {
self.version = Some(version);
self
}
}
impl Workbook {
pub fn new() -> Self {
let mut workbook = Self {
properties: WorkbookProperties::new(),
worksheets: HashMap::new(),
worksheet_order: Vec::new(),
active_worksheet: None,
shared_strings: Vec::new(),
};
workbook
.add_worksheet("Sheet1".to_string())
.map_err(|e| {
OfficeError::Xlsx(XlsxError::WorksheetCreationFailed {
name: e.to_string(),
})
})
.unwrap();
workbook
}
pub fn new_empty() -> Self {
Self {
properties: WorkbookProperties::new(),
worksheets: HashMap::new(),
worksheet_order: Vec::new(),
active_worksheet: None,
shared_strings: Vec::new(),
}
}
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut reader = crate::xlsx::XlsxReader::open(path)?;
reader.read_workbook()
}
pub fn add_worksheet(&mut self, name: String) -> Result<()> {
if self.worksheets.contains_key(&name) {
return Err(
OfficeError::Xlsx(XlsxError::WorksheetCreationFailed {
name: format!("工作表 '{}' 已存在", name),
})
);
}
let worksheet = Worksheet::new(name.clone());
self.worksheets.insert(name.clone(), worksheet);
self.worksheet_order.push(name.clone());
if self.active_worksheet.is_none() {
self.active_worksheet = Some(name);
}
Ok(())
}
pub fn new_worksheet(&mut self) -> Worksheet {
let name = self.generate_next_sheet_name();
Worksheet::new(name)
}
fn generate_next_sheet_name(&self) -> String {
let mut index = 1;
loop {
let name = format!("Sheet{}", index);
if !self.worksheets.contains_key(&name) {
return name;
}
index += 1;
}
}
pub fn add_worksheet_direct(&mut self, name: String, worksheet: Worksheet) {
self.worksheets.insert(name.clone(), worksheet);
self.worksheet_order.push(name.clone());
if self.active_worksheet.is_none() {
self.active_worksheet = Some(name);
}
}
pub fn get_worksheet(&self, name: &str) -> Option<&Worksheet> {
self.worksheets.get(name)
}
pub fn get_worksheet_mut(&mut self, name: &str) -> Option<&mut Worksheet> {
self.worksheets.get_mut(name)
}
pub fn get_worksheet_by_index(&self, index: usize) -> Option<&Worksheet> {
self.worksheet_order.get(index).and_then(|name| self.worksheets.get(name))
}
pub fn get_worksheet_by_index_mut(&mut self, index: usize) -> Option<&mut Worksheet> {
if let Some(name) = self.worksheet_order.get(index).cloned() {
self.worksheets.get_mut(&name)
} else {
None
}
}
pub fn remove_worksheet(&mut self, name: &str) -> Result<Worksheet> {
if !self.worksheets.contains_key(name) {
return Err(
OfficeError::Xlsx(XlsxError::WorksheetNotFound {
name: name.to_string(),
})
);
}
if self.worksheets.len() == 1 {
return Err(OfficeError::Structure("不能删除最后一个工作表".to_string()));
}
self.worksheet_order.retain(|n| n != name);
if self.active_worksheet.as_ref() == Some(&name.to_string()) {
self.active_worksheet = self.worksheet_order.first().cloned();
}
Ok(self.worksheets.remove(name).unwrap())
}
pub fn rename_worksheet(&mut self, old_name: &str, new_name: String) -> Result<()> {
if !self.worksheets.contains_key(old_name) {
return Err(
OfficeError::Xlsx(XlsxError::WorksheetNotFound {
name: old_name.to_string(),
})
);
}
if self.worksheets.contains_key(&new_name) {
return Err(
OfficeError::Xlsx(XlsxError::WorksheetNotFound {
name: format!("工作表 '{}' 已存在", new_name),
})
);
}
let mut worksheet = self.worksheets.remove(old_name).unwrap();
worksheet.set_name(new_name.clone());
self.worksheets.insert(new_name.clone(), worksheet);
if let Some(pos) = self.worksheet_order.iter().position(|n| n == old_name) {
self.worksheet_order[pos] = new_name.clone();
}
if self.active_worksheet.as_ref() == Some(&old_name.to_string()) {
self.active_worksheet = Some(new_name);
}
Ok(())
}
pub fn worksheet_names(&self) -> &[String] {
&self.worksheet_order
}
pub fn worksheet_count(&self) -> usize {
self.worksheets.len()
}
pub fn active_worksheet(&self) -> Option<&Worksheet> {
self.active_worksheet.as_ref().and_then(|name| self.worksheets.get(name))
}
pub fn active_worksheet_mut(&mut self) -> Option<&mut Worksheet> {
if let Some(name) = self.active_worksheet.clone() {
self.worksheets.get_mut(&name)
} else {
None
}
}
pub fn set_active_worksheet(&mut self, name: &str) -> Result<()> {
if !self.worksheets.contains_key(name) {
return Err(
OfficeError::Xlsx(XlsxError::WorksheetNotFound {
name: name.to_string(),
})
);
}
self.active_worksheet = Some(name.to_string());
Ok(())
}
pub fn active_worksheet_name(&self) -> Option<&str> {
self.active_worksheet.as_deref()
}
pub fn move_worksheet(&mut self, name: &str, new_index: usize) -> Result<()> {
if !self.worksheets.contains_key(name) {
return Err(
OfficeError::Xlsx(XlsxError::WorksheetNotFound {
name: name.to_string(),
})
);
}
if new_index >= self.worksheet_order.len() {
return Err(OfficeError::Structure("工作表索引超出范围".to_string()));
}
if let Some(current_index) = self.worksheet_order.iter().position(|n| n == name) {
let worksheet_name = self.worksheet_order.remove(current_index);
self.worksheet_order.insert(new_index, worksheet_name);
}
Ok(())
}
pub fn worksheets(&self) -> impl Iterator<Item = &Worksheet> {
self.worksheet_order.iter().filter_map(|name| self.worksheets.get(name))
}
pub fn add_shared_string(&mut self, text: String) -> usize {
if let Some(index) = self.shared_strings.iter().position(|s| s == &text) {
return index;
}
self.shared_strings.push(text);
self.shared_strings.len() - 1
}
pub fn get_shared_string(&self, index: usize) -> Result<&str> {
self.shared_strings
.get(index)
.map(|s| s.as_str())
.ok_or_else(|| OfficeError::Xlsx(XlsxError::SharedStringIndexOutOfRange { index }))
}
pub fn shared_strings(&self) -> &[String] {
&self.shared_strings
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let mut writer = crate::xlsx::XlsxWriter::create(path)?;
writer.write_workbook(self)
}
pub fn save_as<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.save(path)
}
}
impl Default for WorkbookProperties {
fn default() -> Self {
Self::new()
}
}
impl Default for Workbook {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xlsx::cell::CellValue;
#[test]
fn test_workbook_basic_operations() {
let mut workbook = Workbook::new();
assert_eq!(workbook.worksheet_count(), 1);
assert_eq!(workbook.worksheet_names()[0], "Sheet1");
workbook.add_worksheet("Sheet2".to_string()).unwrap();
assert_eq!(workbook.worksheet_count(), 2);
let sheet1 = workbook.get_worksheet("Sheet1").unwrap();
assert_eq!(sheet1.name(), "Sheet1");
}
#[test]
fn test_worksheet_operations() {
let mut workbook = Workbook::new();
{
let sheet = workbook.get_worksheet_mut("Sheet1").unwrap();
sheet.set_cell_value_by_reference("A1", CellValue::Text("Hello".to_string())).unwrap();
sheet.set_cell_value_by_reference("B1", CellValue::Number(42.0)).unwrap();
}
let sheet = workbook.get_worksheet("Sheet1").unwrap();
let cell_a1 = sheet.get_cell(0, 0).unwrap();
assert_eq!(cell_a1.value, CellValue::Text("Hello".to_string()));
let cell_b1 = sheet.get_cell(0, 1).unwrap();
assert_eq!(cell_b1.value, CellValue::Number(42.0));
}
#[test]
fn test_worksheet_management() {
let mut workbook = Workbook::new();
workbook.add_worksheet("Test Sheet".to_string()).unwrap();
assert_eq!(workbook.worksheet_count(), 2);
workbook.rename_worksheet("Test Sheet", "Renamed Sheet".to_string()).unwrap();
assert!(workbook.get_worksheet("Renamed Sheet").is_some());
assert!(workbook.get_worksheet("Test Sheet").is_none());
workbook.set_active_worksheet("Renamed Sheet").unwrap();
assert_eq!(workbook.active_worksheet_name(), Some("Renamed Sheet"));
}
#[test]
fn test_shared_strings() {
let mut workbook = Workbook::new();
let index1 = workbook.add_shared_string("Hello".to_string());
let index2 = workbook.add_shared_string("World".to_string());
let index3 = workbook.add_shared_string("Hello".to_string());
assert_eq!(index1, 0);
assert_eq!(index2, 1);
assert_eq!(index3, 0);
assert_eq!(workbook.get_shared_string(0).unwrap(), "Hello");
assert_eq!(workbook.get_shared_string(1).unwrap(), "World");
}
#[test]
fn test_worksheet_api() {
let mut workbook = Workbook::new();
workbook.add_worksheet("Data".to_string()).unwrap();
assert!(workbook.get_worksheet_mut("Sheet1").is_some());
assert!(workbook.get_worksheet_mut("Data").is_some());
assert!(workbook.get_worksheet_mut("NonExistent").is_none());
let names = workbook.worksheet_names();
assert_eq!(names.len(), 2);
assert!(names.contains(&"Sheet1".to_string()));
assert!(names.contains(&"Data".to_string()));
}
}