atom_syndication/
text.rs

1use std::borrow::Cow;
2use std::cmp::PartialEq;
3use std::convert::{AsRef, From};
4use std::io::{BufRead, Write};
5use std::ops::Deref;
6use std::str::FromStr;
7
8use quick_xml::events::attributes::Attributes;
9use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
10use quick_xml::Reader;
11use quick_xml::Writer;
12
13use crate::error::{Error, XmlError};
14use crate::fromxml::FromXml;
15use crate::toxml::ToXmlNamed;
16use crate::util::{atom_text, atom_xhtml, attr_value, decode};
17
18#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20/// Represents the value of the [`type` attribute of a text construct](https://tools.ietf.org/html/rfc4287#section-3.1.1)
21/// in an Atom feed, e.g. the type of the content stored in the element.
22pub enum TextType {
23    /// Plain text
24    Text,
25    /// HTML
26    Html,
27    /// XHTML
28    Xhtml,
29}
30
31impl Default for TextType {
32    fn default() -> Self {
33        TextType::Text
34    }
35}
36
37impl TextType {
38    fn as_str(&self) -> &'static str {
39        match self {
40            Self::Text => "text",
41            Self::Html => "html",
42            Self::Xhtml => "xhtml",
43        }
44    }
45}
46
47impl FromStr for TextType {
48    type Err = Error;
49
50    fn from_str(value: &str) -> Result<Self, Self::Err> {
51        match value {
52            "text" => Ok(Self::Text),
53            "html" => Ok(Self::Html),
54            "xhtml" => Ok(Self::Xhtml),
55            _ => Err(Error::WrongAttribute {
56                attribute: "type",
57                value: value.to_owned(),
58            }),
59        }
60    }
61}
62
63#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
64#[derive(Debug, Clone, PartialEq, Default)]
65#[cfg_attr(feature = "builders", derive(Builder))]
66#[cfg_attr(
67    feature = "builders",
68    builder(
69        setter(into),
70        default,
71        build_fn(name = "build_impl", private, error = "never::Never")
72    )
73)]
74/// Represents a [text construct](https://tools.ietf.org/html/rfc4287#section-3.1) in an Atom feed.
75pub struct Text {
76    /// Content of the text construct
77    pub value: String,
78    /// Base URL for resolving any relative references found in the element.
79    pub base: Option<String>,
80    /// Indicates the natural language for the element.
81    pub lang: Option<String>,
82    /// Type of content stored in the element.
83    pub r#type: TextType,
84}
85
86impl Text {
87    /// Creates a plain text construct (type = "text").
88    pub fn plain(value: impl Into<String>) -> Self {
89        Self {
90            value: value.into(),
91            r#type: TextType::Text,
92            ..Self::default()
93        }
94    }
95
96    /// Creates an html text construct (type = "html").
97    pub fn html(value: impl Into<String>) -> Self {
98        Self {
99            value: value.into(),
100            r#type: TextType::Html,
101            ..Self::default()
102        }
103    }
104
105    /// Creates an html text construct (type = "html").
106    pub fn xhtml(value: impl Into<String>) -> Self {
107        Self {
108            value: value.into(),
109            r#type: TextType::Xhtml,
110            ..Self::default()
111        }
112    }
113
114    /// Returns a content as a `str`
115    pub fn as_str(&self) -> &str {
116        &self.value
117    }
118}
119
120impl From<String> for Text {
121    fn from(value: String) -> Self {
122        Self::plain(value)
123    }
124}
125
126impl<'t> From<&'t str> for Text {
127    fn from(value: &'t str) -> Self {
128        Self::plain(value)
129    }
130}
131
132impl AsRef<str> for Text {
133    fn as_ref(&self) -> &str {
134        &self.value
135    }
136}
137
138impl Deref for Text {
139    type Target = str;
140
141    fn deref(&self) -> &Self::Target {
142        &self.value
143    }
144}
145
146impl PartialEq<str> for Text {
147    fn eq(&self, other: &str) -> bool {
148        self.as_str() == other
149    }
150}
151
152impl PartialEq<Text> for str {
153    fn eq(&self, other: &Text) -> bool {
154        self == other.as_str()
155    }
156}
157
158impl FromXml for Text {
159    fn from_xml<B: BufRead>(
160        reader: &mut Reader<B>,
161        mut atts: Attributes<'_>,
162    ) -> Result<Self, Error> {
163        let mut text = Text::default();
164
165        for att in atts.with_checks(false).flatten() {
166            match decode(att.key.as_ref(), reader)? {
167                Cow::Borrowed("xml:base") => {
168                    text.base = Some(attr_value(&att, reader)?.to_string())
169                }
170                Cow::Borrowed("xml:lang") => {
171                    text.lang = Some(attr_value(&att, reader)?.to_string())
172                }
173                Cow::Borrowed("type") => text.r#type = attr_value(&att, reader)?.parse()?,
174                _ => {}
175            }
176        }
177
178        let content = if text.r#type == TextType::Xhtml {
179            atom_xhtml(reader)?
180        } else {
181            atom_text(reader)?
182        };
183
184        text.value = content.unwrap_or_default();
185
186        Ok(text)
187    }
188}
189
190impl ToXmlNamed for Text {
191    fn to_xml_named<W>(&self, writer: &mut Writer<W>, name: &str) -> Result<(), XmlError>
192    where
193        W: Write,
194    {
195        let mut element = BytesStart::new(name);
196        if let Some(ref base) = self.base {
197            element.push_attribute(("xml:base", base.as_str()));
198        }
199        if let Some(ref lang) = self.lang {
200            element.push_attribute(("xml:lang", lang.as_str()));
201        }
202        if self.r#type != TextType::default() {
203            element.push_attribute(("type", self.r#type.as_str()));
204        }
205        writer
206            .write_event(Event::Start(element))
207            .map_err(XmlError::new)?;
208        if self.r#type == TextType::Xhtml {
209            writer
210                .write_event(Event::Text(BytesText::from_escaped(&self.value)))
211                .map_err(XmlError::new)?;
212        } else {
213            writer
214                .write_event(Event::Text(BytesText::new(&self.value)))
215                .map_err(XmlError::new)?;
216        }
217        writer
218            .write_event(Event::End(BytesEnd::new(name)))
219            .map_err(XmlError::new)?;
220
221        Ok(())
222    }
223}
224
225#[cfg(feature = "builders")]
226impl TextBuilder {
227    /// Builds a new `Text`.
228    pub fn build(&self) -> Text {
229        self.build_impl().unwrap()
230    }
231}