export const enum Type { DOCTYPE = 'DOCTYPE', StartTag = 'start tag', EndTag = 'end tag', Comment = 'comment', Character = 'character', EndOfFile = 'end-of-file' } export type Attribute = { name: NonNullable, value: NonNullable }; export class AttributeList { private attributes: Array; public constructor() { this.attributes = new Array(); } public get current(): Attribute { return this.attributes[this.attributes.length - 1]; } public get list(): Array { return this.attributes; } public nonEmpty(): boolean { return this.list.length !== 0; } public append(attribute: Attribute): void { this.attributes.push(attribute); } } export type Token = { type: Type.DOCTYPE, name?: string, publicIdentifier?: string, systemIdentifier?: string, forceQuirks?: true } | { type: Type.StartTag, name: NonNullable, selfClosing?: true, attributes: AttributeList } | { type: Type.EndTag, name: NonNullable, selfClosing?: true, attributes: AttributeList } | { type: Type.Comment, data: NonNullable } | { type: Type.Character, data: NonNullable } | { type: Type.EndOfFile }; export function stringify(token: Token): string { switch (token.type) { case Type.Character: return token.data; case Type.Comment: return ``; case Type.DOCTYPE: return ``; case Type.EndOfFile: return 'EOF'; case Type.EndTag: return ``; case Type.StartTag: { let string = `<${token.name}`; for (const attribute of token.attributes.list) string += ` ${attribute.name}="${attribute.value}"`; if (token.selfClosing) return `${string} />`; return `${string}>`; }; } }