如何使用Golang构建简易Markdown解析器_Golang文本解析与HTML生成方法
技术百科
P粉602998670
发布时间:2025-12-31
浏览: 次 不建议从零实现Markdown解析器,因CommonMark规范有20+边界case,goldmark等成熟库已稳定解决嵌套、缩进、HTML混合等问题;推荐用goldmark禁用非必要扩展并自定义渲染。
为什么不用现成库而自己写 Markdown 解析器
除非你只处理极简的 Markdown 片段(比如仅支持 **bold**、*italic*、`code` 和换行),否则不建议从零实现完整解析器。CommonMark 规范有 20+ 边界 case,比如嵌套强调、列表缩进对齐、HTML 内联混合等,blackfriday、goldmark 已经稳定维护多年。自己写容易在 ***abc*** 或 > > blockquote 这类嵌套场景产出错误 HTML。
用 goldmark 实现可控的简易解析(推荐路径)
goldmark 是目前最符合 CommonMark v0.30 的 Go 库,扩展性好、无 CGO 依赖、API 清晰。所谓“简易”,是指禁用不需要的扩展(如表格、脚注),并自定义渲染规则。
- 默认开启所有扩展,需显式关闭:用
WithExtensions()传入空切片或按需排除 - 关键控制点在
goldmark.WithRenderer()—— 你可以继承html.Renderer并重写RenderText、RenderStrong等方法,避免生成而改用或添加 class - 若只需纯文本提取(如预览摘要),直接用
parser.Parse(text)+ 遍历 AST 节点,比生成 HTML 更轻量
package mainimport ( "bytes" "github.com/yuin/goldmark" "github.com/yuin/goldmark/renderer/html" )
func main() { md := goldmark.New( goldmark.WithExtensions(), // 不传任何扩展 → 只支持基础语法 goldmark.WithRendere
r(html.NewRenderer( html.WithUnsafe(), // 允许原始 HTML(如需保留 @@##@@) )), ) var buf bytes.Buffer err := md.Convert([]byte("# Hello\n\nworld"), &buf) if err != nil { panic(err) } println(buf.String()) // 输出:
Hello
\nworld
\n }
手动解析时如何安全处理 inline 强调标记
如果坚持手写(例如嵌入到已有 parser 中),重点不是匹配 * 或 _,而是遵守「左边界」和「右边界」规则:强调符必须前后紧邻非空白/非标点字符,且成对出现、不跨行。常见错误是用正则 \*(.*?)\* 导致贪婪匹配或忽略嵌套。
- 正确做法:扫描字节流,记录未闭合的强调符位置(
stack),遇到匹配符时检查栈顶类型是否一致、是否满足边界条件(如前一个字符不能是字母/数字) - 特别注意:
**a**b**应解析为ab**,而非整个a**b - Go 标准库
strings.Index和bytes.IndexByte比正则更快,适合单次扫描
HTML 输出中容易被忽略的转义细节
Markdown 输入里的 、&、、> 必须转义,但已由 goldmark 的 html.Renderer 自动处理;真正易漏的是自定义渲染器里手动拼接字符串时:
- 不要直接
fmt.Sprintf("——%s
", text)text中的&会变成&双重编码 - 应使用
html.EscapeString(text)(来自net/html)确保只转义一次 - 若允许用户输入 HTML 片段(如 `
`),需配合html.UnescapeString或白名单过滤,不能简单放行复杂点永远在边界:AST 构建是否支持中断恢复、内联 HTML 是否影响后续解析、代码块缩进是否以 4 空格为唯一标准——这些在
goldmark里已覆盖,自己写时最容易卡在某一个缩进差 1 空格的 case 上。
# ai
# 的是
# 这类
# 你可以
# 是指
# 重写
# markdown
# 已有
# 不需要
# 只需
# 自定义
# go
# golang
# class
# html
# 编码
# 字节
# 标准库
# 字符串
# git
# github
# 为什么
# 栈
# 继承
# 切片
# 遍历
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Mac电脑如何恢复出厂设置_Mac抹掉数据并重装系
- 如何使用Golang指针与接口结合_实现方法调用和
- Python配置文件操作教程_JSONINIYAM
- Win10怎样设置多显示器_Win10多显示器扩展
- Windows系统被恶意软件破坏后的恢复策略_错误
- php打包exe后无法读取环境变量_变量配置方法【
- 如何使用Golang构建基础消息队列模拟_Gola
- Win11怎么硬盘分区 Win11新建磁盘分区详细
- 如何在 Windows 11 中使用 AlomWa
- mac怎么右键_MAC鼠标右键设置与触控板手势技巧
- Go 中 := 短变量声明的类型推导机制详解
- php嵌入式日志记录怎么实现_php将硬件数据写入
- Python多进程教程_multiprocessi
- 微信短链接怎么还原php_用浏览器开发者工具抓包获
- 如何使用 Python 合并文件夹内多个 Exce
- Windows7怎么找回经典开始菜单_Window
- 如何用正则与预处理高效拦截带干扰符的恶意域名
- Win11怎么忘记WiFi网络_Win11删除已保
- 如何提升Golang JSON序列化性能_Gola
- Win11怎么设置声音输出设备_Windows11
- Mac如何彻底清理浏览器缓存?(Safari与Ch
- VSC怎样在Linux运行PHP_Ubuntu系统
- Python技术债务管理_长期维护解析【教程】
- Python函数接口稳定性_版本演进解析【指导】
- 如何在 Go 中正确反序列化多个同级 XML 元素
- Mac电脑进水了怎么办_MacBook进水后紧急处
- Win11摄像头无法使用怎么办_Win11相机隐私
- PHP 中 require() 语句返回值的用法详
- 如何使用Golang defer优化性能_减少不必
- Win11搜索栏无法输入_解决Win11开始菜单搜
- Win11键盘快捷键大全_Windows 11常用
- Python路径拼接规范_跨平台处理说明【指导】
- php后缀怎么变mp4能播放_让php伪装mp4正
- 如何在Golang中修改数组元素_通过指针实现原地
- Win11怎么看电池循环次数_Win11笔记本电池
- 如何使用Golang sort排序切片_Golan
- Windows笔记本无法进入睡眠模式怎么办?(电源
- Windows10蓝屏SYSTEM_SERVICE
- 如何在 Python 测试中动态配置 @backo
- Win11怎么更改管理员名字 Win11修改账户名
- Win10怎么卸载爱奇艺_Win10彻底卸载爱奇艺
- c++中如何对数组进行排序_c++数组排序算法汇总
- Win11如何开启telnet服务 Win11启用
- 零基础学会Python自动化办公_高效处理Exce
- C++友元类使用场景_C++类间协作设计方式讲解
- Windows 11怎么更改锁屏超时时间_Wind
- PythonGIL机制理解_多线程限制解析【教程】
- Windows10如何更改计算机工作组_Win10
- SAX解析器是什么,它与DOM在处理大型XML文件
- 如何减少Golang内存碎片化_Golang内存分

r(html.NewRenderer(
html.WithUnsafe(), // 允许原始 HTML(如需保留 @@##@@)
)),
)
var buf bytes.Buffer
err := md.Convert([]byte("# Hello\n\nworld"), &buf)
if err != nil {
panic(err)
}
println(buf.String()) // 输出:
QQ客服