RelFinder 是针对三元组数据库 Dbpedia 开发的展示工具,官网地址为 http://www.visualdataweb.org/relfinder.php,源代码可从 github 上下载,地址为 https://github.com/VisualDataWeb/RelFinder。
使用过程中,我们发现 RelFinder 画出的线的方向有时候是错的。经过努力,我和同事小段找到了这个bug所在的位置,并进行了改正。现在我将探索的过程记录下来。
RelFinder 的编译工具
RelFinder 是用 actionscript 语言开发的,我们使用的编译工具是 FlashDevelop,需要配置的环境为 flex_sdk 3.5。
配置环境的步骤为:Tool - Program Settings - AS3Context - Installed Flex SDKs,添加 flex_sdk 3.5 路径。
RelFinder 发出的 SPARQL 语句
RelFinder 针对每两个被探索的实体要发出12个 SPARQL 语句,以查找这二者之间的 0跳、1跳、2跳 关系(按照 RelFinder 中的概念,0跳表示两个实体通过一个谓语直接相连)。如下是根据 RelFinder 的源代码整理的12个 SPARQL 语句的思路:
【0】A ⇒ (pf1) ⇒ B
【1】B ⇒ (pf1) ⇒ A
【0】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ B
【1】B ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ A
【2】A ⇒ (pf1) ⇒ middle ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ middle ⇒ (ps1) ⇒ B
【0】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ of2 ⇒ (pf3) ⇒ B
【1】B ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ of2 ⇒ (pf3) ⇒ A
【2】A ⇒ (pf1) ⇒ middle ⇐ (ps2) ⇐ os1 ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【2】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ of1 ⇐ (pf2) ⇐ middle ⇒ (ps1) ⇒ B
这12句SPARQL语句分为三个大组,分别是0跳、1跳、2跳。
其中的 A、B 两个元素是被探索的两个元素。
带括号的以 p 开头的元素是谓语(predicate),又分为 pf 和 ps 两种,f 代表 first,s 代表 second,pf 和 ps 是按照箭头的方向来区分的两组谓语,例如 pf1 代表“first 组 predicate 中的第一个”,ps1代表“second 组 predicate 中的第1个”。
以 o 开头的元素是主语或宾语,为实体(ontology),同理,也分为 of 和 os 两组,f 代表 first,s 代表 second,of1 代表“first 组 ontology 中的第一个”,os1代表“second 组 ontology 中的第1个”。
每个 SPARQL 语句的前面都标记了一个数字,从【0】到【3】,这个数字随着 SPARQL 语句一起生成,用来从“箭头方向”这个角度对12个语句进行分类。其中:
【0】代表从 A 到 B,箭头都是指向一个方向的
【1】代表从 B 到 A,箭头都是指向一个方向的
【2】代表从 A、B 到 middle,箭头都是指向 middle 的
【3】代表从 middle 到 A、B,箭头都是背离 middle 的
RelFinder 的搜索结果在理论应该进行的颠倒操作
SPARQL 返回的搜索结果中只有 pf1、of1 等等变量对应的元素值,并没有被探索的两个元素 A、B 的元素值,同时,搜索得到的结果的箭头方向全是 ⇒,如下所示:
【0】 ⇒ (pf1) ⇒
【1】 ⇒ (pf1) ⇒
【0】 ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒
【1】 ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒
【2】 ⇒ (pf1) ⇒ middle ⇒ (ps1) ⇒
【3】 ⇒ (pf1) ⇒ middle ⇒ (ps1) ⇒
【0】 ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ of2 ⇒ (pf3) ⇒
【1】 ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ of2 ⇒ (pf3) ⇒
【2】 ⇒ (pf1) ⇒ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒
【3】 ⇒ (pf1) ⇒ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒
【2】 ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇒ (ps1) ⇒
【3】 ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇒ (ps1) ⇒
对于标记为【0】的 SPARQL 语句来说,将被探索的两个元素 A、B 按照 A、B 这个顺序与搜索结果进行结合,则各个三元组的顺序都是对的,不需要颠倒,如下:
【0】A ⇒ (pf1) ⇒ B
【0】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ B
【0】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ of2 ⇒ (pf3) ⇒ B
对于标记为【1】的 SPARQL 语句来说,需要将 A、B 颠倒位置为 B、A ,搜索结果才正确,如下:
【1】B ⇒ (pf1) ⇒ A
【1】B ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ A
【1】B ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ of2 ⇒ (pf3) ⇒ A
对于标记为【2】的 SPARQL 语句来说,需要将 A、B 两个元素按 A、B 的顺序与 SPARQL 语句结合,然后将 middle 后面的箭头全部颠倒改为 ⇐,即从:
【2】A ⇒ (pf1) ⇒ middle ⇒ (ps1) ⇒ B
【2】A ⇒ (pf1) ⇒ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【2】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇒ (ps1) ⇒ B
变为
【2】A ⇒ (pf1) ⇒ middle ⇐ (ps1) ⇐ B
【2】A ⇒ (pf1) ⇒ middle ⇐ (ps2) ⇐ os1 ⇐ (ps1) ⇐ B
【2】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇐ (ps1) ⇐ B
对于标记为【3】的 SPARQL 语句来说,需要将 A、B 两个元素按 A、B 的顺序与 SPARQL 语句结合,然后将 middle 前面的箭头全部颠倒改为 ⇐,即从:
【3】A ⇒ (pf1) ⇒ middle ⇒ (ps1) ⇒ B
【3】A ⇒ (pf1) ⇒ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【3】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇒ (ps1) ⇒ B
变为
【3】A ⇐ (pf1) ⇐ middle ⇒ (ps1) ⇒ B
【3】A ⇐ (pf1) ⇐ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【3】A ⇐ (pf1) ⇐ of1 ⇐ (pf2) ⇐ middle ⇒ (ps1) ⇒ B
即,对与【0】不需要颠倒;对于【1】,颠倒被探索的两个元素 A、B 即可;对于【2】【3】,颠倒 middle 前面或者后面的箭头方向。RelFinder 画线方向出现错误就在对【2】【3】的处理上。
RelFinder 实际进行的颠倒操作
分析 RelFinder 的源代码,问题出在 src\connection\SPARQLResultParser.as 文件中。
第一次颠倒:将 A、B 颠倒位置为 B、A ,与标记为【1】的 SPARQL 语句搜索到的结果结合,这样标记为【1】的结果方向就正确了。本次颠倒位于 SPARQLResultParser.as 文件的68-70行,其中 connectedDirectlyInverted 这个常量的值就是1,对应于标记【1】,声明于 SPARQLQueryBuilder.as 这个文件。本次颠倒是正确的。
第二次颠倒:本次颠倒位于 SPARQLResultParser.as 文件的169-172行、195-198行,以及 getRelation 方法256-258行的 if 判断,实际意义是当 parsingInformations 为【1】或【3】的时候,把布尔值 invert 赋值为 true,整理为如下代码:
if (parsingInformations==1 || parsingInformations==3){
invert = true;
}else{
invert = false;
}
第三次颠倒:在方法 getRelation 中进行第三次颠倒,位于260-262行的 if 判断,在个 if 判断之前,布尔值 invert 已经被赋值,
本次的 if 判断实际意义是当一个三元组的宾语是 os1或 B 时(B在此处用os0表示),令布尔值 invert 取反,整理为如下代码:
if (objectBinding.charAt(1) == "s" && objectBinding.charAt(2) >= 0) {
invert = !invert;
}
第一次颠倒是正确的,第二、三次颠倒的思路有些混乱,错误就在这第二、三次颠倒中。
修改后进行的三次颠倒
第一次颠倒针对【1】,是正确的,不改动。
第二、三次颠倒针对【2】【3】,需要修改 src\connection\SPARQLResultParser.as 文件。
第二次颠倒。需要将 parse 函数中的 invertDirection 废弃掉,代之以 parsingInformations。
首先,注释掉169-172行、195-198行,将174、200行的 invertDirection 替换成 parsingInformations。
其次,将230行 getRelation 函数的输入变量 invertDirection:Boolean = false 替换为 parsingInformations:int = 0。
再次,注释掉256-258行,添加如下代码:
if (parsingInformations == 3){
invert = !invert;
}
本次颠倒的目的是将标志为【3】的 SPARQL 语句搜索结果的全部箭头从默认的向右颠倒为向左,即从
【3】A ⇒ (pf1) ⇒ middle ⇒ (ps1) ⇒ B
【3】A ⇒ (pf1) ⇒ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【3】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇒ (ps1) ⇒ B
变为
【3】A ⇐ (pf1) ⇐ middle ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ middle ⇐ (ps2) ⇐ os1 ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ of1 ⇐ (pf2) ⇐ middle ⇐ (ps1) ⇐ B
第三次颠倒。注释掉260-262行,添加如下代码:
if ((parsingInformations == 2 || parsingInformations == 3) && (obj >= 0)){
invert = !invert;
}
本次颠倒的原理是将标志为【2】【3】的 SPARQL 语句搜索结果的 middle 右侧的箭头进行反向,即【2】的 middle 后面箭头由 ⇒ 改为 ⇐,【3】的 middle 后面的箭头由 ⇐ 改为 ⇒。即从:
【2】A ⇒ (pf1) ⇒ middle ⇒ (ps1) ⇒ B
【2】A ⇒ (pf1) ⇒ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【2】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇒ (ps1) ⇒ B
【3】A ⇐ (pf1) ⇐ middle ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ middle ⇐ (ps2) ⇐ os1 ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ of1 ⇐ (pf2) ⇐ middle ⇐ (ps1) ⇐ B
变为
【2】A ⇒ (pf1) ⇒ middle ⇐ (ps1) ⇐ B
【2】A ⇒ (pf1) ⇒ middle ⇐ (ps2) ⇐ os1 ⇐ (ps1) ⇐ B
【2】A ⇒ (pf1) ⇒ of1 ⇒ (pf2) ⇒ middle ⇐ (ps1) ⇐ B
【3】A ⇐ (pf1) ⇐ middle ⇒ (ps1) ⇒ B
【3】A ⇐ (pf1) ⇐ middle ⇒ (ps2) ⇒ os1 ⇒ (ps1) ⇒ B
【3】A ⇐ (pf1) ⇐ of1 ⇐ (pf2) ⇐ middle ⇒ (ps1) ⇒ B
第二、三次颠倒联合起来,实现对【2】【3】的正确处理。
至此,RelFinder 画线方向错误的 bug 就改好了。