_batch_doc_cover_update.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. # -*- coding: utf-8 -*-
  2. """Unify system name and add version record tables in S0-S4 delivery docx."""
  3. from __future__ import annotations
  4. import re
  5. from pathlib import Path
  6. from docx import Document
  7. from docx.enum.text import WD_BREAK
  8. from docx.oxml import OxmlElement
  9. from docx.shared import Pt
  10. from docx.text.paragraph import Paragraph
  11. BASE = Path(
  12. r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档"
  13. )
  14. OLD_NAMES = (
  15. "Ai-DOP 智慧运营决策平台",
  16. "Ai-DOP智慧运营决策平台",
  17. )
  18. NEW_NAME = "Ai-DOP智慧运营管理系统"
  19. TODAY = "2026-06-22"
  20. CHANGE_NOTE = "统一系统名称为 Ai-DOP智慧运营管理系统"
  21. def iter_all_paragraphs(doc: Document):
  22. for p in doc.paragraphs:
  23. yield p
  24. for table in doc.tables:
  25. for row in table.rows:
  26. for cell in row.cells:
  27. for p in cell.paragraphs:
  28. yield p
  29. def replace_in_paragraph(paragraph, old: str, new: str) -> bool:
  30. text = paragraph.text
  31. if old not in text:
  32. return False
  33. if paragraph.runs:
  34. combined = "".join(r.text for r in paragraph.runs)
  35. if old not in combined:
  36. return False
  37. new_text = combined.replace(old, new)
  38. paragraph.runs[0].text = new_text
  39. for run in paragraph.runs[1:]:
  40. run.text = ""
  41. else:
  42. paragraph.add_run(text.replace(old, new))
  43. return True
  44. def replace_system_name(doc: Document) -> int:
  45. changed = 0
  46. for old in OLD_NAMES:
  47. for p in iter_all_paragraphs(doc):
  48. if replace_in_paragraph(p, old, NEW_NAME):
  49. changed += 1
  50. return changed
  51. def parse_metadata(doc: Document) -> dict[str, str]:
  52. meta = {"author": "智造易项目组", "created": "", "updated": "", "version": ""}
  53. for p in doc.paragraphs[:25]:
  54. t = p.text.strip()
  55. if t.startswith("文档作者:"):
  56. meta["author"] = t.split(":", 1)[1].strip()
  57. elif t.startswith("创建日期:"):
  58. meta["created"] = t.split(":", 1)[1].strip()
  59. elif t.startswith("更新日期:"):
  60. meta["updated"] = t.split(":", 1)[1].strip()
  61. elif t.startswith("当前版本:"):
  62. meta["version"] = t.split(":", 1)[1].strip()
  63. elif t.startswith("版本:"):
  64. m = re.search(r"版本:\s*(\S+)", t)
  65. if m:
  66. meta["version"] = m.group(1)
  67. m = re.search(r"日期:\s*([^作者]+?)(?:\s+作者:|$)", t)
  68. if m and not meta["updated"]:
  69. meta["updated"] = m.group(1).strip()
  70. m = re.search(r"作者:\s*(.+)$", t)
  71. if m:
  72. meta["author"] = m.group(1).strip()
  73. if doc.tables:
  74. for row in doc.tables[0].rows:
  75. cells = [c.text.strip() for c in row.cells]
  76. if len(cells) >= 2 and cells[0] == "版本" and cells[1].startswith("V"):
  77. meta["version"] = meta["version"] or cells[1]
  78. if len(cells) >= 2 and cells[0] == "编制单位":
  79. meta["author"] = meta["author"] or cells[1]
  80. if not meta["updated"]:
  81. meta["updated"] = meta["created"] or TODAY
  82. if not meta["created"]:
  83. meta["created"] = meta["updated"]
  84. if not meta["version"]:
  85. meta["version"] = "V1.0"
  86. return meta
  87. def find_toc_paragraph(doc: Document) -> Paragraph | None:
  88. for p in doc.paragraphs:
  89. if p.text.strip() == "目录":
  90. return p
  91. return None
  92. def has_system_name(doc: Document) -> bool:
  93. for p in doc.paragraphs[:20]:
  94. if NEW_NAME in p.text or any(old in p.text for old in OLD_NAMES):
  95. return True
  96. return False
  97. def insert_system_name_line(doc: Document) -> bool:
  98. if has_system_name(doc):
  99. return False
  100. anchor = None
  101. for p in doc.paragraphs[:20]:
  102. if p.text.strip().startswith("文档作者:"):
  103. anchor = p
  104. break
  105. if anchor is None:
  106. return False
  107. el = OxmlElement("w:p")
  108. anchor._element.addprevious(el)
  109. para = Paragraph(el, anchor._parent)
  110. para.add_run(NEW_NAME)
  111. return True
  112. def add_para_before(ref: Paragraph, text: str = "", *, style: str | None = None, page_break: bool = False) -> Paragraph:
  113. el = OxmlElement("w:p")
  114. ref._element.addprevious(el)
  115. para = Paragraph(el, ref._parent)
  116. if page_break:
  117. para.add_run().add_break(WD_BREAK.PAGE)
  118. if text:
  119. run = para.add_run(text)
  120. if style == "Heading 1":
  121. run.bold = True
  122. if style:
  123. para.style = style
  124. return para
  125. def fill_version_table(table, rows: list[tuple[str, str, str, str]]) -> None:
  126. for ri, row_data in enumerate(rows):
  127. for ci, val in enumerate(row_data):
  128. cell = table.rows[ri].cells[ci]
  129. cell.text = ""
  130. run = cell.paragraphs[0].add_run(val)
  131. run.font.size = Pt(10.5)
  132. run.bold = ri == 0
  133. def version_rows(meta: dict[str, str]) -> list[tuple[str, str, str, str]]:
  134. header = ("版本", "日期", "修订人", "修订说明")
  135. current = (meta["version"], TODAY, meta["author"], CHANGE_NOTE)
  136. initial_ver = "V0.1"
  137. if meta["version"] in {"V1.0", "V2.0"}:
  138. initial_ver = "V1.0" if meta["version"] != "V2.0" else "V1.0"
  139. initial = (initial_ver, meta["created"], meta["author"], "初稿")
  140. if current[0] == initial[0] and current[1] == initial[1]:
  141. return [header, current]
  142. return [header, current, initial]
  143. def find_version_record_table(doc: Document):
  144. for i, p in enumerate(doc.paragraphs):
  145. if p.text.strip() != "版本记录":
  146. continue
  147. body = doc.element.body
  148. children = list(body)
  149. idx = children.index(p._element)
  150. for el in children[idx + 1 : idx + 4]:
  151. if el.tag.endswith("tbl"):
  152. for table in doc.tables:
  153. if table._tbl is el:
  154. return table
  155. return None
  156. def upsert_version_record(doc: Document, meta: dict[str, str]) -> str:
  157. rows = version_rows(meta)
  158. existing = find_version_record_table(doc)
  159. if existing is not None:
  160. current = rows[1]
  161. version, date, author, note = current
  162. for table_row in existing.rows[1:]:
  163. cells = [c.text.strip() for c in table_row.cells]
  164. if cells and cells[0] == version and note in "".join(cells):
  165. return "version-table-exists"
  166. new_row = existing.add_row()
  167. for ci, val in enumerate(current):
  168. cell = new_row.cells[ci]
  169. cell.text = ""
  170. run = cell.paragraphs[0].add_run(val)
  171. run.font.size = Pt(10.5)
  172. return "version-table-updated"
  173. toc = find_toc_paragraph(doc)
  174. if toc is None:
  175. return "no-toc"
  176. add_para_before(toc, page_break=True)
  177. add_para_before(toc, "版本记录", style="Heading 1")
  178. table = doc.add_table(rows=len(rows), cols=4)
  179. tbl_el = table._tbl
  180. doc.element.body.remove(tbl_el)
  181. toc._element.addprevious(tbl_el)
  182. fill_version_table(table, rows)
  183. return "version-table-added"
  184. def process_file(path: Path) -> dict[str, int | str]:
  185. doc = Document(path)
  186. meta = parse_metadata(doc)
  187. name_changes = replace_system_name(doc)
  188. inserted_name = insert_system_name_line(doc)
  189. version_action = upsert_version_record(doc, meta)
  190. doc.save(path)
  191. return {
  192. "name_changes": name_changes,
  193. "inserted_name_line": int(inserted_name),
  194. "version_action": version_action,
  195. }
  196. def main() -> None:
  197. for mod in ["S0", "S1", "S2", "S3", "S4"]:
  198. mod_dir = BASE / mod
  199. if not mod_dir.is_dir():
  200. print(f"{mod}: missing directory")
  201. continue
  202. for path in sorted(mod_dir.glob("*.docx")):
  203. result = process_file(path)
  204. print(
  205. f"{mod}/{path.name}: "
  206. f"name={result['name_changes']} "
  207. f"insert_line={result['inserted_name_line']} "
  208. f"{result['version_action']}"
  209. )
  210. if __name__ == "__main__":
  211. main()