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