#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs)]
pub mod attr;
pub mod elt;
mod interop {
#[cfg(feature = "maud_v026")]
mod maud_v026;
#[cfg(feature = "rocket_v05")]
mod rocket_v05;
#[cfg(feature = "salvo_v074")]
mod salvo_v074;
#[cfg(feature = "salvo_v076")]
mod salvo_v076;
}
extern crate alloc;
use alloc::{borrow::Cow, fmt::Display, vec::Vec};
#[derive(Debug, Clone)]
pub struct Document(Element);
#[derive(Debug, Clone, Default)]
pub struct Element(ElementInner);
#[derive(Debug, Clone)]
enum ElementInner {
Parent {
tag: &'static str,
attributes: Vec<Attribute>,
children: Vec<Element>,
},
Void {
tag: &'static str,
attributes: Vec<Attribute>,
},
Text(Cow<'static, str>),
Script(Cow<'static, str>),
Raw(Cow<'static, str>),
Multiple(Vec<Element>),
None,
}
impl Default for ElementInner {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Default)]
pub struct Attribute(AttributeInner);
#[derive(Debug, Clone)]
enum AttributeInner {
KeyValue(Cow<'static, str>, Cow<'static, str>),
KeyValueInt(Cow<'static, str>, i32),
Flag(Cow<'static, str>),
None,
}
impl Default for AttributeInner {
fn default() -> Self {
Self::None
}
}
impl Default for Document {
fn default() -> Self {
Self(Element::new(
"html",
[],
[Element::new("head", [], []), Element::new("body", [], [])],
))
}
}
impl Display for Document {
fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
write!(f, "<!DOCTYPE html>\n{}", self.0)
}
}
impl Element {
pub fn new(
tag: &'static str,
attributes: impl IntoIterator<Item = Attribute>,
children: impl IntoIterator<Item = Element>,
) -> Self {
assert_valid_tag_name(tag);
Self(ElementInner::Parent {
tag,
attributes: attributes.into_iter().map(Into::into).collect(),
children: children.into_iter().collect(),
})
}
pub fn new_void(tag: &'static str, attributes: impl IntoIterator<Item = Attribute>) -> Self {
assert_valid_tag_name(tag);
Self(ElementInner::Void {
tag,
attributes: attributes.into_iter().collect(),
})
}
}
fn assert_valid_tag_name(tag: &str) {
debug_assert!(
!tag.is_empty() && tag.chars().all(|c| !c.is_whitespace()),
"invalid tag name: '{tag}'"
);
}
impl From<ElementInner> for Element {
fn from(value: ElementInner) -> Self {
Self(value)
}
}
impl Display for Element {
fn fmt(&self, f: &mut alloc::fmt::Formatter<'_>) -> alloc::fmt::Result {
match &self.0 {
ElementInner::Parent {
tag,
attributes,
children,
} => {
write!(f, "<{tag}")?;
write_attributes(f, attributes)?;
write!(f, ">")?;
for child in children {
write!(f, "{child}")?;
}
write!(f, "</{tag}>")?;
}
ElementInner::Void { tag, attributes } => {
write!(f, "<{tag}")?;
write_attributes(f, attributes)?;
write!(f, ">")?;
}
ElementInner::Text(text) => write!(f, "{}", html_escape::encode_text(text))?,
ElementInner::Script(text) => write!(f, "{}", html_escape::encode_script(text))?,
ElementInner::Raw(raw) => write!(f, "{raw}")?,
ElementInner::Multiple(elems) => {
for elt in elems {
write!(f, "{elt}")?;
}
}
ElementInner::None => (),
}
Ok(())
}
}
fn write_attributes(
f: &mut alloc::fmt::Formatter<'_>,
attributes: &[Attribute],
) -> Result<(), alloc::fmt::Error> {
for attribute in attributes
.iter()
.filter(|a| !matches!(&a.0, AttributeInner::None))
{
write!(f, " {attribute}")?;
}
Ok(())
}
impl Display for Attribute {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match &self.0 {
AttributeInner::KeyValue(key, value) => {
write!(
f,
"{}=\"{}\"",
key,
html_escape::encode_double_quoted_attribute(&value)
)
}
AttributeInner::KeyValueInt(key, value) => {
write!(f, "{key}=\"{value}\"")
}
AttributeInner::Flag(key) => write!(f, "{key}"),
AttributeInner::None => Ok(()),
}
}
}
impl Attribute {
pub fn new(name: &'static str, value: impl Into<Cow<'static, str>>) -> Self {
assert_valid_attribute_name(name);
Self(AttributeInner::KeyValue(name.into(), value.into()))
}
pub fn new_int(name: &'static str, value: i32) -> Self {
assert_valid_attribute_name(name);
Self(AttributeInner::KeyValueInt(name.into(), value))
}
pub fn new_flag(name: &'static str) -> Self {
assert_valid_attribute_name(name);
Self(AttributeInner::Flag(name.into()))
}
pub fn new_unsafe_name(
name: impl Into<Cow<'static, str>>,
value: impl Into<Cow<'static, str>>,
) -> Self {
Self(AttributeInner::KeyValue(name.into(), value.into()))
}
}
fn assert_valid_attribute_name(name: &str) {
debug_assert!(
!name.is_empty() && name.chars().all(|c| !c.is_whitespace()),
"invalid attribute name: '{name}'"
);
}
impl IntoIterator for Element {
type Item = Self;
type IntoIter = core::iter::Once<Self>;
fn into_iter(self) -> Self::IntoIter {
core::iter::once(self)
}
}
impl<const N: usize> From<[Element; N]> for Element {
fn from(value: [Element; N]) -> Self {
Vec::from(value).into()
}
}
impl From<Vec<Element>> for Element {
fn from(value: Vec<Element>) -> Self {
Self(ElementInner::Multiple(value))
}
}
pub fn html(
attributes: impl IntoIterator<Item = Attribute>,
children: impl IntoIterator<Item = Element>,
) -> Document {
Document(Element::new("html", attributes, children))
}