6 个版本
0.1.5 | 2019年2月12日 |
---|---|
0.1.4 | 2019年2月9日 |
0.1.3 | 2019年1月4日 |
0.1.2 | 2018年10月31日 |
#1095 in 解析器实现
每月371次下载
用于 7 个crate(6 个直接)
225KB
6K SLoC
解析维基文本
将维基文本从 Mediawiki 解析为元素树。
简介
维基文本是一种遵循 PHP 最大化“尽可能使一切不一致和令人困惑”的格式的文本。有数亿份有趣的文档以这种格式编写,在采用 Mediawiki 软件的网站上以免费许可证分发,主要是维基百科和 Wiktionary。能够解析维基文本并处理这些文档将允许访问世界上很大一部分知识。
Mediawiki 软件本身将维基文本文档转换为一种过时的格式,以在浏览器中显示给人类读者。它是通过一系列字符串替换的逐步过程来完成的,其中一些步骤取决于之前步骤的结果。该过程的主要文件有 6200 行代码,第二个最大的文件有 2000 行,还有一个 1400 行的文件,只是为了解析器的选项。
更有趣的是,将维基文本文档解析成一个可以被计算机程序用于推理文档中的事实并以不同方式呈现的结构,使它们可用于各种应用。
有人尝试使用正则表达式解析维基文本。这是非常天真的,一旦维基文本变得复杂,就会失败。正则表达式的功能远远不能达到正确解析维基文本所需的复杂性。有一个项目勇敢地尝试使用解析器生成器来解析维基文本。然而,维基文本从未为正式解析器设计,因此解析器生成器在正确解析维基文本方面也没有任何帮助。
维基文本有着长期的不良设计添加历史,这些添加被粗心地堆叠在一起。维基文本的语法在各个维基之间因其配置不同而不同。你甚至无法知道起始标签是什么,除非你看到相应的结束标签,除非你解析起始标签和结束标签之间的嵌套标签层次结构,否则你无法知道结束标签在哪里。简而言之:如果你认为你理解维基文本,那么你就没有理解维基文本。
解析维基文本试图通过将其转换为易于处理的其他格式来消除解析维基文本的所有不确定性。目标格式是Rust对象,可以使用迭代器和match表达式舒适地进行处理。
设计目标
正确性
解析维基文本被设计成精确地解析MediaWiki解析的维基文本。即使MediaWiki中显然存在错误,Parse Wiki Text也会复制该错误。如果Parse Wiki Text没有像MediaWiki那样完全准确地解析某些内容,请将其报告为一个问题。
速度
解析维基文本被设计成以尽可能少的时间解析页面。它在每个处理器核心上每秒解析数十万个页面,可以快速解析包含数百万页面的整个维基。如果有任何可以改变以使Parse Wiki Text更快的事情,请将其报告为一个问题。
安全性
解析维基文本被设计成与不受信任的输入一起工作。如果任何输入使用合理的资源无法安全地解析,请将其报告为一个问题。不使用任何不安全代码。
平台支持
解析维基文本被设计成可以在各种环境中运行,例如
- 运行机器码的服务器
- 运行Web Assembly的浏览器
- 嵌入在其他编程语言中
解析维基文本可以在没有任何依赖项的任何地方部署。
注意
维基文本是旧软件使用的旧格式。解析维基文本仅用于恢复为运行旧软件的维基编写的文本,复制在旧软件中发现的错误。请勿将维基文本用作新应用程序的格式。维基文本是一个糟糕的格式,具有惊人的不一致性、糟糕的设计选择和错误。对于新应用程序,请使用设计为易于处理格式的格式,例如JSON或更好的CBOR(CBOR)。有关使用JSON作为其格式并提供丰富编辑数据界面的维基示例,请参阅Wikidata。如果您需要将用维基文本编写的文本用于新应用程序,则可以使用Parse Wiki Text将其转换为可以进一步处理为现代格式的中间格式。
站点配置
维基文本具有许多功能,其解析方式取决于维基的配置。这意味着在解析之前必须知道配置。
- 只有当链接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>());
}
}
}