import os
import re
import markdown
from tkinter import Tk, filedialog, Button, Label, messagebox
def convert_md_to_html():
md_filepath = filedialog.askopenfilename(
title="请选择一个 Markdown 文件",
filetypes=[("Markdown 文件", "*.md"), ("所有文件", "*.*")]
)
if not md_filepath:
return
try:
with open(md_filepath, 'r', encoding='utf-8') as md_file:
md_content = md_file.read()
# 转换Markdown为HTML
html_body_content = markdown.markdown(
md_content, extensions=[
'markdown.extensions.tables',
'markdown.extensions.fenced_code',
'markdown.extensions.extra',
]
)
# 处理Mermaid图表
html_body_content = re.sub(
r'<pre><code class="language-mermaid">(.*?)</code></pre>',
r'<div class="mermaid">\1</div>',
html_body_content,
flags=re.DOTALL
)
html_body_content = html_body_content.replace('<', '<').replace('>', '>')
# 为所有标题添加ID(用于目录锚点)
toc_entries = []
heading_counter = {'h1': 0, 'h2': 0, 'h3': 0, 'h4': 0}
def add_id_to_heading(match):
tag = match.group(1).lower()
content = match.group(2)
if tag in heading_counter:
heading_counter[tag] += 1
id_name = f"{tag}-{heading_counter[tag]}"
toc_entries.append((tag, id_name, content))
return f'<{tag} id="{id_name}">{content}</{tag}>'
return match.group(0)
html_body_content = re.sub(
r'<([hH][1-6])>(.*?)</\1>',
add_id_to_heading,
html_body_content
)
# 生成主目录HTML(仅h1和h2)
toc_html = '<div class="toc"><h3>目录</h3><ul>'
current_h1 = None
for tag, id_name, content in toc_entries:
if tag == 'h1':
current_h1 = id_name
toc_html += f'<li><a href="#{id_name}">{content}</a><ul>'
elif tag == 'h2' and current_h1:
toc_html += f'''
<li>
<a href="#{id_name}">{content}</a>
<span class="detail-icon" onclick="showSubPage('{id_name}')">📖</span>
</li>
'''
toc_html += '</ul></li></ul></div>'
# 添加子页面容器
subpage_html = '''
<div id="subpage" class="subpage">
<div class="subpage-header">
<span class="close-btn" onclick="closeSubPage()">×</span>
</div>
<div class="subpage-content-container">
<div class="subpage-toc-container"></div>
<div class="subpage-content"></div>
</div>
</div>
'''
# 完整HTML结构
html_header = f'''
<!DOCTYPE html>
<html>
<head>
<title>Markdown 转 HTML</title>
<meta charset="UTF-8">
<style>
* {{
box-sizing: border-box;
margin: 0;
padding: 0;
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
height: 100vh;
overflow: hidden;
background-color: #f5f7fa;
}}
/* 主页面布局 */
.toc-container {{
width: 280px;
background: linear-gradient(135deg, #2c3e50, #1a2530);
color: #ecf0f1;
height: 100vh;
overflow-y: auto;
padding: 20px 15px;
box-shadow: 3px 0 10px rgba(0,0,0,0.1);
z-index: 10;
}}
.toc h3 {{
margin: 0 0 20px 0;
padding-bottom: 15px;
border-bottom: 1px solid #4a627a;
font-size: 1.4em;
}}
.toc ul {{
list-style: none;
padding-left: 15px;
}}
.toc li {{
margin: 12极狐 0;
position: relative;
}}
.toc a {{
text-decoration: none;
color: #bdc3c7;
display: block;
padding: 8px 5px;
border-radius: 4px;
transition: all 0.3s ease;
}}
.toc a:hover {{
background-color: #34495e;
color: #fff;
padding-left: 10px;
}}
.detail-icon {{
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #3498db;
font-size: 1.2em;
background: rgba(255,255,255,0.1);
border-radius: 50%;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}}
.detail-icon:hover {{
background: rgba(52, 152, 219, 0.3);
color: #fff;
transform: translateY(-50%) scale(1.1);
}}
/* 主内容区样式 */
.content-container {{
flex: 1;
overflow-y: auto;
padding: 30px;
height: 100vh;
background-color: #fff;
}}
/* 子页面样式 */
.subpage {{
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
z-index: 1000;
flex-direction: column;
}}
.subpage-header {{
height: 60px;
background: linear-gradient(135deg, #3498db, #2980b9);
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 25px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}}
.close-btn {{
font-size: 36px;
font-weight: 300;
cursor: pointer;
color: #fff;
transition: transform 0.2s;
}}
.close-btn:hover {{
transform: scale(1.2);
}}
.subpage-content-container {{
display: flex;
flex: 1;
overflow: hidden;
}}
.subpage-toc-container {{
width: 240px;
background-color: #f8f9fa;
height: 100%;
overflow-y: auto;
padding: 20px;
border-right: 1px solid #eaeaea;
}}
.subpage-content {{
flex: 1;
overflow-y: auto;
padding: 30px;
}}
/* 子页面目录样式 */
.sub-toc {{
padding: 15px 0;
}}
.sub-toc h4 {{
margin: 20px 0 10px 0;
color: #2c3e50;
padding-bottom: 8px;
border-bottom: 1px dashed #ddd;
}}
.sub-toc ul {{
list-style: none;
padding-left: 15px;
}}
.sub-toc li {{
margin: 10px 0;
}}
.sub-toc a {{
text-decoration: none;
color: #3498db;
display: block;
padding: 6px 10px;
border-radius: 4px;
transition: all 0.2s;
}}
.sub-t极狐 a:hover {{
background-color: #e3f2fd;
padding-left: 15px;
}}
/* 内容样式 */
.content-section {{
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}}
h1 {{
font-size: 2.2em;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #3498db;
color: #2c3e50;
}}
h2 {{
font-size: 1.8em;
margin: 30px 0 15px 0;
color: #2980b9;
}}
h3 {{
font-size: 1.5em;
margin: 25px 0 12px 0;
color: #3498db;
}}
h4 {{
font-size: 1.3em;
margin: 20px 0 10px 0;
color: #2c3e50;
}}
p {{
font-size: 16px;
line-height: 1.8;
margin-bottom: 15px;
color: #34495e;
}}
table {{
border-collapse: collapse;
width: 100%;
margin: 25px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}}
th, td {{
border: 1px solid #e0e0e0;
padding: 12px 15px;
text-align: left;
}}
th {{
background-color: #f8f9fa;
font-weight: 600;
color: #2c3e50;
}}
tr:nth-child(even) {{
background-color: #fcfcfc;
}}
pre {{
background-color: #2d2d2d;
padding: 20px;
border-radius: 6px;
margin: 20px 0;
overflow-x: auto;
color: #f8f8f2;
}}
code {{
font-family: 'Fira Code', monospace;
background-color: #f1f1f1;
padding: 2px 6px;
border-radius: 4px;
color: #c7254e;
}}
/* Mermaid 图表样式 */
.mermaid {{
background-color: white;
padding: 25px;
margin: 30px 0;
border-radius: 8px;
overflow: visible !important;
text-align: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
border: 1px solid #eaeaea;
}}
</style>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js"></script>
<script>
// 初始化Mermaid
document.addEventListener("DOMContentLoaded", function() {{
mermaid.initialize({{
startOnLoad: true,
theme: 'default',
flowchart: {{
useMaxWidth: false,
htmlLabels: true
}}
}});
// 平滑滚动到锚点
document.querySelectorAll('.toc a, .sub-toc a').forEach(anchor => {{
anchor.addEventListener('click', function(e) {{
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {{
let container = this.closest('.subpage') ?
document.querySelector('.subpage-content') :
document.querySelector('.content-container');
container.scrollTo({{
top: targetElement.offsetTop - 30,
behavior: 'smooth'
}});
}}
}});
}});
}});
// 显示子页面
function showSubPage(sectionId) {{
const section = document.getElementById(sectionId);
if (!section) return;
// 获取该部分所有内容
let contentHtml = section.outerHTML;
let nextElement = section.nextElementSibling;
// 收集直到下一个h1/h2的内容
while (nextElement &&
!['H1', 'H2'].includes(nextElement.tagName)) {{
contentHtml += nextElement.outerHTML;
nextElement = nextElement.nextElementSibling;
}}
// 获取该部分的三四级标题
const tempDiv = document.createElement('div');
tempDiv.innerHTML = contentHtml;
const subHeadings = tempDiv.querySelectorAll('h3, h4');
// 生成子目录
let subTocHtml = '<div class="sub-toc"><h4>内容导航</h4><ul>';
subHeadings.forEach(heading => {{
const id = heading.id;
const text = heading.textContent;
const tag = heading.tagName.toLowerCase();
// 正确变量名:indent
const indent = tag === 'h4' ? 'style="margin-left: 20px;"' : '';
subTocHtml += `
<li ${indent}>
<a href="#${id}">${text}</a>
</li>
`;
}});
subTocHtml += '</ul></div>';
// 更新子页面内容
document.querySelector('.subpage-toc-container').innerHTML = subTocHtml;
document.querySelector('.subpage-content').innerHTML = contentHtml;
// 重新初始化Mermaid图表
mermaid.init(undefined, '.subpage-content .mermaid');
// 显示子页面
document.getElementById('subpage').style.display = 'flex';
}}
// 关闭子页面
function closeSubPage() {{
document.getElementById('subpage').style.display = 'none';
}}
</script>
</head>
<body>
'''
html_footer = '''
</body>
</html>
'''
# 构建完整HTML
full_html_content = f'''
{html_header}
<div class="toc-container">
{toc_html}
</div>
<div class="content-container">
{html_body_content}
</div>
{subpage_html}
{html_footer}
'''
# 保存HTML文件
base_name = os.path.basename(md_filepath)
html_filename = os.path.splitext(base_name)[0] + '.html'
html_filepath = os.path.join(os.path.dirname(md_filepath), html_filename)
with open(html_filepath, 'w', encoding='utf-8') as html_file:
html_file.write(full_html_content)
messagebox.showinfo("成功", f"HTML 文件已成功生成!保存位置:\n{html_filepath}")
except Exception as e:
messagebox.showerror("错误", f"转换过程中出现错误:\n{str(e)}")
def main():
root = Tk()
root.title("Markdown 转 HTML 转换工具")
root.geometry("400x200")
label = Label(root, text="选择 Markdown 文件并生成 HTML 文件:", font=("Arial", 12))
label.pack(pady=20)
convert_button = Button(root, text="选择文件并转换", command=convert_md_to_html, font=("Arial", 10))
convert_button.pack(pady=20)
root.mainloop()
if __name__ == "__main__":
main()
最新发布