# -*- coding: utf-8 -*- """Unify S0-S4 delivery docx front matter to S0 blueprint style. WARNING: Do not run blindly — rebuilds front matter and strips original formatting. Prefer surgical edits via _restore_cover_format.py after manual structure changes. """ from __future__ import annotations from pathlib import Path from docx import Document from docx.oxml import OxmlElement from docx.shared import Pt from docx.table import Table from docx.text.paragraph import Paragraph BASE = Path( r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档" ) SYSTEM_NAME = "Ai-DOP智慧运营管理系统" V01, V02 = "V0.1", "V0.2" NOTE_V01, NOTE_V02 = "初版建立", "增加版本号" DOC_META = { ("S0", "req"): { "title1": "S0 基础主数据", "title2": "业务需求描述", "doc_no": "S0-BRD-001", "author": "智造易项目组", "created": "2026-06-10", "updated": "2026-06-26", }, ("S0", "blue"): { "title1": "S0 基础主数据建模", "title2": "业务蓝图设计方案", "doc_no": "S0-BBP-001", "author": "智造易项目组", "created": "2026-06-10", "updated": "2026-06-26", }, ("S1", "req"): { "title1": "S1 产销协同模块", "title2": "业务需求描述", "doc_no": "S1-BRD-001", "author": "彭熙玉", "created": "2026-06-09", "updated": "2026-06-09", }, ("S1", "blue"): { "title1": "S1 产销协同模块", "title2": "业务蓝图设计方案", "doc_no": "S1-BBP-001", "author": "彭熙玉", "created": "2026-06-09", "updated": "2026-06-09", }, ("S2", "req"): { "title1": "S2 制造协同模块", "title2": "业务需求描述", "doc_no": "S2-BRD-001", "author": "彭熙玉", "created": "2026-06-09", "updated": "2026-06-09", }, ("S2", "blue"): { "title1": "S2 制造协同模块", "title2": "业务蓝图设计方案", "doc_no": "S2-BBP-001", "author": "彭熙玉", "created": "2026-06-09", "updated": "2026-06-09", }, ("S3", "req"): { "title1": "S3 供应协同模块", "title2": "业务需求描述", "doc_no": "S3-BRD-001", "author": "彭熙玉", "created": "2026-06-10", "updated": "2026-06-11", }, ("S3", "blue"): { "title1": "S3 供应协同模块", "title2": "业务蓝图设计方案", "doc_no": "S3-BBP-001", "author": "彭熙玉", "created": "2026-06-10", "updated": "2026-06-10", }, ("S4", "req"): { "title1": "S4 采购执行模块", "title2": "业务需求描述", "doc_no": "S4-BRD-001", "author": "彭熙玉", "created": "2026-06-11", "updated": "2026-06-11", }, ("S4", "blue"): { "title1": "S4 采购执行模块", "title2": "业务蓝图设计方案", "doc_no": "S4-BBP-001", "author": "彭熙玉", "created": "2026-06-11", "updated": "2026-06-11", }, } REMOVE_HEADINGS = {"审核", "发布", "版本记录"} REMOVE_LINE_PREFIXES = ( "文档作者:", "创建日期:", "更新日期:", "当前版本:", "文档编号:", "版本:", ) def body_blocks(doc: Document): body = doc.element.body for child in body: if child.tag.endswith("p"): yield "p", Paragraph(child, doc) elif child.tag.endswith("tbl"): yield "t", Table(child, doc) def block_text(kind: str, obj) -> str: if kind == "p": return obj.text.strip() return "" def is_change_log_table(table: Table) -> bool: if not table.rows: return False header = "".join(c.text.strip() for c in table.rows[0].cells) return "变更说明" in header and ("版本" in header or "日期" in header) def is_cover_meta_table(table: Table) -> bool: if not table.rows: return False return table.rows[0].cells[0].text.strip() == "文档编号" def is_audit_or_release_table(table: Table) -> bool: if not table.rows: return False header = [c.text.strip() for c in table.rows[0].cells] return header[:3] == ["姓名", "职位", "签字/日期"] or header[:3] == ["编号", "名称", "地点"] def is_version_record_table(table: Table) -> bool: if not table.rows: return False header = [c.text.strip() for c in table.rows[0].cells] return header[:4] == ["版本", "日期", "修订人", "修订说明"] def set_cell(cell, text: str, *, bold: bool = False) -> None: cell.text = "" run = cell.paragraphs[0].add_run(text) run.font.size = Pt(10.5) run.bold = bold def build_cover_table(doc: Document, meta: dict) -> Table: rows = [ ("文档编号", meta["doc_no"]), ("版本", V02), ("密级", "内部"), ("编制单位", meta["author"]), ("编制日期", meta["updated"]), ] table = doc.add_table(rows=len(rows), cols=2) for ri, (label, value) in enumerate(rows): set_cell(table.rows[ri].cells[0], label, bold=True) set_cell(table.rows[ri].cells[1], value) return table def build_change_table(doc: Document, meta: dict) -> Table: table = doc.add_table(rows=3, cols=4) headers = ["日期", "版本", "修订人", "变更说明"] data = [ (meta["created"], V01, meta["author"], NOTE_V01), (meta["updated"], V02, meta["author"], NOTE_V02), ] for ci, val in enumerate(headers): set_cell(table.rows[0].cells[ci], val, bold=True) for ri, row in enumerate(data, start=1): for ci, val in enumerate(row): set_cell(table.rows[ri].cells[ci], val) return table def set_paragraph_text(p: Paragraph, text: str, style: str | None = None) -> None: p.text = text if style: p.style = style def insert_paragraph_before(ref_el, doc: Document) -> Paragraph: el = OxmlElement("w:p") ref_el.addprevious(el) return Paragraph(el, doc) def move_element_before(element, ref_el) -> None: parent = element.getparent() if parent is not None: parent.remove(element) ref_el.addprevious(element) def remove_orphan_tables_after_toc(doc: Document, toc_el) -> None: body = doc.element.body for tbl_el in list(body): if not tbl_el.tag.endswith("tbl"): continue if list(body).index(tbl_el) <= list(body).index(toc_el): continue table = Table(tbl_el, doc) if is_version_record_table(table): body.remove(tbl_el) def unify_document(path: Path, module: str, kind: str) -> None: meta = DOC_META[(module, kind)] doc = Document(path) blocks = list(body_blocks(doc)) toc_idx = next( (i for i, (k, o) in enumerate(blocks) if k == "p" and block_text(k, o) == "目录"), None, ) if toc_idx is None: raise RuntimeError(f"未找到目录: {path.name}") toc_el = blocks[toc_idx][1]._element for kind_b, obj in blocks[:toc_idx]: if kind_b == "p": obj._element.getparent().remove(obj._element) elif kind_b == "t": obj._tbl.getparent().remove(obj._tbl) p1 = insert_paragraph_before(toc_el, doc) p2 = insert_paragraph_before(toc_el, doc) p3 = insert_paragraph_before(toc_el, doc) move_element_before(p1._element, toc_el) move_element_before(p2._element, toc_el) move_element_before(p3._element, toc_el) set_paragraph_text(p1, meta["title1"]) set_paragraph_text(p2, meta["title2"]) set_paragraph_text(p3, SYSTEM_NAME) insert_paragraph_before(toc_el, doc) cover = build_cover_table(doc, meta) move_element_before(cover._tbl, toc_el) insert_paragraph_before(toc_el, doc) h1 = insert_paragraph_before(toc_el, doc) set_paragraph_text(h1, "文档控制", "Heading 1") h2 = insert_paragraph_before(toc_el, doc) set_paragraph_text(h2, "更改记录", "Heading 2") insert_paragraph_before(toc_el, doc) change = build_change_table(doc, meta) move_element_before(change._tbl, toc_el) insert_paragraph_before(toc_el, doc) remove_orphan_tables_after_toc(doc, toc_el) doc.save(path) def main() -> None: for module in ["S0", "S1", "S2", "S3", "S4"]: mod_dir = BASE / module for path in sorted(mod_dir.glob("*.docx")): kind = "blue" if "蓝图" in path.name else "req" unify_document(path, module, kind) print(f"OK {module}/{path.name}") if __name__ == "__main__": main()