"""docx → HTML 高层入口(集成清洗 + 转码 + 美化)""" import os import re import zipfile from typing import Callable from .cleaner import clean_docx from .post import post_docx from .render import clean_html, stats as render_stats, copy_mathjax_to def _zip_name(info) -> str: """zip 条目名:带 UTF-8 标志位(0x800)直接用;否则按 cp437→GBK 还原""" if info.flag_bits // 2048 % 2: return info.filename try: return info.filename.encode('cp437').decode('gbk') except Exception: return info.filename def convert_one(docx_path: str, auto_clean: bool = True) -> dict: """单份 docx → HTML 字符串 + stats Returns: {ok, html, stats, error?} """ try: if auto_clean: docx_path = clean_docx(docx_path) raw = post_docx(docx_path) html = clean_html(raw, mathjax=True) return {'ok': True, 'html': html, 'stats': render_stats(html)} except Exception as e: return {'ok': False, 'error': str(e)[:300]} def batch_convert(path: str, out_dir: str, auto_clean: bool = True, progress_cb: Callable | None = None) -> dict: """目录或 zip → 每份 docx 转 HTML 落 out_dir Returns: {count, errors[{file, error}], files[{name, group, stats}]} """ os.makedirs(out_dir, exist_ok=True) copy_mathjax_to(out_dir) items: list[tuple[str, str]] = [] if os.path.isdir(path): for f in sorted(os.listdir(path)): if f.lower().endswith('.docx'): items.append((f, os.path.join(path, f))) else: with zipfile.ZipFile(path) as z: for i in z.infolist(): if i.is_dir() or i.filename.startswith('__MACOSX/') or not i.filename.lower().endswith('.docx'): continue parts = [p for p in _zip_name(i).split('/') if p] rel = '/'.join(parts[1:]) if len(parts) > 1 else parts[-1] # 不解压到磁盘,直接 post bytes items.append((rel, zipfile.ZipFile(path).read(i))) files, errors = [], [] for rel, src in items: try: if isinstance(src, bytes): # 写临时文件 tmp = f'/tmp/_wpc_{abs(hash(rel)) & 0xFFFFFFFF:x}.docx' with open(tmp, 'wb') as f: f.write(src) src = tmp r = convert_one(src, auto_clean=auto_clean) if r['ok']: html = r['html'] rel_html = re.sub(r'\.docx$', '', rel, flags=re.I) + '.html' out = os.path.join(out_dir, rel_html) os.makedirs(os.path.dirname(out) or out_dir, exist_ok=True) with open(out, 'w', encoding='utf-8') as f: f.write(html) files.append({ 'name': rel_html, 'group': os.path.dirname(rel_html), **r['stats'], }) else: errors.append({'file': rel, 'error': r.get('error', '?')}) except Exception as e: errors.append({'file': rel, 'error': str(e)[:200]}) if progress_cb: progress_cb(len(files) + len(errors), len(items)) return {'count': len(files), 'errors': errors, 'files': files}