1 // Copyright 2014-2017 The html5ever Project Developers. See the
2 // COPYRIGHT file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 // The tree builder rules, as a single, enormous nested match expression.
11
12 use markup5ever::{expanded_name, local_name, namespace_prefix, namespace_url, ns};
13 use crate::tokenizer::states::{Plaintext, Rawtext, Rcdata, ScriptData};
14 use crate::tree_builder::tag_sets::*;
15 use crate::tree_builder::types::*;
16
17 use std::borrow::ToOwned;
18
19 use crate::tendril::SliceExt;
20
any_not_whitespace(x: &StrTendril) -> bool21 fn any_not_whitespace(x: &StrTendril) -> bool {
22 // FIXME: this might be much faster as a byte scan
23 x.chars().any(|c| !is_ascii_whitespace(c))
24 }
25
current_node<Handle>(open_elems: &[Handle]) -> &Handle26 fn current_node<Handle>(open_elems: &[Handle]) -> &Handle {
27 open_elems.last().expect("no current element")
28 }
29
30 #[doc(hidden)]
31 impl<Handle, Sink> TreeBuilder<Handle, Sink>
32 where
33 Handle: Clone,
34 Sink: TreeSink<Handle = Handle>,
35 {
step(&mut self, mode: InsertionMode, token: Token) -> ProcessResult<Handle>36 fn step(&mut self, mode: InsertionMode, token: Token) -> ProcessResult<Handle> {
37 self.debug_step(mode, &token);
38
39 match mode {
40 //§ the-initial-insertion-mode
41 Initial => match_token!(token {
42 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
43 CharacterTokens(Whitespace, _) => Done,
44 CommentToken(text) => self.append_comment_to_doc(text),
45 token => {
46 if !self.opts.iframe_srcdoc {
47 self.unexpected(&token);
48 self.set_quirks_mode(Quirks);
49 }
50 Reprocess(BeforeHtml, token)
51 }
52 }),
53
54 //§ the-before-html-insertion-mode
55 BeforeHtml => match_token!(token {
56 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
57 CharacterTokens(Whitespace, _) => Done,
58 CommentToken(text) => self.append_comment_to_doc(text),
59
60 tag @ <html> => {
61 self.create_root(tag.attrs);
62 self.mode = BeforeHead;
63 Done
64 }
65
66 </head> </body> </html> </br> => else,
67
68 tag @ </_> => self.unexpected(&tag),
69
70 token => {
71 self.create_root(vec!());
72 Reprocess(BeforeHead, token)
73 }
74 }),
75
76 //§ the-before-head-insertion-mode
77 BeforeHead => match_token!(token {
78 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
79 CharacterTokens(Whitespace, _) => Done,
80 CommentToken(text) => self.append_comment(text),
81
82 <html> => self.step(InBody, token),
83
84 tag @ <head> => {
85 self.head_elem = Some(self.insert_element_for(tag));
86 self.mode = InHead;
87 Done
88 }
89
90 </head> </body> </html> </br> => else,
91
92 tag @ </_> => self.unexpected(&tag),
93
94 token => {
95 self.head_elem = Some(self.insert_phantom(local_name!("head")));
96 Reprocess(InHead, token)
97 }
98 }),
99
100 //§ parsing-main-inhead
101 InHead => match_token!(token {
102 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
103 CharacterTokens(Whitespace, text) => self.append_text(text),
104 CommentToken(text) => self.append_comment(text),
105
106 <html> => self.step(InBody, token),
107
108 tag @ <base> <basefont> <bgsound> <link> <meta> => {
109 // FIXME: handle <meta charset=...> and <meta http-equiv="Content-Type">
110 self.insert_and_pop_element_for(tag);
111 DoneAckSelfClosing
112 }
113
114 tag @ <title> => {
115 self.parse_raw_data(tag, Rcdata)
116 }
117
118 tag @ <noframes> <style> <noscript> => {
119 if (!self.opts.scripting_enabled) && (tag.name == local_name!("noscript")) {
120 self.insert_element_for(tag);
121 self.mode = InHeadNoscript;
122 Done
123 } else {
124 self.parse_raw_data(tag, Rawtext)
125 }
126 }
127
128 tag @ <script> => {
129 let elem = create_element(
130 &mut self.sink, QualName::new(None, ns!(html), local_name!("script")),
131 tag.attrs);
132 if self.is_fragment() {
133 self.sink.mark_script_already_started(&elem);
134 }
135 self.insert_appropriately(AppendNode(elem.clone()), None);
136 self.open_elems.push(elem);
137 self.to_raw_text_mode(ScriptData)
138 }
139
140 </head> => {
141 self.pop();
142 self.mode = AfterHead;
143 Done
144 }
145
146 </body> </html> </br> => else,
147
148 tag @ <template> => {
149 self.insert_element_for(tag);
150 self.active_formatting.push(Marker);
151 self.frameset_ok = false;
152 self.mode = InTemplate;
153 self.template_modes.push(InTemplate);
154 Done
155 }
156
157 tag @ </template> => {
158 if !self.in_html_elem_named(local_name!("template")) {
159 self.unexpected(&tag);
160 } else {
161 self.generate_implied_end(thorough_implied_end);
162 self.expect_to_close(local_name!("template"));
163 self.clear_active_formatting_to_marker();
164 self.template_modes.pop();
165 self.mode = self.reset_insertion_mode();
166 }
167 Done
168 }
169
170 <head> => self.unexpected(&token),
171 tag @ </_> => self.unexpected(&tag),
172
173 token => {
174 self.pop();
175 Reprocess(AfterHead, token)
176 }
177 }),
178
179 //§ parsing-main-inheadnoscript
180 InHeadNoscript => match_token!(token {
181 <html> => self.step(InBody, token),
182
183 </noscript> => {
184 self.pop();
185 self.mode = InHead;
186 Done
187 },
188
189 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
190 CharacterTokens(Whitespace, _) => self.step(InHead, token),
191
192 CommentToken(_) => self.step(InHead, token),
193
194 <basefont> <bgsound> <link> <meta> <noframes> <style>
195 => self.step(InHead, token),
196
197 </br> => else,
198
199 <head> <noscript> => self.unexpected(&token),
200 tag @ </_> => self.unexpected(&tag),
201
202 token => {
203 self.unexpected(&token);
204 self.pop();
205 Reprocess(InHead, token)
206 },
207 }),
208
209 //§ the-after-head-insertion-mode
210 AfterHead => match_token!(token {
211 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
212 CharacterTokens(Whitespace, text) => self.append_text(text),
213 CommentToken(text) => self.append_comment(text),
214
215 <html> => self.step(InBody, token),
216
217 tag @ <body> => {
218 self.insert_element_for(tag);
219 self.frameset_ok = false;
220 self.mode = InBody;
221 Done
222 }
223
224 tag @ <frameset> => {
225 self.insert_element_for(tag);
226 self.mode = InFrameset;
227 Done
228 }
229
230 <base> <basefont> <bgsound> <link> <meta>
231 <noframes> <script> <style> <template> <title> => {
232 self.unexpected(&token);
233 let head = self.head_elem.as_ref().expect("no head element").clone();
234 self.push(&head);
235 let result = self.step(InHead, token);
236 self.remove_from_stack(&head);
237 result
238 }
239
240 </template> => self.step(InHead, token),
241
242 </body> </html> </br> => else,
243
244 <head> => self.unexpected(&token),
245 tag @ </_> => self.unexpected(&tag),
246
247 token => {
248 self.insert_phantom(local_name!("body"));
249 Reprocess(InBody, token)
250 }
251 }),
252
253 //§ parsing-main-inbody
254 InBody => match_token!(token {
255 NullCharacterToken => self.unexpected(&token),
256
257 CharacterTokens(_, text) => {
258 self.reconstruct_formatting();
259 if any_not_whitespace(&text) {
260 self.frameset_ok = false;
261 }
262 self.append_text(text)
263 }
264
265 CommentToken(text) => self.append_comment(text),
266
267 tag @ <html> => {
268 self.unexpected(&tag);
269 if !self.in_html_elem_named(local_name!("template")) {
270 let top = html_elem(&self.open_elems);
271 self.sink.add_attrs_if_missing(top, tag.attrs);
272 }
273 Done
274 }
275
276 <base> <basefont> <bgsound> <link> <meta> <noframes>
277 <script> <style> <template> <title> </template> => {
278 self.step(InHead, token)
279 }
280
281 tag @ <body> => {
282 self.unexpected(&tag);
283 match self.body_elem().cloned() {
284 Some(ref node) if self.open_elems.len() != 1 &&
285 !self.in_html_elem_named(local_name!("template")) => {
286 self.frameset_ok = false;
287 self.sink.add_attrs_if_missing(node, tag.attrs)
288 },
289 _ => {}
290 }
291 Done
292 }
293
294 tag @ <frameset> => {
295 self.unexpected(&tag);
296 if !self.frameset_ok { return Done; }
297
298 let body = unwrap_or_return!(self.body_elem(), Done).clone();
299 self.sink.remove_from_parent(&body);
300
301 // FIXME: can we get here in the fragment case?
302 // What to do with the first element then?
303 self.open_elems.truncate(1);
304 self.insert_element_for(tag);
305 self.mode = InFrameset;
306 Done
307 }
308
309 EOFToken => {
310 if !self.template_modes.is_empty() {
311 self.step(InTemplate, token)
312 } else {
313 self.check_body_end();
314 self.stop_parsing()
315 }
316 }
317
318 </body> => {
319 if self.in_scope_named(default_scope, local_name!("body")) {
320 self.check_body_end();
321 self.mode = AfterBody;
322 } else {
323 self.sink.parse_error(Borrowed("</body> with no <body> in scope"));
324 }
325 Done
326 }
327
328 </html> => {
329 if self.in_scope_named(default_scope, local_name!("body")) {
330 self.check_body_end();
331 Reprocess(AfterBody, token)
332 } else {
333 self.sink.parse_error(Borrowed("</html> with no <body> in scope"));
334 Done
335 }
336 }
337
338 tag @ <address> <article> <aside> <blockquote> <center> <details> <dialog>
339 <dir> <div> <dl> <fieldset> <figcaption> <figure> <footer> <header>
340 <hgroup> <main> <nav> <ol> <p> <section> <summary> <ul> => {
341 self.close_p_element_in_button_scope();
342 self.insert_element_for(tag);
343 Done
344 }
345
346 tag @ <menu> => {
347 self.close_p_element_in_button_scope();
348 self.insert_element_for(tag);
349 Done
350 }
351
352 tag @ <h1> <h2> <h3> <h4> <h5> <h6> => {
353 self.close_p_element_in_button_scope();
354 if self.current_node_in(heading_tag) {
355 self.sink.parse_error(Borrowed("nested heading tags"));
356 self.pop();
357 }
358 self.insert_element_for(tag);
359 Done
360 }
361
362 tag @ <pre> <listing> => {
363 self.close_p_element_in_button_scope();
364 self.insert_element_for(tag);
365 self.ignore_lf = true;
366 self.frameset_ok = false;
367 Done
368 }
369
370 tag @ <form> => {
371 if self.form_elem.is_some() &&
372 !self.in_html_elem_named(local_name!("template")) {
373 self.sink.parse_error(Borrowed("nested forms"));
374 } else {
375 self.close_p_element_in_button_scope();
376 let elem = self.insert_element_for(tag);
377 if !self.in_html_elem_named(local_name!("template")) {
378 self.form_elem = Some(elem);
379 }
380 }
381 Done
382 }
383
384 tag @ <li> <dd> <dt> => {
385 declare_tag_set!(close_list = "li");
386 declare_tag_set!(close_defn = "dd" "dt");
387 declare_tag_set!(extra_special = [special_tag] - "address" "div" "p");
388 let list = match tag.name {
389 local_name!("li") => true,
390 local_name!("dd") | local_name!("dt") => false,
391 _ => unreachable!(),
392 };
393
394 self.frameset_ok = false;
395
396 let mut to_close = None;
397 for node in self.open_elems.iter().rev() {
398 let name = self.sink.elem_name(node);
399 let can_close = if list {
400 close_list(name)
401 } else {
402 close_defn(name)
403 };
404 if can_close {
405 to_close = Some(name.local.clone());
406 break;
407 }
408 if extra_special(name) {
409 break;
410 }
411 }
412
413 match to_close {
414 Some(name) => {
415 self.generate_implied_end_except(name.clone());
416 self.expect_to_close(name);
417 }
418 None => (),
419 }
420
421 self.close_p_element_in_button_scope();
422 self.insert_element_for(tag);
423 Done
424 }
425
426 tag @ <plaintext> => {
427 self.close_p_element_in_button_scope();
428 self.insert_element_for(tag);
429 ToPlaintext
430 }
431
432 tag @ <button> => {
433 if self.in_scope_named(default_scope, local_name!("button")) {
434 self.sink.parse_error(Borrowed("nested buttons"));
435 self.generate_implied_end(cursory_implied_end);
436 self.pop_until_named(local_name!("button"));
437 }
438 self.reconstruct_formatting();
439 self.insert_element_for(tag);
440 self.frameset_ok = false;
441 Done
442 }
443
444 tag @ </address> </article> </aside> </blockquote> </button> </center>
445 </details> </dialog> </dir> </div> </dl> </fieldset> </figcaption>
446 </figure> </footer> </header> </hgroup> </listing> </main> </menu>
447 </nav> </ol> </pre> </section> </summary> </ul> => {
448 if !self.in_scope_named(default_scope, tag.name.clone()) {
449 self.unexpected(&tag);
450 } else {
451 self.generate_implied_end(cursory_implied_end);
452 self.expect_to_close(tag.name);
453 }
454 Done
455 }
456
457 </form> => {
458 if !self.in_html_elem_named(local_name!("template")) {
459 // Can't use unwrap_or_return!() due to rust-lang/rust#16617.
460 let node = match self.form_elem.take() {
461 None => {
462 self.sink.parse_error(Borrowed("Null form element pointer on </form>"));
463 return Done;
464 }
465 Some(x) => x,
466 };
467 if !self.in_scope(default_scope, |n| self.sink.same_node(&node, &n)) {
468 self.sink.parse_error(Borrowed("Form element not in scope on </form>"));
469 return Done;
470 }
471 self.generate_implied_end(cursory_implied_end);
472 let current = self.current_node().clone();
473 self.remove_from_stack(&node);
474 if !self.sink.same_node(¤t, &node) {
475 self.sink.parse_error(Borrowed("Bad open element on </form>"));
476 }
477 } else {
478 if !self.in_scope_named(default_scope, local_name!("form")) {
479 self.sink.parse_error(Borrowed("Form element not in scope on </form>"));
480 return Done;
481 }
482 self.generate_implied_end(cursory_implied_end);
483 if !self.current_node_named(local_name!("form")) {
484 self.sink.parse_error(Borrowed("Bad open element on </form>"));
485 }
486 self.pop_until_named(local_name!("form"));
487 }
488 Done
489 }
490
491 </p> => {
492 if !self.in_scope_named(button_scope, local_name!("p")) {
493 self.sink.parse_error(Borrowed("No <p> tag to close"));
494 self.insert_phantom(local_name!("p"));
495 }
496 self.close_p_element();
497 Done
498 }
499
500 tag @ </li> </dd> </dt> => {
501 let in_scope = if tag.name == local_name!("li") {
502 self.in_scope_named(list_item_scope, tag.name.clone())
503 } else {
504 self.in_scope_named(default_scope, tag.name.clone())
505 };
506 if in_scope {
507 self.generate_implied_end_except(tag.name.clone());
508 self.expect_to_close(tag.name);
509 } else {
510 self.sink.parse_error(Borrowed("No matching tag to close"));
511 }
512 Done
513 }
514
515 tag @ </h1> </h2> </h3> </h4> </h5> </h6> => {
516 if self.in_scope(default_scope, |n| self.elem_in(&n, heading_tag)) {
517 self.generate_implied_end(cursory_implied_end);
518 if !self.current_node_named(tag.name) {
519 self.sink.parse_error(Borrowed("Closing wrong heading tag"));
520 }
521 self.pop_until(heading_tag);
522 } else {
523 self.sink.parse_error(Borrowed("No heading tag to close"));
524 }
525 Done
526 }
527
528 tag @ <a> => {
529 self.handle_misnested_a_tags(&tag);
530 self.reconstruct_formatting();
531 self.create_formatting_element_for(tag);
532 Done
533 }
534
535 tag @ <b> <big> <code> <em> <font> <i> <s> <small> <strike> <strong> <tt> <u> => {
536 self.reconstruct_formatting();
537 self.create_formatting_element_for(tag);
538 Done
539 }
540
541 tag @ <nobr> => {
542 self.reconstruct_formatting();
543 if self.in_scope_named(default_scope, local_name!("nobr")) {
544 self.sink.parse_error(Borrowed("Nested <nobr>"));
545 self.adoption_agency(local_name!("nobr"));
546 self.reconstruct_formatting();
547 }
548 self.create_formatting_element_for(tag);
549 Done
550 }
551
552 tag @ </a> </b> </big> </code> </em> </font> </i> </nobr>
553 </s> </small> </strike> </strong> </tt> </u> => {
554 self.adoption_agency(tag.name);
555 Done
556 }
557
558 tag @ <applet> <marquee> <object> => {
559 self.reconstruct_formatting();
560 self.insert_element_for(tag);
561 self.active_formatting.push(Marker);
562 self.frameset_ok = false;
563 Done
564 }
565
566 tag @ </applet> </marquee> </object> => {
567 if !self.in_scope_named(default_scope, tag.name.clone()) {
568 self.unexpected(&tag);
569 } else {
570 self.generate_implied_end(cursory_implied_end);
571 self.expect_to_close(tag.name);
572 self.clear_active_formatting_to_marker();
573 }
574 Done
575 }
576
577 tag @ <table> => {
578 if self.quirks_mode != Quirks {
579 self.close_p_element_in_button_scope();
580 }
581 self.insert_element_for(tag);
582 self.frameset_ok = false;
583 self.mode = InTable;
584 Done
585 }
586
587 tag @ </br> => {
588 self.unexpected(&tag);
589 self.step(InBody, TagToken(Tag {
590 kind: StartTag,
591 attrs: vec!(),
592 ..tag
593 }))
594 }
595
596 tag @ <area> <br> <embed> <img> <keygen> <wbr> <input> => {
597 let keep_frameset_ok = match tag.name {
598 local_name!("input") => self.is_type_hidden(&tag),
599 _ => false,
600 };
601 self.reconstruct_formatting();
602 self.insert_and_pop_element_for(tag);
603 if !keep_frameset_ok {
604 self.frameset_ok = false;
605 }
606 DoneAckSelfClosing
607 }
608
609 tag @ <param> <source> <track> => {
610 self.insert_and_pop_element_for(tag);
611 DoneAckSelfClosing
612 }
613
614 tag @ <hr> => {
615 self.close_p_element_in_button_scope();
616 self.insert_and_pop_element_for(tag);
617 self.frameset_ok = false;
618 DoneAckSelfClosing
619 }
620
621 tag @ <image> => {
622 self.unexpected(&tag);
623 self.step(InBody, TagToken(Tag {
624 name: local_name!("img"),
625 ..tag
626 }))
627 }
628
629 tag @ <textarea> => {
630 self.ignore_lf = true;
631 self.frameset_ok = false;
632 self.parse_raw_data(tag, Rcdata)
633 }
634
635 tag @ <xmp> => {
636 self.close_p_element_in_button_scope();
637 self.reconstruct_formatting();
638 self.frameset_ok = false;
639 self.parse_raw_data(tag, Rawtext)
640 }
641
642 tag @ <iframe> => {
643 self.frameset_ok = false;
644 self.parse_raw_data(tag, Rawtext)
645 }
646
647 tag @ <noembed> => {
648 self.parse_raw_data(tag, Rawtext)
649 }
650
651 // <noscript> handled in wildcard case below
652
653 tag @ <select> => {
654 self.reconstruct_formatting();
655 self.insert_element_for(tag);
656 self.frameset_ok = false;
657 // NB: mode == InBody but possibly self.mode != mode, if
658 // we're processing "as in the rules for InBody".
659 self.mode = match self.mode {
660 InTable | InCaption | InTableBody
661 | InRow | InCell => InSelectInTable,
662 _ => InSelect,
663 };
664 Done
665 }
666
667 tag @ <optgroup> <option> => {
668 if self.current_node_named(local_name!("option")) {
669 self.pop();
670 }
671 self.reconstruct_formatting();
672 self.insert_element_for(tag);
673 Done
674 }
675
676 tag @ <rb> <rtc> => {
677 if self.in_scope_named(default_scope, local_name!("ruby")) {
678 self.generate_implied_end(cursory_implied_end);
679 }
680 if !self.current_node_named(local_name!("ruby")) {
681 self.unexpected(&tag);
682 }
683 self.insert_element_for(tag);
684 Done
685 }
686
687 tag @ <rp> <rt> => {
688 if self.in_scope_named(default_scope, local_name!("ruby")) {
689 self.generate_implied_end_except(local_name!("rtc"));
690 }
691 if !self.current_node_named(local_name!("rtc")) && !self.current_node_named(local_name!("ruby")) {
692 self.unexpected(&tag);
693 }
694 self.insert_element_for(tag);
695 Done
696 }
697
698 tag @ <math> => self.enter_foreign(tag, ns!(mathml)),
699
700 tag @ <svg> => self.enter_foreign(tag, ns!(svg)),
701
702 <caption> <col> <colgroup> <frame> <head>
703 <tbody> <td> <tfoot> <th> <thead> <tr> => {
704 self.unexpected(&token);
705 Done
706 }
707
708 tag @ <_> => {
709 if self.opts.scripting_enabled && tag.name == local_name!("noscript") {
710 self.parse_raw_data(tag, Rawtext)
711 } else {
712 self.reconstruct_formatting();
713 self.insert_element_for(tag);
714 Done
715 }
716 }
717
718 tag @ </_> => {
719 self.process_end_tag_in_body(tag);
720 Done
721 }
722
723 // FIXME: This should be unreachable, but match_token requires a
724 // catch-all case.
725 _ => panic!("impossible case in InBody mode"),
726 }),
727
728 //§ parsing-main-incdata
729 Text => match_token!(token {
730 CharacterTokens(_, text) => self.append_text(text),
731
732 EOFToken => {
733 self.unexpected(&token);
734 if self.current_node_named(local_name!("script")) {
735 let current = current_node(&self.open_elems);
736 self.sink.mark_script_already_started(current);
737 }
738 self.pop();
739 Reprocess(self.orig_mode.take().unwrap(), token)
740 }
741
742 tag @ </_> => {
743 let node = self.pop();
744 self.mode = self.orig_mode.take().unwrap();
745 if tag.name == local_name!("script") {
746 return Script(node);
747 }
748 Done
749 }
750
751 // The spec doesn't say what to do here.
752 // Other tokens are impossible?
753 _ => panic!("impossible case in Text mode"),
754 }),
755
756 //§ parsing-main-intable
757 InTable => match_token!(token {
758 // FIXME: hack, should implement pat | pat for match_token instead
759 NullCharacterToken => self.process_chars_in_table(token),
760
761 CharacterTokens(..) => self.process_chars_in_table(token),
762
763 CommentToken(text) => self.append_comment(text),
764
765 tag @ <caption> => {
766 self.pop_until_current(table_scope);
767 self.active_formatting.push(Marker);
768 self.insert_element_for(tag);
769 self.mode = InCaption;
770 Done
771 }
772
773 tag @ <colgroup> => {
774 self.pop_until_current(table_scope);
775 self.insert_element_for(tag);
776 self.mode = InColumnGroup;
777 Done
778 }
779
780 <col> => {
781 self.pop_until_current(table_scope);
782 self.insert_phantom(local_name!("colgroup"));
783 Reprocess(InColumnGroup, token)
784 }
785
786 tag @ <tbody> <tfoot> <thead> => {
787 self.pop_until_current(table_scope);
788 self.insert_element_for(tag);
789 self.mode = InTableBody;
790 Done
791 }
792
793 <td> <th> <tr> => {
794 self.pop_until_current(table_scope);
795 self.insert_phantom(local_name!("tbody"));
796 Reprocess(InTableBody, token)
797 }
798
799 <table> => {
800 self.unexpected(&token);
801 if self.in_scope_named(table_scope, local_name!("table")) {
802 self.pop_until_named(local_name!("table"));
803 Reprocess(self.reset_insertion_mode(), token)
804 } else {
805 Done
806 }
807 }
808
809 </table> => {
810 if self.in_scope_named(table_scope, local_name!("table")) {
811 self.pop_until_named(local_name!("table"));
812 self.mode = self.reset_insertion_mode();
813 } else {
814 self.unexpected(&token);
815 }
816 Done
817 }
818
819 </body> </caption> </col> </colgroup> </html>
820 </tbody> </td> </tfoot> </th> </thead> </tr> =>
821 self.unexpected(&token),
822
823 <style> <script> <template> </template>
824 => self.step(InHead, token),
825
826 tag @ <input> => {
827 self.unexpected(&tag);
828 if self.is_type_hidden(&tag) {
829 self.insert_and_pop_element_for(tag);
830 DoneAckSelfClosing
831 } else {
832 self.foster_parent_in_body(TagToken(tag))
833 }
834 }
835
836 tag @ <form> => {
837 self.unexpected(&tag);
838 if !self.in_html_elem_named(local_name!("template")) && self.form_elem.is_none() {
839 self.form_elem = Some(self.insert_and_pop_element_for(tag));
840 }
841 Done
842 }
843
844 EOFToken => self.step(InBody, token),
845
846 token => {
847 self.unexpected(&token);
848 self.foster_parent_in_body(token)
849 }
850 }),
851
852 //§ parsing-main-intabletext
853 InTableText => match_token!(token {
854 NullCharacterToken => self.unexpected(&token),
855
856 CharacterTokens(split, text) => {
857 self.pending_table_text.push((split, text));
858 Done
859 }
860
861 token => {
862 let pending = replace(&mut self.pending_table_text, vec!());
863 let contains_nonspace = pending.iter().any(|&(split, ref text)| {
864 match split {
865 Whitespace => false,
866 NotWhitespace => true,
867 NotSplit => any_not_whitespace(text),
868 }
869 });
870
871 if contains_nonspace {
872 self.sink.parse_error(Borrowed("Non-space table text"));
873 for (split, text) in pending.into_iter() {
874 match self.foster_parent_in_body(CharacterTokens(split, text)) {
875 Done => (),
876 _ => panic!("not prepared to handle this!"),
877 }
878 }
879 } else {
880 for (_, text) in pending.into_iter() {
881 self.append_text(text);
882 }
883 }
884
885 Reprocess(self.orig_mode.take().unwrap(), token)
886 }
887 }),
888
889 //§ parsing-main-incaption
890 InCaption => match_token!(token {
891 tag @ <caption> <col> <colgroup> <tbody> <td> <tfoot>
892 <th> <thead> <tr> </table> </caption> => {
893 if self.in_scope_named(table_scope, local_name!("caption")) {
894 self.generate_implied_end(cursory_implied_end);
895 self.expect_to_close(local_name!("caption"));
896 self.clear_active_formatting_to_marker();
897 match tag {
898 Tag { kind: EndTag, name: local_name!("caption"), .. } => {
899 self.mode = InTable;
900 Done
901 }
902 _ => Reprocess(InTable, TagToken(tag))
903 }
904 } else {
905 self.unexpected(&tag);
906 Done
907 }
908 }
909
910 </body> </col> </colgroup> </html> </tbody>
911 </td> </tfoot> </th> </thead> </tr> => self.unexpected(&token),
912
913 token => self.step(InBody, token),
914 }),
915
916 //§ parsing-main-incolgroup
917 InColumnGroup => match_token!(token {
918 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
919 CharacterTokens(Whitespace, text) => self.append_text(text),
920 CommentToken(text) => self.append_comment(text),
921
922 <html> => self.step(InBody, token),
923
924 tag @ <col> => {
925 self.insert_and_pop_element_for(tag);
926 DoneAckSelfClosing
927 }
928
929 </colgroup> => {
930 if self.current_node_named(local_name!("colgroup")) {
931 self.pop();
932 self.mode = InTable;
933 } else {
934 self.unexpected(&token);
935 }
936 Done
937 }
938
939 </col> => self.unexpected(&token),
940
941 <template> </template> => self.step(InHead, token),
942
943 EOFToken => self.step(InBody, token),
944
945 token => {
946 if self.current_node_named(local_name!("colgroup")) {
947 self.pop();
948 Reprocess(InTable, token)
949 } else {
950 self.unexpected(&token)
951 }
952 }
953 }),
954
955 //§ parsing-main-intbody
956 InTableBody => match_token!(token {
957 tag @ <tr> => {
958 self.pop_until_current(table_body_context);
959 self.insert_element_for(tag);
960 self.mode = InRow;
961 Done
962 }
963
964 <th> <td> => {
965 self.unexpected(&token);
966 self.pop_until_current(table_body_context);
967 self.insert_phantom(local_name!("tr"));
968 Reprocess(InRow, token)
969 }
970
971 tag @ </tbody> </tfoot> </thead> => {
972 if self.in_scope_named(table_scope, tag.name.clone()) {
973 self.pop_until_current(table_body_context);
974 self.pop();
975 self.mode = InTable;
976 } else {
977 self.unexpected(&tag);
978 }
979 Done
980 }
981
982 <caption> <col> <colgroup> <tbody> <tfoot> <thead> </table> => {
983 declare_tag_set!(table_outer = "table" "tbody" "tfoot");
984 if self.in_scope(table_scope, |e| self.elem_in(&e, table_outer)) {
985 self.pop_until_current(table_body_context);
986 self.pop();
987 Reprocess(InTable, token)
988 } else {
989 self.unexpected(&token)
990 }
991 }
992
993 </body> </caption> </col> </colgroup> </html> </td> </th> </tr>
994 => self.unexpected(&token),
995
996 token => self.step(InTable, token),
997 }),
998
999 //§ parsing-main-intr
1000 InRow => match_token!(token {
1001 tag @ <th> <td> => {
1002 self.pop_until_current(table_row_context);
1003 self.insert_element_for(tag);
1004 self.mode = InCell;
1005 self.active_formatting.push(Marker);
1006 Done
1007 }
1008
1009 </tr> => {
1010 if self.in_scope_named(table_scope, local_name!("tr")) {
1011 self.pop_until_current(table_row_context);
1012 let node = self.pop();
1013 self.assert_named(&node, local_name!("tr"));
1014 self.mode = InTableBody;
1015 } else {
1016 self.unexpected(&token);
1017 }
1018 Done
1019 }
1020
1021 <caption> <col> <colgroup> <tbody> <tfoot> <thead> <tr> </table> => {
1022 if self.in_scope_named(table_scope, local_name!("tr")) {
1023 self.pop_until_current(table_row_context);
1024 let node = self.pop();
1025 self.assert_named(&node, local_name!("tr"));
1026 Reprocess(InTableBody, token)
1027 } else {
1028 self.unexpected(&token)
1029 }
1030 }
1031
1032 tag @ </tbody> </tfoot> </thead> => {
1033 if self.in_scope_named(table_scope, tag.name.clone()) {
1034 if self.in_scope_named(table_scope, local_name!("tr")) {
1035 self.pop_until_current(table_row_context);
1036 let node = self.pop();
1037 self.assert_named(&node, local_name!("tr"));
1038 Reprocess(InTableBody, TagToken(tag))
1039 } else {
1040 Done
1041 }
1042 } else {
1043 self.unexpected(&tag)
1044 }
1045 }
1046
1047 </body> </caption> </col> </colgroup> </html> </td> </th>
1048 => self.unexpected(&token),
1049
1050 token => self.step(InTable, token),
1051 }),
1052
1053 //§ parsing-main-intd
1054 InCell => match_token!(token {
1055 tag @ </td> </th> => {
1056 if self.in_scope_named(table_scope, tag.name.clone()) {
1057 self.generate_implied_end(cursory_implied_end);
1058 self.expect_to_close(tag.name);
1059 self.clear_active_formatting_to_marker();
1060 self.mode = InRow;
1061 } else {
1062 self.unexpected(&tag);
1063 }
1064 Done
1065 }
1066
1067 <caption> <col> <colgroup> <tbody> <td> <tfoot> <th> <thead> <tr> => {
1068 if self.in_scope(table_scope, |n| self.elem_in(&n, td_th)) {
1069 self.close_the_cell();
1070 Reprocess(InRow, token)
1071 } else {
1072 self.unexpected(&token)
1073 }
1074 }
1075
1076 </body> </caption> </col> </colgroup> </html>
1077 => self.unexpected(&token),
1078
1079 tag @ </table> </tbody> </tfoot> </thead> </tr> => {
1080 if self.in_scope_named(table_scope, tag.name.clone()) {
1081 self.close_the_cell();
1082 Reprocess(InRow, TagToken(tag))
1083 } else {
1084 self.unexpected(&tag)
1085 }
1086 }
1087
1088 token => self.step(InBody, token),
1089 }),
1090
1091 //§ parsing-main-inselect
1092 InSelect => match_token!(token {
1093 NullCharacterToken => self.unexpected(&token),
1094 CharacterTokens(_, text) => self.append_text(text),
1095 CommentToken(text) => self.append_comment(text),
1096
1097 <html> => self.step(InBody, token),
1098
1099 tag @ <option> => {
1100 if self.current_node_named(local_name!("option")) {
1101 self.pop();
1102 }
1103 self.insert_element_for(tag);
1104 Done
1105 }
1106
1107 tag @ <optgroup> => {
1108 if self.current_node_named(local_name!("option")) {
1109 self.pop();
1110 }
1111 if self.current_node_named(local_name!("optgroup")) {
1112 self.pop();
1113 }
1114 self.insert_element_for(tag);
1115 Done
1116 }
1117
1118 </optgroup> => {
1119 if self.open_elems.len() >= 2
1120 && self.current_node_named(local_name!("option"))
1121 && self.html_elem_named(&self.open_elems[self.open_elems.len() - 2],
1122 local_name!("optgroup")) {
1123 self.pop();
1124 }
1125 if self.current_node_named(local_name!("optgroup")) {
1126 self.pop();
1127 } else {
1128 self.unexpected(&token);
1129 }
1130 Done
1131 }
1132
1133 </option> => {
1134 if self.current_node_named(local_name!("option")) {
1135 self.pop();
1136 } else {
1137 self.unexpected(&token);
1138 }
1139 Done
1140 }
1141
1142 tag @ <select> </select> => {
1143 let in_scope = self.in_scope_named(select_scope, local_name!("select"));
1144
1145 if !in_scope || tag.kind == StartTag {
1146 self.unexpected(&tag);
1147 }
1148
1149 if in_scope {
1150 self.pop_until_named(local_name!("select"));
1151 self.mode = self.reset_insertion_mode();
1152 }
1153 Done
1154 }
1155
1156 <input> <keygen> <textarea> => {
1157 self.unexpected(&token);
1158 if self.in_scope_named(select_scope, local_name!("select")) {
1159 self.pop_until_named(local_name!("select"));
1160 Reprocess(self.reset_insertion_mode(), token)
1161 } else {
1162 Done
1163 }
1164 }
1165
1166 <script> <template> </template> => self.step(InHead, token),
1167
1168 EOFToken => self.step(InBody, token),
1169
1170 token => self.unexpected(&token),
1171 }),
1172
1173 //§ parsing-main-inselectintable
1174 InSelectInTable => match_token!(token {
1175 <caption> <table> <tbody> <tfoot> <thead> <tr> <td> <th> => {
1176 self.unexpected(&token);
1177 self.pop_until_named(local_name!("select"));
1178 Reprocess(self.reset_insertion_mode(), token)
1179 }
1180
1181 tag @ </caption> </table> </tbody> </tfoot> </thead> </tr> </td> </th> => {
1182 self.unexpected(&tag);
1183 if self.in_scope_named(table_scope, tag.name.clone()) {
1184 self.pop_until_named(local_name!("select"));
1185 Reprocess(self.reset_insertion_mode(), TagToken(tag))
1186 } else {
1187 Done
1188 }
1189 }
1190
1191 token => self.step(InSelect, token),
1192 }),
1193
1194 //§ parsing-main-intemplate
1195 InTemplate => match_token!(token {
1196 CharacterTokens(_, _) => self.step(InBody, token),
1197 CommentToken(_) => self.step(InBody, token),
1198
1199 <base> <basefont> <bgsound> <link> <meta> <noframes> <script>
1200 <style> <template> <title> </template> => {
1201 self.step(InHead, token)
1202 }
1203
1204 <caption> <colgroup> <tbody> <tfoot> <thead> => {
1205 self.template_modes.pop();
1206 self.template_modes.push(InTable);
1207 Reprocess(InTable, token)
1208 }
1209
1210 <col> => {
1211 self.template_modes.pop();
1212 self.template_modes.push(InColumnGroup);
1213 Reprocess(InColumnGroup, token)
1214 }
1215
1216 <tr> => {
1217 self.template_modes.pop();
1218 self.template_modes.push(InTableBody);
1219 Reprocess(InTableBody, token)
1220 }
1221
1222 <td> <th> => {
1223 self.template_modes.pop();
1224 self.template_modes.push(InRow);
1225 Reprocess(InRow, token)
1226 }
1227
1228 EOFToken => {
1229 if !self.in_html_elem_named(local_name!("template")) {
1230 self.stop_parsing()
1231 } else {
1232 self.unexpected(&token);
1233 self.pop_until_named(local_name!("template"));
1234 self.clear_active_formatting_to_marker();
1235 self.template_modes.pop();
1236 self.mode = self.reset_insertion_mode();
1237 Reprocess(self.reset_insertion_mode(), token)
1238 }
1239 }
1240
1241 tag @ <_> => {
1242 self.template_modes.pop();
1243 self.template_modes.push(InBody);
1244 Reprocess(InBody, TagToken(tag))
1245 }
1246
1247 token => self.unexpected(&token),
1248 }),
1249
1250 //§ parsing-main-afterbody
1251 AfterBody => match_token!(token {
1252 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1253 CharacterTokens(Whitespace, _) => self.step(InBody, token),
1254 CommentToken(text) => self.append_comment_to_html(text),
1255
1256 <html> => self.step(InBody, token),
1257
1258 </html> => {
1259 if self.is_fragment() {
1260 self.unexpected(&token);
1261 } else {
1262 self.mode = AfterAfterBody;
1263 }
1264 Done
1265 }
1266
1267 EOFToken => self.stop_parsing(),
1268
1269 token => {
1270 self.unexpected(&token);
1271 Reprocess(InBody, token)
1272 }
1273 }),
1274
1275 //§ parsing-main-inframeset
1276 InFrameset => match_token!(token {
1277 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1278 CharacterTokens(Whitespace, text) => self.append_text(text),
1279 CommentToken(text) => self.append_comment(text),
1280
1281 <html> => self.step(InBody, token),
1282
1283 tag @ <frameset> => {
1284 self.insert_element_for(tag);
1285 Done
1286 }
1287
1288 </frameset> => {
1289 if self.open_elems.len() == 1 {
1290 self.unexpected(&token);
1291 } else {
1292 self.pop();
1293 if !self.is_fragment() && !self.current_node_named(local_name!("frameset")) {
1294 self.mode = AfterFrameset;
1295 }
1296 }
1297 Done
1298 }
1299
1300 tag @ <frame> => {
1301 self.insert_and_pop_element_for(tag);
1302 DoneAckSelfClosing
1303 }
1304
1305 <noframes> => self.step(InHead, token),
1306
1307 EOFToken => {
1308 if self.open_elems.len() != 1 {
1309 self.unexpected(&token);
1310 }
1311 self.stop_parsing()
1312 }
1313
1314 token => self.unexpected(&token),
1315 }),
1316
1317 //§ parsing-main-afterframeset
1318 AfterFrameset => match_token!(token {
1319 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1320 CharacterTokens(Whitespace, text) => self.append_text(text),
1321 CommentToken(text) => self.append_comment(text),
1322
1323 <html> => self.step(InBody, token),
1324
1325 </html> => {
1326 self.mode = AfterAfterFrameset;
1327 Done
1328 }
1329
1330 <noframes> => self.step(InHead, token),
1331
1332 EOFToken => self.stop_parsing(),
1333
1334 token => self.unexpected(&token),
1335 }),
1336
1337 //§ the-after-after-body-insertion-mode
1338 AfterAfterBody => match_token!(token {
1339 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1340 CharacterTokens(Whitespace, _) => self.step(InBody, token),
1341 CommentToken(text) => self.append_comment_to_doc(text),
1342
1343 <html> => self.step(InBody, token),
1344
1345 EOFToken => self.stop_parsing(),
1346
1347 token => {
1348 self.unexpected(&token);
1349 Reprocess(InBody, token)
1350 }
1351 }),
1352
1353 //§ the-after-after-frameset-insertion-mode
1354 AfterAfterFrameset => match_token!(token {
1355 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1356 CharacterTokens(Whitespace, _) => self.step(InBody, token),
1357 CommentToken(text) => self.append_comment_to_doc(text),
1358
1359 <html> => self.step(InBody, token),
1360
1361 EOFToken => self.stop_parsing(),
1362
1363 <noframes> => self.step(InHead, token),
1364
1365 token => self.unexpected(&token),
1366 }),
1367 //§ END
1368 }
1369 }
1370
step_foreign(&mut self, token: Token) -> ProcessResult<Handle>1371 fn step_foreign(&mut self, token: Token) -> ProcessResult<Handle> {
1372 match_token!(token {
1373 NullCharacterToken => {
1374 self.unexpected(&token);
1375 self.append_text("\u{fffd}".to_tendril())
1376 }
1377
1378 CharacterTokens(_, text) => {
1379 if any_not_whitespace(&text) {
1380 self.frameset_ok = false;
1381 }
1382 self.append_text(text)
1383 }
1384
1385 CommentToken(text) => self.append_comment(text),
1386
1387 tag @ <b> <big> <blockquote> <body> <br> <center> <code> <dd> <div> <dl>
1388 <dt> <em> <embed> <h1> <h2> <h3> <h4> <h5> <h6> <head> <hr> <i>
1389 <img> <li> <listing> <menu> <meta> <nobr> <ol> <p> <pre> <ruby>
1390 <s> <small> <span> <strong> <strike> <sub> <sup> <table> <tt>
1391 <u> <ul> <var> => self.unexpected_start_tag_in_foreign_content(tag),
1392
1393 tag @ <font> => {
1394 let unexpected = tag.attrs.iter().any(|attr| {
1395 matches!(attr.name.expanded(),
1396 expanded_name!("", "color") |
1397 expanded_name!("", "face") |
1398 expanded_name!("", "size"))
1399 });
1400 if unexpected {
1401 self.unexpected_start_tag_in_foreign_content(tag)
1402 } else {
1403 self.foreign_start_tag(tag)
1404 }
1405 }
1406
1407 tag @ <_> => self.foreign_start_tag(tag),
1408
1409 // FIXME(#118): </script> in SVG
1410
1411 tag @ </_> => {
1412 let mut first = true;
1413 let mut stack_idx = self.open_elems.len() - 1;
1414 loop {
1415 if stack_idx == 0 {
1416 return Done;
1417 }
1418
1419 let html;
1420 let eq;
1421 {
1422 let node_name = self.sink.elem_name(&self.open_elems[stack_idx]);
1423 html = *node_name.ns == ns!(html);
1424 eq = node_name.local.eq_ignore_ascii_case(&tag.name);
1425 }
1426 if !first && html {
1427 let mode = self.mode;
1428 return self.step(mode, TagToken(tag));
1429 }
1430
1431 if eq {
1432 self.open_elems.truncate(stack_idx);
1433 return Done;
1434 }
1435
1436 if first {
1437 self.unexpected(&tag);
1438 first = false;
1439 }
1440 stack_idx -= 1;
1441 }
1442 }
1443
1444 // FIXME: This should be unreachable, but match_token requires a
1445 // catch-all case.
1446 _ => panic!("impossible case in foreign content"),
1447 })
1448 }
1449 }
1450