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