1mod conditional_formatting;
2mod metadata;
3pub(crate) mod shared_strings;
4mod styles;
5mod tables;
6mod theme;
7mod util;
8mod workbook;
9mod worksheets;
10
11use std::{
12 collections::HashMap,
13 fs,
14 io::{BufReader, Cursor, Read},
15};
16
17use roxmltree::Node;
18
19use ironcalc_base::{
20 expressions::{
21 parser::{new_parser_english, stringify::to_english_string},
22 types::CellReferenceRC,
23 },
24 types::{Metadata, Workbook, WorkbookSettings, WorkbookView},
25 Model,
26};
27
28use crate::error::XlsxError;
29
30use shared_strings::read_shared_strings;
31
32use metadata::load_metadata;
33use styles::load_styles;
34use util::get_attribute;
35use workbook::load_workbook;
36use worksheets::{load_sheets, Relationship};
37
38fn load_relationships<R: Read + std::io::Seek>(
39 archive: &mut zip::ZipArchive<R>,
40) -> Result<HashMap<String, Relationship>, XlsxError> {
41 let mut file = archive.by_name("xl/_rels/workbook.xml.rels")?;
42 let mut text = String::new();
43 file.read_to_string(&mut text)?;
44 let doc = roxmltree::Document::parse(&text)?;
45 let nodes: Vec<Node> = doc
46 .descendants()
47 .filter(|n| n.has_tag_name("Relationship"))
48 .collect();
49 let mut rels = HashMap::new();
50 for node in nodes {
51 rels.insert(
52 get_attribute(&node, "Id")?.to_string(),
53 Relationship {
54 rel_type: get_attribute(&node, "Type")?.to_string(),
55 target: get_attribute(&node, "Target")?.to_string(),
56 },
57 );
58 }
59 Ok(rels)
60}
61
62fn resolve_theme_path(rels: &HashMap<String, Relationship>) -> Option<String> {
63 let target = rels
64 .values()
65 .find(|r| r.rel_type.ends_with("/theme"))?
66 .target
67 .clone();
68 Some(if let Some(absolute) = target.strip_prefix('/') {
69 absolute.to_string()
70 } else {
71 format!("xl/{target}")
72 })
73}
74
75fn reparse_formula_hack(formula: &str, worksheets: &[String]) -> Result<String, XlsxError> {
78 let defined_names = Vec::new();
79 let tables = HashMap::new();
80 let mut parser = new_parser_english(worksheets.to_owned(), defined_names, tables);
81 let cell_reference = CellReferenceRC {
82 sheet: worksheets[0].clone(),
83 column: 1,
84 row: 1,
85 };
86 let t = parser.parse(formula, &cell_reference);
87
88 Ok(to_english_string(&t, &cell_reference))
89}
90
91fn load_xlsx_from_reader<R: Read + std::io::Seek>(
92 name: String,
93 reader: R,
94 locale: &str,
95 tz: &str,
96) -> Result<Workbook, XlsxError> {
97 let mut archive = zip::ZipArchive::new(reader)?;
98
99 let mut shared_strings = read_shared_strings(&mut archive)?;
100 let mut workbook = load_workbook(&mut archive)?;
101 let rels = load_relationships(&mut archive)?;
102 let theme_path = resolve_theme_path(&rels);
103 let theme = theme::load(&mut archive, theme_path.as_deref());
104 let mut tables = HashMap::new();
105 let mut styles = load_styles(&mut archive, &theme)?;
109 let (worksheets, selected_sheet) = load_sheets(
110 &mut archive,
111 &rels,
112 &workbook,
113 &mut tables,
114 &mut shared_strings,
115 &theme,
116 &mut styles.dxfs,
117 )?;
118 let worksheet_names = worksheets
120 .iter()
121 .map(|s| s.name.clone())
122 .collect::<Vec<_>>();
123 for dn in &mut workbook.defined_names {
124 dn.formula = reparse_formula_hack(&dn.formula, &worksheet_names)?;
125 }
126 let metadata = match load_metadata(&mut archive) {
127 Ok(metadata) => metadata,
128 Err(_) => {
129 Metadata {
131 application: "Unknown application".to_string(),
132 app_version: "".to_string(),
133 creator: "".to_string(),
134 last_modified_by: "".to_string(),
135 created: "".to_string(),
136 last_modified: "".to_string(),
137 }
138 }
139 };
140 let mut views = HashMap::new();
141 views.insert(
142 0,
143 WorkbookView {
144 sheet: selected_sheet,
145 window_width: 800,
146 window_height: 600,
147 },
148 );
149 Ok(Workbook {
150 shared_strings,
151 defined_names: workbook.defined_names,
152 worksheets,
153 styles,
154 name,
155 settings: WorkbookSettings {
156 tz: tz.to_string(),
157 locale: locale.to_string(),
158 },
159 metadata,
160 tables,
161 views,
162 theme,
163 })
164}
165
166fn load_from_excel(file_name: &str, locale: &str, tz: &str) -> Result<Workbook, XlsxError> {
168 let file_path = std::path::Path::new(file_name);
169 let file = fs::File::open(file_path)?;
170 let reader = BufReader::new(file);
171 let name = file_path
172 .file_stem()
173 .ok_or_else(|| XlsxError::IO("Could not extract workbook name".to_string()))?
174 .to_string_lossy()
175 .to_string();
176 load_xlsx_from_reader(name, reader, locale, tz)
177}
178
179pub fn load_from_xlsx_bytes(
182 bytes: &[u8],
183 name: &str,
184 locale: &str,
185 tz: &str,
186) -> Result<Workbook, XlsxError> {
187 let cursor = Cursor::new(bytes);
188 let reader = BufReader::new(cursor);
189 load_xlsx_from_reader(name.to_string(), reader, locale, tz)
190}
191
192pub fn load_from_xlsx<'a>(
194 file_name: &str,
195 locale: &str,
196 tz: &str,
197 language: &'a str,
198) -> Result<Model<'a>, XlsxError> {
199 let workbook = load_from_excel(file_name, locale, tz)?;
200 Model::from_workbook(workbook, language).map_err(XlsxError::Workbook)
201}
202
203pub fn load_from_icalc<'a>(file_name: &str, language_id: &'a str) -> Result<Model<'a>, XlsxError> {
205 let contents = fs::read(file_name)
206 .map_err(|e| XlsxError::IO(format!("Could not extract workbook name: {e}")))?;
207 let workbook: Workbook = bitcode::decode(&contents)
208 .map_err(|e| XlsxError::IO(format!("Failed to decode file: {e}")))?;
209 Model::from_workbook(workbook, language_id).map_err(XlsxError::Workbook)
210}