当前位置: 代码网 > it编程>前端脚本>Python > Python结合PyQt5实现MD(Markdown)转DOCX工具

Python结合PyQt5实现MD(Markdown)转DOCX工具

2025年07月23日 Python 我要评论
下面是一个使用 python 和 pyqt5 实现的 markdown 转 docx 工具,具有美观的图形界面,支持表格和代码块转换,并提供转换预览功能。效果图功能特点一比一复刻 markdown 格

下面是一个使用 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内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com