1use std::collections::HashMap;
4use std::io::{Read, Seek};
5
6use opc::Package;
7use xml_core::{BytesStart, Event, Reader};
8
9use crate::error::{Error, Result};
10use crate::model::{
11 AutoFilterColumn, Border, BorderEdge, BorderStyle, CalculationMode, CalculationProperties,
12 Cell, CellAnchorPoint, CellComment, CellFormat, CellHyperlink, CellValue, ColumnSetting,
13 ComparisonOperator, ConditionalFormattingRule, CustomPropertyValue, DataValidationKind,
14 DataValidationRule, DefinedName, DocumentProperties, DrawingAnchor, DrawingObject, ExcelTable,
15 ExternalWorkbookLink, FreezePane, GradientFill, GradientStop, HorizontalAlignment,
16 HyperlinkTarget, IgnoredError, Message, NamedCellStyle, PageOrientation, PatternFill,
17 PatternType, PictureFormat, PrintSettings, ProtectedRange, RichTextRun, Row, Scenario, Sheet,
18 SheetChart, SheetDrawing, SheetPicture, SheetProtection, SheetVisibility, SortCondition,
19 SortState, TableColumn, TableStyle, TableStyleElement, TableStyleElementType,
20 TextComparisonOperator, TotalsRowFunction, VerticalAlignment, Workbook, WorkbookProtection,
21 WriteProtection,
22};
23
24const EXCEL_MAX_ROWS: usize = 1_048_576;
32const EXCEL_MAX_COLUMNS: usize = 16_384;
33
34const MAIN_WORKBOOK_RELATIONSHIP_TYPE: &str =
37 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
38
39const SHARED_STRINGS_RELATIONSHIP_TYPE: &str =
43 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
44
45const STYLES_RELATIONSHIP_TYPE: &str =
48 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
49
50const TABLE_RELATIONSHIP_TYPE: &str =
54 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table";
55const COMMENTS_RELATIONSHIP_TYPE: &str =
56 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
57
58const DRAWING_RELATIONSHIP_TYPE: &str =
60 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
61const IMAGE_RELATIONSHIP_TYPE: &str =
62 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
63const CHART_RELATIONSHIP_TYPE: &str =
64 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
65
66const CORE_PROPERTIES_RELATIONSHIP_TYPE: &str =
68 "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
69const EXTENDED_PROPERTIES_RELATIONSHIP_TYPE: &str =
70 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
71const VBA_PROJECT_RELATIONSHIP_TYPE: &str =
72 "http://schemas.microsoft.com/office/2006/relationships/vbaProject";
73const CUSTOM_PROPERTIES_RELATIONSHIP_TYPE: &str =
75 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties";
76const EXTERNAL_LINK_TARGET_RELATIONSHIP_TYPE: &str =
83 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLinkPath";
84
85impl Workbook {
86 pub fn read_from<R: Read + Seek>(reader: R) -> Result<Self> {
88 let package = Package::read_from(reader)?;
89
90 let main_workbook_relationship = package
91 .relationships()
92 .iter()
93 .find(|relationship| relationship.rel_type == MAIN_WORKBOOK_RELATIONSHIP_TYPE)
94 .ok_or(Error::MissingWorkbook)?;
95
96 let workbook_part_name = format!("/{}", main_workbook_relationship.target);
97 let workbook_part = package
98 .part(&workbook_part_name)
99 .ok_or(Error::MissingWorkbook)?;
100 let workbook_xml = std::str::from_utf8(&workbook_part.data)
101 .map_err(|error| Error::InvalidUtf8(workbook_part_name.clone(), error))?;
102
103 let parsed_workbook = parse_workbook_xml(workbook_xml)?;
104 let sheet_entries = parsed_workbook.sheets;
105 let defined_names = parsed_workbook.defined_names;
106
107 let core_properties_relationship = package
112 .relationships()
113 .iter()
114 .find(|relationship| relationship.rel_type == CORE_PROPERTIES_RELATIONSHIP_TYPE);
115 let ParsedCoreProperties {
116 title,
117 author,
118 subject,
119 keywords,
120 } = match core_properties_relationship {
121 Some(relationship) => {
122 let part_name = format!("/{}", relationship.target);
123 match package.part(&part_name) {
124 Some(part) => {
125 let xml = std::str::from_utf8(&part.data)
126 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
127 parse_core_properties_xml(xml)?
128 }
129 None => ParsedCoreProperties::default(),
130 }
131 }
132 None => ParsedCoreProperties::default(),
133 };
134 let extended_properties_relationship = package
135 .relationships()
136 .iter()
137 .find(|relationship| relationship.rel_type == EXTENDED_PROPERTIES_RELATIONSHIP_TYPE);
138 let (company, manager, hyperlink_base) = match extended_properties_relationship {
139 Some(relationship) => {
140 let part_name = format!("/{}", relationship.target);
141 match package.part(&part_name) {
142 Some(part) => {
143 let xml = std::str::from_utf8(&part.data)
144 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
145 parse_extended_properties_xml(xml)?
146 }
147 None => (None, None, None),
148 }
149 }
150 None => (None, None, None),
151 };
152 let custom_properties_relationship = package
156 .relationships()
157 .iter()
158 .find(|relationship| relationship.rel_type == CUSTOM_PROPERTIES_RELATIONSHIP_TYPE);
159 let custom_properties = match custom_properties_relationship {
160 Some(relationship) => {
161 let part_name = format!("/{}", relationship.target);
162 match package.part(&part_name) {
163 Some(part) => {
164 let xml = std::str::from_utf8(&part.data)
165 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
166 parse_custom_properties_xml(xml)?
167 }
168 None => Vec::new(),
169 }
170 }
171 None => Vec::new(),
172 };
173 let properties = DocumentProperties {
174 title,
175 author,
176 subject,
177 keywords,
178 company,
179 manager,
180 hyperlink_base,
181 custom_properties,
182 };
183
184 let vba_project = workbook_part
187 .relationships
188 .iter()
189 .find(|relationship| relationship.rel_type == VBA_PROJECT_RELATIONSHIP_TYPE)
190 .and_then(|relationship| package.part(&format!("/xl/{}", relationship.target)))
191 .map(|part| part.data.clone());
192
193 let mut external_links = Vec::new();
197 for relationship_id in &parsed_workbook.external_reference_relationship_ids {
198 let Some(relationship) = workbook_part.relationships.by_id(relationship_id) else {
199 continue;
200 };
201 let part_name = format!("/xl/{}", relationship.target);
202 let Some(part) = package.part(&part_name) else {
203 continue;
204 };
205 let xml = std::str::from_utf8(&part.data)
206 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
207 let ParsedExternalLink {
208 sheet_names,
209 defined_names,
210 } = parse_external_link_xml(xml)?;
211 let target = part
212 .relationships
213 .iter()
214 .find(|relationship| {
215 relationship.rel_type == EXTERNAL_LINK_TARGET_RELATIONSHIP_TYPE
216 })
217 .map(|relationship| relationship.target.clone())
218 .unwrap_or_default();
219 external_links.push(ExternalWorkbookLink {
220 target,
221 sheet_names,
222 defined_names,
223 });
224 }
225
226 let shared_strings_relationship = workbook_part
232 .relationships
233 .iter()
234 .find(|relationship| relationship.rel_type == SHARED_STRINGS_RELATIONSHIP_TYPE);
235 let shared_strings = match shared_strings_relationship {
236 Some(relationship) => {
237 let part_name = format!("/xl/{}", relationship.target);
238 match package.part(&part_name) {
239 Some(part) => {
240 let xml = std::str::from_utf8(&part.data)
241 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
242 parse_shared_strings_xml(xml)?
243 }
244 None => Vec::new(),
245 }
246 }
247 None => Vec::new(),
248 };
249
250 let styles_relationship = workbook_part
260 .relationships
261 .iter()
262 .find(|relationship| relationship.rel_type == STYLES_RELATIONSHIP_TYPE);
263 let parsed_styles: ParsedStyles = match styles_relationship {
264 Some(relationship) => {
265 let part_name = format!("/xl/{}", relationship.target);
266 match package.part(&part_name) {
267 Some(part) => {
268 let xml = std::str::from_utf8(&part.data)
269 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
270 parse_styles_xml(xml)?
271 }
272 None => ParsedStyles::default(),
273 }
274 }
275 None => ParsedStyles::default(),
276 };
277 let ParsedStyles {
278 cell_formats,
279 dxfs,
280 table_styles,
281 named_cell_styles,
282 } = parsed_styles;
283
284 let mut sheets = Vec::with_capacity(sheet_entries.len());
285 for (name, relationship_id, visibility) in sheet_entries {
286 let relationship = workbook_part
287 .relationships
288 .by_id(&relationship_id)
289 .ok_or_else(|| Error::MissingSheetRelationship(relationship_id.clone()))?;
290 let worksheet_part_name = format!("/xl/{}", relationship.target);
291 let worksheet_part = package
292 .part(&worksheet_part_name)
293 .ok_or_else(|| Error::MissingWorksheetPart(worksheet_part_name.clone()))?;
294 let worksheet_xml = std::str::from_utf8(&worksheet_part.data)
295 .map_err(|error| Error::InvalidUtf8(worksheet_part_name.clone(), error))?;
296 let sheet_relationships = &worksheet_part.relationships;
297 let parsed = parse_worksheet_xml(
298 worksheet_xml,
299 &shared_strings,
300 &cell_formats,
301 &dxfs,
302 sheet_relationships,
303 )?;
304
305 let comments = match sheet_relationships
311 .iter()
312 .find(|relationship| relationship.rel_type == COMMENTS_RELATIONSHIP_TYPE)
313 {
314 Some(relationship) => {
315 let part_name =
316 format!("/xl/{}", relationship.target.trim_start_matches("../"));
317 match package.part(&part_name) {
318 Some(part) => {
319 let xml = std::str::from_utf8(&part.data)
320 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
321 parse_comments_xml(xml)?
322 }
323 None => Vec::new(),
324 }
325 }
326 None => Vec::new(),
327 };
328
329 let table = match sheet_relationships
330 .iter()
331 .find(|relationship| relationship.rel_type == TABLE_RELATIONSHIP_TYPE)
332 {
333 Some(relationship) => {
334 let part_name =
335 format!("/xl/{}", relationship.target.trim_start_matches("../"));
336 match package.part(&part_name) {
337 Some(part) => {
338 let xml = std::str::from_utf8(&part.data)
339 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
340 Some(parse_table_xml(xml)?)
341 }
342 None => None,
343 }
344 }
345 None => None,
346 };
347
348 let drawing = match sheet_relationships
352 .iter()
353 .find(|relationship| relationship.rel_type == DRAWING_RELATIONSHIP_TYPE)
354 {
355 Some(relationship) => {
356 let part_name =
357 format!("/xl/{}", relationship.target.trim_start_matches("../"));
358 match package.part(&part_name) {
359 Some(part) => {
360 let xml = std::str::from_utf8(&part.data)
361 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
362 Some(parse_drawing_xml(xml, &part.relationships, &package)?)
363 }
364 None => None,
365 }
366 }
367 None => None,
368 };
369
370 sheets.push(Sheet {
371 name,
372 rows: parsed.rows,
373 conditional_formatting_rules: parsed.conditional_formatting_rules,
374 data_validation_rules: parsed.data_validation_rules,
375 merged_ranges: parsed.merged_ranges,
376 column_settings: parsed.column_settings,
377 auto_filter_range: parsed.auto_filter_range,
378 freeze_panes: parsed.freeze_panes,
379 print_settings: parsed.print_settings,
380 protection: parsed.protection,
381 hyperlinks: parsed.hyperlinks,
382 comments,
383 table,
384 drawing,
385 visibility,
386 zoom_scale: parsed.zoom_scale,
387 row_breaks: parsed.row_breaks,
388 column_breaks: parsed.column_breaks,
389 scenarios: parsed.scenarios,
390 auto_filter_columns: parsed.auto_filter_columns,
391 tab_color: parsed.tab_color,
392 show_grid_lines: parsed.show_grid_lines,
393 show_row_col_headers: parsed.show_row_col_headers,
394 show_zeros: parsed.show_zeros,
395 right_to_left: parsed.right_to_left,
396 active_cell: parsed.active_cell,
397 default_column_width: parsed.default_column_width,
398 default_row_height: parsed.default_row_height,
399 ignored_errors: parsed.ignored_errors,
400 protected_ranges: parsed.protected_ranges,
401 sort_state: parsed.sort_state,
402 outline_summary_below: parsed.outline_summary_below,
403 outline_summary_right: parsed.outline_summary_right,
404 });
405 }
406
407 Ok(Workbook {
408 sheets,
409 defined_names,
410 properties,
411 protection: parsed_workbook.protection,
412 write_protection: parsed_workbook.write_protection,
413 active_tab: parsed_workbook.active_tab,
414 external_links,
415 vba_project,
416 calculation: parsed_workbook.calculation,
417 table_styles,
418 named_cell_styles,
419 })
420 }
421}
422
423struct ParsedWorkbook {
438 sheets: Vec<(String, String, SheetVisibility)>,
440 defined_names: Vec<DefinedName>,
441 write_protection: Option<WriteProtection>,
444 protection: Option<WorkbookProtection>,
448 active_tab: Option<usize>,
450 external_reference_relationship_ids: Vec<String>,
453 calculation: Option<CalculationProperties>,
455}
456
457fn parse_workbook_xml(xml: &str) -> Result<ParsedWorkbook> {
458 let mut reader = Reader::from_xml_str(xml);
459 let mut sheets = Vec::new();
460 let mut defined_names = Vec::new();
461 let mut write_protection: Option<WriteProtection> = None;
462 let mut protection: Option<WorkbookProtection> = None;
463 let mut active_tab: Option<usize> = None;
464 let mut external_reference_relationship_ids = Vec::new();
465 let mut calculation: Option<CalculationProperties> = None;
466
467 let mut in_defined_name = false;
468 let mut current_name: Option<String> = None;
469 let mut current_local_sheet_id: Option<usize> = None;
470 let mut current_hidden = false;
471 let mut current_comment: Option<String> = None;
472 let mut refers_to_buffer = String::new();
473
474 loop {
475 match reader.read_event()? {
476 Event::Eof => break,
477
478 Event::Start(start) | Event::Empty(start)
479 if start.local_name().as_ref() == b"sheet" =>
480 {
481 let name = raw_attribute(&start, b"name").unwrap_or_default();
482 let relationship_id = raw_attribute(&start, b"id").unwrap_or_default();
483 let visibility = match raw_attribute(&start, b"state").as_deref() {
484 Some("hidden") => SheetVisibility::Hidden,
485 Some("veryHidden") => SheetVisibility::VeryHidden,
486 _ => SheetVisibility::Visible,
487 };
488 sheets.push((name, relationship_id, visibility));
489 }
490
491 Event::Empty(start) if start.local_name().as_ref() == b"calcPr" => {
492 let mode = match raw_attribute(&start, b"calcMode").as_deref() {
493 Some("manual") => Some(CalculationMode::Manual),
494 Some("autoNoTable") => Some(CalculationMode::AutomaticExceptTables),
495 Some("auto") => Some(CalculationMode::Automatic),
496 _ => None,
497 };
498 let iterate = raw_attribute(&start, b"iterate").as_deref() == Some("1");
499 calculation = Some(CalculationProperties { mode, iterate });
500 }
501
502 Event::Empty(start) if start.local_name().as_ref() == b"workbookProtection" => {
503 protection = Some(WorkbookProtection {
504 password: None,
505 lock_structure: raw_attribute(&start, b"lockStructure").as_deref() == Some("1"),
506 lock_windows: raw_attribute(&start, b"lockWindows").as_deref() == Some("1"),
507 });
508 }
509
510 Event::Empty(start) if start.local_name().as_ref() == b"fileSharing" => {
512 write_protection = Some(WriteProtection {
513 read_only_recommended: raw_attribute(&start, b"readOnlyRecommended").as_deref()
514 == Some("1"),
515 user_name: raw_attribute(&start, b"userName"),
516 password: None,
517 });
518 }
519
520 Event::Empty(start) if start.local_name().as_ref() == b"workbookView" => {
521 active_tab = raw_attribute(&start, b"activeTab")
522 .and_then(|value| value.parse::<usize>().ok());
523 }
524
525 Event::Empty(start) if start.local_name().as_ref() == b"externalReference" => {
526 if let Some(id) = raw_attribute(&start, b"id") {
527 external_reference_relationship_ids.push(id);
528 }
529 }
530
531 Event::Start(start) if start.local_name().as_ref() == b"definedName" => {
532 in_defined_name = true;
533 current_name = raw_attribute(&start, b"name");
534 current_local_sheet_id = raw_attribute(&start, b"localSheetId")
535 .and_then(|value| value.parse::<usize>().ok());
536 current_hidden = raw_attribute(&start, b"hidden").as_deref() == Some("1");
537 current_comment = raw_attribute(&start, b"comment");
538 refers_to_buffer.clear();
539 }
540 Event::Empty(start) if start.local_name().as_ref() == b"definedName" => {
541 if let Some(name) = raw_attribute(&start, b"name") {
545 defined_names.push(DefinedName {
546 name,
547 refers_to: String::new(),
548 local_sheet_id: raw_attribute(&start, b"localSheetId")
549 .and_then(|value| value.parse::<usize>().ok()),
550 hidden: raw_attribute(&start, b"hidden").as_deref() == Some("1"),
551 comment: raw_attribute(&start, b"comment"),
552 });
553 }
554 }
555 Event::End(end) if in_defined_name && end.local_name().as_ref() == b"definedName" => {
556 in_defined_name = false;
557 if let Some(name) = current_name.take() {
558 defined_names.push(DefinedName {
559 name,
560 refers_to: std::mem::take(&mut refers_to_buffer),
561 local_sheet_id: current_local_sheet_id.take(),
562 hidden: std::mem::take(&mut current_hidden),
563 comment: current_comment.take(),
564 });
565 }
566 }
567 Event::Text(text) if in_defined_name => {
568 refers_to_buffer.push_str(&xml_core::decode_text(&text)?);
569 }
570 Event::GeneralRef(reference) if in_defined_name => {
571 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
572 refers_to_buffer.push_str(&resolved);
573 }
574 }
575
576 _ => {}
577 }
578 }
579
580 Ok(ParsedWorkbook {
581 sheets,
582 defined_names,
583 write_protection,
584 protection,
585 active_tab,
586 external_reference_relationship_ids,
587 calculation,
588 })
589}
590
591#[derive(Debug, Clone, PartialEq)]
595enum SharedStringEntry {
596 Plain(String),
597 Rich(Vec<RichTextRun>),
598}
599
600fn parse_shared_strings_xml(xml: &str) -> Result<Vec<SharedStringEntry>> {
608 let mut reader = Reader::from_xml_str(xml);
609 let mut entries = Vec::new();
610
611 let mut in_si = false;
612 let mut si_is_rich = false;
613 let mut si_plain_buffer = String::new();
614 let mut si_runs: Vec<RichTextRun> = Vec::new();
615
616 let mut in_run = false;
617 let mut run_text_buffer = String::new();
618 let mut run_bold = false;
619 let mut run_italic = false;
620 let mut run_size: Option<f64> = None;
621 let mut run_color: Option<String> = None;
622 let mut run_name: Option<String> = None;
623 let mut run_underline = false;
624 let mut run_strike = false;
625 let mut in_rpr = false;
626 let mut in_text = false;
627
628 loop {
629 match reader.read_event()? {
630 Event::Eof => break,
631
632 Event::Start(start) if start.local_name().as_ref() == b"si" => {
633 in_si = true;
634 si_is_rich = false;
635 si_plain_buffer.clear();
636 si_runs.clear();
637 }
638 Event::End(end) if end.local_name().as_ref() == b"si" => {
639 in_si = false;
640 if si_is_rich {
641 entries.push(SharedStringEntry::Rich(std::mem::take(&mut si_runs)));
642 } else {
643 entries.push(SharedStringEntry::Plain(std::mem::take(
644 &mut si_plain_buffer,
645 )));
646 }
647 }
648
649 Event::Start(start) if in_si && start.local_name().as_ref() == b"r" => {
650 in_run = true;
651 si_is_rich = true;
652 run_text_buffer.clear();
653 run_bold = false;
654 run_italic = false;
655 run_size = None;
656 run_color = None;
657 run_name = None;
658 run_underline = false;
659 run_strike = false;
660 }
661 Event::End(end) if in_si && end.local_name().as_ref() == b"r" => {
662 in_run = false;
663 si_runs.push(RichTextRun {
664 text: std::mem::take(&mut run_text_buffer),
665 bold: std::mem::take(&mut run_bold),
666 italic: std::mem::take(&mut run_italic),
667 font_color: run_color.take(),
668 font_size: run_size.take(),
669 font_name: run_name.take(),
670 underline: std::mem::take(&mut run_underline),
671 strike: std::mem::take(&mut run_strike),
672 });
673 }
674
675 Event::Start(start) if in_run && start.local_name().as_ref() == b"rPr" => {
676 in_rpr = true;
677 }
678 Event::End(end) if in_run && end.local_name().as_ref() == b"rPr" => {
679 in_rpr = false;
680 }
681 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"b" => {
682 run_bold = true;
683 }
684 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"i" => {
685 run_italic = true;
686 }
687 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"strike" => {
688 run_strike = true;
689 }
690 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"u" => {
691 run_underline = true;
692 }
693 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"rFont" => {
694 run_name = raw_attribute(&start, b"val");
695 }
696 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"sz" => {
697 run_size =
698 raw_attribute(&start, b"val").and_then(|value| value.parse::<f64>().ok());
699 }
700 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"color" => {
701 run_color = resolve_color(&start);
702 }
703
704 Event::Start(start) if start.local_name().as_ref() == b"t" => {
705 in_text = true;
706 }
707 Event::End(end) if end.local_name().as_ref() == b"t" => {
708 in_text = false;
709 }
710
711 Event::Text(text) if in_text && in_run => {
712 run_text_buffer.push_str(&xml_core::decode_text(&text)?);
713 }
714 Event::GeneralRef(reference) if in_text && in_run => {
715 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
716 run_text_buffer.push_str(&resolved);
717 }
718 }
719 Event::Text(text) if in_text && !in_run => {
720 si_plain_buffer.push_str(&xml_core::decode_text(&text)?);
721 }
722 Event::GeneralRef(reference) if in_text && !in_run => {
723 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
724 si_plain_buffer.push_str(&resolved);
725 }
726 }
727
728 _ => {}
729 }
730 }
731
732 Ok(entries)
733}
734
735struct RawFont {
738 bold: bool,
739 italic: bool,
740 size: f64,
741 color: Option<String>,
742 name: Option<String>,
743 underline: bool,
744 strike: bool,
745}
746
747enum RawFill {
750 None,
751 Gray125,
752 Solid(String),
753 Pattern(PatternFill),
754 Gradient(GradientFill),
755 Other,
758}
759
760#[derive(Default)]
762struct RawBorder {
763 top: Option<(String, Option<String>)>,
764 bottom: Option<(String, Option<String>)>,
765 left: Option<(String, Option<String>)>,
766 right: Option<(String, Option<String>)>,
767 diagonal: Option<(String, Option<String>)>,
768 diagonal_up: bool,
769 diagonal_down: bool,
770}
771
772struct RawXf {
774 num_fmt_id: u32,
775 font_id: usize,
776 fill_id: usize,
777 border_id: usize,
778 xf_id: usize,
781 horizontal: Option<String>,
782 vertical: Option<String>,
783 wrap_text: bool,
784 indent: Option<u32>,
785 text_rotation: Option<u16>,
786 shrink_to_fit: bool,
787 locked: Option<bool>,
788 formula_hidden: Option<bool>,
789}
790
791#[derive(Default)]
817struct ParsedStyles {
818 cell_formats: Vec<Option<CellFormat>>,
819 dxfs: Vec<CellFormat>,
820 table_styles: Vec<TableStyle>,
821 named_cell_styles: Vec<NamedCellStyle>,
822}
823
824fn parse_styles_xml(xml: &str) -> Result<ParsedStyles> {
825 let mut reader = Reader::from_xml_str(xml);
826
827 let mut num_fmts: HashMap<u32, String> = HashMap::new();
828 let mut fonts: Vec<RawFont> = Vec::new();
829 let mut fills: Vec<RawFill> = Vec::new();
830 let mut borders: Vec<RawBorder> = Vec::new();
831 let mut xfs: Vec<RawXf> = Vec::new();
832
833 let mut current_font: Option<RawFont> = None;
834 let mut in_fill = false;
835 let mut current_fill_pattern_type: Option<String> = None;
836 let mut current_fill_fg_color: Option<String> = None;
837 let mut current_fill_bg_color: Option<String> = None;
838 let mut current_fill_is_gradient = false;
839 let mut current_gradient_angle: Option<f64> = None;
840 let mut current_gradient_stops: Vec<GradientStop> = Vec::new();
841 let mut current_gradient_stop_position: Option<f64> = None;
842 let mut in_borders = false;
846 let mut current_border: Option<RawBorder> = None;
847 let mut current_border_edge_name: Option<&'static str> = None;
848 let mut current_border_edge_style: Option<String> = None;
849 let mut current_border_edge_color: Option<String> = None;
850 let mut in_cell_xfs = false;
851 let mut current_xf: Option<RawXf> = None;
852
853 let mut in_cell_style_xfs = false;
861 let mut current_style_xf: Option<RawXf> = None;
862 let mut style_xfs: Vec<RawXf> = Vec::new();
863 let mut cell_styles: Vec<(String, usize, Option<u32>)> = Vec::new();
864
865 let mut in_dxfs = false;
868 let mut current_dxf: Option<CellFormat> = None;
869 let mut in_dxf_font = false;
870 let mut dxf_font_bold = false;
871 let mut dxf_font_italic = false;
872 let mut dxf_font_color: Option<String> = None;
873 let mut in_dxf_fill = false;
874 let mut dxf_fill_pattern_type: Option<String> = None;
875 let mut dxf_fill_bg_color: Option<String> = None;
876 let mut dxfs: Vec<CellFormat> = Vec::new();
877
878 let mut table_styles: Vec<TableStyle> = Vec::new();
880 let mut current_table_style: Option<TableStyle> = None;
881
882 loop {
883 match reader.read_event()? {
884 Event::Eof => break,
885
886 Event::Start(start) if start.local_name().as_ref() == b"tableStyle" => {
887 current_table_style = Some(TableStyle::new(
888 raw_attribute(&start, b"name").unwrap_or_default(),
889 ));
890 }
891 Event::End(end) if end.local_name().as_ref() == b"tableStyle" => {
892 if let Some(style) = current_table_style.take() {
893 table_styles.push(style);
894 }
895 }
896 Event::Empty(start)
897 if current_table_style.is_some()
898 && start.local_name().as_ref() == b"tableStyleElement" =>
899 {
900 if let Some(element_type) = raw_attribute(&start, b"type")
901 .as_deref()
902 .and_then(parse_table_style_element_type)
903 {
904 let dxf_id = raw_attribute(&start, b"dxfId")
905 .and_then(|value| value.parse::<usize>().ok());
906 let format = dxf_id
907 .and_then(|index| dxfs.get(index).cloned())
908 .unwrap_or_default();
909 if let Some(style) = current_table_style.as_mut() {
910 style
911 .elements
912 .push(TableStyleElement::new(element_type, format));
913 }
914 }
915 }
916
917 Event::Empty(start) if !in_dxfs && start.local_name().as_ref() == b"numFmt" => {
918 let id =
919 raw_attribute(&start, b"numFmtId").and_then(|value| value.parse::<u32>().ok());
920 let code = raw_attribute(&start, b"formatCode");
921 if let (Some(id), Some(code)) = (id, code) {
922 num_fmts.insert(id, code);
923 }
924 }
925
926 Event::Start(start) if !in_dxfs && start.local_name().as_ref() == b"font" => {
927 current_font = Some(RawFont {
928 bold: false,
929 italic: false,
930 size: 11.0,
931 color: None,
932 name: None,
933 underline: false,
934 strike: false,
935 });
936 }
937 Event::End(end) if !in_dxfs && end.local_name().as_ref() == b"font" => {
938 if let Some(font) = current_font.take() {
939 fonts.push(font);
940 }
941 }
942 Event::Empty(start)
943 if current_font.is_some() && start.local_name().as_ref() == b"b" =>
944 {
945 if let Some(font) = current_font.as_mut() {
946 font.bold = true;
947 }
948 }
949 Event::Empty(start)
950 if current_font.is_some() && start.local_name().as_ref() == b"i" =>
951 {
952 if let Some(font) = current_font.as_mut() {
953 font.italic = true;
954 }
955 }
956 Event::Empty(start)
958 if current_font.is_some() && start.local_name().as_ref() == b"strike" =>
959 {
960 if let Some(font) = current_font.as_mut() {
961 font.strike = true;
962 }
963 }
964 Event::Empty(start)
965 if current_font.is_some() && start.local_name().as_ref() == b"u" =>
966 {
967 if let Some(font) = current_font.as_mut() {
968 font.underline = true;
969 }
970 }
971 Event::Empty(start)
972 if current_font.is_some() && start.local_name().as_ref() == b"name" =>
973 {
974 if let Some(font) = current_font.as_mut() {
975 font.name = raw_attribute(&start, b"val");
976 }
977 }
978 Event::Empty(start)
979 if current_font.is_some() && start.local_name().as_ref() == b"sz" =>
980 {
981 if let Some(size) =
982 raw_attribute(&start, b"val").and_then(|value| value.parse::<f64>().ok())
983 {
984 if let Some(font) = current_font.as_mut() {
985 font.size = size;
986 }
987 }
988 }
989 Event::Empty(start)
990 if current_font.is_some() && start.local_name().as_ref() == b"color" =>
991 {
992 if let Some(font) = current_font.as_mut() {
993 font.color = resolve_color(&start);
994 }
995 }
996
997 Event::Start(start) if !in_dxfs && start.local_name().as_ref() == b"fill" => {
998 in_fill = true;
999 current_fill_pattern_type = None;
1000 current_fill_fg_color = None;
1001 current_fill_bg_color = None;
1002 current_fill_is_gradient = false;
1003 current_gradient_angle = None;
1004 current_gradient_stops.clear();
1005 }
1006 Event::End(end) if !in_dxfs && end.local_name().as_ref() == b"fill" => {
1007 in_fill = false;
1008 let fill = if current_fill_is_gradient {
1009 RawFill::Gradient(GradientFill {
1010 angle: current_gradient_angle.unwrap_or(0.0),
1011 stops: std::mem::take(&mut current_gradient_stops),
1012 })
1013 } else {
1014 match current_fill_pattern_type.take().as_deref() {
1015 Some("none") => RawFill::None,
1016 Some("gray125") => RawFill::Gray125,
1017 Some("solid") => match current_fill_fg_color.take() {
1018 Some(color) => RawFill::Solid(color),
1019 None => RawFill::Other,
1020 },
1021 Some(other) => match parse_pattern_type(other) {
1022 Some(pattern_type) => RawFill::Pattern(PatternFill {
1023 pattern_type,
1024 fg_color: current_fill_fg_color.take(),
1025 bg_color: current_fill_bg_color.take(),
1026 }),
1027 None => RawFill::Other,
1028 },
1029 None => RawFill::Other,
1030 }
1031 };
1032 fills.push(fill);
1033 }
1034 Event::Start(start) if in_fill && start.local_name().as_ref() == b"patternFill" => {
1035 current_fill_pattern_type = raw_attribute(&start, b"patternType");
1036 }
1037 Event::Empty(start) if in_fill && start.local_name().as_ref() == b"patternFill" => {
1038 current_fill_pattern_type = raw_attribute(&start, b"patternType");
1039 }
1040 Event::Empty(start) if in_fill && start.local_name().as_ref() == b"fgColor" => {
1041 current_fill_fg_color = resolve_color(&start);
1042 }
1043 Event::Empty(start) if in_fill && start.local_name().as_ref() == b"bgColor" => {
1044 current_fill_bg_color = resolve_color(&start);
1045 }
1046 Event::Start(start) if in_fill && start.local_name().as_ref() == b"gradientFill" => {
1048 current_fill_is_gradient = true;
1049 current_gradient_angle =
1050 raw_attribute(&start, b"degree").and_then(|value| value.parse::<f64>().ok());
1051 }
1052 Event::Start(start)
1053 if in_fill
1054 && current_fill_is_gradient
1055 && start.local_name().as_ref() == b"stop" =>
1056 {
1057 current_gradient_stop_position =
1058 raw_attribute(&start, b"position").and_then(|value| value.parse::<f64>().ok());
1059 }
1060 Event::Empty(start)
1061 if in_fill
1062 && current_fill_is_gradient
1063 && start.local_name().as_ref() == b"color" =>
1064 {
1065 if let (Some(position), Some(color)) =
1066 (current_gradient_stop_position.take(), resolve_color(&start))
1067 {
1068 current_gradient_stops.push(GradientStop::new(position, color));
1069 }
1070 }
1071
1072 Event::Start(start) if !in_dxfs && start.local_name().as_ref() == b"borders" => {
1075 in_borders = true;
1076 }
1077 Event::End(end) if end.local_name().as_ref() == b"borders" => {
1078 in_borders = false;
1079 }
1080 Event::Start(start) if in_borders && start.local_name().as_ref() == b"border" => {
1081 current_border = Some(RawBorder {
1082 diagonal_up: raw_attribute(&start, b"diagonalUp").as_deref() == Some("1"),
1083 diagonal_down: raw_attribute(&start, b"diagonalDown").as_deref() == Some("1"),
1084 ..RawBorder::default()
1085 });
1086 }
1087 Event::End(end) if in_borders && end.local_name().as_ref() == b"border" => {
1088 if let Some(border) = current_border.take() {
1089 borders.push(border);
1090 }
1091 }
1092 Event::Start(start)
1093 if current_border.is_some()
1094 && matches!(
1095 start.local_name().as_ref(),
1096 b"left" | b"right" | b"top" | b"bottom" | b"diagonal"
1097 ) =>
1098 {
1099 current_border_edge_name = Some(match start.local_name().as_ref() {
1100 b"left" => "left",
1101 b"right" => "right",
1102 b"top" => "top",
1103 b"bottom" => "bottom",
1104 _ => "diagonal",
1105 });
1106 current_border_edge_style = raw_attribute(&start, b"style");
1107 current_border_edge_color = None;
1108 }
1109 Event::Empty(start)
1110 if current_border.is_some()
1111 && matches!(
1112 start.local_name().as_ref(),
1113 b"left" | b"right" | b"top" | b"bottom" | b"diagonal"
1114 ) =>
1115 {
1116 }
1118 Event::Empty(start)
1119 if current_border_edge_name.is_some()
1120 && start.local_name().as_ref() == b"color" =>
1121 {
1122 current_border_edge_color = resolve_color(&start);
1123 }
1124 Event::End(end)
1125 if current_border_edge_name.is_some()
1126 && matches!(
1127 end.local_name().as_ref(),
1128 b"left" | b"right" | b"top" | b"bottom" | b"diagonal"
1129 ) =>
1130 {
1131 if let (Some(name), Some(style), Some(border)) = (
1132 current_border_edge_name.take(),
1133 current_border_edge_style.take(),
1134 current_border.as_mut(),
1135 ) {
1136 let edge = Some((style, current_border_edge_color.take()));
1137 match name {
1138 "left" => border.left = edge,
1139 "right" => border.right = edge,
1140 "top" => border.top = edge,
1141 "bottom" => border.bottom = edge,
1142 _ => border.diagonal = edge,
1143 }
1144 }
1145 current_border_edge_color = None;
1146 }
1147
1148 Event::Start(start) if start.local_name().as_ref() == b"cellXfs" => {
1149 in_cell_xfs = true;
1150 }
1151 Event::End(end) if end.local_name().as_ref() == b"cellXfs" => {
1152 in_cell_xfs = false;
1153 }
1154 Event::Empty(start) if in_cell_xfs && start.local_name().as_ref() == b"xf" => {
1155 xfs.push(raw_xf_from_attributes(&start));
1156 }
1157 Event::Start(start) if in_cell_xfs && start.local_name().as_ref() == b"xf" => {
1158 current_xf = Some(raw_xf_from_attributes(&start));
1159 }
1160 Event::End(end) if in_cell_xfs && end.local_name().as_ref() == b"xf" => {
1161 if let Some(xf) = current_xf.take() {
1162 xfs.push(xf);
1163 }
1164 }
1165 Event::Empty(start)
1166 if in_cell_xfs
1167 && current_xf.is_some()
1168 && start.local_name().as_ref() == b"alignment" =>
1169 {
1170 if let Some(xf) = current_xf.as_mut() {
1171 xf.horizontal = raw_attribute(&start, b"horizontal");
1172 xf.vertical = raw_attribute(&start, b"vertical");
1173 xf.wrap_text = raw_attribute(&start, b"wrapText").as_deref() == Some("1");
1174 xf.indent = raw_attribute(&start, b"indent")
1175 .and_then(|value| value.parse::<u32>().ok());
1176 xf.text_rotation = raw_attribute(&start, b"textRotation")
1177 .and_then(|value| value.parse::<u16>().ok());
1178 xf.shrink_to_fit =
1179 raw_attribute(&start, b"shrinkToFit").as_deref() == Some("1");
1180 }
1181 }
1182 Event::Empty(start)
1184 if in_cell_xfs
1185 && current_xf.is_some()
1186 && start.local_name().as_ref() == b"protection" =>
1187 {
1188 if let Some(xf) = current_xf.as_mut() {
1189 xf.locked = raw_attribute(&start, b"locked").map(|value| value == "1");
1190 xf.formula_hidden = raw_attribute(&start, b"hidden").map(|value| value == "1");
1191 }
1192 }
1193
1194 Event::Start(start) if start.local_name().as_ref() == b"cellStyleXfs" => {
1198 in_cell_style_xfs = true;
1199 }
1200 Event::End(end) if end.local_name().as_ref() == b"cellStyleXfs" => {
1201 in_cell_style_xfs = false;
1202 }
1203 Event::Empty(start) if in_cell_style_xfs && start.local_name().as_ref() == b"xf" => {
1204 style_xfs.push(raw_xf_from_attributes(&start));
1205 }
1206 Event::Start(start) if in_cell_style_xfs && start.local_name().as_ref() == b"xf" => {
1207 current_style_xf = Some(raw_xf_from_attributes(&start));
1208 }
1209 Event::End(end) if in_cell_style_xfs && end.local_name().as_ref() == b"xf" => {
1210 if let Some(xf) = current_style_xf.take() {
1211 style_xfs.push(xf);
1212 }
1213 }
1214 Event::Empty(start)
1215 if in_cell_style_xfs
1216 && current_style_xf.is_some()
1217 && start.local_name().as_ref() == b"alignment" =>
1218 {
1219 if let Some(xf) = current_style_xf.as_mut() {
1220 xf.horizontal = raw_attribute(&start, b"horizontal");
1221 xf.vertical = raw_attribute(&start, b"vertical");
1222 xf.wrap_text = raw_attribute(&start, b"wrapText").as_deref() == Some("1");
1223 xf.indent = raw_attribute(&start, b"indent")
1224 .and_then(|value| value.parse::<u32>().ok());
1225 xf.text_rotation = raw_attribute(&start, b"textRotation")
1226 .and_then(|value| value.parse::<u16>().ok());
1227 xf.shrink_to_fit =
1228 raw_attribute(&start, b"shrinkToFit").as_deref() == Some("1");
1229 }
1230 }
1231 Event::Empty(start)
1232 if in_cell_style_xfs
1233 && current_style_xf.is_some()
1234 && start.local_name().as_ref() == b"protection" =>
1235 {
1236 if let Some(xf) = current_style_xf.as_mut() {
1237 xf.locked = raw_attribute(&start, b"locked").map(|value| value == "1");
1238 xf.formula_hidden = raw_attribute(&start, b"hidden").map(|value| value == "1");
1239 }
1240 }
1241
1242 Event::Empty(start) | Event::Start(start)
1247 if start.local_name().as_ref() == b"cellStyle" =>
1248 {
1249 if let Some(name) = raw_attribute(&start, b"name") {
1250 let xf_id = raw_attribute(&start, b"xfId")
1251 .and_then(|value| value.parse::<usize>().ok())
1252 .unwrap_or(0);
1253 let builtin_id = raw_attribute(&start, b"builtinId")
1254 .and_then(|value| value.parse::<u32>().ok());
1255 cell_styles.push((name, xf_id, builtin_id));
1256 }
1257 }
1258
1259 Event::Start(start) if start.local_name().as_ref() == b"dxfs" => {
1260 in_dxfs = true;
1261 }
1262 Event::End(end) if end.local_name().as_ref() == b"dxfs" => {
1263 in_dxfs = false;
1264 }
1265 Event::Start(start) if in_dxfs && start.local_name().as_ref() == b"dxf" => {
1266 current_dxf = Some(CellFormat::default());
1267 }
1268 Event::End(end) if in_dxfs && end.local_name().as_ref() == b"dxf" => {
1269 if let Some(dxf) = current_dxf.take() {
1270 dxfs.push(dxf);
1271 }
1272 }
1273
1274 Event::Start(start)
1275 if in_dxfs && current_dxf.is_some() && start.local_name().as_ref() == b"font" =>
1276 {
1277 in_dxf_font = true;
1278 dxf_font_bold = false;
1279 dxf_font_italic = false;
1280 dxf_font_color = None;
1281 }
1282 Event::End(end) if in_dxfs && end.local_name().as_ref() == b"font" => {
1283 in_dxf_font = false;
1284 if let Some(dxf) = current_dxf.as_mut() {
1285 dxf.bold = dxf_font_bold;
1286 dxf.italic = dxf_font_italic;
1287 dxf.font_color = dxf_font_color.take();
1288 }
1289 }
1290 Event::Empty(start) if in_dxf_font && start.local_name().as_ref() == b"b" => {
1291 dxf_font_bold = true;
1292 }
1293 Event::Empty(start) if in_dxf_font && start.local_name().as_ref() == b"i" => {
1294 dxf_font_italic = true;
1295 }
1296 Event::Empty(start) if in_dxf_font && start.local_name().as_ref() == b"color" => {
1297 dxf_font_color = raw_attribute(&start, b"rgb");
1298 }
1299
1300 Event::Empty(start)
1301 if in_dxfs && current_dxf.is_some() && start.local_name().as_ref() == b"numFmt" =>
1302 {
1303 let code = raw_attribute(&start, b"formatCode");
1304 if let Some(dxf) = current_dxf.as_mut() {
1305 dxf.number_format = code;
1306 }
1307 }
1308
1309 Event::Start(start)
1310 if in_dxfs && current_dxf.is_some() && start.local_name().as_ref() == b"fill" =>
1311 {
1312 in_dxf_fill = true;
1313 dxf_fill_pattern_type = None;
1314 dxf_fill_bg_color = None;
1315 }
1316 Event::End(end) if in_dxfs && end.local_name().as_ref() == b"fill" => {
1317 in_dxf_fill = false;
1318 if dxf_fill_pattern_type.as_deref() == Some("solid") {
1319 if let Some(dxf) = current_dxf.as_mut() {
1320 dxf.fill_color = dxf_fill_bg_color.take();
1321 }
1322 }
1323 }
1324 Event::Start(start) if in_dxf_fill && start.local_name().as_ref() == b"patternFill" => {
1325 dxf_fill_pattern_type = raw_attribute(&start, b"patternType");
1326 }
1327 Event::Empty(start) if in_dxf_fill && start.local_name().as_ref() == b"patternFill" => {
1328 dxf_fill_pattern_type = raw_attribute(&start, b"patternType");
1329 }
1330 Event::Empty(start) if in_dxf_fill && start.local_name().as_ref() == b"bgColor" => {
1333 dxf_fill_bg_color = raw_attribute(&start, b"rgb");
1334 }
1335
1336 Event::Empty(start)
1337 if in_dxfs
1338 && current_dxf.is_some()
1339 && start.local_name().as_ref() == b"alignment" =>
1340 {
1341 if let Some(dxf) = current_dxf.as_mut() {
1342 dxf.horizontal_alignment = raw_attribute(&start, b"horizontal")
1343 .as_deref()
1344 .and_then(parse_horizontal_alignment);
1345 dxf.vertical_alignment = raw_attribute(&start, b"vertical")
1346 .as_deref()
1347 .and_then(parse_vertical_alignment);
1348 }
1349 }
1350
1351 _ => {}
1352 }
1353 }
1354
1355 let style_name_by_xf_id: HashMap<usize, String> = cell_styles
1359 .iter()
1360 .filter(|(_, xf_id, _)| *xf_id != 0)
1361 .map(|(name, xf_id, _)| (*xf_id, name.clone()))
1362 .collect();
1363
1364 let cell_formats = (0..xfs.len())
1365 .map(|index| {
1366 resolve_cell_format(
1367 index,
1368 &xfs,
1369 &fonts,
1370 &fills,
1371 &borders,
1372 &num_fmts,
1373 &style_name_by_xf_id,
1374 )
1375 })
1376 .collect();
1377
1378 let named_cell_styles: Vec<NamedCellStyle> = cell_styles
1383 .iter()
1384 .filter(|(_, xf_id, _)| *xf_id != 0)
1385 .filter_map(|(name, xf_id, builtin_id)| {
1386 let xf = style_xfs.get(*xf_id)?;
1387 let format = resolve_cell_format_raw(xf, &fonts, &fills, &borders, &num_fmts, None);
1388 let mut style = NamedCellStyle::new(name.clone(), format);
1389 if let Some(builtin_id) = builtin_id {
1390 style = style.with_builtin_id(*builtin_id);
1391 }
1392 Some(style)
1393 })
1394 .collect();
1395
1396 Ok(ParsedStyles {
1397 cell_formats,
1398 dxfs,
1399 table_styles,
1400 named_cell_styles,
1401 })
1402}
1403
1404fn parse_table_style_element_type(value: &str) -> Option<TableStyleElementType> {
1410 match value {
1411 "wholeTable" => Some(TableStyleElementType::WholeTable),
1412 "headerRow" => Some(TableStyleElementType::HeaderRow),
1413 "totalRow" => Some(TableStyleElementType::TotalRow),
1414 "firstRowStripe" => Some(TableStyleElementType::FirstRowStripe),
1415 "secondRowStripe" => Some(TableStyleElementType::SecondRowStripe),
1416 "firstColumnStripe" => Some(TableStyleElementType::FirstColumnStripe),
1417 "secondColumnStripe" => Some(TableStyleElementType::SecondColumnStripe),
1418 "firstColumn" => Some(TableStyleElementType::FirstColumn),
1419 "lastColumn" => Some(TableStyleElementType::LastColumn),
1420 _ => None,
1421 }
1422}
1423
1424fn raw_xf_from_attributes(start: &BytesStart) -> RawXf {
1425 RawXf {
1426 num_fmt_id: raw_attribute(start, b"numFmtId")
1427 .and_then(|value| value.parse::<u32>().ok())
1428 .unwrap_or(0),
1429 font_id: raw_attribute(start, b"fontId")
1430 .and_then(|value| value.parse::<usize>().ok())
1431 .unwrap_or(0),
1432 fill_id: raw_attribute(start, b"fillId")
1433 .and_then(|value| value.parse::<usize>().ok())
1434 .unwrap_or(0),
1435 border_id: raw_attribute(start, b"borderId")
1436 .and_then(|value| value.parse::<usize>().ok())
1437 .unwrap_or(0),
1438 xf_id: raw_attribute(start, b"xfId")
1439 .and_then(|value| value.parse::<usize>().ok())
1440 .unwrap_or(0),
1441 horizontal: None,
1442 vertical: None,
1443 wrap_text: false,
1444 indent: None,
1445 text_rotation: None,
1446 shrink_to_fit: false,
1447 locked: None,
1448 formula_hidden: None,
1449 }
1450}
1451
1452fn indexed_color(index: u32) -> Option<&'static str> {
1460 const PALETTE: [&str; 64] = [
1461 "FF000000", "FFFFFFFF", "FFFF0000", "FF00FF00", "FF0000FF", "FFFFFF00", "FFFF00FF",
1462 "FF00FFFF", "FF000000", "FFFFFFFF", "FFFF0000", "FF00FF00", "FF0000FF", "FFFFFF00",
1463 "FFFF00FF", "FF00FFFF", "FF800000", "FF008000", "FF000080", "FF808000", "FF800080",
1464 "FF008080", "FFC0C0C0", "FF808080", "FF9999FF", "FF993366", "FFFFFFCC", "FFCCFFFF",
1465 "FF660066", "FFFF8080", "FF0066CC", "FFCCCCFF", "FF000080", "FFFF00FF", "FFFFFF00",
1466 "FF00FFFF", "FF800080", "FF800000", "FF008080", "FF0000FF", "FF00CCFF", "FFCCFFFF",
1467 "FFCCFFCC", "FFFFFF99", "FF99CCFF", "FFFF99CC", "FFCC99FF", "FFFFCC99", "FF3366FF",
1468 "FF33CCCC", "FF99CC00", "FFFFCC00", "FFFF9900", "FFFF6600", "FF666699", "FF969696",
1469 "FF003366", "FF339966", "FF003300", "FF333300", "FF993300", "FF993366", "FF333399",
1470 "FF333333",
1471 ];
1472 match index {
1473 0..=63 => Some(PALETTE[index as usize]),
1474 64 => Some("FF000000"),
1475 65 => Some("FFFFFFFF"),
1476 _ => None,
1477 }
1478}
1479
1480fn resolve_color(start: &BytesStart) -> Option<String> {
1485 if let Some(rgb) = raw_attribute(start, b"rgb") {
1486 return Some(rgb);
1487 }
1488 raw_attribute(start, b"indexed")
1489 .and_then(|value| value.parse::<u32>().ok())
1490 .and_then(indexed_color)
1491 .map(|value| value.to_string())
1492}
1493
1494fn parse_pattern_type(value: &str) -> Option<PatternType> {
1498 match value {
1499 "gray0625" => Some(PatternType::Gray0625),
1500 "darkGray" => Some(PatternType::DarkGray),
1501 "mediumGray" => Some(PatternType::MediumGray),
1502 "lightGray" => Some(PatternType::LightGray),
1503 "darkHorizontal" => Some(PatternType::DarkHorizontal),
1504 "darkVertical" => Some(PatternType::DarkVertical),
1505 "darkDown" => Some(PatternType::DarkDown),
1506 "darkUp" => Some(PatternType::DarkUp),
1507 "darkGrid" => Some(PatternType::DarkGrid),
1508 "darkTrellis" => Some(PatternType::DarkTrellis),
1509 "lightHorizontal" => Some(PatternType::LightHorizontal),
1510 "lightVertical" => Some(PatternType::LightVertical),
1511 "lightDown" => Some(PatternType::LightDown),
1512 "lightUp" => Some(PatternType::LightUp),
1513 "lightGrid" => Some(PatternType::LightGrid),
1514 "lightTrellis" => Some(PatternType::LightTrellis),
1515 _ => None,
1516 }
1517}
1518
1519fn parse_border_style(value: &str) -> Option<BorderStyle> {
1524 match value {
1525 "thin" => Some(BorderStyle::Thin),
1526 "medium" => Some(BorderStyle::Medium),
1527 "thick" => Some(BorderStyle::Thick),
1528 "dashed" => Some(BorderStyle::Dashed),
1529 "dotted" => Some(BorderStyle::Dotted),
1530 "double" => Some(BorderStyle::Double),
1531 "hair" => Some(BorderStyle::Hair),
1532 _ => None,
1533 }
1534}
1535
1536fn resolve_border(border_id: usize, borders: &[RawBorder]) -> Border {
1540 let Some(raw) = borders.get(border_id) else {
1541 return Border::default();
1542 };
1543 let resolve_edge = |edge: &Option<(String, Option<String>)>| -> Option<BorderEdge> {
1544 edge.as_ref().and_then(|(style, color)| {
1545 parse_border_style(style).map(|style| {
1546 let mut edge = BorderEdge::new(style);
1547 if let Some(color) = color {
1548 edge = edge.with_color(color.clone());
1549 }
1550 edge
1551 })
1552 })
1553 };
1554 Border {
1555 top: resolve_edge(&raw.top),
1556 bottom: resolve_edge(&raw.bottom),
1557 left: resolve_edge(&raw.left),
1558 right: resolve_edge(&raw.right),
1559 diagonal: resolve_edge(&raw.diagonal),
1560 diagonal_up: raw.diagonal_up,
1561 diagonal_down: raw.diagonal_down,
1562 }
1563}
1564
1565fn parse_horizontal_alignment(value: &str) -> Option<HorizontalAlignment> {
1566 match value {
1567 "left" => Some(HorizontalAlignment::Left),
1568 "center" => Some(HorizontalAlignment::Center),
1569 "right" => Some(HorizontalAlignment::Right),
1570 _ => None,
1571 }
1572}
1573
1574fn parse_page_orientation(value: &str) -> Option<PageOrientation> {
1578 match value {
1579 "portrait" => Some(PageOrientation::Portrait),
1580 "landscape" => Some(PageOrientation::Landscape),
1581 _ => None,
1582 }
1583}
1584
1585fn parse_vertical_alignment(value: &str) -> Option<VerticalAlignment> {
1586 match value {
1587 "top" => Some(VerticalAlignment::Top),
1588 "center" => Some(VerticalAlignment::Center),
1589 "bottom" => Some(VerticalAlignment::Bottom),
1590 _ => None,
1591 }
1592}
1593
1594fn parse_comparison_operator(value: &str) -> Option<ComparisonOperator> {
1599 match value {
1600 "lessThan" => Some(ComparisonOperator::LessThan),
1601 "lessThanOrEqual" => Some(ComparisonOperator::LessThanOrEqual),
1602 "equal" => Some(ComparisonOperator::Equal),
1603 "notEqual" => Some(ComparisonOperator::NotEqual),
1604 "greaterThanOrEqual" => Some(ComparisonOperator::GreaterThanOrEqual),
1605 "greaterThan" => Some(ComparisonOperator::GreaterThan),
1606 "between" => Some(ComparisonOperator::Between),
1607 "notBetween" => Some(ComparisonOperator::NotBetween),
1608 _ => None,
1609 }
1610}
1611
1612fn text_comparison_operator_from_type(value: &str) -> Option<TextComparisonOperator> {
1616 match value {
1617 "containsText" => Some(TextComparisonOperator::Contains),
1618 "notContainsText" => Some(TextComparisonOperator::NotContains),
1619 "beginsWith" => Some(TextComparisonOperator::BeginsWith),
1620 "endsWith" => Some(TextComparisonOperator::EndsWith),
1621 _ => None,
1622 }
1623}
1624
1625fn resolve_cell_format(
1632 xf_index: usize,
1633 xfs: &[RawXf],
1634 fonts: &[RawFont],
1635 fills: &[RawFill],
1636 borders: &[RawBorder],
1637 num_fmts: &HashMap<u32, String>,
1638 style_name_by_xf_id: &HashMap<usize, String>,
1639) -> Option<CellFormat> {
1640 if xf_index == 0 {
1641 return None;
1642 }
1643 let xf = xfs.get(xf_index)?;
1644 let named_style = style_name_by_xf_id.get(&xf.xf_id).cloned();
1648 Some(resolve_cell_format_raw(
1649 xf,
1650 fonts,
1651 fills,
1652 borders,
1653 num_fmts,
1654 named_style,
1655 ))
1656}
1657
1658fn resolve_cell_format_raw(
1664 xf: &RawXf,
1665 fonts: &[RawFont],
1666 fills: &[RawFill],
1667 borders: &[RawBorder],
1668 num_fmts: &HashMap<u32, String>,
1669 named_style: Option<String>,
1670) -> CellFormat {
1671 let number_format = num_fmts.get(&xf.num_fmt_id).cloned();
1672
1673 let font = fonts.get(xf.font_id);
1674 let bold = font.map(|font| font.bold).unwrap_or(false);
1675 let italic = font.map(|font| font.italic).unwrap_or(false);
1676 let underline = font.map(|font| font.underline).unwrap_or(false);
1677 let strike = font.map(|font| font.strike).unwrap_or(false);
1678 let (font_size, font_color, font_name) = if xf.font_id == 0 {
1682 (None, None, None)
1683 } else {
1684 (
1685 font.map(|font| font.size),
1686 font.and_then(|font| font.color.clone()),
1687 font.and_then(|font| font.name.clone()),
1688 )
1689 };
1690
1691 let (fill_color, pattern_fill, gradient_fill) = match fills.get(xf.fill_id) {
1694 Some(RawFill::Solid(color)) => (Some(color.clone()), None, None),
1695 Some(RawFill::Pattern(pattern)) => (None, Some(pattern.clone()), None),
1696 Some(RawFill::Gradient(gradient)) => (None, None, Some(gradient.clone())),
1697 _ => (None, None, None),
1698 };
1699
1700 let horizontal_alignment = xf
1701 .horizontal
1702 .as_deref()
1703 .and_then(parse_horizontal_alignment);
1704 let vertical_alignment = xf.vertical.as_deref().and_then(parse_vertical_alignment);
1705
1706 CellFormat {
1707 number_format,
1708 bold,
1709 italic,
1710 font_size,
1711 font_color,
1712 fill_color,
1713 horizontal_alignment,
1714 vertical_alignment,
1715 border: resolve_border(xf.border_id, borders),
1716 font_name,
1717 underline,
1718 strike,
1719 wrap_text: xf.wrap_text,
1720 indent: xf.indent,
1721 text_rotation: xf.text_rotation,
1722 shrink_to_fit: xf.shrink_to_fit,
1723 pattern_fill,
1724 gradient_fill,
1725 locked: xf.locked,
1726 formula_hidden: xf.formula_hidden,
1727 named_style,
1728 }
1729}
1730
1731#[derive(Default)]
1737struct ParsedCoreProperties {
1738 title: Option<String>,
1739 author: Option<String>,
1740 subject: Option<String>,
1741 keywords: Option<String>,
1742}
1743
1744fn parse_core_properties_xml(xml: &str) -> Result<ParsedCoreProperties> {
1746 let mut reader = Reader::from_xml_str(xml);
1747 let mut title = None;
1748 let mut author = None;
1749 let mut subject = None;
1750 let mut keywords = None;
1751 let mut in_element: Option<&'static str> = None;
1752 let mut buffer = String::new();
1753
1754 loop {
1755 match reader.read_event()? {
1756 Event::Eof => break,
1757 Event::Start(start) if start.local_name().as_ref() == b"title" => {
1758 in_element = Some("title");
1759 buffer.clear();
1760 }
1761 Event::Start(start) if start.local_name().as_ref() == b"creator" => {
1762 in_element = Some("creator");
1763 buffer.clear();
1764 }
1765 Event::Start(start) if start.local_name().as_ref() == b"subject" => {
1766 in_element = Some("subject");
1767 buffer.clear();
1768 }
1769 Event::Start(start) if start.local_name().as_ref() == b"keywords" => {
1770 in_element = Some("keywords");
1771 buffer.clear();
1772 }
1773 Event::Text(text) if in_element.is_some() => {
1774 buffer.push_str(&xml_core::decode_text(&text)?);
1775 }
1776 Event::GeneralRef(reference) if in_element.is_some() => {
1777 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
1778 buffer.push_str(&resolved);
1779 }
1780 }
1781 Event::End(end)
1782 if in_element.is_some()
1783 && matches!(
1784 end.local_name().as_ref(),
1785 b"title" | b"creator" | b"subject" | b"keywords"
1786 ) =>
1787 {
1788 let text = std::mem::take(&mut buffer);
1789 match in_element.take() {
1790 Some("title") => title = Some(text),
1791 Some("creator") => author = Some(text),
1792 Some("subject") => subject = Some(text),
1793 Some("keywords") => keywords = Some(text),
1794 _ => {}
1795 }
1796 }
1797 _ => {}
1798 }
1799 }
1800
1801 Ok(ParsedCoreProperties {
1802 title,
1803 author,
1804 subject,
1805 keywords,
1806 })
1807}
1808
1809fn parse_extended_properties_xml(
1812 xml: &str,
1813) -> Result<(Option<String>, Option<String>, Option<String>)> {
1814 let mut reader = Reader::from_xml_str(xml);
1815 let mut company = None;
1816 let mut manager = None;
1817 let mut hyperlink_base = None;
1818 let mut in_element: Option<&'static str> = None;
1819 let mut buffer = String::new();
1820
1821 loop {
1822 match reader.read_event()? {
1823 Event::Eof => break,
1824 Event::Start(start) if start.local_name().as_ref() == b"Company" => {
1825 in_element = Some("Company");
1826 buffer.clear();
1827 }
1828 Event::Start(start) if start.local_name().as_ref() == b"Manager" => {
1829 in_element = Some("Manager");
1830 buffer.clear();
1831 }
1832 Event::Start(start) if start.local_name().as_ref() == b"HyperlinkBase" => {
1833 in_element = Some("HyperlinkBase");
1834 buffer.clear();
1835 }
1836 Event::Text(text) if in_element.is_some() => {
1837 buffer.push_str(&xml_core::decode_text(&text)?);
1838 }
1839 Event::GeneralRef(reference) if in_element.is_some() => {
1840 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
1841 buffer.push_str(&resolved);
1842 }
1843 }
1844 Event::End(end)
1845 if in_element.is_some()
1846 && matches!(
1847 end.local_name().as_ref(),
1848 b"Company" | b"Manager" | b"HyperlinkBase"
1849 ) =>
1850 {
1851 let text = std::mem::take(&mut buffer);
1852 match in_element.take() {
1853 Some("Company") => company = Some(text),
1854 Some("Manager") => manager = Some(text),
1855 Some("HyperlinkBase") => hyperlink_base = Some(text),
1856 _ => {}
1857 }
1858 }
1859 _ => {}
1860 }
1861 }
1862
1863 Ok((company, manager, hyperlink_base))
1864}
1865
1866fn parse_custom_properties_xml(xml: &str) -> Result<Vec<(String, CustomPropertyValue)>> {
1876 let mut reader = Reader::from_xml_str(xml);
1877 let mut custom_properties = Vec::new();
1878 let mut current_name: Option<String> = None;
1879 let mut current_variant: Option<Vec<u8>> = None;
1880 let mut buffer = String::new();
1881
1882 loop {
1883 match reader.read_event()? {
1884 Event::Eof => break,
1885
1886 Event::Start(start) if start.local_name().as_ref() == b"property" => {
1887 current_name = raw_attribute(&start, b"name");
1888 }
1889
1890 Event::Start(start) | Event::Empty(start)
1891 if current_name.is_some()
1892 && matches!(
1893 start.local_name().as_ref(),
1894 b"lpwstr" | b"bool" | b"i4" | b"r8"
1895 ) =>
1896 {
1897 current_variant = Some(start.local_name().as_ref().to_vec());
1898 buffer.clear();
1899 }
1900
1901 Event::Text(text) if current_variant.is_some() => {
1902 buffer.push_str(&xml_core::decode_text(&text)?);
1903 }
1904
1905 Event::GeneralRef(reference) if current_variant.is_some() => {
1906 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
1907 buffer.push_str(&resolved);
1908 }
1909 }
1910
1911 Event::End(end) => {
1912 if let Some(variant) = current_variant.take() {
1913 if end.local_name().as_ref() == variant.as_slice() {
1914 if let Some(name) = current_name.clone() {
1915 let value = std::mem::take(&mut buffer);
1916 let parsed = match variant.as_slice() {
1917 b"lpwstr" => Some(CustomPropertyValue::Text(value)),
1918 b"bool" => {
1919 Some(CustomPropertyValue::Bool(value == "true" || value == "1"))
1920 }
1921 b"i4" => value.parse::<i32>().ok().map(CustomPropertyValue::Int),
1922 b"r8" => value.parse::<f64>().ok().map(CustomPropertyValue::Number),
1923 _ => None,
1924 };
1925 if let Some(value) = parsed {
1926 custom_properties.push((name, value));
1927 }
1928 }
1929 } else {
1930 current_variant = Some(variant);
1931 }
1932 } else if end.local_name().as_ref() == b"property" {
1933 current_name = None;
1934 }
1935 }
1936
1937 _ => {}
1938 }
1939 }
1940
1941 Ok(custom_properties)
1942}
1943
1944struct ParsedExternalLink {
1947 sheet_names: Vec<String>,
1948 defined_names: Vec<(String, String)>,
1949}
1950
1951fn parse_external_link_xml(xml: &str) -> Result<ParsedExternalLink> {
1955 let mut reader = Reader::from_xml_str(xml);
1956 let mut sheet_names = Vec::new();
1957 let mut defined_names = Vec::new();
1958
1959 loop {
1960 match reader.read_event()? {
1961 Event::Eof => break,
1962 Event::Empty(start) if start.local_name().as_ref() == b"sheetName" => {
1963 if let Some(name) = raw_attribute(&start, b"val") {
1964 sheet_names.push(name);
1965 }
1966 }
1967 Event::Empty(start) if start.local_name().as_ref() == b"definedName" => {
1968 if let (Some(name), Some(refers_to)) = (
1969 raw_attribute(&start, b"name"),
1970 raw_attribute(&start, b"refersTo"),
1971 ) {
1972 defined_names.push((name, refers_to));
1973 }
1974 }
1975 _ => {}
1976 }
1977 }
1978
1979 Ok(ParsedExternalLink {
1980 sheet_names,
1981 defined_names,
1982 })
1983}
1984
1985fn parse_comments_xml(xml: &str) -> Result<Vec<CellComment>> {
1994 let mut reader = Reader::from_xml_str(xml);
1995 let mut authors: Vec<String> = Vec::new();
1996 let mut in_author = false;
1997 let mut author_buffer = String::new();
1998
1999 let mut comments = Vec::new();
2000 let mut in_comment = false;
2001 let mut current_ref: Option<String> = None;
2002 let mut current_author_id: Option<usize> = None;
2003
2004 let mut in_comment_text = false;
2005 let mut text_is_rich = false;
2006 let mut plain_buffer = String::new();
2007 let mut runs: Vec<RichTextRun> = Vec::new();
2008
2009 let mut in_run = false;
2010 let mut in_rpr = false;
2011 let mut in_text_element = false;
2012 let mut run_text_buffer = String::new();
2013 let mut run_bold = false;
2014 let mut run_italic = false;
2015 let mut run_size: Option<f64> = None;
2016 let mut run_color: Option<String> = None;
2017 let mut run_name: Option<String> = None;
2018 let mut run_underline = false;
2019 let mut run_strike = false;
2020
2021 loop {
2022 match reader.read_event()? {
2023 Event::Eof => break,
2024
2025 Event::Start(start) if start.local_name().as_ref() == b"author" => {
2026 in_author = true;
2027 author_buffer.clear();
2028 }
2029 Event::End(end) if end.local_name().as_ref() == b"author" => {
2030 in_author = false;
2031 authors.push(std::mem::take(&mut author_buffer));
2032 }
2033 Event::Text(text) if in_author => {
2034 author_buffer.push_str(&xml_core::decode_text(&text)?);
2035 }
2036
2037 Event::Start(start) if start.local_name().as_ref() == b"comment" => {
2038 in_comment = true;
2039 current_ref = raw_attribute(&start, b"ref");
2040 current_author_id = raw_attribute(&start, b"authorId")
2041 .and_then(|value| value.parse::<usize>().ok());
2042 }
2043 Event::End(end) if end.local_name().as_ref() == b"comment" => {
2044 in_comment = false;
2045 if let Some(cell) = current_ref.take() {
2046 let author = current_author_id
2047 .take()
2048 .and_then(|id| authors.get(id).cloned())
2049 .unwrap_or_default();
2050 let comment = if text_is_rich {
2051 CellComment::new(cell, author, String::new())
2052 .with_rich_text(std::mem::take(&mut runs))
2053 } else {
2054 CellComment::new(cell, author, std::mem::take(&mut plain_buffer))
2055 };
2056 comments.push(comment);
2057 }
2058 }
2059
2060 Event::Start(start) if in_comment && start.local_name().as_ref() == b"text" => {
2061 in_comment_text = true;
2062 text_is_rich = false;
2063 plain_buffer.clear();
2064 runs.clear();
2065 }
2066 Event::End(end) if in_comment_text && end.local_name().as_ref() == b"text" => {
2067 in_comment_text = false;
2068 }
2069
2070 Event::Start(start) if in_comment_text && start.local_name().as_ref() == b"r" => {
2071 in_run = true;
2072 text_is_rich = true;
2073 run_text_buffer.clear();
2074 run_bold = false;
2075 run_italic = false;
2076 run_size = None;
2077 run_color = None;
2078 run_name = None;
2079 run_underline = false;
2080 run_strike = false;
2081 }
2082 Event::End(end) if in_comment_text && end.local_name().as_ref() == b"r" => {
2083 in_run = false;
2084 runs.push(RichTextRun {
2085 text: std::mem::take(&mut run_text_buffer),
2086 bold: std::mem::take(&mut run_bold),
2087 italic: std::mem::take(&mut run_italic),
2088 font_color: run_color.take(),
2089 font_size: run_size.take(),
2090 font_name: run_name.take(),
2091 underline: std::mem::take(&mut run_underline),
2092 strike: std::mem::take(&mut run_strike),
2093 });
2094 }
2095
2096 Event::Start(start) if in_run && start.local_name().as_ref() == b"rPr" => {
2097 in_rpr = true;
2098 }
2099 Event::End(end) if in_run && end.local_name().as_ref() == b"rPr" => {
2100 in_rpr = false;
2101 }
2102 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"b" => {
2103 run_bold = true;
2104 }
2105 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"i" => {
2106 run_italic = true;
2107 }
2108 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"strike" => {
2109 run_strike = true;
2110 }
2111 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"u" => {
2112 run_underline = true;
2113 }
2114 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"rFont" => {
2115 run_name = raw_attribute(&start, b"val");
2116 }
2117 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"sz" => {
2118 run_size =
2119 raw_attribute(&start, b"val").and_then(|value| value.parse::<f64>().ok());
2120 }
2121 Event::Empty(start) if in_rpr && start.local_name().as_ref() == b"color" => {
2122 run_color = resolve_color(&start);
2123 }
2124
2125 Event::Start(start) if in_comment_text && start.local_name().as_ref() == b"t" => {
2126 in_text_element = true;
2127 }
2128 Event::End(end) if in_comment_text && end.local_name().as_ref() == b"t" => {
2129 in_text_element = false;
2130 }
2131 Event::Text(text) if in_text_element && in_run => {
2132 run_text_buffer.push_str(&xml_core::decode_text(&text)?);
2133 }
2134 Event::GeneralRef(reference) if in_text_element && in_run => {
2135 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
2136 run_text_buffer.push_str(&resolved);
2137 }
2138 }
2139 Event::Text(text) if in_text_element && !in_run => {
2140 plain_buffer.push_str(&xml_core::decode_text(&text)?);
2141 }
2142 Event::GeneralRef(reference) if in_text_element && !in_run => {
2143 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
2144 plain_buffer.push_str(&resolved);
2145 }
2146 }
2147
2148 _ => {}
2149 }
2150 }
2151
2152 Ok(comments)
2153}
2154
2155fn parse_drawing_xml(
2166 xml: &str,
2167 drawing_relationships: &opc::Relationships,
2168 package: &Package,
2169) -> Result<SheetDrawing> {
2170 let mut reader = Reader::from_xml_str(xml);
2171 let mut drawing = SheetDrawing::new();
2172
2173 loop {
2174 match reader.read_event()? {
2175 Event::Start(start) if start.local_name().as_ref() == b"twoCellAnchor" => {
2176 if let Some(anchor) =
2177 parse_drawing_anchor(&mut reader, drawing_relationships, package)?
2178 {
2179 drawing.anchors.push(anchor);
2180 }
2181 }
2182 Event::Eof => break,
2183 _ => {}
2184 }
2185 }
2186
2187 Ok(drawing)
2188}
2189
2190fn parse_drawing_anchor(
2197 reader: &mut Reader,
2198 drawing_relationships: &opc::Relationships,
2199 package: &Package,
2200) -> Result<Option<DrawingAnchor>> {
2201 let mut from: Option<CellAnchorPoint> = None;
2202 let mut to: Option<CellAnchorPoint> = None;
2203 let mut object: Option<DrawingObject> = None;
2204
2205 loop {
2206 match reader.read_event()? {
2207 Event::Start(start) if start.local_name().as_ref() == b"from" => {
2208 from = Some(parse_cell_anchor_point(reader)?);
2209 }
2210 Event::Start(start) if start.local_name().as_ref() == b"to" => {
2211 to = Some(parse_cell_anchor_point(reader)?);
2212 }
2213 Event::Start(start) if start.local_name().as_ref() == b"pic" => {
2214 object = parse_sheet_picture(reader, drawing_relationships, package)?
2215 .map(|picture| DrawingObject::Picture(Box::new(picture)));
2216 }
2217 Event::Start(start) if start.local_name().as_ref() == b"graphicFrame" => {
2218 object = parse_sheet_chart(reader, drawing_relationships, package)?
2219 .map(|chart| DrawingObject::Chart(Box::new(chart)));
2220 }
2221 Event::Start(_) => skip_subtree(reader)?,
2222 Event::Empty(start) if start.local_name().as_ref() == b"clientData" => {}
2223 Event::End(_) => break,
2224 Event::Eof => break,
2225 _ => {}
2226 }
2227 }
2228
2229 match (from, to, object) {
2230 (Some(from), Some(to), Some(object)) => Ok(Some(DrawingAnchor::new(from, to, object))),
2231 _ => Ok(None),
2232 }
2233}
2234
2235fn parse_cell_anchor_point(reader: &mut Reader) -> Result<CellAnchorPoint> {
2238 let mut point = CellAnchorPoint::default();
2239 let mut text_buffer = String::new();
2240 let mut current_field: Option<&str> = None;
2241
2242 loop {
2243 match reader.read_event()? {
2244 Event::Start(start) => {
2245 current_field = match start.local_name().as_ref() {
2246 b"col" => Some("col"),
2247 b"colOff" => Some("colOff"),
2248 b"row" => Some("row"),
2249 b"rowOff" => Some("rowOff"),
2250 _ => None,
2251 };
2252 text_buffer.clear();
2253 }
2254 Event::Text(text) => {
2255 text_buffer.push_str(&xml_core::decode_text(&text)?);
2256 }
2257 Event::End(_) => {
2258 match current_field.take() {
2259 Some("col") => point.column = text_buffer.trim().parse().unwrap_or(0),
2260 Some("colOff") => {
2261 point.column_offset_emu = text_buffer.trim().parse().unwrap_or(0)
2262 }
2263 Some("row") => point.row = text_buffer.trim().parse().unwrap_or(0),
2264 Some("rowOff") => {
2265 point.row_offset_emu = text_buffer.trim().parse().unwrap_or(0)
2266 }
2267 Some(_) | None => {
2268 if current_field.is_none() && text_buffer.is_empty() {
2271 return Ok(point);
2272 }
2273 }
2274 }
2275 text_buffer.clear();
2276 }
2277 Event::Eof => return Ok(point),
2278 _ => {}
2279 }
2280 }
2281}
2282
2283fn parse_sheet_picture(
2290 reader: &mut Reader,
2291 drawing_relationships: &opc::Relationships,
2292 package: &Package,
2293) -> Result<Option<SheetPicture>> {
2294 let mut name = String::new();
2295 let mut relationship_id: Option<String> = None;
2296 let mut shape_properties = None;
2297
2298 loop {
2299 match reader.read_event()? {
2300 Event::Start(start) if start.local_name().as_ref() == b"nvPicPr" => {
2304 name = parse_non_visual_name(reader, b"nvPicPr")?;
2305 }
2306 Event::Start(start) if start.local_name().as_ref() == b"blipFill" => {
2307 relationship_id = parse_picture_blip_fill_relationship_id(reader)?;
2308 }
2309 Event::Empty(start) if start.local_name().as_ref() == b"blip" => {
2310 relationship_id = raw_attribute(&start, b"embed");
2311 }
2312 Event::Start(start) if start.local_name().as_ref() == b"spPr" => {
2313 shape_properties = Some(drawing::read_shape_properties(reader)?);
2323 }
2324 Event::Start(_) => skip_subtree(reader)?,
2325 Event::End(end) if end.local_name().as_ref() == b"pic" => break,
2326 Event::Eof => break,
2327 _ => {}
2328 }
2329 }
2330
2331 let Some(relationship_id) = relationship_id else {
2332 return Ok(None);
2333 };
2334 let Some(relationship) = drawing_relationships.by_id(&relationship_id) else {
2335 return Ok(None);
2336 };
2337 if relationship.rel_type != IMAGE_RELATIONSHIP_TYPE {
2338 return Ok(None);
2339 }
2340 let part_name = format!("/xl/{}", relationship.target.trim_start_matches("../"));
2341 let Some(part) = package.part(&part_name) else {
2342 return Ok(None);
2343 };
2344 let extension = part_name.rsplit('.').next().unwrap_or("");
2345 let Some(format) = PictureFormat::from_extension(extension) else {
2346 return Ok(None);
2347 };
2348
2349 let mut picture = SheetPicture::new(part.data.clone(), format).with_name(name);
2350 if let Some(mut shape_properties) = shape_properties {
2351 shape_properties.transform = None;
2358
2359 if matches!(
2367 shape_properties.geometry,
2368 Some(drawing::Geometry::Preset(drawing::PresetShape::Rectangle))
2369 ) {
2370 shape_properties.geometry = None;
2371 }
2372 let is_fixed_default = shape_properties.geometry.is_none()
2377 && shape_properties.fill.is_none()
2378 && shape_properties.line.is_none()
2379 && shape_properties.effects.is_none();
2380 if !is_fixed_default {
2381 picture = picture.with_shape_properties(shape_properties);
2382 }
2383 }
2384 Ok(Some(picture))
2385}
2386
2387fn parse_picture_blip_fill_relationship_id(reader: &mut Reader) -> Result<Option<String>> {
2390 let mut relationship_id = None;
2391 loop {
2392 match reader.read_event()? {
2393 Event::Empty(start) if start.local_name().as_ref() == b"blip" => {
2394 relationship_id = raw_attribute(&start, b"embed");
2395 }
2396 Event::Start(start) if start.local_name().as_ref() == b"blip" => {
2397 relationship_id = raw_attribute(&start, b"embed");
2398 skip_subtree(reader)?;
2399 }
2400 Event::Start(_) => skip_subtree(reader)?,
2401 Event::End(end) if end.local_name().as_ref() == b"blipFill" => break,
2402 Event::Eof => break,
2403 _ => {}
2404 }
2405 }
2406 Ok(relationship_id)
2407}
2408
2409fn parse_sheet_chart(
2415 reader: &mut Reader,
2416 drawing_relationships: &opc::Relationships,
2417 package: &Package,
2418) -> Result<Option<SheetChart>> {
2419 let mut name = String::new();
2420 let mut relationship_id: Option<String> = None;
2421
2422 loop {
2423 match reader.read_event()? {
2424 Event::Start(start) if start.local_name().as_ref() == b"nvGraphicFramePr" => {
2427 name = parse_non_visual_name(reader, b"nvGraphicFramePr")?;
2428 }
2429 Event::Empty(start) if start.local_name().as_ref() == b"chart" => {
2430 relationship_id = raw_attribute(&start, b"id");
2431 }
2432 Event::Start(start)
2437 if matches!(start.local_name().as_ref(), b"graphic" | b"graphicData") => {}
2438 Event::Start(_) => skip_subtree(reader)?,
2439 Event::End(end) if end.local_name().as_ref() == b"graphicFrame" => break,
2440 Event::Eof => break,
2441 _ => {}
2442 }
2443 }
2444
2445 let Some(relationship_id) = relationship_id else {
2446 return Ok(None);
2447 };
2448 let Some(relationship) = drawing_relationships.by_id(&relationship_id) else {
2449 return Ok(None);
2450 };
2451 if relationship.rel_type != CHART_RELATIONSHIP_TYPE {
2452 return Ok(None);
2453 }
2454 let part_name = format!("/xl/{}", relationship.target.trim_start_matches("../"));
2455 let Some(part) = package.part(&part_name) else {
2456 return Ok(None);
2457 };
2458 let xml = std::str::from_utf8(&part.data)
2459 .map_err(|error| Error::InvalidUtf8(part_name.clone(), error))?;
2460 let chart_space = chart::read_chart_space(xml)?;
2461
2462 Ok(Some(SheetChart::new(chart_space).with_name(name)))
2463}
2464
2465fn parse_non_visual_name(reader: &mut Reader, wrapper_local_name: &[u8]) -> Result<String> {
2471 let mut name = String::new();
2472 loop {
2473 match reader.read_event()? {
2474 Event::Empty(start) if start.local_name().as_ref() == b"cNvPr" => {
2475 name = raw_attribute(&start, b"name").unwrap_or_default();
2476 }
2477 Event::Start(start) if start.local_name().as_ref() == b"cNvPr" => {
2478 name = raw_attribute(&start, b"name").unwrap_or_default();
2479 skip_subtree(reader)?;
2480 }
2481 Event::Start(_) => skip_subtree(reader)?,
2482 Event::End(end) if end.local_name().as_ref() == wrapper_local_name => break,
2483 Event::Eof => break,
2484 _ => {}
2485 }
2486 }
2487 Ok(name)
2488}
2489
2490fn skip_subtree(reader: &mut Reader) -> Result<()> {
2494 let mut depth: u32 = 0;
2495 loop {
2496 match reader.read_event()? {
2497 Event::Start(_) => depth += 1,
2498 Event::End(_) => {
2499 if depth == 0 {
2500 return Ok(());
2501 }
2502 depth -= 1;
2503 }
2504 Event::Eof => return Ok(()),
2505 _ => {}
2506 }
2507 }
2508}
2509
2510fn parse_table_xml(xml: &str) -> Result<ExcelTable> {
2521 let mut reader = Reader::from_xml_str(xml);
2522 let mut name = String::new();
2523 let mut range = String::new();
2524 let mut show_totals_row = false;
2525 let mut columns: Vec<TableColumn> = Vec::new();
2526
2527 let mut current_column: Option<TableColumn> = None;
2528 let mut in_calculated_column_formula = false;
2529 let mut in_totals_row_formula = false;
2530 let mut formula_buffer = String::new();
2531
2532 let mut sort_state: Option<SortState> = None;
2536 let mut current_sort_state: Option<SortState> = None;
2537
2538 let mut table_style_name: Option<String> = None;
2540
2541 loop {
2542 match reader.read_event()? {
2543 Event::Eof => break,
2544
2545 Event::Start(start) | Event::Empty(start)
2546 if start.local_name().as_ref() == b"table" =>
2547 {
2548 name = raw_attribute(&start, b"name").unwrap_or_default();
2549 range = raw_attribute(&start, b"ref").unwrap_or_default();
2550 let totals_row_count = raw_attribute(&start, b"totalsRowCount")
2558 .and_then(|value| value.parse::<u32>().ok())
2559 .unwrap_or(0);
2560 let totals_row_shown = raw_attribute(&start, b"totalsRowShown")
2561 .as_deref()
2562 .map(|value| value != "0")
2563 .unwrap_or(true);
2564 show_totals_row = totals_row_count > 0 && totals_row_shown;
2565 }
2566
2567 Event::Empty(start) if start.local_name().as_ref() == b"tableColumn" => {
2568 let mut column =
2569 TableColumn::new(raw_attribute(&start, b"name").unwrap_or_default());
2570 column.totals_row_function =
2571 parse_totals_row_function(raw_attribute(&start, b"totalsRowFunction"));
2572 column.totals_row_label = raw_attribute(&start, b"totalsRowLabel");
2573 columns.push(column);
2574 }
2575 Event::Start(start) if start.local_name().as_ref() == b"tableColumn" => {
2576 let mut column =
2577 TableColumn::new(raw_attribute(&start, b"name").unwrap_or_default());
2578 column.totals_row_function =
2579 parse_totals_row_function(raw_attribute(&start, b"totalsRowFunction"));
2580 column.totals_row_label = raw_attribute(&start, b"totalsRowLabel");
2581 current_column = Some(column);
2582 }
2583 Event::End(end) if end.local_name().as_ref() == b"tableColumn" => {
2584 if let Some(column) = current_column.take() {
2585 columns.push(column);
2586 }
2587 }
2588
2589 Event::Start(start)
2590 if current_column.is_some()
2591 && start.local_name().as_ref() == b"calculatedColumnFormula" =>
2592 {
2593 in_calculated_column_formula = true;
2594 formula_buffer.clear();
2595 }
2596 Event::End(end)
2597 if in_calculated_column_formula
2598 && end.local_name().as_ref() == b"calculatedColumnFormula" =>
2599 {
2600 in_calculated_column_formula = false;
2601 if let Some(column) = current_column.as_mut() {
2602 column.calculated_formula = Some(std::mem::take(&mut formula_buffer));
2603 }
2604 }
2605 Event::Start(start)
2606 if current_column.is_some()
2607 && start.local_name().as_ref() == b"totalsRowFormula" =>
2608 {
2609 in_totals_row_formula = true;
2610 formula_buffer.clear();
2611 }
2612 Event::End(end)
2613 if in_totals_row_formula && end.local_name().as_ref() == b"totalsRowFormula" =>
2614 {
2615 in_totals_row_formula = false;
2616 if let Some(column) = current_column.as_mut() {
2617 if column.totals_row_function == Some(TotalsRowFunction::Custom) {
2621 column.totals_row_formula = Some(std::mem::take(&mut formula_buffer));
2622 } else {
2623 formula_buffer.clear();
2624 }
2625 }
2626 }
2627 Event::Text(text) if in_calculated_column_formula || in_totals_row_formula => {
2628 formula_buffer.push_str(&xml_core::decode_text(&text)?);
2629 }
2630 Event::GeneralRef(reference)
2631 if in_calculated_column_formula || in_totals_row_formula =>
2632 {
2633 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
2634 formula_buffer.push_str(&resolved);
2635 }
2636 }
2637
2638 Event::Start(start) if start.local_name().as_ref() == b"sortState" => {
2640 current_sort_state = Some(SortState::new(
2641 raw_attribute(&start, b"ref").unwrap_or_default(),
2642 ));
2643 }
2644 Event::Empty(start) if start.local_name().as_ref() == b"sortState" => {
2645 sort_state = Some(SortState::new(
2646 raw_attribute(&start, b"ref").unwrap_or_default(),
2647 ));
2648 }
2649 Event::End(end) if end.local_name().as_ref() == b"sortState" => {
2650 sort_state = current_sort_state.take();
2651 }
2652 Event::Empty(start)
2653 if current_sort_state.is_some()
2654 && start.local_name().as_ref() == b"sortCondition" =>
2655 {
2656 if let Some(range) = raw_attribute(&start, b"ref") {
2657 let descending = raw_attribute(&start, b"descending").as_deref() == Some("1");
2658 if let Some(state) = current_sort_state.as_mut() {
2659 let mut condition = SortCondition::new(range);
2660 condition.descending = descending;
2661 state.conditions.push(condition);
2662 }
2663 }
2664 }
2665
2666 Event::Start(start) | Event::Empty(start)
2668 if start.local_name().as_ref() == b"tableStyleInfo" =>
2669 {
2670 table_style_name = raw_attribute(&start, b"name");
2671 }
2672
2673 _ => {}
2674 }
2675 }
2676
2677 Ok(ExcelTable {
2678 name,
2679 range,
2680 columns,
2681 show_totals_row,
2682 sort_state,
2683 table_style_name,
2684 })
2685}
2686
2687fn parse_totals_row_function(value: Option<String>) -> Option<TotalsRowFunction> {
2691 match value.as_deref() {
2692 Some("sum") => Some(TotalsRowFunction::Sum),
2693 Some("average") => Some(TotalsRowFunction::Average),
2694 Some("count") => Some(TotalsRowFunction::Count),
2695 Some("countNums") => Some(TotalsRowFunction::CountNums),
2696 Some("max") => Some(TotalsRowFunction::Max),
2697 Some("min") => Some(TotalsRowFunction::Min),
2698 Some("stdDev") => Some(TotalsRowFunction::StdDev),
2699 Some("var") => Some(TotalsRowFunction::Var),
2700 Some("custom") => Some(TotalsRowFunction::Custom),
2701 _ => None,
2702 }
2703}
2704
2705struct ParsedWorksheet {
2709 rows: Vec<Row>,
2710 conditional_formatting_rules: Vec<ConditionalFormattingRule>,
2711 data_validation_rules: Vec<DataValidationRule>,
2712 merged_ranges: Vec<String>,
2713 column_settings: Vec<ColumnSetting>,
2714 auto_filter_range: Option<String>,
2715 freeze_panes: Option<FreezePane>,
2716 protection: Option<SheetProtection>,
2717 hyperlinks: Vec<CellHyperlink>,
2718 zoom_scale: Option<u32>,
2719 row_breaks: Vec<u32>,
2720 column_breaks: Vec<u32>,
2721 scenarios: Vec<Scenario>,
2722 auto_filter_columns: Vec<AutoFilterColumn>,
2723 print_settings: PrintSettings,
2725 tab_color: Option<String>,
2727 show_grid_lines: Option<bool>,
2728 show_row_col_headers: Option<bool>,
2729 show_zeros: Option<bool>,
2730 right_to_left: Option<bool>,
2731 active_cell: Option<String>,
2733 default_column_width: Option<f64>,
2735 default_row_height: Option<f64>,
2737 ignored_errors: Vec<IgnoredError>,
2739 protected_ranges: Vec<ProtectedRange>,
2741 sort_state: Option<SortState>,
2743 outline_summary_below: Option<bool>,
2745 outline_summary_right: Option<bool>,
2747}
2748
2749fn parse_worksheet_xml(
2776 xml: &str,
2777 shared_strings: &[SharedStringEntry],
2778 cell_formats: &[Option<CellFormat>],
2779 dxfs: &[CellFormat],
2780 sheet_relationships: &opc::Relationships,
2781) -> Result<ParsedWorksheet> {
2782 let mut reader = Reader::from_xml_str(xml);
2783 let mut rows: Vec<Row> = Vec::new();
2784
2785 let mut in_sheet_data = false;
2786 let mut current_row_cells: Vec<Cell> = Vec::new();
2787 let mut current_row_number: Option<usize> = None;
2788 let mut current_row_height: Option<f64> = None;
2789 let mut current_row_hidden = false;
2790 let mut current_row_outline_level: u8 = 0;
2791 let mut current_row_collapsed = false;
2793 let mut current_row_style: Option<usize> = None;
2796
2797 let mut current_cell_column: Option<usize> = None;
2798 let mut current_cell_type: Option<String> = None;
2799 let mut current_cell_style: Option<usize> = None;
2800 let mut in_value = false;
2801 let mut value_buffer = String::new();
2802 let mut in_formula = false;
2803 let mut has_formula = false;
2804 let mut formula_buffer = String::new();
2805 let mut current_formula_type: Option<String> = None;
2811 let mut current_formula_ref: Option<String> = None;
2812 let mut current_formula_si: Option<u32> = None;
2814
2815 let mut column_settings: Vec<ColumnSetting> = Vec::new();
2817
2818 let mut merged_ranges: Vec<String> = Vec::new();
2820
2821 let mut auto_filter_range: Option<String> = None;
2823
2824 let mut protection: Option<SheetProtection> = None;
2826
2827 let mut freeze_panes: Option<FreezePane> = None;
2830
2831 let mut hyperlinks: Vec<CellHyperlink> = Vec::new();
2833
2834 let mut zoom_scale: Option<u32> = None;
2836
2837 let mut show_grid_lines: Option<bool> = None;
2839 let mut show_row_col_headers: Option<bool> = None;
2840 let mut show_zeros: Option<bool> = None;
2841 let mut right_to_left: Option<bool> = None;
2842
2843 let mut active_cell: Option<String> = None;
2845
2846 let mut tab_color: Option<String> = None;
2848
2849 let mut outline_summary_below: Option<bool> = None;
2851 let mut outline_summary_right: Option<bool> = None;
2852
2853 let mut default_column_width: Option<f64> = None;
2855 let mut default_row_height: Option<f64> = None;
2856
2857 let mut ignored_errors: Vec<IgnoredError> = Vec::new();
2859
2860 let mut protected_ranges: Vec<ProtectedRange> = Vec::new();
2864
2865 let mut row_breaks: Vec<u32> = Vec::new();
2868 let mut column_breaks: Vec<u32> = Vec::new();
2869 let mut in_row_breaks = false;
2870 let mut in_col_breaks = false;
2871
2872 let mut scenarios: Vec<Scenario> = Vec::new();
2874 let mut current_scenario: Option<Scenario> = None;
2875
2876 let mut auto_filter_columns: Vec<AutoFilterColumn> = Vec::new();
2878 let mut current_filter_column: Option<AutoFilterColumn> = None;
2879
2880 let mut sort_state: Option<SortState> = None;
2884 let mut current_sort_state: Option<SortState> = None;
2885
2886 let mut orientation: Option<PageOrientation> = None;
2889 let mut fit_to_width: Option<u32> = None;
2890 let mut fit_to_height: Option<u32> = None;
2891 let mut scale: Option<u32> = None;
2893 let mut header: Option<String> = None;
2894 let mut footer: Option<String> = None;
2895 let mut header_first: Option<String> = None;
2896 let mut footer_first: Option<String> = None;
2897 let mut header_even: Option<String> = None;
2898 let mut footer_even: Option<String> = None;
2899 let mut in_header_footer_element: Option<&'static str> = None;
2900 let mut header_footer_buffer = String::new();
2901
2902 let mut conditional_formatting_rules: Vec<ConditionalFormattingRule> = Vec::new();
2904 let mut current_cf_sqref: Option<String> = None;
2905 let mut in_cf_rule = false;
2906 let mut cf_rule_type: Option<String> = None;
2907 let mut cf_rule_dxf_id: Option<usize> = None;
2908 let mut cf_rule_operator: Option<String> = None;
2909 let mut cf_rule_text: Option<String> = None;
2910 let mut cf_formulas: Vec<String> = Vec::new();
2911 let mut in_cf_formula = false;
2912 let mut cf_formula_buffer = String::new();
2913
2914 let mut data_validation_rules: Vec<DataValidationRule> = Vec::new();
2916 let mut in_data_validation = false;
2917 let mut dv_type: Option<String> = None;
2918 let mut dv_operator: Option<String> = None;
2919 let mut dv_allow_blank = false;
2920 let mut dv_show_input_message = false;
2921 let mut dv_show_error_message = false;
2922 let mut dv_prompt_title: Option<String> = None;
2923 let mut dv_prompt: Option<String> = None;
2924 let mut dv_error_title: Option<String> = None;
2925 let mut dv_error: Option<String> = None;
2926 let mut dv_sqref: Option<String> = None;
2927 let mut dv_formula1: Option<String> = None;
2928 let mut dv_formula2: Option<String> = None;
2929 let mut in_dv_formula1 = false;
2930 let mut in_dv_formula2 = false;
2931 let mut dv_formula_buffer = String::new();
2932
2933 loop {
2934 match reader.read_event()? {
2935 Event::Eof => break,
2936
2937 Event::Start(start) if start.local_name().as_ref() == b"sheetData" => {
2938 in_sheet_data = true;
2939 }
2940 Event::End(end) if end.local_name().as_ref() == b"sheetData" => {
2941 in_sheet_data = false;
2942 }
2943
2944 Event::Start(start) if in_sheet_data && start.local_name().as_ref() == b"row" => {
2945 current_row_number =
2946 raw_attribute(&start, b"r").and_then(|value| value.parse::<usize>().ok());
2947 current_row_cells = Vec::new();
2948 current_row_height =
2949 raw_attribute(&start, b"ht").and_then(|value| value.parse::<f64>().ok());
2950 current_row_hidden = raw_attribute(&start, b"hidden").as_deref() == Some("1");
2951 current_row_outline_level = raw_attribute(&start, b"outlineLevel")
2952 .and_then(|value| value.parse::<u8>().ok())
2953 .unwrap_or(0);
2954 current_row_collapsed = raw_attribute(&start, b"collapsed").as_deref() == Some("1");
2955 current_row_style =
2959 if raw_attribute(&start, b"customFormat").as_deref() == Some("1") {
2960 raw_attribute(&start, b"s").and_then(|value| value.parse::<usize>().ok())
2961 } else {
2962 None
2963 };
2964 }
2965 Event::End(end) if in_sheet_data && end.local_name().as_ref() == b"row" => {
2966 let row_number = current_row_number.take().unwrap_or(rows.len() + 1).max(1);
2969 let default_format = current_row_style
2970 .take()
2971 .and_then(|index| cell_formats.get(index).cloned().flatten());
2972 let cells = std::mem::take(&mut current_row_cells);
2973 let height = current_row_height.take();
2974 let hidden = std::mem::take(&mut current_row_hidden);
2975 let outline_level = std::mem::take(&mut current_row_outline_level);
2976 let collapsed = std::mem::take(&mut current_row_collapsed);
2977 if row_number <= EXCEL_MAX_ROWS {
2983 ensure_len(&mut rows, row_number, Row::default);
2984 rows[row_number - 1] = Row {
2985 cells,
2986 height,
2987 hidden,
2988 outline_level,
2989 collapsed,
2990 default_format,
2991 };
2992 }
2993 }
2994
2995 Event::Start(start) if in_sheet_data && start.local_name().as_ref() == b"c" => {
2996 current_cell_type = raw_attribute(&start, b"t");
2997 current_cell_column = raw_attribute(&start, b"r")
2998 .as_deref()
2999 .and_then(column_index_from_reference);
3000 current_cell_style =
3001 raw_attribute(&start, b"s").and_then(|value| value.parse::<usize>().ok());
3002 has_formula = false;
3003 formula_buffer.clear();
3004 }
3005 Event::Empty(start) if in_sheet_data && start.local_name().as_ref() == b"c" => {
3006 if let Some(column_index) = raw_attribute(&start, b"r")
3010 .as_deref()
3011 .and_then(column_index_from_reference)
3012 {
3013 ensure_len(&mut current_row_cells, column_index + 1, Cell::default);
3014 let format = raw_attribute(&start, b"s")
3015 .and_then(|value| value.parse::<usize>().ok())
3016 .and_then(|index| cell_formats.get(index).cloned().flatten());
3017 current_row_cells[column_index] = Cell {
3018 value: CellValue::Empty,
3019 format,
3020 };
3021 }
3022 }
3023 Event::End(end) if in_sheet_data && end.local_name().as_ref() == b"c" => {
3024 if let Some(column_index) = current_cell_column.take() {
3025 ensure_len(&mut current_row_cells, column_index + 1, Cell::default);
3026 let raw_value = std::mem::take(&mut value_buffer);
3027 let cell_type = current_cell_type.take();
3028 let format = current_cell_style
3029 .take()
3030 .and_then(|index| cell_formats.get(index).cloned().flatten());
3031 let formula_text = std::mem::take(&mut formula_buffer);
3032 let had_formula = std::mem::take(&mut has_formula);
3033 let formula_type = current_formula_type.take();
3034 let formula_ref = current_formula_ref.take();
3035 let formula_si = current_formula_si.take();
3036
3037 let value = if had_formula && formula_type.as_deref() == Some("array") {
3038 CellValue::ArrayFormula {
3039 formula: formula_text,
3040 range: formula_ref.unwrap_or_default(),
3041 cached_value: raw_value.parse::<f64>().ok(),
3042 }
3043 } else if had_formula && formula_type.as_deref() == Some("shared") {
3044 CellValue::SharedFormula {
3048 formula: if formula_text.is_empty() {
3049 None
3050 } else {
3051 Some(formula_text)
3052 },
3053 group_index: formula_si.unwrap_or(0),
3054 range: formula_ref,
3055 cached_value: raw_value.parse::<f64>().ok(),
3056 }
3057 } else if had_formula {
3058 CellValue::Formula {
3059 formula: if formula_text.is_empty() {
3060 None
3061 } else {
3062 Some(formula_text)
3063 },
3064 cached_value: raw_value.parse::<f64>().ok(),
3065 }
3066 } else {
3067 match cell_type.as_deref() {
3068 Some("s") => raw_value
3069 .parse::<usize>()
3070 .ok()
3071 .and_then(|index| shared_strings.get(index))
3072 .map(|entry| match entry {
3073 SharedStringEntry::Plain(text) => CellValue::Text(text.clone()),
3074 SharedStringEntry::Rich(runs) => {
3075 CellValue::RichText(runs.clone())
3076 }
3077 })
3078 .unwrap_or(CellValue::Empty),
3079 Some("b") => CellValue::Boolean(raw_value == "1"),
3081 None | Some("n") => raw_value
3082 .parse::<f64>()
3083 .ok()
3084 .map(CellValue::Number)
3085 .unwrap_or(CellValue::Empty),
3086 _ => CellValue::Empty,
3087 }
3088 };
3089 current_row_cells[column_index] = Cell { value, format };
3090 }
3091 }
3092
3093 Event::Start(start) if in_sheet_data && start.local_name().as_ref() == b"v" => {
3094 in_value = true;
3095 value_buffer.clear();
3096 }
3097 Event::End(end) if in_sheet_data && end.local_name().as_ref() == b"v" => {
3098 in_value = false;
3099 }
3100 Event::Text(text) if in_value => {
3101 value_buffer.push_str(&xml_core::decode_text(&text)?);
3102 }
3103
3104 Event::Start(start) if in_sheet_data && start.local_name().as_ref() == b"f" => {
3105 in_formula = true;
3106 has_formula = true;
3107 current_formula_type = raw_attribute(&start, b"t");
3108 current_formula_ref = raw_attribute(&start, b"ref");
3109 current_formula_si =
3110 raw_attribute(&start, b"si").and_then(|value| value.parse::<u32>().ok());
3111 }
3112 Event::End(end) if in_sheet_data && end.local_name().as_ref() == b"f" => {
3113 in_formula = false;
3114 }
3115 Event::Empty(start) if in_sheet_data && start.local_name().as_ref() == b"f" => {
3116 has_formula = true;
3117 current_formula_type = raw_attribute(&start, b"t");
3118 current_formula_ref = raw_attribute(&start, b"ref");
3119 current_formula_si =
3120 raw_attribute(&start, b"si").and_then(|value| value.parse::<u32>().ok());
3121 }
3122 Event::Text(text) if in_formula => {
3123 formula_buffer.push_str(&xml_core::decode_text(&text)?);
3124 }
3125
3126 Event::Empty(start) if start.local_name().as_ref() == b"col" => {
3131 let min =
3132 raw_attribute(&start, b"min").and_then(|value| value.parse::<usize>().ok());
3133 let max =
3134 raw_attribute(&start, b"max").and_then(|value| value.parse::<usize>().ok());
3135 let width =
3136 raw_attribute(&start, b"width").and_then(|value| value.parse::<f64>().ok());
3137 let hidden = raw_attribute(&start, b"hidden").as_deref() == Some("1");
3138 let outline_level = raw_attribute(&start, b"outlineLevel")
3139 .and_then(|value| value.parse::<u8>().ok())
3140 .unwrap_or(0);
3141 let collapsed = raw_attribute(&start, b"collapsed").as_deref() == Some("1");
3143 let style = raw_attribute(&start, b"style")
3146 .and_then(|value| value.parse::<usize>().ok())
3147 .and_then(|index| cell_formats.get(index).cloned().flatten());
3148 if let (Some(min), Some(max)) = (min, max) {
3149 let min = min.max(1);
3155 let max = max.min(EXCEL_MAX_COLUMNS);
3156 if min <= max {
3157 for column_number in min..=max {
3158 let mut setting = ColumnSetting::new(column_number - 1)
3159 .with_hidden(hidden)
3160 .with_outline_level(outline_level)
3161 .with_collapsed(collapsed);
3162 if let Some(width) = width {
3163 setting = setting.with_width(width);
3164 }
3165 if let Some(style) = style.clone() {
3166 setting = setting.with_style(style);
3167 }
3168 column_settings.push(setting);
3169 }
3170 }
3171 }
3172 }
3173
3174 Event::Empty(start) if start.local_name().as_ref() == b"mergeCell" => {
3176 if let Some(range) = raw_attribute(&start, b"ref") {
3177 merged_ranges.push(range);
3178 }
3179 }
3180
3181 Event::Start(start) | Event::Empty(start)
3183 if start.local_name().as_ref() == b"autoFilter" =>
3184 {
3185 auto_filter_range = raw_attribute(&start, b"ref");
3186 }
3187
3188 Event::Start(start) | Event::Empty(start)
3191 if start.local_name().as_ref() == b"sheetProtection" =>
3192 {
3193 if raw_attribute(&start, b"sheet").as_deref() == Some("1") {
3194 protection = Some(SheetProtection::locked());
3195 }
3196 }
3197
3198 Event::Empty(start) if start.local_name().as_ref() == b"pane" => {
3201 if raw_attribute(&start, b"state").as_deref() == Some("frozen") {
3202 let frozen_columns = raw_attribute(&start, b"xSplit")
3203 .and_then(|value| value.parse::<usize>().ok())
3204 .unwrap_or(0);
3205 let frozen_rows = raw_attribute(&start, b"ySplit")
3206 .and_then(|value| value.parse::<usize>().ok())
3207 .unwrap_or(0);
3208 freeze_panes = Some(FreezePane::new(frozen_columns, frozen_rows));
3209 }
3210 }
3211
3212 Event::Empty(start)
3217 if active_cell.is_none() && start.local_name().as_ref() == b"selection" =>
3218 {
3219 active_cell = raw_attribute(&start, b"activeCell");
3220 }
3221
3222 Event::Empty(start) if start.local_name().as_ref() == b"tabColor" => {
3224 tab_color = raw_attribute(&start, b"rgb");
3225 }
3226
3227 Event::Empty(start) if start.local_name().as_ref() == b"outlinePr" => {
3229 outline_summary_below =
3230 raw_attribute(&start, b"summaryBelow").map(|value| value == "1");
3231 outline_summary_right =
3232 raw_attribute(&start, b"summaryRight").map(|value| value == "1");
3233 }
3234
3235 Event::Start(start) | Event::Empty(start)
3237 if start.local_name().as_ref() == b"sheetFormatPr" =>
3238 {
3239 default_column_width = raw_attribute(&start, b"defaultColWidth")
3240 .and_then(|value| value.parse::<f64>().ok());
3241 default_row_height = raw_attribute(&start, b"defaultRowHeight")
3242 .and_then(|value| value.parse::<f64>().ok());
3243 }
3244
3245 Event::Start(start) | Event::Empty(start)
3252 if start.local_name().as_ref() == b"sheetView" =>
3253 {
3254 zoom_scale =
3255 raw_attribute(&start, b"zoomScale").and_then(|value| value.parse::<u32>().ok());
3256 show_grid_lines = raw_attribute(&start, b"showGridLines")
3261 .as_deref()
3262 .map(|value| value == "1");
3263 show_row_col_headers = raw_attribute(&start, b"showRowColHeaders")
3264 .as_deref()
3265 .map(|value| value == "1");
3266 show_zeros = raw_attribute(&start, b"showZeros")
3267 .as_deref()
3268 .map(|value| value == "1");
3269 right_to_left = raw_attribute(&start, b"rightToLeft")
3270 .as_deref()
3271 .map(|value| value == "1");
3272 }
3273
3274 Event::Start(start) if start.local_name().as_ref() == b"rowBreaks" => {
3276 in_row_breaks = true;
3277 }
3278 Event::End(end) if end.local_name().as_ref() == b"rowBreaks" => {
3279 in_row_breaks = false;
3280 }
3281 Event::Start(start) if start.local_name().as_ref() == b"colBreaks" => {
3282 in_col_breaks = true;
3283 }
3284 Event::End(end) if end.local_name().as_ref() == b"colBreaks" => {
3285 in_col_breaks = false;
3286 }
3287 Event::Empty(start)
3288 if (in_row_breaks || in_col_breaks) && start.local_name().as_ref() == b"brk" =>
3289 {
3290 if let Some(id) =
3291 raw_attribute(&start, b"id").and_then(|value| value.parse::<u32>().ok())
3292 {
3293 if in_row_breaks {
3294 row_breaks.push(id);
3295 } else {
3296 column_breaks.push(id);
3297 }
3298 }
3299 }
3300
3301 Event::Empty(start) if start.local_name().as_ref() == b"protectedRange" => {
3303 if let (Some(name), Some(range)) = (
3304 raw_attribute(&start, b"name"),
3305 raw_attribute(&start, b"sqref"),
3306 ) {
3307 protected_ranges.push(ProtectedRange::new(name, range));
3308 }
3309 }
3310
3311 Event::Empty(start) if start.local_name().as_ref() == b"ignoredError" => {
3316 if let Some(range) = raw_attribute(&start, b"sqref") {
3317 let mut entry = IgnoredError::new(range);
3318 entry.eval_error = raw_attribute(&start, b"evalError").as_deref() == Some("1");
3319 entry.two_digit_text_year =
3320 raw_attribute(&start, b"twoDigitTextYear").as_deref() == Some("1");
3321 entry.number_stored_as_text =
3322 raw_attribute(&start, b"numberStoredAsText").as_deref() == Some("1");
3323 entry.formula = raw_attribute(&start, b"formula").as_deref() == Some("1");
3324 entry.formula_range =
3325 raw_attribute(&start, b"formulaRange").as_deref() == Some("1");
3326 entry.unlocked_formula =
3327 raw_attribute(&start, b"unlockedFormula").as_deref() == Some("1");
3328 entry.empty_cell_reference =
3329 raw_attribute(&start, b"emptyCellReference").as_deref() == Some("1");
3330 entry.list_data_validation =
3331 raw_attribute(&start, b"listDataValidation").as_deref() == Some("1");
3332 entry.calculated_column =
3333 raw_attribute(&start, b"calculatedColumn").as_deref() == Some("1");
3334 ignored_errors.push(entry);
3335 }
3336 }
3337
3338 Event::Start(start) if start.local_name().as_ref() == b"scenario" => {
3340 let mut scenario =
3341 Scenario::new(raw_attribute(&start, b"name").unwrap_or_default());
3342 scenario.comment = raw_attribute(&start, b"comment");
3343 current_scenario = Some(scenario);
3344 }
3345 Event::End(end) if end.local_name().as_ref() == b"scenario" => {
3346 if let Some(scenario) = current_scenario.take() {
3347 scenarios.push(scenario);
3348 }
3349 }
3350 Event::Empty(start)
3351 if current_scenario.is_some() && start.local_name().as_ref() == b"inputCells" =>
3352 {
3353 if let (Some(cell), Some(value)) =
3354 (raw_attribute(&start, b"r"), raw_attribute(&start, b"val"))
3355 {
3356 if let Some(scenario) = current_scenario.as_mut() {
3357 scenario.inputs.push((cell, value));
3358 }
3359 }
3360 }
3361
3362 Event::Start(start) if start.local_name().as_ref() == b"filterColumn" => {
3365 let column_offset = raw_attribute(&start, b"colId")
3366 .and_then(|value| value.parse::<u32>().ok())
3367 .unwrap_or(0);
3368 current_filter_column = Some(AutoFilterColumn::new(column_offset, Vec::new()));
3369 }
3370 Event::End(end) if end.local_name().as_ref() == b"filterColumn" => {
3371 if let Some(column) = current_filter_column.take() {
3372 auto_filter_columns.push(column);
3373 }
3374 }
3375 Event::Empty(start)
3376 if current_filter_column.is_some() && start.local_name().as_ref() == b"filter" =>
3377 {
3378 if let Some(value) = raw_attribute(&start, b"val") {
3379 if let Some(column) = current_filter_column.as_mut() {
3380 column.visible_values.push(value);
3381 }
3382 }
3383 }
3384
3385 Event::Start(start) if start.local_name().as_ref() == b"sortState" => {
3388 current_sort_state = Some(SortState::new(
3389 raw_attribute(&start, b"ref").unwrap_or_default(),
3390 ));
3391 }
3392 Event::Empty(start) if start.local_name().as_ref() == b"sortState" => {
3393 sort_state = Some(SortState::new(
3394 raw_attribute(&start, b"ref").unwrap_or_default(),
3395 ));
3396 }
3397 Event::End(end) if end.local_name().as_ref() == b"sortState" => {
3398 sort_state = current_sort_state.take();
3399 }
3400 Event::Empty(start)
3401 if current_sort_state.is_some()
3402 && start.local_name().as_ref() == b"sortCondition" =>
3403 {
3404 if let Some(range) = raw_attribute(&start, b"ref") {
3405 let descending = raw_attribute(&start, b"descending").as_deref() == Some("1");
3406 if let Some(state) = current_sort_state.as_mut() {
3407 let mut condition = SortCondition::new(range);
3408 condition.descending = descending;
3409 state.conditions.push(condition);
3410 }
3411 }
3412 }
3413
3414 Event::Start(start) | Event::Empty(start)
3416 if start.local_name().as_ref() == b"pageSetup" =>
3417 {
3418 orientation = raw_attribute(&start, b"orientation")
3419 .as_deref()
3420 .and_then(parse_page_orientation);
3421 fit_to_width = raw_attribute(&start, b"fitToWidth")
3422 .and_then(|value| value.parse::<u32>().ok());
3423 fit_to_height = raw_attribute(&start, b"fitToHeight")
3424 .and_then(|value| value.parse::<u32>().ok());
3425 scale = raw_attribute(&start, b"scale").and_then(|value| value.parse::<u32>().ok());
3426 if fit_to_width == Some(0) {
3430 fit_to_width = None;
3431 }
3432 if fit_to_height == Some(0) {
3433 fit_to_height = None;
3434 }
3435 }
3436
3437 Event::Start(start) if start.local_name().as_ref() == b"oddHeader" => {
3440 in_header_footer_element = Some("oddHeader");
3441 header_footer_buffer.clear();
3442 }
3443 Event::Start(start) if start.local_name().as_ref() == b"oddFooter" => {
3444 in_header_footer_element = Some("oddFooter");
3445 header_footer_buffer.clear();
3446 }
3447 Event::Start(start) if start.local_name().as_ref() == b"evenHeader" => {
3448 in_header_footer_element = Some("evenHeader");
3449 header_footer_buffer.clear();
3450 }
3451 Event::Start(start) if start.local_name().as_ref() == b"evenFooter" => {
3452 in_header_footer_element = Some("evenFooter");
3453 header_footer_buffer.clear();
3454 }
3455 Event::Start(start) if start.local_name().as_ref() == b"firstHeader" => {
3456 in_header_footer_element = Some("firstHeader");
3457 header_footer_buffer.clear();
3458 }
3459 Event::Start(start) if start.local_name().as_ref() == b"firstFooter" => {
3460 in_header_footer_element = Some("firstFooter");
3461 header_footer_buffer.clear();
3462 }
3463 Event::Text(text) if in_header_footer_element.is_some() => {
3464 header_footer_buffer.push_str(&xml_core::decode_text(&text)?);
3465 }
3466 Event::GeneralRef(reference) if in_header_footer_element.is_some() => {
3467 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
3468 header_footer_buffer.push_str(&resolved);
3469 }
3470 }
3471 Event::End(end)
3472 if in_header_footer_element.is_some()
3473 && matches!(
3474 end.local_name().as_ref(),
3475 b"oddHeader"
3476 | b"oddFooter"
3477 | b"evenHeader"
3478 | b"evenFooter"
3479 | b"firstHeader"
3480 | b"firstFooter"
3481 ) =>
3482 {
3483 let text = std::mem::take(&mut header_footer_buffer);
3484 match in_header_footer_element.take() {
3485 Some("oddHeader") => header = Some(text),
3486 Some("oddFooter") => footer = Some(text),
3487 Some("evenHeader") => header_even = Some(text),
3488 Some("evenFooter") => footer_even = Some(text),
3489 Some("firstHeader") => header_first = Some(text),
3490 Some("firstFooter") => footer_first = Some(text),
3491 _ => {}
3492 }
3493 }
3494
3495 Event::Start(start) | Event::Empty(start)
3500 if start.local_name().as_ref() == b"hyperlink" =>
3501 {
3502 if let Some(cell) = raw_attribute(&start, b"ref") {
3503 let tooltip = raw_attribute(&start, b"tooltip");
3504 let target = if let Some(location) = raw_attribute(&start, b"location") {
3505 Some(HyperlinkTarget::Internal(location))
3506 } else {
3507 raw_attribute(&start, b"id")
3508 .and_then(|relationship_id| sheet_relationships.by_id(&relationship_id))
3509 .map(|relationship| {
3510 HyperlinkTarget::External(relationship.target.clone())
3511 })
3512 };
3513 if let Some(target) = target {
3514 hyperlinks.push(CellHyperlink {
3515 cell,
3516 target,
3517 tooltip,
3518 });
3519 }
3520 }
3521 }
3522
3523 Event::Start(start) if start.local_name().as_ref() == b"conditionalFormatting" => {
3524 current_cf_sqref = raw_attribute(&start, b"sqref");
3525 }
3526 Event::End(end) if end.local_name().as_ref() == b"conditionalFormatting" => {
3527 current_cf_sqref = None;
3528 }
3529
3530 Event::Start(start) if start.local_name().as_ref() == b"cfRule" => {
3531 in_cf_rule = true;
3532 cf_rule_type = raw_attribute(&start, b"type");
3533 cf_rule_dxf_id =
3534 raw_attribute(&start, b"dxfId").and_then(|value| value.parse::<usize>().ok());
3535 cf_rule_operator = raw_attribute(&start, b"operator");
3536 cf_rule_text = raw_attribute(&start, b"text");
3537 cf_formulas.clear();
3538 }
3539 Event::Empty(start) if start.local_name().as_ref() == b"cfRule" => {
3545 let rule_type = raw_attribute(&start, b"type");
3546 let dxf_id =
3547 raw_attribute(&start, b"dxfId").and_then(|value| value.parse::<usize>().ok());
3548 if let Some(sqref) = current_cf_sqref.clone() {
3549 let format = dxf_id
3550 .and_then(|id| dxfs.get(id).cloned())
3551 .unwrap_or_default();
3552 match rule_type.as_deref() {
3553 Some("top10") => {
3554 let rank = raw_attribute(&start, b"rank")
3555 .and_then(|value| value.parse::<u32>().ok())
3556 .unwrap_or(10);
3557 let percent = raw_attribute(&start, b"percent").as_deref() == Some("1");
3558 let bottom = raw_attribute(&start, b"bottom").as_deref() == Some("1");
3559 let rule =
3560 ConditionalFormattingRule::top10(sqref, rank, percent, format)
3561 .with_bottom(bottom);
3562 conditional_formatting_rules.push(rule);
3563 }
3564 Some("aboveAverage") => {
3565 let above =
3566 raw_attribute(&start, b"aboveAverage").as_deref() != Some("0");
3567 conditional_formatting_rules.push(
3568 ConditionalFormattingRule::above_average(sqref, above, format),
3569 );
3570 }
3571 Some("duplicateValues") => {
3572 conditional_formatting_rules
3573 .push(ConditionalFormattingRule::duplicate_values(sqref, format));
3574 }
3575 Some("uniqueValues") => {
3576 conditional_formatting_rules
3577 .push(ConditionalFormattingRule::unique_values(sqref, format));
3578 }
3579 _ => {}
3580 }
3581 }
3582 }
3583 Event::End(end) if in_cf_rule && end.local_name().as_ref() == b"cfRule" => {
3584 in_cf_rule = false;
3585 if let Some(sqref) = current_cf_sqref.clone() {
3586 let format = cf_rule_dxf_id
3587 .take()
3588 .and_then(|id| dxfs.get(id).cloned())
3589 .unwrap_or_default();
3590 let mut formulas = std::mem::take(&mut cf_formulas).into_iter();
3591 match cf_rule_type.take().as_deref() {
3592 Some("cellIs") => {
3593 if let Some(operator) = cf_rule_operator
3594 .take()
3595 .as_deref()
3596 .and_then(parse_comparison_operator)
3597 {
3598 if let Some(formula1) = formulas.next() {
3599 let mut rule = ConditionalFormattingRule::cell_is(
3600 sqref, operator, formula1, format,
3601 );
3602 if let Some(formula2) = formulas.next() {
3603 rule = rule.with_second_formula(formula2);
3604 }
3605 conditional_formatting_rules.push(rule);
3606 }
3607 }
3608 }
3609 Some("expression") => {
3610 if let Some(formula) = formulas.next() {
3611 conditional_formatting_rules.push(
3612 ConditionalFormattingRule::expression(sqref, formula, format),
3613 );
3614 }
3615 }
3616 Some(
3620 text_type @ ("containsText" | "notContainsText" | "beginsWith"
3621 | "endsWith"),
3622 ) => {
3623 if let Some(operator) = text_comparison_operator_from_type(text_type) {
3624 if let Some(text) = cf_rule_text.take() {
3625 conditional_formatting_rules.push(
3626 ConditionalFormattingRule::contains_text(
3627 sqref, operator, text, format,
3628 ),
3629 );
3630 }
3631 }
3632 }
3633 _ => {}
3634 }
3635 }
3636 cf_rule_operator = None;
3637 cf_rule_text = None;
3638 }
3639 Event::Start(start) if in_cf_rule && start.local_name().as_ref() == b"formula" => {
3640 in_cf_formula = true;
3641 cf_formula_buffer.clear();
3642 }
3643 Event::End(end) if in_cf_rule && end.local_name().as_ref() == b"formula" => {
3644 in_cf_formula = false;
3645 cf_formulas.push(std::mem::take(&mut cf_formula_buffer));
3646 }
3647 Event::Text(text) if in_cf_formula => {
3648 cf_formula_buffer.push_str(&xml_core::decode_text(&text)?);
3649 }
3650 Event::GeneralRef(reference) if in_cf_formula => {
3657 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
3658 cf_formula_buffer.push_str(&resolved);
3659 }
3660 }
3661
3662 Event::Start(start) if start.local_name().as_ref() == b"dataValidation" => {
3663 in_data_validation = true;
3664 dv_type = raw_attribute(&start, b"type");
3665 dv_operator = raw_attribute(&start, b"operator");
3666 dv_allow_blank = raw_attribute(&start, b"allowBlank").as_deref() == Some("1");
3667 dv_show_input_message =
3668 raw_attribute(&start, b"showInputMessage").as_deref() == Some("1");
3669 dv_show_error_message =
3670 raw_attribute(&start, b"showErrorMessage").as_deref() == Some("1");
3671 dv_prompt_title = raw_attribute(&start, b"promptTitle");
3672 dv_prompt = raw_attribute(&start, b"prompt");
3673 dv_error_title = raw_attribute(&start, b"errorTitle");
3674 dv_error = raw_attribute(&start, b"error");
3675 dv_sqref = raw_attribute(&start, b"sqref");
3676 dv_formula1 = None;
3677 dv_formula2 = None;
3678 }
3679 Event::End(end) if end.local_name().as_ref() == b"dataValidation" => {
3680 in_data_validation = false;
3681 if let (Some(sqref), Some(formula1)) = (dv_sqref.take(), dv_formula1.take()) {
3682 let kind = match dv_type.take().as_deref() {
3683 Some("list") => Some(DataValidationKind::List { source: formula1 }),
3684 Some("whole") => dv_operator
3685 .as_deref()
3686 .and_then(parse_comparison_operator)
3687 .map(|operator| DataValidationKind::WholeNumber {
3688 operator,
3689 formula1,
3690 formula2: dv_formula2.clone(),
3691 }),
3692 Some("decimal") => dv_operator
3693 .as_deref()
3694 .and_then(parse_comparison_operator)
3695 .map(|operator| DataValidationKind::Decimal {
3696 operator,
3697 formula1,
3698 formula2: dv_formula2.clone(),
3699 }),
3700 Some("date") => dv_operator
3701 .as_deref()
3702 .and_then(parse_comparison_operator)
3703 .map(|operator| DataValidationKind::Date {
3704 operator,
3705 formula1,
3706 formula2: dv_formula2.clone(),
3707 }),
3708 Some("time") => dv_operator
3709 .as_deref()
3710 .and_then(parse_comparison_operator)
3711 .map(|operator| DataValidationKind::Time {
3712 operator,
3713 formula1,
3714 formula2: dv_formula2.clone(),
3715 }),
3716 Some("textLength") => dv_operator
3717 .as_deref()
3718 .and_then(parse_comparison_operator)
3719 .map(|operator| DataValidationKind::TextLength {
3720 operator,
3721 formula1,
3722 formula2: dv_formula2.clone(),
3723 }),
3724 Some("custom") => Some(DataValidationKind::Custom { formula: formula1 }),
3725 _ => None,
3726 };
3727 if let Some(kind) = kind {
3728 data_validation_rules.push(DataValidationRule {
3729 range: sqref,
3730 kind,
3731 allow_blank: dv_allow_blank,
3732 input_message: if dv_show_input_message {
3733 Some(Message {
3734 title: dv_prompt_title.take(),
3735 text: dv_prompt.take().unwrap_or_default(),
3736 })
3737 } else {
3738 None
3739 },
3740 error_message: if dv_show_error_message {
3741 Some(Message {
3742 title: dv_error_title.take(),
3743 text: dv_error.take().unwrap_or_default(),
3744 })
3745 } else {
3746 None
3747 },
3748 });
3749 }
3750 }
3751 dv_operator = None;
3752 dv_formula2 = None;
3753 }
3754 Event::Start(start)
3755 if in_data_validation && start.local_name().as_ref() == b"formula1" =>
3756 {
3757 in_dv_formula1 = true;
3758 dv_formula_buffer.clear();
3759 }
3760 Event::End(end) if in_data_validation && end.local_name().as_ref() == b"formula1" => {
3761 in_dv_formula1 = false;
3762 dv_formula1 = Some(std::mem::take(&mut dv_formula_buffer));
3763 }
3764 Event::Start(start)
3765 if in_data_validation && start.local_name().as_ref() == b"formula2" =>
3766 {
3767 in_dv_formula2 = true;
3768 dv_formula_buffer.clear();
3769 }
3770 Event::End(end) if in_data_validation && end.local_name().as_ref() == b"formula2" => {
3771 in_dv_formula2 = false;
3772 dv_formula2 = Some(std::mem::take(&mut dv_formula_buffer));
3773 }
3774 Event::Text(text) if in_dv_formula1 || in_dv_formula2 => {
3775 dv_formula_buffer.push_str(&xml_core::decode_text(&text)?);
3776 }
3777 Event::GeneralRef(reference) if in_dv_formula1 || in_dv_formula2 => {
3781 if let Some(resolved) = xml_core::decode_general_ref(&reference)? {
3782 dv_formula_buffer.push_str(&resolved);
3783 }
3784 }
3785
3786 _ => {}
3787 }
3788 }
3789
3790 let print_settings = PrintSettings {
3791 print_area: None,
3792 repeat_rows: None,
3793 repeat_columns: None,
3794 fit_to_width,
3795 fit_to_height,
3796 orientation,
3797 header,
3798 footer,
3799 header_first,
3800 footer_first,
3801 header_even,
3802 footer_even,
3803 scale,
3804 };
3805
3806 Ok(ParsedWorksheet {
3807 rows,
3808 conditional_formatting_rules,
3809 data_validation_rules,
3810 merged_ranges,
3811 column_settings,
3812 auto_filter_range,
3813 freeze_panes,
3814 protection,
3815 hyperlinks,
3816 zoom_scale,
3817 row_breaks,
3818 column_breaks,
3819 scenarios,
3820 auto_filter_columns,
3821 print_settings,
3822 tab_color,
3823 show_grid_lines,
3824 show_row_col_headers,
3825 show_zeros,
3826 right_to_left,
3827 active_cell,
3828 default_column_width,
3829 default_row_height,
3830 ignored_errors,
3831 protected_ranges,
3832 sort_state,
3833 outline_summary_below,
3834 outline_summary_right,
3835 })
3836}
3837
3838fn ensure_len<T>(items: &mut Vec<T>, len: usize, default: fn() -> T) {
3841 while items.len() < len {
3842 items.push(default());
3843 }
3844}
3845
3846fn column_index_from_reference(reference: &str) -> Option<usize> {
3851 let letters: String = reference
3852 .chars()
3853 .take_while(|character| character.is_ascii_alphabetic())
3854 .collect();
3855 if letters.is_empty() || letters.len() > 3 {
3860 return None;
3861 }
3862
3863 let mut index: usize = 0;
3864 for character in letters.chars() {
3865 let digit = (character.to_ascii_uppercase() as u8 - b'A' + 1) as usize;
3866 index = index.checked_mul(26)?.checked_add(digit)?;
3867 }
3868
3869 let column_index = index.checked_sub(1)?;
3870 (column_index < EXCEL_MAX_COLUMNS).then_some(column_index)
3871}
3872
3873fn raw_attribute(start: &BytesStart, name: &[u8]) -> Option<String> {
3877 start
3878 .attributes()
3879 .flatten()
3880 .find(|attribute| attribute.key.local_name().as_ref() == name)
3881 .and_then(|attribute| {
3882 xml_core::decode_attribute_value(attribute.value.as_ref())
3883 .ok()
3884 .flatten()
3885 })
3886}
3887
3888#[cfg(test)]
3889mod tests {
3890 use super::*;
3891
3892 #[test]
3893 fn parses_sheet_names_and_relationship_ids_from_workbook_xml() {
3894 let xml = r#"<?xml version="1.0"?>
3895<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
3896 <sheets>
3897 <sheet name="Feuil1" sheetId="1" r:id="rId1"/>
3898 <sheet name="Feuil2" sheetId="2" r:id="rId2"/>
3899 </sheets>
3900</workbook>"#;
3901
3902 let parsed = parse_workbook_xml(xml).unwrap();
3903
3904 assert_eq!(
3905 parsed.sheets,
3906 vec![
3907 (
3908 "Feuil1".to_string(),
3909 "rId1".to_string(),
3910 SheetVisibility::Visible
3911 ),
3912 (
3913 "Feuil2".to_string(),
3914 "rId2".to_string(),
3915 SheetVisibility::Visible
3916 ),
3917 ]
3918 );
3919 assert!(parsed.defined_names.is_empty());
3920 }
3921
3922 #[test]
3923 fn parses_defined_names_from_workbook_xml() {
3924 let xml = r#"<?xml version="1.0"?>
3925<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
3926 <sheets>
3927 <sheet name="Feuil1" sheetId="1" r:id="rId1"/>
3928 <sheet name="Feuil2" sheetId="2" r:id="rId2"/>
3929 </sheets>
3930 <definedNames>
3931 <definedName name="Plage_Ventes">Feuil1!$A$1:$C$12</definedName>
3932 <definedName name="Northwind_Database" localSheetId="1" hidden="1" comment="Source externe">Feuil2!$A$1:$T$47</definedName>
3933 </definedNames>
3934</workbook>"#;
3935
3936 let parsed = parse_workbook_xml(xml).unwrap();
3937 let defined_names = parsed.defined_names;
3938
3939 assert_eq!(defined_names.len(), 2);
3940 assert_eq!(defined_names[0].name, "Plage_Ventes");
3941 assert_eq!(defined_names[0].refers_to, "Feuil1!$A$1:$C$12");
3942 assert_eq!(defined_names[0].local_sheet_id, None);
3943 assert!(!defined_names[0].hidden);
3944 assert_eq!(defined_names[0].comment, None);
3945
3946 assert_eq!(defined_names[1].name, "Northwind_Database");
3947 assert_eq!(defined_names[1].refers_to, "Feuil2!$A$1:$T$47");
3948 assert_eq!(defined_names[1].local_sheet_id, Some(1));
3949 assert!(defined_names[1].hidden);
3950 assert_eq!(defined_names[1].comment.as_deref(), Some("Source externe"));
3951 }
3952
3953 #[test]
3954 fn parses_simple_shared_strings() {
3955 let xml = r#"<?xml version="1.0"?>
3956<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="2" uniqueCount="2">
3957 <si><t>Nombre_1</t></si>
3958 <si><t>Nombre_2</t></si>
3959</sst>"#;
3960
3961 let strings = parse_shared_strings_xml(xml).unwrap();
3962
3963 assert_eq!(
3964 strings,
3965 vec![
3966 SharedStringEntry::Plain("Nombre_1".to_string()),
3967 SharedStringEntry::Plain("Nombre_2".to_string())
3968 ]
3969 );
3970 }
3971
3972 #[test]
3973 fn parses_rich_text_run_shared_strings_into_distinct_runs() {
3974 let xml = r#"<?xml version="1.0"?>
3975<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="1" uniqueCount="1">
3976 <si><r><rPr><b/></rPr><t>Hello, </t></r><r><t>world!</t></r></si>
3977</sst>"#;
3978
3979 let strings = parse_shared_strings_xml(xml).unwrap();
3980
3981 assert_eq!(strings.len(), 1);
3982 assert_eq!(
3983 strings[0],
3984 SharedStringEntry::Rich(vec![
3985 RichTextRun::new("Hello, ").with_bold(true),
3986 RichTextRun::new("world!"),
3987 ])
3988 );
3989 }
3990
3991 fn no_relationships() -> opc::Relationships {
3994 opc::Relationships::new()
3995 }
3996
3997 #[test]
3998 fn parses_a_simple_worksheet_with_text_and_numeric_cells() {
3999 let shared_strings = vec![
4000 SharedStringEntry::Plain("Nombre_1".to_string()),
4001 SharedStringEntry::Plain("Nombre_2".to_string()),
4002 ];
4003 let xml = r#"<?xml version="1.0"?>
4004<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4005 <sheetData>
4006 <row r="1"><c r="A1" t="s"><v>0</v></c><c r="B1" t="s"><v>1</v></c></row>
4007 <row r="2"><c r="A2"><v>10</v></c><c r="B2"><v>1</v></c></row>
4008 </sheetData>
4009</worksheet>"#;
4010
4011 let parsed =
4012 parse_worksheet_xml(xml, &shared_strings, &[], &[], &no_relationships()).unwrap();
4013
4014 assert_eq!(parsed.rows.len(), 2);
4015 assert_eq!(
4016 parsed.rows[0].cells[0].value,
4017 CellValue::Text("Nombre_1".to_string())
4018 );
4019 assert_eq!(
4020 parsed.rows[0].cells[1].value,
4021 CellValue::Text("Nombre_2".to_string())
4022 );
4023 assert_eq!(parsed.rows[1].cells[0].value, CellValue::Number(10.0));
4024 assert_eq!(parsed.rows[1].cells[1].value, CellValue::Number(1.0));
4025 }
4026
4027 #[test]
4028 fn fills_in_a_gap_left_by_a_skipped_column_as_an_empty_cell() {
4029 let xml = r#"<?xml version="1.0"?>
4030<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4031 <sheetData>
4032 <row r="1"><c r="A1"><v>1</v></c><c r="C1"><v>3</v></c></row>
4033 </sheetData>
4034</worksheet>"#;
4035
4036 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4037
4038 assert_eq!(parsed.rows[0].cells.len(), 3);
4039 assert_eq!(parsed.rows[0].cells[0].value, CellValue::Number(1.0));
4040 assert_eq!(parsed.rows[0].cells[1].value, CellValue::Empty);
4041 assert_eq!(parsed.rows[0].cells[2].value, CellValue::Number(3.0));
4042 }
4043
4044 #[test]
4045 fn fills_in_a_gap_left_by_a_skipped_row_as_an_empty_row() {
4046 let xml = r#"<?xml version="1.0"?>
4047<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4048 <sheetData>
4049 <row r="1"><c r="A1"><v>1</v></c></row>
4050 <row r="3"><c r="A3"><v>3</v></c></row>
4051 </sheetData>
4052</worksheet>"#;
4053
4054 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4055
4056 assert_eq!(parsed.rows.len(), 3);
4057 assert!(parsed.rows[1].cells.is_empty());
4058 assert_eq!(parsed.rows[2].cells[0].value, CellValue::Number(3.0));
4059 }
4060
4061 #[test]
4062 fn converts_a_cell_reference_to_a_0_based_column_index() {
4063 assert_eq!(column_index_from_reference("A1"), Some(0));
4064 assert_eq!(column_index_from_reference("C5"), Some(2));
4065 assert_eq!(column_index_from_reference("AA11"), Some(26));
4066 assert_eq!(column_index_from_reference(""), None);
4067 }
4068
4069 #[test]
4074 fn column_index_from_reference_rejects_columns_beyond_excel_s_own_limit() {
4075 assert_eq!(column_index_from_reference("XFD1"), Some(16_383));
4077 assert_eq!(column_index_from_reference("XFE1"), None);
4079 assert_eq!(column_index_from_reference("ZZZZZZZZZZZZZZ1"), None);
4082 }
4083
4084 #[test]
4089 fn drops_a_row_whose_number_is_beyond_excel_s_own_limit_instead_of_allocating() {
4090 let xml = r#"<?xml version="1.0"?>
4091<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4092 <sheetData>
4093 <row r="1"><c r="A1"><v>1</v></c></row>
4094 <row r="9999999999"><c r="A1"><v>2</v></c></row>
4095 <row r="2"><c r="A2"><v>3</v></c></row>
4096 </sheetData>
4097</worksheet>"#;
4098
4099 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4100
4101 assert_eq!(parsed.rows.len(), 2);
4104 assert_eq!(parsed.rows[0].cells[0].value, CellValue::Number(1.0));
4105 assert_eq!(parsed.rows[1].cells[0].value, CellValue::Number(3.0));
4106 }
4107
4108 #[test]
4112 fn clamps_a_malformed_col_range_to_excel_s_own_column_bounds() {
4113 let xml = r#"<?xml version="1.0"?>
4114<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4115 <cols>
4116 <col min="0" max="2" width="10"/>
4117 </cols>
4118 <sheetData/>
4119</worksheet>"#;
4120
4121 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4122
4123 assert_eq!(parsed.column_settings.len(), 2);
4126
4127 let xml_unbounded = r#"<?xml version="1.0"?>
4128<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4129 <cols>
4130 <col min="1" max="4000000000" width="10"/>
4131 </cols>
4132 <sheetData/>
4133</worksheet>"#;
4134
4135 let parsed_unbounded =
4136 parse_worksheet_xml(xml_unbounded, &[], &[], &[], &no_relationships()).unwrap();
4137
4138 assert_eq!(parsed_unbounded.column_settings.len(), 16_384);
4141 }
4142
4143 #[test]
4144 fn parses_a_normal_formula_cell_with_a_cached_value() {
4145 let xml = r#"<?xml version="1.0"?>
4146<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4147 <sheetData>
4148 <row r="1"><c r="A1"><f>A2*B2</f><v>10</v></c></row>
4149 </sheetData>
4150</worksheet>"#;
4151
4152 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4153
4154 assert_eq!(
4155 parsed.rows[0].cells[0].value,
4156 CellValue::Formula {
4157 formula: Some("A2*B2".to_string()),
4158 cached_value: Some(10.0)
4159 }
4160 );
4161 }
4162
4163 #[test]
4164 fn parses_a_shared_formula_follower_cell_with_no_literal_text_into_shared_formula() {
4165 let xml = r#"<?xml version="1.0"?>
4169<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4170 <sheetData>
4171 <row r="1"><c r="C4"><f t="shared" si="0"/><v>30</v></c></row>
4172 </sheetData>
4173</worksheet>"#;
4174
4175 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4176
4177 assert_eq!(
4178 parsed.rows[0].cells[2].value,
4179 CellValue::SharedFormula {
4180 formula: None,
4181 group_index: 0,
4182 range: None,
4183 cached_value: Some(30.0)
4184 }
4185 );
4186 }
4187
4188 #[test]
4189 fn parses_styles_xml_into_cell_formats_indexed_by_cellxfs_position() {
4190 let xml = r#"<?xml version="1.0"?>
4191<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4192 <numFmts count="1"><numFmt numFmtId="164" formatCode="0.00"/></numFmts>
4193 <fonts count="2">
4194 <font><sz val="11"/><color rgb="FF000000"/><name val="Calibri"/><family val="2"/></font>
4195 <font><b/><sz val="11"/><color rgb="FF000000"/><name val="Calibri"/><family val="2"/></font>
4196 </fonts>
4197 <fills count="3">
4198 <fill><patternFill patternType="none"/></fill>
4199 <fill><patternFill patternType="gray125"/></fill>
4200 <fill><patternFill patternType="solid"><fgColor rgb="FFFFFF00"/></patternFill></fill>
4201 </fills>
4202 <borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
4203 <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
4204 <cellXfs count="2">
4205 <xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
4206 <xf numFmtId="164" fontId="1" fillId="2" borderId="0" xfId="0" applyNumberFormat="1" applyFont="1" applyFill="1" applyAlignment="1"><alignment horizontal="center" vertical="top"/></xf>
4207 </cellXfs>
4208 <cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
4209</styleSheet>"#;
4210
4211 let ParsedStyles {
4212 cell_formats: formats,
4213 dxfs,
4214 ..
4215 } = parse_styles_xml(xml).unwrap();
4216
4217 assert_eq!(formats.len(), 2);
4218 assert_eq!(formats[0], None);
4219 assert_eq!(
4220 formats[1],
4221 Some(CellFormat {
4222 number_format: Some("0.00".to_string()),
4223 bold: true,
4224 italic: false,
4225 font_size: Some(11.0),
4226 font_color: Some("FF000000".to_string()),
4227 fill_color: Some("FFFFFF00".to_string()),
4228 horizontal_alignment: Some(HorizontalAlignment::Center),
4229 vertical_alignment: Some(VerticalAlignment::Top),
4230 border: crate::model::Border::default(),
4231 font_name: Some("Calibri".to_string()),
4232 underline: false,
4233 strike: false,
4234 wrap_text: false,
4235 indent: None,
4236 text_rotation: None,
4237 shrink_to_fit: false,
4238 pattern_fill: None,
4239 gradient_fill: None,
4240 locked: None,
4241 formula_hidden: None,
4242 named_style: None,
4243 })
4244 );
4245 assert!(dxfs.is_empty());
4246 }
4247
4248 #[test]
4249 fn parses_dxfs_as_inline_self_contained_cell_formats() {
4250 let xml = r#"<?xml version="1.0"?>
4251<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4252 <fonts count="1"><font><sz val="11"/><color rgb="FF000000"/><name val="Calibri"/><family val="2"/></font></fonts>
4253 <fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>
4254 <borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
4255 <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
4256 <cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs>
4257 <cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
4258 <dxfs count="1">
4259 <dxf>
4260 <font><b/><color rgb="FF9C0006"/></font>
4261 <fill><patternFill patternType="solid"><bgColor rgb="FFFFC7CE"/></patternFill></fill>
4262 </dxf>
4263 </dxfs>
4264</styleSheet>"#;
4265
4266 let ParsedStyles { dxfs, .. } = parse_styles_xml(xml).unwrap();
4267
4268 assert_eq!(dxfs.len(), 1);
4269 assert_eq!(
4270 dxfs[0],
4271 CellFormat {
4272 number_format: None,
4273 bold: true,
4274 italic: false,
4275 font_size: None,
4276 font_color: Some("FF9C0006".to_string()),
4277 fill_color: Some("FFFFC7CE".to_string()),
4278 horizontal_alignment: None,
4279 vertical_alignment: None,
4280 border: crate::model::Border::default(),
4281 font_name: None,
4282 underline: false,
4283 strike: false,
4284 wrap_text: false,
4285 indent: None,
4286 text_rotation: None,
4287 shrink_to_fit: false,
4288 pattern_fill: None,
4289 gradient_fill: None,
4290 locked: None,
4291 formula_hidden: None,
4292 named_style: None,
4293 }
4294 );
4295 }
4296
4297 #[test]
4298 fn parses_a_cell_is_conditional_formatting_rule_resolving_its_dxf() {
4299 let dxfs = vec![
4300 CellFormat::new()
4301 .with_bold(true)
4302 .with_fill_color("FFFFFF00"),
4303 ];
4304 let xml = r#"<?xml version="1.0"?>
4305<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4306 <sheetData/>
4307 <conditionalFormatting sqref="E3:E9">
4308 <cfRule type="cellIs" dxfId="0" priority="1" operator="greaterThan"><formula>0.5</formula></cfRule>
4309 </conditionalFormatting>
4310</worksheet>"#;
4311
4312 let parsed = parse_worksheet_xml(xml, &[], &[], &dxfs, &no_relationships()).unwrap();
4313 let rules = parsed.conditional_formatting_rules;
4314
4315 assert_eq!(rules.len(), 1);
4316 assert_eq!(rules[0].range, "E3:E9");
4317 assert_eq!(rules[0].format, dxfs[0]);
4318 assert_eq!(
4319 rules[0].condition,
4320 crate::model::ConditionalFormattingCondition::CellIs {
4321 operator: ComparisonOperator::GreaterThan,
4322 formula1: "0.5".to_string(),
4323 formula2: None,
4324 }
4325 );
4326 }
4327
4328 #[test]
4329 fn parses_a_data_validation_list_rule() {
4330 let xml = r#"<?xml version="1.0"?>
4331<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4332 <sheetData/>
4333 <dataValidations count="1">
4334 <dataValidation type="list" allowBlank="1" sqref="B2:B20"><formula1>"Oui,Non"</formula1></dataValidation>
4335 </dataValidations>
4336</worksheet>"#;
4337
4338 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4339 let rules = parsed.data_validation_rules;
4340
4341 assert_eq!(rules.len(), 1);
4342 assert_eq!(rules[0].range, "B2:B20");
4343 assert!(rules[0].allow_blank);
4344 assert_eq!(
4345 rules[0].kind,
4346 DataValidationKind::List {
4347 source: "\"Oui,Non\"".to_string()
4348 }
4349 );
4350 }
4351
4352 #[test]
4353 fn parses_a_whole_number_data_validation_rule_with_messages() {
4354 let xml = r#"<?xml version="1.0"?>
4355<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
4356 <sheetData/>
4357 <dataValidations count="1">
4358 <dataValidation type="whole" operator="greaterThanOrEqual" allowBlank="1" showInputMessage="1" showErrorMessage="1" promptTitle="Saisie" prompt="Entrez un nombre positif" errorTitle="Erreur" error="Valeur invalide" sqref="C2:C20"><formula1>0</formula1></dataValidation>
4359 </dataValidations>
4360</worksheet>"#;
4361
4362 let parsed = parse_worksheet_xml(xml, &[], &[], &[], &no_relationships()).unwrap();
4363 let rules = parsed.data_validation_rules;
4364
4365 assert_eq!(rules.len(), 1);
4366 assert_eq!(
4367 rules[0].kind,
4368 DataValidationKind::WholeNumber {
4369 operator: ComparisonOperator::GreaterThanOrEqual,
4370 formula1: "0".to_string(),
4371 formula2: None,
4372 }
4373 );
4374 let input_message = rules[0].input_message.as_ref().unwrap();
4375 assert_eq!(input_message.title.as_deref(), Some("Saisie"));
4376 assert_eq!(input_message.text, "Entrez un nombre positif");
4377 let error_message = rules[0].error_message.as_ref().unwrap();
4378 assert_eq!(error_message.title.as_deref(), Some("Erreur"));
4379 assert_eq!(error_message.text, "Valeur invalide");
4380 }
4381}