Skip to content

Commit

Permalink
refactor: Use handler for OpenElementStack (#429)
Browse files Browse the repository at this point in the history
Now that the tokenizer is using the delegate pattern, it only makes sense to update the `OpenElementStack` to follow the same pattern.
  • Loading branch information
fb55 committed Mar 3, 2022
1 parent 8ccade0 commit 6e7b230
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 49 deletions.
17 changes: 6 additions & 11 deletions packages/parse5/lib/parser/index.ts
@@ -1,5 +1,5 @@
import { TokenHandler, Tokenizer, TokenizerMode } from '../tokenizer/index.js';
import { OpenElementStack } from './open-element-stack.js';
import { OpenElementStack, StackHandler } from './open-element-stack.js';
import { FormattingElementList, ElementEntry, EntryType } from './formatting-element-list.js';
import * as defaultTreeAdapter from '../tree-adapters/default.js';
import * as doctype from '../common/doctype.js';
Expand Down Expand Up @@ -119,7 +119,7 @@ const defaultParserOptions = {
};

//Parser
export class Parser<T extends TreeAdapterTypeMap> implements TokenHandler {
export class Parser<T extends TreeAdapterTypeMap> implements TokenHandler, StackHandler<T> {
treeAdapter: TreeAdapter<T>;
onParseError: ParserErrorHandler | null;
private currentToken: Token | null = null;
Expand Down Expand Up @@ -152,12 +152,7 @@ export class Parser<T extends TreeAdapterTypeMap> implements TokenHandler {
this.fragmentContextID = fragmentContext ? getTagID(this.treeAdapter.getTagName(fragmentContext)) : $.UNKNOWN;
this._setContextModes(fragmentContext ?? this.document, this.fragmentContextID);

this.openElements = new OpenElementStack(
this.document,
this.treeAdapter,
this.onItemPush.bind(this),
this.onItemPop.bind(this)
);
this.openElements = new OpenElementStack(this.document, this.treeAdapter, this);
}

// API
Expand Down Expand Up @@ -288,13 +283,13 @@ export class Parser<T extends TreeAdapterTypeMap> implements TokenHandler {
writeCallback?.();
}

//Text parsing
private onItemPush(node: T['parentNode'], tid: number, isTop: boolean): void {
//Stack events
onItemPush(node: T['parentNode'], tid: number, isTop: boolean): void {
this.treeAdapter.onItemPush?.(node);
if (isTop && this.openElements.stackTop > 0) this._setContextModes(node, tid);
}

private onItemPop(node: T['parentNode'], isTop: boolean): void {
onItemPop(node: T['parentNode'], isTop: boolean): void {
if (this.options.sourceCodeLocationInfo) {
this._setEndLocation(node, this.currentToken!);
}
Expand Down
59 changes: 32 additions & 27 deletions packages/parse5/lib/parser/open-element-stack.test.ts
Expand Up @@ -8,6 +8,11 @@ function ignore(): void {
/* Ignore */
}

const stackHandler = {
onItemPop: ignore,
onItemPush: ignore,
};

generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
function createElement(tagName: string, namespaceURI = NS.HTML): TreeAdapterTypeMap['element'] {
return treeAdapter.createElement(tagName, namespaceURI, []);
Expand All @@ -17,7 +22,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
const document = treeAdapter.createDocument();
const element1 = createElement('#element1', NS.XLINK);
const element2 = createElement('#element2', NS.SVG);
const stack = new OpenElementStack(document, treeAdapter, ignore, ignore);
const stack = new OpenElementStack(document, treeAdapter, stackHandler);

assert.strictEqual(stack.current, document);
assert.strictEqual(stack.stackTop, -1);
Expand All @@ -33,7 +38,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {

test('Pop element', () => {
const element = createElement('#element', NS.XLINK);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(element, $.UNKNOWN);
stack.push(createElement('#element2', NS.XML), $.UNKNOWN);
Expand All @@ -50,7 +55,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
test('Replace element', () => {
const element = createElement('#element', NS.MATHML);
const newElement = createElement('#newElement', NS.SVG);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement('#element2', NS.XML), $.UNKNOWN);
stack.push(element, $.UNKNOWN);
Expand All @@ -63,7 +68,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
const element1 = createElement('#element1', NS.XLINK);
const element2 = createElement('#element2', NS.SVG);
const element3 = createElement('#element3', NS.XML);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(element1, $.UNKNOWN);
stack.push(element2, $.UNKNOWN);
Expand All @@ -79,7 +84,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
test('Pop elements until popped with given tagName', () => {
const element1 = createElement(TN.ASIDE);
const element2 = createElement(TN.MAIN);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(element2, $.MAIN);
stack.push(element2, $.MAIN);
Expand All @@ -100,7 +105,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
test('Pop elements until given element popped', () => {
const element1 = createElement('#element1');
const element2 = createElement('#element2');
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(element2, $.UNKNOWN);
stack.push(element2, $.UNKNOWN);
Expand All @@ -121,7 +126,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
test('Pop elements until numbered header popped', () => {
const element1 = createElement(TN.H3);
const element2 = createElement(TN.DIV);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(element2, $.DIV);
stack.push(element2, $.DIV);
Expand All @@ -141,7 +146,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {

test('Pop all up to <html> element', () => {
const htmlElement = createElement(TN.HTML);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(htmlElement, $.HTML);
stack.push('#element1', $.UNKNOWN);
Expand All @@ -155,7 +160,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
const htmlElement = createElement(TN.HTML);
const tableElement = createElement(TN.TABLE);
const divElement = createElement(TN.DIV);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(htmlElement, $.HTML);
stack.push(divElement, $.DIV);
Expand All @@ -178,7 +183,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
const htmlElement = createElement(TN.HTML);
const theadElement = createElement(TN.THEAD);
const divElement = createElement(TN.DIV);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(htmlElement, $.HTML);
stack.push(divElement, $.DIV);
Expand All @@ -201,7 +206,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
const htmlElement = createElement(TN.HTML);
const trElement = createElement(TN.TR);
const divElement = createElement(TN.DIV);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(htmlElement, $.HTML);
stack.push(divElement, $.DIV);
Expand All @@ -222,7 +227,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {

test('Remove element', () => {
const element = createElement('#element');
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(element, $.UNKNOWN);
stack.push(createElement('element1'), $.UNKNOWN);
Expand All @@ -239,20 +244,20 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {

test('Try peek properly nested <body> element', () => {
const bodyElement = createElement(TN.BODY);
let stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
let stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(bodyElement, $.BODY);
stack.push(createElement(TN.DIV), $.DIV);
assert.strictEqual(stack.tryPeekProperlyNestedBodyElement(), bodyElement);

stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);
stack.push(createElement(TN.HTML), $.HTML);
assert.ok(!stack.tryPeekProperlyNestedBodyElement());
});

test('Is root <html> element current', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
assert.ok(stack.isRootHtmlElementCurrent());
Expand All @@ -262,7 +267,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Get common ancestor', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);
const element = createElement('#element');
const ancestor = createElement('#ancestor');

Expand All @@ -282,7 +287,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Contains element', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);
const element = createElement('#element');

stack.push(createElement('#someElement'), $.UNKNOWN);
Expand All @@ -293,7 +298,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has element in scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -310,7 +315,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has numbered header in scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -330,7 +335,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has element in list item scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -346,7 +351,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has element in button scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -362,7 +367,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has element in table scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -379,7 +384,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has table body context in table scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -399,7 +404,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Has element in select scope', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.DIV), $.DIV);
Expand All @@ -414,7 +419,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Generate implied end tags', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.LI), $.LI);
Expand All @@ -430,7 +435,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Generate implied end tags with exclusion', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.LI), $.LI);
Expand All @@ -446,7 +451,7 @@ generateTestsForEachTreeAdapter('open-element-stack', (treeAdapter) => {
});

test('Template count', () => {
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, ignore, ignore);
const stack = new OpenElementStack(treeAdapter.createDocument(), treeAdapter, stackHandler);

stack.push(createElement(TN.HTML), $.HTML);
stack.push(createElement(TN.TEMPLATE, NS.MATHML), $.TEMPLATE);
Expand Down
22 changes: 11 additions & 11 deletions packages/parse5/lib/parser/open-element-stack.ts
Expand Up @@ -41,6 +41,11 @@ const TABLE_BODY_CONTEXT = [$.TBODY, $.TFOOT, $.THEAD, $.TEMPLATE, $.HTML];
const TABLE_CONTEXT = [$.TABLE, $.TEMPLATE, $.HTML];
const TABLE_CELLS = [$.TD, $.TH];

export interface StackHandler<T extends TreeAdapterTypeMap> {
onItemPush: (node: T['parentNode'], tid: number, isTop: boolean) => void;
onItemPop: (node: T['parentNode'], isTop: boolean) => void;
}

//Stack of open elements
export class OpenElementStack<T extends TreeAdapterTypeMap> {
items: T['parentNode'][] = [];
Expand All @@ -55,12 +60,7 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
return this._isInTemplate() ? this.treeAdapter.getTemplateContent(this.current) : this.current;
}

constructor(
document: T['document'],
private treeAdapter: TreeAdapter<T>,
private onItemPush: (node: T['parentNode'], tid: number, isTop: boolean) => void,
private onItemPop: (node: T['parentNode'], isTop: boolean) => void
) {
constructor(document: T['document'], private treeAdapter: TreeAdapter<T>, private handler: StackHandler<T>) {
this.current = document;
}

Expand Down Expand Up @@ -92,7 +92,7 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this.tmplCount++;
}

this.onItemPush(element, tagID, true);
this.handler.onItemPush(element, tagID, true);
}

pop(): void {
Expand All @@ -104,7 +104,7 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this.stackTop--;
this._updateCurrentElement();

this.onItemPop(popped, true);
this.handler.onItemPop(popped, true);
}

replace(oldElement: T['element'], newElement: T['element']): void {
Expand All @@ -128,7 +128,7 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this._updateCurrentElement();
}

this.onItemPush(this.current, this.currentTagId, insertionIdx === this.stackTop);
this.handler.onItemPush(this.current, this.currentTagId, insertionIdx === this.stackTop);
}

popUntilTagNamePopped(tagName: $): void {
Expand All @@ -152,7 +152,7 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this.stackTop--;
this._updateCurrentElement();

this.onItemPop(popped, this.stackTop < idx);
this.handler.onItemPop(popped, this.stackTop < idx);
}
}

Expand Down Expand Up @@ -218,7 +218,7 @@ export class OpenElementStack<T extends TreeAdapterTypeMap> {
this.tagIDs.splice(idx, 1);
this.stackTop--;
this._updateCurrentElement();
this.onItemPop(element, false);
this.handler.onItemPop(element, false);
}
}
}
Expand Down

0 comments on commit 6e7b230

Please sign in to comment.