_extend_delivery_front_matter.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # -*- coding: utf-8 -*-
  2. """Apply S0 blueprint front matter to all S0-S4 delivery docx (recursive)."""
  3. from __future__ import annotations
  4. import importlib.util
  5. import shutil
  6. import tempfile
  7. from pathlib import Path
  8. from docx import Document
  9. from docx.oxml.ns import qn
  10. from docx.text.paragraph import Paragraph
  11. BASE = Path(
  12. r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档"
  13. )
  14. MODULES = ["S0", "S1", "S2", "S3", "S4"]
  15. def load_module(name: str, filename: str):
  16. path = Path(__file__).with_name(filename)
  17. spec = importlib.util.spec_from_file_location(name, path)
  18. mod = importlib.util.module_from_spec(spec)
  19. spec.loader.exec_module(mod)
  20. return mod
  21. def iter_delivery_docx() -> list[Path]:
  22. files: list[Path] = []
  23. for module in MODULES:
  24. mod_dir = BASE / module
  25. if not mod_dir.is_dir():
  26. continue
  27. for path in sorted(mod_dir.rglob("*.docx")):
  28. if path.name.startswith("~$"):
  29. continue
  30. files.append(path)
  31. return files
  32. def has_toc(doc: Document) -> bool:
  33. return any(p.text.strip() == "目录" for p in doc.paragraphs)
  34. def has_page_break(p: Paragraph) -> bool:
  35. return bool(p._element.findall(".//" + qn("w:br")))
  36. def front_matter_status(doc: Document) -> str:
  37. if not has_toc(doc):
  38. return "skip-no-toc"
  39. has_control = any(p.text.strip() == "文档控制" for p in doc.paragraphs)
  40. has_change = any(p.text.strip() == "更改记录" for p in doc.paragraphs)
  41. has_version = any(p.text.strip() == "版本记录" for p in doc.paragraphs)
  42. has_old_meta = any(
  43. p.text.strip().startswith(prefix)
  44. for p in doc.paragraphs[:20]
  45. for prefix in ("文档作者:", "创建日期:", "当前版本:")
  46. )
  47. has_cover_table = any(
  48. table.rows
  49. and table.rows[0].cells[0].text.strip() == "文档编号"
  50. and len(table.rows) == 5
  51. for table in doc.tables
  52. )
  53. version_break = False
  54. toc_break = False
  55. for p in doc.paragraphs:
  56. t = p.text.strip()
  57. if t == "版本记录":
  58. version_break = has_page_break(p)
  59. if t == "目录":
  60. toc_break = has_page_break(p)
  61. if has_control or has_change or has_old_meta or not has_cover_table:
  62. return "rebuild"
  63. if not has_version:
  64. return "rebuild"
  65. if not version_break or not toc_break:
  66. return "adjust"
  67. return "ok"
  68. def infer_module_kind(path: Path) -> tuple[str, str]:
  69. rel = path.relative_to(BASE)
  70. module = rel.parts[0]
  71. kind = "blue" if "蓝图" in path.name else "req"
  72. return module, kind
  73. def save_with_fallback(doc: Document, path: Path) -> None:
  74. try:
  75. doc.save(path)
  76. except PermissionError:
  77. tmp = Path(tempfile.gettempdir()) / f"docfix_{path.name}"
  78. doc.save(tmp)
  79. try:
  80. shutil.copy2(tmp, path)
  81. tmp.unlink(missing_ok=True)
  82. except PermissionError:
  83. print(f" !! {path.name} 被占用,已写入: {tmp}")
  84. def process_file(path: Path) -> None:
  85. rel = path.relative_to(BASE)
  86. doc = Document(path)
  87. status = front_matter_status(doc)
  88. if status == "skip-no-toc":
  89. print(f"{rel}: skip (无目录)")
  90. return
  91. if status == "ok":
  92. print(f"{rel}: ok")
  93. return
  94. module, kind = infer_module_kind(path)
  95. adjust = load_module("adjust", "_adjust_front_matter.py")
  96. if status == "rebuild":
  97. unify = load_module("unify", "_unify_doc_format.py")
  98. restore = load_module("restore", "_restore_cover_format.py")
  99. versions = load_module("versions", "_unify_version_records.py")
  100. unify.unify_document(path, module, kind)
  101. restore.restore_file(path)
  102. versions.process_file(path)
  103. adjust.adjust_file(path)
  104. print(f"{rel}: rebuilt + formatted")
  105. return
  106. adjust.adjust_file(path)
  107. print(f"{rel}: adjusted page breaks")
  108. def main() -> None:
  109. for path in iter_delivery_docx():
  110. process_file(path)
  111. if __name__ == "__main__":
  112. main()