回答

收藏

md2word

工具 工具 17 人阅读 | 0 人回复 | 2025-11-26

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Markdown转Word文档转换器
  5. 将Markdown文件转换为Word文档格式
  6. """

  7. import os
  8. import sys
  9. import re
  10. from pathlib import Path
  11. from docx import Document
  12. from docx.shared import Pt, RGBColor
  13. from docx.enum.text import WD_ALIGN_PARAGRAPH
  14. import markdown
  15. from markdown.extensions import codehilite

  16. # 说明:
  17. # 该脚本用于将 Markdown 文本内容转换为 Word(.docx)文档。
  18. # 核心流程为:读取输入文件 → 按行解析 Markdown → 在 docx 文档中生成对应结构 → 保存输出文件。
  19. # 主要依赖:python-docx 用于生成 Word 文档;正则表达式用于简易 Markdown 语法识别。


  20. class MarkdownToWordConverter:
  21.     # 职责:封装将 Markdown 内容映射到 Word 文档的全部步骤与样式。
  22.     # 使用方式:实例化后调用 convert_file 或 convert_markdown_to_word,再通过 self.document.save 输出。
  23.     def __init__(self):
  24.         # 创建空白 Word 文档对象
  25.         self.document = Document()
  26.         # 设置全局字体样式(正文字体、字号等)
  27.         self.setup_document_styles()
  28.    
  29.     def setup_document_styles(self):
  30.         """设置文档样式"""
  31.         # 设置正文字体
  32.         style = self.document.styles['Normal']
  33.         font = style.font
  34.         font.name = '仿宋'
  35.         font.size = Pt(12)
  36.    
  37.     def convert_heading(self, text, level):
  38.         """转换标题"""
  39.         # 在文档中添加指定级别的标题段落
  40.         paragraph = self.document.add_heading(level=level)
  41.         run = paragraph.add_run(text)
  42.         # 根据标题级别设置字体大小
  43.         font_sizes = {1: 18, 2: 16, 3: 14, 4: 12, 5: 11, 6: 10}
  44.         run.font.size = Pt(font_sizes.get(level, 12))
  45.         run.font.name = '黑体' if level <= 2 else '仿宋'
  46.         
  47.         return paragraph
  48.    
  49.     def convert_paragraph(self, text):
  50.         """转换段落"""
  51.         # 添加普通段落并应用正文字体样式
  52.         paragraph = self.document.add_paragraph()
  53.         run = paragraph.add_run(text)
  54.         run.font.name = '仿宋'
  55.         run.font.size = Pt(12)
  56.         return paragraph
  57.    
  58.     def convert_code_block(self, code, language=None):
  59.         """转换代码块"""
  60.         # 代码块以段落形式呈现,使用等宽字体显示
  61.         paragraph = self.document.add_paragraph()
  62.         run = paragraph.add_run(code)
  63.         
  64.         # 设置代码样式:使用等宽字体并缩小字号以区分正文
  65.         font = run.font
  66.         font.name = 'Consolas'
  67.         font.size = Pt(10)
  68.         
  69.         # 设置背景色(浅灰色),以提升代码块的可读性
  70.         # 注意:python-docx 原生不直接支持段落背景色,这里通过底层 OXML 手动设置段落底纹。
  71.         from docx.oxml.ns import qn
  72.         from docx.oxml import OxmlElement
  73.         
  74.         shd = OxmlElement('w:shd')
  75.         shd.set(qn('w:fill'), 'F5F5F5')
  76.         paragraph._p.get_or_add_pPr().append(shd)
  77.         
  78.         return paragraph
  79.    
  80.     def convert_list_item(self, text, level=0):
  81.         """转换列表项"""
  82.         # Word 中使用内置的项目符号样式,不同缩进级别映射到不同的样式名
  83.         paragraph = self.document.add_paragraph(style='List Bullet' if level == 0 else 'List Bullet 2')
  84.         run = paragraph.add_run(text)
  85.         run.font.name = '仿宋'
  86.         run.font.size = Pt(12)
  87.         return paragraph
  88.    
  89.     def convert_table(self, rows):
  90.         """转换表格"""
  91.         if not rows:
  92.             return
  93.         
  94.         # 创建表格,行列数由数据决定,应用网格样式以显示边框
  95.         table = self.document.add_table(rows=len(rows), cols=len(rows[0]))
  96.         table.style = 'Table Grid'
  97.         
  98.         # 填充表格内容并统一单元格字体样式
  99.         for i, row in enumerate(rows):
  100.             for j, cell_text in enumerate(row):
  101.                 cell = table.cell(i, j)
  102.                 cell.text = cell_text
  103.                
  104.                 # 设置单元格字体
  105.                 for paragraph in cell.paragraphs:
  106.                     for run in paragraph.runs:
  107.                         run.font.name = '仿宋'
  108.                         run.font.size = Pt(10)
  109.    
  110.     def parse_markdown_line(self, line):
  111.         """解析Markdown行"""
  112.         # 预处理:去除两端空白,便于匹配
  113.         line = line.strip()
  114.         
  115.         # 标题:匹配 1~6 个 # 后跟至少一个空格与标题文本
  116.         heading_match = re.match(r'^(#{1,6})\s+(.+)$', line)
  117.         if heading_match:
  118.             level = len(heading_match.group(1))
  119.             text = heading_match.group(2)
  120.             return ('heading', text, level)
  121.         
  122.         # 列表项:可选缩进 + 项目符号(- * +)+ 空格 + 文本
  123.         # 通过缩进空格数粗略计算层级(此处按 2 个空格为一个层级进行划分)
  124.         list_match = re.match(r'^(\s*)[-*+]\s+(.+)$', line)
  125.         if list_match:
  126.             indent = len(list_match.group(1))
  127.             text = list_match.group(2)
  128.             return ('list_item', text, indent // 2)
  129.         
  130.         # 空行:用于分隔段落,转换时通常忽略(也可用于增加段前/段后间距)
  131.         if not line:
  132.             return ('empty',)
  133.         
  134.         # 普通段落:未匹配到其他语法时,按正文处理
  135.         return ('paragraph', line)
  136.    
  137.     def convert_markdown_to_word(self, markdown_content):
  138.         """将Markdown内容转换为Word文档"""
  139.         # 将全文按行切分,采用简单的状态机识别代码块,其余按行解析
  140.         lines = markdown_content.split('\n')
  141.         in_code_block = False  # 代码块状态:True 表示当前在 ``` 与 ``` 之间
  142.         code_content = []      # 暂存代码块内容
  143.         
  144.         for line in lines:
  145.             # 代码块处理:以 ``` 作为代码块的开始与结束标记
  146.             if line.strip().startswith('```'):
  147.                 if in_code_block:
  148.                     # 结束代码块:将累计内容写入文档
  149.                     if code_content:
  150.                         self.convert_code_block('\n'.join(code_content))
  151.                     code_content = []
  152.                     in_code_block = False
  153.                 else:
  154.                     # 开始代码块:切换状态并继续读取后续行
  155.                     in_code_block = True
  156.                 continue
  157.             
  158.             if in_code_block:
  159.                 # 处于代码块内:按原样累计行文本,不进行 Markdown 解析
  160.                 code_content.append(line)
  161.                 continue
  162.             
  163.             # 解析并转换其他内容(标题、列表、段落等)
  164.             parsed = self.parse_markdown_line(line)
  165.             
  166.             if parsed[0] == 'heading':
  167.                 self.convert_heading(parsed[1], parsed[2])
  168.             elif parsed[0] == 'list_item':
  169.                 self.convert_list_item(parsed[1], parsed[2])
  170.             elif parsed[0] == 'paragraph':
  171.                 self.convert_paragraph(parsed[1])
  172.             elif parsed[0] == 'empty':
  173.                 # 空行:当前实现选择忽略,必要时可在此添加段前/段后间距控制
  174.                 continue
  175.    
  176.     def convert_file(self, input_path, output_path=None):
  177.         """转换文件"""
  178.         try:
  179.             # 读取 Markdown 文件内容
  180.             with open(input_path, 'r', encoding='utf-8') as f:
  181.                 markdown_content = f.read()
  182.             
  183.             # 执行内容转换:将 Markdown 映射到 Word 文档对象
  184.             self.convert_markdown_to_word(markdown_content)
  185.             
  186.             # 确定输出路径:默认与输入同目录,扩展名改为 .docx
  187.             if not output_path:
  188.                 input_file = Path(input_path)
  189.                 # output_path = input_file.with_suffix('.docx')
  190.                 output_path = input_file.parent / (input_file.stem + '.docx')
  191.             
  192.             
  193.             # 保存生成的 Word 文档
  194.             self.document.save(output_path)
  195.             
  196.             print(f"转换成功!")
  197.             print(f"输入文件: {input_path}")
  198.             print(f"输出文件: {output_path}")
  199.             
  200.             return True
  201.             
  202.         except Exception as e:
  203.             print(f"转换失败: {str(e)}")
  204.             return False


  205. def main():
  206.     """主函数"""
  207.     # 指定输入文件路径:可根据需要修改为目标 Markdown 文件
  208.     input_file = r"D:\BaiduSyncdisk\case\03-先生们.pdf-b6ec8915-b212-4b21-90f6-79dbc013448f\分析报告\报告简洁版.md"
  209.    
  210.     # 检查输入文件是否存在,避免后续读取失败
  211.     if not os.path.exists(input_file):
  212.         print(f"错误: 输入文件不存在 - {input_file}")
  213.         return False
  214.    
  215.     # 创建转换器并转换文件
  216.     converter = MarkdownToWordConverter()
  217.     return converter.convert_file(input_file)


  218. if __name__ == "__main__":
  219.     success = main()
  220.     sys.exit(0 if success else 1)
复制代码



分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

143 积分
25 主题
热门推荐