use std::fmt;
use super::definition::Definition;
use super::error::TryFromNodeError;
use crate::arguments::Arguments;
use crate::parse::{split_name, strip_terminating_slash, validate};
use crate::{Error, ErrorKind};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Tag<'a> {
Close(TagClose<'a>),
Definition(Definition<'a>),
Open(TagOpen<'a>),
}
impl fmt::Display for Tag<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Close(tag) => tag.fmt(f),
Self::Definition(def) => def.fmt(f),
Self::Open(tag) => tag.fmt(f),
}
}
}
impl<'a> Tag<'a> {
pub fn name(&self) -> &'a str {
match self {
Self::Close(tag) => tag.name,
Self::Definition(definition) => definition.name(),
Self::Open(tag) => tag.name,
}
}
pub fn parse(source: &'a str, secure: bool) -> crate::Result<Self> {
let source = source.trim_ascii();
match source.split_at_checked(1) {
None if source.is_empty() => Err(Error::braced("", ErrorKind::EmptyElement)),
Some(("!" | "/", "")) => Err(Error::braced(source, ErrorKind::IncompleteElement)),
Some(("!", _)) if !secure => Err(Error::braced(source, ErrorKind::UnsecuredDefinition)),
Some(("!", body)) => Ok(Self::Definition(Definition::parse(body)?)),
Some(("/", body)) => Ok(Self::Close(TagClose::parse(body)?)),
_ => Ok(Self::Open(TagOpen::parse(source)?)),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TagClose<'a> {
pub name: &'a str,
}
impl fmt::Display for TagClose<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { name } = self;
write!(f, "</{name}>")
}
}
impl<'a> TagClose<'a> {
fn parse(source: &'a str) -> crate::Result<Self> {
let (name, args) = split_name(source);
if name.is_empty() {
return Err(Error::new("</>", ErrorKind::IncompleteElement));
}
validate(name, ErrorKind::InvalidElementName)?;
let args = args.trim_ascii();
if !args.is_empty() {
return Err(Error::new(args, ErrorKind::ArgumentsToClosingTag));
}
Ok(Self { name })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TagOpen<'a, S = &'a str> {
pub name: &'a str,
pub arguments: Arguments<'a, S>,
pub empty: bool,
}
impl<S: AsRef<str>> fmt::Display for TagOpen<'_, S> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self {
name,
arguments,
empty,
} = self;
if *empty {
write!(f, "<{name} {arguments}/>")
} else {
write!(f, "<{name} {arguments}>")
}
}
}
impl<'a> TagOpen<'a> {
fn parse(source: &'a str) -> crate::Result<Self> {
let (name, body) = split_name(source);
if name.is_empty() {
return Err(Error::new("", ErrorKind::EmptyElement));
}
validate(name, ErrorKind::InvalidElementName)?;
let (args, empty) = strip_terminating_slash(body);
Ok(Self {
name,
arguments: Arguments::parse(args)?,
empty,
})
}
}
impl<'a> From<Definition<'a>> for Tag<'a> {
fn from(value: Definition<'a>) -> Self {
Self::Definition(value)
}
}
impl<'a> From<TagClose<'a>> for Tag<'a> {
fn from(value: TagClose<'a>) -> Self {
Self::Close(value)
}
}
impl<'a> From<TagOpen<'a>> for Tag<'a> {
fn from(value: TagOpen<'a>) -> Self {
Self::Open(value)
}
}
impl<'a> TryFrom<Tag<'a>> for Definition<'a> {
type Error = TryFromNodeError;
fn try_from(value: Tag<'a>) -> Result<Self, Self::Error> {
let got = match value {
Tag::Close(_) => "Close",
Tag::Definition(def) => return Ok(def),
Tag::Open(_) => "Open",
};
Err(TryFromNodeError {
prefix: "Tag",
expected: "Definition",
got,
})
}
}
impl<'a> TryFrom<Tag<'a>> for TagClose<'a> {
type Error = TryFromNodeError;
fn try_from(value: Tag<'a>) -> Result<Self, Self::Error> {
let got = match value {
Tag::Close(tag) => return Ok(tag),
Tag::Definition(_) => "Definition",
Tag::Open(_) => "Open",
};
Err(TryFromNodeError {
prefix: "Tag",
expected: "Close",
got,
})
}
}
impl<'a> TryFrom<Tag<'a>> for TagOpen<'a> {
type Error = TryFromNodeError;
fn try_from(value: Tag<'a>) -> Result<Self, Self::Error> {
let got = match value {
Tag::Close(_) => "Close",
Tag::Definition(_) => "Definition",
Tag::Open(tag) => return Ok(tag),
};
Err(TryFromNodeError {
prefix: "Tag",
expected: "Open",
got,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fmt_close() {
let tag = TagClose { name: "custom" };
assert_eq!(tag.to_string(), "</custom>");
}
#[test]
fn fmt_open() {
let tag = TagOpen {
name: "custom",
arguments: "muffled col=red".parse().unwrap(),
empty: false,
};
assert_eq!(tag.to_string(), "<custom muffled col=red>");
}
}