| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- # -*- coding: utf-8 -*-
- """Unify version records to V0.1 初版建立 + V0.2 增加版本号 in delivery docx."""
- from __future__ import annotations
- import re
- from pathlib import Path
- from docx import Document
- from docx.shared import Pt
- BASE = Path(
- r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档"
- )
- V01 = "V0.1"
- V02 = "V0.2"
- NOTE_V01 = "初版建立"
- NOTE_V02 = "增加版本号"
- def parse_metadata(doc: Document) -> dict[str, str]:
- meta = {"author": "智造易项目组", "created": "2026-06-10", "updated": "2026-06-10"}
- for p in doc.paragraphs[:25]:
- t = p.text.strip()
- if t.startswith("文档作者:"):
- meta["author"] = t.split(":", 1)[1].strip()
- if t.startswith("创建日期:"):
- meta["created"] = t.split(":", 1)[1].strip()
- meta["_has_created"] = True
- elif t.startswith("更新日期:"):
- meta["updated"] = t.split(":", 1)[1].strip()
- elif t.startswith("版本:"):
- m = re.search(r"作者:\s*(.+)$", t)
- if m:
- meta["author"] = m.group(1).strip()
- m = re.search(r"日期:\s*([^作者]+?)(?:\s+作者:|$)", t)
- if m:
- d = normalize_date(m.group(1).strip())
- meta["updated"] = d
- if not meta.get("_has_created"):
- meta["created"] = d
- 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]:
- meta["author"] = cells[1]
- if len(cells) >= 2 and cells[0] == "版本" and cells[1].startswith("V"):
- pass
- return meta
- def normalize_date(text: str) -> str:
- text = text.strip()
- m = re.match(r"(\d{4})年(\d{1,2})月(\d{1,2})日", text)
- if m:
- y, mo, d = m.groups()
- return f"{y}-{int(mo):02d}-{int(d):02d}"
- m = re.match(r"(\d{4})/(\d{1,2})/(\d{1,2})", text)
- if m:
- y, mo, d = m.groups()
- return f"{y}-{int(mo):02d}-{int(d):02d}"
- return text
- def set_cell_text(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 resize_table_rows(table, data_row_count: int) -> None:
- target = data_row_count + 1
- while len(table.rows) > target:
- table._tbl.remove(table.rows[-1]._tr)
- while len(table.rows) < target:
- table.add_row()
- def find_paragraph(doc: Document, text: str) -> int | None:
- for i, p in enumerate(doc.paragraphs):
- if p.text.strip() == text:
- return i
- return None
- def find_change_log_table(doc: Document):
- idx = find_paragraph(doc, "更改记录")
- if idx is None:
- return None
- body = list(doc.element.body)
- pos = body.index(doc.paragraphs[idx]._element)
- for el in body[pos + 1 : pos + 10]:
- if not el.tag.endswith("tbl"):
- continue
- for table in doc.tables:
- if table._tbl is not el:
- continue
- header = [c.text.strip() for c in table.rows[0].cells]
- header_text = "".join(header)
- if "变更说明" in header_text and ("版本" in header_text or "日期" in header_text):
- return table
- return None
- def table_after_heading(doc: Document, heading: str):
- idx = find_paragraph(doc, heading)
- if idx is None:
- return None
- body = list(doc.element.body)
- pos = body.index(doc.paragraphs[idx]._element)
- for el in body[pos + 1 : pos + 6]:
- if el.tag.endswith("tbl"):
- for table in doc.tables:
- if table._tbl is el:
- return table
- return None
- def standard_version_rows(meta: dict[str, str]) -> list[tuple[str, str, str, str]]:
- return [
- (V01, meta["created"], meta["author"], NOTE_V01),
- (V02, meta["updated"], meta["author"], NOTE_V02),
- ]
- def fill_standard_version_table(table, meta: dict[str, str]) -> None:
- rows = standard_version_rows(meta)
- resize_table_rows(table, len(rows))
- headers = ["版本", "日期", "修订人", "修订说明"]
- for ci, val in enumerate(headers):
- set_cell_text(table.rows[0].cells[ci], val, bold=True)
- for ri, row_data in enumerate(rows, start=1):
- for ci, val in enumerate(row_data):
- set_cell_text(table.rows[ri].cells[ci], val)
- def fill_change_log_table(table, meta: dict[str, str]) -> None:
- header_cells = [c.text.strip() for c in table.rows[0].cells]
- if "变更说明" not in header_cells and "修订说明" not in "".join(header_cells):
- return
- rows = standard_version_rows(meta)
- resize_table_rows(table, len(rows))
- # 日期 | 版本 | 修订人/姓名 | 变更说明
- name_col = "姓名" if "姓名" in header_cells else "修订人"
- mapping = {
- "日期": lambda r: r[1],
- "版本": lambda r: r[0],
- name_col: lambda r: r[2],
- "修订人": lambda r: r[2],
- "姓名": lambda r: r[2],
- "变更说明": lambda r: r[3],
- "修订说明": lambda r: r[3],
- }
- for ci, title in enumerate(header_cells):
- if title in ("版本", "日期", "修订人", "姓名", "变更说明", "修订说明"):
- set_cell_text(table.rows[0].cells[ci], title, bold=True)
- for ri, row_data in enumerate(rows, start=1):
- for ci, title in enumerate(header_cells):
- fn = mapping.get(title)
- if fn:
- set_cell_text(table.rows[ri].cells[ci], fn(row_data))
- def update_cover_version(doc: Document, meta: dict[str, str]) -> int:
- changed = 0
- for p in doc.paragraphs[:25]:
- t = p.text.strip()
- if t.startswith("当前版本:"):
- new = f"当前版本:{V02}"
- if p.text != new:
- if p.runs:
- p.runs[0].text = new
- for r in p.runs[1:]:
- r.text = ""
- else:
- p.add_run(new)
- changed += 1
- elif t.startswith("版本:"):
- new = f"版本:{V02} 日期:{format_cn_date(meta['updated'])} 作者:{meta['author']}"
- if p.text.strip() != new:
- if p.runs:
- p.runs[0].text = new
- for r in p.runs[1:]:
- r.text = ""
- else:
- p.add_run(new)
- changed += 1
- if doc.tables:
- row0 = doc.tables[0].rows
- for row in row0:
- cells = [c.text.strip() for c in row.cells]
- if len(cells) >= 2 and cells[0] == "版本" and cells[1].startswith("V"):
- if cells[1] != V02:
- set_cell_text(row.cells[1], V02)
- changed += 1
- return changed
- def format_cn_date(iso_date: str) -> str:
- m = re.match(r"(\d{4})-(\d{2})-(\d{2})", iso_date)
- if not m:
- return iso_date
- y, mo, d = m.groups()
- return f"{y}年{int(mo)}月{int(d)}日"
- def process_file(path: Path) -> None:
- doc = Document(path)
- meta = parse_metadata(doc)
- meta["updated"] = normalize_date(meta["updated"])
- meta["created"] = normalize_date(meta["created"])
- cover_changes = update_cover_version(doc, meta)
- version_table = table_after_heading(doc, "版本记录")
- version_updated = False
- if version_table is not None:
- fill_standard_version_table(version_table, meta)
- version_updated = True
- change_table = find_change_log_table(doc)
- change_updated = False
- if change_table is not None:
- fill_change_log_table(change_table, meta)
- change_updated = True
- doc.save(path)
- print(
- f"{path.name}: cover={cover_changes} "
- f"版本记录={'Y' if version_updated else 'N'} "
- f"更改记录={'Y' if change_updated else 'N'}"
- )
- def main() -> None:
- for mod in ["S0", "S1", "S2", "S3", "S4"]:
- mod_dir = BASE / mod
- for path in sorted(mod_dir.glob("*.docx")):
- process_file(path)
- if __name__ == "__main__":
- main()
|