jolicode / jolimarkdown
Markdown 文件样式统一修复工具。
Requires
- php: >=8.1
- ext-mbstring: *
- lib-libxml: *
- league/commonmark: ^2.4
- league/html-to-markdown: ^5.1
- psr/log: ^3.0
Requires (Dev)
- symfony/framework-bundle: ^6.0
- symfony/validator: ^6.0
README
用法
use JoliMarkdown\MarkdownFixer; $markdown = <<<MARKDOWN # A sample Markdown document Some paragraph here with an image <img src="/image.png" alt="description" /> inside. MARKDOWN; $markdownFixer = new MarkdownFixer(); $fixedMarkdown = $markdownFixer->fix($markdown);
上面的代码将返回输入字符串的 "markdown化" 版本
# A sample Markdown document Some paragraph here with an image  inside.
如果您正在使用 Symfony,您可能需要阅读相关捆绑包的文档。
安装
composer require jolicode/jolimarkdown
配置
有几个配置选项可以作为League CommonMark环境配置选项使用,以自定义 Markdown 修复器的行为
use JoliMarkdown\MarkdownFixer; use League\CommonMark\Environment\Environment; $markdown = <<<MARKDOWN - some - list MARKDOWN; $markdownFixer = new MarkdownFixer(new Environment([ 'joli_markdown' => [ 'unordered_list_marker' => '*', ], ])); $fixedMarkdown = $markdownFixer->fix($markdown); // outputs: // * some // * list
internal_domains
:一个包含网站内域名的数组。每当找到图像或链接 URL 时,该 URL 位于所列域名之一下,URL 将被转换为相对 URL。默认为[]
。prefer_asterisk_over_underscore
:一个布尔值,表示是否更喜欢*
而不是_
用于强调。默认为true
。unordered_list_marker
:用于无序列表标记的字符串。默认为-
。
测试
测试位于 tests
目录下,并使用 PHPUnit 编写
castor qa:install castor qa:phpunit
上下文
Markdown 是一种简单的文本语法,用于编写结构化文档。自 2004 年创建以来,这种语法旨在提供一种替代、更快、更简单的 HTML 文档编写方式,用于 Web 发布。在接下来的几年中,Markdown 语法迭代性地发展,没有正式、完美的标准化规范。已经出现了各种变体,但没有一种成为事实标准。
然而,最稳健的替代品之一是 CommonMark,这是一种于 2014 年正式指定的 Markdown 变体,自那时起一直在发展。
Markdown / Commonmark 经常在开发世界中使用(以 Markdown README 文件形式的文档、许多发布平台的采用)并且也经常用于 Web 发布。例如,当 JoliCode 网站 在 2012 年创建时,选择了这种语法,并且至今仍在用于结构化各种内容体(博客文章、客户参考、技术、团队表等)。
然而,在过去的 12 年里,我们转换 Markdown 内容到 HTML 的方式发生了变化:先写几篇纯 HTML 文章,然后使用 客户端 JavaScript Markdown 预处理器(在 Web 浏览器中),最后,在过去的几年里,迁移到 league/commonmark
库,该库允许您在 PHP 服务器端将 Markdown 转换为 HTML。选择这个库是因为它特别完整、维护良好、可扩展和稳健。
在开发 league/commonmark
的过程中,添加了扩展机制,以支持不同的 Markdown "扩展",即支持不属于 CommonMark 标准但为作者提供语法灵活性的语法元素。例如,表格扩展 使得在 Markdown 中以更轻、更易读的语法编写表格成为可能,这在 "标准" CommonMark 中是不可能的。
Markdown的一个基本特性是它与HTML的兼容性:在Markdown中,将HTML标签插入文本是完全可以的,这些标签将直接传递到最终的HTML文档中。例如,你可以写
# A Markdown document <p>An HTML paragraph.</p> A paragraph in Markdown.
这样的文档在HTML中渲染如下
<h1>A Markdown document</h1> <p>A paragraph in HTML.</p> <p>A paragraph in Markdown.</p>
因此,CommonMark的扩展机制非常有趣,因为它允许添加扩展的语法元素,这些元素可以由扩展解释以生成丰富、复杂的HTML输出,而无需最终用户(即编辑器)编写HTML。这种扩展的概念在CommonMark中有规定(CommonMark规范本身是使用CommonMark编写的,并且使用了扩展以生成Markdown语法和相应HTML输出的并排渲染,例如在标签页部分所看到的)。
在JoliCode网站上,我们利用了league/commonmark的灵活性,多年来不断丰富HTML渲染,以便我们可以编写更丰富、更具有表现力和更视觉化的Markdown文档。例如,我们添加了扩展以编写脚注、HTML表格、删除线文本,为外部链接添加HTML属性,自动为<img>
标签添加属性等。
尽管如此,在过去的12年里,我们经常在Markdown文章中编写HTML代码以满足某些需求
- 为HTML元素添加CSS类,以便能够以不同的方式对其进行样式化(例如,在页面上居中图像);
- 插入带有CSS类的代码,以使用语法高亮库;
- 创建HTML结构以使两个图像并排显示;
- 等等。
有时添加HTML代码是因为文章的作者对Markdown的一些晦涩之处感到不适,并选择最直接的方法来发布其内容。当时使用HTML可能是合适的,但随着HTML提供的可能性发生变化,其局限性也随之变化:而对于用Markdown编写的元素,我们现在可以更新负责HTML渲染的程序以支持新的HTML功能,但我们不能这样做对于直接用HTML编写的元素,它们将永远停留在作者选择的形式上。
例如,我们希望能够提供比几年前使用的格式(如webp,它更小且质量更好)的现代、高性能图像。对于这些图像,我们还想摆脱使用<img>
标签,并利用<picture>
、<source>
标签以及如srcset
之类的属性。对于使用Markdown语法插入到文章中的图像,我们可以升级HTML渲染程序以支持这些新格式和标签。但对于在HTML中插入的图像,我们无法做到这一点,因此必须手动替换它们——或者保持原样,这会给用户带来不便,因为必须接受相关的文章使用过时、效率较低的技术,这会影响速度和网站用户提供的舒适度。
因此,我们正在寻找一种方法来纠正现有的Markdown文章,尽可能地将它们包含的HTML元素替换为等效的Markdown元素,而不会扭曲最终的HTML渲染。
一个扩展,已经存在于league/commonmark中几年了,可以专门帮助我们完成这项任务:这是属性扩展,它允许您向Markdown元素添加HTML属性。例如,您可以写
{.block-class}  {.image-class}
这将按以下方式在HTML中渲染
<p class="block-class"><img src="/path/to/image.jpg" alt="Une image"></p> <p><img src="/path/to/image.jpg" alt="Another image" class="image-class"></p>
借助这个扩展,我们希望编写一个程序,该程序可以对网站上每篇Markdown文章执行以下操作:
- 分析文章的Markdown内容;
- 识别可以替换为等效Markdown元素的HTML元素;
- 将这些HTML元素替换为Markdown元素,并添加必要的HTML属性,以确保最终HTML渲染与原始文章相同。
此仓库提出了一种工具来实现这一目标,采用以下总体方法
- 从现有的字符串(文章的初始Markdown内容)开始,使用
league/commonmark
Markdown解析器生成一个抽象语法树(AST)。该解析器经过特别配置,启用了很少的扩展,尽可能接近标准CommonMark语法,并生成一个仅包含(几乎)CommonMark基本语法元素的AST; league/commonmark
解析器返回一个Document
,这是一个Nodes
(AST的一个元素)的层次结构。每个节点都是类型化的(例如,类型为Paragraph
的Node
代表一个段落,类型为Image
的Node
代表一个图像等),HTML代码部分以HtmlBlock
或HtmlInline
节点的形式解析;- 然后,通过一组修正类对这个文档进行修正。例如
- 如果一个类型为
FencedCode
的节点有一个CSS类属性language-php
,则删除此类,并将节点的Info
属性更新为php
值; - 如果一个类型为
Image
的节点有一个绝对URL,而图像是由JoliCode站点提供的,则此URL将被替换为相对URL。可以使用许多测试来检查特殊情况和不同情况。 - HTML链接也是如此;
- 对于
HTMLBlock
和HTMLInline
节点,采取特殊处理- 将HTML内容加载到DOM树中
- 然后递归遍历此树,尝试重新构成等效的纯Markdown节点。例如,如果我们找到DOM中的
<p>
元素,我们将尝试将其替换为类型为Paragraph
的Markdown节点。每次,都将HTML属性转换为league/commonmark
"Attributes"扩展中提出的属性; - 作为最后的手段,如果在递归的某个级别上我们无法重新构成Markdown节点
- 我们使用
league/html-to-markdown
库尝试将HTML转换为Markdown。这一步是必要的,因为我们需要将不支持我们实现的修正类的HTML元素转换为Markdown(例如,HTML表格:我们不提供用于DOM元素<table>
的"Fixer")。 - 将结果字符串返回为新的
HTMLBlock
或HTMLInline
节点,具体取决于顶级节点的类型;
- 我们使用
- 如果一个类型为
- 最后,作为最后一步,将修正后的新
Document
“渲染”为字符串,这是原始文章的修正Markdown内容。为此,编写了一组Renderer类,这些类受到了wnx/commonmark-markdown-renderer库的强烈启发。
许可证
此库受MIT许可证的约束。请参阅LICENSE文件。