paper-burner/js/history/exporter/docx_diagnostic.js

206 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* DOCX 导出诊断工具
* 用于检测和诊断 DOCX 导出中可能导致文件无法打开的问题
*/
(function(window) {
'use strict';
/**
* 诊断 DOCX 导出问题
* @param {Object} payload - 导出数据
* @param {Object} options - 导出选项
* @returns {Object} 诊断报告
*/
function diagnoseDOCXExport(payload, options = {}) {
const report = {
timestamp: new Date().toISOString(),
issues: [],
warnings: [],
info: {},
passed: true
};
console.log('🔍 开始 DOCX 导出诊断...');
// 1. 检查 payload 基本结构
if (!payload) {
report.issues.push('payload 为空');
report.passed = false;
} else {
report.info.hasBodyHtml = !!payload.bodyHtml;
report.info.bodyHtmlLength = payload.bodyHtml ? payload.bodyHtml.length : 0;
report.info.hasImages = Array.isArray(payload.images) && payload.images.length > 0;
report.info.imageCount = payload.images ? payload.images.length : 0;
}
// 2. 检查 HTML 内容中的潜在问题
if (payload && payload.bodyHtml) {
const html = payload.bodyHtml;
// 检查非法字符
const illegalChars = html.match(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F]/g);
if (illegalChars) {
report.warnings.push(`HTML 中发现 ${illegalChars.length} 个非法控制字符`);
console.warn('⚠ 非法字符位置:', illegalChars.map(c => '0x' + c.charCodeAt(0).toString(16)).join(', '));
}
// 检查公式数量
const formulaCount = (html.match(/<math/g) || []).length;
const katexCount = (html.match(/class="katex/g) || []).length;
report.info.mathmlCount = formulaCount;
report.info.katexCount = katexCount;
report.info.totalFormulas = formulaCount + katexCount;
if (report.info.totalFormulas > 50) {
report.warnings.push(`包含大量公式 (${report.info.totalFormulas} 个),可能影响转换性能`);
}
// 检查表格数量
const tableCount = (html.match(/<table/g) || []).length;
report.info.tableCount = tableCount;
// 检查特殊字符
const ampersandCount = (html.match(/&(?!amp;|lt;|gt;|quot;|apos;|#)/g) || []).length;
if (ampersandCount > 0) {
report.warnings.push(`发现 ${ampersandCount} 个未转义的 & 符号`);
}
// 检查标签平衡
const openTags = (html.match(/<[^/][^>]*>/g) || []).length;
const closeTags = (html.match(/<\/[^>]*>/g) || []).length;
const selfCloseTags = (html.match(/<[^>]*\/>/g) || []).length;
report.info.tagBalance = { open: openTags, close: closeTags, selfClose: selfCloseTags };
if (openTags - selfCloseTags !== closeTags) {
report.warnings.push(`HTML 标签可能不平衡 (开始:${openTags}, 结束:${closeTags}, 自闭合:${selfCloseTags})`);
}
}
// 3. 尝试模拟构建过程
try {
console.log('🔨 模拟构建 DOCX...');
const parser = new DOMParser();
const wrapped = `<div>${payload.bodyHtml || ''}</div>`;
const dom = parser.parseFromString(wrapped, 'text/html');
if (dom.querySelector('parsererror')) {
report.issues.push('HTML 解析失败,包含语法错误');
report.passed = false;
}
const bodyNodes = Array.from(dom.body.childNodes || []);
report.info.bodyNodeCount = bodyNodes.length;
// 检查是否有问题节点
let problematicNodes = 0;
bodyNodes.forEach((node, index) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const tag = node.tagName.toLowerCase();
// 检查是否有未知或问题标签
if (tag === 'script' || tag === 'style') {
report.warnings.push(`发现 ${tag} 标签,可能影响导出`);
}
// 检查深度嵌套
const depth = getNodeDepth(node);
if (depth > 20) {
report.warnings.push(`节点 ${index} 嵌套过深 (${depth} 层)`);
problematicNodes++;
}
}
});
report.info.problematicNodes = problematicNodes;
} catch (error) {
report.issues.push(`模拟构建失败: ${error.message}`);
report.passed = false;
}
// 4. 生成报告
console.log('\n📊 诊断报告:');
console.log('═'.repeat(60));
if (report.issues.length > 0) {
console.error('❌ 严重问题:');
report.issues.forEach(issue => console.error(`${issue}`));
}
if (report.warnings.length > 0) {
console.warn('\n⚠ 警告:');
report.warnings.forEach(warning => console.warn(`${warning}`));
}
console.log('\n 信息:');
Object.entries(report.info).forEach(([key, value]) => {
console.log(`${key}: ${JSON.stringify(value)}`);
});
console.log('\n' + '═'.repeat(60));
console.log(report.passed ? '✅ 诊断通过' : '❌ 发现问题');
return report;
}
/**
* 计算节点深度
*/
function getNodeDepth(node, depth = 0) {
if (!node || !node.childNodes || node.childNodes.length === 0) {
return depth;
}
let maxDepth = depth;
Array.from(node.childNodes).forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
const childDepth = getNodeDepth(child, depth + 1);
if (childDepth > maxDepth) {
maxDepth = childDepth;
}
}
});
return maxDepth;
}
/**
* 导出 DOCX 并启用详细日志
*/
async function exportDOCXWithDiagnostics(payload, options = {}, helpers = {}) {
// 先运行诊断
const report = diagnoseDOCXExport(payload, options);
if (!report.passed) {
const continueExport = confirm('诊断发现问题,是否继续导出?\n\n问题:\n' + report.issues.join('\n'));
if (!continueExport) {
throw new Error('用户取消导出');
}
}
// 启用调试模式导出
const diagnosticOptions = {
...options,
debug: true,
strictValidation: true,
validateXml: true
};
console.log('\n📦 开始导出 DOCX (调试模式)...');
try {
await exportAsDocx(payload, diagnosticOptions, helpers);
console.log('✅ 导出成功!');
} catch (error) {
console.error('❌ 导出失败:', error);
throw error;
}
}
// 暴露到全局
window.diagnoseDOCXExport = diagnoseDOCXExport;
window.exportDOCXWithDiagnostics = exportDOCXWithDiagnostics;
})(window);