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)]
20pub enum TextType {
23 Text,
25 Html,
27 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)]
74pub struct Text {
76 pub value: String,
78 pub base: Option<String>,
80 pub lang: Option<String>,
82 pub r#type: TextType,
84}
85
86impl Text {
87 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 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 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 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 pub fn build(&self) -> Text {
229 self.build_impl().unwrap()
230 }
231}