125 lines
3.4 KiB
Rust
125 lines
3.4 KiB
Rust
mod colors;
|
|
mod metadata;
|
|
mod shared_strings;
|
|
mod styles;
|
|
mod tables;
|
|
mod util;
|
|
mod workbook;
|
|
mod worksheets;
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
fs,
|
|
io::{BufReader, Read},
|
|
};
|
|
|
|
use roxmltree::Node;
|
|
|
|
use ironcalc_base::{
|
|
model::Model,
|
|
types::{Metadata, Workbook, WorkbookSettings},
|
|
};
|
|
|
|
use crate::error::XlsxError;
|
|
|
|
use shared_strings::read_shared_strings;
|
|
|
|
use metadata::load_metadata;
|
|
use styles::load_styles;
|
|
use util::get_attribute;
|
|
use workbook::load_workbook;
|
|
use worksheets::{load_sheets, Relationship};
|
|
|
|
fn load_relationships<R: Read + std::io::Seek>(
|
|
archive: &mut zip::ZipArchive<R>,
|
|
) -> Result<HashMap<String, Relationship>, XlsxError> {
|
|
let mut file = archive.by_name("xl/_rels/workbook.xml.rels")?;
|
|
let mut text = String::new();
|
|
file.read_to_string(&mut text)?;
|
|
let doc = roxmltree::Document::parse(&text)?;
|
|
let nodes: Vec<Node> = doc
|
|
.descendants()
|
|
.filter(|n| n.has_tag_name("Relationship"))
|
|
.collect();
|
|
let mut rels = HashMap::new();
|
|
for node in nodes {
|
|
rels.insert(
|
|
get_attribute(&node, "Id")?.to_string(),
|
|
Relationship {
|
|
rel_type: get_attribute(&node, "Type")?.to_string(),
|
|
target: get_attribute(&node, "Target")?.to_string(),
|
|
},
|
|
);
|
|
}
|
|
Ok(rels)
|
|
}
|
|
|
|
fn load_xlsx_from_reader<R: Read + std::io::Seek>(
|
|
name: String,
|
|
reader: R,
|
|
locale: &str,
|
|
tz: &str,
|
|
) -> Result<Workbook, XlsxError> {
|
|
let mut archive = zip::ZipArchive::new(reader)?;
|
|
|
|
let mut shared_strings = read_shared_strings(&mut archive)?;
|
|
let workbook = load_workbook(&mut archive)?;
|
|
let rels = load_relationships(&mut archive)?;
|
|
let mut tables = HashMap::new();
|
|
let worksheets = load_sheets(
|
|
&mut archive,
|
|
&rels,
|
|
&workbook,
|
|
&mut tables,
|
|
&mut shared_strings,
|
|
)?;
|
|
let styles = load_styles(&mut archive)?;
|
|
let metadata = match load_metadata(&mut archive) {
|
|
Ok(metadata) => metadata,
|
|
Err(_) => {
|
|
// In case there is no metadata, add some
|
|
Metadata {
|
|
application: "Unknown application".to_string(),
|
|
app_version: "".to_string(),
|
|
creator: "".to_string(),
|
|
last_modified_by: "".to_string(),
|
|
created: "".to_string(),
|
|
last_modified: "".to_string(),
|
|
}
|
|
}
|
|
};
|
|
Ok(Workbook {
|
|
shared_strings,
|
|
defined_names: workbook.defined_names,
|
|
worksheets,
|
|
styles,
|
|
name,
|
|
settings: WorkbookSettings {
|
|
tz: tz.to_string(),
|
|
locale: locale.to_string(),
|
|
},
|
|
metadata,
|
|
tables,
|
|
})
|
|
}
|
|
|
|
// Public methods
|
|
|
|
/// Imports a file from disk into an internal representation
|
|
pub fn load_from_excel(file_name: &str, locale: &str, tz: &str) -> Result<Workbook, XlsxError> {
|
|
let file_path = std::path::Path::new(file_name);
|
|
let file = fs::File::open(file_path)?;
|
|
let reader = BufReader::new(file);
|
|
let name = file_path
|
|
.file_stem()
|
|
.ok_or_else(|| XlsxError::IO("Could not extract workbook name".to_string()))?
|
|
.to_string_lossy()
|
|
.to_string();
|
|
load_xlsx_from_reader(name, reader, locale, tz)
|
|
}
|
|
|
|
pub fn load_model_from_xlsx(file_name: &str, locale: &str, tz: &str) -> Result<Model, XlsxError> {
|
|
let workbook = load_from_excel(file_name, locale, tz)?;
|
|
Model::from_workbook(workbook).map_err(XlsxError::Workbook)
|
|
}
|