diff --git a/packages/parse5/lib/parser/index.ts b/packages/parse5/lib/parser/index.ts index 8a0fcb341..dfbd7b2e7 100644 --- a/packages/parse5/lib/parser/index.ts +++ b/packages/parse5/lib/parser/index.ts @@ -54,8 +54,6 @@ enum InsertionMode { IN_TABLE_BODY, IN_ROW, IN_CELL, - IN_SELECT, - IN_SELECT_IN_TABLE, IN_TEMPLATE, AFTER_BODY, IN_FRAMESET, @@ -706,10 +704,6 @@ export class Parser implements TokenHandler, Stack this.insertionMode = InsertionMode.IN_FRAMESET; return; } - case $.SELECT: { - this._resetInsertionModeForSelect(i); - return; - } case $.TEMPLATE: { this.insertionMode = this.tmplInsertionModeStack[0]; return; @@ -739,24 +733,6 @@ export class Parser implements TokenHandler, Stack this.insertionMode = InsertionMode.IN_BODY; } - /** @protected */ - _resetInsertionModeForSelect(selectIdx: number): void { - if (selectIdx > 0) { - for (let i = selectIdx - 1; i > 0; i--) { - const tn = this.openElements.tagIDs[i]; - - if (tn === $.TEMPLATE) { - break; - } else if (tn === $.TABLE) { - this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE; - return; - } - } - } - - this.insertionMode = InsertionMode.IN_SELECT; - } - //Foster parenting /** @protected */ @@ -859,9 +835,7 @@ export class Parser implements TokenHandler, Stack characterInBody(this, token); break; } - case InsertionMode.TEXT: - case InsertionMode.IN_SELECT: - case InsertionMode.IN_SELECT_IN_TABLE: { + case InsertionMode.TEXT: { this._insertCharacters(token); break; } @@ -974,8 +948,6 @@ export class Parser implements TokenHandler, Stack case InsertionMode.IN_TABLE_BODY: case InsertionMode.IN_ROW: case InsertionMode.IN_CELL: - case InsertionMode.IN_SELECT: - case InsertionMode.IN_SELECT_IN_TABLE: case InsertionMode.IN_TEMPLATE: case InsertionMode.IN_FRAMESET: case InsertionMode.AFTER_FRAMESET: { @@ -1110,14 +1082,6 @@ export class Parser implements TokenHandler, Stack startTagInCell(this, token); break; } - case InsertionMode.IN_SELECT: { - startTagInSelect(this, token); - break; - } - case InsertionMode.IN_SELECT_IN_TABLE: { - startTagInSelectInTable(this, token); - break; - } case InsertionMode.IN_TEMPLATE: { startTagInTemplate(this, token); break; @@ -1220,14 +1184,6 @@ export class Parser implements TokenHandler, Stack endTagInCell(this, token); break; } - case InsertionMode.IN_SELECT: { - endTagInSelect(this, token); - break; - } - case InsertionMode.IN_SELECT_IN_TABLE: { - endTagInSelectInTable(this, token); - break; - } case InsertionMode.IN_TEMPLATE: { endTagInTemplate(this, token); break; @@ -1285,9 +1241,7 @@ export class Parser implements TokenHandler, Stack case InsertionMode.IN_COLUMN_GROUP: case InsertionMode.IN_TABLE_BODY: case InsertionMode.IN_ROW: - case InsertionMode.IN_CELL: - case InsertionMode.IN_SELECT: - case InsertionMode.IN_SELECT_IN_TABLE: { + case InsertionMode.IN_CELL: { eofInBody(this, token); break; } @@ -1340,8 +1294,6 @@ export class Parser implements TokenHandler, Stack case InsertionMode.AFTER_HEAD: case InsertionMode.TEXT: case InsertionMode.IN_COLUMN_GROUP: - case InsertionMode.IN_SELECT: - case InsertionMode.IN_SELECT_IN_TABLE: case InsertionMode.IN_FRAMESET: case InsertionMode.AFTER_FRAMESET: { this._insertCharacters(token); @@ -2158,6 +2110,13 @@ function hrStartTagInBody(p: Parser, token: Tag p._closePElement(); } + if (p.openElements.hasInScope($.SELECT)) { + p.openElements.generateImpliedEndTagsWithExclusion($.OPTGROUP); + if (p.openElements.hasInScope($.OPTION)) { + p._err(token, ERR.characterReferenceOutsideUnicodeRange); // TODO correct error type + } + } + p._appendElement(token, NS.HTML); p.framesetOk = false; token.ackSelfClosing = true; @@ -2202,26 +2161,45 @@ function rawTextStartTagInBody(p: Parser, token } function selectStartTagInBody(p: Parser, token: TagToken): void { + if (p.openElements.hasInScope($.SELECT)) { + p._err(token, ERR.characterReferenceOutsideUnicodeRange); // TODO correct error type + p.openElements.popUntilTagNamePopped($.SELECT); + } + p._reconstructActiveFormattingElements(); p._insertElement(token, NS.HTML); p.framesetOk = false; +} + +function optionStartTagInBody(p: Parser, token: TagToken): void { + if (p.openElements.hasInScope($.SELECT)) { + p.openElements.generateImpliedEndTagsWithExclusion($.OPTGROUP); + if (p.openElements.hasInScope($.OPTION)) { + p._err(token, ERR.characterReferenceOutsideUnicodeRange); // TODO correct error type + } + } else { + if (p.openElements.currentTagId === $.OPTION) { + p.openElements.pop(); + } + p._reconstructActiveFormattingElements(); + } - p.insertionMode = - p.insertionMode === InsertionMode.IN_TABLE || - p.insertionMode === InsertionMode.IN_CAPTION || - p.insertionMode === InsertionMode.IN_TABLE_BODY || - p.insertionMode === InsertionMode.IN_ROW || - p.insertionMode === InsertionMode.IN_CELL - ? InsertionMode.IN_SELECT_IN_TABLE - : InsertionMode.IN_SELECT; + p._insertElement(token, NS.HTML); } function optgroupStartTagInBody(p: Parser, token: TagToken): void { - if (p.openElements.currentTagId === $.OPTION) { - p.openElements.pop(); + if (p.openElements.hasInScope($.SELECT)) { + p.openElements.generateImpliedEndTags(); + if (p.openElements.hasInScope($.OPTION) || p.openElements.hasInScope($.OPTGROUP)) { + p._err(token, ERR.characterReferenceOutsideUnicodeRange); // TODO correct error type + } + } else { + if (p.openElements.currentTagId == $.OPTION) { + p.openElements.pop(); + } + p._reconstructActiveFormattingElements(); } - p._reconstructActiveFormattingElements(); p._insertElement(token, NS.HTML); } @@ -2444,7 +2422,10 @@ function startTagInBody(p: Parser, token: TagTo selectStartTagInBody(p, token); break; } - case $.OPTION: + case $.OPTION: { + optionStartTagInBody(p, token); + break; + } case $.OPTGROUP: { optgroupStartTagInBody(p, token); break; @@ -3269,155 +3250,6 @@ function endTagInCell(p: Parser, token: TagToke } } -// The "in select" insertion mode -//------------------------------------------------------------------ -function startTagInSelect(p: Parser, token: TagToken): void { - switch (token.tagID) { - case $.HTML: { - startTagInBody(p, token); - break; - } - case $.OPTION: { - if (p.openElements.currentTagId === $.OPTION) { - p.openElements.pop(); - } - - p._insertElement(token, NS.HTML); - break; - } - case $.OPTGROUP: { - if (p.openElements.currentTagId === $.OPTION) { - p.openElements.pop(); - } - - if (p.openElements.currentTagId === $.OPTGROUP) { - p.openElements.pop(); - } - - p._insertElement(token, NS.HTML); - break; - } - case $.HR: { - if (p.openElements.currentTagId === $.OPTION) { - p.openElements.pop(); - } - - if (p.openElements.currentTagId === $.OPTGROUP) { - p.openElements.pop(); - } - - p._appendElement(token, NS.HTML); - token.ackSelfClosing = true; - break; - } - case $.INPUT: - case $.KEYGEN: - case $.TEXTAREA: - case $.SELECT: { - if (p.openElements.hasInSelectScope($.SELECT)) { - p.openElements.popUntilTagNamePopped($.SELECT); - p._resetInsertionMode(); - - if (token.tagID !== $.SELECT) { - p._processStartTag(token); - } - } - break; - } - case $.SCRIPT: - case $.TEMPLATE: { - startTagInHead(p, token); - break; - } - default: - // Do nothing - } -} - -function endTagInSelect(p: Parser, token: TagToken): void { - switch (token.tagID) { - case $.OPTGROUP: { - if ( - p.openElements.stackTop > 0 && - p.openElements.currentTagId === $.OPTION && - p.openElements.tagIDs[p.openElements.stackTop - 1] === $.OPTGROUP - ) { - p.openElements.pop(); - } - - if (p.openElements.currentTagId === $.OPTGROUP) { - p.openElements.pop(); - } - break; - } - case $.OPTION: { - if (p.openElements.currentTagId === $.OPTION) { - p.openElements.pop(); - } - break; - } - case $.SELECT: { - if (p.openElements.hasInSelectScope($.SELECT)) { - p.openElements.popUntilTagNamePopped($.SELECT); - p._resetInsertionMode(); - } - break; - } - case $.TEMPLATE: { - templateEndTagInHead(p, token); - break; - } - default: - // Do nothing - } -} - -// The "in select in table" insertion mode -//------------------------------------------------------------------ -function startTagInSelectInTable(p: Parser, token: TagToken): void { - const tn = token.tagID; - - if ( - tn === $.CAPTION || - tn === $.TABLE || - tn === $.TBODY || - tn === $.TFOOT || - tn === $.THEAD || - tn === $.TR || - tn === $.TD || - tn === $.TH - ) { - p.openElements.popUntilTagNamePopped($.SELECT); - p._resetInsertionMode(); - p._processStartTag(token); - } else { - startTagInSelect(p, token); - } -} - -function endTagInSelectInTable(p: Parser, token: TagToken): void { - const tn = token.tagID; - - if ( - tn === $.CAPTION || - tn === $.TABLE || - tn === $.TBODY || - tn === $.TFOOT || - tn === $.THEAD || - tn === $.TR || - tn === $.TD || - tn === $.TH - ) { - if (p.openElements.hasInTableScope(tn)) { - p.openElements.popUntilTagNamePopped($.SELECT); - p._resetInsertionMode(); - p.onEndTag(token); - } - } else { - endTagInSelect(p, token); - } -} - // The "in template" insertion mode //------------------------------------------------------------------ function startTagInTemplate(p: Parser, token: TagToken): void { diff --git a/packages/parse5/lib/parser/open-element-stack.test.ts b/packages/parse5/lib/parser/open-element-stack.test.ts index 594f81280..deb8aee3f 100644 --- a/packages/parse5/lib/parser/open-element-stack.test.ts +++ b/packages/parse5/lib/parser/open-element-stack.test.ts @@ -409,23 +409,6 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => { assert.ok(stack.hasTableBodyContextInTableScope()); }); - test('Has element in select scope', () => { - const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler); - - assert.ok(stack.hasInSelectScope($.P)); - - stack.push(createElement(TN.HTML), $.HTML); - stack.push(createElement(TN.DIV), $.DIV); - assert.ok(!stack.hasInSelectScope($.P)); - - stack.push(createElement(TN.P), $.P); - stack.push(createElement(TN.OPTION), $.OPTION); - assert.ok(stack.hasInSelectScope($.P)); - - stack.push(createElement(TN.DIV), $.DIV); - assert.ok(!stack.hasInSelectScope($.P)); - }); - test('Generate implied end tags', () => { const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler); diff --git a/packages/parse5/lib/parser/open-element-stack.ts b/packages/parse5/lib/parser/open-element-stack.ts index abeac3094..15d9d8848 100644 --- a/packages/parse5/lib/parser/open-element-stack.ts +++ b/packages/parse5/lib/parser/open-element-stack.ts @@ -18,12 +18,14 @@ const SCOPING_ELEMENTS_HTML = new Set([ $.APPLET, $.CAPTION, $.HTML, - $.MARQUEE, - $.OBJECT, $.TABLE, $.TD, - $.TEMPLATE, $.TH, + $.OBJECT, + $.SELECT, + $.TEMPLATE, + // TODO: this does not seem to belong here? it's not in the spec + $.MARQUEE, ]); const SCOPING_ELEMENTS_HTML_LIST = new Set([...SCOPING_ELEMENTS_HTML, $.OL, $.UL]); const SCOPING_ELEMENTS_HTML_BUTTON = new Set([...SCOPING_ELEMENTS_HTML, $.BUTTON]); @@ -344,29 +346,6 @@ export class OpenElementStack { return true; } - hasInSelectScope(tagName: $): boolean { - for (let i = this.stackTop; i >= 0; i--) { - if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) { - continue; - } - - switch (this.tagIDs[i]) { - case tagName: { - return true; - } - case $.OPTION: - case $.OPTGROUP: { - break; - } - default: { - return false; - } - } - } - - return true; - } - //Implied end tags generateImpliedEndTags(): void { while (IMPLICIT_END_TAG_REQUIRED.has(this.currentTagId)) { diff --git a/test/data/html5lib-tests b/test/data/html5lib-tests index a9f44960a..ee3ebba3e 160000 --- a/test/data/html5lib-tests +++ b/test/data/html5lib-tests @@ -1 +1 @@ -Subproject commit a9f44960a9fedf265093d22b2aa3c7ca123727b9 +Subproject commit ee3ebba3e10c28b45f62b8e75887d5948f799aa9