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,
}
pub(crate) fn parse_workbook_xml(
reader: impl BufRead,
path: &str,
) -> Result<Vec<WorkbookSheetEntry>, 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;
loop {
let event = read_event(&mut xml_reader, &mut buf, path)?;
match &event {
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(entries)
}
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 entries = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].name, "Sheet1");
assert_eq!(entries[0].r_id, "rId1");
assert_eq!(entries[0].visibility, SheetVisibility::Visible);
assert_eq!(entries[1].name, "Sheet2");
assert_eq!(entries[1].visibility, SheetVisibility::Hidden);
}
#[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 entries = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert!(entries.is_empty());
}
#[test]
fn self_closing_empty_sheets_element_produces_empty_vec() {
let xml = b"<workbook><sheets/></workbook>";
let entries = parse_workbook_xml(&xml[..], "xl/workbook.xml").unwrap();
assert!(entries.is_empty());
}
}