1mod 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#[derive(Copy, Clone)]
38pub struct XmlTokenizerOpts {
39 pub exact_errors: bool,
42
43 pub discard_bom: bool,
46
47 pub profile: bool,
50
51 pub initial_state: Option<XmlState>,
54}
55
56fn process_qname(tag_name: StrTendril) -> QualName {
57 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!(); 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}
97pub struct XmlTokenizer<Sink> {
99 opts: XmlTokenizerOpts,
101
102 pub sink: Sink,
104
105 state: Cell<XmlState>,
107
108 at_eof: Cell<bool>,
111
112 char_ref_tokenizer: RefCell<Option<Box<CharRefTokenizer>>>,
115
116 current_char: Cell<char>,
118
119 reconsume: Cell<bool>,
121
122 ignore_lf: Cell<bool>,
125
126 discard_bom: Cell<bool>,
129
130 temp_buf: RefCell<StrTendril>,
132
133 current_tag_kind: Cell<TagKind>,
135
136 current_tag_name: RefCell<StrTendril>,
138
139 current_tag_attrs: RefCell<Vec<Attribute>>,
141
142 current_attr_name: RefCell<StrTendril>,
144
145 current_attr_value: RefCell<StrTendril>,
147
148 current_doctype: RefCell<Doctype>,
149
150 current_comment: RefCell<StrTendril>,
152
153 current_pi_target: RefCell<StrTendril>,
155
156 current_pi_data: RefCell<StrTendril>,
158
159 state_profile: RefCell<BTreeMap<XmlState, u64>>,
161
162 time_in_sink: Cell<u64>,
164}
165
166impl<Sink: TokenSink> XmlTokenizer<Sink> {
167 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 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 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 if c == '\x00' {
246 c = '\u{FFFD}'
247 }
248
249 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 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 _ => d,
293 }
294 }
295
296 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 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 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 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 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 fn emit_chars(&self, b: StrTendril) {
455 self.process_token(Token::Characters(b));
456 }
457
458 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 *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
526macro_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#[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
568macro_rules! go (
570 ( $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 ( $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 ( $me:ident : emit_tag $s:expr ) => ({
587 $me.state.set($s);
588 return $me.emit_current_tag();
589 });
590
591 ( $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 ( $me:ident : $($cmd:tt)+ ) => ( sh_trace!($me: $($cmd)+) );
616
617 ( $me:ident : ) => (());
619);
620
621macro_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
637pub enum ProcessResult<Handle> {
639 Done,
641 Continue,
643 Script(Handle),
646}
647
648impl<Sink: TokenSink> XmlTokenizer<Sink> {
649 #[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 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 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 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 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 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 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 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 XmlState::PiTargetAfter => loop {
729 match get_char!(self, input) {
730 '\t' | '\n' | ' ' => (),
731 _ => go!(self: reconsume XmlState::PiData),
732 }
733 },
734 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 XmlState::PiAfter => loop {
743 match get_char!(self, input) {
744 '>' => go!(self: emit_pi Data),
745 '?' => 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 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 go!(self: error; to XmlState::BogusComment);
763 }
764 },
765 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 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 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 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 XmlState::CommentLessThanBang => loop {
799 match get_char!(self, input) {
800 '-' => go!(self: to XmlState::CommentLessThanBangDash),
801 _ => go!(self: reconsume XmlState::Comment),
802 }
803 },
804 XmlState::CommentLessThanBangDash => loop {
806 match get_char!(self, input) {
807 '-' => go!(self: to XmlState::CommentLessThanBangDashDash),
808 _ => go!(self: reconsume XmlState::CommentEndDash),
809 }
810 },
811 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 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 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 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 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 XmlState::Cdata => loop {
851 match get_char!(self, input) {
852 ']' => go!(self: to XmlState::CdataBracket),
853 cl => go!(self: emit cl),
854 }
855 },
856 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 XmlState::BogusDoctype => loop {
1114 if get_char!(self, input) == '>' {
1115 go!(self: emit_doctype; to XmlState::Data);
1116 }
1117 },
1118 }
1119 }
1120
1121 pub fn end(&self) {
1123 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 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 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 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 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 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 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 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}