Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

基准 IRI 与相对引用

RDF 文档可以用_相对_形式书写 IRI——<other.ttl>、<#me>、<>——而读取方必须先把它转换为绝对 IRI,它才能成为一个项。这种转换需要一个基准 IRI(base IRI)。PurRDF 在每一种语法、每一个接口上,都通过同一层解析每一个相对引用:purrdf-iri。

这是硬失败,而它过去是静默的。 作用域内没有基准的相对 IRI 现在是一个错误。以前以这种方式「能用」的文档其实并不能用:它们把一个相对字符串当作 IRI 驻留,并输出任何符合规范的解析器都不接受的 N-Triples。如果某个文档开始以 iri-relative-no-base 失败,说明它早就在产生无效的 RDF——应当为它提供基准(见下文),而不是绕开这个错误。

基准从何而来

优先级链就是 RFC 3986 §5.1 所规定的,按其顺序排列。第一个得出基准的步骤胜出:

来源RFC 3986示例
1文档内的基准指令§5.1.1Turtle/TriG 的 @base 或 BASE、SPARQL 的 BASE、RDF/XML 的 xml:base、JSON-LD/YAML-LD 的 @context.@base
2调用方提供的基准§5.1.2parse_dataset 的 base 参数、CLI 的 --base
3文档的获取 IRI(retrieval IRI)§5.1.3CLI 所读取文件的 file:// IRI
4(无)——硬失败§5.1.4iri-relative-no-base

第 1 步可以嵌套:文档内的 @base 会相对于当前生效的基准重新绑定,因此在 http://example.org/dir/ 下的 @base <sub/> 得到 http://example.org/dir/sub/。

任何内容都不会被凭空捏造。没有默认基准,没有编造的 urn: 占位符,也不会退回到当前工作目录。第 4 步是一个真实的、有规范规定的结果,而不是一个缺口。

哪些接口拥有获取 IRI

第 3 步需要获取 IRI,而只有自己打开了文件的接口才拥有它。这样的接口有三个。推导逻辑只存在于其中一个之中,另外两个消费它的结果,而不是各自重新推导。

接口有获取 IRI?结果
purrdf-slice(切片树、目录、依赖修复)是——推导它;这是工作区中 §5.1.3 的唯一实现磁盘上切片文档中的相对 IRI 无需任何标志即可解析
purrdf-shapes 形状合并加载器是——消费 purrdf-slice 的推导结果每个形状文件都在其自身的 file:// IRI 下解析
purrdf CLI,按输入文件是——消费 purrdf-slice 的推导结果相对 IRI 无需任何标志即可解析
purrdf CLI,stdin(-)否相对 IRI 报告 iri-relative-no-base;需传入 --base
Rust 字节 API(parse_dataset、purrdf-rdf、purrdf-iri)否传入基准,或文档自身必须携带基准
WebAssembly否同上
C ABI否同上
Python否同上

每一个接收字节而非路径的接口都属于第二组。字节没有获取 IRI,因此 §5.1.3 在那里不起作用,而 §5.1.4——硬失败——就是规范规定的答案。这是有意为之,而不是疏漏:从本地文件系统捏造出的基准会因机器而异,并把本地路径泄漏到发布的 RDF 中,从而破坏整个工具包赖以成立的字节确定性。这也是 purrdf-iri 与 purrdf-rdf 从不接触文件系统的原因——正是这一点让它们保持 wasm32-clean(可干净构建到 wasm32)。

purrdf-slice 从规范化后的路径推导获取 IRI,把 Windows 路径(包括 UNC 主机与扩展长度前缀 \\?\)转换为 RFC 8089 形式,并对每个组成部分进行百分号编码。它的使用方只在没有给出更高优先级的基准时才应用它,而没有可用 file:// IRI 的路径是一个点名 --base 的硬错误,绝不会静默退回到「没有基准」。

两类语法族

基准究竟有没有用,取决于语法,而不是基准本身:

族语法相对引用
允许相对引用Turtle、TriG、RDF/XML、JSON-LD、YAML-LD、SPARQL相对于当前生效的基准解析
按语法只允许绝对形式N-Triples、N-Quads、TriX、HexTuples拒绝——从不应用任何基准

第二族的语法没有基准指令,也没有相对 IRI 产生式。在那里出现相对引用会使文档对其自身语法而言无效,因此 PurRDF 报告一个_不同的_代码,而且提供基准也无法挽救它:应改为把源文件转换为 Turtle,或转换为使用绝对 IRI 的 N-Triples。

