Skip to content

Commit

Permalink
feat(parser): Add isImplied flag to onopentag/onclosetag (#930)
Browse files Browse the repository at this point in the history
  • Loading branch information
fb55 committed Aug 27, 2021
1 parent 28c162b commit f917004
Show file tree
Hide file tree
Showing 30 changed files with 434 additions and 172 deletions.
2 changes: 1 addition & 1 deletion src/Parser.spec.ts
Expand Up @@ -143,7 +143,7 @@ describe("API", () => {
const p = new Parser(null);

// Should not throw
p.write("<__proto__>");
p.parseChunk("<__proto__>");
});

test("should support custom tokenizer", () => {
Expand Down
61 changes: 36 additions & 25 deletions src/Parser.ts
Expand Up @@ -167,7 +167,7 @@ export interface Handler {
*/
onend(): void;
onerror(error: Error): void;
onclosetag(name: string): void;
onclosetag(name: string, isImplied: boolean): void;
onopentagname(name: string): void;
/**
*
Expand All @@ -180,7 +180,11 @@ export interface Handler {
value: string,
quote?: string | undefined | null
): void;
onopentag(name: string, attribs: { [s: string]: string }): void;
onopentag(
name: string,
attribs: { [s: string]: string },
isImplied: boolean
): void;
ontext(data: string): void;
oncomment(data: string): void;
oncdatastart(): void;
Expand Down Expand Up @@ -266,7 +270,7 @@ export class Parser {
impliesClose.has(this.stack[this.stack.length - 1])
) {
const el = this.stack.pop()!;
this.cbs.onclosetag?.(el);
this.cbs.onclosetag?.(el, true);
}
}
if (!this.isVoidElement(name)) {
Expand All @@ -281,20 +285,25 @@ export class Parser {
if (this.cbs.onopentag) this.attribs = {};
}

/** @internal */
onopentagend(): void {
private endOpenTag(isImplied: boolean) {
this.startIndex = this.openTagStart;
this.endIndex = this.tokenizer.getAbsoluteIndex();

if (this.attribs) {
this.cbs.onopentag?.(this.tagname, this.attribs);
this.cbs.onopentag?.(this.tagname, this.attribs, isImplied);
this.attribs = null;
}
if (this.cbs.onclosetag && this.isVoidElement(this.tagname)) {
this.cbs.onclosetag(this.tagname);
this.cbs.onclosetag(this.tagname, true);
}

this.tagname = "";
}

/** @internal */
onopentagend(): void {
this.endOpenTag(false);

// Set `startIndex` for next node
this.startIndex = this.endIndex + 1;
}
Expand All @@ -306,29 +315,33 @@ export class Parser {
if (this.lowerCaseTagNames) {
name = name.toLowerCase();
}

if (
foreignContextElements.has(name) ||
htmlIntegrationElements.has(name)
) {
this.foreignContext.pop();
}
if (this.stack.length && !this.isVoidElement(name)) {
let pos = this.stack.lastIndexOf(name);

if (!this.isVoidElement(name)) {
const pos = this.stack.lastIndexOf(name);
if (pos !== -1) {
if (this.cbs.onclosetag) {
pos = this.stack.length - pos;
while (pos--) {
let count = this.stack.length - pos;
while (count--) {
// We know the stack has sufficient elements.
this.cbs.onclosetag(this.stack.pop() as string);
this.cbs.onclosetag(this.stack.pop()!, pos !== 0);
}
} else this.stack.length = pos;
} else if (name === "p" && !this.options.xmlMode) {
} else if (!this.options.xmlMode && name === "p") {
this.emitOpenTag(name);
this.closeCurrentTag();
this.closeCurrentTag(true);
}
} else if (!this.options.xmlMode && (name === "br" || name === "p")) {
this.emitOpenTag(name);
this.closeCurrentTag();
} else if (!this.options.xmlMode && name === "br") {
// We can't go through `emitOpenTag` here, as `br` would be implicitly closed.
this.cbs.onopentagname?.(name);
this.cbs.onopentag?.(name, {}, true);
this.cbs.onclosetag?.(name, false);
}

// Set `startIndex` for next node
Expand All @@ -342,23 +355,21 @@ export class Parser {
this.options.recognizeSelfClosing ||
this.foreignContext[this.foreignContext.length - 1]
) {
this.closeCurrentTag();
this.closeCurrentTag(false);
} else {
// Ignore the fact that the tag is self-closing.
this.onopentagend();
}
}

private closeCurrentTag() {
private closeCurrentTag(isOpenImplied: boolean) {
const name = this.tagname;
this.onopentagend();
this.endOpenTag(isOpenImplied);

// Self-closing tags will be on the top of the stack
if (this.stack[this.stack.length - 1] === name) {
// Reset the start index
this.startIndex = this.openTagStart;

this.cbs.onclosetag?.(name);
// If the opening tag isn't implied, the closing tag has to be implied.
this.cbs.onclosetag?.(name, !isOpenImplied);
this.stack.pop();
}
}
Expand Down Expand Up @@ -471,7 +482,7 @@ export class Parser {
for (
let i = this.stack.length;
i > 0;
this.cbs.onclosetag(this.stack[--i])
this.cbs.onclosetag(this.stack[--i], true)
);
}
this.cbs.onend?.();
Expand Down
5 changes: 3 additions & 2 deletions src/__fixtures__/Events/01-simple.json
Expand Up @@ -22,7 +22,8 @@
"h1",
{
"class": "test"
}
},
false
]
},
{
Expand All @@ -35,7 +36,7 @@
"event": "closetag",
"startIndex": 19,
"endIndex": 23,
"data": ["h1"]
"data": ["h1", false]
}
]
}
9 changes: 5 additions & 4 deletions src/__fixtures__/Events/02-template.json
Expand Up @@ -12,7 +12,7 @@
"event": "opentag",
"startIndex": 0,
"endIndex": 2,
"data": ["p", {}]
"data": ["p", {}, false]
},
{
"event": "opentagname",
Expand All @@ -34,7 +34,8 @@
"script",
{
"type": "text/template"
}
},
false
]
},
{
Expand All @@ -47,13 +48,13 @@
"event": "closetag",
"startIndex": 49,
"endIndex": 57,
"data": ["script"]
"data": ["script", true]
},
{
"event": "closetag",
"startIndex": 58,
"endIndex": 61,
"data": ["p"]
"data": ["p", false]
}
]
}
5 changes: 3 additions & 2 deletions src/__fixtures__/Events/03-lowercase_tags.json
Expand Up @@ -27,7 +27,8 @@
"h1",
{
"class": "test"
}
},
false
]
},
{
Expand All @@ -40,7 +41,7 @@
"event": "closetag",
"startIndex": 19,
"endIndex": 23,
"data": ["h1"]
"data": ["h1", false]
}
]
}
4 changes: 2 additions & 2 deletions src/__fixtures__/Events/04-cdata.json
Expand Up @@ -17,7 +17,7 @@
"event": "opentag",
"startIndex": 0,
"endIndex": 4,
"data": ["tag", {}]
"data": ["tag", {}, false]
},
{
"event": "cdatastart",
Expand All @@ -41,7 +41,7 @@
"event": "closetag",
"startIndex": 42,
"endIndex": 47,
"data": ["tag"]
"data": ["tag", false]
},
{
"event": "processinginstruction",
Expand Down
4 changes: 2 additions & 2 deletions src/__fixtures__/Events/05-cdata-special.json
Expand Up @@ -12,7 +12,7 @@
"event": "opentag",
"startIndex": 0,
"endIndex": 7,
"data": ["script", {}]
"data": ["script", {}, false]
},
{
"event": "text",
Expand All @@ -24,7 +24,7 @@
"event": "closetag",
"startIndex": 53,
"endIndex": 61,
"data": ["script"]
"data": ["script", false]
}
]
}
9 changes: 5 additions & 4 deletions src/__fixtures__/Events/07-self-closing.json
Expand Up @@ -22,7 +22,8 @@
"a",
{
"href": "http://test.com/"
}
},
false
]
},
{
Expand All @@ -35,7 +36,7 @@
"event": "closetag",
"startIndex": 28,
"endIndex": 31,
"data": ["a"]
"data": ["a", false]
},
{
"event": "opentagname",
Expand All @@ -47,13 +48,13 @@
"event": "opentag",
"startIndex": 32,
"endIndex": 38,
"data": ["hr", {}]
"data": ["hr", {}, false]
},
{
"event": "closetag",
"startIndex": 32,
"endIndex": 38,
"data": ["hr"]
"data": ["hr", true]
}
]
}

0 comments on commit f917004

Please sign in to comment.