linch_docx_rs/document/
body.rs

1//! Document body and block-level content
2
3use crate::document::{Paragraph, Table};
4use crate::error::Result;
5use crate::xml::RawXmlNode;
6use quick_xml::events::{BytesEnd, BytesStart, Event};
7use quick_xml::{Reader, Writer};
8use std::io::BufRead;
9
10/// Block-level content in a document body
11#[derive(Clone, Debug)]
12pub enum BlockContent {
13    /// Paragraph
14    Paragraph(Paragraph),
15    /// Table
16    Table(Table),
17    /// Unknown element (preserved for round-trip)
18    Unknown(RawXmlNode),
19}
20
21/// Document body (w:body)
22#[derive(Clone, Debug, Default)]
23pub struct Body {
24    /// Block-level content
25    pub content: Vec<BlockContent>,
26    /// Section properties (last sectPr in body)
27    pub section_properties: Option<RawXmlNode>,
28}
29
30impl Body {
31    /// Parse body from XML reader (after w:body start tag)
32    pub fn from_reader<R: BufRead>(reader: &mut Reader<R>) -> Result<Self> {
33        let mut body = Body::default();
34        let mut buf = Vec::new();
35
36        loop {
37            match reader.read_event_into(&mut buf)? {
38                Event::Start(e) => {
39                    let name = e.name();
40                    let local = name.local_name();
41
42                    match local.as_ref() {
43                        b"p" => {
44                            let para = Paragraph::from_reader(reader, &e)?;
45                            body.content.push(BlockContent::Paragraph(para));
46                        }
47                        b"tbl" => {
48                            let table = Table::from_reader(reader, &e)?;
49                            body.content.push(BlockContent::Table(table));
50                        }
51                        b"sectPr" => {
52                            // Section properties - preserve raw
53                            let raw = crate::xml::RawXmlElement::from_reader(reader, &e)?;
54                            body.section_properties = Some(RawXmlNode::Element(raw));
55                        }
56                        _ => {
57                            // Unknown element - preserve for round-trip
58                            let raw = crate::xml::RawXmlElement::from_reader(reader, &e)?;
59                            body.content
60                                .push(BlockContent::Unknown(RawXmlNode::Element(raw)));
61                        }
62                    }
63                }
64                Event::Empty(e) => {
65                    let name = e.name();
66                    let local = name.local_name();
67
68                    match local.as_ref() {
69                        b"p" => {
70                            // Empty paragraph
71                            let para = Paragraph::from_empty(&e)?;
72                            body.content.push(BlockContent::Paragraph(para));
73                        }
74                        _ => {
75                            // Preserve unknown empty elements
76                            let raw = crate::xml::RawXmlElement {
77                                name: String::from_utf8_lossy(e.name().as_ref()).to_string(),
78                                attributes: e
79                                    .attributes()
80                                    .filter_map(|a| a.ok())
81                                    .map(|a| {
82                                        (
83                                            String::from_utf8_lossy(a.key.as_ref()).to_string(),
84                                            String::from_utf8_lossy(&a.value).to_string(),
85                                        )
86                                    })
87                                    .collect(),
88                                children: Vec::new(),
89                                self_closing: true,
90                            };
91                            body.content
92                                .push(BlockContent::Unknown(RawXmlNode::Element(raw)));
93                        }
94                    }
95                }
96                Event::End(e) => {
97                    if e.name().local_name().as_ref() == b"body" {
98                        break;
99                    }
100                }
101                Event::Eof => break,
102                _ => {}
103            }
104            buf.clear();
105        }
106
107        Ok(body)
108    }
109
110    /// Get all paragraphs
111    pub fn paragraphs(&self) -> impl Iterator<Item = &Paragraph> {
112        self.content.iter().filter_map(|c| {
113            if let BlockContent::Paragraph(p) = c {
114                Some(p)
115            } else {
116                None
117            }
118        })
119    }
120
121    /// Get all paragraphs mutably
122    pub fn paragraphs_mut(&mut self) -> impl Iterator<Item = &mut Paragraph> {
123        self.content.iter_mut().filter_map(|c| {
124            if let BlockContent::Paragraph(p) = c {
125                Some(p)
126            } else {
127                None
128            }
129        })
130    }
131
132    /// Get all tables
133    pub fn tables(&self) -> impl Iterator<Item = &Table> {
134        self.content.iter().filter_map(|c| {
135            if let BlockContent::Table(t) = c {
136                Some(t)
137            } else {
138                None
139            }
140        })
141    }
142
143    /// Write body to XML writer
144    pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
145        writer.write_event(Event::Start(BytesStart::new("w:body")))?;
146
147        // Write content
148        for content in &self.content {
149            content.write_to(writer)?;
150        }
151
152        // Write section properties
153        if let Some(sect_pr) = &self.section_properties {
154            sect_pr.write_to(writer)?;
155        }
156
157        writer.write_event(Event::End(BytesEnd::new("w:body")))?;
158        Ok(())
159    }
160
161    /// Add a paragraph
162    pub fn add_paragraph(&mut self, para: Paragraph) {
163        self.content.push(BlockContent::Paragraph(para));
164    }
165
166    /// Add a table
167    pub fn add_table(&mut self, table: Table) {
168        self.content.push(BlockContent::Table(table));
169    }
170}
171
172impl BlockContent {
173    /// Write to XML writer
174    pub fn write_to<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
175        match self {
176            BlockContent::Paragraph(para) => para.write_to(writer),
177            BlockContent::Table(table) => table.write_to(writer),
178            BlockContent::Unknown(node) => node.write_to(writer),
179        }
180    }
181}