1use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
4use quick_xml::{Reader, Writer};
5
6use crate::error::Result;
7use crate::header_footer::{HdrFtrRef, HdrFtrType};
8use crate::namespace::{W_NS, matches_local_name};
9use crate::numbering::word_prefixes_at;
10use crate::properties::{get_val_attr, is_word_element};
11use crate::raw_xml::{capture_element, capture_empty_element};
12use crate::shared::{ST_PageOrientation, ST_SectionType};
13use crate::table::CT_Tbl;
14use crate::text::CT_P;
15use crate::units::Twips;
16
17#[derive(Debug, Clone, PartialEq)]
19pub enum BodyContent {
20 Paragraph(CT_P),
21 Table(CT_Tbl),
22 RawXml(Vec<u8>),
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub struct CT_Column {
29 pub width: Option<Twips>,
31 pub space: Option<Twips>,
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct CT_Columns {
38 pub num: Option<u32>,
40 pub space: Option<Twips>,
42 pub equal_width: Option<bool>,
44 pub sep: Option<bool>,
46 pub columns: Vec<CT_Column>,
48}
49
50impl Default for CT_Columns {
51 fn default() -> Self {
52 CT_Columns {
53 num: Some(1),
54 space: Some(Twips(720)),
55 equal_width: Some(true),
56 sep: None,
57 columns: Vec::new(),
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq)]
64#[allow(non_snake_case)]
65pub struct CT_SectPr {
66 pub page_width: Option<Twips>,
68 pub page_height: Option<Twips>,
70 pub orientation: Option<ST_PageOrientation>,
72 pub margin_top: Option<Twips>,
74 pub margin_right: Option<Twips>,
76 pub margin_bottom: Option<Twips>,
78 pub margin_left: Option<Twips>,
80 pub gutter: Option<Twips>,
82 pub header_distance: Option<Twips>,
84 pub footer_distance: Option<Twips>,
86 pub section_type: Option<ST_SectionType>,
88 pub columns: Option<CT_Columns>,
90 pub title_pg: Option<bool>,
92 pub header_refs: Vec<HdrFtrRef>,
94 pub footer_refs: Vec<HdrFtrRef>,
96 pub extra_xml: Vec<Vec<u8>>,
98}
99
100#[allow(non_snake_case)]
101impl CT_SectPr {
102 pub fn default_letter() -> Self {
104 CT_SectPr {
105 page_width: Some(Twips(12240)), page_height: Some(Twips(15840)), orientation: Some(ST_PageOrientation::Portrait),
108 margin_top: Some(Twips(1440)), margin_right: Some(Twips(1440)), margin_bottom: Some(Twips(1440)), margin_left: Some(Twips(1440)), gutter: Some(Twips(0)),
113 header_distance: Some(Twips(720)),
114 footer_distance: Some(Twips(720)),
115 section_type: None,
116 columns: None,
117 title_pg: None,
118 header_refs: Vec::new(),
119 footer_refs: Vec::new(),
120 extra_xml: Vec::new(),
121 }
122 }
123
124 pub fn default_a4() -> Self {
126 CT_SectPr {
127 page_width: Some(Twips(11906)), page_height: Some(Twips(16838)), orientation: Some(ST_PageOrientation::Portrait),
130 margin_top: Some(Twips(1440)),
131 margin_right: Some(Twips(1440)),
132 margin_bottom: Some(Twips(1440)),
133 margin_left: Some(Twips(1440)),
134 gutter: Some(Twips(0)),
135 header_distance: Some(Twips(720)),
136 footer_distance: Some(Twips(720)),
137 section_type: None,
138 columns: None,
139 title_pg: None,
140 header_refs: Vec::new(),
141 footer_refs: Vec::new(),
142 extra_xml: Vec::new(),
143 }
144 }
145
146 pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
147 let mut sect = CT_SectPr {
148 page_width: None,
149 page_height: None,
150 orientation: None,
151 margin_top: None,
152 margin_right: None,
153 margin_bottom: None,
154 margin_left: None,
155 gutter: None,
156 header_distance: None,
157 footer_distance: None,
158 section_type: None,
159 columns: None,
160 title_pg: None,
161 header_refs: Vec::new(),
162 footer_refs: Vec::new(),
163 extra_xml: Vec::new(),
164 };
165 let mut buf = Vec::new();
166
167 loop {
168 match reader.read_event_into(&mut buf) {
169 Ok(Event::Empty(ref e)) => {
170 let name = e.name();
171 if matches_local_name(name.as_ref(), b"pgSz") {
172 for attr in e.attributes() {
173 let attr = attr?;
174 let key = attr.key.as_ref();
175 let val_str = std::str::from_utf8(&attr.value)?;
176 if matches_local_name(key, b"w") {
177 sect.page_width = Some(Twips(val_str.parse()?));
178 } else if matches_local_name(key, b"h") {
179 sect.page_height = Some(Twips(val_str.parse()?));
180 } else if matches_local_name(key, b"orient") {
181 sect.orientation = Some(ST_PageOrientation::from_str(val_str)?);
182 }
183 }
184 } else if matches_local_name(name.as_ref(), b"pgMar") {
185 for attr in e.attributes() {
186 let attr = attr?;
187 let key = attr.key.as_ref();
188 let val: i32 = std::str::from_utf8(&attr.value)?.parse()?;
189 if matches_local_name(key, b"top") {
190 sect.margin_top = Some(Twips(val));
191 } else if matches_local_name(key, b"right")
192 || matches_local_name(key, b"end")
193 {
194 sect.margin_right = Some(Twips(val));
195 } else if matches_local_name(key, b"bottom") {
196 sect.margin_bottom = Some(Twips(val));
197 } else if matches_local_name(key, b"left")
198 || matches_local_name(key, b"start")
199 {
200 sect.margin_left = Some(Twips(val));
201 } else if matches_local_name(key, b"gutter") {
202 sect.gutter = Some(Twips(val));
203 } else if matches_local_name(key, b"header") {
204 sect.header_distance = Some(Twips(val));
205 } else if matches_local_name(key, b"footer") {
206 sect.footer_distance = Some(Twips(val));
207 }
208 }
209 } else if matches_local_name(name.as_ref(), b"type") {
210 if let Some(val) = get_val_attr(e)? {
211 sect.section_type = Some(ST_SectionType::from_str(&val)?);
212 }
213 } else if matches_local_name(name.as_ref(), b"cols") {
214 sect.columns = Some(Self::parse_cols_empty(e)?);
215 } else if matches_local_name(name.as_ref(), b"headerReference") {
216 let mut hdr_type = HdrFtrType::Default;
217 let mut rel_id = String::new();
218 for attr in e.attributes() {
219 let attr = attr?;
220 let key = attr.key.as_ref();
221 let val = std::str::from_utf8(&attr.value)?;
222 if matches_local_name(key, b"type") {
223 hdr_type = HdrFtrType::from_str(val);
224 } else if matches_local_name(key, b"id") {
225 rel_id = val.to_string();
226 }
227 }
228 if !rel_id.is_empty() {
229 sect.header_refs.push(HdrFtrRef {
230 hdr_ftr_type: hdr_type,
231 rel_id,
232 });
233 }
234 } else if matches_local_name(name.as_ref(), b"footerReference") {
235 let mut ftr_type = HdrFtrType::Default;
236 let mut rel_id = String::new();
237 for attr in e.attributes() {
238 let attr = attr?;
239 let key = attr.key.as_ref();
240 let val = std::str::from_utf8(&attr.value)?;
241 if matches_local_name(key, b"type") {
242 ftr_type = HdrFtrType::from_str(val);
243 } else if matches_local_name(key, b"id") {
244 rel_id = val.to_string();
245 }
246 }
247 if !rel_id.is_empty() {
248 sect.footer_refs.push(HdrFtrRef {
249 hdr_ftr_type: ftr_type,
250 rel_id,
251 });
252 }
253 } else if matches_local_name(name.as_ref(), b"titlePg") {
254 sect.title_pg = Some(true);
255 } else {
256 sect.extra_xml.push(capture_empty_element(e)?);
258 }
259 }
260 Ok(Event::Start(ref e)) => {
261 let name = e.name();
262 if matches_local_name(name.as_ref(), b"cols") {
263 sect.columns = Some(Self::parse_cols_start(reader, e)?);
264 } else {
265 sect.extra_xml.push(capture_element(reader, e)?);
267 }
268 }
269 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"sectPr") => {
270 break;
271 }
272 Ok(Event::Eof) => break,
273 Err(e) => return Err(e.into()),
274 _ => {}
275 }
276 buf.clear();
277 }
278
279 Ok(sect)
280 }
281
282 fn parse_cols_attrs(e: &BytesStart) -> Result<CT_Columns> {
283 let mut cols = CT_Columns::default();
284 for attr in e.attributes() {
285 let attr = attr?;
286 let key = attr.key.as_ref();
287 let val_str = std::str::from_utf8(&attr.value)?;
288 if matches_local_name(key, b"num") {
289 cols.num = Some(val_str.parse()?);
290 } else if matches_local_name(key, b"space") {
291 cols.space = Some(Twips(val_str.parse()?));
292 } else if matches_local_name(key, b"equalWidth") {
293 cols.equal_width = Some(val_str == "1" || val_str == "true");
294 } else if matches_local_name(key, b"sep") {
295 cols.sep = Some(val_str == "1" || val_str == "true");
296 }
297 }
298 Ok(cols)
299 }
300
301 fn parse_cols_empty(e: &BytesStart) -> Result<CT_Columns> {
302 Self::parse_cols_attrs(e)
303 }
304
305 fn parse_cols_start(reader: &mut Reader<&[u8]>, e: &BytesStart) -> Result<CT_Columns> {
306 let mut cols = Self::parse_cols_attrs(e)?;
307 let mut buf = Vec::new();
308
309 loop {
310 match reader.read_event_into(&mut buf) {
311 Ok(Event::Empty(ref e)) if matches_local_name(e.name().as_ref(), b"col") => {
312 let mut width = None;
313 let mut space = None;
314 for attr in e.attributes() {
315 let attr = attr?;
316 let key = attr.key.as_ref();
317 let val: i32 = std::str::from_utf8(&attr.value)?.parse()?;
318 if matches_local_name(key, b"w") {
319 width = Some(Twips(val));
320 } else if matches_local_name(key, b"space") {
321 space = Some(Twips(val));
322 }
323 }
324 cols.columns.push(CT_Column { width, space });
325 }
326 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"cols") => {
327 break;
328 }
329 Ok(Event::Eof) => break,
330 Err(e) => return Err(e.into()),
331 _ => {}
332 }
333 buf.clear();
334 }
335
336 Ok(cols)
337 }
338
339 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
340 let mut buf = itoa::Buffer::new();
341 writer.write_event(Event::Start(BytesStart::new("w:sectPr")))?;
342
343 for hdr in &self.header_refs {
345 let mut e = BytesStart::new("w:headerReference");
346 e.push_attribute(("w:type", hdr.hdr_ftr_type.to_str()));
347 e.push_attribute(("r:id", hdr.rel_id.as_str()));
348 writer.write_event(Event::Empty(e))?;
349 }
350
351 for ftr in &self.footer_refs {
353 let mut e = BytesStart::new("w:footerReference");
354 e.push_attribute(("w:type", ftr.hdr_ftr_type.to_str()));
355 e.push_attribute(("r:id", ftr.rel_id.as_str()));
356 writer.write_event(Event::Empty(e))?;
357 }
358
359 if let Some(st) = self.section_type {
361 let mut e = BytesStart::new("w:type");
362 e.push_attribute(("w:val", st.to_str()));
363 writer.write_event(Event::Empty(e))?;
364 }
365
366 if self.page_width.is_some() || self.page_height.is_some() || self.orientation.is_some() {
368 let mut e = BytesStart::new("w:pgSz");
369 if let Some(w) = self.page_width {
370 e.push_attribute(("w:w", buf.format(w.0)));
371 }
372 if let Some(h) = self.page_height {
373 e.push_attribute(("w:h", buf.format(h.0)));
374 }
375 if let Some(orient) = self.orientation
376 && orient == ST_PageOrientation::Landscape
377 {
378 e.push_attribute(("w:orient", orient.to_str()));
379 }
380 writer.write_event(Event::Empty(e))?;
381 }
382
383 if self.margin_top.is_some()
385 || self.margin_right.is_some()
386 || self.margin_bottom.is_some()
387 || self.margin_left.is_some()
388 {
389 let mut e = BytesStart::new("w:pgMar");
390 if let Some(t) = self.margin_top {
391 e.push_attribute(("w:top", buf.format(t.0)));
392 }
393 if let Some(r) = self.margin_right {
394 e.push_attribute(("w:right", buf.format(r.0)));
395 }
396 if let Some(b) = self.margin_bottom {
397 e.push_attribute(("w:bottom", buf.format(b.0)));
398 }
399 if let Some(l) = self.margin_left {
400 e.push_attribute(("w:left", buf.format(l.0)));
401 }
402 if let Some(g) = self.gutter {
403 e.push_attribute(("w:gutter", buf.format(g.0)));
404 }
405 if let Some(h) = self.header_distance {
406 e.push_attribute(("w:header", buf.format(h.0)));
407 }
408 if let Some(f) = self.footer_distance {
409 e.push_attribute(("w:footer", buf.format(f.0)));
410 }
411 writer.write_event(Event::Empty(e))?;
412 }
413
414 if let Some(ref cols) = self.columns {
416 if cols.columns.is_empty() {
417 let mut e = BytesStart::new("w:cols");
419 if let Some(num) = cols.num {
420 e.push_attribute(("w:num", buf.format(num)));
421 }
422 if let Some(space) = cols.space {
423 e.push_attribute(("w:space", buf.format(space.0)));
424 }
425 if let Some(eq) = cols.equal_width
426 && !eq
427 {
428 e.push_attribute(("w:equalWidth", "0"));
429 }
430 if let Some(sep) = cols.sep
431 && sep
432 {
433 e.push_attribute(("w:sep", "1"));
434 }
435 writer.write_event(Event::Empty(e))?;
436 } else {
437 let mut e = BytesStart::new("w:cols");
439 if let Some(num) = cols.num {
440 e.push_attribute(("w:num", buf.format(num)));
441 }
442 if let Some(eq) = cols.equal_width {
443 e.push_attribute(("w:equalWidth", if eq { "1" } else { "0" }));
444 }
445 if let Some(sep) = cols.sep
446 && sep
447 {
448 e.push_attribute(("w:sep", "1"));
449 }
450 writer.write_event(Event::Start(e))?;
451
452 for col in &cols.columns {
453 let mut ce = BytesStart::new("w:col");
454 if let Some(w) = col.width {
455 ce.push_attribute(("w:w", buf.format(w.0)));
456 }
457 if let Some(s) = col.space {
458 ce.push_attribute(("w:space", buf.format(s.0)));
459 }
460 writer.write_event(Event::Empty(ce))?;
461 }
462
463 writer.write_event(Event::End(BytesEnd::new("w:cols")))?;
464 }
465 }
466
467 if let Some(true) = self.title_pg {
469 writer.write_event(Event::Empty(BytesStart::new("w:titlePg")))?;
470 }
471
472 for raw in &self.extra_xml {
474 writer.get_mut().write_all(raw)?;
475 }
476
477 writer.write_event(Event::End(BytesEnd::new("w:sectPr")))?;
478 Ok(())
479 }
480}
481
482#[derive(Debug, Clone, PartialEq)]
484#[allow(non_snake_case)]
485pub struct CT_Body {
486 pub content: Vec<BodyContent>,
488 pub sect_pr: Option<CT_SectPr>,
489}
490
491#[allow(non_snake_case)]
492impl CT_Body {
493 pub fn new() -> Self {
494 CT_Body {
495 content: Vec::new(),
496 sect_pr: Some(CT_SectPr::default_letter()),
497 }
498 }
499
500 pub fn paragraphs(&self) -> impl Iterator<Item = &CT_P> {
502 self.content.iter().filter_map(|c| match c {
503 BodyContent::Paragraph(p) => Some(p),
504 _ => None,
505 })
506 }
507
508 pub fn paragraphs_mut(&mut self) -> impl Iterator<Item = &mut CT_P> {
510 self.content.iter_mut().filter_map(|c| match c {
511 BodyContent::Paragraph(p) => Some(p),
512 _ => None,
513 })
514 }
515
516 pub fn tables(&self) -> impl Iterator<Item = &CT_Tbl> {
518 self.content.iter().filter_map(|c| match c {
519 BodyContent::Table(t) => Some(t),
520 _ => None,
521 })
522 }
523
524 pub fn tables_mut(&mut self) -> impl Iterator<Item = &mut CT_Tbl> {
526 self.content.iter_mut().filter_map(|c| match c {
527 BodyContent::Table(t) => Some(t),
528 _ => None,
529 })
530 }
531
532 pub fn add_paragraph(&mut self, p: CT_P) {
534 self.content.push(BodyContent::Paragraph(p));
535 }
536
537 pub fn add_table(&mut self, tbl: CT_Tbl) {
539 self.content.push(BodyContent::Table(tbl));
540 }
541
542 pub fn content_count(&self) -> usize {
544 self.content.len()
545 }
546
547 pub fn insert_paragraph(&mut self, index: usize, p: CT_P) {
551 self.content.insert(index, BodyContent::Paragraph(p));
552 }
553
554 pub fn insert_table(&mut self, index: usize, tbl: CT_Tbl) {
558 self.content.insert(index, BodyContent::Table(tbl));
559 }
560
561 pub fn find_paragraph_index(&self, text: &str) -> Option<usize> {
563 self.content.iter().position(|c| match c {
564 BodyContent::Paragraph(p) => p.text().contains(text),
565 _ => false,
566 })
567 }
568
569 pub fn remove(&mut self, index: usize) -> Option<BodyContent> {
571 if index < self.content.len() {
572 Some(self.content.remove(index))
573 } else {
574 None
575 }
576 }
577
578 pub fn get(&self, index: usize) -> Option<&BodyContent> {
580 self.content.get(index)
581 }
582
583 pub fn get_mut(&mut self, index: usize) -> Option<&mut BodyContent> {
585 self.content.get_mut(index)
586 }
587
588 pub fn from_xml(reader: &mut Reader<&[u8]>) -> Result<Self> {
589 Self::from_xml_with_prefixes(reader, &["w".to_string()])
590 }
591
592 fn from_xml_with_prefixes(
593 reader: &mut Reader<&[u8]>,
594 word_prefixes: &[String],
595 ) -> Result<Self> {
596 let mut content = Vec::new();
597 let mut sect_pr = None;
598 let mut buf = Vec::new();
599
600 loop {
601 match reader.read_event_into(&mut buf) {
602 Ok(Event::Start(ref e)) => {
603 let name = e.name();
604 let prefixes = word_prefixes_at(e, word_prefixes)?;
605 if is_word_element(name.as_ref(), b"p", &prefixes) {
606 content.push(BodyContent::Paragraph(CT_P::from_xml_with_prefixes(
607 reader, &prefixes,
608 )?));
609 } else if is_word_element(name.as_ref(), b"tbl", &prefixes) {
610 content.push(BodyContent::Table(CT_Tbl::from_xml_with_prefixes(
611 reader, &prefixes,
612 )?));
613 } else if matches_local_name(name.as_ref(), b"sectPr") {
614 sect_pr = Some(CT_SectPr::from_xml(reader)?);
615 } else {
616 content.push(BodyContent::RawXml(capture_element(reader, e)?));
618 }
619 }
620 Ok(Event::Empty(ref e)) => {
621 let name = e.name();
622 if !matches_local_name(name.as_ref(), b"body") {
623 content.push(BodyContent::RawXml(capture_empty_element(e)?));
624 }
625 }
626 Ok(Event::End(ref e)) if matches_local_name(e.name().as_ref(), b"body") => {
627 break;
628 }
629 Ok(Event::Eof) => break,
630 Err(e) => return Err(e.into()),
631 _ => {}
632 }
633 buf.clear();
634 }
635
636 Ok(CT_Body { content, sect_pr })
637 }
638
639 pub fn to_xml<W: std::io::Write>(&self, writer: &mut Writer<W>) -> Result<()> {
640 writer.write_event(Event::Start(BytesStart::new("w:body")))?;
641
642 for item in &self.content {
643 match item {
644 BodyContent::Paragraph(p) => p.to_xml(writer)?,
645 BodyContent::Table(t) => t.to_xml(writer)?,
646 BodyContent::RawXml(raw) => {
647 writer.get_mut().write_all(raw)?;
648 }
649 }
650 }
651
652 if let Some(ref sect) = self.sect_pr {
653 sect.to_xml(writer)?;
654 }
655
656 writer.write_event(Event::End(BytesEnd::new("w:body")))?;
657 Ok(())
658 }
659}
660
661impl Default for CT_Body {
662 fn default() -> Self {
663 Self::new()
664 }
665}
666
667#[derive(Debug, Clone, PartialEq)]
669#[allow(non_snake_case)]
670pub struct CT_Document {
671 pub body: CT_Body,
672 pub extra_namespaces: Vec<(String, String)>,
675 pub background_xml: Option<Vec<u8>>,
677}
678
679#[allow(non_snake_case)]
680impl CT_Document {
681 pub fn new() -> Self {
682 CT_Document {
683 body: CT_Body::new(),
684 extra_namespaces: Vec::new(),
685 background_xml: None,
686 }
687 }
688
689 pub fn from_xml(xml: &[u8]) -> Result<Self> {
691 let mut reader = Reader::from_reader(xml);
692 reader.config_mut().trim_text(true);
693
694 let mut body = None;
695 let mut extra_namespaces = Vec::new();
696 let mut background_xml = None;
697 let mut buf = Vec::new();
698 let mut word_prefixes = Vec::new();
699
700 let known_ns: &[&[u8]] = &[b"xmlns:w", b"xmlns:r", b"xmlns:mc", b"xmlns"];
702
703 loop {
704 match reader.read_event_into(&mut buf) {
705 Ok(Event::Start(ref e)) => {
706 let name = e.name();
707 let prefixes = word_prefixes_at(e, &word_prefixes)?;
708 if matches_local_name(name.as_ref(), b"body") {
709 body = Some(CT_Body::from_xml_with_prefixes(&mut reader, &prefixes)?);
710 } else if matches_local_name(name.as_ref(), b"document") {
711 for attr in e.attributes().flatten() {
713 let key = attr.key.as_ref();
714 if (key.starts_with(b"xmlns:") || key == b"xmlns")
715 && !known_ns.contains(&key)
716 {
717 let key_str = std::str::from_utf8(key).unwrap_or("").to_string();
718 let val_str =
719 std::str::from_utf8(&attr.value).unwrap_or("").to_string();
720 extra_namespaces.push((key_str, val_str));
721 }
722 }
723 word_prefixes = prefixes;
725 } else if matches_local_name(name.as_ref(), b"background") {
726 background_xml = Some(capture_element(&mut reader, e)?);
727 } else {
728 reader.read_to_end_into(name, &mut Vec::new())?;
729 }
730 }
731 Ok(Event::Empty(ref e)) => {
732 if matches_local_name(e.name().as_ref(), b"background") {
733 background_xml = Some(capture_empty_element(e)?);
734 }
735 }
736 Ok(Event::Eof) => break,
737 Err(e) => return Err(e.into()),
738 _ => {}
739 }
740 buf.clear();
741 }
742
743 Ok(CT_Document {
744 body: body.unwrap_or_default(),
745 extra_namespaces,
746 background_xml,
747 })
748 }
749
750 pub fn to_xml(&self) -> Result<Vec<u8>> {
752 let mut writer = Writer::new_with_indent(Vec::new(), b' ', 2);
753
754 writer.write_event(Event::Decl(BytesDecl::new(
755 "1.0",
756 Some("UTF-8"),
757 Some("yes"),
758 )))?;
759
760 let mut doc_start = BytesStart::new("w:document");
761 doc_start.push_attribute(("xmlns:w", W_NS));
762 doc_start.push_attribute((
763 "xmlns:r",
764 "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
765 ));
766 doc_start.push_attribute((
767 "xmlns:mc",
768 "http://schemas.openxmlformats.org/markup-compatibility/2006",
769 ));
770
771 let wp_ns = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
773 let mut has_wp = false;
774 for (key, _) in &self.extra_namespaces {
775 if key == "xmlns:wp" {
776 has_wp = true;
777 break;
778 }
779 }
780 if !has_wp {
781 doc_start.push_attribute(("xmlns:wp", wp_ns));
782 }
783
784 for (key, val) in &self.extra_namespaces {
786 doc_start.push_attribute((key.as_str(), val.as_str()));
787 }
788
789 writer.write_event(Event::Start(doc_start))?;
790
791 if let Some(ref bg) = self.background_xml {
793 writer.get_mut().extend_from_slice(bg);
794 }
795
796 self.body.to_xml(&mut writer)?;
797
798 writer.write_event(Event::End(BytesEnd::new("w:document")))?;
799
800 Ok(writer.into_inner())
801 }
802}
803
804impl Default for CT_Document {
805 fn default() -> Self {
806 Self::new()
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
815 fn round_trip_document() {
816 let mut doc = CT_Document::new();
817 let mut p = CT_P::new();
818 p.add_run("Hello World");
819 doc.body.add_paragraph(p);
820
821 let xml = doc.to_xml().unwrap();
822 let parsed = CT_Document::from_xml(&xml).unwrap();
823
824 let paras: Vec<_> = parsed.body.paragraphs().collect();
825 assert_eq!(paras.len(), 1);
826 assert_eq!(paras[0].text(), "Hello World");
827 }
828
829 #[test]
830 fn default_namespace_document_paragraph_properties_parse_in_scope() {
831 let xml = format!(
832 r#"<document xmlns="{W_NS}" xmlns:q="{W_NS}" xmlns:ext="urn:producer"><body><p><pPr><ext:jc ext:val="right"/><jc q:val="center"/></pPr><r><t>Scoped</t></r></p></body></document>"#
833 );
834 let parsed = CT_Document::from_xml(xml.as_bytes()).unwrap();
835 let paragraph = parsed.body.paragraphs().next().unwrap();
836 assert_eq!(paragraph.text(), "Scoped");
837 assert_eq!(
838 paragraph.properties.as_ref().unwrap().jc,
839 Some(crate::shared::ST_Jc::Center)
840 );
841 }
842
843 #[test]
844 fn round_trip_with_section() {
845 let doc = CT_Document::new();
846 let xml = doc.to_xml().unwrap();
847 let parsed = CT_Document::from_xml(&xml).unwrap();
848 assert!(parsed.body.sect_pr.is_some());
849 let sect = parsed.body.sect_pr.unwrap();
850 assert_eq!(sect.page_width, Some(Twips(12240)));
851 }
852
853 #[test]
854 fn round_trip_landscape() {
855 let mut doc = CT_Document::new();
856 let sect = doc.body.sect_pr.as_mut().unwrap();
857 sect.orientation = Some(ST_PageOrientation::Landscape);
858 sect.page_width = Some(Twips(15840)); sect.page_height = Some(Twips(12240)); let xml = doc.to_xml().unwrap();
862 let parsed = CT_Document::from_xml(&xml).unwrap();
863 let sect = parsed.body.sect_pr.unwrap();
864 assert_eq!(sect.orientation, Some(ST_PageOrientation::Landscape));
865 assert_eq!(sect.page_width, Some(Twips(15840)));
866 }
867
868 #[test]
869 fn round_trip_columns() {
870 let mut doc = CT_Document::new();
871 let sect = doc.body.sect_pr.as_mut().unwrap();
872 sect.columns = Some(CT_Columns {
873 num: Some(2),
874 space: Some(Twips(720)),
875 equal_width: Some(true),
876 sep: Some(true),
877 columns: Vec::new(),
878 });
879
880 let xml = doc.to_xml().unwrap();
881 let parsed = CT_Document::from_xml(&xml).unwrap();
882 let cols = parsed.body.sect_pr.unwrap().columns.unwrap();
883 assert_eq!(cols.num, Some(2));
884 assert_eq!(cols.space, Some(Twips(720)));
885 assert_eq!(cols.sep, Some(true));
886 }
887
888 #[test]
889 fn round_trip_section_type() {
890 let mut doc = CT_Document::new();
891 let sect = doc.body.sect_pr.as_mut().unwrap();
892 sect.section_type = Some(ST_SectionType::Continuous);
893 sect.title_pg = Some(true);
894
895 let xml = doc.to_xml().unwrap();
896 let parsed = CT_Document::from_xml(&xml).unwrap();
897 let sect = parsed.body.sect_pr.unwrap();
898 assert_eq!(sect.section_type, Some(ST_SectionType::Continuous));
899 assert_eq!(sect.title_pg, Some(true));
900 }
901
902 #[test]
903 fn insert_paragraph_at_beginning() {
904 let mut body = CT_Body::new();
905 let mut p1 = CT_P::new();
906 p1.add_run("First");
907 body.add_paragraph(p1);
908
909 let mut p0 = CT_P::new();
910 p0.add_run("Inserted");
911 body.insert_paragraph(0, p0);
912
913 assert_eq!(body.content_count(), 2);
914 match &body.content[0] {
915 BodyContent::Paragraph(p) => assert_eq!(p.text(), "Inserted"),
916 _ => panic!("expected paragraph"),
917 }
918 match &body.content[1] {
919 BodyContent::Paragraph(p) => assert_eq!(p.text(), "First"),
920 _ => panic!("expected paragraph"),
921 }
922 }
923
924 #[test]
925 fn insert_paragraph_in_middle() {
926 let mut body = CT_Body::new();
927 let mut p1 = CT_P::new();
928 p1.add_run("First");
929 body.add_paragraph(p1);
930 let mut p2 = CT_P::new();
931 p2.add_run("Third");
932 body.add_paragraph(p2);
933
934 let mut mid = CT_P::new();
935 mid.add_run("Middle");
936 body.insert_paragraph(1, mid);
937
938 assert_eq!(body.content_count(), 3);
939 let texts: Vec<_> = body.paragraphs().map(|p| p.text()).collect();
940 assert_eq!(texts, vec!["First", "Middle", "Third"]);
941 }
942
943 #[test]
944 fn find_paragraph_index_match() {
945 let mut body = CT_Body::new();
946 let mut p1 = CT_P::new();
947 p1.add_run("Hello World");
948 body.add_paragraph(p1);
949 let mut p2 = CT_P::new();
950 p2.add_run("INSERT_HERE");
951 body.add_paragraph(p2);
952
953 assert_eq!(body.find_paragraph_index("INSERT_HERE"), Some(1));
954 assert_eq!(body.find_paragraph_index("NONEXISTENT"), None);
955 }
956
957 #[test]
958 fn remove_content() {
959 let mut body = CT_Body::new();
960 let mut p1 = CT_P::new();
961 p1.add_run("First");
962 body.add_paragraph(p1);
963 let mut p2 = CT_P::new();
964 p2.add_run("Second");
965 body.add_paragraph(p2);
966
967 let removed = body.remove(0);
968 assert!(removed.is_some());
969 assert_eq!(body.content_count(), 1);
970 match &body.content[0] {
971 BodyContent::Paragraph(p) => assert_eq!(p.text(), "Second"),
972 _ => panic!("expected paragraph"),
973 }
974
975 assert!(body.remove(5).is_none());
977 }
978
979 #[test]
980 fn get_and_get_mut() {
981 let mut body = CT_Body::new();
982 let mut p = CT_P::new();
983 p.add_run("Test");
984 body.add_paragraph(p);
985
986 assert!(body.get(0).is_some());
987 assert!(body.get(1).is_none());
988
989 if let Some(BodyContent::Paragraph(p)) = body.get_mut(0) {
990 p.add_run(" Modified");
991 }
992 match body.get(0).unwrap() {
993 BodyContent::Paragraph(p) => assert_eq!(p.text(), "Test Modified"),
994 _ => panic!("expected paragraph"),
995 }
996 }
997
998 #[test]
999 fn sect_pr_section_type_and_orientation_round_trip() {
1000 let mut doc = CT_Document::new();
1001 let sect = doc.body.sect_pr.as_mut().unwrap();
1002 sect.section_type = Some(ST_SectionType::NextPage);
1003 sect.orientation = Some(ST_PageOrientation::Landscape);
1004 sect.page_width = Some(Twips(15840));
1005 sect.page_height = Some(Twips(12240));
1006
1007 let xml = doc.to_xml().unwrap();
1008 let parsed = CT_Document::from_xml(&xml).unwrap();
1009 let sect2 = parsed.body.sect_pr.unwrap();
1010 assert_eq!(sect2.section_type, Some(ST_SectionType::NextPage));
1011 assert_eq!(sect2.orientation, Some(ST_PageOrientation::Landscape));
1012 assert_eq!(sect2.page_width, Some(Twips(15840)));
1013 assert_eq!(sect2.page_height, Some(Twips(12240)));
1014 }
1015
1016 #[test]
1017 fn sect_pr_all_section_types() {
1018 for section_type in [
1019 ST_SectionType::NextPage,
1020 ST_SectionType::Continuous,
1021 ST_SectionType::EvenPage,
1022 ST_SectionType::OddPage,
1023 ] {
1024 let mut doc = CT_Document::new();
1025 let sect = doc.body.sect_pr.as_mut().unwrap();
1026 sect.section_type = Some(section_type);
1027
1028 let xml = doc.to_xml().unwrap();
1029 let parsed = CT_Document::from_xml(&xml).unwrap();
1030 let sect2 = parsed.body.sect_pr.unwrap();
1031 assert_eq!(
1032 sect2.section_type,
1033 Some(section_type),
1034 "section type round-trip failed for {section_type:?}"
1035 );
1036 }
1037 }
1038
1039 #[test]
1040 fn sect_pr_in_paragraph_ppr_round_trip() {
1041 let mut doc = CT_Document::new();
1043 let mut p = CT_P::new();
1044 p.add_run("Section break paragraph");
1045 let mut ppr = crate::properties::CT_PPr::default();
1046 let mut sect = CT_SectPr::default_letter();
1047 sect.section_type = Some(ST_SectionType::NextPage);
1048 sect.orientation = Some(ST_PageOrientation::Landscape);
1049 sect.page_width = Some(Twips(15840));
1050 sect.page_height = Some(Twips(12240));
1051 ppr.sect_pr = Some(sect);
1052 p.properties = Some(ppr);
1053 doc.body.add_paragraph(p);
1054
1055 let xml = doc.to_xml().unwrap();
1056 let parsed = CT_Document::from_xml(&xml).unwrap();
1057
1058 let paras: Vec<_> = parsed.body.paragraphs().collect();
1059 assert_eq!(paras.len(), 1);
1060 let ppr2 = paras[0].properties.as_ref().unwrap();
1061 let sect2 = ppr2.sect_pr.as_ref().unwrap();
1062 assert_eq!(sect2.section_type, Some(ST_SectionType::NextPage));
1063 assert_eq!(sect2.orientation, Some(ST_PageOrientation::Landscape));
1064 }
1065}