尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
marked 的 Markdown 解析边界:从 docs/broken.md 看引擎差异与列表/引用块实现原理
marked 的 Markdown 解析边界从 docs/broken.md 看引擎差异与列表/引用块实现原理【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked本文以 docs/broken.md 为核心系统梳理各 Markdown 引擎markdown.pl、markdown.js、sundown/upskirt、discount 等在列表、代码块、引用块、HTML 块等解析上的行为差异并对照 marked 的实际输出与 src/Tokenizer.ts、src/rules.ts 的源码实现解释 marked 为何在这些边界场景中给出更合理的结果。读完本文你将理解 Markdown 解析中缩进、嵌套与惰性续行的底层规则掌握用 marked CLI 复现这些差异的验证方法并了解仓库测试是如何固化这些行为的。一、为什么会有 broken.md 这份文档docs/broken.md是 marked 作者多年来收集的 markdown 引擎怪癖 笔记。它的价值不在于规范 Markdown 语法而在于展示一个事实在没有统一规范CommonMark 规范直到 2014 年才发布首个版本的年代不同引擎对同一段输入的解析结果可以天差地别甚至产生非法的 HTML。文档明确指出许多例子只拿某个引擎与 marked 对比但 markdown.pl 的例子几乎可以原样套用到 discount、upskirt 或 markdown.js 上而且会暴露出更多不一致。作者的写作背景是对引擎间不一致感到非常不满因此文中语气带有情绪化表达但这并不影响其技术价值——它是一份难得的引擎行为差异对照表。从 package.json 可知marked 是 A markdown parser and compiler. Built for speed.其命令行入口是bin/marked.js由 bin/main.js 实现。本文所有 marked 输出均可通过npx marked从 stdin 读入、Ctrl-D 结束复现。二、列表解析的愚蠢示例缩进感知是分水岭2.1 例一列表项间的文本归属文档第一个例子输入为* item1 * item2 textmarkdown.pl 输出lipitem1/p ... pptext/p/li产生了p套p、/ul/p这类错位的嵌套标签HTML 结构非法marked 输出lipitem1/pulliitem2/li/ulptext/p/li结构完整闭合。差异根源在于缩进感知indentation-aware解析。在 src/Tokenizer.ts 的list()方法中marked 通过line.search(nonSpaceChar)找到首行第一个非空白字符以此计算indent再决定后续行归属哪个列表项而 markdown.pl 基于正则逐行扫描遇到缩进就断片最终把text错误地并入了前一个li。2.2 例二列表项内嵌引用块输入* hello worldmarkdown.plpullihello/pblockquotepworld/li/ul/p/blockquote——ul出现在p内、/blockquote出现在/ul外完全错位sundownupskirtlihello\ngt; world/li——把 world当成普通文本并转义根本不识别引用块markedullihelloblockquotepworld/p/blockquote/li/ul引用块正确嵌套在列表项内。marked 的实现依据在 src/Tokenizer.ts列表项解析循环中专门检查blockquoteBeginRegex定义于 src/rules.ts一旦发现^ {0,indent}开头的行就结束当前列表项、将引用块交给 blockquote 处理。仓库中的测试用例 test/specs/new/blockquote_list_item.md 第一行就写着 This fails in markdown.pl and upskirt其后输入正是* hello world说明该项目把这类边界场景固化为回归测试。2.3 例三代码块缩进的两难输入缩进 6 空格* hello * world * hi codemarkdown.plcode没有变成代码块而是被吞进lihi\n code/li再增加两个空格8 空格超过常见的 4 空格缩进规则后markdown.pl 依然不识别代码块且第三个列表项hi甚至没有被解析为独立列表项——这正是文档所说的indentation unaware parsingmarkedprecodevar a 1;/code/pre正确生成代码块。关键在 src/Tokenizer.ts 的这行注释与逻辑indent line.search(this.rules.other.nonSpaceChar); // Find first non-space char indent indent 4 ? 1 : indent; // Treat indented code blocks ( 4 spaces) as having only 1 indentmarked 把超过 4 空格的首行缩进按代码块处理缩进计为 1从而允许代码块在列表项中以合理的方式出现。文档在此处的反问Why shouldnt code blocks be able to appear in list items in a sane way? 正是 marked 的设计取向。而 src/Tokenizer.ts 中 4的 indented code block 分支则为列表项内嵌代码块提供了第二个层次的判断。2.4 例四复杂嵌套列表输入* hello * world how are you * today * himarkdown.plhow被吞入world项、are you被当作列表外层段落、today与hello同级错乱markedworld/how与are/you各自成段、today正确成为hello项的二级列表兄弟项、hi成为一级列表兄弟项结构完全符合直觉。这与 src/Tokenizer.ts 的列表项收集循环有关marked 使用nextBulletRegex(indent)、hrRegex(indent)、fencesBeginRegex(indent)等一组按当前缩进动态生成的正则见 src/rules.ts 附近的cachedIndentRegex工具逐行判断后续行应归属、跳出还是开启新块从而保持嵌套结构的正确闭合。三、引用块的歧义markdown.js 的三个翻车现场3.1 连续引用块被吞并输入 a b cmarkdown.jsblockquotepa/ppbundefinedgt; c/p/blockquote——第二个引用块的开头被吞成文本还莫名输出undefinedmarked输出三个相互独立的blockquote每个含一段p。marked 的引用块实现在 src/Tokenizer.ts 的blockquote()先按blockquoteStartsrc/rules.ts^ {0,3}切分连续引用行若遇到空行间断则停止收集、返回当前块从而保证相邻引用块互不干扰。若引用块后面紧跟列表还会在 src/Tokenizer.ts 走 include continuation in nested list 分支做合并处理。3.2 图片嵌套链接解析输入an imagemarkdown.jsa href/image)](/linkan image/a——把)和 会按image→link的优先级在括号匹配完整的前提下逐层解析](结构不会被错误消耗。文档末尾附有对应 issuemarkdown-js#24/#27 等的链接属于历史佐证。3.3 行内 HTML 块的直通输入divhello/div spanhello/spanmarkdown.js把div和span都转义成lt;divgt;文本markeddivhello/div原样输出作为 block-level HTML 块直通spanhello/span则包进p。这源于 src/Tokenizer.ts 的html()方法marked 使用 src/rules.ts 中_tag定义的 block-level 标签清单address|article|aside|base|basefont|blockquote|body|caption|...|div|...命中则产生type: html、block: true的 token 原样透传而span不在块级清单内退回普通行内 HTML 处理并包裹p。四、深入源码这些行为是设计而非巧合将上文现象对照源码可以总结出 marked 在列表与引用块上的三条核心设计缩进即结构list()中indent的计算src/Tokenizer.ts贯穿整个列表项收集循环缩进决定行归属、决定是否开启代码块/引用块/新列表项块级中断interrupt规则列表项循环中按顺序检查 fences、heading、html、blockquote、新 bullet、hr 的起始正则src/Tokenizer.ts任何一种命中都会结束当前列表项交由对应 tokenizer 处理——这与 src/rules.ts 中lheading、_paragraph等规则里blockquote/list/html可中断段落的设定一脉相承引用块内部按顶层重解析blockquote()剥离前缀后调用this.lexer.blockTokens(currentText, tokens, true)且临时置state.top truesrc/Tokenizer.ts将引用内容当作顶层 token 流重新解析因此引用块内的列表、嵌套引用、代码块都能获得与正文一致的解析结果。仓库测试目录 test/specs/new/ 中除了上文提到的 blockquote_list_item.md还有 nested_blockquote_in_list.md覆盖引用块作为列表项子级/兄弟级/父级三种嵌套位置、adjacent_lists.md、tricky_list.md 等共同构成对列表/引用块边界行为的回归保障。这些.md文件与同名.html文件一一对应如 blockquote_list_item.html由 test/run-spec-tests.js 驱动比对任何解析回归都会在 CI 中暴露。五、动手复现用 marked CLI 验证引擎差异文档中的对照均在 shell 中完成你可以用相同的流程亲手验证# 以第一个列表示例为例从 stdin 读入Ctrl-D 结束 npx marked * item1 * item2 text ^D # 输出应为 # ul # lipitem1/p # ul # liitem2/li # /ul # ptext/p # /li # /ul若本地已安装 markedbin字段指向bin/marked.js也可直接调用printf * hello\n world\n | ./bin/marked.js # ullihello blockquotepworld/p/blockquote/li/ul想要观察 token 流而非 HTML可使用 bin/main.js 提供的--tokens能力输出JSON.stringify(marked.lexer(data, options), null, 2)它会把list、blockquote、code等 token 及loose、ordered、start等元信息打印出来便于理解 marked 是如何对上述输入分层的。六、小结从 broken.md 到健壮解析docs/broken.md收集的怪癖在今天看来多数已被 CommonMark 规范收敛但它的方法论依然有效用边界输入去戳穿引擎的实现假设。对照 marked 的 src/Tokenizer.ts 与 src/rules.ts 可以看到marked 对列表缩进、块级中断、引用块重解析的处理是显式设计的并且通过 test/specs/new/ 下成对的.md/.html用例固化为可回归的契约。如果你的业务场景需要把用户输入的 Markdown 渲染成可信的 HTML尤其是列表、引用、代码块混排的富文本理解这些边界行为能帮你预判渲染结果、规避 XSS 或结构错乱风险并在必要时通过 docs/USING_ADVANCED.md 所述的扩展机制定制解析行为。【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

