Files
leaudit-platform-frontend/docs/docxtemplater-placeholder-extraction.md
T
2025-12-05 00:09:32 +08:00

8.6 KiB
Raw Blame History

从 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

import PizZip from 'pizzip';
import fs from 'fs';

/**
 * 从 docx 文件中提取占位符
 */
export async function extractPlaceholdersFromDocx(
  filePath: string
): Promise<string[]> {
  // 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<string>();
  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)

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. 系统自动提取

提取结果:

[
  "甲方名称",
  "甲方地址",
  "甲方代表",
  "乙方名称",
  "乙方地址",
  "乙方代表",
  "合同金额",
  "签订日期",
  "备注说明(可选)"
]

3. 自动生成表单配置

{
  "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. 启动开发服务器

    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:基本信息}}

可以解析占位符中的元数据:

const [key, type, required, group] = placeholder.split(':');

2. 支持默认值

{{甲方名称|默认公司}}
{{合同金额|100000}}

3. 支持枚举选项

{{付款方式|现金,转账,支票}}

渲染为下拉选择框。

4. 支持条件显示

{{#if 需要担保人}}
担保人姓名:{{担保人姓名}}
{{/if}}

注意事项

1. 占位符命名规范

  • 使用有意义的名称
  • 避免特殊字符
  • 保持一致的命名风格

2. 性能考虑

  • 提取占位符很快(毫秒级)
  • 结果可以缓存
  • ⚠️ 大文件(>10MB)可能较慢

3. 错误处理

  • 文件不存在 → 使用空 schema
  • 解析失败 → 回退到数据库配置
  • 无占位符 → 显示提示信息

总结

使用 docxtemplater 自动提取占位符是一个优雅的解决方案:

  1. 零配置 - 模板即配置
  2. 自动化 - 智能推测字段属性
  3. 灵活 - 修改文档即可
  4. 可靠 - 专业工具,提取准确

功能已实现,可以开始测试!🎉