use crate::Tag;
use crate::dom::{Attr, Data, NONE, Node, Span};
use crate::entity::decode_into;
use super::{Builder, MAX_DEPTH, Ns, Open, Scope, is_ws, marker, table_context, void};
impl<'a> Builder<'a> {
pub(super) fn new_node(&mut self, data: Data) -> u32 {
let id = self.nodes.len() as u32;
self.nodes.push(Node::new(data));
id
}
pub(super) fn append(&mut self, parent: u32, child: u32) {
let last = self.nodes[parent as usize].last_child;
{
let c = &mut self.nodes[child as usize];
c.parent = parent;
c.prev = last;
}
if last == NONE {
self.nodes[parent as usize].first_child = child;
} else {
self.nodes[last as usize].next = child;
}
self.nodes[parent as usize].last_child = child;
}
pub(super) fn insert_before(&mut self, parent: u32, child: u32, before: u32) {
let prev = self.nodes[before as usize].prev;
{
let c = &mut self.nodes[child as usize];
c.parent = parent;
c.prev = prev;
c.next = before;
}
self.nodes[before as usize].prev = child;
if prev == NONE {
self.nodes[parent as usize].first_child = child;
} else {
self.nodes[prev as usize].next = child;
}
}
pub(super) fn place(&mut self, child: u32, foster: bool) {
let Some(cur) = self.current() else {
let parent = if self.html != NONE { self.html } else { 0 };
self.append(parent, child);
return;
};
if foster && cur.ns == Ns::Html && table_context(cur.tag) {
self.foster(child, self.stack.len());
} else {
self.append(cur.id, child);
}
}
pub(super) fn foster(&mut self, child: u32, below: usize) {
let Some(i) = self.stack[..below]
.iter()
.rposition(|o| o.tag == Tag::Table && o.ns == Ns::Html)
else {
let parent = self.stack.first().map_or(0, |o| o.id);
self.append(parent, child);
return;
};
let table = self.stack[i].id;
let parent = self.nodes[table as usize].parent;
if parent != NONE {
self.insert_before(parent, child, table);
} else {
let below = self.stack[i.saturating_sub(1)].id;
self.append(below, child);
}
}
pub(super) fn add_text(&mut self, span: Span, foster: bool) {
let (parent, before) = match self.current() {
None => (if self.html != NONE { self.html } else { 0 }, NONE),
Some(cur) => {
let fostered = foster
&& cur.ns == Ns::Html
&& table_context(cur.tag)
&& !self.str(span).bytes().all(is_ws);
match fostered
.then(|| {
self.stack
.iter()
.rposition(|o| o.tag == Tag::Table && o.ns == Ns::Html)
})
.flatten()
{
Some(i) => {
let table = self.stack[i].id;
let p = self.nodes[table as usize].parent;
if p != NONE {
(p, table)
} else {
(self.stack[i.saturating_sub(1)].id, NONE)
}
}
None => (cur.id, NONE),
}
}
};
let prev = if before == NONE {
self.nodes[parent as usize].last_child
} else {
self.nodes[before as usize].prev
};
if prev != NONE
&& let Data::Text(old) = self.nodes[prev as usize].data
{
let merged = self.concat(old, span);
self.nodes[prev as usize].data = Data::Text(merged);
return;
}
let id = self.new_node(Data::Text(span));
if before == NONE {
self.append(parent, id);
} else {
self.insert_before(parent, id, before);
}
}
pub(super) fn concat(&mut self, a: Span, b: Span) -> Span {
if !a.is_owned() && !b.is_owned() && a.end() == b.offset() {
return Span::source(a.offset(), b.end());
}
let start = if a.is_owned() && a.end() == self.buf.len() {
a.offset()
} else {
let s = self.str(a).to_string();
let start = self.buf.len();
self.buf.push_str(&s);
start
};
let s = self.str(b).to_string();
self.buf.push_str(&s);
Span::owned(start, self.buf.len())
}
pub(super) fn lower_span(&mut self, (s, e): (usize, usize)) -> Span {
let raw = &self.src[s..e];
if !raw.bytes().any(|c| c.is_ascii_uppercase()) {
return Span::source(s, e);
}
let start = self.buf.len();
for c in raw.chars() {
self.buf.push(c.to_ascii_lowercase());
}
Span::owned(start, self.buf.len())
}
pub(super) fn value_span(&mut self, (s, e): (usize, usize)) -> Span {
let raw = &self.src[s..e];
if memchr::memchr(b'&', raw.as_bytes()).is_none() {
return Span::source(s, e);
}
let start = self.buf.len();
decode_into(&mut self.buf, raw, true);
Span::owned(start, self.buf.len())
}
pub(super) fn take_attrs(&mut self) -> (u32, u32) {
let start = self.attrs.len() as u32;
let scratch = std::mem::take(&mut self.scratch);
for &(n, v) in &scratch {
let name = self.lower_span(n);
let dup = self.attrs[start as usize..]
.iter()
.any(|a| self.str(a.name) == self.str(name));
if dup {
continue;
}
let value = self.value_span(v);
self.attrs.push(Attr { name, value });
}
self.scratch = scratch;
(start, self.attrs.len() as u32 - start)
}
pub(super) fn create(&mut self, tag: Tag, name: (usize, usize)) -> u32 {
let name = if tag == Tag::Other {
self.lower_span(name)
} else {
Span::default()
};
let (attrs, attrs_len) = self.take_attrs();
self.new_node(Data::Element {
tag,
name,
attrs,
attrs_len,
})
}
pub(super) fn insert(
&mut self,
tag: Tag,
name: (usize, usize),
ns: Ns,
push: bool,
foster: bool,
) -> u32 {
let id = self.create(tag, name);
self.place(id, foster);
if push && self.stack.len() < MAX_DEPTH {
self.stack.push(Open { id, tag, ns });
if ns == Ns::Html && marker(tag) {
self.afe.push(NONE);
}
}
id
}
pub(super) fn insert_html(&mut self, tag: Tag, name: (usize, usize)) {
let push = !void(tag);
let foster = !matches!(
tag,
Tag::Script | Tag::Style | Tag::Template | Tag::Form | Tag::Input
);
self.insert(tag, name, Ns::Html, push, foster);
}
pub(super) fn merge_attrs(&mut self, target: u32) {
if target == NONE {
return;
}
let Data::Element {
tag,
name,
attrs,
attrs_len,
} = self.nodes[target as usize].data
else {
return;
};
let (new_start, new_len) = self.take_attrs();
if new_len == 0 {
return;
}
let old: Vec<Attr> = self.attrs[attrs as usize..(attrs + attrs_len) as usize].to_vec();
let added: Vec<Attr> = self.attrs[new_start as usize..]
.iter()
.copied()
.filter(|a| !old.iter().any(|o| self.str(o.name) == self.str(a.name)))
.collect();
self.attrs.truncate(new_start as usize);
if added.is_empty() {
return;
}
let start = self.attrs.len() as u32;
self.attrs.extend(old);
self.attrs.extend(added);
self.nodes[target as usize].data = Data::Element {
tag,
name,
attrs: start,
attrs_len: self.attrs.len() as u32 - start,
};
}
pub(super) fn in_scope(&self, target: Tag, scope: Scope) -> Option<usize> {
self.find_in_scope(|t| t == target, scope)
}
pub(super) fn find_in_scope(&self, pred: impl Fn(Tag) -> bool, scope: Scope) -> Option<usize> {
for (i, &o) in self.stack.iter().enumerate().rev() {
if o.ns == Ns::Html && pred(o.tag) {
return Some(i);
}
if self.boundary(o, scope) {
return None;
}
}
None
}
pub(super) fn boundary(&self, o: Open, scope: Scope) -> bool {
use Tag::*;
match o.ns {
Ns::Integration => true,
Ns::Foreign => false,
Ns::Html => match scope {
Scope::Table => matches!(o.tag, Html | Table | Template),
_ => {
matches!(
o.tag,
Applet | Caption | Html | Table | Td | Th | Marquee | Object | Template
) || (scope == Scope::ListItem && matches!(o.tag, Ol | Ul))
|| (scope == Scope::Button && o.tag == Button)
}
},
}
}
pub(super) fn pop_to(&mut self, i: usize) {
for k in i..self.stack.len() {
let o = self.stack[k];
if o.ns == Ns::Html && marker(o.tag) {
while let Some(e) = self.afe.pop() {
if e == NONE {
break;
}
}
}
}
self.stack.truncate(i);
}
pub(super) fn pop(&mut self) {
if let Some(n) = self.stack.len().checked_sub(1) {
self.pop_to(n);
}
}
pub(super) fn on_stack(&self, id: u32) -> Option<usize> {
self.stack.iter().rposition(|o| o.id == id)
}
pub(super) fn node_tag(&self, id: u32) -> Tag {
match self.nodes[id as usize].data {
Data::Element { tag, .. } => tag,
_ => Tag::Other,
}
}
pub(super) fn clone_element(&mut self, id: u32) -> u32 {
let data = self.nodes[id as usize].data;
self.new_node(data)
}
pub(super) fn detach(&mut self, id: u32) {
let Node {
parent, prev, next, ..
} = self.nodes[id as usize];
if parent == NONE {
return;
}
if prev == NONE {
self.nodes[parent as usize].first_child = next;
} else {
self.nodes[prev as usize].next = next;
}
if next == NONE {
self.nodes[parent as usize].last_child = prev;
} else {
self.nodes[next as usize].prev = prev;
}
let n = &mut self.nodes[id as usize];
n.parent = NONE;
n.prev = NONE;
n.next = NONE;
}
pub(super) fn move_children(&mut self, from: u32, to: u32) {
let mut c = self.nodes[from as usize].first_child;
while c != NONE {
let next = self.nodes[c as usize].next;
self.detach(c);
self.append(to, c);
c = next;
}
}
pub(super) fn same_element(&self, a: u32, b: u32) -> bool {
let (
Data::Element {
tag: ta,
name: na,
attrs: aa,
attrs_len: la,
},
Data::Element {
tag: tb,
name: nb,
attrs: ab,
attrs_len: lb,
},
) = (self.nodes[a as usize].data, self.nodes[b as usize].data)
else {
return false;
};
if ta != tb || la != lb || self.str(na) != self.str(nb) {
return false;
}
let xs = &self.attrs[aa as usize..(aa + la) as usize];
let ys = &self.attrs[ab as usize..(ab + lb) as usize];
xs.iter().all(|x| {
ys.iter().any(|y| {
self.str(x.name) == self.str(y.name) && self.str(x.value) == self.str(y.value)
})
})
}
}