news 2026/9/20 6:08:21

marked 如何识别并拒绝无效 HTML 标签:深入解析 html_invalid_tag_name 规范测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
marked 如何识别并拒绝无效 HTML 标签:深入解析 html_invalid_tag_name 规范测试

marked 如何识别并拒绝无效 HTML 标签:深入解析 html_invalid_tag_name 规范测试

【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked

导读

本文以 marked 仓库中 html_invalid_tag_name.md 这份规范测试规格为切入点,完整剖析 marked(一个面向性能设计的 Markdown 解析与编译器)如何判定 HTML 标签名的合法性:哪些"貌似标签"的输入会被当作真实 HTML 原样输出,哪些会被识别为无效标签而转义为普通文本。读完本文,你将掌握 marked 块级与行内 HTML 识别的双轨正则机制、标签名合法字符集约束,以及一个 spec 测试用例从输入到期望输出的完整判定链路。

一、测试规格原文:三组"无效标签"输入

该规格位于 test/specs/new/html_invalid_tag_name.md,输入仅有三组"伪标签":

foo <bar_baz> qux foo </a:b> qux <div_>

其对应的期望输出记录在同目录的 html_invalid_tag_name.html 中:

<p>foo &lt;bar_baz&gt; qux</p> <p>foo &lt;/a:b&gt; qux</p> <p>&lt;div_&gt;</p>

三组输入分别覆盖三种常见的"非法标签名"形态:

输入片段非法原因期望输出
<bar_baz>标签名含下划线_&lt;bar_baz&gt;(作为普通文本转义)
</a:b>闭合标签名含冒号:&lt;/a:b&gt;(作为普通文本转义)
<div_>标签名以_结尾,含非法字符&lt;div_&gt;(作为普通文本转义)

注意三个关键行为:

  1. 非法标签不会被当作 HTML 原样透传,而是退化为普通文本
  2. 退化后的文本在输出时,尖括号会被转义为&lt;/&gt;
  3. 每个输入行仍构成独立的<p>段落,说明这些"伪标签"没有打断段落的正常解析流程。

二、块级与行内:marked 对 HTML 的双轨识别机制

marked 对 HTML 的识别分为块级行内两套独立规则,均定义在 src/rules.ts 中。要理解上述规格为何成立,必须先弄清这两条规则各自的门槛。

2.1 块级 HTML:白名单标签 + 行结构约束

块级 HTML 正则见 src/rules.ts,它定义了七种类型(源码注释中编号 (1)~(7)):

  1. <script|pre|style|textarea>起始的整块原始内容;
  2. HTML 注释<!-- ... -->
  3. 处理指令<? ... ?>
  4. 声明<!A-Z ...>(如<!DOCTYPE html>);
  5. CDATA 段<![CDATA[...]]>
  6. 白名单标签_tag构成的开关标签(</?(tag)(?: +|\n|/?>)),内容延伸到空行;
  7. 整行式的普通开/闭标签,要求标签名匹配[a-z][a-z0-9-]*且标签后必须紧跟行尾((?=[ \t]*(?:\n|$)))。

其中类型 (6) 使用的白名单_tag定义在 src/rules.ts,是一份固定的 HTML 标签列表:

address|article|aside|base|basefont|blockquote|body|caption |center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption |figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe |legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option |p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title |tr|track|ul

回到本规格:<bar_baz></a:b><div_>都不在白名单中(类型 6 不匹配),且bar_baza:bdiv_的标签名含_:,不满足类型 (7) 的[a-z][a-z0-9-]*字符集约束,因此块级 HTML 规则整体拒绝这三段输入。

2.2 行内 HTML:标签名字符集是唯一准绳

当块级规则不命中时,行内解析器会用行内tag正则再次尝试,该正则定义在 src/rules.ts:

'^comment' + '|^</[a-zA-Z][a-zA-Z0-9-]*\\s*>' // 闭合标签 + '|^<[a-zA-Z][a-zA-Z0-9-]*(?:attribute)*?\\s*/?>' // 开标签 + '|^<\\?[\\s\\S]*?\\?>' // 处理指令 + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // 声明 + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>' // CDATA 段

