use crate::parser::Namespace;
use crate::{Node, NodeKind};
use bumpalo::Bump;
#[derive(Debug, Default)]
pub struct NodeArena {
pub(crate) inner: Bump,
}
impl NodeArena {
pub fn new() -> NodeArena {
NodeArena::default()
}
pub fn element<'arena, 'attr, A, C>(
&'arena self,
tag: &str,
attributes: A,
children: C,
) -> &'arena Node<'arena>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
C: IntoIterator<Item = &'arena Node<'arena>>,
{
alloc::element(self, tag, attributes.into_iter(), Namespace::Html).append(children)
}
pub fn fragment<'a, I>(&'a self, children: I) -> &'a Node<'a>
where
I: IntoIterator<Item = &'a Node<'a>>,
{
alloc::fragment(self).append(children)
}
pub fn text<'a>(&'a self, text: impl AsRef<str>) -> &'a Node<'a> {
alloc::text(self, text.as_ref(), false)
}
pub fn raw_text<'a>(&'a self, text: impl AsRef<str>) -> &'a Node<'a> {
alloc::text(self, text.as_ref(), true)
}
pub fn deep_copy<'a>(&'a self, node: &'a Node<'a>) -> &'a Node<'a> {
match &node.kind {
NodeKind::Fragment => {
let new_chunk = self.fragment([]);
for child in node.children() {
new_chunk.append(self.deep_copy(child));
}
new_chunk
}
NodeKind::Comment => alloc::comment(self, node.data),
NodeKind::Doctype => alloc::doctype(self, node.data),
NodeKind::Text => alloc::text(self, node.data, node.special),
NodeKind::Element => {
let new_element = alloc::element(self, node.data, node.attrs(), node.namespace);
for child in node.children() {
new_element.append(self.deep_copy(child));
}
new_element
}
}
}
}
macro_rules! tag_name_functions {
($($name:ident),*) => {
$(
pub fn $name<'a, 'attr, A, C>(&'a self, attributes: A, children: C) -> &'a $crate::Node<'a>
where C: IntoIterator<Item = &'a Node<'a>>,
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
self.element(stringify!($name), attributes, children)
}
)*
};
}
impl NodeArena {
pub fn doctype<'a>(&'a self) -> &'a Node<'a> {
alloc::doctype(self, "html")
}
pub fn title<'a, 'attr, A>(&'a self, attributes: A, title: &str) -> &'a Node<'a>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let text = alloc::text(self, title, true);
self.element("title", attributes, text)
}
pub fn textarea<'a, 'attr, A>(&'a self, attributes: A, text: &str) -> &'a Node<'a>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let text = alloc::text(self, text, true);
self.element("textarea", attributes, text)
}
pub fn style<'a, 'attr, A>(&'a self, attributes: A, css: &str) -> &'a Node<'a>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let text = alloc::text(self, css, true);
self.element("style", attributes, text)
}
pub fn iframe<'a, 'attr, A>(&'a self, attributes: A, content: &str) -> &'a Node<'a>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let text = alloc::text(self, content, true);
self.element("iframe", attributes, text)
}
pub fn script<'a, 'attr, A>(&'a self, attributes: A, script: &str) -> &'a Node<'a>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let text = alloc::text(self, script, true);
self.element("script", attributes, text)
}
pub fn noscript<'a, 'attr, A>(&'a self, attributes: A, content: &str) -> &'a Node<'a>
where
A: IntoIterator<Item = (&'attr str, &'attr str)>,
<A as IntoIterator>::IntoIter: ExactSizeIterator,
{
let text = alloc::text(self, content, true);
self.element("noscript", attributes, text)
}
tag_name_functions! {
a,
abbr,
address,
area,
article,
aside,
audio,
b,
base,
bdi,
bdo,
blockquote,
body,
br,
button,
canvas,
caption,
cite,
code,
col,
colgroup,
data,
datalist,
dd,
del,
details,
dfn,
dialog,
div,
dl,
dt,
em,
embed,
fieldset,
figcaption,
figure,
footer,
form,
h1,
h2,
h3,
h4,
h5,
h6,
head,
header,
hgroup,
hr,
html,
i,
img,
input,
ins,
kbd,
label,
legend,
li,
link,
main,
map,
mark,
menu,
meta,
meter,
nav,
object,
ol,
optgroup,
option,
output,
p,
picture,
pre,
progress,
q,
rp,
rt,
ruby,
s,
samp,
search,
section,
select,
selectedcontent,
slot,
small,
source,
span,
strong,
sub,
summary,
sup,
table,
tbody,
td,
template,
tfoot,
th,
thead,
time,
tr,
track,
u,
ul,
var,
video,
wbr
}
}
pub(crate) mod alloc {
use crate::parser::Namespace;
use crate::{Node, NodeArena, NodeKind};
pub(crate) fn fragment<'a>(arena: &'a NodeArena) -> &'a Node<'a> {
arena.inner.alloc(Node::empty(NodeKind::Fragment))
}
pub(crate) fn element<'attr, 'arena, I>(
arena: &'arena NodeArena,
tag: &str,
attrs: I,
namespace: Namespace,
) -> &'arena Node<'arena>
where
I: Iterator<Item = (&'attr str, &'attr str)> + ExactSizeIterator,
{
let tag = arena.inner.alloc_str(tag);
tag.make_ascii_lowercase();
let attributes = attributes(arena, attrs);
arena.inner.alloc(Node {
attributes,
data: tag,
namespace,
..Node::empty(NodeKind::Element)
})
}
pub(crate) fn comment<'a>(arena: &'a NodeArena, comment_text: &str) -> &'a Node<'a> {
arena.inner.alloc(Node {
data: arena.inner.alloc_str(comment_text),
..Node::empty(NodeKind::Comment)
})
}
pub(crate) fn text<'a>(
arena: &'a NodeArena,
contents: &str,
serialize_verbatim: bool,
) -> &'a Node<'a> {
arena.inner.alloc(Node {
data: arena.inner.alloc_str(contents),
special: serialize_verbatim,
..Node::empty(NodeKind::Text)
})
}
pub(crate) fn doctype<'a>(arena: &'a NodeArena, name: &str) -> &'a Node<'a> {
arena.inner.alloc(Node {
data: arena.inner.alloc_str(name),
..Node::empty(NodeKind::Doctype)
})
}
pub(crate) fn attributes<'attr, 'arena, I>(
arena: &'arena NodeArena,
attrs: I,
) -> &'arena [(&'arena str, &'arena str)]
where
I: Iterator<Item = (&'attr str, &'attr str)>,
I: ExactSizeIterator,
{
let allocated_attrs = attrs.into_iter().map(|(name, value)| {
(
arena.inner.alloc_str(name) as &str,
arena.inner.alloc_str(value) as &str,
)
});
arena.inner.alloc_slice_fill_iter(allocated_attrs)
}
}
#[cfg(test)]
mod serialization_tests {
use super::*;
#[test]
fn empty_attribute_and_child_iterators() {
let h = NodeArena::new();
assert_eq!(h.span(None, None).html(), "<span></span>");
assert_eq!(h.span([], []).html(), "<span></span>");
}
#[test]
fn serializing_text_in_different_contexts() {
let h = NodeArena::new();
assert_eq!(
h.text("hello &\u{a0}<>").html(),
"hello & <>"
);
assert_eq!(
h.div(None, h.text("hello &\u{a0}<>")).html(),
"<div>hello & <></div>"
);
assert_eq!(
h.script(None, "hello &\u{a0}<>").html(),
"<script>hello &\u{a0}<></script>"
);
assert_eq!(
h.style(None, "hello &\u{a0}<>").html(),
"<style>hello &\u{a0}<></style>"
);
assert_eq!(
h.iframe(None, "hello &\u{a0}<>").html(),
"<iframe>hello &\u{a0}<></iframe>"
);
assert_eq!(
h.textarea(None, "hello &\u{a0}<>").html(),
"<textarea>hello &\u{a0}<></textarea>"
);
assert_eq!(
h.noscript(None, "hello &\u{a0}<>").html(),
"<noscript>hello &\u{a0}<></noscript>"
);
}
#[test]
fn serializing_attributes() {
let h = NodeArena::new();
assert_eq!(
h.div([("class", "&\u{a0}<>\"⋈")], None).html(),
r#"<div class="& <>"⋈"></div>"#
)
}
#[test]
fn children() {
let h = NodeArena::new();
assert_eq!(
h.div(None, [h.button(None, None)]).html(),
"<div><button></button></div>"
);
assert_eq!(
h.div(None, h.button(None, None)).html(),
"<div><button></button></div>"
);
assert_eq!(
h.div(None, Some(h.button(None, None))).html(),
"<div><button></button></div>"
);
}
}