| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- import type {
- Token,
- HTMLNode,
- TagToken,
- NormalElement,
- TagEndToken,
- AttributeToken,
- TextToken,
- } from './types';
- import { closingTags, closingTagAncestorBreakers, voidTags } from './tags';
- interface StackItem {
- tagName: string | null;
- children: HTMLNode[];
- }
- interface State {
- stack: StackItem[];
- cursor: number;
- tokens: Token[];
- }
- export const parser = (tokens: Token[]) => {
- const root: StackItem = { tagName: null, children: [] };
- const state: State = { tokens, cursor: 0, stack: [root] };
- parse(state);
- return root.children;
- };
- export const hasTerminalParent = (tagName: string, stack: StackItem[]) => {
- const tagParents = closingTagAncestorBreakers[tagName];
- if (tagParents) {
- let currentIndex = stack.length - 1;
- while (currentIndex >= 0) {
- const parentTagName = stack[currentIndex].tagName;
- if (parentTagName === tagName) break;
- if (parentTagName && tagParents.includes(parentTagName)) return true;
- currentIndex--;
- }
- }
- return false;
- };
- export const rewindStack = (stack: StackItem[], newLength: number) => {
- stack.splice(newLength);
- };
- export const parse = (state: State) => {
- const { stack, tokens } = state;
- let { cursor } = state;
- let nodes = stack[stack.length - 1].children;
- const len = tokens.length;
- while (cursor < len) {
- const token = tokens[cursor];
- if (token.type !== 'tag-start') {
- nodes.push(token as TextToken);
- cursor++;
- continue;
- }
- const tagToken = tokens[++cursor] as TagToken;
- cursor++;
- const tagName = tagToken.content.toLowerCase();
- if (token.close) {
- let index = stack.length;
- let shouldRewind = false;
- while (--index > -1) {
- if (stack[index].tagName === tagName) {
- shouldRewind = true;
- break;
- }
- }
- while (cursor < len) {
- if (tokens[cursor].type !== 'tag-end') break;
- cursor++;
- }
- if (shouldRewind) {
- rewindStack(stack, index);
- break;
- } else continue;
- }
- const isClosingTag = closingTags.includes(tagName);
- let shouldRewindToAutoClose = isClosingTag;
- if (shouldRewindToAutoClose) {
- shouldRewindToAutoClose = !hasTerminalParent(tagName, stack);
- }
- if (shouldRewindToAutoClose) {
- let currentIndex = stack.length - 1;
- while (currentIndex > 0) {
- if (tagName === stack[currentIndex].tagName) {
- rewindStack(stack, currentIndex);
- const previousIndex = currentIndex - 1;
- nodes = stack[previousIndex].children;
- break;
- }
- currentIndex = currentIndex - 1;
- }
- }
- const attributes = [];
- let tagEndToken: TagEndToken | undefined;
- while (cursor < len) {
- const _token = tokens[cursor];
- if (_token.type === 'tag-end') {
- tagEndToken = _token;
- break;
- }
- attributes.push((_token as AttributeToken).content);
- cursor++;
- }
- if (!tagEndToken) break;
- cursor++;
- const children: HTMLNode[] = [];
- const elementNode: NormalElement = {
- type: 'element',
- tagName: tagToken.content,
- attributes,
- children,
- };
- nodes.push(elementNode);
- const hasChildren = !(tagEndToken.close || voidTags.includes(tagName));
- if (hasChildren) {
- stack.push({ tagName, children });
- const innerState = { tokens, cursor, stack };
- parse(innerState);
- cursor = innerState.cursor;
- }
- }
- state.cursor = cursor;
- };
|