绝对引用从不被改动。在两个族中,文档以绝对形式书写的 IRI 都按词法原样采用——<http://a/bb/ccc/../d;p?q> 原封不动地保留,无论作用域内是否有基准。若仍然让它经过解析,就会应用 RFC 3986 §5.2.4 的点段移除,而那是 §6.2.2.3 的_基于语法的规范化_,为 RDF Concepts §3.2 所禁止。相同的文档字节必须表示同一个图,并具有同一个规范摘要。

文档之外:IR 边界

上面所说的一切都关于_读取文档_,但文档并不是项进入存储的唯一途径。也可以直接构建数据集——从 Python 四元组对象、从 GTS 容器、重新打开 .pack 文件、通过 SPARQL INSERT DATA,或通过投影——而这些途径都不经过能替调用方解析基准的解析器。

因此,绝对性规则并不位于编解码器之中。它位于上述每条路径都必然到达的驻留项表,所以相对 IRI 不只是在入口处被拒绝:它根本无法表示。无法构造出携带相对 IRI 的冻结数据集,而这种拒绝报告的正是解析器会报告的同一个 iri-relative-no-base 代码。

在那个边界上没有作用域内的基准,也不可能有:冻结的数据集是一组_已解析_的标识,旁边没有文档,也没有可供日后据以解析的 @base。因此在那里出现的相对引用并不是一个等待解析的项——它是一个标识无从知晓的项。应当在它成为项_之前_,按应用程序所指的基准去解析它。

何时会看到这个错误

在执行引入该相对 IRI 的修改操作时。Store.add、MutableDataset.add、RDF/JS 的 dataset.add 以及 purrdf_graph_insert 都会在调用时拒绝相对 IRI,并点名出错的项:

>>> store.add(Quad(NamedNode("rel"), p, o))
ValueError: iri-relative-no-base: relative IRI reference "rel" cannot be resolved:
no base IRI is in scope; add a base to the document (`@base`/`BASE` in
Turtle-family syntaxes, `xml:base` in RDF/XML) or pass a base IRI to the API

被拒绝的四元组不会写入——存储保持不变,且仍然可用。

在工作集变为数据集的每一个节点,这项检查都会重复进行:冻结构建器或存储中待提交的编辑时,对结果进行序列化、规范化或计算摘要时,以及重新打开 .pack 文件时(其字节可能由另一个引擎或较旧的版本写出,或已在磁盘上损坏)。这种重复是有意为之。冻结时的检查是_不变量_——正是它使得相对 IRI 无论从哪个入口进入都无法在 IR 中表示,包括那些根本不经过修改调用的入口。插入时的检查是_诊断_:它的存在是为了让错误能够指出导致它的那一行。二者谁也不能取代谁。

一个可以依赖的例外

空节点标签与字面量的词法形式是任意字符串,不受上述任何规则影响。只有 IRI 才是 IRI。

诊断代码

这些代码稳定且机器可读。每个编解码器、CLI、C ABI、Python 与 wasm 对同一情况都报告同一代码。

代码情况处理方法
iri-relative-no-base在允许相对引用的语法中出现相对引用,而作用域内没有基准为文档添加 @base/BASE/xml:base/@context.@base,或向 API 传入基准(CLI 上用 --base)
iri-not-absolute-by-grammarN-Triples、N-Quads、TriX 或 HexTuples 中的相对引用——包括空引用 <>;或者其 xmlns: 命名空间本身是相对形式的 RDF/XML 元素或属性 QName以绝对形式书写 IRI;基准无济于事——该位置不允许相对引用
iri-non-absolute-base基准_本身_没有 scheme(RFC 3986 §5.1 要求基准是绝对的)提供一个带 scheme 的基准,例如 http://example.org/dir/。文件系统路径是相对引用,而不是基准 IRI——CLI 会在参数边界拒绝它,并提示调用方本意所指的 file:// IRI

RDF/XML 同时出现在两个语法族中是有原因的,上面这一行是其中较窄的那一半。它的 rdf:about / rdf:resource / rdf:ID 值_确实_是引用,并且会相对于 xml:base 解析,这就是为什么前面的表格把它列为允许相对引用。但元素或属性的_名称_是由一个 xmlns: 声明加上一个本地名组合而成的;它不是引用,因此没有任何东西会解析它,而相对的 xmlns:ex="rel/" 会组合出一个任何基准都无法挽救的相对 IRI:

