use crate::common::xml_utils::{ XmlElement, XmlParser };
use crate::common::zip_utils::ZipReader;
use crate::context::ErrorContext;
use crate::error::{ OfficeError, Result };
use crate::xlsx::{
Cell,
CellReference,
CellType,
CellValue,
Workbook,
WorkbookProperties,
Worksheet,
WorksheetProperties,
};
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
pub struct XlsxReader {
zip_reader: ZipReader<BufReader<File>>,
shared_strings: Vec<String>,
workbook_xml: Option<XmlElement>,
}
impl XlsxReader {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let zip_reader = ZipReader::open_file(&path).map_err(|e| {
e.with_context(ErrorContext {
operation: Some("打开XLSX文件".to_string()),
file_path: Some(path.as_ref().to_string_lossy().to_string()),
..Default::default()
})
})?;
let mut reader = Self {
zip_reader,
shared_strings: Vec::new(),
workbook_xml: None,
};
reader.load_shared_strings()?;
reader.load_workbook_xml()?;
Ok(reader)
}
pub fn read_workbook(&mut self) -> Result<Workbook> {
let workbook_xml = self.workbook_xml.as_ref().ok_or_else(|| {
OfficeError::Parse(crate::error::ParseError::MissingElement {
element: "workbook XML".to_string(),
})
})?;
let properties = self.parse_workbook_properties(workbook_xml)?;
let mut workbook = Workbook::new_empty();
let worksheet_names = self.parse_worksheet_list(workbook_xml)?;
for (sheet_name, sheet_id) in worksheet_names {
let worksheet = self.read_worksheet(&sheet_name, sheet_id)?;
workbook.add_worksheet_direct(sheet_name, worksheet);
}
Ok(workbook)
}
pub fn read_worksheet(&mut self, name: &str, sheet_id: u32) -> Result<Worksheet> {
let sheet_path = format!("xl/worksheets/sheet{}.xml", sheet_id);
if !self.zip_reader.contains_file(&sheet_path) {
return Err(
OfficeError::Parse(crate::error::ParseError::MissingElement {
element: format!("worksheet file: {}", sheet_path),
})
);
}
let xml_content = self.zip_reader.read_file_to_string(&sheet_path)?;
let parser = XmlParser::new();
let xml_element = parser.parse_string(&xml_content)?;
self.parse_worksheet(&xml_element, name)
}
fn load_shared_strings(&mut self) -> Result<()> {
const SHARED_STRINGS_PATH: &str = "xl/sharedStrings.xml";
if !self.zip_reader.contains_file(SHARED_STRINGS_PATH) {
return Ok(());
}
let xml_content = self.zip_reader.read_file_to_string(SHARED_STRINGS_PATH)?;
let parser = XmlParser::new();
let xml_element = parser.parse_string(&xml_content)?;
self.shared_strings = self.parse_shared_strings(&xml_element)?;
Ok(())
}
fn load_workbook_xml(&mut self) -> Result<()> {
const WORKBOOK_PATH: &str = "xl/workbook.xml";
if !self.zip_reader.contains_file(WORKBOOK_PATH) {
return Err(
OfficeError::Parse(crate::error::ParseError::MissingElement {
element: "workbook file".to_string(),
})
);
}
let xml_content = self.zip_reader.read_file_to_string(WORKBOOK_PATH)?;
let parser = XmlParser::new();
let xml_element = parser.parse_string(&xml_content)?;
self.workbook_xml = Some(xml_element);
Ok(())
}
fn parse_workbook_properties(&self, workbook_xml: &XmlElement) -> Result<WorkbookProperties> {
let mut properties = WorkbookProperties::default();
if let Some(props_element) = workbook_xml.find_child("fileVersion") {
if let Some(app_name) = props_element.get_attribute("appName") {
properties.title = Some(app_name.clone());
}
}
Ok(properties)
}
fn parse_worksheet_list(&self, workbook_xml: &XmlElement) -> Result<Vec<(String, u32)>> {
let mut worksheets = Vec::new();
if let Some(sheets_element) = workbook_xml.find_child("sheets") {
for sheet_element in &sheets_element.children {
if sheet_element.name == "sheet" {
let name = sheet_element.get_attribute("name").ok_or_else(|| {
OfficeError::Parse(crate::error::ParseError::MissingElement {
element: "worksheet name attribute".to_string(),
})
})?;
let sheet_id_str = sheet_element.get_attribute("sheetId").ok_or_else(|| {
OfficeError::Parse(crate::error::ParseError::MissingElement {
element: "worksheet sheetId attribute".to_string(),
})
})?;
let sheet_id: u32 = sheet_id_str.parse().map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "sheetId".to_string(),
value: sheet_id_str.to_string(),
})
})?;
worksheets.push((name.clone(), sheet_id));
}
}
}
Ok(worksheets)
}
fn parse_worksheet(&self, xml_element: &XmlElement, name: &str) -> Result<Worksheet> {
let mut worksheet = Worksheet::new(name.to_string());
if let Some(sheet_data) = xml_element.find_child("sheetData") {
self.parse_sheet_data(sheet_data, &mut worksheet)?;
}
Ok(worksheet)
}
fn parse_sheet_data(&self, sheet_data: &XmlElement, worksheet: &mut Worksheet) -> Result<()> {
for row_element in &sheet_data.children {
if row_element.name == "row" {
self.parse_row(row_element, worksheet)?;
}
}
Ok(())
}
fn parse_row(&self, row_element: &XmlElement, worksheet: &mut Worksheet) -> Result<()> {
for cell_element in &row_element.children {
if cell_element.name == "c" {
self.parse_cell(cell_element, worksheet)?;
}
}
Ok(())
}
fn parse_cell(&self, cell_element: &XmlElement, worksheet: &mut Worksheet) -> Result<()> {
let cell_ref = cell_element.get_attribute("r").ok_or_else(|| {
OfficeError::Parse(crate::error::ParseError::MissingElement {
element: "cell reference attribute".to_string(),
})
})?;
let (row, col) = self.parse_cell_reference(&cell_ref)?;
let cell_type = cell_element.get_attribute("t").map_or("n", |v| v.as_str());
let cell_value = if let Some(value_element) = cell_element.find_child("v") {
let value_text = value_element.text_content.as_deref().unwrap_or("");
self.parse_cell_value(value_text, cell_type)?
} else {
CellValue::Empty
};
let cell_reference = CellReference::new(col - 1, row - 1); let cell = Cell::new(cell_reference, cell_value);
worksheet.set_cell(cell);
Ok(())
}
fn parse_cell_reference(&self, cell_ref: &str) -> Result<(u32, u32)> {
let mut col = 0u32;
let mut row_start = 0;
for (i, ch) in cell_ref.chars().enumerate() {
if ch.is_ascii_alphabetic() {
col = col * 26 + ((ch.to_ascii_uppercase() as u32) - (b'A' as u32) + 1);
} else {
row_start = i;
break;
}
}
let row_str = &cell_ref[row_start..];
let row: u32 = row_str.parse().map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "cell reference".to_string(),
value: cell_ref.to_string(),
})
})?;
Ok((row, col))
}
fn parse_cell_value(&self, value_text: &str, cell_type: &str) -> Result<CellValue> {
match cell_type {
"s" => {
let index: usize = value_text.parse().map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "shared string index".to_string(),
value: value_text.to_string(),
})
})?;
if index < self.shared_strings.len() {
Ok(CellValue::Text(self.shared_strings[index].clone()))
} else {
Err(
OfficeError::Xlsx(crate::error::XlsxError::SharedStringIndexOutOfRange {
index,
})
)
}
}
"b" => {
match value_text {
"1" | "true" => Ok(CellValue::Boolean(true)),
"0" | "false" => Ok(CellValue::Boolean(false)),
_ =>
Err(
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "boolean value".to_string(),
value: value_text.to_string(),
})
),
}
}
"n" | _ => {
if value_text.is_empty() {
Ok(CellValue::Empty)
} else {
value_text
.parse::<f64>()
.map(CellValue::Number)
.map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "number value".to_string(),
value: value_text.to_string(),
})
})
}
}
}
}
fn parse_shared_strings(&self, xml_element: &XmlElement) -> Result<Vec<String>> {
let mut strings = Vec::new();
for si_element in &xml_element.children {
if si_element.name == "si" {
if let Some(t_element) = si_element.find_child("t") {
let text = t_element.text_content.as_deref().unwrap_or("").to_string();
strings.push(text);
} else {
let mut rich_text = String::new();
for r_element in &si_element.children {
if r_element.name == "r" {
if let Some(t_element) = r_element.find_child("t") {
rich_text.push_str(t_element.text_content.as_deref().unwrap_or(""));
}
}
}
strings.push(rich_text);
}
}
}
Ok(strings)
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestXlsxReader {
shared_strings: Vec<String>,
}
impl TestXlsxReader {
fn new() -> Self {
Self {
shared_strings: Vec::new(),
}
}
fn with_shared_strings(shared_strings: Vec<String>) -> Self {
Self { shared_strings }
}
fn parse_cell_reference(&self, cell_ref: &str) -> Result<(u32, u32)> {
let mut col = 0u32;
let mut row_start = 0;
for (i, ch) in cell_ref.chars().enumerate() {
if ch.is_ascii_alphabetic() {
col = col * 26 + ((ch.to_ascii_uppercase() as u32) - (b'A' as u32) + 1);
} else {
row_start = i;
break;
}
}
let row_str = &cell_ref[row_start..];
let row: u32 = row_str.parse().map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "cell reference".to_string(),
value: cell_ref.to_string(),
})
})?;
Ok((row, col))
}
fn parse_cell_value(&self, value_text: &str, cell_type: &str) -> Result<CellValue> {
match cell_type {
"s" => {
let index: usize = value_text.parse().map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "shared string index".to_string(),
value: value_text.to_string(),
})
})?;
if index < self.shared_strings.len() {
Ok(CellValue::Text(self.shared_strings[index].clone()))
} else {
Err(
OfficeError::Xlsx(crate::error::XlsxError::SharedStringIndexOutOfRange {
index,
})
)
}
}
"b" => {
match value_text {
"1" | "true" => Ok(CellValue::Boolean(true)),
"0" | "false" => Ok(CellValue::Boolean(false)),
_ =>
Err(
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "boolean value".to_string(),
value: value_text.to_string(),
})
),
}
}
"n" | _ => {
if value_text.is_empty() {
Ok(CellValue::Empty)
} else {
value_text
.parse::<f64>()
.map(CellValue::Number)
.map_err(|_| {
OfficeError::Parse(crate::error::ParseError::InvalidAttribute {
attribute: "number value".to_string(),
value: value_text.to_string(),
})
})
}
}
}
}
}
#[test]
fn test_parse_cell_reference() {
let reader = TestXlsxReader::new();
assert_eq!(reader.parse_cell_reference("A1").unwrap(), (1, 1));
assert_eq!(reader.parse_cell_reference("B2").unwrap(), (2, 2));
assert_eq!(reader.parse_cell_reference("Z26").unwrap(), (26, 26));
assert_eq!(reader.parse_cell_reference("AA27").unwrap(), (27, 27));
}
#[test]
fn test_parse_cell_value() {
let reader = TestXlsxReader::with_shared_strings(
vec!["Hello".to_string(), "World".to_string()]
);
assert_eq!(reader.parse_cell_value("123.45", "n").unwrap(), CellValue::Number(123.45));
assert_eq!(reader.parse_cell_value("1", "b").unwrap(), CellValue::Boolean(true));
assert_eq!(reader.parse_cell_value("0", "b").unwrap(), CellValue::Boolean(false));
assert_eq!(
reader.parse_cell_value("0", "s").unwrap(),
CellValue::Text("Hello".to_string())
);
assert_eq!(
reader.parse_cell_value("1", "s").unwrap(),
CellValue::Text("World".to_string())
);
}
}