use std::borrow::Cow;
use std::cmp::PartialEq;
use std::convert::{AsRef, From};
use std::io::{BufRead, Write};
use std::ops::Deref;
use std::str::FromStr;
use quick_xml::events::attributes::Attributes;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use quick_xml::Reader;
use quick_xml::Writer;
use crate::error::{Error, XmlError};
use crate::fromxml::FromXml;
use crate::toxml::ToXmlNamed;
use crate::util::{atom_text, atom_xhtml, attr_value, decode};
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TextType {
Text,
Html,
Xhtml,
}
impl Default for TextType {
fn default() -> Self {
TextType::Text
}
}
impl TextType {
fn as_str(&self) -> &'static str {
match self {
Self::Text => "text",
Self::Html => "html",
Self::Xhtml => "xhtml",
}
}
}
impl FromStr for TextType {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"text" => Ok(Self::Text),
"html" => Ok(Self::Html),
"xhtml" => Ok(Self::Xhtml),
_ => Err(Error::WrongAttribute {
attribute: "type",
value: value.to_owned(),
}),
}
}
}
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "builders", derive(Builder))]
#[cfg_attr(
feature = "builders",
builder(
setter(into),
default,
build_fn(name = "build_impl", private, error = "never::Never")
)
)]
pub struct Text {
pub value: String,
pub base: Option<String>,
pub lang: Option<String>,
pub r#type: TextType,
}
impl Text {
pub fn plain(value: impl Into<String>) -> Self {
Self {
value: value.into(),
r#type: TextType::Text,
..Self::default()
}
}
pub fn html(value: impl Into<String>) -> Self {
Self {
value: value.into(),
r#type: TextType::Html,
..Self::default()
}
}
pub fn xhtml(value: impl Into<String>) -> Self {
Self {
value: value.into(),
r#type: TextType::Xhtml,
..Self::default()
}
}
pub fn as_str(&self) -> &str {
&self.value
}
}
impl From<String> for Text {
fn from(value: String) -> Self {
Self::plain(value)
}
}
impl<'t> From<&'t str> for Text {
fn from(value: &'t str) -> Self {
Self::plain(value)
}
}
impl AsRef<str> for Text {
fn as_ref(&self) -> &str {
&self.value
}
}
impl Deref for Text {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl PartialEq<str> for Text {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<Text> for str {
fn eq(&self, other: &Text) -> bool {
self == other.as_str()
}
}
impl FromXml for Text {
fn from_xml<B: BufRead>(
reader: &mut Reader<B>,
mut atts: Attributes<'_>,
) -> Result<Self, Error> {
let mut text = Text::default();
for att in atts.with_checks(false).flatten() {
match decode(att.key.as_ref(), reader)? {
Cow::Borrowed("xml:base") => {
text.base = Some(attr_value(&att, reader)?.to_string())
}
Cow::Borrowed("xml:lang") => {
text.lang = Some(attr_value(&att, reader)?.to_string())
}
Cow::Borrowed("type") => text.r#type = attr_value(&att, reader)?.parse()?,
_ => {}
}
}
let content = if text.r#type == TextType::Xhtml {
atom_xhtml(reader)?
} else {
atom_text(reader)?
};
text.value = content.unwrap_or_default();
Ok(text)
}
}
impl ToXmlNamed for Text {
fn to_xml_named<W>(&self, writer: &mut Writer<W>, name: &str) -> Result<(), XmlError>
where
W: Write,
{
let mut element = BytesStart::new(name);
if let Some(ref base) = self.base {
element.push_attribute(("xml:base", base.as_str()));
}
if let Some(ref lang) = self.lang {
element.push_attribute(("xml:lang", lang.as_str()));
}
if self.r#type != TextType::default() {
element.push_attribute(("type", self.r#type.as_str()));
}
writer
.write_event(Event::Start(element))
.map_err(XmlError::new)?;
if self.r#type == TextType::Xhtml {
writer
.write_event(Event::Text(BytesText::from_escaped(&self.value)))
.map_err(XmlError::new)?;
} else {
writer
.write_event(Event::Text(BytesText::new(&self.value)))
.map_err(XmlError::new)?;
}
writer
.write_event(Event::End(BytesEnd::new(name)))
.map_err(XmlError::new)?;
Ok(())
}
}
#[cfg(feature = "builders")]
impl TextBuilder {
pub fn build(&self) -> Text {
self.build_impl().unwrap()
}
}