Skip to main content

rs_docx/document/
paragraph.rs

1#![allow(unused_must_use)]
2use derive_more::From;
3use hard_xml::{XmlRead, XmlWrite};
4use std::borrow::{Borrow, Cow};
5
6use crate::{
7    __setter, __xml_test_suites,
8    document::{
9        BookmarkEnd, BookmarkStart, CommentRangeEnd, CommentRangeStart, Deletion, Hyperlink,
10        Insertion, Run, RunContent, Text, SDT,
11    },
12    formatting::ParagraphProperty,
13};
14
15/// Paragraph
16///
17/// Paragraph is the main block-level container for content.
18///
19/// ```rust
20/// use rs_docx::document::*;
21/// use rs_docx::formatting::*;
22///
23/// let par = Paragraph::default()
24///     .property(ParagraphProperty::default())
25///     .push_text("hello,")
26///     .push_text((" world.", TextSpace::Preserve))
27///     .push(Run::default())
28///     .push(BookmarkStart::default())
29///     .push(BookmarkEnd::default());
30/// ```
31#[derive(Debug, Default, XmlRead, XmlWrite, Clone)]
32#[cfg_attr(test, derive(PartialEq))]
33#[xml(tag = "w:p")]
34pub struct Paragraph<'a> {
35    //#[xml(attr = "w14:paraId")]
36    //pub id: Option<Cow<'a, str>>,
37    //#[xml(attr = "w14:textId")]
38    //pub text_id: Option<Cow<'a, str>>,
39    #[xml(attr = "w:rsidR")]
40    pub rsid_r: Option<Cow<'a, str>>,
41    #[xml(attr = "w:rsidRDefault")]
42    pub rsid_r_default: Option<Cow<'a, str>>,
43    /// Specifies the properties of a paragraph
44    ///
45    /// This information is applied to all the contents of the paragraph.
46    #[xml(child = "w:pPr")]
47    pub property: Option<ParagraphProperty<'a>>,
48    /// Specifes the run contents of a paragraph
49    ///
50    /// Run is a region of text with properties. Each paragraph containes one or more runs.
51    #[xml(
52        child = "w:commentRangeStart",
53        child = "w:commentRangeEnd",
54        child = "w:r",
55        child = "w:hyperlink",
56        child = "w:bookmarkStart",
57        child = "w:bookmarkEnd",
58        child = "w:sdt",
59        child = "w:ins",
60        child = "w:del"
61    )]
62    pub content: Vec<ParagraphContent<'a>>,
63}
64
65impl<'a> Paragraph<'a> {
66    __setter!(property: Option<ParagraphProperty<'a>>);
67
68    #[inline(always)]
69    pub fn push<T: Into<ParagraphContent<'a>>>(mut self, content: T) -> Self {
70        self.content.push(content.into());
71        self
72    }
73
74    #[inline(always)]
75    pub fn push_text<T: Into<Text<'a>>>(mut self, content: T) -> Self {
76        self.content.push(ParagraphContent::Run(Run {
77            content: vec![RunContent::Text(content.into())],
78            ..Default::default()
79        }));
80        self
81    }
82
83    pub fn text(&self) -> String {
84        self.iter_text()
85            .map(|c| c.to_string())
86            .collect::<Vec<_>>()
87            .join("")
88    }
89
90    pub fn iter_text(&self) -> Box<dyn Iterator<Item = &Cow<'a, str>> + '_> {
91        Box::new(
92            self.content
93                .iter()
94                .filter_map(|content| match content {
95                    ParagraphContent::Run(run) => Some(run.iter_text()),
96                    ParagraphContent::Link(link) => Some(link.iter_text()),
97                    ParagraphContent::SDT(sdt) => Some(sdt.iter_text()),
98                    _ => None,
99                })
100                .flatten(),
101        )
102    }
103
104    pub fn iter_text_mut(&mut self) -> impl Iterator<Item = &mut Cow<'a, str>> {
105        self.content
106            .iter_mut()
107            .filter_map(|content| match content {
108                ParagraphContent::Run(run) => Some(run.iter_text_mut()),
109                ParagraphContent::Link(link) => Some(link.iter_text_mut()),
110                _ => None,
111            })
112            .flatten()
113    }
114
115    pub fn replace_text<'b, I, T, S>(&mut self, dic: T) -> crate::DocxResult<()>
116    where
117        S: AsRef<str> + 'b,
118        T: IntoIterator<Item = I> + Copy,
119        I: Borrow<(S, S)>,
120    {
121        for content in self.content.iter_mut() {
122            if let ParagraphContent::Run(r) = content {
123                r.replace_text(dic)?;
124            }
125        }
126
127        Ok(())
128    }
129}
130
131/// A set of elements that can be contained as the content of a paragraph.
132#[derive(Debug, From, XmlRead, XmlWrite, Clone)]
133#[cfg_attr(test, derive(PartialEq))]
134#[allow(clippy::large_enum_variant)]
135pub enum ParagraphContent<'a> {
136    #[xml(tag = "w:commentRangeStart")]
137    CommentRangeStart(CommentRangeStart<'a>),
138    #[xml(tag = "w:commentRangeEnd")]
139    CommentRangeEnd(CommentRangeEnd<'a>),
140    #[xml(tag = "w:r")]
141    Run(Run<'a>),
142    #[xml(tag = "w:hyperlink")]
143    Link(Hyperlink<'a>),
144    #[xml(tag = "w:bookmarkStart")]
145    BookmarkStart(BookmarkStart<'a>),
146    #[xml(tag = "w:bookmarkEnd")]
147    BookmarkEnd(BookmarkEnd<'a>),
148    #[xml(tag = "w:sdt")]
149    SDT(SDT<'a>),
150    #[xml(tag = "w:ins")]
151    Insertion(Insertion<'a>),
152    #[xml(tag = "w:del")]
153    Deletion(Deletion<'a>),
154}
155
156__xml_test_suites!(
157    Paragraph,
158    Paragraph::default(),
159    r#"<w:p/>"#,
160    Paragraph::default().push(Run::default()),
161    r#"<w:p><w:r/></w:p>"#,
162    Paragraph::default().push(Hyperlink::default()),
163    r#"<w:p><w:hyperlink/></w:p>"#,
164    Paragraph::default().push(BookmarkStart::default()),
165    r#"<w:p><w:bookmarkStart/></w:p>"#,
166    Paragraph::default().push(BookmarkEnd::default()),
167    r#"<w:p><w:bookmarkEnd/></w:p>"#,
168);