# 从 DOCX 文件自动提取占位符 ## 功能说明 系统会自动从 docx 文档中提取 `{{占位符}}` 格式的变量,并生成表单字段,无需在数据库中手动配置。 --- ## 实现原理 ### 技术栈 - **pizzip** - 用于解压 docx 文件(docx 本质是 zip 格式) - **正则表达式** - 提取占位符(避免 docxtemplater 编译时的格式化标签分割问题) ### 工作流程 ``` 1. 用户点击"起草合同" ↓ 2. Loader 读取 docx 文件 ↓ 3. PizZip 解压文件,读取 word/document.xml ↓ 4. 移除 XML 标签,提取纯文本 ↓ 5. 正则匹配所有 {{...}} 占位符 ↓ 6. 根据占位符名称自动推测: - 字段类型(text/number/date/textarea) - 字段分组(甲方信息/乙方信息/合同条款) - 是否必填 ↓ 7. 生成 PlaceholderSchema ↓ 8. 渲染表单 ``` --- ## 核心代码 ### docx-parser.server.ts ```typescript import PizZip from 'pizzip'; import fs from 'fs'; /** * 从 docx 文件中提取占位符 */ export async function extractPlaceholdersFromDocx( filePath: string ): Promise { // 1. 读取文件 const content = fs.readFileSync(filePath, 'binary'); // 2. 使用 PizZip 解压 const zip = new PizZip(content); // 3. 读取 document.xml 文件(不使用 docxtemplater,避免格式化文本的标签分割问题) const documentXml = zip.file('word/document.xml'); if (!documentXml) { throw new Error('无法找到 word/document.xml 文件'); } // 4. 获取 XML 文本内容 const xmlContent = documentXml.asText(); // 5. 移除所有 XML 标签,只保留纯文本 const fullText = xmlContent.replace(/<[^>]+>/g, ''); // 6. 使用正则表达式提取所有 {{...}} 占位符 const placeholderRegex = /\{\{([^}]+)\}\}/g; const matches = fullText.matchAll(placeholderRegex); // 7. 去重并返回 const placeholders = new Set(); for (const match of matches) { placeholders.add(match[1].trim()); } return Array.from(placeholders); } /** * 生成默认的 PlaceholderSchema */ export function generateDefaultSchema( placeholders: string[] ): PlaceholderSchema { return { fields: placeholders.map(placeholder => ({ key: placeholder, label: placeholder, type: inferType(placeholder), // 推测类型 required: inferRequired(placeholder), // 推测是否必填 group: inferGroup(placeholder) // 推测分组 })) }; } ``` ### contract-draft.$draftId.tsx (loader) ```typescript export async function loader({ params, request }: LoaderFunctionArgs) { // ... 获取用户信息和草稿信息 ... // 从 docx 文件中提取占位符 const testDocPath = path.join(process.cwd(), 'public', 'testWork', '买卖合同 (1).docx'); // 提取占位符 const placeholders = await extractPlaceholdersFromDocx(testDocPath); console.log('提取到的占位符:', placeholders); // 生成默认 schema const placeholderSchema = generateDefaultSchema(placeholders); return json({ draft, template: { ...template, placeholder_schema: placeholderSchema } }); } ``` --- ## 自动推测规则 ### 字段类型推测 | 占位符名称包含 | 推测类型 | 示例 | |--------------|---------|------| | 金额、价格、数量、amount、price | `number` | {{合同金额}}、{{price}} | | 日期、时间、date、time | `date` | {{签订日期}}、{{date}} | | 地址、说明、备注、address、description | `textarea` | {{甲方地址}}、{{remark}} | | 其他 | `text` | {{甲方名称}}、{{项目名称}} | ### 字段分组推测 | 占位符名称包含 | 推测分组 | 示例 | |--------------|---------|------| | 甲方、partyA | 甲方信息 | {{甲方名称}}、{{partyA_address}} | | 乙方、partyB | 乙方信息 | {{乙方代表}}、{{partyB_phone}} | | 金额、价格、数量 | 合同条款 | {{合同金额}}、{{数量}} | | 日期、时间 | 日期信息 | {{签订日期}}、{{生效日期}} | | 其他 | 基本信息 | {{项目名称}}、{{编号}} | ### 必填字段推测 | 占位符名称包含 | 是否必填 | |--------------|---------| | "可选"、"optional" | ❌ 否 | | 其他 | ✅ 是 | --- ## 使用示例 ### 1. 准备模板文档 在 Word 文档中添加占位符: ``` 买卖合同 甲方名称:{{甲方名称}} 甲方地址:{{甲方地址}} 甲方代表:{{甲方代表}} 乙方名称:{{乙方名称}} 乙方地址:{{乙方地址}} 乙方代表:{{乙方代表}} 合同金额:{{合同金额}} 元 签订日期:{{签订日期}} 备注:{{备注说明(可选)}} ``` ### 2. 系统自动提取 提取结果: ```json [ "甲方名称", "甲方地址", "甲方代表", "乙方名称", "乙方地址", "乙方代表", "合同金额", "签订日期", "备注说明(可选)" ] ``` ### 3. 自动生成表单配置 ```json { "fields": [ { "key": "甲方名称", "label": "甲方名称", "type": "text", "required": true, "group": "甲方信息" }, { "key": "甲方地址", "label": "甲方地址", "type": "textarea", "required": true, "group": "甲方信息" }, { "key": "合同金额", "label": "合同金额", "type": "number", "required": true, "group": "合同条款" }, { "key": "签订日期", "label": "签订日期", "type": "date", "required": true, "group": "日期信息" }, { "key": "备注说明(可选)", "label": "备注说明(可选)", "type": "textarea", "required": false, "group": "基本信息" } ] } ``` ### 4. 渲染表单 表单自动分组显示: ``` 【甲方信息】 甲方名称: [_________] * 甲方地址: [_________] * 甲方代表: [_________] * 【乙方信息】 乙方名称: [_________] * 乙方地址: [_________] * 乙方代表: [_________] * 【合同条款】 合同金额: [_________] 元 * 【日期信息】 签订日期: [日期选择器] * 【基本信息】 备注说明(可选): [_________] ``` --- ## 测试验证 ### 当前测试文档 文件路径:`public/testWork/买卖合同 (1).docx` ### 测试步骤 1. **启动开发服务器** ```bash npm run dev ``` 2. **创建测试草稿** - 访问模板详情页 - 点击"起草合同" - 输入标题 3. **查看控制台输出** ``` [DOCX Parser] 开始读取文件: D:\remix_project\docreview\public\testWork\买卖合同 (1).docx [DOCX Parser] 文档文本长度: 1234 [DOCX Parser] 提取到的占位符: ["甲方名称", "乙方名称", "合同金额", ...] [Loader] 生成的 schema: {...} ``` 4. **验证表单渲染** - 检查右侧表单是否正确渲染 - 检查字段分组是否合理 - 检查字段类型是否正确 --- ## 优势总结 ### 1. 无需手动配置 - ✅ 自动提取占位符 - ✅ 自动推测字段类型 - ✅ 自动分组 - ✅ 节省配置时间 ### 2. 灵活性高 - ✅ 修改文档即可,无需改代码 - ✅ 支持任意数量的占位符 - ✅ 支持任意命名规则 ### 3. 用户友好 - ✅ 表单自动生成 - ✅ 智能分组展示 - ✅ 类型自动匹配 ### 4. 维护简单 - ✅ 模板即配置 - ✅ 无需同步数据库 - ✅ 减少维护成本 --- ## 扩展功能 ### 1. 支持更复杂的占位符 ``` {{甲方名称:text:required:甲方信息}} {{合同金额:number:required:合同条款}} {{备注:textarea:optional:基本信息}} ``` 可以解析占位符中的元数据: ```typescript const [key, type, required, group] = placeholder.split(':'); ``` ### 2. 支持默认值 ``` {{甲方名称|默认公司}} {{合同金额|100000}} ``` ### 3. 支持枚举选项 ``` {{付款方式|现金,转账,支票}} ``` 渲染为下拉选择框。 ### 4. 支持条件显示 ``` {{#if 需要担保人}} 担保人姓名:{{担保人姓名}} {{/if}} ``` --- ## 注意事项 ### 1. 占位符命名规范 - ✅ 使用有意义的名称 - ✅ 避免特殊字符 - ✅ 保持一致的命名风格 ### 2. 性能考虑 - ✅ 提取占位符很快(毫秒级) - ✅ 结果可以缓存 - ⚠️ 大文件(>10MB)可能较慢 ### 3. 错误处理 - ✅ 文件不存在 → 使用空 schema - ✅ 解析失败 → 回退到数据库配置 - ✅ 无占位符 → 显示提示信息 --- ## 总结 使用 docxtemplater 自动提取占位符是一个优雅的解决方案: 1. ✅ **零配置** - 模板即配置 2. ✅ **自动化** - 智能推测字段属性 3. ✅ **灵活** - 修改文档即可 4. ✅ **可靠** - 专业工具,提取准确 功能已实现,可以开始测试!🎉