md_to_docx.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # -*- coding: utf-8 -*-
  2. """Convert 9.19 markdown manuals to Word."""
  3. import os, re
  4. from docx import Document
  5. from docx.shared import Pt, Cm, RGBColor
  6. from docx.oxml.ns import qn
  7. from docx.enum.text import WD_ALIGN_PARAGRAPH
  8. from docx.enum.table import WD_TABLE_ALIGNMENT
  9. SRC = r"D:\产线\大洋\大洋操作手册汇总9.19"
  10. FONT = "微软雅黑"
  11. def set_run_font(run, size=11, bold=False, color=None):
  12. run.bold = bold
  13. run.font.size = Pt(size)
  14. run.font.name = FONT
  15. r = run._element
  16. rPr = r.get_or_add_rPr()
  17. rFonts = rPr.find(qn("w:rFonts"))
  18. if rFonts is None:
  19. from lxml import etree
  20. rFonts = rPr.makeelement(qn("w:rFonts"), {})
  21. rPr.append(rFonts)
  22. rFonts.set(qn("w:eastAsia"), FONT)
  23. rFonts.set(qn("w:ascii"), FONT)
  24. rFonts.set(qn("w:hAnsi"), FONT)
  25. if color:
  26. run.font.color.rgb = RGBColor(*color)
  27. def add_runs(p, text, size=11, bold=False):
  28. # split **bold**
  29. parts = re.split(r"(\*\*[^*]+\*\*)", text)
  30. for part in parts:
  31. if part.startswith("**") and part.endswith("**"):
  32. run = p.add_run(part[2:-2])
  33. set_run_font(run, size=size, bold=True)
  34. else:
  35. run = p.add_run(part)
  36. set_run_font(run, size=size, bold=bold)
  37. def parse_table(lines, i):
  38. rows = []
  39. while i < len(lines) and lines[i].startswith("|"):
  40. raw = [c.strip() for c in lines[i].strip().strip("|").split("|")]
  41. if not re.match(r"^:?-+:?$", raw[0].replace(" ", "")):
  42. rows.append(raw)
  43. i += 1
  44. return rows, i
  45. def convert(md_path, docx_path):
  46. text = open(md_path, encoding="utf-8").read()
  47. lines = text.splitlines()
  48. doc = Document()
  49. section = doc.sections[0]
  50. section.top_margin = Cm(2)
  51. section.bottom_margin = Cm(2)
  52. section.left_margin = Cm(2.2)
  53. section.right_margin = Cm(2.2)
  54. style = doc.styles["Normal"]
  55. style.font.name = FONT
  56. style.font.size = Pt(11)
  57. style.element.rPr.rFonts.set(qn("w:eastAsia"), FONT)
  58. i = 0
  59. while i < len(lines):
  60. line = lines[i].rstrip()
  61. if not line:
  62. i += 1
  63. continue
  64. if line.startswith("# "):
  65. p = doc.add_heading("", level=0)
  66. p.clear()
  67. run = p.add_run(line[2:].strip())
  68. set_run_font(run, size=18, bold=True, color=(0x1F, 0x4E, 0x79))
  69. i += 1
  70. continue
  71. if line.startswith("## "):
  72. p = doc.add_heading("", level=1)
  73. p.clear()
  74. run = p.add_run(line[3:].strip())
  75. set_run_font(run, size=16, bold=True, color=(0x2E, 0x75, 0xB6))
  76. i += 1
  77. continue
  78. if line.startswith("### "):
  79. p = doc.add_heading("", level=2)
  80. p.clear()
  81. run = p.add_run(line[4:].strip())
  82. set_run_font(run, size=14, bold=True, color=(0x2E, 0x75, 0xB6))
  83. i += 1
  84. continue
  85. if line.startswith("#### "):
  86. p = doc.add_heading("", level=3)
  87. p.clear()
  88. run = p.add_run(line[5:].strip())
  89. set_run_font(run, size=12, bold=True)
  90. i += 1
  91. continue
  92. if line.startswith("---"):
  93. i += 1
  94. continue
  95. if line.startswith("|"):
  96. rows, i = parse_table(lines, i)
  97. if rows:
  98. cols = max(len(r) for r in rows)
  99. table = doc.add_table(rows=len(rows), cols=cols)
  100. table.style = "Table Grid"
  101. table.alignment = WD_TABLE_ALIGNMENT.CENTER
  102. for r_idx, row in enumerate(rows):
  103. for c_idx in range(cols):
  104. cell = table.cell(r_idx, c_idx)
  105. cell.text = ""
  106. p = cell.paragraphs[0]
  107. val = row[c_idx] if c_idx < len(row) else ""
  108. add_runs(p, val, size=10, bold=(r_idx == 0))
  109. continue
  110. if line.startswith("- "):
  111. p = doc.add_paragraph(style="List Bullet")
  112. p.clear()
  113. add_runs(p, line[2:].strip())
  114. i += 1
  115. continue
  116. if re.match(r"^\d+\.\s", line):
  117. p = doc.add_paragraph(style="List Number")
  118. p.clear()
  119. add_runs(p, re.sub(r"^\d+\.\s", "", line))
  120. i += 1
  121. continue
  122. p = doc.add_paragraph()
  123. add_runs(p, line)
  124. i += 1
  125. doc.save(docx_path)
  126. print("saved", os.path.basename(docx_path))
  127. def main():
  128. files = [f for f in os.listdir(SRC) if f.endswith(".md")]
  129. files.sort()
  130. for f in files:
  131. md = os.path.join(SRC, f)
  132. docx = os.path.join(SRC, os.path.splitext(f)[0] + ".docx")
  133. convert(md, docx)
  134. if __name__ == "__main__":
  135. main()