Files
book-mcp/services/wordparse/cleaner.py
Quentin 9b0910ccb1 Initial commit: book-mcp 教辅处理 MCP server + Arco Vue 管理端
后端 (book_mcp_server.py, 335 行, 15 个 MCP 工具):
- create_book / add_document / mark_html_ready / mark_json_ready / mark_failed
- update_book_status / soft_delete_book / retry_failed
- get_book / list_books / get_document / list_documents / get_document_url
- upload_to_rustfs / health
- 走 JSON-RPC + stateless_http + json_response(前端可直接调)
- launchd 守护 / 6 秒拉起(macOS)
- systemd 守护脚本(Linux/codelab)

存储:
- MongoDB (mongo:8.0 容器) - book/book_document/document_file 三表
- RustFS 对象存储(fs.code-lab.cn/scan/)- book/<id>/docs/<doc_id>/{docx,html,json}
- 幂等设计: zip_sha256 唯一 + (book_id+original_path) 唯一

前端 (Vue 3 + TS + Arco Design):
- 列表页 /book/list - 搜索 + 状态过滤 + 进度
- 详情页 /book/:id - 文档折叠 + 文件下载
- MCP 客户端 src/api/mcp.ts (JSON-RPC over fetch)

部署:
- deploy/install.sh (codelab 一键装 backend + systemd)
- deploy/install-frontend.sh (vite dev / 生产构建)
- docs/deploy.md (运维手册)

测试:
- tests/test_basic.py (sanity 7 项)
- tests/test_e2e.py (完整 e2e: docx→HTML→入库→上传→校验)
2026-10-11 18:03:57 +08:00

77 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""docx XML 清洗:去脏值(单位 / NaN / 浮点)以避开 docx4j 8.3.9 JAXB 解析失败。
参考 wordparse_server.py 的 _clean_docx_xml,独立成模块方便测试。
"""
import re
import os
import zipfile
XML_DECL = re.compile(r'<\?xml[^?]*\?>')
def _fix_text(txt: str) -> str:
"""修三类问题:
1. 带单位的数字属性:"210mm" / "25.4cm" → "0"
2. NaN / Inf / -Inf / 空字符串 → "0"
3. 浮点属性 → 整数(JAXB BigInteger 不接小数)
"""
decl = XML_DECL.search(txt)
decl_str = decl.group(0) if decl else '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
if decl:
txt = XML_DECL.sub('__XMLDECL__', txt, count=1)
# 1. 单位
txt = re.sub(r'="(-?[0-9]*\.?[0-9]+)\s*(mm|cm|pt|px|in|em|%)"', r'="0"', txt, flags=re.I)
# 2. NaN / Inf
txt = re.sub(r'="(NaN|nan|Inf|-Inf|inf|-inf)"', '="0"', txt, flags=re.I)
# 3. 空
txt = re.sub(r'=""', '="0"', txt)
# 4. 浮点 → 整数
def _num(m):
try:
f = float(m.group(1))
if f != f or f == float('inf') or f == float('-inf'):
return '="0"'
return f'="{int(f)}"'
except Exception:
return m.group(0)
txt = re.sub(r'="(-?[0-9]+\.[0-9]+(?:[eE][-+]?[0-9]+)?)"', _num, txt)
if decl:
txt = txt.replace('__XMLDECL__', decl_str, 1)
return txt
def clean_docx(path: str) -> str:
"""清洗 docx 里的 word/document.xml,幂等。失败回退原文件。"""
marker = path + '.cleaned'
if os.path.exists(marker):
return path
try:
with zipfile.ZipFile(path, 'r') as zin:
entries = []
changed = False
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename == 'word/document.xml':
raw = data.decode('utf-8', errors='replace')
new = _fix_text(raw)
if new != raw:
data = new.encode('utf-8')
changed = True
entries.append((item, data))
if not changed:
open(marker, 'w').close()
return path
tmp = path + '.tmp'
with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as zout:
for item, data in entries:
zout.writestr(item, data)
os.replace(tmp, path)
open(marker, 'w').close()
return path
except Exception:
return path