# -*- coding: utf-8 -*- """Apply S0 blueprint front matter to all S0-S4 delivery docx (recursive).""" from __future__ import annotations import importlib.util import shutil import tempfile from pathlib import Path from docx import Document from docx.oxml.ns import qn from docx.text.paragraph import Paragraph BASE = Path( r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档" ) MODULES = ["S0", "S1", "S2", "S3", "S4"] def load_module(name: str, filename: str): path = Path(__file__).with_name(filename) spec = importlib.util.spec_from_file_location(name, path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def iter_delivery_docx() -> list[Path]: files: list[Path] = [] for module in MODULES: mod_dir = BASE / module if not mod_dir.is_dir(): continue for path in sorted(mod_dir.rglob("*.docx")): if path.name.startswith("~$"): continue files.append(path) return files def has_toc(doc: Document) -> bool: return any(p.text.strip() == "目录" for p in doc.paragraphs) def has_page_break(p: Paragraph) -> bool: return bool(p._element.findall(".//" + qn("w:br"))) def front_matter_status(doc: Document) -> str: if not has_toc(doc): return "skip-no-toc" has_control = any(p.text.strip() == "文档控制" for p in doc.paragraphs) has_change = any(p.text.strip() == "更改记录" for p in doc.paragraphs) has_version = any(p.text.strip() == "版本记录" for p in doc.paragraphs) has_old_meta = any( p.text.strip().startswith(prefix) for p in doc.paragraphs[:20] for prefix in ("文档作者:", "创建日期:", "当前版本:") ) has_cover_table = any( table.rows and table.rows[0].cells[0].text.strip() == "文档编号" and len(table.rows) == 5 for table in doc.tables ) version_break = False toc_break = False for p in doc.paragraphs: t = p.text.strip() if t == "版本记录": version_break = has_page_break(p) if t == "目录": toc_break = has_page_break(p) if has_control or has_change or has_old_meta or not has_cover_table: return "rebuild" if not has_version: return "rebuild" if not version_break or not toc_break: return "adjust" return "ok" def infer_module_kind(path: Path) -> tuple[str, str]: rel = path.relative_to(BASE) module = rel.parts[0] kind = "blue" if "蓝图" in path.name else "req" return module, kind def save_with_fallback(doc: Document, path: Path) -> None: try: doc.save(path) except PermissionError: tmp = Path(tempfile.gettempdir()) / f"docfix_{path.name}" doc.save(tmp) try: shutil.copy2(tmp, path) tmp.unlink(missing_ok=True) except PermissionError: print(f" !! {path.name} 被占用,已写入: {tmp}") def process_file(path: Path) -> None: rel = path.relative_to(BASE) doc = Document(path) status = front_matter_status(doc) if status == "skip-no-toc": print(f"{rel}: skip (无目录)") return if status == "ok": print(f"{rel}: ok") return module, kind = infer_module_kind(path) adjust = load_module("adjust", "_adjust_front_matter.py") if status == "rebuild": unify = load_module("unify", "_unify_doc_format.py") restore = load_module("restore", "_restore_cover_format.py") versions = load_module("versions", "_unify_version_records.py") unify.unify_document(path, module, kind) restore.restore_file(path) versions.process_file(path) adjust.adjust_file(path) print(f"{rel}: rebuilt + formatted") return adjust.adjust_file(path) print(f"{rel}: adjusted page breaks") def main() -> None: for path in iter_delivery_docx(): process_file(path) if __name__ == "__main__": main()