1use super::xmlchemy::XmlElement;
6
7#[derive(Debug, Clone, Default)]
9pub struct TableCellProperties {
10 pub margin_left: Option<u32>,
11 pub margin_right: Option<u32>,
12 pub margin_top: Option<u32>,
13 pub margin_bottom: Option<u32>,
14 pub anchor: Option<String>,
15}
16
17impl TableCellProperties {
18 pub fn parse(elem: &XmlElement) -> Self {
19 TableCellProperties {
20 margin_left: elem.attr("marL").and_then(|v| v.parse().ok()),
21 margin_right: elem.attr("marR").and_then(|v| v.parse().ok()),
22 margin_top: elem.attr("marT").and_then(|v| v.parse().ok()),
23 margin_bottom: elem.attr("marB").and_then(|v| v.parse().ok()),
24 anchor: elem.attr("anchor").map(|s| s.to_string()),
25 }
26 }
27
28 pub fn to_xml(&self) -> String {
29 let mut attrs = Vec::new();
30
31 if let Some(l) = self.margin_left {
32 attrs.push(format!(r#"marL="{l}""#));
33 }
34 if let Some(r) = self.margin_right {
35 attrs.push(format!(r#"marR="{r}""#));
36 }
37 if let Some(t) = self.margin_top {
38 attrs.push(format!(r#"marT="{t}""#));
39 }
40 if let Some(b) = self.margin_bottom {
41 attrs.push(format!(r#"marB="{b}""#));
42 }
43 if let Some(ref anchor) = self.anchor {
44 attrs.push(format!(r#"anchor="{anchor}""#));
45 }
46
47 if attrs.is_empty() {
48 "<a:tcPr/>".to_string()
49 } else {
50 format!("<a:tcPr {}/>", attrs.join(" "))
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub struct TableCell {
58 pub text: String,
59 pub row_span: u32,
60 pub col_span: u32,
61 pub properties: TableCellProperties,
62}
63
64impl TableCell {
65 pub fn new(text: &str) -> Self {
66 TableCell {
67 text: text.to_string(),
68 row_span: 1,
69 col_span: 1,
70 properties: TableCellProperties::default(),
71 }
72 }
73
74 pub fn parse(elem: &XmlElement) -> Self {
75 let text = elem.find_descendant("t")
76 .map(|t| t.text_content())
77 .unwrap_or_default();
78
79 let row_span = elem.attr("rowSpan").and_then(|v| v.parse().ok()).unwrap_or(1);
80 let col_span = elem.attr("gridSpan").and_then(|v| v.parse().ok()).unwrap_or(1);
81
82 let properties = elem.find("tcPr")
83 .map(|tc| TableCellProperties::parse(tc))
84 .unwrap_or_default();
85
86 TableCell { text, row_span, col_span, properties }
87 }
88
89 pub fn to_xml(&self) -> String {
90 let mut attrs = Vec::new();
91 if self.row_span > 1 {
92 let row_span = self.row_span;
93 attrs.push(format!(r#"rowSpan="{row_span}""#));
94 }
95 if self.col_span > 1 {
96 let col_span = self.col_span;
97 attrs.push(format!(r#"gridSpan="{col_span}""#));
98 }
99
100 let attr_str = if attrs.is_empty() { String::new() } else { format!(" {}", attrs.join(" ")) };
101
102 format!(
103 r#"<a:tc{}><a:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US"/><a:t>{}</a:t></a:r></a:p></a:txBody>{}</a:tc>"#,
104 attr_str,
105 escape_xml(&self.text),
106 self.properties.to_xml()
107 )
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct TableRow {
114 pub cells: Vec<TableCell>,
115 pub height: u32,
116}
117
118impl TableRow {
119 pub fn new() -> Self {
120 TableRow {
121 cells: Vec::new(),
122 height: 370840, }
124 }
125
126 pub fn parse(elem: &XmlElement) -> Self {
127 let height = elem.attr("h").and_then(|v| v.parse().ok()).unwrap_or(370840);
128 let cells = elem.find_all("tc")
129 .into_iter()
130 .map(|tc| TableCell::parse(tc))
131 .collect();
132
133 TableRow { cells, height }
134 }
135
136 pub fn to_xml(&self) -> String {
137 let height = self.height;
138 let mut xml = format!(r#"<a:tr h="{height}">"#);
139 for cell in &self.cells {
140 xml.push_str(&cell.to_xml());
141 }
142 xml.push_str("</a:tr>");
143 xml
144 }
145
146 pub fn add_cell(mut self, cell: TableCell) -> Self {
147 self.cells.push(cell);
148 self
149 }
150}
151
152impl Default for TableRow {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158#[derive(Debug, Clone)]
160pub struct GridColumn {
161 pub width: u32,
162}
163
164impl GridColumn {
165 pub fn new(width: u32) -> Self {
166 GridColumn { width }
167 }
168
169 pub fn to_xml(&self) -> String {
170 let width = self.width;
171 format!(r#"<a:gridCol w="{width}"/>"#)
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct Table {
178 pub rows: Vec<TableRow>,
179 pub grid_columns: Vec<GridColumn>,
180}
181
182impl Table {
183 pub fn new() -> Self {
184 Table {
185 rows: Vec::new(),
186 grid_columns: Vec::new(),
187 }
188 }
189
190 pub fn parse(elem: &XmlElement) -> Self {
191 let mut table = Table::new();
192
193 if let Some(tbl_grid) = elem.find("tblGrid") {
195 table.grid_columns = tbl_grid.find_all("gridCol")
196 .into_iter()
197 .map(|gc| {
198 let width = gc.attr("w").and_then(|v| v.parse().ok()).unwrap_or(914400);
199 GridColumn::new(width)
200 })
201 .collect();
202 }
203
204 table.rows = elem.find_all("tr")
206 .into_iter()
207 .map(|tr| TableRow::parse(tr))
208 .collect();
209
210 table
211 }
212
213 pub fn to_xml(&self) -> String {
214 let mut xml = String::from("<a:tbl>");
215
216 xml.push_str("<a:tblPr firstRow=\"1\" bandRow=\"1\"/>");
218
219 xml.push_str("<a:tblGrid>");
221 for col in &self.grid_columns {
222 xml.push_str(&col.to_xml());
223 }
224 xml.push_str("</a:tblGrid>");
225
226 for row in &self.rows {
228 xml.push_str(&row.to_xml());
229 }
230
231 xml.push_str("</a:tbl>");
232 xml
233 }
234
235 pub fn row_count(&self) -> usize {
236 self.rows.len()
237 }
238
239 pub fn col_count(&self) -> usize {
240 self.grid_columns.len()
241 }
242
243 pub fn add_row(mut self, row: TableRow) -> Self {
244 self.rows.push(row);
245 self
246 }
247
248 pub fn add_column(mut self, width: u32) -> Self {
249 self.grid_columns.push(GridColumn::new(width));
250 self
251 }
252}
253
254impl Default for Table {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260fn escape_xml(s: &str) -> String {
261 s.replace('&', "&")
262 .replace('<', "<")
263 .replace('>', ">")
264 .replace('"', """)
265 .replace('\'', "'")
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_table_cell_to_xml() {
274 let cell = TableCell::new("Hello");
275 let xml = cell.to_xml();
276
277 assert!(xml.contains("<a:tc>"));
278 assert!(xml.contains("Hello"));
279 }
280
281 #[test]
282 fn test_table_row_to_xml() {
283 let row = TableRow::new()
284 .add_cell(TableCell::new("A"))
285 .add_cell(TableCell::new("B"));
286
287 let xml = row.to_xml();
288 assert!(xml.contains("<a:tr"));
289 assert!(xml.contains("A"));
290 assert!(xml.contains("B"));
291 }
292
293 #[test]
294 fn test_table_to_xml() {
295 let table = Table::new()
296 .add_column(914400)
297 .add_column(914400)
298 .add_row(TableRow::new()
299 .add_cell(TableCell::new("A1"))
300 .add_cell(TableCell::new("B1")));
301
302 let xml = table.to_xml();
303 assert!(xml.contains("<a:tbl>"));
304 assert!(xml.contains("<a:tblGrid>"));
305 assert!(xml.contains("A1"));
306 }
307}