$ purrdf convert data.rdf --from rdfxml --to ntriples
purrdf: error iri-not-absolute-by-grammar: invalid IRI from an XML qualified name:
relative IRI reference "rel/p" is not permitted by this syntax (the caller-supplied
base, <file:///tmp/data.rdf>, is in scope but is never applied here); write the IRI
in absolute form; this syntax admits no relative IRI reference, so supplying a base
will not help

请注意,该消息点名了_确实_在作用域内的基准,并说明它在此处被有意不予应用,因此传入了基准的调用方不会被误导去寻找一个被丢弃的参数。

为每个代码输出的消息本身已经带有补救办法,因此只打印该错误的使用方仍然能告诉其用户该怎么做。

写出基准

IRI 解析(resolution)在序列化一侧有其镜像。能够表达文档基准的语法,在提供了基准时会输出它,并相对于它对其中的 IRI 进行相对化:

语法读取基准写出基准
Turtle、TriG@base / BASE@base
RDF/XMLxml:basexml:base
JSON-LD、YAML-LD@context.@base@context.@base
N-Triples、N-Quads、TriX、HexTuples否否——仅绝对 IRI

最后一行中的格式到达其写出器时没有基准,输出绝对 IRI。这不是静默丢弃:该格式根本没有表达基准的接口,因此没有可写的内容,而输出对其语法而言仍然有效。在 CLI 上还多一步——如果给出了 --base,而本次运行的两侧_都_无法使用它,该命令会被直接拒绝,而不是接受一个不起作用的标志。见下文。

命令行上的 --base

purrdf convert、query、update 以及其他产生 RDF 的子命令接受 --base <IRI>,它作用于两侧:

  • 解析——它是调用方提供的基准(§5.1.2),因此优先于输入的获取 IRI,但不优先于文档内指令。只有当源语法允许相对引用时,解析一侧才能使用该基准。

  • 序列化——如果目标语法能够写出基准,它会作为输出文档的基准被输出,其中的 IRI 相对于它进行相对化。只有当目标语法会输出基准指令时,序列化一侧才能使用该基准。

  • 两侧都无法使用——用法错误,退出码 2。 只要_任何一侧_使用了基准,它就被采纳,因此 --from turtle --to ntriples --base … 没有问题(解析一侧使用它),--from ntriples --to turtle --base … 也没有问题(序列化一侧使用它)。但 --from ntriples --to ntriples --base … 无处安放它。CLI 不会在静默忽略该标志之后以 0 退出,而是点名两侧并拒绝:

    $ purrdf convert data.nt --from ntriples --to ntriples --base http://example.org/dir/
    purrdf: --base has no effect on this run: on the source `data.nt`, ntriples's
    grammar admits no relative IRI reference, so nothing in the document resolves
    against a base; and on the --to target, ntriples can express no base directive,
    so nothing is written under one or relativized against it. Drop --base, or name
    a syntax that carries one (turtle, trig, rdfxml, jsonld, yamlld)
    $ echo $?
    2
    

    这一裁决取自格式注册表的 admits_relative_iri 与 emits_base 两列,因此新注册的语法由它自己的那一行来分类,而不是靠一份手工清单。它适用于 convert、validate、reason、entails、consistency 与 project。query、update、shex 的形状映射以及 describe --iri 被有意豁免:它们各自都有一个来自命令行文本、没有自身文档的 IRI 接口,因此无论格式行怎么说,--base 在那里都绝不会不起作用。

# 文件输入无需任何标志:它自身的 file:// 获取 IRI 就是基准。
purrdf convert data.ttl --to ntriples

# stdin 没有获取 IRI,因此其中的相对 IRI 需要显式提供基准。
cat data.ttl | purrdf convert - --from turtle --to ntriples \
  --base http://example.org/dir/

如果 data.ttl 声明了自己的基准,则保留该解析基准,同时写出一个新的输出基准:

purrdf convert data.ttl --to turtle --base http://example.org/v2/

--base 在输入之处即被验证。不是绝对 IRI 的值属于用法错误,而形如路径的值会得到一个推导出的建议——对 ./vocab/,会给出它实际所指的 file:// IRI,该 IRI 是解析得出的,而不是拼接而成的。

传给 purrdf shex 的形状映射是没有自身文档的命令行文本,因此 --base 是它唯一可能拥有的基准。与之相对,--schema 文件是一份独立的文档,相对于它自身的获取 IRI 或它自己的 BASE 指令进行解析。

一致性

RFC 3986 §5.4 的规范性解析表在 crates/iri/tests/ 中直接针对 IRI 解析器(resolver)进行断言。在端到端层面,W3C rdf-tests 的 IRI-resolution-01/02/07/08 用例——即在真实文档中经由 @base 驱动的同一张表,外加带尾部斜杠的基准、文件路径、空段以及含冒号的段——随库固化于 crates/rdf/tests/corpus/w3c/{turtle,trig}/iri/ 之下,并在每次运行时评分。