pho 0.4.1 → 0.5
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.
- data/CHANGES +18 -1
- data/README +10 -0
- data/Rakefile +2 -1
- data/doc/rdoc/classes/Pho.html +33 -22
- data/doc/rdoc/classes/Pho/DatatypeProperty.html +12 -12
- data/doc/rdoc/classes/Pho/{RDF_JSON.html → Enrichment.html} +8 -7
- data/doc/rdoc/classes/Pho/Enrichment/ResourceEnricher.html +310 -0
- data/doc/rdoc/classes/Pho/Etags.html +42 -42
- data/doc/rdoc/classes/Pho/Facet/Results.html +19 -19
- data/doc/rdoc/classes/Pho/Facet/Term.html +6 -6
- data/doc/rdoc/classes/Pho/FieldPredicateMap.html +110 -105
- data/doc/rdoc/classes/Pho/FieldWeighting.html +12 -12
- data/doc/rdoc/classes/Pho/FileManagement.html +121 -0
- data/doc/rdoc/classes/Pho/FileManagement/AbstractFileManager.html +443 -0
- data/doc/rdoc/classes/Pho/FileManagement/FileManager.html +258 -0
- data/doc/rdoc/classes/Pho/FileManagement/RDFManager.html +271 -0
- data/doc/rdoc/classes/Pho/Job.html +64 -64
- data/doc/rdoc/classes/Pho/Jobs.html +60 -60
- data/doc/rdoc/classes/Pho/QueryProfile.html +60 -60
- data/doc/rdoc/classes/Pho/RDFCollection.html +4 -378
- data/doc/rdoc/classes/Pho/ResourceHash.html +123 -0
- data/doc/rdoc/classes/Pho/ResourceHash/Converter.html +323 -0
- data/doc/rdoc/classes/Pho/{RDF_JSON → ResourceHash}/SetAlgebra.html +18 -18
- data/doc/rdoc/classes/Pho/Snapshot.html +35 -35
- data/doc/rdoc/classes/Pho/Sparql.html +137 -0
- data/doc/rdoc/classes/Pho/Sparql/SparqlClient.html +515 -0
- data/doc/rdoc/classes/Pho/Sparql/SparqlHelper.html +575 -0
- data/doc/rdoc/classes/Pho/Status.html +26 -26
- data/doc/rdoc/classes/Pho/Store.html +271 -241
- data/doc/rdoc/classes/Pho/Update/Changeset.html +73 -73
- data/doc/rdoc/classes/Pho/Update/ChangesetBuilder.html +34 -34
- data/doc/rdoc/classes/Pho/Update/Changesets.html +14 -14
- data/doc/rdoc/classes/Pho/Update/LiteralStatement.html +31 -23
- data/doc/rdoc/classes/Pho/Update/ResourceStatement.html +45 -21
- data/doc/rdoc/classes/Pho/Update/Statement.html +29 -29
- data/doc/rdoc/classes/String.html +1 -1
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/CHANGES.html +49 -3
- data/doc/rdoc/files/README.html +15 -1
- data/doc/rdoc/files/lib/pho/changeset_builder_rb.html +1 -1
- data/doc/rdoc/files/lib/pho/changeset_rb.html +1 -1
- data/doc/rdoc/files/lib/pho/converter_rb.html +108 -0
- data/doc/rdoc/files/lib/pho/enrichment_rb.html +101 -0
- data/doc/rdoc/files/lib/pho/etags_rb.html +1 -1
- data/doc/rdoc/files/lib/pho/field_predicate_map_rb.html +1 -1
- data/doc/rdoc/files/lib/pho/file_management_rb.html +101 -0
- data/doc/rdoc/files/lib/pho/file_manager_rb.html +108 -0
- data/doc/rdoc/files/lib/pho/rdf_collection_rb.html +1 -1
- data/doc/rdoc/files/lib/pho/resource_hash_rb.html +101 -0
- data/doc/rdoc/files/lib/pho/{rdf_json_rb.html → sparql_rb.html} +4 -4
- data/doc/rdoc/files/lib/pho/store_rb.html +1 -1
- data/doc/rdoc/files/lib/pho_rb.html +7 -2
- data/doc/rdoc/fr_class_index.html +12 -2
- data/doc/rdoc/fr_file_index.html +6 -1
- data/doc/rdoc/fr_method_index.html +176 -139
- data/examples/sparql_construct_hash.rb +26 -0
- data/examples/sparql_select.rb +18 -0
- data/lib/pho.rb +6 -1
- data/lib/pho/changeset.rb +24 -9
- data/lib/pho/changeset_builder.rb +10 -10
- data/lib/pho/converter.rb +74 -0
- data/lib/pho/enrichment.rb +81 -0
- data/lib/pho/etags.rb +1 -0
- data/lib/pho/field_predicate_map.rb +6 -1
- data/lib/pho/file_management.rb +102 -0
- data/lib/pho/file_manager.rb +61 -0
- data/lib/pho/rdf_collection.rb +54 -120
- data/lib/pho/{rdf_json.rb → resource_hash.rb} +3 -4
- data/lib/pho/sparql.rb +332 -0
- data/lib/pho/store.rb +20 -14
- data/tests/tc_changeset.rb +46 -0
- data/tests/tc_changeset_builder.rb +122 -1
- data/tests/tc_converter.rb +95 -0
- data/tests/tc_enrichment.rb +83 -0
- data/tests/tc_file_manager.rb +88 -0
- data/tests/tc_rdf_collection.rb +3 -0
- data/tests/{tc_rdf_json.rb → tc_resource_hash.rb} +23 -23
- data/tests/tc_search.rb +1 -1
- data/tests/tc_sparql.rb +131 -6
- data/tests/tc_sparql_helper.rb +214 -0
- data/tests/ts_pho.rb +6 -2
- metadata +47 -8
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Pho
|
|
2
|
+
|
|
3
|
+
require 'mime/types'
|
|
4
|
+
|
|
5
|
+
module FileManagement
|
|
6
|
+
|
|
7
|
+
class FileManager < AbstractFileManager
|
|
8
|
+
|
|
9
|
+
def initialize(store, dir, ok_suffix=OK, fail_suffix=FAIL, sleep=1)
|
|
10
|
+
super(store, dir, ok_suffix, fail_suffix, sleep)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
#List files being managed, i.e. everything not .ok or .fail
|
|
14
|
+
def list()
|
|
15
|
+
files = []
|
|
16
|
+
Dir.glob( File.join(@dir, "*") ) do |file|
|
|
17
|
+
if File.extname(file) != ".#{@ok_suffix}" && File.extname(file) != ".#{@fail_suffix}"
|
|
18
|
+
files << file
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
return files
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#List any new files in the directory
|
|
25
|
+
def new_files()
|
|
26
|
+
newfiles = Array.new
|
|
27
|
+
Dir.glob( File.join(@dir, "*") ) do |file|
|
|
28
|
+
|
|
29
|
+
if File.extname(file) != ".#{@ok_suffix}" && File.extname(file) != ".#{@fail_suffix}"
|
|
30
|
+
ok_file = get_ok_file_for(file)
|
|
31
|
+
fail_file = get_fail_file_for(file)
|
|
32
|
+
if !( File.exists?(ok_file) or File.exists?(fail_file) )
|
|
33
|
+
newfiles << file
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
return newfiles
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def store_file(file, filename)
|
|
46
|
+
response = @store.upload_item(file, MIME::Types.type_for(filename)[0].to_s )
|
|
47
|
+
if (response.status < 300 )
|
|
48
|
+
File.open(get_ok_file_for(filename), "w") do |file|
|
|
49
|
+
file.print( "OK" )
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
File.open(get_fail_file_for(filename), "w") do |file|
|
|
53
|
+
YAML::dump(response, file)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/pho/rdf_collection.rb
CHANGED
|
@@ -1,129 +1,63 @@
|
|
|
1
1
|
module Pho
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
attr_reader :store
|
|
5
|
-
|
|
6
|
-
# Provides a simple mechanism for managing a directory of RDF/XML documents
|
|
7
|
-
# and uploading them to platform store.
|
|
8
|
-
#
|
|
9
|
-
# Allows a collection to be mirrored into the platform
|
|
10
|
-
class RDFCollection
|
|
11
|
-
|
|
12
|
-
RDF = "rdf".freeze
|
|
13
|
-
OK = "ok".freeze
|
|
14
|
-
FAIL = "fail".freeze
|
|
15
|
-
|
|
16
|
-
def initialize(store, dir, rdf_suffix=RDF, ok_suffix=OK, fail_suffix=FAIL, sleep=1)
|
|
17
|
-
@store = store
|
|
18
|
-
@dir = dir
|
|
19
|
-
@sleep = sleep
|
|
20
|
-
@rdf_suffix = rdf_suffix
|
|
21
|
-
@ok_suffix = ok_suffix
|
|
22
|
-
@fail_suffix = fail_suffix
|
|
23
|
-
end
|
|
3
|
+
module FileManagement
|
|
24
4
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
retries = failures()
|
|
37
|
-
retries.each do |filename|
|
|
38
|
-
File.delete( get_fail_file_for(filename) )
|
|
39
|
-
#store it
|
|
40
|
-
file = File.new(filename)
|
|
41
|
-
store_file(file, filename)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
#Reset the directory to clear out any previous statuses
|
|
46
|
-
#Store can also be reset at the same time: use with care!
|
|
47
|
-
def reset(reset_store=false)
|
|
48
|
-
Dir.glob( File.join(@dir, "*.#{@fail_suffix}") ).each do |file|
|
|
49
|
-
File.delete(file)
|
|
50
|
-
end
|
|
51
|
-
Dir.glob( File.join(@dir, "*.#{@ok_suffix}") ).each do |file|
|
|
52
|
-
File.delete(file)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
#List files being managed
|
|
57
|
-
def list()
|
|
58
|
-
return Dir.glob( File.join(@dir, "*.#{@rdf_suffix}") )
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
#List failures
|
|
62
|
-
def failures()
|
|
63
|
-
fails = Array.new
|
|
64
|
-
Dir.glob( File.join(@dir, "*.#{@fail_suffix}") ).each do |file|
|
|
65
|
-
fails << file.gsub(/\.#{@fail_suffix}/, ".#{@rdf_suffix}")
|
|
66
|
-
end
|
|
67
|
-
return fails
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
#List successes
|
|
71
|
-
def successes()
|
|
72
|
-
successes = Array.new
|
|
73
|
-
Dir.glob( File.join(@dir, "*.#{@ok_suffix}") ).each do |file|
|
|
74
|
-
successes << file.gsub(/\.#{@ok_suffix}/, ".#{@rdf_suffix}")
|
|
75
|
-
end
|
|
76
|
-
return successes
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
#List any new files in the directory
|
|
80
|
-
def new_files()
|
|
81
|
-
newfiles = Array.new
|
|
82
|
-
Dir.glob( File.join(@dir, "*.#{@rdf_suffix}") ) do |file|
|
|
83
|
-
ok_file = get_ok_file_for(file)
|
|
84
|
-
fail_file = get_fail_file_for(file)
|
|
85
|
-
if !( File.exists?(ok_file) or File.exists?(fail_file) )
|
|
86
|
-
newfiles << file
|
|
5
|
+
# Provides a simple mechanism for managing a directory of RDF/XML documents
|
|
6
|
+
# and uploading them to platform store.
|
|
7
|
+
#
|
|
8
|
+
# Allows a collection to be mirrored into the platform
|
|
9
|
+
class RDFManager < AbstractFileManager
|
|
10
|
+
|
|
11
|
+
RDF = "rdf".freeze
|
|
12
|
+
|
|
13
|
+
def initialize(store, dir, rdf_suffix=RDF, ok_suffix=OK, fail_suffix=FAIL, sleep=1)
|
|
14
|
+
super(store, dir, ok_suffix, fail_suffix, sleep)
|
|
15
|
+
@rdf_suffix = rdf_suffix
|
|
87
16
|
end
|
|
88
|
-
end
|
|
89
|
-
return newfiles
|
|
90
|
-
end
|
|
91
17
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
successes = successes()
|
|
97
|
-
newfiles = new_files()
|
|
98
|
-
total = failures.size + successes.size + newfiles.size
|
|
99
|
-
summary = "#{@dir} contains #{total} files: #{successes.size} stored, #{failures.size} failed, #{newfiles.size} new"
|
|
100
|
-
return summary
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def get_fail_file_for(filename)
|
|
104
|
-
return filename.gsub(/\.#{@rdf_suffix}/, ".#{@fail_suffix}")
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def get_ok_file_for(filename)
|
|
108
|
-
return filename.gsub(/\.#{@rdf_suffix}/, ".#{@ok_suffix}")
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
private
|
|
112
|
-
|
|
113
|
-
def store_file(file, filename)
|
|
114
|
-
response = @store.store_file(file)
|
|
115
|
-
if (response.status < 300 )
|
|
116
|
-
File.open(get_ok_file_for(filename), "w") do |file|
|
|
117
|
-
file.print( "OK" )
|
|
18
|
+
|
|
19
|
+
#List files being managed
|
|
20
|
+
def list()
|
|
21
|
+
return Dir.glob( File.join(@dir, "*.#{@rdf_suffix}") )
|
|
118
22
|
end
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
23
|
+
|
|
24
|
+
#List any new files in the directory
|
|
25
|
+
def new_files()
|
|
26
|
+
newfiles = Array.new
|
|
27
|
+
Dir.glob( File.join(@dir, "*.#{@rdf_suffix}") ) do |file|
|
|
28
|
+
ok_file = get_ok_file_for(file)
|
|
29
|
+
fail_file = get_fail_file_for(file)
|
|
30
|
+
if !( File.exists?(ok_file) or File.exists?(fail_file) )
|
|
31
|
+
newfiles << file
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
return newfiles
|
|
35
|
+
end
|
|
36
|
+
|
|
126
37
|
|
|
38
|
+
protected
|
|
39
|
+
|
|
40
|
+
def store_file(file, filename)
|
|
41
|
+
response = @store.store_file(file)
|
|
42
|
+
if (response.status < 300 )
|
|
43
|
+
File.open(get_ok_file_for(filename), "w") do |file|
|
|
44
|
+
file.print( "OK" )
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
File.open(get_fail_file_for(filename), "w") do |file|
|
|
48
|
+
YAML::dump(response, file)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
127
56
|
end
|
|
128
|
-
|
|
57
|
+
|
|
58
|
+
#Deprecated. Use Pho::FileManangement::RDFManager instead
|
|
59
|
+
class RDFCollection < Pho::FileManagement::RDFManager
|
|
60
|
+
#for backwards compatibility
|
|
61
|
+
end
|
|
62
|
+
|
|
129
63
|
end
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
module Pho
|
|
2
2
|
|
|
3
|
-
#TODO
|
|
4
|
-
#blank nodes
|
|
3
|
+
#TODO blank nodes
|
|
5
4
|
|
|
6
|
-
#Module providing code for manipulating
|
|
5
|
+
#Module providing code for manipulating resource hashes structured according
|
|
7
6
|
#to the RDF in JSON spec
|
|
8
|
-
module
|
|
7
|
+
module ResourceHash
|
|
9
8
|
|
|
10
9
|
#Class providing set algebra methods over triple hashes
|
|
11
10
|
class SetAlgebra
|
data/lib/pho/sparql.rb
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
module Pho
|
|
2
|
+
|
|
3
|
+
#Module providing a SPARQL client library, support for parsing SPARQL query responses into Ruby objects
|
|
4
|
+
#and other useful behaviour
|
|
5
|
+
module Sparql
|
|
6
|
+
|
|
7
|
+
SPARQL_RESULTS_XML = "application/sparql-results+xml"
|
|
8
|
+
SPARQL_RESULTS_JSON = "application/sparql-results+json"
|
|
9
|
+
|
|
10
|
+
#A simple SPARQL client that handles the basic HTTP traffic
|
|
11
|
+
class SparqlClient
|
|
12
|
+
|
|
13
|
+
#URI of the endpoint
|
|
14
|
+
attr_reader :endpoint
|
|
15
|
+
#HTTPClient object
|
|
16
|
+
attr_reader :client
|
|
17
|
+
#Name of output parameter to use to control response format. If set then this parameter is added
|
|
18
|
+
#to the query string, rather than using Content Negotiation
|
|
19
|
+
attr_accessor :output_parameter_name
|
|
20
|
+
attr_reader :graphs
|
|
21
|
+
attr_reader :named_graphs
|
|
22
|
+
|
|
23
|
+
#Configures whether the remote endpoint supports the RDF-in-JSON specification for serializing
|
|
24
|
+
#RDF graphs as JSON. Will default to false.
|
|
25
|
+
attr_accessor :supports_rdf_json
|
|
26
|
+
#Configures whether the remote endpoint supports SPARQL JSON Results format
|
|
27
|
+
#Will default to true.
|
|
28
|
+
attr_accessor :supports_sparql_json
|
|
29
|
+
|
|
30
|
+
#Initialize a client for a specific endpoint
|
|
31
|
+
#
|
|
32
|
+
# endpoint:: uri of the SPARQL endpoint
|
|
33
|
+
# client:: optionally, a reference to an existing HTTPClient object instance
|
|
34
|
+
def initialize(endpoint, client=HTTPClient.new() )
|
|
35
|
+
@endpoint = endpoint
|
|
36
|
+
@graphs = nil
|
|
37
|
+
@named_graphs = nil
|
|
38
|
+
@client = client
|
|
39
|
+
@output_parameter_name = nil
|
|
40
|
+
@supports_rdf_json = false
|
|
41
|
+
@supports_sparql_json = true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#Add a default graph. This will be added as a default graph in the request protocol
|
|
45
|
+
def add_default_graph(graph_uri)
|
|
46
|
+
if @graphs == nil
|
|
47
|
+
@graphs = []
|
|
48
|
+
end
|
|
49
|
+
@graphs << graph_uri
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#Add a named graph. This will be added as a named graph in the request protocol
|
|
53
|
+
def add_named_graph(graph_uri)
|
|
54
|
+
if @named_graphs == nil
|
|
55
|
+
@named_graphs = []
|
|
56
|
+
end
|
|
57
|
+
@named_graphs << graph_uri
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#Perform a sparql query
|
|
61
|
+
#
|
|
62
|
+
# sparql:: a valid SPARQL query
|
|
63
|
+
# format:: specific a request format. Usually a media-type, but may be a name for a type, if not using Conneg
|
|
64
|
+
# graphs:: an array of default graphs
|
|
65
|
+
# named_graphs:: an array of named graphs
|
|
66
|
+
def query(sparql, format=nil, graphs=nil, named_graphs=nil)
|
|
67
|
+
|
|
68
|
+
params = {}
|
|
69
|
+
params["query"] = sparql
|
|
70
|
+
|
|
71
|
+
if graphs != nil
|
|
72
|
+
params["default-graph-uri"] = graphs
|
|
73
|
+
elsif @graphs != nil
|
|
74
|
+
params["default-graph-uri"] = @graphs
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if named_graphs != nil
|
|
78
|
+
params["named-graph-uri"] = named_graphs
|
|
79
|
+
elsif @named_graphs != nil
|
|
80
|
+
params["named-graph-uri"] = @named_graphs
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
headers = {}
|
|
84
|
+
if format != nil
|
|
85
|
+
|
|
86
|
+
if @output_parameter_name != nil
|
|
87
|
+
params[@output_parameter_name] = format
|
|
88
|
+
else
|
|
89
|
+
headers["Accept"] = format
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
return @client.get( @endpoint, params, headers )
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#Perform a SPARQL DESCRIBE query.
|
|
98
|
+
#
|
|
99
|
+
# query:: the SPARQL query
|
|
100
|
+
# format:: the preferred response format
|
|
101
|
+
def describe(query, format="application/rdf+xml")
|
|
102
|
+
return query(query, format)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#DESCRIBE multiple resources in a single query. The provided array should contain
|
|
106
|
+
#the uris that are to be described
|
|
107
|
+
#
|
|
108
|
+
#This will generate a query like:
|
|
109
|
+
# DESCRIBE <http://www.example.org> <http://www.example.com> ...
|
|
110
|
+
#
|
|
111
|
+
# uris:: list of the uris to be described
|
|
112
|
+
# format:: the preferred response format. Default is RDF/XML
|
|
113
|
+
def multi_describe(uris, format="application/rdf+xml")
|
|
114
|
+
query = "DESCRIBE " + uris.map {|u| "<#{u}>" }.join(" ")
|
|
115
|
+
return query(query, format)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#Perform a SPARQL CONSTRUCT query.
|
|
119
|
+
#
|
|
120
|
+
# query:: the SPARQL query
|
|
121
|
+
# format:: the preferred response format
|
|
122
|
+
def construct(query, format="application/rdf+xml")
|
|
123
|
+
return query(query, format)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#Perform a SPARQL ASK query.
|
|
127
|
+
#
|
|
128
|
+
# query:: the SPARQL query
|
|
129
|
+
# format:: the preferred response format
|
|
130
|
+
def ask(query, format=Pho::Sparql::SPARQL_RESULTS_XML)
|
|
131
|
+
return query(query, format)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#Perform a SPARQL SELECT query.
|
|
135
|
+
#
|
|
136
|
+
# query:: the SPARQL query
|
|
137
|
+
# format:: the preferred response format
|
|
138
|
+
def select(query, format=Pho::Sparql::SPARQL_RESULTS_XML)
|
|
139
|
+
return query(query, format)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#Simple helper class for manipulating and executing SPARQL queries and manipulating the results
|
|
145
|
+
class SparqlHelper
|
|
146
|
+
VARIABLE_MATCHER = /(\?|\$)([a-zA-Z]+)/
|
|
147
|
+
|
|
148
|
+
#Apply some initial bindings to parameters in a query
|
|
149
|
+
#
|
|
150
|
+
#The keys in the values hash are used to replace variables in a query
|
|
151
|
+
#The values are supplied as is, allowing them to be provided as URIs, or typed literals
|
|
152
|
+
#according to Turtle syntax.
|
|
153
|
+
#
|
|
154
|
+
#Any keys in the hash that are not in the query are ignored. Any variables not found
|
|
155
|
+
#in the hash remain unbound.
|
|
156
|
+
#
|
|
157
|
+
# query:: the query whose initial bindings are to be set
|
|
158
|
+
# values:: hash of query name to value
|
|
159
|
+
def SparqlHelper.apply_initial_bindings(query, bindings={})
|
|
160
|
+
copy = query.clone()
|
|
161
|
+
copy.gsub!(VARIABLE_MATCHER) do |pattern|
|
|
162
|
+
key = $2
|
|
163
|
+
if bindings.has_key?(key)
|
|
164
|
+
bindings[key].to_s
|
|
165
|
+
else
|
|
166
|
+
pattern
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
return copy
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
#Convert a SPARQL query result binding into a hash suitable for passing
|
|
173
|
+
#to the apply_initial_bindings method.
|
|
174
|
+
#
|
|
175
|
+
#The result param is assumed to be a Ruby hash that reflects the structure of
|
|
176
|
+
#a binding in a SELECT query result (i.e. the result of parsing the <tt>application/sparql-results+json</tt>
|
|
177
|
+
#format and extracting an specific result binding.
|
|
178
|
+
#
|
|
179
|
+
#The method is intended to be used to support cases where an initial select query is
|
|
180
|
+
#performed to extract some variables that can later be plugged into a subsequent
|
|
181
|
+
#query
|
|
182
|
+
#
|
|
183
|
+
# result:: hash conforming to structure of a <tt>binding</tt> in the SPARQL JSON format
|
|
184
|
+
def SparqlHelper.result_to_query_binding(result)
|
|
185
|
+
hash = {}
|
|
186
|
+
result.each_pair do |key, value|
|
|
187
|
+
if value["type"] == "uri"
|
|
188
|
+
hash[key] = "<#{value["value"]}>"
|
|
189
|
+
elsif (value["type"] == "literal" && !value.has_key?("datatype"))
|
|
190
|
+
hash[key] = "\"#{value["value"]}\""
|
|
191
|
+
elsif (value["type"] == "literal" && value.has_key?("datatype"))
|
|
192
|
+
hash[key] = "\"#{value["value"]}\"^^#{value["datatype"]}"
|
|
193
|
+
else
|
|
194
|
+
#do nothing for bnodes
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
return hash
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#Convert Ruby hash structured according to SPARQL JSON format
|
|
201
|
+
#into an array of hashes by calling result_to_query_binding on each binding
|
|
202
|
+
#into the results.
|
|
203
|
+
#
|
|
204
|
+
#E.g:
|
|
205
|
+
# <tt>results = Pho::Sparql::SparqlHelper.select(query, sparql_client)</tt>
|
|
206
|
+
# <tt>bindings = Pho::Sparql::SparqlHelper.results_to_query_bindings(results)</tt>
|
|
207
|
+
#
|
|
208
|
+
# results:: hash conforming to SPARQL SELECT structure
|
|
209
|
+
def SparqlHelper.results_to_query_bindings(results)
|
|
210
|
+
bindings = []
|
|
211
|
+
|
|
212
|
+
results["results"]["bindings"].each do |result|
|
|
213
|
+
bindings << result_to_query_binding(result)
|
|
214
|
+
end
|
|
215
|
+
return bindings
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
#Perform a simple SELECT query on an endpoint.
|
|
219
|
+
#Will request the results using the SPARQL JSON results format, and parse the
|
|
220
|
+
#resulting JSON results. The result will therefore be a simple ruby hash of the results
|
|
221
|
+
#
|
|
222
|
+
#An error will be raised if the response is HTTP OK.
|
|
223
|
+
#
|
|
224
|
+
# query:: the SPARQL SELECT query
|
|
225
|
+
# sparql_client:: a configured SparqlClient object
|
|
226
|
+
def SparqlHelper.select(query, sparql_client)
|
|
227
|
+
#TODO: test whether endpoint supports json, and if not, switch to parsing XML
|
|
228
|
+
resp = sparql_client.select(query, Pho::Sparql::SPARQL_RESULTS_JSON)
|
|
229
|
+
if resp.status != 200
|
|
230
|
+
raise "Error performing sparql query: #{resp.status} #{resp.reason}\n#{resp.content}"
|
|
231
|
+
end
|
|
232
|
+
return JSON.parse( resp.content )
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
#Performs an ASK query on an endpoint, returing a boolean true/false response
|
|
236
|
+
#
|
|
237
|
+
#Will request the results using the SPARQL JSON results format, parse the
|
|
238
|
+
#resulting JSON results, and extract the true/false response.
|
|
239
|
+
#
|
|
240
|
+
# query:: the SPARQL SELECT query
|
|
241
|
+
# sparql_client:: a configured SparqlClient object
|
|
242
|
+
def SparqlHelper.ask(query, sparql_client)
|
|
243
|
+
json = SparqlHelper.select(query, sparql_client)
|
|
244
|
+
return json["boolean"] == "true"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
#Perform a simple SELECT query on an endpoint and return a simple array of values
|
|
248
|
+
#
|
|
249
|
+
#Will request the results using the SPARQL JSON results format, and parse the
|
|
250
|
+
#resulting JSON results. The assumption is that the SELECT query contains a single "column"
|
|
251
|
+
#of values, which will be returned as an array
|
|
252
|
+
#
|
|
253
|
+
#Note this will lose any type information, only the value of the bindings are returned
|
|
254
|
+
#
|
|
255
|
+
# query:: the SPARQL SELECT query
|
|
256
|
+
# sparql_client:: a configured SparqlClient object
|
|
257
|
+
def SparqlHelper.select_values(query, sparql_client)
|
|
258
|
+
results = SparqlHelper.select(query, sparql_client)
|
|
259
|
+
v = results["head"]["vars"][0];
|
|
260
|
+
values = [];
|
|
261
|
+
results["results"]["bindings"].each do |binding|
|
|
262
|
+
values << binding[v]["value"]
|
|
263
|
+
end
|
|
264
|
+
return values
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
#Perform a simple SELECT query on an endpoint and return a single result
|
|
268
|
+
#
|
|
269
|
+
#Will request the results using the SPARQL JSON results format, and parse the
|
|
270
|
+
#resulting JSON results. The assumption is that the SELECT query returns a single
|
|
271
|
+
#value (i.e single variable, with single binding)
|
|
272
|
+
#
|
|
273
|
+
#Note this will lose any type information, only the value of the binding is returned
|
|
274
|
+
#
|
|
275
|
+
# query:: the SPARQL SELECT query
|
|
276
|
+
# sparql_client:: a configured SparqlClient object
|
|
277
|
+
def SparqlHelper.select_single_value(query, sparql_client)
|
|
278
|
+
results = SparqlHelper.select(query, sparql_client)
|
|
279
|
+
v = results["head"]["vars"][0];
|
|
280
|
+
return results["results"]["bindings"][0][v]["value"]
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
#Perform a SPARQL CONSTRUCT query against an endpoint, requesting the results in JSON
|
|
284
|
+
#
|
|
285
|
+
#Will request the results as application/json (with the expectation that it returns RDF_JSON),
|
|
286
|
+
#and parses the resulting JSON document.
|
|
287
|
+
#
|
|
288
|
+
# query:: the SPARQL SELECT query
|
|
289
|
+
# sparql_client:: a configured SparqlClient object
|
|
290
|
+
def SparqlHelper.construct_to_resource_hash(query, sparql_client)
|
|
291
|
+
#TODO: test whether endpoint supports json, and if not, switch to parsing XML
|
|
292
|
+
resp = sparql_client.construct(query, "application/json")
|
|
293
|
+
if resp.status != 200
|
|
294
|
+
raise "Error performing sparql query: #{resp.status} #{resp.reason}\n#{resp.content}"
|
|
295
|
+
end
|
|
296
|
+
return Pho::ResourceHash::Converter.parse_json( resp.content )
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
#Perform a SPARQL DESCRIBE query against an endpoint, requesting the results in JSON
|
|
300
|
+
#
|
|
301
|
+
#Will request the results as application/json (with the expectation that it returns RDF_JSON),
|
|
302
|
+
#and parses the resulting JSON document.
|
|
303
|
+
#
|
|
304
|
+
# query:: the SPARQL SELECT query
|
|
305
|
+
# sparql_client:: a configured SparqlClient object
|
|
306
|
+
def SparqlHelper.describe_to_resource_hash(query, sparql_client)
|
|
307
|
+
#TODO: test whether endpoint supports json, and if not, switch to parsing XML
|
|
308
|
+
resp = sparql_client.describe(query, "application/json")
|
|
309
|
+
if resp.status != 200
|
|
310
|
+
raise "Error performing sparql query: #{resp.status} #{resp.reason}\n#{resp.content}"
|
|
311
|
+
end
|
|
312
|
+
return Pho::ResourceHash::Converter.parse_json( resp.content )
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
#DESCRIBE multiple resources in a single SPARQL request
|
|
316
|
+
#
|
|
317
|
+
# uris:: an array of URIs
|
|
318
|
+
# sparql_client:: a configured SparqlClient objec
|
|
319
|
+
def SparqlHelper.multi_describe(uris, sparql_client)
|
|
320
|
+
#TODO: test whether endpoint supports json, and if not, switch to parsing XML
|
|
321
|
+
resp = sparql_client.multi_describe(uris, "application/json")
|
|
322
|
+
if resp.status != 200
|
|
323
|
+
raise "Error performing sparql query: #{resp.status} #{resp.reason}\n#{resp.content}"
|
|
324
|
+
end
|
|
325
|
+
return Pho::ResourceHash::Converter.parse_json( resp.content )
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
end
|