"""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 '' 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