chore: establish learning module baseline
This commit is contained in:
136
OpenMAIC/lib/export/html-parser/parser.ts
Normal file
136
OpenMAIC/lib/export/html-parser/parser.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user