# -*- coding: utf-8 -*- """Unify system name and add version record tables in S0-S4 delivery docx.""" from __future__ import annotations import re from pathlib import Path from docx import Document from docx.enum.text import WD_BREAK from docx.oxml import OxmlElement from docx.shared import Pt from docx.text.paragraph import Paragraph BASE = Path( r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档" ) OLD_NAMES = ( "Ai-DOP 智慧运营决策平台", "Ai-DOP智慧运营决策平台", ) NEW_NAME = "Ai-DOP智慧运营管理系统" TODAY = "2026-06-22" CHANGE_NOTE = "统一系统名称为 Ai-DOP智慧运营管理系统" def iter_all_paragraphs(doc: Document): for p in doc.paragraphs: yield p for table in doc.tables: for row in table.rows: for cell in row.cells: for p in cell.paragraphs: yield p def replace_in_paragraph(paragraph, old: str, new: str) -> bool: text = paragraph.text if old not in text: return False if paragraph.runs: combined = "".join(r.text for r in paragraph.runs) if old not in combined: return False new_text = combined.replace(old, new) paragraph.runs[0].text = new_text for run in paragraph.runs[1:]: run.text = "" else: paragraph.add_run(text.replace(old, new)) return True def replace_system_name(doc: Document) -> int: changed = 0 for old in OLD_NAMES: for p in iter_all_paragraphs(doc): if replace_in_paragraph(p, old, NEW_NAME): changed += 1 return changed def parse_metadata(doc: Document) -> dict[str, str]: meta = {"author": "智造易项目组", "created": "", "updated": "", "version": ""} for p in doc.paragraphs[:25]: t = p.text.strip() if t.startswith("文档作者:"): meta["author"] = t.split(":", 1)[1].strip() elif t.startswith("创建日期:"): meta["created"] = t.split(":", 1)[1].strip() elif t.startswith("更新日期:"): meta["updated"] = t.split(":", 1)[1].strip() elif t.startswith("当前版本:"): meta["version"] = t.split(":", 1)[1].strip() elif t.startswith("版本:"): m = re.search(r"版本:\s*(\S+)", t) if m: meta["version"] = m.group(1) m = re.search(r"日期:\s*([^作者]+?)(?:\s+作者:|$)", t) if m and not meta["updated"]: meta["updated"] = m.group(1).strip() m = re.search(r"作者:\s*(.+)$", t) if m: meta["author"] = m.group(1).strip() if doc.tables: for row in doc.tables[0].rows: cells = [c.text.strip() for c in row.cells] if len(cells) >= 2 and cells[0] == "版本" and cells[1].startswith("V"): meta["version"] = meta["version"] or cells[1] if len(cells) >= 2 and cells[0] == "编制单位": meta["author"] = meta["author"] or cells[1] if not meta["updated"]: meta["updated"] = meta["created"] or TODAY if not meta["created"]: meta["created"] = meta["updated"] if not meta["version"]: meta["version"] = "V1.0" return meta def find_toc_paragraph(doc: Document) -> Paragraph | None: for p in doc.paragraphs: if p.text.strip() == "目录": return p return None def has_system_name(doc: Document) -> bool: for p in doc.paragraphs[:20]: if NEW_NAME in p.text or any(old in p.text for old in OLD_NAMES): return True return False def insert_system_name_line(doc: Document) -> bool: if has_system_name(doc): return False anchor = None for p in doc.paragraphs[:20]: if p.text.strip().startswith("文档作者:"): anchor = p break if anchor is None: return False el = OxmlElement("w:p") anchor._element.addprevious(el) para = Paragraph(el, anchor._parent) para.add_run(NEW_NAME) return True def add_para_before(ref: Paragraph, text: str = "", *, style: str | None = None, page_break: bool = False) -> Paragraph: el = OxmlElement("w:p") ref._element.addprevious(el) para = Paragraph(el, ref._parent) if page_break: para.add_run().add_break(WD_BREAK.PAGE) if text: run = para.add_run(text) if style == "Heading 1": run.bold = True if style: para.style = style return para def fill_version_table(table, rows: list[tuple[str, str, str, str]]) -> None: for ri, row_data in enumerate(rows): for ci, val in enumerate(row_data): cell = table.rows[ri].cells[ci] cell.text = "" run = cell.paragraphs[0].add_run(val) run.font.size = Pt(10.5) run.bold = ri == 0 def version_rows(meta: dict[str, str]) -> list[tuple[str, str, str, str]]: header = ("版本", "日期", "修订人", "修订说明") current = (meta["version"], TODAY, meta["author"], CHANGE_NOTE) initial_ver = "V0.1" if meta["version"] in {"V1.0", "V2.0"}: initial_ver = "V1.0" if meta["version"] != "V2.0" else "V1.0" initial = (initial_ver, meta["created"], meta["author"], "初稿") if current[0] == initial[0] and current[1] == initial[1]: return [header, current] return [header, current, initial] def find_version_record_table(doc: Document): for i, p in enumerate(doc.paragraphs): if p.text.strip() != "版本记录": continue body = doc.element.body children = list(body) idx = children.index(p._element) for el in children[idx + 1 : idx + 4]: if el.tag.endswith("tbl"): for table in doc.tables: if table._tbl is el: return table return None def upsert_version_record(doc: Document, meta: dict[str, str]) -> str: rows = version_rows(meta) existing = find_version_record_table(doc) if existing is not None: current = rows[1] version, date, author, note = current for table_row in existing.rows[1:]: cells = [c.text.strip() for c in table_row.cells] if cells and cells[0] == version and note in "".join(cells): return "version-table-exists" new_row = existing.add_row() for ci, val in enumerate(current): cell = new_row.cells[ci] cell.text = "" run = cell.paragraphs[0].add_run(val) run.font.size = Pt(10.5) return "version-table-updated" toc = find_toc_paragraph(doc) if toc is None: return "no-toc" add_para_before(toc, page_break=True) add_para_before(toc, "版本记录", style="Heading 1") table = doc.add_table(rows=len(rows), cols=4) tbl_el = table._tbl doc.element.body.remove(tbl_el) toc._element.addprevious(tbl_el) fill_version_table(table, rows) return "version-table-added" def process_file(path: Path) -> dict[str, int | str]: doc = Document(path) meta = parse_metadata(doc) name_changes = replace_system_name(doc) inserted_name = insert_system_name_line(doc) version_action = upsert_version_record(doc, meta) doc.save(path) return { "name_changes": name_changes, "inserted_name_line": int(inserted_name), "version_action": version_action, } def main() -> None: for mod in ["S0", "S1", "S2", "S3", "S4"]: mod_dir = BASE / mod if not mod_dir.is_dir(): print(f"{mod}: missing directory") continue for path in sorted(mod_dir.glob("*.docx")): result = process_file(path) print( f"{mod}/{path.name}: " f"name={result['name_changes']} " f"insert_line={result['inserted_name_line']} " f"{result['version_action']}" ) if __name__ == "__main__": main()