use crate::error::Error;
use crate::model::SheetVisibility;
use crate::parse::{create_secure_reader, optional_attr, read_event, required_attr};
use quick_xml::events::Event;
use std::io::BufRead;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct WorkbookSheetEntry {
pub name: String,
pub r_id: String,
pub visibility: SheetVisibility,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParsedWorkbookXml {
pub sheets: Vec<WorkbookSheetEntry>,
pub date1904: bool,
}
pub(crate) fn parse_workbook_xml(
reader: impl BufRead,
path: &str,
) -> Result<ParsedWorkbookXml, Error> {
let mut xml_reader = create_secure_reader(reader);
let mut buf = Vec::new();
let mut entries = Vec::new();
let mut in_sheets = false;
let mut saw_sheets = false;
let mut date1904 = false;
loop {
let event = read_event(&mut xml_reader, &mut buf, path)?;
match &event {
Event::Start(e) | Event::Empty(e) if e.local_name().as_ref() == b"workbookPr" => {
let date1904_attr = optional_attr(e, path, "date1904")?;
date1904 = matches!(date1904_attr.as_deref(), Some("1" | "true"));
}
Event::Start(e) if e.local_name().as_ref() == b"sheets" => {
in_sheets = true;
saw_sheets = true;
}
Event::Empty(e) if e.local_name().as_ref() == b"sheets" => {
saw_sheets = true;
}
Event::End(e) if e.local_name().as_ref() == b"sheets" => {
in_sheets = false;
}
Event::Start(e) | Event::Empty(e)
if in_sheets && e.local_name().as_ref() == b"sheet" =>
{
let name = required_attr(e, path, "name")?;
let r_id = required_attr(e, path, "r:id")?;
let state = optional_attr(e, path, "state")?;
entries.push(WorkbookSheetEntry {
name,
r_id,
visibility: parse_visibility(state.as_deref()),
});
}
Event::Eof => break,
_ => {}
}
buf.clear();
}
if !saw_sheets {
return Err(Error::MissingRequiredElement {
path: path.to_string(),
name: "sheets",
});
}
Ok(ParsedWorkbookXml {
sheets: entries,
date1904,
})
}
fn parse_visibility(state: Option<&str>) -> SheetVisibility {
match state {
None | Some("visible") => SheetVisibility::Visible,
Some("hidden") => SheetVisibility::Hidden,
Some("veryHidden") => SheetVisibility::VeryHidden,
Some(_) => SheetVisibility::Visible,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_multiple_sheets_preserving_order() {
let xml = br#"<?xml version="1.0"?>
<workbook xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
<sheet name="Sheet2" sheetId="2" r:id="rId2" state="hidden"/>
</sheets>
</workbook>"#;
let parsed = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert_eq!(parsed.sheets.len(), 2);
assert_eq!(parsed.sheets[0].name, "Sheet1");
assert_eq!(parsed.sheets[0].r_id, "rId1");
assert_eq!(parsed.sheets[0].visibility, SheetVisibility::Visible);
assert_eq!(parsed.sheets[1].name, "Sheet2");
assert_eq!(parsed.sheets[1].visibility, SheetVisibility::Hidden);
assert!(!parsed.date1904);
}
#[test]
fn workbook_pr_date1904_true_is_read() {
let xml = br#"<workbook><workbookPr date1904="1"/><sheets><sheet name="Sheet1" r:id="rId1"/></sheets></workbook>"#;
let parsed = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert!(parsed.date1904);
}
#[test]
fn workbook_pr_date1904_false_form_and_absent_workbook_pr_are_false() {
let explicit_false = br#"<workbook><workbookPr date1904="0"/><sheets><sheet name="Sheet1" r:id="rId1"/></sheets></workbook>"#;
assert!(
!parse_workbook_xml(&explicit_false[..], "xl/workbook.xml")
.unwrap()
.date1904
);
let no_workbook_pr =
br#"<workbook><sheets><sheet name="Sheet1" r:id="rId1"/></sheets></workbook>"#;
assert!(
!parse_workbook_xml(&no_workbook_pr[..], "xl/workbook.xml")
.unwrap()
.date1904
);
}
#[test]
fn workbook_pr_date1904_true_form_word_is_read() {
let xml = br#"<workbook><workbookPr date1904="true"/><sheets><sheet name="Sheet1" r:id="rId1"/></sheets></workbook>"#;
let parsed = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert!(parsed.date1904);
}
#[test]
fn absent_state_is_visible() {
assert_eq!(parse_visibility(None), SheetVisibility::Visible);
}
#[test]
fn state_hidden_and_very_hidden() {
assert_eq!(parse_visibility(Some("hidden")), SheetVisibility::Hidden);
assert_eq!(
parse_visibility(Some("veryHidden")),
SheetVisibility::VeryHidden
);
}
#[test]
fn unrecognized_state_falls_back_to_visible() {
assert_eq!(
parse_visibility(Some("somethingFuture")),
SheetVisibility::Visible
);
}
#[test]
fn sheet_missing_name_is_an_error() {
let xml = br#"<workbook><sheets><sheet r:id="rId1"/></sheets></workbook>"#;
let err = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap_err();
assert!(matches!(
err,
Error::MissingRequiredElement { name: "name", .. }
));
}
#[test]
fn sheet_missing_r_id_is_an_error() {
let xml = br#"<workbook><sheets><sheet name="Sheet1"/></sheets></workbook>"#;
let err = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap_err();
assert!(matches!(
err,
Error::MissingRequiredElement { name: "r:id", .. }
));
}
#[test]
fn missing_sheets_element_is_an_error() {
let xml = b"<workbook></workbook>";
let err = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap_err();
assert!(matches!(
err,
Error::MissingRequiredElement { name: "sheets", .. }
));
}
#[test]
fn empty_sheets_element_produces_empty_vec() {
let xml = b"<workbook><sheets></sheets></workbook>";
let parsed = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert!(parsed.sheets.is_empty());
}
#[test]
fn self_closing_empty_sheets_element_produces_empty_vec() {
let xml = b"<workbook><sheets/></workbook>";
let parsed = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert!(parsed.sheets.is_empty());
}
}