|
|
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- Markdown转Word文档转换器
- 将Markdown文件转换为Word文档格式
- """
- import os
- import sys
- import re
- from pathlib import Path
- from docx import Document
- from docx.shared import Pt, RGBColor
- from docx.enum.text import WD_ALIGN_PARAGRAPH
- import markdown
- from markdown.extensions import codehilite
- # 说明:
- # 该脚本用于将 Markdown 文本内容转换为 Word(.docx)文档。
- # 核心流程为:读取输入文件 → 按行解析 Markdown → 在 docx 文档中生成对应结构 → 保存输出文件。
- # 主要依赖:python-docx 用于生成 Word 文档;正则表达式用于简易 Markdown 语法识别。
- class MarkdownToWordConverter:
- # 职责:封装将 Markdown 内容映射到 Word 文档的全部步骤与样式。
- # 使用方式:实例化后调用 convert_file 或 convert_markdown_to_word,再通过 self.document.save 输出。
- def __init__(self):
- # 创建空白 Word 文档对象
- self.document = Document()
- # 设置全局字体样式(正文字体、字号等)
- self.setup_document_styles()
-
- def setup_document_styles(self):
- """设置文档样式"""
- # 设置正文字体
- style = self.document.styles['Normal']
- font = style.font
- font.name = '仿宋'
- font.size = Pt(12)
-
- def convert_heading(self, text, level):
- """转换标题"""
- # 在文档中添加指定级别的标题段落
- paragraph = self.document.add_heading(level=level)
- run = paragraph.add_run(text)
- # 根据标题级别设置字体大小
- font_sizes = {1: 18, 2: 16, 3: 14, 4: 12, 5: 11, 6: 10}
- run.font.size = Pt(font_sizes.get(level, 12))
- run.font.name = '黑体' if level <= 2 else '仿宋'
-
- return paragraph
-
- def convert_paragraph(self, text):
- """转换段落"""
- # 添加普通段落并应用正文字体样式
- paragraph = self.document.add_paragraph()
- run = paragraph.add_run(text)
- run.font.name = '仿宋'
- run.font.size = Pt(12)
- return paragraph
-
- def convert_code_block(self, code, language=None):
- """转换代码块"""
- # 代码块以段落形式呈现,使用等宽字体显示
- paragraph = self.document.add_paragraph()
- run = paragraph.add_run(code)
-
- # 设置代码样式:使用等宽字体并缩小字号以区分正文
- font = run.font
- font.name = 'Consolas'
- font.size = Pt(10)
-
- # 设置背景色(浅灰色),以提升代码块的可读性
- # 注意:python-docx 原生不直接支持段落背景色,这里通过底层 OXML 手动设置段落底纹。
- from docx.oxml.ns import qn
- from docx.oxml import OxmlElement
-
- shd = OxmlElement('w:shd')
- shd.set(qn('w:fill'), 'F5F5F5')
- paragraph._p.get_or_add_pPr().append(shd)
-
- return paragraph
-
- def convert_list_item(self, text, level=0):
- """转换列表项"""
- # Word 中使用内置的项目符号样式,不同缩进级别映射到不同的样式名
- paragraph = self.document.add_paragraph(style='List Bullet' if level == 0 else 'List Bullet 2')
- run = paragraph.add_run(text)
- run.font.name = '仿宋'
- run.font.size = Pt(12)
- return paragraph
-
- def convert_table(self, rows):
- """转换表格"""
- if not rows:
- return
-
- # 创建表格,行列数由数据决定,应用网格样式以显示边框
- table = self.document.add_table(rows=len(rows), cols=len(rows[0]))
- table.style = 'Table Grid'
-
- # 填充表格内容并统一单元格字体样式
- for i, row in enumerate(rows):
- for j, cell_text in enumerate(row):
- cell = table.cell(i, j)
- cell.text = cell_text
-
- # 设置单元格字体
- for paragraph in cell.paragraphs:
- for run in paragraph.runs:
- run.font.name = '仿宋'
- run.font.size = Pt(10)
-
- def parse_markdown_line(self, line):
- """解析Markdown行"""
- # 预处理:去除两端空白,便于匹配
- line = line.strip()
-
- # 标题:匹配 1~6 个 # 后跟至少一个空格与标题文本
- heading_match = re.match(r'^(#{1,6})\s+(.+)$', line)
- if heading_match:
- level = len(heading_match.group(1))
- text = heading_match.group(2)
- return ('heading', text, level)
-
- # 列表项:可选缩进 + 项目符号(- * +)+ 空格 + 文本
- # 通过缩进空格数粗略计算层级(此处按 2 个空格为一个层级进行划分)
- list_match = re.match(r'^(\s*)[-*+]\s+(.+)$', line)
- if list_match:
- indent = len(list_match.group(1))
- text = list_match.group(2)
- return ('list_item', text, indent // 2)
-
- # 空行:用于分隔段落,转换时通常忽略(也可用于增加段前/段后间距)
- if not line:
- return ('empty',)
-
- # 普通段落:未匹配到其他语法时,按正文处理
- return ('paragraph', line)
-
- def convert_markdown_to_word(self, markdown_content):
- """将Markdown内容转换为Word文档"""
- # 将全文按行切分,采用简单的状态机识别代码块,其余按行解析
- lines = markdown_content.split('\n')
- in_code_block = False # 代码块状态:True 表示当前在 ``` 与 ``` 之间
- code_content = [] # 暂存代码块内容
-
- for line in lines:
- # 代码块处理:以 ``` 作为代码块的开始与结束标记
- if line.strip().startswith('```'):
- if in_code_block:
- # 结束代码块:将累计内容写入文档
- if code_content:
- self.convert_code_block('\n'.join(code_content))
- code_content = []
- in_code_block = False
- else:
- # 开始代码块:切换状态并继续读取后续行
- in_code_block = True
- continue
-
- if in_code_block:
- # 处于代码块内:按原样累计行文本,不进行 Markdown 解析
- code_content.append(line)
- continue
-
- # 解析并转换其他内容(标题、列表、段落等)
- parsed = self.parse_markdown_line(line)
-
- if parsed[0] == 'heading':
- self.convert_heading(parsed[1], parsed[2])
- elif parsed[0] == 'list_item':
- self.convert_list_item(parsed[1], parsed[2])
- elif parsed[0] == 'paragraph':
- self.convert_paragraph(parsed[1])
- elif parsed[0] == 'empty':
- # 空行:当前实现选择忽略,必要时可在此添加段前/段后间距控制
- continue
-
- def convert_file(self, input_path, output_path=None):
- """转换文件"""
- try:
- # 读取 Markdown 文件内容
- with open(input_path, 'r', encoding='utf-8') as f:
- markdown_content = f.read()
-
- # 执行内容转换:将 Markdown 映射到 Word 文档对象
- self.convert_markdown_to_word(markdown_content)
-
- # 确定输出路径:默认与输入同目录,扩展名改为 .docx
- if not output_path:
- input_file = Path(input_path)
- # output_path = input_file.with_suffix('.docx')
- output_path = input_file.parent / (input_file.stem + '.docx')
-
-
- # 保存生成的 Word 文档
- self.document.save(output_path)
-
- print(f"转换成功!")
- print(f"输入文件: {input_path}")
- print(f"输出文件: {output_path}")
-
- return True
-
- except Exception as e:
- print(f"转换失败: {str(e)}")
- return False
- def main():
- """主函数"""
- # 指定输入文件路径:可根据需要修改为目标 Markdown 文件
- input_file = r"D:\BaiduSyncdisk\case\03-先生们.pdf-b6ec8915-b212-4b21-90f6-79dbc013448f\分析报告\报告简洁版.md"
-
- # 检查输入文件是否存在,避免后续读取失败
- if not os.path.exists(input_file):
- print(f"错误: 输入文件不存在 - {input_file}")
- return False
-
- # 创建转换器并转换文件
- converter = MarkdownToWordConverter()
- return converter.convert_file(input_file)
- if __name__ == "__main__":
- success = main()
- sys.exit(0 if success else 1)
复制代码
|
|