dpla-sparql-client 1.1.3.2.pre.dpla.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/AUTHORS +3 -0
- data/CREDITS +12 -0
- data/README +166 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/lib/sparql/client.rb +730 -0
- data/lib/sparql/client/query.rb +412 -0
- data/lib/sparql/client/repository.rb +302 -0
- data/lib/sparql/client/update.rb +293 -0
- data/lib/sparql/client/version.rb +19 -0
- metadata +172 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: c50c325669a54017b51fd39cc9b45c917975fe4b
|
|
4
|
+
data.tar.gz: 1faeb1c880d70a5d17b7ed31728f74d28b4a0d68
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5f7efd4c331a19b1255e24c386364f33e3046b35d6159783f829e54e4dd0ea140749195d8d8dd565e9e923f9310b0aa3314c00e39c1ed8aaf4be05c69cda2810
|
|
7
|
+
data.tar.gz: f077ec74a124366ddbc0633ad5891af4563d4f3885dabc3da26fdd136ae54d30bc7662ab1fb2c94db793d87fcad0680dc2835234bb2d8995d8bd043ec00eed4f
|
data/AUTHORS
ADDED
data/CREDITS
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
* Christoph Badura <bad@bsd.de>
|
|
2
|
+
* Thomas Feron <tho.feron@gmail.com>
|
|
3
|
+
* James Hetherington <jamespjh@googlemail.com>
|
|
4
|
+
* Gabriel Horner <gabriel.horner@gmail.com>
|
|
5
|
+
* Nicholas Humfrey <njh@aelius.com>
|
|
6
|
+
* Fumihiro Kato <fumi@fumi.me>
|
|
7
|
+
* David Nielsen <david@graveyard.dk>
|
|
8
|
+
* Thamaraiselvan Poomalai <p.thamarai@gmail.com>
|
|
9
|
+
* Michael Sokol <mikaa123@gmail.com>
|
|
10
|
+
* Yves Raimond <yves.raimond@bbc.co.uk>
|
|
11
|
+
* Danny Tran <dannybtran@gmail.com>
|
|
12
|
+
* Nick Gottlieb <ngottlieb@gmail.com>
|
data/README
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#SPARQL Client for RDF.rb
|
|
2
|
+
|
|
3
|
+
This is a [Ruby][] implementation of a [SPARQL][] client for [RDF.rb][].
|
|
4
|
+
|
|
5
|
+
* <http://ruby-rdf.github.com/sparql-client/>
|
|
6
|
+
|
|
7
|
+
[](http://badge.fury.io/rb/sparql-client)
|
|
8
|
+
[](http://travis-ci.org/ruby-rdf/sparql-client)
|
|
9
|
+
|
|
10
|
+
##Features
|
|
11
|
+
|
|
12
|
+
* Executes queries against any SPARQL 1.0/1.1-compatible endpoint over HTTP,
|
|
13
|
+
or against an `RDF::Queryable` instance, using the `SPARQL` gem.
|
|
14
|
+
* Provides a query builder [DSL][] for `ASK`, `SELECT`, `DESCRIBE` and
|
|
15
|
+
`CONSTRUCT` queries.
|
|
16
|
+
* Includes preliminary support for some SPARQL 1.1 Update operations.
|
|
17
|
+
* Supports tuple result sets in both XML, JSON, CSV and TSV formats, with JSON being
|
|
18
|
+
the preferred default for content-negotiation purposes.
|
|
19
|
+
* Supports graph results in any RDF serialization format understood by RDF.rb.
|
|
20
|
+
* Returns results using the [RDF.rb object model][RDF.rb model].
|
|
21
|
+
* Supports accessing endpoints as read/write [`RDF::Repository`][RDF::Repository]
|
|
22
|
+
instances {SPARQL::Client::Repository}.
|
|
23
|
+
|
|
24
|
+
##Examples
|
|
25
|
+
|
|
26
|
+
### Querying a remote SPARQL endpoint
|
|
27
|
+
require 'sparql/client'
|
|
28
|
+
|
|
29
|
+
sparql = SPARQL::Client.new("http://dbpedia.org/sparql")
|
|
30
|
+
|
|
31
|
+
### Querying a `RDF::Repository` instance
|
|
32
|
+
|
|
33
|
+
require 'rdf/trig'
|
|
34
|
+
repository = RDF::Repository.load("http://example/dataset.trig")
|
|
35
|
+
|
|
36
|
+
sparql = SPARQL::Client.new(repository)
|
|
37
|
+
|
|
38
|
+
### Executing a boolean query and outputting the result
|
|
39
|
+
|
|
40
|
+
# ASK WHERE { ?s ?p ?o }
|
|
41
|
+
result = sparql.ask.whether([:s, :p, :o]).true?
|
|
42
|
+
|
|
43
|
+
puts result.inspect #=> true or false
|
|
44
|
+
|
|
45
|
+
### Executing a tuple query and iterating over the returned solutions
|
|
46
|
+
|
|
47
|
+
# SELECT * WHERE { ?s ?p ?o } OFFSET 100 LIMIT 10
|
|
48
|
+
query = sparql.select.where([:s, :p, :o]).offset(100).limit(10)
|
|
49
|
+
|
|
50
|
+
query.each_solution do |solution|
|
|
51
|
+
puts solution.inspect
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
### Executing a graph query and iterating over the returned statements
|
|
55
|
+
|
|
56
|
+
# CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o } LIMIT 10
|
|
57
|
+
query = sparql.construct([:s, :p, :o]).where([:s, :p, :o]).limit(10)
|
|
58
|
+
|
|
59
|
+
query.each_statement do |statement|
|
|
60
|
+
puts statement.inspect
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
### Executing an arbitrary textual SPARQL query string
|
|
64
|
+
|
|
65
|
+
result = sparql.query("ASK WHERE { ?s ?p ?o }")
|
|
66
|
+
|
|
67
|
+
puts result.inspect #=> true or false
|
|
68
|
+
|
|
69
|
+
##Documentation
|
|
70
|
+
|
|
71
|
+
* {SPARQL::Client}
|
|
72
|
+
* {SPARQL::Client::Query}
|
|
73
|
+
* {SPARQL::Client::Repository}
|
|
74
|
+
|
|
75
|
+
##Dependencies
|
|
76
|
+
|
|
77
|
+
* [Ruby](http://ruby-lang.org/) (>= 1.9.3)
|
|
78
|
+
* [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.1)
|
|
79
|
+
* [Net::HTTP::Persistent](http://rubygems.org/gems/net-http-persistent) (>= 1.4)
|
|
80
|
+
* Soft dependency on [SPARQL](http://rubygems.org/gems/sparql) (>= 1.1)
|
|
81
|
+
* Soft dependency on [Nokogiri](http://rubygems.org/gems/nokogiri) (>= 1.5)
|
|
82
|
+
|
|
83
|
+
##Installation
|
|
84
|
+
|
|
85
|
+
The recommended installation method is via [RubyGems](http://rubygems.org/).
|
|
86
|
+
To install the latest official release of the `SPARQL::Client` gem, do:
|
|
87
|
+
|
|
88
|
+
% [sudo] gem install sparql-client
|
|
89
|
+
|
|
90
|
+
##Download
|
|
91
|
+
|
|
92
|
+
To get a local working copy of the development repository, do:
|
|
93
|
+
|
|
94
|
+
% git clone git://github.com/ruby-rdf/sparql-client.git
|
|
95
|
+
|
|
96
|
+
Alternatively, download the latest development version as a tarball as
|
|
97
|
+
follows:
|
|
98
|
+
|
|
99
|
+
% wget http://github.com/ruby-rdf/sparql-client/tarball/master
|
|
100
|
+
|
|
101
|
+
##Mailing List
|
|
102
|
+
|
|
103
|
+
* <http://lists.w3.org/Archives/Public/public-rdf-ruby/>
|
|
104
|
+
|
|
105
|
+
##Authors
|
|
106
|
+
|
|
107
|
+
* [Arto Bendiken](http://github.com/bendiken) - <http://ar.to/>
|
|
108
|
+
* [Ben Lavender](http://github.com/bhuga) - <http://bhuga.net/>
|
|
109
|
+
* [Gregg Kellogg](http://github.com/gkellogg) - <http://greggkellogg.net/>
|
|
110
|
+
|
|
111
|
+
##Contributors
|
|
112
|
+
|
|
113
|
+
* [Christoph Badura](http://github.com/bad) - <http://github.com/bad>
|
|
114
|
+
* [James Hetherington](http://github.com/jamespjh) - <http://twitter.com/jamespjh>
|
|
115
|
+
* [Gabriel Horner](http://github.com/cldwalker) - <http://tagaholic.me/>
|
|
116
|
+
* [Nicholas Humfrey](http://github.com/njh) - <http://www.aelius.com/njh/>
|
|
117
|
+
* [Fumihiro Kato](http://github.com/fumi) - <http://fumi.me/>
|
|
118
|
+
* [David Nielsen](http://github.com/drankard) - <http://github.com/drankard>
|
|
119
|
+
* [Thamaraiselvan Poomalai](http://github.com/selvan) - <http://softonaut.blogspot.com/>
|
|
120
|
+
* [Michael Sokol](http://github.com/mikaa123) - <http://sokolmichael.com/>
|
|
121
|
+
* [Yves Raimond](http://github.com/moustaki) - <http://moustaki.org/>
|
|
122
|
+
* [Thomas Feron](http://github.com/thoferon) - <http://github.com/thoferon>
|
|
123
|
+
* [Nick Gottlieb](http://github.com/ngottlieb) - <http://www.nicholasgottlieb.com>
|
|
124
|
+
|
|
125
|
+
##Contributing
|
|
126
|
+
This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration.
|
|
127
|
+
|
|
128
|
+
* Do your best to adhere to the existing coding conventions and idioms.
|
|
129
|
+
* Don't use hard tabs, and don't leave trailing whitespace on any line.
|
|
130
|
+
* Do document every method you add using [YARD][] annotations. Read the
|
|
131
|
+
[tutorial][YARD-GS] or just look at the existing code for examples.
|
|
132
|
+
* Don't touch the `.gemspec`, `VERSION` or `AUTHORS` files. If you need to
|
|
133
|
+
change them, do so on your private branch only.
|
|
134
|
+
* Do feel free to add yourself to the `CREDITS` file and the corresponding
|
|
135
|
+
list in the the `README`. Alphabetical order applies.
|
|
136
|
+
* Do note that in order for us to merge any non-trivial changes (as a rule
|
|
137
|
+
of thumb, additions larger than about 15 lines of code), we need an
|
|
138
|
+
explicit [public domain dedication][PDD] on record from you.
|
|
139
|
+
|
|
140
|
+
##Resources
|
|
141
|
+
|
|
142
|
+
* <http://ruby-rdf.github.com/sparql-client/>
|
|
143
|
+
* <http://github.com/ruby-rdf/sparql-client>
|
|
144
|
+
* <http://rubygems.org/gems/sparql-client>
|
|
145
|
+
* <http://rubyforge.org/projects/sparql/>
|
|
146
|
+
* <http://raa.ruby-lang.org/project/sparql-client/>
|
|
147
|
+
* <http://www.ohloh.net/p/rdf>
|
|
148
|
+
|
|
149
|
+
##License
|
|
150
|
+
|
|
151
|
+
This is free and unencumbered public domain software. For more information,
|
|
152
|
+
see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
|
|
153
|
+
|
|
154
|
+
[Ruby]: http://ruby-lang.org/
|
|
155
|
+
[RDF]: http://www.w3.org/RDF/
|
|
156
|
+
[SPARQL]: http://en.wikipedia.org/wiki/SPARQL
|
|
157
|
+
[SPARQL JSON]: http://www.w3.org/TR/rdf-sparql-json-res/
|
|
158
|
+
[RDF.rb]: http://rubygems.org/gems/rdf
|
|
159
|
+
[RDF.rb model]: http://blog.datagraph.org/2010/03/rdf-for-ruby
|
|
160
|
+
[RDF::Repository]: http://rubydoc.info/github/ruby-rdf/rdf/RDF/Repository
|
|
161
|
+
[DSL]: http://en.wikipedia.org/wiki/Domain-specific_language
|
|
162
|
+
"domain-specific language"
|
|
163
|
+
[YARD]: http://yardoc.org/
|
|
164
|
+
[YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
|
|
165
|
+
[PDD]: http://unlicense.org/#unlicensing-contributions
|
|
166
|
+
[Backports]: http://rubygems.org/gems/backports
|
data/UNLICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.1.3.2.pre.dpla.1
|
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
require 'net/http/persistent' # @see http://rubygems.org/gems/net-http-persistent
|
|
2
|
+
require 'rdf' # @see http://rubygems.org/gems/rdf
|
|
3
|
+
require 'rdf/ntriples' # @see http://rubygems.org/gems/rdf
|
|
4
|
+
begin
|
|
5
|
+
require 'nokogiri'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
require 'rexml/document'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module SPARQL
|
|
11
|
+
##
|
|
12
|
+
# A SPARQL 1.0/1.1 client for RDF.rb.
|
|
13
|
+
#
|
|
14
|
+
# @see http://www.w3.org/TR/sparql11-query/
|
|
15
|
+
# @see http://www.w3.org/TR/sparql11-protocol/
|
|
16
|
+
# @see http://www.w3.org/TR/sparql11-results-json/
|
|
17
|
+
# @see http://www.w3.org/TR/sparql11-results-csv-tsv/
|
|
18
|
+
class Client
|
|
19
|
+
autoload :Query, 'sparql/client/query'
|
|
20
|
+
autoload :Repository, 'sparql/client/repository'
|
|
21
|
+
autoload :Update, 'sparql/client/update'
|
|
22
|
+
autoload :VERSION, 'sparql/client/version'
|
|
23
|
+
|
|
24
|
+
class ClientError < StandardError; end
|
|
25
|
+
class MalformedQuery < ClientError; end
|
|
26
|
+
class ServerError < StandardError; end
|
|
27
|
+
|
|
28
|
+
RESULT_JSON = 'application/sparql-results+json'.freeze
|
|
29
|
+
RESULT_XML = 'application/sparql-results+xml'.freeze
|
|
30
|
+
RESULT_CSV = 'text/csv'.freeze
|
|
31
|
+
RESULT_TSV = 'text/tab-separated-values'.freeze
|
|
32
|
+
RESULT_BOOL = 'text/boolean'.freeze # Sesame-specific
|
|
33
|
+
RESULT_BRTR = 'application/x-binary-rdf-results-table'.freeze # Sesame-specific
|
|
34
|
+
RESULT_ALL = [
|
|
35
|
+
RESULT_JSON,
|
|
36
|
+
RESULT_XML,
|
|
37
|
+
RESULT_BOOL,
|
|
38
|
+
"#{RESULT_TSV};p=0.8",
|
|
39
|
+
"#{RESULT_CSV};p=0.2",
|
|
40
|
+
'*/*;p=0.1'
|
|
41
|
+
].join(', ').freeze
|
|
42
|
+
GRAPH_ALL = (
|
|
43
|
+
RDF::Format.content_types.keys +
|
|
44
|
+
['*/*;p=0.1']
|
|
45
|
+
).join(', ').freeze
|
|
46
|
+
|
|
47
|
+
ACCEPT_JSON = {'Accept' => RESULT_JSON}.freeze
|
|
48
|
+
ACCEPT_XML = {'Accept' => RESULT_XML}.freeze
|
|
49
|
+
ACCEPT_CSV = {'Accept' => RESULT_CSV}.freeze
|
|
50
|
+
ACCEPT_TSV = {'Accept' => RESULT_TSV}.freeze
|
|
51
|
+
ACCEPT_BRTR = {'Accept' => RESULT_BRTR}.freeze
|
|
52
|
+
ACCEPT_RESULTS = {'Accept' => RESULT_ALL}.freeze
|
|
53
|
+
ACCEPT_GRAPH = {'Accept' => GRAPH_ALL}.freeze
|
|
54
|
+
|
|
55
|
+
DEFAULT_PROTOCOL = 1.0
|
|
56
|
+
DEFAULT_METHOD = :post
|
|
57
|
+
|
|
58
|
+
XMLNS = {'sparql' => 'http://www.w3.org/2005/sparql-results#'}.freeze
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# The SPARQL endpoint URL, or an RDF::Queryable instance, to use the native SPARQL engine.
|
|
62
|
+
#
|
|
63
|
+
# @return [RDF::URI, RDF::Queryable]
|
|
64
|
+
attr_reader :url
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# The HTTP headers that will be sent in requests to the endpoint.
|
|
68
|
+
#
|
|
69
|
+
# @return [Hash{String => String}]
|
|
70
|
+
attr_reader :headers
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Any miscellaneous configuration.
|
|
74
|
+
#
|
|
75
|
+
# @return [Hash{Symbol => Object}]
|
|
76
|
+
attr_reader :options
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Initialize a new sparql client, either using the URL of
|
|
80
|
+
# a SPARQL endpoint or an `RDF::Queryable` instance to use
|
|
81
|
+
# the native SPARQL gem.
|
|
82
|
+
#
|
|
83
|
+
# @param [String, RDF::Queryable, #to_s] url
|
|
84
|
+
# URL of endpoint, or queryable object.
|
|
85
|
+
# @param [Hash{Symbol => Object}] options
|
|
86
|
+
# @option options [Symbol] :method (DEFAULT_METHOD)
|
|
87
|
+
# @option options [Number] :protocol (DEFAULT_PROTOCOL)
|
|
88
|
+
# @option options [Hash] :headers
|
|
89
|
+
# @option options [Hash] :read_timeout
|
|
90
|
+
def initialize(url, options = {}, &block)
|
|
91
|
+
case url
|
|
92
|
+
when RDF::Queryable
|
|
93
|
+
@url, @options = url, options.dup
|
|
94
|
+
else
|
|
95
|
+
@url, @options = RDF::URI.new(url.to_s), options.dup
|
|
96
|
+
@headers = @options.delete(:headers) || {}
|
|
97
|
+
@http = http_klass(@url.scheme)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if block_given?
|
|
101
|
+
case block.arity
|
|
102
|
+
when 1 then block.call(self)
|
|
103
|
+
else instance_eval(&block)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Executes a boolean `ASK` query.
|
|
110
|
+
#
|
|
111
|
+
# @return [Query]
|
|
112
|
+
def ask(*args)
|
|
113
|
+
call_query_method(:ask, *args)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Executes a tuple `SELECT` query.
|
|
118
|
+
#
|
|
119
|
+
# @param [Array<Symbol>] args
|
|
120
|
+
# @return [Query]
|
|
121
|
+
def select(*args)
|
|
122
|
+
call_query_method(:select, *args)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
##
|
|
126
|
+
# Executes a `DESCRIBE` query.
|
|
127
|
+
#
|
|
128
|
+
# @param [Array<Symbol, RDF::URI>] args
|
|
129
|
+
# @return [Query]
|
|
130
|
+
def describe(*args)
|
|
131
|
+
call_query_method(:describe, *args)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Executes a graph `CONSTRUCT` query.
|
|
136
|
+
#
|
|
137
|
+
# @param [Array<Symbol>] args
|
|
138
|
+
# @return [Query]
|
|
139
|
+
def construct(*args)
|
|
140
|
+
call_query_method(:construct, *args)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Executes an `INSERT DATA` operation.
|
|
145
|
+
#
|
|
146
|
+
# This requires that the endpoint support SPARQL 1.1 Update.
|
|
147
|
+
#
|
|
148
|
+
# Note that for inserting non-trivial amounts of data, you probably
|
|
149
|
+
# ought to consider using the RDF store's native bulk-loading facilities
|
|
150
|
+
# or APIs, as `INSERT DATA` operations entail comparably higher
|
|
151
|
+
# parsing overhead.
|
|
152
|
+
#
|
|
153
|
+
# @example Inserting data constructed ad-hoc
|
|
154
|
+
# client.insert_data(RDF::Graph.new { |graph|
|
|
155
|
+
# graph << [:jhacker, RDF::FOAF.name, "J. Random Hacker"]
|
|
156
|
+
# })
|
|
157
|
+
#
|
|
158
|
+
# @example Inserting data sourced from a file or URL
|
|
159
|
+
# data = RDF::Graph.load("http://rdf.rubyforge.org/doap.nt")
|
|
160
|
+
# client.insert_data(data)
|
|
161
|
+
#
|
|
162
|
+
# @example Inserting data into a named graph
|
|
163
|
+
# client.insert_data(data, :graph => "http://example.org/")
|
|
164
|
+
#
|
|
165
|
+
# @param [RDF::Enumerable] data
|
|
166
|
+
# @param [Hash{Symbol => Object}] options
|
|
167
|
+
# @option options [RDF::URI, String] :graph
|
|
168
|
+
# @return [void] `self`
|
|
169
|
+
# @see http://www.w3.org/TR/sparql11-update/#insertData
|
|
170
|
+
def insert_data(data, options = {})
|
|
171
|
+
self.update(Update::InsertData.new(data, options))
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
##
|
|
175
|
+
# Executes a `DELETE DATA` operation.
|
|
176
|
+
#
|
|
177
|
+
# This requires that the endpoint support SPARQL 1.1 Update.
|
|
178
|
+
#
|
|
179
|
+
# @example Deleting data sourced from a file or URL
|
|
180
|
+
# data = RDF::Graph.load("http://rdf.rubyforge.org/doap.nt")
|
|
181
|
+
# client.delete_data(data)
|
|
182
|
+
#
|
|
183
|
+
# @example Deleting data from a named graph
|
|
184
|
+
# client.delete_data(data, :graph => "http://example.org/")
|
|
185
|
+
#
|
|
186
|
+
# @param [RDF::Enumerable] data
|
|
187
|
+
# @param [Hash{Symbol => Object}] options
|
|
188
|
+
# @option options [RDF::URI, String] :graph
|
|
189
|
+
# @return [void] `self`
|
|
190
|
+
# @see http://www.w3.org/TR/sparql11-update/#deleteData
|
|
191
|
+
def delete_data(data, options = {})
|
|
192
|
+
self.update(Update::DeleteData.new(data, options))
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# Executes a `DELETE/INSERT` operation.
|
|
197
|
+
#
|
|
198
|
+
# This requires that the endpoint support SPARQL 1.1 Update.
|
|
199
|
+
#
|
|
200
|
+
# @param [RDF::Enumerable] delete_graph
|
|
201
|
+
# @param [RDF::Enumerable] insert_graph
|
|
202
|
+
# @param [RDF::Enumerable] where_graph
|
|
203
|
+
# @param [Hash{Symbol => Object}] options
|
|
204
|
+
# @option options [RDF::URI, String] :graph
|
|
205
|
+
# @return [void] `self`
|
|
206
|
+
# @see http://www.w3.org/TR/sparql11-update/#deleteInsert
|
|
207
|
+
def delete_insert(delete_graph, insert_graph = nil, where_graph = nil, options = {})
|
|
208
|
+
self.update(Update::DeleteInsert.new(delete_graph, insert_graph, where_graph, options))
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
##
|
|
212
|
+
# Executes a `CLEAR GRAPH` operation.
|
|
213
|
+
#
|
|
214
|
+
# This is a convenience wrapper for the {#clear} method.
|
|
215
|
+
#
|
|
216
|
+
# @example `CLEAR GRAPH <http://example.org/>`
|
|
217
|
+
# client.clear_graph("http://example.org/")
|
|
218
|
+
#
|
|
219
|
+
# @param [RDF::URI, String] graph_uri
|
|
220
|
+
# @param [Hash{Symbol => Object}] options
|
|
221
|
+
# @option options [Boolean] :silent
|
|
222
|
+
# @return [void] `self`
|
|
223
|
+
# @see http://www.w3.org/TR/sparql11-update/#clear
|
|
224
|
+
def clear_graph(graph_uri, options = {})
|
|
225
|
+
self.clear(:graph, graph_uri, options)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
##
|
|
229
|
+
# Executes a `CLEAR` operation.
|
|
230
|
+
#
|
|
231
|
+
# This requires that the endpoint support SPARQL 1.1 Update.
|
|
232
|
+
#
|
|
233
|
+
# @example `CLEAR GRAPH <http://example.org/>`
|
|
234
|
+
# client.clear(:graph, RDF::URI("http://example.org/"))
|
|
235
|
+
#
|
|
236
|
+
# @example `CLEAR DEFAULT`
|
|
237
|
+
# client.clear(:default)
|
|
238
|
+
#
|
|
239
|
+
# @example `CLEAR NAMED`
|
|
240
|
+
# client.clear(:named)
|
|
241
|
+
#
|
|
242
|
+
# @example `CLEAR ALL`
|
|
243
|
+
# client.clear(:all)
|
|
244
|
+
#
|
|
245
|
+
# @overload clear(what, *arguments)
|
|
246
|
+
# @param [Symbol, #to_sym] what
|
|
247
|
+
# @param [Array] arguments splat of other arguments to {Update::Clear}.
|
|
248
|
+
# @option options [Boolean] :silent
|
|
249
|
+
# @return [void] `self`
|
|
250
|
+
#
|
|
251
|
+
# @overload clear(what, *arguments, options = {})
|
|
252
|
+
# @param [Symbol, #to_sym] what
|
|
253
|
+
# @param [Array] arguments splat of other arguments to {Update::Clear}.
|
|
254
|
+
# @param [Hash{Symbol => Object}] options
|
|
255
|
+
# @option options [Boolean] :silent
|
|
256
|
+
# @return [void] `self`
|
|
257
|
+
#
|
|
258
|
+
# @see http://www.w3.org/TR/sparql11-update/#clear
|
|
259
|
+
def clear(what, *arguments)
|
|
260
|
+
self.update(Update::Clear.new(what, *arguments))
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
##
|
|
264
|
+
# @private
|
|
265
|
+
def call_query_method(meth, *args)
|
|
266
|
+
client = self
|
|
267
|
+
result = Query.send(meth, *args)
|
|
268
|
+
(class << result; self; end).send(:define_method, :execute) do
|
|
269
|
+
client.query(self)
|
|
270
|
+
end
|
|
271
|
+
result
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
##
|
|
275
|
+
# Returns a mapping of blank node results for this client.
|
|
276
|
+
#
|
|
277
|
+
# @private
|
|
278
|
+
def nodes
|
|
279
|
+
@nodes ||= {}
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
##
|
|
283
|
+
# Executes a SPARQL query and returns the parsed results.
|
|
284
|
+
#
|
|
285
|
+
# @param [String, #to_s] query
|
|
286
|
+
# @param [Hash{Symbol => Object}] options
|
|
287
|
+
# @option options [String] :content_type
|
|
288
|
+
# @option options [Hash] :headers
|
|
289
|
+
# @return [Array<RDF::Query::Solution>]
|
|
290
|
+
# @see http://www.w3.org/TR/sparql11-protocol/#query-operation
|
|
291
|
+
def query(query, options = {})
|
|
292
|
+
@op = :query
|
|
293
|
+
case @url
|
|
294
|
+
when RDF::Queryable
|
|
295
|
+
require 'sparql' unless defined?(::SPARQL::Grammar)
|
|
296
|
+
SPARQL.execute(query, @url, options)
|
|
297
|
+
else
|
|
298
|
+
parse_response(response(query, options), options)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
##
|
|
303
|
+
# Executes a SPARQL update operation.
|
|
304
|
+
#
|
|
305
|
+
# @param [String, #to_s] query
|
|
306
|
+
# @param [Hash{Symbol => Object}] options
|
|
307
|
+
# @option options [String] :endpoint
|
|
308
|
+
# @option options [String] :content_type
|
|
309
|
+
# @option options [Hash] :headers
|
|
310
|
+
# @return [void] `self`
|
|
311
|
+
# @see http://www.w3.org/TR/sparql11-protocol/#update-operation
|
|
312
|
+
def update(query, options = {})
|
|
313
|
+
@op = :update
|
|
314
|
+
@alt_endpoint = options[:endpoint] unless options[:endpoint].nil?
|
|
315
|
+
case @url
|
|
316
|
+
when RDF::Queryable
|
|
317
|
+
require 'sparql' unless defined?(::SPARQL::Grammar)
|
|
318
|
+
SPARQL.execute(query, @url, options)
|
|
319
|
+
else
|
|
320
|
+
parse_response(response(query, options), options)
|
|
321
|
+
end
|
|
322
|
+
self
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
##
|
|
326
|
+
# Executes a SPARQL query and returns the Net::HTTP::Response of the
|
|
327
|
+
# result.
|
|
328
|
+
#
|
|
329
|
+
# @param [String, #to_s] query
|
|
330
|
+
# @param [Hash{Symbol => Object}] options
|
|
331
|
+
# @option options [String] :content_type
|
|
332
|
+
# @option options [Hash] :headers
|
|
333
|
+
# @return [String]
|
|
334
|
+
def response(query, options = {})
|
|
335
|
+
headers = options[:headers] || {}
|
|
336
|
+
headers['Accept'] = options[:content_type] if options[:content_type]
|
|
337
|
+
request(query, headers) do |response|
|
|
338
|
+
case response
|
|
339
|
+
when Net::HTTPBadRequest # 400 Bad Request
|
|
340
|
+
raise MalformedQuery.new(response.body + " Processing query #{query}")
|
|
341
|
+
when Net::HTTPClientError # 4xx
|
|
342
|
+
raise ClientError.new(response.body + " Processing query #{query}")
|
|
343
|
+
when Net::HTTPServerError # 5xx
|
|
344
|
+
raise ServerError.new(response.body + " Processing query #{query}")
|
|
345
|
+
when Net::HTTPSuccess # 2xx
|
|
346
|
+
response
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
##
|
|
352
|
+
# @param [Net::HTTPSuccess] response
|
|
353
|
+
# @param [Hash{Symbol => Object}] options
|
|
354
|
+
# @return [Object]
|
|
355
|
+
def parse_response(response, options = {})
|
|
356
|
+
case options[:content_type] || response.content_type
|
|
357
|
+
when RESULT_BOOL # Sesame-specific
|
|
358
|
+
response.body == 'true'
|
|
359
|
+
when RESULT_JSON
|
|
360
|
+
self.class.parse_json_bindings(response.body, nodes)
|
|
361
|
+
when RESULT_XML
|
|
362
|
+
self.class.parse_xml_bindings(response.body, nodes)
|
|
363
|
+
when RESULT_CSV
|
|
364
|
+
self.class.parse_csv_bindings(response.body, nodes)
|
|
365
|
+
when RESULT_TSV
|
|
366
|
+
self.class.parse_tsv_bindings(response.body, nodes)
|
|
367
|
+
else
|
|
368
|
+
parse_rdf_serialization(response, options)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
##
|
|
373
|
+
# @param [String, Hash] json
|
|
374
|
+
# @return [<RDF::Query::Solutions>]
|
|
375
|
+
# @see http://www.w3.org/TR/rdf-sparql-json-res/#results
|
|
376
|
+
def self.parse_json_bindings(json, nodes = {})
|
|
377
|
+
require 'json' unless defined?(::JSON)
|
|
378
|
+
json = JSON.parse(json.to_s) unless json.is_a?(Hash)
|
|
379
|
+
case
|
|
380
|
+
when json.has_key?('boolean')
|
|
381
|
+
json['boolean']
|
|
382
|
+
when json.has_key?('results')
|
|
383
|
+
solutions = json['results']['bindings'].map do |row|
|
|
384
|
+
row = row.inject({}) do |cols, (name, value)|
|
|
385
|
+
cols.merge(name.to_sym => parse_json_value(value, nodes))
|
|
386
|
+
end
|
|
387
|
+
RDF::Query::Solution.new(row)
|
|
388
|
+
end
|
|
389
|
+
RDF::Query::Solutions.new(solutions)
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
##
|
|
394
|
+
# @param [Hash{String => String}] value
|
|
395
|
+
# @return [RDF::Value]
|
|
396
|
+
# @see http://www.w3.org/TR/sparql11-results-json/#select-encode-terms
|
|
397
|
+
# @see http://www.w3.org/TR/rdf-sparql-json-res/#variable-binding-results
|
|
398
|
+
def self.parse_json_value(value, nodes = {})
|
|
399
|
+
case value['type'].to_sym
|
|
400
|
+
when :bnode
|
|
401
|
+
nodes[id = value['value']] ||= RDF::Node.new(id)
|
|
402
|
+
when :uri
|
|
403
|
+
RDF::URI.new(value['value'])
|
|
404
|
+
when :literal
|
|
405
|
+
RDF::Literal.new(value['value'], :datatype => value['datatype'], :language => value['xml:lang'])
|
|
406
|
+
when :'typed-literal'
|
|
407
|
+
RDF::Literal.new(value['value'], :datatype => value['datatype'])
|
|
408
|
+
else nil
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
##
|
|
413
|
+
# @param [String, Array<Array<String>>] csv
|
|
414
|
+
# @return [<RDF::Query::Solutions>]
|
|
415
|
+
# @see http://www.w3.org/TR/sparql11-results-csv-tsv/
|
|
416
|
+
def self.parse_csv_bindings(csv, nodes = {})
|
|
417
|
+
require 'csv' unless defined?(::CSV)
|
|
418
|
+
csv = CSV.parse(csv.to_s) unless csv.is_a?(Array)
|
|
419
|
+
vars = csv.shift
|
|
420
|
+
solutions = RDF::Query::Solutions.new
|
|
421
|
+
csv.each do |row|
|
|
422
|
+
solution = RDF::Query::Solution.new
|
|
423
|
+
row.each_with_index do |v, i|
|
|
424
|
+
term = case v
|
|
425
|
+
when /^_:(.*)$/ then nodes[$1] ||= RDF::Node($1)
|
|
426
|
+
when /^\w+:.*$/ then RDF::URI(v)
|
|
427
|
+
else RDF::Literal(v)
|
|
428
|
+
end
|
|
429
|
+
solution[vars[i].to_sym] = term
|
|
430
|
+
end
|
|
431
|
+
solutions << solution
|
|
432
|
+
end
|
|
433
|
+
solutions
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
##
|
|
437
|
+
# @param [String, Array<Array<String>>] tsv
|
|
438
|
+
# @return [<RDF::Query::Solutions>]
|
|
439
|
+
# @see http://www.w3.org/TR/sparql11-results-csv-tsv/
|
|
440
|
+
def self.parse_tsv_bindings(tsv, nodes = {})
|
|
441
|
+
tsv = tsv.lines.map {|l| l.chomp.split("\t")} unless tsv.is_a?(Array)
|
|
442
|
+
vars = tsv.shift.map {|h| h.sub(/^\?/, '')}
|
|
443
|
+
solutions = RDF::Query::Solutions.new
|
|
444
|
+
tsv.each do |row|
|
|
445
|
+
solution = RDF::Query::Solution.new
|
|
446
|
+
row.each_with_index do |v, i|
|
|
447
|
+
if !v.empty?
|
|
448
|
+
term = RDF::NTriples.unserialize(v) || case v
|
|
449
|
+
when /^\d+\.\d*[eE][+-]?[0-9]+$/ then RDF::Literal::Double.new(v)
|
|
450
|
+
when /^\d*\.\d+[eE][+-]?[0-9]+$/ then RDF::Literal::Double.new(v)
|
|
451
|
+
when /^\d*\.\d+$/ then RDF::Literal::Decimal.new(v)
|
|
452
|
+
when /^\d+$/ then RDF::Literal::Integer.new(v)
|
|
453
|
+
else
|
|
454
|
+
RDF::Literal(v)
|
|
455
|
+
end
|
|
456
|
+
nodes[term.id] = term if term.is_a? RDF::Node
|
|
457
|
+
solution[vars[i].to_sym] = term
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
solutions << solution
|
|
461
|
+
end
|
|
462
|
+
solutions
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
##
|
|
466
|
+
# @param [String, IO, Nokogiri::XML::Node, REXML::Element] xml
|
|
467
|
+
# @return [<RDF::Query::Solutions>]
|
|
468
|
+
# @see http://www.w3.org/TR/rdf-sparql-json-res/#results
|
|
469
|
+
def self.parse_xml_bindings(xml, nodes = {})
|
|
470
|
+
xml.force_encoding(::Encoding::UTF_8) if xml.respond_to?(:force_encoding)
|
|
471
|
+
|
|
472
|
+
if defined?(::Nokogiri)
|
|
473
|
+
xml = Nokogiri::XML(xml).root unless xml.is_a?(Nokogiri::XML::Document)
|
|
474
|
+
case
|
|
475
|
+
when boolean = xml.xpath("//sparql:boolean", XMLNS)[0]
|
|
476
|
+
boolean.text == 'true'
|
|
477
|
+
when results = xml.xpath("//sparql:results", XMLNS)[0]
|
|
478
|
+
solutions = results.elements.map do |result|
|
|
479
|
+
row = {}
|
|
480
|
+
result.elements.each do |binding|
|
|
481
|
+
name = binding.attr('name').to_sym
|
|
482
|
+
value = binding.elements.first
|
|
483
|
+
row[name] = parse_xml_value(value, nodes)
|
|
484
|
+
end
|
|
485
|
+
RDF::Query::Solution.new(row)
|
|
486
|
+
end
|
|
487
|
+
RDF::Query::Solutions.new(solutions)
|
|
488
|
+
end
|
|
489
|
+
else
|
|
490
|
+
# REXML
|
|
491
|
+
xml = REXML::Document.new(xml).root unless xml.is_a?(REXML::Element)
|
|
492
|
+
case
|
|
493
|
+
when boolean = xml.elements['boolean']
|
|
494
|
+
boolean.text == 'true'
|
|
495
|
+
when results = xml.elements['results']
|
|
496
|
+
solutions = results.elements.map do |result|
|
|
497
|
+
row = {}
|
|
498
|
+
result.elements.each do |binding|
|
|
499
|
+
name = binding.attributes['name'].to_sym
|
|
500
|
+
value = binding.select { |node| node.kind_of?(::REXML::Element) }.first
|
|
501
|
+
row[name] = parse_xml_value(value, nodes)
|
|
502
|
+
end
|
|
503
|
+
RDF::Query::Solution.new(row)
|
|
504
|
+
end
|
|
505
|
+
RDF::Query::Solutions.new(solutions)
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
##
|
|
511
|
+
# @param [Nokogiri::XML::Element, REXML::Element] value
|
|
512
|
+
# @return [RDF::Value]
|
|
513
|
+
# @see http://www.w3.org/TR/rdf-sparql-json-res/#variable-binding-results
|
|
514
|
+
def self.parse_xml_value(value, nodes = {})
|
|
515
|
+
case value.name.to_sym
|
|
516
|
+
when :bnode
|
|
517
|
+
nodes[id = value.text] ||= RDF::Node.new(id)
|
|
518
|
+
when :uri
|
|
519
|
+
RDF::URI.new(value.text)
|
|
520
|
+
when :literal
|
|
521
|
+
lang = value.respond_to?(:attr) ? value.attr('xml:lang') : value.attributes['xml:lang']
|
|
522
|
+
datatype = value.respond_to?(:attr) ? value.attr('datatype') : value.attributes['datatype']
|
|
523
|
+
RDF::Literal.new(value.text, :language => lang, :datatype => datatype)
|
|
524
|
+
else nil
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
##
|
|
529
|
+
# @param [Net::HTTPSuccess] response
|
|
530
|
+
# @param [Hash{Symbol => Object}] options
|
|
531
|
+
# @return [RDF::Enumerable]
|
|
532
|
+
def parse_rdf_serialization(response, options = {})
|
|
533
|
+
options = {:content_type => response.content_type} if options.empty?
|
|
534
|
+
if reader = RDF::Reader.for(options)
|
|
535
|
+
reader.new(response.body)
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
##
|
|
540
|
+
# Serializes a URI or URI string into SPARQL syntax.
|
|
541
|
+
#
|
|
542
|
+
# @param [RDF::URI, String] uri
|
|
543
|
+
# @return [String]
|
|
544
|
+
# @private
|
|
545
|
+
def self.serialize_uri(uri)
|
|
546
|
+
case uri
|
|
547
|
+
when String then RDF::NTriples.serialize(RDF::URI(uri))
|
|
548
|
+
when RDF::URI then RDF::NTriples.serialize(uri)
|
|
549
|
+
else raise ArgumentError, "expected the graph URI to be a String or RDF::URI, but got #{uri.inspect}"
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
##
|
|
554
|
+
# Serializes an `RDF::Value` into SPARQL syntax.
|
|
555
|
+
#
|
|
556
|
+
# @param [RDF::Value] value
|
|
557
|
+
# @param [Boolean] use_vars (false) Use variables in place of BNodes
|
|
558
|
+
# @return [String]
|
|
559
|
+
# @private
|
|
560
|
+
def self.serialize_value(value, use_vars = false)
|
|
561
|
+
# SPARQL queries are UTF-8, but support ASCII-style Unicode escapes, so
|
|
562
|
+
# the N-Triples serializer is fine unless it's a variable:
|
|
563
|
+
case
|
|
564
|
+
when value.nil? then RDF::Query::Variable.new.to_s
|
|
565
|
+
when value.variable? then value.to_s
|
|
566
|
+
when value.node? then (use_vars ? RDF::Query::Variable.new(value.id) : value)
|
|
567
|
+
else RDF::NTriples.serialize(value)
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
##
|
|
572
|
+
# Serializes a SPARQL predicate
|
|
573
|
+
#
|
|
574
|
+
# @param [RDF::Value, Array, String] value
|
|
575
|
+
# @param [Fixnum] rdepth
|
|
576
|
+
# @return [String]
|
|
577
|
+
# @private
|
|
578
|
+
def self.serialize_predicate(value,rdepth=0)
|
|
579
|
+
case value
|
|
580
|
+
when nil
|
|
581
|
+
RDF::Query::Variable.new.to_s
|
|
582
|
+
when String then value
|
|
583
|
+
when Array
|
|
584
|
+
s = value.map{|v|serialize_predicate(v,rdepth+1)}.join
|
|
585
|
+
rdepth > 0 ? "(#{s})" : s
|
|
586
|
+
when RDF::Value
|
|
587
|
+
# abbreviate RDF.type in the predicate position per SPARQL grammar
|
|
588
|
+
value.equal?(RDF.type) ? 'a' : serialize_value(value)
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
##
|
|
593
|
+
# Serializes a SPARQL graph
|
|
594
|
+
#
|
|
595
|
+
# @param [RDF::Enumerable] patterns
|
|
596
|
+
# @param [Boolean] use_vars (false) Use variables in place of BNodes
|
|
597
|
+
# @return [String]
|
|
598
|
+
# @private
|
|
599
|
+
def self.serialize_patterns(patterns, use_vars = false)
|
|
600
|
+
patterns.map do |pattern|
|
|
601
|
+
serialized_pattern = RDF::Statement.from(pattern).to_triple.each_with_index.map do |v, i|
|
|
602
|
+
if i == 1
|
|
603
|
+
SPARQL::Client.serialize_predicate(v)
|
|
604
|
+
else
|
|
605
|
+
SPARQL::Client.serialize_value(v, use_vars)
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
serialized_pattern.join(' ') + ' .'
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
##
|
|
613
|
+
# Outputs a developer-friendly representation of this object to `stderr`.
|
|
614
|
+
#
|
|
615
|
+
# @return [void]
|
|
616
|
+
def inspect!
|
|
617
|
+
warn(inspect)
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
##
|
|
621
|
+
# Returns a developer-friendly representation of this object.
|
|
622
|
+
#
|
|
623
|
+
# @return [String]
|
|
624
|
+
def inspect
|
|
625
|
+
sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, url.to_s)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
protected
|
|
629
|
+
|
|
630
|
+
##
|
|
631
|
+
# Returns an HTTP class or HTTP proxy class based on the `http_proxy`
|
|
632
|
+
# and `https_proxy` environment variables.
|
|
633
|
+
#
|
|
634
|
+
# @param [String] scheme
|
|
635
|
+
# @return [Net::HTTP::Proxy]
|
|
636
|
+
def http_klass(scheme)
|
|
637
|
+
proxy_url = nil
|
|
638
|
+
case scheme
|
|
639
|
+
when 'http'
|
|
640
|
+
value = ENV['http_proxy']
|
|
641
|
+
proxy_url = URI.parse(value) unless value.nil? || value.empty?
|
|
642
|
+
when 'https'
|
|
643
|
+
value = ENV['https_proxy']
|
|
644
|
+
proxy_url = URI.parse(value) unless value.nil? || value.empty?
|
|
645
|
+
end
|
|
646
|
+
klass = Net::HTTP::Persistent.new(self.class.to_s, proxy_url)
|
|
647
|
+
klass.keep_alive = 120 # increase to 2 minutes
|
|
648
|
+
klass.read_timeout = @options[:read_timeout] || 60
|
|
649
|
+
klass
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
##
|
|
653
|
+
# Performs an HTTP request against the SPARQL endpoint.
|
|
654
|
+
#
|
|
655
|
+
# @param [String, #to_s] query
|
|
656
|
+
# @param [Hash{String => String}] headers
|
|
657
|
+
# @yield [response]
|
|
658
|
+
# @yieldparam [Net::HTTPResponse] response
|
|
659
|
+
# @return [Net::HTTPResponse]
|
|
660
|
+
# @see http://www.w3.org/TR/sparql11-protocol/#query-operation
|
|
661
|
+
def request(query, headers = {}, &block)
|
|
662
|
+
method = (self.options[:method] || DEFAULT_METHOD).to_sym
|
|
663
|
+
|
|
664
|
+
# Make sure an appropriate Accept header is present
|
|
665
|
+
headers['Accept'] ||= if (query.respond_to?(:expects_statements?) ?
|
|
666
|
+
query.expects_statements? :
|
|
667
|
+
(query =~ /CONSTRUCT|DESCRIBE|DELETE|CLEAR/))
|
|
668
|
+
GRAPH_ALL
|
|
669
|
+
else
|
|
670
|
+
RESULT_ALL
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
request = send("make_#{method}_request", query, headers)
|
|
674
|
+
|
|
675
|
+
request.basic_auth(url.user, url.password) if url.user && !url.user.empty?
|
|
676
|
+
|
|
677
|
+
response = @http.request(url, request)
|
|
678
|
+
|
|
679
|
+
10.times do
|
|
680
|
+
if response.kind_of? Net::HTTPRedirection
|
|
681
|
+
response = @http.request(RDF::URI(response['location']), request)
|
|
682
|
+
else
|
|
683
|
+
return block_given? ? block.call(response) : response
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
raise ServerError, "Infinite redirect at #{url}. Redirected more than 10 times."
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
##
|
|
690
|
+
# Constructs an HTTP GET request according to the SPARQL Protocol.
|
|
691
|
+
#
|
|
692
|
+
# @param [String, #to_s] query
|
|
693
|
+
# @param [Hash{String => String}] headers
|
|
694
|
+
# @return [Net::HTTPRequest]
|
|
695
|
+
# @see http://www.w3.org/TR/sparql11-protocol/#query-via-get
|
|
696
|
+
def make_get_request(query, headers = {})
|
|
697
|
+
url = self.url.dup
|
|
698
|
+
url.query_values = (url.query_values || {}).merge(:query => query.to_s)
|
|
699
|
+
request = Net::HTTP::Get.new(url.request_uri, self.headers.merge(headers))
|
|
700
|
+
request
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
##
|
|
704
|
+
# Constructs an HTTP POST request according to the SPARQL Protocol.
|
|
705
|
+
#
|
|
706
|
+
# @param [String, #to_s] query
|
|
707
|
+
# @param [Hash{String => String}] headers
|
|
708
|
+
# @return [Net::HTTPRequest]
|
|
709
|
+
# @see http://www.w3.org/TR/sparql11-protocol/#query-via-post-direct
|
|
710
|
+
# @see http://www.w3.org/TR/sparql11-protocol/#query-via-post-urlencoded
|
|
711
|
+
def make_post_request(query, headers = {})
|
|
712
|
+
if @alt_endpoint.nil?
|
|
713
|
+
endpoint = url.request_uri
|
|
714
|
+
else
|
|
715
|
+
endpoint = @alt_endpoint
|
|
716
|
+
end
|
|
717
|
+
request = Net::HTTP::Post.new(endpoint, self.headers.merge(headers))
|
|
718
|
+
case (self.options[:protocol] || DEFAULT_PROTOCOL).to_s
|
|
719
|
+
when '1.1'
|
|
720
|
+
request['Content-Type'] = 'application/sparql-' + (@op || :query).to_s
|
|
721
|
+
request.body = query.to_s
|
|
722
|
+
when '1.0'
|
|
723
|
+
request.set_form_data((@op || :query) => query.to_s)
|
|
724
|
+
else
|
|
725
|
+
raise ArgumentError, "unknown SPARQL protocol version: #{self.options[:protocol].inspect}"
|
|
726
|
+
end
|
|
727
|
+
request
|
|
728
|
+
end
|
|
729
|
+
end # Client
|
|
730
|
+
end # SPARQL
|