_restore_cover_format.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. # -*- coding: utf-8 -*-
  2. """Restore delivery docx cover / document-control formatting (S0 blueprint style)."""
  3. from __future__ import annotations
  4. from pathlib import Path
  5. from docx import Document
  6. from docx.enum.text import WD_ALIGN_PARAGRAPH
  7. from docx.oxml import OxmlElement
  8. from docx.oxml.ns import qn
  9. from docx.shared import Pt, RGBColor
  10. from docx.table import Table
  11. from docx.text.paragraph import Paragraph
  12. BASE = Path(
  13. r"C:\Users\skygu\OneDrive\Projects\AIDOP\项目\项目管理\产互联项目管理\交付文档"
  14. )
  15. FONT = "微软雅黑"
  16. BLUE = RGBColor(0x1F, 0x4E, 0x79)
  17. HEADER_FILL = "1F3864"
  18. TABLE_STYLE = "aff1" # Table Grid in this template
  19. def body_blocks(doc: Document):
  20. for child in doc.element.body:
  21. if child.tag.endswith("p"):
  22. yield "p", Paragraph(child, doc)
  23. elif child.tag.endswith("tbl"):
  24. yield "t", Table(child, doc)
  25. def find_toc_element(doc: Document):
  26. for kind, obj in body_blocks(doc):
  27. if kind == "p" and obj.text.strip() == "目录":
  28. return obj._element
  29. return None
  30. def set_run_font(run, *, size: Pt | None = None, bold: bool | None = None, color: RGBColor | None = None):
  31. run.font.name = FONT
  32. r_pr = run._element.get_or_add_rPr()
  33. r_fonts = r_pr.rFonts
  34. if r_fonts is None:
  35. r_fonts = OxmlElement("w:rFonts")
  36. r_pr.insert(0, r_fonts)
  37. r_fonts.set(qn("w:ascii"), FONT)
  38. r_fonts.set(qn("w:hAnsi"), FONT)
  39. r_fonts.set(qn("w:eastAsia"), FONT)
  40. if size is not None:
  41. run.font.size = size
  42. if bold is not None:
  43. run.bold = bold
  44. if color is not None:
  45. run.font.color.rgb = color
  46. def format_title_paragraph(p: Paragraph, text: str, *, size: Pt, bold: bool = True, color: RGBColor = BLUE):
  47. p.text = text
  48. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  49. for run in p.runs:
  50. set_run_font(run, size=size, bold=bold, color=color)
  51. def format_system_paragraph(p: Paragraph, text: str):
  52. p.text = text
  53. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  54. for run in p.runs:
  55. set_run_font(run, size=Pt(13), bold=False, color=RGBColor(0, 0, 0))
  56. def apply_table_style(table: Table) -> None:
  57. tbl = table._tbl
  58. tbl_pr = tbl.tblPr
  59. if tbl_pr is None:
  60. tbl_pr = OxmlElement("w:tblPr")
  61. tbl.insert(0, tbl_pr)
  62. style = tbl_pr.find(qn("w:tblStyle"))
  63. if style is None:
  64. style = OxmlElement("w:tblStyle")
  65. tbl_pr.insert(0, style)
  66. style.set(qn("w:val"), TABLE_STYLE)
  67. jc = tbl_pr.find(qn("w:jc"))
  68. if jc is None:
  69. jc = OxmlElement("w:jc")
  70. jc.set(qn("w:val"), "center")
  71. tbl_pr.append(jc)
  72. else:
  73. jc.set(qn("w:val"), "center")
  74. def shade_cell(cell, fill: str) -> None:
  75. tc_pr = cell._element.get_or_add_tcPr()
  76. old = tc_pr.find(qn("w:shd"))
  77. if old is not None:
  78. tc_pr.remove(old)
  79. shd = OxmlElement("w:shd")
  80. shd.set(qn("w:val"), "clear")
  81. shd.set(qn("w:color"), "auto")
  82. shd.set(qn("w:fill"), fill)
  83. tc_pr.append(shd)
  84. def set_cell(
  85. cell,
  86. text: str,
  87. *,
  88. bold: bool = False,
  89. white: bool = False,
  90. size: Pt = Pt(10.5),
  91. center: bool = False,
  92. ):
  93. cell.text = ""
  94. p = cell.paragraphs[0]
  95. if center:
  96. p.alignment = WD_ALIGN_PARAGRAPH.CENTER
  97. run = p.add_run(text)
  98. set_run_font(run, size=size, bold=bold, color=RGBColor(255, 255, 255) if white else RGBColor(0, 0, 0))
  99. def format_cover_table(table: Table) -> None:
  100. apply_table_style(table)
  101. for row in table.rows:
  102. set_cell(row.cells[0], row.cells[0].text.strip(), bold=True)
  103. set_cell(row.cells[1], row.cells[1].text.strip())
  104. def format_change_table(table: Table) -> None:
  105. apply_table_style(table)
  106. header = table.rows[0]
  107. for cell in header.cells:
  108. shade_cell(cell, HEADER_FILL)
  109. set_cell(cell, cell.text.strip(), bold=True, white=True, center=True)
  110. for row in table.rows[1:]:
  111. for cell in row.cells:
  112. set_cell(cell, cell.text.strip(), center=True)
  113. def ensure_leading_blank(doc: Document, toc_el) -> None:
  114. body = doc.element.body
  115. children = list(body)
  116. toc_pos = children.index(toc_el)
  117. if toc_pos == 0:
  118. blank = OxmlElement("w:p")
  119. body.insert(0, blank)
  120. return
  121. first = children[0]
  122. if first.tag.endswith("p"):
  123. p = Paragraph(first, doc)
  124. if p.text.strip():
  125. blank = OxmlElement("w:p")
  126. body.insert(0, blank)
  127. def insert_page_break_before_doc_control(doc: Document, toc_el) -> None:
  128. from docx.enum.text import WD_BREAK
  129. for kind, obj in body_blocks(doc):
  130. if kind == "p" and obj.text.strip() == "文档控制":
  131. if obj.runs:
  132. obj.runs[0].add_break(WD_BREAK.PAGE)
  133. else:
  134. obj.add_run().add_break(WD_BREAK.PAGE)
  135. return
  136. def restore_file(path: Path) -> None:
  137. doc = Document(path)
  138. toc_el = find_toc_element(doc)
  139. if toc_el is None:
  140. raise RuntimeError(f"未找到目录: {path.name}")
  141. front_paras: list[Paragraph] = []
  142. front_tables: list[Table] = []
  143. for kind, obj in body_blocks(doc):
  144. if obj._element is toc_el:
  145. break
  146. if kind == "p":
  147. t = obj.text.strip()
  148. if t:
  149. front_paras.append(obj)
  150. elif kind == "t":
  151. front_tables.append(obj)
  152. if len(front_paras) >= 3:
  153. format_title_paragraph(front_paras[0], front_paras[0].text.strip(), size=Pt(28))
  154. format_title_paragraph(front_paras[1], front_paras[1].text.strip(), size=Pt(22))
  155. format_system_paragraph(front_paras[2], front_paras[2].text.strip())
  156. for table in front_tables:
  157. if not table.rows:
  158. continue
  159. cols = len(table.columns)
  160. if cols == 2 and len(table.rows) == 5:
  161. format_cover_table(table)
  162. elif cols == 4 and len(table.rows) == 3:
  163. format_change_table(table)
  164. ensure_leading_blank(doc, toc_el)
  165. insert_page_break_before_doc_control(doc, toc_el)
  166. doc.save(path)
  167. def main() -> None:
  168. for module in ["S0", "S1", "S2", "S3", "S4"]:
  169. for path in sorted((BASE / module).glob("*.docx")):
  170. restore_file(path)
  171. print(f"restored {module}/{path.name}")
  172. if __name__ == "__main__":
  173. main()