Skip to main content

oxml_drawing/
table.rs

1use std::borrow::Cow;
2use std::io::Write;
3
4use oxml_core::OxmlError;
5use oxml_core::raw_xml::{capture_element, capture_empty_element};
6use oxml_core::units::Emu;
7use oxml_core::xml::{local_name, matches_local_name};
8use oxml_core::xml_text::read_element_text;
9use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
10use quick_xml::{Reader, Writer, XmlVersion};
11
12use crate::fill::Fill;
13use crate::line::CT_LineProperties;
14use crate::namespace::{A_NS, reject_conflicting_a_prefix};
15use crate::order::OrderedRawChildren;
16use crate::style_ref::StyleReference;
17use crate::text::CT_TextBody;
18
19pub type Result<T> = std::result::Result<T, OxmlError>;
20type RawAttributes = Vec<(String, String)>;
21
22/// One DrawingML `a:tbl` with typed grid, rows, cells, and banding properties.
23#[allow(non_camel_case_types)]
24#[derive(Clone, Debug)]
25pub struct CT_Table {
26    pub properties: Option<CT_TableProperties>,
27    pub grid: CT_TableGrid,
28    pub rows: Vec<CT_TableRow>,
29    raw_attributes: RawAttributes,
30    raw_children: OrderedRawChildren,
31    original_rows: Vec<CT_TableRow>,
32}
33
34/// The ordered column widths in one `a:tblGrid`.
35#[allow(non_camel_case_types)]
36#[derive(Clone, Debug)]
37pub struct CT_TableGrid {
38    pub columns: Vec<Emu>,
39    column_raw: Vec<GridColumnRaw>,
40    raw_attributes: RawAttributes,
41    raw_children: OrderedRawChildren,
42}
43
44/// Direction, edge emphasis, banding, and style identity from `a:tblPr`.
45#[allow(non_camel_case_types)]
46#[derive(Clone, Debug, Default)]
47pub struct CT_TableProperties {
48    pub right_to_left: bool,
49    pub first_row: bool,
50    pub first_column: bool,
51    pub last_row: bool,
52    pub last_column: bool,
53    pub band_rows: bool,
54    pub band_columns: bool,
55    pub style_id: Option<String>,
56    raw_attributes: RawAttributes,
57    raw_children: OrderedRawChildren,
58}
59
60/// One table row and its required stored height.
61#[allow(non_camel_case_types)]
62#[derive(Clone, Debug)]
63pub struct CT_TableRow {
64    pub height: Emu,
65    pub cells: Vec<CT_TableCell>,
66    raw_attributes: RawAttributes,
67    raw_children: OrderedRawChildren,
68    original_cells: Vec<CT_TableCell>,
69    origin_index: usize,
70}
71
72/// One table cell with typed text and the OOXML merge contract retained.
73#[allow(non_camel_case_types)]
74#[derive(Clone, Debug)]
75pub struct CT_TableCell {
76    pub text_body: Option<CT_TextBody>,
77    pub row_span: u32,
78    pub grid_span: u32,
79    pub horizontal_merge: bool,
80    pub vertical_merge: bool,
81    pub properties: Option<CT_TableCellProperties>,
82    raw_attributes: RawAttributes,
83    raw_children: OrderedRawChildren,
84    origin_index: usize,
85}
86
87#[derive(Clone, Debug, Default)]
88struct GridColumnRaw {
89    width: Emu,
90    attributes: RawAttributes,
91    children: Vec<Vec<u8>>,
92}
93
94impl PartialEq for GridColumnRaw {
95    fn eq(&self, other: &Self) -> bool {
96        self.attributes == other.attributes && self.children == other.children
97    }
98}
99
100impl Eq for GridColumnRaw {}
101
102#[allow(non_camel_case_types)]
103#[derive(Clone, Debug, Default, Eq, PartialEq)]
104pub struct CT_TableCellProperties {
105    pub margin_left: Option<Emu>,
106    pub margin_right: Option<Emu>,
107    pub margin_top: Option<Emu>,
108    pub margin_bottom: Option<Emu>,
109    pub fill: Option<Fill>,
110    pub left: Option<CT_LineProperties>,
111    pub right: Option<CT_LineProperties>,
112    pub top: Option<CT_LineProperties>,
113    pub bottom: Option<CT_LineProperties>,
114    pub unsupported: Vec<String>,
115    raw_attributes: RawAttributes,
116    raw_children: OrderedRawChildren,
117}
118
119/// One DrawingML table style list from `ppt/tableStyles.xml`.
120#[allow(non_camel_case_types)]
121#[derive(Clone, Debug, Eq, PartialEq)]
122pub struct CT_TableStyleList {
123    pub default_style_id: Option<String>,
124    pub styles: Vec<CT_TableStyle>,
125    raw_attributes: RawAttributes,
126    raw_children: OrderedRawChildren,
127}
128
129/// One named table style and its ordered region overlays.
130#[allow(non_camel_case_types)]
131#[derive(Clone, Debug, Default, Eq, PartialEq)]
132pub struct CT_TableStyle {
133    pub style_id: String,
134    pub style_name: String,
135    pub whole_table: Option<CT_TablePartStyle>,
136    pub band1_horizontal: Option<CT_TablePartStyle>,
137    pub band2_horizontal: Option<CT_TablePartStyle>,
138    pub band1_vertical: Option<CT_TablePartStyle>,
139    pub band2_vertical: Option<CT_TablePartStyle>,
140    pub first_column: Option<CT_TablePartStyle>,
141    pub last_column: Option<CT_TablePartStyle>,
142    pub first_row: Option<CT_TablePartStyle>,
143    pub last_row: Option<CT_TablePartStyle>,
144    pub north_west_cell: Option<CT_TablePartStyle>,
145    pub north_east_cell: Option<CT_TablePartStyle>,
146    pub south_west_cell: Option<CT_TablePartStyle>,
147    pub south_east_cell: Option<CT_TablePartStyle>,
148    raw_attributes: RawAttributes,
149    raw_children: OrderedRawChildren,
150}
151
152/// Cell and text formatting contributed by one table region.
153#[allow(non_camel_case_types)]
154#[derive(Clone, Debug, Default, Eq, PartialEq)]
155pub struct CT_TablePartStyle {
156    pub cell_style: Option<CT_TableCellStyle>,
157    pub text_style: Option<CT_TableTextStyle>,
158    raw_attributes: RawAttributes,
159    raw_children: OrderedRawChildren,
160}
161
162/// Fill and edge formatting contributed by a table style region.
163#[allow(non_camel_case_types)]
164#[derive(Clone, Debug, Default, Eq, PartialEq)]
165pub struct CT_TableCellStyle {
166    pub fill: Option<Fill>,
167    pub fill_reference: Option<StyleReference>,
168    pub borders: Option<CT_TableBorders>,
169    pub unsupported: Vec<String>,
170    raw_attributes: RawAttributes,
171    raw_children: OrderedRawChildren,
172}
173
174/// The four renderer-visible table-cell edges.
175#[allow(non_camel_case_types)]
176#[derive(Clone, Debug, Default, Eq, PartialEq)]
177pub struct CT_TableBorders {
178    pub left: Option<CT_LineProperties>,
179    pub right: Option<CT_LineProperties>,
180    pub top: Option<CT_LineProperties>,
181    pub bottom: Option<CT_LineProperties>,
182    pub inside_horizontal: Option<CT_LineProperties>,
183    pub inside_vertical: Option<CT_LineProperties>,
184    pub unsupported: Vec<String>,
185    raw_attributes: RawAttributes,
186    raw_children: OrderedRawChildren,
187}
188
189/// Bold, italic, colour, and theme-font formatting for one table region.
190#[allow(non_camel_case_types)]
191#[derive(Clone, Debug, Default, Eq, PartialEq)]
192pub struct CT_TableTextStyle {
193    pub bold: Option<bool>,
194    pub italic: Option<bool>,
195    pub font_reference: Option<StyleReference>,
196    pub color: Option<crate::color::ColorChoice>,
197    raw_attributes: RawAttributes,
198    raw_children: OrderedRawChildren,
199}
200
201impl PartialEq for CT_Table {
202    fn eq(&self, other: &Self) -> bool {
203        match (self.to_xml(), other.to_xml()) {
204            (Ok(left), Ok(right)) => left == right,
205            _ => {
206                self.properties == other.properties
207                    && self.grid == other.grid
208                    && self.rows == other.rows
209                    && self.raw_attributes == other.raw_attributes
210                    && self.raw_children == other.raw_children
211            }
212        }
213    }
214}
215
216impl Eq for CT_Table {}
217
218impl PartialEq for CT_TableGrid {
219    fn eq(&self, other: &Self) -> bool {
220        match (self.canonical_xml(), other.canonical_xml()) {
221            (Ok(left), Ok(right)) => left == right,
222            _ => {
223                self.columns == other.columns
224                    && self.column_raw == other.column_raw
225                    && self.raw_attributes == other.raw_attributes
226                    && self.raw_children == other.raw_children
227            }
228        }
229    }
230}
231
232impl Eq for CT_TableGrid {}
233
234impl PartialEq for CT_TableProperties {
235    fn eq(&self, other: &Self) -> bool {
236        match (self.canonical_xml(), other.canonical_xml()) {
237            (Ok(left), Ok(right)) => left == right,
238            _ => {
239                self.right_to_left == other.right_to_left
240                    && self.first_row == other.first_row
241                    && self.first_column == other.first_column
242                    && self.last_row == other.last_row
243                    && self.last_column == other.last_column
244                    && self.band_rows == other.band_rows
245                    && self.band_columns == other.band_columns
246                    && self.style_id == other.style_id
247                    && self.raw_attributes == other.raw_attributes
248                    && self.raw_children == other.raw_children
249            }
250        }
251    }
252}
253
254impl Eq for CT_TableProperties {}
255
256impl PartialEq for CT_TableRow {
257    fn eq(&self, other: &Self) -> bool {
258        match (self.canonical_xml(), other.canonical_xml()) {
259            (Ok(left), Ok(right)) => left == right,
260            _ => {
261                self.height == other.height
262                    && self.cells == other.cells
263                    && self.raw_attributes == other.raw_attributes
264                    && self.raw_children == other.raw_children
265            }
266        }
267    }
268}
269
270impl Eq for CT_TableRow {}
271
272impl PartialEq for CT_TableCell {
273    fn eq(&self, other: &Self) -> bool {
274        match (self.canonical_xml(), other.canonical_xml()) {
275            (Ok(left), Ok(right)) => left == right,
276            _ => {
277                self.text_body == other.text_body
278                    && self.row_span == other.row_span
279                    && self.grid_span == other.grid_span
280                    && self.horizontal_merge == other.horizontal_merge
281                    && self.vertical_merge == other.vertical_merge
282                    && self.properties == other.properties
283                    && self.raw_attributes == other.raw_attributes
284                    && self.raw_children == other.raw_children
285            }
286        }
287    }
288}
289
290impl Eq for CT_TableCell {}
291
292impl CT_Table {
293    /// Creates a rectangular table using truncating quotients and assigns each
294    /// remainder to the final column or row.
295    pub fn new(rows: usize, columns: usize, width: Emu, height: Emu) -> Result<Self> {
296        let row_count = u32::try_from(rows).map_err(|_| {
297            OxmlError::InvalidValue("table row count exceeds the DrawingML span range".to_owned())
298        })?;
299        let column_count = u32::try_from(columns).map_err(|_| {
300            OxmlError::InvalidValue(
301                "table column count exceeds the DrawingML span range".to_owned(),
302            )
303        })?;
304        if row_count == 0 || column_count == 0 {
305            return Err(OxmlError::InvalidValue(
306                "table requires at least one row and one column".to_owned(),
307            ));
308        }
309        if width.0 <= 0 || height.0 <= 0 {
310            return Err(OxmlError::InvalidValue(
311                "table width and height must be positive".to_owned(),
312            ));
313        }
314
315        let column_width = Emu(width.0 / i64::from(column_count));
316        let row_height = Emu(height.0 / i64::from(row_count));
317        if column_width.0 <= 0 || row_height.0 <= 0 {
318            return Err(OxmlError::InvalidValue(
319                "table extent is too small for its row or column count".to_owned(),
320            ));
321        }
322
323        let mut column_widths = vec![column_width; columns];
324        let column_remainder = width.0 - column_width.0 * i64::from(column_count);
325        column_widths[columns - 1].0 += column_remainder;
326        let column_raw = column_widths
327            .iter()
328            .copied()
329            .map(|width| GridColumnRaw {
330                width,
331                ..GridColumnRaw::default()
332            })
333            .collect();
334        let grid = CT_TableGrid {
335            columns: column_widths,
336            column_raw,
337            raw_attributes: Vec::new(),
338            raw_children: OrderedRawChildren::default(),
339        };
340        let row_remainder = height.0 - row_height.0 * i64::from(row_count);
341        let mut table_rows = Vec::with_capacity(rows);
342        for row_index in 0..rows {
343            let mut cells = Vec::with_capacity(columns);
344            for column_index in 0..columns {
345                cells.push(CT_TableCell {
346                    text_body: Some(CT_TextBody::new()),
347                    row_span: 1,
348                    grid_span: 1,
349                    horizontal_merge: false,
350                    vertical_merge: false,
351                    properties: Some(CT_TableCellProperties::default()),
352                    raw_attributes: Vec::new(),
353                    raw_children: OrderedRawChildren::default(),
354                    origin_index: column_index,
355                });
356            }
357            table_rows.push(CT_TableRow {
358                height: Emu(row_height.0
359                    + if row_index + 1 == rows {
360                        row_remainder
361                    } else {
362                        0
363                    }),
364                original_cells: cells.clone(),
365                cells,
366                raw_attributes: Vec::new(),
367                raw_children: OrderedRawChildren::default(),
368                origin_index: row_index,
369            });
370        }
371
372        Ok(Self {
373            properties: Some(CT_TableProperties {
374                first_row: true,
375                band_rows: true,
376                ..CT_TableProperties::default()
377            }),
378            grid,
379            original_rows: table_rows.clone(),
380            rows: table_rows,
381            raw_attributes: Vec::new(),
382            raw_children: OrderedRawChildren::default(),
383        })
384    }
385
386    /// Parses a complete `a:tbl` with any namespace prefix.
387    pub fn from_xml(xml: &[u8]) -> Result<Self> {
388        let mut reader = Reader::from_reader(xml);
389        let mut buffer = Vec::new();
390        loop {
391            match reader.read_event_into(&mut buffer)? {
392                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tbl") => {
393                    reject_conflicting_a_prefix(&start)?;
394                    return Self::from_element(&mut reader, &start);
395                }
396                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tbl") => {
397                    reject_conflicting_a_prefix(&start)?;
398                    return Err(missing("a:tblGrid and a:tr"));
399                }
400                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
401                Event::Eof => return Err(missing("a:tbl")),
402                _ => {}
403            }
404            buffer.clear();
405        }
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        let mut table = Self::from_xml(xml)?;
417        for (prefix, uri) in inherited_namespaces {
418            if prefix == "a" {
419                if uri != A_NS && local_a_namespace.as_deref() != Some(A_NS) {
420                    return Err(OxmlError::InvalidValue(
421                        "inherited xmlns:a conflicts with the fixed DrawingML writer namespace"
422                            .to_owned(),
423                    ));
424                }
425                continue;
426            }
427            if prefix == "xml" {
428                if uri != "http://www.w3.org/XML/1998/namespace" {
429                    return Err(OxmlError::InvalidValue(
430                        "inherited xmlns:xml has the wrong namespace URI".to_owned(),
431                    ));
432                }
433                continue;
434            }
435            let attribute_name = namespace_attribute_name(prefix)?;
436            if table
437                .raw_attributes
438                .iter()
439                .any(|(name, _)| name == &attribute_name)
440            {
441                continue;
442            }
443            table.raw_attributes.push((attribute_name, uri.to_owned()));
444        }
445        Ok(table)
446    }
447
448    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
449        let mut properties = None;
450        let mut grid = None;
451        let mut rows = Vec::new();
452        let mut raw_children = OrderedRawChildren::default();
453        let mut boundary = 0usize;
454        let mut buffer = Vec::new();
455        loop {
456            match reader.read_event_into(&mut buffer)? {
457                Event::Start(child) => {
458                    let name = local_name(child.name().as_ref()).to_vec();
459                    if matches!(name.as_slice(), b"tblPr" | b"tblGrid" | b"tr") {
460                        reject_conflicting_a_prefix(&child)?;
461                    }
462                    let raw = capture_element(reader, &child)?;
463                    capture_table_child(
464                        &name,
465                        raw,
466                        &mut properties,
467                        &mut grid,
468                        &mut rows,
469                        &mut raw_children,
470                        &mut boundary,
471                    )?;
472                }
473                Event::Empty(child) => {
474                    let name = local_name(child.name().as_ref()).to_vec();
475                    if matches!(name.as_slice(), b"tblPr" | b"tblGrid" | b"tr") {
476                        reject_conflicting_a_prefix(&child)?;
477                    }
478                    let raw = capture_empty_element(&child)?;
479                    capture_table_child(
480                        &name,
481                        raw,
482                        &mut properties,
483                        &mut grid,
484                        &mut rows,
485                        &mut raw_children,
486                        &mut boundary,
487                    )?;
488                }
489                Event::End(end) if matches_local_name(end.name().as_ref(), b"tbl") => break,
490                Event::Eof => return Err(missing("closing a:tbl")),
491                _ => {}
492            }
493            buffer.clear();
494        }
495        let grid = grid.ok_or_else(|| missing("a:tblGrid"))?;
496        if rows.is_empty() {
497            return Err(missing("at least one a:tr"));
498        }
499        Ok(Self {
500            properties,
501            grid,
502            original_rows: rows.clone(),
503            rows,
504            raw_attributes: raw_attributes(start, &[], true)?,
505            raw_children,
506        })
507    }
508
509    /// Serialises a self-contained table with fixed `a:` prefixes.
510    pub fn to_xml(&self) -> Result<Vec<u8>> {
511        if self.rows.is_empty() {
512            return Err(missing("at least one a:tr"));
513        }
514        let mut writer = Writer::new(Vec::new());
515        let mut start = BytesStart::new("a:tbl");
516        start.push_attribute(("xmlns:a", A_NS));
517        push_attributes(&mut start, &self.raw_attributes);
518        writer.write_event(Event::Start(start))?;
519        emit_raw(&mut writer, self.raw_children.at(0))?;
520        if let Some(properties) = &self.properties {
521            properties.write_xml(&mut writer)?;
522        }
523        emit_raw(&mut writer, self.raw_children.at(1))?;
524        self.grid.write_xml(&mut writer)?;
525        let current_row_origins = self
526            .rows
527            .iter()
528            .map(|row| row.origin_index)
529            .collect::<Vec<_>>();
530        let original_row_origins = self
531            .original_rows
532            .iter()
533            .map(|row| row.origin_index)
534            .collect::<Vec<_>>();
535        let row_matches = matched_original_indices(&current_row_origins, &original_row_origins);
536        let original_to_current = invert_matches(&row_matches, self.original_rows.len());
537        for boundary in 0..=self.rows.len() {
538            emit_raw(
539                &mut writer,
540                self.raw_children
541                    .at_reconciled(boundary, 2, &original_to_current, self.rows.len()),
542            )?;
543            if let Some(row) = self.rows.get(boundary) {
544                row.write_xml(&mut writer)?;
545            }
546        }
547        writer.write_event(Event::End(BytesEnd::new("a:tbl")))?;
548        Ok(writer.into_inner())
549    }
550}
551
552fn root_a_namespace_declaration(xml: &[u8]) -> Result<Option<String>> {
553    let mut reader = Reader::from_reader(xml);
554    let mut buffer = Vec::new();
555    loop {
556        match reader.read_event_into(&mut buffer)? {
557            Event::Start(start) | Event::Empty(start) => {
558                return decoded_attr(&start, b"xmlns:a");
559            }
560            Event::Eof => return Ok(None),
561            _ => buffer.clear(),
562        }
563    }
564}
565
566fn namespace_attribute_name(prefix: &str) -> Result<String> {
567    if prefix.is_empty() {
568        return Ok("xmlns".to_owned());
569    }
570    if prefix == "xmlns" {
571        return Err(OxmlError::InvalidValue(
572            "xmlns cannot be used as an inherited namespace prefix".to_owned(),
573        ));
574    }
575    let mut chars = prefix.chars();
576    let Some(first) = chars.next() else {
577        return Ok("xmlns".to_owned());
578    };
579    if !is_ncname_start(first) || !chars.all(is_ncname_continue) {
580        return Err(OxmlError::InvalidValue(format!(
581            "invalid inherited namespace prefix {prefix}"
582        )));
583    }
584    Ok(format!("xmlns:{prefix}"))
585}
586
587fn is_ncname_start(value: char) -> bool {
588    matches!(
589        value,
590        'A'..='Z'
591            | '_'
592            | 'a'..='z'
593            | '\u{C0}'..='\u{D6}'
594            | '\u{D8}'..='\u{F6}'
595            | '\u{F8}'..='\u{2FF}'
596            | '\u{370}'..='\u{37D}'
597            | '\u{37F}'..='\u{1FFF}'
598            | '\u{200C}'..='\u{200D}'
599            | '\u{2070}'..='\u{218F}'
600            | '\u{2C00}'..='\u{2FEF}'
601            | '\u{3001}'..='\u{D7FF}'
602            | '\u{F900}'..='\u{FDCF}'
603            | '\u{FDF0}'..='\u{FFFD}'
604            | '\u{10000}'..='\u{EFFFF}'
605    )
606}
607
608fn is_ncname_continue(value: char) -> bool {
609    is_ncname_start(value)
610        || matches!(
611            value,
612            '-' | '.' | '0'..='9' | '\u{B7}' | '\u{300}'..='\u{36F}' | '\u{203F}'..='\u{2040}'
613        )
614}
615
616#[allow(clippy::too_many_arguments)]
617fn capture_table_child(
618    name: &[u8],
619    raw: Vec<u8>,
620    properties: &mut Option<CT_TableProperties>,
621    grid: &mut Option<CT_TableGrid>,
622    rows: &mut Vec<CT_TableRow>,
623    raw_children: &mut OrderedRawChildren,
624    boundary: &mut usize,
625) -> Result<()> {
626    match name {
627        b"tblPr" if *boundary == 0 && properties.is_none() => {
628            *properties = Some(CT_TableProperties::from_xml(&raw)?);
629            *boundary = 1;
630        }
631        b"tblGrid" if *boundary <= 1 && grid.is_none() => {
632            *grid = Some(CT_TableGrid::from_xml(&raw)?);
633            *boundary = 2;
634        }
635        b"tr" if grid.is_some() => {
636            let mut row = CT_TableRow::from_xml(&raw)?;
637            row.origin_index = rows.len();
638            rows.push(row);
639            *boundary = 2 + rows.len();
640        }
641        b"tblPr" | b"tblGrid" | b"tr" => {
642            return Err(OxmlError::InvalidValue(
643                "a:tbl children must be a:tblPr?, a:tblGrid, then a:tr+".to_owned(),
644            ));
645        }
646        _ => raw_children.push(*boundary, raw),
647    }
648    Ok(())
649}
650
651fn matched_original_indices<T: PartialEq>(current: &[T], original: &[T]) -> Vec<Option<usize>> {
652    let mut matches = vec![None; current.len()];
653    let mut original_used = vec![false; original.len()];
654
655    for index in 0..current.len().min(original.len()) {
656        if current[index] == original[index] {
657            matches[index] = Some(index);
658            original_used[index] = true;
659        }
660    }
661    for (current_index, value) in current.iter().enumerate() {
662        if matches[current_index].is_some() {
663            continue;
664        }
665        if let Some(original_index) = original
666            .iter()
667            .enumerate()
668            .find(|(original_index, candidate)| {
669                !original_used[*original_index] && *candidate == value
670            })
671            .map(|(original_index, _)| original_index)
672        {
673            matches[current_index] = Some(original_index);
674            original_used[original_index] = true;
675        }
676    }
677    for (current_index, matched) in matches.iter_mut().enumerate() {
678        if matched.is_none() && current_index < original.len() && !original_used[current_index] {
679            *matched = Some(current_index);
680            original_used[current_index] = true;
681        }
682    }
683    matches
684}
685
686fn invert_matches(
687    current_to_original: &[Option<usize>],
688    original_len: usize,
689) -> Vec<Option<usize>> {
690    let mut original_to_current = vec![None; original_len];
691    for (current_index, original_index) in current_to_original.iter().enumerate() {
692        if let Some(original_index) = original_index {
693            original_to_current[*original_index] = Some(current_index);
694        }
695    }
696    original_to_current
697}
698
699impl CT_TableProperties {
700    fn canonical_xml(&self) -> Result<Vec<u8>> {
701        let mut writer = Writer::new(Vec::new());
702        self.write_xml(&mut writer)?;
703        Ok(writer.into_inner())
704    }
705
706    fn from_xml(xml: &[u8]) -> Result<Self> {
707        let mut reader = Reader::from_reader(xml);
708        let mut buffer = Vec::new();
709        loop {
710            match reader.read_event_into(&mut buffer)? {
711                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblPr") => {
712                    reject_conflicting_a_prefix(&start)?;
713                    return Self::from_element(&mut reader, &start);
714                }
715                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblPr") => {
716                    reject_conflicting_a_prefix(&start)?;
717                    return Self::from_start(&start, None, OrderedRawChildren::default());
718                }
719                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
720                Event::Eof => return Err(missing("a:tblPr")),
721                _ => {}
722            }
723            buffer.clear();
724        }
725    }
726
727    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
728        let mut style_id = None;
729        let mut raw_children = OrderedRawChildren::default();
730        let mut boundary = 0usize;
731        let mut buffer = Vec::new();
732        loop {
733            match reader.read_event_into(&mut buffer)? {
734                Event::Start(child)
735                    if matches_local_name(child.name().as_ref(), b"tableStyleId") =>
736                {
737                    reject_conflicting_a_prefix(&child)?;
738                    if style_id.is_some() {
739                        return Err(OxmlError::InvalidValue(
740                            "duplicate a:tableStyleId".to_owned(),
741                        ));
742                    }
743                    style_id = Some(read_element_text(reader, child.name()));
744                    boundary = 1;
745                }
746                Event::Empty(child)
747                    if matches_local_name(child.name().as_ref(), b"tableStyleId") =>
748                {
749                    reject_conflicting_a_prefix(&child)?;
750                    if style_id.replace(String::new()).is_some() {
751                        return Err(OxmlError::InvalidValue(
752                            "duplicate a:tableStyleId".to_owned(),
753                        ));
754                    }
755                    boundary = 1;
756                }
757                Event::Start(child) => {
758                    raw_children.push(boundary, capture_element(reader, &child)?)
759                }
760                Event::Empty(child) => raw_children.push(boundary, capture_empty_element(&child)?),
761                Event::End(end) if matches_local_name(end.name().as_ref(), b"tblPr") => break,
762                Event::Eof => return Err(missing("closing a:tblPr")),
763                _ => {}
764            }
765            buffer.clear();
766        }
767        Self::from_start(start, style_id, raw_children)
768    }
769
770    fn from_start(
771        start: &BytesStart<'_>,
772        style_id: Option<String>,
773        raw_children: OrderedRawChildren,
774    ) -> Result<Self> {
775        Ok(Self {
776            right_to_left: bool_attr(start, b"rtl")?.unwrap_or(false),
777            first_row: bool_attr(start, b"firstRow")?.unwrap_or(false),
778            first_column: bool_attr(start, b"firstCol")?.unwrap_or(false),
779            last_row: bool_attr(start, b"lastRow")?.unwrap_or(false),
780            last_column: bool_attr(start, b"lastCol")?.unwrap_or(false),
781            band_rows: bool_attr(start, b"bandRow")?.unwrap_or(false),
782            band_columns: bool_attr(start, b"bandCol")?.unwrap_or(false),
783            style_id,
784            raw_attributes: raw_attributes(
785                start,
786                &[
787                    b"rtl",
788                    b"firstRow",
789                    b"firstCol",
790                    b"lastRow",
791                    b"lastCol",
792                    b"bandRow",
793                    b"bandCol",
794                ],
795                false,
796            )?,
797            raw_children,
798        })
799    }
800
801    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
802        let mut start = BytesStart::new("a:tblPr");
803        push_true(&mut start, "rtl", self.right_to_left);
804        push_true(&mut start, "firstRow", self.first_row);
805        push_true(&mut start, "firstCol", self.first_column);
806        push_true(&mut start, "lastRow", self.last_row);
807        push_true(&mut start, "lastCol", self.last_column);
808        push_true(&mut start, "bandRow", self.band_rows);
809        push_true(&mut start, "bandCol", self.band_columns);
810        push_attributes(&mut start, &self.raw_attributes);
811        if self.style_id.is_none() && self.raw_children.is_empty() {
812            writer.write_event(Event::Empty(start))?;
813            return Ok(());
814        }
815        writer.write_event(Event::Start(start))?;
816        emit_raw(writer, self.raw_children.at(0))?;
817        if let Some(style_id) = &self.style_id {
818            writer.write_event(Event::Start(BytesStart::new("a:tableStyleId")))?;
819            writer.write_event(Event::Text(BytesText::new(style_id)))?;
820            writer.write_event(Event::End(BytesEnd::new("a:tableStyleId")))?;
821        }
822        emit_raw(writer, self.raw_children.at(1))?;
823        writer.write_event(Event::End(BytesEnd::new("a:tblPr")))?;
824        Ok(())
825    }
826}
827
828impl CT_TableGrid {
829    fn canonical_xml(&self) -> Result<Vec<u8>> {
830        let mut writer = Writer::new(Vec::new());
831        self.write_xml(&mut writer)?;
832        Ok(writer.into_inner())
833    }
834
835    fn from_xml(xml: &[u8]) -> Result<Self> {
836        let mut reader = Reader::from_reader(xml);
837        let mut buffer = Vec::new();
838        loop {
839            match reader.read_event_into(&mut buffer)? {
840                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblGrid") => {
841                    reject_conflicting_a_prefix(&start)?;
842                    return Self::from_element(&mut reader, &start);
843                }
844                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblGrid") => {
845                    reject_conflicting_a_prefix(&start)?;
846                    return Err(missing("at least one a:gridCol"));
847                }
848                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
849                Event::Eof => return Err(missing("a:tblGrid")),
850                _ => {}
851            }
852            buffer.clear();
853        }
854    }
855
856    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
857        let mut columns = Vec::new();
858        let mut column_raw = Vec::new();
859        let mut raw_children = OrderedRawChildren::default();
860        let mut buffer = Vec::new();
861        loop {
862            match reader.read_event_into(&mut buffer)? {
863                Event::Start(child) if matches_local_name(child.name().as_ref(), b"gridCol") => {
864                    reject_conflicting_a_prefix(&child)?;
865                    let raw = capture_element(reader, &child)?;
866                    let (width, metadata) = parse_grid_column(&raw)?;
867                    columns.push(width);
868                    column_raw.push(metadata);
869                }
870                Event::Empty(child) if matches_local_name(child.name().as_ref(), b"gridCol") => {
871                    reject_conflicting_a_prefix(&child)?;
872                    let width = Emu(required_i64(&child, b"w")?);
873                    columns.push(width);
874                    column_raw.push(GridColumnRaw {
875                        width,
876                        attributes: raw_attributes(&child, &[b"w"], false)?,
877                        children: Vec::new(),
878                    });
879                }
880                Event::Start(child) => {
881                    raw_children.push(columns.len(), capture_element(reader, &child)?)
882                }
883                Event::Empty(child) => {
884                    raw_children.push(columns.len(), capture_empty_element(&child)?)
885                }
886                Event::End(end) if matches_local_name(end.name().as_ref(), b"tblGrid") => break,
887                Event::Eof => return Err(missing("closing a:tblGrid")),
888                _ => {}
889            }
890            buffer.clear();
891        }
892        if columns.is_empty() {
893            return Err(missing("at least one a:gridCol"));
894        }
895        Ok(Self {
896            columns,
897            column_raw,
898            raw_attributes: raw_attributes(start, &[], false)?,
899            raw_children,
900        })
901    }
902
903    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
904        if self.columns.is_empty() {
905            return Err(missing("at least one a:gridCol"));
906        }
907        let column_matches = self.matched_column_indices()?;
908        let original_to_current = invert_matches(&column_matches, self.column_raw.len());
909        let mut start = BytesStart::new("a:tblGrid");
910        push_attributes(&mut start, &self.raw_attributes);
911        writer.write_event(Event::Start(start))?;
912        for (boundary, (width, metadata_index)) in
913            self.columns.iter().zip(&column_matches).enumerate()
914        {
915            emit_raw(
916                writer,
917                self.raw_children.at_reconciled(
918                    boundary,
919                    0,
920                    &original_to_current,
921                    self.columns.len(),
922                ),
923            )?;
924            let metadata = metadata_index.map(|index| &self.column_raw[index]);
925            let mut column = BytesStart::new("a:gridCol");
926            let width = width.0.to_string();
927            column.push_attribute(("w", width.as_str()));
928            if let Some(metadata) = metadata {
929                push_attributes(&mut column, &metadata.attributes);
930            }
931            match metadata {
932                Some(metadata) if !metadata.children.is_empty() => {
933                    writer.write_event(Event::Start(column))?;
934                    for child in &metadata.children {
935                        writer.get_mut().write_all(child)?;
936                    }
937                    writer.write_event(Event::End(BytesEnd::new("a:gridCol")))?;
938                }
939                _ => writer.write_event(Event::Empty(column))?,
940            }
941        }
942        emit_raw(
943            writer,
944            self.raw_children.at_reconciled(
945                self.columns.len(),
946                0,
947                &original_to_current,
948                self.columns.len(),
949            ),
950        )?;
951        writer.write_event(Event::End(BytesEnd::new("a:tblGrid")))?;
952        Ok(())
953    }
954
955    fn matched_column_indices(&self) -> Result<Vec<Option<usize>>> {
956        let original_widths = self
957            .column_raw
958            .iter()
959            .map(|metadata| metadata.width)
960            .collect::<Vec<_>>();
961        if self.columns == original_widths {
962            return Ok((0..self.columns.len()).map(Some).collect());
963        }
964
965        let identity_sensitive = self
966            .column_raw
967            .iter()
968            .any(|metadata| !metadata.attributes.is_empty() || !metadata.children.is_empty())
969            || !self.raw_children.is_empty();
970        if !identity_sensitive {
971            return Ok(matched_original_indices(&self.columns, &original_widths));
972        }
973
974        if has_duplicate_values(&self.columns) || has_duplicate_values(&original_widths) {
975            return Err(ambiguous_grid_metadata());
976        }
977
978        let exact_matches = self
979            .columns
980            .iter()
981            .map(|width| {
982                original_widths
983                    .iter()
984                    .position(|original| original == width)
985            })
986            .collect::<Vec<_>>();
987        let unmatched_current = exact_matches
988            .iter()
989            .filter(|matched| matched.is_none())
990            .count();
991        let unmatched_original = original_widths
992            .iter()
993            .enumerate()
994            .filter(|(index, _)| !exact_matches.contains(&Some(*index)))
995            .count();
996        if unmatched_current > 0
997            && unmatched_original > 0
998            && (unmatched_current > 1 || unmatched_original > 1)
999        {
1000            return Err(ambiguous_grid_metadata());
1001        }
1002        Ok(matched_original_indices(&self.columns, &original_widths))
1003    }
1004}
1005
1006fn has_duplicate_values<T: PartialEq>(values: &[T]) -> bool {
1007    values
1008        .iter()
1009        .enumerate()
1010        .any(|(index, value)| values[..index].contains(value))
1011}
1012
1013fn ambiguous_grid_metadata() -> OxmlError {
1014    OxmlError::InvalidValue(
1015        "edited table grid is ambiguous because preserved column metadata cannot be associated reliably"
1016            .to_owned(),
1017    )
1018}
1019
1020fn parse_grid_column(xml: &[u8]) -> Result<(Emu, GridColumnRaw)> {
1021    let mut reader = Reader::from_reader(xml);
1022    let mut buffer = Vec::new();
1023    loop {
1024        match reader.read_event_into(&mut buffer)? {
1025            Event::Start(start) => {
1026                let width = Emu(required_i64(&start, b"w")?);
1027                let attributes = raw_attributes(&start, &[b"w"], false)?;
1028                let mut children = Vec::new();
1029                loop {
1030                    buffer.clear();
1031                    match reader.read_event_into(&mut buffer)? {
1032                        Event::Start(child) => children.push(capture_element(&mut reader, &child)?),
1033                        Event::Empty(child) => children.push(capture_empty_element(&child)?),
1034                        Event::End(end) if matches_local_name(end.name().as_ref(), b"gridCol") => {
1035                            break;
1036                        }
1037                        Event::Eof => return Err(missing("closing a:gridCol")),
1038                        _ => {}
1039                    }
1040                }
1041                return Ok((
1042                    width,
1043                    GridColumnRaw {
1044                        width,
1045                        attributes,
1046                        children,
1047                    },
1048                ));
1049            }
1050            Event::Eof => return Err(missing("a:gridCol")),
1051            _ => {}
1052        }
1053        buffer.clear();
1054    }
1055}
1056
1057impl CT_TableRow {
1058    fn canonical_xml(&self) -> Result<Vec<u8>> {
1059        let mut writer = Writer::new(Vec::new());
1060        self.write_xml(&mut writer)?;
1061        Ok(writer.into_inner())
1062    }
1063
1064    fn from_xml(xml: &[u8]) -> Result<Self> {
1065        let mut reader = Reader::from_reader(xml);
1066        let mut buffer = Vec::new();
1067        loop {
1068            match reader.read_event_into(&mut buffer)? {
1069                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tr") => {
1070                    reject_conflicting_a_prefix(&start)?;
1071                    return Self::from_element(&mut reader, &start);
1072                }
1073                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tr") => {
1074                    reject_conflicting_a_prefix(&start)?;
1075                    return Err(missing("at least one a:tc"));
1076                }
1077                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1078                Event::Eof => return Err(missing("a:tr")),
1079                _ => {}
1080            }
1081            buffer.clear();
1082        }
1083    }
1084
1085    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1086        let mut cells = Vec::new();
1087        let mut raw_children = OrderedRawChildren::default();
1088        let mut buffer = Vec::new();
1089        loop {
1090            match reader.read_event_into(&mut buffer)? {
1091                Event::Start(child) if matches_local_name(child.name().as_ref(), b"tc") => {
1092                    reject_conflicting_a_prefix(&child)?;
1093                    let mut cell = CT_TableCell::from_xml(&capture_element(reader, &child)?)?;
1094                    cell.origin_index = cells.len();
1095                    cells.push(cell);
1096                }
1097                Event::Empty(child) if matches_local_name(child.name().as_ref(), b"tc") => {
1098                    reject_conflicting_a_prefix(&child)?;
1099                    let mut cell = CT_TableCell::from_xml(&capture_empty_element(&child)?)?;
1100                    cell.origin_index = cells.len();
1101                    cells.push(cell);
1102                }
1103                Event::Start(child) => {
1104                    raw_children.push(cells.len(), capture_element(reader, &child)?)
1105                }
1106                Event::Empty(child) => {
1107                    raw_children.push(cells.len(), capture_empty_element(&child)?)
1108                }
1109                Event::End(end) if matches_local_name(end.name().as_ref(), b"tr") => break,
1110                Event::Eof => return Err(missing("closing a:tr")),
1111                _ => {}
1112            }
1113            buffer.clear();
1114        }
1115        if cells.is_empty() {
1116            return Err(missing("at least one a:tc"));
1117        }
1118        Ok(Self {
1119            height: Emu(required_i64(start, b"h")?),
1120            original_cells: cells.clone(),
1121            cells,
1122            raw_attributes: raw_attributes(start, &[b"h"], false)?,
1123            raw_children,
1124            origin_index: 0,
1125        })
1126    }
1127
1128    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1129        if self.cells.is_empty() {
1130            return Err(missing("at least one a:tc"));
1131        }
1132        let mut start = BytesStart::new("a:tr");
1133        let height = self.height.0.to_string();
1134        start.push_attribute(("h", height.as_str()));
1135        push_attributes(&mut start, &self.raw_attributes);
1136        writer.write_event(Event::Start(start))?;
1137        let current_cell_origins = self
1138            .cells
1139            .iter()
1140            .map(|cell| cell.origin_index)
1141            .collect::<Vec<_>>();
1142        let original_cell_origins = self
1143            .original_cells
1144            .iter()
1145            .map(|cell| cell.origin_index)
1146            .collect::<Vec<_>>();
1147        let cell_matches = matched_original_indices(&current_cell_origins, &original_cell_origins);
1148        let original_to_current = invert_matches(&cell_matches, self.original_cells.len());
1149        for boundary in 0..=self.cells.len() {
1150            emit_raw(
1151                writer,
1152                self.raw_children.at_reconciled(
1153                    boundary,
1154                    0,
1155                    &original_to_current,
1156                    self.cells.len(),
1157                ),
1158            )?;
1159            if let Some(cell) = self.cells.get(boundary) {
1160                cell.write_xml(writer)?;
1161            }
1162        }
1163        writer.write_event(Event::End(BytesEnd::new("a:tr")))?;
1164        Ok(())
1165    }
1166}
1167
1168impl CT_TableCell {
1169    fn canonical_xml(&self) -> Result<Vec<u8>> {
1170        let mut writer = Writer::new(Vec::new());
1171        self.write_xml(&mut writer)?;
1172        Ok(writer.into_inner())
1173    }
1174
1175    fn from_xml(xml: &[u8]) -> Result<Self> {
1176        let mut reader = Reader::from_reader(xml);
1177        let mut buffer = Vec::new();
1178        loop {
1179            match reader.read_event_into(&mut buffer)? {
1180                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tc") => {
1181                    reject_conflicting_a_prefix(&start)?;
1182                    return Self::from_element(&mut reader, &start);
1183                }
1184                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tc") => {
1185                    reject_conflicting_a_prefix(&start)?;
1186                    return Self::from_start(&start, None, None, OrderedRawChildren::default());
1187                }
1188                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1189                Event::Eof => return Err(missing("a:tc")),
1190                _ => {}
1191            }
1192            buffer.clear();
1193        }
1194    }
1195
1196    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1197        let mut text_body = None;
1198        let mut properties = None;
1199        let mut raw_children = OrderedRawChildren::default();
1200        let mut boundary = 0usize;
1201        let mut buffer = Vec::new();
1202        loop {
1203            match reader.read_event_into(&mut buffer)? {
1204                Event::Start(child) => {
1205                    let name = local_name(child.name().as_ref()).to_vec();
1206                    if matches!(name.as_slice(), b"txBody" | b"tcPr") {
1207                        reject_conflicting_a_prefix(&child)?;
1208                    }
1209                    let raw = capture_element(reader, &child)?;
1210                    capture_cell_child(
1211                        &name,
1212                        raw,
1213                        &mut text_body,
1214                        &mut properties,
1215                        &mut raw_children,
1216                        &mut boundary,
1217                    )?;
1218                }
1219                Event::Empty(child) => {
1220                    let name = local_name(child.name().as_ref()).to_vec();
1221                    if matches!(name.as_slice(), b"txBody" | b"tcPr") {
1222                        reject_conflicting_a_prefix(&child)?;
1223                    }
1224                    let raw = capture_empty_element(&child)?;
1225                    capture_cell_child(
1226                        &name,
1227                        raw,
1228                        &mut text_body,
1229                        &mut properties,
1230                        &mut raw_children,
1231                        &mut boundary,
1232                    )?;
1233                }
1234                Event::End(end) if matches_local_name(end.name().as_ref(), b"tc") => break,
1235                Event::Eof => return Err(missing("closing a:tc")),
1236                _ => {}
1237            }
1238            buffer.clear();
1239        }
1240        Self::from_start(start, text_body, properties, raw_children)
1241    }
1242
1243    fn from_start(
1244        start: &BytesStart<'_>,
1245        text_body: Option<CT_TextBody>,
1246        properties: Option<CT_TableCellProperties>,
1247        raw_children: OrderedRawChildren,
1248    ) -> Result<Self> {
1249        Ok(Self {
1250            text_body,
1251            row_span: positive_u32_attr(start, b"rowSpan")?.unwrap_or(1),
1252            grid_span: positive_u32_attr(start, b"gridSpan")?.unwrap_or(1),
1253            horizontal_merge: bool_attr(start, b"hMerge")?.unwrap_or(false),
1254            vertical_merge: bool_attr(start, b"vMerge")?.unwrap_or(false),
1255            properties,
1256            raw_attributes: raw_attributes(
1257                start,
1258                &[b"rowSpan", b"gridSpan", b"hMerge", b"vMerge"],
1259                false,
1260            )?,
1261            raw_children,
1262            origin_index: 0,
1263        })
1264    }
1265
1266    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1267        if self.row_span == 0 || self.grid_span == 0 {
1268            return Err(OxmlError::InvalidValue(
1269                "a:tc spans must be positive".to_owned(),
1270            ));
1271        }
1272        let mut start = BytesStart::new("a:tc");
1273        let row_span = self.row_span.to_string();
1274        let grid_span = self.grid_span.to_string();
1275        if self.row_span != 1 {
1276            start.push_attribute(("rowSpan", row_span.as_str()));
1277        }
1278        if self.grid_span != 1 {
1279            start.push_attribute(("gridSpan", grid_span.as_str()));
1280        }
1281        push_true(&mut start, "hMerge", self.horizontal_merge);
1282        push_true(&mut start, "vMerge", self.vertical_merge);
1283        push_attributes(&mut start, &self.raw_attributes);
1284        if self.text_body.is_none() && self.properties.is_none() && self.raw_children.is_empty() {
1285            writer.write_event(Event::Empty(start))?;
1286            return Ok(());
1287        }
1288        writer.write_event(Event::Start(start))?;
1289        emit_raw(writer, self.raw_children.at(0))?;
1290        if let Some(text_body) = &self.text_body {
1291            writer
1292                .get_mut()
1293                .write_all(&text_body.to_xml().map_err(|error| {
1294                    OxmlError::InvalidValue(format!("invalid table-cell text body: {error}"))
1295                })?)?;
1296        }
1297        emit_raw(writer, self.raw_children.at(1))?;
1298        if let Some(properties) = &self.properties {
1299            properties.write_xml(writer)?;
1300        }
1301        emit_raw(writer, self.raw_children.at(2))?;
1302        writer.write_event(Event::End(BytesEnd::new("a:tc")))?;
1303        Ok(())
1304    }
1305}
1306
1307fn capture_cell_child(
1308    name: &[u8],
1309    raw: Vec<u8>,
1310    text_body: &mut Option<CT_TextBody>,
1311    properties: &mut Option<CT_TableCellProperties>,
1312    raw_children: &mut OrderedRawChildren,
1313    boundary: &mut usize,
1314) -> Result<()> {
1315    match name {
1316        b"txBody" if *boundary == 0 && text_body.is_none() => {
1317            *text_body = Some(CT_TextBody::from_xml(&raw).map_err(|error| {
1318                OxmlError::InvalidValue(format!("invalid a:tc/a:txBody: {error}"))
1319            })?);
1320            *boundary = 1;
1321        }
1322        b"tcPr" if *boundary <= 1 && properties.is_none() => {
1323            *properties = Some(CT_TableCellProperties::from_xml(&raw)?);
1324            *boundary = 2;
1325        }
1326        b"txBody" | b"tcPr" => {
1327            return Err(OxmlError::InvalidValue(
1328                "a:tc children must be a:txBody? then a:tcPr?".to_owned(),
1329            ));
1330        }
1331        _ => raw_children.push(*boundary, raw),
1332    }
1333    Ok(())
1334}
1335
1336impl CT_TableCellProperties {
1337    fn from_xml(xml: &[u8]) -> Result<Self> {
1338        let mut reader = Reader::from_reader(xml);
1339        let mut buffer = Vec::new();
1340        loop {
1341            match reader.read_event_into(&mut buffer)? {
1342                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcPr") => {
1343                    return Self::from_element(&mut reader, &start);
1344                }
1345                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcPr") => {
1346                    return Self::from_start(&start);
1347                }
1348                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1349                Event::Eof => return Err(missing("a:tcPr")),
1350                _ => {}
1351            }
1352            buffer.clear();
1353        }
1354    }
1355
1356    fn from_start(start: &BytesStart<'_>) -> Result<Self> {
1357        Ok(Self {
1358            margin_left: optional_emu_attr(start, b"marL")?,
1359            margin_right: optional_emu_attr(start, b"marR")?,
1360            margin_top: optional_emu_attr(start, b"marT")?,
1361            margin_bottom: optional_emu_attr(start, b"marB")?,
1362            raw_attributes: raw_attributes(start, &[b"marL", b"marR", b"marT", b"marB"], false)?,
1363            ..Self::default()
1364        })
1365    }
1366
1367    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1368        let mut properties = Self::from_start(start)?;
1369        let mut boundary = 0usize;
1370        let mut buffer = Vec::new();
1371        loop {
1372            match reader.read_event_into(&mut buffer)? {
1373                Event::Start(child) => {
1374                    let name = local_name(child.name().as_ref()).to_vec();
1375                    let raw = capture_element(reader, &child)?;
1376                    properties.capture_child(&name, raw, &mut boundary)?;
1377                }
1378                Event::Empty(child) => {
1379                    let name = local_name(child.name().as_ref()).to_vec();
1380                    let raw = capture_empty_element(&child)?;
1381                    properties.capture_child(&name, raw, &mut boundary)?;
1382                }
1383                Event::End(end) if matches_local_name(end.name().as_ref(), b"tcPr") => break,
1384                Event::Eof => return Err(missing("closing a:tcPr")),
1385                _ => {}
1386            }
1387            buffer.clear();
1388        }
1389        Ok(properties)
1390    }
1391
1392    fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
1393        let slot = cell_property_slot(name);
1394        ensure_schema_order(slot, *boundary, "a:tcPr")?;
1395        match name {
1396            b"lnL" => set_once(&mut self.left, parse_named_line(&raw)?, "a:tcPr/a:lnL")?,
1397            b"lnR" => set_once(&mut self.right, parse_named_line(&raw)?, "a:tcPr/a:lnR")?,
1398            b"lnT" => set_once(&mut self.top, parse_named_line(&raw)?, "a:tcPr/a:lnT")?,
1399            b"lnB" => set_once(&mut self.bottom, parse_named_line(&raw)?, "a:tcPr/a:lnB")?,
1400            name if is_fill(name) => set_once(&mut self.fill, parse_fill(&raw)?, "a:tcPr fill")?,
1401            b"lnTlToBr" | b"lnBlToTr" => {
1402                self.unsupported.push("diagonal border".to_owned());
1403                self.raw_children.push(slot.unwrap_or(*boundary), raw);
1404            }
1405            b"cell3D" => {
1406                self.unsupported.push("3-D properties".to_owned());
1407                self.raw_children.push(slot.unwrap_or(*boundary), raw);
1408            }
1409            b"effectLst" | b"effectDag" => {
1410                self.unsupported.push("effects".to_owned());
1411                self.raw_children.push(slot.unwrap_or(*boundary), raw);
1412            }
1413            _ => self.raw_children.push(slot.unwrap_or(*boundary), raw),
1414        }
1415        if let Some(slot) = slot {
1416            *boundary = (*boundary).max(slot + 1);
1417        }
1418        Ok(())
1419    }
1420
1421    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1422        let mut start = BytesStart::new("a:tcPr");
1423        push_optional_emu(&mut start, "marL", self.margin_left);
1424        push_optional_emu(&mut start, "marR", self.margin_right);
1425        push_optional_emu(&mut start, "marT", self.margin_top);
1426        push_optional_emu(&mut start, "marB", self.margin_bottom);
1427        push_attributes(&mut start, &self.raw_attributes);
1428        let has_modelled = self.left.is_some()
1429            || self.right.is_some()
1430            || self.top.is_some()
1431            || self.bottom.is_some()
1432            || self.fill.is_some();
1433        if !has_modelled && self.raw_children.is_empty() {
1434            writer.write_event(Event::Empty(start))?;
1435            return Ok(());
1436        }
1437        writer.write_event(Event::Start(start))?;
1438        emit_raw(writer, self.raw_children.at(0))?;
1439        write_optional_named_line(writer, "a:lnL", self.left.as_ref())?;
1440        emit_raw(writer, self.raw_children.at(1))?;
1441        write_optional_named_line(writer, "a:lnR", self.right.as_ref())?;
1442        emit_raw(writer, self.raw_children.at(2))?;
1443        write_optional_named_line(writer, "a:lnT", self.top.as_ref())?;
1444        emit_raw(writer, self.raw_children.at(3))?;
1445        write_optional_named_line(writer, "a:lnB", self.bottom.as_ref())?;
1446        for slot in 4..=7 {
1447            emit_raw(writer, self.raw_children.at(slot))?;
1448        }
1449        if let Some(fill) = &self.fill {
1450            fill.write_xml(writer).map_err(drawing_error)?;
1451        }
1452        for slot in 8..=10 {
1453            emit_raw(writer, self.raw_children.at(slot))?;
1454        }
1455        writer.write_event(Event::End(BytesEnd::new("a:tcPr")))?;
1456        Ok(())
1457    }
1458}
1459
1460impl CT_TableStyleList {
1461    /// Parses a complete table-style list with any DrawingML prefix.
1462    pub fn from_xml(xml: &[u8]) -> Result<Self> {
1463        let mut reader = Reader::from_reader(xml);
1464        let mut buffer = Vec::new();
1465        loop {
1466            match reader.read_event_into(&mut buffer)? {
1467                Event::Start(start)
1468                    if matches_local_name(start.name().as_ref(), b"tblStyleLst") =>
1469                {
1470                    reject_conflicting_a_prefix(&start)?;
1471                    return Self::from_element(&mut reader, &start);
1472                }
1473                Event::Empty(start)
1474                    if matches_local_name(start.name().as_ref(), b"tblStyleLst") =>
1475                {
1476                    reject_conflicting_a_prefix(&start)?;
1477                    return Ok(Self {
1478                        default_style_id: decoded_attr(&start, b"def")?,
1479                        styles: Vec::new(),
1480                        raw_attributes: raw_attributes(&start, &[b"def"], true)?,
1481                        raw_children: OrderedRawChildren::default(),
1482                    });
1483                }
1484                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1485                Event::Eof => return Err(missing("a:tblStyleLst")),
1486                _ => {}
1487            }
1488            buffer.clear();
1489        }
1490    }
1491
1492    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1493        let mut styles = Vec::new();
1494        let mut raw_children = OrderedRawChildren::default();
1495        let mut buffer = Vec::new();
1496        loop {
1497            match reader.read_event_into(&mut buffer)? {
1498                Event::Start(child) if matches_local_name(child.name().as_ref(), b"tblStyle") => {
1499                    let raw = capture_element(reader, &child)?;
1500                    styles.push(CT_TableStyle::from_xml(&raw)?);
1501                }
1502                Event::Empty(child) if matches_local_name(child.name().as_ref(), b"tblStyle") => {
1503                    let raw = capture_empty_element(&child)?;
1504                    styles.push(CT_TableStyle::from_xml(&raw)?);
1505                }
1506                Event::Start(child) => {
1507                    raw_children.push(styles.len(), capture_element(reader, &child)?)
1508                }
1509                Event::Empty(child) => {
1510                    raw_children.push(styles.len(), capture_empty_element(&child)?)
1511                }
1512                Event::End(end) if matches_local_name(end.name().as_ref(), b"tblStyleLst") => break,
1513                Event::Eof => return Err(missing("closing a:tblStyleLst")),
1514                _ => {}
1515            }
1516            buffer.clear();
1517        }
1518        Ok(Self {
1519            default_style_id: decoded_attr(start, b"def")?,
1520            styles,
1521            raw_attributes: raw_attributes(start, &[b"def"], true)?,
1522            raw_children,
1523        })
1524    }
1525
1526    /// Returns the explicitly selected style, then the list default.
1527    pub fn style(&self, style_id: Option<&str>) -> Option<&CT_TableStyle> {
1528        style_id
1529            .and_then(|style_id| self.styles.iter().find(|style| style.style_id == style_id))
1530            .or_else(|| {
1531                self.default_style_id.as_deref().and_then(|style_id| {
1532                    self.styles.iter().find(|style| style.style_id == style_id)
1533                })
1534            })
1535    }
1536
1537    /// Serialises with fixed `a:` prefixes and table-style schema order.
1538    pub fn to_xml(&self) -> Result<Vec<u8>> {
1539        let mut writer = Writer::new(Vec::new());
1540        let mut start = BytesStart::new("a:tblStyleLst");
1541        start.push_attribute(("xmlns:a", A_NS));
1542        if let Some(default_style_id) = &self.default_style_id {
1543            start.push_attribute(("def", default_style_id.as_str()));
1544        }
1545        push_attributes(&mut start, &self.raw_attributes);
1546        if self.styles.is_empty() && self.raw_children.is_empty() {
1547            writer.write_event(Event::Empty(start))?;
1548            return Ok(writer.into_inner());
1549        }
1550        writer.write_event(Event::Start(start))?;
1551        for boundary in 0..=self.styles.len() {
1552            emit_raw(&mut writer, self.raw_children.at(boundary))?;
1553            if let Some(style) = self.styles.get(boundary) {
1554                style.write_xml(&mut writer)?;
1555            }
1556        }
1557        writer.write_event(Event::End(BytesEnd::new("a:tblStyleLst")))?;
1558        Ok(writer.into_inner())
1559    }
1560}
1561
1562impl CT_TableStyle {
1563    fn from_xml(xml: &[u8]) -> Result<Self> {
1564        let mut reader = Reader::from_reader(xml);
1565        let mut buffer = Vec::new();
1566        loop {
1567            match reader.read_event_into(&mut buffer)? {
1568                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tblStyle") => {
1569                    return Self::from_element(&mut reader, &start);
1570                }
1571                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tblStyle") => {
1572                    return Self::from_start(&start);
1573                }
1574                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1575                Event::Eof => return Err(missing("a:tblStyle")),
1576                _ => {}
1577            }
1578            buffer.clear();
1579        }
1580    }
1581
1582    fn from_start(start: &BytesStart<'_>) -> Result<Self> {
1583        Ok(Self {
1584            style_id: required_string(start, b"styleId")?,
1585            style_name: required_string(start, b"styleName")?,
1586            raw_attributes: raw_attributes(start, &[b"styleId", b"styleName"], false)?,
1587            ..Self::default()
1588        })
1589    }
1590
1591    fn from_element(reader: &mut Reader<&[u8]>, start: &BytesStart<'_>) -> Result<Self> {
1592        let mut style = Self::from_start(start)?;
1593        let mut boundary = 0usize;
1594        let mut buffer = Vec::new();
1595        loop {
1596            match reader.read_event_into(&mut buffer)? {
1597                Event::Start(child) => {
1598                    let name = local_name(child.name().as_ref()).to_vec();
1599                    let raw = capture_element(reader, &child)?;
1600                    style.capture_child(&name, raw, &mut boundary)?;
1601                }
1602                Event::Empty(child) => {
1603                    let name = local_name(child.name().as_ref()).to_vec();
1604                    let raw = capture_empty_element(&child)?;
1605                    style.capture_child(&name, raw, &mut boundary)?;
1606                }
1607                Event::End(end) if matches_local_name(end.name().as_ref(), b"tblStyle") => break,
1608                Event::Eof => return Err(missing("closing a:tblStyle")),
1609                _ => {}
1610            }
1611            buffer.clear();
1612        }
1613        Ok(style)
1614    }
1615
1616    fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
1617        let Some(slot) = table_style_slot(name) else {
1618            self.raw_children.push(*boundary, raw);
1619            return Ok(());
1620        };
1621        if slot < *boundary {
1622            return Err(OxmlError::InvalidValue(
1623                "a:tblStyle children violate schema order".to_owned(),
1624            ));
1625        }
1626        let destination = self.region_mut(name);
1627        if destination.is_some() {
1628            return Err(OxmlError::InvalidValue(format!(
1629                "duplicate a:{}",
1630                String::from_utf8_lossy(name)
1631            )));
1632        }
1633        *destination = Some(CT_TablePartStyle::from_xml(&raw, name)?);
1634        *boundary = slot + 1;
1635        Ok(())
1636    }
1637
1638    fn region_mut(&mut self, name: &[u8]) -> &mut Option<CT_TablePartStyle> {
1639        match name {
1640            b"wholeTbl" => &mut self.whole_table,
1641            b"band1H" => &mut self.band1_horizontal,
1642            b"band2H" => &mut self.band2_horizontal,
1643            b"band1V" => &mut self.band1_vertical,
1644            b"band2V" => &mut self.band2_vertical,
1645            b"firstCol" => &mut self.first_column,
1646            b"lastCol" => &mut self.last_column,
1647            b"firstRow" => &mut self.first_row,
1648            b"lastRow" => &mut self.last_row,
1649            b"nwCell" => &mut self.north_west_cell,
1650            b"neCell" => &mut self.north_east_cell,
1651            b"swCell" => &mut self.south_west_cell,
1652            b"seCell" => &mut self.south_east_cell,
1653            _ => unreachable!("modelled table region"),
1654        }
1655    }
1656
1657    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1658        let mut start = BytesStart::new("a:tblStyle");
1659        start.push_attribute(("styleId", self.style_id.as_str()));
1660        start.push_attribute(("styleName", self.style_name.as_str()));
1661        push_attributes(&mut start, &self.raw_attributes);
1662        writer.write_event(Event::Start(start))?;
1663        let ordered = [
1664            ("a:wholeTbl", self.whole_table.as_ref()),
1665            ("a:band1H", self.band1_horizontal.as_ref()),
1666            ("a:band2H", self.band2_horizontal.as_ref()),
1667            ("a:band1V", self.band1_vertical.as_ref()),
1668            ("a:band2V", self.band2_vertical.as_ref()),
1669            ("a:lastCol", self.last_column.as_ref()),
1670            ("a:firstCol", self.first_column.as_ref()),
1671            ("a:lastRow", self.last_row.as_ref()),
1672            ("a:seCell", self.south_east_cell.as_ref()),
1673            ("a:swCell", self.south_west_cell.as_ref()),
1674            ("a:firstRow", self.first_row.as_ref()),
1675            ("a:neCell", self.north_east_cell.as_ref()),
1676            ("a:nwCell", self.north_west_cell.as_ref()),
1677        ];
1678        for (slot, (tag, region)) in ordered.into_iter().enumerate() {
1679            emit_raw(writer, self.raw_children.at(slot))?;
1680            if let Some(region) = region {
1681                region.write_xml(writer, tag)?;
1682            }
1683        }
1684        emit_raw(writer, self.raw_children.at(13))?;
1685        emit_raw(writer, self.raw_children.at(14))?;
1686        writer.write_event(Event::End(BytesEnd::new("a:tblStyle")))?;
1687        Ok(())
1688    }
1689}
1690
1691impl CT_TablePartStyle {
1692    fn from_xml(xml: &[u8], expected: &[u8]) -> Result<Self> {
1693        let mut reader = Reader::from_reader(xml);
1694        let mut buffer = Vec::new();
1695        loop {
1696            match reader.read_event_into(&mut buffer)? {
1697                Event::Start(start) if matches_local_name(start.name().as_ref(), expected) => {
1698                    let mut value = Self {
1699                        raw_attributes: raw_attributes(&start, &[], false)?,
1700                        ..Self::default()
1701                    };
1702                    let mut boundary = 0usize;
1703                    loop {
1704                        buffer.clear();
1705                        match reader.read_event_into(&mut buffer)? {
1706                            Event::Start(child) => {
1707                                let name = local_name(child.name().as_ref()).to_vec();
1708                                let raw = capture_element(&mut reader, &child)?;
1709                                value.capture_child(&name, raw, &mut boundary)?;
1710                            }
1711                            Event::Empty(child) => {
1712                                let name = local_name(child.name().as_ref()).to_vec();
1713                                let raw = capture_empty_element(&child)?;
1714                                value.capture_child(&name, raw, &mut boundary)?;
1715                            }
1716                            Event::End(end)
1717                                if matches_local_name(end.name().as_ref(), expected) =>
1718                            {
1719                                return Ok(value);
1720                            }
1721                            Event::Eof => return Err(missing("closing table style region")),
1722                            _ => {}
1723                        }
1724                    }
1725                }
1726                Event::Empty(start) if matches_local_name(start.name().as_ref(), expected) => {
1727                    return Ok(Self {
1728                        raw_attributes: raw_attributes(&start, &[], false)?,
1729                        ..Self::default()
1730                    });
1731                }
1732                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1733                Event::Eof => return Err(missing("table style region")),
1734                _ => {}
1735            }
1736            buffer.clear();
1737        }
1738    }
1739
1740    fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
1741        match name {
1742            b"tcTxStyle" if self.text_style.is_none() && *boundary == 0 => {
1743                self.text_style = Some(CT_TableTextStyle::from_xml(&raw)?);
1744                *boundary = 1;
1745            }
1746            b"tcStyle" if self.cell_style.is_none() => {
1747                self.cell_style = Some(CT_TableCellStyle::from_xml(&raw)?);
1748                *boundary = 2;
1749            }
1750            b"tcTxStyle" | b"tcStyle" => {
1751                return Err(OxmlError::InvalidValue(
1752                    "table region style children violate schema order or are duplicated".to_owned(),
1753                ));
1754            }
1755            _ => self.raw_children.push(*boundary, raw),
1756        }
1757        Ok(())
1758    }
1759
1760    fn write_xml<W: Write>(&self, writer: &mut Writer<W>, tag: &str) -> Result<()> {
1761        let mut start = BytesStart::new(tag);
1762        push_attributes(&mut start, &self.raw_attributes);
1763        if self.text_style.is_none() && self.cell_style.is_none() && self.raw_children.is_empty() {
1764            writer.write_event(Event::Empty(start))?;
1765            return Ok(());
1766        }
1767        writer.write_event(Event::Start(start))?;
1768        emit_raw(writer, self.raw_children.at(0))?;
1769        if let Some(text_style) = &self.text_style {
1770            text_style.write_xml(writer)?;
1771        }
1772        emit_raw(writer, self.raw_children.at(1))?;
1773        if let Some(cell_style) = &self.cell_style {
1774            cell_style.write_xml(writer)?;
1775        }
1776        emit_raw(writer, self.raw_children.at(2))?;
1777        writer.write_event(Event::End(BytesEnd::new(tag)))?;
1778        Ok(())
1779    }
1780}
1781
1782impl CT_TableCellStyle {
1783    fn from_xml(xml: &[u8]) -> Result<Self> {
1784        let mut reader = Reader::from_reader(xml);
1785        let mut buffer = Vec::new();
1786        loop {
1787            match reader.read_event_into(&mut buffer)? {
1788                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcStyle") => {
1789                    let mut value = Self {
1790                        raw_attributes: raw_attributes(&start, &[], false)?,
1791                        ..Self::default()
1792                    };
1793                    let mut boundary = 0usize;
1794                    loop {
1795                        buffer.clear();
1796                        match reader.read_event_into(&mut buffer)? {
1797                            Event::Start(child) => {
1798                                let name = local_name(child.name().as_ref()).to_vec();
1799                                let raw = capture_element(&mut reader, &child)?;
1800                                value.capture_child(&name, raw, &mut boundary)?;
1801                            }
1802                            Event::Empty(child) => {
1803                                let name = local_name(child.name().as_ref()).to_vec();
1804                                let raw = capture_empty_element(&child)?;
1805                                value.capture_child(&name, raw, &mut boundary)?;
1806                            }
1807                            Event::End(end)
1808                                if matches_local_name(end.name().as_ref(), b"tcStyle") =>
1809                            {
1810                                return Ok(value);
1811                            }
1812                            Event::Eof => return Err(missing("closing a:tcStyle")),
1813                            _ => {}
1814                        }
1815                    }
1816                }
1817                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcStyle") => {
1818                    return Ok(Self {
1819                        raw_attributes: raw_attributes(&start, &[], false)?,
1820                        ..Self::default()
1821                    });
1822                }
1823                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1824                Event::Eof => return Err(missing("a:tcStyle")),
1825                _ => {}
1826            }
1827            buffer.clear();
1828        }
1829    }
1830
1831    fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
1832        let slot = match name {
1833            b"tcBdr" => Some(0),
1834            b"fill" => Some(1),
1835            name if is_fill(name) => Some(1),
1836            b"fillRef" => Some(2),
1837            b"cell3D" => Some(3),
1838            _ => None,
1839        };
1840        ensure_schema_order(slot, *boundary, "a:tcStyle")?;
1841        match name {
1842            b"tcBdr" if self.borders.is_some() => {
1843                return Err(OxmlError::InvalidValue("duplicate a:tcBdr".to_owned()));
1844            }
1845            b"tcBdr" => self.borders = Some(CT_TableBorders::from_xml(&raw)?),
1846            b"fill" => {
1847                if self.fill.is_some() || self.fill_reference.is_some() {
1848                    return Err(OxmlError::InvalidValue(
1849                        "duplicate a:tcStyle fill choice".to_owned(),
1850                    ));
1851                }
1852                if let Some(fill) = parse_fill_wrapper(&raw)? {
1853                    self.fill = Some(fill);
1854                } else {
1855                    self.unsupported.push("fill form".to_owned());
1856                    self.raw_children.push(slot.unwrap(), raw);
1857                }
1858            }
1859            name if is_fill(name) => {
1860                if self.fill.is_some() || self.fill_reference.is_some() {
1861                    return Err(OxmlError::InvalidValue(
1862                        "duplicate a:tcStyle fill choice".to_owned(),
1863                    ));
1864                }
1865                self.fill = Some(parse_fill(&raw)?);
1866            }
1867            b"fillRef" => {
1868                if self.fill.is_some() || self.fill_reference.is_some() {
1869                    return Err(OxmlError::InvalidValue(
1870                        "duplicate a:tcStyle fill choice".to_owned(),
1871                    ));
1872                }
1873                self.fill_reference = Some(parse_style_reference(&raw)?);
1874            }
1875            b"cell3D" => {
1876                self.unsupported.push("3-D properties".to_owned());
1877                self.raw_children.push(slot.unwrap(), raw);
1878            }
1879            _ => self.raw_children.push(*boundary, raw),
1880        }
1881        if let Some(slot) = slot {
1882            *boundary = (*boundary).max(slot + 1);
1883        }
1884        Ok(())
1885    }
1886
1887    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
1888        let mut start = BytesStart::new("a:tcStyle");
1889        push_attributes(&mut start, &self.raw_attributes);
1890        let has_modelled =
1891            self.borders.is_some() || self.fill.is_some() || self.fill_reference.is_some();
1892        if !has_modelled && self.raw_children.is_empty() {
1893            writer.write_event(Event::Empty(start))?;
1894            return Ok(());
1895        }
1896        writer.write_event(Event::Start(start))?;
1897        emit_raw(writer, self.raw_children.at(0))?;
1898        if let Some(borders) = &self.borders {
1899            borders.write_xml(writer)?;
1900        }
1901        emit_raw(writer, self.raw_children.at(1))?;
1902        if let Some(fill) = &self.fill {
1903            writer.write_event(Event::Start(BytesStart::new("a:fill")))?;
1904            fill.write_xml(writer).map_err(drawing_error)?;
1905            writer.write_event(Event::End(BytesEnd::new("a:fill")))?;
1906        }
1907        emit_raw(writer, self.raw_children.at(2))?;
1908        if let Some(reference) = &self.fill_reference {
1909            reference.write_xml(writer).map_err(drawing_error)?;
1910        }
1911        emit_raw(writer, self.raw_children.at(3))?;
1912        emit_raw(writer, self.raw_children.at(4))?;
1913        writer.write_event(Event::End(BytesEnd::new("a:tcStyle")))?;
1914        Ok(())
1915    }
1916}
1917
1918impl CT_TableBorders {
1919    fn from_xml(xml: &[u8]) -> Result<Self> {
1920        let mut reader = Reader::from_reader(xml);
1921        let mut buffer = Vec::new();
1922        loop {
1923            match reader.read_event_into(&mut buffer)? {
1924                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcBdr") => {
1925                    let mut value = Self {
1926                        raw_attributes: raw_attributes(&start, &[], false)?,
1927                        ..Self::default()
1928                    };
1929                    let mut boundary = 0usize;
1930                    loop {
1931                        buffer.clear();
1932                        match reader.read_event_into(&mut buffer)? {
1933                            Event::Start(child) => {
1934                                let name = local_name(child.name().as_ref()).to_vec();
1935                                let raw = capture_element(&mut reader, &child)?;
1936                                value.capture_child(&name, raw, &mut boundary)?;
1937                            }
1938                            Event::Empty(child) => {
1939                                let name = local_name(child.name().as_ref()).to_vec();
1940                                let raw = capture_empty_element(&child)?;
1941                                value.capture_child(&name, raw, &mut boundary)?;
1942                            }
1943                            Event::End(end)
1944                                if matches_local_name(end.name().as_ref(), b"tcBdr") =>
1945                            {
1946                                return Ok(value);
1947                            }
1948                            Event::Eof => return Err(missing("closing a:tcBdr")),
1949                            _ => {}
1950                        }
1951                    }
1952                }
1953                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcBdr") => {
1954                    return Ok(Self {
1955                        raw_attributes: raw_attributes(&start, &[], false)?,
1956                        ..Self::default()
1957                    });
1958                }
1959                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
1960                Event::Eof => return Err(missing("a:tcBdr")),
1961                _ => {}
1962            }
1963            buffer.clear();
1964        }
1965    }
1966
1967    fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
1968        let slot = table_border_slot(name);
1969        ensure_schema_order(slot, *boundary, "a:tcBdr")?;
1970        match name {
1971            b"left" => self.capture_border(&raw, slot, "left")?,
1972            b"right" => self.capture_border(&raw, slot, "right")?,
1973            b"top" => self.capture_border(&raw, slot, "top")?,
1974            b"bottom" => self.capture_border(&raw, slot, "bottom")?,
1975            b"lnL" => self.capture_border(&raw, slot, "lnL")?,
1976            b"lnR" => self.capture_border(&raw, slot, "lnR")?,
1977            b"lnT" => self.capture_border(&raw, slot, "lnT")?,
1978            b"lnB" => self.capture_border(&raw, slot, "lnB")?,
1979            b"insideH" => self.capture_border(&raw, slot, "insideH")?,
1980            b"insideV" => self.capture_border(&raw, slot, "insideV")?,
1981            b"tl2br" | b"tr2bl" | b"lnTlToBr" | b"lnBlToTr" => {
1982                self.unsupported.push("diagonal border".to_owned());
1983                self.raw_children.push(slot.unwrap_or(*boundary), raw);
1984            }
1985            _ => self.raw_children.push(slot.unwrap_or(*boundary), raw),
1986        }
1987        if let Some(slot) = slot {
1988            *boundary = (*boundary).max(slot + 1);
1989        }
1990        Ok(())
1991    }
1992
1993    fn capture_border(&mut self, raw: &[u8], slot: Option<usize>, name: &str) -> Result<()> {
1994        let line = if name.starts_with("ln") {
1995            Some(parse_named_line(raw)?)
1996        } else {
1997            parse_border_wrapper(raw)?
1998        };
1999        let Some(line) = line else {
2000            self.unsupported.push("border form".to_owned());
2001            self.raw_children.push(slot.unwrap_or(0), raw.to_vec());
2002            return Ok(());
2003        };
2004        let target = match name {
2005            "left" | "lnL" => &mut self.left,
2006            "right" | "lnR" => &mut self.right,
2007            "top" | "lnT" => &mut self.top,
2008            "bottom" | "lnB" => &mut self.bottom,
2009            "insideH" => &mut self.inside_horizontal,
2010            "insideV" => &mut self.inside_vertical,
2011            _ => unreachable!("table border name"),
2012        };
2013        set_once(target, line, "a:tcBdr edge")
2014    }
2015
2016    fn has_values(&self) -> bool {
2017        self.left.is_some()
2018            || self.right.is_some()
2019            || self.top.is_some()
2020            || self.bottom.is_some()
2021            || self.inside_horizontal.is_some()
2022            || self.inside_vertical.is_some()
2023            || !self.raw_attributes.is_empty()
2024            || !self.raw_children.is_empty()
2025    }
2026
2027    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2028        let mut start = BytesStart::new("a:tcBdr");
2029        push_attributes(&mut start, &self.raw_attributes);
2030        if !self.has_values() {
2031            writer.write_event(Event::Empty(start))?;
2032            return Ok(());
2033        }
2034        writer.write_event(Event::Start(start))?;
2035        let lines = [
2036            ("a:left", self.left.as_ref()),
2037            ("a:right", self.right.as_ref()),
2038            ("a:top", self.top.as_ref()),
2039            ("a:bottom", self.bottom.as_ref()),
2040            ("a:insideH", self.inside_horizontal.as_ref()),
2041            ("a:insideV", self.inside_vertical.as_ref()),
2042        ];
2043        for (slot, (tag, line)) in lines.into_iter().enumerate() {
2044            emit_raw(writer, self.raw_children.at(slot))?;
2045            write_optional_border_wrapper(writer, tag, line)?;
2046        }
2047        for slot in 6..=8 {
2048            emit_raw(writer, self.raw_children.at(slot))?;
2049        }
2050        writer.write_event(Event::End(BytesEnd::new("a:tcBdr")))?;
2051        Ok(())
2052    }
2053}
2054
2055impl CT_TableTextStyle {
2056    fn from_xml(xml: &[u8]) -> Result<Self> {
2057        let mut reader = Reader::from_reader(xml);
2058        let mut buffer = Vec::new();
2059        loop {
2060            match reader.read_event_into(&mut buffer)? {
2061                Event::Start(start) if matches_local_name(start.name().as_ref(), b"tcTxStyle") => {
2062                    let mut value = Self {
2063                        bold: bool_attr(&start, b"b")?,
2064                        italic: bool_attr(&start, b"i")?,
2065                        raw_attributes: raw_attributes(&start, &[b"b", b"i"], false)?,
2066                        ..Self::default()
2067                    };
2068                    let mut boundary = 0usize;
2069                    loop {
2070                        buffer.clear();
2071                        match reader.read_event_into(&mut buffer)? {
2072                            Event::Start(child) => {
2073                                let name = local_name(child.name().as_ref()).to_vec();
2074                                let raw = capture_element(&mut reader, &child)?;
2075                                value.capture_child(&name, raw, &mut boundary)?;
2076                            }
2077                            Event::Empty(child) => {
2078                                let name = local_name(child.name().as_ref()).to_vec();
2079                                let raw = capture_empty_element(&child)?;
2080                                value.capture_child(&name, raw, &mut boundary)?;
2081                            }
2082                            Event::End(end)
2083                                if matches_local_name(end.name().as_ref(), b"tcTxStyle") =>
2084                            {
2085                                return Ok(value);
2086                            }
2087                            Event::Eof => return Err(missing("closing a:tcTxStyle")),
2088                            _ => {}
2089                        }
2090                    }
2091                }
2092                Event::Empty(start) if matches_local_name(start.name().as_ref(), b"tcTxStyle") => {
2093                    return Ok(Self {
2094                        bold: bool_attr(&start, b"b")?,
2095                        italic: bool_attr(&start, b"i")?,
2096                        raw_attributes: raw_attributes(&start, &[b"b", b"i"], false)?,
2097                        ..Self::default()
2098                    });
2099                }
2100                Event::Start(start) | Event::Empty(start) => return Err(unexpected(&start)),
2101                Event::Eof => return Err(missing("a:tcTxStyle")),
2102                _ => {}
2103            }
2104            buffer.clear();
2105        }
2106    }
2107
2108    fn capture_child(&mut self, name: &[u8], raw: Vec<u8>, boundary: &mut usize) -> Result<()> {
2109        match name {
2110            b"fontRef" if self.font_reference.is_none() => {
2111                self.font_reference = Some(parse_style_reference(&raw)?);
2112                *boundary = 1;
2113            }
2114            name if is_color(name) && self.color.is_none() => {
2115                self.color = Some(parse_color(&raw)?);
2116                *boundary = 2;
2117            }
2118            b"fontRef" => {
2119                return Err(OxmlError::InvalidValue(
2120                    "duplicate a:tcTxStyle/a:fontRef".to_owned(),
2121                ));
2122            }
2123            name if is_color(name) => {
2124                return Err(OxmlError::InvalidValue(format!(
2125                    "duplicate a:tcTxStyle colour {}",
2126                    String::from_utf8_lossy(name)
2127                )));
2128            }
2129            _ => self.raw_children.push(*boundary, raw),
2130        }
2131        Ok(())
2132    }
2133
2134    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
2135        let mut start = BytesStart::new("a:tcTxStyle");
2136        push_optional_bool(&mut start, "b", self.bold);
2137        push_optional_bool(&mut start, "i", self.italic);
2138        push_attributes(&mut start, &self.raw_attributes);
2139        if self.font_reference.is_none() && self.color.is_none() && self.raw_children.is_empty() {
2140            writer.write_event(Event::Empty(start))?;
2141            return Ok(());
2142        }
2143        writer.write_event(Event::Start(start))?;
2144        emit_raw(writer, self.raw_children.at(0))?;
2145        if let Some(reference) = &self.font_reference {
2146            reference.write_xml(writer).map_err(drawing_error)?;
2147        }
2148        emit_raw(writer, self.raw_children.at(1))?;
2149        if let Some(color) = &self.color {
2150            color.to_xml(writer).map_err(drawing_error)?;
2151        }
2152        emit_raw(writer, self.raw_children.at(2))?;
2153        writer.write_event(Event::End(BytesEnd::new("a:tcTxStyle")))?;
2154        Ok(())
2155    }
2156}
2157
2158fn table_style_slot(name: &[u8]) -> Option<usize> {
2159    match name {
2160        b"wholeTbl" => Some(0),
2161        b"band1H" => Some(1),
2162        b"band2H" => Some(2),
2163        b"band1V" => Some(3),
2164        b"band2V" => Some(4),
2165        b"lastCol" => Some(5),
2166        b"firstCol" => Some(6),
2167        b"lastRow" => Some(7),
2168        b"seCell" => Some(8),
2169        b"swCell" => Some(9),
2170        b"firstRow" => Some(10),
2171        b"neCell" => Some(11),
2172        b"nwCell" => Some(12),
2173        _ => None,
2174    }
2175}
2176
2177fn table_border_slot(name: &[u8]) -> Option<usize> {
2178    match name {
2179        b"left" | b"lnL" => Some(0),
2180        b"right" | b"lnR" => Some(1),
2181        b"top" | b"lnT" => Some(2),
2182        b"bottom" | b"lnB" => Some(3),
2183        b"insideH" => Some(4),
2184        b"insideV" => Some(5),
2185        b"tl2br" | b"lnTlToBr" => Some(6),
2186        b"tr2bl" | b"lnBlToTr" => Some(7),
2187        _ => None,
2188    }
2189}
2190
2191fn cell_property_slot(name: &[u8]) -> Option<usize> {
2192    match name {
2193        b"lnL" => Some(0),
2194        b"lnR" => Some(1),
2195        b"lnT" => Some(2),
2196        b"lnB" => Some(3),
2197        b"lnTlToBr" => Some(4),
2198        b"lnBlToTr" => Some(5),
2199        b"cell3D" | b"effectLst" | b"effectDag" => Some(6),
2200        name if is_fill(name) => Some(7),
2201        b"headers" => Some(8),
2202        b"extLst" => Some(9),
2203        _ => None,
2204    }
2205}
2206
2207fn ensure_schema_order(slot: Option<usize>, boundary: usize, parent: &str) -> Result<()> {
2208    if slot.is_some_and(|slot| slot < boundary) {
2209        return Err(OxmlError::InvalidValue(format!(
2210            "{parent} children violate schema order"
2211        )));
2212    }
2213    Ok(())
2214}
2215
2216fn set_once<T>(target: &mut Option<T>, value: T, name: &str) -> Result<()> {
2217    if target.replace(value).is_some() {
2218        return Err(OxmlError::InvalidValue(format!("duplicate {name}")));
2219    }
2220    Ok(())
2221}
2222
2223fn is_fill(name: &[u8]) -> bool {
2224    matches!(
2225        name,
2226        b"noFill" | b"solidFill" | b"gradFill" | b"pattFill" | b"blipFill"
2227    )
2228}
2229
2230fn is_color(name: &[u8]) -> bool {
2231    matches!(
2232        name,
2233        b"scrgbClr" | b"srgbClr" | b"hslClr" | b"sysClr" | b"schemeClr" | b"prstClr"
2234    )
2235}
2236
2237fn parse_fill(xml: &[u8]) -> Result<Fill> {
2238    Fill::from_xml(xml).map_err(drawing_error)
2239}
2240
2241fn parse_fill_wrapper(xml: &[u8]) -> Result<Option<Fill>> {
2242    let mut reader = Reader::from_reader(xml);
2243    let mut buffer = Vec::new();
2244    let mut root_seen = false;
2245    let mut fill = None;
2246    loop {
2247        match reader.read_event_into(&mut buffer)? {
2248            Event::Start(start) if !root_seen => {
2249                root_seen = true;
2250                if start.attributes().next().is_some() {
2251                    return Ok(None);
2252                }
2253            }
2254            Event::Start(child) if root_seen && is_fill(local_name(child.name().as_ref())) => {
2255                if fill.is_some() {
2256                    return Ok(None);
2257                }
2258                fill = Some(parse_fill(&capture_element(&mut reader, &child)?)?);
2259            }
2260            Event::Empty(child) if root_seen && is_fill(local_name(child.name().as_ref())) => {
2261                if fill.is_some() {
2262                    return Ok(None);
2263                }
2264                fill = Some(parse_fill(&capture_empty_element(&child)?)?);
2265            }
2266            Event::Start(child) if root_seen => {
2267                let _ = capture_element(&mut reader, &child)?;
2268                return Ok(None);
2269            }
2270            Event::Empty(_) if root_seen => return Ok(None),
2271            Event::End(_) if root_seen => return Ok(fill),
2272            Event::Eof => return Err(missing("closing a:fill")),
2273            Event::Text(text) if !xml_text_is_whitespace(&text) => {
2274                return Ok(None);
2275            }
2276            _ => {}
2277        }
2278        buffer.clear();
2279    }
2280}
2281
2282fn parse_style_reference(xml: &[u8]) -> Result<StyleReference> {
2283    StyleReference::from_xml(xml).map_err(drawing_error)
2284}
2285
2286fn parse_color(xml: &[u8]) -> Result<crate::color::ColorChoice> {
2287    let mut reader = Reader::from_reader(xml);
2288    let mut buffer = Vec::new();
2289    loop {
2290        match reader.read_event_into(&mut buffer)? {
2291            Event::Start(start) => {
2292                return crate::color::ColorChoice::from_xml(&mut reader, &start)
2293                    .map_err(drawing_error);
2294            }
2295            Event::Empty(start) => {
2296                return crate::color::ColorChoice::from_empty_xml(&start).map_err(drawing_error);
2297            }
2298            Event::Eof => return Err(missing("DrawingML colour")),
2299            _ => {}
2300        }
2301        buffer.clear();
2302    }
2303}
2304
2305fn parse_named_line(xml: &[u8]) -> Result<CT_LineProperties> {
2306    let text = std::str::from_utf8(xml)?;
2307    let name_start = text.find('<').ok_or_else(|| missing("table border"))? + 1;
2308    let name_end = text[name_start..]
2309        .find(|character: char| character.is_whitespace() || matches!(character, '>' | '/'))
2310        .map(|offset| name_start + offset)
2311        .ok_or_else(|| missing("table border name"))?;
2312    let original_name = &text[name_start..name_end];
2313    let mut renamed = String::with_capacity(text.len() + 8);
2314    renamed.push_str(&text[..name_start]);
2315    renamed.push_str("a:ln");
2316    renamed.push_str(&text[name_end..]);
2317    let closing = format!("</{original_name}>");
2318    if renamed.ends_with(&closing) {
2319        renamed.truncate(renamed.len() - closing.len());
2320        renamed.push_str("</a:ln>");
2321    }
2322    CT_LineProperties::from_xml(renamed.as_bytes()).map_err(drawing_error)
2323}
2324
2325fn parse_border_wrapper(xml: &[u8]) -> Result<Option<CT_LineProperties>> {
2326    let mut reader = Reader::from_reader(xml);
2327    let mut buffer = Vec::new();
2328    let mut root_seen = false;
2329    let mut line = None;
2330    loop {
2331        match reader.read_event_into(&mut buffer)? {
2332            Event::Start(start) if !root_seen => {
2333                root_seen = true;
2334                if start.attributes().next().is_some() {
2335                    return Ok(None);
2336                }
2337            }
2338            Event::Start(child)
2339                if root_seen && matches_local_name(child.name().as_ref(), b"ln") =>
2340            {
2341                if line.is_some() {
2342                    return Ok(None);
2343                }
2344                line = Some(parse_named_line(&capture_element(&mut reader, &child)?)?);
2345            }
2346            Event::Empty(child)
2347                if root_seen && matches_local_name(child.name().as_ref(), b"ln") =>
2348            {
2349                if line.is_some() {
2350                    return Ok(None);
2351                }
2352                line = Some(parse_named_line(&capture_empty_element(&child)?)?);
2353            }
2354            Event::Start(child) if root_seen => {
2355                let _ = capture_element(&mut reader, &child)?;
2356                return Ok(None);
2357            }
2358            Event::Empty(_) if root_seen => return Ok(None),
2359            Event::End(_) if root_seen => return Ok(line),
2360            Event::Eof => return Err(missing("closing table border wrapper")),
2361            Event::Text(text) if !xml_text_is_whitespace(&text) => {
2362                return Ok(None);
2363            }
2364            _ => {}
2365        }
2366        buffer.clear();
2367    }
2368}
2369
2370fn write_optional_named_line<W: Write>(
2371    writer: &mut Writer<W>,
2372    tag: &str,
2373    line: Option<&CT_LineProperties>,
2374) -> Result<()> {
2375    let Some(line) = line else {
2376        return Ok(());
2377    };
2378    let xml = line.to_xml().map_err(drawing_error)?;
2379    let text = std::str::from_utf8(&xml)?;
2380    let mut renamed = text.replacen("<a:ln", &format!("<{tag}"), 1);
2381    if renamed.ends_with("</a:ln>") {
2382        renamed.truncate(renamed.len() - "</a:ln>".len());
2383        renamed.push_str("</");
2384        renamed.push_str(tag);
2385        renamed.push('>');
2386    }
2387    writer.get_mut().write_all(renamed.as_bytes())?;
2388    Ok(())
2389}
2390
2391fn write_optional_border_wrapper<W: Write>(
2392    writer: &mut Writer<W>,
2393    tag: &str,
2394    line: Option<&CT_LineProperties>,
2395) -> Result<()> {
2396    let Some(line) = line else {
2397        return Ok(());
2398    };
2399    writer.write_event(Event::Start(BytesStart::new(tag)))?;
2400    line.write_xml(writer).map_err(drawing_error)?;
2401    writer.write_event(Event::End(BytesEnd::new(tag)))?;
2402    Ok(())
2403}
2404
2405fn drawing_error(error: impl std::fmt::Display) -> OxmlError {
2406    OxmlError::InvalidValue(error.to_string())
2407}
2408
2409fn xml_text_is_whitespace(text: &BytesText<'_>) -> bool {
2410    let bytes: &[u8] = text.as_ref();
2411    bytes.iter().all(u8::is_ascii_whitespace)
2412}
2413
2414fn required_string(start: &BytesStart<'_>, name: &[u8]) -> Result<String> {
2415    decoded_attr(start, name)?.ok_or_else(|| {
2416        missing(&format!(
2417            "{}@{}",
2418            String::from_utf8_lossy(local_name(start.name().as_ref())),
2419            String::from_utf8_lossy(name)
2420        ))
2421    })
2422}
2423
2424fn optional_emu_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<Emu>> {
2425    decoded_attr(start, name)?
2426        .map(|value| {
2427            value.parse::<i64>().map(Emu).map_err(|error| {
2428                OxmlError::InvalidValue(format!(
2429                    "invalid {}: {error}",
2430                    String::from_utf8_lossy(name)
2431                ))
2432            })
2433        })
2434        .transpose()
2435}
2436
2437fn push_optional_emu(start: &mut BytesStart<'_>, name: &'static str, value: Option<Emu>) {
2438    if let Some(value) = value {
2439        start.push_attribute((name, Cow::Owned(value.0.to_string())));
2440    }
2441}
2442
2443fn push_optional_bool(start: &mut BytesStart<'_>, name: &'static str, value: Option<bool>) {
2444    if let Some(value) = value {
2445        start.push_attribute((name, if value { "1" } else { "0" }));
2446    }
2447}
2448
2449fn raw_attributes(
2450    start: &BytesStart<'_>,
2451    modelled: &[&[u8]],
2452    is_root: bool,
2453) -> Result<RawAttributes> {
2454    let mut raw = Vec::new();
2455    for attribute in start.attributes() {
2456        let attribute = attribute?;
2457        let key = attribute.key.as_ref();
2458        if modelled.contains(&key) || (is_root && key == b"xmlns:a") {
2459            continue;
2460        }
2461        raw.push((
2462            std::str::from_utf8(key)?.to_owned(),
2463            attribute
2464                .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
2465                .into_owned(),
2466        ));
2467    }
2468    Ok(raw)
2469}
2470
2471fn decoded_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<String>> {
2472    for attribute in start.attributes() {
2473        let attribute = attribute?;
2474        if attribute.key.as_ref() == name {
2475            return Ok(Some(
2476                attribute
2477                    .decoded_and_normalized_value(XmlVersion::Implicit1_0, start.decoder())?
2478                    .into_owned(),
2479            ));
2480        }
2481    }
2482    Ok(None)
2483}
2484
2485fn required_i64(start: &BytesStart<'_>, name: &[u8]) -> Result<i64> {
2486    let value = decoded_attr(start, name)?.ok_or_else(|| {
2487        missing(&format!(
2488            "{}@{}",
2489            String::from_utf8_lossy(local_name(start.name().as_ref())),
2490            String::from_utf8_lossy(name)
2491        ))
2492    })?;
2493    value.parse::<i64>().map_err(|error| {
2494        OxmlError::InvalidValue(format!(
2495            "invalid {}@{} {value}: {error}",
2496            String::from_utf8_lossy(local_name(start.name().as_ref())),
2497            String::from_utf8_lossy(name)
2498        ))
2499    })
2500}
2501
2502fn positive_u32_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<u32>> {
2503    let Some(value) = decoded_attr(start, name)? else {
2504        return Ok(None);
2505    };
2506    let parsed = value.parse::<u32>().map_err(|error| {
2507        OxmlError::InvalidValue(format!(
2508            "invalid a:tc@{} {value}: {error}",
2509            String::from_utf8_lossy(name)
2510        ))
2511    })?;
2512    if parsed == 0 {
2513        return Err(OxmlError::InvalidValue(format!(
2514            "a:tc@{} must be positive",
2515            String::from_utf8_lossy(name)
2516        )));
2517    }
2518    Ok(Some(parsed))
2519}
2520
2521fn bool_attr(start: &BytesStart<'_>, name: &[u8]) -> Result<Option<bool>> {
2522    let Some(value) = decoded_attr(start, name)? else {
2523        return Ok(None);
2524    };
2525    match value.as_str() {
2526        "true" | "1" | "on" => Ok(Some(true)),
2527        "false" | "0" | "off" => Ok(Some(false)),
2528        _ => Err(OxmlError::InvalidValue(format!(
2529            "invalid {}@{} boolean: {value}",
2530            String::from_utf8_lossy(local_name(start.name().as_ref())),
2531            String::from_utf8_lossy(name)
2532        ))),
2533    }
2534}
2535
2536fn push_true(start: &mut BytesStart<'_>, name: &'static str, value: bool) {
2537    if value {
2538        start.push_attribute((name, "1"));
2539    }
2540}
2541
2542fn push_attributes(start: &mut BytesStart<'_>, attributes: &RawAttributes) {
2543    for (name, value) in attributes {
2544        start.push_attribute((name.as_str(), value.as_str()));
2545    }
2546}
2547
2548fn emit_raw<'a, W: Write>(
2549    writer: &mut Writer<W>,
2550    children: impl Iterator<Item = &'a [u8]>,
2551) -> Result<()> {
2552    for child in children {
2553        writer.get_mut().write_all(child)?;
2554    }
2555    Ok(())
2556}
2557
2558fn missing(element: &str) -> OxmlError {
2559    OxmlError::MissingElement(element.to_owned())
2560}
2561
2562fn unexpected(start: &BytesStart<'_>) -> OxmlError {
2563    OxmlError::UnexpectedElement(String::from_utf8_lossy(start.name().as_ref()).into_owned())
2564}
2565
2566#[cfg(test)]
2567mod tests {
2568    use std::path::PathBuf;
2569
2570    use oxml_opc::OpcPackage;
2571
2572    use super::{A_NS, CT_Table, CT_TableStyleList, Emu, OxmlError};
2573
2574    #[test]
2575    fn new_table_uses_truncating_dimensions_and_valid_cell_shells() {
2576        let table = CT_Table::new(2, 3, Emu(302), Emu(201)).unwrap();
2577        assert_eq!(table.grid.columns, vec![Emu(100), Emu(100), Emu(102)]);
2578        assert_eq!(
2579            table.rows.iter().map(|row| row.height).collect::<Vec<_>>(),
2580            vec![Emu(100), Emu(101)]
2581        );
2582        assert!(table.properties.as_ref().unwrap().first_row);
2583        assert!(table.properties.as_ref().unwrap().band_rows);
2584        assert!(table.rows.iter().all(|row| row.cells.len() == 3));
2585        assert!(table.rows.iter().flat_map(|row| &row.cells).all(|cell| {
2586            cell.text_body.as_ref().unwrap().paragraph_count() == 1 && cell.properties.is_some()
2587        }));
2588        assert_eq!(CT_Table::from_xml(&table.to_xml().unwrap()).unwrap(), table);
2589    }
2590
2591    #[test]
2592    fn new_table_rejects_invalid_dimensions_before_allocation() {
2593        for result in [
2594            CT_Table::new(0, 1, Emu(1), Emu(1)),
2595            CT_Table::new(1, 0, Emu(1), Emu(1)),
2596            CT_Table::new(1, 1, Emu(0), Emu(1)),
2597            CT_Table::new(1, 1, Emu(1), Emu(-1)),
2598            CT_Table::new(2, 1, Emu(1), Emu(1)),
2599            CT_Table::new(1, 2, Emu(1), Emu(1)),
2600        ] {
2601            assert!(result.is_err());
2602        }
2603    }
2604
2605    #[test]
2606    fn table_style_and_cell_properties_preserve_unmodelled_xml_byte_for_byte() {
2607        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>"#;
2608
2609        let styles = CT_TableStyleList::from_xml(xml).expect("parse table style list");
2610        let written = styles.to_xml().expect("write table style list");
2611
2612        assert!(
2613            written
2614                .windows(b"<x:before/>".len())
2615                .any(|part| part == b"<x:before/>")
2616        );
2617        assert!(
2618            written
2619                .windows(b"<x:unsupported value=\"kept\"/>".len())
2620                .any(|part| part == b"<x:unsupported value=\"kept\"/>")
2621        );
2622        assert!(
2623            written
2624                .windows(b"<x:after/>".len())
2625                .any(|part| part == b"<x:after/>")
2626        );
2627        assert!(written.starts_with(
2628            br#"<a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main""#
2629        ));
2630        assert_eq!(styles, CT_TableStyleList::from_xml(&written).unwrap());
2631
2632        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>"#;
2633        let producer_style = CT_TableStyleList::from_xml(producer_style).unwrap();
2634        let whole = producer_style.styles[0].whole_table.as_ref().unwrap();
2635        assert_eq!(whole.text_style.as_ref().unwrap().bold, Some(true));
2636        assert!(whole.cell_style.as_ref().unwrap().fill.is_some());
2637        assert!(
2638            whole
2639                .cell_style
2640                .as_ref()
2641                .unwrap()
2642                .borders
2643                .as_ref()
2644                .unwrap()
2645                .left
2646                .is_some()
2647        );
2648        assert!(
2649            whole
2650                .cell_style
2651                .as_ref()
2652                .unwrap()
2653                .borders
2654                .as_ref()
2655                .unwrap()
2656                .inside_horizontal
2657                .is_some()
2658        );
2659        let producer_written = producer_style.to_xml().unwrap();
2660        assert_eq!(
2661            producer_style,
2662            CT_TableStyleList::from_xml(&producer_written).unwrap()
2663        );
2664
2665        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>"#;
2666        let direct_edge_alias = CT_TableStyleList::from_xml(direct_edge_alias).unwrap();
2667        let borders = direct_edge_alias.styles[0]
2668            .whole_table
2669            .as_ref()
2670            .unwrap()
2671            .cell_style
2672            .as_ref()
2673            .unwrap()
2674            .borders
2675            .as_ref()
2676            .unwrap();
2677        assert!(borders.left.is_some());
2678        assert!(borders.unsupported.is_empty());
2679
2680        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>"#;
2681        let table = CT_Table::from_xml(table_xml).unwrap();
2682        let properties = table.rows[0].cells[0].properties.as_ref().unwrap();
2683        assert_eq!(properties.margin_left, Some(Emu(12_700)));
2684        assert_eq!(properties.margin_bottom, Some(Emu(50_800)));
2685        assert!(properties.left.is_some());
2686        assert!(properties.fill.is_some());
2687        assert_eq!(properties.unsupported, ["diagonal border"]);
2688        let table_written = table.to_xml().unwrap();
2689        assert!(
2690            table_written
2691                .windows(b"<x:diagonal/>".len())
2692                .any(|part| part == b"<x:diagonal/>")
2693        );
2694        assert!(
2695            table_written
2696                .windows(b"<x:after-fill/>".len())
2697                .any(|part| part == b"<x:after-fill/>")
2698        );
2699        assert_eq!(table, CT_Table::from_xml(&table_written).unwrap());
2700    }
2701
2702    #[test]
2703    fn every_corpus_table_style_list_parses_and_round_trips() {
2704        let corpus = std::env::var_os("RDOCX_PPTX_CORPUS_DIR")
2705            .map(PathBuf::from)
2706            .unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../corpus/pptx"));
2707        if !corpus.is_dir() {
2708            assert_ne!(
2709                std::env::var_os("RDOCX_PPTX_CORPUS_REQUIRED").as_deref(),
2710                Some(std::ffi::OsStr::new("1")),
2711                "the required pinned corpus is missing at {}",
2712                corpus.display()
2713            );
2714            return;
2715        }
2716        for entry in std::fs::read_dir(&corpus).unwrap() {
2717            let path = entry.unwrap().path();
2718            if path.extension().and_then(|extension| extension.to_str()) != Some("pptx") {
2719                continue;
2720            }
2721            let package = OpcPackage::open(&path).unwrap();
2722            let Some(xml) = package.get_part("/ppt/tableStyles.xml") else {
2723                continue;
2724            };
2725            let styles = CT_TableStyleList::from_xml(xml)
2726                .unwrap_or_else(|error| panic!("{}: {error}", path.display()));
2727            let written = styles.to_xml().unwrap();
2728            assert_eq!(
2729                styles,
2730                CT_TableStyleList::from_xml(&written).unwrap(),
2731                "{}",
2732                path.display()
2733            );
2734        }
2735    }
2736
2737    #[test]
2738    fn table_properties_preserve_style_and_banding_flags() {
2739        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>"#;
2740        let table = CT_Table::from_xml(xml).unwrap();
2741        let properties = table.properties.as_ref().unwrap();
2742        assert!(properties.right_to_left);
2743        assert!(properties.first_row && properties.first_column);
2744        assert!(properties.last_row && properties.last_column);
2745        assert!(properties.band_rows && properties.band_columns);
2746        assert_eq!(
2747            properties.style_id.as_deref(),
2748            Some("{5940675A-B579-460E-94D1-54222C63F5DA}")
2749        );
2750        assert_eq!(table, CT_Table::from_xml(&table.to_xml().unwrap()).unwrap());
2751    }
2752
2753    #[test]
2754    fn table_reader_is_prefix_tolerant_and_writer_uses_schema_order() {
2755        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>"#;
2756        let table = CT_Table::from_xml(xml).unwrap();
2757        let written = table.to_xml().unwrap();
2758        let text = std::str::from_utf8(&written).unwrap();
2759        assert!(text.starts_with("<a:tbl xmlns:a="));
2760        assert_order(
2761            text,
2762            &[
2763                "<a:tblPr",
2764                "<a:tblGrid",
2765                "<a:tr",
2766                "<a:tc",
2767                "<a:txBody",
2768                "<a:tcPr",
2769            ],
2770        );
2771        let reparsed = CT_Table::from_xml(&written).unwrap();
2772        assert_eq!(table.grid, reparsed.grid);
2773        assert_eq!(table.rows[0], reparsed.rows[0]);
2774        assert_eq!(table, reparsed);
2775    }
2776
2777    #[test]
2778    fn unmodelled_table_and_cell_content_is_preserved_in_place() {
2779        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>"#;
2780        let table = CT_Table::from_xml(xml).unwrap();
2781        let written = table.to_xml().unwrap();
2782        let text = std::str::from_utf8(&written).unwrap();
2783        for raw in [
2784            r#"<x:before/>"#,
2785            r#"<x:opaque xmlns:a="urn:producer"><a:data/></x:opaque>"#,
2786            r#"<x:fill/>"#,
2787            r#"<x:effect/>"#,
2788            r#"<x:column-child/>"#,
2789            r#"<a:tcPr x:style="kept"><x:border/></a:tcPr>"#,
2790            r#"<x:cell-opaque xmlns:a="urn:producer"><a:data/></x:cell-opaque>"#,
2791            r#"<x:after-properties/>"#,
2792            r#"<x:after/>"#,
2793        ] {
2794            assert!(text.contains(raw), "missing {raw}");
2795        }
2796        assert_order(
2797            text,
2798            &[
2799                "<x:before-text",
2800                "<a:txBody",
2801                "<x:before-properties",
2802                "<a:tcPr",
2803                "<x:after-properties",
2804            ],
2805        );
2806        assert_eq!(table, CT_Table::from_xml(&written).unwrap());
2807    }
2808
2809    #[test]
2810    fn collection_edits_keep_surviving_metadata_and_trailing_raw_children() {
2811        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>"#;
2812        let mut table = CT_Table::from_xml(xml).unwrap();
2813        table.grid.columns.swap(0, 1);
2814        table.grid.columns.remove(1);
2815        table.rows[0].cells.remove(0);
2816        table.rows[0].cells[0].grid_span = 2;
2817        table.rows.remove(1);
2818
2819        let written = table.to_xml().unwrap();
2820        let text = std::str::from_utf8(&written).unwrap();
2821        assert!(text.contains(r#"<a:gridCol w="200" x:id="second"><x:second-child/></a:gridCol>"#));
2822        assert!(!text.contains(r#"x:id="first""#));
2823        for raw in [
2824            "<x:grid-tail/>",
2825            "<x:between-cells/>",
2826            "<x:row-tail/>",
2827            "<x:between-rows/>",
2828            "<x:table-tail/>",
2829        ] {
2830            assert!(text.contains(raw), "missing {raw}");
2831        }
2832        assert!(text.contains(
2833            r#"<a:tr h="300"><x:between-cells/><a:tc gridSpan="2"/><x:row-tail/></a:tr>"#
2834        ));
2835        let reparsed = CT_Table::from_xml(&written).unwrap();
2836        assert_eq!(table.grid, reparsed.grid);
2837        assert_eq!(table.rows[0], reparsed.rows[0]);
2838        assert_eq!(table, reparsed);
2839
2840        let mut width_edit = CT_Table::from_xml(xml).unwrap();
2841        width_edit.grid.columns[1] = Emu(250);
2842        let width_edit_xml = width_edit.to_xml().unwrap();
2843        assert!(
2844            std::str::from_utf8(&width_edit_xml)
2845                .unwrap()
2846                .contains(r#"<a:gridCol w="250" x:id="second"><x:second-child/></a:gridCol>"#)
2847        );
2848        assert_eq!(width_edit, CT_Table::from_xml(&width_edit_xml).unwrap());
2849    }
2850
2851    #[test]
2852    fn ambiguous_grid_edits_return_a_typed_serialization_error() {
2853        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>"#;
2854
2855        let mut collision = CT_Table::from_xml(xml).unwrap();
2856        collision.grid.columns[0] = Emu(200);
2857        assert_ambiguous_grid_error(collision.to_xml().unwrap_err());
2858
2859        let mut combined_reorder_and_edit = CT_Table::from_xml(xml).unwrap();
2860        combined_reorder_and_edit.grid.columns.swap(0, 1);
2861        combined_reorder_and_edit.grid.columns[0] = Emu(100);
2862        assert_ambiguous_grid_error(combined_reorder_and_edit.to_xml().unwrap_err());
2863
2864        let mut duplicate_insertion = CT_Table::from_xml(xml).unwrap();
2865        duplicate_insertion.grid.columns.insert(0, Emu(100));
2866        assert_ambiguous_grid_error(duplicate_insertion.to_xml().unwrap_err());
2867
2868        let mut delete_and_edit = CT_Table::from_xml(xml).unwrap();
2869        delete_and_edit.grid.columns.remove(1);
2870        delete_and_edit.grid.columns[0] = Emu(150);
2871        assert_ambiguous_grid_error(delete_and_edit.to_xml().unwrap_err());
2872
2873        let mut insert_and_edit = CT_Table::from_xml(xml).unwrap();
2874        insert_and_edit.grid.columns[0] = Emu(150);
2875        insert_and_edit.grid.columns.insert(1, Emu(300));
2876        assert_ambiguous_grid_error(insert_and_edit.to_xml().unwrap_err());
2877    }
2878
2879    #[test]
2880    fn inherited_namespaces_make_extracted_table_output_self_contained() {
2881        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>"#;
2882        let inherited = vec![
2883            ("q".to_owned(), A_NS.to_owned()),
2884            ("x".to_owned(), "urn:producer".to_owned()),
2885        ];
2886        let table = CT_Table::from_xml_with_inherited_namespaces(xml, &inherited).unwrap();
2887        let written = table.to_xml().unwrap();
2888        let text = std::str::from_utf8(&written).unwrap();
2889        assert!(text.starts_with(&format!(r#"<a:tbl xmlns:a="{A_NS}""#)));
2890        assert!(text.contains(r#"xmlns:x="urn:producer""#));
2891        assert!(text.contains("<x:extension/>"));
2892        assert_eq!(table, CT_Table::from_xml(&written).unwrap());
2893
2894        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>"#;
2895        let producer_a = vec![("a".to_owned(), "urn:producer".to_owned())];
2896        assert!(
2897            CT_Table::from_xml_with_inherited_namespaces(locally_shadowed, &producer_a).is_ok()
2898        );
2899    }
2900
2901    #[test]
2902    fn inherited_namespace_prefixes_accept_xml_ncname_unicode() {
2903        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>"#;
2904        let inherited = vec![
2905            ("q".to_owned(), A_NS.to_owned()),
2906            ("é".to_owned(), "urn:producer".to_owned()),
2907        ];
2908        let table =
2909            CT_Table::from_xml_with_inherited_namespaces(xml.as_bytes(), &inherited).unwrap();
2910        let written = table.to_xml().unwrap();
2911        let text = std::str::from_utf8(&written).unwrap();
2912        assert!(text.contains(r#"xmlns:é="urn:producer""#));
2913        assert!(text.contains("<é:extension/>"));
2914        assert_eq!(table, CT_Table::from_xml(&written).unwrap());
2915    }
2916
2917    #[test]
2918    fn optional_child_edits_round_trip_as_equal_models() {
2919        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>"#;
2920
2921        let mut no_properties = CT_Table::from_xml(xml).unwrap();
2922        no_properties.properties = None;
2923        let written = no_properties.to_xml().unwrap();
2924        assert_eq!(no_properties, CT_Table::from_xml(&written).unwrap());
2925
2926        let mut no_style = CT_Table::from_xml(xml).unwrap();
2927        no_style.properties.as_mut().unwrap().style_id = None;
2928        let written = no_style.to_xml().unwrap();
2929        let reparsed = CT_Table::from_xml(&written).unwrap();
2930        assert_eq!(
2931            no_style.properties.as_ref().unwrap(),
2932            reparsed.properties.as_ref().unwrap()
2933        );
2934        assert_eq!(no_style, reparsed);
2935
2936        let mut no_text = CT_Table::from_xml(xml).unwrap();
2937        no_text.rows[0].cells[0].text_body = None;
2938        let written = no_text.to_xml().unwrap();
2939        let reparsed = CT_Table::from_xml(&written).unwrap();
2940        assert_eq!(no_text.rows[0].cells[0], reparsed.rows[0].cells[0]);
2941        assert_eq!(no_text, reparsed);
2942    }
2943
2944    fn assert_ambiguous_grid_error(error: OxmlError) {
2945        assert!(matches!(
2946            error,
2947            OxmlError::InvalidValue(message)
2948                if message == "edited table grid is ambiguous because preserved column metadata cannot be associated reliably"
2949        ));
2950    }
2951
2952    fn assert_order(text: &str, tags: &[&str]) {
2953        let mut previous = 0usize;
2954        for tag in tags {
2955            let position = text[previous..].find(tag).unwrap() + previous;
2956            assert!(position >= previous, "{tag}");
2957            previous = position + tag.len();
2958        }
2959    }
2960}