Copyright © 2025-2026 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
本文档定义了 SPARQL-RL,一种 面向 RDF 的 Datalog 风格规则语言。
SPARQL-RL 通过针对输入 RDF 图对一组声明式 规则进行求值来生成新的 RDF 数据。规则使用一种 类似 SPARQL 的文本语法编写。
本规范由 W3C Data Shapes 工作组发布。
本节描述本文档 发布时的状态。当前 W3C 出版物列表以及本技术报告的最新修订版可在 W3C 标准和草案 索引中找到。
本文档由 Data Shapes 工作 组作为 工作草案,按照 推荐标准 轨道发布。
作为 工作草案发布并不意味着 获得 W3C 及其成员的认可。
这是一份草案文档,可能随时由其他 文档更新、替换或废止。不应以 进行中的工作以外的形式引用本文档。 此即将发布的推荐标准的未来更新可能会纳入 新特性。
本文档由一个 根据 W3C 专利 政策运作的工作组制定。 W3C 维护着一份 与该工作组交付成果相关的任何专利 披露的公开列表, 该页面还包含 披露专利的说明。任何实际 知晓某项专利,并且认为该专利包含 必要权利要求 的个人,都必须按照 W3C 专利政策第 6 节的规定披露该信息。
本文档受 2025 年 8 月 18 日 W3C 流程文档约束。
本文档介绍 SPARQL-RL。它是一种通过声明式规则 从现有 RDF 数据派生新 RDF 三元组的机制。 本文档定义了基于规则的推理的语法和语义。
SPARQL-RL 的实现提供以下一种或两种操作: 推理和查询。 推理操作将规则应用于给定的 基础图,并生成一个推理图,其中包含 由规则求值派生且未出现在基础图中的 RDF 三元组。 是否将推理图与基础图合并是可选的,并 由用户决定。 查询操作确定给定的 目标模式是否可以以及如何使用规则 从基础图派生出来。
SPARQL-RL 允许创建新的 RDF 术语,包括空白节点, 这些术语可以用于规则头中的三元组模板。
SPARQL-RL は失敗による否定をサポートしており、これにより 規則が評価される順序に応じて異なる推論グラフが 生成される可能性がある。これを防ぐため、規則の評価には 層化という手法を用いる。この手法は規則間の順序を定め、 常に同じ推論グラフが生成されることを保証する。
SPARQL-RL はグループ化と集約を提供し、その際、 集約関数の結果を規則の本体内の変数に代入する。
当上下文明确时,SPARQL-RL 可以称为 SRL。
以下规范提供了本文档中使用的 基础术语:
本文档中的 RDF 数据示例使用 RDF 1.2 Turtle [RDF12-TURTLE]。
在本文档中,使用以下命名空间前缀绑定:
| 前缀 | 命名空间 |
|---|---|
rdf: |
http://www.w3.org/1999/02/22-rdf-syntax-ns# |
xsd: |
http://www.w3.org/2001/XMLSchema# |
: |
http://example/ |
在整个文档中,带颜色标记的框包含 SPARQL-RL 语法中的规则 或 Turtle 语法中的 RDF 图。
# 此框表示规则
# 此框表示输入数据
# 此框表示推理数据
除标记为非规范性的章节外,本规范中的所有编写指南、图表、示例和注释均为非规范性内容。本规范中的其他所有内容均为规范性内容。
本文档中的关键字 MAY、MUST 和 MUST NOT 仅当它们以全大写形式出现时(如上所示),才应按照 BCP 14 [RFC2119] [RFC8174] 中的规定进行解释。
本规范为以下内容定义了一致性标准:
符合规范的 SPARQL-RL 文档 是一个采用 UTF-8 编码 [RFC3629] 的 RDF 字符串, 并且符合 7.6 语法 中定义的语法和附加约束。
本规范未定义 SPARQL-RL 处理器 如何处理不符合规范的 SPARQL-RL 文档。
本节为非规范性的。
给定一个 基础图和一个 规则集,SPARQL-RL 会推理出新的三元组。 求值的输出是一个 推理图,其中包含派生出的 未出现在基础图中的三元组。
每个 规则都有一个模式,称为主体,以及一个结果 模板,称为头部。 执行规则时,会查找主体中变量的值 使主体与合并后的基础图以及 到当前执行阶段为止的所有推理三元组相匹配。 然后使用这些值实例化 规则头中的三元组模板,以生成新的推理三元组。
规则会一直执行,直到不再推理出新的三元组, 并且随着新的推理三元组 可用,规则可能会执行多次。
SPARQL-RL 的执行定义确保规则执行顺序 在创建新的 RDF 术语时不会导致不同结果, 包括新的空白节点,或在测试某个 模式是否不存在时。换言之,都会生成相同的推理图, 无论规则执行顺序如何。
SPARQL-RL 具有受 SPARQL 1.2 查询语言启发的易于人类使用的语法。 规则集求值包含与 SPARQL 类似的元素,但在 细节上有所不同,以确保生成相同的推理图, 无论规则执行顺序如何。
本节中的示例描述软件组件及其 依赖关系:一个前端调用应用服务器,而 应用服务器查询一个存在已知 漏洞的数据库。
一个 三元组模式 与三元组数据进行匹配,以便为 变量赋值。该模式具有 三个元素,每个元素都是一个 RDF 术语、一个变量, 或者是一个 三元组术语 的包含变量的模式。
在第一个示例中,基础图和规则集中包含以下数据:
将上述规则应用于数据,将得出以下结论:
:frontend 依赖于 :app,而 :app 依赖于
:db,无论依赖关系的类型如何。
随后,我们可以通过添加一条依赖于
其他规则所生成的 :dependsOn 三元组的规则来推导 :exposedTo 关系
(另见下文的 4.3 规则
依赖):
上一节中的 :exposedTo 规则仅涉及直接
依赖::frontend 并不直接依赖于 :db,因此
不会为 :frontend 推断出 :exposedTo 三元组。
为了沿任意长度的依赖链传播暴露关系,我们
将该规则替换为两条规则:组件暴露于其自身具有的
漏洞,并且组件也暴露于任何
其直接依赖项所暴露的漏洞:
与前一个示例相比,这里增加了
:db :exposedTo :vuln1 — 数据库暴露于其
自身的漏洞 — 以及 :frontend :exposedTo :vuln1:
暴露关系通过依赖链到达 :frontend
,无论该依赖链有多长。
最后一条规则是一条递归规则:规则体 依赖于规则头。
我们可以在规则体中使用表达式来限制变量的值 ,这些变量用于规则体的匹配。 例如,给定每个漏洞的严重程度, 我们可以为暴露于严重 漏洞的组件赋予一个状态:
FILTER 对表达式求值,并保留当前的
变量绑定集合(如果表达式求值为真),而它会丢弃
当前的变量绑定集合(如果表达式求值为假)。
这与
FILTER 这一 SPARQL 操作相同
,并且 SPARQL-RL 提供了许多与 SPARQL 相同的函数和运算符。
否定用于指定一个不得匹配的模式。 这称为“失败即否定”。
为了对否定元素求值,规则集求值算法 确保所有可能生成与该模式匹配的三元组的规则 都已执行完毕,该模式位于否定元素中。这称为 分层,并确保否定所依据的是 所有相关的可能三元组,无论它们来自数据还是 来自其他规则。包含否定的规则 被称为依赖于生成这些三元组的规则 (参见下文的 4.3 规则 依赖)。
赋值允许规则将表达式的结果赋给一个变量 ,该变量位于规则体中。这可用于创建新的 RDF 术语,其依据 是数据。
空白节点可以用于规则头,并且每个空白节点都会生成一个全新的空白节点 ,每当规则求值生成三元组时都是如此。
涉及赋值的规则、通过对规则头求值来创建空白节点的规则、涉及聚合的规则,以及其模板涉及包含变量的三元组术语的规则,都是仅运行一次的规则。此类规则在所有可能生成其所依赖数据的规则之后运行,并且在任何依赖于其所生成数据的规则之前运行。
此条件确保此类规则不会循环回到自身,从而导致创建无界数量的 RDF 术语,或导致三元组术语的无界嵌套。
分组与聚合允许规则将模式的解归入各个分组,然后为每个分组计算汇总值。聚合值被赋给变量,这些变量随后可用于规则头。包含分组元素的规则是仅运行一次的规则。
分组元素处理前面规则元素产生的每个解序列时,只取分组键的绑定,求值分组模式,然后使用分组键对解映射进行分组。
输出是一个解序列,每个分组对应一个解映射:即用分组键的值和聚合变量的值扩展输入解映射。
例如,我们可以统计每个组件暴露于多少个漏洞:
对前面的三元组模式找到的每个组件,都匹配分组模式
?x :exposedTo ?v。解按照分组键(此处为 ?x)归入各个分组。对于每个分组,将
COUNT(*)(该分组中的解的数量)赋给聚合变量 ?n。
:logger 未暴露于任何漏洞,因此分组模式对它没有解,也就没有分组,因而不会为 :logger
推断出 :vulnerabilityCount 三元组。
仅在分组模式内部绑定的变量(如 ?v)不在分组元素之后的作用域内:只有分组键和聚合变量会添加到解映射中,因此 ?v 不能用于后续的规则元素或规则头。
一个 SPARQL-RL 规则集 可以纳入其他规则集,方式是 将其 URL 包含在该规则集的 规则集导入 中。 这允许将规则组织成可在规则集之间共享的库。
规则集的 IMPORTS 语句会先被处理,然后才会对
规则集中的任何规则求值。在导入步骤中,如果导入的规则集
自身也有导入项,则这些导入项也会被递归处理。
在处理规则集期间遍历 IMPORTS 语句时,可能
遇到循环导入。一个规则集只会被导入一次;
导入语句图中的环不会导致无限循环。
对导入规则集的支持是可选的,对于 SPARQL-RL 处理器 而言。 参见 4.5 处理 导入 以了解更多详细信息。
数据块直接为规则集求值提供 RDF 三元组。 数据块中的三元组会被添加到推断图中,并可用于 规则体中的匹配。
例如,规则集可以附带已知事实,这些事实是直接 断言的,而非从数据中推导出的:
数据块等价于规则体为空的规则: 其三元组是推断图的一部分,无需对任何规则 求值。
在 规则集求值 期间,可能 需要仅对原始的 基础图 进行模式匹配, 而不对任何推断出的三元组进行匹配。
设置默认值就是一个例子。 规则必须测试基础图是否已包含某个值,如果没有, 则该规则可以计算一个默认值。
使用 NOT DATA 允许规则体的其他部分匹配推断出的三元组。
也可以执行所有 规则体 匹配,针对 基础图,方法是
使用 WHERE DATA。
此类规则便仅依赖于来自基础图的匹配。
规则被组织成 规则集。 一个规则集和一个数据图(即 基础图)是求值的输入。 输出是一个图,称为 推断图, 它是求值过程生成的三元组的集合 ,这些三元组未出现在 基础图 中。
在求值期间,基于一条规则推断出的三元组 可用于其他规则中的匹配。 规则集求值持续进行,直到 无法再推断出更多三元组 ,这些推断以 规则集 和 基础图 为输入。
求值首先执行以下步骤来 准备规则集 ,然后才对规则本身求值:
一旦一个 规则集 准备完毕,求值便继续进行 ,依次取出每个 层,按分层中的顺序, 对该层中的规则求值直至完成, 然后继续处理下一层。 当执行规则推断出三元组时,这些三元组会立即 可供其他规则在执行时使用。
SPARQL-RL 抽象语法是 SPARQL-RL 的逻辑结构。 它用于定义 SPARQL-RL 的执行算法。
一个 三元组模板 是一个三元组,其中每个元素都是 一个 变量、一个 RDF 项,或者是一个模板,用于表示 三元组项,其中包含 变量。 该元组的第二个元素是一个 IRI 或一个 变量。 三元组模板 出现在 头部 中,该头部属于一个 规则,并 用于生成 RDF 三元组。
.evaldata,
用于指示使用哪个图来匹配
该 否定元素主体。
IMPORTS 语句
用于指定求值过程中将包含的其他规则集。
.evaldata
用于指示使用哪个
图来匹配 三元组模式,这些模式位于主体中。
可以为规则指定一个 URI 或空白节点,以帮助标识它。
一个 单次运行规则 是一个恰好运行一次的规则,运行于 规则集求值过程中的某个特定时刻。 一个 规则 属于 单次运行 规则,如果以下任一条件成立:
查询 是确定给定模式是否 可从一个 基础图 推导出的操作,使用该 规则集,并 返回一个 解序列,其中每个 解映射 为变量给出 RDF 术语。
查询 的效果相当于执行一次 推断 操作, 然后将目标模式与合并后的 基础图 和 推断 图 进行匹配。
该 查询 操作可以不对所有规则求值;而是,它 可以只对回答查询所必需的规则求值 ,以满足查询目标。
在一个 三元组模式 或一个 三元组 模板 中, 元组的位置 1 非正式地称为主语, 位置 2 非正式地称为谓语,而 位置 3 非正式地称为宾语。
一个序列中的元素(该序列由 规则元素 组成)从 1 开始编号。
以下记法用于规则集和规则的各个组成部分。
| 组成部分 | 用途 |
|---|---|
ruleset.rules |
规则,属于一个 规则集。 |
ruleset.data |
该 RDF 图 由以下各项的并集形成:数据块,这些数据块位于规则集中。 |
ruleset.imports |
一组 导入,属于一个 规则集。 |
rule.head |
这些 三元组模板 属于该 规则头。 |
rule.body |
这些 规则元素 属于该 规则。 |
rule.evaldata |
一个布尔标志;如果为 false,则 规则体 的 匹配 针对该 基础图。 |
rule.id |
规则的标识符,是一个 空白节点 或 IRI。 |
filter.expr |
该 表达式 属于一个 过滤 元素。 |
assign.var |
该 赋值变量 属于该 赋值元素。 |
assign.expr |
该 赋值表达式。 |
negation.inner |
该 否定元素主体 属于一个 否定元素。 |
negation.evaldata |
一个布尔标志;如果为 false,则 negation.inner 的匹配
针对该 基础图。 |
group.keys |
一组 分组键,属于一个 分组 元素。 |
group.pattern |
该 分组模式 属于一个 分组元素。 |
group.aggregations |
这些 聚合 赋值 属于一个 分组元素。 |
aggregation.variable |
该 聚合变量 属于一个 聚合赋值。 |
aggregation.function |
该 聚合函数 属于一个 聚合赋值。 |
良构性 是一组关于抽象语法的条件,适用于 一个 规则集。这些条件共同确保 一个 变量,若位于 头部(属于某个规则),则其值已定义在 主体(属于该规则)中; 每个变量,若位于一个 过滤元素 或 赋值表达式 中,则在求值时具有一个值; 并且每个 赋值变量 和 聚合变量 在规则中都是新的,且此前未在规则体中使用过。
令 S 为一个序列,其元素为 规则元素。
令 elti 为 S 的第 i 个元素, 并令 N 为该序列的长度。
令 V0 为一组变量,称为初始 变量,所属序列为 S。
将 vars(S, i, V0) 定义为一个变量集合 这些变量在序列 S 的第 i 个元素之后处于作用域内, 给定初始变量 V0。vars(S, i, V0) 的定义采用 归纳方式:
{ avar } ∪ vars(S, i-1,
V0)。
将 vars(S, V0),即 S 中处于作用域内的变量集合,定义 为 vars(S, N, V0),即处于作用域内的变量集合 在该序列的最后一个元素之后。
一个序列,其元素为 规则元素,记为 S, 是一个 良构序列, 给定一组变量 V0, 如果以下条件对每个 i 均成立, 其范围为 1 到 N:
一个 规则 是一个 良构 规则 如果以下元素组成的序列:规则元素,属于 规则体, 是一个 良构序列,且起始时 V0 为空集, 并且每个变量,若位于一个 三元组模板 中,该模板属于 规则头, 则是 vars(S, V0) 的元素。
规则 R1 依赖于规则 R2,如果第二个规则的输出
影响第一个规则主体的求值。也就是说,R2 的头部
有一个 三元组模板,能够生成一个三元组,该三元组可匹配
一个 三元组模式,位于 R1 的主体中,
该模式可以作为一个 三元组模式元素,也可以位于一个 否定元素
或一个 分组模式 内。
依赖有两种:
封闭依赖 和 开放
依赖。
一个封闭依赖确保规则 R2
在规则 R1 执行之前已生成其所有可能的输出。
如果规则依赖不是封闭的,则它是一个开放依赖。
它允许第一条规则 R1 执行
,同时规则 R2 仍可能再次运行以生成更多三元组
,这些三元组随后可能使 R1 使用来自 R2 的新三元组重新求值。
标志 .evaldata 设置为 false 的规则使用
基础图 进行匹配,并且该规则没有依赖。
在第一个示例中,第一条规则具有一个开放依赖 ,依赖于第二条规则。
在第二个示例中,第一条规则具有一个封闭依赖
,依赖于第二条规则:三元组模式 ?x :status :criticallyExposed
出现在一个 否定元素 内部,该元素属于第一条规则,并且该模式
匹配一个 三元组模板,该模板位于第二条规则的规则头中。
第一条规则不能求值,直到第二条规则
已生成其所有可能的输出。
规则 R1 依赖
于
规则 R2,如果
R1.evaldata 为 true,并且任一 三元组
模式(位于 R1 的主体中),
无论其作为一个 三元组模式元素,
还是位于一个
否定元素 neg 中,其中
neg.evaldata 为 true,
或者位于一个 分组模式 中,
依赖于一个
三元组模板
位于 R2 的头部。
如果存在三元组模板变量的值,使得在模板中用值替换变量
得到一个三元组 T2,其中
T2 与 T1 相等,则一个三元组模板可以生成一个
RDF 三元组 T1。
类似地,如果存在一组变量值,使得将三元组模式中的变量替换为这些值后,匹配三元组 T1,
则该变量值必须使
三元组模式中的变量被替换,包括
将空白节点视为
变量,
从而得到三元组 T2,其中
T2 与 T1 相等
。
如果一个变量在三元组 模板 或三元组模式中使用多次,则使用相同的 RDF 术语 作为替换项。
在三元组模式中用 RDF 术语替换变量 包括替换三元组术语内部的变量。
规则之间的依赖表示为一个有向图, 称为依赖图。 图的顶点是规则集中的规则,而边 根据依赖是开放依赖还是闭合依赖, 分别标记为开放或闭合。
R1
依赖于规则 R2,则存在一条从规则 R1 到规则 R2 的边。
该边根据依赖
是开放依赖还是闭合依赖,
分别标记为开放
或闭合。
R1 到 R2 的路径,
则规则 R1 对规则
R2 具有传递依赖。
依赖图不受基础图影响。
以下算法给出一种 构造以下对象的可行方法:依赖 图,来源为一个 规则集。符合性取决于是否生成一个依赖图 该图满足依赖图的定义, 而不取决于是否使用此过程。
define mergeLabel(oldLabel, newLabel):
# 封闭依赖覆盖开放依赖。
if oldLabel == "open" and newLabel == "open":
the result is "open"
else:
the result is "closed"
endif
enddefine
# 输出 -- 以规则为顶点且边带有标签的依赖图。
define buildDependencyGraph(ruleSet):
# edgeLabelMap 将 (R1, R2) 映射到 "open" 或 "closed"
let edgeLabelMap be a map from pair (rule, rule) to label
foreach rule R1 in ruleSet.rules:
let bodyDependencies = {}
if R1.evaldata then:
# 将规则中的每个三元组模式 TP 分类为需要 "open" 或 "closed"
# 取决于它是否位于否定元素或分组模式中。
foreach rule element RBE in R1.body:
if RBE is a triple pattern element of triple pattern TP:
let item be a pair (TP, "open")
add item to bodyDependencies
else if RBE is a filter element:
# 不执行任何操作
else if RBE is a negation element:
if RBE.evaldata then:
foreach triple pattern TP in RBE.inner:
let item be a pair (TP, "closed")
add item to bodyDependencies
endfor
endif
else if RBE is an assignment element:
# 不执行任何操作
else if RBE is a group element:
foreach triple pattern TP in RBE.pattern:
let item be a pair (TP, "closed")
add item to bodyDependencies
endfor
endif
endfor
endif
foreach pair (triple pattern TP, depLabel) in bodyDependencies:
if R1 is a run-once rule:
set depLabel to "closed"
endif
# 查找此三元组模式元素或否定元素的依赖关系。
foreach rule R2 in ruleSet.rules:
foreach triple template TT in R2.head:
if triple pattern TP matches triple template TT:
let key = (R1, R2)
if edgeLabelMap contains key:
let oldLabel = edgeLabelMap.get(key)
let merged = mergeLabel(oldLabel, depLabel)
edgeLabelMap.set(key, merged)
else:
edgeLabelMap.set(key, depLabel)
endif
endif
endfor
endfor
endfor
endfor
let DP = { }
foreach entry ((R1, R2), label) in edgeLabelMap:
add an edge (R1 -> R2) labeled with label to DP
endfor
the result is DP
enddefine
分层 是划分一个 规则集 的过程 将其划分为一个有序序列,其元素为 分层层级(也称 为“strata”,单数为“stratum”)。规则若位于较低的 层,则其 求值先于位于较高 层 中的规则。
分层 对以下各项之间的依赖关系施加约束:规则 以确保 否定元素、分组元素, 以及 单次运行规则 仅依赖 计算结果,这些结果使用较早(较低)的 层 和 基础图 计算得出。 这保证了一个唯一、定义明确且有限的 求值结果,其求值对象为一个 规则集,求值基于给定的 基础图。
分层过程也可用于作出其他求值 决策。本文档描述了必要条件,用以实现 一致的求值,并给出一种可行的方式来形成 分层。实现需要满足条件, 即此处描述的条件,以获得兼容的行为,但并不 要求按所呈现的形式实现此算法。
一个 分层层级 SL 是一个有序对
由两个不相交的集合构成,其中元素为
规则 (SL.once, SL.general)。
SL.once 恰好是这些 单次运行
规则,它们属于该层级;
这些规则中的每一个都被求值
恰好一次,发生在以下对象的求值开始时:分层层级。
SL.general 是该层级中其余规则的集合,
这些规则会被反复求值,直到不再推导出新的三元组。
一个 分层,属于一个 规则集,是一个序列,其元素为 分层层级。 每个规则,若位于一个 规则集 中,均恰好出现在某一个集合中,该集合属于某一个 分层层级。
这些 分层层级,属于一个 分层,其编号 从零开始。层号,对于一个 规则,指的是编号 该编号属于一个 分层层级,其中包含该规则。 规则若具有较小的 层号,则位于一个 较低 的层; 规则若具有较大的 层号,则位于一个 较高 的层。
分层 有定义的前提是以下 条件得到 满足。如果一个 规则集 不满足此条件,那么本 规范不定义求值结果,其求值对象为 这样的一个 规则集。
以下算法给出一种可能的分层,其依据仅为 规则集。
define stratification(ruleSet):
let DP = Dependency graph for the rule set.
let stratumMap be a map from rule to integer
# 依赖图应满足分层条件。
# 对无界分层的检查是一项防护措施
# 用于应对分层条件被违反的情况。
let limit = num rules + 1
let maxStratum = 0
# 初始化 stratumMap
foreach rule in ruleSet.rules:
stratumMap.set(rule, 0)
endfor
let changed = true
while changed:
changed = false
foreach edge E in DP:
# 从 pRule 指向 qRule 的带标签的边
let pRule = source of edge
let qRule = destination of the edge
let label = edge label
if label == "open":
if stratumMap.get(pRule) < stratumMap.get(qRule):
stratumMap.set(pRule, stratumMap.get(qRule))
changed = true
endif
endif
if label == "closed":
if stratumMap.get(pRule) <= stratumMap.get(qRule):
let xStratum = 1 + stratumMap.get(qRule)
if xStratum > limit:
# 违反分层要求
error "分层错误"
endif
stratumMap.set(pRule, xStratum)
maxStratum = max(maxStratum, xStratum)
changed = true
endif
endif
endfor
endwhile
# 初始化结果映射。
let stratumRules be a map from integer to rules
foreach i = 0 to maxStratum:
stratumRules.set(i, {})
endfor
# 将 stratumMap 中具有相同层号的规则归集在一起
foreach rule R in map stratumMap:
let stratumNum = stratumMap.get(R)
add R to stratumRules.get(stratumNum)
endfor
# 将每个层级划分为 once 和 general
let stratumLevels be a sequence of pairs of sets of rules
foreach i = 0 to maxStratum:
let rules = stratumRules.get(i)
let once = { R in rules | R is a run-once rule }
let general = rules \ once
stratumLevels.set(i, pair(once, general))
endfor
the result is stratumLevels
enddefine
逐步应用依赖分析和 分层到一个完整规则集,并继续进行 求值的过程,见 6.6 详细 示例。
从 Web 读取文档会涉及安全问题。 是否支持导入规则集由SPARQL-RL 处理器自行决定。 此外,实现可以提供部分支持, 例如仅支持导入某些规则集,而不支持其他规则集, 并且可能从经过验证的副本导入。
以下条件适用于导入处理:
IMPORTS 语句,并
报告错误。
IMPORTS 语句时出现无限循环。
已解析规则集是通过 递归读取另一个规则集的导入中提及的所有规则集, 从该规则集生成的。
示例算法见 B. 导入算法 示例。
本节为非规范性的。
SRL 和 SPARQL 关系密切。SRL 旨在兼容 SPARQL,并且 SRL 中的许多构造源自或受到启发于 SPARQL 模式匹配。不过,两者也存在一些差异。
在 SRL 中,RULE 变量始终在使用前绑定,无论是用于
FILTER 或 SET 的表达式中,还是用于 三元组模板 中,
这些模板属于
规则头。SPARQL CONSTRUCT 查询和
INSERT 更新会
产生部分结果,如果某个变量出现在 CONSTRUCT 或
INSERT 模板中,但未在 WHERE 子句中被赋值。
SET 形式与 SPARQL 的 BIND 形式具有不同的
错误处理行为。在 SET 中遇到错误会导致
当前解被过滤掉,而 BIND 不会
设置当前解中的变量,但会继续传递该解。
SET(?var := expr) 等同于
SPARQL 中的
BIND(expr AS ?var),后接
FILTER(BOUND(?var))。
WHERE 子句的语法
不包含 UNION 或 OPTIONAL 语法。
这些 SPARQL 元素可能导致未绑定的变量。
UNION 或 OPTIONAL 的效果可以通过使用
良构规则 来实现。
其他差异包括:
NOT 的语法将内部主体限制为三元组
模式和过滤器,并且不允许嵌套模式。
NOT 作为过滤器执行,执行位置是规则体中
书写它的位置,这与 SPARQL FILTER NOT EXISTS 不同。
/ 和 ^ 的路径。
这些路径可以展开为三元组模式。
它不允许任意长度运算符 * 和 +。
任意长度路径可以在 SPARQL-RL 中表示,
使用递归作为一种更通用的方法。
COALESCE 或 BOUND。也没有哈希函数。其中
没有 RAND,因为它每次被
调用时都会产生不同的结果。
NOW() 是允许的,并被定义为返回同一个
时间点,在整个规则集求值过程中都是如此。
BNODE 会在每次调用时产生不同的空白节点。
规则集求值的结果将产生彼此
图同构的推断图。
UUID 和 STRUUID 可用于生成唯一标识符。
规则不应检查这些生成的标识符,而应仅使用它们
来生成三元组。
可以使用专用生成器来测试可重现性,
类似于测试 NOW 的结果。
本节定义了对给定数据求值规则集所产生的结果。 实现不要求使用此算法。 实现可以使用任何能够生成相同结果的算法。
输入:图 G(称为基础图)和规则集 RS。 输出:由推导出的三元组组成的 RDF 图 GI
推导出的三元组不包括 该 基础图中已有的三元组。
μ : V → T,
其中 V 是所有变量的集合
而 T 是以下所有项的集合:RDF
项。
μ 的定义域表示
为 dom(μ),它是一个子集
属于 V,且 μ 在其上有定义。术语
解 可用于明确指代一个 解映射 的场合。
用 μ0 表示满足以下条件的解映射:
dom(μ0) 为空集。
该 替换函数,或简称为一个替换,
是一个函数
subst(μ, 三元组模式)
它返回一个 三元组模式
其中,每个出现位置(位于 三元组模式 中),若对应的变量
var 属于
dom(μ)
则替换为一个 RDF 项,该项由
解映射 为 var 给出。
如果作为结果的三元组模式没有变量,则它是一个
RDF 三元组。
函数
subst(μ, 三元组模板)
的定义类似。变量的每个出现位置,若位于
三元组模板 中,都会被替换为一个 RDF 项,该项由
解映射 为 var 给出。
replaceBlankNodes(列表,其元素为 三元组模板)
它
返回一个列表,其元素为 三元组模板,其中每个 空白节点
若位于实参中,则被替换为一个新的空白节点。
同一空白节点的多次出现
会被替换为同一个新的空白节点。
令 G 为一个 RDF 图,并令 TP 为一个 三元组模式。
函数 graphMatch(G, TP) 返回一个集合,包含所有可能的
解,这些解
在应用于该三元组模式时,会生成一个三元组,该三元组位于
图 G 中。
令 S1 和 S2 为解。
compatible(μ1, μ2) = true
if forall v in dom(μ1) intersection dom(μ2)
μ1(v) = μ2(v)
compatible(μ1, μ2) = false otherwise
merge(μ1, μ2) = μ such that
μ(v) = μ1(v) if v in dom(μ1)
μ(v) = μ2(v) otherwise
merge(S1, S2) = { μ |
μ1 in S1, μ2 in S2
and compatible(μ1, μ2)
μ = merge(μ1, μ2) }
merge(μ1, μ2) 的定义域
是 domain(μ1) ∪ domain(μ2)。
如果两个解没有共有变量,则它们兼容, 且这两个解的合并是以下两者的并集: μ1 和 μ2。
EBV(x) 返回有效布尔值
对应于一个 RDF 项。
规则集求值涉及收集所有导入的规则集, 按照 4.5 处理 导入中所述构建一个单一的组合规则集。
接下来,使用以下步骤准备组合规则集 以进行求值。
一个表达式,无论其用于一个 过滤元素 还是 一个 赋值元素,其求值依据 都是一个 解映射。该解映射提供 表达式中每个变量的 RDF 项值。 以下章节中的良构性要求:4.2 良构性条件 确保表达式中的所有变量 都出现在解映射中。
define evalFunction(F, μ):
# F 是一个表达式:一个 RDF 项、一个变量,或 op(expr1, ..., exprN)
# 其中 op 是一个函数或函数形式。
# μ 是一个解映射
# 求值过程中的任何错误都会导致 evalFunction 返回一个错误。
if F is an error:
return error
endif
if F is an RDF term:
return F
endif
if F is a variable:
# 根据良构性,F ∈ dom(μ)。
return μ(F)
endif
# F 的形式为 F = op(expr1, ..., exprN)
if op is a functional form (e.g., IF, logical-or):
# 按 op 的特定规则求值;op 可能只对部分实参求值。
# 例如,IF(c, t, f) 对 c 求值,然后恰好
# 对 t 或 f 中的一个求值。
return the value defined for op over expr1, ..., exprN under μ
endif
# op 是一个普通函数:先对所有实参求值。
return op(evalFunction(expr1, μ), ..., evalFunction(exprN, μ))
enddefine
define evalAggregate(AGGF, SEQ):
@@
# AGGF 是一个聚合函数,其实参包括一个变量
# 和一个解序列。它返回一个 RDF 项值。
# 注意,DISTINCT 仅允许用于 COUNT
# 特殊情况 COUNT(*)。
enddefine
| 聚合函数 | SPARQL 等价项 |
|---|---|
COUNT |
SPARQL COUNT
其中 distinct 设为 false |
COUNT DISTINCT |
SPARQL COUNT
其中 distinct 设为 true |
MIN |
SPARQL MIN |
MAX |
SPARQL MAX |
SUM |
SPARQL SUM |
计算聚合函数时的任何错误都会导致一个 求值错误,且该分组不会添加到结果中。
一条 规则 的求值方式是计算一个 解 序列,其来源为 规则体 然后使用每个 解映射,这些解映射属于该 解序列,以便 利用 规则头 生成三元组。
在规则体中,位于 三元组模式 中的空白节点的行为与变量相同。 这兼容于 SPARQL 图模式匹配。
# 对规则体求值
# 此函数返回一个解序列
define evalRuleElements(B, SEQ, G, GD):
where
B is a sequence of rule elements
SEQ is a solution sequence
G and GD are RDF graphs
foreach rule element rElt in B:
if rElt is a triple pattern element TP:
X = graphMatch(G, TP)
SEQ1 = {}
foreach μ1 in X:
foreach μ2 in SEQ:
if compatible(μ1, μ2):
μ3 = merge(μ1, μ2)
add μ3 to SEQ1
endif
endfor
endfor
endif
if rElt is a filter element F:
SEQ1 = {}
foreach solution μ in SEQ:
let x = evalFunction(F.expr, μ)
if x is not an error and EBV(x) is true:
add μ to SEQ1
endif
endfor
endif
if rElt is a negation element N:
SEQ1 = {}
foreach solution μ in SEQ:
S = sequence{ μ }
if N.evaldata:
NEG = evalRuleElements(N.inner, S, G, GD)
else:
NEG = evalRuleElements(N.inner, S, GD, GD)
endif
if NEG is empty:
add μ to SEQ1
endif
endfor
endif
if rElt is an assignment element A:
SEQ1 = {}
foreach solution μ in SEQ:
let x = evalFunction(A.expr, μ)
if x is not an error:
# 将映射 A.var -> x 添加到解 μ 中
let μ2 be a solution mapping μ ∪ { (A.var, x) }
add μ2 to SEQ1
else:
# 错误:丢弃解 μ
endif
endfor
endif
## COUNT(*) 如何处理?
if rElt is a group element GRP:
SEQ1 = {}
foreach solution μ in SEQ:
let μp be the restriction of μ to GRP.keys
let SEQX = evalRuleElements(GRP.pattern, { μp }, G, GD)
# 将解映射分配到各个分组。
let KGRP = empty map of solution mappings to solution sequences
foreach solution μx in SEQX:
let μk be the restriction of μx to GRP.keys
if KGRP[μk] is defined:
add μx to the solution sequence KGRP[μk]
else:
KGRP[μk] = solution sequence of one element μx
endif
endfor
# 对于每个分组,计算聚合函数,并扩展
# 当前解映射 μ,使用分组键
# 以及聚合赋值的结果。
foreach key K of map KGRP:
let X be the solution sequence of KGRP[K]
let Z be an empty solution mapping
foreach aggregation function F in GRP.aggregations:
let V = calculate F.function(X)
add solution mapping (F.var, V) to Z
endfor
Z = merge(Z, K)
Z = merge(Z, μ)
add Z to SEQ1
endfor
endfor
endif
SEQ = SEQ1
endfor
return SEQ
enddefine
define evalRule(R, G, GD):
where
R is a well-formed rule
G and GD are RDF graphs
let B be R.body
treating blank nodes as variables.
# 仅包含一个不映射任何变量的解的解序列。
let SEQ0 be the solution sequence { μ0 }
if R.evaldata:
let SEQ = evalRuleElements(B, SEQ0, G, GD)
else:
let SEQ = evalRuleElements(B, SEQ0, GD, GD)
endif
# 对规则头求值
let OUT = empty set
foreach μ in SEQ:
let S = {}
# 将规则头中的每个空白节点替换为一个新的空白节点
let HT = replaceBlankNodes(R.head)
foreach triple template TT in HT:
let triple = subst(μ, TT)
add triple to S
endfor
OUT = OUT ∪ S
endfor
return OUT
enddefine
OUT 可能包含也存在于 基础图 中的三元组。
对规则集求值,定义为执行该规则集某个层,这些层来自该规则集的 分层,其中每一层都必须 完整执行, 并按顺序执行,然后才能进入下一层。 对层求值时,首先执行 该层中的每条仅运行一次的规则,然后反复执行该层的 一般规则,直到不再有新的三元组 产生。
let G0 be the input base graph
let RS be the rule set
let D be the graph of all DATA triples in RS
Apply stratification to RS
let LS be the sequence of layers after stratification
# 推理图
let GI = { t ∈ D | t ∉ G0 }
# 求值图:基础图和数据块
let GE = G0 ∪ D
foreach stratum ST in LS:
foreach rule R in ST.once:
let X = evalRule(R, GE, G0)
let Y = { t ∈ X | t ∉ GE }
GI = GI ∪ Y
GE = GE ∪ Y
endfor
let finished = false
while ! finished:
finished = true
foreach rule R in ST.general:
let X = evalRule(R, GE, G0)
let Y = { t ∈ X | t ∉ GE }
if Y is not empty:
finished = false
GI = GI ∪ Y
GE = GE ∪ Y
endif
endfor
endwhile
endfor
the result is GI
本节为非规范性的。
本节逐步展示一个完整 规则集的求值过程:确定规则依赖 (4.3 规则 依赖)、 计算分层 (4.4 分层), 并依次对每个层求值以产生 推理图 (6.5 规则集 求值)。
该示例描述了软件组件及其依赖关系:
一个前端依赖于应用服务器,而应用服务器依赖于
数据库和日志库。数据库存在一个已知
漏洞。规则沿依赖链传播漏洞暴露关系,
为暴露于严重漏洞的组件赋予
:criticallyExposed 状态,为不严重的组件赋予
:safeToDeploy 状态,并为每个严重暴露的组件创建
通知。
(实际中,一个漏洞是否影响组件
取决于部署的版本;该示例省略了版本信息,并
直接声明漏洞。)
为简洁起见,基础图直接声明 :dependsOn 三元组,
而不是像
3.1 三元组
模式中那样从更具体的关系派生它们。
这些规则在注释中标记为 R1 到 R5,本节其余部分
使用这些标签引用它们。
它们是从
3.2 递归到
3.5 赋值
和创建 RDF 术语中引入的规则。
该规则集没有 IMPORTS,因此导入处理
不会改变它;也没有 DATA 块,因此求值仅
从基础图开始。
将每个规则主体中的每个三元组模式与 每个规则头中的三元组模板进行比较,以确定哪些规则 依赖于哪些规则 (4.3 规则 依赖)。
R1 的主体是单个三元组模式
?x :hasVulnerability ?v。没有任何规则头包含能
生成谓语为 :hasVulnerability 的三元组的三元组模板,
因此该模式只能匹配数据。
R1 没有依赖。
R2 的主体有两个三元组模式。
?x :dependsOn ?y 无法匹配任何规则头,因此它只能匹配
数据。?y :exposedTo ?v 与 R1 的头模板
?x :exposedTo ?v 匹配,也与 R2 自身的头匹配。
两次出现都是三元组模式元素
(它们不出现在否定元素内部,并且
R2
不是单次运行规则),因此
R2 对 R1 具有开放依赖,并且
对自身具有开放依赖。
R2 对自身的依赖使 R2 成为递归规则。
R3 的主体中,模式 ?x :exposedTo ?v 与
R1 和 R2 的头匹配,因此 R3 对它们各自具有开放
依赖。模式 ?v :severity ?s 不匹配任何
规则头。
R4 的主体中,模式 ?x rdf:type :Component
不匹配任何规则头:唯一一个谓语为
rdf:type 的头模板位于 R5 中,而它的宾语 :Notification 是
与 :Component 不同的 RDF 术语,因此任何生成的
三元组都无法匹配。
模式 ?x :status :criticallyExposed 出现在
否定元素
NOT { ?x :status :criticallyExposed } 中,并且
与 R3 的头模板匹配。因为该模式
出现在否定元素中,所以 R4 对 R3 具有闭合
依赖。
R5 的主体中,模式 ?x :status :criticallyExposed
与 R3 的头匹配。R5 的头包含一个
空白节点,因此它对 R3 的依赖是闭合
依赖,即使该模式是普通的
三元组模式元素。
任何规则主体中的模式都不匹配 R5 的头模板,
因此没有规则依赖于 R5。
因此,依赖图有五个顶点和六条 边:
R2 → R1(开放)
R2 → R2(开放)
R3 → R1(开放)
R3 → R2(开放)
R4 → R3(闭合)
R5 → R3(闭合)
尽管 R4 和 R5 都不直接依赖于 R1 或
R2,但它们各自通过
R3 对二者都具有传递依赖。
图中唯一的循环是自环 R2 → R2,
它是开放依赖。没有循环涉及闭合
依赖,因此满足分层条件,
并且该规则集具有明确定义的结果。
按照 分层算法, 每个规则都从第 0 层开始。然后反复检查各条边, 直到没有任何变化:
R4 → R3
(闭合)发现 R4 和 R3 都处于第 0 层,因此 R4 被
移到第 1 层。同样,边 R5 → R3
(闭合)将 R5 移到第 1 层。
再次遍历各条边不会产生变化,因此这些层
已最终确定。然后将每个层划分为
单次运行规则和一般规则。
R5 的头中有一个空白
节点,因此它是单次运行规则;该规则集的其他规则
都不是单次运行规则。
分层如下:
R1、R2、R3;没有单次运行规则R5;一般规则 R4
此顺序体现了较高
层中规则的意图。一个组件是否可以安全部署,以及哪些
组件需要通知,只有在
所有 :status :criticallyExposed 三元组都已派生后才能确定
—— 而派生这些三元组的规则 R3 会在
较低的层中完成,同时完成的还有向它提供数据的规则 R1 和 R2。
求值遵循
6.5 规则集
求值的算法。
求值图 GE 以基础图开始,而
推理图 GI 初始为空。每个层都会按顺序求值至
完成:该层的规则按轮次求值,
并重复这些轮次,直到某一轮不产生新的三元组。
该算法并未固定同一轮中
对一个层内规则求值的顺序;此处跟踪使用
R1、R2、R3 的顺序。不同的顺序可能会将相同的
推理分散到不同数量的轮次中,但由于
第 0 层会运行至完成,因此会产生相同的最终图。
由于此规则集没有 DATA 块,因此在求值的每个时刻,GE 都是 G0 加
GI。因此,下面的跟踪
只记录 GI,显示求值进行时它的状态,并
用产生每个新三元组的规则进行标记。
第 0 层,第一轮。
R1:主体匹配
:db :hasVulnerability :vuln1,绑定
?x = :db 和 ?v = :vuln1。
头部产生 :db :exposedTo :vuln1。
该三元组不在 GE 中,因此被添加到 GE 和 GI 中。
R2:主体将 ?x :dependsOn ?y 与
?y :exposedTo ?v 连接。GE 现在包含
:db :exposedTo :vuln1,因此连接得到一个解:
?x = :app、?y = :db 和 ?v = :vuln1
(来自 :app :dependsOn :db)。
头部产生 :app :exposedTo :vuln1,这是新的,
因而被添加。此时还没有 :frontend 的解,因为
在此次对 R2 的求值开始时,
:app :exposedTo :vuln1 尚不在 GE 中。
R3:?x :exposedTo ?v 现在匹配 :db 和
:app;
与 :vuln1 :severity 9.1 连接并应用
FILTER(?s >= 9.0) 后保留两个解。
头部产生 :db :status :criticallyExposed 和
:app :status :criticallyExposed,两者都是新的。
# 第 0 层第一轮后的 GI
:db :exposedTo :vuln1 . # 新增 (R1)
:app :exposedTo :vuln1 . # 新增 (R2)
:db :status :criticallyExposed . # 新增 (R3)
:app :status :criticallyExposed . # 新增 (R3)
产生了新的三元组,因此第 0 层的求值 继续进行下一轮。
第 0 层,第二轮。
R1:与之前一样进行匹配;:db :exposedTo :vuln1 已经
在 GE 中,因此不会产生新内容。
R2:连接现在还会得到
?x = :frontend、?y = :app 和 ?v = :vuln1,产生
新的三元组 :frontend :exposedTo :vuln1。
暴露关系用了两轮沿依赖链传播了两步。
R3:产生新的三元组
:frontend :status :criticallyExposed。
# 第 0 层第二轮后的 GI
:db :exposedTo :vuln1 .
:app :exposedTo :vuln1 .
:frontend :exposedTo :vuln1 . # 新增 (R2)
:db :status :criticallyExposed .
:app :status :criticallyExposed .
:frontend :status :criticallyExposed . # 新增 (R3)
第 0 层,第三轮。
现在每个规则的每个解都只产生已经
位于 GE 中的三元组。GI 保持不变。
没有新的三元组意味着第 0 层已经完成:所有
能够派生的 :exposedTo 三元组以及所有 :status :criticallyExposed 三元组
都已经派生出来。
第 1 层,单次运行规则。 首先对该层的单次运行规则求值, 每条规则恰好求值一次。
R5:主体匹配三个 :status :criticallyExposed
三元组,得到解 ?x = :db、?x = :app 和
?x = :frontend。对于每个解,实例化头部
会创建一个新的空白节点,每个解产生两个三元组,
它们构成每个严重暴露
组件的通知。即使该层中的
一般规则随后会被
重复求值,R5 也不会再次求值。
第 1 层,一般规则的第一轮。
R4:模式 ?x rdf:type :Component 匹配
:frontend、:app、:db 和 :logger。
(它不会匹配刚刚由 R5 创建的
rdf:type :Notification 三元组。)
否定元素 NOT { ?x :status :criticallyExposed } 拒绝
:frontend、:app 和 :db 的解,因为
GE 中分别包含它们的 :status :criticallyExposed 三元组。
只有解 ?x = :logger 保留下来,产生新的
三元组 :logger :status :safeToDeploy。
# 第 1 层后的 GI
:db :exposedTo :vuln1 .
:app :exposedTo :vuln1 .
:frontend :exposedTo :vuln1 .
:db :status :criticallyExposed .
:app :status :criticallyExposed .
:frontend :status :criticallyExposed .
_:n1 rdf:type :Notification . # 新增 (R5)
_:n1 :concerns :db . # 新增 (R5)
_:n2 rdf:type :Notification . # 新增 (R5)
_:n2 :concerns :app . # 新增 (R5)
_:n3 rdf:type :Notification . # 新增 (R5)
_:n3 :concerns :frontend . # 新增 (R5)
:logger :status :safeToDeploy . # 新增 (R4)
第 1 层,一般规则的第二轮。
没有新的三元组。求值完成,并且上面所示的 GI
是最终得到的推理图。
正是分层使这一结果可靠。
如果 R4 在第 0 层第一轮期间、
:frontend :status :criticallyExposed 被派生之前求值,它会
错误地得出 :frontend :status :safeToDeploy,而
R5 会为 :db 和 :app 创建通知,
却不会为 :frontend 创建通知。
将二者都推迟到第 1 层意味着它们能够看到完整的
:status :criticallyExposed 三元组集合,因此每个层中
规则的求值顺序不会影响
最终结果。
一个 SPARQL-RL 文档
是一个 RDF 字符串,使用 UTF-8 编码 [RFC3629],
且符合语法和
附加约束,其定义见
7.6 语法。只有
Unicode 标量值,
即 U+0000 至 U+D7FF
和 U+E000 至 U+10FFFF 范围内的值,
才允许使用。
这排除了
代理码点,
即 U+D800 至
U+DFFF 的范围。
一个 版本标签 是一个字符串,用于标识语法和语义方面的 SPARQL-RL 符合性。
| 版本标签 |
|---|
| "1.2" |
版本声明是一种提示。它应出现在 文档的靠前位置,并位于任何依赖该版本的特性之前。
多个 VERSION 指令
可以出现在一个 SPARQL-RL 文档 中。
每个指令适用于文档中该指令之后的部分,
直到遇到另一个指令或到达文档末尾。
版本标签 也可通过 version
参数给出,该参数属于
媒体类型。如果当前没有
VERSION 指令,则
考虑作为媒体类型的一部分指定的版本。
空白字符
(产生式 WS)用于
分隔两个原本会被错误地
识别为一个终结符的终结符。
下文以大写字母表示的规则名称指明空白字符在何处
具有重要意义;这些规则构成了用于构建
SPARQL-RL 解析器时可选用的一组终结符。
空白字符在产生式
String中具有重要意义。
注释以位于
IRIREF、
STRING_LITERAL1、
STRING_LITERAL2、
STRING_LITERAL_LONG1 或
STRING_LITERAL_LONG2
之外的 # 开始,
并持续到行尾(由
LF 或
CR 标记),
如果注释标记之后没有行尾,则持续到文件末尾。
注释被视为空白字符。
相对 IRI 引用使用基 IRI 按照 统一资源标识符(URI):通用 语法 [RFC3986] 进行解析,仅使用 第 5.2 节中的基本算法。 不执行基于语法的规范化或基于方案的规范化 (RFC3986 第 6.2.2 节和第 6.2.3 节中所述)。 IRI 引用中额外允许的字符按照与 URI 引用中非保留字符相同的方式处理,依据 国际化资源标识符 (IRI) [RFC3987] 第 6.5 节。
BASE
指令定义用于解析
相对 IRI 引用的基 IRI,依据 [RFC3986]
第 5.1.1 节,“嵌入内容中的基 URI
”。
第 5.1.2 节,“来自封装
实体的基 URI”
定义作用域内基 IRI 如何来自封装文档,
例如具有 xml:base 指令的 SOAP 信封或具有
Content-Location 标头的 MIME 多部分文档。
在 5.1.3,“来自检索 URI 的基
URI”中标识的“检索 URI”,
是从中检索特定SPARQL-RL 文档的 URL。
如果上述内容均未指定基 URI,则使用默认
基 URI(第 5.1.4 节,“默认基 URI”)。
每个 BASE 指令都会设置一个新的作用域内基 URI,
相对于前一个基 URI。
SPARQL-RL 文档中使用三种形式的转义:
一个 数字转义序列表示 一个 Unicode 代码点的值。
一个数字转义序列不得产生
范围为 U+D800 到 U+DFFF 的代码点值,
这是
Unicode 代理的范围。
| 转义序列 | Unicode 代码点 |
|---|---|
\u hex
hex
hex
hex
|
一个 Unicode 代码点
位于 U+0000 到 U+D7FF
以及 U+E000 到 U+FFFF
的范围内,
对应于由四个十六进制数字编码的值,按
从最高有效位到最低有效位的顺序解释。 |
\U hex
hex
hex
hex
hex
hex
hex
hex
|
一个 Unicode 代码点
位于 U+0000 到
U+D7FF
以及 U+E000 到 U+10FFFF
的范围内,
对应于由八个十六进制数字编码的值,
按从最高有效位到最低有效位的顺序解释。
|
其中 hex 是一个十六进制字符
HEX ::= [0-9] | [A-F] | [a-f]
一个 字符串转义序列表示传统上 在字符串字面量中转义的字符:
| 转义序列 | Unicode 代码点 |
|---|---|
\t |
U+0009 |
\b |
U+0008 |
\n |
U+000A |
\r |
U+000D |
\f |
U+000C |
\" |
U+0022 |
\' |
U+0027 |
\\ |
U+005C |
一个 保留字符转义序列由一个
\ 后跟
以下字符之一 ~.-!$&'()*+,;=/?#@%_ 组成,并
表示位于
\右侧的字符。
| 数字 转义 |
字符串 转义 |
保留字符 转义 |
|
|---|---|---|---|
IRI,
用作RDF 术语、
PREFIX、
或 BASE 声明
|
是 | 否 | 否 |
| 本地名称 | 否 | 否 | 是 |
| 字符串 | 是 | 是 | 否 |
此处使用的 EBNF 定义于 XML 1.0 [EBNF-NOTATION]。
注:
[1] |
RuleSet |
::= | RuleOrDataBlock |
[2] |
RuleOrDataBlock |
::= | Prologue ( RuleOrData+ ( Prologue1 RuleOrData* )* )?
|
[3] |
RuleOrData |
::= | Rule | Data
|
[4] |
Prologue |
::= | Prologue1* |
[5] |
Prologue1 |
::= | BaseDecl | PrefixDecl | VersionDecl | ImportsDecl
|
[6] |
BaseDecl |
::= | 'BASE' IRIREF
|
[7] |
PrefixDecl |
::= | 'PREFIX' PNAME_NS IRIREF
|
[8] |
VersionDecl |
::= | 'VERSION' VersionSpecifier
|
[9] |
VersionSpecifier
|
::= | STRING_LITERAL1 | STRING_LITERAL2
|
[10] |
ImportsDecl |
::= | 'IMPORTS' iri
|
[11] |
Rule |
::= | 'RULE' iri? HeadTemplate 'WHERE' 'DATA'? BodyPattern
|
[12] |
Data |
::= | 'DATA' '{' DataTriplesBlock? '}'
|
[13] |
HeadTemplate |
::= | '{' HeadTemplateBlock? '}'
|
[14] |
BodyPattern |
::= | '{' BodyTriplesBlock? ( BodyNotTriples '.'? BodyTriplesBlock? )* '}'
|
[15] |
BodyNotTriples |
::= | Filter | Negation | Assignment | GroupAggregate
|
[16] |
Filter |
::= | 'FILTER' Constraint
|
[17] |
Constraint |
::= | BrackettedExpression | BuiltInCall | FunctionCall
|
[18] |
Negation |
::= | 'NOT' 'DATA'? '{' NegationPattern '}'
|
[19] |
NegationPattern |
::= | BodyTriplesBlock? ( NegationPatternNotTriples '.'? BodyTriplesBlock? )*
|
[20] |
NegationPatternNotTriples
|
::= | Filter |
[21] |
Assignment |
::= | 'SET' '(' Var ':=' Expression ')'
|
[22] |
GroupAggregate |
::= | 'GROUP' ( 'BY' ListVars )? '{' GroupPattern '}' ( ANON | '[' AggregationAssignments ']' )
|
[23] |
GroupPattern |
::= | BodyTriplesBlock? ( GroupPatternNotTriples '.'? BodyTriplesBlock? )*
|
[24] |
GroupPatternNotTriples
|
::= | Filter | Assignment
|
[25] |
ListVars |
::= | Var+ |
[26] |
AggregationAssignments
|
::= | ( AggregationAssignment ( ',' AggregationAssignment )* )?
|
[27] |
AggregationAssignment
|
::= | Var ':=' AggregationFunction
|
[28] |
AggregationFunction
|
::= | ( 'COUNT' '(' DISTINCT? VarOrStar ')' | 'MIN' '(' Var ')' | 'MAX' '(' Var ')' | 'SUM' '(' Var ')' )
|
[29] |
VarOrStar |
::= | Var | '*'
|
[30] |
DataTriplesBlock
|
::= | TriplesSameSubjectData ( '.' DataTriplesBlock? )?
|
[31] |
TriplesSameSubjectData
|
::= | RDFTermData PropertyListNotEmptyData | TriplesNodeData PropertyListData | ReifiedTripleBlockData
|
[32] |
PropertyListData
|
::= | PropertyListNotEmptyData?
|
[33] |
PropertyListNotEmptyData
|
::= | VerbData ObjectListData ( ';' ( VerbData ObjectListData )? )*
|
[34] |
VerbData |
::= | iri | 'a'
|
[35] |
ObjectListData |
::= | ObjectData ( ',' ObjectData )*
|
[36] |
ObjectData |
::= | GraphNodeData AnnotationData
|
[37] |
GraphNodeData |
::= | RDFTermData | TriplesNodeData | ReifiedTripleData
|
[38] |
TriplesNodeData |
::= | CollectionData | BlankNodePropertyListData
|
[39] |
BlankNodePropertyListData
|
::= | '[' PropertyListNotEmptyData ']'
|
[40] |
CollectionData |
::= | '(' GraphNodeData+ ')'
|
[41] |
AnnotationData |
::= | ( ReifierData | AnnotationBlockData )*
|
[42] |
AnnotationBlockData
|
::= | '{|' PropertyListNotEmptyData '|}'
|
[43] |
ReifierData |
::= | '~' ReifierIdData?
|
[44] |
ReifierIdData |
::= | iri | BlankNode
|
[45] |
ReifiedTripleBlockData
|
::= | ReifiedTripleData PropertyListData
|
[46] |
ReifiedTripleData
|
::= | '<<' ReifiedTripleSubjectData VerbData ReifiedTripleObjectData ReifierData? '>>'
|
[47] |
ReifiedTripleSubjectData
|
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTripleData | TripleTermData
|
[48] |
ReifiedTripleObjectData
|
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTripleData | TripleTermData
|
[49] |
TripleTermData |
::= | '<<(' TripleTermSubjectData VerbData TripleTermObjectData ')>>'
|
[50] |
TripleTermSubjectData
|
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTermData
|
[51] |
TripleTermObjectData
|
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTermData
|
[52] |
HeadTemplateBlock
|
::= | TriplesBlockTemplate
|
[53] |
TriplesBlockTemplate
|
::= | TriplesSameSubjectTemplate ( '.' TriplesBlockTemplate? )?
|
[54] |
TriplesSameSubjectTemplate
|
::= | VarOrRDFTerm PropertyListNotEmptyTemplate | TriplesNodeTemplate PropertyListTemplate | ReifiedTripleBlockTemplate
|
[55] |
PropertyListTemplate
|
::= | PropertyListNotEmptyTemplate?
|
[56] |
PropertyListNotEmptyTemplate
|
::= | Verb ObjectListTemplate ( ';' ( Verb ObjectListTemplate )? )*
|
[57] |
ObjectListTemplate
|
::= | ObjectTemplate ( ',' ObjectTemplate )*
|
[58] |
ObjectTemplate |
::= | GraphNodeTemplate AnnotationTemplate
|
[59] |
GraphNodeTemplate
|
::= | VarOrRDFTerm | TriplesNodeTemplate | ReifiedTriple
|
[60] |
TriplesNodeTemplate
|
::= | CollectionTemplate | BlankNodePropertyListTemplate
|
[61] |
BlankNodePropertyListTemplate
|
::= | '[' PropertyListNotEmptyTemplate ']'
|
[62] |
CollectionTemplate
|
::= | '(' GraphNodeTemplate+ ')'
|
[63] |
AnnotationTemplate
|
::= | ( Reifier | AnnotationBlockTemplate )*
|
[64] |
AnnotationBlockTemplate
|
::= | '{|' PropertyListNotEmptyTemplate '|}'
|
[65] |
ReifiedTripleBlockTemplate
|
::= | ReifiedTriple PropertyListTemplate
|
[66] |
BodyTriplesBlock
|
::= | TriplesBlockPattern
|
[67] |
TriplesBlockPattern
|
::= | TriplesSameSubjectPattern ( '.' TriplesBlockPattern? )?
|
[68] |
ReifiedTripleBlockPattern
|
::= | ReifiedTriple PropertyListPattern
|
[69] |
TriplesSameSubjectPattern
|
::= | VarOrRDFTerm PropertyListNotEmptyPattern | TriplesNodePattern PropertyListPattern | ReifiedTripleBlockPattern
|
[70] |
PropertyListPattern
|
::= | PropertyListNotEmptyPattern?
|
[71] |
PropertyListNotEmptyPattern
|
::= | ( VerbPath | Var ) ObjectListPattern ( ';' ( ( VerbPath | Var ) ObjectListPattern )? )*
|
[72] |
ObjectListPattern
|
::= | ObjectPattern ( ',' ObjectPattern )*
|
[73] |
ObjectPattern |
::= | GraphNodePattern AnnotationPattern
|
[74] |
TriplesNodePattern
|
::= | CollectionPattern | BlankNodePropertyListPattern
|
[75] |
BlankNodePropertyListPattern
|
::= | '[' PropertyListNotEmptyPattern ']'
|
[76] |
CollectionPattern
|
::= | '(' GraphNodePattern+ ')'
|
[77] |
AnnotationPattern
|
::= | ( Reifier | AnnotationBlockPattern )*
|
[78] |
AnnotationBlockPattern
|
::= | '{|' PropertyListNotEmptyPattern '|}'
|
[79] |
GraphNodePattern
|
::= | VarOrRDFTerm | TriplesNodePattern | ReifiedTriple
|
[80] |
Reifier |
::= | '~' ReifierId?
|
[81] |
ReifierId |
::= | Var | iri | BlankNode
|
[82] |
ReifiedTriple |
::= | '<<' ReifiedTripleSubject Verb ReifiedTripleObject Reifier? '>>'
|
[83] |
ReifiedTripleSubject
|
::= | Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTriple | TripleTerm
|
[84] |
ReifiedTripleObject
|
::= | Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | ReifiedTriple | TripleTerm
|
[85] |
TripleTerm |
::= | '<<(' TripleTermSubject Verb TripleTermObject ')>>'
|
[86] |
TripleTermSubject
|
::= | Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTerm
|
[87] |
TripleTermObject
|
::= | Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | TripleTerm
|
[88] |
Verb |
::= | VarOrIri | 'a'
|
[89] |
VerbPath |
::= | Path |
[90] |
Path |
::= | PathSequence |
[91] |
PathSequence |
::= | PathEltOrInverse ( '/' PathEltOrInverse )*
|
[92] |
PathEltOrInverse
|
::= | PathElt | '^' PathElt
|
[93] |
PathElt |
::= | ( iri | 'a' | '(' Path ')' )
|
[94] |
RDFTermData |
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | TripleTermData
|
[95] |
VarOrRDFTerm |
::= | Var | iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL | TripleTerm
|
[96] |
VarOrIri |
::= | Var | iri
|
[97] |
Var |
::= | VAR1 | VAR2
|
[98] |
RDFLiteral |
::= | String ( LANG_DIR | '^^' iri )?
|
[99] |
NumericLiteral |
::= | NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
|
[100] |
NumericLiteralUnsigned
|
::= | INTEGER | DECIMAL | DOUBLE
|
[101] |
NumericLiteralPositive
|
::= | INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
|
[102] |
NumericLiteralNegative
|
::= | INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
|
[103] |
BooleanLiteral |
::= | 'true' | 'false'
|
[104] |
String |
::= | STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
|
[105] |
iri |
::= | IRIREF | PrefixedName
|
[106] |
PrefixedName |
::= | PNAME_LN | PNAME_NS
|
[107] |
BlankNode |
::= | BLANK_NODE_LABEL | ANON
|
[108] |
Expression |
::= | ConditionalOrExpression
|
[109] |
ConditionalOrExpression
|
::= | ConditionalAndExpression ( '||' ConditionalAndExpression )*
|
[110] |
ConditionalAndExpression
|
::= | ValueLogical ( '&&' ValueLogical )*
|
[111] |
ValueLogical |
::= | RelationalExpression
|
[112] |
RelationalExpression
|
::= | NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression | 'IN' ExpressionList | 'NOT' 'IN' ExpressionList )?
|
[113] |
NumericExpression
|
::= | AdditiveExpression
|
[114] |
AdditiveExpression
|
::= | MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | ( NumericLiteralPositive | NumericLiteralNegative ) ( ( '*' UnaryExpression ) | ( '/' UnaryExpression ) )* )*
|
[115] |
MultiplicativeExpression
|
::= | UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
|
[116] |
UnaryExpression |
::= | '!' PrimaryExpression
|
[117] |
PrimaryExpression
|
::= | BrackettedExpression | BuiltInCall | iriOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var | ExprTripleTerm
|
[118] |
FunctionCall |
::= | iri ArgList
|
[119] |
ArgList |
::= | NIL | '(' Expression ( ',' Expression )* ')'
|
[120] |
ExpressionList |
::= | NIL | '(' Expression ( ',' Expression )* ')'
|
[121] |
iriOrFunction |
::= | iri ArgList?
|
[122] |
ExprTripleTerm |
::= | '<<(' ExprTripleTermSubject Verb ExprTripleTermObject ')>>'
|
[123] |
ExprTripleTermSubject
|
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | Var
|
[124] |
ExprTripleTermObject
|
::= | iri | RDFLiteral | NumericLiteral | BooleanLiteral | Var | ExprTripleTerm
|
[125] |
BrackettedExpression
|
::= | '(' Expression ')'
|
[126] |
BuiltInCall |
::= | 'STR' '(' Expression ')'
|
终结符的产生式:
[127] |
IRIREF |
::= | '<' ([^<>"{}|^`\]-[#x00-#x20] | UCHAR )* '>'
|
[128] |
PNAME_NS |
::= | PN_PREFIX? ':' |
[129] |
PNAME_LN |
::= | PNAME_NS PN_LOCAL
|
[130] |
BLANK_NODE_LABEL
|
::= | '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)?
|
[131] |
VAR1 |
::= | '?' VARNAME |
[132] |
VAR2 |
::= | '$' VARNAME |
[133] |
LANG_DIR |
::= | '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)* ('--' [a-zA-Z]+)?
|
[134] |
INTEGER |
::= | [0-9]+ |
[135] |
DECIMAL |
::= | [0-9]* '.' [0-9]+ |
[136] |
DOUBLE |
::= | ( ([0-9]+ ('.'[0-9]*)? ) | ( '.' ([0-9])+ ) ) [eE][+-]?[0-9]+
|
[137] |
INTEGER_POSITIVE
|
::= | '+' INTEGER
|
[138] |
DECIMAL_POSITIVE
|
::= | '+' DECIMAL
|
[139] |
DOUBLE_POSITIVE |
::= | '+' DOUBLE
|
[140] |
INTEGER_NEGATIVE
|
::= | '-' INTEGER
|
[141] |
DECIMAL_NEGATIVE
|
::= | '-' DECIMAL
|
[142] |
DOUBLE_NEGATIVE |
::= | '-' DOUBLE
|
[143] |
STRING_LITERAL1 |
::= | "'" ( ([^#x27#x5C#xA#xD]) | ECHAR | UCHAR )* "'"
|
[144] |
STRING_LITERAL2 |
::= | '"' ( ([^#x22#x5C#xA#xD]) | ECHAR | UCHAR )* '"'
|
[145] |
STRING_LITERAL_LONG1
|
::= | "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR | UCHAR ) )* "'''"
|
[146] |
STRING_LITERAL_LONG2
|
::= | '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR | UCHAR ) )* '"""'
|
[147] |
ECHAR |
::= | '\' [tbnrf\"'] |
[148] |
UCHAR |
::= | ('\u' HEX HEX HEX HEX) | ('\U' HEX HEX HEX HEX HEX HEX HEX HEX)
|
[149] |
NIL |
::= | '(' WS* ')' |
[150] |
WS |
::= | #x20 | #x9 | #xD | #xA |
[151] |
ANON |
::= | '[' WS* ']' |
[152] |
PN_CHARS_BASE |
::= | [A-Z] | [a-z] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D] | [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
|
[153] |
PN_CHARS_U |
::= | PN_CHARS_BASE | '_'
|
[154] |
VARNAME |
::= | ( PN_CHARS_U | [0-9] ) ( PN_CHARS_U | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] )*
|
[155] |
PN_CHARS |
::= | PN_CHARS_U | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
|
[156] |
PN_PREFIX |
::= | PN_CHARS_BASE ((PN_CHARS|'.')* PN_CHARS)?
|
[157] |
PN_LOCAL |
::= | (PN_CHARS_U | ':' | [0-9] | PLX ) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX) )?
|
[158] |
PLX |
::= | PERCENT | PN_LOCAL_ESC
|
[159] |
PERCENT |
::= | '%' HEX HEX
|
[160] |
HEX |
::= | [0-9] | [A-F] | [a-f] |
[161] |
PN_LOCAL_ESC |
::= | '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '/' | '?' | '#' | '@' | '%' )
|
此语法的文本版本可在以下文件中获取:
sparql-rl-grammar.bnf。
本文档使用了一些终结符字面字符串 [EBNF-NOTATION]。为明确用于 这些 终结符字面字符串的 Unicode 码点,下表列出了所使用的具体 字符。
| 代码 | 字形 | 说明 |
|---|---|---|
U+000A |
LF |
换行符 |
U+000D |
CR |
回车符 |
U+0023 |
# |
数字符号 |
U+0025 |
% |
百分号 |
U+005C |
\ |
反斜杠 |
本节为非规范性内容。
以下算法展示了一种解析 IMPORTS 语句的方法,
即递归访问所有被引用的文档。
两个规则集 RS1 和 RS2 的规则集合并
会产生一个规则集 MR,其定义如下:
let RS be a rule set
let V = {}
if RS has a location:
V = { location of RS }
endif
define ruleSetMerge(rule set RS1, rule set RS2):
let MR be a rule set
MR.rules = RS1.rules ∪ RS2.rules
MR.data = rdf_merge(RS1.data, RS2.data)
MR.imports = {}
the result is MR
enddefine
define imports(rule set RS):
let RS2 be a rule set formed from RS.rules and RS.data
foreach URL x in RS.imports:
if x ∉ V:
V = V ∪ { x }
read rule set RS3 from URL x
RS2 = ruleSetMerge(RS2, imports(RS3))
endif
endfor
the result is RS2
enddefine
result is imports(RS)
其中 rdf_merge 是
RDF
合并
操作。
SPARQL-RL 的互联网媒体类型(以前称为 MIME 类型)
是 "application/sparql-rl"。
以下信息已提交给互联网工程 指导委员会(IESG),以供审查、批准并在 IANA 注册。
本节为非规范性内容。
SPARQL-RL 文档可以包含 IMPORTS 语句,这些语句引用其他
SPARQL-RL 文档,并将其内容导入当前
文档。
如果导入的文档本身包含自己的 IMPORTS 语句,
则这些语句引用的文档也会被导入。
尽管有助于模块化 规则集,但使用 IMPORTS 语句可能
引入安全风险。
风险包括但不限于:规则集
在求值时导致过量计算,无论是恶意还是意外所致;
以及 HTTP 请求被拦截并返回不同的文档,
包括过时的副本。
将 SPARQL-RL 规则集应用于 RDF 图可能导致 大量计算和内存消耗,这可能被利用 来造成拒绝服务。 应用程序应注意限制计算量和 内存使用量,这些消耗可能由应用 SPARQL-RL 规则集造成。
SPARQL-RL 语法使用 UTF-8 编码 [RFC3629],并且 允许 在字符串数据中使用未转义的控制字符。 虽然本规范不会直接向最终用户暴露此内容, 但它可能通过用户代理呈现,这可能导致所呈现的文本 因此类字符而产生误导。
SPARQL-RL 可用于处理和创建任意应用数据; 安全考虑因素会因使用领域而异。 适用于文本的安全工具和协议 (例如,PGP 加密、校验和验证、密码保护的压缩) 也可用于 SPARQL-RL 文档。 必须实施安全/隐私协议,以反映 SPARQL-RL 规则集求值结果中信息的敏感程度。
SPARQL-RL 的安全考虑因素包括 RDF 数据 的安全考虑因素,以及 以下格式的安全考虑因素,例如 RDF Turtle。
本节为非规范性内容。
SPARQL-RL 文档可以包含其他应用程序数据, 其中可能包含个人身份信息(PII) 或其他可能被视为敏感的信息。 建议发布包含此类信息的规则集的作者仔细 考虑发布此类信息的必要性和用途, 以及数据预计会被使用和可能被披露的地区 所适用的法规(例如 GDPR、 CCPA 和其他法规), 尤其应考虑访问这些数据是否需要授权措施。
本节为非规范性内容。
以下人员在数据形状工作组的 规则任务组中为 SPARQL-RL 的制定作出了贡献: Robert David, David Habgood, Livio Robaldo, Ognjen Savkovic, Andy Seaborne, Simon Steyskal, 和 Ted Thibodeau Jr.
数据形状工作组的成员包括 @@。
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: