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