资讯详情

相邻强调与加粗混排解析规范:marked 的 em/strong 相邻分隔符测试与底层实现剖析

📅 2026/10/10 2:15:21 | 华诺云谱 👁 阅读
相邻强调与加粗混排解析规范:marked 的 em/strong 相邻分隔符测试与底层实现剖析
前端【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址https://gitcode.com/gh_mirrors/ma/marked点击查看免费下载本文围绕 marked 仓库中的回归测试规范 em_strong_adjacent_mixed.md 展开系统讲解强调*/_与加粗**/__分隔符相邻混排adjacent mixed时的解析行为并结合 Tokenizer.ts 的emStrong实现与 rules.ts 中的分隔符正则剖析 marked 如何判定左/右分隔符并配对生成嵌套 token同时给出在仓库内运行与验证测试的完整方法。一、测试规范定位一组针对“相邻混排”的回归用例在 marked 的测试体系中test/specs/new目录存放着 CommonMark 与 GFM 标准之外的新增回归测试用例用于锁定项目自身的行为边界。每一条用例都由一对同名文件组成*.mdMarkdown 输入源*.html期望输出expected output。测试运行器 run-spec-tests.js 会把new目录下的全部用例加载进来逐个用new Marked(options).parse(markdown)生成 HTML再与.html文件比对见 run-spec-tests.js 中getTests([./specs/new])与runTests({ tests: newTests, parse })。em_strong_adjacent_mixed.md正是这一体系中的一员。它专门构造强调分隔符与加粗分隔符紧贴相邻的输入检验解析器在“前一个 span 刚结束、后一个 span 立即开始”的边界上能否正确配对。二、用例内容四组相邻混排输入该规范文件全文共四组用例每组之间以空行分隔即四个独立段落_**foo**_ **bar** _**foo**_ **bar** _**foo**_ *__foo__* __bar__ *__foo__* __bar__ *__foo__*这四组用例刻意覆盖了两类关键组合分隔符符号交叉_**_下划线强调 星号加粗与*__星号强调 下划线加粗两种交叉写法邻接形态既有“单个 span 后紧跟另一 span”_**foo**_ **bar**也有“span 与 span 之间紧贴无空格、再跟第三个 span”_**foo**_ **bar** _**foo**_。注意第二、四组中第二个与第三个 span 之间没有空格**bar** _**foo**_这比第一、三组更考验分隔符配对逻辑因为它要求解析器在结束**bar**后立刻识别新的_**foo**_起始分隔符。三、期望输出每对分隔符独立成 span对应期望文件 em_strong_adjacent_mixed.html 给出了标准输出pemstrongfoo/strong/em strongbar/strong/p pemstrongfoo/strong/em strongbar/strong emstrongfoo/strong/em/p pemstrongfoo/strong/em strongbar/strong/p pemstrongfoo/strong/em strongbar/strong emstrongfoo/strong/em/p由此可以提炼出本规范的核心断言输入期望结构_**foo**_emstrongfoo/strong/em下划线包裹星号外层 em、内层 strong**bar**strongbar/strong独立 strong*__foo__*emstrongfoo/strong/em星号包裹下划线同样外层 em、内层 strong__bar__strongbar/strong独立 strong也就是说无论分隔符是*还是_内层双字符分隔符**或__一律优先配对为 strong外层单字符分隔符*或_配为 em且相邻的多个 span 互不吞并、互不干扰。这一行为与 CommonMark 强调规则相邻 span 应独立成段一致而 marked 将其固化为了回归用例。四、源码级剖析emStrong 如何完成配对4.1 调用链从 Lexer 到 Tokenizer行内解析的入口在 Lexer.ts 的inlineTokens循环中。解析器按顺序尝试各类行内 token其中强调/加粗的处理位于// em strong if (token this.tokenizer.emStrong(src, maskedSrc, prevChar)) { src src.substring(token.raw.length); tokens.push(token); continue; }见 Lexer.ts。这里传入了三个关键参数src剩余待解析源码maskedSrc将链接、代码、HTML 等内容“屏蔽”后的源码副本用于防止强调分隔符误配链接标题等内部字符prevChar前一个已消费的字符用于判定当前分隔符是否处于“词中”位置。4.2 emStrong 的分隔符配对算法核心实现在 Tokenizer.ts 的emStrong方法中其工作流程可概括为五步匹配左分隔符用this.rules.inline.emStrongLDelim匹配当前串首的分隔符序列下划线词中规则若左分隔符是_且前一字符为字母数字Unicode 字母/数字直接返回不匹配——这正是_不能出现在单词中间的原因如foo_bar_baz中的_不会被视为强调扫描右分隔符根据分隔符字符选择右分隔符正则——*用emStrongRDelimAst_用emStrongRDelimUnd在maskedSrc中循环扫描计数配对通过累计“剩余可关闭的分隔符数量”delimTotal找到第一个能把左分隔符全部抵消的右分隔符位置生成 token取左右两侧分隔符数量的最小值若为奇数则生成emtoken若为偶数则生成strongtoken并用this.lexer.inlineTokens(text)递归解析内部文本。// Create em if smallest delimiter has odd char count. *a*** if (Math.min(lLength, rLength) % 2) { const text raw.slice(1, -1); return { type: em, raw, text, tokens: this.lexer.inlineTokens(text) }; } // Create strong if smallest delimiter has even char count. **a*** const text raw.slice(2, -2); return { type: strong, raw, text, tokens: this.lexer.inlineTokens(text) };见 Tokenizer.ts。以_**foo**_为例左侧是_**右侧是**_最小分隔符数量为 1奇数故外层生成emem 的内容**foo**递归再解析出strong。这与期望输出emstrongfoo/strong/em完全吻合。4.3 分隔符“左右属性”的判定六类规则emStrong之所以能正确处理_**foo**_ **bar**这类“上一个 span 刚结束、下一个 span 立刻开始”的场景关键在于 rules.ts 中对每个分隔符串严格分类为只能左、只能右或左右皆可以星号右分隔符为例rules.ts(1) #*** 只能右标点后、空格/行尾前 (2) a***# / a*** 只能右非标点非空格后、标点空格或行尾前 (3) #***a / ***a 只能左标点/空格后、非标点前 (4) ***# 只能左空格后、标点前 (5) #***# 左右皆可标点包围 (6) a***a 左右皆可非标点非空格包围下划线版本emStrongRDelimUnd在正常模式下故意省略了规则 6即a___a不能作为强调闭合见 rules.ts 注释 “Normal: no rule 6 for _”。对相邻混排用例而言_紧跟在**bar**的**之后、foo之前此时_处于“非标点非空格字符之后、非标点字符之前”属于只能左的分隔符对应规则 6 / 规则 3因此被正确识别为下一个 span 的起始而不是试图与前文配对。这就是第二、四组用例中**bar** _**foo**_能各自独立成 span 的底层原因。4.4 特殊分支mid-run opener 与 CommonMark 规则 9–10源码中还包含两处不易察觉的边界处理正好服务于“相邻/混排”这一主题mid-run opener当prevChar delimChar例如**a*b*c中第二个*的 prevChar 也是*时该 opener 只能与“只能右”的分隔符配对否则它会在**a处提前闭合抢走后续 span 的开头源码注释原文“A mid-run opener … otherwise it steals the opener of a later span”。见 Tokenizer.ts 与 Tokenizer.ts。CommonMark 规则 9–10当遇到左右皆可的模糊分隔符时若lLength % 3 ! 0且(lLength rLength) % 3 0则先记录到midDelimTotal并继续扫描即“三字符分隔符必须整体配对”避免*a**b*类输入产生错误嵌套。见 Tokenizer.ts。这些分支与相邻用例同样相关_**foo**_中的_之后紧贴**构成“下划线 双星”的连续分隔符流midRun判定与三字符规则正是为这类“连续、混合、相邻”的分隔符序列设计的。五、姊妹用例单字符相邻组合的完整矩阵与em_strong_adjacent_mixed.md配套仓库中还有 em_strong_adjacent.md它覆盖了单分隔符相邻的 8 种组合构成一张完整的交叉矩阵_te_*st* _te_**st** *te*_st_ *te*__st__ __te__*st* __te__**st** **te**_st_ **te**__st__其期望输出 em_strong_adjacent.html 显示_te_之后紧贴*st*会被解析为emte/ememst/em即相邻的强调 span 同样彼此独立pemte/ememst/em/p pemte/emstrongst/strong/p ...这两个文件一“简”单字符一“混”单字符双字符共同定义了 marked 对“相邻强调/加粗”的全部行为边界。六、验证与扩展如何在仓库内运行测试6.1 运行全部 specs 测试仓库根目录执行相关脚本定义见 package.jsonnpm run build npm run test:specs:only其中test:specs:only对应的命令为node --test --test-only --test-reporterspec test/run-spec-tests.js会依次加载commonmark、gfm、new、original、redos五类规范用例并逐一比对输出见 run-spec-tests.js。em_strong_adjacent_mixed即属于其中的new类别。6.2 手动复现解析结果不使用构建产物时可直接调用源码入口验证npx marked --gfm --pedanticfalse然后在标准输入中粘贴em_strong_adjacent_mixed.md的内容观察输出是否与 em_strong_adjacent_mixed.html 一致。此外仓库提供的在线实验页 docs/demo/index.html配套 demo.js也可用于交互式验证同一组输入。6.3 修改用例仅供实验仓库只读若要在本地实验新边界可临时参考 update-specs.js 与 utils.js 了解用例的组织与断言方式。注意当前仓库为只读不应直接改动测试文件仅建议通过命令行输入或 demo 页面验证。七、小结从一条 spec 看 marked 的解析哲学em_strong_adjacent_mixed.md虽然只有四行输入却浓缩了 marked 行内强调解析的三层设计规则先行emStrongLDelim/emStrongRDelimAst/emStrongRDelimUnd等正则rules.ts预先对分隔符的左右属性做了穷举分类杜绝“见分隔符就配对”的简单贪心递归配对emStrong在 Tokenizer.ts 中通过左右分隔符计数与inlineTokens递归正确产出 em/strong 嵌套与相邻独立性回归固化将这些边界行为固化为new规范用例由 run-spec-tests.js 在每次测试中把关防止后续改动破坏相邻混排的输出一致性。对于任何需要在自有项目中集成或定制 marked 强调行为的开发者这条规范连同其姊妹用例 em_strong_adjacent.md都是理解“分隔符如何被判定、如何配对、如何嵌套”的最佳入门样本。赞分享前端【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址https://gitcode.com/gh_mirrors/ma/marked点击查看免费下载相关推荐marked 相邻强调与加粗定界符解析em_strong_adjacent 规范测试深度解析marked 相邻强调与加粗定界符解析 em_strong_adjacent 规范测试深度解析 导读 本文以 marked 仓库中 test/specs/n前端抖音视频批量下载免水印免费开源工具 douyin-downloader 十分钟上手抖音视频批量下载免水印免费开源工具 douyin downloader 十分钟上手 douyin downloader 是一款开源的抖音批量下载工具主页链接网页爬虫CLImarked 引用式链接与行内强调嵌套解析以 em_list_links 测试用例剖析 em/strong/code 与 reflink 的协同机制marked 引用式链接与行内强调嵌套解析以 em_list_links 测试用例剖析 em/strong/code 与 reflink 的协同机制 mark前端上一篇LinkSwift九大网盘直链下载的智能助手告别繁琐操作下一篇libwebsockets 内存证书 TLS 最小 HTTP 服务器实战minimal-http-server-tls-mem 原理与配置详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