vize_l1 0.429.2

L1 - the lossless Vue-template surface tree (codename Sinopia, Davinci P2-7)
Documentation
//! HTML tokenizer for Vue templates.
//!
//! This tokenizer is adapted from htmlparser2 and Vue's compiler-core.
//! It uses a state machine to tokenize HTML/Vue templates.

#[cfg(feature = "native-markup-lex")]
pub mod adapter;
pub mod char_codes;
mod dynamic_arg;
pub mod entity_decode;
mod in_tag_comment;
mod sequences;
mod states;
mod types;

pub use types::*;

use sequences::Sequence;

/// HTML tokenizer
pub struct Tokenizer<'a, C: Callbacks> {
    /// Input source
    input: &'a [u8],
    /// Current state
    state: State,
    /// Some behavior, eg. when decoding entities, is done while we are in another state. This keeps track of the other state type.
    base_state: State,
    /// Buffer start position
    section_start: usize,
    /// Current index
    index: usize,
    /// Callbacks
    callbacks: C,
    /// Delimiter open sequence
    delimiter_open: &'a [u8],
    /// Delimiter close sequence
    delimiter_close: &'a [u8],
    /// Current delimiter index
    delimiter_index: usize,
    /// The start of the last entity.
    entity_start: usize,

    /// Closing sequence currently being matched.
    current_sequence: Option<Sequence>,
    /// Index of the next expected byte in that sequence.
    sequence_index: usize,

    /// For special parsing behavior inside of script and style tags.
    in_rcdata: bool,

    /// True immediately after a quoted attribute value ended.
    ///
    /// The tokenizer needs this one-token memory to report `<div a="b"c>` as
    /// recoverable missing whitespace instead of silently starting `c`.
    after_quoted_attr_value: bool,

    /// Whether to tolerate top-level markup declarations such as `<!DOCTYPE html>`.
    ///
    /// In the default (SFC `<template>`) mode this stays `false`, so a stray
    /// declaration is reported as a recoverable `IncorrectlyOpenedComment` and
    /// the hot path is byte-identical. Document mode sets it `true` so a real
    /// HTML document's doctype is skipped silently instead of producing a
    /// spurious parse error.
    tolerate_declarations: bool,

    /// Whether to recognize Vue 1.x triple-mustache raw-HTML interpolation,
    /// `{{{ expr }}}` (the pre-Vue-2 `v-html` equivalent).
    ///
    /// Resolved once per file from the dialect capabilities; the default Vue 3
    /// path leaves it `false`, so the interpolation states stay byte-identical
    /// to today and `{{{ x }}}` keeps tokenizing as a `{{ … }}` mustache with a
    /// stray brace. Only ever set behind the `legacy` feature for a Vue 1.x
    /// document, and only honored with the default `{{`/`}}` delimiters.
    triple_mustache: bool,

    /// True while the currently open interpolation is a `{{{ … }}}` raw-HTML
    /// one. Reset when the interpolation closes; meaningless (always `false`)
    /// unless [`Tokenizer::triple_mustache`] is set.
    in_raw_interpolation: bool,

    /// Whether to recognize experimental Vue `//` in-tag comments in opening
    /// tag attribute lists.
    in_tag_comments: bool,
}

impl<'a, C: Callbacks> Tokenizer<'a, C> {
    /// Create a new tokenizer
    pub fn new(input: &'a str, callbacks: C) -> Self {
        Self::with_delimiters(input, callbacks, b"{{", b"}}")
    }

    /// Create a new tokenizer with custom delimiters
    pub fn with_delimiters(
        input: &'a str,
        callbacks: C,
        delimiter_open: &'a [u8],
        delimiter_close: &'a [u8],
    ) -> Self {
        Self {
            input: input.as_bytes(),
            state: State::Text,
            section_start: 0,
            index: 0,
            callbacks,
            delimiter_open,
            delimiter_close,
            delimiter_index: 0,
            entity_start: 0,
            base_state: State::Text,
            current_sequence: None,
            sequence_index: 0,
            in_rcdata: false,
            after_quoted_attr_value: false,
            tolerate_declarations: false,
            triple_mustache: false,
            in_raw_interpolation: false,
            in_tag_comments: false,
        }
    }

