利用DeepSeek将AI输出的混合latex标记的markdown文件渲染成html格式 - l1t1/note GitHub Wiki
在与AI对话过程中,它的输出有很多格式,如果用网页版中的复制按钮,得到的是一个混合latex标记的markdown格式文件。直接将这个格式粘贴到诸如github的编辑框,发表后markdown格式显示了,跨行公式可以显示,行内的不行,显得非常怪异。
我们的需求是将markdown标记转换成html,同时保留latex标记包围的内容,然后用MathJax.js渲染公式。
起初直接问DeepSeek和Qwen3, 两者都给出用marked和MathJax结合的方案。遗憾的是,输出结果中的公式没能转换。而分别用上述两个js工具,无论是单独转latex或markdown标记,都没问题。
改为让DeepSeek自己编程转换,结果总是处理不好带反斜杠的标记,如\(
和\)
, 输出遗漏反斜杠,导致渲染失败。
还是回归传统的搜索引擎,找到这篇文章:markdown文档使用mistune转为html,如何保留LaTex中的_不被转码?写mistune子类,介绍用mistune包实现两者同时转换。
我把python代码复制到conv.py, 并把js代码复制到showLaTex.js,执行时报错,说from mistune import Renderer, Markdown, InlineLexer ImportError: cannot import name 'Renderer' from 'mistune' (\Python313\Lib\site-packages\mistune\__init__.py). Did you mean: 'renderers'?
。应该是版本问题。
我把conv.py上传到DeepSeek对话,请他基于当前版本 mistune 3 重写,一次成功,完美转换带$的公式,但反斜杠的标记没有处理,跟他说“要不先简单把(和)替换成$,[和]替换成$$,然后再处理?用这个思路写一个函数backslash_to_dollar,插入到html = markdown(text)一行前调用,只输出这个函数”,这次完成得相当好,我测试了包含两种latex标记的文件都转成功了。这样就可以用浏览器回看对话内容,也可以单独把某段对话输出成pdf格式离线查看,而不用庞大的latex工具。
完整的conv.py内容如下
import re
from mistune import HTMLRenderer, create_markdown
class LaTexRenderer(HTMLRenderer):
def __init__(self, escape=False): # 禁用自动转义
super().__init__(escape)
self.protected = []
def protect_latex(self, text):
"""保护$$...$$之间的LaTeX内容"""
def repl(match):
self.protected.append(match.group(0))
return f'LATEX_BLOCK_{len(self.protected)-1}_'
return re.sub(r'\$\$([\s\S]+?)\$\$', repl, text)
def restore_latex(self, text):
"""恢复被保护的LaTeX块"""
for i, latex in enumerate(self.protected):
text = text.replace(f'LATEX_BLOCK_{i}_', latex)
return text
def block_text(self, text):
"""处理块级文本时保护LaTeX"""
protected = self.protect_latex(text)
result = super().block_text(protected)
return self.restore_latex(result)
def backslash_to_dollar(text):
"""
将LaTeX分隔符转换为更通用的格式:
- 将 \(...\) 转换为 $...$
- 将 \[...\] 转换为 $$...$$
同时保留所有反斜杠不变
参数:
text: 原始Markdown文本
返回:
转换后的文本,其中LaTeX分隔符已标准化
"""
# 处理行内公式 \(...\) → $...$
text = re.sub(
r'\\\((.*?)\\\)',
lambda m: f'${m.group(1)}$',
text,
flags=re.DOTALL
)
# 处理块级公式 \[...\] → $$...$$
text = re.sub(
r'\\\[(.*?)\\\]',
lambda m: f'$${m.group(1)}$$',
text,
flags=re.DOTALL
)
return text
def convert_md_to_html(input_path, output_path):
# 初始化渲染器
renderer = LaTexRenderer()
markdown = create_markdown(renderer=renderer)
# 读取Markdown文件
with open(input_path, 'r', encoding='utf-8') as f:
text = f.read()
text=backslash_to_dollar(text)
# 转换Markdown
html = markdown(text)
# 添加MathJax支持
mathjax_js = '''<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<script src="showLaTex.js"></script>'''
# 写入输出文件
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html + '\n' + mathjax_js)
print(f"Conversion complete. Output length: {len(html)}")
if __name__ == "__main__":
convert_md_to_html("file.md", "index4.html")
showLaTex.js如下,放在与conv.py同一个目录
/*
* name showLaTex.js
* 依赖于 MathJax.js
* varsion: v0.1
*ES6*/
let isMathjaxConfig = false; // 防止重复调用Config,造成性能损耗
const initMathjaxConfig = () => {
if (!window.MathJax) {
return;
}
window.MathJax.Hub.Config({
showProcessingMessages: false, //关闭js加载过程信息
messageStyle: "none", //不显示信息
jax: ["input/TeX", "output/HTML-CSS"],
tex2jax: {
inlineMath: [["$", "$"], ["\\(", "\\)"]], //行内公式选择符
displayMath: [["$$", "$$"], ["\\[", "\\]"]], //段内公式选择符
skipTags: ["script", "noscript", "style", "textarea", "pre", "code", "a"] //避开某些标签
},
"HTML-CSS": {
availableFonts: ["STIX", "TeX"], //可选字体
showMathMenu: false //关闭右击菜单显示
}
});
isMathjaxConfig = true; //
};
if (isMathjaxConfig === false) { // 如果:没有配置MathJax
initMathjaxConfig();
}
// 如果,不传入第三个参数,则渲染整个document
window.MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
// 因为使用的Vuejs,所以指明#app,以提高速度
//window.MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.getElementById('app')]);
感兴趣的可以将下述内容保存为file.md
,放在与conv.py同一个目录测试。
### 最佳容差(Tolerance, `tol`)计算公式:
对于目标精度为 \( P \) 位十进制数的情况,AGM迭代的容差应设为:
\[
tol = 10^{-P} \cdot \exp\left(-\frac{\pi \cdot P}{2 \ln(10)}\right)
\]
#### 公式推导依据:
1. **AGM收敛性**:每次迭代有效位数约翻倍(二次收敛)
2. **误差传播**:最终误差与初始容差的关系为 \( E_{\text{final}} \approx \frac{\pi}{2} \cdot tol \)
3. **精度匹配**:要求 \( E_{\text{final}} \leq 10^{-P} \)
#### 使用说明:
1. 计算步骤:
- 先确定目标精度 \( P \)(如100万位则 \( P=10^6 \))
- 代入公式计算 \( tol \)
- 设置GMP上下文精度为 \( \lceil P \cdot \log_2(10) \rceil + 50 \)(二进制位缓冲)
2. 特性:
- 当 \( P=6\times10^6 \)(600万位)时,\( tol \approx 10^{-6.5\times10^6} \)
- 自动保证迭代在约 \( \log_2(P) + 4 \) 次后终止
#### 数学验证:
对于43次迭代达到600万位精度的案例:
\[
P=6\times10^6 \implies tol \approx 10^{-6.5\times10^6} \\
\text{迭代次数} = \left\lceil \log_2\left(\frac{\ln(10^{6.5\times10^6})}{\ln(2)}\right) \right\rceil = 43
\]