1 | // Copyright 2014-2017 The html5ever Project Developers. See the |
2 | // COPYRIGHT file at the top-level directory of this distribution. |
3 | // |
4 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
5 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
6 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
7 | // option. This file may not be copied, modified, or distributed |
8 | // except according to those terms. |
9 | |
10 | //! The HTML5 tree builder. |
11 | |
12 | pub use crate::interface::{ |
13 | create_element, ElemName, ElementFlags, NextParserState, Tracer, TreeSink, |
14 | }; |
15 | pub use crate::interface::{AppendNode, AppendText, Attribute, NodeOrText}; |
16 | pub use crate::interface::{LimitedQuirks, NoQuirks, Quirks, QuirksMode}; |
17 | |
18 | use self::types::*; |
19 | |
20 | use crate::tendril::StrTendril; |
21 | use crate::{ExpandedName, LocalName, Namespace, QualName}; |
22 | |
23 | use crate::tokenizer; |
24 | use crate::tokenizer::states as tok_state; |
25 | use crate::tokenizer::{Doctype, EndTag, StartTag, Tag, TokenSink, TokenSinkResult}; |
26 | |
27 | use std::borrow::Cow::Borrowed; |
28 | use std::cell::{Cell, Ref, RefCell}; |
29 | use std::collections::VecDeque; |
30 | use std::iter::{Enumerate, Rev}; |
31 | use std::{fmt, slice}; |
32 | |
33 | use crate::tokenizer::states::RawKind; |
34 | use crate::tree_builder::tag_sets::*; |
35 | use crate::util::str::to_escaped_string; |
36 | use log::{debug, log_enabled, warn, Level}; |
37 | use mac::format_if; |
38 | use markup5ever::{expanded_name, local_name, namespace_prefix, namespace_url, ns}; |
39 | |
40 | pub use self::PushFlag::*; |
41 | |
42 | #[macro_use ] |
43 | mod tag_sets; |
44 | |
45 | mod data; |
46 | mod rules; |
47 | mod types; |
48 | |
49 | /// Tree builder options, with an impl for Default. |
50 | #[derive (Copy, Clone)] |
51 | pub struct TreeBuilderOpts { |
52 | /// Report all parse errors described in the spec, at some |
53 | /// performance penalty? Default: false |
54 | pub exact_errors: bool, |
55 | |
56 | /// Is scripting enabled? |
57 | pub scripting_enabled: bool, |
58 | |
59 | /// Is this an `iframe srcdoc` document? |
60 | pub iframe_srcdoc: bool, |
61 | |
62 | /// Should we drop the DOCTYPE (if any) from the tree? |
63 | pub drop_doctype: bool, |
64 | |
65 | /// Obsolete, ignored. |
66 | pub ignore_missing_rules: bool, |
67 | |
68 | /// Initial TreeBuilder quirks mode. Default: NoQuirks |
69 | pub quirks_mode: QuirksMode, |
70 | } |
71 | |
72 | impl Default for TreeBuilderOpts { |
73 | fn default() -> TreeBuilderOpts { |
74 | TreeBuilderOpts { |
75 | exact_errors: false, |
76 | scripting_enabled: true, |
77 | iframe_srcdoc: false, |
78 | drop_doctype: false, |
79 | ignore_missing_rules: false, |
80 | quirks_mode: NoQuirks, |
81 | } |
82 | } |
83 | } |
84 | |
85 | /// The HTML tree builder. |
86 | pub struct TreeBuilder<Handle, Sink> { |
87 | /// Options controlling the behavior of the tree builder. |
88 | opts: TreeBuilderOpts, |
89 | |
90 | /// Consumer of tree modifications. |
91 | pub sink: Sink, |
92 | |
93 | /// Insertion mode. |
94 | mode: Cell<InsertionMode>, |
95 | |
96 | /// Original insertion mode, used by Text and InTableText modes. |
97 | orig_mode: Cell<Option<InsertionMode>>, |
98 | |
99 | /// Stack of template insertion modes. |
100 | template_modes: RefCell<Vec<InsertionMode>>, |
101 | |
102 | /// Pending table character tokens. |
103 | pending_table_text: RefCell<Vec<(SplitStatus, StrTendril)>>, |
104 | |
105 | /// Quirks mode as set by the parser. |
106 | /// FIXME: can scripts etc. change this? |
107 | quirks_mode: Cell<QuirksMode>, |
108 | |
109 | /// The document node, which is created by the sink. |
110 | doc_handle: Handle, |
111 | |
112 | /// Stack of open elements, most recently added at end. |
113 | open_elems: RefCell<Vec<Handle>>, |
114 | |
115 | /// List of active formatting elements. |
116 | active_formatting: RefCell<Vec<FormatEntry<Handle>>>, |
117 | |
118 | //§ the-element-pointers |
119 | /// Head element pointer. |
120 | head_elem: RefCell<Option<Handle>>, |
121 | |
122 | /// Form element pointer. |
123 | form_elem: RefCell<Option<Handle>>, |
124 | //§ END |
125 | /// Frameset-ok flag. |
126 | frameset_ok: Cell<bool>, |
127 | |
128 | /// Ignore a following U+000A LINE FEED? |
129 | ignore_lf: Cell<bool>, |
130 | |
131 | /// Is foster parenting enabled? |
132 | foster_parenting: Cell<bool>, |
133 | |
134 | /// The context element for the fragment parsing algorithm. |
135 | context_elem: RefCell<Option<Handle>>, |
136 | |
137 | /// Track current line |
138 | current_line: Cell<u64>, |
139 | // WARNING: If you add new fields that contain Handles, you |
140 | // must add them to trace_handles() below to preserve memory |
141 | // safety! |
142 | // |
143 | // FIXME: Auto-generate the trace hooks like Servo does. |
144 | } |
145 | |
146 | impl<Handle, Sink> TreeBuilder<Handle, Sink> |
147 | where |
148 | Handle: Clone, |
149 | Sink: TreeSink<Handle = Handle>, |
150 | { |
151 | /// Create a new tree builder which sends tree modifications to a particular `TreeSink`. |
152 | /// |
153 | /// The tree builder is also a `TokenSink`. |
154 | pub fn new(sink: Sink, opts: TreeBuilderOpts) -> TreeBuilder<Handle, Sink> { |
155 | let doc_handle = sink.get_document(); |
156 | TreeBuilder { |
157 | opts, |
158 | sink, |
159 | mode: Cell::new(Initial), |
160 | orig_mode: Cell::new(None), |
161 | template_modes: Default::default(), |
162 | pending_table_text: Default::default(), |
163 | quirks_mode: Cell::new(opts.quirks_mode), |
164 | doc_handle, |
165 | open_elems: Default::default(), |
166 | active_formatting: Default::default(), |
167 | head_elem: Default::default(), |
168 | form_elem: Default::default(), |
169 | frameset_ok: Cell::new(true), |
170 | ignore_lf: Default::default(), |
171 | foster_parenting: Default::default(), |
172 | context_elem: Default::default(), |
173 | current_line: Cell::new(1), |
174 | } |
175 | } |
176 | |
177 | /// Create a new tree builder which sends tree modifications to a particular `TreeSink`. |
178 | /// This is for parsing fragments. |
179 | /// |
180 | /// The tree builder is also a `TokenSink`. |
181 | pub fn new_for_fragment( |
182 | sink: Sink, |
183 | context_elem: Handle, |
184 | form_elem: Option<Handle>, |
185 | opts: TreeBuilderOpts, |
186 | ) -> TreeBuilder<Handle, Sink> { |
187 | let doc_handle = sink.get_document(); |
188 | let context_is_template = |
189 | sink.elem_name(&context_elem).expanded() == expanded_name!(html "template" ); |
190 | let template_modes = if context_is_template { |
191 | RefCell::new(vec![InTemplate]) |
192 | } else { |
193 | RefCell::new(vec![]) |
194 | }; |
195 | |
196 | let tb = TreeBuilder { |
197 | opts, |
198 | sink, |
199 | mode: Cell::new(Initial), |
200 | orig_mode: Cell::new(None), |
201 | template_modes, |
202 | pending_table_text: Default::default(), |
203 | quirks_mode: Cell::new(opts.quirks_mode), |
204 | doc_handle, |
205 | open_elems: Default::default(), |
206 | active_formatting: Default::default(), |
207 | head_elem: Default::default(), |
208 | form_elem: RefCell::new(form_elem), |
209 | frameset_ok: Cell::new(true), |
210 | ignore_lf: Default::default(), |
211 | foster_parenting: Default::default(), |
212 | context_elem: RefCell::new(Some(context_elem)), |
213 | current_line: Cell::new(1), |
214 | }; |
215 | |
216 | // https://html.spec.whatwg.org/multipage/#parsing-html-fragments |
217 | // 5. Let root be a new html element with no attributes. |
218 | // 6. Append the element root to the Document node created above. |
219 | // 7. Set up the parser's stack of open elements so that it contains just the single element root. |
220 | tb.create_root(vec![]); |
221 | // 10. Reset the parser's insertion mode appropriately. |
222 | let old_insertion_mode = tb.reset_insertion_mode(); |
223 | tb.mode.set(old_insertion_mode); |
224 | |
225 | tb |
226 | } |
227 | |
228 | // https://html.spec.whatwg.org/multipage/#concept-frag-parse-context |
229 | // Step 4. Set the state of the HTML parser's tokenization stage as follows: |
230 | pub fn tokenizer_state_for_context_elem(&self) -> tok_state::State { |
231 | let context_elem = self.context_elem.borrow(); |
232 | let elem = context_elem.as_ref().expect("no context element" ); |
233 | let elem_name = self.sink.elem_name(elem); |
234 | let name = match elem_name.expanded() { |
235 | ExpandedName { |
236 | ns: &ns!(html), |
237 | local, |
238 | } => local, |
239 | _ => return tok_state::Data, |
240 | }; |
241 | match *name { |
242 | local_name!("title" ) | local_name!("textarea" ) => tok_state::RawData(tok_state::Rcdata), |
243 | |
244 | local_name!("style" ) |
245 | | local_name!("xmp" ) |
246 | | local_name!("iframe" ) |
247 | | local_name!("noembed" ) |
248 | | local_name!("noframes" ) => tok_state::RawData(tok_state::Rawtext), |
249 | |
250 | local_name!("script" ) => tok_state::RawData(tok_state::ScriptData), |
251 | |
252 | local_name!("noscript" ) => { |
253 | if self.opts.scripting_enabled { |
254 | tok_state::RawData(tok_state::Rawtext) |
255 | } else { |
256 | tok_state::Data |
257 | } |
258 | }, |
259 | |
260 | local_name!("plaintext" ) => tok_state::Plaintext, |
261 | |
262 | _ => tok_state::Data, |
263 | } |
264 | } |
265 | |
266 | /// Call the `Tracer`'s `trace_handle` method on every `Handle` in the tree builder's |
267 | /// internal state. This is intended to support garbage-collected DOMs. |
268 | pub fn trace_handles(&self, tracer: &dyn Tracer<Handle = Handle>) { |
269 | tracer.trace_handle(&self.doc_handle); |
270 | for e in &*self.open_elems.borrow() { |
271 | tracer.trace_handle(e); |
272 | } |
273 | |
274 | for e in &*self.active_formatting.borrow() { |
275 | if let FormatEntry::Element(handle, _) = e { |
276 | tracer.trace_handle(handle); |
277 | } |
278 | } |
279 | |
280 | if let Some(head_elem) = self.head_elem.borrow().as_ref() { |
281 | tracer.trace_handle(head_elem); |
282 | } |
283 | |
284 | if let Some(form_elem) = self.form_elem.borrow().as_ref() { |
285 | tracer.trace_handle(form_elem); |
286 | } |
287 | |
288 | if let Some(context_elem) = self.context_elem.borrow().as_ref() { |
289 | tracer.trace_handle(context_elem); |
290 | } |
291 | } |
292 | |
293 | #[allow (dead_code)] |
294 | fn dump_state(&self, label: String) { |
295 | println!("dump_state on {label}" ); |
296 | print!(" open_elems:" ); |
297 | for node in self.open_elems.borrow().iter() { |
298 | let name = self.sink.elem_name(node); |
299 | match *name.ns() { |
300 | ns!(html) => print!(" {}" , name.local_name()), |
301 | _ => panic!(), |
302 | } |
303 | } |
304 | println!(); |
305 | print!(" active_formatting:" ); |
306 | for entry in self.active_formatting.borrow().iter() { |
307 | match entry { |
308 | &Marker => print!(" Marker" ), |
309 | Element(h, _) => { |
310 | let name = self.sink.elem_name(h); |
311 | match *name.ns() { |
312 | ns!(html) => print!(" {}" , name.local_name()), |
313 | _ => panic!(), |
314 | } |
315 | }, |
316 | } |
317 | } |
318 | println!(); |
319 | } |
320 | |
321 | fn debug_step(&self, mode: InsertionMode, token: &Token) { |
322 | if log_enabled!(Level::Debug) { |
323 | debug!( |
324 | "processing {} in insertion mode {:?}" , |
325 | to_escaped_string(token), |
326 | mode |
327 | ); |
328 | } |
329 | } |
330 | |
331 | fn process_to_completion(&self, mut token: Token) -> TokenSinkResult<Handle> { |
332 | // Queue of additional tokens yet to be processed. |
333 | // This stays empty in the common case where we don't split whitespace. |
334 | let mut more_tokens = VecDeque::new(); |
335 | |
336 | loop { |
337 | let should_have_acknowledged_self_closing_flag = matches!( |
338 | token, |
339 | TagToken(Tag { |
340 | self_closing: true, |
341 | kind: StartTag, |
342 | .. |
343 | }) |
344 | ); |
345 | let result = if self.is_foreign(&token) { |
346 | self.step_foreign(token) |
347 | } else { |
348 | let mode = self.mode.get(); |
349 | self.step(mode, token) |
350 | }; |
351 | match result { |
352 | Done => { |
353 | if should_have_acknowledged_self_closing_flag { |
354 | self.sink |
355 | .parse_error(Borrowed("Unacknowledged self-closing tag" )); |
356 | } |
357 | token = unwrap_or_return!( |
358 | more_tokens.pop_front(), |
359 | tokenizer::TokenSinkResult::Continue |
360 | ); |
361 | }, |
362 | DoneAckSelfClosing => { |
363 | token = unwrap_or_return!( |
364 | more_tokens.pop_front(), |
365 | tokenizer::TokenSinkResult::Continue |
366 | ); |
367 | }, |
368 | Reprocess(m, t) => { |
369 | self.mode.set(m); |
370 | token = t; |
371 | }, |
372 | ReprocessForeign(t) => { |
373 | token = t; |
374 | }, |
375 | SplitWhitespace(mut buf) => { |
376 | let p = buf.pop_front_char_run(|c| c.is_ascii_whitespace()); |
377 | let (first, is_ws) = unwrap_or_return!(p, tokenizer::TokenSinkResult::Continue); |
378 | let status = if is_ws { Whitespace } else { NotWhitespace }; |
379 | token = CharacterTokens(status, first); |
380 | |
381 | if buf.len32() > 0 { |
382 | more_tokens.push_back(CharacterTokens(NotSplit, buf)); |
383 | } |
384 | }, |
385 | Script(node) => { |
386 | assert!(more_tokens.is_empty()); |
387 | return tokenizer::TokenSinkResult::Script(node); |
388 | }, |
389 | ToPlaintext => { |
390 | assert!(more_tokens.is_empty()); |
391 | return tokenizer::TokenSinkResult::Plaintext; |
392 | }, |
393 | ToRawData(k) => { |
394 | assert!(more_tokens.is_empty()); |
395 | return tokenizer::TokenSinkResult::RawData(k); |
396 | }, |
397 | } |
398 | } |
399 | } |
400 | |
401 | /// Are we parsing a HTML fragment? |
402 | pub fn is_fragment(&self) -> bool { |
403 | self.context_elem.borrow().is_some() |
404 | } |
405 | |
406 | /// https://html.spec.whatwg.org/multipage/#appropriate-place-for-inserting-a-node |
407 | fn appropriate_place_for_insertion( |
408 | &self, |
409 | override_target: Option<Handle>, |
410 | ) -> InsertionPoint<Handle> { |
411 | use self::tag_sets::*; |
412 | |
413 | declare_tag_set!(foster_target = "table" "tbody" "tfoot" "thead" "tr" ); |
414 | let target = override_target.unwrap_or_else(|| self.current_node().clone()); |
415 | if !(self.foster_parenting.get() && self.elem_in(&target, foster_target)) { |
416 | if self.html_elem_named(&target, local_name!("template" )) { |
417 | // No foster parenting (inside template). |
418 | let contents = self.sink.get_template_contents(&target); |
419 | return LastChild(contents); |
420 | } else { |
421 | // No foster parenting (the common case). |
422 | return LastChild(target); |
423 | } |
424 | } |
425 | |
426 | // Foster parenting |
427 | let open_elems = self.open_elems.borrow(); |
428 | let mut iter = open_elems.iter().rev().peekable(); |
429 | while let Some(elem) = iter.next() { |
430 | if self.html_elem_named(elem, local_name!("template" )) { |
431 | let contents = self.sink.get_template_contents(elem); |
432 | return LastChild(contents); |
433 | } else if self.html_elem_named(elem, local_name!("table" )) { |
434 | return TableFosterParenting { |
435 | element: elem.clone(), |
436 | prev_element: (*iter.peek().unwrap()).clone(), |
437 | }; |
438 | } |
439 | } |
440 | let html_elem = self.html_elem(); |
441 | LastChild(html_elem.clone()) |
442 | } |
443 | |
444 | fn insert_at(&self, insertion_point: InsertionPoint<Handle>, child: NodeOrText<Handle>) { |
445 | match insertion_point { |
446 | LastChild(parent) => self.sink.append(&parent, child), |
447 | BeforeSibling(sibling) => self.sink.append_before_sibling(&sibling, child), |
448 | TableFosterParenting { |
449 | element, |
450 | prev_element, |
451 | } => self |
452 | .sink |
453 | .append_based_on_parent_node(&element, &prev_element, child), |
454 | } |
455 | } |
456 | } |
457 | |
458 | impl<Handle, Sink> TokenSink for TreeBuilder<Handle, Sink> |
459 | where |
460 | Handle: Clone, |
461 | Sink: TreeSink<Handle = Handle>, |
462 | { |
463 | type Handle = Handle; |
464 | |
465 | fn process_token(&self, token: tokenizer::Token, line_number: u64) -> TokenSinkResult<Handle> { |
466 | if line_number != self.current_line.get() { |
467 | self.sink.set_current_line(line_number); |
468 | } |
469 | let ignore_lf = self.ignore_lf.take(); |
470 | |
471 | // Handle `ParseError` and `DoctypeToken`; convert everything else to the local `Token` type. |
472 | let token = match token { |
473 | tokenizer::ParseError(e) => { |
474 | self.sink.parse_error(e); |
475 | return tokenizer::TokenSinkResult::Continue; |
476 | }, |
477 | |
478 | tokenizer::DoctypeToken(dt) => { |
479 | if self.mode.get() == Initial { |
480 | let (err, quirk) = data::doctype_error_and_quirks(&dt, self.opts.iframe_srcdoc); |
481 | if err { |
482 | self.sink.parse_error(format_if!( |
483 | self.opts.exact_errors, |
484 | "Bad DOCTYPE" , |
485 | "Bad DOCTYPE: {:?}" , |
486 | dt |
487 | )); |
488 | } |
489 | let Doctype { |
490 | name, |
491 | public_id, |
492 | system_id, |
493 | force_quirks: _, |
494 | } = dt; |
495 | if !self.opts.drop_doctype { |
496 | self.sink.append_doctype_to_document( |
497 | name.unwrap_or(StrTendril::new()), |
498 | public_id.unwrap_or(StrTendril::new()), |
499 | system_id.unwrap_or(StrTendril::new()), |
500 | ); |
501 | } |
502 | self.set_quirks_mode(quirk); |
503 | |
504 | self.mode.set(BeforeHtml); |
505 | return tokenizer::TokenSinkResult::Continue; |
506 | } else { |
507 | self.sink.parse_error(format_if!( |
508 | self.opts.exact_errors, |
509 | "DOCTYPE in body" , |
510 | "DOCTYPE in insertion mode {:?}" , |
511 | self.mode.get() |
512 | )); |
513 | return tokenizer::TokenSinkResult::Continue; |
514 | } |
515 | }, |
516 | |
517 | tokenizer::TagToken(x) => TagToken(x), |
518 | tokenizer::CommentToken(x) => CommentToken(x), |
519 | tokenizer::NullCharacterToken => NullCharacterToken, |
520 | tokenizer::EOFToken => EOFToken, |
521 | |
522 | tokenizer::CharacterTokens(mut x) => { |
523 | if ignore_lf && x.starts_with(" \n" ) { |
524 | x.pop_front(1); |
525 | } |
526 | if x.is_empty() { |
527 | return tokenizer::TokenSinkResult::Continue; |
528 | } |
529 | CharacterTokens(NotSplit, x) |
530 | }, |
531 | }; |
532 | |
533 | self.process_to_completion(token) |
534 | } |
535 | |
536 | fn end(&self) { |
537 | for elem in self.open_elems.borrow_mut().drain(..).rev() { |
538 | self.sink.pop(&elem); |
539 | } |
540 | } |
541 | |
542 | fn adjusted_current_node_present_but_not_in_html_namespace(&self) -> bool { |
543 | !self.open_elems.borrow().is_empty() |
544 | && *self.sink.elem_name(&self.adjusted_current_node()).ns() != ns!(html) |
545 | } |
546 | } |
547 | |
548 | pub fn html_elem<Handle>(open_elems: &[Handle]) -> &Handle { |
549 | &open_elems[0] |
550 | } |
551 | |
552 | struct ActiveFormattingView<'a, Handle: 'a> { |
553 | data: Ref<'a, Vec<FormatEntry<Handle>>>, |
554 | } |
555 | |
556 | impl<'a, Handle: 'a> ActiveFormattingView<'a, Handle> { |
557 | fn iter(&'a self) -> impl Iterator<Item = (usize, &'a Handle, &'a Tag)> + 'a { |
558 | ActiveFormattingIter { |
559 | iter: self.data.iter().enumerate().rev(), |
560 | } |
561 | } |
562 | } |
563 | |
564 | pub struct ActiveFormattingIter<'a, Handle: 'a> { |
565 | iter: Rev<Enumerate<slice::Iter<'a, FormatEntry<Handle>>>>, |
566 | } |
567 | |
568 | impl<'a, Handle> Iterator for ActiveFormattingIter<'a, Handle> { |
569 | type Item = (usize, &'a Handle, &'a Tag); |
570 | fn next(&mut self) -> Option<(usize, &'a Handle, &'a Tag)> { |
571 | match self.iter.next() { |
572 | None | Some((_, &Marker)) => None, |
573 | Some((i: usize, Element(h: &Handle, t: &Tag))) => Some((i, h, t)), |
574 | } |
575 | } |
576 | } |
577 | |
578 | pub enum PushFlag { |
579 | Push, |
580 | NoPush, |
581 | } |
582 | |
583 | enum Bookmark<Handle> { |
584 | Replace(Handle), |
585 | InsertAfter(Handle), |
586 | } |
587 | |
588 | macro_rules! qualname { |
589 | ("" , $local:tt) => { |
590 | QualName { |
591 | prefix: None, |
592 | ns: ns!(), |
593 | local: local_name!($local), |
594 | } |
595 | }; |
596 | ($prefix: tt $ns:tt $local:tt) => { |
597 | QualName { |
598 | prefix: Some(namespace_prefix!($prefix)), |
599 | ns: ns!($ns), |
600 | local: local_name!($local), |
601 | } |
602 | }; |
603 | } |
604 | |
605 | #[doc (hidden)] |
606 | impl<Handle, Sink> TreeBuilder<Handle, Sink> |
607 | where |
608 | Handle: Clone, |
609 | Sink: TreeSink<Handle = Handle>, |
610 | { |
611 | fn unexpected<T: fmt::Debug>(&self, _thing: &T) -> ProcessResult<Handle> { |
612 | self.sink.parse_error(format_if!( |
613 | self.opts.exact_errors, |
614 | "Unexpected token" , |
615 | "Unexpected token {} in insertion mode {:?}" , |
616 | to_escaped_string(_thing), |
617 | self.mode.get() |
618 | )); |
619 | Done |
620 | } |
621 | |
622 | fn assert_named(&self, node: &Handle, name: LocalName) { |
623 | assert!(self.html_elem_named(node, name)); |
624 | } |
625 | |
626 | /// Iterate over the active formatting elements (with index in the list) from the end |
627 | /// to the last marker, or the beginning if there are no markers. |
628 | fn active_formatting_end_to_marker(&self) -> ActiveFormattingView<'_, Handle> { |
629 | ActiveFormattingView { |
630 | data: self.active_formatting.borrow(), |
631 | } |
632 | } |
633 | |
634 | fn position_in_active_formatting(&self, element: &Handle) -> Option<usize> { |
635 | self.active_formatting |
636 | .borrow() |
637 | .iter() |
638 | .position(|n| match n { |
639 | FormatEntry::Marker => false, |
640 | FormatEntry::Element(ref handle, _) => self.sink.same_node(handle, element), |
641 | }) |
642 | } |
643 | |
644 | fn set_quirks_mode(&self, mode: QuirksMode) { |
645 | self.quirks_mode.set(mode); |
646 | self.sink.set_quirks_mode(mode); |
647 | } |
648 | |
649 | fn stop_parsing(&self) -> ProcessResult<Handle> { |
650 | Done |
651 | } |
652 | |
653 | //§ parsing-elements-that-contain-only-text |
654 | // Switch to `Text` insertion mode, save the old mode, and |
655 | // switch the tokenizer to a raw-data state. |
656 | // The latter only takes effect after the current / next |
657 | // `process_token` of a start tag returns! |
658 | fn to_raw_text_mode(&self, k: RawKind) -> ProcessResult<Handle> { |
659 | self.orig_mode.set(Some(self.mode.get())); |
660 | self.mode.set(Text); |
661 | ToRawData(k) |
662 | } |
663 | |
664 | // The generic raw text / RCDATA parsing algorithm. |
665 | fn parse_raw_data(&self, tag: Tag, k: RawKind) -> ProcessResult<Handle> { |
666 | self.insert_element_for(tag); |
667 | self.to_raw_text_mode(k) |
668 | } |
669 | //§ END |
670 | |
671 | fn current_node(&self) -> Ref<Handle> { |
672 | Ref::map(self.open_elems.borrow(), |elems| { |
673 | elems.last().expect("no current element" ) |
674 | }) |
675 | } |
676 | |
677 | fn adjusted_current_node(&self) -> Ref<Handle> { |
678 | if self.open_elems.borrow().len() == 1 { |
679 | let context_elem = self.context_elem.borrow(); |
680 | let ctx = Ref::filter_map(context_elem, |e| e.as_ref()); |
681 | if let Ok(ctx) = ctx { |
682 | return ctx; |
683 | } |
684 | } |
685 | self.current_node() |
686 | } |
687 | |
688 | fn current_node_in<TagSet>(&self, set: TagSet) -> bool |
689 | where |
690 | TagSet: Fn(ExpandedName) -> bool, |
691 | { |
692 | set(self.sink.elem_name(&self.current_node()).expanded()) |
693 | } |
694 | |
695 | // Insert at the "appropriate place for inserting a node". |
696 | fn insert_appropriately(&self, child: NodeOrText<Handle>, override_target: Option<Handle>) { |
697 | let insertion_point = self.appropriate_place_for_insertion(override_target); |
698 | self.insert_at(insertion_point, child); |
699 | } |
700 | |
701 | fn adoption_agency(&self, subject: LocalName) { |
702 | // 1. |
703 | if self.current_node_named(subject.clone()) |
704 | && self |
705 | .position_in_active_formatting(&self.current_node()) |
706 | .is_none() |
707 | { |
708 | self.pop(); |
709 | return; |
710 | } |
711 | |
712 | // 2. 3. 4. |
713 | for _ in 0..8 { |
714 | // 5. |
715 | let (fmt_elem_index, fmt_elem, fmt_elem_tag) = unwrap_or_return!( |
716 | // We clone the Handle and Tag so they don't cause an immutable borrow of self. |
717 | self.active_formatting_end_to_marker() |
718 | .iter() |
719 | .find(|&(_, _, tag)| tag.name == subject) |
720 | .map(|(i, h, t)| (i, h.clone(), t.clone())), |
721 | { |
722 | self.process_end_tag_in_body(Tag { |
723 | kind: EndTag, |
724 | name: subject, |
725 | self_closing: false, |
726 | attrs: vec![], |
727 | }); |
728 | } |
729 | ); |
730 | |
731 | let fmt_elem_stack_index = unwrap_or_return!( |
732 | self.open_elems |
733 | .borrow() |
734 | .iter() |
735 | .rposition(|n| self.sink.same_node(n, &fmt_elem)), |
736 | { |
737 | self.sink |
738 | .parse_error(Borrowed("Formatting element not open" )); |
739 | self.active_formatting.borrow_mut().remove(fmt_elem_index); |
740 | } |
741 | ); |
742 | |
743 | // 7. |
744 | if !self.in_scope(default_scope, |n| self.sink.same_node(&n, &fmt_elem)) { |
745 | self.sink |
746 | .parse_error(Borrowed("Formatting element not in scope" )); |
747 | return; |
748 | } |
749 | |
750 | // 8. |
751 | if !self.sink.same_node(&self.current_node(), &fmt_elem) { |
752 | self.sink |
753 | .parse_error(Borrowed("Formatting element not current node" )); |
754 | } |
755 | |
756 | // 9. |
757 | let (furthest_block_index, furthest_block) = unwrap_or_return!( |
758 | self.open_elems |
759 | .borrow() |
760 | .iter() |
761 | .enumerate() |
762 | .skip(fmt_elem_stack_index) |
763 | .find(|&(_, open_element)| self.elem_in(open_element, special_tag)) |
764 | .map(|(i, h)| (i, h.clone())), |
765 | // 10. |
766 | { |
767 | self.open_elems.borrow_mut().truncate(fmt_elem_stack_index); |
768 | self.active_formatting.borrow_mut().remove(fmt_elem_index); |
769 | } |
770 | ); |
771 | |
772 | // 11. |
773 | let common_ancestor = self.open_elems.borrow()[fmt_elem_stack_index - 1].clone(); |
774 | |
775 | // 12. |
776 | let mut bookmark = Bookmark::Replace(fmt_elem.clone()); |
777 | |
778 | // 13. |
779 | let mut node; |
780 | let mut node_index = furthest_block_index; |
781 | let mut last_node = furthest_block.clone(); |
782 | |
783 | // 13.1. |
784 | let mut inner_counter = 0; |
785 | loop { |
786 | // 13.2. |
787 | inner_counter += 1; |
788 | |
789 | // 13.3. |
790 | node_index -= 1; |
791 | node = self.open_elems.borrow()[node_index].clone(); |
792 | |
793 | // 13.4. |
794 | if self.sink.same_node(&node, &fmt_elem) { |
795 | break; |
796 | } |
797 | |
798 | // 13.5. |
799 | if inner_counter > 3 { |
800 | self.position_in_active_formatting(&node) |
801 | .map(|position| self.active_formatting.borrow_mut().remove(position)); |
802 | self.open_elems.borrow_mut().remove(node_index); |
803 | continue; |
804 | } |
805 | |
806 | let node_formatting_index = unwrap_or_else!( |
807 | self.position_in_active_formatting(&node), |
808 | // 13.6. |
809 | { |
810 | self.open_elems.borrow_mut().remove(node_index); |
811 | continue; |
812 | } |
813 | ); |
814 | |
815 | // 13.7. |
816 | let tag = match self.active_formatting.borrow()[node_formatting_index] { |
817 | Element(ref h, ref t) => { |
818 | assert!(self.sink.same_node(h, &node)); |
819 | t.clone() |
820 | }, |
821 | Marker => panic!("Found marker during adoption agency" ), |
822 | }; |
823 | // FIXME: Is there a way to avoid cloning the attributes twice here (once on their |
824 | // own, once as part of t.clone() above)? |
825 | let new_element = create_element( |
826 | &self.sink, |
827 | QualName::new(None, ns!(html), tag.name.clone()), |
828 | tag.attrs.clone(), |
829 | ); |
830 | self.open_elems.borrow_mut()[node_index] = new_element.clone(); |
831 | self.active_formatting.borrow_mut()[node_formatting_index] = |
832 | Element(new_element.clone(), tag); |
833 | node = new_element; |
834 | |
835 | // 13.8. |
836 | if self.sink.same_node(&last_node, &furthest_block) { |
837 | bookmark = Bookmark::InsertAfter(node.clone()); |
838 | } |
839 | |
840 | // 13.9. |
841 | self.sink.remove_from_parent(&last_node); |
842 | self.sink.append(&node, AppendNode(last_node.clone())); |
843 | |
844 | // 13.10. |
845 | last_node = node.clone(); |
846 | |
847 | // 13.11. |
848 | } |
849 | |
850 | // 14. |
851 | self.sink.remove_from_parent(&last_node); |
852 | self.insert_appropriately(AppendNode(last_node.clone()), Some(common_ancestor)); |
853 | |
854 | // 15. |
855 | // FIXME: Is there a way to avoid cloning the attributes twice here (once on their own, |
856 | // once as part of t.clone() above)? |
857 | let new_element = create_element( |
858 | &self.sink, |
859 | QualName::new(None, ns!(html), fmt_elem_tag.name.clone()), |
860 | fmt_elem_tag.attrs.clone(), |
861 | ); |
862 | let new_entry = Element(new_element.clone(), fmt_elem_tag); |
863 | |
864 | // 16. |
865 | self.sink.reparent_children(&furthest_block, &new_element); |
866 | |
867 | // 17. |
868 | self.sink |
869 | .append(&furthest_block, AppendNode(new_element.clone())); |
870 | |
871 | // 18. |
872 | // FIXME: We could probably get rid of the position_in_active_formatting() calls here |
873 | // if we had a more clever Bookmark representation. |
874 | match bookmark { |
875 | Bookmark::Replace(to_replace) => { |
876 | let index = self |
877 | .position_in_active_formatting(&to_replace) |
878 | .expect("bookmark not found in active formatting elements" ); |
879 | self.active_formatting.borrow_mut()[index] = new_entry; |
880 | }, |
881 | Bookmark::InsertAfter(previous) => { |
882 | let index = self |
883 | .position_in_active_formatting(&previous) |
884 | .expect("bookmark not found in active formatting elements" ) |
885 | + 1; |
886 | self.active_formatting.borrow_mut().insert(index, new_entry); |
887 | let old_index = self |
888 | .position_in_active_formatting(&fmt_elem) |
889 | .expect("formatting element not found in active formatting elements" ); |
890 | self.active_formatting.borrow_mut().remove(old_index); |
891 | }, |
892 | } |
893 | |
894 | // 19. |
895 | self.remove_from_stack(&fmt_elem); |
896 | let new_furthest_block_index = self |
897 | .open_elems |
898 | .borrow() |
899 | .iter() |
900 | .position(|n| self.sink.same_node(n, &furthest_block)) |
901 | .expect("furthest block missing from open element stack" ); |
902 | self.open_elems |
903 | .borrow_mut() |
904 | .insert(new_furthest_block_index + 1, new_element); |
905 | |
906 | // 20. |
907 | } |
908 | } |
909 | |
910 | fn push(&self, elem: &Handle) { |
911 | self.open_elems.borrow_mut().push(elem.clone()); |
912 | } |
913 | |
914 | fn pop(&self) -> Handle { |
915 | let elem = self |
916 | .open_elems |
917 | .borrow_mut() |
918 | .pop() |
919 | .expect("no current element" ); |
920 | self.sink.pop(&elem); |
921 | elem |
922 | } |
923 | |
924 | fn remove_from_stack(&self, elem: &Handle) { |
925 | let position = self |
926 | .open_elems |
927 | .borrow() |
928 | .iter() |
929 | .rposition(|x| self.sink.same_node(elem, x)); |
930 | if let Some(position) = position { |
931 | self.open_elems.borrow_mut().remove(position); |
932 | self.sink.pop(elem); |
933 | } |
934 | } |
935 | |
936 | fn is_marker_or_open(&self, entry: &FormatEntry<Handle>) -> bool { |
937 | match *entry { |
938 | Marker => true, |
939 | Element(ref node, _) => self |
940 | .open_elems |
941 | .borrow() |
942 | .iter() |
943 | .rev() |
944 | .any(|n| self.sink.same_node(n, node)), |
945 | } |
946 | } |
947 | |
948 | /// Reconstruct the active formatting elements. |
949 | fn reconstruct_formatting(&self) { |
950 | { |
951 | let active_formatting = self.active_formatting.borrow(); |
952 | let last = unwrap_or_return!(active_formatting.last()); |
953 | if self.is_marker_or_open(last) { |
954 | return; |
955 | } |
956 | } |
957 | |
958 | let mut entry_index = self.active_formatting.borrow().len() - 1; |
959 | loop { |
960 | if entry_index == 0 { |
961 | break; |
962 | } |
963 | entry_index -= 1; |
964 | if self.is_marker_or_open(&self.active_formatting.borrow()[entry_index]) { |
965 | entry_index += 1; |
966 | break; |
967 | } |
968 | } |
969 | |
970 | loop { |
971 | let tag = match self.active_formatting.borrow()[entry_index] { |
972 | Element(_, ref t) => t.clone(), |
973 | Marker => panic!("Found marker during formatting element reconstruction" ), |
974 | }; |
975 | |
976 | // FIXME: Is there a way to avoid cloning the attributes twice here (once on their own, |
977 | // once as part of t.clone() above)? |
978 | let new_element = |
979 | self.insert_element(Push, ns!(html), tag.name.clone(), tag.attrs.clone()); |
980 | self.active_formatting.borrow_mut()[entry_index] = Element(new_element, tag); |
981 | if entry_index == self.active_formatting.borrow().len() - 1 { |
982 | break; |
983 | } |
984 | entry_index += 1; |
985 | } |
986 | } |
987 | |
988 | /// Get the first element on the stack, which will be the <html> element. |
989 | fn html_elem(&self) -> Ref<Handle> { |
990 | Ref::map(self.open_elems.borrow(), |elems| &elems[0]) |
991 | } |
992 | |
993 | /// Get the second element on the stack, if it's a HTML body element. |
994 | fn body_elem(&self) -> Option<Ref<Handle>> { |
995 | if self.open_elems.borrow().len() <= 1 { |
996 | return None; |
997 | } |
998 | |
999 | let node = Ref::map(self.open_elems.borrow(), |elems| &elems[1]); |
1000 | if self.html_elem_named(&node, local_name!("body" )) { |
1001 | Some(node) |
1002 | } else { |
1003 | None |
1004 | } |
1005 | } |
1006 | |
1007 | /// Signal an error depending on the state of the stack of open elements at |
1008 | /// the end of the body. |
1009 | fn check_body_end(&self) { |
1010 | declare_tag_set!(body_end_ok = |
1011 | "dd" "dt" "li" "optgroup" "option" "p" "rp" "rt" "tbody" "td" "tfoot" "th" |
1012 | "thead" "tr" "body" "html" ); |
1013 | |
1014 | for elem in self.open_elems.borrow().iter() { |
1015 | let error; |
1016 | { |
1017 | let elem_name = self.sink.elem_name(elem); |
1018 | let name = elem_name.expanded(); |
1019 | if body_end_ok(name) { |
1020 | continue; |
1021 | } |
1022 | error = format_if!( |
1023 | self.opts.exact_errors, |
1024 | "Unexpected open tag at end of body" , |
1025 | "Unexpected open tag {:?} at end of body" , |
1026 | name |
1027 | ); |
1028 | } |
1029 | self.sink.parse_error(error); |
1030 | // FIXME: Do we keep checking after finding one bad tag? |
1031 | // The spec suggests not. |
1032 | return; |
1033 | } |
1034 | } |
1035 | |
1036 | fn in_scope<TagSet, Pred>(&self, scope: TagSet, pred: Pred) -> bool |
1037 | where |
1038 | TagSet: Fn(ExpandedName) -> bool, |
1039 | Pred: Fn(Handle) -> bool, |
1040 | { |
1041 | for node in self.open_elems.borrow().iter().rev() { |
1042 | if pred(node.clone()) { |
1043 | return true; |
1044 | } |
1045 | if scope(self.sink.elem_name(node).expanded()) { |
1046 | return false; |
1047 | } |
1048 | } |
1049 | |
1050 | // supposed to be impossible, because <html> is always in scope |
1051 | |
1052 | false |
1053 | } |
1054 | |
1055 | fn elem_in<TagSet>(&self, elem: &Handle, set: TagSet) -> bool |
1056 | where |
1057 | TagSet: Fn(ExpandedName) -> bool, |
1058 | { |
1059 | set(self.sink.elem_name(elem).expanded()) |
1060 | } |
1061 | |
1062 | fn html_elem_named(&self, elem: &Handle, name: LocalName) -> bool { |
1063 | let elem_name = self.sink.elem_name(elem); |
1064 | *elem_name.ns() == ns!(html) && *elem_name.local_name() == name |
1065 | } |
1066 | |
1067 | fn in_html_elem_named(&self, name: LocalName) -> bool { |
1068 | self.open_elems |
1069 | .borrow() |
1070 | .iter() |
1071 | .any(|elem| self.html_elem_named(elem, name.clone())) |
1072 | } |
1073 | |
1074 | fn current_node_named(&self, name: LocalName) -> bool { |
1075 | self.html_elem_named(&self.current_node(), name) |
1076 | } |
1077 | |
1078 | fn in_scope_named<TagSet>(&self, scope: TagSet, name: LocalName) -> bool |
1079 | where |
1080 | TagSet: Fn(ExpandedName) -> bool, |
1081 | { |
1082 | self.in_scope(scope, |elem| self.html_elem_named(&elem, name.clone())) |
1083 | } |
1084 | |
1085 | //§ closing-elements-that-have-implied-end-tags |
1086 | fn generate_implied_end<TagSet>(&self, set: TagSet) |
1087 | where |
1088 | TagSet: Fn(ExpandedName) -> bool, |
1089 | { |
1090 | loop { |
1091 | { |
1092 | let open_elems = self.open_elems.borrow(); |
1093 | let elem = unwrap_or_return!(open_elems.last()); |
1094 | let elem_name = self.sink.elem_name(elem); |
1095 | if !set(elem_name.expanded()) { |
1096 | return; |
1097 | } |
1098 | } |
1099 | self.pop(); |
1100 | } |
1101 | } |
1102 | |
1103 | fn generate_implied_end_except(&self, except: LocalName) { |
1104 | self.generate_implied_end(|p| { |
1105 | if *p.ns == ns!(html) && *p.local == except { |
1106 | false |
1107 | } else { |
1108 | cursory_implied_end(p) |
1109 | } |
1110 | }); |
1111 | } |
1112 | //§ END |
1113 | |
1114 | // Pop elements until the current element is in the set. |
1115 | fn pop_until_current<TagSet>(&self, tag_set: TagSet) |
1116 | where |
1117 | TagSet: Fn(ExpandedName) -> bool, |
1118 | { |
1119 | while !self.current_node_in(&tag_set) { |
1120 | self.open_elems.borrow_mut().pop(); |
1121 | } |
1122 | } |
1123 | |
1124 | // Pop elements until an element from the set has been popped. Returns the |
1125 | // number of elements popped. |
1126 | fn pop_until<P>(&self, pred: P) -> usize |
1127 | where |
1128 | P: Fn(ExpandedName) -> bool, |
1129 | { |
1130 | let mut n = 0; |
1131 | loop { |
1132 | n += 1; |
1133 | match self.open_elems.borrow_mut().pop() { |
1134 | None => break, |
1135 | Some(elem) => { |
1136 | if pred(self.sink.elem_name(&elem).expanded()) { |
1137 | break; |
1138 | } |
1139 | }, |
1140 | } |
1141 | } |
1142 | n |
1143 | } |
1144 | |
1145 | fn pop_until_named(&self, name: LocalName) -> usize { |
1146 | self.pop_until(|p| *p.ns == ns!(html) && *p.local == name) |
1147 | } |
1148 | |
1149 | // Pop elements until one with the specified name has been popped. |
1150 | // Signal an error if it was not the first one. |
1151 | fn expect_to_close(&self, name: LocalName) { |
1152 | if self.pop_until_named(name.clone()) != 1 { |
1153 | self.sink.parse_error(format_if!( |
1154 | self.opts.exact_errors, |
1155 | "Unexpected open element" , |
1156 | "Unexpected open element while closing {:?}" , |
1157 | name |
1158 | )); |
1159 | } |
1160 | } |
1161 | |
1162 | fn close_p_element(&self) { |
1163 | declare_tag_set!(implied = [cursory_implied_end] - "p" ); |
1164 | self.generate_implied_end(implied); |
1165 | self.expect_to_close(local_name!("p" )); |
1166 | } |
1167 | |
1168 | fn close_p_element_in_button_scope(&self) { |
1169 | if self.in_scope_named(button_scope, local_name!("p" )) { |
1170 | self.close_p_element(); |
1171 | } |
1172 | } |
1173 | |
1174 | // Check <input> tags for type=hidden |
1175 | fn is_type_hidden(&self, tag: &Tag) -> bool { |
1176 | match tag |
1177 | .attrs |
1178 | .iter() |
1179 | .find(|&at| at.name.expanded() == expanded_name!("" , "type" )) |
1180 | { |
1181 | None => false, |
1182 | Some(at) => at.value.eq_ignore_ascii_case("hidden" ), |
1183 | } |
1184 | } |
1185 | |
1186 | fn foster_parent_in_body(&self, token: Token) -> ProcessResult<Handle> { |
1187 | warn!("foster parenting not implemented" ); |
1188 | self.foster_parenting.set(true); |
1189 | let res = self.step(InBody, token); |
1190 | // FIXME: what if res is Reprocess? |
1191 | self.foster_parenting.set(false); |
1192 | res |
1193 | } |
1194 | |
1195 | fn process_chars_in_table(&self, token: Token) -> ProcessResult<Handle> { |
1196 | declare_tag_set!(table_outer = "table" "tbody" "tfoot" "thead" "tr" ); |
1197 | if self.current_node_in(table_outer) { |
1198 | assert!(self.pending_table_text.borrow().is_empty()); |
1199 | self.orig_mode.set(Some(self.mode.get())); |
1200 | Reprocess(InTableText, token) |
1201 | } else { |
1202 | self.sink.parse_error(format_if!( |
1203 | self.opts.exact_errors, |
1204 | "Unexpected characters in table" , |
1205 | "Unexpected characters {} in table" , |
1206 | to_escaped_string(&token) |
1207 | )); |
1208 | self.foster_parent_in_body(token) |
1209 | } |
1210 | } |
1211 | |
1212 | // https://html.spec.whatwg.org/multipage/#reset-the-insertion-mode-appropriately |
1213 | fn reset_insertion_mode(&self) -> InsertionMode { |
1214 | let open_elems = self.open_elems.borrow(); |
1215 | for (i, mut node) in open_elems.iter().enumerate().rev() { |
1216 | let last = i == 0usize; |
1217 | let context_elem = self.context_elem.borrow(); |
1218 | if let (true, Some(ctx)) = (last, context_elem.as_ref()) { |
1219 | node = ctx; |
1220 | } |
1221 | let elem_name = self.sink.elem_name(node); |
1222 | let name = match elem_name.expanded() { |
1223 | ExpandedName { |
1224 | ns: &ns!(html), |
1225 | local, |
1226 | } => local, |
1227 | _ => continue, |
1228 | }; |
1229 | match *name { |
1230 | local_name!("select" ) => { |
1231 | for ancestor in self.open_elems.borrow()[0..i].iter().rev() { |
1232 | if self.html_elem_named(ancestor, local_name!("template" )) { |
1233 | return InSelect; |
1234 | } else if self.html_elem_named(ancestor, local_name!("table" )) { |
1235 | return InSelectInTable; |
1236 | } |
1237 | } |
1238 | return InSelect; |
1239 | }, |
1240 | local_name!("td" ) | local_name!("th" ) => { |
1241 | if !last { |
1242 | return InCell; |
1243 | } |
1244 | }, |
1245 | local_name!("tr" ) => return InRow, |
1246 | local_name!("tbody" ) | local_name!("thead" ) | local_name!("tfoot" ) => { |
1247 | return InTableBody; |
1248 | }, |
1249 | local_name!("caption" ) => return InCaption, |
1250 | local_name!("colgroup" ) => return InColumnGroup, |
1251 | local_name!("table" ) => return InTable, |
1252 | local_name!("template" ) => return *self.template_modes.borrow().last().unwrap(), |
1253 | local_name!("head" ) => { |
1254 | if !last { |
1255 | return InHead; |
1256 | } |
1257 | }, |
1258 | local_name!("body" ) => return InBody, |
1259 | local_name!("frameset" ) => return InFrameset, |
1260 | local_name!("html" ) => match *self.head_elem.borrow() { |
1261 | None => return BeforeHead, |
1262 | Some(_) => return AfterHead, |
1263 | }, |
1264 | |
1265 | _ => (), |
1266 | } |
1267 | } |
1268 | InBody |
1269 | } |
1270 | |
1271 | fn close_the_cell(&self) { |
1272 | self.generate_implied_end(cursory_implied_end); |
1273 | if self.pop_until(td_th) != 1 { |
1274 | self.sink |
1275 | .parse_error(Borrowed("expected to close <td> or <th> with cell" )); |
1276 | } |
1277 | self.clear_active_formatting_to_marker(); |
1278 | } |
1279 | |
1280 | fn append_text(&self, text: StrTendril) -> ProcessResult<Handle> { |
1281 | self.insert_appropriately(AppendText(text), None); |
1282 | Done |
1283 | } |
1284 | |
1285 | fn append_comment(&self, text: StrTendril) -> ProcessResult<Handle> { |
1286 | let comment = self.sink.create_comment(text); |
1287 | self.insert_appropriately(AppendNode(comment), None); |
1288 | Done |
1289 | } |
1290 | |
1291 | fn append_comment_to_doc(&self, text: StrTendril) -> ProcessResult<Handle> { |
1292 | let comment = self.sink.create_comment(text); |
1293 | self.sink.append(&self.doc_handle, AppendNode(comment)); |
1294 | Done |
1295 | } |
1296 | |
1297 | fn append_comment_to_html(&self, text: StrTendril) -> ProcessResult<Handle> { |
1298 | let open_elems = self.open_elems.borrow(); |
1299 | let target = html_elem(&open_elems); |
1300 | let comment = self.sink.create_comment(text); |
1301 | self.sink.append(target, AppendNode(comment)); |
1302 | Done |
1303 | } |
1304 | |
1305 | //§ creating-and-inserting-nodes |
1306 | fn create_root(&self, attrs: Vec<Attribute>) { |
1307 | let elem = create_element( |
1308 | &self.sink, |
1309 | QualName::new(None, ns!(html), local_name!("html" )), |
1310 | attrs, |
1311 | ); |
1312 | self.push(&elem); |
1313 | self.sink.append(&self.doc_handle, AppendNode(elem)); |
1314 | // FIXME: application cache selection algorithm |
1315 | } |
1316 | |
1317 | // https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token |
1318 | fn insert_element( |
1319 | &self, |
1320 | push: PushFlag, |
1321 | ns: Namespace, |
1322 | name: LocalName, |
1323 | attrs: Vec<Attribute>, |
1324 | ) -> Handle { |
1325 | declare_tag_set!(form_associatable = |
1326 | "button" "fieldset" "input" "object" |
1327 | "output" "select" "textarea" "img" ); |
1328 | |
1329 | declare_tag_set!(listed = [form_associatable] - "img" ); |
1330 | |
1331 | // Step 7. |
1332 | let qname = QualName::new(None, ns, name); |
1333 | let elem = create_element(&self.sink, qname.clone(), attrs.clone()); |
1334 | |
1335 | let insertion_point = self.appropriate_place_for_insertion(None); |
1336 | let (node1, node2) = match insertion_point { |
1337 | LastChild(ref p) | BeforeSibling(ref p) => (p.clone(), None), |
1338 | TableFosterParenting { |
1339 | ref element, |
1340 | ref prev_element, |
1341 | } => (element.clone(), Some(prev_element.clone())), |
1342 | }; |
1343 | |
1344 | // Step 12. |
1345 | if form_associatable(qname.expanded()) |
1346 | && self.form_elem.borrow().is_some() |
1347 | && !self.in_html_elem_named(local_name!("template" )) |
1348 | && !(listed(qname.expanded()) |
1349 | && attrs |
1350 | .iter() |
1351 | .any(|a| a.name.expanded() == expanded_name!("" , "form" ))) |
1352 | { |
1353 | let form = self.form_elem.borrow().as_ref().unwrap().clone(); |
1354 | self.sink |
1355 | .associate_with_form(&elem, &form, (&node1, node2.as_ref())); |
1356 | } |
1357 | |
1358 | self.insert_at(insertion_point, AppendNode(elem.clone())); |
1359 | |
1360 | match push { |
1361 | Push => self.push(&elem), |
1362 | NoPush => (), |
1363 | } |
1364 | // FIXME: Remove from the stack if we can't append? |
1365 | elem |
1366 | } |
1367 | |
1368 | fn insert_element_for(&self, tag: Tag) -> Handle { |
1369 | self.insert_element(Push, ns!(html), tag.name, tag.attrs) |
1370 | } |
1371 | |
1372 | fn insert_and_pop_element_for(&self, tag: Tag) -> Handle { |
1373 | self.insert_element(NoPush, ns!(html), tag.name, tag.attrs) |
1374 | } |
1375 | |
1376 | fn insert_phantom(&self, name: LocalName) -> Handle { |
1377 | self.insert_element(Push, ns!(html), name, vec![]) |
1378 | } |
1379 | |
1380 | /// <https://html.spec.whatwg.org/multipage/parsing.html#insert-an-element-at-the-adjusted-insertion-location> |
1381 | fn insert_foreign_element( |
1382 | &self, |
1383 | tag: Tag, |
1384 | ns: Namespace, |
1385 | only_add_to_element_stack: bool, |
1386 | ) -> Handle { |
1387 | let adjusted_insertion_location = self.appropriate_place_for_insertion(None); |
1388 | let qname = QualName::new(None, ns, tag.name); |
1389 | let elem = create_element(&self.sink, qname.clone(), tag.attrs.clone()); |
1390 | |
1391 | if !only_add_to_element_stack { |
1392 | self.insert_at(adjusted_insertion_location, AppendNode(elem.clone())); |
1393 | } |
1394 | |
1395 | self.push(&elem); |
1396 | |
1397 | elem |
1398 | } |
1399 | //§ END |
1400 | |
1401 | /// <https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead> |
1402 | /// |
1403 | /// A start tag whose tag name is "template" |
1404 | fn should_attach_declarative_shadow(&self, tag: &Tag) -> bool { |
1405 | let adjusted_insertion_location = self.appropriate_place_for_insertion(None); |
1406 | |
1407 | let (intended_parent, _node2) = match adjusted_insertion_location { |
1408 | LastChild(ref p) | BeforeSibling(ref p) => (p.clone(), None), |
1409 | TableFosterParenting { |
1410 | ref element, |
1411 | ref prev_element, |
1412 | } => (element.clone(), Some(prev_element.clone())), |
1413 | }; |
1414 | |
1415 | // template start tag's shadowrootmode is not in the none state |
1416 | let is_shadow_root_mode = tag.attrs.iter().any(|attr| { |
1417 | attr.name.local == local_name!("shadowrootmode" ) |
1418 | && (attr.value.as_ref() == "open" || attr.value.as_ref() == "closed" ) |
1419 | }); |
1420 | |
1421 | // Check if intended_parent's document allows declarative shadow roots |
1422 | let allow_declarative_shadow_roots = |
1423 | self.sink.allow_declarative_shadow_roots(&intended_parent); |
1424 | |
1425 | // the adjusted current node is not the topmost element in the stack of open elements |
1426 | let adjusted_current_node_not_topmost = match self.open_elems.borrow().first() { |
1427 | // The stack grows downwards; the topmost node on the stack is the first one added to the stack |
1428 | // The current node is the bottommost node in this stack of open elements. |
1429 | // |
1430 | // (1) The adjusted current node is the context element if the parser was created as part of the HTML fragment parsing algorithm |
1431 | // and the stack of open elements has only one element in it (fragment case); |
1432 | // (2) otherwise, the adjusted current node is the current node (the bottomost node) |
1433 | // |
1434 | // => adjusted current node != topmost element in the stack when the stack size > 1 |
1435 | Some(_) => self.open_elems.borrow().len() > 1, |
1436 | None => true, |
1437 | }; |
1438 | |
1439 | is_shadow_root_mode && allow_declarative_shadow_roots && adjusted_current_node_not_topmost |
1440 | } |
1441 | |
1442 | /// <https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead> |
1443 | /// |
1444 | /// A start tag whose tag name is "template" |
1445 | fn attach_declarative_shadow( |
1446 | &self, |
1447 | tag: &Tag, |
1448 | shadow_host: &Handle, |
1449 | template: &Handle, |
1450 | ) -> Result<(), String> { |
1451 | self.sink |
1452 | .attach_declarative_shadow(shadow_host, template, tag.attrs.clone()) |
1453 | } |
1454 | |
1455 | fn create_formatting_element_for(&self, tag: Tag) -> Handle { |
1456 | // FIXME: This really wants unit tests. |
1457 | let mut first_match = None; |
1458 | let mut matches = 0usize; |
1459 | for (i, _, old_tag) in self.active_formatting_end_to_marker().iter() { |
1460 | if tag.equiv_modulo_attr_order(old_tag) { |
1461 | first_match = Some(i); |
1462 | matches += 1; |
1463 | } |
1464 | } |
1465 | |
1466 | if matches >= 3 { |
1467 | self.active_formatting |
1468 | .borrow_mut() |
1469 | .remove(first_match.expect("matches with no index" )); |
1470 | } |
1471 | |
1472 | let elem = self.insert_element(Push, ns!(html), tag.name.clone(), tag.attrs.clone()); |
1473 | self.active_formatting |
1474 | .borrow_mut() |
1475 | .push(Element(elem.clone(), tag)); |
1476 | elem |
1477 | } |
1478 | |
1479 | fn clear_active_formatting_to_marker(&self) { |
1480 | loop { |
1481 | match self.active_formatting.borrow_mut().pop() { |
1482 | None | Some(Marker) => break, |
1483 | _ => (), |
1484 | } |
1485 | } |
1486 | } |
1487 | |
1488 | fn process_end_tag_in_body(&self, tag: Tag) { |
1489 | // Look back for a matching open element. |
1490 | let mut match_idx = None; |
1491 | for (i, elem) in self.open_elems.borrow().iter().enumerate().rev() { |
1492 | if self.html_elem_named(elem, tag.name.clone()) { |
1493 | match_idx = Some(i); |
1494 | break; |
1495 | } |
1496 | |
1497 | if self.elem_in(elem, special_tag) { |
1498 | self.sink |
1499 | .parse_error(Borrowed("Found special tag while closing generic tag" )); |
1500 | return; |
1501 | } |
1502 | } |
1503 | |
1504 | // Can't use unwrap_or_return!() due to rust-lang/rust#16617. |
1505 | let match_idx = match match_idx { |
1506 | None => { |
1507 | // I believe this is impossible, because the root |
1508 | // <html> element is in special_tag. |
1509 | self.unexpected(&tag); |
1510 | return; |
1511 | }, |
1512 | Some(x) => x, |
1513 | }; |
1514 | |
1515 | self.generate_implied_end_except(tag.name.clone()); |
1516 | |
1517 | if match_idx != self.open_elems.borrow().len() - 1 { |
1518 | // mis-nested tags |
1519 | self.unexpected(&tag); |
1520 | } |
1521 | self.open_elems.borrow_mut().truncate(match_idx); |
1522 | } |
1523 | |
1524 | fn handle_misnested_a_tags(&self, tag: &Tag) { |
1525 | let node = unwrap_or_return!(self |
1526 | .active_formatting_end_to_marker() |
1527 | .iter() |
1528 | .find(|&(_, n, _)| self.html_elem_named(n, local_name!("a" ))) |
1529 | .map(|(_, n, _)| n.clone())); |
1530 | |
1531 | self.unexpected(tag); |
1532 | self.adoption_agency(local_name!("a" )); |
1533 | self.position_in_active_formatting(&node) |
1534 | .map(|index| self.active_formatting.borrow_mut().remove(index)); |
1535 | self.remove_from_stack(&node); |
1536 | } |
1537 | |
1538 | //§ tree-construction |
1539 | fn is_foreign(&self, token: &Token) -> bool { |
1540 | if let EOFToken = *token { |
1541 | return false; |
1542 | } |
1543 | |
1544 | if self.open_elems.borrow().is_empty() { |
1545 | return false; |
1546 | } |
1547 | |
1548 | let current = self.adjusted_current_node(); |
1549 | let elem_name = self.sink.elem_name(¤t); |
1550 | let name = elem_name.expanded(); |
1551 | if let ns!(html) = *name.ns { |
1552 | return false; |
1553 | } |
1554 | |
1555 | if mathml_text_integration_point(name) { |
1556 | match *token { |
1557 | CharacterTokens(..) | NullCharacterToken => return false, |
1558 | TagToken(Tag { |
1559 | kind: StartTag, |
1560 | ref name, |
1561 | .. |
1562 | }) if !matches!(*name, local_name!("mglyph" ) | local_name!("malignmark" )) => { |
1563 | return false; |
1564 | }, |
1565 | _ => (), |
1566 | } |
1567 | } |
1568 | |
1569 | if svg_html_integration_point(name) { |
1570 | match *token { |
1571 | CharacterTokens(..) | NullCharacterToken => return false, |
1572 | TagToken(Tag { kind: StartTag, .. }) => return false, |
1573 | _ => (), |
1574 | } |
1575 | } |
1576 | |
1577 | if let expanded_name!(mathml "annotation-xml" ) = name { |
1578 | match *token { |
1579 | TagToken(Tag { |
1580 | kind: StartTag, |
1581 | name: local_name!("svg" ), |
1582 | .. |
1583 | }) => return false, |
1584 | CharacterTokens(..) | NullCharacterToken | TagToken(Tag { kind: StartTag, .. }) => { |
1585 | return !self |
1586 | .sink |
1587 | .is_mathml_annotation_xml_integration_point(&self.adjusted_current_node()); |
1588 | }, |
1589 | _ => {}, |
1590 | }; |
1591 | } |
1592 | |
1593 | true |
1594 | } |
1595 | //§ END |
1596 | |
1597 | fn enter_foreign(&self, mut tag: Tag, ns: Namespace) -> ProcessResult<Handle> { |
1598 | match ns { |
1599 | ns!(mathml) => self.adjust_mathml_attributes(&mut tag), |
1600 | ns!(svg) => self.adjust_svg_attributes(&mut tag), |
1601 | _ => (), |
1602 | } |
1603 | self.adjust_foreign_attributes(&mut tag); |
1604 | |
1605 | if tag.self_closing { |
1606 | self.insert_element(NoPush, ns, tag.name, tag.attrs); |
1607 | DoneAckSelfClosing |
1608 | } else { |
1609 | self.insert_element(Push, ns, tag.name, tag.attrs); |
1610 | Done |
1611 | } |
1612 | } |
1613 | |
1614 | fn adjust_svg_tag_name(&self, tag: &mut Tag) { |
1615 | let Tag { ref mut name, .. } = *tag; |
1616 | match *name { |
1617 | local_name!("altglyph" ) => *name = local_name!("altGlyph" ), |
1618 | local_name!("altglyphdef" ) => *name = local_name!("altGlyphDef" ), |
1619 | local_name!("altglyphitem" ) => *name = local_name!("altGlyphItem" ), |
1620 | local_name!("animatecolor" ) => *name = local_name!("animateColor" ), |
1621 | local_name!("animatemotion" ) => *name = local_name!("animateMotion" ), |
1622 | local_name!("animatetransform" ) => *name = local_name!("animateTransform" ), |
1623 | local_name!("clippath" ) => *name = local_name!("clipPath" ), |
1624 | local_name!("feblend" ) => *name = local_name!("feBlend" ), |
1625 | local_name!("fecolormatrix" ) => *name = local_name!("feColorMatrix" ), |
1626 | local_name!("fecomponenttransfer" ) => *name = local_name!("feComponentTransfer" ), |
1627 | local_name!("fecomposite" ) => *name = local_name!("feComposite" ), |
1628 | local_name!("feconvolvematrix" ) => *name = local_name!("feConvolveMatrix" ), |
1629 | local_name!("fediffuselighting" ) => *name = local_name!("feDiffuseLighting" ), |
1630 | local_name!("fedisplacementmap" ) => *name = local_name!("feDisplacementMap" ), |
1631 | local_name!("fedistantlight" ) => *name = local_name!("feDistantLight" ), |
1632 | local_name!("fedropshadow" ) => *name = local_name!("feDropShadow" ), |
1633 | local_name!("feflood" ) => *name = local_name!("feFlood" ), |
1634 | local_name!("fefunca" ) => *name = local_name!("feFuncA" ), |
1635 | local_name!("fefuncb" ) => *name = local_name!("feFuncB" ), |
1636 | local_name!("fefuncg" ) => *name = local_name!("feFuncG" ), |
1637 | local_name!("fefuncr" ) => *name = local_name!("feFuncR" ), |
1638 | local_name!("fegaussianblur" ) => *name = local_name!("feGaussianBlur" ), |
1639 | local_name!("feimage" ) => *name = local_name!("feImage" ), |
1640 | local_name!("femerge" ) => *name = local_name!("feMerge" ), |
1641 | local_name!("femergenode" ) => *name = local_name!("feMergeNode" ), |
1642 | local_name!("femorphology" ) => *name = local_name!("feMorphology" ), |
1643 | local_name!("feoffset" ) => *name = local_name!("feOffset" ), |
1644 | local_name!("fepointlight" ) => *name = local_name!("fePointLight" ), |
1645 | local_name!("fespecularlighting" ) => *name = local_name!("feSpecularLighting" ), |
1646 | local_name!("fespotlight" ) => *name = local_name!("feSpotLight" ), |
1647 | local_name!("fetile" ) => *name = local_name!("feTile" ), |
1648 | local_name!("feturbulence" ) => *name = local_name!("feTurbulence" ), |
1649 | local_name!("foreignobject" ) => *name = local_name!("foreignObject" ), |
1650 | local_name!("glyphref" ) => *name = local_name!("glyphRef" ), |
1651 | local_name!("lineargradient" ) => *name = local_name!("linearGradient" ), |
1652 | local_name!("radialgradient" ) => *name = local_name!("radialGradient" ), |
1653 | local_name!("textpath" ) => *name = local_name!("textPath" ), |
1654 | _ => (), |
1655 | } |
1656 | } |
1657 | |
1658 | fn adjust_attributes<F>(&self, tag: &mut Tag, mut map: F) |
1659 | where |
1660 | F: FnMut(LocalName) -> Option<QualName>, |
1661 | { |
1662 | for &mut Attribute { ref mut name, .. } in &mut tag.attrs { |
1663 | if let Some(replacement) = map(name.local.clone()) { |
1664 | *name = replacement; |
1665 | } |
1666 | } |
1667 | } |
1668 | |
1669 | fn adjust_svg_attributes(&self, tag: &mut Tag) { |
1670 | self.adjust_attributes(tag, |k| match k { |
1671 | local_name!("attributename" ) => Some(qualname!("" , "attributeName" )), |
1672 | local_name!("attributetype" ) => Some(qualname!("" , "attributeType" )), |
1673 | local_name!("basefrequency" ) => Some(qualname!("" , "baseFrequency" )), |
1674 | local_name!("baseprofile" ) => Some(qualname!("" , "baseProfile" )), |
1675 | local_name!("calcmode" ) => Some(qualname!("" , "calcMode" )), |
1676 | local_name!("clippathunits" ) => Some(qualname!("" , "clipPathUnits" )), |
1677 | local_name!("diffuseconstant" ) => Some(qualname!("" , "diffuseConstant" )), |
1678 | local_name!("edgemode" ) => Some(qualname!("" , "edgeMode" )), |
1679 | local_name!("filterunits" ) => Some(qualname!("" , "filterUnits" )), |
1680 | local_name!("glyphref" ) => Some(qualname!("" , "glyphRef" )), |
1681 | local_name!("gradienttransform" ) => Some(qualname!("" , "gradientTransform" )), |
1682 | local_name!("gradientunits" ) => Some(qualname!("" , "gradientUnits" )), |
1683 | local_name!("kernelmatrix" ) => Some(qualname!("" , "kernelMatrix" )), |
1684 | local_name!("kernelunitlength" ) => Some(qualname!("" , "kernelUnitLength" )), |
1685 | local_name!("keypoints" ) => Some(qualname!("" , "keyPoints" )), |
1686 | local_name!("keysplines" ) => Some(qualname!("" , "keySplines" )), |
1687 | local_name!("keytimes" ) => Some(qualname!("" , "keyTimes" )), |
1688 | local_name!("lengthadjust" ) => Some(qualname!("" , "lengthAdjust" )), |
1689 | local_name!("limitingconeangle" ) => Some(qualname!("" , "limitingConeAngle" )), |
1690 | local_name!("markerheight" ) => Some(qualname!("" , "markerHeight" )), |
1691 | local_name!("markerunits" ) => Some(qualname!("" , "markerUnits" )), |
1692 | local_name!("markerwidth" ) => Some(qualname!("" , "markerWidth" )), |
1693 | local_name!("maskcontentunits" ) => Some(qualname!("" , "maskContentUnits" )), |
1694 | local_name!("maskunits" ) => Some(qualname!("" , "maskUnits" )), |
1695 | local_name!("numoctaves" ) => Some(qualname!("" , "numOctaves" )), |
1696 | local_name!("pathlength" ) => Some(qualname!("" , "pathLength" )), |
1697 | local_name!("patterncontentunits" ) => Some(qualname!("" , "patternContentUnits" )), |
1698 | local_name!("patterntransform" ) => Some(qualname!("" , "patternTransform" )), |
1699 | local_name!("patternunits" ) => Some(qualname!("" , "patternUnits" )), |
1700 | local_name!("pointsatx" ) => Some(qualname!("" , "pointsAtX" )), |
1701 | local_name!("pointsaty" ) => Some(qualname!("" , "pointsAtY" )), |
1702 | local_name!("pointsatz" ) => Some(qualname!("" , "pointsAtZ" )), |
1703 | local_name!("preservealpha" ) => Some(qualname!("" , "preserveAlpha" )), |
1704 | local_name!("preserveaspectratio" ) => Some(qualname!("" , "preserveAspectRatio" )), |
1705 | local_name!("primitiveunits" ) => Some(qualname!("" , "primitiveUnits" )), |
1706 | local_name!("refx" ) => Some(qualname!("" , "refX" )), |
1707 | local_name!("refy" ) => Some(qualname!("" , "refY" )), |
1708 | local_name!("repeatcount" ) => Some(qualname!("" , "repeatCount" )), |
1709 | local_name!("repeatdur" ) => Some(qualname!("" , "repeatDur" )), |
1710 | local_name!("requiredextensions" ) => Some(qualname!("" , "requiredExtensions" )), |
1711 | local_name!("requiredfeatures" ) => Some(qualname!("" , "requiredFeatures" )), |
1712 | local_name!("specularconstant" ) => Some(qualname!("" , "specularConstant" )), |
1713 | local_name!("specularexponent" ) => Some(qualname!("" , "specularExponent" )), |
1714 | local_name!("spreadmethod" ) => Some(qualname!("" , "spreadMethod" )), |
1715 | local_name!("startoffset" ) => Some(qualname!("" , "startOffset" )), |
1716 | local_name!("stddeviation" ) => Some(qualname!("" , "stdDeviation" )), |
1717 | local_name!("stitchtiles" ) => Some(qualname!("" , "stitchTiles" )), |
1718 | local_name!("surfacescale" ) => Some(qualname!("" , "surfaceScale" )), |
1719 | local_name!("systemlanguage" ) => Some(qualname!("" , "systemLanguage" )), |
1720 | local_name!("tablevalues" ) => Some(qualname!("" , "tableValues" )), |
1721 | local_name!("targetx" ) => Some(qualname!("" , "targetX" )), |
1722 | local_name!("targety" ) => Some(qualname!("" , "targetY" )), |
1723 | local_name!("textlength" ) => Some(qualname!("" , "textLength" )), |
1724 | local_name!("viewbox" ) => Some(qualname!("" , "viewBox" )), |
1725 | local_name!("viewtarget" ) => Some(qualname!("" , "viewTarget" )), |
1726 | local_name!("xchannelselector" ) => Some(qualname!("" , "xChannelSelector" )), |
1727 | local_name!("ychannelselector" ) => Some(qualname!("" , "yChannelSelector" )), |
1728 | local_name!("zoomandpan" ) => Some(qualname!("" , "zoomAndPan" )), |
1729 | _ => None, |
1730 | }); |
1731 | } |
1732 | |
1733 | fn adjust_mathml_attributes(&self, tag: &mut Tag) { |
1734 | self.adjust_attributes(tag, |k| match k { |
1735 | local_name!("definitionurl" ) => Some(qualname!("" , "definitionURL" )), |
1736 | _ => None, |
1737 | }); |
1738 | } |
1739 | |
1740 | fn adjust_foreign_attributes(&self, tag: &mut Tag) { |
1741 | self.adjust_attributes(tag, |k| match k { |
1742 | local_name!("xlink:actuate" ) => Some(qualname!("xlink" xlink "actuate" )), |
1743 | local_name!("xlink:arcrole" ) => Some(qualname!("xlink" xlink "arcrole" )), |
1744 | local_name!("xlink:href" ) => Some(qualname!("xlink" xlink "href" )), |
1745 | local_name!("xlink:role" ) => Some(qualname!("xlink" xlink "role" )), |
1746 | local_name!("xlink:show" ) => Some(qualname!("xlink" xlink "show" )), |
1747 | local_name!("xlink:title" ) => Some(qualname!("xlink" xlink "title" )), |
1748 | local_name!("xlink:type" ) => Some(qualname!("xlink" xlink "type" )), |
1749 | local_name!("xml:lang" ) => Some(qualname!("xml" xml "lang" )), |
1750 | local_name!("xml:space" ) => Some(qualname!("xml" xml "space" )), |
1751 | local_name!("xmlns" ) => Some(qualname!("" xmlns "xmlns" )), |
1752 | local_name!("xmlns:xlink" ) => Some(qualname!("xmlns" xmlns "xlink" )), |
1753 | _ => None, |
1754 | }); |
1755 | } |
1756 | |
1757 | fn foreign_start_tag(&self, mut tag: Tag) -> ProcessResult<Handle> { |
1758 | let current_ns = self |
1759 | .sink |
1760 | .elem_name(&self.adjusted_current_node()) |
1761 | .ns() |
1762 | .clone(); |
1763 | match current_ns { |
1764 | ns!(mathml) => self.adjust_mathml_attributes(&mut tag), |
1765 | ns!(svg) => { |
1766 | self.adjust_svg_tag_name(&mut tag); |
1767 | self.adjust_svg_attributes(&mut tag); |
1768 | }, |
1769 | _ => (), |
1770 | } |
1771 | self.adjust_foreign_attributes(&mut tag); |
1772 | if tag.self_closing { |
1773 | // FIXME(#118): <script /> in SVG |
1774 | self.insert_element(NoPush, current_ns, tag.name, tag.attrs); |
1775 | DoneAckSelfClosing |
1776 | } else { |
1777 | self.insert_element(Push, current_ns, tag.name, tag.attrs); |
1778 | Done |
1779 | } |
1780 | } |
1781 | |
1782 | fn unexpected_start_tag_in_foreign_content(&self, tag: Tag) -> ProcessResult<Handle> { |
1783 | self.unexpected(&tag); |
1784 | while !self.current_node_in(|n| { |
1785 | *n.ns == ns!(html) || mathml_text_integration_point(n) || svg_html_integration_point(n) |
1786 | }) { |
1787 | self.pop(); |
1788 | } |
1789 | self.step(self.mode.get(), TagToken(tag)) |
1790 | } |
1791 | } |
1792 | |