| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- # -*- 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()
|