use alloc::borrow::Cow;
use alloc::vec::Vec;
use crate::error::{ErrorKind, ParseError};
use crate::node::{AttrRange, NodeId, RawKind, Span, SrcRange};
use crate::tags;
use crate::token::{AttrValue, Token};
use crate::tokenizer::Tokenizer;
use crate::tree::Dom;
pub(crate) fn parse_document(src: &str) -> Dom {
let mut tk = Tokenizer::new(src);
let mut builder = TreeBuilder::new(DomSink::new(src.len()), src);
while let Some((tok, pos)) = tk.next_token() {
let end = tk.offset();
builder.token(tok, tk.attrs(), pos, end);
}
builder.finish(src.len(), &mut tk.errors);
let mut dom = builder.sink.dom;
dom.errors = builder.errors;
dom.release_spare_capacity();
dom
}
pub(crate) trait Sink<'a> {
type Open;
fn name<'s>(&'s self, open: &'s Self::Open) -> &'s str;
fn attr(&mut self, name: &'a str, value: &'a str) -> Option<usize>;
fn start(
&mut self,
parent: Option<&Self::Open>,
name: Cow<'a, str>,
attrs: usize,
start: usize,
end: usize,
) -> Self::Open;
fn end(&mut self, open: Self::Open, start: usize, end: usize, implied: bool);
fn text(
&mut self,
parent: Option<&Self::Open>,
text: &'a str,
decode: bool,
start: usize,
end: usize,
) -> Option<usize>;
fn comment(&mut self, parent: Option<&Self::Open>, text: &'a str, start: usize, end: usize);
fn doctype(&mut self, parent: Option<&Self::Open>, text: &'a str, start: usize, end: usize);
}
pub(crate) struct TreeBuilder<'a, S: Sink<'a>> {
pub(crate) sink: S,
open: OpenElements<S::Open>,
pub(crate) errors: Vec<ParseError>,
src: &'a str,
}
impl<'a, S: Sink<'a>> TreeBuilder<'a, S> {
pub(crate) fn new(sink: S, src: &'a str) -> Self {
TreeBuilder {
sink,
open: OpenElements::default(),
errors: Vec::new(),
src,
}
}
fn source_offset(&self, s: &str) -> Option<usize> {
let offset = s.as_ptr().addr().checked_sub(self.src.as_ptr().addr())?;
(offset.checked_add(s.len())? <= self.src.len()).then_some(offset)
}
#[inline]
pub(crate) fn token(
&mut self,
tok: Token<'a>,
attrs: &[(&'a str, AttrValue<'a>)],
pos: usize,
end: usize,
) {
match tok {
Token::StartTag {
name, self_closing, ..
} => self.start_tag(name, attrs, self_closing, pos, end),
Token::EndTag { name } => self.end_tag(name, pos, end),
Token::Text(t) => self.text(t, pos, end),
Token::Cdata(c) => {
if !c.is_empty() {
self.sink.text(self.open.top(), c, false, pos, end);
}
}
Token::Comment(c) => self.sink.comment(self.open.top(), c, pos, end),
Token::Doctype(d) => self.sink.doctype(self.open.top(), d.trim(), pos, end),
}
}
pub(crate) fn finish(&mut self, len: usize, lexical: &mut Vec<ParseError>) {
if self.open.len() > 0 {
self.error(len, ErrorKind::ImplicitlyClosed);
}
while self.open.len() > 0 {
self.pop(len, len, true);
}
self.errors.append(lexical);
self.errors.sort_by_key(|e| e.position);
}
#[inline]
fn error(&mut self, pos: usize, kind: ErrorKind) {
self.errors.push(ParseError::new(pos, kind));
}
#[inline]
fn pop(&mut self, start: usize, end: usize, implied: bool) {
if let Some(open) = self.open.pop() {
self.sink.end(open, start, end, implied);
}
}
fn start_tag(
&mut self,
name: &'a str,
attrs: &[(&'a str, AttrValue<'a>)],
self_closing: bool,
pos: usize,
end: usize,
) {
let lname = lower(name);
while self
.open
.top()
.is_some_and(|top| tags::implicitly_closes(&lname, self.sink.name(top)))
{
self.pop(pos, pos, true);
self.error(pos, ErrorKind::ImplicitlyClosed);
}
for &(k, v) in attrs {
let value = v.as_str();
if let Some(at) = self.sink.attr(k, value) {
let at = self.source_offset(value).map_or(pos, |offset| offset + at);
self.error(at, ErrorKind::UnknownEntity);
}
}
let void = self_closing || tags::is_void(&lname);
let open = self
.sink
.start(self.open.top(), lname, attrs.len(), pos, end);
if void {
self.sink.end(open, end, end, true);
} else {
self.open.push(open);
}
}
fn end_tag(&mut self, name: &'a str, pos: usize, end: usize) {
let lname = lower(name);
if tags::is_void(&lname) {
return; }
match self.open.find(&self.sink, &lname) {
Some(idx) => {
if idx + 1 != self.open.len() {
let kind = if self.open.only_optional_above(&self.sink, idx) {
ErrorKind::ImplicitlyClosed
} else {
ErrorKind::MismatchedEndTag
};
self.error(pos, kind);
}
while self.open.len() > idx + 1 {
self.pop(pos, pos, true);
}
self.pop(pos, end, false);
}
None => self.error(pos, ErrorKind::StrayEndTag),
}
}
fn text(&mut self, t: &'a str, pos: usize, end: usize) {
if t.is_empty() {
return;
}
let (raw, closes) = {
let top = self.open.top().map_or("", |o| self.sink.name(o));
(tags::is_raw_text(top), tags::text_closes(top))
};
if closes && t.bytes().any(|b| !b.is_ascii_whitespace()) {
self.pop(pos, pos, true);
self.error(pos, ErrorKind::ImplicitlyClosed);
}
if let Some(at) = self.sink.text(self.open.top(), t, !raw, pos, end) {
self.error(pos + at, ErrorKind::UnknownEntity);
}
}
}
pub(crate) struct DomSink {
pub(crate) dom: Dom,
fits: bool,
}
impl DomSink {
pub(crate) fn new(src_len: usize) -> Self {
DomSink {
dom: Dom::with_capacity(src_len),
fits: src_len < u32::MAX as usize,
}
}
#[inline]
fn range(&self, start: usize, end: usize) -> SrcRange {
if self.fits {
SrcRange::new(start as u32, end as u32)
} else {
SrcRange::NONE
}
}
#[inline]
fn leaf(&mut self, parent: Option<&(NodeId, Span)>, raw: RawKind, start: usize, end: usize) {
let id = self.dom.alloc(raw, self.range(start, end));
self.dom.append(parent.map(|&(p, _)| p), id);
}
}
impl<'a> Sink<'a> for DomSink {
type Open = (NodeId, Span);
#[inline]
fn name<'s>(&'s self, open: &'s (NodeId, Span)) -> &'s str {
self.dom.span_str(open.1)
}
#[inline]
fn attr(&mut self, name: &'a str, value: &'a str) -> Option<usize> {
let key = self.dom.intern_lower(name);
let (value, unknown) = self.dom.intern_decoded(value, true);
self.dom.attrs.push((key, value));
unknown
}
#[inline]
fn start(
&mut self,
parent: Option<&(NodeId, Span)>,
name: Cow<'a, str>,
attrs: usize,
start: usize,
end: usize,
) -> (NodeId, Span) {
let name = self.dom.intern(&name);
let first = self.dom.attrs.len().saturating_sub(attrs);
let attrs = AttrRange {
start: first as u32,
len: (self.dom.attrs.len() - first) as u32,
};
let id = self
.dom
.alloc(RawKind::Element { name, attrs }, self.range(start, end));
self.dom.append(parent.map(|&(p, _)| p), id);
(id, name)
}
#[inline]
fn end(&mut self, (id, _): (NodeId, Span), _start: usize, end: usize, _implied: bool) {
if let Some(n) = self.dom.node_mut(id) {
n.src.set_end(end as u32);
}
}
#[inline]
fn text(
&mut self,
parent: Option<&(NodeId, Span)>,
text: &'a str,
decode: bool,
start: usize,
end: usize,
) -> Option<usize> {
let (span, unknown) = if decode {
self.dom.intern_decoded(text, false)
} else {
(self.dom.intern(text), None)
};
self.leaf(parent, RawKind::Text(span), start, end);
unknown
}
fn comment(
&mut self,
parent: Option<&(NodeId, Span)>,
text: &'a str,
start: usize,
end: usize,
) {
let span = self.dom.intern(text);
self.leaf(parent, RawKind::Comment(span), start, end);
}
fn doctype(
&mut self,
parent: Option<&(NodeId, Span)>,
text: &'a str,
start: usize,
end: usize,
) {
let span = self.dom.intern(text);
self.leaf(parent, RawKind::Doctype(span), start, end);
}
}
const MAX_END_TAG_SEARCH: usize = 256;
const OUTER_END_TAG_SEARCH: usize = 8;
struct OpenElements<O> {
stack: Vec<O>,
}
impl<O> Default for OpenElements<O> {
fn default() -> Self {
OpenElements { stack: Vec::new() }
}
}
impl<O> OpenElements<O> {
#[inline]
fn top(&self) -> Option<&O> {
self.stack.last()
}
#[inline]
fn len(&self) -> usize {
self.stack.len()
}
#[inline]
fn push(&mut self, open: O) {
self.stack.push(open);
}
#[inline]
fn pop(&mut self) -> Option<O> {
self.stack.pop()
}
fn only_optional_above<'a, S: Sink<'a, Open = O>>(&self, sink: &S, idx: usize) -> bool {
self.stack
.get(idx + 1..)
.unwrap_or(&[])
.iter()
.all(|open| tags::end_tag_optional(sink.name(open)))
}
fn find<'a, S: Sink<'a, Open = O>>(&self, sink: &S, name: &str) -> Option<usize> {
let named = |open: &O| sink.name(open) == name;
let inner = self.stack.len().saturating_sub(MAX_END_TAG_SEARCH);
if let Some(i) = self
.stack
.get(inner..)
.and_then(|s| s.iter().rposition(named))
{
return Some(inner + i);
}
let outer = inner.min(OUTER_END_TAG_SEARCH);
self.stack.get(..outer)?.iter().rposition(named)
}
}
pub(crate) fn lower(name: &str) -> Cow<'_, str> {
if name.bytes().any(|b| b.is_ascii_uppercase()) {
Cow::Owned(name.to_ascii_lowercase())
} else {
Cow::Borrowed(name)
}
}