95 lines
3.8 KiB
Markdown
95 lines
3.8 KiB
Markdown
# x2t 视觉验证与 OOXML 文本提取
|
|
|
|
## 为什么用 x2t 而不是 libreoffice
|
|
|
|
Doro 使用 OnlyOffice 查看交付物。libreoffice 和 OnlyOffice 使用不同的渲染引擎,同一 docx 在两者中可能显示不同(如页数、换行位置)。x2t 是 OnlyOffice Document Server 的内置转换器,渲染结果与 Doro 看到的完全一致。
|
|
|
|
## x2t 转换命令
|
|
|
|
```bash
|
|
# 1. 复制文件到 OnlyOffice 容器
|
|
docker cp /path/to/contract.docx nextcloud-onlyoffice-1:/tmp/check.docx
|
|
|
|
# 2. 创建转换任务 XML
|
|
cat > /tmp/convert_task.xml << 'XMLEOF'
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<TaskQueueDataConvert>
|
|
<m_sFileFrom>/tmp/check.docx</m_sFileFrom>
|
|
<m_sFileTo>/tmp/check.pdf</m_sFileTo>
|
|
<m_nFormatTo>513</m_nFormatTo>
|
|
<m_sFontDir>/usr/share/fonts</m_sFontDir>
|
|
<m_bIsNoBaseCss>false</m_bIsNoBaseCss>
|
|
</TaskQueueDataConvert>
|
|
XMLEOF
|
|
|
|
# 3. 执行转换
|
|
docker cp /tmp/convert_task.xml nextcloud-onlyoffice-1:/tmp/convert_task.xml
|
|
docker exec nextcloud-onlyoffice-1 /var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t /tmp/convert_task.xml
|
|
|
|
# 4. 取回 PDF
|
|
docker cp nextcloud-onlyoffice-1:/tmp/check.pdf /tmp/contract-review/check.pdf
|
|
|
|
# 5. 转图片查看(可选)
|
|
pdftoppm -png -r 150 /tmp/contract-review/check.pdf /tmp/contract-review/page
|
|
```
|
|
|
|
## x2t 渲染模式说明
|
|
|
|
x2t 转换时**保留 track changes 标记**(即"标记模式"),不会自动接受修订。这意味着:
|
|
- 删除的文本显示为 strikethrough
|
|
- 新增的文本显示为 underline
|
|
- `pdftotext` 提取时会同时输出旧文本和新文本(因为它无法区分视觉标记)
|
|
|
|
**判断方法**:看到 `pdftotext` 输出中同时出现新旧编号(如"5.6.包装要求"),不代表合同有重复内容——这是 track changes 标记模式的正常显示。
|
|
|
|
## 接受修订后的文本提取(OOXML 级别)
|
|
|
|
当需要提取"接受所有修订后"的纯文本时,使用以下函数:
|
|
|
|
```python
|
|
import zipfile
|
|
from lxml import etree
|
|
|
|
def qn(tag):
|
|
return '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}' + tag
|
|
|
|
def accept_revisions_text(p):
|
|
"""接受所有修订,返回段落纯文本"""
|
|
result = []
|
|
for child in p:
|
|
tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag
|
|
if tag == 'r':
|
|
# 跳过包含 delText 的 run(已删除文本)
|
|
if child.find(qn('delText')) is not None:
|
|
continue
|
|
t = child.find(qn('t'))
|
|
if t is not None:
|
|
result.append(t.text or '')
|
|
elif tag == 'del':
|
|
continue # 跳过整个删除元素
|
|
elif tag == 'ins':
|
|
# 包含插入元素中的文本
|
|
for r in child.findall(f'.//{qn("r")}'):
|
|
t = r.find(qn('t'))
|
|
if t is not None:
|
|
result.append(t.text or '')
|
|
return ''.join(result)
|
|
```
|
|
|
|
### 与标记模式文本提取的区别
|
|
|
|
| 函数 | 用途 | 输出示例 |
|
|
|------|------|----------|
|
|
| `accept_revisions_text` | 看最终效果 | "6.包装要求" |
|
|
| 含标记的提取 | 看修订过程 | "[DEL:5.][INS:6.]6.包装要求" |
|
|
| x2t pdftotext | 视觉验证 | "5.6.包装要求"(标记模式) |
|
|
|
|
**复核轮必用 `accept_revisions_text`**:验证 editor 的修改在最终文本中是否正确,不受 track changes 标记干扰。
|
|
|
|
## 常见误判
|
|
|
|
1. **pdftotext 显示"重复编号"**:如"5.6.包装要求"——这是 DEL "5." + INS "6." 的标记模式显示,不是合同错误。用 `accept_revisions_text` 验证最终文本。
|
|
|
|
2. **`get_text_from_element` 显示重复**:如果函数同时提取 INS 文本和原始文本,会导致看起来像重复。必须区分"标记模式提取"和"接受修订后提取"。
|
|
|
|
3. **x2t 转换需要容器内字体**:如果 x2t 输出字体异常,检查容器内是否有中文字体(`fc-list :lang=zh`)。 |