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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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 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 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 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 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(¤t_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(¤t_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 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 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 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}