use super::context::HtmlContext;
use super::escape::escape_char;
use super::render::ElementRender;
macro_rules! tag_method {
($tag:tt) => {
pub fn $tag(self) -> HtmlBuilderTag<'c, 'i, 'h, 'static> {
self.tag(stringify!($tag))
}
};
}
#[derive(Debug)]
pub struct HtmlBuilder<'c, 'i, 'h> {
ctx: &'c mut HtmlContext<'i, 'h>,
}
impl<'c, 'i, 'h> HtmlBuilder<'c, 'i, 'h> {
#[inline]
pub fn new(ctx: &'c mut HtmlContext<'i, 'h>) -> Self {
HtmlBuilder { ctx }
}
#[inline]
pub fn tag<'t>(self, tag: &'t str) -> HtmlBuilderTag<'c, 'i, 'h, 't> {
debug_assert!(is_alphanumeric(tag));
let HtmlBuilder { ctx } = self;
HtmlBuilderTag::new(ctx, tag)
}
tag_method!(a);
tag_method!(b);
tag_method!(blockquote);
tag_method!(br);
tag_method!(code);
tag_method!(div);
tag_method!(hr);
tag_method!(i);
tag_method!(iframe);
tag_method!(img);
tag_method!(li);
tag_method!(ol);
tag_method!(p);
tag_method!(script);
tag_method!(span);
tag_method!(strike);
tag_method!(sub);
tag_method!(sup);
tag_method!(table);
tag_method!(tr);
tag_method!(tt);
tag_method!(u);
tag_method!(ul);
#[inline]
pub fn text(&mut self, text: &str) {
self.ctx.push_escaped(text);
}
}
#[derive(Debug)]
pub struct HtmlBuilderTag<'c, 'i, 'h, 't> {
ctx: &'c mut HtmlContext<'i, 'h>,
tag: &'t str,
in_tag: bool,
}
impl<'c, 'i, 'h, 't> HtmlBuilderTag<'c, 'i, 'h, 't> {
pub fn new(ctx: &'c mut HtmlContext<'i, 'h>, tag: &'t str) -> Self {
ctx.push_raw('<');
ctx.push_raw_str(tag);
HtmlBuilderTag {
ctx,
tag,
in_tag: true,
}
}
fn attr_key(&mut self, key: &str) {
debug_assert!(is_alphanumeric(key));
debug_assert!(self.in_tag);
self.ctx.push_raw(' ');
self.ctx.push_escaped(key);
self.ctx.push_raw('=');
}
pub fn attr(&mut self, key: &str, value_parts: &[&str]) -> &mut Self {
self.attr_key(key);
self.ctx.push_raw('"');
for part in value_parts {
self.ctx.push_escaped(part);
}
self.ctx.push_raw('"');
self
}
pub fn attr_fmt<F>(&mut self, key: &str, mut value_fn: F) -> &mut Self
where
F: FnMut(&mut HtmlContext),
{
self.attr_key(key);
self.ctx.push_raw('"');
let mut index = self.ctx.buffer().len();
value_fn(self.ctx);
let buffer = self.ctx.buffer();
while index < buffer.len() {
let ch = {
let remainder = &buffer[index..];
remainder
.chars()
.next()
.expect("Character buffer exhausted")
};
if let Some(subst) = escape_char(ch) {
buffer.replace_range(index..=index, subst);
}
index += ch.len_utf8();
}
self.ctx.push_raw('"');
self
}
fn content_start(&mut self) {
if self.in_tag {
self.ctx.push_raw('>');
self.in_tag = false;
}
}
#[inline]
pub fn inner(&mut self, component: &dyn ElementRender) -> &mut Self {
self.content_start();
component.render(self.ctx);
self
}
pub fn contents<F>(&mut self, mut f: F) -> &mut Self
where
F: FnMut(&mut HtmlContext),
{
self.content_start();
f(self.ctx);
self
}
}
impl<'c, 'i, 'h, 't> Drop for HtmlBuilderTag<'c, 'i, 'h, 't> {
fn drop(&mut self) {
if !self.in_tag {
self.ctx.push_raw_str("</");
self.ctx.push_raw_str(self.tag);
}
self.ctx.push_raw('>');
}
}
fn is_alphanumeric(value: &str) -> bool {
value
.chars()
.all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '-')
}