/**
* 将JSON格式的HTML结构转换为HTML字符串
* @param {Object} json - JSON格式的HTML结构
* @returns {string} HTML字符串
*/
function jsonToHtml(json) {
if (!json || typeof json !== 'object') {
return '';
}
function parseElement(obj) {
if (obj === null || obj === undefined) {
return '';
}
if (typeof obj === 'string') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => parseElement(item)).join('');
}
// 检查是否为文本节点 (#text)
if ('#text' in obj) {
let tagName = 'div'; // 默认标签
let attributes = '';
let content = obj['#text'] || '';
// 处理属性和其他设置
for (const key in obj) {
if (key === '#text') continue;
if (key.startsWith('@')) {
// 属性 (@class, @id等)
const attrName = key.substring(1);
attributes += ` ${attrName}="${obj[key]}"`;
} else if (['div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'section', 'header', 'footer', 'main', 'article', 'aside'].includes(key)) {
// 子元素标签
tagName = key;
content = parseElement(obj[key]);
}
}
return `<${tagName}${attributes}>${content}${tagName}>`;
}
// 普通元素节点
let tagName = 'div'; // 默认标签
let attributes = '';
let content = '';
for (const key in obj) {
if (key.startsWith('@')) {
// 属性 (@class, @id等)
const attrName = key.substring(1);
attributes += ` ${attrName}="${obj[key]}"`;
} else if (['div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'section', 'header', 'footer', 'main', 'article', 'aside', 'ul', 'ol', 'li', 'table', 'thead', 'tbody', 'tr', 'td', 'th'].includes(key)) {
// 明确指定的标签类型
tagName = key;
content = parseElement(obj[key]);
} else if (key === 'style') {
// style标签特殊处理
content += ``;
} else if (key === 'title') {
// title标签特殊处理
content += `
${obj[key]}`;
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
// 其他嵌套对象
content += parseElement(obj[key]);
} else if (key !== '#text') {
// 其他属性
attributes += ` ${key}="${obj[key]}"`;
}
}
return `<${tagName}${attributes}>${content}${tagName}>`;
}
// 处理顶层html结构
if (json.html) {
const headContent = json.html.head ? parseElement(json.html.head) : '';
const bodyContent = json.html.body ? parseElement(json.html.body) : '';
const headSection = headContent ? `${headContent}` : '';
const bodySection = bodyContent ? `${bodyContent}` : '';
return `${headSection}${bodySection}`;
}
if (json.head || json.body) {
const headContent = json.head ? parseElement(json.head) : '';
const bodyContent = json.body ? parseElement(json.body) : '';
const headSection = headContent ? `${headContent}` : '';
const bodySection = bodyContent ? `${bodyContent}` : '';
return `${headSection}${bodySection}`;
}
return parseElement(json);
}
/**
* 生成随机字符串
* @param {number} length - 字符串长度
* @returns {string} 随机字符串
*/
function randomString(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
/**
* Sanitizes HTML content by removing potentially dangerous elements and attributes
* @param {string} html - HTML content to sanitize
* @returns {string} Sanitized HTML content
*/
async function sanitizeHtml(html) {
if (typeof html !== 'string') {
return '';
}
// Dynamically import DOMPurify and JSDOM
const { JSDOM } = await import('jsdom');
const { default: DOMPurify } = await import('dompurify');
// Create a window object from jsdom for DOMPurify
const window = new JSDOM('').window;
const purify = DOMPurify(window);
// Define allowed tags and attributes for posters
const allowedTags = [
'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'section', 'header', 'footer', 'main', 'article', 'aside',
'ul', 'ol', 'li', 'table', 'thead', 'tbody', 'tr', 'td', 'th',
'strong', 'em', 'b', 'i', 'u', 's', 'small', 'sub', 'sup',
'a', 'img', 'br', 'hr', 'pre', 'code', 'blockquote', 'cite',
'figure', 'figcaption', 'mark', 'time', 'address', 'dl', 'dt', 'dd'
];
const allowedAttributes = [
'class', 'id', 'style', 'title', 'alt', 'href', 'src', 'width', 'height',
'colspan', 'rowspan', 'align', 'valign', 'scope', 'headers', 'abbr',
'datetime', 'cite', 'rel', 'target', 'name', 'value', 'type'
];
// Sanitize the HTML
const sanitized = purify.sanitize(html, {
ALLOWED_TAGS: allowedTags,
ALLOWED_ATTR: allowedAttributes,
FORBID_TAGS: ['script', 'object', 'embed', 'form', 'input', 'button', 'textarea', 'select', 'option', 'iframe', 'frame', 'frameset', 'applet', 'base', 'meta', 'link', 'noscript'],
FORBID_ATTR: ['on*', 'src*', 'href*', 'action*', 'data*', 'vbscript*', 'javascript*', 'expression', 'behavior', 'xmlns']
});
return sanitized;
}
export {
jsonToHtml,
randomString,
sanitizeHtml
};