8.6 KiB
8.6 KiB
从 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
测试步骤
-
启动开发服务器
npm run dev -
创建测试草稿
- 访问模板详情页
- 点击"起草合同"
- 输入标题
-
查看控制台输出
[DOCX Parser] 开始读取文件: D:\remix_project\docreview\public\testWork\买卖合同 (1).docx [DOCX Parser] 文档文本长度: 1234 [DOCX Parser] 提取到的占位符: ["甲方名称", "乙方名称", "合同金额", ...] [Loader] 生成的 schema: {...} -
验证表单渲染
- 检查右侧表单是否正确渲染
- 检查字段分组是否合理
- 检查字段类型是否正确
优势总结
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 自动提取占位符是一个优雅的解决方案:
- ✅ 零配置 - 模板即配置
- ✅ 自动化 - 智能推测字段属性
- ✅ 灵活 - 修改文档即可
- ✅ 可靠 - 专业工具,提取准确
功能已实现,可以开始测试!🎉