下面是一个使用 python 和 pyqt5 实现的 markdown 转 docx 工具,具有美观的图形界面,支持表格和代码块转换,并提供转换预览功能。
效果图
功能特点
一比一复刻 markdown 格式到 docx
支持表格、代码块等复杂元素转换
美观的图形界面
转换过程实时预览
文件选择对话框
代码实现
import sys import os from markdown import markdown from docx import document from docx.shared import pt, rgbcolor, inches from docx.oxml.ns import qn from docx.enum.text import wd_paragraph_alignment from pyqt5.qtwidgets import (qapplication, qmainwindow, qvboxlayout, qhboxlayout, qpushbutton, qfiledialog, qtextedit, qlabel, qwidget, qmessagebox, qprogressbar) from pyqt5.qtcore import qt from pyqt5.qtgui import qfont, qicon class markdowntodocxconverter(qmainwindow): def __init__(self): super().__init__() self.initui() self.setwindowicon(qicon('icon.png')) # 请准备一个图标文件或删除这行 def initui(self): self.setwindowtitle('markdown 转 docx 工具') self.setgeometry(300, 300, 800, 600) # 主窗口部件 main_widget = qwidget() self.setcentralwidget(main_widget) # 主布局 main_layout = qvboxlayout() main_widget.setlayout(main_layout) # 标题 title_label = qlabel('markdown 转 docx 转换器') title_label.setfont(qfont('microsoft yahei', 16, qfont.bold)) title_label.setalignment(qt.aligncenter) title_label.setstylesheet('color: #2c3e50; margin-bottom: 20px;') main_layout.addwidget(title_label) # 文件选择区域 file_layout = qhboxlayout() self.md_file_label = qlabel('未选择文件') self.md_file_label.setfont(qfont('microsoft yahei', 10)) self.md_file_label.setstylesheet('border: 1px solid #ddd; padding: 5px;') self.md_file_label.setfixedheight(30) select_btn = qpushbutton('选择 markdown 文件') select_btn.setfont(qfont('microsoft yahei', 10)) select_btn.setstylesheet(''' qpushbutton { background-color: #3498db; color: white; border: none; padding: 8px 15px; border-radius: 4px; } qpushbutton:hover { background-color: #2980b9; } ''') select_btn.clicked.connect(self.select_md_file) file_layout.addwidget(self.md_file_label, stretch=4) file_layout.addwidget(select_btn, stretch=1) main_layout.addlayout(file_layout) # 预览区域 preview_label = qlabel('预览内容:') preview_label.setfont(qfont('microsoft yahei', 10, qfont.bold)) main_layout.addwidget(preview_label) self.preview_text = qtextedit() self.preview_text.setfont(qfont('consolas', 10)) self.preview_text.setreadonly(true) self.preview_text.setstylesheet(''' qtextedit { border: 1px solid #ddd; padding: 10px; background-color: #f9f9f9; } ''') main_layout.addwidget(self.preview_text, stretch=3) # 进度条 self.progress_bar = qprogressbar() self.progress_bar.setrange(0, 100) self.progress_bar.setvalue(0) self.progress_bar.settextvisible(true) self.progress_bar.setstylesheet(''' qprogressbar { border: 1px solid #ddd; border-radius: 3px; text-align: center; height: 20px; } qprogressbar::chunk { background-color: #2ecc71; width: 10px; } ''') main_layout.addwidget(self.progress_bar) # 转换按钮 convert_btn = qpushbutton('转换为 docx') convert_btn.setfont(qfont('microsoft yahei', 12, qfont.bold)) convert_btn.setstylesheet(''' qpushbutton { background-color: #2ecc71; color: white; border: none; padding: 10px 20px; border-radius: 4px; margin-top: 15px; } qpushbutton:hover { background-color: #27ae60; } qpushbutton:disabled { background-color: #95a5a6; } ''') convert_btn.clicked.connect(self.convert_to_docx) convert_btn.setenabled(false) self.convert_btn = convert_btn main_layout.addwidget(convert_btn, alignment=qt.aligncenter) # 状态栏 self.statusbar().showmessage('准备就绪') # 成员变量 self.md_file_path = '' def select_md_file(self): options = qfiledialog.options() file_path, _ = qfiledialog.getopenfilename( self, "选择 markdown 文件", "", "markdown files (*.md *.markdown);;all files (*)", options=options ) if file_path: self.md_file_path = file_path self.md_file_label.settext(file_path) self.convert_btn.setenabled(true) # 预览文件内容 try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() self.preview_text.setplaintext(content) self.statusbar().showmessage('文件加载成功') except exception as e: qmessagebox.warning(self, '错误', f'无法读取文件: {str(e)}') self.statusbar().showmessage('文件读取失败') def convert_to_docx(self): if not self.md_file_path: qmessagebox.warning(self, '警告', '请先选择 markdown 文件') return # 设置保存路径 options = qfiledialog.options() save_path, _ = qfiledialog.getsavefilename( self, "保存 docx 文件", os.path.splitext(self.md_file_path)[0] + '.docx', "word documents (*.docx);;all files (*)", options=options ) if not save_path: return self.progress_bar.setvalue(10) self.statusbar().showmessage('正在转换...') qapplication.processevents() # 更新ui try: # 读取markdown内容 with open(self.md_file_path, 'r', encoding='utf-8') as f: md_content = f.read() self.progress_bar.setvalue(30) # 创建word文档 doc = document() # 设置默认字体 doc.styles['normal'].font.name = '微软雅黑' doc.styles['normal']._element.rpr.rfonts.set(qn('w:eastasia'), '微软雅黑') doc.styles['normal'].font.size = pt(10.5) # 转换markdown为html html_content = markdown(md_content, extensions=[ 'extra', # 支持表格、代码块等 'codehilite', # 代码高亮 'tables', # 表格支持 'fenced_code' # 围栏代码块 ]) self.progress_bar.setvalue(50) # 将html内容添加到word文档 self.add_html_to_doc(html_content, doc) self.progress_bar.setvalue(80) # 保存文档 doc.save(save_path) self.progress_bar.setvalue(100) self.statusbar().showmessage('转换完成!') qmessagebox.information(self, '成功', '文件转换完成!') except exception as e: qmessagebox.critical(self, '错误', f'转换过程中出错: {str(e)}') self.statusbar().showmessage('转换失败') finally: self.progress_bar.setvalue(0) def add_html_to_doc(self, html, doc): from bs4 import beautifulsoup soup = beautifulsoup(html, 'html.parser') for element in soup.children: if element.name == 'h1': self.add_heading(doc, element.text, 0) elif element.name == 'h2': self.add_heading(doc, element.text, 1) elif element.name == 'h3': self.add_heading(doc, element.text, 2) elif element.name == 'h4': self.add_heading(doc, element.text, 3) elif element.name == 'h5': self.add_heading(doc, element.text, 4) elif element.name == 'h6': self.add_heading(doc, element.text, 5) elif element.name == 'p': self.add_paragraph(doc, element.text) elif element.name == 'ul': self.add_list(doc, element, false) elif element.name == 'ol': self.add_list(doc, element, true) elif element.name == 'table': self.add_table(doc, element) elif element.name == 'pre': self.add_code_block(doc, element) elif element.name == 'blockquote': self.add_quote(doc, element) elif element.name == 'hr': self.add_horizontal_rule(doc) def add_heading(self, doc, text, level): heading = doc.add_heading(text, level) # 设置中文字体 for run in heading.runs: run.font.name = '微软雅黑' run._element.rpr.rfonts.set(qn('w:eastasia'), '微软雅黑') def add_paragraph(self, doc, text): p = doc.add_paragraph(text) # 设置中文字体 for run in p.runs: run.font.name = '微软雅黑' run._element.rpr.rfonts.set(qn('w:eastasia'), '微软雅黑') def add_list(self, doc, element, ordered): for li in element.find_all('li', recursive=false): if ordered: doc.add_paragraph(li.text, style='list number') else: doc.add_paragraph(li.text, style='list bullet') # 递归处理子列表 for child in li.children: if child.name in ['ul', 'ol']: self.add_list(doc, child, child.name == 'ol') def add_table(self, doc, element): rows = element.find_all('tr') if not rows: return # 创建表格 table = doc.add_table(rows=len(rows), cols=len(rows[0].find_all(['th', 'td']))) table.style = 'table grid' # 添加边框 for i, row in enumerate(rows): cells = row.find_all(['th', 'td']) for j, cell in enumerate(cells): table.cell(i, j).text = cell.get_text() # 设置中文字体 for paragraph in table.cell(i, j).paragraphs: for run in paragraph.runs: run.font.name = '微软雅黑' run._element.rpr.rfonts.set(qn('w:eastasia'), '微软雅黑') # 表头加粗 if cell.name == 'th': for paragraph in table.cell(i, j).paragraphs: for run in paragraph.runs: run.font.bold = true def add_code_block(self, doc, element): code = element.find('code') if not code: return code_text = code.get_text() # 添加代码段落 p = doc.add_paragraph() p.paragraph_format.left_indent = inches(0.5) p.paragraph_format.space_before = pt(6) p.paragraph_format.space_after = pt(6) run = p.add_run(code_text) run.font.name = 'consolas' run.font.size = pt(10) run.font.color.rgb = rgbcolor(0x36, 0x36, 0x36) # 添加灰色背景 shading_elm = p._element.get_or_add_ppr().get_or_add_shd() shading_elm.set(qn('w:fill'), 'f0f0f0') def add_quote(self, doc, element): p = doc.add_paragraph() p.paragraph_format.left_indent = inches(0.5) p.paragraph_format.first_line_indent = inches(-0.25) p.paragraph_format.space_before = pt(6) p.paragraph_format.space_after = pt(6) run = p.add_run(element.get_text()) run.font.name = '微软雅黑' run._element.rpr.rfonts.set(qn('w:eastasia'), '微软雅黑') run.font.italic = true run.font.color.rgb = rgbcolor(0x66, 0x66, 0x66) # 添加左边框 p._element.get_or_add_ppr().get_or_add_pbdr().left.val = 'single' p._element.get_or_add_ppr().get_or_add_pbdr().left.sz = 4 p._element.get_or_add_ppr().get_or_add_pbdr().left.color = 'auto' def add_horizontal_rule(self, doc): p = doc.add_paragraph() p.paragraph_format.alignment = wd_paragraph_alignment.center run = p.add_run('―' * 30) # 使用长破折号作为分隔线 run.font.color.rgb = rgbcolor(0xcc, 0xcc, 0xcc) if __name__ == '__main__': app = qapplication(sys.argv) # 设置应用程序字体 font = qfont('microsoft yahei', 10) app.setfont(font) # 设置样式表 app.setstylesheet(''' qmainwindow { background-color: #f5f7fa; } qlabel { color: #34495e; } ''') converter = markdowntodocxconverter() converter.show() sys.exit(app.exec_())
使用说明
运行程序后,点击"选择 markdown 文件"按钮选择要转换的.md文件
文件内容将显示在预览框中
点击"转换为 docx"按钮选择保存位置并开始转换
转换过程中会显示进度条和状态信息
转换完成后会弹出提示框
依赖安装
在运行此程序前,需要安装以下依赖:
pip install pyqt5 markdown python-docx beautifulsoup4
功能扩展建议
- 可以添加批量转换功能
- 可以增加对更多markdown扩展语法的支持
- 可以添加主题切换功能
- 可以增加转换历史记录功能
这个工具提供了美观的界面和完整的markdown到docx转换功能,支持表格、代码块等复杂元素的转换,并提供了实时预览和进度显示。
到此这篇关于python结合pyqt5实现md(markdown)转docx工具的文章就介绍到这了,更多相关python markdown转docx内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论