use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::mem::take;
use crate::{Error, HtmlString, Span, SpanBound, Spanned};
use crate::emitters::callback::{Callback, CallbackEmitter, CallbackEvent};
use super::{Emitter, ForwardingEmitter};
#[derive(Debug, Default)]
struct OurCallback<S: SpanBound> {
tag_name: Vec<u8>,
tag_start_span: S,
attribute_name: Spanned<HtmlString, S>,
attribute_map: BTreeMap<HtmlString, Spanned<HtmlString, S>>,
}
impl<S: SpanBound> Callback<Token<S>, S> for OurCallback<S> {
fn handle_event(&mut self, event: CallbackEvent<'_>, span: Span<S>) -> Option<Token<S>> {
crate::utils::trace_log!("event: {:?}", event);
match event {
CallbackEvent::OpenStartTag { name } => {
self.tag_name.clear();
self.tag_name.extend(name);
self.tag_start_span = span.start;
None
}
CallbackEvent::AttributeName { name } => {
self.attribute_name.clear();
match self.attribute_map.entry(name.to_owned().into()) {
Entry::Occupied(_) => Some(Token::Error(Spanned {
value: Error::DuplicateAttribute,
span,
})),
Entry::Vacant(vacant) => {
self.attribute_name.extend(name);
vacant.insert(Spanned {
value: Default::default(),
span,
});
None
}
}
}
CallbackEvent::AttributeValue { value } => {
if !self.attribute_name.is_empty() {
let attr = self.attribute_map.get_mut(&*self.attribute_name).unwrap();
attr.extend(value);
attr.span.end = span.end.offset(1);
}
None
}
CallbackEvent::CloseStartTag { self_closing } => Some(Token::StartTag(StartTag {
self_closing,
name: take(&mut self.tag_name).into(),
span: Span {
start: self.tag_start_span,
end: span.end,
},
attributes: take(&mut self.attribute_map),
})),
CallbackEvent::EndTag { name } => {
self.attribute_map.clear();
Some(Token::EndTag(EndTag {
name: name.to_owned().into(),
span,
}))
}
CallbackEvent::String { value } => Some(Token::String(Spanned {
value: value.to_owned().into(),
span,
})),
CallbackEvent::Comment { value } => Some(Token::Comment(Spanned {
value: value.to_owned().into(),
span,
})),
CallbackEvent::Doctype {
name,
public_identifier,
system_identifier,
force_quirks,
} => Some(Token::Doctype(Spanned {
value: Doctype {
force_quirks,
name: name.to_owned().into(),
public_identifier: public_identifier.map(|x| x.to_owned().into()),
system_identifier: system_identifier.map(|x| x.to_owned().into()),
},
span,
})),
CallbackEvent::Error(error) => Some(Token::Error(Spanned { value: error, span })),
}
}
}
#[derive(Debug)]
pub struct DefaultEmitter<S: SpanBound = ()> {
inner: CallbackEmitter<OurCallback<S>, Token<S>, S>,
}
impl Default for DefaultEmitter<()> {
fn default() -> Self {
Self {
inner: Default::default(),
}
}
}
impl<S: SpanBound> DefaultEmitter<S> {
#[must_use]
pub fn new_with_span() -> Self {
Self {
inner: Default::default(),
}
}
pub fn naively_switch_states(&mut self, yes: bool) {
self.inner.naively_switch_states(yes)
}
}
impl<S: SpanBound> ForwardingEmitter for DefaultEmitter<S> {
type Token = Token<S>;
fn inner(&mut self) -> &mut impl Emitter<Token = Self::Token> {
&mut self.inner
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct StartTag<S: SpanBound> {
pub self_closing: bool,
pub name: HtmlString,
pub attributes: BTreeMap<HtmlString, Spanned<HtmlString, S>>,
pub span: Span<S>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EndTag<S: SpanBound> {
pub name: HtmlString,
pub span: Span<S>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Doctype {
pub force_quirks: bool,
pub name: HtmlString,
pub public_identifier: Option<HtmlString>,
pub system_identifier: Option<HtmlString>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Token<S: SpanBound = ()> {
StartTag(StartTag<S>),
EndTag(EndTag<S>),
String(Spanned<HtmlString, S>),
Comment(Spanned<HtmlString, S>),
Doctype(Spanned<Doctype, S>),
Error(Spanned<Error, S>),
}