#![allow(unsafe_code)]
use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use html5ever::interface::tree_builder::{
ElementFlags, NodeOrText, QuirksMode as H5QuirksMode, TreeSink,
};
use html5ever::tendril::StrTendril;
use html5ever::{Attribute as H5Attribute, QualName};
use markup5ever::interface::tree_builder::ElemName;
use markup5ever::{LocalName, Namespace as H5Namespace};
use sup_xml_tree::dom::{Document, DocumentBuilder, Node};
use sup_xml_tree::{HtmlDoctype, HtmlMeta, QuirksMode as TreeQuirksMode};
use crate::error::{ErrorDomain, ErrorLevel, XmlError};
use super::options::HtmlParseOptions;
type ErasedHandle = *const ();
const DOCUMENT_SENTINEL: ErasedHandle = 0x1 as *const ();
const DOCTYPE_SENTINEL: ErasedHandle = 0x2 as *const ();
#[inline]
fn erase(n: &Node<'_>) -> ErasedHandle {
n as *const Node<'_> as *const ()
}
#[inline]
unsafe fn unerase<'a>(p: ErasedHandle) -> &'a Node<'a> {
debug_assert!(!is_sentinel(p), "unerase called on sentinel handle");
unsafe { &*(p as *const Node<'a>) }
}
#[inline]
fn is_document(p: ErasedHandle) -> bool { p == DOCUMENT_SENTINEL }
#[inline]
fn is_doctype(p: ErasedHandle) -> bool { p == DOCTYPE_SENTINEL }
#[inline]
fn is_sentinel(p: ErasedHandle) -> bool { is_document(p) || is_doctype(p) }
pub struct OwnedElemName {
inner: QualName,
}
impl std::fmt::Debug for OwnedElemName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&self.inner, f)
}
}
impl ElemName for OwnedElemName {
fn ns(&self) -> &H5Namespace { &self.inner.ns }
fn local_name(&self) -> &LocalName { &self.inner.local }
}
type QNameMap = RefCell<rustc_hash::FxHashMap<ErasedHandle, QualName>>;
struct PendingText {
handle: ErasedHandle,
buf: String,
}
pub(super) struct BatchSinkArena {
builder: DocumentBuilder,
doc_children: RefCell<Vec<ErasedHandle>>,
qnames: QNameMap,
doctype: RefCell<Option<HtmlDoctype>>,
quirks_mode: Cell<H5QuirksMode>,
opts: HtmlParseOptions,
parse_errors: RefCell<Vec<XmlError>>,
text_bytes: Cell<u64>,
pending_text: RefCell<Option<PendingText>>,
aborted: Cell<bool>,
fatal_error: RefCell<Option<XmlError>>,
tree_started: Cell<bool>,
}
impl BatchSinkArena {
pub(super) fn new(opts: HtmlParseOptions) -> Self {
Self::new_inner(DocumentBuilder::new(), opts)
}
#[cfg(feature = "c-abi")]
pub(super) unsafe fn new_with_dict(
opts: HtmlParseOptions,
dict: *mut sup_xml_tree::dict::Dict,
) -> Self {
Self::new_inner(unsafe { DocumentBuilder::new_with_dict(dict) }, opts)
}
#[cfg(feature = "c-abi")]
pub(super) unsafe fn new_with_dict_and_arena(
opts: HtmlParseOptions,
dict: *mut sup_xml_tree::dict::Dict,
arena: std::sync::Arc<bumpalo::Bump>,
) -> Self {
Self::new_inner(
unsafe { DocumentBuilder::new_with_dict_and_arena(dict, arena) },
opts,
)
}
fn new_inner(builder: DocumentBuilder, opts: HtmlParseOptions) -> Self {
Self {
builder,
doc_children: RefCell::new(Vec::new()),
qnames: RefCell::new(rustc_hash::FxHashMap::default()),
doctype: RefCell::new(None),
quirks_mode: Cell::new(H5QuirksMode::NoQuirks),
opts,
parse_errors: RefCell::new(Vec::new()),
text_bytes: Cell::new(0),
pending_text: RefCell::new(None),
aborted: Cell::new(false),
fatal_error: RefCell::new(None),
tree_started: Cell::new(false),
}
}
fn record_error(&self, msg: impl Into<String>, level: ErrorLevel) {
let err = XmlError::new(ErrorDomain::Html, level, msg);
if level == ErrorLevel::Fatal && self.fatal_error.borrow().is_none() {
*self.fatal_error.borrow_mut() = Some(err.clone());
}
self.parse_errors.borrow_mut().push(err);
}
fn abort(&self, msg: impl Into<String>) {
if !self.aborted.get() {
self.aborted.set(true);
self.record_error(msg, ErrorLevel::Fatal);
}
}
fn alloc_element(&self, name: QualName, attrs: Vec<H5Attribute>) -> ErasedHandle {
let local_name: &str = &name.local;
let n: &Node<'_> = {
let alloced_name = self.builder.alloc_str(local_name);
let el = self.builder.new_element(alloced_name);
for a in &attrs {
let aname = self.builder.alloc_str(&a.name.local);
let avalue = self.builder.alloc_str(&a.value);
let attr = self.builder.new_attribute(aname, avalue);
self.builder.append_attribute(el, attr);
}
&*el
};
let handle = erase(n);
self.qnames.borrow_mut().insert(handle, name);
handle
}
fn alloc_text(&self, s: &str) -> ErasedHandle {
let content = self.builder.alloc_str(s);
let n: &Node<'_> = self.builder.new_text(content);
erase(n)
}
fn alloc_comment(&self, s: &str) -> ErasedHandle {
let content = self.builder.alloc_str(s);
let n: &Node<'_> = self.builder.new_comment(content);
erase(n)
}
unsafe fn depth_of(&self, handle: ErasedHandle) -> u32 {
if is_sentinel(handle) {
return 0;
}
let mut d = 0u32;
let mut cur = unsafe { unerase(handle) };
while let Some(p) = cur.parent.get() {
d += 1;
cur = p;
}
d
}
fn detach_from_anywhere(&self, handle: ErasedHandle) {
if is_sentinel(handle) { return; }
let n = unsafe { unerase(handle) };
if n.parent.get().is_some() {
self.builder.detach(n);
return;
}
self.doc_children.borrow_mut().retain(|&p| p != handle);
}
fn link_append(&self, parent: ErasedHandle, child: ErasedHandle) {
self.detach_from_anywhere(child);
if is_document(parent) {
self.doc_children.borrow_mut().push(child);
return;
}
let p = unsafe { unerase(parent) };
let c = unsafe { unerase(child) };
self.builder.append_child(p, c);
}
fn link_before(&self, sibling: ErasedHandle, new_child: ErasedHandle) {
debug_assert!(!is_sentinel(sibling));
self.detach_from_anywhere(new_child);
let s = unsafe { unerase(sibling) };
let parent = match s.parent.get() {
Some(p) => p,
None => return,
};
let nc = unsafe { unerase(new_child) };
let prev = s.prev_sibling.get();
nc.parent.set(Some(parent));
nc.prev_sibling.set(prev);
nc.next_sibling.set(Some(s));
s.prev_sibling.set(Some(nc));
match prev {
Some(p) => p.next_sibling.set(Some(nc)),
None => parent.first_child.set(Some(nc)),
}
}
fn last_child(&self, parent: ErasedHandle) -> Option<ErasedHandle> {
if is_document(parent) {
return self.doc_children.borrow().last().copied();
}
let p = unsafe { unerase(parent) };
p.last_child.get().map(erase)
}
fn flush_pending_text(&self) {
let pending = self.pending_text.borrow_mut().take();
if let Some(p) = pending {
let content = self.builder.alloc_str(&p.buf);
let n = unsafe { unerase(p.handle) };
n.set_content(&self.builder, content);
}
}
fn append_text(&self, parent: ErasedHandle, text: &str) {
let added = text.len() as u64;
let merge_target = self.last_child(parent).filter(|&h| {
if is_sentinel(h) { return false; }
unsafe { unerase(h) }.is_text()
});
if let Some(tgt) = merge_target {
if self.text_bytes.get().saturating_add(added) > self.opts.max_text_bytes {
self.abort(format!(
"max_text_bytes ({}) exceeded during text merge",
self.opts.max_text_bytes
));
return;
}
self.text_bytes.set(self.text_bytes.get() + added);
{
let mut slot = self.pending_text.borrow_mut();
if let Some(p) = slot.as_mut() {
if p.handle == tgt {
p.buf.push_str(text);
return;
}
}
}
self.flush_pending_text();
let n = unsafe { unerase(tgt) };
let cur = n.content();
let mut buf = String::with_capacity(cur.len() + text.len());
buf.push_str(cur);
buf.push_str(text);
*self.pending_text.borrow_mut() = Some(PendingText { handle: tgt, buf });
return;
}
if self.text_bytes.get().saturating_add(added) > self.opts.max_text_bytes {
self.abort(format!(
"max_text_bytes ({}) exceeded",
self.opts.max_text_bytes
));
return;
}
self.text_bytes.set(self.text_bytes.get() + added);
let new_depth = unsafe { self.depth_of(parent) } + 1;
if new_depth > self.opts.max_element_depth {
self.abort(format!(
"max_element_depth ({}) exceeded",
self.opts.max_element_depth
));
return;
}
self.flush_pending_text();
let t = self.alloc_text(text);
self.link_append(parent, t);
}
fn insert_text_before(&self, sibling: ErasedHandle, text: &str) {
debug_assert!(!is_sentinel(sibling));
let s = unsafe { unerase(sibling) };
let parent = match s.parent.get() {
Some(p) => p,
None => return,
};
let added = text.len() as u64;
let prev = s.prev_sibling.get();
if let Some(prev_node) = prev {
if prev_node.is_text() {
if self.text_bytes.get().saturating_add(added) > self.opts.max_text_bytes {
self.abort(format!(
"max_text_bytes ({}) exceeded during text merge",
self.opts.max_text_bytes
));
return;
}
self.text_bytes.set(self.text_bytes.get() + added);
let tgt = erase(prev_node);
{
let mut slot = self.pending_text.borrow_mut();
if let Some(p) = slot.as_mut() {
if p.handle == tgt {
p.buf.push_str(text);
return;
}
}
}
self.flush_pending_text();
let cur = prev_node.content();
let mut buf = String::with_capacity(cur.len() + text.len());
buf.push_str(cur);
buf.push_str(text);
*self.pending_text.borrow_mut() = Some(PendingText { handle: tgt, buf });
return;
}
}
if self.text_bytes.get().saturating_add(added) > self.opts.max_text_bytes {
self.abort(format!(
"max_text_bytes ({}) exceeded",
self.opts.max_text_bytes
));
return;
}
self.text_bytes.set(self.text_bytes.get() + added);
let new_depth = unsafe { self.depth_of(erase(parent)) } + 1;
if new_depth > self.opts.max_element_depth {
self.abort(format!(
"max_element_depth ({}) exceeded",
self.opts.max_element_depth
));
return;
}
self.flush_pending_text();
let t = self.alloc_text(text);
self.link_before(sibling, t);
}
}
fn convert_quirks(q: H5QuirksMode) -> TreeQuirksMode {
match q {
H5QuirksMode::NoQuirks => TreeQuirksMode::NoQuirks,
H5QuirksMode::LimitedQuirks => TreeQuirksMode::LimitedQuirks,
H5QuirksMode::Quirks => TreeQuirksMode::Quirks,
}
}
impl TreeSink for BatchSinkArena {
type Handle = ErasedHandle;
type Output = Self;
type ElemName<'a>
= OwnedElemName
where
Self: 'a;
fn finish(self) -> Self { self }
fn parse_error(&self, msg: Cow<'static, str>) {
if self.opts.recovery_mode && !self.tree_started.get() {
return;
}
let level = if self.opts.recovery_mode {
ErrorLevel::Error
} else if self.fatal_error.borrow().is_none() {
ErrorLevel::Fatal
} else {
ErrorLevel::Error
};
self.record_error(msg.into_owned(), level);
}
fn get_document(&self) -> ErasedHandle { DOCUMENT_SENTINEL }
fn elem_name<'a>(&'a self, target: &'a ErasedHandle) -> OwnedElemName {
let qnames = self.qnames.borrow();
let q = qnames.get(target).expect("elem_name on non-element handle");
OwnedElemName { inner: q.clone() }
}
fn create_element(
&self,
name: QualName,
attrs: Vec<H5Attribute>,
_flags: ElementFlags,
) -> ErasedHandle {
if self.aborted.get() {
}
self.tree_started.set(true);
self.alloc_element(name, attrs)
}
fn create_comment(&self, text: StrTendril) -> ErasedHandle {
self.alloc_comment(&text)
}
fn create_pi(&self, _target: StrTendril, _data: StrTendril) -> ErasedHandle {
self.alloc_comment("")
}
fn append(&self, parent: &ErasedHandle, child: NodeOrText<ErasedHandle>) {
if self.aborted.get() { return; }
let parent = *parent;
match child {
NodeOrText::AppendText(t) => self.append_text(parent, &t),
NodeOrText::AppendNode(n) => {
let new_depth = unsafe { self.depth_of(parent) } + 1;
if new_depth > self.opts.max_element_depth {
self.abort(format!(
"max_element_depth ({}) exceeded",
self.opts.max_element_depth
));
return;
}
self.link_append(parent, n);
}
}
}
fn append_based_on_parent_node(
&self,
element: &ErasedHandle,
prev_element: &ErasedHandle,
child: NodeOrText<ErasedHandle>,
) {
if self.aborted.get() { return; }
let element = *element;
let element_has_parent = !is_sentinel(element) && {
unsafe { unerase(element) }.parent.get().is_some()
};
if element_has_parent {
self.append_before_sibling(&element, child);
} else {
self.append(prev_element, child);
}
}
fn append_doctype_to_document(
&self,
name: StrTendril,
public_id: StrTendril,
system_id: StrTendril,
) {
*self.doctype.borrow_mut() = Some(HtmlDoctype {
name: name.to_string(),
public_id: public_id.to_string(),
system_id: system_id.to_string(),
});
}
fn get_template_contents(&self, target: &ErasedHandle) -> ErasedHandle {
*target
}
fn same_node(&self, x: &ErasedHandle, y: &ErasedHandle) -> bool { x == y }
fn set_quirks_mode(&self, mode: H5QuirksMode) {
self.quirks_mode.set(mode);
}
fn append_before_sibling(
&self,
sibling: &ErasedHandle,
new_node: NodeOrText<ErasedHandle>,
) {
if self.aborted.get() { return; }
let sibling = *sibling;
if is_sentinel(sibling) { return; }
match new_node {
NodeOrText::AppendText(t) => self.insert_text_before(sibling, &t),
NodeOrText::AppendNode(n) => {
let parent = match unsafe { unerase(sibling) }.parent.get() {
Some(p) => p,
None => return,
};
let new_depth = unsafe { self.depth_of(erase(parent)) } + 1;
if new_depth > self.opts.max_element_depth {
self.abort(format!(
"max_element_depth ({}) exceeded",
self.opts.max_element_depth
));
return;
}
self.link_before(sibling, n);
}
}
}
fn add_attrs_if_missing(&self, target: &ErasedHandle, attrs: Vec<H5Attribute>) {
if self.aborted.get() { return; }
if is_sentinel(*target) { return; }
let el = unsafe { unerase(*target) };
if !el.is_element() { return; }
for a in attrs {
let local: &str = &a.name.local;
let mut cur = el.first_attribute.get();
let mut have = false;
while let Some(attr) = cur {
if attr.name() == local { have = true; break; }
cur = attr.next.get();
}
if !have {
let aname = self.builder.alloc_str(local);
let avalue = self.builder.alloc_str(&a.value);
let attr = self.builder.new_attribute(aname, avalue);
self.builder.append_attribute(el, attr);
}
}
}
fn remove_from_parent(&self, target: &ErasedHandle) {
let target = *target;
if is_sentinel(target) { return; }
let n = unsafe { unerase(target) };
if n.parent.get().is_some() {
self.builder.detach(n);
return;
}
self.doc_children.borrow_mut().retain(|&h| h != target);
}
fn reparent_children(&self, node: &ErasedHandle, new_parent: &ErasedHandle) {
let node = *node;
let new_parent = *new_parent;
if is_sentinel(node) {
let moved: Vec<ErasedHandle> =
std::mem::take(&mut *self.doc_children.borrow_mut());
for c in moved {
self.link_append(new_parent, c);
}
return;
}
let src = unsafe { unerase(node) };
let mut moved: Vec<ErasedHandle> = Vec::new();
let mut cur = src.first_child.get();
while let Some(c) = cur {
moved.push(erase(c));
cur = c.next_sibling.get();
}
for c in moved {
self.link_append(new_parent, c);
}
}
}
#[allow(dead_code)]
const _DOCTYPE_SENTINEL_REF: ErasedHandle = DOCTYPE_SENTINEL;
pub(super) fn finalize_arena(
sink: BatchSinkArena,
) -> (Document, Vec<XmlError>, Option<XmlError>) {
sink.flush_pending_text();
let fatal = sink.fatal_error.borrow().clone();
let errors = std::mem::take(&mut *sink.parse_errors.borrow_mut());
let meta = HtmlMeta {
quirks_mode: convert_quirks(sink.quirks_mode.get()),
doctype: sink.doctype.borrow_mut().take(),
};
sink.builder.set_html_metadata(Some(meta));
let doc_children = sink.doc_children.borrow();
let root_handle = doc_children.iter().copied().find(|&h| {
if is_sentinel(h) { return false; }
unsafe { unerase(h) }.is_element()
});
let root_ptr = match root_handle {
Some(h) => h,
None => {
let synth_name = sink.builder.alloc_str("html");
let synth: &Node<'_> = sink.builder.new_element(synth_name);
erase(synth)
}
};
drop(doc_children);
let root_ref: &Node<'_> = unsafe { unerase(root_ptr) };
sink.builder.set_root(root_ref);
let doc = sink.builder.build();
(doc, errors, fatal)
}