pub mod owned;
use std::borrow::Cow;
#[cfg(feature = "extra-validation")]
use std::collections::HashSet;
#[cfg(feature = "extra-validation")]
lazy_static::lazy_static! {
static ref VOID_ELEMENTS: HashSet<&'static str> = {
HashSet::from([
"area", "base", "br", "col", "embed", "hr", "img", "input", "link",
"meta", "param", "source", "track", "wbr"
])
};
}
enum AttrType<'a> {
KV(&'a str, String),
Bool(&'a str),
Data(&'a str, String),
BoolData(&'a str)
}
pub struct Element<'a> {
tag: &'a str,
classes: Vec<&'a str>,
alst: Vec<AttrType<'a>>
}
impl<'a> Element<'a> {
#[must_use]
pub const fn new(tag: &'a str) -> Self {
Element {
tag,
classes: Vec::new(),
alst: Vec::new()
}
}
#[inline]
#[must_use]
pub fn class(mut self, cls: &'a str) -> Self {
self.classes.push(cls);
self
}
#[inline]
pub fn class_r(&mut self, cls: &'a str) -> &mut Self {
self.classes.push(cls);
self
}
#[inline]
#[must_use]
pub fn flag(mut self, key: &'a str) -> Self {
self.alst.push(AttrType::Bool(key));
self
}
#[inline]
pub fn flag_r(&mut self, key: &'a str) -> &mut Self {
self.alst.push(AttrType::Bool(key));
self
}
#[inline]
#[must_use]
pub fn flag_if(mut self, f: bool, key: &'a str) -> Self {
if f {
self.alst.push(AttrType::Bool(key));
}
self
}
#[inline]
#[must_use]
pub fn attr(mut self, key: &'a str, value: impl AsRef<str>) -> Self {
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.alst.push(AttrType::KV(
key,
html_escape::encode_double_quoted_attribute(value.as_ref()).to_string()
));
self
}
#[inline]
#[must_use]
pub fn attr_if<V>(self, flag: bool, key: &'a str, value: V) -> Self
where
V: AsRef<str>
{
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.optattr(key, flag.then_some(value))
}
#[inline]
#[must_use]
pub fn data_attr(mut self, key: &'a str, value: impl AsRef<str>) -> Self {
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.alst.push(AttrType::Data(
key,
html_escape::encode_double_quoted_attribute(value.as_ref()).to_string()
));
self
}
#[inline]
pub fn data_attr_r(
&mut self,
key: &'a str,
value: impl AsRef<str>
) -> &mut Self {
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.alst.push(AttrType::Data(
key,
html_escape::encode_double_quoted_attribute(value.as_ref()).to_string()
));
self
}
#[inline]
#[must_use]
pub fn data_flag(mut self, key: &'a str) -> Self {
self.alst.push(AttrType::BoolData(key));
self
}
#[inline]
#[must_use]
pub fn data_flag_if(mut self, flag: bool, key: &'a str) -> Self {
if flag {
self.alst.push(AttrType::BoolData(key));
}
self
}
#[inline]
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn optattr<T>(mut self, key: &'a str, value: Option<T>) -> Self
where
T: AsRef<str>
{
if let Some(v) = value.as_ref() {
self.alst.push(AttrType::KV(
key,
html_escape::encode_double_quoted_attribute(v).to_string()
));
}
self
}
#[inline]
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn optattr_map<T, F>(self, key: &'a str, value: Option<T>, f: F) -> Self
where
F: FnOnce(&T) -> String
{
self.map_attr(key, value, f)
}
#[inline]
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn map_attr<T, F>(mut self, key: &'a str, value: Option<T>, f: F) -> Self
where
F: FnOnce(&T) -> String
{
if let Some(v) = value.as_ref() {
let s = f(v);
self.alst.push(AttrType::KV(
key,
html_escape::encode_double_quoted_attribute(&s).to_string()
));
}
self
}
#[inline]
#[must_use]
pub fn opt_map<T, F>(self, value: Option<&'_ T>, f: F) -> Self
where
F: FnOnce(Self, &T) -> Self
{
self.map(value, f)
}
#[must_use]
pub fn map<T, F>(self, value: Option<&'_ T>, f: F) -> Self
where
F: FnOnce(Self, &T) -> Self
{
if let Some(v) = value.as_ref() {
f(self, v)
} else {
self
}
}
#[inline]
#[must_use]
pub fn map_opt<T, F>(self, o: Option<T>, f: F) -> Self
where
F: FnOnce(Self, T) -> Self
{
match o {
Some(t) => f(self, t),
None => self
}
}
#[inline]
#[must_use]
pub fn map_attr_if<T, F>(
self,
flag: bool,
key: &'a str,
data: &T,
f: F
) -> Self
where
F: FnOnce(&T) -> String
{
if flag {
self.attr(key, f(data))
} else {
self
}
}
#[inline]
#[must_use]
pub fn map_if<F>(self, flag: bool, f: F) -> Self
where
F: FnOnce(Self) -> Self
{
if flag {
f(self)
} else {
self
}
}
#[inline]
pub fn mod_if<F>(&mut self, flag: bool, f: F) -> &mut Self
where
F: FnOnce(&mut Self)
{
if flag {
f(self);
}
self
}
}
impl<'a> Element<'a> {
#[inline]
#[must_use]
#[allow(clippy::needless_pass_by_value)]
pub fn raw_attr(mut self, key: &'a str, value: impl ToString) -> Self {
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.alst.push(AttrType::KV(key, value.to_string()));
self
}
#[inline]
#[must_use]
pub fn raw_optattr<T>(mut self, key: &'a str, value: Option<&T>) -> Self
where
T: ToString
{
if let Some(v) = value.as_ref() {
self.alst.push(AttrType::KV(key, v.to_string()));
}
self
}
#[inline]
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn raw_attr_if(
self,
flag: bool,
key: &'a str,
value: impl ToString
) -> Self {
debug_assert!(
key != "class",
"Use the dedicated .class() method to add classes to elements"
);
self.raw_optattr(key, flag.then_some(&value))
}
}
impl<'a> Element<'a> {
fn gen_attr_list(&self) -> Option<Vec<String>> {
if self.alst.is_empty() && self.classes.is_empty() {
None
} else {
let mut ret = Vec::new();
if !self.classes.is_empty() {
ret.push(format!(r#"class="{}""#, self.classes.join(" ")));
}
let it = self.alst.iter().map(|a| match a {
AttrType::KV(k, v) => {
format!(r#"{k}="{v}""#)
}
AttrType::Bool(a) => (*a).to_string(),
AttrType::Data(k, v) => {
format!(r#"data-{k}="{v}""#)
}
AttrType::BoolData(a) => {
format!("data-{a}")
}
});
ret.extend(it);
Some(ret)
}
}
}
impl<'a> Element<'a> {
pub fn sub<F>(self, bldr: &mut sidoc::Builder, f: F)
where
F: FnOnce(&mut sidoc::Builder)
{
#[cfg(feature = "extra-validation")]
assert!(!VOID_ELEMENTS.contains(self.tag));
if let Some(lst) = self.gen_attr_list() {
bldr.scope(
format!("<{} {}>", self.tag, lst.join(" ")),
Some(format!("</{}>", self.tag))
);
} else {
let stag = format!("<{}>", self.tag);
let etag = format!("</{}>", self.tag);
bldr.scope(stag, Some(etag));
}
f(bldr);
bldr.exit();
}
}
impl<'a> Element<'a> {
#[inline]
pub fn add_empty(self, bldr: &mut sidoc::Builder) {
#[cfg(feature = "extra-validation")]
assert!(VOID_ELEMENTS.contains(self.tag));
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>", self.tag, alst.join(" "))
} else {
format!("<{}>", self.tag)
};
bldr.line(line);
}
#[inline]
pub fn add_content(self, text: &str, bldr: &mut sidoc::Builder) {
#[cfg(feature = "extra-validation")]
assert!(!VOID_ELEMENTS.contains(self.tag));
let line = if let Some(alst) = self.gen_attr_list() {
format!(
"<{} {}>{}</{}>",
self.tag,
alst.join(" "),
html_escape::encode_text(text),
self.tag
)
} else {
format!(
"<{}>{}</{}>",
self.tag,
html_escape::encode_text(text),
self.tag
)
};
bldr.line(line);
}
#[inline]
pub fn add_raw_content(self, text: &str, bldr: &mut sidoc::Builder) {
#[cfg(feature = "extra-validation")]
assert!(!VOID_ELEMENTS.contains(self.tag));
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>{}</{}>", self.tag, alst.join(" "), text, self.tag)
} else {
format!("<{}>{}</{}>", self.tag, text, self.tag)
};
bldr.line(line);
}
pub fn add_opt_content<T>(self, text: &Option<T>, bldr: &mut sidoc::Builder)
where
T: AsRef<str>
{
#[cfg(feature = "extra-validation")]
assert!(!VOID_ELEMENTS.contains(self.tag));
let t = text
.as_ref()
.map_or_else(|| Cow::from(""), |t| html_escape::encode_text(t));
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>{}</{}>", self.tag, alst.join(" "), t, self.tag)
} else {
format!("<{}>{}</{}>", self.tag, t, self.tag)
};
bldr.line(line);
}
pub fn add_scope(self, bldr: &mut sidoc::Builder) {
#[cfg(feature = "extra-validation")]
assert!(!VOID_ELEMENTS.contains(self.tag));
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>", self.tag, alst.join(" "))
} else {
format!("<{}>", self.tag)
};
bldr.scope(line, Some(format!("</{}>", self.tag)));
}
pub fn scope<F>(self, bldr: &mut sidoc::Builder, f: F)
where
F: FnOnce(&mut sidoc::Builder)
{
let line = if let Some(alst) = self.gen_attr_list() {
format!("<{} {}>", self.tag, alst.join(" "))
} else {
format!("<{}>", self.tag)
};
bldr.scope(line, Some(format!("</{}>", self.tag)));
f(bldr);
bldr.exit();
}
}