2个不稳定版本
0.2.0 | 2024年3月3日 |
---|---|
0.1.6-alpha.0 | 2023年7月2日 |
#559 in 解析器实现
63 每月下载量
190KB
6.5K SLoC
解析Wiki文本
将MediaWiki中的wiki文本解析为元素树。
简介
Wiki文本是一种遵循PHP最大化的格式:“尽可能让一切变得不一致和混乱”。在采用MediaWiki软件的网站上,有数亿份有趣的文档使用这种格式编写,并在这些网站上以免费许可方式分发,主要是维基百科和维基词典。能够解析wiki文本并处理这些文档,将允许访问世界上很大一部分的知识。
MediaWiki软件本身将wiki文本文档转换为一种过时的HTML文档格式,以便在浏览器中显示给人类读者。它是通过一系列的字符串替换过程来实现的,其中一些步骤依赖于前一步的结果。 这个过程是逐步的。 这个过程的主要文件有6200行代码,其次是 最大的文件有2000行,还有一个 1400行的文件仅用于解析器的选项。
更有趣的是,将wiki文本文档解析成一个可以由计算机程序使用来推理文档中的事实并以不同方式呈现的结构,使其适用于各种应用。
有些人尝试使用正则表达式解析wiki文本。这是极其天真的,一旦wiki文本变得复杂,就会失败。正则表达式的功能远远无法达到正确解析wiki文本所需的复杂性。有一个项目勇敢地尝试使用解析器生成器来解析wiki文本。然而,wiki文本从未为正式解析器设计,因此解析器生成器也无法正确解析wiki文本。
维基文本有着长期的历史,其中充斥着设计不良、随意堆叠的添加内容。维基文本的语法因每个维基的配置不同而异。你甚至无法知道起始标签,除非看到相应的结束标签,而且除非解析起始标签和结束标签之间嵌套标签的整个层次结构,否则你无法知道结束标签在哪里。简而言之:如果你认为自己理解了维基文本,那么你并不真正理解维基文本。
解析维基文本试图通过将其转换为易于处理的其他格式,来消除解析维基文本的所有不确定性。目标格式是Rust对象,可以使用迭代器和匹配表达式以舒适的方式进行处理。
设计目标
正确性
解析维基文本旨在精确解析Mediawiki解析的维基文本。即使Mediawiki中明显存在错误,解析维基文本也会复制该精确错误。如果解析维基文本与Mediawiki不完全相同,请将其报告为问题。
速度
解析维基文本旨在以尽可能少的时间解析页面。它在每个处理器核心上每秒解析数十万个页面,并且可以快速解析包含数百万页面的整个维基。如果可以更改任何内容来使解析维基文本更快,请将其报告为问题。
安全性
解析维基文本旨在与不受信任的输入一起工作。如果任何输入在合理资源下无法安全解析,请将其报告为问题。没有使用不安全代码。
平台支持
解析维基文本旨在在各种环境中运行,例如
- 运行机器码的服务器
- 运行Web Assembly的浏览器
- 嵌入到其他编程语言中
解析维基文本可以在没有任何依赖的情况下部署到任何地方。
注意
维基文本是用于旧软件的遗留格式。解析维基文本仅用于恢复为在旧软件上运行的维基编写的文本,复制在旧软件中找到的精确错误。请不要将维基文本用作新应用的格式。维基文本是一个糟糕的格式,有着惊人的不一致性、糟糕的设计选择和错误。对于新应用,请使用易于处理格式的格式,如JSON,甚至更好的CBOR。有关使用JSON作为格式并提供丰富接口以编辑数据而不是让用户编写代码的维基示例,请参阅Wikidata。如果您需要将用维基文本编写的文本用于新应用,则可以使用解析维基文本将其转换为中间格式,然后可以进一步将其处理为现代格式。
站点配置
维基文本有许多特征,其解析方式取决于维基的配置。这意味着在解析之前必须知道配置。
- 只有当链接的URI方案在配置的有效协议列表中时,才会解析外部链接。如果方案无效,则将链接解析为纯文本。
- 类别和图像在外观上与链接相同,但解析方式不同。这些只能通过了解维基配置中的命名空间别名来区分。
- 与配置的魔法词集合匹配的文本被解析为魔法词。
- 扩展标签的语法与HTML标签相同,但解析方式不同。配置告诉哪些标签名应被视为扩展标签。
可以通过向维基上的站点信息资源发起请求来查看配置。实用工具Fetch site configuration可以获取解析维基页面所需的配置部分,并输出用于实例化具有该配置的解析器的Rust代码。Parse Wiki Text包含一个默认配置,可用于测试。
局限性
维基文本从未被设计为可以解析成结构化格式。它被设计成需要多次解析,每次解析都依赖于前一次解析的输出。最重要的是,模板在较早的解析阶段被展开,格式化代码在较晚的解析阶段被解析。这意味着您在原始文本中看到的格式化代码不一定与模板展开后解析器看到的格式化代码相同。幸运的是,这对人类编辑者和计算机来说都一样糟糕,因此人们往往会避免编写会导致格式化代码以与阅读模板展开前的原始维基文本不同的方式进行解析的模板。Parse Wiki Text假设模板永远不会改变其周围格式化代码的含义。
沙盒
有一个沙盒(Github,在线尝试)可供交互式输入维基文本并检查解析结果。
与Mediawiki解析器的比较
还有一个名为Mediawiki Parser的crate(crates.io,Github),它基本上执行相同的操作,将维基文本解析为元素树。然而,该crate没有考虑到正确解析维基文本所需的惊人数量的奇怪性。该crate诚然只解析维基文本的一个子集,目的是报告不适合该子集的任何文本的错误,这是一个好意图,但在检查它时,很快会发现这个子集太小,无法解析实际维基中的页面,更糟糕的是,错误报告只是一个空承诺,没有指示文本何时被错误解析。
该crate可能可以通过在文本不在支持的子集时始终报告错误来得到改进,但实际维基中找到的页面通常不符合可以无奇怪性解析的维基文本的小子集,所以它仍然没有用。将该crate改进为正确解析足够的维基文本的子集所需的努力与从头开始一样多,这就是为什么Parse Wiki Text是在没有从Mediawiki Parser中拿任何东西的情况下制作的。Parse Wiki Text的目标是正确解析所有维基文本,而不仅仅是子集,并在遇到应避免的奇怪性时报告警告。
示例
默认配置仅用于测试目的。解析实际维基需要特定于站点的配置。为了提高效率,在解析多个页面时重用相同的配置。
use parse_wiki_text::{Configuration, Node};
let wiki_text = concat!(
"==Our values==\n",
"*Correctness\n",
"*Speed\n",
"*Ergonomics"
);
let result = Configuration::default().parse(wiki_text);
assert!(result.warnings.is_empty());
for node in result.nodes {
if let Node::UnorderedList { items, .. } = node {
println!("Our values are:");
for item in items {
println!("- {}", item.nodes.iter().map(|node| match node {
Node::Text { value, .. } => value,
_ => ""
}).collect::<String>());
}
}
}