Grafana Tempo 所依赖的 OTel Go Prometheus Exporter 实验特性:OTEL_GO_X_OBSERVABILITY 可观测性开关与指标体系详解

Grafana Tempo 所依赖的 OTel Go Prometheus Exporter 实验特性:OTEL_GO_X_OBSERVABILITY 可观测性开关与指标体系详解

后端可观测性链路追踪 【免费下载链接】tempo Grafana Tempo is a high volume, minimal dependency distributed tracing backend. 项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo 点击查看 免费下载 本文以 Grafana Tempo 仓库内 vendor/go.opente…

📅 2026/9/19 17:48:40
肌电图基本操作课件设计:从信号原理到可复现流程的完整指南

肌电图基本操作课件设计:从信号原理到可复现流程的完整指南

简介:这份《肌电图基本操作》PPT课件面向神经电生理初学者、临床规培医师及康复医学相关专业学生,系统梳理肌电图检查的基础理论与操作要点,帮助读者建立从概念到判读的完整知识框架。资源包内含1个PPT文件,压缩包约4.8MB&#xf…

📅 2026/9/19 17:48:40
OpenDesign 中的 Framer 设计系统包:Agent 驱动的黑蓝极简视觉契约全解析

OpenDesign 中的 Framer 设计系统包:Agent 驱动的黑蓝极简视觉契约全解析

