docx_rs/documents/elements/
hyperlink.rs

1use serde::Serialize;
2use std::io::Write;
3
4use super::*;
5use crate::documents::BuildXML;
6use crate::escape::escape;
7use crate::types::*;
8use crate::{create_hyperlink_rid, generate_hyperlink_id, xml_builder::*};
9
10#[derive(Serialize, Debug, Clone, PartialEq)]
11#[serde(tag = "type")]
12#[serde(rename_all = "camelCase")]
13pub enum HyperlinkData {
14    External {
15        rid: String,
16        // path is writer only
17        #[serde(skip_serializing_if = "String::is_empty")]
18        path: String,
19    },
20    Anchor {
21        anchor: String,
22    },
23}
24
25#[derive(Serialize, Debug, Clone, PartialEq)]
26#[serde(rename_all = "camelCase")]
27pub struct Hyperlink {
28    #[serde(flatten)]
29    pub link: HyperlinkData,
30    pub history: Option<usize>,
31    pub children: Vec<ParagraphChild>,
32}
33
34impl Hyperlink {
35    pub fn new(value: impl Into<String>, t: HyperlinkType) -> Self {
36        let link = {
37            match t {
38                HyperlinkType::External => HyperlinkData::External {
39                    rid: create_hyperlink_rid(generate_hyperlink_id()),
40                    path: escape(&value.into()),
41                },
42                HyperlinkType::Anchor => HyperlinkData::Anchor {
43                    anchor: value.into(),
44                },
45            }
46        };
47        Hyperlink {
48            link,
49            history: None,
50            children: vec![],
51        }
52    }
53
54    pub fn add_run(mut self, run: Run) -> Self {
55        self.children.push(ParagraphChild::Run(Box::new(run)));
56        self
57    }
58
59    pub fn add_structured_data_tag(mut self, t: StructuredDataTag) -> Self {
60        self.children
61            .push(ParagraphChild::StructuredDataTag(Box::new(t)));
62        self
63    }
64
65    pub fn add_insert(mut self, insert: Insert) -> Self {
66        self.children.push(ParagraphChild::Insert(insert));
67        self
68    }
69
70    pub fn add_delete(mut self, delete: Delete) -> Self {
71        self.children.push(ParagraphChild::Delete(delete));
72        self
73    }
74
75    pub fn add_bookmark_start(mut self, id: usize, name: impl Into<String>) -> Self {
76        self.children
77            .push(ParagraphChild::BookmarkStart(BookmarkStart::new(id, name)));
78        self
79    }
80
81    pub fn add_bookmark_end(mut self, id: usize) -> Self {
82        self.children
83            .push(ParagraphChild::BookmarkEnd(BookmarkEnd::new(id)));
84        self
85    }
86
87    pub fn add_comment_start(mut self, comment: Comment) -> Self {
88        self.children.push(ParagraphChild::CommentStart(Box::new(
89            CommentRangeStart::new(comment),
90        )));
91        self
92    }
93
94    pub fn add_comment_end(mut self, id: usize) -> Self {
95        self.children
96            .push(ParagraphChild::CommentEnd(CommentRangeEnd::new(id)));
97        self
98    }
99}
100
101impl BuildXML for Hyperlink {
102    fn build_to<W: Write>(
103        &self,
104        stream: xml::writer::EventWriter<W>,
105    ) -> xml::writer::Result<xml::writer::EventWriter<W>> {
106        XMLBuilder::from(stream)
107            .apply(|b| match self.link {
108                HyperlinkData::Anchor { ref anchor } => b.open_hyperlink(
109                    None,
110                    Some(anchor.clone()).as_ref(),
111                    Some(self.history.unwrap_or(1)),
112                ),
113                HyperlinkData::External { ref rid, .. } => b.open_hyperlink(
114                    Some(rid.clone()).as_ref(),
115                    None,
116                    Some(self.history.unwrap_or(1)),
117                ),
118            })?
119            .add_children(&self.children)?
120            .close()?
121            .into_inner()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127
128    use super::*;
129    #[cfg(test)]
130    use pretty_assertions::assert_eq;
131    use std::str;
132
133    #[test]
134    fn test_hyperlink() {
135        let l = Hyperlink::new("ToC1", HyperlinkType::Anchor).add_run(Run::new().add_text("hello"));
136        let b = l.build();
137        assert_eq!(
138            str::from_utf8(&b).unwrap(),
139            r#"<w:hyperlink w:anchor="ToC1" w:history="1"><w:r><w:rPr /><w:t xml:space="preserve">hello</w:t></w:r></w:hyperlink>"#
140        );
141    }
142}