linch_docx_rs/document/
table.rs

1//! Table elements (w:tbl, w:tr, w:tc)
2
3use crate::document::Paragraph;
4use crate::error::Result;
5use crate::xml::{RawXmlElement, RawXmlNode};
6use quick_xml::events::{BytesEnd, BytesStart, Event};
7use quick_xml::{Reader, Writer};
8use std::io::BufRead;
9
10/// Table element (w:tbl)
11#[derive(Clone, Debug, Default)]
12pub struct Table {
13    /// Table properties
14    pub properties: Option<RawXmlNode>,
15    /// Table grid
16    pub grid: Vec<GridColumn>,
17    /// Table rows
18    pub rows: Vec<TableRow>,
19    /// Unknown children (preserved)
20    pub unknown_children: Vec<RawXmlNode>,
21}
22
23/// Grid column definition
24#[derive(Clone, Debug, Default)]
25pub struct GridColumn {
26    /// Width in twips
27    pub width: Option<i32>,
28}
29
30/// Table row (w:tr)
31#[derive(Clone, Debug, Default)]
32pub struct TableRow {
33    /// Row properties
34    pub properties: Option<RawXmlNode>,
35    /// Cells
36    pub cells: Vec<TableCell>,
37    /// Unknown children (preserved)
38    pub unknown_children: Vec<RawXmlNode>,
39}
40
41/// Table cell (w:tc)
42#[derive(Clone, Debug, Default)]
43pub struct TableCell {
44    /// Cell properties
45    pub properties: Option<TableCellProperties>,
46    /// Cell content (paragraphs)
47    pub paragraphs: Vec<Paragraph>,
48    /// Unknown children (preserved)
49    pub unknown_children: Vec<RawXmlNode>,
50}
51
52/// Table cell properties
53#[derive(Clone, Debug, Default)]
54pub struct TableCellProperties {
55    /// Cell width
56    pub width: Option<i32>,
57    /// Grid span (horizontal merge)
58    pub grid_span: Option<u32>,
59    /// Vertical merge
60    pub v_merge: Option<VMerge>,
61    /// Vertical alignment
62    pub v_align: Option<String>,
63    /// Unknown children (preserved)
64    pub unknown_children: Vec<RawXmlNode>,
65}
66
67/// Vertical merge type
68#[derive(Clone, Debug)]
69pub enum VMerge {
70    Restart,
71    Continue,
72}
73
74impl Table {
75    /// Create a new table with the specified number of rows and columns
76    pub fn new(rows: usize, cols: usize) -> Self {
77        let mut table_rows = Vec::with_capacity(rows);
78        for _ in 0..rows {
79            let mut cells = Vec::with_capacity(cols);
80            for _ in 0..cols {
81                cells.push(TableCell::new(""));
82            }
83            table_rows.push(TableRow {
84                cells,
85                ..Default::default()
86            });
87        }
88
89        let grid = (0..cols).map(|_| GridColumn { width: None }).collect();
90
91        Table {
92            grid,
93            rows: table_rows,
94            ..Default::default()
95        }
96    }
97
98    /// Create a table from a 2D array of strings
99    pub fn from_data<S: Into<String> + Clone>(data: &[&[S]]) -> Self {
100        let rows: Vec<TableRow> = data
101            .iter()
102            .map(|row| {
103                let cells: Vec<TableCell> = row
104                    .iter()
105                    .map(|text| TableCell::new(text.clone()))
106                    .collect();
107                TableRow {
108                    cells,
109                    ..Default::default()
110                }
111            })
112            .collect();
113
114        let cols = data.first().map(|r| r.len()).unwrap_or(0);
115        let grid = (0..cols).map(|_| GridColumn { width: None }).collect();
116
117        Table {
118            grid,
119            rows,
120            ..Default::default()
121        }
122    }
123
124    /// Parse from reader (after w:tbl start tag)
125    pub fn from_reader<R: BufRead>(reader: &mut Reader<R>, _start: &BytesStart) -> Result<Self> {
126        let mut table = Table::default();
127        let mut buf = Vec::new();
128
129        loop {
130            match reader.read_event_into(&mut buf)? {
131                Event::Start(e) => {
132                    let local = e.name().local_name();
133
134                    match local.as_ref() {
135                        b"tblPr" => {
136                            let raw = RawXmlElement::from_reader(reader, &e)?;
137                            table.properties = Some(RawXmlNode::Element(raw));
138                        }
139                        b"tblGrid" => {
140                            table.grid = parse_table_grid(reader)?;
141                        }
142                        b"tr" => {
143                            let row = TableRow::from_reader(reader, &e)?;
144                            table.rows.push(row);
145                        }
146                        _ => {
147                            let raw = RawXmlElement::from_reader(reader, &e)?;
148                            table.unknown_children.push(RawXmlNode::Element(raw));
149                        }
150                    }
151                }
152                Event::Empty(e) => {
153                    // Handle empty elements
154                    let raw = RawXmlElement {
155                        name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
156                        attributes: e
157                            .attributes()
158                            .filter_map(|a| a.ok())
159                            .map(|a| {
160                                (
161                                    String::from_utf8_lossy(a.key.as_ref()).to_string(),
162                                    String::from_utf8_lossy(&a.value).to_string(),
163                                )
164                            })
165                            .collect(),
166                        children: Vec::new(),
167                        self_closing: true,
168                    };
169                    table.unknown_children.push(RawXmlNode::Element(raw));
170                }
171                Event::End(e) => {
172                    if e.name().local_name().as_ref() == b"tbl" {
173                        break;
174                    }
175                }
176                Event::Eof => break,
177                _ => {}
178            }
179            buf.clear();
180        }
181
182        Ok(table)
183    }
184
185    /// Get row count
186    pub fn row_count(&self) -> usize {
187        self.rows.len()
188    }
189
190    /// Get column count (based on first row)
191    pub fn column_count(&self) -> usize {
192        self.rows.first().map(|r| r.cells.len()).unwrap_or(0)
193    }
194
195    /// Get cell at position
196    pub fn cell(&self, row: usize, col: usize) -> Option<&TableCell> {
197        self.rows.get(row)?.cells.get(col)
198    }
199
200    /// Iterate over rows
201    pub fn rows(&self) -> impl Iterator<Item = &TableRow> {
202        self.rows.iter()
203    }
204
205    /// Get mutable cell at position
206    pub fn cell_mut(&mut self, row: usize, col: usize) -> Option<&mut TableCell> {
207        self.rows.get_mut(row)?.cells.get_mut(col)
208    }
209
210    /// Get mutable row
211    pub fn row_mut(&mut self, index: usize) -> Option<&mut TableRow> {
212        self.rows.get_mut(index)
213    }
214
215    /// Add a row to the table
216    pub fn add_row(&mut self, row: TableRow) {
217        self.rows.push(row);
218    }
219
220    /// Set cell text at position
221    pub fn set_cell_text(&mut self, row: usize, col: usize, text: impl Into<String>) {
222        if let Some(cell) = self.cell_mut(row, col) {
223            cell.set_text(text);
224        }
225    }
226
227    /// Set column width (in twips, 1/20 of a point)
228    pub fn set_column_width(&mut self, col: usize, width: i32) {
229        if col < self.grid.len() {
230            self.grid[col].width = Some(width);
231        }
232    }
233
234    /// Write to XML writer
235    pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
236        writer.write_event(Event::Start(BytesStart::new("w:tbl")))?;
237
238        // Table properties
239        if let Some(props) = &self.properties {
240            props.write_to(writer)?;
241        }
242
243        // Table grid
244        if !self.grid.is_empty() {
245            writer.write_event(Event::Start(BytesStart::new("w:tblGrid")))?;
246            for col in &self.grid {
247                let mut elem = BytesStart::new("w:gridCol");
248                if let Some(w) = col.width {
249                    elem.push_attribute(("w:w", w.to_string().as_str()));
250                }
251                writer.write_event(Event::Empty(elem))?;
252            }
253            writer.write_event(Event::End(BytesEnd::new("w:tblGrid")))?;
254        }
255
256        // Rows
257        for row in &self.rows {
258            row.write_to(writer)?;
259        }
260
261        // Unknown children
262        for child in &self.unknown_children {
263            child.write_to(writer)?;
264        }
265
266        writer.write_event(Event::End(BytesEnd::new("w:tbl")))?;
267        Ok(())
268    }
269}
270
271impl TableRow {
272    /// Create a new row with empty cells
273    pub fn new(cell_count: usize) -> Self {
274        let cells = (0..cell_count).map(|_| TableCell::new("")).collect();
275        TableRow {
276            cells,
277            ..Default::default()
278        }
279    }
280
281    /// Create a row from cell texts
282    pub fn from_texts<S: Into<String>>(texts: impl IntoIterator<Item = S>) -> Self {
283        let cells = texts.into_iter().map(TableCell::new).collect();
284        TableRow {
285            cells,
286            ..Default::default()
287        }
288    }
289
290    /// Parse from reader
291    pub fn from_reader<R: BufRead>(reader: &mut Reader<R>, _start: &BytesStart) -> Result<Self> {
292        let mut row = TableRow::default();
293        let mut buf = Vec::new();
294
295        loop {
296            match reader.read_event_into(&mut buf)? {
297                Event::Start(e) => {
298                    let local = e.name().local_name();
299
300                    match local.as_ref() {
301                        b"trPr" => {
302                            let raw = RawXmlElement::from_reader(reader, &e)?;
303                            row.properties = Some(RawXmlNode::Element(raw));
304                        }
305                        b"tc" => {
306                            let cell = TableCell::from_reader(reader, &e)?;
307                            row.cells.push(cell);
308                        }
309                        _ => {
310                            let raw = RawXmlElement::from_reader(reader, &e)?;
311                            row.unknown_children.push(RawXmlNode::Element(raw));
312                        }
313                    }
314                }
315                Event::Empty(e) => {
316                    let raw = RawXmlElement {
317                        name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
318                        attributes: e
319                            .attributes()
320                            .filter_map(|a| a.ok())
321                            .map(|a| {
322                                (
323                                    String::from_utf8_lossy(a.key.as_ref()).to_string(),
324                                    String::from_utf8_lossy(&a.value).to_string(),
325                                )
326                            })
327                            .collect(),
328                        children: Vec::new(),
329                        self_closing: true,
330                    };
331                    row.unknown_children.push(RawXmlNode::Element(raw));
332                }
333                Event::End(e) => {
334                    if e.name().local_name().as_ref() == b"tr" {
335                        break;
336                    }
337                }
338                Event::Eof => break,
339                _ => {}
340            }
341            buf.clear();
342        }
343
344        Ok(row)
345    }
346
347    /// Get cell count
348    pub fn cell_count(&self) -> usize {
349        self.cells.len()
350    }
351
352    /// Iterate over cells
353    pub fn cells(&self) -> impl Iterator<Item = &TableCell> {
354        self.cells.iter()
355    }
356
357    /// Get mutable cell at index
358    pub fn cell_mut(&mut self, index: usize) -> Option<&mut TableCell> {
359        self.cells.get_mut(index)
360    }
361
362    /// Add a cell to the row
363    pub fn add_cell(&mut self, cell: TableCell) {
364        self.cells.push(cell);
365    }
366
367    /// Write to XML writer
368    pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
369        writer.write_event(Event::Start(BytesStart::new("w:tr")))?;
370
371        // Row properties
372        if let Some(props) = &self.properties {
373            props.write_to(writer)?;
374        }
375
376        // Cells
377        for cell in &self.cells {
378            cell.write_to(writer)?;
379        }
380
381        // Unknown children
382        for child in &self.unknown_children {
383            child.write_to(writer)?;
384        }
385
386        writer.write_event(Event::End(BytesEnd::new("w:tr")))?;
387        Ok(())
388    }
389}
390
391impl TableCell {
392    /// Create a new cell with text
393    pub fn new(text: impl Into<String>) -> Self {
394        let text = text.into();
395        let paragraphs = if text.is_empty() {
396            vec![Paragraph::default()]
397        } else {
398            vec![Paragraph::new(text)]
399        };
400        TableCell {
401            paragraphs,
402            ..Default::default()
403        }
404    }
405
406    /// Set the cell text (replaces all paragraphs with a single one)
407    pub fn set_text(&mut self, text: impl Into<String>) {
408        self.paragraphs.clear();
409        self.paragraphs.push(Paragraph::new(text));
410    }
411
412    /// Add a paragraph to the cell
413    pub fn add_paragraph(&mut self, para: Paragraph) {
414        self.paragraphs.push(para);
415    }
416
417    /// Set cell width (in twips)
418    pub fn set_width(&mut self, width: i32) {
419        self.properties.get_or_insert_with(Default::default).width = Some(width);
420    }
421
422    /// Set horizontal merge (grid span)
423    pub fn set_grid_span(&mut self, span: u32) {
424        self.properties
425            .get_or_insert_with(Default::default)
426            .grid_span = Some(span);
427    }
428
429    /// Set vertical merge
430    pub fn set_v_merge(&mut self, v_merge: VMerge) {
431        self.properties.get_or_insert_with(Default::default).v_merge = Some(v_merge);
432    }
433
434    /// Set vertical alignment
435    pub fn set_v_align(&mut self, align: impl Into<String>) {
436        self.properties.get_or_insert_with(Default::default).v_align = Some(align.into());
437    }
438
439    /// Parse from reader
440    pub fn from_reader<R: BufRead>(reader: &mut Reader<R>, _start: &BytesStart) -> Result<Self> {
441        let mut cell = TableCell::default();
442        let mut buf = Vec::new();
443
444        loop {
445            match reader.read_event_into(&mut buf)? {
446                Event::Start(e) => {
447                    let local = e.name().local_name();
448
449                    match local.as_ref() {
450                        b"tcPr" => {
451                            cell.properties = Some(TableCellProperties::from_reader(reader)?);
452                        }
453                        b"p" => {
454                            let para = Paragraph::from_reader(reader, &e)?;
455                            cell.paragraphs.push(para);
456                        }
457                        _ => {
458                            let raw = RawXmlElement::from_reader(reader, &e)?;
459                            cell.unknown_children.push(RawXmlNode::Element(raw));
460                        }
461                    }
462                }
463                Event::Empty(e) => {
464                    let local = e.name().local_name();
465                    if local.as_ref() == b"p" {
466                        let para = Paragraph::from_empty(&e)?;
467                        cell.paragraphs.push(para);
468                    } else {
469                        let raw = RawXmlElement {
470                            name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
471                            attributes: e
472                                .attributes()
473                                .filter_map(|a| a.ok())
474                                .map(|a| {
475                                    (
476                                        String::from_utf8_lossy(a.key.as_ref()).to_string(),
477                                        String::from_utf8_lossy(&a.value).to_string(),
478                                    )
479                                })
480                                .collect(),
481                            children: Vec::new(),
482                            self_closing: true,
483                        };
484                        cell.unknown_children.push(RawXmlNode::Element(raw));
485                    }
486                }
487                Event::End(e) => {
488                    if e.name().local_name().as_ref() == b"tc" {
489                        break;
490                    }
491                }
492                Event::Eof => break,
493                _ => {}
494            }
495            buf.clear();
496        }
497
498        Ok(cell)
499    }
500
501    /// Get cell text (all paragraphs concatenated)
502    pub fn text(&self) -> String {
503        self.paragraphs
504            .iter()
505            .map(|p| p.text())
506            .collect::<Vec<_>>()
507            .join("\n")
508    }
509
510    /// Iterate over paragraphs
511    pub fn paragraphs(&self) -> impl Iterator<Item = &Paragraph> {
512        self.paragraphs.iter()
513    }
514
515    /// Write to XML writer
516    pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
517        writer.write_event(Event::Start(BytesStart::new("w:tc")))?;
518
519        // Cell properties
520        if let Some(props) = &self.properties {
521            props.write_to(writer)?;
522        }
523
524        // Paragraphs (at least one required in cell)
525        if self.paragraphs.is_empty() {
526            // Write empty paragraph if none exist
527            writer.write_event(Event::Empty(BytesStart::new("w:p")))?;
528        } else {
529            for para in &self.paragraphs {
530                para.write_to(writer)?;
531            }
532        }
533
534        // Unknown children
535        for child in &self.unknown_children {
536            child.write_to(writer)?;
537        }
538
539        writer.write_event(Event::End(BytesEnd::new("w:tc")))?;
540        Ok(())
541    }
542}
543
544impl TableCellProperties {
545    /// Parse from reader
546    pub fn from_reader<R: BufRead>(reader: &mut Reader<R>) -> Result<Self> {
547        let mut props = TableCellProperties::default();
548        let mut buf = Vec::new();
549
550        loop {
551            match reader.read_event_into(&mut buf)? {
552                Event::Start(e) => {
553                    let raw = RawXmlElement::from_reader(reader, &e)?;
554                    props.unknown_children.push(RawXmlNode::Element(raw));
555                }
556                Event::Empty(e) => {
557                    let local = e.name().local_name();
558
559                    match local.as_ref() {
560                        b"tcW" => {
561                            props.width = crate::xml::get_attr(&e, "w:w")
562                                .or_else(|| crate::xml::get_attr(&e, "w"))
563                                .and_then(|v| v.parse().ok());
564                        }
565                        b"gridSpan" => {
566                            props.grid_span =
567                                crate::xml::get_w_val(&e).and_then(|v| v.parse().ok());
568                        }
569                        b"vMerge" => {
570                            let val = crate::xml::get_w_val(&e);
571                            props.v_merge = Some(match val.as_deref() {
572                                Some("restart") => VMerge::Restart,
573                                _ => VMerge::Continue,
574                            });
575                        }
576                        b"vAlign" => {
577                            props.v_align = crate::xml::get_w_val(&e);
578                        }
579                        _ => {
580                            let raw = RawXmlElement {
581                                name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
582                                attributes: e
583                                    .attributes()
584                                    .filter_map(|a| a.ok())
585                                    .map(|a| {
586                                        (
587                                            String::from_utf8_lossy(a.key.as_ref()).to_string(),
588                                            String::from_utf8_lossy(&a.value).to_string(),
589                                        )
590                                    })
591                                    .collect(),
592                                children: Vec::new(),
593                                self_closing: true,
594                            };
595                            props.unknown_children.push(RawXmlNode::Element(raw));
596                        }
597                    }
598                }
599                Event::End(e) => {
600                    if e.name().local_name().as_ref() == b"tcPr" {
601                        break;
602                    }
603                }
604                Event::Eof => break,
605                _ => {}
606            }
607            buf.clear();
608        }
609
610        Ok(props)
611    }
612
613    /// Write to XML writer
614    pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
615        let has_content = self.width.is_some()
616            || self.grid_span.is_some()
617            || self.v_merge.is_some()
618            || self.v_align.is_some()
619            || !self.unknown_children.is_empty();
620
621        if !has_content {
622            return Ok(());
623        }
624
625        writer.write_event(Event::Start(BytesStart::new("w:tcPr")))?;
626
627        // Width
628        if let Some(width) = self.width {
629            let mut elem = BytesStart::new("w:tcW");
630            elem.push_attribute(("w:w", width.to_string().as_str()));
631            elem.push_attribute(("w:type", "dxa"));
632            writer.write_event(Event::Empty(elem))?;
633        }
634
635        // Grid span
636        if let Some(span) = self.grid_span {
637            let mut elem = BytesStart::new("w:gridSpan");
638            elem.push_attribute(("w:val", span.to_string().as_str()));
639            writer.write_event(Event::Empty(elem))?;
640        }
641
642        // Vertical merge
643        if let Some(v_merge) = &self.v_merge {
644            let mut elem = BytesStart::new("w:vMerge");
645            match v_merge {
646                VMerge::Restart => elem.push_attribute(("w:val", "restart")),
647                VMerge::Continue => {}
648            }
649            writer.write_event(Event::Empty(elem))?;
650        }
651
652        // Vertical alignment
653        if let Some(v_align) = &self.v_align {
654            let mut elem = BytesStart::new("w:vAlign");
655            elem.push_attribute(("w:val", v_align.as_str()));
656            writer.write_event(Event::Empty(elem))?;
657        }
658
659        // Unknown children
660        for child in &self.unknown_children {
661            child.write_to(writer)?;
662        }
663
664        writer.write_event(Event::End(BytesEnd::new("w:tcPr")))?;
665        Ok(())
666    }
667}
668
669/// Parse table grid
670fn parse_table_grid<R: BufRead>(reader: &mut Reader<R>) -> Result<Vec<GridColumn>> {
671    let mut columns = Vec::new();
672    let mut buf = Vec::new();
673
674    loop {
675        match reader.read_event_into(&mut buf)? {
676            Event::Empty(e) => {
677                if e.name().local_name().as_ref() == b"gridCol" {
678                    let width = crate::xml::get_attr(&e, "w:w")
679                        .or_else(|| crate::xml::get_attr(&e, "w"))
680                        .and_then(|v| v.parse().ok());
681                    columns.push(GridColumn { width });
682                }
683            }
684            Event::End(e) => {
685                if e.name().local_name().as_ref() == b"tblGrid" {
686                    break;
687                }
688            }
689            Event::Eof => break,
690            _ => {}
691        }
692        buf.clear();
693    }
694
695    Ok(columns)
696}