use vize_relief::ErrorCode;
use super::char_codes::AMP;
use super::sequences::Sequence;
use super::{
Tokenizer,
char_codes::{
AT, COLON, DASH, DOT, DOUBLE_QUOTE, EQ, EXCLAMATION_MARK, GRAVE_ACCENT, GT, LEFT_SQUARE,
LOWER_V, LT, NUMBER, QUESTION_MARK, SINGLE_QUOTE, SLASH,
},
types::{Callbacks, QuoteType, State, is_end_of_tag_section, is_tag_start_char, is_whitespace},
};
use super::entity_decode::try_decode_entity;
use htmlize::Context;
impl<'a, C: Callbacks> Tokenizer<'a, C> {
pub(super) fn cleanup(&mut self) {
let has_section = self.section_start < self.index;
match self.state {
State::Text if has_section => {
self.callbacks.on_text(self.section_start, self.index);
}
State::InRCDATA if has_section => {
self.callbacks.on_text(self.section_start, self.index);
}
State::Interpolation | State::InterpolationClose if has_section => {
self.callbacks
.on_error(ErrorCode::MissingInterpolationEnd, self.index);
let start = self.section_start.saturating_sub(self.delimiter_open.len());
self.callbacks.on_text(start, self.index);
}
State::BeforeTagName if has_section => {
self.callbacks
.on_error(ErrorCode::EofBeforeTagName, self.index);
self.callbacks.on_text(self.section_start, self.index);
}
State::InTagName
| State::InSFCRootTagName
| State::BeforeSpecialS
| State::BeforeSpecialT
| State::SpecialStartSequence
| State::BeforeClosingTagName
| State::InClosingTagName
| State::AfterClosingTagName
| State::BeforeAttrName
| State::InTagComment
| State::InAttrName
| State::InDirName
| State::InDirArg
| State::InDirDynamicArg
| State::InDirModifier
| State::AfterAttrName
| State::BeforeAttrValue
| State::InAttrValueDq
| State::InAttrValueSq
| State::InAttrValueNq => {
self.callbacks.on_error(ErrorCode::EofInTag, self.index);
self.recover_incomplete_tag_at_eof();
}
_ => {}
}
if self.state == State::InCommentLike {
let code = match self.current_sequence {
Some(Sequence::CdataEnd) => ErrorCode::EofInCdata,
_ => ErrorCode::EofInComment,
};
let error_index = match self.current_sequence {
Some(Sequence::CdataEnd) => self.section_start.saturating_sub(9),
_ => self.section_start.saturating_sub(4),
};
self.callbacks.on_error(code, error_index);
match self.current_sequence {
Some(Sequence::CdataEnd) => {
self.callbacks.on_cdata(self.section_start, self.index);
}
_ => {
self.callbacks.on_comment(self.section_start, self.index);
}
}
}
}
fn recover_incomplete_tag_at_eof(&mut self) {
let inferred_tag_end = self.index.saturating_sub(1);
match self.state {
State::InTagName
| State::InSFCRootTagName
| State::BeforeSpecialS
| State::BeforeSpecialT
| State::SpecialStartSequence => {
self.callbacks
.on_open_tag_name(self.section_start, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::BeforeAttrName => {
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InTagComment => {
self.recover_in_tag_comment_at_eof(inferred_tag_end);
}
State::InAttrName => {
self.callbacks
.on_attrib_name(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InDirName => {
self.callbacks.on_dir_name(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InDirArg => {
if self.section_start < self.index {
self.callbacks.on_dir_arg(self.section_start, self.index);
}
self.callbacks.on_attrib_name_end(self.index);
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InDirDynamicArg => {
self.callbacks
.on_error(ErrorCode::MissingDynamicDirectiveArgumentEnd, self.index);
if self.section_start < self.index {
self.callbacks.on_dir_arg(self.section_start, self.index);
}
self.callbacks.on_attrib_name_end(self.index);
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InDirModifier => {
self.callbacks
.on_dir_modifier(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::AfterAttrName => {
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::BeforeAttrValue => {
self.callbacks
.on_error(ErrorCode::MissingAttributeValue, self.index);
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InAttrValueDq => {
self.emit_unclosed_attr_value(QuoteType::Double);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InAttrValueSq => {
self.emit_unclosed_attr_value(QuoteType::Single);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InAttrValueNq => {
self.emit_unclosed_attr_value(QuoteType::Unquoted);
self.callbacks.on_open_tag_end(inferred_tag_end);
}
State::InClosingTagName if self.section_start < self.index => {
self.callbacks.on_close_tag(self.section_start, self.index);
}
State::InClosingTagName | State::BeforeClosingTagName | State::AfterClosingTagName => {}
_ => {}
}
self.state = State::Text;
self.section_start = self.index;
}
fn emit_unclosed_attr_value(&mut self, quote: QuoteType) {
if self.section_start < self.index {
self.callbacks
.on_attrib_data(self.section_start, self.index);
}
self.callbacks.on_attrib_end(quote, self.index);
}
pub(super) fn state_text(&mut self, c: u8) {
if c == LT {
if self.index > self.section_start {
self.callbacks.on_text(self.section_start, self.index);
}
self.state = State::BeforeTagName;
self.section_start = self.index;
} else if c == AMP {
self.start_entity();
} else if !self.callbacks.is_in_v_pre() && self.at_opening_delimiter(c) {
self.state = State::InterpolationOpen;
self.delimiter_index = 0;
self.state_interpolation_open(c);
}
}
pub(super) fn state_interpolation_open(&mut self, c: u8) {
if self.delimiter_open.get(self.delimiter_index) == Some(&c) {
self.delimiter_index += 1;
if self.delimiter_index == self.delimiter_open.len() {
let start = self.index + 1 - self.delimiter_open.len();
if start > self.section_start {
self.callbacks.on_text(self.section_start, start);
}
self.section_start = self.index + 1;
self.state = State::Interpolation;
self.delimiter_index = 0;
if self.triple_mustache && self.input.get(self.index + 1) == Some(&b'{') {
self.in_raw_interpolation = true;
self.section_start = self.index + 2;
}
}
} else if self.in_rcdata {
self.state = State::InRCDATA;
self.state_in_rcdata(c);
} else {
self.state = State::Text;
self.state_text(c);
}
}
pub(super) fn state_interpolation(&mut self, c: u8) {
if self.at_closing_delimiter(c) {
self.state = State::InterpolationClose;
self.delimiter_index = 0;
self.state_interpolation_close(c);
}
}
pub(super) fn state_interpolation_close(&mut self, c: u8) {
if self.delimiter_close.get(self.delimiter_index) == Some(&c) {
self.delimiter_index += 1;
if self.delimiter_index == self.delimiter_close.len() {
let expr_end = self.index + 1 - self.delimiter_close.len();
if self.in_raw_interpolation {
self.in_raw_interpolation = false;
self.callbacks
.on_raw_interpolation(self.section_start, expr_end);
let consumed_brace = self.input.get(self.index + 1) == Some(&b'}');
self.section_start = self.index + 1 + usize::from(consumed_brace);
if consumed_brace {
self.index += 1;
}
} else {
self.callbacks
.on_interpolation(self.section_start, expr_end);
self.section_start = self.index + 1;
}
if self.in_rcdata {
self.state = State::InRCDATA
} else {
self.state = State::Text
}
}
} else {
self.state = State::Interpolation;
self.state_interpolation(c);
}
}
pub(super) fn state_before_tag_name(&mut self, c: u8) {
if c == EXCLAMATION_MARK {
self.state = State::BeforeDeclaration;
self.section_start = self.index + 1;
} else if c == QUESTION_MARK {
self.callbacks.on_error(
ErrorCode::UnexpectedQuestionMarkInsteadOfTagName,
self.index,
);
self.state = State::InProcessingInstruction;
self.section_start = self.index + 1;
} else if is_tag_start_char(c) {
self.section_start = self.index;
if c == b't' {
self.state = State::BeforeSpecialT;
} else if c == b's' {
self.state = State::BeforeSpecialS;
} else {
self.state = State::InTagName;
}
} else if c == SLASH {
self.state = State::BeforeClosingTagName;
} else {
self.state = State::Text;
self.state_text(c);
}
}
pub(super) fn state_in_tag_name(&mut self, c: u8) {
if is_end_of_tag_section(c) {
self.callbacks
.on_open_tag_name(self.section_start, self.index);
self.section_start = self.index;
self.state = State::BeforeAttrName;
self.state_before_attr_name(c);
}
}
pub(super) fn state_in_self_closing_tag(&mut self, c: u8) {
if c == GT {
self.callbacks.on_self_closing_tag(self.index);
self.current_sequence = None;
self.sequence_index = 0;
self.state = State::Text;
self.section_start = self.index + 1;
} else if !is_whitespace(c) {
self.callbacks
.on_error(ErrorCode::UnexpectedSolidusInTag, self.section_start);
self.state = State::BeforeAttrName;
self.state_before_attr_name(c);
}
}
pub(super) fn state_before_closing_tag_name(&mut self, c: u8) {
if is_whitespace(c) {
} else if c == GT {
self.callbacks
.on_error(ErrorCode::MissingEndTagName, self.index);
self.state = State::Text;
self.section_start = self.index + 1;
} else if !is_tag_start_char(c) {
self.callbacks
.on_error(ErrorCode::InvalidFirstCharacterOfTagName, self.index);
self.state = State::InClosingTagName;
self.section_start = self.index;
} else {
self.state = State::InClosingTagName;
self.section_start = self.index;
}
}
pub(super) fn state_in_closing_tag_name(&mut self, c: u8) {
if c == GT || is_whitespace(c) {
self.callbacks.on_close_tag(self.section_start, self.index);
self.section_start = self.index + 1;
self.state = if c == GT {
State::Text
} else {
State::AfterClosingTagName
};
}
}
pub(super) fn state_after_closing_tag_name(&mut self, c: u8) {
if c == GT {
self.state = State::Text;
self.section_start = self.index + 1;
} else if c == SLASH {
self.callbacks
.on_error(ErrorCode::EndTagWithTrailingSolidus, self.index);
} else if !is_whitespace(c) {
self.callbacks
.on_error(ErrorCode::EndTagWithAttributes, self.index);
}
}
pub(super) fn state_before_attr_name(&mut self, c: u8) {
if c == GT {
self.after_quoted_attr_value = false;
self.callbacks.on_open_tag_end(self.index);
if self.in_rcdata {
self.state = State::InRCDATA;
} else {
self.state = State::Text;
}
self.section_start = self.index + 1;
} else if self.try_start_in_tag_comment(c) {
} else if c == SLASH {
self.after_quoted_attr_value = false;
self.state = State::InSelfClosingTag;
} else if is_whitespace(c) {
self.after_quoted_attr_value = false;
} else if c == EQ {
self.after_quoted_attr_value = false;
self.callbacks.on_error(
ErrorCode::UnexpectedEqualsSignBeforeAttributeName,
self.index,
);
} else if !is_whitespace(c) {
if self.after_quoted_attr_value {
self.callbacks
.on_error(ErrorCode::MissingWhitespaceBetweenAttributes, self.index);
}
self.after_quoted_attr_value = false;
self.handle_attr_start(c);
}
}
pub(super) fn handle_attr_start(&mut self, c: u8) {
if self.callbacks.is_in_v_pre() {
self.state = State::InAttrName;
self.section_start = self.index;
return;
}
if c == LOWER_V && self.input.get(self.index + 1) == Some(&DASH) {
self.state = State::InDirName;
self.section_start = self.index;
} else if c == DOT || c == COLON || c == AT || c == NUMBER {
self.callbacks.on_dir_name(self.index, self.index + 1);
self.state = State::InDirArg;
self.section_start = self.index + 1;
} else {
self.state = State::InAttrName;
self.section_start = self.index;
}
}
pub(super) fn state_in_attr_name(&mut self, c: u8) {
if c == EQ || is_end_of_tag_section(c) {
self.callbacks
.on_attrib_name(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.section_start = self.index;
self.state = State::AfterAttrName;
self.state_after_attr_name(c);
} else if c == DOUBLE_QUOTE || c == SINGLE_QUOTE {
self.callbacks
.on_error(ErrorCode::UnexpectedCharacterInAttributeName, self.index);
self.callbacks
.on_attrib_name(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.section_start = self.index + 1;
self.state = if c == DOUBLE_QUOTE {
State::InAttrValueDq
} else {
State::InAttrValueSq
};
} else if c == LT {
self.callbacks
.on_error(ErrorCode::UnexpectedCharacterInAttributeName, self.index);
}
}
pub(super) fn state_in_dir_name(&mut self, c: u8) {
if c == EQ || is_end_of_tag_section(c) {
self.callbacks.on_dir_name(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.section_start = self.index;
self.state = State::AfterAttrName;
self.state_after_attr_name(c);
} else if c == COLON {
self.callbacks.on_dir_name(self.section_start, self.index);
self.state = State::InDirArg;
self.section_start = self.index + 1;
} else if c == DOT {
self.callbacks.on_dir_name(self.section_start, self.index);
self.state = State::InDirModifier;
self.section_start = self.index + 1;
} else if c == LEFT_SQUARE {
self.callbacks.on_dir_name(self.section_start, self.index);
self.state = State::InDirDynamicArg;
self.section_start = self.index + 1;
}
}
pub(super) fn state_in_dir_arg(&mut self, c: u8) {
if c == EQ || is_end_of_tag_section(c) {
if self.section_start < self.index {
self.callbacks.on_dir_arg(self.section_start, self.index);
}
self.callbacks.on_attrib_name_end(self.index);
self.section_start = self.index;
self.state = State::AfterAttrName;
self.state_after_attr_name(c);
} else if c == LEFT_SQUARE {
if self.section_start < self.index {
self.callbacks.on_dir_arg(self.section_start, self.index);
}
self.state = State::InDirDynamicArg;
self.section_start = self.index + 1;
} else if c == DOT {
if self.section_start < self.index {
self.callbacks.on_dir_arg(self.section_start, self.index);
}
self.state = State::InDirModifier;
self.section_start = self.index + 1;
}
}
pub(super) fn state_in_dir_modifier(&mut self, c: u8) {
if c == EQ || is_end_of_tag_section(c) {
self.callbacks
.on_dir_modifier(self.section_start, self.index);
self.callbacks.on_attrib_name_end(self.index);
self.section_start = self.index;
self.state = State::AfterAttrName;
self.state_after_attr_name(c);
} else if c == DOT {
self.callbacks
.on_dir_modifier(self.section_start, self.index);
self.section_start = self.index + 1;
}
}
pub(super) fn state_after_attr_name(&mut self, c: u8) {
if c == EQ {
self.state = State::BeforeAttrValue;
} else if c == SLASH || c == GT {
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.state = State::BeforeAttrName;
self.state_before_attr_name(c);
} else if !is_whitespace(c) {
self.callbacks.on_attrib_end(QuoteType::NoValue, self.index);
self.handle_attr_start(c);
}
}
pub(super) fn state_before_attr_value(&mut self, c: u8) {
if c == DOUBLE_QUOTE {
self.state = State::InAttrValueDq;
self.section_start = self.index + 1;
} else if c == SINGLE_QUOTE {
self.state = State::InAttrValueSq;
self.section_start = self.index + 1;
} else if c == EQ {
self.callbacks.on_error(
ErrorCode::UnexpectedEqualsSignBeforeAttributeName,
self.index,
);
} else if c == GT {
self.callbacks
.on_error(ErrorCode::MissingAttributeValue, self.index);
self.callbacks
.on_attrib_end(QuoteType::Unquoted, self.index);
self.state = State::BeforeAttrName;
self.state_before_attr_name(c);
} else if !is_whitespace(c) {
self.section_start = self.index;
self.state = State::InAttrValueNq;
self.state_in_attr_value_nq(c);
}
}
fn handle_in_attr_value(&mut self, c: u8, quote: u8, quote_type: QuoteType) {
if c == quote {
self.emit_attr_value(quote_type);
} else if c == AMP {
self.start_entity();
}
}
pub(super) fn state_in_attr_value_dq(&mut self, c: u8) {
self.handle_in_attr_value(c, DOUBLE_QUOTE, QuoteType::Double);
}
pub(super) fn state_in_attr_value_sq(&mut self, c: u8) {
self.handle_in_attr_value(c, SINGLE_QUOTE, QuoteType::Single);
}
pub(super) fn state_in_attr_value_nq(&mut self, c: u8) {
if is_whitespace(c) || c == GT {
self.emit_attr_value(QuoteType::Unquoted);
self.state_before_attr_name(c);
} else if c == AMP {
self.start_entity();
} else if matches!(c, DOUBLE_QUOTE | SINGLE_QUOTE | LT | EQ | GRAVE_ACCENT) {
self.callbacks.on_error(
ErrorCode::UnexpectedCharacterInUnquotedAttributeValue,
self.index,
);
}
}
pub(super) fn emit_attr_value(&mut self, quote: QuoteType) {
if self.section_start < self.index {
self.callbacks
.on_attrib_data(self.section_start, self.index);
}
self.callbacks.on_attrib_end(quote, self.index);
self.section_start = self.index + 1;
self.state = State::BeforeAttrName;
self.after_quoted_attr_value = matches!(quote, QuoteType::Double | QuoteType::Single);
}
pub(super) fn state_before_declaration(&mut self, c: u8) {
if c == DASH {
self.state = State::BeforeComment;
self.section_start = self.index + 1;
} else if c == LEFT_SQUARE {
self.sequence_index = 0;
self.state = State::CDATASequence;
self.section_start = self.index + 1;
} else {
if !self.tolerate_declarations {
self.callbacks.on_error(
ErrorCode::IncorrectlyOpenedComment,
self.section_start.saturating_sub(2),
);
}
self.state = State::InDeclaration;
}
}
pub(super) fn state_in_declaration(&mut self, c: u8) {
if c == GT {
self.state = State::Text;
self.section_start = self.index + 1;
}
}
pub(super) fn state_in_processing_instruction(&mut self, c: u8) {
if c == GT {
self.callbacks
.on_processing_instruction(self.section_start, self.index);
self.state = State::Text;
self.section_start = self.index + 1;
}
}
pub(super) fn state_before_comment(&mut self, c: u8) {
if c == DASH {
self.sequence_index = 2;
self.state = State::InCommentLike;
self.current_sequence = Some(Sequence::CommentEnd);
self.section_start = self.index + 1;
} else {
self.state = State::InDeclaration;
}
}
pub(super) fn state_cdata_sequence(&mut self, c: u8) {
let prefix = Sequence::Cdata.bytes();
if prefix.get(self.sequence_index) == Some(&c) {
self.sequence_index += 1;
if self.sequence_index == prefix.len() {
self.state = State::InCommentLike;
self.current_sequence = Some(Sequence::CdataEnd);
self.sequence_index = 0;
self.section_start = self.index + 1;
}
} else {
self.sequence_index = 0;
self.state = State::InDeclaration;
self.state_in_declaration(c);
}
}
pub(super) fn state_in_special_comment(&mut self, c: u8) {
if c == GT {
self.callbacks.on_comment(self.section_start, self.index);
self.state = State::Text;
self.section_start = self.index + 1;
}
}
#[inline]
fn finish_comment_like(&mut self, closing: Sequence) {
let end = self.index.saturating_sub(2);
match closing {
Sequence::CdataEnd => self.callbacks.on_cdata(self.section_start, end),
_ => self.callbacks.on_comment(self.section_start, end),
}
self.sequence_index = 0;
self.current_sequence = None;
self.section_start = self.index + 1;
self.state = State::Text;
}
#[inline]
fn current_sequence_with_bytes(&self) -> Option<(Sequence, &'static [u8])> {
self.current_sequence
.map(|sequence| (sequence, sequence.bytes()))
}
pub(super) fn state_in_comment_like(&mut self, c: u8) {
let Some((sequence, sequence_bytes)) = self.current_sequence_with_bytes() else {
self.state = State::Text;
return;
};
if sequence == Sequence::CommentEnd
&& self.sequence_index == 2
&& c == GT
&& self.index.saturating_sub(self.section_start) < 2
{
self.callbacks
.on_error(ErrorCode::AbruptClosingOfEmptyComment, self.index);
self.callbacks
.on_comment(self.section_start, self.section_start);
self.sequence_index = 0;
self.current_sequence = None;
self.section_start = self.index + 1;
self.state = State::Text;
} else if sequence_bytes.get(self.sequence_index) == Some(&c) {
self.sequence_index += 1;
if self.sequence_index == sequence_bytes.len() {
self.finish_comment_like(sequence);
}
} else if self.sequence_index == 0 {
if sequence == Sequence::CommentEnd
&& self.input.get(self.index..self.index + 4) == Some(b"<!--")
{
self.callbacks
.on_error(ErrorCode::NestedComment, self.index);
} else if sequence != Sequence::CommentEnd
&& self.fast_forward_to(sequence.first_byte())
{
self.sequence_index = 1;
}
} else if sequence == Sequence::CommentEnd
&& self.sequence_index == 2
&& c == EXCLAMATION_MARK
{
self.callbacks
.on_error(ErrorCode::IncorrectlyClosedComment, self.index);
} else if sequence_bytes.get(self.sequence_index - 1) != Some(&c) {
self.sequence_index = 0;
}
}
pub(super) fn state_before_special_s(&mut self, c: u8) {
if Sequence::ScriptEnd.bytes().get(3) == Some(&c) {
self.start_special(Sequence::ScriptEnd, 4);
} else if Sequence::StyleEnd.bytes().get(3) == Some(&c) {
self.start_special(Sequence::StyleEnd, 4);
} else {
self.state = State::InTagName;
self.state_in_tag_name(c);
}
}
pub(super) fn state_before_special_t(&mut self, c: u8) {
if Sequence::TitleEnd.bytes().get(3) == Some(&c) {
self.start_special(Sequence::TitleEnd, 4);
} else if Sequence::TextareaEnd.bytes().get(3) == Some(&c) {
self.start_special(Sequence::TextareaEnd, 4);
} else {
self.state = State::InTagName;
self.state_in_tag_name(c);
}
}
pub(super) fn enter_rcdata(&mut self, sequence: Sequence, offset: usize) {
self.in_rcdata = true;
self.current_sequence = Some(sequence);
self.sequence_index = offset;
}
fn start_special(&mut self, sequence: Sequence, offset: usize) {
self.enter_rcdata(sequence, offset);
self.state = State::SpecialStartSequence;
}
pub(super) fn state_special_start_sequence(&mut self, c: u8) {
let Some((_, sequence_bytes)) = self.current_sequence_with_bytes() else {
self.state = State::InTagName;
self.state_in_tag_name(c);
return;
};
let is_end = self.sequence_index == sequence_bytes.len();
let is_match = if is_end {
is_end_of_tag_section(c)
} else {
sequence_bytes.get(self.sequence_index) == Some(&(c | 0x20))
};
if !is_match {
self.in_rcdata = false;
} else if !is_end {
self.sequence_index += 1;
return;
}
self.sequence_index = 0;
self.state = State::InTagName;
self.state_in_tag_name(c);
}
pub(super) fn state_in_rcdata(&mut self, c: u8) {
let Some((sequence, sequence_bytes)) = self.current_sequence_with_bytes() else {
self.state = State::Text;
self.state_text(c);
return;
};
if self.sequence_index == sequence_bytes.len() {
if c == GT || is_whitespace(c) {
let end_of_text: usize = self.index - sequence_bytes.len();
if self.section_start < end_of_text {
let actual_index = self.index;
self.index = end_of_text;
self.callbacks.on_text(self.section_start, end_of_text);
self.index = actual_index;
}
self.section_start = end_of_text + 2; self.state_in_closing_tag_name(c);
self.in_rcdata = false;
return;
}
self.sequence_index = 0;
}
if sequence_bytes.get(self.sequence_index) == Some(&(c | 0x20)) {
self.sequence_index += 1;
} else if self.sequence_index == 0 {
if matches!(sequence, Sequence::TitleEnd | Sequence::TextareaEnd) {
if c == AMP {
self.start_entity();
} else if !self.callbacks.is_in_v_pre() && self.at_opening_delimiter(c) {
self.state = State::InterpolationOpen;
self.delimiter_index = 0;
self.state_interpolation_open(c);
}
} else if self.fast_forward_to(LT) {
self.sequence_index = 1;
}
} else {
self.sequence_index = if c == LT { 1 } else { 0 };
}
}
pub(super) fn start_entity(&mut self) {
self.base_state = self.state;
self.state = State::InEntity;
self.entity_start = self.index;
}
pub(super) fn state_in_entity(&mut self) {
let raw = self.input.get(self.entity_start..).unwrap_or_default();
let context = match self.base_state {
State::Text | State::InRCDATA => Context::General,
_ => Context::Attribute,
};
if let Some((ch, consumed)) = try_decode_entity(raw, context) {
self.emit_entity_char(ch, consumed);
} else {
self.index = self.entity_start;
}
self.state = self.base_state;
}
pub(super) fn emit_entity_char(&mut self, ch: char, consumed: usize) {
if self.base_state != State::Text && self.base_state != State::InRCDATA {
if self.section_start < self.entity_start {
self.callbacks
.on_attrib_data(self.section_start, self.entity_start);
}
self.section_start = self.entity_start + consumed;
self.index = self.section_start - 1;
self.callbacks
.on_attrib_entity(ch, self.entity_start, self.section_start);
} else {
if self.section_start < self.entity_start {
self.callbacks
.on_text(self.section_start, self.entity_start);
}
self.section_start = self.entity_start + consumed;
self.index = self.section_start - 1;
self.callbacks
.on_text_entity(ch, self.entity_start, self.section_start);
}
}
pub(super) fn state_in_sfc_root_tag_name(&mut self, c: u8) {
if is_end_of_tag_section(c) {
self.callbacks
.on_open_tag_name(self.section_start, self.index);
self.section_start = self.index;
self.state = State::BeforeAttrName;
self.state_before_attr_name(c);
}
}
}