mammoth.js终极配置管理指南高效实现DOCX到HTML转换的最佳实践【免费下载链接】mammoth.jsConvert Word documents (.docx files) to HTML项目地址: https://gitcode.com/gh_mirrors/ma/mammoth.jsmammoth.js是一款专注于将Microsoft Word文档.docx格式转换为HTML的轻量级JavaScript库。 作为企业级文档处理流水线的核心组件mammoth.js通过语义化解析DOCX文件结构生成简洁干净的HTML代码完美解决了传统文档转换中的格式丢失问题。本文将深入解析mammoth.js的配置系统架构并提供完整的配置管理集成方案帮助开发者构建可维护、可扩展的文档转换系统。文档转换配置的挑战与机遇在企业级应用开发中文档转换配置管理常常面临诸多挑战不同团队使用重复的样式映射规则却各自维护、复杂的CLI参数导致部署脚本难以维护、多环境切换时配置文件版本混乱、紧急修复样式问题需要修改代码而非配置。mammoth.js的灵活配置系统为解决这些问题提供了坚实基础。通过本文您将掌握mammoth.js配置系统的核心原理学习如何与主流配置管理工具集成并构建企业级文档处理流水线。mammoth.js配置系统架构深度解析核心配置模块的三层架构mammoth.js的配置系统采用模块化设计由三个核心组件构成Options Reader选项读取器- 位于lib/options-reader.js负责解析和合并用户配置Style Reader样式读取器- 位于lib/style-reader.js负责解析样式映射规则Document Converter文档转换器- 位于lib/document-to-html.js负责应用配置规则进行文档转换// 配置加载流程示例 const mammoth require(mammoth); const options { styleMap: [ p[style-nameSection Title] h1:fresh, p[style-nameSubsection Title] h2:fresh ], includeDefaultStyleMap: true, includeEmbeddedStyleMap: true }; mammoth.convertToHtml({path: document.docx}, options) .then(result { console.log(result.value); // 生成的HTML });配置加载优先级机制mammoth.js采用三级配置合并策略确保配置的灵活性和可覆盖性运行时参数最高优先级 - 通过options对象传入的配置文档内嵌样式映射- DOCX文件中包含的样式规则默认样式映射表- mammoth.js内置的基础转换规则// options-reader.js中的配置合并逻辑 function readOptions(options) { options options || {}; return _.extend({}, standardOptions, options, { customStyleMap: readStyleMap(options.styleMap), readStyleMap: function() { var styleMap this.customStyleMap; if (this.includeEmbeddedStyleMap) { styleMap styleMap.concat(readStyleMap(this.embeddedStyleMap)); } if (this.includeDefaultStyleMap) { styleMap styleMap.concat(defaultStyleMap); } return styleMap; } }); }默认样式映射分析mammoth.js内置了丰富的默认样式映射规则覆盖了常见文档元素// 默认样式映射核心片段 var defaultStyleMap [ p.Heading1 h1:fresh, p.Heading2 h2:fresh, p.Heading3 h3:fresh, p.Heading4 h4:fresh, p.Heading5 h5:fresh, p.Heading6 h6:fresh, p[style-nameHeading 1] h1:fresh, p[style-nameHeading 2] h2:fresh, p:unordered-list(1) ul li:fresh, p:ordered-list(1) ol li:fresh, r[style-nameStrong] strong, p[style-nameNormal] p:fresh ];环境变量驱动的配置管理dotenv集成方案将mammoth.js与dotenv集成可以实现环境变量驱动的配置管理特别适合不同环境下的样式规则切换。实现步骤安装依赖npm install dotenv --save创建环境配置文件# .env.development MAMMOTH_STYLE_MAPp.Heading1 h1:fresh\np.Heading2 h2:fresh MAMMOTH_INCLUDE_DEFAULTtrue MAMMOTH_OUTPUT_FORMAThtml MAMMOTH_IMAGE_HANDLERdataUri集成代码实现// config/mammoth-config.js require(dotenv).config({ path: .env.${process.env.NODE_ENV || development} }); const mammoth require(mammoth); class MammothConfig { constructor() { this.config this.loadConfig(); } loadConfig() { const styleMap process.env.MAMMOTH_STYLE_MAP ? process.env.MAMMOTH_STYLE_MAP.split(\\n) : []; return { styleMap: styleMap, includeDefaultStyleMap: process.env.MAMMOTH_INCLUDE_DEFAULT true, includeEmbeddedStyleMap: process.env.MAMMOTH_INCLUDE_EMBEDDED true, outputFormat: process.env.MAMMOTH_OUTPUT_FORMAT || html, transformDocument: this.getTransformFunction() }; } getTransformFunction() { // 根据环境变量返回不同的转换函数 if (process.env.MAMMOTH_TRANSFORM_TYPE clean) { return this.cleanTransform; } return null; } cleanTransform(element) { // 清理文档元素的转换逻辑 if (element.children) { element.children element.children.map(child this.cleanTransform(child) ); } return element; } } module.exports new MammothConfig();环境切换实践# 开发环境 NODE_ENVdevelopment node app.js # 测试环境 NODE_ENVtesting node app.js # 生产环境 NODE_ENVproduction node app.js配置文件管理工具集成Cosmiconfig集成方案Cosmiconfig是Node.js生态中流行的配置文件加载器支持多种格式和查找策略非常适合构建企业级配置系统。项目结构设计project/ ├── .mammothrc.js # 主配置文件 ├── config/ │ ├── base.js # 基础配置 │ ├── development.js # 开发环境配置 │ ├── production.js # 生产环境配置 │ └── mammoth.js # 配置加载器 ├── styles/ │ ├── headings.js # 标题样式规则 │ ├── lists.js # 列表样式规则 │ ├── tables.js # 表格样式规则 │ └── custom.js # 自定义样式规则 └── transforms/ ├── document.js # 文档转换逻辑 └── image.js # 图片处理逻辑配置加载器实现// config/mammoth.js const { cosmiconfigSync } require(cosmiconfig); const path require(path); class ConfigLoader { constructor() { this.explorer cosmiconfigSync(mammoth, { searchPlaces: [ .mammothrc, .mammothrc.json, .mammothrc.js, .mammothrc.cjs, mammoth.config.js, mammoth.config.cjs, package.json ], loaders: { .js: this.loadJsConfig, .cjs: this.loadJsConfig, .json: this.loadJsonConfig } }); } loadJsConfig(filepath) { // 动态加载JS配置文件 const config require(filepath); return { config, filepath }; } loadJsonConfig(filepath) { // 加载JSON配置文件 const fs require(fs); const content fs.readFileSync(filepath, utf8); return { config: JSON.parse(content), filepath }; } load() { const result this.explorer.search(); if (!result) { throw new Error(Mammoth configuration not found); } // 合并环境特定配置 const env process.env.NODE_ENV || development; const envConfigPath path.join(__dirname, ${env}.js); let config result.config; if (require(fs).existsSync(envConfigPath)) { const envConfig require(envConfigPath); config this.mergeConfigs(config, envConfig); } return config; } mergeConfigs(base, override) { // 深度合并配置对象 return require(lodash).merge({}, base, override); } } module.exports new ConfigLoader().load();模块化配置组织对于复杂项目建议采用模块化配置组织方式// styles/headings.js - 标题样式规则 module.exports [ p.Heading1 h1:fresh, p.Heading2 h2:fresh, p.Heading3 h3:fresh, p[style-nameTitle] h1.title:fresh, p[style-nameSubtitle] h2.subtitle:fresh ]; // styles/lists.js - 列表样式规则 module.exports [ p:unordered-list(1) ul li:fresh, p:unordered-list(2) ul li ul li:fresh, p:ordered-list(1) ol li:fresh, p:ordered-list(2) ol li ol li:fresh ]; // .mammothrc.js - 主配置文件 const headings require(./styles/headings); const lists require(./styles/lists); const tables require(./styles/tables); const custom require(./styles/custom); module.exports { styleMap: [...headings, ...lists, ...tables, ...custom], includeDefaultStyleMap: false, includeEmbeddedStyleMap: true, transformDocument: require(./transforms/document), convertImage: require(./transforms/image).convertImage, ignoreEmptyParagraphs: true, idPrefix: doc- };企业级配置管理架构集中化配置服务集成对于大型组织建议将mammoth.js配置纳入企业配置中心实现配置的统一管理和动态更新。架构设计配置客户端实现// services/config-client.js const axios require(axios); const EventEmitter require(events); class ConfigClient extends EventEmitter { constructor(configServerUrl, appId, env) { super(); this.configServerUrl configServerUrl; this.appId appId; this.env env; this.config null; this.cache new Map(); } async fetchConfig() { try { const response await axios.get( ${this.configServerUrl}/api/v1/config/${this.appId}/${this.env}, { headers: { Authorization: Bearer ${process.env.CONFIG_API_KEY} } } ); const newConfig response.data.mammoth; // 检查配置是否变更 if (JSON.stringify(this.config) ! JSON.stringify(newConfig)) { this.config newConfig; this.emit(configChanged, newConfig); console.log(Configuration updated successfully); } return this.config; } catch (error) { console.error(Failed to fetch configuration:, error.message); // 使用缓存的配置或默认配置 return this.config || this.getDefaultConfig(); } } getDefaultConfig() { return { styleMap: [], includeDefaultStyleMap: true, includeEmbeddedStyleMap: true }; } // 轮询更新配置 startPolling(interval 30000) { this.pollingInterval setInterval(async () { await this.fetchConfig(); }, interval); // 初始加载 this.fetchConfig(); } stopPolling() { if (this.pollingInterval) { clearInterval(this.pollingInterval); } } // 获取特定配置项 getStyleMap() { return this.config?.styleMap || []; } getOption(key, defaultValue) { return this.config?.[key] ?? defaultValue; } } module.exports ConfigClient;配置版本控制与审计企业级配置管理必须包含版本控制机制推荐采用以下方案// services/config-versioning.js const { execSync } require(child_process); const fs require(fs); const path require(path); class ConfigVersioning { constructor(configPath) { this.configPath configPath; this.versionHistory []; } // 获取配置的Git版本信息 getConfigVersion() { try { const gitHash execSync( git log -n 1 --prettyformat:%H -- ${this.configPath}, { cwd: path.dirname(this.configPath) } ).toString().trim(); const author execSync( git log -n 1 --prettyformat:%an -- ${this.configPath}, { cwd: path.dirname(this.configPath) } ).toString().trim(); const timestamp execSync( git log -n 1 --prettyformat:%at -- ${this.configPath}, { cwd: path.dirname(this.configPath) } ).toString().trim(); const message execSync( git log -n 1 --prettyformat:%s -- ${this.configPath}, { cwd: path.dirname(this.configPath) } ).toString().trim(); return { hash: gitHash, author: author, timestamp: new Date(parseInt(timestamp) * 1000), message: message, fileStats: fs.statSync(this.configPath) }; } catch (error) { console.warn(Git version info not available:, error.message); return { hash: unknown, author: unknown, timestamp: new Date(), message: No version info, fileStats: fs.statSync(this.configPath) }; } } // 记录配置变更 recordChange(oldConfig, newConfig, changeType, user) { const change { timestamp: new Date(), user: user, type: changeType, oldConfig: oldConfig, newConfig: newConfig, version: this.getConfigVersion() }; this.versionHistory.push(change); // 保存到审计日志 this.saveAuditLog(change); return change; } saveAuditLog(change) { const logPath path.join(path.dirname(this.configPath), config-audit.log); const logEntry JSON.stringify(change) \n; fs.appendFileSync(logPath, logEntry, utf8); } // 回滚到指定版本 async rollbackToVersion(versionHash) { try { execSync( git checkout ${versionHash} -- ${this.configPath}, { cwd: path.dirname(this.configPath) } ); console.log(Rolled back to version ${versionHash}); return true; } catch (error) { console.error(Failed to rollback to version ${versionHash}:, error.message); return false; } } } module.exports ConfigVersioning;高级配置模式与最佳实践配置驱动的样式引擎构建基于配置的样式转换引擎实现复杂的文档样式定制// engines/style-engine.js const mammoth require(mammoth); const { readDocumentMatcher } mammoth.lib.styleReader; class StyleEngine { constructor(config) { this.config config; this.rules this.compileRules(config.styleMap); this.cache new Map(); // 缓存解析结果 } compileRules(styleMap) { return styleMap .split(\n) .filter(line line.trim() !line.startsWith(#)) .map((line, index) { const cacheKey line.trim(); if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const rule this.parseRule(line, index 1); this.cache.set(cacheKey, rule); return rule; }); } parseRule(line, lineNumber) { const [fromPart, toPart] line.split().map(part part.trim()); try { const matcher readDocumentMatcher(fromPart); const transformer this.createTransformer(toPart); return { lineNumber, matcher, transformer, originalRule: line }; } catch (error) { console.warn(Invalid style rule at line ${lineNumber}: ${line}); console.warn(Error: ${error.message}); return null; } } createTransformer(toRule) { if (toRule !) { return () null; // 忽略该元素 } // 解析HTML路径和修饰符 const parts toRule.split(:); const htmlPath parts[0]; const modifiers parts.slice(1); return (element, context) { const result { element: this.createHtmlElement(htmlPath), context: { ...context } }; // 应用修饰符 modifiers.forEach(modifier { this.applyModifier(result, modifier); }); return result; }; } createHtmlElement(path) { // 解析HTML路径如 div.aside h2.fresh const parts path.split().map(part part.trim()); // 构建DOM树结构 return this.buildElementTree(parts); } buildElementTree(parts) { // 递归构建元素树 // 简化实现实际需要更复杂的解析 return { tag: this.extractTag(parts[0]), classes: this.extractClasses(parts[0]), attributes: this.extractAttributes(parts[0]), children: parts.length 1 ? [this.buildElementTree(parts.slice(1))] : [] }; } applyModifier(result, modifier) { if (modifier.startsWith(fresh)) { result.context.fresh true; } else if (modifier.startsWith(separator)) { const separator modifier.match(/separator\(([^])\)/)?.[1]; if (separator) { result.context.separator separator; } } } applyToElement(element, context {}) { for (const rule of this.rules) { if (!rule) continue; if (rule.matcher.matches(element)) { const transformed rule.transformer(element, context); if (transformed null) { return null; // 元素被忽略 } return transformed; } } // 没有匹配的规则返回原始元素 return { element, context }; } // 验证样式映射 validateStyleMap(styleMap) { const errors []; const warnings []; styleMap.split(\n).forEach((line, index) { if (!line.trim() || line.startsWith(#)) { return; } try { const [fromPart, toPart] line.split().map(part part.trim()); if (!fromPart || !toPart) { errors.push({ line: index 1, message: Invalid rule format. Expected: from to, rule: line }); return; } // 尝试解析文档匹配器 readDocumentMatcher(fromPart); // 验证HTML路径 this.validateHtmlPath(toPart, index 1, warnings); } catch (error) { errors.push({ line: index 1, message: error.message, rule: line }); } }); return { valid: errors.length 0, errors, warnings }; } validateHtmlPath(path, lineNumber, warnings) { // 验证HTML路径语法 if (path !) { return; // 忽略规则是有效的 } // 检查是否有未闭合的括号 const openParens (path.match(/\(/g) || []).length; const closeParens (path.match(/\)/g) || []).length; if (openParens ! closeParens) { warnings.push({ line: lineNumber, message: Mismatched parentheses in HTML path, path: path }); } } } module.exports StyleEngine;配置即代码Configuration as Code最佳实践将配置视为代码进行管理遵循以下原则版本控制所有配置文件纳入Git管理代码审查配置变更需要代码审查自动化测试为配置规则编写单元测试文档化为配置选项提供详细文档渐进式部署逐步应用配置变更// tests/style-rules.test.js const assert require(assert); const StyleEngine require(../engines/style-engine); describe(Style Engine Validation, () { it(should validate correct style rules, () { const styleMap p.Heading1 h1:fresh p[style-nameCode Block] pre:separator(\\n) p[style-nameComment] ! ; const engine new StyleEngine({ styleMap }); const validation engine.validateStyleMap(styleMap); assert.strictEqual(validation.valid, true); assert.strictEqual(validation.errors.length, 0); }); it(should detect invalid style rules, () { const styleMap p.Heading1 h1:fresh invalid-rule div p[style-nameTest div # 缺少右括号 ; const engine new StyleEngine({ styleMap }); const validation engine.validateStyleMap(styleMap); assert.strictEqual(validation.valid, false); assert.strictEqual(validation.errors.length, 2); }); it(should apply style rules correctly, () { const styleMap p.Heading1 h1:fresh; const engine new StyleEngine({ styleMap }); const element { type: paragraph, styleId: Heading1 }; const result engine.applyToElement(element); assert(result.element.tag h1); assert(result.context.fresh true); }); });性能优化与调试技巧配置缓存策略大型配置可能影响转换性能建议采用以下优化措施// utils/config-cache.js class ConfigCache { constructor(ttl 300000) { // 5分钟默认TTL this.cache new Map(); this.ttl ttl; this.stats { hits: 0, misses: 0, size: 0 }; } get(key) { const entry this.cache.get(key); if (entry) { if (Date.now() - entry.timestamp this.ttl) { this.stats.hits; return entry.value; } else { // 缓存过期 this.cache.delete(key); this.stats.size--; } } this.stats.misses; return null; } set(key, value) { const entry { value, timestamp: Date.now() }; this.cache.set(key, entry); this.stats.size this.cache.size; // 清理过期缓存 this.cleanup(); return value; } cleanup() { const now Date.now(); for (const [key, entry] of this.cache.entries()) { if (now - entry.timestamp this.ttl) { this.cache.delete(key); this.stats.size--; } } } clear() { this.cache.clear(); this.stats.size 0; this.stats.hits 0; this.stats.misses 0; } getStats() { const hitRate this.stats.hits this.stats.misses 0 ? (this.stats.hits / (this.stats.hits this.stats.misses)) * 100 : 0; return { ...this.stats, hitRate: ${hitRate.toFixed(2)}%, ttl: this.ttl }; } } // 使用缓存 const configCache new ConfigCache(); async function getCachedConfig(configKey) { const cached configCache.get(configKey); if (cached) { return cached; } // 从文件或API加载配置 const config await loadConfig(configKey); return configCache.set(configKey, config); }配置调试工具mammoth.js提供了内置的配置解析验证机制结合自定义调试工具可有效定位配置问题// utils/config-debugger.js const mammoth require(mammoth); class ConfigDebugger { constructor() { this.verbose process.env.MAMMOTH_DEBUG true; } // 验证样式映射 validateStyleMap(styleMap) { const errors []; const warnings []; styleMap.split(\n).forEach((line, index) { if (!line.trim()) return; if (line.startsWith(#)) return; try { const result mammoth.lib.styleReader.readStyle(line); if (result.warnings result.warnings.length 0) { warnings.push({ line: index 1, content: line, warnings: result.warnings.map(w w.message) }); } // 检查规则语法 this.validateRuleSyntax(line, index 1, errors); } catch (error) { errors.push({ line: index 1, content: line, error: error.message }); } }); return { valid: errors.length 0, errors, warnings, summary: { totalRules: styleMap.split(\n).filter(l l.trim() !l.startsWith(#)).length, validRules: styleMap.split(\n).filter(l l.trim() !l.startsWith(#)).length - errors.length, errorCount: errors.length, warningCount: warnings.length } }; } validateRuleSyntax(line, lineNumber, errors) { // 检查基本语法结构 if (!line.includes()) { errors.push({ line: lineNumber, message: Missing operator, content: line }); return; } const [left, right] line.split().map(part part.trim()); if (!left || !right) { errors.push({ line: lineNumber, message: Invalid rule format, content: line }); return; } // 检查文档匹配器 if (!this.isValidDocumentMatcher(left)) { errors.push({ line: lineNumber, message: Invalid document matcher, content: left }); } // 检查HTML路径 if (right ! ! !this.isValidHtmlPath(right)) { errors.push({ line: lineNumber, message: Invalid HTML path, content: right }); } } isValidDocumentMatcher(matcher) { // 简化的文档匹配器验证 const validPatterns [ /^[prtbiustrike]/, // 元素类型 /^[prt]\.\w/, // 样式ID /^[prt]\[style-name/, // 样式名称 /^[prt]:unordered-list\(\d\)/, // 无序列表 /^[prt]:ordered-list\(\d\)/, // 有序列表 /^b$/, // 粗体 /^i$/, // 斜体 /^u$/, // 下划线 /^strike$/, // 删除线 /^all-caps$/, // 全大写 /^small-caps$/, // 小型大写 /^highlight/ // 高亮 ]; return validPatterns.some(pattern pattern.test(matcher)); } isValidHtmlPath(path) { if (path !) return true; // 简化的HTML路径验证 const validPatterns [ /^[a-z][a-z0-9]*/, // 简单元素 /^[a-z][a-z0-9]*\.[a-z][a-z0-9-]*/, // 带class的元素 /^[a-z][a-z0-9]*\[[^\]]\]/, // 带属性的元素 /^[a-z][a-z0-9]*:fresh/, // 带fresh修饰符 /^[a-z][a-z0-9]*:separator\([][^][]\)/, // 带separator修饰符 /^[a-z][a-z0-9]*(\.[a-z][a-z0-9-]*)?(:fresh)?(:separator\([][^][]\))?/, // 组合 /^[a-z][a-z0-9]*(\.[a-z][a-z0-9-]*)?(:fresh)?(:separator\([][^][]\))?\s*\s*[a-z][a-z0-9]*/ // 嵌套 ]; return validPatterns.some(pattern pattern.test(path)); } // 生成配置报告 generateReport(config, styleMap) { const validation this.validateStyleMap(styleMap); return { timestamp: new Date().toISOString(), config: { includeDefaultStyleMap: config.includeDefaultStyleMap, includeEmbeddedStyleMap: config.includeEmbeddedStyleMap, hasTransformDocument: !!config.transformDocument, hasConvertImage: !!config.convertImage }, styleMap: validation, performance: this.collectPerformanceMetrics() }; } collectPerformanceMetrics() { return { memoryUsage: process.memoryUsage(), uptime: process.uptime(), configCacheSize: this.configCache ? this.configCache.getStats().size : 0 }; } } module.exports ConfigDebugger;配置管理最佳实践总结配置组织原则环境分离开发、测试、生产环境使用独立配置模块化设计按功能拆分配置文件版本控制所有配置纳入Git管理文档化为每个配置选项提供说明验证机制配置加载时进行语法和语义验证性能优化建议配置缓存解析后的样式映射进行缓存懒加载按需加载配置模块增量更新只更新变更的配置部分预编译生产环境预编译配置规则安全考虑输入验证严格验证用户提供的样式映射沙箱环境在安全环境中执行转换资源限制限制转换过程中的资源使用审计日志记录所有配置变更和转换操作未来发展方向mammoth.js配置系统的发展将呈现以下趋势声明式配置语言开发专用的样式映射DSL提供更强大的表达能力可视化配置工具图形界面生成样式规则降低使用门槛AI辅助配置基于样本文档自动生成转换规则配置市场共享和复用配置模板构建生态系统实时预览配置变更即时预览转换效果智能推荐基于文档内容推荐最佳样式映射通过本文介绍的集成方案mammoth.js能够无缝融入现代DevOps和CI/CD流程实现文档转换配置的集中化、标准化和自动化管理。无论是简单的个人项目还是复杂的企业级应用合理的配置管理都能显著提升开发效率和系统可维护性。记住好的配置管理不仅仅是技术实现更是一种工程实践。通过遵循本文的最佳实践您将能够构建出健壮、可维护、高效的文档转换系统为企业级文档处理流水线提供坚实基础。附录核心配置选项参考表选项名类型默认值描述styleMapstring/array[]自定义样式映射规则includeDefaultStyleMapbooleantrue是否包含默认样式映射includeEmbeddedStyleMapbooleantrue是否包含文档内嵌样式映射outputFormatstringhtml输出格式可选html或markdowntransformDocumentfunctionidentity文档转换钩子函数convertImagefunctionmammoth.images.dataUri图片转换处理器ignoreEmptyParagraphsbooleantrue是否忽略空段落idPrefixstring生成ID的前缀externalFileAccessbooleanfalse是否允许访问外部文件样式映射语法速查表基础语法[文档元素匹配器] [HTML路径][修饰符]文档元素匹配器p- 段落r- 文本runtable- 表格p.Heading1- 按样式ID匹配p[style-name标题1]- 按样式名称匹配p:unordered-list(1)- 无序列表第一级p:ordered-list(2)- 有序列表第二级HTML路径选项h1:fresh- 创建新元素div.aside h2- 嵌套元素pre:separator(\n)- 设置分隔符!- 忽略该元素常用修饰符:fresh- 强制创建新元素:separator(string)- 设置元素间分隔符.className- 添加CSS类[attributevalue]- 添加HTML属性通过掌握这些配置技巧您将能够充分发挥mammoth.js的潜力构建出高效、可靠的文档转换系统。【免费下载链接】mammoth.jsConvert Word documents (.docx files) to HTML项目地址: https://gitcode.com/gh_mirrors/ma/mammoth.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考