Skip to content

Commit

Permalink
refactor(open-element-stack): Use sets everywhere (#1086)
Browse files Browse the repository at this point in the history
Replaces the array searches and map lookups with sets and switch statements.

Also replaces the `isNumberedHeader` helper function with a set.
  • Loading branch information
fb55 authored Dec 23, 2023
1 parent 47b5ea3 commit db9072d
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 109 deletions.
4 changes: 1 addition & 3 deletions packages/parse5/lib/common/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,7 @@ export const SPECIAL_ELEMENTS: Record<NS, Set<TAG_ID>> = {
[NS.XMLNS]: new Set(),
};

export function isNumberedHeader(tn: TAG_ID): boolean {
return tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6;
}
export const NUMBERED_HEADERS = new Set([$.H1, $.H2, $.H3, $.H4, $.H5, $.H6]);

const UNESCAPED_TEXT = new Set<string>([
TAG_NAMES.STYLE,
Expand Down
4 changes: 2 additions & 2 deletions packages/parse5/lib/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
ATTRS,
SPECIAL_ELEMENTS,
DOCUMENT_MODE,
isNumberedHeader,
NUMBERED_HEADERS,
getTagID,
} from '../common/html.js';
import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js';
Expand Down Expand Up @@ -1986,7 +1986,7 @@ function numberedHeaderStartTagInBody<T extends TreeAdapterTypeMap>(p: Parser<T>
p._closePElement();
}

if (isNumberedHeader(p.openElements.currentTagId)) {
if (NUMBERED_HEADERS.has(p.openElements.currentTagId)) {
p.openElements.pop();
}

Expand Down
196 changes: 92 additions & 104 deletions packages/parse5/lib/parser/open-element-stack.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TAG_ID as $, NS, isNumberedHeader } from '../common/html.js';
import { TAG_ID as $, NS, NUMBERED_HEADERS } from '../common/html.js';
import type { TreeAdapter, TreeAdapterTypeMap } from '../tree-adapters/interface.js';

//Element utils
Expand All @@ -14,32 +14,26 @@ const IMPLICIT_END_TAG_REQUIRED_THOROUGHLY = new Set([
$.THEAD,
$.TR,
]);
const SCOPING_ELEMENT_NS = new Map<$, NS>([
[$.APPLET, NS.HTML],
[$.CAPTION, NS.HTML],
[$.HTML, NS.HTML],
[$.MARQUEE, NS.HTML],
[$.OBJECT, NS.HTML],
[$.TABLE, NS.HTML],
[$.TD, NS.HTML],
[$.TEMPLATE, NS.HTML],
[$.TH, NS.HTML],
[$.ANNOTATION_XML, NS.MATHML],
[$.MI, NS.MATHML],
[$.MN, NS.MATHML],
[$.MO, NS.MATHML],
[$.MS, NS.MATHML],
[$.MTEXT, NS.MATHML],
[$.DESC, NS.SVG],
[$.FOREIGN_OBJECT, NS.SVG],
[$.TITLE, NS.SVG],
const SCOPING_ELEMENTS_HTML = new Set([
$.APPLET,
$.CAPTION,
$.HTML,
$.MARQUEE,
$.OBJECT,
$.TABLE,
$.TD,
$.TEMPLATE,
$.TH,
]);
const SCOPING_ELEMENTS_HTML_LIST = new Set([...SCOPING_ELEMENTS_HTML, $.OL, $.UL]);
const SCOPING_ELEMENTS_HTML_BUTTON = new Set([...SCOPING_ELEMENTS_HTML, $.BUTTON]);
const SCOPING_ELEMENTS_MATHML = new Set([$.ANNOTATION_XML, $.MI, $.MN, $.MO, $.MS, $.MTEXT]);
const SCOPING_ELEMENTS_SVG = new Set([$.DESC, $.FOREIGN_OBJECT, $.TITLE]);

const NAMED_HEADERS = [$.H1, $.H2, $.H3, $.H4, $.H5, $.H6];
const TABLE_ROW_CONTEXT = [$.TR, $.TEMPLATE, $.HTML];
const TABLE_BODY_CONTEXT = [$.TBODY, $.TFOOT, $.THEAD, $.TEMPLATE, $.HTML];
const TABLE_CONTEXT = [$.TABLE, $.TEMPLATE, $.HTML];
const TABLE_CELLS = [$.TD, $.TH];
const TABLE_ROW_CONTEXT = new Set([$.TR, $.TEMPLATE, $.HTML]);
const TABLE_BODY_CONTEXT = new Set([$.TBODY, $.TFOOT, $.THEAD, $.TEMPLATE, $.HTML]);
const TABLE_CONTEXT = new Set([$.TABLE, $.TEMPLATE, $.HTML]);
const TABLE_CELLS = new Set([$.TD, $.TH]);

export interface StackHandler<T extends TreeAdapterTypeMap> {
onItemPush: (node: T['parentNode'], tid: number, isTop: boolean) => void;
Expand Down Expand Up @@ -161,13 +155,13 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this.shortenToLength(idx < 0 ? 0 : idx);
}

private popUntilPopped(tagNames: $[], targetNS: NS): void {
private popUntilPopped(tagNames: Set<$>, targetNS: NS): void {
const idx = this._indexOfTagNames(tagNames, targetNS);
this.shortenToLength(idx < 0 ? 0 : idx);
}

popUntilNumberedHeaderPopped(): void {
this.popUntilPopped(NAMED_HEADERS, NS.HTML);
this.popUntilPopped(NUMBERED_HEADERS, NS.HTML);
}

popUntilTableCellPopped(): void {
Expand All @@ -181,16 +175,16 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this.shortenToLength(1);
}

private _indexOfTagNames(tagNames: $[], namespace: NS): number {
private _indexOfTagNames(tagNames: Set<$>, namespace: NS): number {
for (let i = this.stackTop; i >= 0; i--) {
if (tagNames.includes(this.tagIDs[i]) && this.treeAdapter.getNamespaceURI(this.items[i]) === namespace) {
if (tagNames.has(this.tagIDs[i]) && this.treeAdapter.getNamespaceURI(this.items[i]) === namespace) {
return i;
}
}
return -1;
}

private clearBackTo(tagNames: $[], targetNS: NS): void {
private clearBackTo(tagNames: Set<$>, targetNS: NS): void {
const idx = this._indexOfTagNames(tagNames, targetNS);
this.shortenToLength(idx + 1);
}
Expand Down Expand Up @@ -244,68 +238,60 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
}

//Element in scope
hasInScope(tagName: $): boolean {
private hasInDynamicScope(tagName: $, htmlScope: Set<$>): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (tn === tagName && ns === NS.HTML) {
return true;
}

if (SCOPING_ELEMENT_NS.get(tn) === ns) {
return false;
switch (this.treeAdapter.getNamespaceURI(this.items[i])) {
case NS.HTML: {
if (tn === tagName) return true;
if (htmlScope.has(tn)) return false;
break;
}
case NS.SVG: {
if (SCOPING_ELEMENTS_SVG.has(tn)) return false;
break;
}
case NS.MATHML: {
if (SCOPING_ELEMENTS_MATHML.has(tn)) return false;
break;
}
}
}

return true;
}

hasNumberedHeaderInScope(): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (isNumberedHeader(tn) && ns === NS.HTML) {
return true;
}

if (SCOPING_ELEMENT_NS.get(tn) === ns) {
return false;
}
}

return true;
hasInScope(tagName: $): boolean {
return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML);
}

hasInListItemScope(tagName: $): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (tn === tagName && ns === NS.HTML) {
return true;
}

if (((tn === $.UL || tn === $.OL) && ns === NS.HTML) || SCOPING_ELEMENT_NS.get(tn) === ns) {
return false;
}
}

return true;
return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML_LIST);
}

hasInButtonScope(tagName: $): boolean {
return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML_BUTTON);
}

hasNumberedHeaderInScope(): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (tn === tagName && ns === NS.HTML) {
return true;
}

if ((tn === $.BUTTON && ns === NS.HTML) || SCOPING_ELEMENT_NS.get(tn) === ns) {
return false;
switch (this.treeAdapter.getNamespaceURI(this.items[i])) {
case NS.HTML: {
if (NUMBERED_HEADERS.has(tn)) return true;
if (SCOPING_ELEMENTS_HTML.has(tn)) return false;
break;
}
case NS.SVG: {
if (SCOPING_ELEMENTS_SVG.has(tn)) return false;
break;
}
case NS.MATHML: {
if (SCOPING_ELEMENTS_MATHML.has(tn)) return false;
break;
}
}
}

Expand All @@ -314,19 +300,18 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {

hasInTableScope(tagName: $): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (ns !== NS.HTML) {
if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) {
continue;
}

if (tn === tagName) {
return true;
}

if (tn === $.TABLE || tn === $.TEMPLATE || tn === $.HTML) {
return false;
switch (this.tagIDs[i]) {
case tagName: {
return true;
}
case $.TABLE:
case $.HTML: {
return false;
}
}
}

Expand All @@ -335,19 +320,20 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {

hasTableBodyContextInTableScope(): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (ns !== NS.HTML) {
if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) {
continue;
}

if (tn === $.TBODY || tn === $.THEAD || tn === $.TFOOT) {
return true;
}

if (tn === $.TABLE || tn === $.HTML) {
return false;
switch (this.tagIDs[i]) {
case $.TBODY:
case $.THEAD:
case $.TFOOT: {
return true;
}
case $.TABLE:
case $.HTML: {
return false;
}
}
}

Expand All @@ -356,19 +342,21 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {

hasInSelectScope(tagName: $): boolean {
for (let i = this.stackTop; i >= 0; i--) {
const tn = this.tagIDs[i];
const ns = this.treeAdapter.getNamespaceURI(this.items[i]);

if (ns !== NS.HTML) {
if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) {
continue;
}

if (tn === tagName) {
return true;
}

if (tn !== $.OPTION && tn !== $.OPTGROUP) {
return false;
switch (this.tagIDs[i]) {
case tagName: {
return true;
}
case $.OPTION:
case $.OPTGROUP: {
break;
}
default: {
return false;
}
}
}

Expand Down

0 comments on commit db9072d

Please sign in to comment.