1use std::borrow::Cow;
2use std::io::Write;
3use std::ops::Range;
4
5use oxml_core::OxmlError;
6use oxml_core::raw_xml::{capture_element, capture_empty_element};
7use oxml_core::units::Emu;
8use oxml_core::xml::{local_name, matches_local_name};
9use oxml_core::xml_text::read_element_text;
10use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
11use quick_xml::{Reader, Writer, XmlVersion};
12
13use crate::fill::Fill;
14use crate::line::CT_LineProperties;
15use crate::namespace::{A_NS, reject_conflicting_a_prefix};
16use crate::order::OrderedRawChildren;
17use crate::style_ref::StyleReference;
18use crate::text::CT_TextBody;
19
20pub type Result<T> = std::result::Result<T, OxmlError>;
21type RawAttributes = Vec<(String, String)>;
22
23#[allow(non_camel_case_types)]
25#[derive(Clone, Debug)]
26pub struct CT_Table {
27 pub properties: Option<CT_TableProperties>,
28 pub grid: CT_TableGrid,
29 pub rows: Vec<CT_TableRow>,
30 raw_attributes: RawAttributes,
31 raw_children: OrderedRawChildren,
32 original_rows: Vec<CT_TableRow>,
33}
34
35#[allow(non_camel_case_types)]
37#[derive(Clone, Debug)]
38pub struct CT_TableGrid {
39 pub columns: Vec<Emu>,
40 column_raw: Vec<GridColumnRaw>,
41 raw_attributes: RawAttributes,
42 raw_children: OrderedRawChildren,
43}
44
45#[allow(non_camel_case_types)]
47#[derive(Clone, Debug, Default)]
48pub struct CT_TableProperties {
49 pub right_to_left: bool,
50 pub first_row: bool,
51 pub first_column: bool,
52 pub last_row: bool,
53 pub last_column: bool,
54 pub band_rows: bool,
55 pub band_columns: bool,
56 pub style_id: Option<String>,
57 raw_attributes: RawAttributes,
58 raw_children: OrderedRawChildren,
59}
60
61#[allow(non_camel_case_types)]
63#[derive(Clone, Debug)]
64pub struct CT_TableRow {
65 pub height: Emu,
66 pub cells: Vec<CT_TableCell>,
67 raw_attributes: RawAttributes,
68 raw_children: OrderedRawChildren,
69 original_cells: Vec<CT_TableCell>,
70 origin_index: usize,
71}
72
73#[allow(non_camel_case_types)]
75#[derive(Clone, Debug)]
76pub struct CT_TableCell {
77 pub text_body: Option<CT_TextBody>,
78 pub row_span: u32,
79 pub grid_span: u32,
80 pub horizontal_merge: bool,
81 pub vertical_merge: bool,
82 pub properties: Option<CT_TableCellProperties>,
83 raw_attributes: RawAttributes,
84 raw_children: OrderedRawChildren,
85 origin_index: usize,
86}
87
88#[derive(Clone, Debug, Default)]
89struct GridColumnRaw {
90 width: Emu,
91 attributes: RawAttributes,
92 children: Vec<Vec<u8>>,
93}
94
95impl PartialEq for GridColumnRaw {
96 fn eq(&self, other: &Self) -> bool {
97 self.attributes == other.attributes && self.children == other.children
98 }
99}
100
101impl Eq for GridColumnRaw {}
102
103#[allow(non_camel_case_types)]
104#[derive(Clone, Debug, Default, Eq, PartialEq)]
105pub struct CT_TableCellProperties {
106 pub margin_left: Option<Emu>,
107 pub margin_right: Option<Emu>,
108 pub margin_top: Option<Emu>,
109 pub margin_bottom: Option<Emu>,
110 pub fill: Option<Fill>,
111 pub left: Option<CT_LineProperties>,
112 pub right: Option<CT_LineProperties>,
113 pub top: Option<CT_LineProperties>,
114 pub bottom: Option<CT_LineProperties>,
115 pub unsupported: Vec<String>,
116 raw_attributes: RawAttributes,
117 raw_children: OrderedRawChildren,
118}
119
120#[allow(non_camel_case_types)]
122#[derive(Clone, Debug, Eq, PartialEq)]
123pub struct CT_TableStyleList {
124 pub default_style_id: Option<String>,
125 pub styles: Vec<CT_TableStyle>,
126 raw_attributes: RawAttributes,
127 raw_children: OrderedRawChildren,
128}
129
130#[allow(non_camel_case_types)]
132#[derive(Clone, Debug, Default, Eq, PartialEq)]
133pub struct CT_TableStyle {
134 pub style_id: String,
135 pub style_name: String,
136 pub background: Option<CT_TableBackgroundStyle>,
137 pub whole_table: Option<CT_TablePartStyle>,
138 pub band1_horizontal: Option<CT_TablePartStyle>,
139 pub band2_horizontal: Option<CT_TablePartStyle>,
140 pub band1_vertical: Option<CT_TablePartStyle>,
141 pub band2_vertical: Option<CT_TablePartStyle>,
142 pub first_column: Option<CT_TablePartStyle>,
143 pub last_column: Option<CT_TablePartStyle>,
144 pub first_row: Option<CT_TablePartStyle>,
145 pub last_row: Option<CT_TablePartStyle>,
146 pub north_west_cell: Option<CT_TablePartStyle>,
147 pub north_east_cell: Option<CT_TablePartStyle>,
148 pub south_west_cell: Option<CT_TablePartStyle>,
149 pub south_east_cell: Option<CT_TablePartStyle>,
150 raw_attributes: RawAttributes,
151 raw_children: OrderedRawChildren,
152}
153
154#[allow(non_camel_case_types)]
156#[derive(Clone, Debug, Default, Eq, PartialEq)]
157pub struct CT_TableBackgroundStyle {
158 pub fill: Option<Fill>,
159 pub fill_reference: Option<StyleReference>,
160 pub unsupported: Vec<String>,
161 raw_attributes: RawAttributes,
162 raw_children: OrderedRawChildren,
163}
164
165#[allow(non_camel_case_types)]
167#[derive(Clone, Debug, Default, Eq, PartialEq)]
168pub struct CT_TablePartStyle {
169 pub cell_style: Option<CT_TableCellStyle>,
170 pub text_style: Option<CT_TableTextStyle>,
171 raw_attributes: RawAttributes,
172 raw_children: OrderedRawChildren,
173}
174
175#[allow(non_camel_case_types)]
177#[derive(Clone, Debug, Default, Eq, PartialEq)]
178pub struct CT_TableCellStyle {
179 pub fill: Option<Fill>,
180 pub fill_reference: Option<StyleReference>,
181 pub borders: Option<CT_TableBorders>,
182 pub unsupported: Vec<String>,
183 raw_attributes: RawAttributes,
184 raw_children: OrderedRawChildren,
185}
186
187#[allow(non_camel_case_types)]
189#[derive(Clone, Debug, Default, Eq, PartialEq)]
190pub struct CT_TableBorders {
191 pub left: Option<CT_LineProperties>,
192 pub right: Option<CT_LineProperties>,
193 pub top: Option<CT_LineProperties>,
194 pub bottom: Option<CT_LineProperties>,
195 pub inside_horizontal: Option<CT_LineProperties>,
196 pub inside_vertical: Option<CT_LineProperties>,
197 pub unsupported: Vec<String>,
198 raw_attributes: RawAttributes,
199 raw_children: OrderedRawChildren,
200}
201
202#[allow(non_camel_case_types)]
204#[derive(Clone, Debug, Default, Eq, PartialEq)]
205pub struct CT_TableTextStyle {
206 pub bold: Option<bool>,
207 pub italic: Option<bool>,
208 pub font_reference: Option<StyleReference>,
209 pub color: Option<crate::color::ColorChoice>,
210 raw_attributes: RawAttributes,
211 raw_children: OrderedRawChildren,
212}
213
214impl PartialEq for CT_Table {
215 fn eq(&self, other: &Self) -> bool {
216 match (self.to_xml(), other.to_xml()) {
217 (Ok(left), Ok(right)) => left == right,
218 _ => {
219 self.properties == other.properties
220 && self.grid == other.grid
221 && self.rows == other.rows
222 && self.raw_attributes == other.raw_attributes
223 && self.raw_children == other.raw_children
224 }
225 }
226 }
227}
228
229impl Eq for CT_Table {}
230
231impl PartialEq for CT_TableGrid {
232 fn eq(&self, other: &Self) -> bool {
233 match (self.canonical_xml(), other.canonical_xml()) {
234 (Ok(left), Ok(right)) => left == right,
235 _ => {
236 self.columns == other.columns
237 && self.column_raw == other.column_raw
238 && self.raw_attributes == other.raw_attributes
239 && self.raw_children == other.raw_children
240 }
241 }
242 }
243}
244
245impl Eq for CT_TableGrid {}
246
247impl PartialEq for CT_TableProperties {
248 fn eq(&self, other: &Self) -> bool {
249 match (self.canonical_xml(), other.canonical_xml()) {
250 (Ok(left), Ok(right)) => left == right,
251 _ => {
252 self.right_to_left == other.right_to_left
253 && self.first_row == other.first_row
254 && self.first_column == other.first_column
255 && self.last_row == other.last_row
256 && self.last_column == other.last_column
257 && self.band_rows == other.band_rows
258 && self.band_columns == other.band_columns
259 && self.style_id == other.style_id
260 && self.raw_attributes == other.raw_attributes
261 && self.raw_children == other.raw_children
262 }
263 }
264 }
265}
266
267impl Eq for CT_TableProperties {}
268
269impl PartialEq for CT_TableRow {
270 fn eq(&self, other: &Self) -> bool {
271 match (self.canonical_xml(), other.canonical_xml()) {
272 (Ok(left), Ok(right)) => left == right,
273 _ => {
274 self.height == other.height
275 && self.cells == other.cells
276 && self.raw_attributes == other.raw_attributes
277 && self.raw_children == other.raw_children
278 }
279 }
280 }
281}
282
283impl Eq for CT_TableRow {}
284
285impl PartialEq for CT_TableCell {
286 fn eq(&self, other: &Self) -> bool {
287 match (self.canonical_xml(), other.canonical_xml()) {
288 (Ok(left), Ok(right)) => left == right,
289 _ => {
290 self.text_body == other.text_body
291 && self.row_span == other.row_span
292 && self.grid_span == other.grid_span
293 && self.horizontal_merge == other.horizontal_merge
294 && self.vertical_merge == other.vertical_merge
295 && self.properties == other.properties
296 && self.raw_attributes == other.raw_attributes
297 && self.raw_children == other.raw_children
298 }
299 }
300 }
301}
302
303impl Eq for CT_TableCell {}
304
305impl CT_Table {
306 pub fn new(rows: usize, columns: usize, width: Emu, height: Emu) -> Result<Self> {
309 let row_count = u32::try_from(rows).map_err(|_| {
310 OxmlError::InvalidValue("table row count exceeds the DrawingML span range".to_owned())
311 })?;
312 let column_count = u32::try_from(columns).map_err(|_| {
313 OxmlError::InvalidValue(
314 "table column count exceeds the DrawingML span range".to_owned(),
315 )
316 })?;
317 if row_count == 0 || column_count == 0 {
318 return Err(OxmlError::InvalidValue(
319 "table requires at least one row and one column".to_owned(),
320 ));
321 }
322 if width.0 <= 0 || height.0 <= 0 {
323 return Err(OxmlError::InvalidValue(
324 "table width and height must be positive".to_owned(),
325 ));
326 }
327
328 let column_width = Emu(width.0 / i64::from(column_count));
329 let row_height = Emu(height.0 / i64::from(row_count));
330 if column_width.0 <= 0 || row_height.0 <= 0 {
331 return Err(OxmlError::InvalidValue(
332 "table extent is too small for its row or column count".to_owned(),
333 ));
334 }
335
336 let mut column_widths = vec![column_width; columns];
337 let column_remainder = width.0 - column_width.0 * i64::from(column_count);
338 column_widths[columns - 1].0 += column_remainder;
339 let column_raw = column_widths
340 .iter()
341 .copied()
342 .map(|width| GridColumnRaw {
343 width,
344 ..GridColumnRaw::default()
345 })
346 .collect();
347 let grid = CT_TableGrid {
348 columns: column_widths,
349 column_raw,
350 raw_attributes: Vec::new(),
351 raw_children: OrderedRawChildren::default(),
352 };
353 let row_remainder = height.0 - row_height.0 * i64::from(row_count);
354 let mut table_rows = Vec::with_capacity(rows);
355 for row_index in 0..rows {
356 let mut cells = Vec::with_capacity(columns);
357 for column_index in 0..columns {
358 cells.push(CT_TableCell {
359 text_body: Some(CT_TextBody::new()),
360 row_span: 1,
361 grid_span: 1,
362 horizontal_merge: false,
363 vertical_merge: false,
364 properties: Some(CT_TableCellProperties::default()),
365 raw_attributes: Vec::new(),
366 raw_children: OrderedRawChildren::default(),
367 origin_index: column_index,
368 });
369 }
370 table_rows.push(CT_TableRow {
371 height: Emu(row_height.0
372 + if row_index + 1 == rows {
373 row_remainder
374 } else {
375 0
376 }),
377 original_cells: cells.clone(),
378 cells,
379 raw_attributes: Vec::new(),
380 raw_children: OrderedRawChildren::default(),
381 origin_index: row_index,
382 });
383 }
384
385 Ok(Self {
386 properties: Some(CT_TableProperties {
387 first_row: true,
388 band_rows: true,
389 ..CT_TableProperties::default()
390 }),
391 grid,
392 original_rows: table_rows.clone(),
393 rows: table_rows,
394 raw_attributes: Vec::new(),
395 raw_children: OrderedRawChildren::default(),
396 })
397 }
398
399 pub fn from_xml(xml: &[u8]) -> Result<Self> {
401 let mut reader = Reader::from_reader(xml);
402 let mut buffer = Vec::new();
403 loop {
404 match reader.read_event_into(&mut buffer)? {
405 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tbl") => {
406 reject_conflicting_a_prefix(&start)?;
407 return Self::from_element(&mut reader, &start);
408 }
409 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tbl") => {
410 reject_conflicting_a_prefix(&start)?;
411 return Err(missing("a:tblGrid and a:tr"));
412 }
413 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
414 Event::Eof => return Err(missing("a:tbl")),
415 _ => {}
416 }
417 buffer.clear();
418 }
419 }
420
421 pub fn from_xml_with_inherited_namespaces(
425 xml: &[u8],
426 inherited_namespaces: &[(String, String)],
427 ) -> Result<Self> {
428 let local_a_namespace = root_a_namespace_declaration(xml)?;
429 let mut table = Self::from_xml(xml)?;
430 for (prefix, uri) in inherited_namespaces {
431 if prefix == "a" {
432 if uri != A_NS && local_a_namespace.as_deref() != Some(A_NS) {
433 return Err(OxmlError::InvalidValue(
434 "inherited xmlns:a conflicts with the fixed DrawingML writer namespace"
435 .to_owned(),
436 ));
437 }
438 continue;
439 }
440 if prefix == "xml" {
441 if uri != "http://www.w3.org/XML/1998/namespace" {
442 return Err(OxmlError::InvalidValue(
443 "inherited xmlns:xml has the wrong namespace URI".to_owned(),
444 ));
445 }
446 continue;
447 }
448 let attribute_name = namespace_attribute_name(prefix)?;
449 if table
450 .raw_attributes
451 .iter()
452 .any(|(name, _)| name == &attribute_name)
453 {
454 continue;
455 }
456 table.raw_attributes.push((attribute_name, uri.to_owned()));
457 }
458 Ok(table)
459 }
460
461 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
462 let mut properties = None;
463 let mut grid = None;
464 let mut rows = Vec::new();
465 let mut raw_children = OrderedRawChildren::default();
466 let mut boundary = 0usize;
467 let mut buffer = Vec::new();
468 loop {
469 match reader.read_event_into(&mut buffer)? {
470 Event::Start(child) => {
471 let name = local_name(child.name().as_ref()).to_vec();
472 if matches!(name.as_slice(), b"tblPr" | b"tblGrid" | b"tr") {
473 reject_conflicting_a_prefix(&child)?;
474 }
475 let raw = capture_element(reader, &child)?;
476 capture_table_child(
477 &name,
478 raw,
479 &mut properties,
480 &mut grid,
481 &mut rows,
482 &mut raw_children,
483 &mut boundary,
484 )?;
485 }
486 Event::Empty(child) => {
487 let name = local_name(child.name().as_ref()).to_vec();
488 if matches!(name.as_slice(), b"tblPr" | b"tblGrid" | b"tr") {
489 reject_conflicting_a_prefix(&child)?;
490 }
491 let raw = capture_empty_element(&child)?;
492 capture_table_child(
493 &name,
494 raw,
495 &mut properties,
496 &mut grid,
497 &mut rows,
498 &mut raw_children,
499 &mut boundary,
500 )?;
501 }
502 Event::End(end) if matches_local_name(end.name().as_ref(), b"tbl") => break,
503 Event::Eof => return Err(missing("closing a:tbl")),
504 _ => {}
505 }
506 buffer.clear();
507 }
508 let grid = grid.ok_or_else(|| missing("a:tblGrid"))?;
509 if rows.is_empty() {
510 return Err(missing("at least one a:tr"));
511 }
512 Ok(Self {
513 properties,
514 grid,
515 original_rows: rows.clone(),
516 rows,
517 raw_attributes: raw_attributes(start, &[], true)?,
518 raw_children,
519 })
520 }
521
522 pub fn to_xml(&self) -> Result<Vec<u8>> {
524 if self.rows.is_empty() {
525 return Err(missing("at least one a:tr"));
526 }
527 let mut writer = Writer::new(Vec::new());
528 let mut start = BytesStart::new("a:tbl");
529 start.push_attribute(("xmlns:a", A_NS));
530 push_attributes(&mut start, &self.raw_attributes);
531 writer.write_event(Event::Start(start))?;
532 emit_raw(&mut writer, self.raw_children.at(0))?;
533 if let Some(properties) = &self.properties {
534 properties.write_xml(&mut writer)?;
535 }
536 emit_raw(&mut writer, self.raw_children.at(1))?;
537 self.grid.write_xml(&mut writer)?;
538 let current_row_origins = self
539 .rows
540 .iter()
541 .map(|row| row.origin_index)
542 .collect::<Vec<_>>();
543 let original_row_origins = self
544 .original_rows
545 .iter()
546 .map(|row| row.origin_index)
547 .collect::<Vec<_>>();
548 let row_matches = matched_original_indices(¤t_row_origins, &original_row_origins);
549 let original_to_current = invert_matches(&row_matches, self.original_rows.len());
550 for boundary in 0..=self.rows.len() {
551 emit_raw(
552 &mut writer,
553 self.raw_children
554 .at_reconciled(boundary, 2, &original_to_current, self.rows.len()),
555 )?;
556 if let Some(row) = self.rows.get(boundary) {
557 row.write_xml(&mut writer)?;
558 }
559 }
560 writer.write_event(Event::End(BytesEnd::new("a:tbl")))?;
561 Ok(writer.into_inner())
562 }
563
564 pub fn set_column_width(&mut self, index: usize, width: Emu) -> Result<()> {
568 if index >= self.grid.columns.len() {
569 return Err(index_out_of_range("column", index));
570 }
571 self.grid.align_column_metadata()?;
572 self.grid.columns[index] = width;
573 self.grid.column_raw[index].width = width;
574 Ok(())
575 }
576
577 pub fn insert_row(&mut self, index: usize) -> Result<()> {
586 let regions = self.merge_regions()?;
587 if index > self.rows.len() {
588 return Err(index_out_of_range("row", index));
589 }
590 let template = self
591 .rows
592 .get(index.saturating_sub(1))
593 .ok_or_else(|| missing("at least one a:tr"))?;
594 let grown = regions
595 .into_iter()
596 .filter(|region| region.top < index && index < region.rows().end)
597 .collect::<Vec<_>>();
598 let mut cells = template
599 .cells
600 .iter()
601 .map(CT_TableCell::empty_like)
602 .collect::<Vec<_>>();
603 for region in &grown {
604 for column in region.columns() {
605 cells[column].copy_merge_state(&self.rows[index].cells[column]);
606 }
607 }
608 let row = CT_TableRow {
609 height: template.height,
610 cells,
611 raw_attributes: Vec::new(),
612 raw_children: OrderedRawChildren::default(),
613 original_cells: Vec::new(),
614 origin_index: usize::MAX,
615 };
616 self.rows.insert(index, row);
617 for region in grown {
618 self.replace_spans(
619 region.top..region.rows().end + 1,
620 region.columns(),
621 |cell| &mut cell.row_span,
622 region.row_span,
623 region.row_span + 1,
624 );
625 }
626 Ok(())
627 }
628
629 pub fn remove_row(&mut self, index: usize) -> Result<()> {
635 let regions = self.merge_regions()?;
636 if index >= self.rows.len() {
637 return Err(index_out_of_range("row", index));
638 }
639 if self.rows.len() == 1 {
640 return Err(OxmlError::InvalidValue(
641 "a table keeps at least one row".to_owned(),
642 ));
643 }
644 for region in regions
645 .iter()
646 .filter(|region| region.row_span > 1 && region.rows().contains(&index))
647 {
648 if index == region.top {
649 let (above, below) = self.rows.split_at_mut(index + 1);
650 for column in region.columns() {
651 below[0].cells[column]
652 .take_merge_origin(&mut above[index].cells[column], column == region.left);
653 }
654 }
655 self.replace_spans(
656 region.rows(),
657 region.columns(),
658 |cell| &mut cell.row_span,
659 region.row_span,
660 region.row_span - 1,
661 );
662 }
663 self.rows.remove(index);
664 Ok(())
665 }
666
667 pub fn insert_column(&mut self, index: usize) -> Result<()> {
676 let regions = self.merge_regions()?;
677 if index > self.grid.columns.len() {
678 return Err(index_out_of_range("column", index));
679 }
680 let template = index.saturating_sub(1);
681 let width = self
682 .grid
683 .columns
684 .get(template)
685 .copied()
686 .ok_or_else(|| missing("at least one a:gridCol"))?;
687 let grown = regions
688 .into_iter()
689 .filter(|region| region.left < index && index < region.columns().end)
690 .collect::<Vec<_>>();
691 self.grid.insert_column(index, width)?;
692 for (row_index, row) in self.rows.iter_mut().enumerate() {
693 let mut cell = row.cells[template].empty_like();
694 if grown
695 .iter()
696 .any(|region| region.rows().contains(&row_index))
697 {
698 cell.copy_merge_state(&row.cells[index]);
699 }
700 row.cells.insert(index, cell);
701 }
702 for region in grown {
703 self.replace_spans(
704 region.rows(),
705 region.left..region.columns().end + 1,
706 |cell| &mut cell.grid_span,
707 region.grid_span,
708 region.grid_span + 1,
709 );
710 }
711 Ok(())
712 }
713
714 pub fn remove_column(&mut self, index: usize) -> Result<()> {
720 let regions = self.merge_regions()?;
721 if index >= self.grid.columns.len() {
722 return Err(index_out_of_range("column", index));
723 }
724 if self.grid.columns.len() == 1 {
725 return Err(OxmlError::InvalidValue(
726 "a table keeps at least one column".to_owned(),
727 ));
728 }
729 self.grid.remove_column(index)?;
730 for region in regions
731 .iter()
732 .filter(|region| region.grid_span > 1 && region.columns().contains(&index))
733 {
734 if index == region.left {
735 for row in region.rows() {
736 let (left, right) = self.rows[row].cells.split_at_mut(index + 1);
737 right[0].take_merge_origin(&mut left[index], row == region.top);
738 }
739 }
740 self.replace_spans(
741 region.rows(),
742 region.columns(),
743 |cell| &mut cell.grid_span,
744 region.grid_span,
745 region.grid_span - 1,
746 );
747 }
748 for row in &mut self.rows {
749 row.cells.remove(index);
750 }
751 Ok(())
752 }
753
754 fn merge_regions(&self) -> Result<Vec<MergeRegion>> {
758 let columns = self.grid.columns.len();
759 if self.rows.iter().any(|row| row.cells.len() != columns) {
760 return Err(OxmlError::InvalidValue(
761 "table does not have a rectangular explicit cell grid".to_owned(),
762 ));
763 }
764 let mut regions = Vec::new();
765 for (top, row) in self.rows.iter().enumerate() {
766 for (left, cell) in row.cells.iter().enumerate() {
767 if cell.horizontal_merge
768 || cell.vertical_merge
769 || (cell.row_span == 1 && cell.grid_span == 1)
770 {
771 continue;
772 }
773 let region = MergeRegion {
774 top,
775 left,
776 row_span: cell.row_span,
777 grid_span: cell.grid_span,
778 };
779 if region.rows().end > self.rows.len() || region.columns().end > columns {
780 return Err(OxmlError::InvalidValue(
781 "merge origin span exceeds the table grid".to_owned(),
782 ));
783 }
784 regions.push(region);
785 }
786 }
787 Ok(regions)
788 }
789
790 fn replace_spans(
793 &mut self,
794 rows: Range<usize>,
795 columns: Range<usize>,
796 span: fn(&mut CT_TableCell) -> &mut u32,
797 old: u32,
798 new: u32,
799 ) {
800 for row in &mut self.rows[rows] {
801 for cell in &mut row.cells[columns.clone()] {
802 let value = span(cell);
803 if *value == old {
804 *value = new;
805 }
806 }
807 }
808 }
809}
810
811struct MergeRegion {
813 top: usize,
814 left: usize,
815 row_span: u32,
816 grid_span: u32,
817}
818
819impl MergeRegion {
820 fn rows(&self) -> Range<usize> {
821 self.top..self.top.saturating_add(self.row_span as usize)
822 }
823
824 fn columns(&self) -> Range<usize> {
825 self.left..self.left.saturating_add(self.grid_span as usize)
826 }
827}
828
829fn index_out_of_range(kind: &str, index: usize) -> OxmlError {
830 OxmlError::InvalidValue(format!("{kind} index {index} is out of range"))
831}
832
833fn root_a_namespace_declaration(xml: &[u8]) -> Result<Option<String>> {
834 let mut reader = Reader::from_reader(xml);
835 let mut buffer = Vec::new();
836 loop {
837 match reader.read_event_into(&mut buffer)? {
838 Event::Start(start) | Event::Empty(start) => {
839 return decoded_attr(&start, b"xmlns:a");
840 }
841 Event::Eof => return Ok(None),
842 _ => buffer.clear(),
843 }
844 }
845}
846
847fn namespace_attribute_name(prefix: &str) -> Result<String> {
848 if prefix.is_empty() {
849 return Ok("xmlns".to_owned());
850 }
851 if prefix == "xmlns" {
852 return Err(OxmlError::InvalidValue(
853 "xmlns cannot be used as an inherited namespace prefix".to_owned(),
854 ));
855 }
856 let mut chars = prefix.chars();
857 let Some(first) = chars.next() else {
858 return Ok("xmlns".to_owned());
859 };
860 if !is_ncname_start(first) || !chars.all(is_ncname_continue) {
861 return Err(OxmlError::InvalidValue(format!(
862 "invalid inherited namespace prefix {prefix}"
863 )));
864 }
865 Ok(format!("xmlns:{prefix}"))
866}
867
868fn is_ncname_start(value: char) -> bool {
869 matches!(
870 value,
871 'A'..='Z'
872 | '_'
873 | 'a'..='z'
874 | '\u{C0}'..='\u{D6}'
875 | '\u{D8}'..='\u{F6}'
876 | '\u{F8}'..='\u{2FF}'
877 | '\u{370}'..='\u{37D}'
878 | '\u{37F}'..='\u{1FFF}'
879 | '\u{200C}'..='\u{200D}'
880 | '\u{2070}'..='\u{218F}'
881 | '\u{2C00}'..='\u{2FEF}'
882 | '\u{3001}'..='\u{D7FF}'
883 | '\u{F900}'..='\u{FDCF}'
884 | '\u{FDF0}'..='\u{FFFD}'
885 | '\u{10000}'..='\u{EFFFF}'
886 )
887}
888
889fn is_ncname_continue(value: char) -> bool {
890 is_ncname_start(value)
891 || matches!(
892 value,
893 '-' | '.' | '0'..='9' | '\u{B7}' | '\u{300}'..='\u{36F}' | '\u{203F}'..='\u{2040}'
894 )
895}
896
897#[allow(clippy::too_many_arguments)]
898fn capture_table_child(
899 name: &[u8],
900 raw: Vec<u8>,
901 properties: &mut Option<CT_TableProperties>,
902 grid: &mut Option<CT_TableGrid>,
903 rows: &mut Vec<CT_TableRow>,
904 raw_children: &mut OrderedRawChildren,
905 boundary: &mut usize,
906) -> Result<()> {
907 match name {
908 b"tblPr" if *boundary == 0 && properties.is_none() => {
909 *properties = Some(CT_TableProperties::from_xml(&raw)?);
910 *boundary = 1;
911 }
912 b"tblGrid" if *boundary <= 1 && grid.is_none() => {
913 *grid = Some(CT_TableGrid::from_xml(&raw)?);
914 *boundary = 2;
915 }
916 b"tr" if grid.is_some() => {
917 let mut row = CT_TableRow::from_xml(&raw)?;
918 row.origin_index = rows.len();
919 rows.push(row);
920 *boundary = 2 + rows.len();
921 }
922 b"tblPr" | b"tblGrid" | b"tr" => {
923 return Err(OxmlError::InvalidValue(
924 "a:tbl children must be a:tblPr?, a:tblGrid, then a:tr+".to_owned(),
925 ));
926 }
927 _ => raw_children.push(*boundary, raw),
928 }
929 Ok(())
930}
931
932fn matched_original_indices<T: PartialEq>(current: &[T], original: &[T]) -> Vec<Option<usize>> {
933 let mut matches = vec![None; current.len()];
934 let mut original_used = vec![false; original.len()];
935
936 for index in 0..current.len().min(original.len()) {
937 if current[index] == original[index] {
938 matches[index] = Some(index);
939 original_used[index] = true;
940 }
941 }
942 for (current_index, value) in current.iter().enumerate() {
943 if matches[current_index].is_some() {
944 continue;
945 }
946 if let Some(original_index) = original
947 .iter()
948 .enumerate()
949 .find(|(original_index, candidate)| {
950 !original_used[*original_index] && *candidate == value
951 })
952 .map(|(original_index, _)| original_index)
953 {
954 matches[current_index] = Some(original_index);
955 original_used[original_index] = true;
956 }
957 }
958 for (current_index, matched) in matches.iter_mut().enumerate() {
959 if matched.is_none() && current_index < original.len() && !original_used[current_index] {
960 *matched = Some(current_index);
961 original_used[current_index] = true;
962 }
963 }
964 matches
965}
966
967fn invert_matches(
968 current_to_original: &[Option<usize>],
969 original_len: usize,
970) -> Vec<Option<usize>> {
971 let mut original_to_current = vec![None; original_len];
972 for (current_index, original_index) in current_to_original.iter().enumerate() {
973 if let Some(original_index) = original_index {
974 original_to_current[*original_index] = Some(current_index);
975 }
976 }
977 original_to_current
978}
979
980impl CT_TableProperties {
981 fn canonical_xml(&self) -> Result<Vec<u8>> {
982 let mut writer = Writer::new(Vec::new());
983 self.write_xml(&mut writer)?;
984 Ok(writer.into_inner())
985 }
986
987 fn from_xml(xml: &[u8]) -> Result<Self> {
988 let mut reader = Reader::from_reader(xml);
989 let mut buffer = Vec::new();
990 loop {
991 match reader.read_event_into(&mut buffer)? {
992 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblPr") => {
993 reject_conflicting_a_prefix(&start)?;
994 return Self::from_element(&mut reader, &start);
995 }
996 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblPr") => {
997 reject_conflicting_a_prefix(&start)?;
998 return Self::from_start(&start, None, OrderedRawChildren::default());
999 }
1000 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1001 Event::Eof => return Err(missing("a:tblPr")),
1002 _ => {}
1003 }
1004 buffer.clear();
1005 }
1006 }
1007
1008 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1009 let mut style_id = None;
1010 let mut raw_children = OrderedRawChildren::default();
1011 let mut boundary = 0usize;
1012 let mut buffer = Vec::new();
1013 loop {
1014 match reader.read_event_into(&mut buffer)? {
1015 Event::Start(child)
1016 if matches_local_name(child.name().as_ref(), b"tableStyleId") =>
1017 {
1018 reject_conflicting_a_prefix(&child)?;
1019 if style_id.is_some() {
1020 return Err(OxmlError::InvalidValue(
1021 "duplicate a:tableStyleId".to_owned(),
1022 ));
1023 }
1024 style_id = Some(read_element_text(reader, child.name()));
1025 boundary = 1;
1026 }
1027 Event::Empty(child)
1028 if matches_local_name(child.name().as_ref(), b"tableStyleId") =>
1029 {
1030 reject_conflicting_a_prefix(&child)?;
1031 if style_id.replace(String::new()).is_some() {
1032 return Err(OxmlError::InvalidValue(
1033 "duplicate a:tableStyleId".to_owned(),
1034 ));
1035 }
1036 boundary = 1;
1037 }
1038 Event::Start(child) => {
1039 raw_children.push(boundary, capture_element(reader, &child)?)
1040 }
1041 Event::Empty(child) => raw_children.push(boundary, capture_empty_element(&child)?),
1042 Event::End(end) if matches_local_name(end.name().as_ref(), b"tblPr") => break,
1043 Event::Eof => return Err(missing("closing a:tblPr")),
1044 _ => {}
1045 }
1046 buffer.clear();
1047 }
1048 Self::from_start(start, style_id, raw_children)
1049 }
1050
1051 fn from_start(
1052 start: &BytesStart<'_>,
1053 style_id: Option<String>,
1054 raw_children: OrderedRawChildren,
1055 ) -> Result<Self> {
1056 Ok(Self {
1057 right_to_left: bool_attr(start, b"rtl")?.unwrap_or(false),
1058 first_row: bool_attr(start, b"firstRow")?.unwrap_or(false),
1059 first_column: bool_attr(start, b"firstCol")?.unwrap_or(false),
1060 last_row: bool_attr(start, b"lastRow")?.unwrap_or(false),
1061 last_column: bool_attr(start, b"lastCol")?.unwrap_or(false),
1062 band_rows: bool_attr(start, b"bandRow")?.unwrap_or(false),
1063 band_columns: bool_attr(start, b"bandCol")?.unwrap_or(false),
1064 style_id,
1065 raw_attributes: raw_attributes(
1066 start,
1067 &[
1068 b"rtl",
1069 b"firstRow",
1070 b"firstCol",
1071 b"lastRow",
1072 b"lastCol",
1073 b"bandRow",
1074 b"bandCol",
1075 ],
1076 false,
1077 )?,
1078 raw_children,
1079 })
1080 }
1081
1082 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1083 let mut start = BytesStart::new("a:tblPr");
1084 push_true(&mut start, "rtl", self.right_to_left);
1085 push_true(&mut start, "firstRow", self.first_row);
1086 push_true(&mut start, "firstCol", self.first_column);
1087 push_true(&mut start, "lastRow", self.last_row);
1088 push_true(&mut start, "lastCol", self.last_column);
1089 push_true(&mut start, "bandRow", self.band_rows);
1090 push_true(&mut start, "bandCol", self.band_columns);
1091 push_attributes(&mut start, &self.raw_attributes);
1092 if self.style_id.is_none() && self.raw_children.is_empty() {
1093 writer.write_event(Event::Empty(start))?;
1094 return Ok(());
1095 }
1096 writer.write_event(Event::Start(start))?;
1097 emit_raw(writer, self.raw_children.at(0))?;
1098 if let Some(style_id) = &self.style_id {
1099 writer.write_event(Event::Start(BytesStart::new("a:tableStyleId")))?;
1100 writer.write_event(Event::Text(BytesText::new(style_id)))?;
1101 writer.write_event(Event::End(BytesEnd::new("a:tableStyleId")))?;
1102 }
1103 emit_raw(writer, self.raw_children.at(1))?;
1104 writer.write_event(Event::End(BytesEnd::new("a:tblPr")))?;
1105 Ok(())
1106 }
1107}
1108
1109impl CT_TableGrid {
1110 fn canonical_xml(&self) -> Result<Vec<u8>> {
1111 let mut writer = Writer::new(Vec::new());
1112 self.write_xml(&mut writer)?;
1113 Ok(writer.into_inner())
1114 }
1115
1116 fn from_xml(xml: &[u8]) -> Result<Self> {
1117 let mut reader = Reader::from_reader(xml);
1118 let mut buffer = Vec::new();
1119 loop {
1120 match reader.read_event_into(&mut buffer)? {
1121 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblGrid") => {
1122 reject_conflicting_a_prefix(&start)?;
1123 return Self::from_element(&mut reader, &start);
1124 }
1125 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblGrid") => {
1126 reject_conflicting_a_prefix(&start)?;
1127 return Err(missing("at least one a:gridCol"));
1128 }
1129 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1130 Event::Eof => return Err(missing("a:tblGrid")),
1131 _ => {}
1132 }
1133 buffer.clear();
1134 }
1135 }
1136
1137 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1138 let mut columns = Vec::new();
1139 let mut column_raw = Vec::new();
1140 let mut raw_children = OrderedRawChildren::default();
1141 let mut buffer = Vec::new();
1142 loop {
1143 match reader.read_event_into(&mut buffer)? {
1144 Event::Start(child) if matches_local_name(child.name().as_ref(), b"gridCol") => {
1145 reject_conflicting_a_prefix(&child)?;
1146 let raw = capture_element(reader, &child)?;
1147 let (width, metadata) = parse_grid_column(&raw)?;
1148 columns.push(width);
1149 column_raw.push(metadata);
1150 }
1151 Event::Empty(child) if matches_local_name(child.name().as_ref(), b"gridCol") => {
1152 reject_conflicting_a_prefix(&child)?;
1153 let width = Emu(required_i64(&child, b"w")?);
1154 columns.push(width);
1155 column_raw.push(GridColumnRaw {
1156 width,
1157 attributes: raw_attributes(&child, &[b"w"], false)?,
1158 children: Vec::new(),
1159 });
1160 }
1161 Event::Start(child) => {
1162 raw_children.push(columns.len(), capture_element(reader, &child)?)
1163 }
1164 Event::Empty(child) => {
1165 raw_children.push(columns.len(), capture_empty_element(&child)?)
1166 }
1167 Event::End(end) if matches_local_name(end.name().as_ref(), b"tblGrid") => break,
1168 Event::Eof => return Err(missing("closing a:tblGrid")),
1169 _ => {}
1170 }
1171 buffer.clear();
1172 }
1173 if columns.is_empty() {
1174 return Err(missing("at least one a:gridCol"));
1175 }
1176 Ok(Self {
1177 columns,
1178 column_raw,
1179 raw_attributes: raw_attributes(start, &[], false)?,
1180 raw_children,
1181 })
1182 }
1183
1184 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1185 if self.columns.is_empty() {
1186 return Err(missing("at least one a:gridCol"));
1187 }
1188 let column_matches = self.matched_column_indices()?;
1189 let original_to_current = invert_matches(&column_matches, self.column_raw.len());
1190 let mut start = BytesStart::new("a:tblGrid");
1191 push_attributes(&mut start, &self.raw_attributes);
1192 writer.write_event(Event::Start(start))?;
1193 for (boundary, (width, metadata_index)) in
1194 self.columns.iter().zip(&column_matches).enumerate()
1195 {
1196 emit_raw(
1197 writer,
1198 self.raw_children.at_reconciled(
1199 boundary,
1200 0,
1201 &original_to_current,
1202 self.columns.len(),
1203 ),
1204 )?;
1205 let metadata = metadata_index.map(|index| &self.column_raw[index]);
1206 let mut column = BytesStart::new("a:gridCol");
1207 let width = width.0.to_string();
1208 column.push_attribute(("w", width.as_str()));
1209 if let Some(metadata) = metadata {
1210 push_attributes(&mut column, &metadata.attributes);
1211 }
1212 match metadata {
1213 Some(metadata) if !metadata.children.is_empty() => {
1214 writer.write_event(Event::Start(column))?;
1215 for child in &metadata.children {
1216 writer.get_mut().write_all(child)?;
1217 }
1218 writer.write_event(Event::End(BytesEnd::new("a:gridCol")))?;
1219 }
1220 _ => writer.write_event(Event::Empty(column))?,
1221 }
1222 }
1223 emit_raw(
1224 writer,
1225 self.raw_children.at_reconciled(
1226 self.columns.len(),
1227 0,
1228 &original_to_current,
1229 self.columns.len(),
1230 ),
1231 )?;
1232 writer.write_event(Event::End(BytesEnd::new("a:tblGrid")))?;
1233 Ok(())
1234 }
1235
1236 fn matched_column_indices(&self) -> Result<Vec<Option<usize>>> {
1237 let original_widths = self
1238 .column_raw
1239 .iter()
1240 .map(|metadata| metadata.width)
1241 .collect::<Vec<_>>();
1242 if self.columns == original_widths {
1243 return Ok((0..self.columns.len()).map(Some).collect());
1244 }
1245
1246 let identity_sensitive = self
1247 .column_raw
1248 .iter()
1249 .any(|metadata| !metadata.attributes.is_empty() || !metadata.children.is_empty())
1250 || !self.raw_children.is_empty();
1251 if !identity_sensitive {
1252 return Ok(matched_original_indices(&self.columns, &original_widths));
1253 }
1254
1255 if has_duplicate_values(&self.columns) || has_duplicate_values(&original_widths) {
1256 return Err(ambiguous_grid_metadata());
1257 }
1258
1259 let exact_matches = self
1260 .columns
1261 .iter()
1262 .map(|width| {
1263 original_widths
1264 .iter()
1265 .position(|original| original == width)
1266 })
1267 .collect::<Vec<_>>();
1268 let unmatched_current = exact_matches
1269 .iter()
1270 .filter(|matched| matched.is_none())
1271 .count();
1272 let unmatched_original = original_widths
1273 .iter()
1274 .enumerate()
1275 .filter(|(index, _)| !exact_matches.contains(&Some(*index)))
1276 .count();
1277 if unmatched_current > 0
1278 && unmatched_original > 0
1279 && (unmatched_current > 1 || unmatched_original > 1)
1280 {
1281 return Err(ambiguous_grid_metadata());
1282 }
1283 Ok(matched_original_indices(&self.columns, &original_widths))
1284 }
1285
1286 fn align_column_metadata(&mut self) -> Result<()> {
1290 let matches = self.matched_column_indices()?;
1291 let original_to_current = invert_matches(&matches, self.column_raw.len());
1292 let mut raw_children = OrderedRawChildren::default();
1293 for boundary in 0..=self.columns.len() {
1294 for child in self.raw_children.at_reconciled(
1295 boundary,
1296 0,
1297 &original_to_current,
1298 self.columns.len(),
1299 ) {
1300 raw_children.push(boundary, child.to_vec());
1301 }
1302 }
1303 self.column_raw = self
1304 .columns
1305 .iter()
1306 .zip(&matches)
1307 .map(|(width, matched)| GridColumnRaw {
1308 width: *width,
1309 ..matched.map_or_else(GridColumnRaw::default, |index| {
1310 self.column_raw[index].clone()
1311 })
1312 })
1313 .collect();
1314 self.raw_children = raw_children;
1315 Ok(())
1316 }
1317
1318 fn insert_column(&mut self, index: usize, width: Emu) -> Result<()> {
1321 self.align_column_metadata()?;
1322 self.raw_children.shift_boundaries_from(index);
1323 self.columns.insert(index, width);
1324 self.column_raw.insert(
1325 index,
1326 GridColumnRaw {
1327 width,
1328 ..GridColumnRaw::default()
1329 },
1330 );
1331 Ok(())
1332 }
1333
1334 fn remove_column(&mut self, index: usize) -> Result<()> {
1337 self.align_column_metadata()?;
1338 let mut raw_children = OrderedRawChildren::default();
1339 for boundary in 0..=self.columns.len() {
1340 let target = if boundary > index {
1341 boundary - 1
1342 } else {
1343 boundary
1344 };
1345 for child in self.raw_children.at(boundary) {
1346 raw_children.push(target, child.to_vec());
1347 }
1348 }
1349 self.raw_children = raw_children;
1350 self.columns.remove(index);
1351 self.column_raw.remove(index);
1352 Ok(())
1353 }
1354}
1355
1356fn has_duplicate_values<T: PartialEq>(values: &[T]) -> bool {
1357 values
1358 .iter()
1359 .enumerate()
1360 .any(|(index, value)| values[..index].contains(value))
1361}
1362
1363fn ambiguous_grid_metadata() -> OxmlError {
1364 OxmlError::InvalidValue(
1365 "edited table grid is ambiguous because preserved column metadata cannot be associated reliably"
1366 .to_owned(),
1367 )
1368}
1369
1370fn parse_grid_column(xml: &[u8]) -> Result<(Emu, GridColumnRaw)> {
1371 let mut reader = Reader::from_reader(xml);
1372 let mut buffer = Vec::new();
1373 loop {
1374 match reader.read_event_into(&mut buffer)? {
1375 Event::Start(start) => {
1376 let width = Emu(required_i64(&start, b"w")?);
1377 let attributes = raw_attributes(&start, &[b"w"], false)?;
1378 let mut children = Vec::new();
1379 loop {
1380 buffer.clear();
1381 match reader.read_event_into(&mut buffer)? {
1382 Event::Start(child) => children.push(capture_element(&mut reader, &child)?),
1383 Event::Empty(child) => children.push(capture_empty_element(&child)?),
1384 Event::End(end) if matches_local_name(end.name().as_ref(), b"gridCol") => {
1385 break;
1386 }
1387 Event::Eof => return Err(missing("closing a:gridCol")),
1388 _ => {}
1389 }
1390 }
1391 return Ok((
1392 width,
1393 GridColumnRaw {
1394 width,
1395 attributes,
1396 children,
1397 },
1398 ));
1399 }
1400 Event::Eof => return Err(missing("a:gridCol")),
1401 _ => {}
1402 }
1403 buffer.clear();
1404 }
1405}
1406
1407impl CT_TableRow {
1408 fn canonical_xml(&self) -> Result<Vec<u8>> {
1409 let mut writer = Writer::new(Vec::new());
1410 self.write_xml(&mut writer)?;
1411 Ok(writer.into_inner())
1412 }
1413
1414 fn from_xml(xml: &[u8]) -> Result<Self> {
1415 let mut reader = Reader::from_reader(xml);
1416 let mut buffer = Vec::new();
1417 loop {
1418 match reader.read_event_into(&mut buffer)? {
1419 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tr") => {
1420 reject_conflicting_a_prefix(&start)?;
1421 return Self::from_element(&mut reader, &start);
1422 }
1423 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tr") => {
1424 reject_conflicting_a_prefix(&start)?;
1425 return Err(missing("at least one a:tc"));
1426 }
1427 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1428 Event::Eof => return Err(missing("a:tr")),
1429 _ => {}
1430 }
1431 buffer.clear();
1432 }
1433 }
1434
1435 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1436 let mut cells = Vec::new();
1437 let mut raw_children = OrderedRawChildren::default();
1438 let mut buffer = Vec::new();
1439 loop {
1440 match reader.read_event_into(&mut buffer)? {
1441 Event::Start(child) if matches_local_name(child.name().as_ref(), b"tc") => {
1442 reject_conflicting_a_prefix(&child)?;
1443 let mut cell = CT_TableCell::from_xml(&capture_element(reader, &child)?)?;
1444 cell.origin_index = cells.len();
1445 cells.push(cell);
1446 }
1447 Event::Empty(child) if matches_local_name(child.name().as_ref(), b"tc") => {
1448 reject_conflicting_a_prefix(&child)?;
1449 let mut cell = CT_TableCell::from_xml(&capture_empty_element(&child)?)?;
1450 cell.origin_index = cells.len();
1451 cells.push(cell);
1452 }
1453 Event::Start(child) => {
1454 raw_children.push(cells.len(), capture_element(reader, &child)?)
1455 }
1456 Event::Empty(child) => {
1457 raw_children.push(cells.len(), capture_empty_element(&child)?)
1458 }
1459 Event::End(end) if matches_local_name(end.name().as_ref(), b"tr") => break,
1460 Event::Eof => return Err(missing("closing a:tr")),
1461 _ => {}
1462 }
1463 buffer.clear();
1464 }
1465 if cells.is_empty() {
1466 return Err(missing("at least one a:tc"));
1467 }
1468 Ok(Self {
1469 height: Emu(required_i64(start, b"h")?),
1470 original_cells: cells.clone(),
1471 cells,
1472 raw_attributes: raw_attributes(start, &[b"h"], false)?,
1473 raw_children,
1474 origin_index: 0,
1475 })
1476 }
1477
1478 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1479 if self.cells.is_empty() {
1480 return Err(missing("at least one a:tc"));
1481 }
1482 let mut start = BytesStart::new("a:tr");
1483 let height = self.height.0.to_string();
1484 start.push_attribute(("h", height.as_str()));
1485 push_attributes(&mut start, &self.raw_attributes);
1486 writer.write_event(Event::Start(start))?;
1487 let current_cell_origins = self
1488 .cells
1489 .iter()
1490 .map(|cell| cell.origin_index)
1491 .collect::<Vec<_>>();
1492 let original_cell_origins = self
1493 .original_cells
1494 .iter()
1495 .map(|cell| cell.origin_index)
1496 .collect::<Vec<_>>();
1497 let cell_matches = matched_original_indices(¤t_cell_origins, &original_cell_origins);
1498 let original_to_current = invert_matches(&cell_matches, self.original_cells.len());
1499 for boundary in 0..=self.cells.len() {
1500 emit_raw(
1501 writer,
1502 self.raw_children.at_reconciled(
1503 boundary,
1504 0,
1505 &original_to_current,
1506 self.cells.len(),
1507 ),
1508 )?;
1509 if let Some(cell) = self.cells.get(boundary) {
1510 cell.write_xml(writer)?;
1511 }
1512 }
1513 writer.write_event(Event::End(BytesEnd::new("a:tr")))?;
1514 Ok(())
1515 }
1516}
1517
1518impl CT_TableCell {
1519 fn canonical_xml(&self) -> Result<Vec<u8>> {
1520 let mut writer = Writer::new(Vec::new());
1521 self.write_xml(&mut writer)?;
1522 Ok(writer.into_inner())
1523 }
1524
1525 fn from_xml(xml: &[u8]) -> Result<Self> {
1526 let mut reader = Reader::from_reader(xml);
1527 let mut buffer = Vec::new();
1528 loop {
1529 match reader.read_event_into(&mut buffer)? {
1530 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tc") => {
1531 reject_conflicting_a_prefix(&start)?;
1532 return Self::from_element(&mut reader, &start);
1533 }
1534 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tc") => {
1535 reject_conflicting_a_prefix(&start)?;
1536 return Self::from_start(&start, None, None, OrderedRawChildren::default());
1537 }
1538 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1539 Event::Eof => return Err(missing("a:tc")),
1540 _ => {}
1541 }
1542 buffer.clear();
1543 }
1544 }
1545
1546 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1547 let mut text_body = None;
1548 let mut properties = None;
1549 let mut raw_children = OrderedRawChildren::default();
1550 let mut boundary = 0usize;
1551 let mut buffer = Vec::new();
1552 loop {
1553 match reader.read_event_into(&mut buffer)? {
1554 Event::Start(child) => {
1555 let name = local_name(child.name().as_ref()).to_vec();
1556 if matches!(name.as_slice(), b"txBody" | b"tcPr") {
1557 reject_conflicting_a_prefix(&child)?;
1558 }
1559 let raw = capture_element(reader, &child)?;
1560 capture_cell_child(
1561 &name,
1562 raw,
1563 &mut text_body,
1564 &mut properties,
1565 &mut raw_children,
1566 &mut boundary,
1567 )?;
1568 }
1569 Event::Empty(child) => {
1570 let name = local_name(child.name().as_ref()).to_vec();
1571 if matches!(name.as_slice(), b"txBody" | b"tcPr") {
1572 reject_conflicting_a_prefix(&child)?;
1573 }
1574 let raw = capture_empty_element(&child)?;
1575 capture_cell_child(
1576 &name,
1577 raw,
1578 &mut text_body,
1579 &mut properties,
1580 &mut raw_children,
1581 &mut boundary,
1582 )?;
1583 }
1584 Event::End(end) if matches_local_name(end.name().as_ref(), b"tc") => break,
1585 Event::Eof => return Err(missing("closing a:tc")),
1586 _ => {}
1587 }
1588 buffer.clear();
1589 }
1590 Self::from_start(start, text_body, properties, raw_children)
1591 }
1592
1593 fn from_start(
1594 start: &BytesStart<'_>,
1595 text_body: Option<CT_TextBody>,
1596 properties: Option<CT_TableCellProperties>,
1597 raw_children: OrderedRawChildren,
1598 ) -> Result<Self> {
1599 Ok(Self {
1600 text_body,
1601 row_span: positive_u32_attr(start, b"rowSpan")?.unwrap_or(1),
1602 grid_span: positive_u32_attr(start, b"gridSpan")?.unwrap_or(1),
1603 horizontal_merge: bool_attr(start, b"hMerge")?.unwrap_or(false),
1604 vertical_merge: bool_attr(start, b"vMerge")?.unwrap_or(false),
1605 properties,
1606 raw_attributes: raw_attributes(
1607 start,
1608 &[b"rowSpan", b"gridSpan", b"hMerge", b"vMerge"],
1609 false,
1610 )?,
1611 raw_children,
1612 origin_index: 0,
1613 })
1614 }
1615
1616 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1617 if self.row_span == 0 || self.grid_span == 0 {
1618 return Err(OxmlError::InvalidValue(
1619 "a:tc spans must be positive".to_owned(),
1620 ));
1621 }
1622 let mut start = BytesStart::new("a:tc");
1623 let row_span = self.row_span.to_string();
1624 let grid_span = self.grid_span.to_string();
1625 if self.row_span != 1 {
1626 start.push_attribute(("rowSpan", row_span.as_str()));
1627 }
1628 if self.grid_span != 1 {
1629 start.push_attribute(("gridSpan", grid_span.as_str()));
1630 }
1631 push_true(&mut start, "hMerge", self.horizontal_merge);
1632 push_true(&mut start, "vMerge", self.vertical_merge);
1633 push_attributes(&mut start, &self.raw_attributes);
1634 if self.text_body.is_none() && self.properties.is_none() && self.raw_children.is_empty() {
1635 writer.write_event(Event::Empty(start))?;
1636 return Ok(());
1637 }
1638 writer.write_event(Event::Start(start))?;
1639 emit_raw(writer, self.raw_children.at(0))?;
1640 if let Some(text_body) = &self.text_body {
1641 writer
1642 .get_mut()
1643 .write_all(&text_body.to_xml().map_err(|error| {
1644 OxmlError::InvalidValue(format!("invalid table-cell text body: {error}"))
1645 })?)?;
1646 }
1647 emit_raw(writer, self.raw_children.at(1))?;
1648 if let Some(properties) = &self.properties {
1649 properties.write_xml(writer)?;
1650 }
1651 emit_raw(writer, self.raw_children.at(2))?;
1652 writer.write_event(Event::End(BytesEnd::new("a:tc")))?;
1653 Ok(())
1654 }
1655
1656 fn empty_like(&self) -> Self {
1659 Self {
1660 text_body: Some(
1661 self.text_body
1662 .as_ref()
1663 .map_or_else(CT_TextBody::new, CT_TextBody::empty_like),
1664 ),
1665 row_span: 1,
1666 grid_span: 1,
1667 horizontal_merge: false,
1668 vertical_merge: false,
1669 properties: self.properties.clone(),
1670 raw_attributes: Vec::new(),
1671 raw_children: OrderedRawChildren::default(),
1672 origin_index: usize::MAX,
1673 }
1674 }
1675
1676 fn copy_merge_state(&mut self, other: &Self) {
1677 self.row_span = other.row_span;
1678 self.grid_span = other.grid_span;
1679 self.horizontal_merge = other.horizontal_merge;
1680 self.vertical_merge = other.vertical_merge;
1681 }
1682
1683 fn take_merge_origin(&mut self, edge: &mut Self, content: bool) {
1686 self.copy_merge_state(edge);
1687 if content {
1688 std::mem::swap(&mut self.text_body, &mut edge.text_body);
1689 std::mem::swap(&mut self.properties, &mut edge.properties);
1690 }
1691 }
1692}
1693
1694fn capture_cell_child(
1695 name: &[u8],
1696 raw: Vec<u8>,
1697 text_body: &mut Option<CT_TextBody>,
1698 properties: &mut Option<CT_TableCellProperties>,
1699 raw_children: &mut OrderedRawChildren,
1700 boundary: &mut usize,
1701) -> Result<()> {
1702 match name {
1703 b"txBody" if *boundary == 0 && text_body.is_none() => {
1704 *text_body = Some(CT_TextBody::from_xml(&raw).map_err(|error| {
1705 OxmlError::InvalidValue(format!("invalid a:tc/a:txBody: {error}"))
1706 })?);
1707 *boundary = 1;
1708 }
1709 b"tcPr" if *boundary <= 1 && properties.is_none() => {
1710 *properties = Some(CT_TableCellProperties::from_xml(&raw)?);
1711 *boundary = 2;
1712 }
1713 b"txBody" | b"tcPr" => {
1714 return Err(OxmlError::InvalidValue(
1715 "a:tc children must be a:txBody? then a:tcPr?".to_owned(),
1716 ));
1717 }
1718 _ => raw_children.push(*boundary, raw),
1719 }
1720 Ok(())
1721}
1722
1723impl CT_TableCellProperties {
1724 fn from_xml(xml: &[u8]) -> Result<Self> {
1725 let mut reader = Reader::from_reader(xml);
1726 let mut buffer = Vec::new();
1727 loop {
1728 match reader.read_event_into(&mut buffer)? {
1729 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcPr") => {
1730 return Self::from_element(&mut reader, &start);
1731 }
1732 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcPr") => {
1733 return Self::from_start(&start);
1734 }
1735 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1736 Event::Eof => return Err(missing("a:tcPr")),
1737 _ => {}
1738 }
1739 buffer.clear();
1740 }
1741 }
1742
1743 fn from_start(start: &BytesStart<'_>) -> Result<Self> {
1744 Ok(Self {
1745 margin_left: optional_emu_attr(start, b"marL")?,
1746 margin_right: optional_emu_attr(start, b"marR")?,
1747 margin_top: optional_emu_attr(start, b"marT")?,
1748 margin_bottom: optional_emu_attr(start, b"marB")?,
1749 raw_attributes: raw_attributes(start, &[b"marL", b"marR", b"marT", b"marB"], false)?,
1750 ..Self::default()
1751 })
1752 }
1753
1754 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1755 let mut properties = Self::from_start(start)?;
1756 let mut boundary = 0usize;
1757 let mut buffer = Vec::new();
1758 loop {
1759 match reader.read_event_into(&mut buffer)? {
1760 Event::Start(child) => {
1761 let name = local_name(child.name().as_ref()).to_vec();
1762 let raw = capture_element(reader, &child)?;
1763 properties.capture_child(&name, raw, &mut boundary)?;
1764 }
1765 Event::Empty(child) => {
1766 let name = local_name(child.name().as_ref()).to_vec();
1767 let raw = capture_empty_element(&child)?;
1768 properties.capture_child(&name, raw, &mut boundary)?;
1769 }
1770 Event::End(end) if matches_local_name(end.name().as_ref(), b"tcPr") => break,
1771 Event::Eof => return Err(missing("closing a:tcPr")),
1772 _ => {}
1773 }
1774 buffer.clear();
1775 }
1776 Ok(properties)
1777 }
1778
1779 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
1780 let slot = cell_property_slot(name);
1781 ensure_schema_order(slot, *boundary, "a:tcPr")?;
1782 match name {
1783 b"lnL" => set_once(&mut self.left, parse_named_line(&raw)?, "a:tcPr/a:lnL")?,
1784 b"lnR" => set_once(&mut self.right, parse_named_line(&raw)?, "a:tcPr/a:lnR")?,
1785 b"lnT" => set_once(&mut self.top, parse_named_line(&raw)?, "a:tcPr/a:lnT")?,
1786 b"lnB" => set_once(&mut self.bottom, parse_named_line(&raw)?, "a:tcPr/a:lnB")?,
1787 name if is_fill(name) => set_once(&mut self.fill, parse_fill(&raw)?, "a:tcPr fill")?,
1788 b"lnTlToBr" | b"lnBlToTr" => {
1789 self.unsupported.push("diagonal border".to_owned());
1790 self.raw_children.push(slot.unwrap_or(*boundary), raw);
1791 }
1792 b"cell3D" => {
1793 self.unsupported.push("3-D properties".to_owned());
1794 self.raw_children.push(slot.unwrap_or(*boundary), raw);
1795 }
1796 b"effectLst" | b"effectDag" => {
1797 self.unsupported.push("effects".to_owned());
1798 self.raw_children.push(slot.unwrap_or(*boundary), raw);
1799 }
1800 _ => self.raw_children.push(slot.unwrap_or(*boundary), raw),
1801 }
1802 if let Some(slot) = slot {
1803 *boundary = (*boundary).max(slot + 1);
1804 }
1805 Ok(())
1806 }
1807
1808 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1809 let mut start = BytesStart::new("a:tcPr");
1810 push_optional_emu(&mut start, "marL", self.margin_left);
1811 push_optional_emu(&mut start, "marR", self.margin_right);
1812 push_optional_emu(&mut start, "marT", self.margin_top);
1813 push_optional_emu(&mut start, "marB", self.margin_bottom);
1814 push_attributes(&mut start, &self.raw_attributes);
1815 let has_modelled = self.left.is_some()
1816 || self.right.is_some()
1817 || self.top.is_some()
1818 || self.bottom.is_some()
1819 || self.fill.is_some();
1820 if !has_modelled && self.raw_children.is_empty() {
1821 writer.write_event(Event::Empty(start))?;
1822 return Ok(());
1823 }
1824 writer.write_event(Event::Start(start))?;
1825 emit_raw(writer, self.raw_children.at(0))?;
1826 write_optional_named_line(writer, "a:lnL", self.left.as_ref())?;
1827 emit_raw(writer, self.raw_children.at(1))?;
1828 write_optional_named_line(writer, "a:lnR", self.right.as_ref())?;
1829 emit_raw(writer, self.raw_children.at(2))?;
1830 write_optional_named_line(writer, "a:lnT", self.top.as_ref())?;
1831 emit_raw(writer, self.raw_children.at(3))?;
1832 write_optional_named_line(writer, "a:lnB", self.bottom.as_ref())?;
1833 for slot in 4..=7 {
1834 emit_raw(writer, self.raw_children.at(slot))?;
1835 }
1836 if let Some(fill) = &self.fill {
1837 fill.write_xml(writer).map_err(drawing_error)?;
1838 }
1839 for slot in 8..=10 {
1840 emit_raw(writer, self.raw_children.at(slot))?;
1841 }
1842 writer.write_event(Event::End(BytesEnd::new("a:tcPr")))?;
1843 Ok(())
1844 }
1845}
1846
1847impl CT_TableStyleList {
1848 pub fn from_xml(xml: &[u8]) -> Result<Self> {
1850 let mut reader = Reader::from_reader(xml);
1851 let mut buffer = Vec::new();
1852 loop {
1853 match reader.read_event_into(&mut buffer)? {
1854 Event::Start(start)
1855 if matches_local_name(start.name().as_ref(), b"tblStyleLst") =>
1856 {
1857 reject_conflicting_a_prefix(&start)?;
1858 return Self::from_element(&mut reader, &start);
1859 }
1860 Event::Empty(start)
1861 if matches_local_name(start.name().as_ref(), b"tblStyleLst") =>
1862 {
1863 reject_conflicting_a_prefix(&start)?;
1864 return Ok(Self {
1865 default_style_id: decoded_attr(&start, b"def")?,
1866 styles: Vec::new(),
1867 raw_attributes: raw_attributes(&start, &[b"def"], true)?,
1868 raw_children: OrderedRawChildren::default(),
1869 });
1870 }
1871 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1872 Event::Eof => return Err(missing("a:tblStyleLst")),
1873 _ => {}
1874 }
1875 buffer.clear();
1876 }
1877 }
1878
1879 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1880 let mut styles = Vec::new();
1881 let mut raw_children = OrderedRawChildren::default();
1882 let mut buffer = Vec::new();
1883 loop {
1884 match reader.read_event_into(&mut buffer)? {
1885 Event::Start(child) if matches_local_name(child.name().as_ref(), b"tblStyle") => {
1886 let raw = capture_element(reader, &child)?;
1887 styles.push(CT_TableStyle::from_xml(&raw)?);
1888 }
1889 Event::Empty(child) if matches_local_name(child.name().as_ref(), b"tblStyle") => {
1890 let raw = capture_empty_element(&child)?;
1891 styles.push(CT_TableStyle::from_xml(&raw)?);
1892 }
1893 Event::Start(child) => {
1894 raw_children.push(styles.len(), capture_element(reader, &child)?)
1895 }
1896 Event::Empty(child) => {
1897 raw_children.push(styles.len(), capture_empty_element(&child)?)
1898 }
1899 Event::End(end) if matches_local_name(end.name().as_ref(), b"tblStyleLst") => break,
1900 Event::Eof => return Err(missing("closing a:tblStyleLst")),
1901 _ => {}
1902 }
1903 buffer.clear();
1904 }
1905 Ok(Self {
1906 default_style_id: decoded_attr(start, b"def")?,
1907 styles,
1908 raw_attributes: raw_attributes(start, &[b"def"], true)?,
1909 raw_children,
1910 })
1911 }
1912
1913 pub fn style(&self, style_id: Option<&str>) -> Option<&CT_TableStyle> {
1915 style_id
1916 .and_then(|style_id| self.styles.iter().find(|style| style.style_id == style_id))
1917 .or_else(|| {
1918 self.default_style_id.as_deref().and_then(|style_id| {
1919 self.styles.iter().find(|style| style.style_id == style_id)
1920 })
1921 })
1922 }
1923
1924 pub fn to_xml(&self) -> Result<Vec<u8>> {
1926 let mut writer = Writer::new(Vec::new());
1927 let mut start = BytesStart::new("a:tblStyleLst");
1928 start.push_attribute(("xmlns:a", A_NS));
1929 if let Some(default_style_id) = &self.default_style_id {
1930 start.push_attribute(("def", default_style_id.as_str()));
1931 }
1932 push_attributes(&mut start, &self.raw_attributes);
1933 if self.styles.is_empty() && self.raw_children.is_empty() {
1934 writer.write_event(Event::Empty(start))?;
1935 return Ok(writer.into_inner());
1936 }
1937 writer.write_event(Event::Start(start))?;
1938 for boundary in 0..=self.styles.len() {
1939 emit_raw(&mut writer, self.raw_children.at(boundary))?;
1940 if let Some(style) = self.styles.get(boundary) {
1941 style.write_xml(&mut writer)?;
1942 }
1943 }
1944 writer.write_event(Event::End(BytesEnd::new("a:tblStyleLst")))?;
1945 Ok(writer.into_inner())
1946 }
1947}
1948
1949impl CT_TableStyle {
1950 pub fn builtin(style_id: &str) -> Option<Self> {
1958 let (_, family, accent) = builtin_table_styles()
1959 .into_iter()
1960 .find(|(id, ..)| *id == style_id)?;
1961 let xml = format!(
1962 r#"<a:tblStyle styleId="{style_id}" styleName="{}">{}</a:tblStyle>"#,
1963 family.style_name(accent),
1964 family.regions(accent)
1965 );
1966 let style = Self::from_xml(xml.as_bytes()).unwrap_or_else(|error| {
1967 panic!("built-in table style {style_id} is malformed: {error}")
1968 });
1969 Some(style)
1970 }
1971
1972 fn from_xml(xml: &[u8]) -> Result<Self> {
1973 let mut reader = Reader::from_reader(xml);
1974 let mut buffer = Vec::new();
1975 loop {
1976 match reader.read_event_into(&mut buffer)? {
1977 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblStyle") => {
1978 return Self::from_element(&mut reader, &start);
1979 }
1980 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblStyle") => {
1981 return Self::from_start(&start);
1982 }
1983 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1984 Event::Eof => return Err(missing("a:tblStyle")),
1985 _ => {}
1986 }
1987 buffer.clear();
1988 }
1989 }
1990
1991 fn from_start(start: &BytesStart<'_>) -> Result<Self> {
1992 Ok(Self {
1993 style_id: required_string(start, b"styleId")?,
1994 style_name: required_string(start, b"styleName")?,
1995 raw_attributes: raw_attributes(start, &[b"styleId", b"styleName"], false)?,
1996 ..Self::default()
1997 })
1998 }
1999
2000 fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
2001 let mut style = Self::from_start(start)?;
2002 let mut boundary = 0usize;
2003 let mut buffer = Vec::new();
2004 loop {
2005 match reader.read_event_into(&mut buffer)? {
2006 Event::Start(child) => {
2007 let name = local_name(child.name().as_ref()).to_vec();
2008 let raw = capture_element(reader, &child)?;
2009 style.capture_child(&name, raw, &mut boundary)?;
2010 }
2011 Event::Empty(child) => {
2012 let name = local_name(child.name().as_ref()).to_vec();
2013 let raw = capture_empty_element(&child)?;
2014 style.capture_child(&name, raw, &mut boundary)?;
2015 }
2016 Event::End(end) if matches_local_name(end.name().as_ref(), b"tblStyle") => break,
2017 Event::Eof => return Err(missing("closing a:tblStyle")),
2018 _ => {}
2019 }
2020 buffer.clear();
2021 }
2022 Ok(style)
2023 }
2024
2025 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2026 let Some(slot) = table_style_slot(name) else {
2027 self.raw_children.push(*boundary, raw);
2028 return Ok(());
2029 };
2030 if slot < *boundary {
2031 return Err(OxmlError::InvalidValue(
2032 "a:tblStyle children violate schema order".to_owned(),
2033 ));
2034 }
2035 if name == b"tblBg" {
2036 self.background = Some(CT_TableBackgroundStyle::from_xml(&raw)?);
2037 *boundary = slot + 1;
2038 return Ok(());
2039 }
2040 let destination = self.region_mut(name);
2041 if destination.is_some() {
2042 return Err(OxmlError::InvalidValue(format!(
2043 "duplicate a:{}",
2044 String::from_utf8_lossy(name)
2045 )));
2046 }
2047 *destination = Some(CT_TablePartStyle::from_xml(&raw, name)?);
2048 *boundary = slot + 1;
2049 Ok(())
2050 }
2051
2052 fn region_mut(&mut self, name: &[u8]) -> &mut Option<CT_TablePartStyle> {
2053 match name {
2054 b"wholeTbl" => &mut self.whole_table,
2055 b"band1H" => &mut self.band1_horizontal,
2056 b"band2H" => &mut self.band2_horizontal,
2057 b"band1V" => &mut self.band1_vertical,
2058 b"band2V" => &mut self.band2_vertical,
2059 b"firstCol" => &mut self.first_column,
2060 b"lastCol" => &mut self.last_column,
2061 b"firstRow" => &mut self.first_row,
2062 b"lastRow" => &mut self.last_row,
2063 b"nwCell" => &mut self.north_west_cell,
2064 b"neCell" => &mut self.north_east_cell,
2065 b"swCell" => &mut self.south_west_cell,
2066 b"seCell" => &mut self.south_east_cell,
2067 _ => unreachable!("modelled table region"),
2068 }
2069 }
2070
2071 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2072 let mut start = BytesStart::new("a:tblStyle");
2073 start.push_attribute(("styleId", self.style_id.as_str()));
2074 start.push_attribute(("styleName", self.style_name.as_str()));
2075 push_attributes(&mut start, &self.raw_attributes);
2076 writer.write_event(Event::Start(start))?;
2077 emit_raw(writer, self.raw_children.at(0))?;
2078 if let Some(background) = &self.background {
2079 background.write_xml(writer)?;
2080 }
2081 let ordered = [
2082 ("a:wholeTbl", self.whole_table.as_ref()),
2083 ("a:band1H", self.band1_horizontal.as_ref()),
2084 ("a:band2H", self.band2_horizontal.as_ref()),
2085 ("a:band1V", self.band1_vertical.as_ref()),
2086 ("a:band2V", self.band2_vertical.as_ref()),
2087 ("a:lastCol", self.last_column.as_ref()),
2088 ("a:firstCol", self.first_column.as_ref()),
2089 ("a:lastRow", self.last_row.as_ref()),
2090 ("a:seCell", self.south_east_cell.as_ref()),
2091 ("a:swCell", self.south_west_cell.as_ref()),
2092 ("a:firstRow", self.first_row.as_ref()),
2093 ("a:neCell", self.north_east_cell.as_ref()),
2094 ("a:nwCell", self.north_west_cell.as_ref()),
2095 ];
2096 for (index, (tag, region)) in ordered.into_iter().enumerate() {
2097 emit_raw(writer, self.raw_children.at(index + 1))?;
2098 if let Some(region) = region {
2099 region.write_xml(writer, tag)?;
2100 }
2101 }
2102 emit_raw(writer, self.raw_children.at(14))?;
2103 emit_raw(writer, self.raw_children.at(15))?;
2104 writer.write_event(Event::End(BytesEnd::new("a:tblStyle")))?;
2105 Ok(())
2106 }
2107}
2108
2109impl CT_TableBackgroundStyle {
2110 fn from_xml(xml: &[u8]) -> Result<Self> {
2111 let mut reader = Reader::from_reader(xml);
2112 let mut buffer = Vec::new();
2113 loop {
2114 match reader.read_event_into(&mut buffer)? {
2115 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblBg") => {
2116 let mut value = Self {
2117 raw_attributes: raw_attributes(&start, &[], false)?,
2118 ..Self::default()
2119 };
2120 let mut boundary = 0usize;
2121 loop {
2122 buffer.clear();
2123 match reader.read_event_into(&mut buffer)? {
2124 Event::Start(child) => {
2125 let name = local_name(child.name().as_ref()).to_vec();
2126 let raw = capture_element(&mut reader, &child)?;
2127 value.capture_child(&name, raw, &mut boundary)?;
2128 }
2129 Event::Empty(child) => {
2130 let name = local_name(child.name().as_ref()).to_vec();
2131 let raw = capture_empty_element(&child)?;
2132 value.capture_child(&name, raw, &mut boundary)?;
2133 }
2134 Event::End(end)
2135 if matches_local_name(end.name().as_ref(), b"tblBg") =>
2136 {
2137 return Ok(value);
2138 }
2139 Event::Eof => return Err(missing("closing a:tblBg")),
2140 _ => {}
2141 }
2142 }
2143 }
2144 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblBg") => {
2145 return Ok(Self {
2146 raw_attributes: raw_attributes(&start, &[], false)?,
2147 ..Self::default()
2148 });
2149 }
2150 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
2151 Event::Eof => return Err(missing("a:tblBg")),
2152 _ => {}
2153 }
2154 buffer.clear();
2155 }
2156 }
2157
2158 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2159 let slot = match name {
2160 b"fill" | b"fillRef" => Some(0),
2161 b"effect" | b"effectRef" => Some(1),
2162 _ => None,
2163 };
2164 ensure_schema_order(slot, *boundary, "a:tblBg")?;
2165 match name {
2166 b"fill" | b"fillRef" if self.fill.is_some() || self.fill_reference.is_some() => {
2167 return Err(OxmlError::InvalidValue(
2168 "duplicate a:tblBg fill choice".to_owned(),
2169 ));
2170 }
2171 b"fill" => {
2172 if let Some(fill) = parse_fill_wrapper(&raw)? {
2173 self.fill = Some(fill);
2174 } else {
2175 self.unsupported.push("fill form".to_owned());
2176 self.raw_children.push(0, raw);
2177 }
2178 }
2179 b"fillRef" => self.fill_reference = Some(parse_style_reference(&raw)?),
2180 b"effect" | b"effectRef" => {
2181 self.unsupported.push("effect".to_owned());
2182 self.raw_children.push(1, raw);
2183 }
2184 _ => self.raw_children.push(*boundary, raw),
2185 }
2186 if let Some(slot) = slot {
2187 *boundary = (*boundary).max(slot + 1);
2188 }
2189 Ok(())
2190 }
2191
2192 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2193 let mut start = BytesStart::new("a:tblBg");
2194 push_attributes(&mut start, &self.raw_attributes);
2195 if self.fill.is_none() && self.fill_reference.is_none() && self.raw_children.is_empty() {
2196 writer.write_event(Event::Empty(start))?;
2197 return Ok(());
2198 }
2199 writer.write_event(Event::Start(start))?;
2200 emit_raw(writer, self.raw_children.at(0))?;
2201 if let Some(fill) = &self.fill {
2202 writer.write_event(Event::Start(BytesStart::new("a:fill")))?;
2203 fill.write_xml(writer).map_err(drawing_error)?;
2204 writer.write_event(Event::End(BytesEnd::new("a:fill")))?;
2205 }
2206 if let Some(reference) = &self.fill_reference {
2207 reference.write_xml(writer).map_err(drawing_error)?;
2208 }
2209 emit_raw(writer, self.raw_children.at(1))?;
2210 emit_raw(writer, self.raw_children.at(2))?;
2211 writer.write_event(Event::End(BytesEnd::new("a:tblBg")))?;
2212 Ok(())
2213 }
2214}
2215
2216impl CT_TablePartStyle {
2217 fn from_xml(xml: &[u8], expected: &[u8]) -> Result<Self> {
2218 let mut reader = Reader::from_reader(xml);
2219 let mut buffer = Vec::new();
2220 loop {
2221 match reader.read_event_into(&mut buffer)? {
2222 Event::Start(start) if matches_local_name(start.name().as_ref(), expected) => {
2223 let mut value = Self {
2224 raw_attributes: raw_attributes(&start, &[], false)?,
2225 ..Self::default()
2226 };
2227 let mut boundary = 0usize;
2228 loop {
2229 buffer.clear();
2230 match reader.read_event_into(&mut buffer)? {
2231 Event::Start(child) => {
2232 let name = local_name(child.name().as_ref()).to_vec();
2233 let raw = capture_element(&mut reader, &child)?;
2234 value.capture_child(&name, raw, &mut boundary)?;
2235 }
2236 Event::Empty(child) => {
2237 let name = local_name(child.name().as_ref()).to_vec();
2238 let raw = capture_empty_element(&child)?;
2239 value.capture_child(&name, raw, &mut boundary)?;
2240 }
2241 Event::End(end)
2242 if matches_local_name(end.name().as_ref(), expected) =>
2243 {
2244 return Ok(value);
2245 }
2246 Event::Eof => return Err(missing("closing table style region")),
2247 _ => {}
2248 }
2249 }
2250 }
2251 Event::Empty(start) if matches_local_name(start.name().as_ref(), expected) => {
2252 return Ok(Self {
2253 raw_attributes: raw_attributes(&start, &[], false)?,
2254 ..Self::default()
2255 });
2256 }
2257 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
2258 Event::Eof => return Err(missing("table style region")),
2259 _ => {}
2260 }
2261 buffer.clear();
2262 }
2263 }
2264
2265 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2266 match name {
2267 b"tcTxStyle" if self.text_style.is_none() && *boundary == 0 => {
2268 self.text_style = Some(CT_TableTextStyle::from_xml(&raw)?);
2269 *boundary = 1;
2270 }
2271 b"tcStyle" if self.cell_style.is_none() => {
2272 self.cell_style = Some(CT_TableCellStyle::from_xml(&raw)?);
2273 *boundary = 2;
2274 }
2275 b"tcTxStyle" | b"tcStyle" => {
2276 return Err(OxmlError::InvalidValue(
2277 "table region style children violate schema order or are duplicated".to_owned(),
2278 ));
2279 }
2280 _ => self.raw_children.push(*boundary, raw),
2281 }
2282 Ok(())
2283 }
2284
2285 fn write_xml<W: Write>(&self, writer: &mut Writer<W>, tag: &str) -> Result<()> {
2286 let mut start = BytesStart::new(tag);
2287 push_attributes(&mut start, &self.raw_attributes);
2288 if self.text_style.is_none() && self.cell_style.is_none() && self.raw_children.is_empty() {
2289 writer.write_event(Event::Empty(start))?;
2290 return Ok(());
2291 }
2292 writer.write_event(Event::Start(start))?;
2293 emit_raw(writer, self.raw_children.at(0))?;
2294 if let Some(text_style) = &self.text_style {
2295 text_style.write_xml(writer)?;
2296 }
2297 emit_raw(writer, self.raw_children.at(1))?;
2298 if let Some(cell_style) = &self.cell_style {
2299 cell_style.write_xml(writer)?;
2300 }
2301 emit_raw(writer, self.raw_children.at(2))?;
2302 writer.write_event(Event::End(BytesEnd::new(tag)))?;
2303 Ok(())
2304 }
2305}
2306
2307impl CT_TableCellStyle {
2308 fn from_xml(xml: &[u8]) -> Result<Self> {
2309 let mut reader = Reader::from_reader(xml);
2310 let mut buffer = Vec::new();
2311 loop {
2312 match reader.read_event_into(&mut buffer)? {
2313 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcStyle") => {
2314 let mut value = Self {
2315 raw_attributes: raw_attributes(&start, &[], false)?,
2316 ..Self::default()
2317 };
2318 let mut boundary = 0usize;
2319 loop {
2320 buffer.clear();
2321 match reader.read_event_into(&mut buffer)? {
2322 Event::Start(child) => {
2323 let name = local_name(child.name().as_ref()).to_vec();
2324 let raw = capture_element(&mut reader, &child)?;
2325 value.capture_child(&name, raw, &mut boundary)?;
2326 }
2327 Event::Empty(child) => {
2328 let name = local_name(child.name().as_ref()).to_vec();
2329 let raw = capture_empty_element(&child)?;
2330 value.capture_child(&name, raw, &mut boundary)?;
2331 }
2332 Event::End(end)
2333 if matches_local_name(end.name().as_ref(), b"tcStyle") =>
2334 {
2335 return Ok(value);
2336 }
2337 Event::Eof => return Err(missing("closing a:tcStyle")),
2338 _ => {}
2339 }
2340 }
2341 }
2342 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcStyle") => {
2343 return Ok(Self {
2344 raw_attributes: raw_attributes(&start, &[], false)?,
2345 ..Self::default()
2346 });
2347 }
2348 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
2349 Event::Eof => return Err(missing("a:tcStyle")),
2350 _ => {}
2351 }
2352 buffer.clear();
2353 }
2354 }
2355
2356 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2357 let slot = match name {
2358 b"tcBdr" => Some(0),
2359 b"fill" => Some(1),
2360 name if is_fill(name) => Some(1),
2361 b"fillRef" => Some(2),
2362 b"cell3D" => Some(3),
2363 _ => None,
2364 };
2365 ensure_schema_order(slot, *boundary, "a:tcStyle")?;
2366 match name {
2367 b"tcBdr" if self.borders.is_some() => {
2368 return Err(OxmlError::InvalidValue("duplicate a:tcBdr".to_owned()));
2369 }
2370 b"tcBdr" => self.borders = Some(CT_TableBorders::from_xml(&raw)?),
2371 b"fill" => {
2372 if self.fill.is_some() || self.fill_reference.is_some() {
2373 return Err(OxmlError::InvalidValue(
2374 "duplicate a:tcStyle fill choice".to_owned(),
2375 ));
2376 }
2377 if let Some(fill) = parse_fill_wrapper(&raw)? {
2378 self.fill = Some(fill);
2379 } else {
2380 self.unsupported.push("fill form".to_owned());
2381 self.raw_children.push(slot.unwrap(), raw);
2382 }
2383 }
2384 name if is_fill(name) => {
2385 if self.fill.is_some() || self.fill_reference.is_some() {
2386 return Err(OxmlError::InvalidValue(
2387 "duplicate a:tcStyle fill choice".to_owned(),
2388 ));
2389 }
2390 self.fill = Some(parse_fill(&raw)?);
2391 }
2392 b"fillRef" => {
2393 if self.fill.is_some() || self.fill_reference.is_some() {
2394 return Err(OxmlError::InvalidValue(
2395 "duplicate a:tcStyle fill choice".to_owned(),
2396 ));
2397 }
2398 self.fill_reference = Some(parse_style_reference(&raw)?);
2399 }
2400 b"cell3D" => {
2401 self.unsupported.push("3-D properties".to_owned());
2402 self.raw_children.push(slot.unwrap(), raw);
2403 }
2404 _ => self.raw_children.push(*boundary, raw),
2405 }
2406 if let Some(slot) = slot {
2407 *boundary = (*boundary).max(slot + 1);
2408 }
2409 Ok(())
2410 }
2411
2412 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2413 let mut start = BytesStart::new("a:tcStyle");
2414 push_attributes(&mut start, &self.raw_attributes);
2415 let has_modelled =
2416 self.borders.is_some() || self.fill.is_some() || self.fill_reference.is_some();
2417 if !has_modelled && self.raw_children.is_empty() {
2418 writer.write_event(Event::Empty(start))?;
2419 return Ok(());
2420 }
2421 writer.write_event(Event::Start(start))?;
2422 emit_raw(writer, self.raw_children.at(0))?;
2423 if let Some(borders) = &self.borders {
2424 borders.write_xml(writer)?;
2425 }
2426 emit_raw(writer, self.raw_children.at(1))?;
2427 if let Some(fill) = &self.fill {
2428 writer.write_event(Event::Start(BytesStart::new("a:fill")))?;
2429 fill.write_xml(writer).map_err(drawing_error)?;
2430 writer.write_event(Event::End(BytesEnd::new("a:fill")))?;
2431 }
2432 emit_raw(writer, self.raw_children.at(2))?;
2433 if let Some(reference) = &self.fill_reference {
2434 reference.write_xml(writer).map_err(drawing_error)?;
2435 }
2436 emit_raw(writer, self.raw_children.at(3))?;
2437 emit_raw(writer, self.raw_children.at(4))?;
2438 writer.write_event(Event::End(BytesEnd::new("a:tcStyle")))?;
2439 Ok(())
2440 }
2441}
2442
2443impl CT_TableBorders {
2444 fn from_xml(xml: &[u8]) -> Result<Self> {
2445 let mut reader = Reader::from_reader(xml);
2446 let mut buffer = Vec::new();
2447 loop {
2448 match reader.read_event_into(&mut buffer)? {
2449 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcBdr") => {
2450 let mut value = Self {
2451 raw_attributes: raw_attributes(&start, &[], false)?,
2452 ..Self::default()
2453 };
2454 let mut boundary = 0usize;
2455 loop {
2456 buffer.clear();
2457 match reader.read_event_into(&mut buffer)? {
2458 Event::Start(child) => {
2459 let name = local_name(child.name().as_ref()).to_vec();
2460 let raw = capture_element(&mut reader, &child)?;
2461 value.capture_child(&name, raw, &mut boundary)?;
2462 }
2463 Event::Empty(child) => {
2464 let name = local_name(child.name().as_ref()).to_vec();
2465 let raw = capture_empty_element(&child)?;
2466 value.capture_child(&name, raw, &mut boundary)?;
2467 }
2468 Event::End(end)
2469 if matches_local_name(end.name().as_ref(), b"tcBdr") =>
2470 {
2471 return Ok(value);
2472 }
2473 Event::Eof => return Err(missing("closing a:tcBdr")),
2474 _ => {}
2475 }
2476 }
2477 }
2478 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcBdr") => {
2479 return Ok(Self {
2480 raw_attributes: raw_attributes(&start, &[], false)?,
2481 ..Self::default()
2482 });
2483 }
2484 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
2485 Event::Eof => return Err(missing("a:tcBdr")),
2486 _ => {}
2487 }
2488 buffer.clear();
2489 }
2490 }
2491
2492 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2493 let slot = table_border_slot(name);
2494 ensure_schema_order(slot, *boundary, "a:tcBdr")?;
2495 match name {
2496 b"left" => self.capture_border(&raw, slot, "left")?,
2497 b"right" => self.capture_border(&raw, slot, "right")?,
2498 b"top" => self.capture_border(&raw, slot, "top")?,
2499 b"bottom" => self.capture_border(&raw, slot, "bottom")?,
2500 b"lnL" => self.capture_border(&raw, slot, "lnL")?,
2501 b"lnR" => self.capture_border(&raw, slot, "lnR")?,
2502 b"lnT" => self.capture_border(&raw, slot, "lnT")?,
2503 b"lnB" => self.capture_border(&raw, slot, "lnB")?,
2504 b"insideH" => self.capture_border(&raw, slot, "insideH")?,
2505 b"insideV" => self.capture_border(&raw, slot, "insideV")?,
2506 b"tl2br" | b"tr2bl" | b"lnTlToBr" | b"lnBlToTr" => {
2507 self.unsupported.push("diagonal border".to_owned());
2508 self.raw_children.push(slot.unwrap_or(*boundary), raw);
2509 }
2510 _ => self.raw_children.push(slot.unwrap_or(*boundary), raw),
2511 }
2512 if let Some(slot) = slot {
2513 *boundary = (*boundary).max(slot + 1);
2514 }
2515 Ok(())
2516 }
2517
2518 fn capture_border(&mut self, raw: &[u8], slot: Option<usize>, name: &str) -> Result<()> {
2519 let line = if name.starts_with("ln") {
2520 Some(parse_named_line(raw)?)
2521 } else {
2522 parse_border_wrapper(raw)?
2523 };
2524 let Some(line) = line else {
2525 self.unsupported.push("border form".to_owned());
2526 self.raw_children.push(slot.unwrap_or(0), raw.to_vec());
2527 return Ok(());
2528 };
2529 let target = match name {
2530 "left" | "lnL" => &mut self.left,
2531 "right" | "lnR" => &mut self.right,
2532 "top" | "lnT" => &mut self.top,
2533 "bottom" | "lnB" => &mut self.bottom,
2534 "insideH" => &mut self.inside_horizontal,
2535 "insideV" => &mut self.inside_vertical,
2536 _ => unreachable!("table border name"),
2537 };
2538 set_once(target, line, "a:tcBdr edge")
2539 }
2540
2541 fn has_values(&self) -> bool {
2542 self.left.is_some()
2543 || self.right.is_some()
2544 || self.top.is_some()
2545 || self.bottom.is_some()
2546 || self.inside_horizontal.is_some()
2547 || self.inside_vertical.is_some()
2548 || !self.raw_attributes.is_empty()
2549 || !self.raw_children.is_empty()
2550 }
2551
2552 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2553 let mut start = BytesStart::new("a:tcBdr");
2554 push_attributes(&mut start, &self.raw_attributes);
2555 if !self.has_values() {
2556 writer.write_event(Event::Empty(start))?;
2557 return Ok(());
2558 }
2559 writer.write_event(Event::Start(start))?;
2560 let lines = [
2561 ("a:left", self.left.as_ref()),
2562 ("a:right", self.right.as_ref()),
2563 ("a:top", self.top.as_ref()),
2564 ("a:bottom", self.bottom.as_ref()),
2565 ("a:insideH", self.inside_horizontal.as_ref()),
2566 ("a:insideV", self.inside_vertical.as_ref()),
2567 ];
2568 for (slot, (tag, line)) in lines.into_iter().enumerate() {
2569 emit_raw(writer, self.raw_children.at(slot))?;
2570 write_optional_border_wrapper(writer, tag, line)?;
2571 }
2572 for slot in 6..=8 {
2573 emit_raw(writer, self.raw_children.at(slot))?;
2574 }
2575 writer.write_event(Event::End(BytesEnd::new("a:tcBdr")))?;
2576 Ok(())
2577 }
2578}
2579
2580impl CT_TableTextStyle {
2581 fn from_xml(xml: &[u8]) -> Result<Self> {
2582 let mut reader = Reader::from_reader(xml);
2583 let mut buffer = Vec::new();
2584 loop {
2585 match reader.read_event_into(&mut buffer)? {
2586 Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcTxStyle") => {
2587 let mut value = Self {
2588 bold: bool_attr(&start, b"b")?,
2589 italic: bool_attr(&start, b"i")?,
2590 raw_attributes: raw_attributes(&start, &[b"b", b"i"], false)?,
2591 ..Self::default()
2592 };
2593 let mut boundary = 0usize;
2594 loop {
2595 buffer.clear();
2596 match reader.read_event_into(&mut buffer)? {
2597 Event::Start(child) => {
2598 let name = local_name(child.name().as_ref()).to_vec();
2599 let raw = capture_element(&mut reader, &child)?;
2600 value.capture_child(&name, raw, &mut boundary)?;
2601 }
2602 Event::Empty(child) => {
2603 let name = local_name(child.name().as_ref()).to_vec();
2604 let raw = capture_empty_element(&child)?;
2605 value.capture_child(&name, raw, &mut boundary)?;
2606 }
2607 Event::End(end)
2608 if matches_local_name(end.name().as_ref(), b"tcTxStyle") =>
2609 {
2610 return Ok(value);
2611 }
2612 Event::Eof => return Err(missing("closing a:tcTxStyle")),
2613 _ => {}
2614 }
2615 }
2616 }
2617 Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcTxStyle") => {
2618 return Ok(Self {
2619 bold: bool_attr(&start, b"b")?,
2620 italic: bool_attr(&start, b"i")?,
2621 raw_attributes: raw_attributes(&start, &[b"b", b"i"], false)?,
2622 ..Self::default()
2623 });
2624 }
2625 Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
2626 Event::Eof => return Err(missing("a:tcTxStyle")),
2627 _ => {}
2628 }
2629 buffer.clear();
2630 }
2631 }
2632
2633 fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2634 match name {
2635 b"fontRef" if self.font_reference.is_none() => {
2636 self.font_reference = Some(parse_style_reference(&raw)?);
2637 *boundary = 1;
2638 }
2639 name if is_color(name) && self.color.is_none() => {
2640 self.color = Some(parse_color(&raw)?);
2641 *boundary = 2;
2642 }
2643 b"fontRef" => {
2644 return Err(OxmlError::InvalidValue(
2645 "duplicate a:tcTxStyle/a:fontRef".to_owned(),
2646 ));
2647 }
2648 name if is_color(name) => {
2649 return Err(OxmlError::InvalidValue(format!(
2650 "duplicate a:tcTxStyle colour {}",
2651 String::from_utf8_lossy(name)
2652 )));
2653 }
2654 _ => self.raw_children.push(*boundary, raw),
2655 }
2656 Ok(())
2657 }
2658
2659 fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2660 let mut start = BytesStart::new("a:tcTxStyle");
2661 push_optional_bool(&mut start, "b", self.bold);
2662 push_optional_bool(&mut start, "i", self.italic);
2663 push_attributes(&mut start, &self.raw_attributes);
2664 if self.font_reference.is_none() && self.color.is_none() && self.raw_children.is_empty() {
2665 writer.write_event(Event::Empty(start))?;
2666 return Ok(());
2667 }
2668 writer.write_event(Event::Start(start))?;
2669 emit_raw(writer, self.raw_children.at(0))?;
2670 if let Some(reference) = &self.font_reference {
2671 reference.write_xml(writer).map_err(drawing_error)?;
2672 }
2673 emit_raw(writer, self.raw_children.at(1))?;
2674 if let Some(color) = &self.color {
2675 color.to_xml(writer).map_err(drawing_error)?;
2676 }
2677 emit_raw(writer, self.raw_children.at(2))?;
2678 writer.write_event(Event::End(BytesEnd::new("a:tcTxStyle")))?;
2679 Ok(())
2680 }
2681}
2682
2683fn builtin_table_styles() -> [(&'static str, BuiltinTableFamily, u8); 74] {
2688 use BuiltinTableFamily::*;
2689 [
2690 ("{2D5ABB26-0587-4C30-8999-92F81FD0307C}", NoGrid, 0),
2691 ("{3C2FFA5D-87B4-456A-9821-1D502468CF0F}", Themed1, 1),
2692 ("{284E427A-3D55-4303-BF80-6455036E1DE7}", Themed1, 2),
2693 ("{69C7853C-536D-4A76-A0AE-DD22124D55A5}", Themed1, 3),
2694 ("{775DCB02-9BB8-47FD-8907-85C794F793BA}", Themed1, 4),
2695 ("{35758FB7-9AC5-4552-8A53-C91805E547FA}", Themed1, 5),
2696 ("{08FB837D-C827-4EFA-A057-4D05807E0F7C}", Themed1, 6),
2697 ("{5940675A-B579-460E-94D1-54222C63F5DA}", TableGrid, 0),
2698 ("{D113A9D2-9D6B-4929-AA2D-F23B5EE8CBE7}", Themed2, 1),
2699 ("{18603FDC-E32A-4AB5-989C-0864C3EAD2B8}", Themed2, 2),
2700 ("{306799F8-075E-4A3A-A7F6-7FBC6576F1A4}", Themed2, 3),
2701 ("{E269D01E-BC32-4049-B463-5C60D7B0CCD2}", Themed2, 4),
2702 ("{327F97BB-C833-4FB7-BDE5-3F7075034690}", Themed2, 5),
2703 ("{638B1855-1B75-4FBE-930C-398BA8C253C6}", Themed2, 6),
2704 ("{9D7B26C5-4107-4FEC-AEDC-1716B250A1EF}", Light1, 0),
2705 ("{3B4B98B0-60AC-42C2-AFA5-B58CD77FA1E5}", Light1, 1),
2706 ("{0E3FDE45-AF77-4B5C-9715-49D594BDF05E}", Light1, 2),
2707 ("{C083E6E3-FA7D-4D7B-A595-EF9225AFEA82}", Light1, 3),
2708 ("{D27102A9-8310-4765-A935-A1911B00CA55}", Light1, 4),
2709 ("{5FD0F851-EC5A-4D38-B0AD-8093EC10F338}", Light1, 5),
2710 ("{68D230F3-CF80-4859-8CE7-A43EE81993B5}", Light1, 6),
2711 ("{7E9639D4-E3E2-4D34-9284-5A2195B3D0D7}", Light2, 0),
2712 ("{69012ECD-51FC-41F1-AA8D-1B2483CD663E}", Light2, 1),
2713 ("{72833802-FEF1-4C79-8D5D-14CF1EAF98D9}", Light2, 2),
2714 ("{F2DE63D5-997A-4646-A377-4702673A728D}", Light2, 3),
2715 ("{17292A2E-F333-43FB-9621-5CBBE7FDCDCB}", Light2, 4),
2716 ("{5A111915-BE36-4E01-A7E5-04B1672EAD32}", Light2, 5),
2717 ("{912C8C85-51F0-491E-9774-3900AFEF0FD7}", Light2, 6),
2718 ("{616DA210-FB5B-4158-B5E0-FEB733F419BA}", Light3, 0),
2719 ("{BC89EF96-8CEA-46FF-86C4-4CE0E7609802}", Light3, 1),
2720 ("{5DA37D80-6434-44D0-A028-1B22A696006F}", Light3, 2),
2721 ("{8799B23B-EC83-4686-B30A-512413B5E67A}", Light3, 3),
2722 ("{ED083AE6-46FA-4A59-8FB0-9F97EB10719F}", Light3, 4),
2723 ("{BDBED569-4797-4DF1-A0F4-6AAB3CD982D8}", Light3, 5),
2724 ("{E8B1032C-EA38-4F05-BA0D-38AFFFC7BED3}", Light3, 6),
2725 ("{793D81CF-94F2-401A-BA57-92F5A7B2D0C5}", Medium1, 0),
2726 ("{B301B821-A1FF-4177-AEE7-76D212191A09}", Medium1, 1),
2727 ("{9DCAF9ED-07DC-4A11-8D7F-57B35C25682E}", Medium1, 2),
2728 ("{1FECB4D8-DB02-4DC6-A0A2-4F2EBAE1DC90}", Medium1, 3),
2729 ("{1E171933-4619-4E11-9A3F-F7608DF75F80}", Medium1, 4),
2730 ("{FABFCF23-3B69-468F-B69F-88F6DE6A72F2}", Medium1, 5),
2731 ("{10A1B5D5-9B99-4C35-A422-299274C87663}", Medium1, 6),
2732 ("{073A0DAA-6AF3-43AB-8588-CEC1D06C72B9}", Medium2, 0),
2733 ("{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}", Medium2, 1),
2734 ("{21E4AEA4-8DFA-4A89-87EB-49C32662AFE0}", Medium2, 2),
2735 ("{F5AB1C69-6EDB-4FF4-983F-18BD219EF322}", Medium2, 3),
2736 ("{00A15C55-8517-42AA-B614-E9B94910E393}", Medium2, 4),
2737 ("{7DF18680-E054-41AD-8BC1-D1AEF772440D}", Medium2, 5),
2738 ("{93296810-A885-4BE3-A3E7-6D5BEEA58F35}", Medium2, 6),
2739 ("{8EC20E35-A176-4012-BC5E-935CFFF8708E}", Medium3, 0),
2740 ("{6E25E649-3F16-4E02-A733-19D2CDBF48F0}", Medium3, 1),
2741 ("{85BE263C-DBD7-4A20-BB59-AAB30ACAA65A}", Medium3, 2),
2742 ("{EB344D84-9AFB-497E-A393-DC336BA19D2E}", Medium3, 3),
2743 ("{EB9631B5-78F2-41C9-869B-9F39066F8104}", Medium3, 4),
2744 ("{74C1A8A3-306A-4EB7-A6B1-4F7E0EB9C5D6}", Medium3, 5),
2745 ("{2A488322-F2BA-4B5B-9748-0D474271808F}", Medium3, 6),
2746 ("{D7AC3CCA-C797-4891-BE02-D94E43425B78}", Medium4, 0),
2747 ("{69CF1AB2-1976-4502-BF36-3FF5EA218861}", Medium4, 1),
2748 ("{8A107856-5554-42FB-B03E-39F5DBC370BA}", Medium4, 2),
2749 ("{0505E3EF-67EA-436B-97B2-0124C06EBD24}", Medium4, 3),
2750 ("{C4B1156A-380E-4F78-BDF5-A606A8083BF9}", Medium4, 4),
2751 ("{22838BEF-8BB2-4498-84A7-C5851F593DF1}", Medium4, 5),
2752 ("{16D9F66E-5EB9-4882-86FB-DCBF35E3C3E4}", Medium4, 6),
2753 ("{E8034E78-7F5D-4C2E-B375-FC64B27BC917}", Dark1, 0),
2754 ("{125E5076-3810-47DD-B79F-674D7AD40C01}", Dark1, 1),
2755 ("{37CE84F3-28C3-443E-9E96-99CF82512B78}", Dark1, 2),
2756 ("{D03447BB-5D67-496B-8E87-E561075AD55C}", Dark1, 3),
2757 ("{E929F9F4-4A8F-4326-A1B4-22849713DDAB}", Dark1, 4),
2758 ("{8FD4443E-F989-4FC4-A0C8-D5A2AF1F390B}", Dark1, 5),
2759 ("{AF606853-7671-496A-8E4F-DF71F8EC918B}", Dark1, 6),
2760 ("{5202B0CA-FC54-4496-8BCA-5EF66A818D29}", Dark2, 0),
2761 ("{0660B408-B3CF-4A94-85FC-2B1E0A45F4A2}", Dark2, 1),
2762 ("{91EBBBCC-DAD2-459C-BE2E-F6DE35CF9A28}", Dark2, 3),
2763 ("{46F890A9-2807-4EBB-B81D-B2AA78EC7F39}", Dark2, 5),
2764 ]
2765}
2766
2767#[derive(Clone, Copy)]
2769enum BuiltinTableFamily {
2770 NoGrid,
2771 TableGrid,
2772 Themed1,
2773 Themed2,
2774 Light1,
2775 Light2,
2776 Light3,
2777 Medium1,
2778 Medium2,
2779 Medium3,
2780 Medium4,
2781 Dark1,
2782 Dark2,
2783}
2784
2785impl BuiltinTableFamily {
2786 fn style_name(self, accent: u8) -> String {
2787 let family = match self {
2788 Self::NoGrid => "No Style, No Grid",
2789 Self::TableGrid => "No Style, Table Grid",
2790 Self::Themed1 => "Themed Style 1",
2791 Self::Themed2 => "Themed Style 2",
2792 Self::Light1 => "Light Style 1",
2793 Self::Light2 => "Light Style 2",
2794 Self::Light3 => "Light Style 3",
2795 Self::Medium1 => "Medium Style 1",
2796 Self::Medium2 => "Medium Style 2",
2797 Self::Medium3 => "Medium Style 3",
2798 Self::Medium4 => "Medium Style 4",
2799 Self::Dark1 => "Dark Style 1",
2800 Self::Dark2 => "Dark Style 2",
2801 };
2802 match (self, accent) {
2803 (_, 0) => family.to_owned(),
2804 (Self::Dark2, _) => format!("{family} - Accent {accent}/Accent {}", accent + 1),
2805 _ => format!("{family} - Accent {accent}"),
2806 }
2807 }
2808
2809 fn regions(self, accent: u8) -> String {
2811 let c = match (self, accent) {
2813 (Self::Light1 | Self::Light2 | Self::Light3, 0) => "tx1".to_owned(),
2814 (_, 0) => "dk1".to_owned(),
2815 _ => format!("accent{accent}"),
2816 };
2817 match self {
2818 Self::NoGrid => part("wholeTbl", &text("tx1"), &all_edges(NO_LINE), NO_FILL),
2819 Self::TableGrid => {
2820 let grid = line(1, &clr("tx1"));
2821 part("wholeTbl", &text("tx1"), &all_edges(&grid), NO_FILL)
2822 }
2823 Self::Themed1 => {
2824 let (thin, thick) = (line_ref(1, &clr(&c)), line_ref(2, &clr(&c)));
2825 let divider = line_ref(2, &clr("lt1"));
2826 let band = solid(&alpha(&c, 40));
2827 let last_col = edges(&thick, &thin, &thin, &thin, &thin, NO_LINE);
2828 let first_col = edges(&thin, &thick, &thin, &thin, &thin, NO_LINE);
2829 let last_row = edges(&thin, &thin, &thick, &thick, NO_LINE, NO_LINE);
2830 let first_row = edges(&thin, &thin, &thin, ÷r, NO_LINE, NO_LINE);
2831 [
2832 table_background(2, 1, &clr(&c)),
2833 part("wholeTbl", &text("dk1"), &all_edges(&thin), NO_FILL),
2834 part("band1H", "", &[], &band),
2835 part("band2H", "", &[], ""),
2836 part("band1V", "", &[("top", &thin), ("bottom", &thin)], &band),
2837 part("band2V", "", &[], ""),
2838 part("lastCol", BOLD, &last_col, ""),
2839 part("firstCol", BOLD, &first_col, ""),
2840 part("lastRow", BOLD, &last_row, NO_FILL),
2841 part("firstRow", &bold("lt1"), &first_row, &solid(&clr(&c))),
2842 ]
2843 .concat()
2844 }
2845 Self::Themed2 => {
2846 let edge = line_ref(1, &tint(&c, 50));
2847 let divider = line_ref(2, &clr("lt1"));
2848 let heading = line_ref(3, &clr("lt1"));
2849 let band = solid(&alpha("lt1", 20));
2850 let whole = edges(&edge, &edge, &edge, &edge, NO_LINE, NO_LINE);
2851 [
2852 table_background(3, 3, &clr(&c)),
2853 part("wholeTbl", &text("lt1"), &whole, NO_FILL),
2854 part("band1H", "", &[], &band),
2855 part("band1V", "", &[], &band),
2856 part("lastCol", BOLD, &[("left", ÷r)], ""),
2857 part("firstCol", BOLD, &[("right", ÷r)], ""),
2858 part("lastRow", BOLD, &[("top", ÷r)], NO_FILL),
2859 part("seCell", "", &[("left", NO_LINE), ("top", NO_LINE)], ""),
2860 part("swCell", "", &[("right", NO_LINE), ("top", NO_LINE)], ""),
2861 part("firstRow", BOLD, &[("bottom", &heading)], NO_FILL),
2862 part("neCell", "", &[("bottom", NO_LINE)], ""),
2863 ]
2864 .concat()
2865 }
2866 Self::Light1 => {
2867 let rule = line(1, &clr(&c));
2868 let band = solid(&alpha(&c, 20));
2869 let whole = edges(NO_LINE, NO_LINE, &rule, &rule, NO_LINE, NO_LINE);
2870 [
2871 part("wholeTbl", &text("tx1"), &whole, NO_FILL),
2872 part("band1H", "", &[], &band),
2873 part("band2H", "", &[], ""),
2874 part("band1V", "", &[], &band),
2875 part("lastCol", BOLD, &[], ""),
2876 part("firstCol", BOLD, &[], ""),
2877 part("lastRow", BOLD, &[("top", &rule)], NO_FILL),
2878 part("firstRow", BOLD, &[("bottom", &rule)], NO_FILL),
2879 ]
2880 .concat()
2881 }
2882 Self::Light2 => {
2883 let edge = line_ref(1, &clr(&c));
2884 let whole = edges(&edge, &edge, &edge, &edge, NO_LINE, NO_LINE);
2885 let total = double_line(4, &clr(&c));
2886 [
2887 part("wholeTbl", &text("tx1"), &whole, NO_FILL),
2888 part("band1H", "", &[("top", &edge), ("bottom", &edge)], ""),
2889 part("band1V", "", &[("left", &edge), ("right", &edge)], ""),
2890 part("band2V", "", &[("left", &edge), ("right", &edge)], ""),
2891 part("lastCol", BOLD, &[], ""),
2892 part("firstCol", BOLD, &[], ""),
2893 part("lastRow", BOLD, &[("top", &total)], ""),
2894 part("firstRow", &bold("bg1"), &[], &fill_ref(1, &clr(&c))),
2895 ]
2896 .concat()
2897 }
2898 Self::Light3 => {
2899 let grid = line(1, &clr(&c));
2900 let band = solid(&alpha(&c, 20));
2901 [
2902 part("wholeTbl", &text("tx1"), &all_edges(&grid), NO_FILL),
2903 part("band1H", "", &[], &band),
2904 part("band1V", "", &[], &band),
2905 part("lastCol", BOLD, &[], ""),
2906 part("firstCol", BOLD, &[], ""),
2907 part(
2908 "lastRow",
2909 BOLD,
2910 &[("top", &double_line(4, &clr(&c)))],
2911 NO_FILL,
2912 ),
2913 part("firstRow", BOLD, &[("bottom", &line(2, &clr(&c)))], NO_FILL),
2914 ]
2915 .concat()
2916 }
2917 Self::Medium1 => {
2918 let rule = line(1, &clr(&c));
2919 let whole = edges(&rule, &rule, &rule, &rule, &rule, NO_LINE);
2920 let band = solid(&tint(&c, 20));
2921 let white = solid(&clr("lt1"));
2922 [
2923 part("wholeTbl", &text("dk1"), &whole, &white),
2924 part("band1H", "", &[], &band),
2925 part("band1V", "", &[], &band),
2926 part("lastCol", BOLD, &[], ""),
2927 part("firstCol", BOLD, &[], ""),
2928 part(
2929 "lastRow",
2930 BOLD,
2931 &[("top", &double_line(4, &clr(&c)))],
2932 &white,
2933 ),
2934 part("firstRow", &bold("lt1"), &[], &solid(&clr(&c))),
2935 ]
2936 .concat()
2937 }
2938 Self::Medium2 => {
2939 let grid = line(1, &clr("lt1"));
2940 let divider = line(3, &clr("lt1"));
2941 let body = solid(&tint(&c, 20));
2942 let band = solid(&tint(&c, 40));
2943 let heading = solid(&clr(&c));
2944 [
2945 part("wholeTbl", &text("dk1"), &all_edges(&grid), &body),
2946 part("band1H", "", &[], &band),
2947 part("band2H", "", &[], ""),
2948 part("band1V", "", &[], &band),
2949 part("band2V", "", &[], ""),
2950 part("lastCol", &bold("lt1"), &[], &heading),
2951 part("firstCol", &bold("lt1"), &[], &heading),
2952 part("lastRow", &bold("lt1"), &[("top", ÷r)], &heading),
2953 part("firstRow", &bold("lt1"), &[("bottom", ÷r)], &heading),
2954 ]
2955 .concat()
2956 }
2957 Self::Medium3 => {
2958 let rule = line(2, &clr("dk1"));
2960 let whole = edges(NO_LINE, NO_LINE, &rule, &rule, NO_LINE, NO_LINE);
2961 let band = solid(&tint("dk1", 20));
2962 let white = solid(&clr("lt1"));
2963 let heading = solid(&clr(&c));
2964 [
2965 part("wholeTbl", &text("dk1"), &whole, &white),
2966 part("band1H", "", &[], &band),
2967 part("band1V", "", &[], &band),
2968 part("lastCol", &bold("lt1"), &[], &heading),
2969 part("firstCol", &bold("lt1"), &[], &heading),
2970 part(
2971 "lastRow",
2972 BOLD,
2973 &[("top", &double_line(4, &clr("dk1")))],
2974 &white,
2975 ),
2976 part("seCell", &bold("dk1"), &[], ""),
2977 part("swCell", &bold("dk1"), &[], ""),
2978 part("firstRow", &bold("lt1"), &[("bottom", &rule)], &heading),
2979 ]
2980 .concat()
2981 }
2982 Self::Medium4 => {
2983 let grid = line(1, &clr(&c));
2984 let body = solid(&tint(&c, 20));
2985 let band = solid(&tint(&c, 40));
2986 [
2987 part("wholeTbl", &text("dk1"), &all_edges(&grid), &body),
2988 part("band1H", "", &[], &band),
2989 part("band1V", "", &[], &band),
2990 part("lastCol", BOLD, &[], ""),
2991 part("firstCol", BOLD, &[], ""),
2992 part("lastRow", BOLD, &[("top", &line(2, &clr(&c)))], &body),
2993 part("firstRow", BOLD, &[], &body),
2994 ]
2995 .concat()
2996 }
2997 Self::Dark1 => {
2998 let plain = accent == 0;
3000 let body = solid(&if plain { tint("dk1", 20) } else { clr(&c) });
3001 let band = solid(&if plain {
3002 tint("dk1", 40)
3003 } else {
3004 shade(&c, 60)
3005 });
3006 let edge = solid(&if plain {
3007 tint("dk1", 60)
3008 } else {
3009 shade(&c, 60)
3010 });
3011 let total = solid(&if plain {
3012 tint("dk1", 60)
3013 } else {
3014 shade(&c, 40)
3015 });
3016 let divider = line(2, &clr("lt1"));
3017 [
3018 part("wholeTbl", &text("lt1"), &all_edges(NO_LINE), &body),
3019 part("band1H", "", &[], &band),
3020 part("band1V", "", &[], &band),
3021 part("lastCol", BOLD, &[("left", ÷r)], &edge),
3022 part("firstCol", BOLD, &[("right", ÷r)], &edge),
3023 part("lastRow", BOLD, &[("top", ÷r)], &total),
3024 part("seCell", "", &[("left", NO_LINE)], ""),
3025 part("swCell", "", &[("right", NO_LINE)], ""),
3026 part(
3027 "firstRow",
3028 BOLD,
3029 &[("bottom", ÷r)],
3030 &solid(&clr("dk1")),
3031 ),
3032 part("neCell", "", &[("left", NO_LINE)], ""),
3033 part("nwCell", "", &[("right", NO_LINE)], ""),
3034 ]
3035 .concat()
3036 }
3037 Self::Dark2 => {
3038 let heading = match accent {
3040 0 => clr("dk1"),
3041 _ => clr(&format!("accent{}", accent + 1)),
3042 };
3043 let body = solid(&tint(&c, 20));
3044 let band = solid(&tint(&c, 40));
3045 [
3046 part("wholeTbl", &text("dk1"), &all_edges(NO_LINE), &body),
3047 part("band1H", "", &[], &band),
3048 part("band1V", "", &[], &band),
3049 part("lastCol", BOLD, &[], ""),
3050 part("firstCol", BOLD, &[], ""),
3051 part(
3052 "lastRow",
3053 BOLD,
3054 &[("top", &double_line(4, &clr("dk1")))],
3055 &body,
3056 ),
3057 part("firstRow", &bold("lt1"), &[], &solid(&heading)),
3058 ]
3059 .concat()
3060 }
3061 }
3062 }
3063}
3064
3065const NO_LINE: &str = "<a:ln><a:noFill/></a:ln>";
3066const NO_FILL: &str = "<a:fill><a:noFill/></a:fill>";
3067const BOLD: &str = r#"<a:tcTxStyle b="on"/>"#;
3068
3069fn part(tag: &str, text: &str, edges: &[(&str, &str)], fill: &str) -> String {
3072 let borders = edges
3073 .iter()
3074 .map(|(edge, line)| format!("<a:{edge}>{line}</a:{edge}>"))
3075 .collect::<String>();
3076 let borders = if borders.is_empty() {
3077 "<a:tcBdr/>".to_owned()
3078 } else {
3079 format!("<a:tcBdr>{borders}</a:tcBdr>")
3080 };
3081 format!("<a:{tag}>{text}<a:tcStyle>{borders}{fill}</a:tcStyle></a:{tag}>")
3082}
3083
3084fn edges<'a>(
3086 left: &'a str,
3087 right: &'a str,
3088 top: &'a str,
3089 bottom: &'a str,
3090 inside_h: &'a str,
3091 inside_v: &'a str,
3092) -> [(&'static str, &'a str); 6] {
3093 [
3094 ("left", left),
3095 ("right", right),
3096 ("top", top),
3097 ("bottom", bottom),
3098 ("insideH", inside_h),
3099 ("insideV", inside_v),
3100 ]
3101}
3102
3103fn all_edges(line: &str) -> [(&'static str, &str); 6] {
3104 edges(line, line, line, line, line, line)
3105}
3106
3107fn text(color: &str) -> String {
3109 format!(r#"<a:tcTxStyle>{}</a:tcTxStyle>"#, minor_font(color))
3110}
3111
3112fn bold(color: &str) -> String {
3113 format!(r#"<a:tcTxStyle b="on">{}</a:tcTxStyle>"#, minor_font(color))
3114}
3115
3116fn minor_font(color: &str) -> String {
3117 let font = r#"<a:fontRef idx="minor"><a:scrgbClr r="0" g="0" b="0"/></a:fontRef>"#;
3118 format!("{font}{}", clr(color))
3119}
3120
3121fn clr(color: &str) -> String {
3122 format!(r#"<a:schemeClr val="{color}"/>"#)
3123}
3124
3125fn tint(color: &str, percent: u32) -> String {
3126 modified(color, "tint", percent)
3127}
3128
3129fn shade(color: &str, percent: u32) -> String {
3130 modified(color, "shade", percent)
3131}
3132
3133fn alpha(color: &str, percent: u32) -> String {
3134 modified(color, "alpha", percent)
3135}
3136
3137fn modified(color: &str, modifier: &str, percent: u32) -> String {
3138 let value = percent * 1_000;
3139 format!(r#"<a:schemeClr val="{color}"><a:{modifier} val="{value}"/></a:schemeClr>"#)
3140}
3141
3142fn line(points: u32, color: &str) -> String {
3144 let width = points * 12_700;
3145 format!(r#"<a:ln w="{width}" cmpd="sng"><a:solidFill>{color}</a:solidFill></a:ln>"#)
3146}
3147
3148fn double_line(points: u32, color: &str) -> String {
3149 let width = points * 12_700;
3150 format!(r#"<a:ln w="{width}" cmpd="dbl"><a:solidFill>{color}</a:solidFill></a:ln>"#)
3151}
3152
3153fn line_ref(index: u8, color: &str) -> String {
3154 format!(r#"<a:lnRef idx="{index}">{color}</a:lnRef>"#)
3155}
3156
3157fn solid(color: &str) -> String {
3158 format!("<a:fill><a:solidFill>{color}</a:solidFill></a:fill>")
3159}
3160
3161fn fill_ref(index: u8, color: &str) -> String {
3162 format!(r#"<a:fillRef idx="{index}">{color}</a:fillRef>"#)
3163}
3164
3165fn table_background(fill: u8, effect: u8, color: &str) -> String {
3167 let fill = format!(r#"<a:fillRef idx="{fill}">{color}</a:fillRef>"#);
3168 let effect = format!(r#"<a:effectRef idx="{effect}">{color}</a:effectRef>"#);
3169 format!("<a:tblBg>{fill}{effect}</a:tblBg>")
3170}
3171
3172fn table_style_slot(name: &[u8]) -> Option<usize> {
3173 match name {
3174 b"tblBg" => Some(0),
3175 b"wholeTbl" => Some(1),
3176 b"band1H" => Some(2),
3177 b"band2H" => Some(3),
3178 b"band1V" => Some(4),
3179 b"band2V" => Some(5),
3180 b"lastCol" => Some(6),
3181 b"firstCol" => Some(7),
3182 b"lastRow" => Some(8),
3183 b"seCell" => Some(9),
3184 b"swCell" => Some(10),
3185 b"firstRow" => Some(11),
3186 b"neCell" => Some(12),
3187 b"nwCell" => Some(13),
3188 _ => None,
3189 }
3190}
3191
3192fn table_border_slot(name: &[u8]) -> Option<usize> {
3193 match name {
3194 b"left" | b"lnL" => Some(0),
3195 b"right" | b"lnR" => Some(1),
3196 b"top" | b"lnT" => Some(2),
3197 b"bottom" | b"lnB" => Some(3),
3198 b"insideH" => Some(4),
3199 b"insideV" => Some(5),
3200 b"tl2br" | b"lnTlToBr" => Some(6),
3201 b"tr2bl" | b"lnBlToTr" => Some(7),
3202 _ => None,
3203 }
3204}
3205
3206fn cell_property_slot(name: &[u8]) -> Option<usize> {
3207 match name {
3208 b"lnL" => Some(0),
3209 b"lnR" => Some(1),
3210 b"lnT" => Some(2),
3211 b"lnB" => Some(3),
3212 b"lnTlToBr" => Some(4),
3213 b"lnBlToTr" => Some(5),
3214 b"cell3D" | b"effectLst" | b"effectDag" => Some(6),
3215 name if is_fill(name) => Some(7),
3216 b"headers" => Some(8),
3217 b"extLst" => Some(9),
3218 _ => None,
3219 }
3220}
3221
3222fn ensure_schema_order(slot: Option<usize>, boundary: usize, parent: &str) -> Result<()> {
3223 if slot.is_some_and(|slot| slot < boundary) {
3224 return Err(OxmlError::InvalidValue(format!(
3225 "{parent} children violate schema order"
3226 )));
3227 }
3228 Ok(())
3229}
3230
3231fn set_once<T>(target: &mut Option<T>, value: T, name: &str) -> Result<()> {
3232 if target.replace(value).is_some() {
3233 return Err(OxmlError::InvalidValue(format!("duplicate {name}")));
3234 }
3235 Ok(())
3236}
3237
3238fn is_fill(name: &[u8]) -> bool {
3239 matches!(
3240 name,
3241 b"noFill" | b"solidFill" | b"gradFill" | b"pattFill" | b"blipFill"
3242 )
3243}
3244
3245fn is_color(name: &[u8]) -> bool {
3246 matches!(
3247 name,
3248 b"scrgbClr" | b"srgbClr" | b"hslClr" | b"sysClr" | b"schemeClr" | b"prstClr"
3249 )
3250}
3251
3252fn parse_fill(xml: &[u8]) -> Result<Fill> {
3253 Fill::from_xml(xml).map_err(drawing_error)
3254}
3255
3256fn parse_fill_wrapper(xml: &[u8]) -> Result<Option<Fill>> {
3257 let mut reader = Reader::from_reader(xml);
3258 let mut buffer = Vec::new();
3259 let mut root_seen = false;
3260 let mut fill = None;
3261 loop {
3262 match reader.read_event_into(&mut buffer)? {
3263 Event::Start(start) if !root_seen => {
3264 root_seen = true;
3265 if start.attributes().next().is_some() {
3266 return Ok(None);
3267 }
3268 }
3269 Event::Start(child) if root_seen && is_fill(local_name(child.name().as_ref())) => {
3270 if fill.is_some() {
3271 return Ok(None);
3272 }
3273 fill = Some(parse_fill(&capture_element(&mut reader, &child)?)?);
3274 }
3275 Event::Empty(child) if root_seen && is_fill(local_name(child.name().as_ref())) => {
3276 if fill.is_some() {
3277 return Ok(None);
3278 }
3279 fill = Some(parse_fill(&capture_empty_element(&child)?)?);
3280 }
3281 Event::Start(child) if root_seen => {
3282 let _ = capture_element(&mut reader, &child)?;
3283 return Ok(None);
3284 }
3285 Event::Empty(_) if root_seen => return Ok(None),
3286 Event::End(_) if root_seen => return Ok(fill),
3287 Event::Eof => return Err(missing("closing a:fill")),
3288 Event::Text(text) if !xml_text_is_whitespace(&text) => {
3289 return Ok(None);
3290 }
3291 _ => {}
3292 }
3293 buffer.clear();
3294 }
3295}
3296
3297fn parse_style_reference(xml: &[u8]) -> Result<StyleReference> {
3298 StyleReference::from_xml(xml).map_err(drawing_error)
3299}
3300
3301fn parse_color(xml: &[u8]) -> Result<crate::color::ColorChoice> {
3302 let mut reader = Reader::from_reader(xml);
3303 let mut buffer = Vec::new();
3304 loop {
3305 match reader.read_event_into(&mut buffer)? {
3306 Event::Start(start) => {
3307 return crate::color::ColorChoice::from_xml(&mut reader, &start)
3308 .map_err(drawing_error);
3309 }
3310 Event::Empty(start) => {
3311 return crate::color::ColorChoice::from_empty_xml(&start).map_err(drawing_error);
3312 }
3313 Event::Eof => return Err(missing("DrawingML colour")),
3314 _ => {}
3315 }
3316 buffer.clear();
3317 }
3318}
3319
3320fn parse_named_line(xml: &[u8]) -> Result<CT_LineProperties> {
3321 let text = std::str::from_utf8(xml)?;
3322 let name_start = text.find('<').ok_or_else(|| missing("table border"))? + 1;
3323 let name_end = text[name_start..]
3324 .find(|character: char| character.is_whitespace() || matches!(character, '>' | '/'))
3325 .map(|offset| name_start + offset)
3326 .ok_or_else(|| missing("table border name"))?;
3327 let original_name = &text[name_start..name_end];
3328 let mut renamed = String::with_capacity(text.len() + 8);
3329 renamed.push_str(&text[..name_start]);
3330 renamed.push_str("a:ln");
3331 renamed.push_str(&text[name_end..]);
3332 let closing = format!("</{original_name}>");
3333 if renamed.ends_with(&closing) {
3334 renamed.truncate(renamed.len() - closing.len());
3335 renamed.push_str("</a:ln>");
3336 }
3337 CT_LineProperties::from_xml(renamed.as_bytes()).map_err(drawing_error)
3338}
3339
3340fn parse_border_wrapper(xml: &[u8]) -> Result<Option<CT_LineProperties>> {
3341 let mut reader = Reader::from_reader(xml);
3342 let mut buffer = Vec::new();
3343 let mut root_seen = false;
3344 let mut line = None;
3345 loop {
3346 match reader.read_event_into(&mut buffer)? {
3347 Event::Start(start) if !root_seen => {
3348 root_seen = true;
3349 if start.attributes().next().is_some() {
3350 return Ok(None);
3351 }
3352 }
3353 Event::Start(child)
3354 if root_seen && matches_local_name(child.name().as_ref(), b"ln") =>
3355 {
3356 if line.is_some() {
3357 return Ok(None);
3358 }
3359 line = Some(parse_named_line(&capture_element(&mut reader, &child)?)?);
3360 }
3361 Event::Empty(child)
3362 if root_seen && matches_local_name(child.name().as_ref(), b"ln") =>
3363 {
3364 if line.is_some() {
3365 return Ok(None);
3366 }
3367 line = Some(parse_named_line(&capture_empty_element(&child)?)?);
3368 }
3369 Event::Start(child) if root_seen => {
3370 let _ = capture_element(&mut reader, &child)?;
3371 return Ok(None);
3372 }
3373 Event::Empty(_) if root_seen => return Ok(None),
3374 Event::End(_) if root_seen => return Ok(line),
3375 Event::Eof => return Err(missing("closing table border wrapper")),
3376 Event::Text(text) if !xml_text_is_whitespace(&text) => {
3377 return Ok(None);
3378 }
3379 _ => {}
3380 }
3381 buffer.clear();
3382 }
3383}
3384
3385fn write_optional_named_line<W: Write>(
3386 writer: &mut Writer<W>,
3387 tag: &str,
3388 line: Option<&CT_LineProperties>,
3389) -> Result<()> {
3390 let Some(line) = line else {
3391 return Ok(());
3392 };
3393 let xml = line.to_xml().map_err(drawing_error)?;
3394 let text = std::str::from_utf8(&xml)?;
3395 let mut renamed = text.replacen("<a:ln", &format!("<{tag}"), 1);
3396 if renamed.ends_with("</a:ln>") {
3397 renamed.truncate(renamed.len() - "</a:ln>".len());
3398 renamed.push_str("</");
3399 renamed.push_str(tag);
3400 renamed.push('>');
3401 }
3402 writer.get_mut().write_all(renamed.as_bytes())?;
3403 Ok(())
3404}
3405
3406fn write_optional_border_wrapper<W: Write>(
3407 writer: &mut Writer<W>,
3408 tag: &str,
3409 line: Option<&CT_LineProperties>,
3410) -> Result<()> {
3411 let Some(line) = line else {
3412 return Ok(());
3413 };
3414 writer.write_event(Event::Start(BytesStart::new(tag)))?;
3415 line.write_xml(writer).map_err(drawing_error)?;
3416 writer.write_event(Event::End(BytesEnd::new(tag)))?;
3417 Ok(())
3418}
3419
3420fn drawing_error(error: impl std::fmt::Display) -> OxmlError {
3421 OxmlError::InvalidValue(error.to_string())
3422}
3423
3424fn xml_text_is_whitespace(text: &BytesText<'_>) -> bool {
3425 let bytes: &[u8] = text.as_ref();
3426 bytes.iter().all(u8::is_ascii_whitespace)
3427}
3428
3429fn required_string(start: &BytesStart<'_>, name: &[u8]) -> Result<String> {
3430 decoded_attr(start, name)?.ok_or_else(|| {
3431 missing(&format!(
3432 "{}@{}",
3433 String::from_utf8_lossy(local_name(start.name().as_ref())),
3434 String::from_utf8_lossy(name)
3435 ))
3436 })
3437}
3438
3439fn optional_emu_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<Emu>> {
3440 decoded_attr(start, name)?
3441 .map(|value| {
3442 value.parse::<i64>().map(Emu).map_err(|error| {
3443 OxmlError::InvalidValue(format!(
3444 "invalid {}: {error}",
3445 String::from_utf8_lossy(name)
3446 ))
3447 })
3448 })
3449 .transpose()
3450}
3451
3452fn push_optional_emu(start: &mut BytesStart<'_>, name: &'static str, value: Option<Emu>) {
3453 if let Some(value) = value {
3454 start.push_attribute((name, Cow::Owned(value.0.to_string())));
3455 }
3456}
3457
3458fn push_optional_bool(start: &mut BytesStart<'_>, name: &'static str, value: Option<bool>) {
3459 if let Some(value) = value {
3460 start.push_attribute((name, if value { "1" } else { "0" }));
3461 }
3462}
3463
3464fn raw_attributes(
3465 start: &BytesStart<'_>,
3466 modelled: &[&[u8]],
3467 is_root: bool,
3468) -> Result<RawAttributes> {
3469 let mut raw = Vec::new();
3470 for attribute in start.attributes() {
3471 let attribute = attribute?;
3472 let key = attribute.key.as_ref();
3473 if modelled.contains(&key) || (is_root && key == b"xmlns:a") {
3474 continue;
3475 }
3476 raw.push((
3477 std::str::from_utf8(key)?.to_owned(),
3478 attribute
3479 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
3480 .into_owned(),
3481 ));
3482 }
3483 Ok(raw)
3484}
3485
3486fn decoded_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<String>> {
3487 for attribute in start.attributes() {
3488 let attribute = attribute?;
3489 if attribute.key.as_ref() == name {
3490 return Ok(Some(
3491 attribute
3492 .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
3493 .into_owned(),
3494 ));
3495 }
3496 }
3497 Ok(None)
3498}
3499
3500fn required_i64(start: &BytesStart<'_>, name: &[u8]) -> Result<i64> {
3501 let value = decoded_attr(start, name)?.ok_or_else(|| {
3502 missing(&format!(
3503 "{}@{}",
3504 String::from_utf8_lossy(local_name(start.name().as_ref())),
3505 String::from_utf8_lossy(name)
3506 ))
3507 })?;
3508 value.parse::<i64>().map_err(|error| {
3509 OxmlError::InvalidValue(format!(
3510 "invalid {}@{} {value}: {error}",
3511 String::from_utf8_lossy(local_name(start.name().as_ref())),
3512 String::from_utf8_lossy(name)
3513 ))
3514 })
3515}
3516
3517fn positive_u32_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<u32>> {
3518 let Some(value) = decoded_attr(start, name)? else {
3519 return Ok(None);
3520 };
3521 let parsed = value.parse::<u32>().map_err(|error| {
3522 OxmlError::InvalidValue(format!(
3523 "invalid a:tc@{} {value}: {error}",
3524 String::from_utf8_lossy(name)
3525 ))
3526 })?;
3527 if parsed == 0 {
3528 return Err(OxmlError::InvalidValue(format!(
3529 "a:tc@{} must be positive",
3530 String::from_utf8_lossy(name)
3531 )));
3532 }
3533 Ok(Some(parsed))
3534}
3535
3536fn bool_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<bool>> {
3537 let Some(value) = decoded_attr(start, name)? else {
3538 return Ok(None);
3539 };
3540 match value.as_str() {
3541 "true" | "1" | "on" => Ok(Some(true)),
3542 "false" | "0" | "off" => Ok(Some(false)),
3543 _ => Err(OxmlError::InvalidValue(format!(
3544 "invalid {}@{} boolean: {value}",
3545 String::from_utf8_lossy(local_name(start.name().as_ref())),
3546 String::from_utf8_lossy(name)
3547 ))),
3548 }
3549}
3550
3551fn push_true(start: &mut BytesStart<'_>, name: &'static str, value: bool) {
3552 if value {
3553 start.push_attribute((name, "1"));
3554 }
3555}
3556
3557fn push_attributes(start: &mut BytesStart<'_>, attributes: &RawAttributes) {
3558 for (name, value) in attributes {
3559 start.push_attribute((name.as_str(), value.as_str()));
3560 }
3561}
3562
3563fn emit_raw<'a, W: Write>(
3564 writer: &mut Writer<W>,
3565 children: impl Iterator<Item = &'a [u8]>,
3566) -> Result<()> {
3567 for child in children {
3568 writer.get_mut().write_all(child)?;
3569 }
3570 Ok(())
3571}
3572
3573fn missing(element: &str) -> OxmlError {
3574 OxmlError::MissingElement(element.to_owned())
3575}
3576
3577fn unexpected(start: &BytesStart<'_>) -> OxmlError {
3578 OxmlError::UnexpectedElement(String::from_utf8_lossy(start.name().as_ref()).into_owned())
3579}
3580
3581#[cfg(test)]
3582mod tests {
3583 use std::path::PathBuf;
3584
3585 use oxml_opc::OpcPackage;
3586
3587 use std::collections::HashSet;
3588
3589 use super::{
3590 A_NS, CT_Table, CT_TableStyle, CT_TableStyleList, Emu, Fill, OxmlError, StyleReference,
3591 builtin_table_styles,
3592 };
3593 use crate::color::{ColorChoice, ColorTransform};
3594
3595 #[test]
3596 fn new_table_uses_truncating_dimensions_and_valid_cell_shells() {
3597 let table = CT_Table::new(2, 3, Emu(302), Emu(201)).unwrap();
3598 assert_eq!(table.grid.columns, vec![Emu(100), Emu(100), Emu(102)]);
3599 assert_eq!(
3600 table.rows.iter().map(|row| row.height).collect::<Vec<_>>(),
3601 vec![Emu(100), Emu(101)]
3602 );
3603 assert!(table.properties.as_ref().unwrap().first_row);
3604 assert!(table.properties.as_ref().unwrap().band_rows);
3605 assert!(table.rows.iter().all(|row| row.cells.len() == 3));
3606 assert!(table.rows.iter().flat_map(|row| &row.cells).all(|cell| {
3607 cell.text_body.as_ref().unwrap().paragraph_count() == 1 && cell.properties.is_some()
3608 }));
3609 assert_eq!(CT_Table::from_xml(&table.to_xml().unwrap()).unwrap(), table);
3610 }
3611
3612 #[test]
3613 fn new_table_rejects_invalid_dimensions_before_allocation() {
3614 for result in [
3615 CT_Table::new(0, 1, Emu(1), Emu(1)),
3616 CT_Table::new(1, 0, Emu(1), Emu(1)),
3617 CT_Table::new(1, 1, Emu(0), Emu(1)),
3618 CT_Table::new(1, 1, Emu(1), Emu(-1)),
3619 CT_Table::new(2, 1, Emu(1), Emu(1)),
3620 CT_Table::new(1, 2, Emu(1), Emu(1)),
3621 ] {
3622 assert!(result.is_err());
3623 }
3624 }
3625
3626 #[test]
3627 fn table_style_and_cell_properties_preserve_unmodelled_xml_byte_for_byte() {
3628 let xml = br#"<q:tblStyleLst xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer" def="style"><x:before/><q:tblStyle styleId="style" styleName="Style"><q:wholeTbl><q:tcStyle><q:solidFill><q:srgbClr val="112233"/></q:solidFill><x:unsupported value="kept"/></q:tcStyle></q:wholeTbl></q:tblStyle><x:after/></q:tblStyleLst>"#;
3629
3630 let styles = CT_TableStyleList::from_xml(xml).expect("parse table style list");
3631 let written = styles.to_xml().expect("write table style list");
3632
3633 assert!(
3634 written
3635 .windows(b"<x:before/>".len())
3636 .any(|part| part == b"<x:before/>")
3637 );
3638 assert!(
3639 written
3640 .windows(b"<x:unsupported value=\"kept\"/>".len())
3641 .any(|part| part == b"<x:unsupported value=\"kept\"/>")
3642 );
3643 assert!(
3644 written
3645 .windows(b"<x:after/>".len())
3646 .any(|part| part == b"<x:after/>")
3647 );
3648 assert!(written.starts_with(
3649 br#"<a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main""#
3650 ));
3651 assert_eq!(styles, CT_TableStyleList::from_xml(&written).unwrap());
3652
3653 let producer_style = br#"<a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="style"><a:tblStyle styleId="style" styleName="Producer"><a:wholeTbl><a:tcTxStyle b="on"><a:fontRef idx="minor"><a:prstClr val="black"/></a:fontRef><a:schemeClr val="dk1"/></a:tcTxStyle><a:tcStyle><a:tcBdr><a:left><a:ln w="12700"><a:solidFill><a:schemeClr val="lt1"/></a:solidFill></a:ln></a:left><a:insideH><a:ln w="25400"><a:solidFill><a:schemeClr val="lt1"/></a:solidFill></a:ln></a:insideH></a:tcBdr><a:fill><a:solidFill><a:schemeClr val="accent1"/></a:solidFill></a:fill></a:tcStyle></a:wholeTbl></a:tblStyle></a:tblStyleLst>"#;
3654 let producer_style = CT_TableStyleList::from_xml(producer_style).unwrap();
3655 let whole = producer_style.styles[0].whole_table.as_ref().unwrap();
3656 assert_eq!(whole.text_style.as_ref().unwrap().bold, Some(true));
3657 assert!(whole.cell_style.as_ref().unwrap().fill.is_some());
3658 assert!(
3659 whole
3660 .cell_style
3661 .as_ref()
3662 .unwrap()
3663 .borders
3664 .as_ref()
3665 .unwrap()
3666 .left
3667 .is_some()
3668 );
3669 assert!(
3670 whole
3671 .cell_style
3672 .as_ref()
3673 .unwrap()
3674 .borders
3675 .as_ref()
3676 .unwrap()
3677 .inside_horizontal
3678 .is_some()
3679 );
3680 let producer_written = producer_style.to_xml().unwrap();
3681 assert_eq!(
3682 producer_style,
3683 CT_TableStyleList::from_xml(&producer_written).unwrap()
3684 );
3685
3686 let direct_edge_alias = br#"<a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:tblStyle styleId="alias" styleName="Alias"><a:wholeTbl><a:tcStyle><a:tcBdr><a:lnL w="12700"><a:solidFill><a:srgbClr val="000000"/></a:solidFill></a:lnL></a:tcBdr></a:tcStyle></a:wholeTbl></a:tblStyle></a:tblStyleLst>"#;
3687 let direct_edge_alias = CT_TableStyleList::from_xml(direct_edge_alias).unwrap();
3688 let borders = direct_edge_alias.styles[0]
3689 .whole_table
3690 .as_ref()
3691 .unwrap()
3692 .cell_style
3693 .as_ref()
3694 .unwrap()
3695 .borders
3696 .as_ref()
3697 .unwrap();
3698 assert!(borders.left.is_some());
3699 assert!(borders.unsupported.is_empty());
3700
3701 let table_xml = br#"<q:tbl xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer"><q:tblGrid><q:gridCol w="127000"/></q:tblGrid><q:tr h="127000"><q:tc><q:tcPr marL="12700" marR="25400" marT="38100" marB="50800"><q:lnL w="12700"><q:solidFill><q:srgbClr val="000000"/></q:solidFill></q:lnL><q:lnTlToBr><x:diagonal/></q:lnTlToBr><q:solidFill><q:srgbClr val="ABCDEF"/></q:solidFill><x:after-fill/></q:tcPr></q:tc></q:tr></q:tbl>"#;
3702 let table = CT_Table::from_xml(table_xml).unwrap();
3703 let properties = table.rows[0].cells[0].properties.as_ref().unwrap();
3704 assert_eq!(properties.margin_left, Some(Emu(12_700)));
3705 assert_eq!(properties.margin_bottom, Some(Emu(50_800)));
3706 assert!(properties.left.is_some());
3707 assert!(properties.fill.is_some());
3708 assert_eq!(properties.unsupported, ["diagonal border"]);
3709 let table_written = table.to_xml().unwrap();
3710 assert!(
3711 table_written
3712 .windows(b"<x:diagonal/>".len())
3713 .any(|part| part == b"<x:diagonal/>")
3714 );
3715 assert!(
3716 table_written
3717 .windows(b"<x:after-fill/>".len())
3718 .any(|part| part == b"<x:after-fill/>")
3719 );
3720 assert_eq!(table, CT_Table::from_xml(&table_written).unwrap());
3721 }
3722
3723 #[test]
3724 fn every_corpus_table_style_list_parses_and_round_trips() {
3725 let corpus = std::env::var_os("RDOCX_PPTX_CORPUS_DIR")
3726 .map(PathBuf::from)
3727 .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../corpus/pptx"));
3728 if !corpus.is_dir() {
3729 assert_ne!(
3730 std::env::var_os("RDOCX_PPTX_CORPUS_REQUIRED").as_deref(),
3731 Some(std::ffi::OsStr::new("1")),
3732 "the required pinned corpus is missing at {}",
3733 corpus.display()
3734 );
3735 return;
3736 }
3737 for entry in std::fs::read_dir(&corpus).unwrap() {
3738 let path = entry.unwrap().path();
3739 if path.extension().and_then(|extension| extension.to_str()) != Some("pptx") {
3740 continue;
3741 }
3742 let package = OpcPackage::open(&path).unwrap();
3743 let Some(xml) = package.get_part("/ppt/tableStyles.xml") else {
3744 continue;
3745 };
3746 let styles = CT_TableStyleList::from_xml(xml)
3747 .unwrap_or_else(|error| panic!("{}: {error}", path.display()));
3748 let written = styles.to_xml().unwrap();
3749 assert_eq!(
3750 styles,
3751 CT_TableStyleList::from_xml(&written).unwrap(),
3752 "{}",
3753 path.display()
3754 );
3755 }
3756 }
3757
3758 #[test]
3759 fn table_properties_preserve_style_and_banding_flags() {
3760 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:tblPr rtl="1" firstRow="true" firstCol="1" lastRow="true" lastCol="1" bandRow="true" bandCol="1"><a:tableStyleId>{5940675A-B579-460E-94D1-54222C63F5DA}</a:tableStyleId></a:tblPr><a:tblGrid><a:gridCol w="1"/></a:tblGrid><a:tr h="2"><a:tc/></a:tr></a:tbl>"#;
3761 let table = CT_Table::from_xml(xml).unwrap();
3762 let properties = table.properties.as_ref().unwrap();
3763 assert!(properties.right_to_left);
3764 assert!(properties.first_row && properties.first_column);
3765 assert!(properties.last_row && properties.last_column);
3766 assert!(properties.band_rows && properties.band_columns);
3767 assert_eq!(
3768 properties.style_id.as_deref(),
3769 Some("{5940675A-B579-460E-94D1-54222C63F5DA}")
3770 );
3771 assert_eq!(table, CT_Table::from_xml(&table.to_xml().unwrap()).unwrap());
3772 }
3773
3774 #[test]
3775 fn table_reader_is_prefix_tolerant_and_writer_uses_schema_order() {
3776 let xml = br#"<q:tbl xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main"><q:tblPr firstRow="1"/><q:tblGrid><q:gridCol w="100"/></q:tblGrid><q:tr h="200"><q:tc><q:txBody><q:bodyPr/><q:p/></q:txBody><q:tcPr/></q:tc></q:tr></q:tbl>"#;
3777 let table = CT_Table::from_xml(xml).unwrap();
3778 let written = table.to_xml().unwrap();
3779 let text = std::str::from_utf8(&written).unwrap();
3780 assert!(text.starts_with("<a:tbl xmlns:a="));
3781 assert_order(
3782 text,
3783 &[
3784 "<a:tblPr",
3785 "<a:tblGrid",
3786 "<a:tr",
3787 "<a:tc",
3788 "<a:txBody",
3789 "<a:tcPr",
3790 ],
3791 );
3792 let reparsed = CT_Table::from_xml(&written).unwrap();
3793 assert_eq!(table.grid, reparsed.grid);
3794 assert_eq!(table.rows[0], reparsed.rows[0]);
3795 assert_eq!(table, reparsed);
3796 }
3797
3798 #[test]
3799 fn unmodelled_table_and_cell_content_is_preserved_in_place() {
3800 let xml = br#"<q:tbl xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer" x:table="kept"><x:before/><x:opaque xmlns:a="urn:producer"><a:data/></x:opaque><q:tblPr x:properties="kept"><x:fill/><q:tableStyleId>style</q:tableStyleId><x:effect/></q:tblPr><x:between/><q:tblGrid x:grid="kept"><x:grid-before/><q:gridCol w="100" x:column="kept"><x:column-child/></q:gridCol><x:grid-after/></q:tblGrid><x:before-row/><q:tr h="200" x:row="kept"><x:before-cell/><q:tc x:cell="kept"><x:before-text/><x:cell-opaque xmlns:a="urn:producer"><a:data/></x:cell-opaque><q:txBody><q:bodyPr/><q:p/></q:txBody><x:before-properties/><q:tcPr x:style="kept"><x:border/></q:tcPr><x:after-properties/></q:tc><x:after-cell/></q:tr><x:after/></q:tbl>"#;
3801 let table = CT_Table::from_xml(xml).unwrap();
3802 let written = table.to_xml().unwrap();
3803 let text = std::str::from_utf8(&written).unwrap();
3804 for raw in [
3805 r#"<x:before/>"#,
3806 r#"<x:opaque xmlns:a="urn:producer"><a:data/></x:opaque>"#,
3807 r#"<x:fill/>"#,
3808 r#"<x:effect/>"#,
3809 r#"<x:column-child/>"#,
3810 r#"<a:tcPr x:style="kept"><x:border/></a:tcPr>"#,
3811 r#"<x:cell-opaque xmlns:a="urn:producer"><a:data/></x:cell-opaque>"#,
3812 r#"<x:after-properties/>"#,
3813 r#"<x:after/>"#,
3814 ] {
3815 assert!(text.contains(raw), "missing {raw}");
3816 }
3817 assert_order(
3818 text,
3819 &[
3820 "<x:before-text",
3821 "<a:txBody",
3822 "<x:before-properties",
3823 "<a:tcPr",
3824 "<x:after-properties",
3825 ],
3826 );
3827 assert_eq!(table, CT_Table::from_xml(&written).unwrap());
3828 }
3829
3830 #[test]
3831 fn collection_edits_keep_surviving_metadata_and_trailing_raw_children() {
3832 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer"><a:tblGrid><a:gridCol w="100" x:id="first"><x:first-child/></a:gridCol><a:gridCol w="200" x:id="second"><x:second-child/></a:gridCol><x:grid-tail/></a:tblGrid><a:tr h="300"><a:tc/><x:between-cells/><a:tc/><x:row-tail/></a:tr><x:between-rows/><a:tr h="400"><a:tc/></a:tr><x:table-tail/></a:tbl>"#;
3833 let mut table = CT_Table::from_xml(xml).unwrap();
3834 table.grid.columns.swap(0, 1);
3835 table.grid.columns.remove(1);
3836 table.rows[0].cells.remove(0);
3837 table.rows[0].cells[0].grid_span = 2;
3838 table.rows.remove(1);
3839
3840 let written = table.to_xml().unwrap();
3841 let text = std::str::from_utf8(&written).unwrap();
3842 assert!(text.contains(r#"<a:gridCol w="200" x:id="second"><x:second-child/></a:gridCol>"#));
3843 assert!(!text.contains(r#"x:id="first""#));
3844 for raw in [
3845 "<x:grid-tail/>",
3846 "<x:between-cells/>",
3847 "<x:row-tail/>",
3848 "<x:between-rows/>",
3849 "<x:table-tail/>",
3850 ] {
3851 assert!(text.contains(raw), "missing {raw}");
3852 }
3853 assert!(text.contains(
3854 r#"<a:tr h="300"><x:between-cells/><a:tc gridSpan="2"/><x:row-tail/></a:tr>"#
3855 ));
3856 let reparsed = CT_Table::from_xml(&written).unwrap();
3857 assert_eq!(table.grid, reparsed.grid);
3858 assert_eq!(table.rows[0], reparsed.rows[0]);
3859 assert_eq!(table, reparsed);
3860
3861 let mut width_edit = CT_Table::from_xml(xml).unwrap();
3862 width_edit.grid.columns[1] = Emu(250);
3863 let width_edit_xml = width_edit.to_xml().unwrap();
3864 assert!(
3865 std::str::from_utf8(&width_edit_xml)
3866 .unwrap()
3867 .contains(r#"<a:gridCol w="250" x:id="second"><x:second-child/></a:gridCol>"#)
3868 );
3869 assert_eq!(width_edit, CT_Table::from_xml(&width_edit_xml).unwrap());
3870 }
3871
3872 #[test]
3873 fn ambiguous_grid_edits_return_a_typed_serialization_error() {
3874 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer"><a:tblGrid><a:gridCol w="100" x:id="first"/><a:gridCol w="200" x:id="second"/></a:tblGrid><a:tr h="300"><a:tc/></a:tr></a:tbl>"#;
3875
3876 let mut collision = CT_Table::from_xml(xml).unwrap();
3877 collision.grid.columns[0] = Emu(200);
3878 assert_ambiguous_grid_error(collision.to_xml().unwrap_err());
3879
3880 let mut combined_reorder_and_edit = CT_Table::from_xml(xml).unwrap();
3881 combined_reorder_and_edit.grid.columns.swap(0, 1);
3882 combined_reorder_and_edit.grid.columns[0] = Emu(100);
3883 assert_ambiguous_grid_error(combined_reorder_and_edit.to_xml().unwrap_err());
3884
3885 let mut duplicate_insertion = CT_Table::from_xml(xml).unwrap();
3886 duplicate_insertion.grid.columns.insert(0, Emu(100));
3887 assert_ambiguous_grid_error(duplicate_insertion.to_xml().unwrap_err());
3888
3889 let mut delete_and_edit = CT_Table::from_xml(xml).unwrap();
3890 delete_and_edit.grid.columns.remove(1);
3891 delete_and_edit.grid.columns[0] = Emu(150);
3892 assert_ambiguous_grid_error(delete_and_edit.to_xml().unwrap_err());
3893
3894 let mut insert_and_edit = CT_Table::from_xml(xml).unwrap();
3895 insert_and_edit.grid.columns[0] = Emu(150);
3896 insert_and_edit.grid.columns.insert(1, Emu(300));
3897 assert_ambiguous_grid_error(insert_and_edit.to_xml().unwrap_err());
3898 }
3899
3900 #[test]
3901 fn column_width_edits_keep_metadata_when_columns_share_a_width() {
3902 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer"><a:tblGrid><a:gridCol w="100"><x:id val="1"/></a:gridCol><x:before-second/><a:gridCol w="100"><x:id val="2"/></a:gridCol><a:gridCol w="100"><x:id val="3"/></a:gridCol><x:grid-tail/></a:tblGrid><a:tr h="300"><a:tc/><a:tc/><a:tc/></a:tr></a:tbl>"#;
3903 let mut table = CT_Table::from_xml(xml).unwrap();
3904 table.set_column_width(1, Emu(250)).unwrap();
3905 table.set_column_width(2, Emu(100)).unwrap();
3906 table.set_column_width(0, Emu(250)).unwrap();
3907 assert!(table.set_column_width(3, Emu(1)).is_err());
3908
3909 let written = table.to_xml().unwrap();
3910 assert!(
3911 std::str::from_utf8(&written).unwrap().contains(
3912 r#"<a:tblGrid><a:gridCol w="250"><x:id val="1"/></a:gridCol><x:before-second/><a:gridCol w="250"><x:id val="2"/></a:gridCol><a:gridCol w="100"><x:id val="3"/></a:gridCol><x:grid-tail/></a:tblGrid>"#
3913 )
3914 );
3915 assert_eq!(table, CT_Table::from_xml(&written).unwrap());
3916 }
3917
3918 const PYTHON_PPTX_TABLE: &[u8] = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><a:tblPr firstRow="1" bandRow="1"><a:tableStyleId>{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}</a:tableStyleId></a:tblPr><a:tblGrid><a:gridCol w="2057400"/><a:gridCol w="1828800"/></a:tblGrid><a:tr h="548640"><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p/></a:txBody><a:tcPr><a:solidFill><a:srgbClr val="3E4A59"/></a:solidFill></a:tcPr></a:tc><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr sz="1500"/><a:t>A. Repair</a:t></a:r></a:p></a:txBody><a:tcPr><a:solidFill><a:srgbClr val="3E4A59"/></a:solidFill></a:tcPr></a:tc></a:tr><a:tr h="457200"><a:tc><a:txBody><a:bodyPr anchor="ctr"/><a:lstStyle/><a:p><a:pPr algn="r"/><a:r><a:rPr sz="1500" b="1"><a:hlinkClick r:id="rId9"/></a:rPr><a:t>Cost</a:t></a:r><a:r><a:rPr sz="900"/><a:t> (EUR)</a:t></a:r></a:p><a:p><a:r><a:t>second</a:t></a:r></a:p></a:txBody><a:tcPr marL="0"/></a:tc><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr sz="1100"/></a:p></a:txBody><a:tcPr/></a:tc></a:tr></a:tbl>"#;
3919
3920 #[test]
3921 fn inserted_rows_and_columns_copy_neighbour_formatting_without_text() {
3922 let mut table = CT_Table::from_xml(PYTHON_PPTX_TABLE).unwrap();
3923 table.insert_row(2).unwrap();
3924 table.insert_row(0).unwrap();
3925 table.insert_column(2).unwrap();
3926 assert_eq!(
3927 table.rows.iter().map(|row| row.height).collect::<Vec<_>>(),
3928 [Emu(548_640), Emu(548_640), Emu(457_200), Emu(457_200)]
3929 );
3930 assert_eq!(
3931 table.grid.columns,
3932 [Emu(2_057_400), Emu(1_828_800), Emu(1_828_800)]
3933 );
3934 assert!(table.rows.iter().all(|row| row.cells.len() == 3));
3935
3936 let written = String::from_utf8(table.to_xml().unwrap()).unwrap();
3937 let rows = written.split("<a:tr ").skip(1).collect::<Vec<_>>();
3938 let header = r#"<a:tcPr><a:solidFill><a:srgbClr val="3E4A59"/></a:solidFill></a:tcPr>"#;
3939 assert_eq!(
3940 rows[0],
3941 format!(
3942 r#"h="548640"><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p/></a:txBody>{header}</a:tc><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr sz="1500"/></a:p></a:txBody>{header}</a:tc><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr sz="1500"/></a:p></a:txBody>{header}</a:tc></a:tr>"#
3943 )
3944 );
3945 assert!(rows[1].contains("A. Repair"));
3946 assert!(rows[2].contains("Cost") && rows[2].contains("second"));
3947 assert_eq!(
3948 rows[3],
3949 r#"h="457200"><a:tc><a:txBody><a:bodyPr anchor="ctr"/><a:lstStyle/><a:p><a:pPr algn="r"/><a:endParaRPr sz="1500" b="1"/></a:p></a:txBody><a:tcPr marL="0"/></a:tc><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr sz="1100"/></a:p></a:txBody><a:tcPr/></a:tc><a:tc><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:endParaRPr sz="1100"/></a:p></a:txBody><a:tcPr/></a:tc></a:tr></a:tbl>"#
3950 );
3951 assert!(written.contains(
3952 r#"<a:tableStyleId>{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}</a:tableStyleId>"#
3953 ));
3954 assert_eq!(written.matches("r:id=").count(), 1);
3955 assert_eq!(table, CT_Table::from_xml(written.as_bytes()).unwrap());
3956
3957 table.remove_row(3).unwrap();
3958 table.remove_row(0).unwrap();
3959 table.remove_column(2).unwrap();
3960 assert_eq!(
3961 table.to_xml().unwrap(),
3962 CT_Table::from_xml(PYTHON_PPTX_TABLE)
3963 .unwrap()
3964 .to_xml()
3965 .unwrap()
3966 );
3967 }
3968
3969 #[test]
3970 fn row_and_column_edits_refuse_what_would_break_the_grid() {
3971 let mut table = CT_Table::new(1, 1, Emu(100), Emu(100)).unwrap();
3972 let before = table.clone();
3973 for result in [
3974 table.remove_row(0),
3975 table.remove_column(0),
3976 table.insert_row(2),
3977 table.insert_column(2),
3978 table.remove_row(1),
3979 table.remove_column(1),
3980 ] {
3981 assert!(matches!(result, Err(OxmlError::InvalidValue(_))));
3982 }
3983 assert_eq!(table, before);
3984
3985 let mut emptied = CT_Table::new(1, 1, Emu(100), Emu(100)).unwrap();
3986 emptied.rows.clear();
3987 assert!(matches!(
3988 emptied.insert_row(0),
3989 Err(OxmlError::MissingElement(_))
3990 ));
3991 let mut emptied = CT_Table::new(1, 1, Emu(100), Emu(100)).unwrap();
3992 emptied.grid.columns.clear();
3993 emptied.rows[0].cells.clear();
3994 assert!(matches!(
3995 emptied.insert_column(0),
3996 Err(OxmlError::MissingElement(_))
3997 ));
3998
3999 let ragged = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:tblGrid><a:gridCol w="100"/><a:gridCol w="100"/></a:tblGrid><a:tr h="100"><a:tc/></a:tr></a:tbl>"#;
4000 let overflowing = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:tblGrid><a:gridCol w="100"/></a:tblGrid><a:tr h="100"><a:tc rowSpan="2"/></a:tr><a:tr h="100"><a:tc vMerge="1"/></a:tr><a:tr h="100"><a:tc gridSpan="2"/></a:tr></a:tbl>"#;
4001 for xml in [&ragged[..], &overflowing[..]] {
4002 let mut table = CT_Table::from_xml(xml).unwrap();
4003 assert!(table.insert_row(1).is_err());
4004 assert!(table.remove_column(0).is_err());
4005 assert_eq!(table, CT_Table::from_xml(xml).unwrap());
4006 }
4007 }
4008
4009 fn merge_pattern(table: &CT_Table) -> Vec<String> {
4012 table
4013 .rows
4014 .iter()
4015 .map(|row| {
4016 row.cells
4017 .iter()
4018 .map(|cell| {
4019 let flags = match (cell.horizontal_merge, cell.vertical_merge) {
4020 (true, true) => "hv",
4021 (true, false) => "h",
4022 (false, true) => "v",
4023 (false, false) if cell.row_span == 1 && cell.grid_span == 1 => ".",
4024 (false, false) => "o",
4025 };
4026 format!("{flags}{}x{}", cell.row_span, cell.grid_span)
4027 })
4028 .collect::<Vec<_>>()
4029 .join(" ")
4030 })
4031 .collect()
4032 }
4033
4034 fn merged_three_by_three() -> CT_Table {
4035 let mut table = CT_Table::new(3, 3, Emu(300), Emu(300)).unwrap();
4036 for (row, column, row_span, grid_span, horizontal, vertical) in [
4037 (0, 0, 2, 2, false, false),
4038 (0, 1, 2, 1, true, false),
4039 (1, 0, 1, 2, false, true),
4040 (1, 1, 1, 1, true, true),
4041 ] {
4042 let cell = &mut table.rows[row].cells[column];
4043 cell.row_span = row_span;
4044 cell.grid_span = grid_span;
4045 cell.horizontal_merge = horizontal;
4046 cell.vertical_merge = vertical;
4047 cell.text_body
4048 .as_mut()
4049 .unwrap()
4050 .set_text(&format!("{row}{column}"));
4051 }
4052 table.rows[0].cells[0]
4053 .properties
4054 .as_mut()
4055 .unwrap()
4056 .margin_left = Some(Emu(7));
4057 table
4058 }
4059
4060 #[test]
4061 fn row_and_column_edits_grow_shrink_and_move_merged_cells() {
4062 assert_eq!(
4063 merge_pattern(&merged_three_by_three()),
4064 ["o2x2 h2x1 .1x1", "v1x2 hv1x1 .1x1", ".1x1 .1x1 .1x1"]
4065 );
4066
4067 let mut inside = merged_three_by_three();
4068 inside.insert_row(1).unwrap();
4069 assert_eq!(
4070 merge_pattern(&inside),
4071 [
4072 "o3x2 h3x1 .1x1",
4073 "v1x2 hv1x1 .1x1",
4074 "v1x2 hv1x1 .1x1",
4075 ".1x1 .1x1 .1x1"
4076 ]
4077 );
4078 inside.insert_column(1).unwrap();
4079 assert_eq!(
4080 merge_pattern(&inside),
4081 [
4082 "o3x3 h3x1 h3x1 .1x1",
4083 "v1x3 hv1x1 hv1x1 .1x1",
4084 "v1x3 hv1x1 hv1x1 .1x1",
4085 ".1x1 .1x1 .1x1 .1x1"
4086 ]
4087 );
4088 assert_eq!(
4089 inside,
4090 CT_Table::from_xml(&inside.to_xml().unwrap()).unwrap()
4091 );
4092
4093 for index in [0, 2] {
4094 let mut outside = merged_three_by_three();
4095 outside.insert_row(index).unwrap();
4096 outside.insert_column(index).unwrap();
4097 let pattern = merge_pattern(&outside);
4098 assert_eq!(pattern.len(), 4);
4099 let without_new = pattern
4100 .iter()
4101 .enumerate()
4102 .filter(|(row, _)| *row != index)
4103 .map(|(_, row)| {
4104 row.split(' ')
4105 .enumerate()
4106 .filter(|(column, _)| *column != index)
4107 .map(|(_, cell)| cell)
4108 .collect::<Vec<_>>()
4109 .join(" ")
4110 })
4111 .collect::<Vec<_>>();
4112 assert_eq!(without_new, merge_pattern(&merged_three_by_three()));
4113 assert!(pattern[index].split(' ').all(|cell| cell == ".1x1"));
4114 assert!(
4115 pattern
4116 .iter()
4117 .all(|row| row.split(' ').nth(index) == Some(".1x1"))
4118 );
4119 }
4120
4121 let mut top_removed = merged_three_by_three();
4122 top_removed.remove_row(0).unwrap();
4123 assert_eq!(
4124 merge_pattern(&top_removed),
4125 ["o1x2 h1x1 .1x1", ".1x1 .1x1 .1x1"]
4126 );
4127 let origin = &top_removed.rows[0].cells[0];
4128 assert_eq!(origin.text_body.as_ref().unwrap().plain_text(), "00");
4129 assert_eq!(
4130 origin.properties.as_ref().unwrap().margin_left,
4131 Some(Emu(7))
4132 );
4133
4134 let mut covered_removed = merged_three_by_three();
4135 covered_removed.remove_row(1).unwrap();
4136 assert_eq!(
4137 merge_pattern(&covered_removed),
4138 ["o1x2 h1x1 .1x1", ".1x1 .1x1 .1x1"]
4139 );
4140
4141 let mut left_removed = merged_three_by_three();
4142 left_removed.remove_column(0).unwrap();
4143 assert_eq!(
4144 merge_pattern(&left_removed),
4145 ["o2x1 .1x1", "v1x1 .1x1", ".1x1 .1x1"]
4146 );
4147 let origin = &left_removed.rows[0].cells[0];
4148 assert_eq!(origin.text_body.as_ref().unwrap().plain_text(), "00");
4149 assert_eq!(
4150 origin.properties.as_ref().unwrap().margin_left,
4151 Some(Emu(7))
4152 );
4153 assert_eq!(left_removed.grid.columns, [Emu(100), Emu(100)]);
4154
4155 let mut collapsed = merged_three_by_three();
4156 collapsed.remove_row(1).unwrap();
4157 collapsed.remove_column(1).unwrap();
4158 assert_eq!(merge_pattern(&collapsed), [".1x1 .1x1", ".1x1 .1x1"]);
4159 }
4160
4161 #[test]
4162 fn row_and_column_edits_keep_unmodelled_row_and_grid_content_in_place() {
4163 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer"><a:tblGrid><a:gridCol w="100"><x:id val="1"/></a:gridCol><x:before-second/><a:gridCol w="100"><x:id val="2"/></a:gridCol><x:grid-tail/></a:tblGrid><a:tr h="300"><a:tc/><a:tc x:cell="kept"/><x:row-id val="1"/></a:tr><x:between-rows/><a:tr h="400"><a:tc/><a:tc/><x:row-id val="2"/></a:tr><x:table-tail/></a:tbl>"#;
4164 let mut table = CT_Table::from_xml(xml).unwrap();
4165 table.insert_row(1).unwrap();
4166 table.insert_column(1).unwrap();
4167 table.set_column_width(1, Emu(150)).unwrap();
4168 let written = String::from_utf8(table.to_xml().unwrap()).unwrap();
4169 assert!(written.contains(
4170 r#"<a:tblGrid><a:gridCol w="100"><x:id val="1"/></a:gridCol><a:gridCol w="150"/><x:before-second/><a:gridCol w="100"><x:id val="2"/></a:gridCol><x:grid-tail/></a:tblGrid>"#
4171 ), "{written}");
4172 assert!(written.contains(
4173 r#"<a:tr h="300"><a:tc/><a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody></a:tc><a:tc x:cell="kept"/><x:row-id val="1"/></a:tr><a:tr h="300"><a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody></a:tc><a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody></a:tc><a:tc><a:txBody><a:bodyPr/><a:p/></a:txBody></a:tc></a:tr><x:between-rows/><a:tr h="400">"#
4174 ), "{written}");
4175 assert!(written.ends_with(r#"<x:row-id val="2"/></a:tr><x:table-tail/></a:tbl>"#));
4176 assert_eq!(table, CT_Table::from_xml(written.as_bytes()).unwrap());
4177
4178 table.remove_column(0).unwrap();
4179 table.remove_row(0).unwrap();
4180 let written = String::from_utf8(table.to_xml().unwrap()).unwrap();
4181 assert!(written.contains(
4182 r#"<a:tblGrid><a:gridCol w="150"/><x:before-second/><a:gridCol w="100"><x:id val="2"/></a:gridCol><x:grid-tail/></a:tblGrid>"#
4183 ), "{written}");
4184 assert!(!written.contains(r#"<x:row-id val="1"/>"#));
4185 assert!(written.ends_with(r#"<x:row-id val="2"/></a:tr><x:table-tail/></a:tbl>"#));
4186 assert_eq!(table, CT_Table::from_xml(written.as_bytes()).unwrap());
4187 }
4188
4189 #[test]
4190 fn inherited_namespaces_make_extracted_table_output_self_contained() {
4191 let xml = br#"<q:tbl><q:tblGrid><q:gridCol w="100"/></q:tblGrid><q:tr h="200"><q:tc><x:extension/></q:tc></q:tr></q:tbl>"#;
4192 let inherited = vec![
4193 ("q".to_owned(), A_NS.to_owned()),
4194 ("x".to_owned(), "urn:producer".to_owned()),
4195 ];
4196 let table = CT_Table::from_xml_with_inherited_namespaces(xml, &inherited).unwrap();
4197 let written = table.to_xml().unwrap();
4198 let text = std::str::from_utf8(&written).unwrap();
4199 assert!(text.starts_with(&format!(r#"<a:tbl xmlns:a="{A_NS}""#)));
4200 assert!(text.contains(r#"xmlns:x="urn:producer""#));
4201 assert!(text.contains("<x:extension/>"));
4202 assert_eq!(table, CT_Table::from_xml(&written).unwrap());
4203
4204 let locally_shadowed = br#"<q:tbl xmlns:q="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><q:tblGrid><q:gridCol w="100"/></q:tblGrid><q:tr h="200"><q:tc/></q:tr></q:tbl>"#;
4205 let producer_a = vec![("a".to_owned(), "urn:producer".to_owned())];
4206 assert!(
4207 CT_Table::from_xml_with_inherited_namespaces(locally_shadowed, &producer_a).is_ok()
4208 );
4209 }
4210
4211 #[test]
4212 fn inherited_namespace_prefixes_accept_xml_ncname_unicode() {
4213 let xml = r#"<q:tbl><q:tblGrid><q:gridCol w="100"/></q:tblGrid><q:tr h="200"><q:tc><é:extension/></q:tc></q:tr></q:tbl>"#;
4214 let inherited = vec![
4215 ("q".to_owned(), A_NS.to_owned()),
4216 ("é".to_owned(), "urn:producer".to_owned()),
4217 ];
4218 let table =
4219 CT_Table::from_xml_with_inherited_namespaces(xml.as_bytes(), &inherited).unwrap();
4220 let written = table.to_xml().unwrap();
4221 let text = std::str::from_utf8(&written).unwrap();
4222 assert!(text.contains(r#"xmlns:é="urn:producer""#));
4223 assert!(text.contains("<é:extension/>"));
4224 assert_eq!(table, CT_Table::from_xml(&written).unwrap());
4225 }
4226
4227 #[test]
4228 fn optional_child_edits_round_trip_as_equal_models() {
4229 let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:x="urn:producer"><a:tblPr><x:before-style/><a:tableStyleId>style</a:tableStyleId><x:after-style/></a:tblPr><x:between-properties-grid/><a:tblGrid><a:gridCol w="100"/></a:tblGrid><a:tr h="200"><a:tc><x:before-text/><a:txBody><a:bodyPr/><a:p/></a:txBody><x:between-text-properties/><a:tcPr/></a:tc></a:tr></a:tbl>"#;
4230
4231 let mut no_properties = CT_Table::from_xml(xml).unwrap();
4232 no_properties.properties = None;
4233 let written = no_properties.to_xml().unwrap();
4234 assert_eq!(no_properties, CT_Table::from_xml(&written).unwrap());
4235
4236 let mut no_style = CT_Table::from_xml(xml).unwrap();
4237 no_style.properties.as_mut().unwrap().style_id = None;
4238 let written = no_style.to_xml().unwrap();
4239 let reparsed = CT_Table::from_xml(&written).unwrap();
4240 assert_eq!(
4241 no_style.properties.as_ref().unwrap(),
4242 reparsed.properties.as_ref().unwrap()
4243 );
4244 assert_eq!(no_style, reparsed);
4245
4246 let mut no_text = CT_Table::from_xml(xml).unwrap();
4247 no_text.rows[0].cells[0].text_body = None;
4248 let written = no_text.to_xml().unwrap();
4249 let reparsed = CT_Table::from_xml(&written).unwrap();
4250 assert_eq!(no_text.rows[0].cells[0], reparsed.rows[0].cells[0]);
4251 assert_eq!(no_text, reparsed);
4252 }
4253
4254 #[test]
4255 fn builtin_table_styles_resolve_all_74_powerpoint_ids_and_round_trip() {
4256 let mut list = CT_TableStyleList::from_xml(
4257 format!(r#"<a:tblStyleLst xmlns:a="{A_NS}" def="{{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}}"/>"#)
4258 .as_bytes(),
4259 )
4260 .unwrap();
4261 for (style_id, ..) in builtin_table_styles() {
4262 let style = CT_TableStyle::builtin(style_id).unwrap();
4263 assert_eq!(style.style_id, style_id);
4264 assert!(style.whole_table.is_some(), "{style_id}");
4265 list.styles.push(style);
4266 }
4267 let names = list
4268 .styles
4269 .iter()
4270 .map(|style| style.style_name.as_str())
4271 .collect::<HashSet<_>>();
4272 assert_eq!(names.len(), 74);
4273 for name in [
4274 "No Style, No Grid",
4275 "Themed Style 2 - Accent 6",
4276 "Medium Style 2 - Accent 1",
4277 "Dark Style 2",
4278 "Dark Style 2 - Accent 5/Accent 6",
4279 ] {
4280 assert!(names.contains(name), "{name}");
4281 }
4282 assert_eq!(
4283 CT_TableStyleList::from_xml(&list.to_xml().unwrap()).unwrap(),
4284 list
4285 );
4286
4287 for unknown in [
4289 "{5c22544a-7ee6-4342-b048-85bdc9fd1c3a}",
4290 "5C22544A-7EE6-4342-B048-85BDC9FD1C3A",
4291 "{00000000-0000-0000-0000-000000000000}",
4292 ] {
4293 assert!(CT_TableStyle::builtin(unknown).is_none(), "{unknown}");
4294 }
4295 }
4296
4297 #[test]
4298 fn builtin_table_style_families_place_their_variant_colour() {
4299 assert_regions(
4301 "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}",
4302 &[
4303 "wholeTbl text dk1",
4304 "wholeTbl fill accent1 tint 20%",
4305 "wholeTbl insideH 12700 lt1",
4306 "wholeTbl insideV 12700 lt1",
4307 "band1H fill accent1 tint 40%",
4308 "band1V fill accent1 tint 40%",
4309 "firstCol text bold lt1",
4310 "firstCol fill accent1",
4311 "lastCol fill accent1",
4312 "lastRow top 38100 lt1",
4313 "lastRow fill accent1",
4314 "firstRow text bold lt1",
4315 "firstRow bottom 38100 lt1",
4316 "firstRow fill accent1",
4317 ],
4318 );
4319 assert_regions(
4321 "{073A0DAA-6AF3-43AB-8588-CEC1D06C72B9}",
4322 &["wholeTbl fill dk1 tint 20%", "firstRow fill dk1"],
4323 );
4324 assert_regions(
4326 "{0E3FDE45-AF77-4B5C-9715-49D594BDF05E}",
4327 &[
4328 "wholeTbl text tx1",
4329 "wholeTbl left none",
4330 "wholeTbl top 12700 accent2",
4331 "band1H fill accent2 alpha 20%",
4332 "firstRow text bold",
4333 "firstRow bottom 12700 accent2",
4334 "lastRow top 12700 accent2",
4335 ],
4336 );
4337 assert_regions(
4339 "{69012ECD-51FC-41F1-AA8D-1B2483CD663E}",
4340 &["firstRow text bold bg1", "firstRow fill ref 1 accent1"],
4341 );
4342 assert_regions(
4344 "{74C1A8A3-306A-4EB7-A6B1-4F7E0EB9C5D6}",
4345 &[
4346 "wholeTbl top 25400 dk1",
4347 "band1H fill dk1 tint 20%",
4348 "firstCol fill accent5",
4349 "firstRow bottom 25400 dk1",
4350 "firstRow fill accent5",
4351 "seCell text bold dk1",
4352 ],
4353 );
4354 assert_regions(
4356 "{16D9F66E-5EB9-4882-86FB-DCBF35E3C3E4}",
4357 &[
4358 "wholeTbl insideV 12700 accent6",
4359 "wholeTbl fill accent6 tint 20%",
4360 "band1V fill accent6 tint 40%",
4361 "lastRow top 25400 accent6",
4362 "firstRow text bold",
4363 ],
4364 );
4365 assert_regions(
4367 "{E8034E78-7F5D-4C2E-B375-FC64B27BC917}",
4368 &[
4369 "wholeTbl text lt1",
4370 "wholeTbl fill dk1 tint 20%",
4371 "band1H fill dk1 tint 40%",
4372 "firstCol fill dk1 tint 60%",
4373 "lastRow fill dk1 tint 60%",
4374 "firstRow fill dk1",
4375 ],
4376 );
4377 assert_regions(
4378 "{D03447BB-5D67-496B-8E87-E561075AD55C}",
4379 &[
4380 "wholeTbl fill accent3",
4381 "band1H fill accent3 shade 60%",
4382 "firstCol right 25400 lt1",
4383 "lastRow fill accent3 shade 40%",
4384 "firstRow fill dk1",
4385 "nwCell right none",
4386 ],
4387 );
4388 assert_regions(
4390 "{91EBBBCC-DAD2-459C-BE2E-F6DE35CF9A28}",
4391 &[
4392 "wholeTbl text dk1",
4393 "wholeTbl fill accent3 tint 20%",
4394 "band1H fill accent3 tint 40%",
4395 "lastRow top 50800 dk1",
4396 "firstRow text bold lt1",
4397 "firstRow fill accent4",
4398 ],
4399 );
4400 assert_regions(
4402 "{08FB837D-C827-4EFA-A057-4D05807E0F7C}",
4403 &[
4404 "band1H fill accent6 alpha 40%",
4405 "firstRow text bold lt1",
4406 "firstRow fill accent6",
4407 ],
4408 );
4409 for (style_id, index) in [
4410 ("{08FB837D-C827-4EFA-A057-4D05807E0F7C}", 2),
4411 ("{306799F8-075E-4A3A-A7F6-7FBC6576F1A4}", 3),
4412 ] {
4413 let style = CT_TableStyle::builtin(style_id).unwrap();
4414 let background = style.background.unwrap();
4415 let Some(StyleReference::Fill(reference)) = background.fill_reference else {
4416 panic!("{style_id}: expected a background fill reference");
4417 };
4418 assert_eq!(reference.index, index);
4419 }
4420 assert_regions(
4421 "{306799F8-075E-4A3A-A7F6-7FBC6576F1A4}",
4422 &[
4423 "wholeTbl text lt1",
4424 "band1H fill lt1 alpha 20%",
4425 "firstRow text bold",
4426 ],
4427 );
4428 }
4429
4430 #[test]
4431 fn table_background_models_its_fill_and_keeps_its_effect_in_place() {
4432 let xml = format!(
4433 r#"<q:tblStyleLst xmlns:q="{A_NS}" xmlns:x="urn:producer" def="s"><q:tblStyle styleId="s" styleName="S"><x:first/><q:tblBg><q:fillRef idx="2"><q:schemeClr val="accent1"/></q:fillRef><x:between/><q:effectRef idx="1"><q:schemeClr val="accent1"/></q:effectRef></q:tblBg><q:wholeTbl><q:tcStyle/></q:wholeTbl></q:tblStyle></q:tblStyleLst>"#
4434 );
4435 let styles = CT_TableStyleList::from_xml(xml.as_bytes()).unwrap();
4436 let background = styles.styles[0].background.as_ref().unwrap();
4437 let Some(StyleReference::Fill(reference)) = &background.fill_reference else {
4438 panic!("expected a fill reference");
4439 };
4440 assert_eq!(reference.index, 2);
4441 assert!(background.fill.is_none());
4442 assert_eq!(background.unsupported, ["effect"]);
4443
4444 let written = String::from_utf8(styles.to_xml().unwrap()).unwrap();
4445 assert_order(
4446 &written,
4447 &[
4448 "<x:first",
4449 "<a:tblBg>",
4450 r#"<a:fillRef idx="2">"#,
4451 "<x:between",
4452 r#"effectRef idx="1">"#,
4453 "</a:tblBg>",
4454 "<a:wholeTbl>",
4455 ],
4456 );
4457 assert_eq!(
4458 CT_TableStyleList::from_xml(written.as_bytes()).unwrap(),
4459 styles
4460 );
4461
4462 let solid = format!(
4463 r#"<a:tblStyleLst xmlns:a="{A_NS}"><a:tblStyle styleId="s" styleName="S"><a:tblBg><a:fill><a:solidFill><a:srgbClr val="112233"/></a:solidFill></a:fill></a:tblBg></a:tblStyle></a:tblStyleLst>"#
4464 );
4465 let styles = CT_TableStyleList::from_xml(solid.as_bytes()).unwrap();
4466 let background = styles.styles[0].background.as_ref().unwrap();
4467 assert!(matches!(background.fill, Some(Fill::Solid(_))));
4468 assert!(background.unsupported.is_empty());
4469 assert_eq!(
4470 CT_TableStyleList::from_xml(&styles.to_xml().unwrap()).unwrap(),
4471 styles
4472 );
4473 }
4474
4475 fn assert_regions(style_id: &str, expected: &[&str]) {
4477 let style = CT_TableStyle::builtin(style_id).unwrap();
4478 let regions = [
4479 ("wholeTbl", &style.whole_table),
4480 ("band1H", &style.band1_horizontal),
4481 ("band2H", &style.band2_horizontal),
4482 ("band1V", &style.band1_vertical),
4483 ("band2V", &style.band2_vertical),
4484 ("lastCol", &style.last_column),
4485 ("firstCol", &style.first_column),
4486 ("lastRow", &style.last_row),
4487 ("seCell", &style.south_east_cell),
4488 ("swCell", &style.south_west_cell),
4489 ("firstRow", &style.first_row),
4490 ("neCell", &style.north_east_cell),
4491 ("nwCell", &style.north_west_cell),
4492 ];
4493 let mut phrases = Vec::new();
4494 for (name, region) in regions {
4495 let Some(region) = region else { continue };
4496 if let Some(text) = ®ion.text_style {
4497 let bold = if text.bold == Some(true) { " bold" } else { "" };
4498 let color = text
4499 .color
4500 .as_ref()
4501 .map(|color| format!(" {}", scheme(color)));
4502 let color = color.unwrap_or_default();
4503 phrases.push(format!("{name} text{bold}{color}"));
4504 }
4505 let Some(cell) = ®ion.cell_style else {
4506 continue;
4507 };
4508 match (&cell.fill, &cell.fill_reference) {
4509 (Some(Fill::Solid(fill)), _) => {
4510 phrases.push(format!(
4511 "{name} fill {}",
4512 scheme(fill.color.as_ref().unwrap())
4513 ));
4514 }
4515 (Some(Fill::NoFill(_)), _) => phrases.push(format!("{name} fill none")),
4516 (_, Some(StyleReference::Fill(reference))) => phrases.push(format!(
4517 "{name} fill ref {} {}",
4518 reference.index,
4519 scheme(reference.color.as_ref().unwrap())
4520 )),
4521 _ => {}
4522 }
4523 let Some(borders) = &cell.borders else {
4524 continue;
4525 };
4526 for (edge, line) in [
4527 ("left", &borders.left),
4528 ("right", &borders.right),
4529 ("top", &borders.top),
4530 ("bottom", &borders.bottom),
4531 ("insideH", &borders.inside_horizontal),
4532 ("insideV", &borders.inside_vertical),
4533 ] {
4534 match line.as_ref().map(|line| (line.width, &line.fill)) {
4535 Some((Some(width), Some(Fill::Solid(fill)))) => phrases.push(format!(
4536 "{name} {edge} {width} {}",
4537 scheme(fill.color.as_ref().unwrap())
4538 )),
4539 Some((None, Some(Fill::NoFill(_)))) => {
4540 phrases.push(format!("{name} {edge} none"));
4541 }
4542 _ => {}
4543 }
4544 }
4545 }
4546 for phrase in expected {
4547 assert!(
4548 phrases.iter().any(|candidate| candidate == phrase),
4549 "{style_id}: {phrase} not in {phrases:#?}"
4550 );
4551 }
4552 }
4553
4554 fn scheme(color: &ColorChoice) -> String {
4555 let ColorChoice::Scheme {
4556 value, transforms, ..
4557 } = color
4558 else {
4559 panic!("expected a scheme colour, got {color:?}");
4560 };
4561 transforms
4562 .iter()
4563 .fold(value.clone(), |text, transform| match transform {
4564 ColorTransform::Tint(value) => format!("{text} tint {}%", value.0 / 1000),
4565 ColorTransform::Shade(value) => format!("{text} shade {}%", value.0 / 1000),
4566 ColorTransform::Alpha(value) => format!("{text} alpha {}%", value.0 / 1000),
4567 other => panic!("unexpected transform {other:?}"),
4568 })
4569 }
4570
4571 fn assert_ambiguous_grid_error(error: OxmlError) {
4572 assert!(matches!(
4573 error,
4574 OxmlError::InvalidValue(message)
4575 if message == "edited table grid is ambiguous because preserved column metadata cannot be associated reliably"
4576 ));
4577 }
4578
4579 fn assert_order(text: &str, tags: &[&str]) {
4580 let mut previous = 0usize;
4581 for tag in tags {
4582 let position = text[previous..].find(tag).unwrap() + previous;
4583 assert!(position >= previous, "{tag}");
4584 previous = position + tag.len();
4585 }
4586 }
4587}