in packages/miniapp-runtime/src/dom-external/inner-html/parser.ts [188:278]
function parse(state: State) {
const { tokens, stack } = state;
let { cursor } = state;
const len = tokens.length;
let nodes = stack[stack.length - 1].children;
while (cursor < len) {
const token = tokens[cursor];
if (token.type !== 'tag-start') {
// comment or text
nodes.push(token as ChildNode);
cursor++;
continue;
}
const tagToken = tokens[++cursor];
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) {
const endToken = tokens[cursor];
if (endToken.type !== 'tag-end') break;
cursor++;
}
if (shouldRewind) {
stack.splice(index);
break;
} else {
continue;
}
}
const isClosingTag = options.html!.closingElements.has(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) {
stack.splice(currentIndex);
const previousIndex = currentIndex - 1;
nodes = stack[previousIndex].children;
break;
}
currentIndex = currentIndex - 1;
}
}
const attributes: string[] = [];
let attrToken: Token;
while (cursor < len) {
attrToken = tokens[cursor];
if (attrToken.type === 'tag-end') break;
attributes.push(attrToken.content!);
cursor++;
}
cursor++;
const children: Element[] = [];
const element: Element = {
type: 'element',
tagName: tagToken.content!,
attributes,
children,
};
nodes.push(element);
const hasChildren = !(attrToken!.close || options.html!.voidElements.has(tagName));
if (hasChildren) {
stack.push({ tagName, children } as any);
const innerState: State = { tokens, cursor, stack };
parse(innerState);
cursor = innerState.cursor;
}
}
state.cursor = cursor;
}