行内 HTML 标签名的判定规则是:以 ASCII 字母开头,后跟零个或多个字母、数字或连字符([a-zA-Z][a-zA-Z0-9-]*。这与 CommonMark 0.31.2 规范对标签名的定义一致(仓库测试基准见 test/specs/commonmark/commonmark.0.31.2.json)。

逐一验证本规格的三组输入:

  • bar_baz_不在[a-zA-Z0-9-]字符集内 → 行内 tag 正则不匹配;
  • a:b:同样不在字符集内(注意:属性名允许冒号,见 src/rules.ts 中attribute[a-zA-Z:_],但标签名不允许)→ 不匹配;
  • div_:末尾的_导致整体不满足字符集约束 → 不匹配。

正是这套"标签名字符集白名单",让所有三个"貌似标签"的片段被行内规则拒绝。

三、无效标签的归宿:退化为文本并被转义

行内与块级规则双双拒绝之后,这些片段会落入普通文本 token 的处理路径。此时尖括号必须被转义,否则会破坏 HTML 安全性。转义由 src/helpers.ts 中的escapeHtmlEntities完成,它依据转义表将<>&"'等特殊字符替换为对应实体(源码第 10~12 行可见"&quot;'&#39;等映射)。

因此<bar_baz>先退化为纯文本<bar_baz>,再在渲染阶段被转义为&lt;bar_baz&gt;,最终落入<p>段落标签之间输出。这也是 CommonMark 安全模型的关键一环:未通过标签名校验的内容绝不原样透传到输出 HTML 中,从而避免用户输入中的伪标签被当作真实标签注入。

四、从 Tokenizer 到输出的完整判定链路

整个判定过程在 src/Tokenizer.ts 中按顺序执行:

  1. 块级判定html(src)方法(src/Tokenizer.ts)用this.rules.block.html尝试匹配块级 HTML,命中则生成block: true的 html token,并依据捕获组判断pre标志(script/pre/style 原始块);
  2. 行内判定tag(src)方法(src/Tokenizer.ts)用行内tag正则匹配,命中则生成block: false的 html token,同时维护inLinkinRawBlock两个词法状态(用于<a><script>等上下文的正确嵌套判断);
  3. 退化为文本:两次判定均失败后,片段作为普通文本继续解析;
  4. 转义输出:文本 token 渲染时调用escapeHtmlEntities完成实体转义,得到规格文件中的期望 HTML。

从源码结构看,Token 类型Tokens.HTML同时承载block布尔标志区分块级/行内两种形态,而tag方法中对inLink/inRawBlock状态的跟踪(src/Tokenizer.ts)保证了对<a>链接内标签与原始块内标签的特殊处理,这些细节共同保证了标签判定的精确性。

五、该规格如何被测试框架执行

这份规格是 marked 自建的specs/new测试套件的一部分。测试框架 test/run-spec-tests.js 启动时会同时加载五组规格:

const [commonMarkTests, gfmTests, newTests, originalTests, redosTests] = ...

其中newTests对应./specs/new目录(见 test/run-spec-tests.js),即 marked 针对自身行为特性补充的新规格集,与 CommonMark 官方套件(specs/commonmark)、GFM 套件(specs/gfm,使用gfm: true)、历史行为套件(specs/original,使用pedantic: true)并列。

执行机制为:逐一读取specs/new下成对的.md输入与.html期望输出,用 marked 解析.md,再将结果与.html逐字比对。因此 html_invalid_tag_name.md 与其同名的 html_invalid_tag_name.html 构成一个最小化的、可自动回归验证的行为契约:任何未来对正则或转义逻辑的改动,只要破坏这三组输入输出的对应关系,测试就会立即失败。

六、边界场景与实战启示

基于本规格与源码,可以总结出几条对日常使用 marked 有直接指导意义的结论:

  1. 标签名合法字符集是[a-zA-Z][a-zA-Z0-9-]*<my-tag><h1>合法;<my_tag><a:b><div_><1abc>均非法,会被转义为普通文本;
  2. 属性名与标签名的字符集不同:属性名允许冒号([a-zA-Z:_][\w.:-]*,见 src/rules.ts),标签名不允许,这是容易踩坑的差异点;
  3. 块级 HTML 还要求"行结构":白名单标签(类型 6)或整行标签(类型 7)才能形成块级 HTML;位于段落中间的标签只走行内判定,例如本规格的foo <bar_baz> qux整行只是普通段落;
  4. 转义是不可绕过的安全底线:只要标签名校验失败,尖括号一律实体化输出,防止伪标签注入破坏页面结构。

如果你想亲自验证这套行为,可以打开 test/specs/new/html_invalid_tag_name.md 修改输入,对照 html_invalid_tag_name.html 观察期望输出,并通过 test/run-spec-tests.js 驱动回归测试——这是理解 marked 解析边界最直接、也最可靠的方式。

【免费下载链接】markedA markdown parser and compiler. Built for speed.项目地址: https://gitcode.com/gh_mirrors/ma/marked

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 6:03:09

python-pptx 自动化生成初升高数学衔接 PPT 课件

简介&#xff1a;这份《初升高数学衔接PPT课件》定位为专业课件&#xff0c;面向即将升入高中或刚进入高一的学生、家长及数学教师&#xff0c;用于弥补初中到高中在知识深度、教学节奏与思维要求上的落差。课件围绕高中数学学习特点展开&#xff0c;涵盖预习课本、认真听讲、课…

作者头像 李华
网站建设 2026/9/20 6:01:37

柔性开断点(SOP)在配电网电压控制中的应用与优化

1. 项目概述在分布式能源快速发展的背景下&#xff0c;主动配电网面临着前所未有的电压控制挑战。作为一名长期从事电力系统优化研究的工程师&#xff0c;我最近完成了一个基于柔性开断点(SOP)的配电网电压与无功协调控制项目&#xff0c;这个方案在实际电网仿真中展现出了显著…

作者头像 李华
网站建设 2026/9/20 6:01:07

Obsidian侧边栏嵌入Claude Code:从配置到高效工作流

我刚开始把 Obsidian 当成纯笔记工具用时&#xff0c;从来没想过有朝一日会把 Claude Code 这种命令行 AI 编程助手直接塞进它的侧边栏。直到我那个"Obsidian 教程"系列写到第 15 篇&#xff0c;决定认真折腾一次 Claudian 插件&#xff0c;结果发现这东西彻底改变了…

作者头像 李华
网站建设 2026/9/20 5:57:40

软件工程毕业设计选题创新指南与实战案例

1. 项目背景与核心痛点每年三四月份&#xff0c;计算机相关专业的毕业生们都会面临一个共同的难题——如何选择一个既符合专业要求又具备创新性的毕业设计题目。作为带过7届毕业设计的导师&#xff0c;我见过太多学生在选题阶段反复折腾&#xff0c;最后要么选题太泛难以实现&a…

作者头像 李华