in src/parser/htmlParser.ts [66:164]
export function parse(text: string): HTMLDocument {
const scanner = createScanner(text, undefined, undefined, true);
const htmlDocument = new Node(0, text.length, [], void 0);
let curr = htmlDocument;
let endTagStart: number = -1;
let endTagName: string | undefined = undefined;
let pendingAttribute: string | null = null;
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTagOpen:
const child = new Node(scanner.getTokenOffset(), text.length, [], curr);
curr.children.push(child);
curr = child;
break;
case TokenType.StartTag:
curr.tag = scanner.getTokenText();
break;
case TokenType.StartTagClose:
if (curr.parent) {
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
if (scanner.getTokenLength()) {
curr.startTagEnd = scanner.getTokenEnd();
if (curr.tag && isVoidElement(curr.tag)) {
curr.closed = true;
curr = curr.parent;
}
} else {
// pseudo close token from an incomplete start tag
curr = curr.parent;
}
}
break;
case TokenType.StartTagSelfClose:
if (curr.parent) {
curr.closed = true;
curr.end = scanner.getTokenEnd();
curr.startTagEnd = scanner.getTokenEnd();
curr = curr.parent;
}
break;
case TokenType.EndTagOpen:
endTagStart = scanner.getTokenOffset();
endTagName = undefined;
break;
case TokenType.EndTag:
endTagName = scanner.getTokenText().toLowerCase();
break;
case TokenType.EndTagClose:
let node = curr;
// see if we can find a matching tag
while (!node.isSameTag(endTagName) && node.parent) {
node = node.parent;
}
if (node.parent) {
while (curr !== node) {
curr.end = endTagStart;
curr.closed = false;
curr = curr.parent!;
}
curr.closed = true;
curr.endTagStart = endTagStart;
curr.end = scanner.getTokenEnd();
curr = curr.parent!;
}
break;
case TokenType.AttributeName: {
pendingAttribute = scanner.getTokenText();
let attributes = curr.attributes;
if (!attributes) {
curr.attributes = attributes = {};
}
attributes[pendingAttribute] = null; // Support valueless attributes such as 'checked'
break;
}
case TokenType.AttributeValue: {
const value = scanner.getTokenText();
const attributes = curr.attributes;
if (attributes && pendingAttribute) {
attributes[pendingAttribute] = value;
pendingAttribute = null;
}
break;
}
}
token = scanner.scan();
}
while (curr.parent) {
curr.end = text.length;
curr.closed = false;
curr = curr.parent;
}
return {
roots: htmlDocument.children,
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
};
}