#!/usr/bin/env python3 """将操作说明 Markdown 转为 docx(供交付文档使用)。""" import re import sys from pathlib import Path from docx import Document from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.shared import Pt def set_doc_font(doc): style = doc.styles["Normal"] style.font.name = "宋体" style.font.size = Pt(11) style._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") def add_rich_text(paragraph, text): pattern = re.compile(r"(\*\*[^*]+\*\*)") parts = pattern.split(text) for part in parts: if not part: continue if part.startswith("**") and part.endswith("**"): run = paragraph.add_run(part[2:-2]) run.bold = True else: paragraph.add_run(part) def parse_table_row(line): line = line.strip() if not line.startswith("|"): return None cells = [c.strip() for c in line.strip("|").split("|")] return cells def is_separator_row(cells): return all(re.fullmatch(r":?-+:?", c.replace(" ", "")) for c in cells if c != "") def md_to_docx(md_path, docx_path): lines = Path(md_path).read_text(encoding="utf-8").splitlines() doc = Document() set_doc_font(doc) i = 0 while i < len(lines): line = lines[i] stripped = line.strip() if not stripped: i += 1 continue if stripped == "---": doc.add_paragraph("") i += 1 continue if stripped.startswith("#"): level = len(stripped) - len(stripped.lstrip("#")) title = stripped[level:].strip() style = "Title" if level == 1 else f"Heading {min(level, 4)}" p = doc.add_paragraph(style=style) add_rich_text(p, title) i += 1 continue if stripped.startswith("```"): lang = stripped[3:].strip() i += 1 code_lines = [] while i < len(lines) and not lines[i].strip().startswith("```"): code_lines.append(lines[i]) i += 1 if i < len(lines): i += 1 p = doc.add_paragraph() run = p.add_run("\n".join(code_lines)) run.font.name = "Consolas" run._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") run.font.size = Pt(9) doc.add_paragraph("") continue if stripped.startswith("|"): table_lines = [] while i < len(lines) and lines[i].strip().startswith("|"): row = parse_table_row(lines[i]) if row is not None: table_lines.append(row) i += 1 data_rows = [] for row in table_lines: if is_separator_row(row): continue data_rows.append(row) if not data_rows: continue cols = max(len(r) for r in data_rows) table = doc.add_table(rows=len(data_rows), cols=cols) table.style = "Table Grid" for r_idx, row in enumerate(data_rows): for c_idx in range(cols): cell_text = row[c_idx] if c_idx < len(row) else "" cell = table.rows[r_idx].cells[c_idx] cell.text = "" p = cell.paragraphs[0] add_rich_text(p, cell_text) doc.add_paragraph("") continue if stripped.startswith(">"): p = doc.add_paragraph(style="Intense Quote") add_rich_text(p, stripped.lstrip("> ").strip()) i += 1 continue if stripped.startswith("- "): p = doc.add_paragraph(style="List Bullet") add_rich_text(p, stripped[2:].strip()) i += 1 continue p = doc.add_paragraph() add_rich_text(p, stripped) i += 1 for section in doc.sections: section.top_margin = Pt(72) section.bottom_margin = Pt(72) section.left_margin = Pt(90) section.right_margin = Pt(90) doc.save(docx_path) print(f"Generated: {docx_path}") def main(): base = Path(__file__).parent pairs = [ ("OPERATION_MANUAL_HEALTH.md", "OPERATION_MANUAL_HEALTH.docx"), ("OPERATION_MANUAL_ANOMALY.md", "OPERATION_MANUAL_ANOMALY.docx"), ("OPERATION_MANUAL_ANOMALY.md", "异常检测及风资源-操作说明.docx"), ("OPERATION_MANUAL_LEDGER.md", "OPERATION_MANUAL_LEDGER.docx"), ("OPERATION_MANUAL_LEDGER.md", "台账管理-操作说明.docx"), ("DEPLOY_FRONTEND.md", "DEPLOY_FRONTEND.docx"), ("DESIGN_SPEC_HEALTH.md", "健康评估系统设计说明.docx"), ("DESIGN_SPEC_ANOMALY_WRW.md", "异常检测及风资源尾流系统设计说明.docx"), ] for md_name, docx_name in pairs: md_to_docx(base / md_name, base / docx_name) if __name__ == "__main__": main()