1use super::Document;
14use crate::edit::Refused;
15use crate::media_type;
16use crate::value::Length;
17use crate::xml::{Element, Name, Node, Ns};
18use crate::{Error, Family, Properties};
19
20pub struct SheetDocument {
22 pub document: Document,
24 sheets: Vec<Sheet>,
25}
26
27pub struct Sheet {
29 pub name: String,
31 pub columns: Vec<Column>,
35 pub used_rows: usize,
38 pub used_columns: usize,
40 pub visible: bool,
42 rows: Vec<RowRange>,
43 table: usize,
46}
47
48pub struct Column {
50 pub width: Option<Length>,
52 pub default_cell_style: Option<String>,
54 pub visible: bool,
56}
57
58struct RowRange {
60 first: usize,
62 count: usize,
64 path: RowPath,
66}
67
68enum RowPath {
76 Direct(usize),
78 Nested(Box<[usize]>),
80}
81
82#[derive(Debug, Clone, PartialEq)]
88pub enum Value {
89 Empty,
91 Number(f64),
93 Percentage(f64),
95 Currency(f64, Option<String>),
97 Date(String),
99 Time(String),
101 Boolean(bool),
103 Text(String),
105}
106
107impl Value {
108 pub fn from_input(input: &str) -> Self {
116 let trimmed = input.trim();
117 if trimmed.is_empty() {
118 return Self::Empty;
119 }
120 if trimmed.eq_ignore_ascii_case("true") {
121 return Self::Boolean(true);
122 }
123 if trimmed.eq_ignore_ascii_case("false") {
124 return Self::Boolean(false);
125 }
126 if let Ok(number) = trimmed.parse::<f64>()
127 && number.is_finite()
128 && !trimmed.contains(['i', 'n', 'I', 'N'])
129 {
130 return Self::Number(number);
131 }
132 Self::Text(input.to_owned())
133 }
134
135 pub fn input_text(&self) -> String {
142 match self {
143 Self::Percentage(n) => n.to_string(),
144 _ => self.cached_text(),
145 }
146 }
147
148 pub fn cached_text(&self) -> String {
153 match self {
154 Self::Empty => String::new(),
155 Self::Number(n) | Self::Currency(n, _) => n.to_string(),
156 Self::Percentage(n) => format!("{}%", n * 100.0),
157 Self::Date(s) | Self::Time(s) | Self::Text(s) => s.clone(),
158 Self::Boolean(true) => "TRUE".to_owned(),
159 Self::Boolean(false) => "FALSE".to_owned(),
160 }
161 }
162
163 pub fn is_numeric(&self) -> bool {
167 matches!(
168 self,
169 Self::Number(_)
170 | Self::Percentage(_)
171 | Self::Currency(..)
172 | Self::Date(_)
173 | Self::Time(_)
174 )
175 }
176}
177
178pub struct Cell<'a> {
180 pub element: &'a Element,
183 pub covered: bool,
187}
188
189impl Cell<'_> {
190 pub fn value(&self) -> Value {
192 let e = self.element;
193 match e.attr(&Ns::Office, "value-type") {
194 Some("float") => e
195 .attr(&Ns::Office, "value")
196 .and_then(|v| v.parse().ok())
197 .map_or(Value::Empty, Value::Number),
198 Some("percentage") => e
199 .attr(&Ns::Office, "value")
200 .and_then(|v| v.parse().ok())
201 .map_or(Value::Empty, Value::Percentage),
202 Some("currency") => e
203 .attr(&Ns::Office, "value")
204 .and_then(|v| v.parse().ok())
205 .map_or(Value::Empty, |amount| {
206 Value::Currency(
207 amount,
208 e.attr(&Ns::Office, "currency").map(ToOwned::to_owned),
209 )
210 }),
211 Some("date") => e
212 .attr(&Ns::Office, "date-value")
213 .map_or(Value::Empty, |v| Value::Date(v.to_owned())),
214 Some("time") => e
215 .attr(&Ns::Office, "time-value")
216 .map_or(Value::Empty, |v| Value::Time(v.to_owned())),
217 Some("boolean") => e
218 .attr(&Ns::Office, "boolean-value")
219 .and_then(crate::value::boolean)
220 .map_or(Value::Empty, Value::Boolean),
221 Some("string") => match e.attr(&Ns::Office, "string-value") {
225 Some(text) => Value::Text(text.to_owned()),
226 None => Value::Text(self.text()),
227 },
228 _ => {
229 let text = self.text();
230 if text.is_empty() {
231 Value::Empty
232 } else {
233 Value::Text(text)
234 }
235 }
236 }
237 }
238
239 pub fn text(&self) -> String {
252 let mut out = String::new();
253 for paragraph in self.element.elements() {
254 if paragraph.is(&Ns::Text, "p") {
255 if !out.is_empty() {
256 out.push('\n');
257 }
258 out.push_str(¶graph.plain_text());
259 }
260 }
261 out
262 }
263
264 pub fn formula(&self) -> Option<&str> {
271 let formula = self.element.attr(&Ns::Table, "formula")?;
272 Some(match formula.split_once(":=") {
273 Some((_, expression)) => expression,
274 None => formula,
275 })
276 }
277
278 pub fn style_name(&self) -> Option<&str> {
280 self.element.attr(&Ns::Table, "style-name")
281 }
282
283 pub fn columns_spanned(&self) -> usize {
285 self.element
286 .attr_usize(&Ns::Table, "number-columns-spanned")
287 .unwrap_or(1)
288 .max(1)
289 }
290
291 pub fn rows_spanned(&self) -> usize {
293 self.element
294 .attr_usize(&Ns::Table, "number-rows-spanned")
295 .unwrap_or(1)
296 .max(1)
297 }
298
299 fn occupied(&self) -> bool {
302 self.element.attr(&Ns::Office, "value-type").is_some()
303 || self.element.attr(&Ns::Table, "formula").is_some()
304 || self.element.elements().any(|e| e.is(&Ns::Text, "p"))
305 }
306}
307
308impl SheetDocument {
309 pub fn read(bytes: &[u8]) -> Result<Self, Error> {
315 let document = Document::read(bytes, media_type::SPREADSHEET_ANY)?;
316 let sheets = index_sheets(&document);
317 Ok(Self { document, sheets })
318 }
319
320 pub fn sheets(&self) -> &[Sheet] {
322 &self.sheets
323 }
324
325 pub fn reindex(&mut self) {
328 self.sheets = index_sheets(&self.document);
329 }
330
331 pub fn can_edit(&self, sheet: usize, row: usize, column: usize) -> Result<(), Refused> {
339 let index = self.sheets.get(sheet).ok_or(Refused::NotFound)?;
340 if let Some(cell) = self.cell(index, row, column) {
341 if cell.covered {
342 return Err(Refused::Covered);
343 }
344 if cell.formula().is_some() {
345 return Err(Refused::Formula);
346 }
347 }
348 Ok(())
349 }
350
351 pub fn set_cell(
365 &mut self,
366 sheet: usize,
367 row: usize,
368 column: usize,
369 value: &Value,
370 ) -> Result<(), Refused> {
371 self.can_edit(sheet, row, column)?;
372 let index = self.sheets.get(sheet).ok_or(Refused::NotFound)?;
373 let table_position = index.table;
374 let range = index
375 .row_range(row)
376 .map(|r| (r.path.steps().to_vec(), r.first, r.count));
377 let rows_written = index.rows.last().map_or(0, |r| r.first + r.count);
378
379 let names = CellNames::of(&self.document);
380 let calcext = self.document.declares(&Ns::Calcext);
381
382 let table = self
383 .document
384 .content
385 .child_mut(&Ns::Office, "body")
386 .and_then(|body| body.child_mut(&Ns::Office, "spreadsheet"))
387 .and_then(|sheet| sheet.at_mut(&[table_position]))
388 .ok_or(Refused::NotFound)?;
389
390 let row_element = reach_row(table, range.as_ref(), row, rows_written, &names)?;
391 let cell_index = reach_cell(row_element, column, &names);
392 let cell = row_element.at_mut(&[cell_index]).ok_or(Refused::NotFound)?;
393 write_value(cell, value, &names, calcext);
394 self.reindex();
395 Ok(())
396 }
397
398 fn table(&self, sheet: &Sheet) -> Option<&Element> {
400 let body = self.document.body_of("spreadsheet")?;
401 body.children.get(sheet.table).and_then(|node| match node {
402 crate::xml::Node::Element(e) => Some(e),
403 _ => None,
404 })
405 }
406
407 pub fn cell(&self, sheet: &Sheet, row: usize, column: usize) -> Option<Cell<'_>> {
413 cell_in_row(self.row_element(sheet, row)?, column)
414 }
415
416 pub fn row_element(&self, sheet: &Sheet, row: usize) -> Option<&Element> {
418 let table = self.table(sheet)?;
419 let range = sheet.row_range(row)?;
420 let mut element = table;
421 for step in range.path.steps() {
422 let crate::xml::Node::Element(child) = element.children.get(*step)? else {
423 return None;
424 };
425 element = child;
426 }
427 Some(element)
428 }
429
430 pub fn row_height(&self, sheet: &Sheet, row: usize) -> Option<Length> {
432 let name = self
433 .row_element(sheet, row)?
434 .attr(&Ns::Table, "style-name")?;
435 self.document
436 .styles
437 .resolve(&Family::TableRow, name)
438 .row_height
439 }
440
441 pub fn cell_style(
444 &self,
445 sheet: &Sheet,
446 cell: Option<&Cell<'_>>,
447 column: usize,
448 ) -> std::rc::Rc<Properties> {
449 let named = cell.and_then(Cell::style_name);
450 let from_column = sheet
451 .columns
452 .get(column)
453 .and_then(|c| c.default_cell_style.as_deref());
454 let name = named.or(from_column).unwrap_or("Default");
455 self.document.styles.resolve(&Family::TableCell, name)
456 }
457}
458
459impl Sheet {
460 fn row_range(&self, row: usize) -> Option<&RowRange> {
462 let found = self
463 .rows
464 .binary_search_by(|range| {
465 if row < range.first {
466 std::cmp::Ordering::Greater
467 } else if row >= range.first + range.count {
468 std::cmp::Ordering::Less
469 } else {
470 std::cmp::Ordering::Equal
471 }
472 })
473 .ok()?;
474 self.rows.get(found)
475 }
476
477 pub fn column_width(&self, column: usize) -> Option<Length> {
479 self.columns.get(column).and_then(|c| c.width)
480 }
481}
482
483struct CellNames {
485 row: Name,
486 cell: Name,
487 rows_repeated: Name,
488 columns_repeated: Name,
489 value_type: Name,
490 value: Name,
491 boolean_value: Name,
492 date_value: Name,
493 time_value: Name,
494 currency: Name,
495 calcext_value_type: Name,
496 paragraph: Name,
497}
498
499impl CellNames {
500 fn of(document: &Document) -> Self {
501 Self {
502 row: document.name(&Ns::Table, "table-row"),
503 cell: document.name(&Ns::Table, "table-cell"),
504 rows_repeated: document.name(&Ns::Table, "number-rows-repeated"),
505 columns_repeated: document.name(&Ns::Table, "number-columns-repeated"),
506 value_type: document.name(&Ns::Office, "value-type"),
507 value: document.name(&Ns::Office, "value"),
508 boolean_value: document.name(&Ns::Office, "boolean-value"),
509 date_value: document.name(&Ns::Office, "date-value"),
510 time_value: document.name(&Ns::Office, "time-value"),
511 currency: document.name(&Ns::Office, "currency"),
512 calcext_value_type: document.name(&Ns::Calcext, "value-type"),
513 paragraph: document.name(&Ns::Text, "p"),
514 }
515 }
516}
517
518fn empty_cell(names: &CellNames) -> Element {
519 let mut cell = Element::new("", "table-cell", Ns::Table);
520 cell.name = names.cell.clone();
521 cell
522}
523
524fn reach_row<'a>(
528 table: &'a mut Element,
529 range: Option<&(Vec<usize>, usize, usize)>,
530 row: usize,
531 rows_written: usize,
532 names: &CellNames,
533) -> Result<&'a mut Element, Refused> {
534 let Some((steps, first, count)) = range else {
535 let gap = row - rows_written;
536 if gap > 0 {
537 let mut filler = empty_row(names);
538 if gap > 1 {
539 filler.set_attr(names.rows_repeated.clone(), gap.to_string());
540 }
541 table.children.push(Node::Element(filler));
542 }
543 table.children.push(Node::Element(empty_row(names)));
544 let last = table.children.len() - 1;
545 return table.at_mut(&[last]).ok_or(Refused::NotFound);
546 };
547 let (last, above) = steps.split_last().ok_or(Refused::NotFound)?;
548 let parent = table.at_mut(above).ok_or(Refused::NotFound)?;
549 let at = split_run(parent, *last, row - first, *count, &names.rows_repeated);
550 parent.at_mut(&[at]).ok_or(Refused::NotFound)
551}
552
553fn reach_cell(row: &mut Element, column: usize, names: &CellNames) -> usize {
556 let mut at = 0usize;
558 let mut found = None;
559 for (index, child) in row.elements_indexed() {
560 if !child.is(&Ns::Table, "table-cell") && !child.is(&Ns::Table, "covered-table-cell") {
561 continue;
562 }
563 let repeat = child
564 .attr_usize(&Ns::Table, "number-columns-repeated")
565 .unwrap_or(1)
566 .max(1);
567 if column < at + repeat {
568 found = Some((index, column - at, repeat));
569 break;
570 }
571 at += repeat;
572 }
573 if let Some((index, offset, repeat)) = found {
574 return split_run(row, index, offset, repeat, &names.columns_repeated);
575 }
576 let gap = column - at;
577 if gap > 0 {
578 let mut filler = empty_cell(names);
579 if gap > 1 {
580 filler.set_attr(names.columns_repeated.clone(), gap.to_string());
581 }
582 row.children.push(Node::Element(filler));
583 }
584 row.children.push(Node::Element(empty_cell(names)));
585 row.self_closing = false;
586 row.children.len() - 1
587}
588
589fn empty_row(names: &CellNames) -> Element {
591 let mut row = Element::new("", "table-row", Ns::Table);
592 row.name = names.row.clone();
593 row.children.push(Node::Element(empty_cell(names)));
594 row.self_closing = false;
595 row
596}
597
598fn split_run(
605 parent: &mut Element,
606 index: usize,
607 offset: usize,
608 repeat: usize,
609 repeated: &Name,
610) -> usize {
611 if repeat <= 1 {
612 return index;
613 }
614 let Some(Node::Element(original)) = parent.children.get(index) else {
615 return index;
616 };
617 let one = {
618 let mut one = original.clone();
619 one.remove_attr(&repeated.ns, &repeated.local);
620 one
621 };
622 let mut replacement = Vec::with_capacity(3);
623 let before = offset;
624 let after = repeat - offset - 1;
625 if before > 0 {
626 replacement.push(Node::Element(with_count(
627 original.clone(),
628 before,
629 repeated,
630 )));
631 }
632 replacement.push(Node::Element(one));
633 if after > 0 {
634 replacement.push(Node::Element(with_count(original.clone(), after, repeated)));
635 }
636 parent.children.splice(index..=index, replacement);
637 index + usize::from(before > 0)
638}
639
640fn with_count(mut element: Element, count: usize, repeated: &Name) -> Element {
641 if count > 1 {
642 element.set_attr(repeated.clone(), count.to_string());
643 } else {
644 element.remove_attr(&repeated.ns, &repeated.local);
645 }
646 element
647}
648
649fn write_value(cell: &mut Element, value: &Value, names: &CellNames, calcext: bool) {
652 for local in [
653 "value-type",
654 "value",
655 "boolean-value",
656 "date-value",
657 "time-value",
658 "string-value",
659 "currency",
660 ] {
661 cell.remove_attr(&Ns::Office, local);
662 }
663 cell.remove_attr(&Ns::Calcext, "value-type");
664 cell.remove_attr(&Ns::Table, "formula");
665 cell.children
666 .retain(|node| !matches!(node, Node::Element(e) if e.is(&Ns::Text, "p")));
667
668 let value_type = match value {
669 Value::Empty => None,
670 Value::Number(_) => Some("float"),
671 Value::Percentage(_) => Some("percentage"),
672 Value::Currency(..) => Some("currency"),
673 Value::Date(_) => Some("date"),
674 Value::Time(_) => Some("time"),
675 Value::Boolean(_) => Some("boolean"),
676 Value::Text(_) => Some("string"),
677 };
678 if let Some(value_type) = value_type {
679 cell.set_attr(names.value_type.clone(), value_type);
680 if calcext {
684 cell.set_attr(names.calcext_value_type.clone(), value_type);
685 }
686 }
687 match value {
688 Value::Empty | Value::Text(_) => {}
689 Value::Number(n) | Value::Percentage(n) => {
690 cell.set_attr(names.value.clone(), n.to_string());
691 }
692 Value::Currency(n, code) => {
693 cell.set_attr(names.value.clone(), n.to_string());
694 if let Some(code) = code {
695 cell.set_attr(names.currency.clone(), code.clone());
696 }
697 }
698 Value::Date(s) => cell.set_attr(names.date_value.clone(), s.clone()),
699 Value::Time(s) => cell.set_attr(names.time_value.clone(), s.clone()),
700 Value::Boolean(b) => cell.set_attr(names.boolean_value.clone(), b.to_string()),
701 }
702 let text = value.cached_text();
703 if !text.is_empty() {
704 for line in text.split('\n') {
705 let mut paragraph = Element::new("", "p", Ns::Text);
706 paragraph.name = names.paragraph.clone();
707 if !line.is_empty() {
708 paragraph.children.push(Node::Text(line.to_owned()));
709 paragraph.self_closing = false;
710 }
711 cell.children.push(Node::Element(paragraph));
712 }
713 }
714 cell.self_closing = cell.children.is_empty();
715 for node in &mut cell.children {
718 if let Node::Element(e) = node
719 && e.is(&Ns::Text, "p")
720 {
721 crate::edit::replace(e, 0..0, "");
722 }
723 }
724}
725
726fn cell_in_row(row: &Element, column: usize) -> Option<Cell<'_>> {
728 let mut at = 0usize;
729 for child in row.elements() {
730 let covered = child.is(&Ns::Table, "covered-table-cell");
731 if !covered && !child.is(&Ns::Table, "table-cell") {
732 continue;
733 }
734 let repeat = child
735 .attr_usize(&Ns::Table, "number-columns-repeated")
736 .unwrap_or(1)
737 .max(1);
738 if column < at + repeat {
739 return Some(Cell {
740 element: child,
741 covered,
742 });
743 }
744 at += repeat;
745 }
746 None
747}
748
749fn index_sheets(document: &Document) -> Vec<Sheet> {
751 let Some(body) = document.body_of("spreadsheet") else {
752 return Vec::new();
753 };
754 let mut sheets = Vec::new();
755 for (position, node) in body.children.iter().enumerate() {
756 let crate::xml::Node::Element(table) = node else {
757 continue;
758 };
759 if !table.is(&Ns::Table, "table") {
760 continue;
761 }
762 sheets.push(index_sheet(document, table, position));
763 }
764 sheets
765}
766
767fn index_sheet(document: &Document, table: &Element, position: usize) -> Sheet {
768 let mut index = Index {
769 document,
770 rows: Vec::new(),
771 columns: Vec::new(),
772 at_row: 0,
773 used_rows: 0,
774 used_columns: 0,
775 };
776 index.walk(table, &mut Vec::new());
777
778 Sheet {
779 name: table
780 .attr(&Ns::Table, "name")
781 .unwrap_or_default()
782 .to_owned(),
783 columns: index.columns,
784 used_rows: index.used_rows,
785 used_columns: index.used_columns,
786 visible: table.attr(&Ns::Table, "display").unwrap_or("true") != "false",
787 rows: index.rows,
788 table: position,
789 }
790}
791
792struct Index<'a> {
794 document: &'a Document,
795 rows: Vec<RowRange>,
796 columns: Vec<Column>,
797 at_row: usize,
798 used_rows: usize,
799 used_columns: usize,
800}
801
802impl Index<'_> {
803 fn walk(&mut self, parent: &Element, path: &mut Vec<usize>) {
809 for (child_index, child) in parent.children.iter().enumerate() {
810 let crate::xml::Node::Element(element) = child else {
811 continue;
812 };
813
814 if element.is(&Ns::Table, "table-column") {
815 self.column(element);
816 } else if element.is(&Ns::Table, "table-row") {
817 path.push(child_index);
818 self.row(element, path);
819 path.pop();
820 } else if is_row_container(element) || is_column_container(element) {
821 path.push(child_index);
826 self.walk(element, path);
827 path.pop();
828 }
829 }
830 }
831
832 fn column(&mut self, element: &Element) {
833 let repeat = element
834 .attr_usize(&Ns::Table, "number-columns-repeated")
835 .unwrap_or(1)
836 .max(1);
837 let width = element
838 .attr(&Ns::Table, "style-name")
839 .map(|name| self.document.styles.resolve(&Family::TableColumn, name))
840 .and_then(|p| p.column_width);
841 let default_cell_style = element
842 .attr(&Ns::Table, "default-cell-style-name")
843 .map(ToOwned::to_owned);
844 let visible = element.attr(&Ns::Table, "visibility").unwrap_or("visible") == "visible";
845 let keep = repeat.min(MAX_COLUMNS.saturating_sub(self.columns.len()));
850 for _ in 0..keep {
851 self.columns.push(Column {
852 width,
853 default_cell_style: default_cell_style.clone(),
854 visible,
855 });
856 }
857 }
858
859 fn row(&mut self, element: &Element, path: &[usize]) {
860 let repeat = element
861 .attr_usize(&Ns::Table, "number-rows-repeated")
862 .unwrap_or(1)
863 .max(1);
864 if let Some(last) = last_occupied_column(element) {
865 self.used_rows = self.at_row + repeat;
866 self.used_columns = self.used_columns.max(last + 1);
867 }
868 self.rows.push(RowRange {
869 first: self.at_row,
870 count: repeat,
871 path: RowPath::of(path),
872 });
873 self.at_row += repeat;
874 }
875}
876
877impl RowPath {
878 fn of(path: &[usize]) -> Self {
881 match path {
882 [only] => Self::Direct(*only),
883 nested => Self::Nested(nested.into()),
884 }
885 }
886
887 fn steps(&self) -> &[usize] {
889 match self {
890 Self::Direct(only) => std::slice::from_ref(only),
891 Self::Nested(path) => path,
892 }
893 }
894}
895
896fn is_row_container(element: &Element) -> bool {
898 element.is(&Ns::Table, "table-rows")
899 || element.is(&Ns::Table, "table-header-rows")
900 || element.is(&Ns::Table, "table-row-group")
901}
902
903fn is_column_container(element: &Element) -> bool {
905 element.is(&Ns::Table, "table-columns")
906 || element.is(&Ns::Table, "table-header-columns")
907 || element.is(&Ns::Table, "table-column-group")
908}
909
910const MAX_COLUMNS: usize = 16_384;
913
914fn last_occupied_column(row: &Element) -> Option<usize> {
916 let mut at = 0usize;
917 let mut last = None;
918 for child in row.elements() {
919 let covered = child.is(&Ns::Table, "covered-table-cell");
920 if !covered && !child.is(&Ns::Table, "table-cell") {
921 continue;
922 }
923 let repeat = child
924 .attr_usize(&Ns::Table, "number-columns-repeated")
925 .unwrap_or(1)
926 .max(1);
927 let cell = Cell {
928 element: child,
929 covered,
930 };
931 if cell.occupied() {
932 last = Some(at + repeat - 1);
933 }
934 at += repeat;
935 }
936 last
937}