BeautifulSoup实战 用python把md文件转换为html网页

使用Python将Markdown文件转换为HTML文件

前情

在制作网页的时候,有时需要将在网页上显示md文件,但是将Md文件嵌入HTML的操作极为繁琐,或者遇到某些网站为了安全和用户隐私禁用了JS,这时就需要将Md文件转换为HTML了。而这个脚本解决了这个问题。而且支持调整缩进大小,支持GUI的同时支持命令行,便于批量操作。

代码

下面是要安装的库

pip install markdown
pip install bs4

导入库

import os
import re
import sys

#GUI库
import tkinter as tk
import tkinter.filedialog as filedialog
import tkinter.messagebox as messagebox
import tkinter.ttk as ttk

import markdown
from bs4 import BeautifulSoup

解析Markdown

Markdown是一种轻量级标记语言,它以纯文本形式(易读、易写、易更改)编写文档,并最终以HTML格式发布。下面是md和HTML对照表,我们只需要根据对照表将MD语法替换为HTML并进行缩进就可以完成基本的转化。Markdown 是一种轻量级的标记语言,可用于在纯文本文档中添加格式化元素。Markdown 由 John Gruber 于 2004 年创建,如今已成为世界上最受欢迎的标记语言之一。
使用 Markdown 与使用 Word 类编辑器不同。在 Word 之类的应用程序中,单击按钮以设置单词和短语的格式,并且,更改立即可见。而 Markdown 与此不同,当你创建 Markdown 格式的文件时,可以在文本中添加 Markdown 语法,以指示哪些单词和短语看起来应该有所不同。
例如,要表示标题,只须在短语前面添加一个井号即可(例如, # Heading One)。或者要加粗一个短语,只须在短语前后各加两个星号即可(例如,this text is bold)。可能需要一段时间才能习惯在文本中看到 Markdown 语法,尤其是如果你已习惯了所见即所得的应用程序。

部分对照表:

类型 MD HTML
标题1 # 标题1

标题2 ## 标题1

标题3 ### 标题1

标题4 #### 标题1

标题5 ##### 标题1
标题6 ###### 标题1
段落 段落

段落

粗体 just love **bold text** just love bold text
斜体 Italicized text is the *cat's meow*. Italicized text is the cat's meow
# 导入re模块,用于正则表达式匹配
import re
# 保存原始的BeautifulSoup.prettify方法
orig_prettify = BeautifulSoup.prettify
# 创建一个正则表达式对象,用于匹配每行开头的空白字符
r = re.compile(r"^(\s*)", re.MULTILINE)


def prettify(self, encoding=None, formatter="minimal", indent_width=2):
    # 这个函数是对原始的BeautifulSoup.prettify方法的修改,用于调整缩进宽度
    # 使用正则表达式替换每行开头的空白字符,乘以指定的缩进宽度
    return r.sub(r"\1" * indent_width, orig_prettify(self, encoding, formatter))


# 用修改后的prettify函数替换原始的BeautifulSoup.prettify方法
BeautifulSoup.prettify = prettify


def md2html(input_text, indent=2, model=True):
    # 这个函数用于将markdown文本转换为html文本,并格式化输出
    # 定义一个扩展列表,包含一些markdown的扩展功能
    extensions = [
        "abbr",
        "admonition",
        "attr_list",
        "codehilite",
        "def_list",
        "extra",
        "fenced_code",
        "footnotes",
        "legacy_attrs",
        "legacy_em",
        "md_in_html",
        "meta",
        "nl2br",
        "sane_lists",
        "smarty",
        "tables",
        "toc",
    ]

    # 定义一个html文档的头部,包含DOCTYPE声明和meta标签
    head = """
    
    
    
    
    
    """
    # 定义一个html文档的尾部,包含结束标签
    end = """
    
    """
    # 如果model参数为True,则在markdown文本前后添加头部和尾部,生成完整的html文档
    if model:
        html_text = head + markdown.markdown(input_text, extension=extensions) + end
    # 如果model参数为False,则只生成markdown文本对应的html内容
    else:
        html_text = markdown.markdown(input_text, extensions=extensions)
    # 使用BeautifulSoup解析html文本,并指定解析器为html.parser
    soup = BeautifulSoup(html_text, features="html.parser")
    # 使用prettify方法格式化html文本,并指定缩进宽度
    formatted_text = soup.prettify(indent_width=indent)
    # 返回格式化后的html文本
    return formatted_text
# 定义一个类,叫FileChooser
class FileChooser:
    # 定义初始化方法,创建一个tkinter变量,用来存储文件路径
    def __init__(self):
        self.file_path = tk.StringVar()
        # 给变量添加一个属性,指向自己
        self.file_path.choose_file = self.file_path

    # 定义一个方法,用来弹出对话框,选择文件,并返回文件路径
    def select_file(self):
        # 设置变量的值为选择的文件路径
        self.file_path.choose_file.set(filedialog.askopenfilename())
        # 返回变量的值
        return self.file_path.get()

    # 定义一个方法,用来解析Markdown文件,接受文件路径,缩进,和模型作为参数,并返回HTML文本
    def markdown_parser(self, file_path, indent, model):
        # 打开文件,以只读和utf-8编码的方式
        with open(file_path, "r", encoding="utf-8") as f:
            # 调用md2html函数,把文件内容,缩进,和模型作为参数,并返回HTML文本
            return md2html(f.read(), indent=indent, model=model)

    # 定义一个方法,用来保存HTML文本,接受HTML文本作为参数
    def to_new(self, html_text):
        # 弹出对话框,让用户选择保存的文件名和类型,默认扩展名为.html
        filename = filedialog.asksaveasfilename(
            filetypes=[("HTML", "*.html"), ("所有文件", "*.*")], defaultextension=".html"
        )
        # 如果用户选择了文件名
        if filename:
            # 打开文件,以写入和utf-8编码的方式
            with open(filename, "w", encoding="utf-8") as f:
                # 把HTML文本写入文件
                f.write(html_text)

GUI界面

然后写一个GUI,封装为GUI类。

class GUI(ttk.Frame, FileChooser):
    def __init__(self, master=None):
        super().__init__(master)
        FileChooser.__init__(self)
        self.master = master
        self.master.title("Markdown转HTML")
        self.master.geometry("325x100")
        self.master.resizable(False, False)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        self.left_frame = ttk.Frame(self, width=200, height=200)
        self.left_frame.grid(row=0, column=0, padx=10, pady=10)

        self.right_frame = ttk.Frame(self, width=200, height=200)
        self.right_frame.grid(row=0, column=1, padx=10, pady=10)

        self.browse_button = ttk.Button(
            self.left_frame, text="浏览", command=self.choose_file
        )
        self.browse_button.grid(row=0, column=0, sticky=tk.W)

        self.file_label = ttk.Label(self.left_frame, text="请选择一个文件")
        self.file_label.grid(row=1, column=0, columnspan=2, sticky=tk.W)

        self.entry_var = tk.StringVar()
        self.entry_var.set("请输入缩进宽度(默认为2)")
        self.entry = ttk.Entry(
            self.left_frame, textvariable=self.entry_var, justify=tk.CENTER
        )
        self.entry.grid(row=2, column=0, columnspan=2, sticky=tk.W)

        self.export_button = ttk.Button(
            self.right_frame, text="导出", command=self.output
        )
        self.export_button.grid(row=1, column=0)

        self.cb = tk.BooleanVar()
        self.cb.set(True)

        self.check_button = ttk.Checkbutton(
            self.right_frame, text="是否完整导出", variable=self.cb
        )
        self.check_button.grid(row=0, column=0)

    def choose_file(self):
        file_name = self.select_file()
        chosen_file = os.path.abspath(file_name)
        self.file_label.config(text=chosen_file)

    def output(self):
        file_chosen = self.file_label.cget("text")
        if not os.path.isfile(file_chosen):
            messagebox.showwarning("错误", "请先选择文件")
            return ""
        else:
            if self.entry_var.get() == "请输入缩进宽度(默认为2)":
                indent_width = 2
                self.entry_var.set(indent_width)
            else:
                try:
                    indent_width = int(self.entry_var.get())
                except ValueError:
                    messagebox.showwarning("错误", "请输入正确的缩进宽度")
                    return ""
            model = self.cb.get()
            html_text = self.markdown_parser(file_chosen, indent_width, model)
            self.to_new(html_text)

主函数

最后是程序的主函数。如果在运行时没有传入任何参数,就启动GUI界面;如果传递了参数,就以命令行模式运行。
命令行下用法:

python3 xxx.py [md文件名.md] [输出HTML文件名] [缩进距离]
def main()
    if len(sys.argv) == 1:
        root = tk.Tk()
        gui = GUI(master=root)
        gui.master.mainloop()
    elif len(sys.argv) != 4 and len(sys.argv) != 5:
        sys.stderr.write(
            f"Usage: {sys.argv[0]} input_file output_file completeness(CPL/INC) indent_width(Optional, default 2)"
        )
        sys.exit(1)
    else:
        input_file = sys.argv[1]
        output_file = sys.argv[2]
        model = sys.argv[3] == "CPL"
        indent_width = int(sys.argv[4]) if len(sys.argv) == 5 else 2

        try:
            with open(input_file, "r", encoding="utf-8") as in_file:
                program = in_file.read()
        except IOError:
            sys.stderr.write(f"Cannot open input file: {input_file}")
            sys.exit(2)

        try:
            with open(output_file, "w", encoding="utf-8") as out_file:
                out_file.write(md2html(program, indent=indent_width, model=model))
        except IOError:
            print(f"Cannot open output file: {output_file}")
            sys.exit(3)

        sys.exit(0)

if __name__ =="__main__":
    main()

感谢你的观看
欢迎大家尝试作者的另一个工具密码强度检测

源代码

下面是整个程序源代码:

import os
import re
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.filedialog as filedialog
import tkinter.messagebox as messagebox
import sys
import markdown
from bs4 import BeautifulSoup


orig_prettify = BeautifulSoup.prettify
r = re.compile(r"^(\s*)", re.MULTILINE)


def prettify(self, encoding=None, formatter="minimal", indent_width=2):
    return r.sub(r"\1" * indent_width, orig_prettify(self, encoding, formatter))


BeautifulSoup.prettify = prettify


def md2html(input_text, indent=2, model=True):
    extensions = [
        "abbr",
        "admonition",
        "attr_list",
        "codehilite",
        "def_list",
        "extra",
        "fenced_code",
        "footnotes",
        "legacy_attrs",
        "legacy_em",
        "md_in_html",
        "meta",
        "nl2br",
        "sane_lists",
        "smarty",
        "tables",
        "toc",
    ]

    head = """
    
    
    
    
    
    """
    end = """
    
    """
    if model:
        html_text = head + markdown.markdown(input_text, extension=extensions) + end
    else:
        html_text = markdown.markdown(input_text, extensions=extensions)
    soup = BeautifulSoup(html_text, features="html.parser")
    formatted_text = soup.prettify(indent_width=indent)
    return formatted_text


class FileChooser:
    def __init__(self):
        self.file_path = tk.StringVar()
        self.file_path.choose_file = self.file_path

    def select_file(self):
        self.file_path.choose_file.set(filedialog.askopenfilename())
        return self.file_path.get()

    def markdown_parser(self, file_path, indent, model):
        with open(file_path, "r", encoding="utf-8") as f:
            return md2html(f.read(), indent=indent, model=model)

    def to_new(self, html_text):
        filename = filedialog.asksaveasfilename(
            filetypes=[("HTML", "*.html"), ("所有文件", "*.*")], defaultextension=".html"
        )
        if filename:
            with open(filename, "w", encoding="utf-8") as f:
                f.write(html_text)


class GUI(ttk.Frame, FileChooser):
    def __init__(self, master=None):
        super().__init__(master)
        FileChooser.__init__(self)
        self.master = master
        self.master.title("Markdown转HTML")
        self.master.geometry("325x100")
        self.master.resizable(False, False)
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        self.left_frame = ttk.Frame(self, width=200, height=200)
        self.left_frame.grid(row=0, column=0, padx=10, pady=10)

        self.right_frame = ttk.Frame(self, width=200, height=200)
        self.right_frame.grid(row=0, column=1, padx=10, pady=10)

        self.browse_button = ttk.Button(
            self.left_frame, text="浏览", command=self.choose_file
        )
        self.browse_button.grid(row=0, column=0, sticky=tk.W)

        self.file_label = ttk.Label(self.left_frame, text="请选择一个文件")
        self.file_label.grid(row=1, column=0, columnspan=2, sticky=tk.W)

        self.entry_var = tk.StringVar()
        self.entry_var.set("请输入缩进宽度(默认为2)")
        self.entry = ttk.Entry(
            self.left_frame, textvariable=self.entry_var, justify=tk.CENTER
        )
        self.entry.grid(row=2, column=0, columnspan=2, sticky=tk.W)

        self.export_button = ttk.Button(
            self.right_frame, text="导出", command=self.output
        )
        self.export_button.grid(row=1, column=0)

        self.cb = tk.BooleanVar()
        self.cb.set(True)

        self.check_button = ttk.Checkbutton(
            self.right_frame, text="是否完整导出", variable=self.cb
        )
        self.check_button.grid(row=0, column=0)

    def choose_file(self):
        file_name = self.select_file()
        chosen_file = os.path.abspath(file_name)
        self.file_label.config(text=chosen_file)

    def output(self):
        file_chosen = self.file_label.cget("text")
        if not os.path.isfile(file_chosen):
            messagebox.showwarning("错误", "请先选择文件")
            return ""
        else:
            if self.entry_var.get() == "请输入缩进宽度(默认为2)":
                indent_width = 2
                self.entry_var.set(indent_width)
            else:
                try:
                    indent_width = int(self.entry_var.get())
                except ValueError:
                    messagebox.showwarning("错误", "请输入正确的缩进宽度")
                    return ""
            model = self.cb.get()
            html_text = self.markdown_parser(file_chosen, indent_width, model)
            self.to_new(html_text)

def main()
    if len(sys.argv) == 1:
        root = tk.Tk()
        gui = GUI(master=root)
        gui.master.mainloop()
    elif len(sys.argv) != 4 and len(sys.argv) != 5:
        sys.stderr.write(
            f"Usage: {sys.argv[0]} input_file output_file completeness(CPL/INC) indent_width(Optional, default 2)"
        )
        sys.exit(1)
    else:
        input_file = sys.argv[1]
        output_file = sys.argv[2]
        model = sys.argv[3] == "CPL"
        indent_width = int(sys.argv[4]) if len(sys.argv) == 5 else 2

        try:
            with open(input_file, "r", encoding="utf-8") as in_file:
                program = in_file.read()
        except IOError:
            sys.stderr.write(f"Cannot open input file: {input_file}")
            sys.exit(2)

        try:
            with open(output_file, "w", encoding="utf-8") as out_file:
                out_file.write(md2html(program, indent=indent_width, model=model))
        except IOError:
            print(f"Cannot open output file: {output_file}")
            sys.exit(3)

        sys.exit(0)

if __name__ =="__main__":
    main()

谢谢你看到最后,制作不易,点个赞不过分吧!(>_O)

你可能感兴趣的:(Python高效办公,python,html,开发语言,beautifulsoup)