/**
* Based on the https://github.com/HCESrl/html-to-vue
*/
// @ts-expect-error - html-to-ast has type definition issues with package.json exports
import { parse } from "html-to-ast";
import type { NodeObject } from "./getOptionsFromNode";
type ASTNode = NodeObject | NodeObject[];
type VisitCallback = (
node: unknown,
parent: NodeObject | null,
key: string | null,
index: number | undefined,
) => void;
type ExtraComponentConfig = {
conditions: (node: NodeObject) => boolean;
renderer?: unknown;
};
export type RectifyConfig = {
extraComponentsMap?: Record;
};
/**
* Visit each node in the AST - with callback (adapted from https://lihautan.com/manipulating-ast-with-javascript/)
* @param ast html-parse-stringify AST
* @param callback
*/
function _visitAST(ast: ASTNode, callback: VisitCallback): void {
function _visit(
node: unknown,
parent: NodeObject | null,
key: string | null,
index: number | undefined,
): void {
callback(node, parent, key, index);
if (Array.isArray(node)) {
// node is an array
node.forEach((value, idx) => {
_visit(value, node as unknown as NodeObject, null, idx);
});
} else if (isNode(node)) {
const keys = Object.keys(node);
for (let i = 0; i < keys.length; i++) {
const childKey = keys[i];
if (childKey === undefined) continue;
const child = (node as Record)[childKey];
if (Array.isArray(child)) {
for (let j = 0; j < child.length; j++) {
_visit(child[j], node, key, j);
}
} else if (isNode(child)) {
_visit(child, node, key, undefined);
}
}
}
}
_visit(ast, null, null, undefined);
}
/**
*
* @param node html-parse-stringify AST node
* @returns {boolean}
*/
export function isNode(node: unknown): node is NodeObject {
return (
typeof node === "object" &&
node !== null &&
typeof (node as NodeObject).type !== "undefined"
);
}
export function generateAST(html: string): ASTNode {
return parse(html) as ASTNode;
}
/**
* Converts ast html nodes in vue components
* @param ast
* @param config
* @returns {*}
*/
export function rectifyAST(ast: ASTNode, config: RectifyConfig): ASTNode {
const _ast = JSON.parse(JSON.stringify(ast)) as ASTNode;
const keys = config.extraComponentsMap
? Object.keys(config.extraComponentsMap)
: [];
_visitAST(_ast, (node) => {
if (!isNode(node)) {
return;
}
// checking whether the AST has some components that has to become Vue Components
for (let i = 0; i < keys.length; i++) {
const currentKey = keys[i];
if (currentKey === undefined) continue;
const componentConfig = config.extraComponentsMap?.[currentKey];
if (componentConfig?.conditions(node)) {
node.name = currentKey;
}
}
});
return _ast;
}