    #[inline]
    fn at_opening_delimiter(&self, c: u8) -> bool {
        self.delimiter_open.first() == Some(&c)
    }

    #[inline]
    fn at_closing_delimiter(&self, c: u8) -> bool {
        self.delimiter_close.first() == Some(&c)
    }

    /// Tolerate top-level markup declarations (e.g. `<!DOCTYPE html>`) instead of
    /// reporting them as recoverable errors. Used by document parse mode.
    pub fn set_tolerate_declarations(&mut self, tolerate: bool) {
        self.tolerate_declarations = tolerate;
    }

    /// Enable recognition of Vue 1.x triple-mustache raw-HTML interpolation
    /// (`{{{ expr }}}`). Off by default; only the `legacy` Vue 1.x path turns it
    /// on. Honored only with the default `{{`/`}}` delimiters, so a custom
    /// delimiter configuration is unaffected.
    pub fn set_triple_mustache(&mut self, enabled: bool) {
        self.triple_mustache = enabled && self.delimiter_open == b"{{";
    }

    /// Enable experimental Vue `//` in-tag comments in opening tag attribute lists.
    pub fn set_in_tag_comments(&mut self, enabled: bool) {
        self.in_tag_comments = enabled;
    }

    /// Skip through the buffer until a target byte is found.
    fn fast_forward_to(&mut self, c: u8) -> bool {
        while self.index + 1 < self.input.len() {
            self.index += 1;
            if self.input.get(self.index) == Some(&c) {
                return true;
            }
        }
        self.index = self.input.len().saturating_sub(1);
        false
    }

    /// Tokenize the input
    pub fn tokenize(&mut self) {
        while let Some(&c) = self.input.get(self.index) {
            match self.state {
                State::Text => self.state_text(c),
                State::InterpolationOpen => self.state_interpolation_open(c),
                State::Interpolation => self.state_interpolation(c),
                State::InterpolationClose => self.state_interpolation_close(c),
                State::BeforeTagName => self.state_before_tag_name(c),
                State::InTagName => self.state_in_tag_name(c),
                State::InSelfClosingTag => self.state_in_self_closing_tag(c),
                State::BeforeClosingTagName => self.state_before_closing_tag_name(c),
                State::InClosingTagName => self.state_in_closing_tag_name(c),
                State::AfterClosingTagName => self.state_after_closing_tag_name(c),
                State::BeforeAttrName => self.state_before_attr_name(c),
                State::InTagComment => self.state_in_tag_comment(c),
                State::InAttrName => self.state_in_attr_name(c),
                State::InDirName => self.state_in_dir_name(c),
                State::InDirArg => self.state_in_dir_arg(c),
                State::InDirDynamicArg => self.state_in_dir_dynamic_arg(c),
                State::InDirModifier => self.state_in_dir_modifier(c),
                State::AfterAttrName => self.state_after_attr_name(c),
                State::BeforeAttrValue => self.state_before_attr_value(c),
                State::InAttrValueDq => self.state_in_attr_value_dq(c),
                State::InAttrValueSq => self.state_in_attr_value_sq(c),
                State::InAttrValueNq => self.state_in_attr_value_nq(c),
                State::BeforeDeclaration => self.state_before_declaration(c),
                State::InDeclaration => self.state_in_declaration(c),
                State::InProcessingInstruction => self.state_in_processing_instruction(c),
                State::BeforeComment => self.state_before_comment(c),
                State::CDATASequence => self.state_cdata_sequence(c),
                State::InSpecialComment => self.state_in_special_comment(c),
                State::InCommentLike => self.state_in_comment_like(c),
                State::BeforeSpecialS => self.state_before_special_s(c),
                State::BeforeSpecialT => self.state_before_special_t(c),
                State::SpecialStartSequence => self.state_special_start_sequence(c),
                State::InRCDATA => self.state_in_rcdata(c),
                State::InEntity => self.state_in_entity(),
                State::InSFCRootTagName => self.state_in_sfc_root_tag_name(c),
            }

            self.index += 1;
        }

        // Handle remaining content
        self.cleanup();
        self.callbacks.on_end();
    }
}

#[cfg(test)]
mod empty_delimiter_tests;
#[cfg(test)]
mod tests;