Skip to main content

xml5ever/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
10mod char_ref;
11mod interface;
12mod qname;
13pub mod states;
14
15pub use self::interface::{
16    Doctype, EmptyTag, EndTag, Pi, ShortTag, StartTag, Tag, TagKind, Token, TokenSink,
17};
18pub use crate::{LocalName, Namespace, Prefix};
19
20use crate::macros::time;
21use crate::tendril::StrTendril;
22use crate::{buffer_queue, Attribute, QualName, SmallCharSet};
23use log::debug;
24use markup5ever::{local_name, namespace_prefix, ns, small_char_set, TokenizerResult};
25use std::borrow::Cow::{self, Borrowed};
26use std::cell::{Cell, RefCell, RefMut};
27use std::cmp::Reverse;
28use std::collections::BTreeMap;
29use std::mem::replace;
30
31use buffer_queue::{BufferQueue, FromSet, NotFromSet, SetResult};
32use char_ref::{CharRef, CharRefTokenizer};
33use qname::QualNameTokenizer;
34use states::{AttrValueKind::*, DoctypeKind, DoctypeKind::*, XmlState};
35
36/// Copy of Tokenizer options, with an impl for `Default`.
37#[derive(Copy, Clone)]
38pub struct XmlTokenizerOpts {
39    /// Report all parse errors described in the spec, at some
40    /// performance penalty?  Default: false
41    pub exact_errors: bool,
42
43    /// Discard a `U+FEFF BYTE ORDER MARK` if we see one at the beginning
44    /// of the stream?  Default: true
45    pub discard_bom: bool,
46
47    /// Keep a record of how long we spent in each state?  Printed
48    /// when `end()` is called.  Default: false
49    pub profile: bool,
50
51    /// Initial state override.  Only the test runner should use
52    /// a non-`None` value!
53    pub initial_state: Option<XmlState>,
54}
55
56fn process_qname(tag_name: StrTendril) -> QualName {
57    // If tag name can't possibly contain full namespace, skip qualified name
58    // parsing altogether. For a tag to have namespace it must look like:
59    //     a:b
60    // Since StrTendril are UTF-8, we know that minimal size in bytes must be
61    // three bytes minimum.
62    let split = if (*tag_name).len() < 3 {
63        None
64    } else {
65        QualNameTokenizer::new((*tag_name).as_bytes()).run()
66    };
67
68    match split {
69        None => QualName::new(None, ns!(), LocalName::from(&*tag_name)),
70        Some(col) => {
71            let len = (*tag_name).len() as u32;
72            let prefix = tag_name.subtendril(0, col);
73            let local = tag_name.subtendril(col + 1, len - col - 1);
74            let ns = ns!(); // Actual namespace URL set in XmlTreeBuilder::bind_qname
75            QualName::new(Some(Prefix::from(&*prefix)), ns, LocalName::from(&*local))
76        },
77    }
78}
79
80fn option_push(opt_str: &mut Option<StrTendril>, c: char) {
81    match *opt_str {
82        Some(ref mut s) => s.push_char(c),
83        None => *opt_str = Some(StrTendril::from_char(c)),
84    }
85}
86
87impl Default for XmlTokenizerOpts {
88    fn default() -> XmlTokenizerOpts {
89        XmlTokenizerOpts {
90            exact_errors: false,
91            discard_bom: true,
92            profile: false,
93            initial_state: None,
94        }
95    }
96}
97/// The Xml tokenizer.
98pub struct XmlTokenizer<Sink> {
99    /// Options controlling the behavior of the tokenizer.
100    opts: XmlTokenizerOpts,
101
102    /// Destination for tokens we emit.
103    pub sink: Sink,
104
105    /// The abstract machine state as described in the spec.
106    state: Cell<XmlState>,
107
108    /// Are we at the end of the file, once buffers have been processed
109    /// completely? This affects whether we will wait for lookahead or not.
110    at_eof: Cell<bool>,
111
112    /// Tokenizer for character references, if we're tokenizing
113    /// one at the moment.
114    char_ref_tokenizer: RefCell<Option<Box<CharRefTokenizer>>>,
115
116    /// Current input character.  Just consumed, may reconsume.
117    current_char: Cell<char>,
118
119    /// Should we reconsume the current input character?
120    reconsume: Cell<bool>,
121
122    /// Did we just consume \r, translating it to \n?  In that case we need
123    /// to ignore the next character if it's \n.
124    ignore_lf: Cell<bool>,
125
126    /// Discard a U+FEFF BYTE ORDER MARK if we see one?  Only done at the
127    /// beginning of the stream.
128    discard_bom: Cell<bool>,
129
130    /// Temporary buffer
131    temp_buf: RefCell<StrTendril>,
132
133    /// Current tag kind.
134    current_tag_kind: Cell<TagKind>,
135
136    /// Current tag name.
137    current_tag_name: RefCell<StrTendril>,
138
139    /// Current tag attributes.
140    current_tag_attrs: RefCell<Vec<Attribute>>,
141
142    /// Current attribute name.
143    current_attr_name: RefCell<StrTendril>,
144
145    /// Current attribute value.
146    current_attr_value: RefCell<StrTendril>,
147
148    current_doctype: RefCell<Doctype>,
149
150    /// Current comment.
151    current_comment: RefCell<StrTendril>,
152
153    /// Current processing instruction target.
154    current_pi_target: RefCell<StrTendril>,
155
156    /// Current processing instruction value.
157    current_pi_data: RefCell<StrTendril>,
158
159    /// Record of how many ns we spent in each state, if profiling is enabled.
160    state_profile: RefCell<BTreeMap<XmlState, u64>>,
161
162    /// Record of how many ns we spent in the token sink.
163    time_in_sink: Cell<u64>,
164}
165
166impl<Sink: TokenSink> XmlTokenizer<Sink> {
167    /// Create a new tokenizer which feeds tokens to a particular `TokenSink`.
168    pub fn new(sink: Sink, opts: XmlTokenizerOpts) -> XmlTokenizer<Sink> {
169        if opts.profile && cfg!(for_c) {
170            panic!("Can't profile tokenizer when built as a C library");
171        }
172
173        let state = *opts.initial_state.as_ref().unwrap_or(&XmlState::Data);
174        let discard_bom = opts.discard_bom;
175        XmlTokenizer {
176            opts,
177            sink,
178            state: Cell::new(state),
179            char_ref_tokenizer: RefCell::new(None),
180            at_eof: Cell::new(false),
181            current_char: Cell::new('\0'),
182            reconsume: Cell::new(false),
183            ignore_lf: Cell::new(false),
184            temp_buf: RefCell::new(StrTendril::new()),
185            discard_bom: Cell::new(discard_bom),
186            current_tag_kind: Cell::new(StartTag),
187            current_tag_name: RefCell::new(StrTendril::new()),
188            current_tag_attrs: RefCell::new(vec![]),
189            current_attr_name: RefCell::new(StrTendril::new()),
190            current_attr_value: RefCell::new(StrTendril::new()),
191            current_comment: RefCell::new(StrTendril::new()),
192            current_pi_data: RefCell::new(StrTendril::new()),
193            current_pi_target: RefCell::new(StrTendril::new()),
194            current_doctype: RefCell::new(Doctype::default()),
195            state_profile: RefCell::new(BTreeMap::new()),
196            time_in_sink: Cell::new(0),
197        }
198    }
199
200    /// Feed an input string into the tokenizer.
201    pub fn feed(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
202        if input.is_empty() {
203            return TokenizerResult::Done;
204        }
205
206        if self.discard_bom.get() {
207            if let Some(c) = input.peek() {
208                if c == '\u{feff}' {
209                    input.next();
210                }
211            } else {
212                return TokenizerResult::Done;
213            }
214        };
215
216        self.run(input)
217    }
218
219    fn process_token(&self, token: Token) -> ProcessResult<Sink::Handle> {
220        if self.opts.profile {
221            let (result, dt) = time!(self.sink.process_token(token));
222            self.time_in_sink.set(self.time_in_sink.get() + dt);
223            result
224        } else {
225            self.sink.process_token(token)
226        }
227    }
228
229    // Get the next input character, which might be the character
230    // 'c' that we already consumed from the buffers.
231    fn get_preprocessed_char(&self, mut c: char, input: &BufferQueue) -> Option<char> {
232        if self.ignore_lf.get() {
233            self.ignore_lf.set(false);
234            if c == '\n' {
235                c = input.next()?;
236            }
237        }
238
239        if c == '\r' {
240            self.ignore_lf.set(true);
241            c = '\n';
242        }
243
244        // Normalize \x00 into \uFFFD
245        if c == '\x00' {
246            c = '\u{FFFD}'
247        }
248
249        // Exclude forbidden Unicode characters
250        if self.opts.exact_errors
251            && match c as u32 {
252                0x01..=0x08 | 0x0B | 0x0E..=0x1F | 0x7F..=0x9F | 0xFDD0..=0xFDEF => true,
253                n if (n & 0xFFFE) == 0xFFFE => true,
254                _ => false,
255            }
256        {
257            let msg = format!("Bad character {c}");
258            self.emit_error(Cow::Owned(msg));
259        }
260
261        debug!("got character {c}");
262        self.current_char.set(c);
263        Some(c)
264    }
265
266    fn bad_eof_error(&self) {
267        let msg = if self.opts.exact_errors {
268            Cow::from(format!("Saw EOF in state {:?}", self.state))
269        } else {
270            Cow::from("Unexpected EOF")
271        };
272        self.emit_error(msg);
273    }
274
275    fn pop_except_from(&self, input: &BufferQueue, set: SmallCharSet) -> Option<SetResult> {
276        // Bail to the slow path for various corner cases.
277        // This means that `FromSet` can contain characters not in the set!
278        // It shouldn't matter because the fallback `FromSet` case should
279        // always do the same thing as the `NotFromSet` case.
280        if self.opts.exact_errors || self.reconsume.get() || self.ignore_lf.get() {
281            return self.get_char(input).map(FromSet);
282        }
283
284        let d = input.pop_except_from(set);
285        debug!("got characters {d:?}");
286        match d {
287            Some(FromSet(c)) => self.get_preprocessed_char(c, input).map(FromSet),
288
289            // NB: We don't set self.current_char for a run of characters not
290            // in the set.  It shouldn't matter for the codepaths that use
291            // this.
292            _ => d,
293        }
294    }
295
296    // Check if the next characters are an ASCII case-insensitive match.  See
297    // BufferQueue::eat.
298    //
299    // NB: this doesn't do input stream preprocessing or set the current input
300    // character.
301    fn eat(&self, input: &BufferQueue, pat: &str) -> Option<bool> {
302        input.push_front(replace(&mut *self.temp_buf.borrow_mut(), StrTendril::new()));
303        match input.eat(pat, u8::eq_ignore_ascii_case) {
304            None if self.at_eof.get() => Some(false),
305            None => {
306                let mut temp_buf = self.temp_buf.borrow_mut();
307                while let Some(data) = input.next() {
308                    temp_buf.push_char(data);
309                }
310                None
311            },
312            Some(matched) => Some(matched),
313        }
314    }
315
316    /// Run the state machine for as long as we can.
317    pub fn run(&self, input: &BufferQueue) -> TokenizerResult<Sink::Handle> {
318        if self.opts.profile {
319            loop {
320                let state = self.state.get();
321                let old_sink = self.time_in_sink.get();
322                let (run, mut dt) = time!(self.step(input));
323                dt -= self.time_in_sink.get() - old_sink;
324                let new = match self.state_profile.borrow_mut().get_mut(&state) {
325                    Some(x) => {
326                        *x += dt;
327                        false
328                    },
329                    None => true,
330                };
331                if new {
332                    // do this here because of borrow shenanigans
333                    self.state_profile.borrow_mut().insert(state, dt);
334                }
335                match run {
336                    ProcessResult::Continue => continue,
337                    ProcessResult::Done => return TokenizerResult::Done,
338                    ProcessResult::Script(handle) => return TokenizerResult::Script(handle),
339                }
340            }
341        } else {
342            loop {
343                match self.step(input) {
344                    ProcessResult::Continue => continue,
345                    ProcessResult::Done => return TokenizerResult::Done,
346                    ProcessResult::Script(handle) => return TokenizerResult::Script(handle),
347                }
348            }
349        }
350    }
351
352    //§ tokenization
353    // Get the next input character, if one is available.
354    fn get_char(&self, input: &BufferQueue) -> Option<char> {
355        if self.reconsume.get() {
356            self.reconsume.set(false);
357            Some(self.current_char.get())
358        } else {
359            input
360                .next()
361                .and_then(|c| self.get_preprocessed_char(c, input))
362        }
363    }
364
365    fn bad_char_error(&self) {
366        let msg = if self.opts.exact_errors {
367            let c = self.current_char.get();
368            let state = self.state.get();
369            Cow::from(format!("Saw {c} in state {state:?}"))
370        } else {
371            Cow::from("Bad character")
372        };
373        self.emit_error(msg);
374    }
375
376    fn discard_tag(&self) {
377        *self.current_tag_name.borrow_mut() = StrTendril::new();
378        *self.current_tag_attrs.borrow_mut() = Vec::new();
379    }
380
381    fn create_tag(&self, kind: TagKind, c: char) {
382        self.discard_tag();
383        self.current_tag_name.borrow_mut().push_char(c);
384        self.current_tag_kind.set(kind);
385    }
386
387    // This method creates a PI token and
388    // sets its target to given char
389    fn create_pi(&self, c: char) {
390        *self.current_pi_target.borrow_mut() = StrTendril::new();
391        *self.current_pi_data.borrow_mut() = StrTendril::new();
392        self.current_pi_target.borrow_mut().push_char(c);
393    }
394
395    fn emit_char(&self, c: char) {
396        self.process_token(Token::Characters(StrTendril::from_char(match c {
397            '\0' => '\u{FFFD}',
398            c => c,
399        })));
400    }
401
402    fn emit_short_tag(&self) -> ProcessResult<Sink::Handle> {
403        self.current_tag_kind.set(ShortTag);
404        *self.current_tag_name.borrow_mut() = StrTendril::new();
405        self.emit_current_tag()
406    }
407
408    fn emit_empty_tag(&self) -> ProcessResult<Sink::Handle> {
409        self.current_tag_kind.set(EmptyTag);
410        self.emit_current_tag()
411    }
412
413    fn set_empty_tag(&self) {
414        self.current_tag_kind.set(EmptyTag);
415    }
416
417    fn emit_start_tag(&self) -> ProcessResult<Sink::Handle> {
418        self.current_tag_kind.set(StartTag);
419        self.emit_current_tag()
420    }
421
422    fn emit_current_tag(&self) -> ProcessResult<Sink::Handle> {
423        self.finish_attribute();
424
425        let qname = process_qname(replace(
426            &mut *self.current_tag_name.borrow_mut(),
427            StrTendril::new(),
428        ));
429
430        match self.current_tag_kind.get() {
431            StartTag | EmptyTag => {},
432            EndTag => {
433                if !self.current_tag_attrs.borrow().is_empty() {
434                    self.emit_error(Borrowed("Attributes on an end tag"));
435                }
436            },
437            ShortTag => {
438                if !self.current_tag_attrs.borrow().is_empty() {
439                    self.emit_error(Borrowed("Attributes on a short tag"));
440                }
441            },
442        }
443
444        let token = Token::Tag(Tag {
445            kind: self.current_tag_kind.get(),
446            name: qname,
447            attrs: self.current_tag_attrs.take(),
448        });
449
450        self.process_token(token)
451    }
452
453    // The string must not contain '\0'!
454    fn emit_chars(&self, b: StrTendril) {
455        self.process_token(Token::Characters(b));
456    }
457
458    // Emits the current Processing Instruction
459    fn emit_pi(&self) -> ProcessResult<<Sink as TokenSink>::Handle> {
460        let token = Token::ProcessingInstruction(Pi {
461            target: replace(&mut *self.current_pi_target.borrow_mut(), StrTendril::new()),
462            data: replace(&mut *self.current_pi_data.borrow_mut(), StrTendril::new()),
463        });
464        self.process_token(token)
465    }
466
467    fn consume_char_ref(&self, addnl_allowed: Option<char>) {
468        // NB: The char ref tokenizer assumes we have an additional allowed
469        // character iff we're tokenizing in an attribute value.
470        *self.char_ref_tokenizer.borrow_mut() =
471            Some(Box::new(CharRefTokenizer::new(addnl_allowed)));
472    }
473
474    fn emit_eof(&self) {
475        self.process_token(Token::EndOfFile);
476    }
477
478    fn emit_error(&self, error: Cow<'static, str>) {
479        self.process_token(Token::ParseError(error));
480    }
481
482    fn emit_current_comment(&self) {
483        let comment = self.current_comment.take();
484        self.process_token(Token::Comment(comment));
485    }
486
487    fn emit_current_doctype(&self) {
488        let doctype = self.current_doctype.take();
489        self.process_token(Token::Doctype(doctype));
490    }
491
492    fn doctype_id(&self, kind: DoctypeKind) -> RefMut<'_, Option<StrTendril>> {
493        let current_doctype = self.current_doctype.borrow_mut();
494        match kind {
495            DoctypeKind::Public => RefMut::map(current_doctype, |d| &mut d.public_id),
496            DoctypeKind::System => RefMut::map(current_doctype, |d| &mut d.system_id),
497        }
498    }
499
500    fn clear_doctype_id(&self, kind: DoctypeKind) {
501        let mut id = self.doctype_id(kind);
502        match *id {
503            Some(ref mut s) => s.clear(),
504            None => *id = Some(StrTendril::new()),
505        }
506    }
507
508    fn peek(&self, input: &BufferQueue) -> Option<char> {
509        if self.reconsume.get() {
510            Some(self.current_char.get())
511        } else {
512            input.peek()
513        }
514    }
515
516    fn discard_char(&self, input: &BufferQueue) {
517        let c = self.get_char(input);
518        assert!(c.is_some());
519    }
520
521    fn unconsume(&self, input: &BufferQueue, buf: StrTendril) {
522        input.push_front(buf);
523    }
524}
525
526// Shorthand for common state machine behaviors.
527macro_rules! shorthand (
528    ( $me:ident : emit $c:expr                     ) => ( $me.emit_char($c)                                   );
529    ( $me:ident : create_tag $kind:ident $c:expr   ) => ( $me.create_tag($kind, $c)                           );
530    ( $me:ident : push_tag $c:expr                 ) => ( $me.current_tag_name.borrow_mut().push_char($c)     );
531    ( $me:ident : discard_tag $input:expr          ) => ( $me.discard_tag($input)                             );
532    ( $me:ident : discard_char                     ) => ( $me.discard_char()                                  );
533    ( $me:ident : push_temp $c:expr                ) => ( $me.temp_buf.borrow_mut().push_char($c)             );
534    ( $me:ident : emit_temp                        ) => ( $me.emit_temp_buf()                                 );
535    ( $me:ident : clear_temp                       ) => ( $me.clear_temp_buf()                                );
536    ( $me:ident : create_attr $c:expr              ) => ( $me.create_attribute($c)                            );
537    ( $me:ident : push_name $c:expr                ) => ( $me.current_attr_name.borrow_mut().push_char($c)    );
538    ( $me:ident : push_value $c:expr               ) => ( $me.current_attr_value.borrow_mut().push_char($c)   );
539    ( $me:ident : append_value $c:expr             ) => ( $me.current_attr_value.borrow_mut().push_tendril($c));
540    ( $me:ident : push_comment $c:expr             ) => ( $me.current_comment.borrow_mut().push_char($c)      );
541    ( $me:ident : append_comment $c:expr           ) => ( $me.current_comment.borrow_mut().push_slice($c)     );
542    ( $me:ident : emit_comment                     ) => ( $me.emit_current_comment()                          );
543    ( $me:ident : clear_comment                    ) => ( $me.current_comment.borrow_mut().clear()            );
544    ( $me:ident : create_doctype                   ) => ( *$me.current_doctype.borrow_mut() = Doctype::default() );
545    ( $me:ident : push_doctype_name $c:expr        ) => ( option_push(&mut $me.current_doctype.borrow_mut().name, $c) );
546    ( $me:ident : push_doctype_id $k:ident $c:expr ) => ( option_push(&mut $me.doctype_id($k), $c)            );
547    ( $me:ident : clear_doctype_id $k:ident        ) => ( $me.clear_doctype_id($k)                            );
548    ( $me:ident : emit_doctype                     ) => ( $me.emit_current_doctype()                          );
549    ( $me:ident : error                            ) => ( $me.bad_char_error()                                );
550    ( $me:ident : error_eof                        ) => ( $me.bad_eof_error()                                 );
551    ( $me:ident : create_pi $c:expr                ) => ( $me.create_pi($c)                                   );
552    ( $me:ident : push_pi_target $c:expr           ) => ( $me.current_pi_target.borrow_mut().push_char($c)    );
553    ( $me:ident : push_pi_data $c:expr             ) => ( $me.current_pi_data.borrow_mut().push_char($c)      );
554    ( $me:ident : set_empty_tag                    ) => ( $me.set_empty_tag()                                 );
555);
556
557// Tracing of tokenizer actions.  This adds significant bloat and compile time,
558// so it's behind a cfg flag.
559#[cfg(feature = "trace_tokenizer")]
560macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ({
561    debug!("  {:?}", stringify!($($cmds)*));
562    shorthand!($me : $($cmds)*);
563}));
564
565#[cfg(not(feature = "trace_tokenizer"))]
566macro_rules! sh_trace ( ( $me:ident : $($cmds:tt)* ) => ( shorthand!($me: $($cmds)*) ) );
567
568// A little DSL for sequencing shorthand actions.
569macro_rules! go (
570    // A pattern like $($cmd:tt)* ; $($rest:tt)* causes parse ambiguity.
571    // We have to tell the parser how much lookahead we need.
572
573    ( $me:ident : $a:tt                   ; $($rest:tt)* ) => ({ sh_trace!($me: $a);          go!($me: $($rest)*); });
574    ( $me:ident : $a:tt $b:tt             ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b);       go!($me: $($rest)*); });
575    ( $me:ident : $a:tt $b:tt $c:tt       ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c);    go!($me: $($rest)*); });
576    ( $me:ident : $a:tt $b:tt $c:tt $d:tt ; $($rest:tt)* ) => ({ sh_trace!($me: $a $b $c $d); go!($me: $($rest)*); });
577
578    // These can only come at the end.
579
580    ( $me:ident : to $s:expr                   ) => ({ $me.state.set($s); return ProcessResult::Continue;                  });
581    ( $me:ident : reconsume $s:expr            ) => ({ $me.reconsume.set(true); go!($me: to $s);                           });
582    ( $me:ident : consume_char_ref             ) => ({ $me.consume_char_ref(None); return ProcessResult::Continue;         });
583    ( $me:ident : consume_char_ref $addnl:expr ) => ({ $me.consume_char_ref(Some($addnl)); return ProcessResult::Continue; });
584
585    // We have a default next state after emitting a tag, but the sink can override.
586    ( $me:ident : emit_tag $s:expr ) => ({
587        $me.state.set($s);
588        return $me.emit_current_tag();
589    });
590
591    // We have a special when dealing with empty and short tags in Xml
592    ( $me:ident : emit_short_tag $s:expr ) => ({
593        $me.state.set($s);
594        return $me.emit_short_tag();
595    });
596
597    ( $me:ident : emit_empty_tag $s:ident ) => ({
598        $me.state.set(XmlState::$s);
599        return $me.emit_empty_tag();
600    });
601
602    ( $me:ident : emit_start_tag $s:ident ) => ({
603        $me.state.set(XmlState::$s);
604        return $me.emit_start_tag();
605    });
606
607    ( $me:ident : emit_pi $s:ident ) => ({
608        $me.state.set(XmlState::$s);
609        return $me.emit_pi();
610    });
611
612    ( $me:ident : eof ) => ({ $me.emit_eof(); return ProcessResult::Done; });
613
614    // If nothing else matched, it's a single command
615    ( $me:ident : $($cmd:tt)+ ) => ( sh_trace!($me: $($cmd)+) );
616
617    // or nothing.
618    ( $me:ident : ) => (());
619);
620
621// This is a macro because it can cause early return
622// from the function where it is used.
623macro_rules! get_char ( ($me:expr, $input:expr) => {{
624    let Some(character) = $me.get_char($input) else {
625        return ProcessResult::Done;
626    };
627    character
628}});
629
630macro_rules! eat ( ($me:expr, $input:expr, $pat:expr) => {{
631    let Some(value) = $me.eat($input, $pat) else {
632        return ProcessResult::Done;
633    };
634    value
635}});
636
637/// The result of a single tokenization operation
638pub enum ProcessResult<Handle> {
639    /// The tokenizer needs more input before it can continue
640    Done,
641    /// The tokenizer can be invoked again immediately
642    Continue,
643    /// The tokenizer encountered a script element that must be executed
644    /// before tokenization can continue
645    Script(Handle),
646}
647
648impl<Sink: TokenSink> XmlTokenizer<Sink> {
649    // Run the state machine for a while.
650    #[allow(clippy::never_loop)]
651    fn step(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
652        if self.char_ref_tokenizer.borrow().is_some() {
653            return self.step_char_ref_tokenizer(input);
654        }
655
656        debug!("processing in state {:?}", self.state);
657        match self.state.get() {
658            //§ data-state
659            XmlState::Data => loop {
660                let Some(popped_element) =
661                    self.pop_except_from(input, small_char_set!('\r' '&' '<'))
662                else {
663                    return ProcessResult::Done;
664                };
665
666                match popped_element {
667                    FromSet('&') => go!(self: consume_char_ref),
668                    FromSet('<') => go!(self: to XmlState::TagState),
669                    FromSet(c) => go!(self: emit c),
670                    NotFromSet(b) => self.emit_chars(b),
671                }
672            },
673            //§ tag-state
674            XmlState::TagState => loop {
675                match get_char!(self, input) {
676                    '!' => go!(self: to XmlState::MarkupDecl),
677                    '/' => go!(self: to XmlState::EndTagState),
678                    '?' => go!(self: to XmlState::Pi),
679                    '\t' | '\n' | ' ' | ':' | '<' | '>' => {
680                        go!(self: error; emit '<'; reconsume XmlState::Data)
681                    },
682                    cl => go!(self: create_tag StartTag cl; to XmlState::TagName),
683                }
684            },
685            //§ end-tag-state
686            XmlState::EndTagState => loop {
687                match get_char!(self, input) {
688                    '>' => go!(self:  emit_short_tag XmlState::Data),
689                    '\t' | '\n' | ' ' | '<' | ':' => {
690                        go!(self: error; emit '<'; emit '/'; reconsume XmlState::Data)
691                    },
692                    cl => go!(self: create_tag EndTag cl; to XmlState::EndTagName),
693                }
694            },
695            //§ end-tag-name-state
696            XmlState::EndTagName => loop {
697                match get_char!(self, input) {
698                    '\t' | '\n' | ' ' => go!(self: to XmlState::EndTagNameAfter),
699                    '/' => go!(self: error; to XmlState::EndTagNameAfter),
700                    '>' => go!(self: emit_tag XmlState::Data),
701                    cl => go!(self: push_tag cl),
702                }
703            },
704            //§ end-tag-name-after-state
705            XmlState::EndTagNameAfter => loop {
706                match get_char!(self, input) {
707                    '>' => go!(self: emit_tag XmlState::Data),
708                    '\t' | '\n' | ' ' => (),
709                    _ => self.emit_error(Borrowed("Unexpected element in tag name")),
710                }
711            },
712            //§ pi-state
713            XmlState::Pi => loop {
714                match get_char!(self, input) {
715                    '\t' | '\n' | ' ' => go!(self: error; reconsume XmlState::BogusComment),
716                    cl => go!(self: create_pi cl; to XmlState::PiTarget),
717                }
718            },
719            //§ pi-target-state
720            XmlState::PiTarget => loop {
721                match get_char!(self, input) {
722                    '\t' | '\n' | ' ' => go!(self: to XmlState::PiTargetAfter),
723                    '?' => go!(self: to XmlState::PiAfter),
724                    cl => go!(self: push_pi_target cl),
725                }
726            },
727            //§ pi-target-after-state
728            XmlState::PiTargetAfter => loop {
729                match get_char!(self, input) {
730                    '\t' | '\n' | ' ' => (),
731                    _ => go!(self: reconsume XmlState::PiData),
732                }
733            },
734            //§ pi-data-state
735            XmlState::PiData => loop {
736                match get_char!(self, input) {
737                    '?' => go!(self: to XmlState::PiAfter),
738                    cl => go!(self: push_pi_data cl),
739                }
740            },
741            //§ pi-after-state
742            XmlState::PiAfter => loop {
743                match get_char!(self, input) {
744                    '>' => go!(self: emit_pi Data),
745                    // The '?' that got us here is not part of a '?>', so it is
746                    // ordinary data. The one we just read may still close the
747                    // processing instruction, so stay in this state.
748                    '?' => go!(self: push_pi_data '?'; to XmlState::PiAfter),
749                    cl => go!(self: push_pi_data '?'; push_pi_data cl; to XmlState::PiData),
750                }
751            },
752            //§ markup-declaration-state
753            XmlState::MarkupDecl => loop {
754                if eat!(self, input, "--") {
755                    go!(self: clear_comment; to XmlState::CommentStart);
756                } else if eat!(self, input, "[CDATA[") {
757                    go!(self: to XmlState::Cdata);
758                } else if eat!(self, input, "DOCTYPE") {
759                    go!(self: to XmlState::Doctype);
760                } else {
761                    // FIXME: 'error' gives wrong message
762                    go!(self: error; to XmlState::BogusComment);
763                }
764            },
765            //§ comment-start-state
766            XmlState::CommentStart => loop {
767                match get_char!(self, input) {
768                    '-' => go!(self: to XmlState::CommentStartDash),
769                    '>' => go!(self: error; emit_comment; to XmlState::Data),
770                    _ => go!(self: reconsume XmlState::Comment),
771                }
772            },
773            //§ comment-start-dash-state
774            XmlState::CommentStartDash => loop {
775                match get_char!(self, input) {
776                    '-' => go!(self: to XmlState::CommentEnd),
777                    '>' => go!(self: error; emit_comment; to XmlState::Data),
778                    _ => go!(self: push_comment '-'; reconsume XmlState::Comment),
779                }
780            },
781            //§ comment-state
782            XmlState::Comment => loop {
783                match get_char!(self, input) {
784                    '<' => go!(self: push_comment '<'; to XmlState::CommentLessThan),
785                    '-' => go!(self: to XmlState::CommentEndDash),
786                    c => go!(self: push_comment c),
787                }
788            },
789            //§ comment-less-than-sign-state
790            XmlState::CommentLessThan => loop {
791                match get_char!(self, input) {
792                    '!' => go!(self: push_comment '!';to XmlState::CommentLessThanBang),
793                    '<' => go!(self: push_comment '<'),
794                    _ => go!(self: reconsume XmlState::Comment),
795                }
796            },
797            //§ comment-less-than-sign-bang-state
798            XmlState::CommentLessThanBang => loop {
799                match get_char!(self, input) {
800                    '-' => go!(self: to XmlState::CommentLessThanBangDash),
801                    _ => go!(self: reconsume XmlState::Comment),
802                }
803            },
804            //§ comment-less-than-sign-bang-dash-state
805            XmlState::CommentLessThanBangDash => loop {
806                match get_char!(self, input) {
807                    '-' => go!(self: to XmlState::CommentLessThanBangDashDash),
808                    _ => go!(self: reconsume XmlState::CommentEndDash),
809                }
810            },
811            //§ comment-less-than-sign-bang-dash-dash-state
812            XmlState::CommentLessThanBangDashDash => loop {
813                match get_char!(self, input) {
814                    '>' => go!(self: reconsume XmlState::CommentEnd),
815                    _ => go!(self: error; reconsume XmlState::CommentEnd),
816                }
817            },
818            //§ comment-end-dash-state
819            XmlState::CommentEndDash => loop {
820                match get_char!(self, input) {
821                    '-' => go!(self: to XmlState::CommentEnd),
822                    _ => go!(self: push_comment '-'; reconsume XmlState::Comment),
823                }
824            },
825            //§ comment-end-state
826            XmlState::CommentEnd => loop {
827                match get_char!(self, input) {
828                    '>' => go!(self: emit_comment; to XmlState::Data),
829                    '!' => go!(self: to XmlState::CommentEndBang),
830                    '-' => go!(self: push_comment '-'),
831                    _ => go!(self: append_comment "--"; reconsume XmlState::Comment),
832                }
833            },
834            //§ comment-end-bang-state
835            XmlState::CommentEndBang => loop {
836                match get_char!(self, input) {
837                    '-' => go!(self: append_comment "--!"; to XmlState::CommentEndDash),
838                    '>' => go!(self: error; emit_comment; to XmlState::Data),
839                    _ => go!(self: append_comment "--!"; reconsume XmlState::Comment),
840                }
841            },
842            //§ bogus-comment-state
843            XmlState::BogusComment => loop {
844                match get_char!(self, input) {
845                    '>' => go!(self: emit_comment; to XmlState::Data),
846                    c => go!(self: push_comment c),
847                }
848            },
849            //§ cdata-state
850            XmlState::Cdata => loop {
851                match get_char!(self, input) {
852                    ']' => go!(self: to XmlState::CdataBracket),
853                    cl => go!(self: emit cl),
854                }
855            },
856            //§ cdata-bracket-state
857            XmlState::CdataBracket => loop {
858                match get_char!(self, input) {
859                    ']' => go!(self: to XmlState::CdataEnd),
860                    cl => go!(self: emit ']'; emit cl; to XmlState::Cdata),
861                }
862            },
863            //§ cdata-end-state
864            XmlState::CdataEnd => loop {
865                match get_char!(self, input) {
866                    '>' => go!(self: to XmlState::Data),
867                    ']' => go!(self: emit ']'),
868                    cl => go!(self: emit ']'; emit ']'; emit cl; to XmlState::Cdata),
869                }
870            },
871            //§ tag-name-state
872            XmlState::TagName => loop {
873                match get_char!(self, input) {
874                    '\t' | '\n' | ' ' => go!(self: to XmlState::TagAttrNameBefore),
875                    '>' => go!(self: emit_tag XmlState::Data),
876                    '/' => go!(self: set_empty_tag; to XmlState::TagEmpty),
877                    cl => go!(self: push_tag cl),
878                }
879            },
880            //§ empty-tag-state
881            XmlState::TagEmpty => loop {
882                match get_char!(self, input) {
883                    '>' => go!(self: emit_empty_tag Data),
884                    _ => go!(self: reconsume XmlState::TagAttrValueBefore),
885                }
886            },
887            //§ tag-attribute-name-before-state
888            XmlState::TagAttrNameBefore => loop {
889                match get_char!(self, input) {
890                    '\t' | '\n' | ' ' => (),
891                    '>' => go!(self: emit_tag XmlState::Data),
892                    '/' => go!(self: set_empty_tag; to XmlState::TagEmpty),
893                    ':' => go!(self: error),
894                    cl => go!(self: create_attr cl; to XmlState::TagAttrName),
895                }
896            },
897            //§ tag-attribute-name-state
898            XmlState::TagAttrName => loop {
899                match get_char!(self, input) {
900                    '=' => go!(self: to XmlState::TagAttrValueBefore),
901                    '>' => go!(self: emit_tag XmlState::Data),
902                    '\t' | '\n' | ' ' => go!(self: to XmlState::TagAttrNameAfter),
903                    '/' => go!(self: set_empty_tag; to XmlState::TagEmpty),
904                    cl => go!(self: push_name cl),
905                }
906            },
907            //§ tag-attribute-name-after-state
908            XmlState::TagAttrNameAfter => loop {
909                match get_char!(self, input) {
910                    '\t' | '\n' | ' ' => (),
911                    '=' => go!(self: to XmlState::TagAttrValueBefore),
912                    '>' => go!(self: emit_tag XmlState::Data),
913                    '/' => go!(self: set_empty_tag; to XmlState::TagEmpty),
914                    cl => go!(self: create_attr cl; to XmlState::TagAttrName),
915                }
916            },
917            //§ tag-attribute-value-before-state
918            XmlState::TagAttrValueBefore => loop {
919                match get_char!(self, input) {
920                    '\t' | '\n' | ' ' => (),
921                    '"' => go!(self: to XmlState::TagAttrValue(DoubleQuoted)),
922                    '\'' => go!(self: to XmlState::TagAttrValue(SingleQuoted)),
923                    '&' => go!(self: reconsume XmlState::TagAttrValue(Unquoted)),
924                    '>' => go!(self: emit_tag XmlState::Data),
925                    cl => go!(self: push_value cl; to XmlState::TagAttrValue(Unquoted)),
926                }
927            },
928            //§ tag-attribute-value-double-quoted-state
929            XmlState::TagAttrValue(DoubleQuoted) => loop {
930                let Some(popped_element) =
931                    self.pop_except_from(input, small_char_set!('\n' '"' '&'))
932                else {
933                    return ProcessResult::Done;
934                };
935
936                match popped_element {
937                    FromSet('"') => go!(self: to XmlState::TagAttrNameBefore),
938                    FromSet('&') => go!(self: consume_char_ref '"' ),
939                    FromSet(c) => go!(self: push_value c),
940                    NotFromSet(ref b) => go!(self: append_value b),
941                }
942            },
943            //§ tag-attribute-value-single-quoted-state
944            XmlState::TagAttrValue(SingleQuoted) => loop {
945                let Some(popped_element) =
946                    self.pop_except_from(input, small_char_set!('\n' '\'' '&'))
947                else {
948                    return ProcessResult::Done;
949                };
950
951                match popped_element {
952                    FromSet('\'') => go!(self: to XmlState::TagAttrNameBefore),
953                    FromSet('&') => go!(self: consume_char_ref '\''),
954                    FromSet(c) => go!(self: push_value c),
955                    NotFromSet(ref b) => go!(self: append_value b),
956                }
957            },
958            //§ tag-attribute-value-double-quoted-state
959            XmlState::TagAttrValue(Unquoted) => loop {
960                let Some(popped_element) =
961                    self.pop_except_from(input, small_char_set!('\n' '\t' ' ' '&' '>'))
962                else {
963                    return ProcessResult::Done;
964                };
965
966                match popped_element {
967                    FromSet('\t') | FromSet('\n') | FromSet(' ') => {
968                        go!(self: to XmlState::TagAttrNameBefore)
969                    },
970                    FromSet('&') => go!(self: consume_char_ref),
971                    FromSet('>') => go!(self: emit_tag XmlState::Data),
972                    FromSet(c) => go!(self: push_value c),
973                    NotFromSet(ref b) => go!(self: append_value b),
974                }
975            },
976
977            //§ doctype-state
978            XmlState::Doctype => loop {
979                match get_char!(self, input) {
980                    '\t' | '\n' | '\x0C' | ' ' => go!(self: to XmlState::BeforeDoctypeName),
981                    _ => go!(self: error; reconsume XmlState::BeforeDoctypeName),
982                }
983            },
984            //§ before-doctype-name-state
985            XmlState::BeforeDoctypeName => loop {
986                match get_char!(self, input) {
987                    '\t' | '\n' | '\x0C' | ' ' => (),
988                    '>' => go!(self: error; emit_doctype; to XmlState::Data),
989                    c => go!(self: create_doctype; push_doctype_name (c.to_ascii_lowercase());
990                                  to XmlState::DoctypeName),
991                }
992            },
993            //§ doctype-name-state
994            XmlState::DoctypeName => loop {
995                match get_char!(self, input) {
996                    '\t' | '\n' | '\x0C' | ' ' => go!(self: to XmlState::AfterDoctypeName),
997                    '>' => go!(self: emit_doctype; to XmlState::Data),
998                    c => go!(self: push_doctype_name (c.to_ascii_lowercase());
999                                  to XmlState::DoctypeName),
1000                }
1001            },
1002            //§ after-doctype-name-state
1003            XmlState::AfterDoctypeName => loop {
1004                if eat!(self, input, "public") {
1005                    go!(self: to XmlState::AfterDoctypeKeyword(Public));
1006                } else if eat!(self, input, "system") {
1007                    go!(self: to XmlState::AfterDoctypeKeyword(System));
1008                } else {
1009                    match get_char!(self, input) {
1010                        '\t' | '\n' | '\x0C' | ' ' => (),
1011                        '>' => go!(self: emit_doctype; to XmlState::Data),
1012                        _ => go!(self: error; to XmlState::BogusDoctype),
1013                    }
1014                }
1015            },
1016            //§ after-doctype-public-keyword-state
1017            XmlState::AfterDoctypeKeyword(Public) => loop {
1018                match get_char!(self, input) {
1019                    '\t' | '\n' | '\x0C' | ' ' => {
1020                        go!(self: to XmlState::BeforeDoctypeIdentifier(Public))
1021                    },
1022                    '"' => {
1023                        go!(self: error; clear_doctype_id Public; to XmlState::DoctypeIdentifierDoubleQuoted(Public))
1024                    },
1025                    '\'' => {
1026                        go!(self: error; clear_doctype_id Public; to XmlState::DoctypeIdentifierSingleQuoted(Public))
1027                    },
1028                    '>' => go!(self: error; emit_doctype; to XmlState::Data),
1029                    _ => go!(self: error; to XmlState::BogusDoctype),
1030                }
1031            },
1032            //§ after-doctype-system-keyword-state
1033            XmlState::AfterDoctypeKeyword(System) => loop {
1034                match get_char!(self, input) {
1035                    '\t' | '\n' | '\x0C' | ' ' => {
1036                        go!(self: to XmlState::BeforeDoctypeIdentifier(System))
1037                    },
1038                    '"' => {
1039                        go!(self: error; clear_doctype_id System; to XmlState::DoctypeIdentifierDoubleQuoted(System))
1040                    },
1041                    '\'' => {
1042                        go!(self: error; clear_doctype_id System; to XmlState::DoctypeIdentifierSingleQuoted(System))
1043                    },
1044                    '>' => go!(self: error; emit_doctype; to XmlState::Data),
1045                    _ => go!(self: error; to XmlState::BogusDoctype),
1046                }
1047            },
1048            //§ before_doctype_public_identifier_state before_doctype_system_identifier_state
1049            XmlState::BeforeDoctypeIdentifier(kind) => loop {
1050                match get_char!(self, input) {
1051                    '\t' | '\n' | '\x0C' | ' ' => (),
1052                    '"' => {
1053                        go!(self: clear_doctype_id kind; to XmlState::DoctypeIdentifierDoubleQuoted(kind))
1054                    },
1055                    '\'' => {
1056                        go!(self: clear_doctype_id kind; to XmlState::DoctypeIdentifierSingleQuoted(kind))
1057                    },
1058                    '>' => go!(self: error; emit_doctype; to XmlState::Data),
1059                    _ => go!(self: error; to XmlState::BogusDoctype),
1060                }
1061            },
1062            //§ doctype_public_identifier_double_quoted_state doctype_system_identifier_double_quoted_state
1063            XmlState::DoctypeIdentifierDoubleQuoted(kind) => loop {
1064                match get_char!(self, input) {
1065                    '"' => go!(self: to XmlState::AfterDoctypeIdentifier(kind)),
1066                    '>' => go!(self: error; emit_doctype; to XmlState::Data),
1067                    c => go!(self: push_doctype_id kind c),
1068                }
1069            },
1070            //§ doctype_public_identifier_single_quoted_state doctype_system_identifier_single_quoted_state
1071            XmlState::DoctypeIdentifierSingleQuoted(kind) => loop {
1072                match get_char!(self, input) {
1073                    '\'' => go!(self: to XmlState::AfterDoctypeIdentifier(kind)),
1074                    '>' => go!(self: error; emit_doctype; to XmlState::Data),
1075                    c => go!(self: push_doctype_id kind c),
1076                }
1077            },
1078            //§ doctype_public_identifier_single_quoted_state
1079            XmlState::AfterDoctypeIdentifier(Public) => loop {
1080                match get_char!(self, input) {
1081                    '\t' | '\n' | '\x0C' | ' ' => {
1082                        go!(self: to XmlState::BetweenDoctypePublicAndSystemIdentifiers)
1083                    },
1084                    '\'' => {
1085                        go!(self: error; clear_doctype_id System; to XmlState::DoctypeIdentifierSingleQuoted(System))
1086                    },
1087                    '"' => {
1088                        go!(self: error; clear_doctype_id System; to XmlState::DoctypeIdentifierDoubleQuoted(System))
1089                    },
1090                    '>' => go!(self: emit_doctype; to XmlState::Data),
1091                    _ => go!(self: error; to XmlState::BogusDoctype),
1092                }
1093            },
1094            //§ doctype_system_identifier_single_quoted_state
1095            XmlState::AfterDoctypeIdentifier(System) => loop {
1096                match get_char!(self, input) {
1097                    '\t' | '\n' | '\x0C' | ' ' => (),
1098                    '>' => go!(self: emit_doctype; to XmlState::Data),
1099                    _ => go!(self: error; to XmlState::BogusDoctype),
1100                }
1101            },
1102            //§ between_doctype_public_and_system_identifier_state
1103            XmlState::BetweenDoctypePublicAndSystemIdentifiers => loop {
1104                match get_char!(self, input) {
1105                    '\t' | '\n' | '\x0C' | ' ' => (),
1106                    '>' => go!(self: emit_doctype; to XmlState::Data),
1107                    '\'' => go!(self: to XmlState::DoctypeIdentifierSingleQuoted(System)),
1108                    '"' => go!(self: to XmlState::DoctypeIdentifierDoubleQuoted(System)),
1109                    _ => go!(self: error; to XmlState::BogusDoctype),
1110                }
1111            },
1112            //§ bogus_doctype_state
1113            XmlState::BogusDoctype => loop {
1114                if get_char!(self, input) == '>' {
1115                    go!(self: emit_doctype; to XmlState::Data);
1116                }
1117            },
1118        }
1119    }
1120
1121    /// Indicate that we have reached the end of the input.
1122    pub fn end(&self) {
1123        // Handle EOF in the char ref sub-tokenizer, if there is one.
1124        // Do this first because it might un-consume stuff.
1125        let input = BufferQueue::default();
1126        match self.char_ref_tokenizer.take() {
1127            None => (),
1128            Some(mut tok) => {
1129                tok.end_of_file(self, &input);
1130                self.process_char_ref(tok.get_result());
1131            },
1132        }
1133
1134        // Process all remaining buffered input.
1135        // If we're waiting for lookahead, we're not gonna get it.
1136        self.at_eof.set(true);
1137        let _ = self.run(&input);
1138
1139        loop {
1140            if !matches!(self.eof_step(), ProcessResult::Continue) {
1141                break;
1142            }
1143        }
1144
1145        self.sink.end();
1146
1147        if self.opts.profile {
1148            self.dump_profile();
1149        }
1150    }
1151
1152    #[cfg(for_c)]
1153    fn dump_profile(&self) {
1154        unreachable!();
1155    }
1156
1157    #[cfg(not(for_c))]
1158    fn dump_profile(&self) {
1159        let mut results: Vec<(XmlState, u64)> = self
1160            .state_profile
1161            .borrow()
1162            .iter()
1163            .map(|(s, t)| (*s, *t))
1164            .collect();
1165        results.sort_by_key(|&(_, x)| Reverse(x));
1166
1167        let total: u64 = results.iter().map(|&(_, t)| t).sum();
1168        debug!("\nTokenizer profile, in nanoseconds");
1169        debug!(
1170            "\n{:12}         total in token sink",
1171            self.time_in_sink.get()
1172        );
1173        debug!("\n{total:12}         total in tokenizer");
1174
1175        for (k, v) in results.into_iter() {
1176            let pct = 100.0 * (v as f64) / (total as f64);
1177            debug!("{v:12}  {pct:4.1}%  {k:?}");
1178        }
1179    }
1180
1181    fn eof_step(&self) -> ProcessResult<Sink::Handle> {
1182        debug!("processing EOF in state {:?}", self.state.get());
1183        match self.state.get() {
1184            XmlState::Data => go!(self: eof),
1185            XmlState::CommentStart | XmlState::CommentLessThan | XmlState::CommentLessThanBang => {
1186                go!(self: reconsume XmlState::Comment)
1187            },
1188            XmlState::CommentLessThanBangDash => go!(self: reconsume XmlState::CommentEndDash),
1189            XmlState::CommentLessThanBangDashDash => go!(self: reconsume XmlState::CommentEnd),
1190            XmlState::CommentStartDash
1191            | XmlState::Comment
1192            | XmlState::CommentEndDash
1193            | XmlState::CommentEnd
1194            | XmlState::CommentEndBang => go!(self: error_eof; emit_comment; eof),
1195            XmlState::TagState => go!(self: error_eof; emit '<'; to XmlState::Data),
1196            XmlState::EndTagState => go!(self: error_eof; emit '<'; emit '/'; to XmlState::Data),
1197            XmlState::TagEmpty => go!(self: error_eof; to XmlState::TagAttrNameBefore),
1198            XmlState::Cdata | XmlState::CdataBracket | XmlState::CdataEnd => {
1199                go!(self: error_eof; to XmlState::Data)
1200            },
1201            XmlState::Pi => go!(self: error_eof; to XmlState::BogusComment),
1202            XmlState::PiTargetAfter => go!(self: reconsume XmlState::PiData),
1203            // The '?' we consumed to get here never became a '?>', so keep it.
1204            XmlState::PiAfter => go!(self: push_pi_data '?'; reconsume XmlState::PiData),
1205            XmlState::MarkupDecl => go!(self: error_eof; to XmlState::BogusComment),
1206            XmlState::TagName
1207            | XmlState::TagAttrNameBefore
1208            | XmlState::EndTagName
1209            | XmlState::TagAttrNameAfter
1210            | XmlState::EndTagNameAfter
1211            | XmlState::TagAttrValueBefore
1212            | XmlState::TagAttrValue(_) => go!(self: error_eof; emit_tag XmlState::Data),
1213            XmlState::PiData | XmlState::PiTarget => go!(self: error_eof; emit_pi Data),
1214            XmlState::TagAttrName => go!(self: error_eof; emit_start_tag Data),
1215            XmlState::BeforeDoctypeName
1216            | XmlState::Doctype
1217            | XmlState::DoctypeName
1218            | XmlState::AfterDoctypeName
1219            | XmlState::AfterDoctypeKeyword(_)
1220            | XmlState::BeforeDoctypeIdentifier(_)
1221            | XmlState::AfterDoctypeIdentifier(_)
1222            | XmlState::DoctypeIdentifierSingleQuoted(_)
1223            | XmlState::DoctypeIdentifierDoubleQuoted(_)
1224            | XmlState::BetweenDoctypePublicAndSystemIdentifiers => {
1225                go!(self: error_eof; emit_doctype; to XmlState::Data)
1226            },
1227            XmlState::BogusDoctype => go!(self: emit_doctype; to XmlState::Data),
1228            XmlState::BogusComment => go!(self: emit_comment; to XmlState::Data),
1229        }
1230    }
1231
1232    fn process_char_ref(&self, char_ref: CharRef) {
1233        let CharRef {
1234            mut chars,
1235            mut num_chars,
1236        } = char_ref;
1237
1238        if num_chars == 0 {
1239            chars[0] = '&';
1240            num_chars = 1;
1241        }
1242
1243        for i in 0..num_chars {
1244            let c = chars[i as usize];
1245            match self.state.get() {
1246                XmlState::Data | XmlState::Cdata => go!(self: emit c),
1247
1248                XmlState::TagAttrValue(_) => go!(self: push_value c),
1249
1250                _ => panic!(
1251                    "state {:?} should not be reachable in process_char_ref",
1252                    self.state.get()
1253                ),
1254            }
1255        }
1256    }
1257
1258    fn step_char_ref_tokenizer(&self, input: &BufferQueue) -> ProcessResult<Sink::Handle> {
1259        let mut tok = self.char_ref_tokenizer.take().unwrap();
1260        let outcome = tok.step(self, input);
1261
1262        let progress = match outcome {
1263            char_ref::Done => {
1264                self.process_char_ref(tok.get_result());
1265                return ProcessResult::Continue;
1266            },
1267
1268            char_ref::Stuck => ProcessResult::Done,
1269            char_ref::Progress => ProcessResult::Continue,
1270        };
1271
1272        *self.char_ref_tokenizer.borrow_mut() = Some(tok);
1273        progress
1274    }
1275
1276    fn finish_attribute(&self) {
1277        if self.current_attr_name.borrow().is_empty() {
1278            return;
1279        }
1280
1281        let qname = process_qname(replace(
1282            &mut self.current_attr_name.borrow_mut(),
1283            StrTendril::new(),
1284        ));
1285
1286        // Check for a duplicate attribute. Two attributes are the same only if
1287        // both their prefix and their local name match, so xml:lang and lang
1288        // are distinct names and may sit on the same element.
1289        // FIXME: the spec says we should error as soon as the name is finished.
1290        // FIXME: linear time search, do we care?
1291        let dup = self
1292            .current_tag_attrs
1293            .borrow()
1294            .iter()
1295            .any(|a| a.name.prefix == qname.prefix && a.name.local == qname.local);
1296
1297        if dup {
1298            self.emit_error(Borrowed("Duplicate attribute"));
1299            self.current_attr_value.borrow_mut().clear();
1300        } else {
1301            let attr = Attribute {
1302                name: qname.clone(),
1303                value: replace(&mut self.current_attr_value.borrow_mut(), StrTendril::new()),
1304            };
1305
1306            if qname.local == local_name!("xmlns")
1307                || qname.prefix == Some(namespace_prefix!("xmlns"))
1308            {
1309                self.current_tag_attrs.borrow_mut().insert(0, attr);
1310            } else {
1311                self.current_tag_attrs.borrow_mut().push(attr);
1312            }
1313        }
1314    }
1315
1316    fn create_attribute(&self, c: char) {
1317        self.finish_attribute();
1318
1319        self.current_attr_name.borrow_mut().push_char(c);
1320    }
1321}
1322
1323#[cfg(test)]
1324mod test {
1325
1326    use super::{process_qname, ProcessResult, Token, TokenSink, XmlTokenizer};
1327    use crate::tendril::{SliceExt, StrTendril};
1328    use crate::{LocalName, Prefix};
1329    use markup5ever::buffer_queue::BufferQueue;
1330    use std::cell::RefCell;
1331
1332    struct PiCollector {
1333        pis: RefCell<Vec<(String, String)>>,
1334    }
1335
1336    impl TokenSink for PiCollector {
1337        type Handle = ();
1338
1339        fn process_token(&self, token: Token) -> ProcessResult<()> {
1340            if let Token::ProcessingInstruction(pi) = token {
1341                self.pis
1342                    .borrow_mut()
1343                    .push((pi.target.to_string(), pi.data.to_string()));
1344            }
1345            ProcessResult::Continue
1346        }
1347    }
1348
1349    struct ErrorCollector {
1350        errors: RefCell<Vec<String>>,
1351    }
1352
1353    impl TokenSink for ErrorCollector {
1354        type Handle = ();
1355
1356        fn process_token(&self, token: Token) -> ProcessResult<()> {
1357            if let Token::ParseError(error) = token {
1358                self.errors.borrow_mut().push(error.to_string());
1359            }
1360            ProcessResult::Continue
1361        }
1362    }
1363
1364    fn tokenize_pis(input: &str) -> Vec<(String, String)> {
1365        let sink = PiCollector {
1366            pis: RefCell::new(Vec::new()),
1367        };
1368        let queue = BufferQueue::default();
1369        queue.push_back(StrTendril::from(input));
1370        let tokenizer = XmlTokenizer::new(sink, Default::default());
1371        let _ = tokenizer.feed(&queue);
1372        tokenizer.end();
1373        tokenizer.sink.pis.into_inner()
1374    }
1375
1376    fn tokenize_errors(input: &str) -> Vec<String> {
1377        let sink = ErrorCollector {
1378            errors: RefCell::new(Vec::new()),
1379        };
1380        let queue = BufferQueue::default();
1381        queue.push_back(StrTendril::from(input));
1382        let tokenizer = XmlTokenizer::new(sink, Default::default());
1383        let _ = tokenizer.feed(&queue);
1384        tokenizer.end();
1385        tokenizer.sink.errors.into_inner()
1386    }
1387
1388    #[test]
1389    fn pi_data_keeps_question_marks() {
1390        assert_eq!(
1391            tokenize_pis(r#"<?xml-stylesheet href="style.xsl?v=2"?>"#),
1392            vec![(
1393                "xml-stylesheet".to_owned(),
1394                r#"href="style.xsl?v=2""#.to_owned()
1395            )]
1396        );
1397
1398        assert_eq!(
1399            tokenize_pis("<?target a?b?c?>"),
1400            vec![("target".to_owned(), "a?b?c".to_owned())]
1401        );
1402
1403        // A run of question marks only ends the instruction when one of them
1404        // is followed by '>'.
1405        assert_eq!(
1406            tokenize_pis("<?target a???>"),
1407            vec![("target".to_owned(), "a??".to_owned())]
1408        );
1409    }
1410
1411    #[test]
1412    fn pi_only_ends_on_question_mark_gt() {
1413        // The '>' here is data, because the character before it is not a '?'.
1414        assert_eq!(
1415            tokenize_pis("<?target a?b>c?>"),
1416            vec![("target".to_owned(), "a?b>c".to_owned())]
1417        );
1418    }
1419
1420    #[test]
1421    fn pi_without_data_is_empty() {
1422        assert_eq!(
1423            tokenize_pis("<?target?>"),
1424            vec![("target".to_owned(), String::new())]
1425        );
1426
1427        assert_eq!(
1428            tokenize_pis("<?target ?>"),
1429            vec![("target".to_owned(), String::new())]
1430        );
1431    }
1432
1433    #[test]
1434    fn unterminated_pi_keeps_trailing_question_mark() {
1435        assert_eq!(
1436            tokenize_pis("<?target a?"),
1437            vec![("target".to_owned(), "a?".to_owned())]
1438        );
1439    }
1440
1441    #[test]
1442    fn qualified_and_unqualified_names_are_distinct() {
1443        // The xml prefix is bound to http://www.w3.org/XML/1998/namespace by
1444        // definition, so xml:lang and lang have different expanded names and
1445        // both orderings are fine. This is the second of the two legal cases
1446        // in https://www.w3.org/TR/REC-xml-names/#uniqAttrs
1447        assert!(tokenize_errors(r#"<root xml:lang="en" lang="en"/>"#).is_empty());
1448        assert!(tokenize_errors(r#"<root lang="en" xml:lang="en"/>"#).is_empty());
1449    }
1450
1451    #[test]
1452    fn different_prefixes_are_left_to_the_tree_builder() {
1453        // Whether these two are duplicates depends on what a and b are bound
1454        // to, and the tokenizer has no bindings, so it says nothing either way.
1455        // XmlTreeBuilder::bind_attr_qname resolves the prefixes and compares
1456        // expanded names, which is where a real duplicate gets caught.
1457        assert!(tokenize_errors(r#"<root a:name="1" b:name="2"/>"#).is_empty());
1458    }
1459
1460    #[test]
1461    fn real_duplicates_are_still_reported() {
1462        assert_eq!(
1463            tokenize_errors(r#"<root lang="en" lang="fr"/>"#),
1464            vec!["Duplicate attribute".to_owned()]
1465        );
1466
1467        assert_eq!(
1468            tokenize_errors(r#"<root xml:lang="en" xml:lang="fr"/>"#),
1469            vec!["Duplicate attribute".to_owned()]
1470        );
1471    }
1472
1473    #[test]
1474    fn simple_namespace() {
1475        let qname = process_qname("prefix:local".to_tendril());
1476        assert_eq!(qname.prefix, Some(Prefix::from("prefix")));
1477        assert_eq!(qname.local, LocalName::from("local"));
1478
1479        let qname = process_qname("a:b".to_tendril());
1480        assert_eq!(qname.prefix, Some(Prefix::from("a")));
1481        assert_eq!(qname.local, LocalName::from("b"));
1482    }
1483
1484    #[test]
1485    fn wrong_namespaces() {
1486        let qname = process_qname(":local".to_tendril());
1487        assert_eq!(qname.prefix, None);
1488        assert_eq!(qname.local, LocalName::from(":local"));
1489
1490        let qname = process_qname("::local".to_tendril());
1491        assert_eq!(qname.prefix, None);
1492        assert_eq!(qname.local, LocalName::from("::local"));
1493
1494        let qname = process_qname("a::local".to_tendril());
1495        assert_eq!(qname.prefix, None);
1496        assert_eq!(qname.local, LocalName::from("a::local"));
1497
1498        let qname = process_qname("fake::".to_tendril());
1499        assert_eq!(qname.prefix, None);
1500        assert_eq!(qname.local, LocalName::from("fake::"));
1501
1502        let qname = process_qname(":::".to_tendril());
1503        assert_eq!(qname.prefix, None);
1504        assert_eq!(qname.local, LocalName::from(":::"));
1505
1506        let qname = process_qname(":a:b:".to_tendril());
1507        assert_eq!(qname.prefix, None);
1508        assert_eq!(qname.local, LocalName::from(":a:b:"));
1509    }
1510}