#!/usr/bin/env python3 """ 将 Markdown 文章转换为 Word 文档,嵌入图片 """ import os import re from docx import Document from docx.shared import Inches, Pt, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH # 路径配置 MARKDOWN_FILE = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/final-article.md" IMAGES_DIR = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/publishing/images" OUTPUT_DOCX = "/root/.openclaw/workspaces/yzr-yxl/projects/yu-zhi-ran/content/published/2026-04-14-上海阳台种菜一年/上海阳台种菜一年_最终版.docx" # 读取 Markdown with open(MARKDOWN_FILE, "r", encoding="utf-8") as f: lines = f.readlines() doc = Document() doc.styles['Normal'].font.name = '微软雅黑' doc.styles['Normal'].font.size = Pt(11) # 样式函数 def add_heading(text, level=1): heading = doc.add_heading(text, level=level) heading.alignment = WD_ALIGN_PARAGRAPH.LEFT return heading def add_paragraph(text, bold=False, italic=False): p = doc.add_paragraph() run = p.add_run(text) run.bold = bold run.italic = italic return p # 解析 Markdown in_code_block = False in_table = False table_data = [] for i, line in enumerate(lines): line = line.rstrip('\n') # 代码块跳过 if line.startswith('```'): in_code_block = not in_code_block continue if in_code_block: continue # 标题 if line.startswith('# '): add_heading(line[2:], level=1) continue if line.startswith('## '): add_heading(line[3:], level=2) continue if line.startswith('### '): add_heading(line[4:], level=3) continue # 表格处理(简化:将表格转为文本,图片位置用占位) if line.startswith('|'): in_table = True table_data.append(line) continue if in_table and not line.startswith('|'): in_table = False # 可以在此转换表格,为简化直接跳过 continue # 图片:![alt](path) img_match = re.match(r'!\[(.*?)\]\((images/.*?)\)', line) if img_match: alt, path = img_match.groups() img_full_path = os.path.join(os.path.dirname(MARKDOWN_FILE), path) if os.path.exists(img_full_path): try: # 插入图片,宽度 6 英寸(约 15cm) doc.add_picture(img_full_path, width=Inches(6)) # 居中 last_para = doc.paragraphs[-1] last_para.alignment = WD_ALIGN_PARAGRAPH.CENTER # 添加图片说明(可选) if alt: cap = doc.add_paragraph(alt) cap.alignment = WD_ALIGN_PARAGRAPH.CENTER cap.style = 'Caption' except Exception as e: doc.add_paragraph(f"[图片加载失败: {path}]") else: doc.add_paragraph(f"[图片缺失: {img_full_path}]") continue # 引用 if line.startswith('> '): p = doc.add_paragraph(line[2:]) p.paragraph_format.left_indent = Inches(0.5) p.italic = True continue # 列表 if re.match(r'^[-*] ', line): p = doc.add_paragraph(line[2:], style='List Bullet') continue if re.match(r'^\d+\. ', line): p = doc.add_paragraph(line[line.find('.')+2:], style='List Number') continue # 分隔线 if line.strip() == '---': doc.add_paragraph('_' * 50) continue # 普通段落 if line.strip(): # 处理行内加粗、斜体 p = doc.add_paragraph() parts = re.split(r'(\*\*[^*]+\*\*|\*[^*]+\*)', line) for part in parts: if part.startswith('**') and part.endswith('**'): run = p.add_run(part[2:-2]) run.bold = True elif part.startswith('*') and part.endswith('*'): run = p.add_run(part[1:-1]) run.italic = True else: run = p.add_run(part) else: doc.add_paragraph() # 空行 # 保存文档 doc.save(OUTPUT_DOCX) print(f"✅ Word 文档已生成: {OUTPUT_DOCX}") print(f"📄 页数: {len(doc.paragraphs)} 段落") print(f"🖼️ 图片路径: {IMAGES_DIR}")