html5ever/tokenizer/mod.rs
1// Copyright 2014-2017 The html5ever Project Developers. See the
2// COPYRIGHT file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! The HTML5 tokenizer.
11
12pub use self::interface::{CharacterTokens, EOFToken, NullCharacterToken, ParseError};
13pub use self::interface::{CommentToken, DoctypeToken, TagToken, Token};
14pub use self::interface::{Doctype, EndTag, StartTag, Tag, TagKind};
15pub use self::interface::{TokenSink, TokenSinkResult};
16
17use self::states::AttrValueKind::*;
18use self::states::DoctypeIdKind::{self, *};
19use self::states::RawKind::*;
20use self::states::ScriptEscapeKind::*;
21use self::states::State::{self, *};
22
23use self::char_ref::{CharRef, CharRefTokenizer};
24
25use crate::util::str::lower_ascii_letter;
26
27use log::{debug, trace};
28use markup5ever::{ns, small_char_set, TokenizerResult};
29use std::borrow::Cow::{self, Borrowed};
30use std::cell::{Cell, RefCell, RefMut};
31use std::cmp::Reverse;
32use std::collections::BTreeMap;
33use std::mem;
34
35pub use crate::buffer_queue::{BufferQueue, FromSet, NotFromSet, SetResult};
36use crate::macros::{time, unwrap_or_return};
37use crate::tendril::StrTendril;
38use crate::{Attribute, LocalName, QualName, SmallCharSet};
39
40mod char_ref;
41mod interface;
42pub mod states;
43
44/// The result of invoking the tokenizer once.
45pub enum ProcessResult<Handle> {
46 /// The tokenizer should be re-invoked immediately.
47 Continue,
48 /// The tokenizer has not finished, but it needs to wait for more
49 /// input to arrive before it can continue.
50 Suspend,
51 /// The tokenizer was blocked by a `<script>`.
52 ///
53 /// This `<script>` needs to be executed before tokenization
54 /// can continue, as it might invoke `document.write`.
55 Script(Handle),
56 /// The tokenizer was blocked because it found a `<meta charset>` tag.
57 ///
58 /// Such tags may force the user agent to re-parse the document with the new
59 /// encoding, but non-conformant implementations can reasonably treat
60 /// this as [Self::Continue].
61 EncodingIndicator(StrTendril),
62}
63
64fn option_push(opt_str: &mut Option<StrTendril>, c: char) {
65 match *opt_str {
66 Some(ref mut s) => s.push_char(c),
67 None => *opt_str = Some(StrTendril::from_char(c)),
68 }
69}
70
71/// Tokenizer options, with an impl for `Default`.
72#[derive(Clone)]
73pub struct TokenizerOpts {
74 /// Report all parse errors described in the spec, at some
75 /// performance penalty? Default: false
76 pub exact_errors: bool,
77
78 /// Discard a `U+FEFF BYTE ORDER MARK` if we see one at the beginning
79 /// of the stream? Default: true
80 pub discard_bom: bool,
81
82 /// Keep a record of how long we spent in each state? Printed
83 /// when `end()` is called. Default: false
84 pub profile: bool,
85
86 /// Initial state override. Only the test runner should use
87 /// a non-`None` value!
88 pub initial_state: Option<states::State>,
89
90 /// Last start tag. Only the test runner should use a
91 /// non-`None` value!
92 ///
93 /// FIXME: Can't use Tendril because we want TokenizerOpts
94 /// to be Send.
95 pub last_start_tag_name: Option<String>,
96}
97
98impl Default for TokenizerOpts {
99 fn default() -> TokenizerOpts {
100 TokenizerOpts {
101 exact_errors: false,
102 discard_bom: true,
103 profile: false,
104 initial_state: None,
105 last_start_tag_name: None,
106 }
107 }
108}
109
110/// The HTML tokenizer.
111pub struct Tokenizer<Sink> {
112 /// Options controlling the behavior of the tokenizer.
113 opts: TokenizerOpts,
114
115 /// Destination for tokens we emit.
116 pub sink: Sink,
117
118 /// The abstract machine state as described in the spec.
119 state: Cell<states::State>,
120
121 /// Are we at the end of the file, once buffers have been processed
122 /// completely? This affects whether we will wait for lookahead or not.
123 at_eof: Cell<bool>,
124
125 /// Tokenizer for character references, if we're tokenizing
126 /// one at the moment.
127 char_ref_tokenizer: RefCell<Option<CharRefTokenizer>>,
128
129 /// Current input character. Just consumed, may reconsume.
130 current_char: Cell<char>,
131
132 /// Should we reconsume the current input character?
133 reconsume: Cell<bool>,
134
135 /// Did we just consume \r, translating it to \n? In that case we need
136 /// to ignore the next character if it's \n.
137 ignore_lf: Cell<bool>,
138
139 /// Discard a U+FEFF BYTE ORDER MARK if we see one? Only done at the
140 /// beginning of the stream.
141 discard_bom: Cell<bool>,
142
143 /// Current tag kind.
144 current_tag_kind: Cell<TagKind>,
145
146 /// Current tag name.
147 current_tag_name: RefCell<StrTendril>,
148
149 /// Current tag is self-closing?
150 current_tag_self_closing: Cell<bool>,
151
152 /// Current tag had duplicate attributes?
153 current_tag_had_duplicate_attributes: Cell<bool>,
154
155 /// Current tag attributes.
156 current_tag_attrs: RefCell<Vec<Attribute>>,
157
158 /// Current attribute name.
159 current_attr_name: RefCell<StrTendril>,
160
161 /// Current attribute value.
162 current_attr_value: RefCell<StrTendril>,
163
164 /// Current comment.
165 current_comment: RefCell<StrTendril>,
166
167 /// Current doctype token.
168 current_doctype: RefCell<Doctype>,
169
170 /// Last start tag name, for use in checking "appropriate end tag".
171 last_start_tag_name: RefCell<Option<LocalName>>,
172
173 /// The "temporary buffer" mentioned in the spec.
174 temp_buf: RefCell<StrTendril>,
175
176 /// Record of how many ns we spent in each state, if profiling is enabled.
177 state_profile: RefCell<BTreeMap<states::State, u64>>,
178
179 /// Record of how many ns we spent in the token sink.
180 time_in_sink: Cell<u64>,
181
182 /// Track current line
183 current_line: Cell<u64>,
184}
185
186impl<Sink: TokenSink> Tokenizer<Sink> {
187 /// Create a new tokenizer which feeds tokens to a particular `TokenSink`.
188 pub fn new(sink: Sink, mut opts: TokenizerOpts) -> Tokenizer<Sink> {
189 let start_tag_name = opts
190 .last_start_tag_name
191 .take()
192 .map(|s| LocalName::from(&*s));
193 let state = opts.initial_state.unwrap_or(states::Data);
194 let discard_bom = opts.discard_bom;
195 Tokenizer {
196 opts,
197 sink,
198 state: Cell::new(state),
199 char_ref_tokenizer: RefCell::new(None),
200 at_eof: Cell::new(false),
201 current_char: Cell::new('\0'),
202 reconsume: Cell::new(false),
203 ignore_lf: Cell::new(false),
204 discard_bom: Cell::new(discard_bom),
205 current_tag_kind: Cell::new(StartTag),
206 current_tag_name: RefCell::new(StrTendril::new()),
207 current_tag_self_closing: Cell::new(false),
208 current_tag_had_duplicate_attributes: Cell::new(false),
209 current_tag_attrs: RefCell::new(vec![]),
210 current_attr_name: RefCell::new(StrTendril::new()),
211 current_attr_value: RefCell::new(StrTendril::new()),
212 current_comment: RefCell::new(StrTendril::new()),
213 current_doctype: RefCell::new(Doctype::default()),
214 last_start_tag_name: RefCell::new(start_tag_name),
215 temp_buf: RefCell::new(StrTendril::new()),
216 state_profile: RefCell::new(BTreeMap::new()),
217 time_in_sink: Cell::new(0),
218 current_line: Cell::new(1),
219 }
220 }
221
222 /// Feed an input string into the tokenizer.
223 pub fn feed(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
224 if input.is_empty() {
225 return TokenizerResult::Done;
226 }
227
228 if self.discard_bom.get() {
229 if let Some(c) = input.peek() {
230 if c == '\u{feff}' {
231 input.next();
232 }
233 } else {
234 return TokenizerResult::Done;
235 }
236 };
237
238 self.run(input)
239 }
240
241 pub fn set_plaintext_state(&self) {
242 self.state.set(states::Plaintext);
243 }
244
245 fn process_token(&self, token: Token) -> TokenSinkResult<Sink::Handle> {
246 if self.opts.profile {
247 let (ret, dt) = time!(self.sink.process_token(token, self.current_line.get()));
248 self.time_in_sink.set(self.time_in_sink.get() + dt);
249 ret
250 } else {
251 self.sink.process_token(token, self.current_line.get())
252 }
253 }
254
255 fn process_token_and_continue(&self, token: Token) {
256 assert!(matches!(
257 self.process_token(token),
258 TokenSinkResult::Continue
259 ));
260 }
261
262 //§ preprocessing-the-input-stream
263 // Get the next input character, which might be the character
264 // 'c' that we already consumed from the buffers.
265 fn get_preprocessed_char(&self, mut c: char, input: &BufferQueue) -> Option<char> {
266 if self.ignore_lf.get() {
267 self.ignore_lf.set(false);
268 if c == '\n' {
269 c = input.next()?;
270 }
271 }
272
273 if c == '\r' {
274 self.ignore_lf.set(true);
275 c = '\n';
276 }
277
278 if c == '\n' {
279 self.current_line.set(self.current_line.get() + 1);
280 }
281
282 if self.opts.exact_errors
283 && match c as u32 {
284 0x01..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F..=0x9F | 0xFDD0..=0xFDEF => true,
285 n if (n & 0xFFFE) == 0xFFFE => true,
286 _ => false,
287 }
288 {
289 let msg = format!("Bad character {c}");
290 self.emit_error(Cow::Owned(msg));
291 }
292
293 trace!("got character {c}");
294 self.current_char.set(c);
295 Some(c)
296 }
297
298 //§ tokenization
299 // Get the next input character, if one is available.
300 fn get_char(&self, input: &BufferQueue) -> Option<char> {
301 if self.reconsume.get() {
302 self.reconsume.set(false);
303 Some(self.current_char.get())
304 } else {
305 input
306 .next()
307 .and_then(|c| self.get_preprocessed_char(c, input))
308 }
309 }
310
311 fn pop_except_from(&self, input: &BufferQueue, set: SmallCharSet) -> Option<SetResult> {
312 // Bail to the slow path for various corner cases.
313 // This means that `FromSet` can contain characters not in the set!
314 // It shouldn't matter because the fallback `FromSet` case should
315 // always do the same thing as the `NotFromSet` case.
316 if self.opts.exact_errors || self.reconsume.get() || self.ignore_lf.get() {
317 return self.get_char(input).map(FromSet);
318 }
319
320 let d = input.pop_except_from(set);
321 trace!("got characters {d:?}");
322 match d {
323 Some(FromSet(c)) => self.get_preprocessed_char(c, input).map(FromSet),
324
325 // NB: We don't set self.current_char for a run of characters not
326 // in the set. It shouldn't matter for the codepaths that use
327 // this.
328 _ => d,
329 }
330 }
331
332 // Check if the next characters are an ASCII case-insensitive match. See
333 // BufferQueue::eat.
334 //
335 // NB: this doesn't set the current input character.
336 fn eat(&self, input: &BufferQueue, pat: &str, eq: fn(&u8, &u8) -> bool) -> Option<bool> {
337 if self.ignore_lf.get() {
338 self.ignore_lf.set(false);
339 if self.peek(input) == Some('\n') {
340 self.discard_char(input);
341 }
342 }
343
344 input.push_front(mem::take(&mut self.temp_buf.borrow_mut()));
345 match input.eat(pat, eq) {
346 None if self.at_eof.get() => Some(false),
347 None => {
348 while let Some(data) = input.next() {
349 self.temp_buf.borrow_mut().push_char(data);
350 }
351 None
352 },
353 Some(matched) => Some(matched),
354 }
355 }
356
357 /// Run the state machine for as long as we can.
358 fn run(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
359 if self.opts.profile {
360 loop {
361 let state = self.state.get();
362 let old_sink = self.time_in_sink.get();
363 let (run, mut dt) = time!(self.step(input));
364 dt -= (self.time_in_sink.get() - old_sink);
365 let new = match self.state_profile.borrow_mut().get_mut(&state) {
366 Some(x) => {
367 *x += dt;
368 false
369 },
370 None => true,
371 };
372 if new {
373 // do this here because of borrow shenanigans
374 self.state_profile.borrow_mut().insert(state, dt);
375 }
376 match run {
377 ProcessResult::Continue => (),
378 ProcessResult::Suspend => break,
379 ProcessResult::Script(node) => return TokenizerResult::Script(node),
380 ProcessResult::EncodingIndicator(encoding) => {
381 return TokenizerResult::EncodingIndicator(encoding)
382 },
383 }
384 }
385 } else {
386 loop {
387 match self.step(input) {
388 ProcessResult::Continue => (),
389 ProcessResult::Suspend => break,
390 ProcessResult::Script(node) => return TokenizerResult::Script(node),
391 ProcessResult::EncodingIndicator(encoding) => {
392 return TokenizerResult::EncodingIndicator(encoding)
393 },
394 }
395 }
396 }
397 TokenizerResult::Done
398 }
399
400 #[inline]
401 fn bad_char_error(&self) {
402 #[cfg(feature = "trace_tokenizer")]
403 trace!(" error");
404
405 let msg = if self.opts.exact_errors {
406 Cow::from("Bad character")
407 } else {
408 let c = self.current_char.get();
409 let state = self.state.get();
410 Cow::from(format!("Saw {c} in state {state:?}"))
411 };
412 self.emit_error(msg);
413 }
414
415 #[inline]
416 fn bad_eof_error(&self) {
417 #[cfg(feature = "trace_tokenizer")]
418 trace!(" error_eof");
419
420 let msg = if self.opts.exact_errors {
421 Cow::from("Unexpected EOF")
422 } else {
423 let state = self.state.get();
424 Cow::from(format!("Saw EOF in state {state:?}"))
425 };
426 self.emit_error(msg);
427 }
428
429 fn emit_char(&self, c: char) {
430 #[cfg(feature = "trace_tokenizer")]
431 trace!(" emit");
432
433 self.process_token_and_continue(match c {
434 '\0' => NullCharacterToken,
435 _ => CharacterTokens(StrTendril::from_char(c)),
436 });
437 }
438
439 // The string must not contain '\0'!
440 fn emit_chars(&self, b: StrTendril) {
441 self.process_token_and_continue(CharacterTokens(b));
442 }
443
444 fn emit_current_tag(&self) -> ProcessResult<Sink::Handle> {
445 self.finish_attribute();
446
447 let name = LocalName::from(&**self.current_tag_name.borrow());
448 self.current_tag_name.borrow_mut().clear();
449
450 match self.current_tag_kind.get() {
451 StartTag => {
452 *self.last_start_tag_name.borrow_mut() = Some(name.clone());
453 },
454 EndTag => {
455 if !self.current_tag_attrs.borrow().is_empty() {
456 self.emit_error(Borrowed("Attributes on an end tag"));
457 }
458 if self.current_tag_self_closing.get() {
459 self.emit_error(Borrowed("Self-closing end tag"));
460 }
461 },
462 }
463
464 let token = TagToken(Tag {
465 kind: self.current_tag_kind.get(),
466 name,
467 self_closing: self.current_tag_self_closing.get(),
468 attrs: std::mem::take(&mut self.current_tag_attrs.borrow_mut()),
469 had_duplicate_attributes: self.current_tag_had_duplicate_attributes.get(),
470 });
471
472 match self.process_token(token) {
473 TokenSinkResult::Continue => ProcessResult::Continue,
474 TokenSinkResult::Plaintext => {
475 self.state.set(states::Plaintext);
476 ProcessResult::Continue
477 },
478 TokenSinkResult::Script(node) => {
479 self.state.set(states::Data);
480 ProcessResult::Script(node)
481 },
482 TokenSinkResult::RawData(kind) => {
483 self.state.set(states::RawData(kind));
484 ProcessResult::Continue
485 },
486 TokenSinkResult::EncodingIndicator(encoding) => {
487 ProcessResult::EncodingIndicator(encoding)
488 },
489 }
490 }
491
492 fn emit_temp_buf(&self) {
493 #[cfg(feature = "trace_tokenizer")]
494 trace!(" emit_temp");
495
496 // FIXME: Make sure that clearing on emit is spec-compatible.
497 let buf = mem::take(&mut *self.temp_buf.borrow_mut());
498 self.emit_chars(buf);
499 }
500
501 fn clear_temp_buf(&self) {
502 // Do this without a new allocation.
503 self.temp_buf.borrow_mut().clear();
504 }
505
506 fn emit_current_comment(&self) {
507 let comment = mem::take(&mut *self.current_comment.borrow_mut());
508 self.process_token_and_continue(CommentToken(comment));
509 }
510
511 fn discard_tag(&self) {
512 self.current_tag_name.borrow_mut().clear();
513 self.current_tag_self_closing.set(false);
514 self.current_tag_had_duplicate_attributes.set(false);
515 *self.current_tag_attrs.borrow_mut() = vec![];
516 }
517
518 fn create_tag(&self, kind: TagKind, c: char) {
519 self.discard_tag();
520 self.current_tag_name.borrow_mut().push_char(c);
521 self.current_tag_kind.set(kind);
522 }
523
524 fn have_appropriate_end_tag(&self) -> bool {
525 match self.last_start_tag_name.borrow().as_ref() {
526 Some(last) => {
527 (self.current_tag_kind.get() == EndTag)
528 && (**self.current_tag_name.borrow() == **last)
529 },
530 None => false,
531 }
532 }
533
534 fn create_attribute(&self, c: char) {
535 self.finish_attribute();
536
537 self.current_attr_name.borrow_mut().push_char(c);
538 }
539
540 fn finish_attribute(&self) {
541 if self.current_attr_name.borrow().is_empty() {
542 return;
543 }
544 let name = LocalName::from(&**self.current_attr_name.borrow());
545 self.current_attr_name.borrow_mut().clear();
546 // Check for a duplicate attribute.
547 // FIXME: the spec says we should error as soon as the name is finished.
548 let dup = {
549 self.current_tag_attrs
550 .borrow()
551 .iter()
552 .any(|a| a.name.local == name)
553 };
554
555 if dup {
556 self.emit_error(Borrowed("Duplicate attribute"));
557 self.current_tag_had_duplicate_attributes.set(true);
558 self.current_attr_value.borrow_mut().clear();
559 } else {
560 self.current_tag_attrs.borrow_mut().push(Attribute {
561 // The tree builder will adjust the namespace if necessary.
562 // This only happens in foreign elements.
563 name: QualName::new(None, ns!(), name),
564 value: mem::take(&mut self.current_attr_value.borrow_mut()),
565 });
566 }
567 }
568
569 fn emit_current_doctype(&self) {
570 let doctype = self.current_doctype.take();
571 self.process_token_and_continue(DoctypeToken(doctype));
572 }
573
574 fn doctype_id(&self, kind: DoctypeIdKind) -> RefMut<'_, Option<StrTendril>> {
575 let current_doctype = self.current_doctype.borrow_mut();
576 match kind {
577 Public => RefMut::map(current_doctype, |d| &mut d.public_id),
578 System => RefMut::map(current_doctype, |d| &mut d.system_id),
579 }
580 }
581
582 fn clear_doctype_id(&self, kind: DoctypeIdKind) {
583 let mut id = self.doctype_id(kind);
584 match *id {
585 Some(ref mut s) => s.clear(),
586 None => *id = Some(StrTendril::new()),
587 }
588 }
589
590 fn start_consuming_character_reference(&self) {
591 debug_assert!(
592 self.char_ref_tokenizer.borrow().is_none(),
593 "Nested character references are impossible"
594 );
595
596 let is_in_attribute = matches!(self.state.get(), states::AttributeValue(_));
597 *self.char_ref_tokenizer.borrow_mut() = Some(CharRefTokenizer::new(is_in_attribute));
598 }
599
600 fn emit_eof(&self) {
601 self.process_token_and_continue(EOFToken);
602 }
603
604 fn peek(&self, input: &BufferQueue) -> Option<char> {
605 if self.reconsume.get() {
606 Some(self.current_char.get())
607 } else {
608 input.peek()
609 }
610 }
611
612 fn discard_char(&self, input: &BufferQueue) {
613 // peek() deals in un-processed characters (no newline normalization), while get_char()
614 // does.
615 //
616 // since discard_char is supposed to be used in combination with peek(), discard_char must
617 // discard a single raw input character, not a normalized newline.
618 if self.reconsume.get() {
619 self.reconsume.set(false);
620 } else {
621 input.next();
622 }
623 }
624
625 fn emit_error(&self, error: Cow<'static, str>) {
626 self.process_token_and_continue(ParseError(error));
627 }
628}
629//§ END
630
631// Shorthand for common state machine behaviors.
632macro_rules! shorthand (
633 ( $me:ident : create_tag $kind:ident $c:expr ) => ( $me.create_tag($kind, $c) );
634 ( $me:ident : push_tag $c:expr ) => ( $me.current_tag_name.borrow_mut().push_char($c) );
635 ( $me:ident : discard_tag ) => ( $me.discard_tag() );
636 ( $me:ident : discard_char $input:expr ) => ( $me.discard_char($input) );
637 ( $me:ident : push_temp $c:expr ) => ( $me.temp_buf.borrow_mut().push_char($c) );
638 ( $me:ident : clear_temp ) => ( $me.clear_temp_buf() );
639 ( $me:ident : create_attr $c:expr ) => ( $me.create_attribute($c) );
640 ( $me:ident : push_name $c:expr ) => ( $me.current_attr_name.borrow_mut().push_char($c) );
641 ( $me:ident : push_value $c:expr ) => ( $me.current_attr_value.borrow_mut().push_char($c) );
642 ( $me:ident : append_value $c:expr ) => ( $me.current_attr_value.borrow_mut().push_tendril($c));
643 ( $me:ident : push_comment $c:expr ) => ( $me.current_comment.borrow_mut().push_char($c) );
644 ( $me:ident : append_comment $c:expr ) => ( $me.current_comment.borrow_mut().push_slice($c) );
645 ( $me:ident : emit_comment ) => ( $me.emit_current_comment() );
646 ( $me:ident : clear_comment ) => ( $me.current_comment.borrow_mut().clear() );
647 ( $me:ident : create_doctype ) => ( *$me.current_doctype.borrow_mut() = Doctype::default() );
648 ( $me:ident : push_doctype_name $c:expr ) => ( option_push(&mut $me.current_doctype.borrow_mut().name, $c) );
649 ( $me:ident : push_doctype_id $k:ident $c:expr ) => ( option_push(&mut $me.doctype_id($k), $c) );
650 ( $me:ident : clear_doctype_id $k:ident ) => ( $me.clear_doctype_id($k) );
651 ( $me:ident : force_quirks ) => ( $me.current_doctype.borrow_mut().force_quirks = true);
652 ( $me:ident : emit_doctype ) => ( $me.emit_current_doctype() );
653);
654
655// Tracing of tokenizer actions. This adds significant bloat and compile time,
656// so it's behind a cfg flag.
657#[cfg(feature = "trace_tokenizer")]
658macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ({
659 trace!(" {:?}", stringify!($($cmds)*));
660 shorthand!($me : $($cmds)*);
661}));
662
663#[cfg(not(feature = "trace_tokenizer"))]
664macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ( shorthand!($me: $($cmds)*) ) );
665
666// A little DSL for sequencing shorthand actions.
667macro_rules! go (
668 // A pattern like $($cmd:tt)* ; $($rest:tt)* causes parse ambiguity.
669 // We have to tell the parser how much lookahead we need.
670
671 ( $me:ident : $a:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a); go!($me: $($rest)*); });
672 ( $me:ident : $a:tt $b:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b); go!($me: $($rest)*); });
673 ( $me:ident : $a:tt $b:tt $c:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c); go!($me: $($rest)*); });
674 ( $me:ident : $a:tt $b:tt $c:tt $d:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c $d); go!($me: $($rest)*); });
675
676 // These can only come at the end.
677
678 ( $me:ident : to $s:expr ) => ({ $me.state.set($s); return ProcessResult::Continue; });
679 ( $me:ident : reconsume $s:expr ) => ({ $me.reconsume.set(true); go!($me: to $s); });
680 ( $me:ident : consume_char_ref ) => ({ $me.start_consuming_character_reference(); return ProcessResult::Continue;});
681
682 // We have a default next state after emitting a tag, but the sink can override.
683 ( $me:ident : emit_tag $s:ident ) => ({
684 $me.state.set(states::$s);
685 return $me.emit_current_tag();
686 });
687
688 ( $me:ident : eof ) => ({ $me.emit_eof(); return ProcessResult::Suspend; });
689
690 // If nothing else matched, it's a single command
691 ( $me:ident : $($cmd:tt)+ ) => ( sh_trace!($me: $($cmd)+) );
692
693 // or nothing.
694 ( $me:ident : ) => (());
695);
696
697// This is a macro because it can cause early return
698// from the function where it is used.
699macro_rules! get_char ( ($me:expr, $input:expr) => (
700 unwrap_or_return!($me.get_char($input), ProcessResult::Suspend)
701));
702
703macro_rules! peek ( ($me:expr, $input:expr) => (
704 unwrap_or_return!($me.peek($input), ProcessResult::Suspend)
705));
706
707macro_rules! eat ( ($me:expr, $input:expr, $pat:expr) => (
708 unwrap_or_return!($me.eat($input, $pat, u8::eq_ignore_ascii_case), ProcessResult::Suspend)
709));
710
711macro_rules! eat_exact ( ($me:expr, $input:expr, $pat:expr) => (
712 unwrap_or_return!($me.eat($input, $pat, u8::eq), ProcessResult::Suspend)
713));
714
715impl<Sink: TokenSink> Tokenizer<Sink> {
716 // Run the state machine for a while.
717 // Return true if we should be immediately re-invoked
718 // (this just simplifies control flow vs. break / continue).
719 #[allow(clippy::never_loop)]
720 fn step(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
721 if self.char_ref_tokenizer.borrow().is_some() {
722 return self.step_char_ref_tokenizer(input);
723 }
724
725 trace!("processing in state {:?}", self.state);
726 match self.state.get() {
727 // https://html.spec.whatwg.org/#data-state
728 states::Data => loop {
729 // Step 1. Consume the next input character:
730 let set = small_char_set!('\r' '\0' '&' '<' '\n');
731
732 #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
733 let set_result = if !(self.opts.exact_errors
734 || self.reconsume.get()
735 || self.ignore_lf.get())
736 && Self::is_supported_simd_feature_detected()
737 {
738 let front_buffer = input.peek_front_chunk_mut();
739 let Some(mut front_buffer) = front_buffer else {
740 return ProcessResult::Suspend;
741 };
742
743 // Special case: The fast path is not worth taking if the first character is already in the set,
744 // which is fairly common
745 let first_char = front_buffer
746 .chars()
747 .next()
748 .expect("Input buffers are never empty");
749
750 if matches!(first_char, '\r' | '\0' | '&' | '<' | '\n') {
751 drop(front_buffer);
752 self.pop_except_from(input, set)
753 } else {
754 // SAFETY:
755 // This CPU is guaranteed to support SIMD due to the is_supported_simd_feature_detected check above
756 let result = unsafe { self.data_state_simd_fast_path(&mut front_buffer) };
757
758 if front_buffer.is_empty() {
759 drop(front_buffer);
760 input.pop_front();
761 }
762
763 result
764 }
765 } else {
766 self.pop_except_from(input, set)
767 };
768
769 #[cfg(not(any(
770 target_arch = "x86",
771 target_arch = "x86_64",
772 target_arch = "aarch64"
773 )))]
774 let set_result = self.pop_except_from(input, set);
775
776 let Some(set_result) = set_result else {
777 return ProcessResult::Suspend;
778 };
779 match set_result {
780 // ↪ U+0026 AMPERSAND (&)
781 FromSet('&') => {
782 // Set the return state to the data state. Switch to the character reference state.
783 go!(self: consume_char_ref)
784 },
785 // ↪ U+003C LESS-THAN SIGN (<)
786 FromSet('<') => {
787 // Switch to the tag open state.
788 go!(self: to State::TagOpen)
789 },
790 // ↪ U+0000 NULL
791 FromSet('\0') => {
792 // This is an unexpected-null-character parse error.
793 // Emit the current input character as a character token.
794 self.bad_char_error();
795 self.emit_char('\0');
796 },
797 // ↪ Anything else
798 // Emit the current input character as a character token.
799 FromSet(character) => self.emit_char(character),
800 NotFromSet(characters) => self.emit_chars(characters),
801 }
802 },
803
804 // https://html.spec.whatwg.org/#rcdata-state
805 states::RawData(Rcdata) => loop {
806 // Consume the next input character:
807 let Some(set_result) =
808 self.pop_except_from(input, small_char_set!('\r' '\0' '&' '<' '\n'))
809 else {
810 return ProcessResult::Suspend;
811 };
812
813 match set_result {
814 // ↪ U+0026 AMPERSAND (&)
815 FromSet('&') => {
816 go!(self: consume_char_ref)
817 },
818 // ↪ U+003C LESS-THAN SIGN (<)
819 FromSet('<') => {
820 // Switch to the RCDATA less-than sign state.
821 go!(self: to State::RawLessThanSign(Rcdata))
822 },
823 // ↪ U+0000 NULL
824 FromSet('\0') => {
825 self.bad_char_error();
826 self.emit_char('\u{fffd}');
827 },
828 // ↪ Anything else
829 // Emit the current input character as a character token.
830 FromSet(character) => self.emit_char(character),
831 NotFromSet(characters) => self.emit_chars(characters),
832 }
833 },
834
835 // https://html.spec.whatwg.org/#rawtext-state
836 states::RawData(Rawtext) => loop {
837 // Consume the next input character:
838 let Some(set_result) =
839 self.pop_except_from(input, small_char_set!('\r' '\0' '<' '\n'))
840 else {
841 return ProcessResult::Suspend;
842 };
843
844 match set_result {
845 // ↪ U+003C LESS-THAN SIGN (<)
846 FromSet('<') => {
847 // Switch to the RAWTEXT less-than sign state.
848 go!(self: to State::RawLessThanSign(Rawtext));
849 },
850 // ↪ U+0000 NULL
851 FromSet('\0') => {
852 // This is an unexpected-null-character parse error.
853 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
854 self.bad_char_error();
855 self.emit_char('\u{fffd}');
856 },
857 // ↪ Anything else
858 // Emit the current input character as a character token.
859 FromSet(character) => self.emit_char(character),
860 NotFromSet(characters) => self.emit_chars(characters),
861 }
862 },
863
864 // https://html.spec.whatwg.org/#script-data-state
865 states::RawData(ScriptData) => loop {
866 // Consume the next input character:
867 let Some(set_result) =
868 self.pop_except_from(input, small_char_set!('\r' '\0' '<' '\n'))
869 else {
870 return ProcessResult::Suspend;
871 };
872
873 match set_result {
874 // ↪ U+003C LESS-THAN SIGN (<)
875 FromSet('<') => {
876 // Switch to the script data less-than sign state.
877 go!(self: to State::RawLessThanSign(ScriptData));
878 },
879 // ↪ U+0000 NULL
880 FromSet('\0') => {
881 // This is an unexpected-null-character parse error.
882 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
883 self.bad_char_error();
884 self.emit_char('\u{fffd}');
885 },
886 // ↪ Anything else
887 // Emit the current input character as a character token.
888 FromSet(character) => self.emit_char(character),
889 NotFromSet(characters) => self.emit_chars(characters),
890 }
891 },
892
893 // https://html.spec.whatwg.org/#plaintext-state
894 states::Plaintext => loop {
895 // Consume the next input character:
896 let Some(set_result) = self.pop_except_from(input, small_char_set!('\r' '\0' '\n'))
897 else {
898 return ProcessResult::Suspend;
899 };
900
901 match set_result {
902 // ↪ U+0000 NULL
903 FromSet('\0') => {
904 // This is an unexpected-null-character parse error.
905 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
906 self.bad_char_error();
907 self.emit_char('\u{fffd}');
908 },
909 // ↪ Anything else
910 // Emit the current input character as a character token.
911 FromSet(character) => self.emit_char(character),
912 NotFromSet(characters) => self.emit_chars(characters),
913 }
914 },
915
916 // https://html.spec.whatwg.org/#tag-open-state
917 states::TagOpen => loop {
918 // Consume the next input character:
919 match get_char!(self, input) {
920 // ↪ U+0021 EXCLAMATION MARK (!)
921 '!' => {
922 // Switch to the markup declaration open state.
923 go!(self: to State::MarkupDeclarationOpen)
924 },
925 // ↪ U+002F SOLIDUS (/)
926 '/' => {
927 // Switch to the end tag open state.
928 go!(self: to State::EndTagOpen)
929 },
930 // ↪ ASCII alpha
931 character if character.is_ascii_alphabetic() => {
932 // Create a new start tag token, set its tag name to the empty string.
933 // Reconsume in the tag name state.
934 // NOTE: We don't reconsume the character but instead immediately append it in lowercase
935 // to the new tag (as that is what the "tag name" state would do).
936 let character = character.to_ascii_lowercase();
937 go!(self: create_tag StartTag character; to State::TagName)
938 },
939 // ↪ U+003F QUESTION MARK (?)
940 '?' => {
941 // Set the temporary buffer to the empty string.
942 // Switch to the processing instruction open state.
943 self.bad_char_error();
944 go!(self: clear_comment; reconsume BogusComment)
945 },
946 // ↪ Anything else
947 _ => {
948 // This is an invalid-first-character-of-tag-name parse error.
949 // Emit a U+003C LESS-THAN SIGN character token.
950 // Reconsume in the data state.
951 self.bad_char_error();
952 self.emit_char('<');
953 go!(self: reconsume Data)
954 },
955 }
956 },
957
958 // https://html.spec.whatwg.org/#end-tag-open-state
959 states::EndTagOpen => loop {
960 // Consume the next input character:
961 match get_char!(self, input) {
962 // ↪ ASCII alpha
963 character if character.is_ascii_alphabetic() => {
964 // Create a new end tag token, set its tag name to the empty string.
965 // Reconsume in the tag name state.
966 // NOTE: We don't reconsume the character but instead immediately append in lowercase
967 // to the new tag (as that is what the "tag name" state would do).
968 let character = character.to_ascii_lowercase();
969 go!(self: create_tag EndTag character; to State::TagName)
970 },
971 // ↪ U+003E GREATER-THAN SIGN (>)
972 '>' => {
973 // This is a missing-end-tag-name parse error.
974 // Switch to the data state.
975 self.bad_char_error();
976 go!(self: to State::Data)
977 },
978 // ↪ Anything else
979 _ => {
980 // This is an invalid-first-character-of-tag-name parse error.
981 // Create a comment token whose data is the empty string.
982 // Reconsume in the bogus comment state.
983 self.bad_char_error();
984 go!(self: clear_comment; reconsume BogusComment)
985 },
986 }
987 },
988
989 // https://html.spec.whatwg.org/#tag-name-state
990 states::TagName => loop {
991 // Consume the next input character:
992 match get_char!(self, input) {
993 // ↪ U+0009 CHARACTER TABULATION (tab)
994 // ↪ U+000A LINE FEED (LF)
995 // ↪ U+000C FORM FEED (FF)
996 // ↪ U+0020 SPACE
997 '\t' | '\n' | '\x0C' | ' ' => {
998 // Switch to the before attribute name state.
999 go!(self: to State::BeforeAttributeName)
1000 },
1001 // ↪ U+002F SOLIDUS (/)
1002 '/' => {
1003 // Switch to the self-closing start tag state.
1004 go!(self: to State::SelfClosingStartTag)
1005 },
1006 // ↪ U+003E GREATER-THAN SIGN (>)
1007 '>' => {
1008 // Switch to the data state.
1009 // Emit the current tag token.
1010 go!(self: emit_tag Data)
1011 },
1012 // ↪ ASCII upper alpha
1013 character if character.is_ascii_uppercase() => {
1014 // Append the lowercase version of the current input character (add 0x0020 to the
1015 // character's code point) to the current tag token's tag name.
1016 go!(self: push_tag (character.to_ascii_lowercase()))
1017 },
1018 // ↪ U+0000 NULL
1019 '\0' => {
1020 self.bad_char_error();
1021 go!(self: push_tag '\u{fffd}')
1022 },
1023 // ↪ Anything else
1024 character => {
1025 // Append the current input character to the current tag token's tag name.
1026 go!(self: push_tag (character))
1027 },
1028 }
1029 },
1030
1031 // https://html.spec.whatwg.org/#script-data-escaped-less-than-sign-state
1032 states::RawLessThanSign(ScriptDataEscaped(Escaped)) => loop {
1033 // Consume the next input character:
1034 match get_char!(self, input) {
1035 // ↪ U+002F SOLIDUS (/)
1036 '/' => {
1037 go!(self: clear_temp; to State::RawEndTagOpen(ScriptDataEscaped(Escaped)))
1038 },
1039 character => match lower_ascii_letter(character) {
1040 // ↪ ASCII alpha
1041 Some(character_lowercase) => {
1042 // Set the temporary buffer to the empty string.
1043 // Emit a U+003C LESS-THAN SIGN character token.
1044 // Reconsume in the script data double escape start state.
1045 // NOTE: We don't reconsume, and instead emit the lowercased character immediately,
1046 // as that is what the "script data double escape start" state would do.
1047 go!(self: clear_temp; push_temp character_lowercase);
1048 self.emit_char('<');
1049 self.emit_char(character);
1050 go!(self: to State::ScriptDataEscapeStart(DoubleEscaped));
1051 },
1052 // ↪ Anything else
1053 None => {
1054 self.emit_char('<');
1055 go!(self: reconsume RawData(ScriptDataEscaped(Escaped)));
1056 },
1057 },
1058 }
1059 },
1060
1061 // https://html.spec.whatwg.org/#script-data-double-escaped-less-than-sign-state
1062 states::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)) => loop {
1063 // Consume the next input character:
1064 match get_char!(self, input) {
1065 // ↪ U+002F SOLIDUS (/)
1066 '/' => {
1067 // Set the temporary buffer to the empty string.
1068 // Switch to the script data double escape end state.
1069 // Emit a U+002F SOLIDUS character token.
1070 go!(self: clear_temp);
1071 self.emit_char('/');
1072 go!(self: to State::ScriptDataDoubleEscapeEnd);
1073 },
1074 // ↪ Anything else
1075 _ => {
1076 // Reconsume in the script data double escaped state.
1077 go!(self: reconsume RawData(ScriptDataEscaped(DoubleEscaped)))
1078 },
1079 }
1080 },
1081
1082 // https://html.spec.whatwg.org/#rcdata-less-than-sign-state
1083 // https://html.spec.whatwg.org/#script-data-less-than-sign-state
1084 // https://html.spec.whatwg.org/#rawtext-less-than-sign-state
1085 states::RawLessThanSign(kind) => loop {
1086 // Consume the next input character:
1087 match get_char!(self, input) {
1088 // ↪ U+002F SOLIDUS (/)
1089 '/' => {
1090 // Set the temporary buffer to the empty string.
1091 // Switch to the RCDATA end tag open state.
1092 go!(self: clear_temp; to State::RawEndTagOpen(kind))
1093 },
1094 // ↪ U+0021 EXCLAMATION MARK (!)
1095 '!' if kind == ScriptData => {
1096 // Switch to the script data escape start state.
1097 // Emit a U+003C LESS-THAN SIGN character token and a U+0021 EXCLAMATION MARK character token.
1098 self.emit_char('<');
1099 self.emit_char('!');
1100 go!(self: to State::ScriptDataEscapeStart(Escaped));
1101 },
1102 // ↪ Anything else
1103 _ => {
1104 // Emit a U+003C LESS-THAN SIGN character token.
1105 // Reconsume in the RCDATA/script data/RAWTEXT state.
1106 self.emit_char('<');
1107 go!(self: reconsume RawData(kind));
1108 },
1109 }
1110 },
1111
1112 // https://html.spec.whatwg.org/#rcdata-end-tag-open-state
1113 // https://html.spec.whatwg.org/#rawtext-end-tag-open-state
1114 // https://html.spec.whatwg.org/#script-data-end-tag-open-state
1115 // https://html.spec.whatwg.org/#script-data-escaped-end-tag-open-state
1116 states::RawEndTagOpen(kind) => loop {
1117 // Consume the next input character:
1118 let character = get_char!(self, input);
1119
1120 // ↪ ASCII alpha
1121 if character.is_ascii_alphabetic() {
1122 // Create a new end tag token, set its tag name to the empty string.
1123 // Reconsume in the RCDATA end tag name state.
1124 // NOTE: We don't reconsume the character but instead immediately append its lowercase version
1125 // to the end tag (as that is what the new state would do).
1126 let character_lowercase = character.to_ascii_lowercase();
1127 go!(self: create_tag EndTag character_lowercase; push_temp character; to State::RawEndTagName(kind))
1128 }
1129 // ↪ Anything else
1130 else {
1131 // Emit a U+003C LESS-THAN SIGN character token and a U+002F SOLIDUS character token.
1132 // Reconsume in the RCDATA/RAWTEXT/script data/script data escaped state.
1133 self.emit_char('<');
1134 self.emit_char('/');
1135 go!(self: reconsume RawData(kind));
1136 }
1137 },
1138
1139 // https://html.spec.whatwg.org/#rcdata-end-tag-name-state
1140 // https://html.spec.whatwg.org/#rawtext-end-tag-name-state
1141 // https://html.spec.whatwg.org/#script-data-end-tag-name-state
1142 // https://html.spec.whatwg.org/#script-data-escaped-end-tag-name-state
1143 states::RawEndTagName(kind) => loop {
1144 let character = get_char!(self, input);
1145
1146 // NOTE: The first three match arms in the specification are treated as "anything else" if the current
1147 // end tag token is NOT an appropriate end tag, so we move them into their own match block.
1148 if self.have_appropriate_end_tag() {
1149 match character {
1150 // ↪ U+0009 CHARACTER TABULATION (tab)
1151 // ↪ U+000A LINE FEED (LF)
1152 // ↪ U+000C FORM FEED (FF)
1153 // ↪ U+0020 SPACE
1154 '\t' | '\n' | '\x0C' | ' ' => {
1155 // Switch to the before attribute name state
1156 go!(self: clear_temp; to State::BeforeAttributeName)
1157 },
1158 // ↪ U+002F SOLIDUS (/)
1159 '/' => {
1160 // Switch to the self-closing start tag state
1161 go!(self: clear_temp; to State::SelfClosingStartTag)
1162 },
1163 // ↪ U+003E GREATER-THAN SIGN (>)
1164 '>' => {
1165 // Switch to the data state and emit the current tag token
1166 go!(self: clear_temp; emit_tag Data)
1167 },
1168 _ => {},
1169 }
1170 }
1171
1172 match lower_ascii_letter(character) {
1173 // ↪ ASCII upper alpha
1174 // NOTE: This is the same as for lower alpha, but the character is lowercased first.
1175 // This is handled by lower_ascii_letter.
1176 // ↪ ASCII lower alpha
1177 Some(character_lowercase) => {
1178 // Append the current input character to the current tag token's tag name.
1179 // Append the current input character to the temporary buffer.
1180 go!(self: push_tag character_lowercase; push_temp character)
1181 },
1182 // ↪ Anything else
1183 None => {
1184 // Emit a U+003C LESS-THAN SIGN character token, a U+002F SOLIDUS character token,
1185 // and a character token for each of the characters in the temporary buffer
1186 // (in the order they were added to the buffer).
1187 // Reconsume in the RCDATA/RAWTEXT/script data/script data escaped state.
1188 go!(self: discard_tag);
1189 self.emit_char('<');
1190 self.emit_char('/');
1191 self.emit_temp_buf();
1192 go!(self: reconsume RawData(kind));
1193 },
1194 }
1195 },
1196
1197 // https://html.spec.whatwg.org/#script-data-double-escape-start-state
1198 states::ScriptDataEscapeStart(DoubleEscaped) => loop {
1199 // Consume the next input character:
1200 match get_char!(self, input) {
1201 // ↪ U+0009 CHARACTER TABULATION (tab)
1202 // ↪ U+000A LINE FEED (LF)
1203 // ↪ U+000C FORM FEED (FF)
1204 // ↪ U+0020 SPACE
1205 // ↪ U+002F SOLIDUS (/)
1206 // ↪ U+003E GREATER-THAN SIGN (>)
1207 character @ ('\t' | '\n' | '\x0C' | ' ' | '/' | '>') => {
1208 // If the temporary buffer is "script", then switch to the script data double escaped state.
1209 // Otherwise, switch to the script data escaped state.
1210 // Emit the current input character as a character token.
1211 let escaped_kind = if &**self.temp_buf.borrow() == "script" {
1212 DoubleEscaped
1213 } else {
1214 Escaped
1215 };
1216 self.emit_char(character);
1217 go!(self: to State::RawData(ScriptDataEscaped(escaped_kind)));
1218 },
1219 // ↪ ASCII upper alpha
1220 // NOTE: This is the same as the "ASCII lower alpha" branch, except we lowercase the character first
1221 // ↪ ASCII lower alpha
1222 character => match lower_ascii_letter(character) {
1223 Some(character_lowercase) => {
1224 // Append the current input character to the temporary buffer.
1225 // Emit the current input character as a character token.
1226 go!(self: push_temp character_lowercase);
1227 self.emit_char(character);
1228 },
1229 // ↪ Anything else
1230 None => {
1231 // Reconsume in the script data escaped state.
1232 go!(self: reconsume RawData(ScriptDataEscaped(Escaped)))
1233 },
1234 },
1235 }
1236 },
1237
1238 // https://html.spec.whatwg.org/#script-data-double-escaped-state
1239 states::RawData(ScriptDataEscaped(DoubleEscaped)) => loop {
1240 // Consume the next input character:
1241 let Some(set_result) =
1242 self.pop_except_from(input, small_char_set!('\r' '\0' '-' '<' '\n'))
1243 else {
1244 return ProcessResult::Suspend;
1245 };
1246
1247 match set_result {
1248 // ↪ U+002D HYPHEN-MINUS (-)
1249 FromSet('-') => {
1250 // Switch to the script data double escaped dash state.
1251 // Emit a U+002D HYPHEN-MINUS character token.
1252 self.emit_char('-');
1253 go!(self: to State::ScriptDataEscapedDash(DoubleEscaped));
1254 },
1255 // ↪ U+003C LESS-THAN SIGN (<)
1256 FromSet('<') => {
1257 // Switch to the script data double escaped less-than sign state.
1258 // Emit a U+003C LESS-THAN SIGN character token.
1259 self.emit_char('<');
1260 go!(self: to State::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)))
1261 },
1262 // ↪ U+0000 NULL
1263 FromSet('\0') => {
1264 // This is an unexpected-null-character parse error.
1265 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1266 self.bad_char_error();
1267 self.emit_char('\u{fffd}');
1268 },
1269 // ↪ Anything else
1270 // Emit the current input character as a character token.
1271 FromSet(character) => self.emit_char(character),
1272 NotFromSet(characters) => self.emit_chars(characters),
1273 }
1274 },
1275
1276 // https://html.spec.whatwg.org/#script-data-escape-start-state
1277 states::ScriptDataEscapeStart(Escaped) => loop {
1278 // Consume the next input character:
1279 match get_char!(self, input) {
1280 // ↪ U+002D HYPHEN-MINUS (-)
1281 '-' => {
1282 // Switch to the script data escape start dash state.
1283 // Emit a U+002D HYPHEN-MINUS character token.
1284 self.emit_char('-');
1285 go!(self: to State::ScriptDataEscapeStartDash);
1286 },
1287 // ↪ Anything else
1288 _ => {
1289 // Reconsume in the script data state.
1290 go!(self: reconsume RawData(ScriptData))
1291 },
1292 }
1293 },
1294
1295 // https://html.spec.whatwg.org/#script-data-escape-start-dash-state
1296 states::ScriptDataEscapeStartDash => loop {
1297 // Consume the next input character:
1298 match get_char!(self, input) {
1299 // ↪ U+002D HYPHEN-MINUS (-)
1300 '-' => {
1301 // Switch to the script data escaped dash dash state.
1302 // Emit a U+002D HYPHEN-MINUS character token.
1303 self.emit_char('-');
1304 go!(self: to State::ScriptDataEscapedDashDash(Escaped));
1305 },
1306 // ↪ Anything else
1307 _ => {
1308 // Reconsume in the script data state.
1309 go!(self: reconsume RawData(ScriptData))
1310 },
1311 }
1312 },
1313
1314 // https://html.spec.whatwg.org/#script-data-escaped-state
1315 states::RawData(ScriptDataEscaped(Escaped)) => loop {
1316 // Consume the next input character:
1317 let Some(set_result) =
1318 self.pop_except_from(input, small_char_set!('\r' '\0' '-' '<' '\n'))
1319 else {
1320 return ProcessResult::Suspend;
1321 };
1322
1323 match set_result {
1324 // ↪ U+002D HYPHEN-MINUS (-)
1325 FromSet('-') => {
1326 // Switch to the script data escaped dash state.
1327 // Emit a U+002D HYPHEN-MINUS character token.
1328 self.emit_char('-');
1329 go!(self: to State::ScriptDataEscapedDash(Escaped));
1330 },
1331 // ↪ U+003C LESS-THAN SIGN (<)
1332 FromSet('<') => {
1333 // Switch to the script data escaped less-than sign state.
1334 go!(self: to State::RawLessThanSign(ScriptDataEscaped(Escaped)))
1335 },
1336 // ↪ U+0000 NULL
1337 FromSet('\0') => {
1338 // This is an unexpected-null-character parse error.
1339 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1340 self.bad_char_error();
1341 self.emit_char('\u{fffd}');
1342 },
1343 // ↪ Anything else
1344 // Emit the current input character as a character token.
1345 FromSet(character) => self.emit_char(character),
1346 NotFromSet(characters) => self.emit_chars(characters),
1347 }
1348 },
1349
1350 // https://html.spec.whatwg.org/#script-data-escaped-dash-state
1351 // https://html.spec.whatwg.org/#script-data-double-escaped-dash-state
1352 states::ScriptDataEscapedDash(kind) => loop {
1353 // Consume the next input character:
1354 match get_char!(self, input) {
1355 // ↪ U+002D HYPHEN-MINUS (-)
1356 '-' => {
1357 // Switch to the script data escaped dash dash/script data double escaped dash dash state.
1358 // Emit a U+002D HYPHEN-MINUS character token.
1359 self.emit_char('-');
1360 go!(self: to State::ScriptDataEscapedDashDash(kind));
1361 },
1362 // ↪ U+003C LESS-THAN SIGN (<)
1363 '<' => {
1364 // Switch to the script data escaped less-than sign/script data double escaped less-than sign state.
1365 if kind == DoubleEscaped {
1366 self.emit_char('<');
1367 }
1368 go!(self: to State::RawLessThanSign(ScriptDataEscaped(kind)));
1369 },
1370 // ↪ U+0000 NULL
1371 '\0' => {
1372 // This is an unexpected-null-character parse error.
1373 // Switch to the script data escaped/script data double escaped state.
1374 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1375 self.bad_char_error();
1376 self.emit_char('\u{fffd}');
1377 go!(self: to State::RawData(ScriptDataEscaped(kind)));
1378 },
1379 // ↪ Anything else
1380 c => {
1381 // Switch to the script data escaped/script data double escaped state.
1382 // Emit the current input character as a character token.
1383 self.emit_char(c);
1384 go!(self: to State::RawData(ScriptDataEscaped(kind)));
1385 },
1386 }
1387 },
1388
1389 // https://html.spec.whatwg.org/#script-data-escaped-dash-dash-state
1390 // https://html.spec.whatwg.org/#script-data-double-escaped-dash-dash-state
1391 states::ScriptDataEscapedDashDash(kind) => loop {
1392 // Consume the next input character:
1393 match get_char!(self, input) {
1394 // ↪ U+002D HYPHEN-MINUS (-)
1395 '-' => {
1396 // Emit a U+002D HYPHEN-MINUS character token.
1397 self.emit_char('-');
1398 },
1399 // ↪ U+003C LESS-THAN SIGN (<)
1400 '<' => {
1401 // Switch to the script data escaped less-than sign/script data double escaped less-than sign state.
1402 if kind == DoubleEscaped {
1403 self.emit_char('<');
1404 }
1405 go!(self: to State::RawLessThanSign(ScriptDataEscaped(kind)));
1406 },
1407 // ↪ U+003E GREATER-THAN SIGN (>)
1408 '>' => {
1409 // Switch to the script data state.
1410 // Emit a U+003E GREATER-THAN SIGN character token.
1411 self.emit_char('>');
1412 go!(self: to State::RawData(ScriptData));
1413 },
1414 // ↪ U+0000 NULL
1415 '\0' => {
1416 // This is an unexpected-null-character parse error.
1417 // Switch to the script data escaped/script data double escaped state.
1418 // Emit a U+FFFD REPLACEMENT CHARACTER character token.
1419 self.bad_char_error();
1420 self.emit_char('\u{fffd}');
1421 go!(self: to State::RawData(ScriptDataEscaped(kind)))
1422 },
1423 // ↪ Anything else
1424 character => {
1425 // Switch to the script data escaped/script data double escaped state.
1426 // Emit the current input character as a character token.
1427 self.emit_char(character);
1428 go!(self: to State::RawData(ScriptDataEscaped(kind)));
1429 },
1430 }
1431 },
1432
1433 // https://html.spec.whatwg.org/#script-data-double-escape-end-state
1434 states::ScriptDataDoubleEscapeEnd => loop {
1435 // Consume the next input character:
1436 match get_char!(self, input) {
1437 // ↪ U+0009 CHARACTER TABULATION (tab)
1438 // ↪ U+000A LINE FEED (LF)
1439 // ↪ U+000C FORM FEED (FF)
1440 // ↪ U+0020 SPACE
1441 // ↪ U+002F SOLIDUS (/)
1442 // ↪ U+003E GREATER-THAN SIGN (>)
1443 character @ ('\t' | '\n' | '\x0C' | ' ' | '/' | '>') => {
1444 // If the temporary buffer is "script", then switch to the script data escaped state.
1445 // Otherwise, switch to the script data double escaped state.
1446 // Emit the current input character as a character token.
1447 let escaped_kind = if &**self.temp_buf.borrow() == "script" {
1448 Escaped
1449 } else {
1450 DoubleEscaped
1451 };
1452 self.emit_char(character);
1453 go!(self: to State::RawData(ScriptDataEscaped(escaped_kind)));
1454 },
1455
1456 character => match lower_ascii_letter(character) {
1457 // ↪ ASCII upper alpha
1458 // NOTE: This is the same as "ASCII lower alpha", except we lowercase the character first.
1459 // That is handled by lower_ascii_letter.
1460 // ↪ ASCII lower alpha
1461 Some(character_lowercase) => {
1462 // Append the current input character to the temporary buffer.
1463 // Emit the current input character as a character token.
1464 go!(self: push_temp character_lowercase);
1465 self.emit_char(character);
1466 },
1467 // ↪ Anything else
1468 None => {
1469 // Reconsume in the script data double escaped state.
1470 go!(self: reconsume RawData(ScriptDataEscaped(DoubleEscaped)))
1471 },
1472 },
1473 }
1474 },
1475
1476 // https://html.spec.whatwg.org/#before-attribute-name-state
1477 states::BeforeAttributeName => loop {
1478 match get_char!(self, input) {
1479 // ↪ U+0009 CHARACTER TABULATION (tab)
1480 // ↪ U+000A LINE FEED (LF)
1481 // ↪ U+000C FORM FEED (FF)
1482 // ↪ U+0020 SPACE
1483 '\t' | '\n' | '\x0C' | ' ' => {
1484 // Ignore the character.
1485 },
1486 // U+002F SOLIDUS (/)
1487 '/' => {
1488 // Reconsume in the after attribute name state.
1489 // NOTE: Instead we move to the self closing start tag,
1490 // as that is what the "after attribute name" state would do.
1491 go!(self: to State::SelfClosingStartTag)
1492 },
1493 // U+003E GREATER-THAN SIGN (>)
1494 '>' => {
1495 // Reconsume in the after attribute name state.
1496 // NOTE: Instead we emit the current tag and move to the data state,
1497 // as that is what the "after attribute name" state would do.
1498 go!(self: emit_tag Data)
1499 },
1500 // NOTE: In the "anything else" case we should reconsume in the attribute name state,
1501 // but instead of reconsuming we inline what that state *would* do here.
1502 '\0' => {
1503 self.bad_char_error();
1504 go!(self: create_attr '\u{fffd}'; to State::AttributeName)
1505 },
1506 character => match lower_ascii_letter(character) {
1507 Some(character) => {
1508 go!(self: create_attr character; to State::AttributeName)
1509 },
1510 None => {
1511 if matches!(character, '"' | '\'' | '<' | '=') {
1512 self.bad_char_error();
1513 }
1514
1515 go!(self: create_attr character; to State::AttributeName);
1516 },
1517 },
1518 }
1519 },
1520
1521 // https://html.spec.whatwg.org/#attribute-name-state
1522 states::AttributeName => loop {
1523 // Consume the next input character:
1524 match get_char!(self, input) {
1525 // ↪ U+0009 CHARACTER TABULATION (tab)
1526 // ↪ U+000A LINE FEED (LF)
1527 // ↪ U+000C FORM FEED (FF)
1528 // ↪ U+0020 SPACE
1529 '\t' | '\n' | '\x0C' | ' ' => {
1530 // Reconsume in the after attribute name state.
1531 // NOTE: Instead we move to the after attribute name state and ignore
1532 // the character, as that state would ignore it as well.
1533 go!(self: to State::AfterAttributeName)
1534 },
1535 // ↪ U+002F SOLIDUS (/)
1536 '/' => {
1537 // Reconsume in the after attribute name state.
1538 // NOTE: Instead we move to the self closing start tag state, as that is
1539 // what the after attribute name state would do when it encounters a '/'.
1540 go!(self: to State::SelfClosingStartTag)
1541 },
1542 // ↪ U+003E GREATER-THAN SIGN (>)
1543 '>' => {
1544 // Reconsume in the after attribute name state.
1545 // NOTE: Instead we move to the data state, as that is
1546 // what the after attribute name state would do when it encounters a '>'.
1547 go!(self: emit_tag Data)
1548 },
1549 // ↪ U+003D EQUALS SIGN (=)
1550 '=' => {
1551 // Switch to the before attribute value state.
1552 go!(self: to State::BeforeAttributeValue)
1553 },
1554 // ↪ U+0000 NULL
1555 '\0' => {
1556 // This is an unexpected-null-character parse error.
1557 // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's name.
1558 self.bad_char_error();
1559 go!(self: push_name '\u{fffd}')
1560 },
1561 character => match lower_ascii_letter(character) {
1562 // ↪ ASCII upper alpha
1563 Some(character_lowercase) => {
1564 // Append the lowercase version of the current input character
1565 // (add 0x0020 to the character's code point) to the current attribute's name.
1566 go!(self: push_name character_lowercase)
1567 },
1568 None => {
1569 // ↪ U+0022 QUOTATION MARK (")
1570 // ↪ U+0027 APOSTROPHE (')
1571 // ↪ U+003C LESS-THAN SIGN (<)
1572 if matches!(character, '"' | '\'' | '<') {
1573 // This is an unexpected-character-in-attribute-name parse error.
1574 // Treat it as per the "anything else" entry below.
1575 self.bad_char_error();
1576 }
1577 // ↪ Anything else
1578 // Append the current input character to the current attribute's name.
1579 go!(self: push_name character);
1580 },
1581 },
1582 }
1583 },
1584
1585 // https://html.spec.whatwg.org/#after-attribute-name-state
1586 states::AfterAttributeName => loop {
1587 // Consume the next input character:
1588 match get_char!(self, input) {
1589 // ↪ U+0009 CHARACTER TABULATION (tab)
1590 // ↪ U+000A LINE FEED (LF)
1591 // ↪ U+000C FORM FEED (FF)
1592 // ↪ U+0020 SPACE
1593 '\t' | '\n' | '\x0C' | ' ' => {
1594 // Ignore the character.
1595 },
1596 // ↪ U+002F SOLIDUS (/)
1597 '/' => {
1598 // Switch to the self-closing start tag state.
1599 go!(self: to State::SelfClosingStartTag)
1600 },
1601 // ↪ U+003D EQUALS SIGN (=)
1602 '=' => {
1603 // Switch to the before attribute value state.
1604 go!(self: to State::BeforeAttributeValue)
1605 },
1606 // ↪ U+003E GREATER-THAN SIGN (>)
1607 '>' => {
1608 // Switch to the data state. Emit the current tag token.
1609 go!(self: emit_tag Data)
1610 },
1611 // NOTE: The "anything else" match arm in the specification reconsumes
1612 // the input in the attribute name state. Instead of reconsuming we inline
1613 // what the attribute name state *would* do, and the move to it.
1614 '\0' => {
1615 self.bad_char_error();
1616 go!(self: create_attr '\u{fffd}'; to State::AttributeName)
1617 },
1618 character => match lower_ascii_letter(character) {
1619 Some(character_lowercase) => {
1620 go!(self: create_attr character_lowercase; to State::AttributeName)
1621 },
1622 None => {
1623 if matches!(character, '"' | '\'' | '<') {
1624 self.bad_char_error();
1625 }
1626
1627 go!(self: create_attr character; to State::AttributeName);
1628 },
1629 },
1630 }
1631 },
1632
1633 // https://html.spec.whatwg.org/#before-attribute-value-state
1634 // Use peek so we can handle the first attr character along with the rest,
1635 // hopefully in the same zero-copy buffer.
1636 states::BeforeAttributeValue => loop {
1637 // Consume the next input character:
1638 match peek!(self, input) {
1639 // ↪ U+0009 CHARACTER TABULATION (tab)
1640 // ↪ U+000A LINE FEED (LF)
1641 // ↪ U+000C FORM FEED (FF)
1642 // ↪ U+0020 SPACE
1643 '\t' | '\n' | '\r' | '\x0C' | ' ' => {
1644 // Ignore the character.
1645 go!(self: discard_char input)
1646 },
1647 // ↪ U+0022 QUOTATION MARK (")
1648 '"' => {
1649 // Switch to the attribute value (double-quoted) state.
1650 go!(self: discard_char input; to State::AttributeValue(DoubleQuoted))
1651 },
1652 // ↪ U+0027 APOSTROPHE (')
1653 '\'' => {
1654 // Switch to the attribute value (single-quoted) state.
1655 go!(self: discard_char input; to State::AttributeValue(SingleQuoted))
1656 },
1657 // ↪ U+003E GREATER-THAN SIGN (>)
1658 '>' => {
1659 // This is a missing-attribute-value parse error.
1660 // Switch to the data state.
1661 // Emit the current tag token.
1662 go!(self: discard_char input);
1663 self.bad_char_error();
1664 go!(self: emit_tag Data)
1665 },
1666 // ↪ Anything else
1667 _ => {
1668 // Reconsume in the attribute value (unquoted) state.
1669 go!(self: to State::AttributeValue(Unquoted))
1670 },
1671 }
1672 },
1673
1674 // https://html.spec.whatwg.org/#attribute-value-(double-quoted)-state
1675 states::AttributeValue(DoubleQuoted) => loop {
1676 // Consume the next input character:
1677 let Some(set_result) =
1678 self.pop_except_from(input, small_char_set!('\r' '"' '&' '\0' '\n'))
1679 else {
1680 return ProcessResult::Suspend;
1681 };
1682
1683 match set_result {
1684 // ↪ U+0022 QUOTATION MARK (")
1685 FromSet('"') => {
1686 // Switch to the after attribute value (quoted) state.
1687 go!(self: to State::AfterAttributeValueQuoted)
1688 },
1689 // ↪ U+0026 AMPERSAND (&)
1690 FromSet('&') => {
1691 // Set the return state to the attribute value (double-quoted) state.
1692 // Switch to the character reference state.
1693 go!(self: consume_char_ref)
1694 },
1695 // ↪ U+0000 NULL
1696 FromSet('\0') => {
1697 // This is an unexpected-null-character parse error.
1698 // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's value.
1699 self.bad_char_error();
1700 go!(self: push_value '\u{fffd}')
1701 },
1702 // ↪ Anything else
1703 // Append the current input character to the current attribute's value.
1704 FromSet(character) => go!(self: push_value character),
1705 NotFromSet(ref characters) => go!(self: append_value characters),
1706 }
1707 },
1708
1709 // https://html.spec.whatwg.org/#attribute-value-(single-quoted)-state
1710 states::AttributeValue(SingleQuoted) => loop {
1711 // Consume the next input character:
1712 let Some(set_result) =
1713 self.pop_except_from(input, small_char_set!('\r' '\'' '&' '\0' '\n'))
1714 else {
1715 return ProcessResult::Suspend;
1716 };
1717
1718 match set_result {
1719 // ↪ U+0027 APOSTROPHE (')
1720 FromSet('\'') => {
1721 // Switch to the after attribute value (quoted) state.
1722 go!(self: to State::AfterAttributeValueQuoted)
1723 },
1724 // ↪ U+0026 AMPERSAND (&)
1725 FromSet('&') => {
1726 // Set the return state to the attribute value (single-quoted) state.
1727 // Switch to the character reference state.
1728 go!(self: consume_char_ref)
1729 },
1730 // ↪ U+0000 NULL
1731 FromSet('\0') => {
1732 // This is an unexpected-null-character parse error.
1733 // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's value.
1734 self.bad_char_error();
1735 go!(self: push_value '\u{fffd}')
1736 },
1737 // ↪ Anything else
1738 // Append the current input character to the current attribute's value.
1739 FromSet(character) => go!(self: push_value character),
1740 NotFromSet(ref characters) => go!(self: append_value characters),
1741 }
1742 },
1743
1744 // https://html.spec.whatwg.org/#attribute-value-(unquoted)-state
1745 states::AttributeValue(Unquoted) => loop {
1746 // Consume the next input character:
1747 let Some(set_result) = self.pop_except_from(
1748 input,
1749 small_char_set!('\r' '\t' '\n' '\x0C' ' ' '&' '>' '\0'),
1750 ) else {
1751 return ProcessResult::Suspend;
1752 };
1753
1754 match set_result {
1755 // ↪ U+0009 CHARACTER TABULATION (tab)
1756 // ↪ U+000A LINE FEED (LF)
1757 // ↪ U+000C FORM FEED (FF)
1758 // ↪ U+0020 SPACE
1759 FromSet('\t') | FromSet('\n') | FromSet('\x0C') | FromSet(' ') => {
1760 // Switch to the before attribute name state.
1761 go!(self: to State::BeforeAttributeName)
1762 },
1763 // ↪ U+0026 AMPERSAND (&)
1764 FromSet('&') => {
1765 // Set the return state to the attribute value (unquoted) state.
1766 // Switch to the character reference state.
1767 go!(self: consume_char_ref)
1768 },
1769 // ↪ U+003E GREATER-THAN SIGN (>)
1770 FromSet('>') => {
1771 // Switch to the data state.
1772 // Emit the current tag token.
1773 go!(self: emit_tag Data)
1774 },
1775 // ↪ U+0000 NULL
1776 FromSet('\0') => {
1777 // This is an unexpected-null-character parse error.
1778 // Append a U+FFFD REPLACEMENT CHARACTER character to the current attribute's value.
1779 self.bad_char_error();
1780 go!(self: push_value '\u{fffd}')
1781 },
1782 FromSet(c) => {
1783 // ↪ U+0022 QUOTATION MARK (")
1784 // ↪ U+0027 APOSTROPHE (')
1785 // ↪ U+003C LESS-THAN SIGN (<)
1786 // ↪ U+003D EQUALS SIGN (=)
1787 // ↪ U+0060 GRAVE ACCENT (`)
1788 if matches!(c, '"' | '\'' | '<' | '=' | '`') {
1789 // This is an unexpected-character-in-unquoted-attribute-value parse error.
1790 // Treat it as per the "anything else" entry below.
1791 self.bad_char_error();
1792 }
1793 // ↪ Anything else
1794 // Append the current input character to the current attribute's value.
1795 go!(self: push_value c);
1796 },
1797 // ↪ Anything else
1798 NotFromSet(ref characters) => {
1799 // Append the current input character to the current attribute's value.
1800 go!(self: append_value characters)
1801 },
1802 }
1803 },
1804
1805 // https://html.spec.whatwg.org/#after-attribute-value-(quoted)-state
1806 states::AfterAttributeValueQuoted => loop {
1807 // Consume the next input character:
1808 match get_char!(self, input) {
1809 // ↪ U+0009 CHARACTER TABULATION (tab)
1810 // ↪ U+000A LINE FEED (LF)
1811 // ↪ U+000C FORM FEED (FF)
1812 // ↪ U+0020 SPACE
1813 '\t' | '\n' | '\x0C' | ' ' => {
1814 // Switch to the before attribute name state.
1815 go!(self: to State::BeforeAttributeName)
1816 },
1817 // ↪ U+002F SOLIDUS (/)
1818 '/' => {
1819 // Switch to the self-closing start tag state.
1820 go!(self: to State::SelfClosingStartTag)
1821 },
1822 // ↪ U+003E GREATER-THAN SIGN (>)
1823 '>' => {
1824 // Switch to the data state. Emit the current tag token.
1825 go!(self: emit_tag Data)
1826 },
1827 // ↪ Anything else
1828 _ => {
1829 // This is a missing-whitespace-between-attributes parse error.
1830 // Reconsume in the before attribute name state.
1831 self.bad_char_error();
1832 go!(self: reconsume BeforeAttributeName)
1833 },
1834 }
1835 },
1836
1837 // https://html.spec.whatwg.org/#self-closing-start-tag-state
1838 states::SelfClosingStartTag => loop {
1839 // Consume the next input character:
1840 match get_char!(self, input) {
1841 // ↪ U+003E GREATER-THAN SIGN (>)
1842 '>' => {
1843 // Set the self-closing flag of the current tag token.
1844 // Switch to the data state. Emit the current tag token.
1845 self.current_tag_self_closing.set(true);
1846 go!(self: emit_tag Data);
1847 },
1848 // ↪ Anything else
1849 _ => {
1850 // This is an unexpected-solidus-in-tag parse error.
1851 // Reconsume in the before attribute name state.
1852 self.bad_char_error();
1853 go!(self: reconsume BeforeAttributeName)
1854 },
1855 }
1856 },
1857
1858 //§ bogus-comment-state
1859 states::BogusComment => loop {
1860 // Consume the next input character:
1861 match get_char!(self, input) {
1862 // ↪ U+003E GREATER-THAN SIGN (>)
1863 '>' => {
1864 // Switch to the data state. Emit the current comment token.
1865 go!(self: emit_comment; to State::Data)
1866 },
1867 // ↪ U+0000 NULL
1868 '\0' => {
1869 // This is an unexpected-null-character parse error.
1870 // Append a U+FFFD REPLACEMENT CHARACTER character to the comment token's data.
1871 self.bad_char_error();
1872 go!(self: push_comment '\u{fffd}')
1873 },
1874 // ↪ Anything else
1875 character => {
1876 // Append the current input character to the comment token's data.
1877 go!(self: push_comment character)
1878 },
1879 }
1880 },
1881
1882 // https://html.spec.whatwg.org/#markup-declaration-open-state
1883 states::MarkupDeclarationOpen => loop {
1884 // If the next few characters are:
1885 // ↪ Two U+002D HYPHEN-MINUS characters (-)
1886 if eat_exact!(self, input, "--") {
1887 go!(self: clear_comment; to State::CommentStart);
1888 }
1889 // ↪ ASCII case-insensitive match for "DOCTYPE"
1890 else if eat!(self, input, "doctype") {
1891 go!(self: to State::Doctype);
1892 } else {
1893 // ↪ "[CDATA["
1894 if self
1895 .sink
1896 .adjusted_current_node_present_but_not_in_html_namespace()
1897 && eat_exact!(self, input, "[CDATA[")
1898 {
1899 // Consume those characters.
1900 // If there is an adjusted current node and it is not an element in the HTML namespace,
1901 // then switch to the CDATA section state. Otherwise, this is a cdata-in-html-content parse error.
1902 // Create a comment token whose data is "[CDATA[". Switch to the bogus comment state.
1903 // FIXME: Create that comment token.
1904 go!(self: clear_temp; to State::CdataSection);
1905 }
1906
1907 // ↪ Anything else
1908 // This is an incorrectly-opened-comment parse error.
1909 // Create a comment token whose data is the empty string.
1910 // Switch to the bogus comment state (don't consume anything in the current state).
1911 // FIXME: Create that comment token.
1912 self.bad_char_error();
1913 go!(self: clear_comment; to State::BogusComment);
1914 }
1915 },
1916
1917 // https://html.spec.whatwg.org/#comment-start-state
1918 states::CommentStart => loop {
1919 // Consume the next input character:
1920 match get_char!(self, input) {
1921 // ↪ U+002D HYPHEN-MINUS (-)
1922 '-' => {
1923 // Switch to the comment start dash state.
1924 go!(self: to State::CommentStartDash)
1925 },
1926 // ↪ U+003E GREATER-THAN SIGN (>)
1927 '>' => {
1928 // This is an abrupt-closing-of-empty-comment parse error.
1929 // Switch to the data state.
1930 // Emit the current comment token.
1931 self.bad_char_error();
1932 go!(self: emit_comment; to State::Data)
1933 },
1934 // NOTE: The "anything else" case in the specification reconsumes the character in the
1935 // comment state, instead we inline what the comment state *would* do if it encountered
1936 // that character.
1937 '\0' => {
1938 self.bad_char_error();
1939 go!(self: push_comment '\u{fffd}'; to State::Comment)
1940 },
1941 character => go!(self: push_comment character; to State::Comment),
1942 }
1943 },
1944
1945 // https://html.spec.whatwg.org/#comment-start-dash-state
1946 states::CommentStartDash => loop {
1947 // Consume the next input character:
1948 match get_char!(self, input) {
1949 // ↪ U+002D HYPHEN-MINUS (-)
1950 '-' => {
1951 // Switch to the comment end state.
1952 go!(self: to State::CommentEnd)
1953 },
1954 // ↪ U+003E GREATER-THAN SIGN (>)
1955 '>' => {
1956 // This is an abrupt-closing-of-empty-comment parse error.
1957 // Switch to the data state.
1958 // Emit the current comment token.
1959 self.bad_char_error();
1960 go!(self: emit_comment; to State::Data)
1961 },
1962 // NOTE: The "anything else" case in the specification reconsumes the character in the
1963 // comment state, instead we inline what the comment state *would* do if it encountered
1964 // that character.
1965 '\0' => {
1966 self.bad_char_error();
1967 go!(self: append_comment "-\u{fffd}"; to State::Comment)
1968 },
1969 character => {
1970 go!(self: push_comment '-'; push_comment character; to State::Comment)
1971 },
1972 }
1973 },
1974
1975 // https://html.spec.whatwg.org/#comment-state
1976 states::Comment => loop {
1977 // Consume the next input character:
1978 match get_char!(self, input) {
1979 // ↪ U+003C LESS-THAN SIGN (<)
1980 c @ '<' => {
1981 // Append the current input character to the comment token's data.
1982 // Switch to the comment less-than sign state.
1983 go!(self: push_comment c; to State::CommentLessThanSign)
1984 },
1985 // ↪ U+002D HYPHEN-MINUS (-)
1986 '-' => {
1987 // Switch to the comment end dash state.
1988 go!(self: to State::CommentEndDash)
1989 },
1990 // ↪ U+0000 NULL
1991 '\0' => {
1992 // This is an unexpected-null-character parse error.
1993 // Append a U+FFFD REPLACEMENT CHARACTER character to the comment token's data.
1994 self.bad_char_error();
1995 go!(self: push_comment '\u{fffd}')
1996 },
1997 // ↪ Anything else
1998 character => {
1999 // Append the current input character to the comment token's data.
2000 go!(self: push_comment character)
2001 },
2002 }
2003 },
2004
2005 // https://html.spec.whatwg.org/#comment-less-than-sign-state
2006 states::CommentLessThanSign => loop {
2007 // Consume the next input character:
2008 match get_char!(self, input) {
2009 // ↪ U+0021 EXCLAMATION MARK (!)
2010 c @ '!' => {
2011 // Append the current input character to the comment token's data.
2012 // Switch to the comment less-than sign bang state.
2013 go!(self: push_comment c; to State::CommentLessThanSignBang)
2014 },
2015 // ↪ U+003C LESS-THAN SIGN (<)
2016 c @ '<' => {
2017 // Append the current input character to the comment token's data.
2018 go!(self: push_comment c)
2019 },
2020 // ↪ Anything else
2021 _ => {
2022 // Reconsume in the comment state.
2023 go!(self: reconsume Comment)
2024 },
2025 }
2026 },
2027
2028 // https://html.spec.whatwg.org/#comment-less-than-sign-bang-state
2029 states::CommentLessThanSignBang => loop {
2030 // Consume the next input character:
2031 match get_char!(self, input) {
2032 // ↪ U+002D HYPHEN-MINUS (-)
2033 '-' => {
2034 // Switch to the comment less-than sign bang dash state.
2035 go!(self: to State::CommentLessThanSignBangDash)
2036 },
2037 // ↪ Anything else
2038 _ => {
2039 // Reconsume in the comment state.
2040 go!(self: reconsume Comment)
2041 },
2042 }
2043 },
2044
2045 // https://html.spec.whatwg.org/#comment-less-than-sign-bang-dash-state
2046 states::CommentLessThanSignBangDash => loop {
2047 // Consume the next input character:
2048 match get_char!(self, input) {
2049 // ↪ U+002D HYPHEN-MINUS (-)
2050 '-' => {
2051 // Switch to the comment less-than sign bang dash dash state.
2052 go!(self: to State::CommentLessThanSignBangDashDash)
2053 },
2054 // ↪ Anything else
2055 _ => {
2056 // Reconsume in the comment end dash state.
2057 go!(self: reconsume CommentEndDash)
2058 },
2059 }
2060 },
2061
2062 // https://html.spec.whatwg.org/#comment-less-than-sign-bang-dash-dash-state
2063 states::CommentLessThanSignBangDashDash => loop {
2064 match get_char!(self, input) {
2065 // ↪ U+003E GREATER-THAN SIGN (>)
2066 '>' => {
2067 // Reconsume in the comment end state.
2068 go!(self: reconsume CommentEnd)
2069 },
2070 // ↪ Anything else
2071 _ => {
2072 // This is a nested-comment parse error.
2073 // Reconsume in the comment end state.
2074 self.bad_char_error();
2075 go!(self: reconsume CommentEnd)
2076 },
2077 }
2078 },
2079
2080 // https://html.spec.whatwg.org/#comment-end-dash-state
2081 states::CommentEndDash => loop {
2082 // Consume the next input character:
2083 match get_char!(self, input) {
2084 // ↪ U+002D HYPHEN-MINUS (-)
2085 '-' => {
2086 // Switch to the comment end state.
2087 go!(self: to State::CommentEnd)
2088 },
2089 // NOTE: The "anything else" case in the specification reconsumes the character in the
2090 // comment state. Instead we inline what the comment state *would* do here.
2091 '\0' => {
2092 self.bad_char_error();
2093 go!(self: append_comment "-\u{fffd}"; to State::Comment)
2094 },
2095 character => {
2096 go!(self: push_comment '-'; push_comment character; to State::Comment)
2097 },
2098 }
2099 },
2100
2101 // https://html.spec.whatwg.org/#comment-end-state
2102 states::CommentEnd => loop {
2103 // Consume the next input character:
2104 match get_char!(self, input) {
2105 // ↪ U+003E GREATER-THAN SIGN (>)
2106 '>' => {
2107 // Switch to the data state.
2108 // Emit the current comment token.
2109 go!(self: emit_comment; to State::Data)
2110 },
2111 // ↪ U+0021 EXCLAMATION MARK (!)
2112 '!' => {
2113 // Switch to the comment end bang state.
2114 go!(self: to State::CommentEndBang)
2115 },
2116 // ↪ U+002D HYPHEN-MINUS (-)
2117 '-' => {
2118 // Append a U+002D HYPHEN-MINUS character (-) to the comment token's data.
2119 go!(self: push_comment '-')
2120 },
2121 // ↪ Anything else
2122 _ => {
2123 // Append two U+002D HYPHEN-MINUS characters (-) to the comment token's data.
2124 // Reconsume in the comment state.
2125 go!(self: append_comment "--"; reconsume Comment)
2126 },
2127 }
2128 },
2129
2130 // https://html.spec.whatwg.org/#comment-end-bang-state
2131 states::CommentEndBang => loop {
2132 // Consume the next input character:
2133 match get_char!(self, input) {
2134 // ↪ U+002D HYPHEN-MINUS (-)
2135 '-' => go!(self: append_comment "--!"; to State::CommentEndDash),
2136 // ↪ U+003E GREATER-THAN SIGN (>)
2137 '>' => {
2138 self.bad_char_error();
2139 go!(self: emit_comment; to State::Data)
2140 },
2141 // NOTE: The "anything else" case in the specification reconsumes the character in the
2142 // comment state. Instead we inline what the comment state *would* do here.
2143 '\0' => {
2144 self.bad_char_error();
2145 go!(self: append_comment "--!\u{fffd}"; to State::Comment)
2146 },
2147 character => {
2148 go!(self: append_comment "--!"; push_comment character; to State::Comment)
2149 },
2150 }
2151 },
2152
2153 // https://html.spec.whatwg.org/#doctype-state
2154 states::Doctype => loop {
2155 // Consume the next input character:
2156 match get_char!(self, input) {
2157 // ↪ U+0009 CHARACTER TABULATION (tab)
2158 // ↪ U+000A LINE FEED (LF)
2159 // ↪ U+000C FORM FEED (FF)
2160 // ↪ U+0020 SPACE
2161 '\t' | '\n' | '\x0C' | ' ' => {
2162 // Switch to the before DOCTYPE name state.
2163 go!(self: to State::BeforeDoctypeName)
2164 },
2165 // ↪ U+003E GREATER-THAN SIGN (>)
2166 '>' => {
2167 // Reconsume in the before DOCTYPE name state.
2168 go!(self: reconsume BeforeDoctypeName)
2169 },
2170 // ↪ Anything else
2171 _ => {
2172 // This is a missing-whitespace-before-doctype-name parse error.
2173 // Reconsume in the before DOCTYPE name state.
2174 self.bad_char_error();
2175 go!(self: reconsume BeforeDoctypeName)
2176 },
2177 }
2178 },
2179
2180 // https://html.spec.whatwg.org/#before-doctype-name-state
2181 states::BeforeDoctypeName => loop {
2182 // Consume the next input character:
2183 match get_char!(self, input) {
2184 // ↪ U+0009 CHARACTER TABULATION (tab)
2185 // ↪ U+000A LINE FEED (LF)
2186 // ↪ U+000C FORM FEED (FF)
2187 // ↪ U+0020 SPACE
2188 '\t' | '\n' | '\x0C' | ' ' => {
2189 // Ignore the character.
2190 },
2191 // ↪ U+0000 NULL
2192 '\0' => {
2193 // This is an unexpected-null-character parse error.
2194 // Create a new DOCTYPE token.
2195 // Set the token's name to a U+FFFD REPLACEMENT CHARACTER character.
2196 // Switch to the DOCTYPE name state.
2197 self.bad_char_error();
2198 go!(self: create_doctype; push_doctype_name '\u{fffd}'; to State::DoctypeName)
2199 },
2200 // ↪ U+003E GREATER-THAN SIGN (>)
2201 '>' => {
2202 // This is a missing-doctype-name parse error.
2203 // Create a new DOCTYPE token.
2204 // Set its force-quirks flag to on.
2205 // Switch to the data state.
2206 // Emit the current token.
2207 self.bad_char_error();
2208 go!(self: create_doctype; force_quirks; emit_doctype; to State::Data)
2209 },
2210 // ↪ ASCII upper alpha
2211 // NOTE: This is the same as "anythign else", except we use the lowercase version of the character.
2212 // ↪ Anything else
2213 character => {
2214 // Create a new DOCTYPE token.
2215 // Set the token's name to the current input character.
2216 // Switch to the DOCTYPE name state.
2217 go!(self: create_doctype; push_doctype_name (character.to_ascii_lowercase());
2218 to State::DoctypeName)
2219 },
2220 }
2221 },
2222
2223 // https://html.spec.whatwg.org/#doctype-name-state
2224 states::DoctypeName => loop {
2225 // Consume the next input character:
2226 match get_char!(self, input) {
2227 // ↪ U+0009 CHARACTER TABULATION (tab)
2228 // ↪ U+000A LINE FEED (LF)
2229 // ↪ U+000C FORM FEED (FF)
2230 // ↪ U+0020 SPACE
2231 '\t' | '\n' | '\x0C' | ' ' => {
2232 // Switch to the after DOCTYPE name state.
2233 go!(self: clear_temp; to State::AfterDoctypeName)
2234 },
2235 // ↪ U+003E GREATER-THAN SIGN (>)
2236 '>' => {
2237 // Switch to the data state. Emit the current DOCTYPE token.
2238 go!(self: emit_doctype; to State::Data)
2239 },
2240 // ↪ U+0000 NULL
2241 '\0' => {
2242 // This is an unexpected-null-character parse error.
2243 // Append a U+FFFD REPLACEMENT CHARACTER character to the current DOCTYPE token's name.
2244 self.bad_char_error();
2245 go!(self: push_doctype_name '\u{fffd}')
2246 },
2247 // ↪ ASCII upper alpha
2248 // NOTE: This is the same as "anythign else", except we use the lowercase version of the character.
2249 // ↪ Anything else
2250 character => {
2251 // Append the current input character to the current DOCTYPE token's name.
2252 go!(self: push_doctype_name (character.to_ascii_lowercase()))
2253 },
2254 }
2255 },
2256
2257 // https://html.spec.whatwg.org/#after-doctype-name-state
2258 states::AfterDoctypeName => loop {
2259 // NOTE: We move some steps out of the "anything else" case to the front for convenience.
2260
2261 // If the six characters starting from the current input character are an
2262 // ASCII case-insensitive match for "PUBLIC", then consume those characters
2263 // and switch to the after DOCTYPE public keyword state.
2264 if eat!(self, input, "public") {
2265 go!(self: to State::AfterDoctypeKeyword(Public));
2266 }
2267 // Otherwise, if the six characters starting from the current input character
2268 // are an ASCII case-insensitive match for "SYSTEM", then consume those characters
2269 // and switch to the after DOCTYPE system keyword state.
2270 else if eat!(self, input, "system") {
2271 go!(self: to State::AfterDoctypeKeyword(System));
2272 } else {
2273 // Consume the next input character:
2274 match get_char!(self, input) {
2275 // ↪ U+0009 CHARACTER TABULATION (tab)
2276 // ↪ U+000A LINE FEED (LF)
2277 // ↪ U+000C FORM FEED (FF)
2278 // ↪ U+0020 SPACE
2279 '\t' | '\n' | '\x0C' | ' ' => {
2280 // Ignore the character.
2281 },
2282 // ↪ U+003E GREATER-THAN SIGN (>)
2283 '>' => {
2284 // Switch to the data state. Emit the current DOCTYPE token.
2285 go!(self: emit_doctype; to State::Data)
2286 },
2287 // ↪ Anything else
2288 _ => {
2289 // Otherwise, this is an invalid-character-sequence-after-doctype-name parse error.
2290 // Set the current DOCTYPE token's force-quirks flag to on.
2291 // Reconsume in the bogus DOCTYPE state.
2292 self.bad_char_error();
2293 go!(self: force_quirks; reconsume BogusDoctype)
2294 },
2295 }
2296 }
2297 },
2298
2299 // https://html.spec.whatwg.org/#after-doctype-public-keyword-state
2300 // https://html.spec.whatwg.org/#after-doctype-system-keyword-state
2301 states::AfterDoctypeKeyword(kind) => loop {
2302 match get_char!(self, input) {
2303 // ↪ U+0009 CHARACTER TABULATION (tab)
2304 // ↪ U+000A LINE FEED (LF)
2305 // ↪ U+000C FORM FEED (FF)
2306 // ↪ U+0020 SPACE
2307 '\t' | '\n' | '\x0C' | ' ' => {
2308 // Switch to the before DOCTYPE public/system identifier state.
2309 go!(self: to State::BeforeDoctypeIdentifier(kind))
2310 },
2311 // ↪ U+0022 QUOTATION MARK (")
2312 '"' => {
2313 // This is a missing-whitespace-after-doctype-public/system-keyword parse error.
2314 // Set the current DOCTYPE token's public/system identifier to the empty string
2315 // (not missing), then switch to the DOCTYPE public/system identifier (double-quoted)
2316 // state.
2317 self.bad_char_error();
2318 go!(self: clear_doctype_id kind; to State::DoctypeIdentifierDoubleQuoted(kind))
2319 },
2320 // ↪ U+0027 APOSTROPHE (')
2321 '\'' => {
2322 // This is a missing-whitespace-after-doctype-public-keyword parse error.
2323 // Set the current DOCTYPE token's public identifier to the empty string
2324 // (not missing), then switch to the DOCTYPE public/system identifier (single-quoted)
2325 // state.
2326 self.bad_char_error();
2327 go!(self: clear_doctype_id kind; to State::DoctypeIdentifierSingleQuoted(kind))
2328 },
2329 // ↪ U+003E GREATER-THAN SIGN (>)
2330 '>' => {
2331 // This is a missing-doctype-public-identifier parse error.
2332 // Set the current DOCTYPE token's force-quirks flag to on.
2333 // Switch to the data state.
2334 // Emit the current DOCTYPE token.
2335 self.bad_char_error();
2336 go!(self: force_quirks; emit_doctype; to State::Data)
2337 },
2338 // ↪ Anything else
2339 _ => {
2340 // This is a missing-quote-before-doctype-public-identifier parse error.
2341 // Set the current DOCTYPE token's force-quirks flag to on.
2342 // Reconsume in the bogus DOCTYPE state.
2343 self.bad_char_error();
2344 go!(self: force_quirks; reconsume BogusDoctype)
2345 },
2346 }
2347 },
2348
2349 // https://html.spec.whatwg.org/#before-doctype-public-identifier-state
2350 // https://html.spec.whatwg.org/#before-doctype-system-identifier-state
2351 states::BeforeDoctypeIdentifier(kind) => loop {
2352 // Consume the next input character:
2353 match get_char!(self, input) {
2354 // ↪ U+0009 CHARACTER TABULATION (tab)
2355 // ↪ U+000A LINE FEED (LF)
2356 // ↪ U+000C FORM FEED (FF)
2357 // ↪ U+0020 SPACE
2358 '\t' | '\n' | '\x0C' | ' ' => {
2359 // Ignore the character.
2360 },
2361 // ↪ U+0022 QUOTATION MARK (")
2362 '"' => {
2363 // Set the current DOCTYPE token's public/system identifier to the empty string
2364 // (not missing), then switch to the DOCTYPE public/system identifier (double-quoted)
2365 // state.
2366 go!(self: clear_doctype_id kind; to State::DoctypeIdentifierDoubleQuoted(kind))
2367 },
2368 // ↪ U+0027 APOSTROPHE (')
2369 '\'' => {
2370 // Set the current DOCTYPE token's public/systen identifier to the empty string
2371 // (not missing), then switch to the DOCTYPE public/system identifier (single-quoted)
2372 // state.
2373 go!(self: clear_doctype_id kind; to State::DoctypeIdentifierSingleQuoted(kind))
2374 },
2375 // ↪ U+003E GREATER-THAN SIGN (>)
2376 '>' => {
2377 // This is a missing-doctype-public/system-identifier parse error.
2378 // Set the current DOCTYPE token's force-quirks flag to on.
2379 // Switch to the data state.
2380 // Emit the current DOCTYPE token.
2381 self.bad_char_error();
2382 go!(self: force_quirks; emit_doctype; to State::Data)
2383 },
2384 // ↪ Anything else
2385 _ => {
2386 // This is a missing-quote-before-doctype-public/system-identifier parse error.
2387 // Set the current DOCTYPE token's force-quirks flag to on.
2388 // Reconsume in the bogus DOCTYPE state.
2389 self.bad_char_error();
2390 go!(self: force_quirks; reconsume BogusDoctype)
2391 },
2392 }
2393 },
2394
2395 // https://html.spec.whatwg.org/#doctype-public-identifier-(double-quoted)-state
2396 // https://html.spec.whatwg.org/#doctype-system-identifier-(double-quoted)-state
2397 states::DoctypeIdentifierDoubleQuoted(kind) => loop {
2398 // Consume the next input character:
2399 match get_char!(self, input) {
2400 // ↪ U+0022 QUOTATION MARK (")
2401 '"' => {
2402 // Switch to the after DOCTYPE public/system identifier state.
2403 go!(self: to State::AfterDoctypeIdentifier(kind))
2404 },
2405 // ↪ U+0000 NULL
2406 '\0' => {
2407 // This is an unexpected-null-character parse error.
2408 // Append a U+FFFD REPLACEMENT CHARACTER character to the current
2409 // DOCTYPE token's public identifier.
2410 self.bad_char_error();
2411 go!(self: push_doctype_id kind '\u{fffd}')
2412 },
2413 // ↪ U+003E GREATER-THAN SIGN (>)
2414 '>' => {
2415 // This is an abrupt-doctype-public-identifier parse error.
2416 // Set the current DOCTYPE token's force-quirks flag to on.
2417 // Switch to the data state.
2418 // Emit the current DOCTYPE token.
2419 self.bad_char_error();
2420 go!(self: force_quirks; emit_doctype; to State::Data)
2421 },
2422 // ↪ Anything else
2423 character => go!(self: push_doctype_id kind character),
2424 }
2425 },
2426
2427 // https://html.spec.whatwg.org/#doctype-public-identifier-(single-quoted)-state
2428 // https://html.spec.whatwg.org/#doctype-system-identifier-(single-quoted)-state
2429 states::DoctypeIdentifierSingleQuoted(kind) => loop {
2430 // Consume the next input character:
2431 match get_char!(self, input) {
2432 // ↪ U+0027 APOSTROPHE (')
2433 '\'' => {
2434 // Switch to the after DOCTYPE public/system identifier state.
2435 go!(self: to State::AfterDoctypeIdentifier(kind))
2436 },
2437 // ↪ U+0000 NULL
2438 '\0' => {
2439 // This is an unexpected-null-character parse error.
2440 // Append a U+FFFD REPLACEMENT CHARACTER character
2441 // to the current DOCTYPE token's public/system identifier.
2442 self.bad_char_error();
2443 go!(self: push_doctype_id kind '\u{fffd}')
2444 },
2445 // ↪ U+003E GREATER-THAN SIGN (>)
2446 '>' => {
2447 // This is an abrupt-doctype-public/system-identifier parse error.
2448 // Set the current DOCTYPE token's force-quirks flag to on.
2449 // Switch to the data state.
2450 // Emit the current DOCTYPE token.
2451 self.bad_char_error();
2452 go!(self: force_quirks; emit_doctype; to State::Data)
2453 },
2454 // ↪ Anything else
2455 character => {
2456 // Append the current input character to the current DOCTYPE token's
2457 // public/system identifier.
2458 go!(self: push_doctype_id kind character)
2459 },
2460 }
2461 },
2462
2463 // https://html.spec.whatwg.org/#after-doctype-public-identifier-state
2464 states::AfterDoctypeIdentifier(Public) => loop {
2465 // Consume the next input character:
2466 match get_char!(self, input) {
2467 // ↪ U+0009 CHARACTER TABULATION (tab)
2468 // ↪ U+000A LINE FEED (LF)
2469 // ↪ U+000C FORM FEED (FF)
2470 // ↪ U+0020 SPACE
2471 '\t' | '\n' | '\x0C' | ' ' => {
2472 // Switch to the between DOCTYPE public and system identifiers state.
2473 go!(self: to State::BetweenDoctypePublicAndSystemIdentifiers)
2474 },
2475 // ↪ U+003E GREATER-THAN SIGN (>)
2476 '>' => {
2477 // Switch to the data state. Emit the current DOCTYPE token.
2478 go!(self: emit_doctype; to State::Data)
2479 },
2480 // ↪ U+0022 QUOTATION MARK (")
2481 '"' => {
2482 // This is a missing-whitespace-between-doctype-public-and-system-identifiers
2483 // parse error. Set the current DOCTYPE token's system identifier to the empty string
2484 // (not missing), then switch to the DOCTYPE system identifier (double-quoted) state.
2485 self.bad_char_error();
2486 go!(self: clear_doctype_id System; to State::DoctypeIdentifierDoubleQuoted(System))
2487 },
2488 // ↪ U+0027 APOSTROPHE (')
2489 '\'' => {
2490 // This is a missing-whitespace-between-doctype-public-and-system-identifiers parse error.
2491 // Set the current DOCTYPE token's system identifier to the empty string (not missing),
2492 // then switch to the DOCTYPE system identifier (single-quoted) state.
2493 self.bad_char_error();
2494 go!(self: clear_doctype_id System; to State::DoctypeIdentifierSingleQuoted(System))
2495 },
2496 // ↪ Anything else
2497 _ => {
2498 // This is a missing-quote-before-doctype-system-identifier parse error.
2499 // Set the current DOCTYPE token's force-quirks flag to on.
2500 // Reconsume in the bogus DOCTYPE state.
2501 self.bad_char_error();
2502 go!(self: force_quirks; reconsume BogusDoctype)
2503 },
2504 }
2505 },
2506
2507 // https://html.spec.whatwg.org/#between-doctype-public-and-system-identifiers-state
2508 states::BetweenDoctypePublicAndSystemIdentifiers => loop {
2509 // Consume the next input character:
2510 match get_char!(self, input) {
2511 // ↪ U+0009 CHARACTER TABULATION (tab)
2512 // ↪ U+000A LINE FEED (LF)
2513 // ↪ U+000C FORM FEED (FF)
2514 // ↪ U+0020 SPACE
2515 '\t' | '\n' | '\x0C' | ' ' => {
2516 // Ignore the character.
2517 },
2518 // ↪ U+003E GREATER-THAN SIGN (>)
2519 '>' => {
2520 // Switch to the data state. Emit the current DOCTYPE token.
2521 go!(self: emit_doctype; to State::Data)
2522 },
2523 // ↪ U+0022 QUOTATION MARK (")
2524 '"' => {
2525 // Set the current DOCTYPE token's system identifier to the empty string (not missing),
2526 // then switch to the DOCTYPE system identifier (double-quoted) state.
2527 go!(self: clear_doctype_id System; to State::DoctypeIdentifierDoubleQuoted(System))
2528 },
2529 // ↪ U+0027 APOSTROPHE (')
2530 '\'' => {
2531 // Set the current DOCTYPE token's system identifier to the empty string (not missing),
2532 // then switch to the DOCTYPE system identifier (single-quoted) state.
2533 go!(self: clear_doctype_id System; to State::DoctypeIdentifierSingleQuoted(System))
2534 },
2535 // ↪ Anything else
2536 _ => {
2537 // This is a missing-quote-before-doctype-system-identifier parse error.
2538 // Set the current DOCTYPE token's force-quirks flag to on.
2539 // Reconsume in the bogus DOCTYPE state.
2540 self.bad_char_error();
2541 go!(self: force_quirks; reconsume BogusDoctype)
2542 },
2543 }
2544 },
2545
2546 // https://html.spec.whatwg.org/#after-doctype-system-identifier-state
2547 states::AfterDoctypeIdentifier(System) => loop {
2548 // Consume the next input character:
2549 match get_char!(self, input) {
2550 // ↪ U+0009 CHARACTER TABULATION (tab)
2551 // ↪ U+000A LINE FEED (LF)
2552 // ↪ U+000C FORM FEED (FF)
2553 // ↪ U+0020 SPACE
2554 '\t' | '\n' | '\x0C' | ' ' => {
2555 // Ignore the character.
2556 },
2557 // ↪ U+003E GREATER-THAN SIGN (>)
2558 '>' => {
2559 // Switch to the data state. Emit the current DOCTYPE token.
2560 go!(self: emit_doctype; to State::Data)
2561 },
2562 // ↪ Anything else
2563 _ => {
2564 // This is an unexpected-character-after-doctype-system-identifier parse error.
2565 // Reconsume in the bogus DOCTYPE state.
2566 self.bad_char_error();
2567 go!(self: reconsume BogusDoctype)
2568 },
2569 }
2570 },
2571
2572 // https://html.spec.whatwg.org/#bogus-doctype-state
2573 states::BogusDoctype => loop {
2574 // Consume the next input character:
2575 match get_char!(self, input) {
2576 // ↪ U+003E GREATER-THAN SIGN (>)
2577 '>' => {
2578 // Switch to the data state.
2579 // Emit the current DOCTYPE token.
2580 go!(self: emit_doctype; to State::Data)
2581 },
2582 // ↪ U+0000 NULL
2583 '\0' => {
2584 // This is an unexpected-null-character parse error.
2585 // Ignore the character.
2586 self.bad_char_error();
2587 },
2588 // ↪ Anything else
2589 _ => {
2590 // Ignore the character.
2591 },
2592 }
2593 },
2594
2595 // https://html.spec.whatwg.org/#cdata-section-state
2596 states::CdataSection => loop {
2597 // Consume the next input character:
2598 match get_char!(self, input) {
2599 // ↪ U+005D RIGHT SQUARE BRACKET (])
2600 ']' => {
2601 // Switch to the CDATA section bracket state.
2602 go!(self: to State::CdataSectionBracket)
2603 },
2604 // FIXME: This is not in the specification.
2605 '\0' => {
2606 self.emit_temp_buf();
2607 self.emit_char('\0');
2608 },
2609 // ↪ Anything else
2610 character => {
2611 // Emit the current input character as a character token.
2612 go!(self: push_temp character)
2613 },
2614 }
2615 },
2616
2617 // https://html.spec.whatwg.org/#cdata-section-bracket-state
2618 states::CdataSectionBracket => {
2619 // Consume the next input character:
2620 match get_char!(self, input) {
2621 // ↪ U+005D RIGHT SQUARE BRACKET (])
2622 ']' => {
2623 // Switch to the CDATA section end state.
2624 go!(self: to State::CdataSectionEnd)
2625 },
2626 // ↪ Anything else
2627 _ => {
2628 // Emit a U+005D RIGHT SQUARE BRACKET character token. Reconsume in the CDATA section state.
2629 go!(self: push_temp ']'; reconsume CdataSection)
2630 },
2631 }
2632 },
2633
2634 // https://html.spec.whatwg.org/#cdata-section-end-state
2635 states::CdataSectionEnd => loop {
2636 // Consume the next input character:
2637 match get_char!(self, input) {
2638 // U+005D RIGHT SQUARE BRACKET (])
2639 ']' => {
2640 // Emit a U+005D RIGHT SQUARE BRACKET character token.
2641 go!(self: push_temp ']')
2642 },
2643 // U+003E GREATER-THAN SIGN (>)
2644 '>' => {
2645 // Switch to the data state.
2646 self.emit_temp_buf();
2647 go!(self: to State::Data);
2648 },
2649 // Anything else
2650 _ => {
2651 // Emit two U+005D RIGHT SQUARE BRACKET character tokens.
2652 // Reconsume in the CDATA section state.
2653 go!(self: push_temp ']'; push_temp ']'; reconsume CdataSection)
2654 },
2655 }
2656 },
2657 // TODO: What about the processing-instruction related states?
2658 //§ END
2659 }
2660 }
2661
2662 fn step_char_ref_tokenizer(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
2663 let mut char_ref_tokenizer = self.char_ref_tokenizer.borrow_mut();
2664 let progress = match char_ref_tokenizer.as_mut().unwrap().step(self, input) {
2665 char_ref::Status::Done(char_ref) => {
2666 self.process_char_ref(char_ref);
2667 *char_ref_tokenizer = None;
2668 return ProcessResult::Continue;
2669 },
2670
2671 char_ref::Status::Stuck => ProcessResult::Suspend,
2672 char_ref::Status::Progress => ProcessResult::Continue,
2673 };
2674
2675 progress
2676 }
2677
2678 fn process_char_ref(&self, char_ref: CharRef) {
2679 let CharRef {
2680 mut chars,
2681 mut num_chars,
2682 } = char_ref;
2683
2684 if num_chars == 0 {
2685 chars[0] = '&';
2686 num_chars = 1;
2687 }
2688
2689 for i in 0..num_chars {
2690 let c = chars[i as usize];
2691 match self.state.get() {
2692 states::Data | states::RawData(states::Rcdata) => self.emit_char(c),
2693
2694 states::AttributeValue(_) => go!(self: push_value c),
2695
2696 _ => panic!(
2697 "state {:?} should not be reachable in process_char_ref",
2698 self.state.get()
2699 ),
2700 }
2701 }
2702 }
2703
2704 /// Indicate that we have reached the end of the input.
2705 pub fn end(&self) {
2706 // Handle EOF in the char ref sub-tokenizer, if there is one.
2707 // Do this first because it might un-consume stuff.
2708 let input = BufferQueue::default();
2709 match self.char_ref_tokenizer.take() {
2710 None => (),
2711 Some(mut tokenizer) => {
2712 self.process_char_ref(tokenizer.end_of_file(self, &input));
2713 },
2714 }
2715
2716 // Process all remaining buffered input.
2717 // If we're waiting for lookahead, we're not gonna get it.
2718 self.at_eof.set(true);
2719 assert!(matches!(self.run(&input), TokenizerResult::Done));
2720 assert!(input.is_empty());
2721
2722 loop {
2723 match self.eof_step() {
2724 ProcessResult::Continue => (),
2725 ProcessResult::Suspend => break,
2726 ProcessResult::Script(_) | ProcessResult::EncodingIndicator(_) => unreachable!(),
2727 }
2728 }
2729
2730 self.sink.end();
2731
2732 if self.opts.profile {
2733 self.dump_profile();
2734 }
2735 }
2736
2737 fn dump_profile(&self) {
2738 let mut results: Vec<(states::State, u64)> = self
2739 .state_profile
2740 .borrow()
2741 .iter()
2742 .map(|(s, t)| (*s, *t))
2743 .collect();
2744 results.sort_by_key(|&(_, x)| Reverse(x));
2745
2746 let total: u64 = results.iter().map(|&(_, t)| t).sum();
2747 println!("\nTokenizer profile, in nanoseconds");
2748 println!(
2749 "\n{:12} total in token sink",
2750 self.time_in_sink.get()
2751 );
2752 println!("\n{total:12} total in tokenizer");
2753
2754 for (k, v) in results.into_iter() {
2755 let pct = 100.0 * (v as f64) / (total as f64);
2756 println!("{v:12} {pct:4.1}% {k:?}");
2757 }
2758 }
2759
2760 fn eof_step(&self) -> ProcessResult<Sink::Handle> {
2761 debug!("processing EOF in state {:?}", self.state.get());
2762 match self.state.get() {
2763 states::Data
2764 | states::RawData(Rcdata)
2765 | states::RawData(Rawtext)
2766 | states::RawData(ScriptData)
2767 | states::Plaintext => go!(self: eof),
2768
2769 states::TagName
2770 | states::RawData(ScriptDataEscaped(_))
2771 | states::BeforeAttributeName
2772 | states::AttributeName
2773 | states::AfterAttributeName
2774 | states::AttributeValue(_)
2775 | states::AfterAttributeValueQuoted
2776 | states::SelfClosingStartTag
2777 | states::ScriptDataEscapedDash(_)
2778 | states::ScriptDataEscapedDashDash(_) => {
2779 self.bad_eof_error();
2780 go!(self: to State::Data)
2781 },
2782
2783 states::BeforeAttributeValue => go!(self: reconsume AttributeValue(Unquoted)),
2784
2785 states::TagOpen => {
2786 self.bad_eof_error();
2787 self.emit_char('<');
2788 go!(self: to State::Data);
2789 },
2790
2791 states::EndTagOpen => {
2792 self.bad_eof_error();
2793 self.emit_char('<');
2794 self.emit_char('/');
2795 go!(self: to State::Data);
2796 },
2797
2798 states::RawLessThanSign(ScriptDataEscaped(DoubleEscaped)) => {
2799 go!(self: to State::RawData(ScriptDataEscaped(DoubleEscaped)))
2800 },
2801
2802 states::RawLessThanSign(kind) => {
2803 self.emit_char('<');
2804 go!(self: to State::RawData(kind));
2805 },
2806
2807 states::RawEndTagOpen(kind) => {
2808 self.emit_char('<');
2809 self.emit_char('/');
2810 go!(self: to State::RawData(kind));
2811 },
2812
2813 states::RawEndTagName(kind) => {
2814 self.emit_char('<');
2815 self.emit_char('/');
2816 self.emit_temp_buf();
2817 go!(self: to State::RawData(kind))
2818 },
2819
2820 states::ScriptDataEscapeStart(kind) => {
2821 go!(self: to State::RawData(ScriptDataEscaped(kind)))
2822 },
2823
2824 states::ScriptDataEscapeStartDash => go!(self: to State::RawData(ScriptData)),
2825
2826 states::ScriptDataDoubleEscapeEnd => {
2827 go!(self: to State::RawData(ScriptDataEscaped(DoubleEscaped)))
2828 },
2829
2830 states::CommentStart
2831 | states::CommentStartDash
2832 | states::Comment
2833 | states::CommentEndDash
2834 | states::CommentEnd
2835 | states::CommentEndBang => {
2836 self.bad_eof_error();
2837 go!(self: emit_comment; to State::Data)
2838 },
2839
2840 states::CommentLessThanSign | states::CommentLessThanSignBang => {
2841 go!(self: reconsume Comment)
2842 },
2843
2844 states::CommentLessThanSignBangDash => go!(self: reconsume CommentEndDash),
2845
2846 states::CommentLessThanSignBangDashDash => go!(self: reconsume CommentEnd),
2847
2848 states::Doctype | states::BeforeDoctypeName => {
2849 self.bad_eof_error();
2850 go!(self: create_doctype; force_quirks; emit_doctype; to State::Data)
2851 },
2852
2853 states::DoctypeName
2854 | states::AfterDoctypeName
2855 | states::AfterDoctypeKeyword(_)
2856 | states::BeforeDoctypeIdentifier(_)
2857 | states::DoctypeIdentifierDoubleQuoted(_)
2858 | states::DoctypeIdentifierSingleQuoted(_)
2859 | states::AfterDoctypeIdentifier(_)
2860 | states::BetweenDoctypePublicAndSystemIdentifiers => {
2861 self.bad_eof_error();
2862 go!(self: force_quirks; emit_doctype; to State::Data)
2863 },
2864
2865 states::BogusDoctype => go!(self: emit_doctype; to State::Data),
2866
2867 states::BogusComment => go!(self: emit_comment; to State::Data),
2868
2869 states::MarkupDeclarationOpen => {
2870 self.bad_char_error();
2871 go!(self: to State::BogusComment)
2872 },
2873
2874 states::CdataSection => {
2875 self.emit_temp_buf();
2876 self.bad_eof_error();
2877 go!(self: to State::Data)
2878 },
2879
2880 states::CdataSectionBracket => go!(self: push_temp ']'; to State::CdataSection),
2881
2882 states::CdataSectionEnd => {
2883 go!(self: push_temp ']'; push_temp ']'; to State::CdataSection)
2884 },
2885 }
2886 }
2887
2888 /// Checks for supported SIMD feature, which is now either SSE2 for x86/x86_64 or NEON for aarch64.
2889 fn is_supported_simd_feature_detected() -> bool {
2890 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2891 {
2892 is_x86_feature_detected!("sse2")
2893 }
2894
2895 #[cfg(target_arch = "aarch64")]
2896 {
2897 std::arch::is_aarch64_feature_detected!("neon")
2898 }
2899
2900 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
2901 false
2902 }
2903
2904 #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
2905 /// Implements the [data state] with SIMD instructions.
2906 /// Calls SSE2- or NEON-specific function for chunks and processes any remaining bytes.
2907 ///
2908 /// The algorithm implemented is the naive SIMD approach described [here].
2909 ///
2910 /// ### SAFETY:
2911 /// Calling this function on a CPU that supports neither SSE2 nor NEON causes undefined behaviour.
2912 ///
2913 /// [data state]: https://html.spec.whatwg.org/#data-state
2914 /// [here]: https://lemire.me/blog/2024/06/08/scan-html-faster-with-simd-instructions-chrome-edition/
2915 unsafe fn data_state_simd_fast_path(&self, input: &mut StrTendril) -> Option<SetResult> {
2916 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2917 let (mut i, mut n_newlines) = self.data_state_sse2_fast_path(input);
2918
2919 #[cfg(target_arch = "aarch64")]
2920 let (mut i, mut n_newlines) = self.data_state_neon_fast_path(input);
2921
2922 // Process any remaining bytes (less than STRIDE)
2923 while let Some(c) = input.as_bytes().get(i) {
2924 if matches!(*c, b'<' | b'&' | b'\r' | b'\0') {
2925 break;
2926 }
2927 if *c == b'\n' {
2928 n_newlines += 1;
2929 }
2930
2931 i += 1;
2932 }
2933
2934 let set_result = if i == 0 {
2935 let first_char = input.pop_front_char().unwrap();
2936 debug_assert!(matches!(first_char, '<' | '&' | '\r' | '\0'));
2937
2938 // FIXME: Passing a bogus input queue is only relevant when c is \n, which can never happen in this case.
2939 // Still, it would be nice to not have to do that.
2940 // The same is true for the unwrap call.
2941 let preprocessed_char = self
2942 .get_preprocessed_char(first_char, &BufferQueue::default())
2943 .unwrap();
2944 SetResult::FromSet(preprocessed_char)
2945 } else {
2946 debug_assert!(
2947 input.len() >= i,
2948 "Trying to remove {:?} bytes from a tendril that is only {:?} bytes long",
2949 i,
2950 input.len()
2951 );
2952 let consumed_chunk = input.unsafe_subtendril(0, i as u32);
2953 input.unsafe_pop_front(i as u32);
2954 SetResult::NotFromSet(consumed_chunk)
2955 };
2956
2957 self.current_line.set(self.current_line.get() + n_newlines);
2958
2959 Some(set_result)
2960 }
2961
2962 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2963 #[target_feature(enable = "sse2")]
2964 /// Implements the [data state] with SSE2 instructions for x86/x86_64.
2965 /// Returns a pair of the number of bytes processed and the number of newlines found.
2966 ///
2967 /// ### SAFETY:
2968 /// Calling this function on a CPU that does not support NEON causes undefined behaviour.
2969 ///
2970 /// [data state]: https://html.spec.whatwg.org/#data-state
2971 unsafe fn data_state_sse2_fast_path(&self, input: &mut StrTendril) -> (usize, u64) {
2972 #[cfg(target_arch = "x86")]
2973 use std::arch::x86::{
2974 __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
2975 _mm_set1_epi8,
2976 };
2977 #[cfg(target_arch = "x86_64")]
2978 use std::arch::x86_64::{
2979 __m128i, _mm_cmpeq_epi8, _mm_loadu_si128, _mm_movemask_epi8, _mm_or_si128,
2980 _mm_set1_epi8,
2981 };
2982
2983 debug_assert!(!input.is_empty());
2984
2985 let quote_mask = _mm_set1_epi8('<' as i8);
2986 let escape_mask = _mm_set1_epi8('&' as i8);
2987 let carriage_return_mask = _mm_set1_epi8('\r' as i8);
2988 let zero_mask = _mm_set1_epi8('\0' as i8);
2989 let newline_mask = _mm_set1_epi8('\n' as i8);
2990
2991 let raw_bytes: &[u8] = input.as_bytes();
2992 let start = raw_bytes.as_ptr();
2993
2994 const STRIDE: usize = 16;
2995 let mut i = 0;
2996 let mut n_newlines = 0;
2997 while i + STRIDE <= raw_bytes.len() {
2998 // Load a 16 byte chunk from the input
2999 let data = _mm_loadu_si128(start.add(i) as *const __m128i);
3000
3001 // Compare the chunk against each mask
3002 let quotes = _mm_cmpeq_epi8(data, quote_mask);
3003 let escapes = _mm_cmpeq_epi8(data, escape_mask);
3004 let carriage_returns = _mm_cmpeq_epi8(data, carriage_return_mask);
3005 let zeros = _mm_cmpeq_epi8(data, zero_mask);
3006 let newlines = _mm_cmpeq_epi8(data, newline_mask);
3007
3008 // Combine all test results and create a bitmask from them.
3009 // Each bit in the mask will be 1 if the character at the bit position is in the set and 0 otherwise.
3010 let test_result = _mm_or_si128(
3011 _mm_or_si128(quotes, zeros),
3012 _mm_or_si128(escapes, carriage_returns),
3013 );
3014 let bitmask = _mm_movemask_epi8(test_result);
3015 let newline_mask = _mm_movemask_epi8(newlines);
3016
3017 if (bitmask != 0) {
3018 // We have reached one of the characters that cause the state machine to transition
3019 let position = if cfg!(target_endian = "little") {
3020 bitmask.trailing_zeros() as usize
3021 } else {
3022 bitmask.leading_zeros() as usize
3023 };
3024
3025 n_newlines += (newline_mask & ((1 << position) - 1)).count_ones() as u64;
3026 i += position;
3027 break;
3028 } else {
3029 n_newlines += newline_mask.count_ones() as u64;
3030 }
3031
3032 i += STRIDE;
3033 }
3034
3035 (i, n_newlines)
3036 }
3037
3038 #[cfg(target_arch = "aarch64")]
3039 #[target_feature(enable = "neon")]
3040 /// Implements the [data state] with NEON SIMD instructions for AArch64.
3041 /// Returns a pair of the number of bytes processed and the number of newlines found.
3042 ///
3043 /// ### SAFETY:
3044 /// Calling this function on a CPU that does not support NEON causes undefined behaviour.
3045 ///
3046 /// [data state]: https://html.spec.whatwg.org/#data-state
3047 unsafe fn data_state_neon_fast_path(&self, input: &mut StrTendril) -> (usize, u64) {
3048 use std::arch::aarch64::{vceqq_u8, vdupq_n_u8, vld1q_u8, vmaxvq_u8, vorrq_u8};
3049
3050 debug_assert!(!input.is_empty());
3051
3052 let quote_mask = vdupq_n_u8(b'<');
3053 let escape_mask = vdupq_n_u8(b'&');
3054 let carriage_return_mask = vdupq_n_u8(b'\r');
3055 let zero_mask = vdupq_n_u8(b'\0');
3056 let newline_mask = vdupq_n_u8(b'\n');
3057
3058 let raw_bytes: &[u8] = input.as_bytes();
3059 let start = raw_bytes.as_ptr();
3060
3061 const STRIDE: usize = 16;
3062 let mut i = 0;
3063 let mut n_newlines = 0;
3064 while i + STRIDE <= raw_bytes.len() {
3065 // Load a 16 byte chunk from the input
3066 let data = vld1q_u8(start.add(i));
3067
3068 // Compare the chunk against each mask
3069 let quotes = vceqq_u8(data, quote_mask);
3070 let escapes = vceqq_u8(data, escape_mask);
3071 let carriage_returns = vceqq_u8(data, carriage_return_mask);
3072 let zeros = vceqq_u8(data, zero_mask);
3073 let newlines = vceqq_u8(data, newline_mask);
3074
3075 // Combine all test results and create a bitmask from them.
3076 // Each bit in the mask will be 1 if the character at the bit position is in the set and 0 otherwise.
3077 let test_result =
3078 vorrq_u8(vorrq_u8(quotes, zeros), vorrq_u8(escapes, carriage_returns));
3079 let bitmask = vmaxvq_u8(test_result);
3080 let newline_mask = vmaxvq_u8(newlines);
3081 if bitmask != 0 {
3082 // We have reached one of the characters that cause the state machine to transition
3083 let chunk_bytes = std::slice::from_raw_parts(start.add(i), STRIDE);
3084 let position = chunk_bytes
3085 .iter()
3086 .position(|&b| matches!(b, b'<' | b'&' | b'\r' | b'\0'))
3087 .unwrap();
3088
3089 n_newlines += chunk_bytes[..position]
3090 .iter()
3091 .filter(|&&b| b == b'\n')
3092 .count() as u64;
3093
3094 i += position;
3095 break;
3096 } else if newline_mask != 0 {
3097 let chunk_bytes = std::slice::from_raw_parts(start.add(i), STRIDE);
3098 n_newlines += chunk_bytes.iter().filter(|&&b| b == b'\n').count() as u64;
3099 }
3100
3101 i += STRIDE;
3102 }
3103
3104 (i, n_newlines)
3105 }
3106}
3107
3108#[cfg(test)]
3109#[allow(non_snake_case)]
3110mod test {
3111 use super::option_push; // private items
3112 use crate::tendril::{SliceExt, StrTendril};
3113
3114 use super::{TokenSink, TokenSinkResult, Tokenizer, TokenizerOpts};
3115
3116 use super::interface::{CharacterTokens, EOFToken, NullCharacterToken, ParseError};
3117 use super::interface::{EndTag, StartTag, Tag, TagKind};
3118 use super::interface::{TagToken, Token};
3119
3120 use markup5ever::buffer_queue::BufferQueue;
3121 use std::cell::RefCell;
3122
3123 use crate::LocalName;
3124
3125 // LinesMatch implements the TokenSink trait. It is used for testing to see
3126 // if current_line is being updated when process_token is called. The lines
3127 // vector is a collection of the line numbers that each token is on.
3128 struct LinesMatch {
3129 tokens: RefCell<Vec<Token>>,
3130 current_str: RefCell<StrTendril>,
3131 lines: RefCell<Vec<(Token, u64)>>,
3132 }
3133
3134 impl LinesMatch {
3135 fn new() -> LinesMatch {
3136 LinesMatch {
3137 tokens: RefCell::new(vec![]),
3138 current_str: RefCell::new(StrTendril::new()),
3139 lines: RefCell::new(vec![]),
3140 }
3141 }
3142
3143 fn push(&self, token: Token, line_number: u64) {
3144 self.finish_str();
3145 self.lines.borrow_mut().push((token, line_number));
3146 }
3147
3148 fn finish_str(&self) {
3149 if !self.current_str.borrow().is_empty() {
3150 let s = self.current_str.take();
3151 self.tokens.borrow_mut().push(CharacterTokens(s));
3152 }
3153 }
3154 }
3155
3156 impl TokenSink for LinesMatch {
3157 type Handle = ();
3158
3159 fn process_token(&self, token: Token, line_number: u64) -> TokenSinkResult<Self::Handle> {
3160 match token {
3161 CharacterTokens(b) => {
3162 self.current_str.borrow_mut().push_slice(&b);
3163 },
3164
3165 NullCharacterToken => {
3166 self.current_str.borrow_mut().push_char('\0');
3167 },
3168
3169 ParseError(_) => {
3170 panic!("unexpected parse error");
3171 },
3172
3173 TagToken(mut t) => {
3174 // The spec seems to indicate that one can emit
3175 // erroneous end tags with attrs, but the test
3176 // cases don't contain them.
3177 match t.kind {
3178 EndTag => {
3179 t.self_closing = false;
3180 t.attrs = vec![];
3181 },
3182 _ => t.attrs.sort_by(|a1, a2| a1.name.cmp(&a2.name)),
3183 }
3184 self.push(TagToken(t), line_number);
3185 },
3186
3187 EOFToken => (),
3188
3189 _ => self.push(token, line_number),
3190 }
3191 TokenSinkResult::Continue
3192 }
3193 }
3194
3195 // Take in tokens, process them, and return vector with line
3196 // numbers that each token is on
3197 fn tokenize(input: Vec<StrTendril>, opts: TokenizerOpts) -> Vec<(Token, u64)> {
3198 let sink = LinesMatch::new();
3199 let tok = Tokenizer::new(sink, opts);
3200 let buffer = BufferQueue::default();
3201 for chunk in input.into_iter() {
3202 buffer.push_back(chunk);
3203 let _ = tok.feed(&buffer);
3204 }
3205 tok.end();
3206 tok.sink.lines.take()
3207 }
3208
3209 // Create a tag token
3210 fn create_tag(token: StrTendril, tagkind: TagKind) -> Token {
3211 let name = LocalName::from(&*token);
3212
3213 TagToken(Tag {
3214 kind: tagkind,
3215 name,
3216 self_closing: false,
3217 attrs: vec![],
3218 had_duplicate_attributes: false,
3219 })
3220 }
3221
3222 #[test]
3223 fn push_to_None_gives_singleton() {
3224 let mut s: Option<StrTendril> = None;
3225 option_push(&mut s, 'x');
3226 assert_eq!(s, Some("x".to_tendril()));
3227 }
3228
3229 #[test]
3230 fn push_to_empty_appends() {
3231 let mut s: Option<StrTendril> = Some(StrTendril::new());
3232 option_push(&mut s, 'x');
3233 assert_eq!(s, Some("x".to_tendril()));
3234 }
3235
3236 #[test]
3237 fn push_to_nonempty_appends() {
3238 let mut s: Option<StrTendril> = Some(StrTendril::from_slice("y"));
3239 option_push(&mut s, 'x');
3240 assert_eq!(s, Some("yx".to_tendril()));
3241 }
3242
3243 #[test]
3244 fn check_lines() {
3245 let opts = TokenizerOpts {
3246 exact_errors: false,
3247 discard_bom: true,
3248 profile: false,
3249 initial_state: None,
3250 last_start_tag_name: None,
3251 };
3252 let vector = vec![
3253 StrTendril::from("<a>\n"),
3254 StrTendril::from("<b>\n"),
3255 StrTendril::from("</b>\n"),
3256 StrTendril::from("</a>\n"),
3257 ];
3258 let expected = vec![
3259 (create_tag(StrTendril::from("a"), StartTag), 1),
3260 (create_tag(StrTendril::from("b"), StartTag), 2),
3261 (create_tag(StrTendril::from("b"), EndTag), 3),
3262 (create_tag(StrTendril::from("a"), EndTag), 4),
3263 ];
3264 let results = tokenize(vector, opts);
3265 assert_eq!(results, expected);
3266 }
3267
3268 #[test]
3269 fn check_lines_with_new_line() {
3270 let opts = TokenizerOpts {
3271 exact_errors: false,
3272 discard_bom: true,
3273 profile: false,
3274 initial_state: None,
3275 last_start_tag_name: None,
3276 };
3277 let vector = vec![
3278 StrTendril::from("<a>\r\n"),
3279 StrTendril::from("<b>\r\n"),
3280 StrTendril::from("</b>\r\n"),
3281 StrTendril::from("</a>\r\n"),
3282 ];
3283 let expected = vec![
3284 (create_tag(StrTendril::from("a"), StartTag), 1),
3285 (create_tag(StrTendril::from("b"), StartTag), 2),
3286 (create_tag(StrTendril::from("b"), EndTag), 3),
3287 (create_tag(StrTendril::from("a"), EndTag), 4),
3288 ];
3289 let results = tokenize(vector, opts);
3290 assert_eq!(results, expected);
3291 }
3292}