• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3var Tokenizer = require('../tokenization/tokenizer'),
4    OpenElementStack = require('./open_element_stack'),
5    FormattingElementList = require('./formatting_element_list'),
6    LocationInfoMixin = require('./location_info_mixin'),
7    DefaultTreeAdapter = require('../tree_adapters/default'),
8    Doctype = require('../common/doctype'),
9    ForeignContent = require('../common/foreign_content'),
10    Utils = require('../common/utils'),
11    UNICODE = require('../common/unicode'),
12    HTML = require('../common/html');
13
14//Aliases
15var $ = HTML.TAG_NAMES,
16    NS = HTML.NAMESPACES,
17    ATTRS = HTML.ATTRS;
18
19//Default options
20var DEFAULT_OPTIONS = {
21    decodeHtmlEntities: true,
22    locationInfo: false
23};
24
25//Misc constants
26var SEARCHABLE_INDEX_DEFAULT_PROMPT = 'This is a searchable index. Enter search keywords: ',
27    SEARCHABLE_INDEX_INPUT_NAME = 'isindex',
28    HIDDEN_INPUT_TYPE = 'hidden';
29
30//Adoption agency loops iteration count
31var AA_OUTER_LOOP_ITER = 8,
32    AA_INNER_LOOP_ITER = 3;
33
34//Insertion modes
35var INITIAL_MODE = 'INITIAL_MODE',
36    BEFORE_HTML_MODE = 'BEFORE_HTML_MODE',
37    BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE',
38    IN_HEAD_MODE = 'IN_HEAD_MODE',
39    AFTER_HEAD_MODE = 'AFTER_HEAD_MODE',
40    IN_BODY_MODE = 'IN_BODY_MODE',
41    TEXT_MODE = 'TEXT_MODE',
42    IN_TABLE_MODE = 'IN_TABLE_MODE',
43    IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE',
44    IN_CAPTION_MODE = 'IN_CAPTION_MODE',
45    IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE',
46    IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE',
47    IN_ROW_MODE = 'IN_ROW_MODE',
48    IN_CELL_MODE = 'IN_CELL_MODE',
49    IN_SELECT_MODE = 'IN_SELECT_MODE',
50    IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE',
51    IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE',
52    AFTER_BODY_MODE = 'AFTER_BODY_MODE',
53    IN_FRAMESET_MODE = 'IN_FRAMESET_MODE',
54    AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE',
55    AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE',
56    AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE';
57
58//Insertion mode reset map
59var INSERTION_MODE_RESET_MAP = {};
60
61INSERTION_MODE_RESET_MAP[$.TR] = IN_ROW_MODE;
62INSERTION_MODE_RESET_MAP[$.TBODY] =
63INSERTION_MODE_RESET_MAP[$.THEAD] =
64INSERTION_MODE_RESET_MAP[$.TFOOT] = IN_TABLE_BODY_MODE;
65INSERTION_MODE_RESET_MAP[$.CAPTION] = IN_CAPTION_MODE;
66INSERTION_MODE_RESET_MAP[$.COLGROUP] = IN_COLUMN_GROUP_MODE;
67INSERTION_MODE_RESET_MAP[$.TABLE] = IN_TABLE_MODE;
68INSERTION_MODE_RESET_MAP[$.BODY] = IN_BODY_MODE;
69INSERTION_MODE_RESET_MAP[$.FRAMESET] = IN_FRAMESET_MODE;
70
71//Template insertion mode switch map
72var TEMPLATE_INSERTION_MODE_SWITCH_MAP = {};
73
74TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.CAPTION] =
75TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.COLGROUP] =
76TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TBODY] =
77TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TFOOT] =
78TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.THEAD] = IN_TABLE_MODE;
79TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.COL] = IN_COLUMN_GROUP_MODE;
80TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TR] = IN_TABLE_BODY_MODE;
81TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TD] =
82TEMPLATE_INSERTION_MODE_SWITCH_MAP[$.TH] = IN_ROW_MODE;
83
84//Token handlers map for insertion modes
85var _ = {};
86
87_[INITIAL_MODE] = {};
88_[INITIAL_MODE][Tokenizer.CHARACTER_TOKEN] =
89_[INITIAL_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenInInitialMode;
90_[INITIAL_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken;
91_[INITIAL_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
92_[INITIAL_MODE][Tokenizer.DOCTYPE_TOKEN] = doctypeInInitialMode;
93_[INITIAL_MODE][Tokenizer.START_TAG_TOKEN] =
94_[INITIAL_MODE][Tokenizer.END_TAG_TOKEN] =
95_[INITIAL_MODE][Tokenizer.EOF_TOKEN] = tokenInInitialMode;
96
97_[BEFORE_HTML_MODE] = {};
98_[BEFORE_HTML_MODE][Tokenizer.CHARACTER_TOKEN] =
99_[BEFORE_HTML_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenBeforeHtml;
100_[BEFORE_HTML_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken;
101_[BEFORE_HTML_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
102_[BEFORE_HTML_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
103_[BEFORE_HTML_MODE][Tokenizer.START_TAG_TOKEN] = startTagBeforeHtml;
104_[BEFORE_HTML_MODE][Tokenizer.END_TAG_TOKEN] = endTagBeforeHtml;
105_[BEFORE_HTML_MODE][Tokenizer.EOF_TOKEN] = tokenBeforeHtml;
106
107_[BEFORE_HEAD_MODE] = {};
108_[BEFORE_HEAD_MODE][Tokenizer.CHARACTER_TOKEN] =
109_[BEFORE_HEAD_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenBeforeHead;
110_[BEFORE_HEAD_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = ignoreToken;
111_[BEFORE_HEAD_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
112_[BEFORE_HEAD_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
113_[BEFORE_HEAD_MODE][Tokenizer.START_TAG_TOKEN] = startTagBeforeHead;
114_[BEFORE_HEAD_MODE][Tokenizer.END_TAG_TOKEN] = endTagBeforeHead;
115_[BEFORE_HEAD_MODE][Tokenizer.EOF_TOKEN] = tokenBeforeHead;
116
117_[IN_HEAD_MODE] = {};
118_[IN_HEAD_MODE][Tokenizer.CHARACTER_TOKEN] =
119_[IN_HEAD_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenInHead;
120_[IN_HEAD_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
121_[IN_HEAD_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
122_[IN_HEAD_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
123_[IN_HEAD_MODE][Tokenizer.START_TAG_TOKEN] = startTagInHead;
124_[IN_HEAD_MODE][Tokenizer.END_TAG_TOKEN] = endTagInHead;
125_[IN_HEAD_MODE][Tokenizer.EOF_TOKEN] = tokenInHead;
126
127_[AFTER_HEAD_MODE] = {};
128_[AFTER_HEAD_MODE][Tokenizer.CHARACTER_TOKEN] =
129_[AFTER_HEAD_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterHead;
130_[AFTER_HEAD_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
131_[AFTER_HEAD_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
132_[AFTER_HEAD_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
133_[AFTER_HEAD_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterHead;
134_[AFTER_HEAD_MODE][Tokenizer.END_TAG_TOKEN] = endTagAfterHead;
135_[AFTER_HEAD_MODE][Tokenizer.EOF_TOKEN] = tokenAfterHead;
136
137_[IN_BODY_MODE] = {};
138_[IN_BODY_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody;
139_[IN_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
140_[IN_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
141_[IN_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
142_[IN_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
143_[IN_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagInBody;
144_[IN_BODY_MODE][Tokenizer.END_TAG_TOKEN] = endTagInBody;
145_[IN_BODY_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
146
147_[TEXT_MODE] = {};
148_[TEXT_MODE][Tokenizer.CHARACTER_TOKEN] =
149_[TEXT_MODE][Tokenizer.NULL_CHARACTER_TOKEN] =
150_[TEXT_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
151_[TEXT_MODE][Tokenizer.COMMENT_TOKEN] =
152_[TEXT_MODE][Tokenizer.DOCTYPE_TOKEN] =
153_[TEXT_MODE][Tokenizer.START_TAG_TOKEN] = ignoreToken;
154_[TEXT_MODE][Tokenizer.END_TAG_TOKEN] = endTagInText;
155_[TEXT_MODE][Tokenizer.EOF_TOKEN] = eofInText;
156
157_[IN_TABLE_MODE] = {};
158_[IN_TABLE_MODE][Tokenizer.CHARACTER_TOKEN] =
159_[IN_TABLE_MODE][Tokenizer.NULL_CHARACTER_TOKEN] =
160_[IN_TABLE_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable;
161_[IN_TABLE_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
162_[IN_TABLE_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
163_[IN_TABLE_MODE][Tokenizer.START_TAG_TOKEN] = startTagInTable;
164_[IN_TABLE_MODE][Tokenizer.END_TAG_TOKEN] = endTagInTable;
165_[IN_TABLE_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
166
167_[IN_TABLE_TEXT_MODE] = {};
168_[IN_TABLE_TEXT_MODE][Tokenizer.CHARACTER_TOKEN] = characterInTableText;
169_[IN_TABLE_TEXT_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
170_[IN_TABLE_TEXT_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInTableText;
171_[IN_TABLE_TEXT_MODE][Tokenizer.COMMENT_TOKEN] =
172_[IN_TABLE_TEXT_MODE][Tokenizer.DOCTYPE_TOKEN] =
173_[IN_TABLE_TEXT_MODE][Tokenizer.START_TAG_TOKEN] =
174_[IN_TABLE_TEXT_MODE][Tokenizer.END_TAG_TOKEN] =
175_[IN_TABLE_TEXT_MODE][Tokenizer.EOF_TOKEN] = tokenInTableText;
176
177_[IN_CAPTION_MODE] = {};
178_[IN_CAPTION_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody;
179_[IN_CAPTION_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
180_[IN_CAPTION_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
181_[IN_CAPTION_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
182_[IN_CAPTION_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
183_[IN_CAPTION_MODE][Tokenizer.START_TAG_TOKEN] = startTagInCaption;
184_[IN_CAPTION_MODE][Tokenizer.END_TAG_TOKEN] = endTagInCaption;
185_[IN_CAPTION_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
186
187_[IN_COLUMN_GROUP_MODE] = {};
188_[IN_COLUMN_GROUP_MODE][Tokenizer.CHARACTER_TOKEN] =
189_[IN_COLUMN_GROUP_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenInColumnGroup;
190_[IN_COLUMN_GROUP_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
191_[IN_COLUMN_GROUP_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
192_[IN_COLUMN_GROUP_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
193_[IN_COLUMN_GROUP_MODE][Tokenizer.START_TAG_TOKEN] = startTagInColumnGroup;
194_[IN_COLUMN_GROUP_MODE][Tokenizer.END_TAG_TOKEN] = endTagInColumnGroup;
195_[IN_COLUMN_GROUP_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
196
197_[IN_TABLE_BODY_MODE] = {};
198_[IN_TABLE_BODY_MODE][Tokenizer.CHARACTER_TOKEN] =
199_[IN_TABLE_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] =
200_[IN_TABLE_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable;
201_[IN_TABLE_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
202_[IN_TABLE_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
203_[IN_TABLE_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagInTableBody;
204_[IN_TABLE_BODY_MODE][Tokenizer.END_TAG_TOKEN] = endTagInTableBody;
205_[IN_TABLE_BODY_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
206
207_[IN_ROW_MODE] = {};
208_[IN_ROW_MODE][Tokenizer.CHARACTER_TOKEN] =
209_[IN_ROW_MODE][Tokenizer.NULL_CHARACTER_TOKEN] =
210_[IN_ROW_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = characterInTable;
211_[IN_ROW_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
212_[IN_ROW_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
213_[IN_ROW_MODE][Tokenizer.START_TAG_TOKEN] = startTagInRow;
214_[IN_ROW_MODE][Tokenizer.END_TAG_TOKEN] = endTagInRow;
215_[IN_ROW_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
216
217_[IN_CELL_MODE] = {};
218_[IN_CELL_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody;
219_[IN_CELL_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
220_[IN_CELL_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
221_[IN_CELL_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
222_[IN_CELL_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
223_[IN_CELL_MODE][Tokenizer.START_TAG_TOKEN] = startTagInCell;
224_[IN_CELL_MODE][Tokenizer.END_TAG_TOKEN] = endTagInCell;
225_[IN_CELL_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
226
227_[IN_SELECT_MODE] = {};
228_[IN_SELECT_MODE][Tokenizer.CHARACTER_TOKEN] = insertCharacters;
229_[IN_SELECT_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
230_[IN_SELECT_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
231_[IN_SELECT_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
232_[IN_SELECT_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
233_[IN_SELECT_MODE][Tokenizer.START_TAG_TOKEN] = startTagInSelect;
234_[IN_SELECT_MODE][Tokenizer.END_TAG_TOKEN] = endTagInSelect;
235_[IN_SELECT_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
236
237_[IN_SELECT_IN_TABLE_MODE] = {};
238_[IN_SELECT_IN_TABLE_MODE][Tokenizer.CHARACTER_TOKEN] = insertCharacters;
239_[IN_SELECT_IN_TABLE_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
240_[IN_SELECT_IN_TABLE_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
241_[IN_SELECT_IN_TABLE_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
242_[IN_SELECT_IN_TABLE_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
243_[IN_SELECT_IN_TABLE_MODE][Tokenizer.START_TAG_TOKEN] = startTagInSelectInTable;
244_[IN_SELECT_IN_TABLE_MODE][Tokenizer.END_TAG_TOKEN] = endTagInSelectInTable;
245_[IN_SELECT_IN_TABLE_MODE][Tokenizer.EOF_TOKEN] = eofInBody;
246
247_[IN_TEMPLATE_MODE] = {};
248_[IN_TEMPLATE_MODE][Tokenizer.CHARACTER_TOKEN] = characterInBody;
249_[IN_TEMPLATE_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
250_[IN_TEMPLATE_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
251_[IN_TEMPLATE_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
252_[IN_TEMPLATE_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
253_[IN_TEMPLATE_MODE][Tokenizer.START_TAG_TOKEN] = startTagInTemplate;
254_[IN_TEMPLATE_MODE][Tokenizer.END_TAG_TOKEN] = endTagInTemplate;
255_[IN_TEMPLATE_MODE][Tokenizer.EOF_TOKEN] = eofInTemplate;
256
257_[AFTER_BODY_MODE] = {};
258_[AFTER_BODY_MODE][Tokenizer.CHARACTER_TOKEN] =
259_[AFTER_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterBody;
260_[AFTER_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
261_[AFTER_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendCommentToRootHtmlElement;
262_[AFTER_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
263_[AFTER_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterBody;
264_[AFTER_BODY_MODE][Tokenizer.END_TAG_TOKEN] = endTagAfterBody;
265_[AFTER_BODY_MODE][Tokenizer.EOF_TOKEN] = stopParsing;
266
267_[IN_FRAMESET_MODE] = {};
268_[IN_FRAMESET_MODE][Tokenizer.CHARACTER_TOKEN] =
269_[IN_FRAMESET_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
270_[IN_FRAMESET_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
271_[IN_FRAMESET_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
272_[IN_FRAMESET_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
273_[IN_FRAMESET_MODE][Tokenizer.START_TAG_TOKEN] = startTagInFrameset;
274_[IN_FRAMESET_MODE][Tokenizer.END_TAG_TOKEN] = endTagInFrameset;
275_[IN_FRAMESET_MODE][Tokenizer.EOF_TOKEN] = stopParsing;
276
277_[AFTER_FRAMESET_MODE] = {};
278_[AFTER_FRAMESET_MODE][Tokenizer.CHARACTER_TOKEN] =
279_[AFTER_FRAMESET_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
280_[AFTER_FRAMESET_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = insertCharacters;
281_[AFTER_FRAMESET_MODE][Tokenizer.COMMENT_TOKEN] = appendComment;
282_[AFTER_FRAMESET_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
283_[AFTER_FRAMESET_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterFrameset;
284_[AFTER_FRAMESET_MODE][Tokenizer.END_TAG_TOKEN] = endTagAfterFrameset;
285_[AFTER_FRAMESET_MODE][Tokenizer.EOF_TOKEN] = stopParsing;
286
287_[AFTER_AFTER_BODY_MODE] = {};
288_[AFTER_AFTER_BODY_MODE][Tokenizer.CHARACTER_TOKEN] = tokenAfterAfterBody;
289_[AFTER_AFTER_BODY_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = tokenAfterAfterBody;
290_[AFTER_AFTER_BODY_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
291_[AFTER_AFTER_BODY_MODE][Tokenizer.COMMENT_TOKEN] = appendCommentToDocument;
292_[AFTER_AFTER_BODY_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
293_[AFTER_AFTER_BODY_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterAfterBody;
294_[AFTER_AFTER_BODY_MODE][Tokenizer.END_TAG_TOKEN] = tokenAfterAfterBody;
295_[AFTER_AFTER_BODY_MODE][Tokenizer.EOF_TOKEN] = stopParsing;
296
297_[AFTER_AFTER_FRAMESET_MODE] = {};
298_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.CHARACTER_TOKEN] =
299_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.NULL_CHARACTER_TOKEN] = ignoreToken;
300_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.WHITESPACE_CHARACTER_TOKEN] = whitespaceCharacterInBody;
301_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.COMMENT_TOKEN] = appendCommentToDocument;
302_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.DOCTYPE_TOKEN] = ignoreToken;
303_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.START_TAG_TOKEN] = startTagAfterAfterFrameset;
304_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.END_TAG_TOKEN] = ignoreToken;
305_[AFTER_AFTER_FRAMESET_MODE][Tokenizer.EOF_TOKEN] = stopParsing;
306
307//Searchable index building utils (<isindex> tag)
308function getSearchableIndexFormAttrs(isindexStartTagToken) {
309    var indexAction = Tokenizer.getTokenAttr(isindexStartTagToken, ATTRS.ACTION),
310        attrs = [];
311
312    if (indexAction !== null) {
313        attrs.push({
314            name: ATTRS.ACTION,
315            value: indexAction
316        });
317    }
318
319    return attrs;
320}
321
322function getSearchableIndexLabelText(isindexStartTagToken) {
323    var indexPrompt = Tokenizer.getTokenAttr(isindexStartTagToken, ATTRS.PROMPT);
324
325    return indexPrompt === null ? SEARCHABLE_INDEX_DEFAULT_PROMPT : indexPrompt;
326}
327
328function getSearchableIndexInputAttrs(isindexStartTagToken) {
329    var isindexAttrs = isindexStartTagToken.attrs,
330        inputAttrs = [];
331
332    for (var i = 0; i < isindexAttrs.length; i++) {
333        var name = isindexAttrs[i].name;
334
335        if (name !== ATTRS.NAME && name !== ATTRS.ACTION && name !== ATTRS.PROMPT)
336            inputAttrs.push(isindexAttrs[i]);
337    }
338
339    inputAttrs.push({
340        name: ATTRS.NAME,
341        value: SEARCHABLE_INDEX_INPUT_NAME
342    });
343
344    return inputAttrs;
345}
346
347//Parser
348var Parser = module.exports = function (treeAdapter, options) {
349    this.treeAdapter = treeAdapter || DefaultTreeAdapter;
350    this.options = Utils.mergeOptions(DEFAULT_OPTIONS, options);
351    this.scriptHandler = null;
352
353    if (this.options.locationInfo)
354        LocationInfoMixin.assign(this);
355};
356
357//API
358Parser.prototype.parse = function (html) {
359    var document = this.treeAdapter.createDocument();
360
361    this._reset(html, document, null);
362    this._runParsingLoop();
363
364    return document;
365};
366
367Parser.prototype.parseFragment = function (html, fragmentContext) {
368    //NOTE: use <template> element as a fragment context if context element was not provided,
369    //so we will parse in "forgiving" manner
370    if (!fragmentContext)
371        fragmentContext = this.treeAdapter.createElement($.TEMPLATE, NS.HTML, []);
372
373    //NOTE: create fake element which will be used as 'document' for fragment parsing.
374    //This is important for jsdom there 'document' can't be recreated, therefore
375    //fragment parsing causes messing of the main `document`.
376    var documentMock = this.treeAdapter.createElement('documentmock', NS.HTML, []);
377
378    this._reset(html, documentMock, fragmentContext);
379
380    if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE)
381        this._pushTmplInsertionMode(IN_TEMPLATE_MODE);
382
383    this._initTokenizerForFragmentParsing();
384    this._insertFakeRootElement();
385    this._resetInsertionMode();
386    this._findFormInFragmentContext();
387    this._runParsingLoop();
388
389    var rootElement = this.treeAdapter.getFirstChild(documentMock),
390        fragment = this.treeAdapter.createDocumentFragment();
391
392    this._adoptNodes(rootElement, fragment);
393
394    return fragment;
395};
396
397//Reset state
398Parser.prototype._reset = function (html, document, fragmentContext) {
399    this.tokenizer = new Tokenizer(html, this.options);
400
401    this.stopped = false;
402
403    this.insertionMode = INITIAL_MODE;
404    this.originalInsertionMode = '';
405
406    this.document = document;
407    this.fragmentContext = fragmentContext;
408
409    this.headElement = null;
410    this.formElement = null;
411
412    this.openElements = new OpenElementStack(this.document, this.treeAdapter);
413    this.activeFormattingElements = new FormattingElementList(this.treeAdapter);
414
415    this.tmplInsertionModeStack = [];
416    this.tmplInsertionModeStackTop = -1;
417    this.currentTmplInsertionMode = null;
418
419    this.pendingCharacterTokens = [];
420    this.hasNonWhitespacePendingCharacterToken = false;
421
422    this.framesetOk = true;
423    this.skipNextNewLine = false;
424    this.fosterParentingEnabled = false;
425};
426
427//Parsing loop
428Parser.prototype._iterateParsingLoop = function () {
429    this._setupTokenizerCDATAMode();
430
431    var token = this.tokenizer.getNextToken();
432
433    if (this.skipNextNewLine) {
434        this.skipNextNewLine = false;
435
436        if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') {
437            if (token.chars.length === 1)
438                return;
439
440            token.chars = token.chars.substr(1);
441        }
442    }
443
444    if (this._shouldProcessTokenInForeignContent(token))
445        this._processTokenInForeignContent(token);
446
447    else
448        this._processToken(token);
449};
450
451Parser.prototype._runParsingLoop = function () {
452    while (!this.stopped)
453        this._iterateParsingLoop();
454};
455
456//Text parsing
457Parser.prototype._setupTokenizerCDATAMode = function () {
458    var current = this._getAdjustedCurrentElement();
459
460    this.tokenizer.allowCDATA = current && current !== this.document &&
461                                this.treeAdapter.getNamespaceURI(current) !== NS.HTML &&
462                                (!this._isHtmlIntegrationPoint(current)) &&
463                                (!this._isMathMLTextIntegrationPoint(current));
464};
465
466Parser.prototype._switchToTextParsing = function (currentToken, nextTokenizerState) {
467    this._insertElement(currentToken, NS.HTML);
468    this.tokenizer.state = nextTokenizerState;
469    this.originalInsertionMode = this.insertionMode;
470    this.insertionMode = TEXT_MODE;
471};
472
473//Fragment parsing
474Parser.prototype._getAdjustedCurrentElement = function () {
475    return this.openElements.stackTop === 0 && this.fragmentContext ?
476           this.fragmentContext :
477           this.openElements.current;
478};
479
480Parser.prototype._findFormInFragmentContext = function () {
481    var node = this.fragmentContext;
482
483    do {
484        if (this.treeAdapter.getTagName(node) === $.FORM) {
485            this.formElement = node;
486            break;
487        }
488
489        node = this.treeAdapter.getParentNode(node);
490    } while (node);
491};
492
493Parser.prototype._initTokenizerForFragmentParsing = function () {
494    var tn = this.treeAdapter.getTagName(this.fragmentContext);
495
496    if (tn === $.TITLE || tn === $.TEXTAREA)
497        this.tokenizer.state = Tokenizer.MODE.RCDATA;
498
499    else if (tn === $.STYLE || tn === $.XMP || tn === $.IFRAME ||
500             tn === $.NOEMBED || tn === $.NOFRAMES || tn === $.NOSCRIPT) {
501        this.tokenizer.state = Tokenizer.MODE.RAWTEXT;
502    }
503
504    else if (tn === $.SCRIPT)
505        this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA;
506
507    else if (tn === $.PLAINTEXT)
508        this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
509};
510
511//Tree mutation
512Parser.prototype._setDocumentType = function (token) {
513    this.treeAdapter.setDocumentType(this.document, token.name, token.publicId, token.systemId);
514};
515
516Parser.prototype._attachElementToTree = function (element) {
517    if (this._shouldFosterParentOnInsertion())
518        this._fosterParentElement(element);
519
520    else {
521        var parent = this.openElements.currentTmplContent || this.openElements.current;
522
523        this.treeAdapter.appendChild(parent, element);
524    }
525};
526
527Parser.prototype._appendElement = function (token, namespaceURI) {
528    var element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
529
530    this._attachElementToTree(element);
531};
532
533Parser.prototype._insertElement = function (token, namespaceURI) {
534    var element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
535
536    this._attachElementToTree(element);
537    this.openElements.push(element);
538};
539
540Parser.prototype._insertTemplate = function (token) {
541    var tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs),
542        content = this.treeAdapter.createDocumentFragment();
543
544    this.treeAdapter.appendChild(tmpl, content);
545    this._attachElementToTree(tmpl);
546    this.openElements.push(tmpl);
547};
548
549Parser.prototype._insertFakeRootElement = function () {
550    var element = this.treeAdapter.createElement($.HTML, NS.HTML, []);
551
552    this.treeAdapter.appendChild(this.openElements.current, element);
553    this.openElements.push(element);
554};
555
556Parser.prototype._appendCommentNode = function (token, parent) {
557    var commentNode = this.treeAdapter.createCommentNode(token.data);
558
559    this.treeAdapter.appendChild(parent, commentNode);
560};
561
562Parser.prototype._insertCharacters = function (token) {
563    if (this._shouldFosterParentOnInsertion())
564        this._fosterParentText(token.chars);
565
566    else {
567        var parent = this.openElements.currentTmplContent || this.openElements.current;
568
569        this.treeAdapter.insertText(parent, token.chars);
570    }
571};
572
573Parser.prototype._adoptNodes = function (donor, recipient) {
574    while (true) {
575        var child = this.treeAdapter.getFirstChild(donor);
576
577        if (!child)
578            break;
579
580        this.treeAdapter.detachNode(child);
581        this.treeAdapter.appendChild(recipient, child);
582    }
583};
584
585//Token processing
586Parser.prototype._shouldProcessTokenInForeignContent = function (token) {
587    var current = this._getAdjustedCurrentElement();
588
589    if (!current || current === this.document)
590        return false;
591
592    var ns = this.treeAdapter.getNamespaceURI(current);
593
594    if (ns === NS.HTML)
595        return false;
596
597    if (this.treeAdapter.getTagName(current) === $.ANNOTATION_XML && ns === NS.MATHML &&
598        token.type === Tokenizer.START_TAG_TOKEN && token.tagName === $.SVG) {
599        return false;
600    }
601
602    var isCharacterToken = token.type === Tokenizer.CHARACTER_TOKEN ||
603                           token.type === Tokenizer.NULL_CHARACTER_TOKEN ||
604                           token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN,
605        isMathMLTextStartTag = token.type === Tokenizer.START_TAG_TOKEN &&
606                               token.tagName !== $.MGLYPH &&
607                               token.tagName !== $.MALIGNMARK;
608
609    if ((isMathMLTextStartTag || isCharacterToken) && this._isMathMLTextIntegrationPoint(current))
610        return false;
611
612    if ((token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) && this._isHtmlIntegrationPoint(current))
613        return false;
614
615    return token.type !== Tokenizer.EOF_TOKEN;
616};
617
618Parser.prototype._processToken = function (token) {
619    _[this.insertionMode][token.type](this, token);
620};
621
622Parser.prototype._processTokenInBodyMode = function (token) {
623    _[IN_BODY_MODE][token.type](this, token);
624};
625
626Parser.prototype._processTokenInForeignContent = function (token) {
627    if (token.type === Tokenizer.CHARACTER_TOKEN)
628        characterInForeignContent(this, token);
629
630    else if (token.type === Tokenizer.NULL_CHARACTER_TOKEN)
631        nullCharacterInForeignContent(this, token);
632
633    else if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN)
634        insertCharacters(this, token);
635
636    else if (token.type === Tokenizer.COMMENT_TOKEN)
637        appendComment(this, token);
638
639    else if (token.type === Tokenizer.START_TAG_TOKEN)
640        startTagInForeignContent(this, token);
641
642    else if (token.type === Tokenizer.END_TAG_TOKEN)
643        endTagInForeignContent(this, token);
644};
645
646Parser.prototype._processFakeStartTagWithAttrs = function (tagName, attrs) {
647    var fakeToken = this.tokenizer.buildStartTagToken(tagName);
648
649    fakeToken.attrs = attrs;
650    this._processToken(fakeToken);
651};
652
653Parser.prototype._processFakeStartTag = function (tagName) {
654    var fakeToken = this.tokenizer.buildStartTagToken(tagName);
655
656    this._processToken(fakeToken);
657    return fakeToken;
658};
659
660Parser.prototype._processFakeEndTag = function (tagName) {
661    var fakeToken = this.tokenizer.buildEndTagToken(tagName);
662
663    this._processToken(fakeToken);
664    return fakeToken;
665};
666
667//Integration points
668Parser.prototype._isMathMLTextIntegrationPoint = function (element) {
669    var tn = this.treeAdapter.getTagName(element),
670        ns = this.treeAdapter.getNamespaceURI(element);
671
672    return ForeignContent.isMathMLTextIntegrationPoint(tn, ns);
673};
674
675Parser.prototype._isHtmlIntegrationPoint = function (element) {
676    var tn = this.treeAdapter.getTagName(element),
677        ns = this.treeAdapter.getNamespaceURI(element),
678        attrs = this.treeAdapter.getAttrList(element);
679
680    return ForeignContent.isHtmlIntegrationPoint(tn, ns, attrs);
681};
682
683//Active formatting elements reconstruction
684Parser.prototype._reconstructActiveFormattingElements = function () {
685    var listLength = this.activeFormattingElements.length;
686
687    if (listLength) {
688        var unopenIdx = listLength,
689            entry = null;
690
691        do {
692            unopenIdx--;
693            entry = this.activeFormattingElements.entries[unopenIdx];
694
695            if (entry.type === FormattingElementList.MARKER_ENTRY || this.openElements.contains(entry.element)) {
696                unopenIdx++;
697                break;
698            }
699        } while (unopenIdx > 0);
700
701        for (var i = unopenIdx; i < listLength; i++) {
702            entry = this.activeFormattingElements.entries[i];
703            this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element));
704            entry.element = this.openElements.current;
705        }
706    }
707};
708
709//Close elements
710Parser.prototype._closeTableCell = function () {
711    if (this.openElements.hasInTableScope($.TD))
712        this._processFakeEndTag($.TD);
713
714    else
715        this._processFakeEndTag($.TH);
716};
717
718Parser.prototype._closePElement = function () {
719    this.openElements.generateImpliedEndTagsWithExclusion($.P);
720    this.openElements.popUntilTagNamePopped($.P);
721};
722
723//Insertion modes
724Parser.prototype._resetInsertionMode = function () {
725    for (var i = this.openElements.stackTop, last = false; i >= 0; i--) {
726        var element = this.openElements.items[i];
727
728        if (i === 0) {
729            last = true;
730
731            if (this.fragmentContext)
732                element = this.fragmentContext;
733        }
734
735        var tn = this.treeAdapter.getTagName(element),
736            newInsertionMode = INSERTION_MODE_RESET_MAP[tn];
737
738        if (newInsertionMode) {
739            this.insertionMode = newInsertionMode;
740            break;
741        }
742
743        else if (!last && (tn === $.TD || tn === $.TH)) {
744            this.insertionMode = IN_CELL_MODE;
745            break;
746        }
747
748        else if (!last && tn === $.HEAD) {
749            this.insertionMode = IN_HEAD_MODE;
750            break;
751        }
752
753        else if (tn === $.SELECT) {
754            this._resetInsertionModeForSelect(i);
755            break;
756        }
757
758        else if (tn === $.TEMPLATE) {
759            this.insertionMode = this.currentTmplInsertionMode;
760            break;
761        }
762
763        else if (tn === $.HTML) {
764            this.insertionMode = this.headElement ? AFTER_HEAD_MODE : BEFORE_HEAD_MODE;
765            break;
766        }
767
768        else if (last) {
769            this.insertionMode = IN_BODY_MODE;
770            break;
771        }
772    }
773};
774
775Parser.prototype._resetInsertionModeForSelect = function (selectIdx) {
776    if (selectIdx > 0) {
777        for (var i = selectIdx - 1; i > 0; i--) {
778            var ancestor = this.openElements.items[i],
779                tn = this.treeAdapter.getTagName(ancestor);
780
781            if (tn === $.TEMPLATE)
782                break;
783
784            else if (tn === $.TABLE) {
785                this.insertionMode = IN_SELECT_IN_TABLE_MODE;
786                return;
787            }
788        }
789    }
790
791    this.insertionMode = IN_SELECT_MODE;
792};
793
794Parser.prototype._pushTmplInsertionMode = function (mode) {
795    this.tmplInsertionModeStack.push(mode);
796    this.tmplInsertionModeStackTop++;
797    this.currentTmplInsertionMode = mode;
798};
799
800Parser.prototype._popTmplInsertionMode = function () {
801    this.tmplInsertionModeStack.pop();
802    this.tmplInsertionModeStackTop--;
803    this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop];
804};
805
806//Foster parenting
807Parser.prototype._isElementCausesFosterParenting = function (element) {
808    var tn = this.treeAdapter.getTagName(element);
809
810    return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn == $.THEAD || tn === $.TR;
811};
812
813Parser.prototype._shouldFosterParentOnInsertion = function () {
814    return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current);
815};
816
817Parser.prototype._findFosterParentingLocation = function () {
818    var location = {
819        parent: null,
820        beforeElement: null
821    };
822
823    for (var i = this.openElements.stackTop; i >= 0; i--) {
824        var openElement = this.openElements.items[i],
825            tn = this.treeAdapter.getTagName(openElement),
826            ns = this.treeAdapter.getNamespaceURI(openElement);
827
828        if (tn === $.TEMPLATE && ns === NS.HTML) {
829            location.parent = this.treeAdapter.getChildNodes(openElement)[0];
830            break;
831        }
832
833        else if (tn === $.TABLE) {
834            location.parent = this.treeAdapter.getParentNode(openElement);
835
836            if (location.parent)
837                location.beforeElement = openElement;
838            else
839                location.parent = this.openElements.items[i - 1];
840
841            break;
842        }
843    }
844
845    if (!location.parent)
846        location.parent = this.openElements.items[0];
847
848    return location;
849};
850
851Parser.prototype._fosterParentElement = function (element) {
852    var location = this._findFosterParentingLocation();
853
854    if (location.beforeElement)
855        this.treeAdapter.insertBefore(location.parent, element, location.beforeElement);
856    else
857        this.treeAdapter.appendChild(location.parent, element);
858};
859
860Parser.prototype._fosterParentText = function (chars) {
861    var location = this._findFosterParentingLocation();
862
863    if (location.beforeElement)
864        this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement);
865    else
866        this.treeAdapter.insertText(location.parent, chars);
867};
868
869//Special elements
870Parser.prototype._isSpecialElement = function (element) {
871    var tn = this.treeAdapter.getTagName(element),
872        ns = this.treeAdapter.getNamespaceURI(element);
873
874    return HTML.SPECIAL_ELEMENTS[ns][tn];
875};
876
877//Adoption agency algorithm
878//(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency)
879//------------------------------------------------------------------
880
881//Steps 5-8 of the algorithm
882function aaObtainFormattingElementEntry(p, token) {
883    var formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName);
884
885    if (formattingElementEntry) {
886        if (!p.openElements.contains(formattingElementEntry.element)) {
887            p.activeFormattingElements.removeEntry(formattingElementEntry);
888            formattingElementEntry = null;
889        }
890
891        else if (!p.openElements.hasInScope(token.tagName))
892            formattingElementEntry = null;
893    }
894
895    else
896        genericEndTagInBody(p, token);
897
898    return formattingElementEntry;
899}
900
901//Steps 9 and 10 of the algorithm
902function aaObtainFurthestBlock(p, formattingElementEntry) {
903    var furthestBlock = null;
904
905    for (var i = p.openElements.stackTop; i >= 0; i--) {
906        var element = p.openElements.items[i];
907
908        if (element === formattingElementEntry.element)
909            break;
910
911        if (p._isSpecialElement(element))
912            furthestBlock = element;
913    }
914
915    if (!furthestBlock) {
916        p.openElements.popUntilElementPopped(formattingElementEntry.element);
917        p.activeFormattingElements.removeEntry(formattingElementEntry);
918    }
919
920    return furthestBlock;
921}
922
923//Step 13 of the algorithm
924function aaInnerLoop(p, furthestBlock, formattingElement) {
925    var element = null,
926        lastElement = furthestBlock,
927        nextElement = p.openElements.getCommonAncestor(furthestBlock);
928
929    for (var i = 0; i < AA_INNER_LOOP_ITER; i++) {
930        element = nextElement;
931
932        //NOTE: store next element for the next loop iteration (it may be deleted from the stack by step 9.5)
933        nextElement = p.openElements.getCommonAncestor(element);
934
935        var elementEntry = p.activeFormattingElements.getElementEntry(element);
936
937        if (!elementEntry) {
938            p.openElements.remove(element);
939            continue;
940        }
941
942        if (element === formattingElement)
943            break;
944
945        element = aaRecreateElementFromEntry(p, elementEntry);
946
947        if (lastElement === furthestBlock)
948            p.activeFormattingElements.bookmark = elementEntry;
949
950        p.treeAdapter.detachNode(lastElement);
951        p.treeAdapter.appendChild(element, lastElement);
952        lastElement = element;
953    }
954
955    return lastElement;
956}
957
958//Step 13.7 of the algorithm
959function aaRecreateElementFromEntry(p, elementEntry) {
960    var ns = p.treeAdapter.getNamespaceURI(elementEntry.element),
961        newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs);
962
963    p.openElements.replace(elementEntry.element, newElement);
964    elementEntry.element = newElement;
965
966    return newElement;
967}
968
969//Step 14 of the algorithm
970function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) {
971    if (p._isElementCausesFosterParenting(commonAncestor))
972        p._fosterParentElement(lastElement);
973
974    else {
975        var tn = p.treeAdapter.getTagName(commonAncestor),
976            ns = p.treeAdapter.getNamespaceURI(commonAncestor);
977
978        if (tn === $.TEMPLATE && ns === NS.HTML)
979            commonAncestor = p.treeAdapter.getChildNodes(commonAncestor)[0];
980
981        p.treeAdapter.appendChild(commonAncestor, lastElement);
982    }
983}
984
985//Steps 15-19 of the algorithm
986function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {
987    var ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element),
988        token = formattingElementEntry.token,
989        newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs);
990
991    p._adoptNodes(furthestBlock, newElement);
992    p.treeAdapter.appendChild(furthestBlock, newElement);
993
994    p.activeFormattingElements.insertElementAfterBookmark(newElement, formattingElementEntry.token);
995    p.activeFormattingElements.removeEntry(formattingElementEntry);
996
997    p.openElements.remove(formattingElementEntry.element);
998    p.openElements.insertAfter(furthestBlock, newElement);
999}
1000
1001//Algorithm entry point
1002function callAdoptionAgency(p, token) {
1003    for (var i = 0; i < AA_OUTER_LOOP_ITER; i++) {
1004        var formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry);
1005
1006        if (!formattingElementEntry)
1007            break;
1008
1009        var furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry);
1010
1011        if (!furthestBlock)
1012            break;
1013
1014        p.activeFormattingElements.bookmark = formattingElementEntry;
1015
1016        var lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element),
1017            commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element);
1018
1019        p.treeAdapter.detachNode(lastElement);
1020        aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement);
1021        aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry);
1022    }
1023}
1024
1025
1026//Generic token handlers
1027//------------------------------------------------------------------
1028function ignoreToken(p, token) {
1029    //NOTE: do nothing =)
1030}
1031
1032function appendComment(p, token) {
1033    p._appendCommentNode(token, p.openElements.currentTmplContent || p.openElements.current)
1034}
1035
1036function appendCommentToRootHtmlElement(p, token) {
1037    p._appendCommentNode(token, p.openElements.items[0]);
1038}
1039
1040function appendCommentToDocument(p, token) {
1041    p._appendCommentNode(token, p.document);
1042}
1043
1044function insertCharacters(p, token) {
1045    p._insertCharacters(token);
1046}
1047
1048function stopParsing(p, token) {
1049    p.stopped = true;
1050}
1051
1052//12.2.5.4.1 The "initial" insertion mode
1053//------------------------------------------------------------------
1054function doctypeInInitialMode(p, token) {
1055    p._setDocumentType(token);
1056
1057    if (token.forceQuirks || Doctype.isQuirks(token.name, token.publicId, token.systemId))
1058        p.treeAdapter.setQuirksMode(p.document);
1059
1060    p.insertionMode = BEFORE_HTML_MODE;
1061}
1062
1063function tokenInInitialMode(p, token) {
1064    p.treeAdapter.setQuirksMode(p.document);
1065    p.insertionMode = BEFORE_HTML_MODE;
1066    p._processToken(token);
1067}
1068
1069
1070//12.2.5.4.2 The "before html" insertion mode
1071//------------------------------------------------------------------
1072function startTagBeforeHtml(p, token) {
1073    if (token.tagName === $.HTML) {
1074        p._insertElement(token, NS.HTML);
1075        p.insertionMode = BEFORE_HEAD_MODE;
1076    }
1077
1078    else
1079        tokenBeforeHtml(p, token);
1080}
1081
1082function endTagBeforeHtml(p, token) {
1083    var tn = token.tagName;
1084
1085    if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR)
1086        tokenBeforeHtml(p, token);
1087}
1088
1089function tokenBeforeHtml(p, token) {
1090    p._insertFakeRootElement();
1091    p.insertionMode = BEFORE_HEAD_MODE;
1092    p._processToken(token);
1093}
1094
1095
1096//12.2.5.4.3 The "before head" insertion mode
1097//------------------------------------------------------------------
1098function startTagBeforeHead(p, token) {
1099    var tn = token.tagName;
1100
1101    if (tn === $.HTML)
1102        startTagInBody(p, token);
1103
1104    else if (tn === $.HEAD) {
1105        p._insertElement(token, NS.HTML);
1106        p.headElement = p.openElements.current;
1107        p.insertionMode = IN_HEAD_MODE;
1108    }
1109
1110    else
1111        tokenBeforeHead(p, token);
1112}
1113
1114function endTagBeforeHead(p, token) {
1115    var tn = token.tagName;
1116
1117    if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR)
1118        tokenBeforeHead(p, token);
1119}
1120
1121function tokenBeforeHead(p, token) {
1122    p._processFakeStartTag($.HEAD);
1123    p._processToken(token);
1124}
1125
1126
1127//12.2.5.4.4 The "in head" insertion mode
1128//------------------------------------------------------------------
1129function startTagInHead(p, token) {
1130    var tn = token.tagName;
1131
1132    if (tn === $.HTML)
1133        startTagInBody(p, token);
1134
1135    else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND ||
1136             tn === $.COMMAND || tn === $.LINK || tn === $.META) {
1137        p._appendElement(token, NS.HTML);
1138    }
1139
1140    else if (tn === $.TITLE)
1141        p._switchToTextParsing(token, Tokenizer.MODE.RCDATA);
1142
1143    //NOTE: here we assume that we always act as an interactive user agent with enabled scripting, so we parse
1144    //<noscript> as a rawtext.
1145    else if (tn === $.NOSCRIPT || tn === $.NOFRAMES || tn === $.STYLE)
1146        p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1147
1148    else if (tn === $.SCRIPT)
1149        p._switchToTextParsing(token, Tokenizer.MODE.SCRIPT_DATA);
1150
1151    else if (tn === $.TEMPLATE) {
1152        p._insertTemplate(token, NS.HTML);
1153        p.activeFormattingElements.insertMarker();
1154        p.framesetOk = false;
1155        p.insertionMode = IN_TEMPLATE_MODE;
1156        p._pushTmplInsertionMode(IN_TEMPLATE_MODE);
1157    }
1158
1159    else if (tn !== $.HEAD)
1160        tokenInHead(p, token);
1161}
1162
1163function endTagInHead(p, token) {
1164    var tn = token.tagName;
1165
1166    if (tn === $.HEAD) {
1167        p.openElements.pop();
1168        p.insertionMode = AFTER_HEAD_MODE;
1169    }
1170
1171    else if (tn === $.BODY || tn === $.BR || tn === $.HTML)
1172        tokenInHead(p, token);
1173
1174    else if (tn === $.TEMPLATE && p.openElements.tmplCount > 0) {
1175        p.openElements.generateImpliedEndTags();
1176        p.openElements.popUntilTemplatePopped();
1177        p.activeFormattingElements.clearToLastMarker();
1178        p._popTmplInsertionMode();
1179        p._resetInsertionMode();
1180    }
1181}
1182
1183function tokenInHead(p, token) {
1184    p._processFakeEndTag($.HEAD);
1185    p._processToken(token);
1186}
1187
1188
1189//12.2.5.4.6 The "after head" insertion mode
1190//------------------------------------------------------------------
1191function startTagAfterHead(p, token) {
1192    var tn = token.tagName;
1193
1194    if (tn === $.HTML)
1195        startTagInBody(p, token);
1196
1197    else if (tn === $.BODY) {
1198        p._insertElement(token, NS.HTML);
1199        p.framesetOk = false;
1200        p.insertionMode = IN_BODY_MODE;
1201    }
1202
1203    else if (tn === $.FRAMESET) {
1204        p._insertElement(token, NS.HTML);
1205        p.insertionMode = IN_FRAMESET_MODE;
1206    }
1207
1208    else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META ||
1209             tn === $.NOFRAMES || tn === $.SCRIPT || tn === $.STYLE || tn === $.TEMPLATE || tn === $.TITLE) {
1210        p.openElements.push(p.headElement);
1211        startTagInHead(p, token);
1212        p.openElements.remove(p.headElement);
1213    }
1214
1215    else if (tn !== $.HEAD)
1216        tokenAfterHead(p, token);
1217}
1218
1219function endTagAfterHead(p, token) {
1220    var tn = token.tagName;
1221
1222    if (tn === $.BODY || tn === $.HTML || tn === $.BR)
1223        tokenAfterHead(p, token);
1224
1225    else if (tn === $.TEMPLATE)
1226        endTagInHead(p, token);
1227}
1228
1229function tokenAfterHead(p, token) {
1230    p._processFakeStartTag($.BODY);
1231    p.framesetOk = true;
1232    p._processToken(token);
1233}
1234
1235
1236//12.2.5.4.7 The "in body" insertion mode
1237//------------------------------------------------------------------
1238function whitespaceCharacterInBody(p, token) {
1239    p._reconstructActiveFormattingElements();
1240    p._insertCharacters(token);
1241}
1242
1243function characterInBody(p, token) {
1244    p._reconstructActiveFormattingElements();
1245    p._insertCharacters(token);
1246    p.framesetOk = false;
1247}
1248
1249function htmlStartTagInBody(p, token) {
1250    if (p.openElements.tmplCount === 0)
1251        p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs);
1252}
1253
1254function bodyStartTagInBody(p, token) {
1255    var bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
1256
1257    if (bodyElement && p.openElements.tmplCount === 0) {
1258        p.framesetOk = false;
1259        p.treeAdapter.adoptAttributes(bodyElement, token.attrs);
1260    }
1261}
1262
1263function framesetStartTagInBody(p, token) {
1264    var bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
1265
1266    if (p.framesetOk && bodyElement) {
1267        p.treeAdapter.detachNode(bodyElement);
1268        p.openElements.popAllUpToHtmlElement();
1269        p._insertElement(token, NS.HTML);
1270        p.insertionMode = IN_FRAMESET_MODE;
1271    }
1272}
1273
1274function addressStartTagInBody(p, token) {
1275    if (p.openElements.hasInButtonScope($.P))
1276        p._closePElement();
1277
1278    p._insertElement(token, NS.HTML);
1279}
1280
1281function numberedHeaderStartTagInBody(p, token) {
1282    if (p.openElements.hasInButtonScope($.P))
1283        p._closePElement();
1284
1285    var tn = p.openElements.currentTagName;
1286
1287    if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6)
1288        p.openElements.pop();
1289
1290    p._insertElement(token, NS.HTML);
1291}
1292
1293function preStartTagInBody(p, token) {
1294    if (p.openElements.hasInButtonScope($.P))
1295        p._closePElement();
1296
1297    p._insertElement(token, NS.HTML);
1298    //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
1299    //on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.)
1300    p.skipNextNewLine = true;
1301    p.framesetOk = false;
1302}
1303
1304function formStartTagInBody(p, token) {
1305    var inTemplate = p.openElements.tmplCount > 0;
1306
1307    if (!p.formElement || inTemplate) {
1308        if (p.openElements.hasInButtonScope($.P))
1309            p._closePElement();
1310
1311        p._insertElement(token, NS.HTML);
1312
1313        if (!inTemplate)
1314            p.formElement = p.openElements.current;
1315    }
1316}
1317
1318function listItemStartTagInBody(p, token) {
1319    p.framesetOk = false;
1320
1321    for (var i = p.openElements.stackTop; i >= 0; i--) {
1322        var element = p.openElements.items[i],
1323            tn = p.treeAdapter.getTagName(element);
1324
1325        if ((token.tagName === $.LI && tn === $.LI) ||
1326            ((token.tagName === $.DD || token.tagName === $.DT) && (tn === $.DD || tn == $.DT))) {
1327            p._processFakeEndTag(tn);
1328            break;
1329        }
1330
1331        if (tn !== $.ADDRESS && tn !== $.DIV && tn !== $.P && p._isSpecialElement(element))
1332            break;
1333    }
1334
1335    if (p.openElements.hasInButtonScope($.P))
1336        p._closePElement();
1337
1338    p._insertElement(token, NS.HTML);
1339}
1340
1341function plaintextStartTagInBody(p, token) {
1342    if (p.openElements.hasInButtonScope($.P))
1343        p._closePElement();
1344
1345    p._insertElement(token, NS.HTML);
1346    p.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
1347}
1348
1349function buttonStartTagInBody(p, token) {
1350    if (p.openElements.hasInScope($.BUTTON)) {
1351        p._processFakeEndTag($.BUTTON);
1352        buttonStartTagInBody(p, token);
1353    }
1354
1355    else {
1356        p._reconstructActiveFormattingElements();
1357        p._insertElement(token, NS.HTML);
1358        p.framesetOk = false;
1359    }
1360}
1361
1362function aStartTagInBody(p, token) {
1363    var activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName($.A);
1364
1365    if (activeElementEntry) {
1366        p._processFakeEndTag($.A);
1367        p.openElements.remove(activeElementEntry.element);
1368        p.activeFormattingElements.removeEntry(activeElementEntry);
1369    }
1370
1371    p._reconstructActiveFormattingElements();
1372    p._insertElement(token, NS.HTML);
1373    p.activeFormattingElements.pushElement(p.openElements.current, token);
1374}
1375
1376function bStartTagInBody(p, token) {
1377    p._reconstructActiveFormattingElements();
1378    p._insertElement(token, NS.HTML);
1379    p.activeFormattingElements.pushElement(p.openElements.current, token);
1380}
1381
1382function nobrStartTagInBody(p, token) {
1383    p._reconstructActiveFormattingElements();
1384
1385    if (p.openElements.hasInScope($.NOBR)) {
1386        p._processFakeEndTag($.NOBR);
1387        p._reconstructActiveFormattingElements();
1388    }
1389
1390    p._insertElement(token, NS.HTML);
1391    p.activeFormattingElements.pushElement(p.openElements.current, token);
1392}
1393
1394function appletStartTagInBody(p, token) {
1395    p._reconstructActiveFormattingElements();
1396    p._insertElement(token, NS.HTML);
1397    p.activeFormattingElements.insertMarker();
1398    p.framesetOk = false;
1399}
1400
1401function tableStartTagInBody(p, token) {
1402    if (!p.treeAdapter.isQuirksMode(p.document) && p.openElements.hasInButtonScope($.P))
1403        p._closePElement();
1404
1405    p._insertElement(token, NS.HTML);
1406    p.framesetOk = false;
1407    p.insertionMode = IN_TABLE_MODE;
1408}
1409
1410function areaStartTagInBody(p, token) {
1411    p._reconstructActiveFormattingElements();
1412    p._appendElement(token, NS.HTML);
1413    p.framesetOk = false;
1414}
1415
1416function inputStartTagInBody(p, token) {
1417    p._reconstructActiveFormattingElements();
1418    p._appendElement(token, NS.HTML);
1419
1420    var inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
1421
1422    if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE)
1423        p.framesetOk = false;
1424
1425}
1426
1427function paramStartTagInBody(p, token) {
1428    p._appendElement(token, NS.HTML);
1429}
1430
1431function hrStartTagInBody(p, token) {
1432    if (p.openElements.hasInButtonScope($.P))
1433        p._closePElement();
1434
1435    p._appendElement(token, NS.HTML);
1436    p.framesetOk = false;
1437}
1438
1439function imageStartTagInBody(p, token) {
1440    token.tagName = $.IMG;
1441    areaStartTagInBody(p, token);
1442}
1443
1444function isindexStartTagInBody(p, token) {
1445    if (!p.formElement || p.openElements.tmplCount > 0) {
1446        p._processFakeStartTagWithAttrs($.FORM, getSearchableIndexFormAttrs(token));
1447        p._processFakeStartTag($.HR);
1448        p._processFakeStartTag($.LABEL);
1449        p.treeAdapter.insertText(p.openElements.current, getSearchableIndexLabelText(token));
1450        p._processFakeStartTagWithAttrs($.INPUT, getSearchableIndexInputAttrs(token));
1451        p._processFakeEndTag($.LABEL);
1452        p._processFakeStartTag($.HR);
1453        p._processFakeEndTag($.FORM);
1454    }
1455}
1456
1457function textareaStartTagInBody(p, token) {
1458    p._insertElement(token, NS.HTML);
1459    //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
1460    //on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.)
1461    p.skipNextNewLine = true;
1462    p.tokenizer.state = Tokenizer.MODE.RCDATA;
1463    p.originalInsertionMode = p.insertionMode;
1464    p.framesetOk = false;
1465    p.insertionMode = TEXT_MODE;
1466}
1467
1468function xmpStartTagInBody(p, token) {
1469    if (p.openElements.hasInButtonScope($.P))
1470        p._closePElement();
1471
1472    p._reconstructActiveFormattingElements();
1473    p.framesetOk = false;
1474    p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1475}
1476
1477function iframeStartTagInBody(p, token) {
1478    p.framesetOk = false;
1479    p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1480}
1481
1482//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse
1483//<noembed> as a rawtext.
1484function noembedStartTagInBody(p, token) {
1485    p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1486}
1487
1488function selectStartTagInBody(p, token) {
1489    p._reconstructActiveFormattingElements();
1490    p._insertElement(token, NS.HTML);
1491    p.framesetOk = false;
1492
1493    if (p.insertionMode === IN_TABLE_MODE || p.insertionMode === IN_CAPTION_MODE ||
1494        p.insertionMode === IN_TABLE_BODY_MODE || p.insertionMode === IN_ROW_MODE ||
1495        p.insertionMode === IN_CELL_MODE) {
1496        p.insertionMode = IN_SELECT_IN_TABLE_MODE;
1497    }
1498
1499    else
1500        p.insertionMode = IN_SELECT_MODE;
1501}
1502
1503function optgroupStartTagInBody(p, token) {
1504    if (p.openElements.currentTagName === $.OPTION)
1505        p._processFakeEndTag($.OPTION);
1506
1507    p._reconstructActiveFormattingElements();
1508    p._insertElement(token, NS.HTML);
1509}
1510
1511function rpStartTagInBody(p, token) {
1512    if (p.openElements.hasInScope($.RUBY))
1513        p.openElements.generateImpliedEndTags();
1514
1515    p._insertElement(token, NS.HTML);
1516}
1517
1518function menuitemStartTagInBody(p, token) {
1519    p._appendElement(token, NS.HTML);
1520}
1521
1522function mathStartTagInBody(p, token) {
1523    p._reconstructActiveFormattingElements();
1524
1525    ForeignContent.adjustTokenMathMLAttrs(token);
1526    ForeignContent.adjustTokenXMLAttrs(token);
1527
1528    if (token.selfClosing)
1529        p._appendElement(token, NS.MATHML);
1530    else
1531        p._insertElement(token, NS.MATHML);
1532}
1533
1534function svgStartTagInBody(p, token) {
1535    p._reconstructActiveFormattingElements();
1536
1537    ForeignContent.adjustTokenSVGAttrs(token);
1538    ForeignContent.adjustTokenXMLAttrs(token);
1539
1540    if (token.selfClosing)
1541        p._appendElement(token, NS.SVG);
1542    else
1543        p._insertElement(token, NS.SVG);
1544}
1545
1546function genericStartTagInBody(p, token) {
1547    p._reconstructActiveFormattingElements();
1548    p._insertElement(token, NS.HTML);
1549}
1550
1551//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
1552//It's faster than using dictionary.
1553function startTagInBody(p, token) {
1554    var tn = token.tagName;
1555
1556    switch (tn.length) {
1557        case 1:
1558            if (tn === $.I || tn === $.S || tn === $.B || tn === $.U)
1559                bStartTagInBody(p, token);
1560
1561            else if (tn === $.P)
1562                addressStartTagInBody(p, token);
1563
1564            else if (tn === $.A)
1565                aStartTagInBody(p, token);
1566
1567            else
1568                genericStartTagInBody(p, token);
1569
1570            break;
1571
1572        case 2:
1573            if (tn === $.DL || tn === $.OL || tn === $.UL)
1574                addressStartTagInBody(p, token);
1575
1576            else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6)
1577                numberedHeaderStartTagInBody(p, token);
1578
1579            else if (tn === $.LI || tn === $.DD || tn === $.DT)
1580                listItemStartTagInBody(p, token);
1581
1582            else if (tn === $.EM || tn === $.TT)
1583                bStartTagInBody(p, token);
1584
1585            else if (tn === $.BR)
1586                areaStartTagInBody(p, token);
1587
1588            else if (tn === $.HR)
1589                hrStartTagInBody(p, token);
1590
1591            else if (tn === $.RP || tn === $.RT)
1592                rpStartTagInBody(p, token);
1593
1594            else if (tn !== $.TH && tn !== $.TD && tn !== $.TR)
1595                genericStartTagInBody(p, token);
1596
1597            break;
1598
1599        case 3:
1600            if (tn === $.DIV || tn === $.DIR || tn === $.NAV)
1601                addressStartTagInBody(p, token);
1602
1603            else if (tn === $.PRE)
1604                preStartTagInBody(p, token);
1605
1606            else if (tn === $.BIG)
1607                bStartTagInBody(p, token);
1608
1609            else if (tn === $.IMG || tn === $.WBR)
1610                areaStartTagInBody(p, token);
1611
1612            else if (tn === $.XMP)
1613                xmpStartTagInBody(p, token);
1614
1615            else if (tn === $.SVG)
1616                svgStartTagInBody(p, token);
1617
1618            else if (tn !== $.COL)
1619                genericStartTagInBody(p, token);
1620
1621            break;
1622
1623        case 4:
1624            if (tn === $.HTML)
1625                htmlStartTagInBody(p, token);
1626
1627            else if (tn === $.BASE || tn === $.LINK || tn === $.META)
1628                startTagInHead(p, token);
1629
1630            else if (tn === $.BODY)
1631                bodyStartTagInBody(p, token);
1632
1633            else if (tn === $.MAIN || tn === $.MENU)
1634                addressStartTagInBody(p, token);
1635
1636            else if (tn === $.FORM)
1637                formStartTagInBody(p, token);
1638
1639            else if (tn === $.CODE || tn === $.FONT)
1640                bStartTagInBody(p, token);
1641
1642            else if (tn === $.NOBR)
1643                nobrStartTagInBody(p, token);
1644
1645            else if (tn === $.AREA)
1646                areaStartTagInBody(p, token);
1647
1648            else if (tn === $.MATH)
1649                mathStartTagInBody(p, token);
1650
1651            else if (tn !== $.HEAD)
1652                genericStartTagInBody(p, token);
1653
1654            break;
1655
1656        case 5:
1657            if (tn === $.STYLE || tn === $.TITLE)
1658                startTagInHead(p, token);
1659
1660            else if (tn === $.ASIDE)
1661                addressStartTagInBody(p, token);
1662
1663            else if (tn === $.SMALL)
1664                bStartTagInBody(p, token);
1665
1666            else if (tn === $.TABLE)
1667                tableStartTagInBody(p, token);
1668
1669            else if (tn === $.EMBED)
1670                areaStartTagInBody(p, token);
1671
1672            else if (tn === $.INPUT)
1673                inputStartTagInBody(p, token);
1674
1675            else if (tn === $.PARAM || tn === $.TRACK)
1676                paramStartTagInBody(p, token);
1677
1678            else if (tn === $.IMAGE)
1679                imageStartTagInBody(p, token);
1680
1681            else if (tn !== $.FRAME && tn !== $.TBODY && tn !== $.TFOOT && tn !== $.THEAD)
1682                genericStartTagInBody(p, token);
1683
1684            break;
1685
1686        case 6:
1687            if (tn === $.SCRIPT)
1688                startTagInHead(p, token);
1689
1690            else if (tn === $.CENTER || tn === $.FIGURE || tn === $.FOOTER || tn === $.HEADER || tn === $.HGROUP)
1691                addressStartTagInBody(p, token);
1692
1693            else if (tn === $.BUTTON)
1694                buttonStartTagInBody(p, token);
1695
1696            else if (tn === $.STRIKE || tn === $.STRONG)
1697                bStartTagInBody(p, token);
1698
1699            else if (tn === $.APPLET || tn === $.OBJECT)
1700                appletStartTagInBody(p, token);
1701
1702            else if (tn === $.KEYGEN)
1703                areaStartTagInBody(p, token);
1704
1705            else if (tn === $.SOURCE)
1706                paramStartTagInBody(p, token);
1707
1708            else if (tn === $.IFRAME)
1709                iframeStartTagInBody(p, token);
1710
1711            else if (tn === $.SELECT)
1712                selectStartTagInBody(p, token);
1713
1714            else if (tn === $.OPTION)
1715                optgroupStartTagInBody(p, token);
1716
1717            else
1718                genericStartTagInBody(p, token);
1719
1720            break;
1721
1722        case 7:
1723            if (tn === $.BGSOUND || tn === $.COMMAND)
1724                startTagInHead(p, token);
1725
1726            else if (tn === $.DETAILS || tn === $.ADDRESS || tn === $.ARTICLE || tn === $.SECTION || tn === $.SUMMARY)
1727                addressStartTagInBody(p, token);
1728
1729            else if (tn === $.LISTING)
1730                preStartTagInBody(p, token);
1731
1732            else if (tn === $.MARQUEE)
1733                appletStartTagInBody(p, token);
1734
1735            else if (tn === $.ISINDEX)
1736                isindexStartTagInBody(p, token);
1737
1738            else if (tn === $.NOEMBED)
1739                noembedStartTagInBody(p, token);
1740
1741            else if (tn !== $.CAPTION)
1742                genericStartTagInBody(p, token);
1743
1744            break;
1745
1746        case 8:
1747            if (tn === $.BASEFONT || tn === $.MENUITEM)
1748                menuitemStartTagInBody(p, token);
1749
1750            else if (tn === $.FRAMESET)
1751                framesetStartTagInBody(p, token);
1752
1753            else if (tn === $.FIELDSET)
1754                addressStartTagInBody(p, token);
1755
1756            else if (tn === $.TEXTAREA)
1757                textareaStartTagInBody(p, token);
1758
1759            else if (tn === $.TEMPLATE)
1760                startTagInHead(p, token);
1761
1762            else if (tn === $.NOSCRIPT)
1763                noembedStartTagInBody(p, token);
1764
1765            else if (tn === $.OPTGROUP)
1766                optgroupStartTagInBody(p, token);
1767
1768            else if (tn !== $.COLGROUP)
1769                genericStartTagInBody(p, token);
1770
1771            break;
1772
1773        case 9:
1774            if (tn === $.PLAINTEXT)
1775                plaintextStartTagInBody(p, token);
1776
1777            else
1778                genericStartTagInBody(p, token);
1779
1780            break;
1781
1782        case 10:
1783            if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION)
1784                addressStartTagInBody(p, token);
1785
1786            else
1787                genericStartTagInBody(p, token);
1788
1789            break;
1790
1791        default:
1792            genericStartTagInBody(p, token);
1793    }
1794}
1795
1796function bodyEndTagInBody(p, token) {
1797    if (p.openElements.hasInScope($.BODY))
1798        p.insertionMode = AFTER_BODY_MODE;
1799
1800    else
1801        token.ignored = true;
1802}
1803
1804function htmlEndTagInBody(p, token) {
1805    var fakeToken = p._processFakeEndTag($.BODY);
1806
1807    if (!fakeToken.ignored)
1808        p._processToken(token);
1809}
1810
1811function addressEndTagInBody(p, token) {
1812    var tn = token.tagName;
1813
1814    if (p.openElements.hasInScope(tn)) {
1815        p.openElements.generateImpliedEndTags();
1816        p.openElements.popUntilTagNamePopped(tn);
1817    }
1818}
1819
1820function formEndTagInBody(p, token) {
1821    var inTemplate = p.openElements.tmplCount > 0,
1822        formElement = p.formElement;
1823
1824    if (!inTemplate)
1825        p.formElement = null;
1826
1827    if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) {
1828        p.openElements.generateImpliedEndTags();
1829
1830        if (inTemplate)
1831            p.openElements.popUntilTagNamePopped($.FORM);
1832
1833        else
1834            p.openElements.remove(formElement);
1835    }
1836}
1837
1838function pEndTagInBody(p, token) {
1839    if (p.openElements.hasInButtonScope($.P)) {
1840        p.openElements.generateImpliedEndTagsWithExclusion($.P);
1841        p.openElements.popUntilTagNamePopped($.P);
1842    }
1843
1844    else {
1845        p._processFakeStartTag($.P);
1846        p._processToken(token);
1847    }
1848}
1849
1850function liEndTagInBody(p, token) {
1851    if (p.openElements.hasInListItemScope($.LI)) {
1852        p.openElements.generateImpliedEndTagsWithExclusion($.LI);
1853        p.openElements.popUntilTagNamePopped($.LI);
1854    }
1855}
1856
1857function ddEndTagInBody(p, token) {
1858    var tn = token.tagName;
1859
1860    if (p.openElements.hasInScope(tn)) {
1861        p.openElements.generateImpliedEndTagsWithExclusion(tn);
1862        p.openElements.popUntilTagNamePopped(tn);
1863    }
1864}
1865
1866function numberedHeaderEndTagInBody(p, token) {
1867    if (p.openElements.hasNumberedHeaderInScope()) {
1868        p.openElements.generateImpliedEndTags();
1869        p.openElements.popUntilNumberedHeaderPopped();
1870    }
1871}
1872
1873function appletEndTagInBody(p, token) {
1874    var tn = token.tagName;
1875
1876    if (p.openElements.hasInScope(tn)) {
1877        p.openElements.generateImpliedEndTags();
1878        p.openElements.popUntilTagNamePopped(tn);
1879        p.activeFormattingElements.clearToLastMarker();
1880    }
1881}
1882
1883function brEndTagInBody(p, token) {
1884    p._processFakeStartTag($.BR);
1885}
1886
1887function genericEndTagInBody(p, token) {
1888    var tn = token.tagName;
1889
1890    for (var i = p.openElements.stackTop; i > 0; i--) {
1891        var element = p.openElements.items[i];
1892
1893        if (p.treeAdapter.getTagName(element) === tn) {
1894            p.openElements.generateImpliedEndTagsWithExclusion(tn);
1895            p.openElements.popUntilElementPopped(element);
1896            break;
1897        }
1898
1899        if (p._isSpecialElement(element))
1900            break;
1901    }
1902}
1903
1904//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
1905//It's faster than using dictionary.
1906function endTagInBody(p, token) {
1907    var tn = token.tagName;
1908
1909    switch (tn.length) {
1910        case 1:
1911            if (tn === $.A || tn === $.B || tn === $.I || tn === $.S || tn == $.U)
1912                callAdoptionAgency(p, token);
1913
1914            else if (tn === $.P)
1915                pEndTagInBody(p, token);
1916
1917            else
1918                genericEndTagInBody(p, token);
1919
1920            break;
1921
1922        case 2:
1923            if (tn == $.DL || tn === $.UL || tn === $.OL)
1924                addressEndTagInBody(p, token);
1925
1926            else if (tn === $.LI)
1927                liEndTagInBody(p, token);
1928
1929            else if (tn === $.DD || tn === $.DT)
1930                ddEndTagInBody(p, token);
1931
1932            else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6)
1933                numberedHeaderEndTagInBody(p, token);
1934
1935            else if (tn === $.BR)
1936                brEndTagInBody(p, token);
1937
1938            else if (tn === $.EM || tn === $.TT)
1939                callAdoptionAgency(p, token);
1940
1941            else
1942                genericEndTagInBody(p, token);
1943
1944            break;
1945
1946        case 3:
1947            if (tn === $.BIG)
1948                callAdoptionAgency(p, token);
1949
1950            else if (tn === $.DIR || tn === $.DIV || tn === $.NAV)
1951                addressEndTagInBody(p, token);
1952
1953            else
1954                genericEndTagInBody(p, token);
1955
1956            break;
1957
1958        case 4:
1959            if (tn === $.BODY)
1960                bodyEndTagInBody(p, token);
1961
1962            else if (tn === $.HTML)
1963                htmlEndTagInBody(p, token);
1964
1965            else if (tn === $.FORM)
1966                formEndTagInBody(p, token);
1967
1968            else if (tn === $.CODE || tn === $.FONT || tn === $.NOBR)
1969                callAdoptionAgency(p, token);
1970
1971            else if (tn === $.MAIN || tn === $.MENU)
1972                addressEndTagInBody(p, token);
1973
1974            else
1975                genericEndTagInBody(p, token);
1976
1977            break;
1978
1979        case 5:
1980            if (tn === $.ASIDE)
1981                addressEndTagInBody(p, token);
1982
1983            else if (tn === $.SMALL)
1984                callAdoptionAgency(p, token);
1985
1986            else
1987                genericEndTagInBody(p, token);
1988
1989            break;
1990
1991        case 6:
1992            if (tn === $.CENTER || tn === $.FIGURE || tn === $.FOOTER || tn === $.HEADER || tn === $.HGROUP)
1993                addressEndTagInBody(p, token);
1994
1995            else if (tn === $.APPLET || tn === $.OBJECT)
1996                appletEndTagInBody(p, token);
1997
1998            else if (tn == $.STRIKE || tn === $.STRONG)
1999                callAdoptionAgency(p, token);
2000
2001            else
2002                genericEndTagInBody(p, token);
2003
2004            break;
2005
2006        case 7:
2007            if (tn === $.ADDRESS || tn === $.ARTICLE || tn === $.DETAILS || tn === $.SECTION || tn === $.SUMMARY)
2008                addressEndTagInBody(p, token);
2009
2010            else if (tn === $.MARQUEE)
2011                appletEndTagInBody(p, token);
2012
2013            else
2014                genericEndTagInBody(p, token);
2015
2016            break;
2017
2018        case 8:
2019            if (tn === $.FIELDSET)
2020                addressEndTagInBody(p, token);
2021
2022            else if (tn === $.TEMPLATE)
2023                endTagInHead(p, token);
2024
2025            else
2026                genericEndTagInBody(p, token);
2027
2028            break;
2029
2030        case 10:
2031            if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION)
2032                addressEndTagInBody(p, token);
2033
2034            else
2035                genericEndTagInBody(p, token);
2036
2037            break;
2038
2039        default :
2040            genericEndTagInBody(p, token);
2041    }
2042}
2043
2044function eofInBody(p, token) {
2045    if (p.tmplInsertionModeStackTop > -1)
2046        eofInTemplate(p, token);
2047
2048    else
2049        p.stopped = true;
2050}
2051
2052//12.2.5.4.8 The "text" insertion mode
2053//------------------------------------------------------------------
2054function endTagInText(p, token) {
2055    if (!p.fragmentContext && p.scriptHandler && token.tagName === $.SCRIPT)
2056        p.scriptHandler(p.document, p.openElements.current);
2057
2058    p.openElements.pop();
2059    p.insertionMode = p.originalInsertionMode;
2060}
2061
2062
2063function eofInText(p, token) {
2064    p.openElements.pop();
2065    p.insertionMode = p.originalInsertionMode;
2066    p._processToken(token);
2067}
2068
2069
2070//12.2.5.4.9 The "in table" insertion mode
2071//------------------------------------------------------------------
2072function characterInTable(p, token) {
2073    var curTn = p.openElements.currentTagName;
2074
2075    if (curTn === $.TABLE || curTn === $.TBODY || curTn === $.TFOOT || curTn === $.THEAD || curTn === $.TR) {
2076        p.pendingCharacterTokens = [];
2077        p.hasNonWhitespacePendingCharacterToken = false;
2078        p.originalInsertionMode = p.insertionMode;
2079        p.insertionMode = IN_TABLE_TEXT_MODE;
2080        p._processToken(token);
2081    }
2082
2083    else
2084        tokenInTable(p, token);
2085}
2086
2087function captionStartTagInTable(p, token) {
2088    p.openElements.clearBackToTableContext();
2089    p.activeFormattingElements.insertMarker();
2090    p._insertElement(token, NS.HTML);
2091    p.insertionMode = IN_CAPTION_MODE;
2092}
2093
2094function colgroupStartTagInTable(p, token) {
2095    p.openElements.clearBackToTableContext();
2096    p._insertElement(token, NS.HTML);
2097    p.insertionMode = IN_COLUMN_GROUP_MODE;
2098}
2099
2100function colStartTagInTable(p, token) {
2101    p._processFakeStartTag($.COLGROUP);
2102    p._processToken(token);
2103}
2104
2105function tbodyStartTagInTable(p, token) {
2106    p.openElements.clearBackToTableContext();
2107    p._insertElement(token, NS.HTML);
2108    p.insertionMode = IN_TABLE_BODY_MODE;
2109}
2110
2111function tdStartTagInTable(p, token) {
2112    p._processFakeStartTag($.TBODY);
2113    p._processToken(token);
2114}
2115
2116function tableStartTagInTable(p, token) {
2117    var fakeToken = p._processFakeEndTag($.TABLE);
2118
2119    //NOTE: The fake end tag token here can only be ignored in the fragment case.
2120    if (!fakeToken.ignored)
2121        p._processToken(token);
2122}
2123
2124function inputStartTagInTable(p, token) {
2125    var inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
2126
2127    if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE)
2128        p._appendElement(token, NS.HTML);
2129
2130    else
2131        tokenInTable(p, token);
2132}
2133
2134function formStartTagInTable(p, token) {
2135    if (!p.formElement && p.openElements.tmplCount === 0) {
2136        p._insertElement(token, NS.HTML);
2137        p.formElement = p.openElements.current;
2138        p.openElements.pop();
2139    }
2140}
2141
2142function startTagInTable(p, token) {
2143    var tn = token.tagName;
2144
2145    switch (tn.length) {
2146        case 2:
2147            if (tn === $.TD || tn === $.TH || tn === $.TR)
2148                tdStartTagInTable(p, token);
2149
2150            else
2151                tokenInTable(p, token);
2152
2153            break;
2154
2155        case 3:
2156            if (tn === $.COL)
2157                colStartTagInTable(p, token);
2158
2159            else
2160                tokenInTable(p, token);
2161
2162            break;
2163
2164        case 4:
2165            if (tn === $.FORM)
2166                formStartTagInTable(p, token);
2167
2168            else
2169                tokenInTable(p, token);
2170
2171            break;
2172
2173        case 5:
2174            if (tn === $.TABLE)
2175                tableStartTagInTable(p, token);
2176
2177            else if (tn === $.STYLE)
2178                startTagInHead(p, token);
2179
2180            else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD)
2181                tbodyStartTagInTable(p, token);
2182
2183            else if (tn === $.INPUT)
2184                inputStartTagInTable(p, token);
2185
2186            else
2187                tokenInTable(p, token);
2188
2189            break;
2190
2191        case 6:
2192            if (tn === $.SCRIPT)
2193                startTagInHead(p, token);
2194
2195            else
2196                tokenInTable(p, token);
2197
2198            break;
2199
2200        case 7:
2201            if (tn === $.CAPTION)
2202                captionStartTagInTable(p, token);
2203
2204            else
2205                tokenInTable(p, token);
2206
2207            break;
2208
2209        case 8:
2210            if (tn === $.COLGROUP)
2211                colgroupStartTagInTable(p, token);
2212
2213            else if (tn === $.TEMPLATE)
2214                startTagInHead(p, token);
2215
2216            else
2217                tokenInTable(p, token);
2218
2219            break;
2220
2221        default:
2222            tokenInTable(p, token);
2223    }
2224
2225}
2226
2227function endTagInTable(p, token) {
2228    var tn = token.tagName;
2229
2230    if (tn === $.TABLE) {
2231        if (p.openElements.hasInTableScope($.TABLE)) {
2232            p.openElements.popUntilTagNamePopped($.TABLE);
2233            p._resetInsertionMode();
2234        }
2235
2236        else
2237            token.ignored = true;
2238    }
2239
2240    else if (tn === $.TEMPLATE)
2241        endTagInHead(p, token);
2242
2243    else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML &&
2244             tn !== $.TBODY && tn !== $.TD && tn !== $.TFOOT && tn !== $.TH && tn !== $.THEAD && tn !== $.TR) {
2245        tokenInTable(p, token);
2246    }
2247}
2248
2249function tokenInTable(p, token) {
2250    var savedFosterParentingState = p.fosterParentingEnabled;
2251
2252    p.fosterParentingEnabled = true;
2253    p._processTokenInBodyMode(token);
2254    p.fosterParentingEnabled = savedFosterParentingState;
2255}
2256
2257
2258//12.2.5.4.10 The "in table text" insertion mode
2259//------------------------------------------------------------------
2260function whitespaceCharacterInTableText(p, token) {
2261    p.pendingCharacterTokens.push(token);
2262}
2263
2264function characterInTableText(p, token) {
2265    p.pendingCharacterTokens.push(token);
2266    p.hasNonWhitespacePendingCharacterToken = true;
2267}
2268
2269function tokenInTableText(p, token) {
2270    if (p.hasNonWhitespacePendingCharacterToken) {
2271        for (var i = 0; i < p.pendingCharacterTokens.length; i++)
2272            tokenInTable(p, p.pendingCharacterTokens[i]);
2273    }
2274
2275    else {
2276        for (var i = 0; i < p.pendingCharacterTokens.length; i++)
2277            p._insertCharacters(p.pendingCharacterTokens[i]);
2278    }
2279
2280    p.insertionMode = p.originalInsertionMode;
2281    p._processToken(token);
2282}
2283
2284
2285//12.2.5.4.11 The "in caption" insertion mode
2286//------------------------------------------------------------------
2287function startTagInCaption(p, token) {
2288    var tn = token.tagName;
2289
2290    if (tn === $.CAPTION || tn === $.COL || tn === $.COLGROUP || tn === $.TBODY ||
2291        tn === $.TD || tn === $.TFOOT || tn === $.TH || tn === $.THEAD || tn === $.TR) {
2292        var fakeToken = p._processFakeEndTag($.CAPTION);
2293
2294        //NOTE: The fake end tag token here can only be ignored in the fragment case.
2295        if (!fakeToken.ignored)
2296            p._processToken(token);
2297    }
2298
2299    else
2300        startTagInBody(p, token);
2301}
2302
2303function endTagInCaption(p, token) {
2304    var tn = token.tagName;
2305
2306    if (tn === $.CAPTION) {
2307        if (p.openElements.hasInTableScope($.CAPTION)) {
2308            p.openElements.generateImpliedEndTags();
2309            p.openElements.popUntilTagNamePopped($.CAPTION);
2310            p.activeFormattingElements.clearToLastMarker();
2311            p.insertionMode = IN_TABLE_MODE;
2312        }
2313
2314        else
2315            token.ignored = true;
2316    }
2317
2318    else if (tn === $.TABLE) {
2319        var fakeToken = p._processFakeEndTag($.CAPTION);
2320
2321        //NOTE: The fake end tag token here can only be ignored in the fragment case.
2322        if (!fakeToken.ignored)
2323            p._processToken(token);
2324    }
2325
2326    else if (tn !== $.BODY && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML && tn !== $.TBODY &&
2327             tn !== $.TD && tn !== $.TFOOT && tn !== $.TH && tn !== $.THEAD && tn !== $.TR) {
2328        endTagInBody(p, token);
2329    }
2330}
2331
2332
2333//12.2.5.4.12 The "in column group" insertion mode
2334//------------------------------------------------------------------
2335function startTagInColumnGroup(p, token) {
2336    var tn = token.tagName;
2337
2338    if (tn === $.HTML)
2339        startTagInBody(p, token);
2340
2341    else if (tn === $.COL)
2342        p._appendElement(token, NS.HTML);
2343
2344    else if (tn === $.TEMPLATE)
2345        startTagInHead(p, token);
2346
2347    else
2348        tokenInColumnGroup(p, token);
2349}
2350
2351function endTagInColumnGroup(p, token) {
2352    var tn = token.tagName;
2353
2354    if (tn === $.COLGROUP) {
2355        if (p.openElements.currentTagName !== $.COLGROUP)
2356            token.ignored = true;
2357
2358        else {
2359            p.openElements.pop();
2360            p.insertionMode = IN_TABLE_MODE;
2361        }
2362    }
2363
2364    else if (tn === $.TEMPLATE)
2365        endTagInHead(p, token);
2366
2367    else if (tn !== $.COL)
2368        tokenInColumnGroup(p, token);
2369}
2370
2371function tokenInColumnGroup(p, token) {
2372    var fakeToken = p._processFakeEndTag($.COLGROUP);
2373
2374    //NOTE: The fake end tag token here can only be ignored in the fragment case.
2375    if (!fakeToken.ignored)
2376        p._processToken(token);
2377}
2378
2379//12.2.5.4.13 The "in table body" insertion mode
2380//------------------------------------------------------------------
2381function startTagInTableBody(p, token) {
2382    var tn = token.tagName;
2383
2384    if (tn === $.TR) {
2385        p.openElements.clearBackToTableBodyContext();
2386        p._insertElement(token, NS.HTML);
2387        p.insertionMode = IN_ROW_MODE;
2388    }
2389
2390    else if (tn === $.TH || tn === $.TD) {
2391        p._processFakeStartTag($.TR);
2392        p._processToken(token);
2393    }
2394
2395    else if (tn === $.CAPTION || tn === $.COL || tn === $.COLGROUP ||
2396             tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
2397
2398        if (p.openElements.hasTableBodyContextInTableScope()) {
2399            p.openElements.clearBackToTableBodyContext();
2400            p._processFakeEndTag(p.openElements.currentTagName);
2401            p._processToken(token);
2402        }
2403    }
2404
2405    else
2406        startTagInTable(p, token);
2407}
2408
2409function endTagInTableBody(p, token) {
2410    var tn = token.tagName;
2411
2412    if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
2413        if (p.openElements.hasInTableScope(tn)) {
2414            p.openElements.clearBackToTableBodyContext();
2415            p.openElements.pop();
2416            p.insertionMode = IN_TABLE_MODE;
2417        }
2418    }
2419
2420    else if (tn === $.TABLE) {
2421        if (p.openElements.hasTableBodyContextInTableScope()) {
2422            p.openElements.clearBackToTableBodyContext();
2423            p._processFakeEndTag(p.openElements.currentTagName);
2424            p._processToken(token);
2425        }
2426    }
2427
2428    else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP ||
2429             tn !== $.HTML && tn !== $.TD && tn !== $.TH && tn !== $.TR) {
2430        endTagInTable(p, token);
2431    }
2432}
2433
2434//12.2.5.4.14 The "in row" insertion mode
2435//------------------------------------------------------------------
2436function startTagInRow(p, token) {
2437    var tn = token.tagName;
2438
2439    if (tn === $.TH || tn === $.TD) {
2440        p.openElements.clearBackToTableRowContext();
2441        p._insertElement(token, NS.HTML);
2442        p.insertionMode = IN_CELL_MODE;
2443        p.activeFormattingElements.insertMarker();
2444    }
2445
2446    else if (tn === $.CAPTION || tn === $.COL || tn === $.COLGROUP || tn === $.TBODY ||
2447             tn === $.TFOOT || tn === $.THEAD || tn === $.TR) {
2448        var fakeToken = p._processFakeEndTag($.TR);
2449
2450        //NOTE: The fake end tag token here can only be ignored in the fragment case.
2451        if (!fakeToken.ignored)
2452            p._processToken(token);
2453    }
2454
2455    else
2456        startTagInTable(p, token);
2457}
2458
2459function endTagInRow(p, token) {
2460    var tn = token.tagName;
2461
2462    if (tn === $.TR) {
2463        if (p.openElements.hasInTableScope($.TR)) {
2464            p.openElements.clearBackToTableRowContext();
2465            p.openElements.pop();
2466            p.insertionMode = IN_TABLE_BODY_MODE;
2467        }
2468
2469        else
2470            token.ignored = true;
2471    }
2472
2473    else if (tn === $.TABLE) {
2474        var fakeToken = p._processFakeEndTag($.TR);
2475
2476        //NOTE: The fake end tag token here can only be ignored in the fragment case.
2477        if (!fakeToken.ignored)
2478            p._processToken(token);
2479    }
2480
2481    else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
2482        if (p.openElements.hasInTableScope(tn)) {
2483            p._processFakeEndTag($.TR);
2484            p._processToken(token);
2485        }
2486    }
2487
2488    else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP ||
2489             tn !== $.HTML && tn !== $.TD && tn !== $.TH) {
2490        endTagInTable(p, token);
2491    }
2492}
2493
2494
2495//12.2.5.4.15 The "in cell" insertion mode
2496//------------------------------------------------------------------
2497function startTagInCell(p, token) {
2498    var tn = token.tagName;
2499
2500    if (tn === $.CAPTION || tn === $.COL || tn === $.COLGROUP || tn === $.TBODY ||
2501        tn === $.TD || tn === $.TFOOT || tn === $.TH || tn === $.THEAD || tn === $.TR) {
2502
2503        if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) {
2504            p._closeTableCell();
2505            p._processToken(token);
2506        }
2507    }
2508
2509    else
2510        startTagInBody(p, token);
2511}
2512
2513function endTagInCell(p, token) {
2514    var tn = token.tagName;
2515
2516    if (tn === $.TD || tn === $.TH) {
2517        if (p.openElements.hasInTableScope(tn)) {
2518            p.openElements.generateImpliedEndTags();
2519            p.openElements.popUntilTagNamePopped(tn);
2520            p.activeFormattingElements.clearToLastMarker();
2521            p.insertionMode = IN_ROW_MODE;
2522        }
2523    }
2524
2525    else if (tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR) {
2526        if (p.openElements.hasInTableScope(tn)) {
2527            p._closeTableCell();
2528            p._processToken(token);
2529        }
2530    }
2531
2532    else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML)
2533        endTagInBody(p, token);
2534}
2535
2536//12.2.5.4.16 The "in select" insertion mode
2537//------------------------------------------------------------------
2538function startTagInSelect(p, token) {
2539    var tn = token.tagName;
2540
2541    if (tn === $.HTML)
2542        startTagInBody(p, token);
2543
2544    else if (tn === $.OPTION) {
2545        if (p.openElements.currentTagName === $.OPTION)
2546            p._processFakeEndTag($.OPTION);
2547
2548        p._insertElement(token, NS.HTML);
2549    }
2550
2551    else if (tn === $.OPTGROUP) {
2552        if (p.openElements.currentTagName === $.OPTION)
2553            p._processFakeEndTag($.OPTION);
2554
2555        if (p.openElements.currentTagName === $.OPTGROUP)
2556            p._processFakeEndTag($.OPTGROUP);
2557
2558        p._insertElement(token, NS.HTML);
2559    }
2560
2561    else if (tn === $.SELECT)
2562        p._processFakeEndTag($.SELECT);
2563
2564    else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA) {
2565        if (p.openElements.hasInSelectScope($.SELECT)) {
2566            p._processFakeEndTag($.SELECT);
2567            p._processToken(token);
2568        }
2569    }
2570
2571    else if (tn === $.SCRIPT || tn === $.TEMPLATE)
2572        startTagInHead(p, token);
2573}
2574
2575function endTagInSelect(p, token) {
2576    var tn = token.tagName;
2577
2578    if (tn === $.OPTGROUP) {
2579        var prevOpenElement = p.openElements.items[p.openElements.stackTop - 1],
2580            prevOpenElementTn = prevOpenElement && p.treeAdapter.getTagName(prevOpenElement);
2581
2582        if (p.openElements.currentTagName === $.OPTION && prevOpenElementTn === $.OPTGROUP)
2583            p._processFakeEndTag($.OPTION);
2584
2585        if (p.openElements.currentTagName === $.OPTGROUP)
2586            p.openElements.pop();
2587    }
2588
2589    else if (tn === $.OPTION) {
2590        if (p.openElements.currentTagName === $.OPTION)
2591            p.openElements.pop();
2592    }
2593
2594    else if (tn === $.SELECT && p.openElements.hasInSelectScope($.SELECT)) {
2595        p.openElements.popUntilTagNamePopped($.SELECT);
2596        p._resetInsertionMode();
2597    }
2598
2599    else if (tn === $.TEMPLATE)
2600        endTagInHead(p, token);
2601}
2602
2603//12.2.5.4.17 The "in select in table" insertion mode
2604//------------------------------------------------------------------
2605function startTagInSelectInTable(p, token) {
2606    var tn = token.tagName;
2607
2608    if (tn === $.CAPTION || tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT ||
2609        tn === $.THEAD || tn === $.TR || tn === $.TD || tn === $.TH) {
2610        p._processFakeEndTag($.SELECT);
2611        p._processToken(token);
2612    }
2613
2614    else
2615        startTagInSelect(p, token);
2616}
2617
2618function endTagInSelectInTable(p, token) {
2619    var tn = token.tagName;
2620
2621    if (tn === $.CAPTION || tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT ||
2622        tn === $.THEAD || tn === $.TR || tn === $.TD || tn === $.TH) {
2623        if (p.openElements.hasInTableScope(tn)) {
2624            p._processFakeEndTag($.SELECT);
2625            p._processToken(token);
2626        }
2627    }
2628
2629    else
2630        endTagInSelect(p, token);
2631}
2632
2633//12.2.5.4.18 The "in template" insertion mode
2634//------------------------------------------------------------------
2635function startTagInTemplate(p, token) {
2636    var tn = token.tagName;
2637
2638    if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META ||
2639        tn === $.NOFRAMES || tn === $.SCRIPT || tn === $.STYLE || tn === $.TEMPLATE || tn === $.TITLE) {
2640        startTagInHead(p, token);
2641    }
2642
2643    else {
2644        var newInsertionMode = TEMPLATE_INSERTION_MODE_SWITCH_MAP[tn] || IN_BODY_MODE;
2645
2646        p._popTmplInsertionMode();
2647        p._pushTmplInsertionMode(newInsertionMode);
2648        p.insertionMode = newInsertionMode;
2649        p._processToken(token);
2650    }
2651}
2652
2653function endTagInTemplate(p, token) {
2654    if (token.tagName === $.TEMPLATE)
2655        endTagInHead(p, token);
2656}
2657
2658function eofInTemplate(p, token) {
2659    if (p.openElements.tmplCount > 0) {
2660        p.openElements.popUntilTemplatePopped();
2661        p.activeFormattingElements.clearToLastMarker();
2662        p._popTmplInsertionMode();
2663        p._resetInsertionMode();
2664        p._processToken(token);
2665    }
2666
2667    else
2668        p.stopped = true;
2669}
2670
2671
2672//12.2.5.4.19 The "after body" insertion mode
2673//------------------------------------------------------------------
2674function startTagAfterBody(p, token) {
2675    if (token.tagName === $.HTML)
2676        startTagInBody(p, token);
2677
2678    else
2679        tokenAfterBody(p, token);
2680}
2681
2682function endTagAfterBody(p, token) {
2683    if (token.tagName === $.HTML) {
2684        if (!p.fragmentContext)
2685            p.insertionMode = AFTER_AFTER_BODY_MODE;
2686    }
2687
2688    else
2689        tokenAfterBody(p, token);
2690}
2691
2692function tokenAfterBody(p, token) {
2693    p.insertionMode = IN_BODY_MODE;
2694    p._processToken(token);
2695}
2696
2697//12.2.5.4.20 The "in frameset" insertion mode
2698//------------------------------------------------------------------
2699function startTagInFrameset(p, token) {
2700    var tn = token.tagName;
2701
2702    if (tn === $.HTML)
2703        startTagInBody(p, token);
2704
2705    else if (tn === $.FRAMESET)
2706        p._insertElement(token, NS.HTML);
2707
2708    else if (tn === $.FRAME)
2709        p._appendElement(token, NS.HTML);
2710
2711    else if (tn === $.NOFRAMES)
2712        startTagInHead(p, token);
2713}
2714
2715function endTagInFrameset(p, token) {
2716    if (token.tagName === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) {
2717        p.openElements.pop();
2718
2719        if (!p.fragmentContext && p.openElements.currentTagName !== $.FRAMESET)
2720            p.insertionMode = AFTER_FRAMESET_MODE;
2721    }
2722}
2723
2724//12.2.5.4.21 The "after frameset" insertion mode
2725//------------------------------------------------------------------
2726function startTagAfterFrameset(p, token) {
2727    var tn = token.tagName;
2728
2729    if (tn === $.HTML)
2730        startTagInBody(p, token);
2731
2732    else if (tn === $.NOFRAMES)
2733        startTagInHead(p, token);
2734}
2735
2736function endTagAfterFrameset(p, token) {
2737    if (token.tagName === $.HTML)
2738        p.insertionMode = AFTER_AFTER_FRAMESET_MODE;
2739}
2740
2741//12.2.5.4.22 The "after after body" insertion mode
2742//------------------------------------------------------------------
2743function startTagAfterAfterBody(p, token) {
2744    if (token.tagName === $.HTML)
2745        startTagInBody(p, token);
2746
2747    else
2748        tokenAfterAfterBody(p, token);
2749}
2750
2751function tokenAfterAfterBody(p, token) {
2752    p.insertionMode = IN_BODY_MODE;
2753    p._processToken(token);
2754}
2755
2756//12.2.5.4.23 The "after after frameset" insertion mode
2757//------------------------------------------------------------------
2758function startTagAfterAfterFrameset(p, token) {
2759    var tn = token.tagName;
2760
2761    if (tn === $.HTML)
2762        startTagInBody(p, token);
2763
2764    else if (tn === $.NOFRAMES)
2765        startTagInHead(p, token);
2766}
2767
2768
2769//12.2.5.5 The rules for parsing tokens in foreign content
2770//------------------------------------------------------------------
2771function nullCharacterInForeignContent(p, token) {
2772    token.chars = UNICODE.REPLACEMENT_CHARACTER;
2773    p._insertCharacters(token);
2774}
2775
2776function characterInForeignContent(p, token) {
2777    p._insertCharacters(token);
2778    p.framesetOk = false;
2779}
2780
2781function startTagInForeignContent(p, token) {
2782    if (ForeignContent.causesExit(token) && !p.fragmentContext) {
2783        while (p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML &&
2784               (!p._isMathMLTextIntegrationPoint(p.openElements.current)) &&
2785               (!p._isHtmlIntegrationPoint(p.openElements.current))) {
2786            p.openElements.pop();
2787        }
2788
2789        p._processToken(token);
2790    }
2791
2792    else {
2793        var current = p._getAdjustedCurrentElement(),
2794            currentNs = p.treeAdapter.getNamespaceURI(current);
2795
2796        if (currentNs === NS.MATHML)
2797            ForeignContent.adjustTokenMathMLAttrs(token);
2798
2799        else if (currentNs === NS.SVG) {
2800            ForeignContent.adjustTokenSVGTagName(token);
2801            ForeignContent.adjustTokenSVGAttrs(token);
2802        }
2803
2804        ForeignContent.adjustTokenXMLAttrs(token);
2805
2806        if (token.selfClosing)
2807            p._appendElement(token, currentNs);
2808        else
2809            p._insertElement(token, currentNs);
2810    }
2811}
2812
2813function endTagInForeignContent(p, token) {
2814    for (var i = p.openElements.stackTop; i > 0; i--) {
2815        var element = p.openElements.items[i];
2816
2817        if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) {
2818            p._processToken(token);
2819            break;
2820        }
2821
2822        if (p.treeAdapter.getTagName(element).toLowerCase() === token.tagName) {
2823            p.openElements.popUntilElementPopped(element);
2824            break;
2825        }
2826    }
2827}
2828