一份文档如何在这两种格式之间往返
两个方向读的是同一份结构——标题、段落、引用、列表、代码与表格,再把它写成另一种语言。这就是一份文档能在 Markdown 与 HTML 之间来回转换而不丢字的原因:第二次转换承接的是第一次读懂的内容,而不是重新猜一遍原文。
页面上的往返结果,就是把文档写回去再与你粘贴的内容比较。它报出的每一处差异,都是两种语言之间的差异——强调符号换了一种写法、列表的空行排布不同——而不是转换本身出错。
粘贴一份文档,得到它的 HTML;或者粘贴一个页面,把 Markdown 拿回来。两个方向都在这个标签页里运行:转换按固定规则处理文本,所以没有任何上传,页面背后也没有能接收草稿的接口,即便有人尝试发送。真正值得读的是答案旁边打印出来的东西——文档大纲、每个链接及其协议、Markdown 承载不了的元素、这个页面没有实现的语法,以及往返结果,也就是把文档写回去再与你粘贴的内容比较。
Markdown 转 HTML - Danfio
https://danfio.com/tools/markdown-to-html/ - 从本页打印
Markdown 对应的 HTML
2.1 KB
43 行Markdown 产出 31 行HTML,包含 4 个标题、3 个链接和 1 张图片;文字通过了往返检查。
<h1 id="markdown-to-html-and-back">Markdown to HTML, and back</h1>
<p>Markdown is a small language, and this page is honest about which part of it it speaks. The text you type is converted in the tab you typed it in - <strong>nothing</strong> is uploaded - and the same function produced the HTML below.</p>
<h2 id="what-this-page-does">What this page does</h2>
<ul>
<li>Reads a pipe table with alignment, and a task list</li>
<li><input type="checkbox" checked disabled> Converts fenced code, with the language after the fence</li>
<li><input type="checkbox" disabled> Implements footnotes, which it does not</li>
</ul>
<table>
<thead>
<tr><th style="text-align: left">Setting</th><th style="text-align: right">Default</th><th style="text-align: center">What it changes</th></tr>
</thead>
<tbody>
<tr><td style="text-align: left">tables</td><td style="text-align: right">on</td><td style="text-align: center">GFM only</td></tr>
<tr><td style="text-align: left">line breaks</td><td style="text-align: right">off</td><td style="text-align: center"></td></tr>
</tbody>
</table>
<h4 id="a-sub-subsection-which-skips-a-level">A sub-subsection, which skips a level</h4>
<blockquote>
<p>A quotation, with <strong>emphasis</strong> inside it, and a second paragraph that shows how a quote is
written back out.</p>
<p>Like that.</p>
</blockquote>
<pre><code class="language-js">const heading = document.querySelector('h1');
heading.textContent = 'Markdown';</code></pre>
<pre><code>A fence with no language, which no highlighter can color.</code></pre>
<hr>
<h2 id="setext-headings-work-as-well">Setext headings work as well</h2>
<p>A footnote marker[^1], a phrase someone wanted ==highlighted==, and an ordinary link: <a href="https://example.com/reset">reset your password</a>.</p>
<p>Raw HTML is escaped by default, so <span class="badge">Beta</span> reads as the characters it is.</p>
<p>The guide is <a href="https://example.com/guide" title="The guide">over here</a>, the bare address <a href="https://example.com/bare">https://example.com/bare</a> is recognized, and an image with no alt text looks like this: <img src="/images/diagram.png" alt=""></p>
下面的第一个下载就是这段文本,逐字节相同。把它复制进文件即可打开,或者直接粘进接受 HTML 的页面。
| 层级 | 标题 | 行 |
|---|---|---|
| h1 | Markdown to HTML, and back | 1 |
| h2 | What this page does | 5 |
| h4 | A sub-subsection, which skips a level | 16 |
| h2 | Setext headings work as well | 34 |
| 层级只表示“比上一级深一层”,所以跳级值得注意:它读起来像缺了一节,而不是字号变小。 | 这里最深的标题是 h4,最长的标题有 37 个字符。 | HTML 里的每个标题都会得到一个 `id`,由标题文字生成;文字相同的第二个标题会追加 `-1`——两个元素共用一个 id 是无效 HTML,第二个链接会落到第一个上。 |
引用或列表项内部的标题属于那个条目,而不是这份大纲:把引用当成章节列进目录,会把文档说错。
| 文字 | 目标 | 协议 | 行 | 处理结果 |
|---|---|---|---|---|
| reset your password | https://example.com/reset | https | 37 | 保留 |
| over here | https://example.com/guide | https | 41 | 保留,来自引用定义 |
| https://example.com/bare | https://example.com/bare | https | 41 | 保留 |
| 这份文档里没有任何链接使用可执行代码的协议,所以拒绝开关没有可做的事。 `relative` 表示地址完全没有协议——`/about`、`page.html`、`#section`——页面上大多数链接都是这样写的。 凡是链接可以被点击的地方,`data:` 都会被拒绝;在图片上只允许 `data:image/...`:图片源不是导航目标,而在本该放图片的位置塞进一整份 HTML 文档,正是页面执行别人脚本的方式。 | ||||
写成引用定义的链接会单独标出:定义行永远不会变成内容,而文档里任何地方都可以指向它。
| 替代文本 | 来源 | 行 | 处理结果 |
|---|---|---|---|
| (无) | /images/diagram.png | 41 | 保留 |
| 没有替代文本的图片会渲染成 `alt=""`,屏幕阅读器读到的是一片空白——索引页面图片的搜索引擎也一样。 页面本身走 `https://` 时,以 `http://` 加载的图片会被当作混合内容拦截,根本不会显示。 | |||
这个页面不抓取任何内容:图片只是文本里的一个 URL,这里没有任何东西会去读它指向的图片。
| 类型 | 位置 | 内容 | 大小 |
|---|---|---|---|
| 代码 | 第 23 行 | js | 2 行 |
| 代码 | 第 28 行 | 无语言 | 1 line |
| 表格 | 第 11 行 | left right center | 3 列,2 行 |
| 任务列表 | — | — | 完成 1,未完成 1 |
| 没有语言的围栏代码块只会得到裸的 `<pre><code>`,没有 `class`——语法高亮器正是靠它来上色,所以围栏后面什么都不写,就是一个没人能上色的代码块。 管道表格必须保持同一种形状,所以单元格数量不同的行会被补齐或裁掉,审计会说明这一点:内容悄悄消失就发生在这里。 任务列表项在 HTML 里会变成禁用的复选框,这正是 GFM 的产物——它也能被读回成任务,因为那个复选框是 HTML 里唯一能说“这原本是任务”的地方。 | |||
目录、标题和段落不在这里列出:上面的提纲已经覆盖了标题,而段落就是输出框里的内容。
| 区域 | 行 | 层级 | 说明 |
|---|---|---|---|
| 表格 | 14 | 值得一看 | 这一行有 2 个单元格,表头行有 3 个。管道表格必须保持同一种形状,所以这一行会被补齐或裁掉——单元格悄悄消失就发生在这里。 |
| 结构 | 16 | 值得一看 | 标题层级跳级了:上一个标题是 h2,这一行是 h4。层级只表示“比上一级深一层”,所以这个缺口意味着缺了一节——对屏幕阅读器、目录和搜索引擎都是如此。 |
| 代码 | 28 | 仅供参考 | 这个代码块的围栏后面什么都没写,所以 HTML 是裸的 `<pre><code>`,没有 `class`。放在那里的语言标记,正是语法高亮器要读的东西。 |
| 语法 | 37 | 值得一看 | `[^1]` 是多种 Markdown 方言与静态站点生成器里的脚注语法。这里不实现脚注,所以标记按它本来的文本打印,而不是悄悄变成一个没人能读的注释。 |
| 语法 | 37 | 值得一看 | `==highlighted==` 是某些方言使用的高亮标记。GFM 和 CommonMark 都没有它,所以按文本打印,而不是变成 `<mark>`。 |
| 原始 HTML | 39 | 值得一看 | 这一行里的内联 HTML `<span class="badge">` 被转义了,而不是交给浏览器。粘贴进来的文档通常就是这样带着 `<b>` 出现,而原样放行正是页面渲染出没人读过的标记的方式。 |
| 图片 | 41 | 值得一看 | 这一行的图片没有替代文本。它会渲染成 `<img src="…" alt="">`,屏幕阅读器读到的是一片空白。 |
| 表格里的文字就是发现本身的原话:这张表由报告和 CSV 携带的同一批句子生成,所以页面、报告与下载内容不会对“注意到了什么”产生分歧。 所有发现都打印在这里,行号就是上面输入框里的行号。 往返类的发现说的是这个页面,而不是你的文档:它意味着本页写出的 Markdown 读回来时文字不同,那会是本页的 bug。 | |||
这个引擎没有实现的语法,会按它最终会变成的文本报告出来,而不是悄悄打印得好像真有什么含义。
--- the first text +++ the second text @@ -11,7 +11,7 @@ | Setting | Default | What it changes | | :--- | ---: | :---: | | tables | on | GFM only | -| line breaks | off | +| line breaks | off | | #### A sub-subsection, which skips a level @@ -31,13 +31,10 @@ --- -Setext headings work as well +## Setext headings work as well ----------------------------- + - +A footnote marker\[^1\], a phrase someone wanted ==highlighted==, and an ordinary link: [reset your password](https://example.com/reset). -A footnote marker[^1], a phrase someone wanted ==highlighted==, and an ordinary link: [reset your password](https://example.com/reset). + - +Raw HTML is escaped by default, so \<span class="badge">Beta\</span> reads as the characters it is. -Raw HTML is escaped by default, so <span class="badge">Beta</span> reads as the characters it is. + - +The guide is [over here](https://example.com/guide "The guide"), the bare address [https://example.com/bare](https://example.com/bare) is recognized, and an image with no alt text looks like this:  -The guide is [over here][the-guide], the bare address <https://example.com/bare> is recognized, and an image with no alt text looks like this:  - -[the-guide]: https://example.com/guide "The guide"
原文消失了 11 行,新增了 8 行。经过另一种语言的往返不是恒等变换,差异就在这里:强调换了拼法,列表写成紧凑或松散,表格被补齐以维持形状。不能变的是文字,上面那条读回检查测的就是这一点。
| 决定 | 作用 |
|---|---|
| 方向 | Markdown 转成 HTML |
| 方言 | GFM:管道表格、任务列表、删除线和裸链接都会读取并写出 |
| 原始 HTML | 转义成文本,因为粘贴进来的文档是不可信输入 |
| 危险协议 | 凡是链接可以被点击的地方,`javascript:`、`vbscript:`、`data:` 和 `file:` 都会被拒绝,并打印所在行 |
| 实体 | 实体引用原样保留:`©` 仍是 `©`,浏览器画出 ©,这正是 CommonMark 规定的含义 |
| 标题 id | 为每个标题添加,由标题文字生成,重复时追加 `-1` |
| 单个换行 | 在 HTML 源码里保持为换行,浏览器把它画成空格 |
| 输出 | 只输出片段,可以直接粘进现有页面 |
| 表格对齐 | 写成 `style="text-align: …"`,这是现行标准要求的写法,而不是已过时的 `align` 属性 |
| 缩进代码 | 四个空格在这里不会开启代码块,这是对 CommonMark 的一处偏离:请用围栏 |
| 列表续行 | 续行必须缩进到条目正文的位置,这是另一处偏离:没有惰性续行 |
| 未实现 | 脚注、定义列表、数学公式、`==highlight==`、警告块引用、容器、属性块和 emoji 短代码都会被识别并按文本报告,而不是转换 |
| 其中两条是对 CommonMark 的偏离——缩进代码与惰性续行——它们和引擎完全不实现的语法一样,被打印出来而不是藏起来。方言、原始 HTML 设置和协议设置是页面在下方解释里专门讨论的三项;其余都是每种只有一个合理答案的实现选择。 | |
肯列出自己决定的转换器,才是行为可预测的转换器;在这里这比速度更重要:产出的 HTML 最终会出现在别人阅读的页面上。
它不是 CommonMark 实现,也从不这样宣称。它只实现一个有名字的子集,会把它列在下面,并点明两处刻意的偏离。它认得出但没有实现的语法——脚注标记、`$math$`、`==highlight==`、GitHub 警告块——会被当作最终会变成的文本*报告*出来,而不是悄悄打印得好像真有什么含义。可信的转换器和不可信的转换器,区别就在这里:后者会在文章中间留下一个 `[^1]` 而什么都不说。
答案是源码,不是预览。页面把它产出的 HTML 打印出来,而不是塞回自己体内,因为绘制别人的标记,正是转义要避免的事。把结果放进文件就能看到它被绘制——如果你选了整份文档,它会按原样打开。
如果你把文档粘进一个转换器、把页面粘进另一个,就会有两套心智模型,却只有一份事实来源。这一页把两个方向放在同一处完成:Markdown 转 HTML,以及 HTML 转回 Markdown,并在答案旁边打印大纲、每个链接及其协议、无法保留的内容,还有往返结果。
没有任何上传,因为页面背后没有能接收草稿的接口:两个转换都按固定规则处理文本,都在你正在阅读的标签页里运行。这一点对往返尤其重要,因为它会改写你自己的文档并与原文比较——看见转换器改了什么,不该以交出一份私人草稿为代价。
它不是 CommonMark 实现,也从不假装是:它只实现一个有名字的子集,把它列在输入框下面,并点明两处刻意的偏离。它认得出但没有实现的语法,会按最终会变成的文本报告,所以脚注标记或数学片段不会以别的样子混进文章里。
两个方向读的是同一份结构——标题、段落、引用、列表、代码与表格,再把它写成另一种语言。这就是一份文档能在 Markdown 与 HTML 之间来回转换而不丢字的原因:第二次转换承接的是第一次读懂的内容,而不是重新猜一遍原文。
页面上的往返结果,就是把文档写回去再与你粘贴的内容比较。它报出的每一处差异,都是两种语言之间的差异——强调符号换了一种写法、列表的空行排布不同——而不是转换本身出错。
别人给的 Markdown 是不可信输入,而转换器正是不可信输入变成标记的地方。所以文本里的每个字符都会被转义——`<`、`>`、`&` 和 `"` 变成实体,唯一放过的是本来就已经是实体引用的那些 &。原始 HTML 块默认同样被转义:`<div class="note">` 变成 `<div class="note">`,读者看到的就是这些字符本身。页面提供了放行的开关,因为 CommonMark 规定应当放行,也因为你自己的标记不是别人的标记。
链接有自己的规则,而且是实践中最要紧的一条。链接里的 `javascript:` 地址,会让页面执行随文档一起送来的代码;`data:text/html,...` 则是在本该放链接的位置塞进一整份文档。凡是链接可以被点击的地方,这两类默认都被拒绝,每次拒绝都会连同所在行号打印出来。不被拒绝的是图片里的 `data:image/...`:图片源不是导航目标,把像素内联进来是常规做法,区分两者的只有协议。检查会按浏览器读取地址的方式来判断——先解码、再去掉空白——所以 `javascript:` 与 `java	script:` 同样会被拦下。
页面写出的 Markdown 会被读回来,再比较两份文档的文本。如果有词变了,报告会指出来并给出位置。结构允许在途中变化——`_emphasis_` 会写成 `*emphasis*`,列表回来时空行的排布可能不同——因为那是同一份文档的另一种拼法,报告会说明它遇到的是哪一种。
比较之前会折叠连续空白,所以段落重新折行不算差异,词变了才算。这是一个很窄的承诺,页面上写明了;每个答案旁边的往返结果就是它的证据。
Markdown 是更小的语言,所以进入它的方向一定会丢东西:`<div>` 没有对应物,会被脱壳;`<span>` 消失而其中的文字留下;`<sup>` 因为没有上标语法而退化成普通文本;`<script>` 则被直接丢弃,而不是转换。这些都不留给访问者事后发现——无法保留内容的表格会写出元素名、出现次数以及最终的处理方式。
另一个方向不会丢东西,因为 Markdown 能表达的内容在 HTML 里都有地方可去。这个方向有的是它拒绝解释的语法清单:脚注、定义列表、行间与行内数学公式、`==highlight==`、警告块引用、容器指令、属性块和 emoji 短代码都会被识别,并按它们最终会变成的文本报告出来。页面还印出两处对 CommonMark 的偏离:四个空格在这里不会开启代码块,请用围栏;列表的续行必须缩进到条目正文的位置,因此没有惰性续行。
示例使用页面自带的默认输入,只改设置;下面的结果与你在页面上看到的完全一致。
转换方向: Markdown 转成 HTML · Markdown 文本: # Widget - Built with **care** - [x] Tested | Flag | Default | | --- | ---: | | `--fast` | off | · 用哪种 Markdown: GFM:表格、任务列表、删除线、裸链接
Markdown 对应的 HTML
349 字节
8 行Markdown 产出 13 行HTML,包含 1 个标题、0 个链接和 0 张图片;文字通过了往返检查。
输入: 10 词 · 输出: 13 行 · 标题: 1 · 链接: 0
上面的主数字是产出的 HTML 大小;统计数据来自页面自己的计数;页面使用时,大纲、链接和审计会打印在答案旁边。
转换方向: HTML 转成 Markdown · HTML 文本: <h2>Notes</h2> <p>A <strong>paragraph</strong> with a <a href="/guide">link</a> and a <sup>marker</sup>.</p> <div><p>In a div, which Markdown has no container for.</p></div>
HTML 对应的 Markdown
110 字节
3 行HTML 产出 5 行Markdown,包含 1 个标题、1 个链接和 0 张图片;文字通过了往返检查。
输入: 19 词 · 输出: 5 行 · 标题: 1 · 链接: 1
这个方向上 Markdown 是更小的语言,所以无法保留内容的表格会列出 `<sup>` 和 `<div>`——而它们里面的文字依然保留了下来。
转换方向: Markdown 转成 HTML · Markdown 文本: Reset your password [here](javascript:alert(1)) and keep the . · 能执行代码的链接: 拒绝,保留文字
Markdown 对应的 HTML
97 字节
1 行Markdown 产出 1 行HTML,包含 0 个标题、1 个链接和 1 张图片;文字通过了往返检查。
输入: 8 词 · 输出: 1 行 · 标题: 0 · 链接: 1
链接的文字留下,目标被丢掉:上面的开关决定这一点,审计会打印它所在的行号。图片是另一种情况,允许把像素内联进来。
不是,页面会说明它实际实现了哪个子集,而不是暗示其余部分也有。两个方向在重叠之处都遵循 CommonMark 规则——决定 `snake_case` 与 `2*3*4` 含义的侧翼规则,围栏优先于标题、下划线标题优先于分隔线的顺序——有两处行为是刻意不同的:四个空格不开启代码块,列表续行必须缩进到条目正文的位置。两条都印在规则表里,所以输出没有意外。
因为预览意味着在这个页面里绘制别人的标记,而那正是转义要消除的风险。这一页给出的答案是源码:它产出的 HTML,可以直接复制或下载。把它放进文件,或者选择整份文档,浏览器会在自己的标签页里绘制它——那里出了错,是你文件里的错,而不是这个页面的错。
默认会被拒绝:链接的文字保留,地址被丢掉,因为删掉整句话比要求的改动更大。拒绝会带着行号记入审计,表单上的开关可以为你自己写的文档关掉这条规则。检查针对解码并去掉空白后的地址,所以 `javascript:` 同样被拒绝——浏览器把它读作 `javascript:`,这一页也必须这样读。
因为粘贴进转换器的文档通常是别人的,把它的标记原样放行,正是转换器变成注入最后一环的方式。转义之后,`<div onclick="...">` 变成读者看得见、浏览器执行不了的文本。CommonMark 说渲染器应当放行原始 HTML,开关正是为 CommonMark 设想的场景准备的:你自己的标记,你自己的文档。
不会。转换按同一条固定规则处理文本,所以在输入它的标签页里运行;页面背后没有可以接收文档的接口,即便有人尝试发送也没有落点。这一点在这里比在多数页面上更重要:人们粘进转换器的,往往是还没发布的草稿,或者竞争对手的页面。
它把文档按原来的形式写回去,再和你粘贴的内容做 diff。经过另一种语言的往返不是恒等变换,所以有意思的是差异出现在哪里:强调从 `_x_` 改成 `*x*`,列表由紧凑变成松散,表格被补齐以维持形状。不能变的是文字,旁边那条读回检查测的就是这一点——如果有词变了,页面会说出来并指出位置。
因为它们是同一份文档的两种拼法,而页面写回时对每样东西只保留一种拼法。另一种做法是记住访问者用的是哪一种,那意味着把原文一路带着,往返检查就变成了自己测自己,而不是在测两种语言。
会被识别并报告。脚注标记、`$math$`、`==highlight==`、GitHub 警告块、定义列表、容器指令、属性块和 emoji 短代码,都会以它们最终会变成的文本出现在审计里,并附上所在行。可信的转换器和不可信的转换器,区别就在这里:后者会在文章中间留下一个 `[^1]`,什么都不说。
最近复核于 2026-09-18.
本页在您的浏览器本地运行,请保留原文件备份;不涉及财务建议。 阅读完整免责声明。