From 2409e2a4e3847fe11772cf1c46373c37c7092e17 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com>
Date: Tue, 18 Jan 2022 18:51:24 +0000
Subject: [PATCH 1/4] feat(parser): Add hooks for stack events
Fixes #237
---
packages/parse5/lib/parser/index.test.ts | 24 ++++++++++++++++++++++++
packages/parse5/lib/parser/index.ts | 19 +++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/packages/parse5/lib/parser/index.test.ts b/packages/parse5/lib/parser/index.test.ts
index 82fc0c06f..7ddd8a344 100644
--- a/packages/parse5/lib/parser/index.test.ts
+++ b/packages/parse5/lib/parser/index.test.ts
@@ -1,10 +1,12 @@
import * as assert from 'node:assert';
import * as parse5 from 'parse5';
+import { jest } from '@jest/globals';
import { Parser, ParserOptions } from './index.js';
import type { TreeAdapterTypeMap } from './../tree-adapters/interface.js';
import { generateParsingTests } from 'parse5-test-utils/utils/generate-parsing-tests.js';
import { treeAdapters } from 'parse5-test-utils/utils/common.js';
import { NAMESPACES as NS } from '../common/html.js';
+import { isElementNode } from '../tree-adapters/default.js';
const origParseFragment = Parser.prototype.parseFragment;
@@ -98,4 +100,26 @@ describe('parser', () => {
expect(doctype).toHaveProperty('publicId', '');
expect(doctype).toHaveProperty('systemId', '');
});
+
+ describe('Options', () => {
+ it('should support onItemPush and onItemPop', () => {
+ const onItemPush = jest.fn();
+ const onItemPop = jest.fn();
+ const document = parse5.parse('
', {
+ onItemPush,
+ onItemPop,
+ });
+
+ const htmlElement = document.childNodes[0];
+ assert.ok(isElementNode(htmlElement));
+ const bodyElement = htmlElement.childNodes[1];
+ assert.ok(isElementNode(bodyElement));
+ expect(onItemPush).toHaveBeenCalledTimes(5);
+ expect(onItemPush).toHaveBeenNthCalledWith(1, htmlElement);
+ expect(onItemPush).toHaveBeenNthCalledWith(3, bodyElement);
+ expect(onItemPush).toHaveBeenLastCalledWith(bodyElement.childNodes[1]);
+ expect(onItemPop).toHaveBeenCalledTimes(2);
+ expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0]);
+ });
+ });
});
diff --git a/packages/parse5/lib/parser/index.ts b/packages/parse5/lib/parser/index.ts
index 3ddb4905b..57cc9c10d 100644
--- a/packages/parse5/lib/parser/index.ts
+++ b/packages/parse5/lib/parser/index.ts
@@ -109,6 +109,22 @@ export interface ParserOptions {
* @default `null`
*/
onParseError?: ParserErrorHandler | null;
+
+ /**
+ * Callback for elements being pushed to the stack of open elements.
+ *
+ * @default `null`
+ * @param element The element being pushed to the stack of open elements.
+ */
+ onItemPush?: ((item: T['element']) => void) | null;
+
+ /**
+ * Callback for elements being popped from the stack of open elements.
+ *
+ * @default `null`
+ * @param item The element being popped.
+ */
+ onItemPop?: ((item: T['element']) => void) | null;
}
//Parser
@@ -317,6 +333,7 @@ export class Parser {
//Text parsing
private onItemPush(node: T['parentNode'], tid: number, isTop: boolean): void {
+ this.options.onItemPush?.(node);
if (isTop && this.openElements.stackTop > 0) this._setContextModes(node, tid);
}
@@ -325,6 +342,8 @@ export class Parser {
this._setEndLocation(node, this.currentToken!);
}
+ this.options.onItemPop?.(node);
+
if (isTop) {
let current;
let currentTagId;
From 9c60b206039d3654c2f1760b4912291eb27ade5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com>
Date: Fri, 4 Feb 2022 20:57:09 -0600
Subject: [PATCH 2/4] Move stack events to tree adapter
---
packages/parse5/lib/parser/index.test.ts | 9 ++++++---
packages/parse5/lib/parser/index.ts | 20 ++-----------------
.../parse5/lib/tree-adapters/interface.ts | 14 +++++++++++++
3 files changed, 22 insertions(+), 21 deletions(-)
diff --git a/packages/parse5/lib/parser/index.test.ts b/packages/parse5/lib/parser/index.test.ts
index 7ddd8a344..e6a692f22 100644
--- a/packages/parse5/lib/parser/index.test.ts
+++ b/packages/parse5/lib/parser/index.test.ts
@@ -101,13 +101,16 @@ describe('parser', () => {
expect(doctype).toHaveProperty('systemId', '');
});
- describe('Options', () => {
+ describe('Tree adapters', () => {
it('should support onItemPush and onItemPop', () => {
const onItemPush = jest.fn();
const onItemPop = jest.fn();
const document = parse5.parse('', {
- onItemPush,
- onItemPop,
+ treeAdapter: {
+ ...treeAdapters.default,
+ onItemPush,
+ onItemPop,
+ },
});
const htmlElement = document.childNodes[0];
diff --git a/packages/parse5/lib/parser/index.ts b/packages/parse5/lib/parser/index.ts
index 57cc9c10d..6a87e421f 100644
--- a/packages/parse5/lib/parser/index.ts
+++ b/packages/parse5/lib/parser/index.ts
@@ -109,22 +109,6 @@ export interface ParserOptions {
* @default `null`
*/
onParseError?: ParserErrorHandler | null;
-
- /**
- * Callback for elements being pushed to the stack of open elements.
- *
- * @default `null`
- * @param element The element being pushed to the stack of open elements.
- */
- onItemPush?: ((item: T['element']) => void) | null;
-
- /**
- * Callback for elements being popped from the stack of open elements.
- *
- * @default `null`
- * @param item The element being popped.
- */
- onItemPop?: ((item: T['element']) => void) | null;
}
//Parser
@@ -333,7 +317,7 @@ export class Parser {
//Text parsing
private onItemPush(node: T['parentNode'], tid: number, isTop: boolean): void {
- this.options.onItemPush?.(node);
+ this.treeAdapter.onItemPush?.(node);
if (isTop && this.openElements.stackTop > 0) this._setContextModes(node, tid);
}
@@ -342,7 +326,7 @@ export class Parser {
this._setEndLocation(node, this.currentToken!);
}
- this.options.onItemPop?.(node);
+ this.treeAdapter.onItemPop?.(node);
if (isTop) {
let current;
diff --git a/packages/parse5/lib/tree-adapters/interface.ts b/packages/parse5/lib/tree-adapters/interface.ts
index d0cac0466..54137cfaf 100644
--- a/packages/parse5/lib/tree-adapters/interface.ts
+++ b/packages/parse5/lib/tree-adapters/interface.ts
@@ -279,4 +279,18 @@ export interface TreeAdapter
* @param contentElement - Content element.
*/
setTemplateContent(templateElement: T['template'], contentElement: T['documentFragment']): void;
+
+ /**
+ * Optional callback for elements being pushed to the stack of open elements.
+ *
+ * @param element The element being pushed to the stack of open elements.
+ */
+ onItemPush?: (item: T['element']) => void;
+
+ /**
+ * Optional callback for elements being popped from the stack of open elements.
+ *
+ * @param item The element being popped.
+ */
+ onItemPop?: (item: T['element']) => void;
}
From cc0d661fa37dada169e25933d05b995f8286127c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com>
Date: Sat, 5 Feb 2022 08:43:17 -0600
Subject: [PATCH 3/4] Add new stack top as an additional parameter for
`onItemPop`
---
packages/parse5/lib/parser/index.test.ts | 2 +-
packages/parse5/lib/parser/index.ts | 2 +-
packages/parse5/lib/tree-adapters/interface.ts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/parse5/lib/parser/index.test.ts b/packages/parse5/lib/parser/index.test.ts
index e6a692f22..407ad8d07 100644
--- a/packages/parse5/lib/parser/index.test.ts
+++ b/packages/parse5/lib/parser/index.test.ts
@@ -122,7 +122,7 @@ describe('parser', () => {
expect(onItemPush).toHaveBeenNthCalledWith(3, bodyElement);
expect(onItemPush).toHaveBeenLastCalledWith(bodyElement.childNodes[1]);
expect(onItemPop).toHaveBeenCalledTimes(2);
- expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0]);
+ expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0], bodyElement);
});
});
});
diff --git a/packages/parse5/lib/parser/index.ts b/packages/parse5/lib/parser/index.ts
index 6a87e421f..97b528237 100644
--- a/packages/parse5/lib/parser/index.ts
+++ b/packages/parse5/lib/parser/index.ts
@@ -326,7 +326,7 @@ export class Parser {
this._setEndLocation(node, this.currentToken!);
}
- this.treeAdapter.onItemPop?.(node);
+ this.treeAdapter.onItemPop?.(node, this.openElements.current);
if (isTop) {
let current;
diff --git a/packages/parse5/lib/tree-adapters/interface.ts b/packages/parse5/lib/tree-adapters/interface.ts
index 54137cfaf..eaffab277 100644
--- a/packages/parse5/lib/tree-adapters/interface.ts
+++ b/packages/parse5/lib/tree-adapters/interface.ts
@@ -292,5 +292,5 @@ export interface TreeAdapter
*
* @param item The element being popped.
*/
- onItemPop?: (item: T['element']) => void;
+ onItemPop?: (item: T['element'], newTop: T['parentNode']) => void;
}
From bf5c969940d8050f24f37afd0e02a48f673e2d99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com>
Date: Thu, 10 Feb 2022 16:40:30 +0100
Subject: [PATCH 4/4] Add some comments about which elements we are testing
---
packages/parse5/lib/parser/index.test.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/parse5/lib/parser/index.test.ts b/packages/parse5/lib/parser/index.test.ts
index 407ad8d07..53170bafb 100644
--- a/packages/parse5/lib/parser/index.test.ts
+++ b/packages/parse5/lib/parser/index.test.ts
@@ -117,11 +117,15 @@ describe('parser', () => {
assert.ok(isElementNode(htmlElement));
const bodyElement = htmlElement.childNodes[1];
assert.ok(isElementNode(bodyElement));
+ // Expect 5 opened elements; in order: html, head, body, and 2x p
expect(onItemPush).toHaveBeenCalledTimes(5);
expect(onItemPush).toHaveBeenNthCalledWith(1, htmlElement);
expect(onItemPush).toHaveBeenNthCalledWith(3, bodyElement);
+ // The last opened element is the second p
expect(onItemPush).toHaveBeenLastCalledWith(bodyElement.childNodes[1]);
+ // The second p isn't closed, plus we never pop body and html. Alas, only 2 pop events (head and p).
expect(onItemPop).toHaveBeenCalledTimes(2);
+ // The last pop event should be the first p.
expect(onItemPop).toHaveBeenLastCalledWith(bodyElement.childNodes[0], bodyElement);
});
});