语义 Web 为更轻松的数据集成开辟了一个新时代。当然,已经有很多现成的数据以各种不同的格式存在。要将所有这些数据转化成 RDF(语义 Web 使用的格式)将是一项庞大的工程,因此最好将已有的数据作为 RDF 公开。本文介绍了语义 Web 的核心概念和标准,阐述了如何将 LDAP 目录公开为语义 Web 应用程序可使用开放源码 SquirrelRDF 工具使用的服务。
本文的目的在于说明如何为 LDAP 目录创建可通过 SPARQL 查询的端点,同时介绍了重要的语义 Web 概念。建立端点之后,我还将说明如何使用一些 Jena Java? 类进一步完善,最后使用 JavaScript 从基于浏览器的客户端查询它,
语义 Web 是一种新出现的技术,它以表示数据、查询数据和对数据应用规则的一组标准为基础。核心技术包括用于表示的 RDF、用于查询的 SPARQL、用于构造的 RDFS 以及用于构造和推理的 OWL。语义 Web 有很多突出的优点,包括数据集成更简单、搜索更精确、知识管理更方便等等,结果语义 Web 这个词的含义越来越丰富。
RDF 和 XML 有一些重要的区别。首先,RDF 是基于图的,而 XML 是基于树的。RDF 没有明确的顺序,所有的边组成一个集合,而 XML 元素是有顺序的。最后,RDF 是一种不含标准序列化的数据模型。RDF 可以序列化成多种形式,包括 RDF-XML、n3、Terse RDF Triple Language等。清单 1 中的例子采用 Turtle 描述了关于两个人的一些联系信息。
第一行定义了数据前缀,这样 DE>DE> 就能简写为 DE>foaf:nameDE>。可以指定多个前缀。此外,Turtle 用分号(DE>;DE>)表示后续行中的谓词和宾语使用相同的主语。谓词 DE>aDE> 是 RDF 类型谓词(DE>DE>)的缩写,可用于表示资源属于特定类型。
使用 SPARQL 查询 RDF
试验下面的查询可使用通用的 SPARQL 处理程序(请参阅 参考资料)。将 DE>http://wingerz.com/dw/listing1.ttlDE> 粘贴到 Target graph URI 字段中。输入查询并单击 Get Results 之后,就会检索 Turtle 文件并进行查询。
DE>SELECTDE>:返回满足查询的一组变量绑定(类似 SQL DE>SELECTDE>)。非常适合产生应用程序要消费的数据。
DE>CONSTRUCTDE>:返回一个图(一组 RDF 命题)。适合检索和转换 RDF。
DE>ASKDE>:返回一个布尔值,说明是否存在查询的结果。
DE>DESCRIBEDE>:依赖于实现。接收一个资源并返回描述该资源的图。
清单 2 示范了 DE>SELECTDE> 查询。
Result:
DE>CONSTRUCTDE> 非常重要,可以根据 SPARQL 查询结果构造 RDF 图。在数据合并的时候很方便,因为 CONSTRUCT 图中的谓词不一定和原图中的相同。清单中查询 DE>foaf:phoneDE>,不过在它的位置使用不同的谓词构造了一个新的图(DE>wingerz:officephoneDE>)。
Result:
试验 DE>CONSTRUCTDE> 查询的时候,在资源页面上可以看到完整的响应。遗憾的是,结果被序列化为 RDF-XML 格式,不是很优雅的一种 RDF 序列化格式。
初看起来,RDF 数据似乎有点笨拙而且非常罗嗦。但是有一些突出的优点。
数据的结构没有限制。XML 是层次化的,在关系数据库中建模(和查询)图结构非常困难。
数据容易合并。合并数据(不同的图)是一项简单操作,只需要建立包含图中所有三元组的集合。全球唯一资源减少了歧义。而且如果必要的话,可以用 OWL 规则将不同 URI 的资源映射到同一个 URI。
SquirrelRDF 入门
LDAP 目录本身的结构很容易转化成 RDF。每个 LDAP 对象类都有一组属性。其中一些属性指向文字值(比如名称),另一些则指向其他对象[比如专用名词(DN)指定的工作地点]。
安装后,第一步是寻找 LDAP 存储的模式。如果没有可供试验的 LDAP 存储,可以安装 OpenLDAP 并按照使用说明创建一个简单的地址簿(请参阅 参考资料,尽管可能没有必要费这么多事,因为本文的目标是利用已有的数据源)。模式包含所有不同的对象类及其属性。通过分析属性,可以确定目录中对象之间的关系。
现在来创建从 LDAP 模式到 RDF 的映射。SquirrelRDF 使用 RDF 文件表示这种映射。DE>lmap:serverDE> 谓词用于指定 LDAP 存储的位置。映射允许分配两种类型的 RDF 谓词:文字宾语或者资源宾语。姓名和电话这类基本属性映射为文字宾语谓词。这类映射需要 LDAP 属性名和 RDF 谓词名。然后即可将其链接到资源。假设需要将 LDAP DE>cnDE> 映射到 RDF DE>foaf:nameDE>。清单 4 中创建了一个资源来进行链接 (DE>:namemappingDE>),然后将其链接到配置资源(DE>DE>)。
请注意,将这些链接到一起的 URI 无关紧要,可以改用空白节点,即没有 URI 的资源。它与空白 URI 的资源不同,后面也包括这样的资源。空白 URI 是一种相对 URI,因而解析到 Turtle 文件所在的位置。
清单 5. SquirrelRDF 文字映射到空白节点
要映射指向专有名词的 LDAP 属性,可将空白节点的类型指定为 DE>lmap:ObjectPropertyDE>。这样可以保证分配的谓词指向资源(而不是文字)。清单 6 显示了完整的配置文件,包括两个 DE>lmap:ObjectPropertyDE> 属性。LDAP 属性默认分配文字资源谓词。要注意,可能的话最好使用已有的谓词,这里使用了 FOAF 本体的一些词汇表。对于地点也有一个词汇表,不过这里没有用到。
实验映射
清单 7. 测试查询 (test.rq)
以 DE>dw.ttlDE>(映射文件名)和 DE>test.rqDE>(查询文件名)为参数运行 DE>squirrelrdf.QueryDE>。应该会得到某个人的 URI 和电话号码。
清单 8. 测试查询 (test2.rq)
将 SquirrelRDF 设置成服务
缺省 Servlet 非常小,只能回答 DE>SELECTDE> 查询,用标准 XML 格式表示结果集。为了使 SPARQL 端点更有用,可让它支持更多的特性。
增加更多的功能之前,我们先要找一种更好的办法测试端点,因为 URL 编码查询和手工创建 URL 都很麻烦。第一个例子使用的通用 SPARQL 处理程序无法再用,因为它取回执行查询的整个 RDF 图,我们的 SquirrelRDF 服务本身是一个能回答查询的 SPARQL 端点(通过将其转化成 LDAP 查询)。创建一个简单 HTML 表单编写和提交查询。一个例子是功能更完善的 SPARQLer(请参阅 参考资料)。单击 Graphs 选项卡来设置端点 endpoint(比如 http://localhost:8080/squirrel)。单击 Change。建立查询提交的端点。尝试本文前面用到的查询。
如前所述,DE>CONSTRUCTDE> 查询很重要,尤其是对非 RDF 格式存储的数据。现在,Servlet 仅支持 DE>SELECTDE> 查询,但是底层的查询引擎可以执行全部四种查询类型。扩展现有的 Servlet 需要修改 DE>doQuery()DE> 方法,如清单 9 所示。DE>com.hp.hpl.jena.query.QueryDE> 对象知道是什么类型的查询,因此可通过它确定 DE>com.hp.hpl.jena.query.engine1.QueryEngineDE> 应该怎么做。不同的查询类型返回不同类型的数据:DE>ASKDE> 返回布尔值,DE>CONSTRUCTDE> 和 DE>DESCRIBEDE> 返回图(从技术上说,返回的是图的包装 DE>ModelDE>),DE>SELECTDE> 返回结果集。
获得 JSON 输出
清单 10. JSON 查询结果
DE>com.hp.hpl.jena.query.ResultSetFormatterDE> 工具类用于输出 SPARQL 查询结果。清单 9 使用它以 XML 格式输出结果(DE>SELECTDE> 和 DE>ASKDE> 查询)。结果也可以用 RDF 格式。毫不奇怪,使用这个工具类也能将 DE>SELECTDE> 结果邦定输出为 JSON,如清单 11 所示。
支持 XSLT
可以向服务器上增加 XSLT 服务,通过外部 XSLT 服务处理查询结果,或者在 XML 输出重增加样式表链接。在 XML 输出中添加样式表链接,将 DE>com.hp.hpl.jena.query.ResultSetFormatterDE> 作为 XSLT URI 的新增参数传递,如清单 13 所示。这样不会执行转换,只是在生成的文档中包括到 XSLT 的链接。当 XSLT 处理程序(比如现代 Web 浏览器)准备文档的时候就会执行转换了(请参阅 参考资料)。
http://wingerz.com/who#wing1-555-555-5555
清单 13. 包括 XSLT
从 JavaScript 查询
中提供了可免费下载的 SPARQL JavaScript 库。主要用于处理 SPARQL DE>SELECTDE> 查询返回的 JSON 格式的结果。所幸的是,可以让 SPARQL 服务支持这种功能。该库背后最突出的思想是转换器的概念——SPARQL 端点的 JSON 输出包含一般客户机可能用不到的一些数据,比如某些数据类型信息。转换器将 JSON 输出转换成更容易使用的、更自然的对象。
本文的目的在于说明如何为 LDAP 目录创建可通过 SPARQL 查询的端点,同时介绍了重要的语义 Web 概念。建立端点之后,我还将说明如何使用一些 Jena Java? 类进一步完善,最后使用 JavaScript 从基于浏览器的客户端查询它,
语义 Web 是一种新出现的技术,它以表示数据、查询数据和对数据应用规则的一组标准为基础。核心技术包括用于表示的 RDF、用于查询的 SPARQL、用于构造的 RDFS 以及用于构造和推理的 OWL。语义 Web 有很多突出的优点,包括数据集成更简单、搜索更精确、知识管理更方便等等,结果语义 Web 这个词的含义越来越丰富。
RDF 和 XML 有一些重要的区别。首先,RDF 是基于图的,而 XML 是基于树的。RDF 没有明确的顺序,所有的边组成一个集合,而 XML 元素是有顺序的。最后,RDF 是一种不含标准序列化的数据模型。RDF 可以序列化成多种形式,包括 RDF-XML、n3、Terse RDF Triple Language等。清单 1 中的例子采用 Turtle 描述了关于两个人的一些联系信息。
- @prefix foaf: .
- a foaf:Person ;
- foaf:name "Wing C. Yung" ;
- foaf:mbox ;
- foaf:phone "1-555-555-5555" ;
- foaf:knows .
- a foaf:Person ;
- foaf:name "Lee Feigenbaum" ;
- foaf:mbox ;
- foaf:phone "1-555-555-5556" .
@prefix foaf: .
a foaf:Person ;
foaf:name "Wing C. Yung" ;
foaf:mbox ;
foaf:phone "1-555-555-5555" ;
foaf:knows .
a foaf:Person ;
foaf:name "Lee Feigenbaum" ;
foaf:mbox ;
foaf:phone "1-555-555-5556" .
第一行定义了数据前缀,这样 DE>DE> 就能简写为 DE>foaf:nameDE>。可以指定多个前缀。此外,Turtle 用分号(DE>;DE>)表示后续行中的谓词和宾语使用相同的主语。谓词 DE>aDE> 是 RDF 类型谓词(DE>DE>)的缩写,可用于表示资源属于特定类型。
使用 SPARQL 查询 RDF
试验下面的查询可使用通用的 SPARQL 处理程序(请参阅 参考资料)。将 DE>http://wingerz.com/dw/listing1.ttlDE> 粘贴到 Target graph URI 字段中。输入查询并单击 Get Results 之后,就会检索 Turtle 文件并进行查询。
DE>SELECTDE>:返回满足查询的一组变量绑定(类似 SQL DE>SELECTDE>)。非常适合产生应用程序要消费的数据。
DE>CONSTRUCTDE>:返回一个图(一组 RDF 命题)。适合检索和转换 RDF。
DE>ASKDE>:返回一个布尔值,说明是否存在查询的结果。
DE>DESCRIBEDE>:依赖于实现。接收一个资源并返回描述该资源的图。
清单 2 示范了 DE>SELECTDE> 查询。
- Query:
- PREFIX foaf:
- SELECT ?person ?phone
- WHERE {
- ?person foaf:name "Wing C. Yung" .
- ?person foaf:phone ?phone
- }
Query:
PREFIX foaf:
SELECT ?person ?phone
WHERE {
?person foaf:name "Wing C. Yung" .
?person foaf:phone ?phone
}
Result:
- ----------------------------------------------------
- | person | phone |
- ====================================================
- | | "1-555-555-5555" |
- ----------------------------------------------------
----------------------------------------------------
| person | phone |
====================================================
| | "1-555-555-5555" |
----------------------------------------------------
DE>CONSTRUCTDE> 非常重要,可以根据 SPARQL 查询结果构造 RDF 图。在数据合并的时候很方便,因为 CONSTRUCT 图中的谓词不一定和原图中的相同。清单中查询 DE>foaf:phoneDE>,不过在它的位置使用不同的谓词构造了一个新的图(DE>wingerz:officephoneDE>)。
- Query:
- PREFIX foaf:
- PREFIX wingerz:
- CONSTRUCT {
- ?person foaf:name "Wing C. Yung" .
- ?person wingerz:officephone ?phone
- } WHERE {
- ?person foaf:name "Wing C. Yung" .
- ?person foaf:phone ?phone
- }
Query:
PREFIX foaf:
PREFIX wingerz:
CONSTRUCT {
?person foaf:name "Wing C. Yung" .
?person wingerz:officephone ?phone
} WHERE {
?person foaf:name "Wing C. Yung" .
?person foaf:phone ?phone
}
Result:
- 1-555-555-5555Wing C. Yung
1-555-555-5555Wing C. Yung
试验 DE>CONSTRUCTDE> 查询的时候,在资源页面上可以看到完整的响应。遗憾的是,结果被序列化为 RDF-XML 格式,不是很优雅的一种 RDF 序列化格式。
初看起来,RDF 数据似乎有点笨拙而且非常罗嗦。但是有一些突出的优点。
数据的结构没有限制。XML 是层次化的,在关系数据库中建模(和查询)图结构非常困难。
数据容易合并。合并数据(不同的图)是一项简单操作,只需要建立包含图中所有三元组的集合。全球唯一资源减少了歧义。而且如果必要的话,可以用 OWL 规则将不同 URI 的资源映射到同一个 URI。
SquirrelRDF 入门
LDAP 目录本身的结构很容易转化成 RDF。每个 LDAP 对象类都有一组属性。其中一些属性指向文字值(比如名称),另一些则指向其他对象[比如专用名词(DN)指定的工作地点]。
安装后,第一步是寻找 LDAP 存储的模式。如果没有可供试验的 LDAP 存储,可以安装 OpenLDAP 并按照使用说明创建一个简单的地址簿(请参阅 参考资料,尽管可能没有必要费这么多事,因为本文的目标是利用已有的数据源)。模式包含所有不同的对象类及其属性。通过分析属性,可以确定目录中对象之间的关系。
现在来创建从 LDAP 模式到 RDF 的映射。SquirrelRDF 使用 RDF 文件表示这种映射。DE>lmap:serverDE> 谓词用于指定 LDAP 存储的位置。映射允许分配两种类型的 RDF 谓词:文字宾语或者资源宾语。姓名和电话这类基本属性映射为文字宾语谓词。这类映射需要 LDAP 属性名和 RDF 谓词名。然后即可将其链接到资源。假设需要将 LDAP DE>cnDE> 映射到 RDF DE>foaf:nameDE>。清单 4 中创建了一个资源来进行链接 (DE>:namemappingDE>),然后将其链接到配置资源(DE>DE>)。
- @prefix foaf: .
- @prefix lmap: .
- lmap:mapsProp :namemapping .
- :namemapping lmap:property foaf:name
- ; lmap:attribute "cn" .
@prefix foaf: .
@prefix lmap: .
lmap:mapsProp :namemapping .
:namemapping lmap:property foaf:name
; lmap:attribute "cn" .
请注意,将这些链接到一起的 URI 无关紧要,可以改用空白节点,即没有 URI 的资源。它与空白 URI 的资源不同,后面也包括这样的资源。空白 URI 是一种相对 URI,因而解析到 Turtle 文件所在的位置。
清单 5. SquirrelRDF 文字映射到空白节点
- @prefix foaf: .
- @prefix lmap: .
- lmap:mapsProp
- [ lmap:property foaf:name
- ; lmap:attribute "cn" ]
@prefix foaf: .
@prefix lmap: .
lmap:mapsProp
[ lmap:property foaf:name
; lmap:attribute "cn" ]
要映射指向专有名词的 LDAP 属性,可将空白节点的类型指定为 DE>lmap:ObjectPropertyDE>。这样可以保证分配的谓词指向资源(而不是文字)。清单 6 显示了完整的配置文件,包括两个 DE>lmap:ObjectPropertyDE> 属性。LDAP 属性默认分配文字资源谓词。要注意,可能的话最好使用已有的谓词,这里使用了 FOAF 本体的一些词汇表。对于地点也有一个词汇表,不过这里没有用到。
- @prefix rdf: .
- @prefix foaf: .
- @prefix lmap: .
- @prefix people: .
- @prefix ol: .
- a lmap:Map ;
- lmap:server ;
- # Person properties
- lmap:mapsProp
- [ lmap:property foaf:name
- ; lmap:attribute "cn" ; ] ;
- lmap:mapsProp
- [ lmap:property foaf:phone
- ; lmap:attribute "telephoneNumber" ; ] ;
- lmap:mapsProp
- [ lmap:property people:ol
- ; lmap:attribute "officeLocation"
- ; a lmap:ObjectProperty ; ] ;
- lmap:mapsProp
- [ lmap:property people:manager
- ; lmap:attribute "manager"
- ; a lmap:ObjectProperty ; ] ;
- # OfficeLocation properties
- lmap:mapsProp
- [ lmap:property ol:address1
- ; lmap:attribute "address1" ; ] ;
- lmap:mapsProp
- [ lmap:property ol:address2
- ; lmap:attribute "address2" ; ] ;
- lmap:mapsProp
- [ lmap:property ol:city
- ; lmap:attribute "city" ; ] ;
- lmap:mapsProp
- [ lmap:property ol:state
- ; lmap:attribute "state" ; ] ;
- lmap:mapsProp
- [ lmap:property ol:postalCode
- ; lmap:attribute "postalCode" ; ] ;
- .
@prefix rdf: .
@prefix foaf: .
@prefix lmap: .
@prefix people: .
@prefix ol: .
a lmap:Map ;
lmap:server ;
# Person properties
lmap:mapsProp
[ lmap:property foaf:name
; lmap:attribute "cn" ; ] ;
lmap:mapsProp
[ lmap:property foaf:phone
; lmap:attribute "telephoneNumber" ; ] ;
lmap:mapsProp
[ lmap:property people:ol
; lmap:attribute "officeLocation"
; a lmap:ObjectProperty ; ] ;
lmap:mapsProp
[ lmap:property people:manager
; lmap:attribute "manager"
; a lmap:ObjectProperty ; ] ;
# OfficeLocation properties
lmap:mapsProp
[ lmap:property ol:address1
; lmap:attribute "address1" ; ] ;
lmap:mapsProp
[ lmap:property ol:address2
; lmap:attribute "address2" ; ] ;
lmap:mapsProp
[ lmap:property ol:city
; lmap:attribute "city" ; ] ;
lmap:mapsProp
[ lmap:property ol:state
; lmap:attribute "state" ; ] ;
lmap:mapsProp
[ lmap:property ol:postalCode
; lmap:attribute "postalCode" ; ] ;
.
实验映射
清单 7. 测试查询 (test.rq)
- PREFIX foaf:
- SELECT ?person ?phone
- WHERE {
- ?person foaf:name "Wing C Yung" .
- ?person foaf:phone ?phone
- }
PREFIX foaf:
SELECT ?person ?phone
WHERE {
?person foaf:name "Wing C Yung" .
?person foaf:phone ?phone
}
以 DE>dw.ttlDE>(映射文件名)和 DE>test.rqDE>(查询文件名)为参数运行 DE>squirrelrdf.QueryDE>。应该会得到某个人的 URI 和电话号码。
清单 8. 测试查询 (test2.rq)
- PREFIX foaf:
- SELECT ?person ?city ?name
- WHERE{
- ?person foaf:name ?name .
- ?person people:ol ?officelocation .
- ?officelocation ol:state "MA" ;
- ol:city ?city
- }
PREFIX foaf:
SELECT ?person ?city ?name
WHERE{
?person foaf:name ?name .
?person people:ol ?officelocation .
?officelocation ol:state "MA" ;
ol:city ?city
}
将 SquirrelRDF 设置成服务
缺省 Servlet 非常小,只能回答 DE>SELECTDE> 查询,用标准 XML 格式表示结果集。为了使 SPARQL 端点更有用,可让它支持更多的特性。
增加更多的功能之前,我们先要找一种更好的办法测试端点,因为 URL 编码查询和手工创建 URL 都很麻烦。第一个例子使用的通用 SPARQL 处理程序无法再用,因为它取回执行查询的整个 RDF 图,我们的 SquirrelRDF 服务本身是一个能回答查询的 SPARQL 端点(通过将其转化成 LDAP 查询)。创建一个简单 HTML 表单编写和提交查询。一个例子是功能更完善的 SPARQLer(请参阅 参考资料)。单击 Graphs 选项卡来设置端点 endpoint(比如 http://localhost:8080/squirrel)。单击 Change。建立查询提交的端点。尝试本文前面用到的查询。
如前所述,DE>CONSTRUCTDE> 查询很重要,尤其是对非 RDF 格式存储的数据。现在,Servlet 仅支持 DE>SELECTDE> 查询,但是底层的查询引擎可以执行全部四种查询类型。扩展现有的 Servlet 需要修改 DE>doQuery()DE> 方法,如清单 9 所示。DE>com.hp.hpl.jena.query.QueryDE> 对象知道是什么类型的查询,因此可通过它确定 DE>com.hp.hpl.jena.query.engine1.QueryEngineDE> 应该怎么做。不同的查询类型返回不同类型的数据:DE>ASKDE> 返回布尔值,DE>CONSTRUCTDE> 和 DE>DESCRIBEDE> 返回图(从技术上说,返回的是图的包装 DE>ModelDE>),DE>SELECTDE> 返回结果集。
- Query q = QueryFactory.create(theQuery, ".", Syntax.syntaxSPARQL);
- int queryType = q.getQueryType();
- Model m = null;
- switch(queryType){
- case Query.QueryTypeAsk:
- boolean b = qe.execAsk();
- String str = ResultSetFormatter.asXMLString(b);
- resp.setHeader("Content-Type", "application/xml");
- resp.getOutputStream().write(str.getBytes());
- break;
- case Query.QueryTypeConstruct:
- // Gets a model.
- m = qe.execConstruct();
- resp.setHeader("Content-Type", "application/rdf+xml");
- // A Model can serialize itself.
- // The serialization format can be passed in as an argument,
- // default is to write out as RDF/XML.
- m.write(resp.getOutputStream());
- break;
- case Query.QueryTypeDescribe:
- m = qe.execConstruct();
- resp.setHeader("Content-Type", "application/rdf+xml");
- m.write(resp.getOutputStream());
- break;
- case Query.QueryTypeSelect:
- ResultSet results = qe.execSelect();
- resp.setHeader("Content-Type", "application/xml");
- ResultSetFormatter.outputAsXML(resp.getOutputStream(), results);
- break;
- }
Query q = QueryFactory.create(theQuery, ".", Syntax.syntaxSPARQL);
int queryType = q.getQueryType();
Model m = null;
switch(queryType){
case Query.QueryTypeAsk:
boolean b = qe.execAsk();
String str = ResultSetFormatter.asXMLString(b);
resp.setHeader("Content-Type", "application/xml");
resp.getOutputStream().write(str.getBytes());
break;
case Query.QueryTypeConstruct:
// Gets a model.
m = qe.execConstruct();
resp.setHeader("Content-Type", "application/rdf+xml");
// A Model can serialize itself.
// The serialization format can be passed in as an argument,
// default is to write out as RDF/XML.
m.write(resp.getOutputStream());
break;
case Query.QueryTypeDescribe:
m = qe.execConstruct();
resp.setHeader("Content-Type", "application/rdf+xml");
m.write(resp.getOutputStream());
break;
case Query.QueryTypeSelect:
ResultSet results = qe.execSelect();
resp.setHeader("Content-Type", "application/xml");
ResultSetFormatter.outputAsXML(resp.getOutputStream(), results);
break;
}
获得 JSON 输出
清单 10. JSON 查询结果
- {
- "head": {
- "vars": [ "person" , "phone" ]
- } ,
- "results": {
- "distinct": false ,
- "ordered": false ,
- "bindings": [
- {
- "person": { "type": "uri" , "value": "http://wingerz.com/who#wing" } ,
- "phone": { "type": "literal" , "value": "1-555-555-5555" }
- }
- ]
- }
- }
{
"head": {
"vars": [ "person" , "phone" ]
} ,
"results": {
"distinct": false ,
"ordered": false ,
"bindings": [
{
"person": { "type": "uri" , "value": "http://wingerz.com/who#wing" } ,
"phone": { "type": "literal" , "value": "1-555-555-5555" }
}
]
}
}
DE>com.hp.hpl.jena.query.ResultSetFormatterDE> 工具类用于输出 SPARQL 查询结果。清单 9 使用它以 XML 格式输出结果(DE>SELECTDE> 和 DE>ASKDE> 查询)。结果也可以用 RDF 格式。毫不奇怪,使用这个工具类也能将 DE>SELECTDE> 结果邦定输出为 JSON,如清单 11 所示。
- String output = req.getParameter("output");
- if (output.equals("json")){
- resp.setHeader("Content-Type", "application/json");
- ResultSetFormatter.outputAsJSON(resp.getOutputStream(), results);
- }
String output = req.getParameter("output");
if (output.equals("json")){
resp.setHeader("Content-Type", "application/json");
ResultSetFormatter.outputAsJSON(resp.getOutputStream(), results);
}
支持 XSLT
可以向服务器上增加 XSLT 服务,通过外部 XSLT 服务处理查询结果,或者在 XML 输出重增加样式表链接。在 XML 输出中添加样式表链接,将 DE>com.hp.hpl.jena.query.ResultSetFormatterDE> 作为 XSLT URI 的新增参数传递,如清单 13 所示。这样不会执行转换,只是在生成的文档中包括到 XSLT 的链接。当 XSLT 处理程序(比如现代 Web 浏览器)准备文档的时候就会执行转换了(请参阅 参考资料)。
http://wingerz.com/who#wing1-555-555-5555
清单 13. 包括 XSLT
引用
String stylesheet = req.getParameter("xslt");
if (stylesheet != null)
ResultSetFormatter.outputAsXML(resp.getOutputStream(), results, stylesheet);
else
ResultSetFormatter.outputAsXML(resp.getOutputStream(), results);
if (stylesheet != null)
ResultSetFormatter.outputAsXML(resp.getOutputStream(), results, stylesheet);
else
ResultSetFormatter.outputAsXML(resp.getOutputStream(), results);
从 JavaScript 查询
中提供了可免费下载的 SPARQL JavaScript 库。主要用于处理 SPARQL DE>SELECTDE> 查询返回的 JSON 格式的结果。所幸的是,可以让 SPARQL 服务支持这种功能。该库背后最突出的思想是转换器的概念——SPARQL 端点的 JSON 输出包含一般客户机可能用不到的一些数据,比如某些数据类型信息。转换器将 JSON 输出转换成更容易使用的、更自然的对象。