1mod restructure;
14pub use restructure::{Blocked, Restructure};
15
16use super::Document;
17use crate::edit::Refused;
18use crate::media_type;
19use crate::value::Length;
20use crate::xml::{Element, Name, Node, Ns};
21use crate::{Error, Family, Properties};
22
23pub struct SheetDocument {
25 pub document: Document,
27 sheets: Vec<Sheet>,
28}
29
30pub struct Sheet {
32 pub name: String,
34 pub columns: Vec<Column>,
38 pub used_rows: usize,
41 pub used_columns: usize,
43 pub visible: bool,
45 rows: Vec<RowRange>,
46 table: usize,
49}
50
51pub struct Column {
53 pub width: Option<Length>,
55 pub default_cell_style: Option<String>,
57 pub visible: bool,
59}
60
61struct RowRange {
63 first: usize,
65 count: usize,
67 path: RowPath,
69}
70
71enum RowPath {
79 Direct(usize),
81 Nested(Box<[usize]>),
83}
84
85#[derive(Debug, Clone, PartialEq)]
91pub enum Value {
92 Empty,
94 Number(f64),
96 Percentage(f64),
98 Currency(f64, Option<String>),
100 Date(String),
102 Time(String),
104 Boolean(bool),
106 Text(String),
108}
109
110impl Value {
111 pub fn from_input(input: &str) -> Self {
119 let trimmed = input.trim();
120 if trimmed.is_empty() {
121 return Self::Empty;
122 }
123 if trimmed.eq_ignore_ascii_case("true") {
124 return Self::Boolean(true);
125 }
126 if trimmed.eq_ignore_ascii_case("false") {
127 return Self::Boolean(false);
128 }
129 if let Ok(number) = trimmed.parse::<f64>()
130 && number.is_finite()
131 && !trimmed.contains(['i', 'n', 'I', 'N'])
132 {
133 return Self::Number(number);
134 }
135 Self::Text(input.to_owned())
136 }
137
138 pub fn input_text(&self) -> String {
145 match self {
146 Self::Percentage(n) => n.to_string(),
147 _ => self.cached_text(),
148 }
149 }
150
151 pub fn cached_text(&self) -> String {
156 match self {
157 Self::Empty => String::new(),
158 Self::Number(n) | Self::Currency(n, _) => n.to_string(),
159 Self::Percentage(n) => format!("{}%", n * 100.0),
160 Self::Date(s) | Self::Time(s) | Self::Text(s) => s.clone(),
161 Self::Boolean(true) => "TRUE".to_owned(),
162 Self::Boolean(false) => "FALSE".to_owned(),
163 }
164 }
165
166 pub fn is_numeric(&self) -> bool {
170 matches!(
171 self,
172 Self::Number(_)
173 | Self::Percentage(_)
174 | Self::Currency(..)
175 | Self::Date(_)
176 | Self::Time(_)
177 )
178 }
179}
180
181pub struct Cell<'a> {
183 pub element: &'a Element,
186 pub covered: bool,
190}
191
192impl Cell<'_> {
193 pub fn value(&self) -> Value {
195 let e = self.element;
196 match e.attr(&Ns::Office, "value-type") {
197 Some("float") => e
198 .attr(&Ns::Office, "value")
199 .and_then(|v| v.parse().ok())
200 .map_or(Value::Empty, Value::Number),
201 Some("percentage") => e
202 .attr(&Ns::Office, "value")
203 .and_then(|v| v.parse().ok())
204 .map_or(Value::Empty, Value::Percentage),
205 Some("currency") => e
206 .attr(&Ns::Office, "value")
207 .and_then(|v| v.parse().ok())
208 .map_or(Value::Empty, |amount| {
209 Value::Currency(
210 amount,
211 e.attr(&Ns::Office, "currency").map(ToOwned::to_owned),
212 )
213 }),
214 Some("date") => e
215 .attr(&Ns::Office, "date-value")
216 .map_or(Value::Empty, |v| Value::Date(v.to_owned())),
217 Some("time") => e
218 .attr(&Ns::Office, "time-value")
219 .map_or(Value::Empty, |v| Value::Time(v.to_owned())),
220 Some("boolean") => e
221 .attr(&Ns::Office, "boolean-value")
222 .and_then(crate::value::boolean)
223 .map_or(Value::Empty, Value::Boolean),
224 Some("string") => match e.attr(&Ns::Office, "string-value") {
228 Some(text) => Value::Text(text.to_owned()),
229 None => Value::Text(self.text()),
230 },
231 _ => {
232 let text = self.text();
233 if text.is_empty() {
234 Value::Empty
235 } else {
236 Value::Text(text)
237 }
238 }
239 }
240 }
241
242 pub fn text(&self) -> String {
255 let mut out = String::new();
256 for paragraph in self.element.elements() {
257 if paragraph.is(&Ns::Text, "p") {
258 if !out.is_empty() {
259 out.push('\n');
260 }
261 out.push_str(¶graph.plain_text());
262 }
263 }
264 out
265 }
266
267 pub fn formula(&self) -> Option<&str> {
274 let formula = self.element.attr(&Ns::Table, "formula")?;
275 Some(match formula.split_once(":=") {
276 Some((_, expression)) => expression,
277 None => formula,
278 })
279 }
280
281 pub fn style_name(&self) -> Option<&str> {
283 self.element.attr(&Ns::Table, "style-name")
284 }
285
286 pub fn columns_spanned(&self) -> usize {
288 self.element
289 .attr_usize(&Ns::Table, "number-columns-spanned")
290 .unwrap_or(1)
291 .max(1)
292 }
293
294 pub fn rows_spanned(&self) -> usize {
296 self.element
297 .attr_usize(&Ns::Table, "number-rows-spanned")
298 .unwrap_or(1)
299 .max(1)
300 }
301
302 fn occupied(&self) -> bool {
305 self.element.attr(&Ns::Office, "value-type").is_some()
306 || self.element.attr(&Ns::Table, "formula").is_some()
307 || self.element.elements().any(|e| e.is(&Ns::Text, "p"))
308 }
309}
310
311impl SheetDocument {
312 pub fn read(bytes: &[u8]) -> Result<Self, Error> {
318 let document = Document::read(bytes, media_type::SPREADSHEET_ANY)?;
319 let sheets = index_sheets(&document);
320 Ok(Self { document, sheets })
321 }
322
323 pub fn sheets(&self) -> &[Sheet] {
325 &self.sheets
326 }
327
328 pub fn reindex(&mut self) {
331 self.sheets = index_sheets(&self.document);
332 }
333
334 pub fn can_edit(&self, sheet: usize, row: usize, column: usize) -> Result<(), Refused> {
342 let index = self.sheets.get(sheet).ok_or(Refused::NotFound)?;
343 if let Some(cell) = self.cell(index, row, column) {
344 if cell.covered {
345 return Err(Refused::Covered);
346 }
347 if cell.formula().is_some() {
348 return Err(Refused::Formula);
349 }
350 }
351 Ok(())
352 }
353
354 pub fn set_cell(
368 &mut self,
369 sheet: usize,
370 row: usize,
371 column: usize,
372 value: &Value,
373 ) -> Result<(), Refused> {
374 self.can_edit(sheet, row, column)?;
375 let index = self.sheets.get(sheet).ok_or(Refused::NotFound)?;
376 let table_position = index.table;
377 let range = index
378 .row_range(row)
379 .map(|r| (r.path.steps().to_vec(), r.first, r.count));
380 let rows_written = index.rows.last().map_or(0, |r| r.first + r.count);
381
382 let names = CellNames::of(&self.document);
383 let calcext = self.document.declares(&Ns::Calcext);
384
385 let table = self
386 .document
387 .content
388 .child_mut(&Ns::Office, "body")
389 .and_then(|body| body.child_mut(&Ns::Office, "spreadsheet"))
390 .and_then(|sheet| sheet.at_mut(&[table_position]))
391 .ok_or(Refused::NotFound)?;
392
393 let row_element = reach_row(table, range.as_ref(), row, rows_written, &names)?;
394 let cell_index = reach_cell(row_element, column, &names);
395 let cell = row_element.at_mut(&[cell_index]).ok_or(Refused::NotFound)?;
396 write_value(cell, value, &names, calcext);
397 self.reindex();
398 Ok(())
399 }
400
401 fn table(&self, sheet: &Sheet) -> Option<&Element> {
403 let body = self.document.body_of("spreadsheet")?;
404 body.children.get(sheet.table).and_then(|node| match node {
405 crate::xml::Node::Element(e) => Some(e),
406 _ => None,
407 })
408 }
409
410 pub fn cell(&self, sheet: &Sheet, row: usize, column: usize) -> Option<Cell<'_>> {
416 cell_in_row(self.row_element(sheet, row)?, column)
417 }
418
419 pub fn row_element(&self, sheet: &Sheet, row: usize) -> Option<&Element> {
421 let table = self.table(sheet)?;
422 let range = sheet.row_range(row)?;
423 let mut element = table;
424 for step in range.path.steps() {
425 let crate::xml::Node::Element(child) = element.children.get(*step)? else {
426 return None;
427 };
428 element = child;
429 }
430 Some(element)
431 }
432
433 pub fn row_height(&self, sheet: &Sheet, row: usize) -> Option<Length> {
435 let name = self
436 .row_element(sheet, row)?
437 .attr(&Ns::Table, "style-name")?;
438 self.document
439 .styles
440 .resolve(&Family::TableRow, name)
441 .row_height
442 }
443
444 pub fn cell_style(
447 &self,
448 sheet: &Sheet,
449 cell: Option<&Cell<'_>>,
450 column: usize,
451 ) -> std::rc::Rc<Properties> {
452 let named = cell.and_then(Cell::style_name);
453 let from_column = sheet
454 .columns
455 .get(column)
456 .and_then(|c| c.default_cell_style.as_deref());
457 let name = named.or(from_column).unwrap_or("Default");
458 self.document.styles.resolve(&Family::TableCell, name)
459 }
460}
461
462impl Sheet {
463 fn row_range(&self, row: usize) -> Option<&RowRange> {
465 let found = self
466 .rows
467 .binary_search_by(|range| {
468 if row < range.first {
469 std::cmp::Ordering::Greater
470 } else if row >= range.first + range.count {
471 std::cmp::Ordering::Less
472 } else {
473 std::cmp::Ordering::Equal
474 }
475 })
476 .ok()?;
477 self.rows.get(found)
478 }
479
480 pub fn column_width(&self, column: usize) -> Option<Length> {
482 self.columns.get(column).and_then(|c| c.width)
483 }
484}
485
486struct CellNames {
488 row: Name,
489 cell: Name,
490 rows_repeated: Name,
491 columns_repeated: Name,
492 value_type: Name,
493 value: Name,
494 boolean_value: Name,
495 date_value: Name,
496 time_value: Name,
497 currency: Name,
498 calcext_value_type: Name,
499 paragraph: Name,
500}
501
502impl CellNames {
503 fn of(document: &Document) -> Self {
504 Self {
505 row: document.name(&Ns::Table, "table-row"),
506 cell: document.name(&Ns::Table, "table-cell"),
507 rows_repeated: document.name(&Ns::Table, "number-rows-repeated"),
508 columns_repeated: document.name(&Ns::Table, "number-columns-repeated"),
509 value_type: document.name(&Ns::Office, "value-type"),
510 value: document.name(&Ns::Office, "value"),
511 boolean_value: document.name(&Ns::Office, "boolean-value"),
512 date_value: document.name(&Ns::Office, "date-value"),
513 time_value: document.name(&Ns::Office, "time-value"),
514 currency: document.name(&Ns::Office, "currency"),
515 calcext_value_type: document.name(&Ns::Calcext, "value-type"),
516 paragraph: document.name(&Ns::Text, "p"),
517 }
518 }
519}
520
521fn empty_cell(names: &CellNames) -> Element {
522 let mut cell = Element::new("", "table-cell", Ns::Table);
523 cell.name = names.cell.clone();
524 cell
525}
526
527fn reach_row<'a>(
531 table: &'a mut Element,
532 range: Option<&(Vec<usize>, usize, usize)>,
533 row: usize,
534 rows_written: usize,
535 names: &CellNames,
536) -> Result<&'a mut Element, Refused> {
537 let Some((steps, first, count)) = range else {
538 let gap = row - rows_written;
539 if gap > 0 {
540 let mut filler = empty_row(names);
541 if gap > 1 {
542 filler.set_attr(names.rows_repeated.clone(), gap.to_string());
543 }
544 table.children.push(Node::Element(filler));
545 }
546 table.children.push(Node::Element(empty_row(names)));
547 let last = table.children.len() - 1;
548 return table.at_mut(&[last]).ok_or(Refused::NotFound);
549 };
550 let (last, above) = steps.split_last().ok_or(Refused::NotFound)?;
551 let parent = table.at_mut(above).ok_or(Refused::NotFound)?;
552 let at = split_run(parent, *last, row - first, *count, &names.rows_repeated);
553 parent.at_mut(&[at]).ok_or(Refused::NotFound)
554}
555
556fn reach_cell(row: &mut Element, column: usize, names: &CellNames) -> usize {
559 let mut at = 0usize;
561 let mut found = None;
562 for (index, child) in row.elements_indexed() {
563 if !child.is(&Ns::Table, "table-cell") && !child.is(&Ns::Table, "covered-table-cell") {
564 continue;
565 }
566 let repeat = child
567 .attr_usize(&Ns::Table, "number-columns-repeated")
568 .unwrap_or(1)
569 .max(1);
570 if column < at + repeat {
571 found = Some((index, column - at, repeat));
572 break;
573 }
574 at += repeat;
575 }
576 if let Some((index, offset, repeat)) = found {
577 return split_run(row, index, offset, repeat, &names.columns_repeated);
578 }
579 let gap = column - at;
580 if gap > 0 {
581 let mut filler = empty_cell(names);
582 if gap > 1 {
583 filler.set_attr(names.columns_repeated.clone(), gap.to_string());
584 }
585 row.children.push(Node::Element(filler));
586 }
587 row.children.push(Node::Element(empty_cell(names)));
588 row.self_closing = false;
589 row.children.len() - 1
590}
591
592fn empty_row(names: &CellNames) -> Element {
594 let mut row = Element::new("", "table-row", Ns::Table);
595 row.name = names.row.clone();
596 row.children.push(Node::Element(empty_cell(names)));
597 row.self_closing = false;
598 row
599}
600
601fn split_run(
608 parent: &mut Element,
609 index: usize,
610 offset: usize,
611 repeat: usize,
612 repeated: &Name,
613) -> usize {
614 if repeat <= 1 {
615 return index;
616 }
617 let Some(Node::Element(original)) = parent.children.get(index) else {
618 return index;
619 };
620 let one = {
621 let mut one = original.clone();
622 one.remove_attr(&repeated.ns, &repeated.local);
623 one
624 };
625 let mut replacement = Vec::with_capacity(3);
626 let before = offset;
627 let after = repeat - offset - 1;
628 if before > 0 {
629 replacement.push(Node::Element(with_count(
630 original.clone(),
631 before,
632 repeated,
633 )));
634 }
635 replacement.push(Node::Element(one));
636 if after > 0 {
637 replacement.push(Node::Element(with_count(original.clone(), after, repeated)));
638 }
639 parent.children.splice(index..=index, replacement);
640 index + usize::from(before > 0)
641}
642
643fn with_count(mut element: Element, count: usize, repeated: &Name) -> Element {
644 if count > 1 {
645 element.set_attr(repeated.clone(), count.to_string());
646 } else {
647 element.remove_attr(&repeated.ns, &repeated.local);
648 }
649 element
650}
651
652fn write_value(cell: &mut Element, value: &Value, names: &CellNames, calcext: bool) {
655 for local in [
656 "value-type",
657 "value",
658 "boolean-value",
659 "date-value",
660 "time-value",
661 "string-value",
662 "currency",
663 ] {
664 cell.remove_attr(&Ns::Office, local);
665 }
666 cell.remove_attr(&Ns::Calcext, "value-type");
667 cell.remove_attr(&Ns::Table, "formula");
668 cell.children
669 .retain(|node| !matches!(node, Node::Element(e) if e.is(&Ns::Text, "p")));
670
671 let value_type = match value {
672 Value::Empty => None,
673 Value::Number(_) => Some("float"),
674 Value::Percentage(_) => Some("percentage"),
675 Value::Currency(..) => Some("currency"),
676 Value::Date(_) => Some("date"),
677 Value::Time(_) => Some("time"),
678 Value::Boolean(_) => Some("boolean"),
679 Value::Text(_) => Some("string"),
680 };
681 if let Some(value_type) = value_type {
682 cell.set_attr(names.value_type.clone(), value_type);
683 if calcext {
687 cell.set_attr(names.calcext_value_type.clone(), value_type);
688 }
689 }
690 match value {
691 Value::Empty | Value::Text(_) => {}
692 Value::Number(n) | Value::Percentage(n) => {
693 cell.set_attr(names.value.clone(), n.to_string());
694 }
695 Value::Currency(n, code) => {
696 cell.set_attr(names.value.clone(), n.to_string());
697 if let Some(code) = code {
698 cell.set_attr(names.currency.clone(), code.clone());
699 }
700 }
701 Value::Date(s) => cell.set_attr(names.date_value.clone(), s.clone()),
702 Value::Time(s) => cell.set_attr(names.time_value.clone(), s.clone()),
703 Value::Boolean(b) => cell.set_attr(names.boolean_value.clone(), b.to_string()),
704 }
705 let text = value.cached_text();
706 if !text.is_empty() {
707 for line in text.split('\n') {
708 let mut paragraph = Element::new("", "p", Ns::Text);
709 paragraph.name = names.paragraph.clone();
710 if !line.is_empty() {
711 paragraph.children.push(Node::Text(line.to_owned()));
712 paragraph.self_closing = false;
713 }
714 cell.children.push(Node::Element(paragraph));
715 }
716 }
717 cell.self_closing = cell.children.is_empty();
718 for node in &mut cell.children {
721 if let Node::Element(e) = node
722 && e.is(&Ns::Text, "p")
723 {
724 crate::edit::replace(e, 0..0, "");
725 }
726 }
727}
728
729fn cell_in_row(row: &Element, column: usize) -> Option<Cell<'_>> {
731 let mut at = 0usize;
732 for child in row.elements() {
733 let covered = child.is(&Ns::Table, "covered-table-cell");
734 if !covered && !child.is(&Ns::Table, "table-cell") {
735 continue;
736 }
737 let repeat = child
738 .attr_usize(&Ns::Table, "number-columns-repeated")
739 .unwrap_or(1)
740 .max(1);
741 if column < at + repeat {
742 return Some(Cell {
743 element: child,
744 covered,
745 });
746 }
747 at += repeat;
748 }
749 None
750}
751
752fn index_sheets(document: &Document) -> Vec<Sheet> {
754 let Some(body) = document.body_of("spreadsheet") else {
755 return Vec::new();
756 };
757 let mut sheets = Vec::new();
758 for (position, node) in body.children.iter().enumerate() {
759 let crate::xml::Node::Element(table) = node else {
760 continue;
761 };
762 if !table.is(&Ns::Table, "table") {
763 continue;
764 }
765 sheets.push(index_sheet(document, table, position));
766 }
767 sheets
768}
769
770fn index_sheet(document: &Document, table: &Element, position: usize) -> Sheet {
771 let mut index = Index {
772 document,
773 rows: Vec::new(),
774 columns: Vec::new(),
775 at_row: 0,
776 used_rows: 0,
777 used_columns: 0,
778 };
779 index.walk(table, &mut Vec::new());
780
781 Sheet {
782 name: table
783 .attr(&Ns::Table, "name")
784 .unwrap_or_default()
785 .to_owned(),
786 columns: index.columns,
787 used_rows: index.used_rows,
788 used_columns: index.used_columns,
789 visible: table.attr(&Ns::Table, "display").unwrap_or("true") != "false",
790 rows: index.rows,
791 table: position,
792 }
793}
794
795struct Index<'a> {
797 document: &'a Document,
798 rows: Vec<RowRange>,
799 columns: Vec<Column>,
800 at_row: usize,
801 used_rows: usize,
802 used_columns: usize,
803}
804
805impl Index<'_> {
806 fn walk(&mut self, parent: &Element, path: &mut Vec<usize>) {
812 for (child_index, child) in parent.children.iter().enumerate() {
813 let crate::xml::Node::Element(element) = child else {
814 continue;
815 };
816
817 if element.is(&Ns::Table, "table-column") {
818 self.column(element);
819 } else if element.is(&Ns::Table, "table-row") {
820 path.push(child_index);
821 self.row(element, path);
822 path.pop();
823 } else if is_row_container(element) || is_column_container(element) {
824 path.push(child_index);
829 self.walk(element, path);
830 path.pop();
831 }
832 }
833 }
834
835 fn column(&mut self, element: &Element) {
836 let repeat = element
837 .attr_usize(&Ns::Table, "number-columns-repeated")
838 .unwrap_or(1)
839 .max(1);
840 let width = element
841 .attr(&Ns::Table, "style-name")
842 .map(|name| self.document.styles.resolve(&Family::TableColumn, name))
843 .and_then(|p| p.column_width);
844 let default_cell_style = element
845 .attr(&Ns::Table, "default-cell-style-name")
846 .map(ToOwned::to_owned);
847 let visible = element.attr(&Ns::Table, "visibility").unwrap_or("visible") == "visible";
848 let keep = repeat.min(MAX_COLUMNS.saturating_sub(self.columns.len()));
853 for _ in 0..keep {
854 self.columns.push(Column {
855 width,
856 default_cell_style: default_cell_style.clone(),
857 visible,
858 });
859 }
860 }
861
862 fn row(&mut self, element: &Element, path: &[usize]) {
863 let repeat = element
864 .attr_usize(&Ns::Table, "number-rows-repeated")
865 .unwrap_or(1)
866 .max(1);
867 if let Some(last) = last_occupied_column(element) {
868 self.used_rows = self.at_row + repeat;
869 self.used_columns = self.used_columns.max(last + 1);
870 }
871 self.rows.push(RowRange {
872 first: self.at_row,
873 count: repeat,
874 path: RowPath::of(path),
875 });
876 self.at_row += repeat;
877 }
878}
879
880impl RowPath {
881 fn of(path: &[usize]) -> Self {
884 match path {
885 [only] => Self::Direct(*only),
886 nested => Self::Nested(nested.into()),
887 }
888 }
889
890 fn steps(&self) -> &[usize] {
892 match self {
893 Self::Direct(only) => std::slice::from_ref(only),
894 Self::Nested(path) => path,
895 }
896 }
897}
898
899fn is_row_container(element: &Element) -> bool {
901 element.is(&Ns::Table, "table-rows")
902 || element.is(&Ns::Table, "table-header-rows")
903 || element.is(&Ns::Table, "table-row-group")
904}
905
906fn is_column_container(element: &Element) -> bool {
908 element.is(&Ns::Table, "table-columns")
909 || element.is(&Ns::Table, "table-header-columns")
910 || element.is(&Ns::Table, "table-column-group")
911}
912
913const MAX_COLUMNS: usize = 16_384;
916
917fn last_occupied_column(row: &Element) -> Option<usize> {
919 let mut at = 0usize;
920 let mut last = None;
921 for child in row.elements() {
922 let covered = child.is(&Ns::Table, "covered-table-cell");
923 if !covered && !child.is(&Ns::Table, "table-cell") {
924 continue;
925 }
926 let repeat = child
927 .attr_usize(&Ns::Table, "number-columns-repeated")
928 .unwrap_or(1)
929 .max(1);
930 let cell = Cell {
931 element: child,
932 covered,
933 };
934 if cell.occupied() {
935 last = Some(at + repeat - 1);
936 }
937 at += repeat;
938 }
939 last
940}