use std::collections::btree_map::Entry;
use std::collections::BTreeSet;
use std::collections::VecDeque;
use std::ops::Range;
use crate::let_else::{assume, know};
use crate::token::{Doctype, EndTag, StartTag, Token};
use crate::trace::AttributeTrace;
use crate::trace::AttributeTraceList;
use crate::trace::{
AttrValueSyntax, CommentTrace, DoctypeTrace, EndTagTrace, StartTagTrace, Trace,
};
use crate::Emitter;
use crate::Error;
pub struct TracingEmitter {
current_token: Option<Token>,
current_trace: Option<Trace>,
current_attribute_name: String,
current_attr_internal: crate::token::AttrInternal,
current_attribute_trace: crate::trace::AttributeTrace,
seen_attributes: BTreeSet<String>,
emitted_tokens: VecDeque<(Token, Trace)>,
errors: VecDeque<(Error, Range<usize>)>,
attr_in_end_tag_span: Option<Range<usize>>,
}
impl Default for TracingEmitter {
fn default() -> Self {
TracingEmitter {
current_token: None,
current_trace: None,
current_attribute_name: String::new(),
current_attr_internal: Default::default(),
current_attribute_trace: crate::trace::AttributeTrace::new(),
seen_attributes: BTreeSet::new(),
emitted_tokens: VecDeque::new(),
errors: VecDeque::new(),
attr_in_end_tag_span: None,
}
}
}
impl TracingEmitter {
pub fn drain_errors(&mut self) -> impl Iterator<Item = (Error, Range<usize>)> + '_ {
self.errors.drain(0..)
}
}
impl Iterator for TracingEmitter {
type Item = (Token, Trace);
fn next(&mut self) -> Option<Self::Item> {
self.emitted_tokens.pop_back()
}
}
impl Emitter<usize> for TracingEmitter {
fn report_error(&mut self, error: Error, span: Range<usize>) {
self.errors.push_back((error, span));
}
fn emit_char(&mut self, c: char, span: Range<usize>) {
self.emit_token(Token::Char(c), Trace::Char(span));
}
fn emit_eof(&mut self, offset: usize) {
self.emit_token(Token::EndOfFile, Trace::EndOfFile(offset));
}
fn init_start_tag(&mut self, tag_offset: usize, name_offset: usize) {
self.current_token = Some(Token::StartTag(StartTag {
self_closing: false,
name: String::new(),
attributes: Default::default(),
}));
self.current_trace = Some(Trace::StartTag(StartTagTrace {
span: tag_offset..0,
name_span: name_offset..0,
attribute_traces: AttributeTraceList::new(),
}));
}
fn init_end_tag(&mut self, tag_offset: usize, name_offset: usize) {
self.current_token = Some(Token::EndTag(EndTag {
name: String::new(),
}));
self.current_trace = Some(Trace::EndTag(EndTagTrace {
span: tag_offset..0,
name_span: name_offset..0,
}));
self.seen_attributes.clear();
}
fn push_tag_name(&mut self, s: &str) {
assume!(
Some(Token::StartTag(StartTag { name, .. }) | Token::EndTag(EndTag { name, .. })),
&mut self.current_token
);
name.push_str(s);
}
fn terminate_tag_name(&mut self, offset: usize) {
assume!(
Some(
Trace::StartTag(StartTagTrace { name_span, .. })
| Trace::EndTag(EndTagTrace { name_span, .. })
),
&mut self.current_trace
);
name_span.end = offset;
}
fn init_attribute_name(&mut self, offset: usize) {
self.flush_current_attribute();
self.current_attribute_trace.name_span.start = offset;
}
fn push_attribute_name(&mut self, s: &str) {
self.current_attribute_name.push_str(s);
}
fn terminate_attribute_name(&mut self, offset: usize) {
self.current_attribute_trace.name_span.end = offset;
}
fn init_attribute_value(&mut self, syntax: AttrValueSyntax, offset: usize) {
self.current_attribute_trace.value_span.start = offset;
self.current_attribute_trace.value_syntax = Some(syntax);
}
fn push_attribute_value(&mut self, s: &str) {
self.current_attr_internal.value.push_str(s);
}
fn terminate_attribute_value(&mut self, offset: usize) {
self.current_attribute_trace.value_span.end = offset;
}
fn set_self_closing(&mut self, slash_span: Range<usize>) {
let token = self.current_token.as_mut().unwrap();
match token {
Token::StartTag(tag) => {
tag.self_closing = true;
}
Token::EndTag(_) => {
self.report_error(Error::EndTagWithTrailingSolidus, slash_span);
}
other => debug_assert!(false, "unexpected current_token: {other:?}"),
}
}
fn emit_current_tag(&mut self, offset: usize) {
self.flush_current_attribute();
let mut token = self.current_token.take().unwrap();
let mut trace = self.current_trace.take().unwrap();
match &mut token {
Token::EndTag(_) => {
if !self.seen_attributes.is_empty() {
let span = self.attr_in_end_tag_span.take().unwrap();
self.report_error(Error::EndTagWithAttributes, span);
}
self.seen_attributes.clear();
know!(Trace::EndTag(tag_trace), &mut trace);
tag_trace.span.end = offset;
}
Token::StartTag(_) => {
know!(Trace::StartTag(tag_trace), &mut trace);
tag_trace.span.end = offset;
}
other => {
debug_assert!(false, "unexpected current_token: {other:?}");
return;
}
}
self.emit_token(token, trace);
}
fn init_comment(&mut self, data_start_offset: usize) {
self.current_token = Some(Token::Comment(String::new()));
self.current_trace = Some(Trace::Comment(CommentTrace {
data_span: data_start_offset..0,
}));
}
fn push_comment(&mut self, s: &str) {
assume!(Some(Token::Comment(data)), &mut self.current_token);
data.push_str(s);
}
fn emit_current_comment(&mut self, data_end_offset: usize) {
let token = self.current_token.take().unwrap();
let mut trace = self.current_trace.take().unwrap();
assume!(Trace::Comment(comment_trace), &mut trace);
comment_trace.data_span.end = data_end_offset;
self.emit_token(token, trace);
}
fn init_doctype(&mut self, offset: usize) {
self.current_token = Some(Token::Doctype(Doctype {
name: None,
force_quirks: false,
public_id: None,
system_id: None,
}));
self.current_trace = Some(Trace::Doctype(DoctypeTrace::new(offset)));
}
fn init_doctype_name(&mut self, offset: usize) {
assume!(Some(Token::Doctype(doctype)), &mut self.current_token);
doctype.name = Some("".into());
know!(Some(Trace::Doctype(doctype_trace)), &mut self.current_trace);
doctype_trace.set_name_start(offset);
}
fn push_doctype_name(&mut self, s: &str) {
assume!(
Some(Token::Doctype(Doctype {
name: Some(name),
..
})),
&mut self.current_token
);
name.push_str(s);
}
fn terminate_doctype_name(&mut self, offset: usize) {
assume!(Some(Trace::Doctype(doctype_trace)), &mut self.current_trace);
doctype_trace.set_name_end(offset);
}
fn init_doctype_public_id(&mut self, offset: usize) {
assume!(Some(Token::Doctype(doctype)), &mut self.current_token);
doctype.public_id = Some("".to_owned());
know!(Some(Trace::Doctype(doctype_trace)), &mut self.current_trace);
doctype_trace.set_public_id_start(offset);
}
fn push_doctype_public_id(&mut self, s: &str) {
assume!(
Some(Token::Doctype(Doctype {
public_id: Some(public_id),
..
})),
&mut self.current_token
);
public_id.push_str(s);
}
fn terminate_doctype_public_id(&mut self, offset: usize) {
assume!(Some(Trace::Doctype(doctype_trace)), &mut self.current_trace);
doctype_trace.set_public_id_end(offset);
}
fn init_doctype_system_id(&mut self, offset: usize) {
assume!(Some(Token::Doctype(doctype)), &mut self.current_token);
doctype.system_id = Some("".to_owned());
know!(Some(Trace::Doctype(doctype_trace)), &mut self.current_trace);
doctype_trace.set_system_id_start(offset);
}
fn push_doctype_system_id(&mut self, s: &str) {
assume!(
Some(Token::Doctype(Doctype {
system_id: Some(id),
..
})),
&mut self.current_token
);
id.push_str(s);
}
fn terminate_doctype_system_id(&mut self, offset: usize) {
assume!(Some(Trace::Doctype(doctype_trace)), &mut self.current_trace);
doctype_trace.set_system_id_end(offset);
}
fn set_force_quirks(&mut self) {
assume!(Some(Token::Doctype(doctype)), &mut self.current_token);
doctype.force_quirks = true;
}
fn emit_current_doctype(&mut self, offset: usize) {
assume!(Some(mut trace), self.current_trace.take());
assume!(Trace::Doctype(doctype_trace), &mut trace);
doctype_trace.span.end = offset;
let token = self.current_token.take().unwrap();
self.emit_token(token, trace);
}
}
impl TracingEmitter {
fn emit_token(&mut self, token: Token, trace: Trace) {
self.emitted_tokens.push_front((token, trace));
}
fn flush_current_attribute(&mut self) {
if self.current_attribute_name.is_empty() {
return;
}
let name = std::mem::take(&mut self.current_attribute_name);
let mut attr_internal = std::mem::take(&mut self.current_attr_internal);
let attr_trace =
std::mem::replace(&mut self.current_attribute_trace, AttributeTrace::new());
match &mut self.current_token {
Some(Token::StartTag(tag)) => match tag.attributes.inner.entry(name) {
Entry::Vacant(vacant) => {
know!(Some(Trace::StartTag(trace)), &mut self.current_trace);
let trace_idx = trace.attribute_traces.insert(attr_trace);
attr_internal.trace_idx = Some(trace_idx);
vacant.insert(attr_internal);
}
Entry::Occupied(_) => {
self.report_error(Error::DuplicateAttribute, attr_trace.name_span);
}
},
Some(Token::EndTag(_)) => {
self.attr_in_end_tag_span = Some(attr_trace.name_span.clone());
if !self.seen_attributes.insert(name) {
self.report_error(Error::DuplicateAttribute, attr_trace.name_span);
}
}
other => debug_assert!(false, "unexpected current_token: {other:?}"),
}
}
}
impl From<(Token, Trace)> for Token {
fn from((token, _): (Token, Trace)) -> Self {
token
}
}
#[cfg(test)]
mod tests {
use super::TracingEmitter;
use crate::offset::PosTrackingReader;
use crate::trace::{AttrValueSyntax, Trace};
use crate::{Event, Token, Tokenizer};
#[test]
fn test_attribute_value_syntax() {
let mut tokenizer = Tokenizer::new(
PosTrackingReader::new(
"<div empty unquoted=foo single-quoted='foo' double-quoted=\"foo\">",
),
TracingEmitter::default(),
)
.flatten();
let Event::Token((Token::StartTag(tag), Trace::StartTag(tag_trace))) =
tokenizer.next().unwrap()
else {
panic!("expected start tag");
};
for (name, syntax) in [
("empty", None),
("unquoted", Some(AttrValueSyntax::Unquoted)),
("single-quoted", Some(AttrValueSyntax::SingleQuoted)),
("double-quoted", Some(AttrValueSyntax::DoubleQuoted)),
] {
let (_, Some(attr_trace_idx)) = tag.attributes.value_and_trace_idx(name).unwrap()
else {
panic!()
};
assert_eq!(
tag_trace.attribute_traces[attr_trace_idx].value_syntax(),
syntax,
"unexpected value for attribute {name}"
);
}
}
}