use crate::html::{LocalNameHash, Tag};
use std::fmt::{self, Display};
use thiserror::Error;
#[derive(Error, Debug, Eq, PartialEq)]
pub struct ParsingAmbiguityError {
on_tag_name: Box<str>,
}
impl Display for ParsingAmbiguityError {
#[cold]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
concat!(
"The parser has encountered a text content tag (`<{}>`) in the context where it is ",
"ambiguous whether this tag should be ignored or not. And, thus, is is unclear is ",
"consequent content should be parsed as raw text or HTML markup.",
"\n\n",
"This error occurs due to the limited capabilities of the streaming parsing. However, ",
"almost all of the cases of this error are caused by a non-conforming markup (e.g. a ",
"`<script>` element in `<select>` element)."
),
self.on_tag_name
)
}
}
macro_rules! create_assert_for_tags {
( $($tag:ident),+ ) => {
#[cold]
fn tag_hash_to_string(tag_name: LocalNameHash) -> Box<str> {
let s = match tag_name {
$(t if t == Tag::$tag => stringify!($tag),)+
_ => "no string representation",
};
s.to_ascii_lowercase().into_boxed_str()
}
#[inline]
fn assert_not_ambigious_text_type_switch(
tag_name: LocalNameHash,
) -> Result<(), ParsingAmbiguityError> {
if tag_is_one_of!(tag_name, [ $($tag),+ ]) {
Err(ParsingAmbiguityError {
on_tag_name: tag_hash_to_string(tag_name)
})
} else {
Ok(())
}
}
};
}
create_assert_for_tags!(
Textarea, Title, Plaintext, Script, Style, Iframe, Xmp, Noembed, Noframes, Noscript
);
#[derive(Copy, Clone)]
enum State {
Default,
InSelect,
InTemplateInSelect(u64),
InOrAfterFrameset,
}
pub(crate) struct AmbiguityGuard {
state: State,
}
impl Default for AmbiguityGuard {
fn default() -> Self {
Self {
state: State::Default,
}
}
}
impl AmbiguityGuard {
pub fn track_start_tag(
&mut self,
tag_name: LocalNameHash,
) -> Result<(), ParsingAmbiguityError> {
match self.state {
State::Default => {
if tag_name == Tag::Select {
self.state = State::InSelect;
} else if tag_name == Tag::Frameset {
self.state = State::InOrAfterFrameset;
}
}
State::InSelect => {
if tag_is_one_of!(tag_name, [Select, Textarea, Input, Keygen]) {
self.state = State::Default;
} else if tag_name == Tag::Template {
self.state = State::InTemplateInSelect(1);
}
else if tag_name != Tag::Script {
assert_not_ambigious_text_type_switch(tag_name)?;
}
}
State::InTemplateInSelect(depth) => {
if tag_name == Tag::Template {
self.state = State::InTemplateInSelect(depth + 1);
} else {
assert_not_ambigious_text_type_switch(tag_name)?;
}
}
State::InOrAfterFrameset => {
if tag_name != Tag::Noframes {
assert_not_ambigious_text_type_switch(tag_name)?;
}
}
}
Ok(())
}
pub fn track_end_tag(&mut self, tag_name: LocalNameHash) {
match self.state {
State::InSelect if tag_name == Tag::Select => {
self.state = State::Default;
}
State::InTemplateInSelect(depth) if tag_name == Tag::Template => {
self.state = if depth == 1 {
State::InSelect
} else {
State::InTemplateInSelect(depth - 1)
}
}
_ => (),
}
}
}