AI 应用人工智能AI 技能设计系统媒体生成 【免费下载链接】open-design 🎨 Best DeepSeek Harness Design Plugin. The open-source Claude Design alternative. 🖥️ Local-first desktop app. 🖼️ Your coding agent becomes the design e…

📅 2026/9/19 17:48:40
MORE NEWS

更多资讯

📰

PyPTO vf.truncate 详解:向量寄存器浮点截断指令的语义、实现与实战

人工智能编译器模型编译高性能计算深度学习CANN 【免费下载链接】pypto PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。 项目地址: https://gitcode.com/cann/pypto 点击查看 免费下载 导读 vf.truncate 是 …

📰

PicoClaw 接入 QQ 开放平台机器人:官方 API 配置、部署与源码解析

人工智能AI 应用AI Agent交互助手工具调用MCP ClientsAgent 记忆 【免费下载链接】picoclaw Tiny, Fast, and Deployable anywhere — automate the mundane, unleash your creativity 项目地址: https://gitcode.com/gh_mirrors/pi/picoclaw 点击查看 免费下载 导…

📰

NumPy 用户自定义 DType 的 Partition / Argpartition 支持:基于 ArrayMethod API 实现自定义分区算法

NumPy 用户自定义 DType 的 Partition / Argpartition 支持:基于 ArrayMethod API 实现自定义分区算法 【免费下载链接】numpy The fundamental package for scientific computing with Python. 项目地址: https://gitcode.com/gh_mirrors/nu/numpy 本篇技术…

📰

EsDA低代码实现Modbus RTU Master转UDP Client协议转换

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

大文件分片上传实战:Blob切片、秒传与断点续传五层追问

1. 大文件分片上传到底在解决什么问题1.1 从一个真实场景说起去年帮一个做在线教育的朋友处理课件上传的问题,他们平台允许老师上传录播视频,单个文件动辄两三个G。最开始用的是最朴素的方式——一个FormData把整个文件塞进去,axios发一个 PO…

📰

C# HttpClient跳过HTTPS证书验证的三种写法与踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