93 lines
1.9 KiB
TypeScript
93 lines
1.9 KiB
TypeScript
export type ParsedTag = {
|
|
tagName: string;
|
|
attributes: string;
|
|
parsedAttrs: Record<string, string>;
|
|
isClosing: boolean;
|
|
isSelfClosing: boolean;
|
|
};
|
|
|
|
type TagInfo = ParsedTag & {
|
|
from: number;
|
|
to: number;
|
|
text: string;
|
|
};
|
|
|
|
export type MatchedPair = {
|
|
open: TagInfo;
|
|
close: TagInfo;
|
|
};
|
|
|
|
const TAG_REGEX = /^<(\/?)([a-zA-Z][\w-]*)((?:\s[^>]*?)?)\s*(\/?)>$/s;
|
|
|
|
const ATTR_REGEX =
|
|
/([a-zA-Z:_][\w\-.:]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
|
|
const VOID_ELEMENTS = new Set([
|
|
"area",
|
|
"base",
|
|
"br",
|
|
"col",
|
|
"embed",
|
|
"hr",
|
|
"img",
|
|
"input",
|
|
"link",
|
|
"meta",
|
|
"param",
|
|
"source",
|
|
"track",
|
|
"wbr",
|
|
]);
|
|
|
|
export function parseHtmlTag(text: string): ParsedTag | null {
|
|
const match = TAG_REGEX.exec(text);
|
|
if (!match) return null;
|
|
const rawAttrs = match[3].trim();
|
|
const parsedAttrs: Record<string, string> = {};
|
|
if (rawAttrs) {
|
|
let attrMatch;
|
|
ATTR_REGEX.lastIndex = 0;
|
|
while ((attrMatch = ATTR_REGEX.exec(rawAttrs)) !== null) {
|
|
parsedAttrs[attrMatch[1]] =
|
|
attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? "";
|
|
}
|
|
}
|
|
return {
|
|
tagName: match[2],
|
|
isClosing: match[1] === "/",
|
|
attributes: rawAttrs,
|
|
parsedAttrs,
|
|
isSelfClosing:
|
|
match[4] === "/" || VOID_ELEMENTS.has(match[2].toLowerCase()),
|
|
};
|
|
}
|
|
|
|
export function matchHtmlTagPairs(tags: TagInfo[]): {
|
|
pairs: MatchedPair[];
|
|
voidElements: TagInfo[];
|
|
} {
|
|
const pairs: MatchedPair[] = [];
|
|
const voidElements: TagInfo[] = [];
|
|
const stack: TagInfo[] = [];
|
|
|
|
for (const tag of tags) {
|
|
if (tag.isSelfClosing) {
|
|
voidElements.push(tag);
|
|
continue;
|
|
}
|
|
if (tag.isClosing) {
|
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
if (stack[i].tagName.toLowerCase() === tag.tagName.toLowerCase()) {
|
|
pairs.push({ open: stack[i], close: tag });
|
|
stack.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
stack.push(tag);
|
|
}
|
|
}
|
|
|
|
return { pairs, voidElements };
|
|
}
|