Skip to main content

oxml_drawing/
table.rs

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/// One DrawingML `a:tbl` with typed grid, rows, cells, and banding properties.
24#[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/// The ordered column widths in one `a:tblGrid`.
36#[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/// Direction, edge emphasis, banding, and style identity from `a:tblPr`.
46#[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/// One table row and its required stored height.
62#[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/// One table cell with typed text and the OOXML merge contract retained.
74#[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    /// Direct cell text anchoring from a:tcPr, distinct from a:bodyPr.
107    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    // Unknown root attributes retain their original quotes and entity spelling.
121    raw_attribute_spellings: Vec<String>,
122}
123
124/// One DrawingML table style list from `ppt/tableStyles.xml`.
125#[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/// One named table style and its ordered region overlays.
135#[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/// The fill painted behind a whole table, from a style's `a:tblBg`.
159#[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/// Cell and text formatting contributed by one table region.
170#[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/// Fill and edge formatting contributed by a table style region.
180#[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/// The four renderer-visible table-cell edges.
192#[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/// Bold, italic, colour, and theme-font formatting for one table region.
207#[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    /// Creates a rectangular table using truncating quotients and assigns each
311    /// remainder to the final column or row.
312    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    /// Parses a complete `a:tbl` with any namespace prefix.
404    pub fn from_xml(xml: &[u8]) -> Result<Self> {
405        Self::from_xml_with_inherited_namespaces(xml, &[])
406    }
407
408    /// Parses an extracted table and retains namespace bindings inherited from
409    /// its original ancestors so the standalone writer remains namespace
410    /// complete.
411    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        // Canonical a-prefixed fragments retain the existing fixed-prefix
417        // entrypoint convention. Actual inherited/local bindings override it.
418        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    /// Serialises a self-contained table with fixed `a:` prefixes.
561    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(&current_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    /// Changes one grid-column width and keeps the column's preserved
603    /// `a:gridCol` content with it, even when other columns share the old or
604    /// the new width.
605    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    /// Inserts a row before the row at `index`, or appends one when `index`
616    /// equals the row count.
617    ///
618    /// The new row copies the height of the row above it, or of the first
619    /// row when it becomes the first. Each new cell copies the cell
620    /// properties of that row's cell in the same column and the formatting of
621    /// its first paragraph without the text. A row inserted inside a merged
622    /// cell extends the merge, and every other new cell is unmerged.
623    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    /// Removes the row at `index`, which must not be the only row.
668    ///
669    /// A merged cell across the row loses one row of its span. When the row
670    /// holds the top of such a merge, the row below takes over the origin,
671    /// with the origin's text and cell properties.
672    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    /// Inserts a grid column before the column at `index`, or appends one
706    /// when `index` equals the column count.
707    ///
708    /// The new column copies the width of the column left of it, or of the
709    /// first column when it becomes the first, and each new cell copies that
710    /// column's cell in the same row as `insert_row` does. A column inserted
711    /// inside a merged cell extends the merge, and every other new cell is
712    /// unmerged.
713    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    /// Removes the grid column at `index`, which must not be the only one.
753    ///
754    /// A merged cell across the column loses one column of its span. When
755    /// the column holds the left edge of such a merge, the column to its right
756    /// takes over the origin, with the origin's text and cell properties.
757    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    /// Returns every merged rectangle by its origin, after checking that each
793    /// row has one explicit cell per grid column and that every merge fits
794    /// the grid.
795    /// Checks the explicit grid and every modeled merge continuation.
796    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    /// Replaces the span `old` with `new` on every cell of a rectangle, which
858    /// keeps the span on whichever cells the producer stored it.
859    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
878/// One merged rectangle of explicit cells, by its origin and its spans.
879struct 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    /// Pairs every current column with the preserved metadata it is matched
1399    /// with, and anchors raw grid children to current columns, so the next
1400    /// edit is matched by position instead of by width.
1401    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    /// Inserts a column without preserved metadata before the column at
1431    /// `index`, keeping raw grid children before the column they preceded.
1432    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    /// Removes one column and its preserved metadata, moving raw grid
1447    /// children that preceded it before the next column.
1448    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(&current_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    /// Parses one cell whose DrawingML namespace is explicitly qualified.
1639    pub fn from_xml_fragment(xml: &[u8]) -> Result<Self> {
1640        Self::from_xml(xml, &[])
1641    }
1642
1643    /// Serializes the cell with a standalone DrawingML namespace.
1644    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    /// Returns an unmerged cell with this cell's properties and an empty text
1820    /// body that keeps its formatting, for a new row or column.
1821    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    /// Takes over the merge state of `edge`, a cell of a removed first row or
1847    /// column of a merge, and with `content` its text and cell properties.
1848    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
1887// The reader already validates attributes and duplicates through raw_attributes.
1888// Keep only unmodelled complete attribute tokens, including whitespace and quotes.
1889fn 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    /// Parses a complete table-style list with any DrawingML prefix.
2099    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    /// Returns the explicitly selected style, then the list default.
2164    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    /// Serialises with fixed `a:` prefixes and table-style schema order.
2175    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    /// Returns PowerPoint's definition of one of its 74 built-in table styles.
2201    ///
2202    /// A deck may name a built-in style by GUID in `a:tableStyleId` without
2203    /// defining it in `ppt/tableStyles.xml`, as python-pptx does, and
2204    /// PowerPoint then applies its own definition. Each definition is built
2205    /// from its family's rules and the variant's colour as `a:tblStyle` XML,
2206    /// which the ordinary parser reads. An unknown id returns `None`.
2207    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
2933/// PowerPoint's built-in table styles in gallery order, with their family and
2934/// colour variant. Variant 0 is a family's plain style and 1 to 6 are Accent 1
2935/// to Accent 6. Dark Style 2 pairs accents: its variants 1, 3 and 5 are
2936/// Accent 1/Accent 2, Accent 3/Accent 4 and Accent 5/Accent 6.
2937fn 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/// A family of built-in table styles, whose rules place a variant colour.
3018#[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    /// Returns the family's regions for one variant, in schema order.
3060    fn regions(self, accent: u8) -> String {
3061        // `c` is the variant colour. A plain style takes the text colour.
3062        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, &divider, 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", &divider)], ""),
3107                    part("firstCol", BOLD, &[("right", &divider)], ""),
3108                    part("lastRow", BOLD, &[("top", &divider)], 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", &divider)], &heading),
3203                    part("firstRow", &bold("lt1"), &[("bottom", &divider)], &heading),
3204                ]
3205                .concat()
3206            }
3207            Self::Medium3 => {
3208                // Only the heading fills take the variant colour.
3209                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                // The plain style tints black, the accent styles shade the accent.
3249                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", &divider)], &edge),
3272                    part("firstCol", BOLD, &[("right", &divider)], &edge),
3273                    part("lastRow", BOLD, &[("top", &divider)], &total),
3274                    part("seCell", "", &[("left", NO_LINE)], ""),
3275                    part("swCell", "", &[("right", NO_LINE)], ""),
3276                    part(
3277                        "firstRow",
3278                        BOLD,
3279                        &[("bottom", &divider)],
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                // The heading row takes the second accent of the pair.
3289                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
3319/// One table style region: its text style, its `a:tcBdr` edges in schema
3320/// order, and its fill.
3321fn 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
3334/// The six `a:tcBdr` edges in schema order.
3335fn 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
3357/// Table text in the theme's minor font and a scheme colour.
3358fn 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
3392/// A solid line whose width is in points.
3393fn 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
3415/// A `tblBg` of theme fill and effect references.
3416fn 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='&#65;&#x42;' 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='&#65;&#x42;'"));
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="&#65;"><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="&#65;"><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    /// Draws each cell as its origin spans, `h` and `v` continuation flags,
4452    /// or `.` for an unmerged cell, row by row.
4453    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        // ST_Guid is upper case in braces, and PowerPoint refuses a deck otherwise.
4730        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        // Medium Style 2 - Accent 1, python-pptx's default: a white grid on accent tints.
4742        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        // A plain variant places dk1 where an accent variant places its accent.
4762        assert_regions(
4763            "{073A0DAA-6AF3-43AB-8588-CEC1D06C72B9}",
4764            &["wholeTbl fill dk1 tint 20%", "firstRow fill dk1"],
4765        );
4766        // Light Style 1 - Accent 2: accent rules and translucent bands under black text.
4767        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        // Light Style 2 - Accent 1 fills its heading from the theme's first fill style.
4780        assert_regions(
4781            "{69012ECD-51FC-41F1-AA8D-1B2483CD663E}",
4782            &["firstRow text bold bg1", "firstRow fill ref 1 accent1"],
4783        );
4784        // Medium Style 3 - Accent 5 places its accent in the heading fills only.
4785        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        // Medium Style 4 - Accent 6: an accent grid over accent tints.
4797        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        // Dark Style 1 tints black when plain and shades the accent otherwise.
4808        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        // Dark Style 2 - Accent 3/Accent 4 heads a body of the first accent with the second.
4831        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        // Themed styles band with translucent colour over their table background.
4843        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    /// Checks phrases describing a built-in style's modelled region properties.
4918    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) = &region.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) = &region.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}