sparql 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,20 +1,67 @@
1
1
  # SPARQL for RDF.rb
2
2
 
3
- This is a [Ruby][] implementation of the [SPARQL][] algebra for [RDF.rb][].
3
+ This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][].
4
4
 
5
5
  ## Features
6
6
 
7
7
  * 100% free and unencumbered [public domain](http://unlicense.org/) software.
8
8
  * [SPARQL 1.0][] query parsing and execution
9
- * SPARQL results as [XML][SPARQL XML] or [JSON][SPARQL JSON].
9
+ * SPARQL results as [XML][SPARQL XML], [JSON][SPARQL JSON] or HTML.
10
10
  * SPARQL CONSTRUCT or DESCRIBE serialized based on Format, Extension of Mime Type
11
- using available RDF Writers (see [Linked Data](http://rubygems.org/gems/linkeddata))
11
+ using available RDF Writers (see [Linked Data][])
12
12
  * SPARQL Client for accessing remote SPARQL endpoints.
13
- * Helper method for describing [SPARQL Service Description][]
14
- * Compatible with Ruby Ruby 1.9.x.
13
+ * [Rack][] and [Sinatra][] middleware to perform [HTTP content negotiation][conneg] for result formats
14
+ * Compatible with any [Rack][] or [Sinatra][] application and any Rack-based framework.
15
+ * Helper method for describing [SPARQL Service Description][SSD]
16
+ * Compatible with Ruby 1.9.x.
15
17
  * Compatible with older Ruby versions with the help of the [Backports][] gem.
16
18
  * Supports Unicode query strings both on Ruby 1.8.x and 1.9.x.
17
19
 
20
+ ## Description
21
+
22
+ The {SPARQL} gem implements [SPARQL 1.0 Query] and provides [Rack][] and [Sinatra][]
23
+ middleware to provide results using [HTTP Content Negotiation][conneg].
24
+
25
+ * {SPARQL::Grammar} implements a [SPARQL 1.0 Query] parser generating [SPARQL S-Expressions (SSE)][SSE].
26
+ * {SPARQL::Algebra} executes SSE against Any `RDF::Graph` or `RDF::Repository`, including
27
+ compliant [RDF.rb][] repository adaptors such as [RDF::DO][] and [RDF::Mongo][].
28
+ * {Rack::SPARQL} and {Sinatra::SPARQL} provide middleware components to format results
29
+ using an appropriate format based on [HTTP content negotiation][conneg].
30
+
31
+ ### Middleware
32
+
33
+ `Rack::SPARQL` is a superset of [Rack::LinkedData][] to allow content negotiated results
34
+ to be returned any `RDF::Enumerable` or `RDF::Query::Solutions` compatible results.
35
+ You would typically return an instance of `RDF::Graph`, `RDF::Repository` or `RDF::Query::Solutions`
36
+ from your Rack application, and let the `Rack::SPARQL::ContentNegotiation` middleware
37
+ take care of serializing your response into whatever format the HTTP
38
+ client requested and understands.
39
+
40
+ `Sinatra::SPARQL` is a thin Sinatra-specific wrapper around the
41
+ {Rack::SPARQL} middleware, which implements SPARQL
42
+ content negotiation for Rack applications.
43
+
44
+ The middleware queries [RDF.rb][] for the MIME content types of known RDF
45
+ serialization formats, so it will work with whatever serialization plugins
46
+ that are currently available for RDF.rb. (At present, this includes support
47
+ for N-Triples, N-Quads, Turtle, RDF/XML, RDF/JSON, JSON-LD, RDFa, TriG and TriX.)
48
+
49
+ ### Remote datasets
50
+
51
+ A SPARQL query containing `FROM` or `FROM NAMED` will load the referenced IRI unless the repository
52
+ already contains a context with that same IRI. This is performed using [RDF.rb][] `RDF::Util::File.open_file`
53
+ passing HTTP Accept headers for various available RDF formats. For best results, require [Linked Data][] to enable
54
+ a full set of RDF formats in the `GET` request. Also, consider overriding `RDF::Util::File.open_file` with
55
+ an implementation with support for HTTP Get headers (such as `Net::HTTP`).
56
+
57
+ ### Result formats
58
+
59
+ {SPARQL.serialize_results} may be used on it's own, or in conjunction with {Rack::SPARQL} or {Sinatra::SPARQL}
60
+ to provide content-negotiated query results. For basic `SELECT` and `ASK` this includes HTML, XML and JSON formats.
61
+ `DESCRIBE` and `CONSTRUCT` create an `RDF::Graph`, which can be serialized through [HTTP Content Netogiation][conneg]
62
+ using available RDF writers. For best results, require [Linked Data][] to enable
63
+ a full set of RDF formats.
64
+
18
65
  ## Examples
19
66
 
20
67
  require 'rubygems'
@@ -49,6 +96,42 @@ This is a [Ruby][] implementation of the [SPARQL][] algebra for [RDF.rb][].
49
96
  sparql --default-graph etc/doap.ttl --sse etc/input.sse
50
97
  sparql --sse -e "(dataset (<etc/doap.ttl>) (bgp (triple ?s ?p ?o))))"
51
98
 
99
+ ### Adding SPARQL content negotiation to a Rails 3.x application
100
+
101
+ # config/application.rb
102
+ require 'rack/sparql'
103
+
104
+ class Application < Rails::Application
105
+ config.middleware.use Rack::SPARQL::ContentNegotiation
106
+ end
107
+
108
+ ### Adding SPARQL content negotiation to a Rackup application
109
+
110
+ #!/usr/bin/env rackup
111
+ require 'rack/sparql'
112
+
113
+ repository = RDF::Repository.new do |graph|
114
+ graph << [RDF::Node.new, RDF::DC.title, "Hello, world!"]
115
+ end
116
+ results = SPARQL.execute("SELECT * WHERE { ?s ?p ?o }", repository)
117
+
118
+ use Rack::SPARQL::ContentNegotiation
119
+ run lambda { |env| [200, {}, results] }
120
+
121
+ ### Adding SPARQL content negotiation to a classic Sinatra application
122
+
123
+ #!/usr/bin/env ruby -rubygems
124
+ require 'sinatra'
125
+ require 'sinatra/sparql'
126
+
127
+ repository = RDF::Repository.new do |graph|
128
+ graph << [RDF::Node.new, RDF::DC.title, "Hello, world!"]
129
+ end
130
+
131
+ get '/sparql' do
132
+ SPARQL.execute("SELECT * WHERE { ?s ?p ?o }", repository)
133
+ end
134
+
52
135
  ## Documentation
53
136
 
54
137
  Full documentation available on [Rubydoc.info][SPARQL doc]
@@ -63,15 +146,19 @@ Full documentation available on [Rubydoc.info][SPARQL doc]
63
146
  * {SPARQL::Grammar}
64
147
  * {SPARQL::Grammar::Parser}
65
148
  * {SPARQL::Grammar::Lexer}
149
+ * {Sinatra::SPARQL}
150
+ * {Rack::SPARQL}
151
+ * {Rack::SPARQL::ContentNegotiation}
66
152
 
67
153
  ## Dependencies
68
154
 
69
155
  * [Ruby](http://ruby-lang.org/) (>= 1.9) or (>= 1.8.1 with [Backports][])
70
- * [RDF.rb](http://rubygems.org/gems/rdf) (>= 0.3.4)
156
+ * [RDF.rb](http://rubygems.org/gems/rdf) (>= 0.3.5)
71
157
  * [SPARQL::Client](https://rubygems.org/gems/sparql-client) (>= 0.0.11)
72
158
  * [SXP](https://rubygems.org/gems/sxp) (>= 0.0.15)
73
159
  * [Builder](https://rubygems.org/gems/builder) (>= 3.0.0)
74
160
  * [JSON](https://rubygems.org/gems/json) (>= 1.5.1)
161
+ * Soft dependency on [Linked Data][]
75
162
 
76
163
  ## Installation
77
164
 
@@ -117,6 +204,9 @@ see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
117
204
 
118
205
  [Ruby]: http://ruby-lang.org/
119
206
  [RDF]: http://www.w3.org/RDF/
207
+ [RDF::DO]: http://rubygems.org/gems/rdf-do
208
+ [RDF::Mongo]: http://rubygems.org/gems/rdf-mongo
209
+ [Rack::LinkedData]: http://rubygems.org/gems/rack-linkeddata
120
210
  [YARD]: http://yardoc.org/
121
211
  [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
122
212
  [PDD]: http://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html
@@ -131,8 +221,12 @@ see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
131
221
  [YARD-GS]: http://rubydoc.info/docs/yard/file/docs/GettingStarted.md
132
222
  [PDD]: http://unlicense.org/#unlicensing-contributions
133
223
  [Backports]: http://rubygems.org/gems/backports
224
+ [Linked Data]: http://rubygems.org/gems/linkeddata
134
225
  [SPARQL doc]: http://rubydoc.info/github/gkellogg/sparql/frames
135
226
  [SPARQL XML]: http://www.w3.org/TR/rdf-sparql-XMLres/
136
227
  [SPARQL JSON]: http://www.w3.org/TR/rdf-sparql-json-res/
137
228
  [SPARQL Protocol]: http://www.w3.org/TR/rdf-sparql-protocol/
138
- [SPARQL Service]: http://www.w3.org/TR/sparql11-service-description/
229
+ [SSD]: http://www.w3.org/TR/sparql11-service-description/
230
+ [Rack]: http://rack.rubyforge.org/
231
+ [Sinatra]: http://www.sinatrarb.com/
232
+ [conneg]: http://en.wikipedia.org/wiki/Content_negotiation
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
data/bin/sparql CHANGED
@@ -2,7 +2,11 @@
2
2
  require 'rubygems'
3
3
  $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib')))
4
4
  require 'sparql'
5
- require 'linkeddata'
5
+ begin
6
+ require 'linkeddata'
7
+ rescue LoadError => e
8
+ require 'rdf/ntriples'
9
+ end
6
10
  require 'getoptlong'
7
11
 
8
12
  def run(input, options = {})
@@ -22,12 +26,13 @@ def run(input, options = {})
22
26
  end
23
27
 
24
28
  if options[:to_sse]
25
- puts ("\nSSE:\n" + query.to_sxp)
29
+ puts ("\nSSE:\n" + query.to_sse)
26
30
  else
27
31
  res = query.execute(options[:graph])
28
32
  puts res.inspect if options[:verbose]
29
33
  puts case res
30
34
  when RDF::Graph then res.dump(:ttl, :base_uri => query.base_uri, :prefixes => query.prefixes)
35
+ when RDF::Literal then res.inspect
31
36
  else res.map {|s| s.bindings.map {|k,v| "#{k}: #{v}"}}.join("\n")
32
37
  end
33
38
  end
@@ -0,0 +1,38 @@
1
+ require 'rack'
2
+ begin
3
+ require 'linkeddata'
4
+ rescue LoadError => e
5
+ require 'rdf/ntriples'
6
+ end
7
+ require 'sparql'
8
+
9
+ module Rack
10
+ module SPARQL
11
+ autoload :ContentNegotiation, 'rack/sparql/conneg'
12
+
13
+ ##
14
+ # Registers all known RDF formats with Rack's MIME types registry.
15
+ #
16
+ # Registers both known file extensions and format symbols.
17
+ #
18
+ # @param [Hash{Symbol => Object}] options
19
+ # @option options [Boolean] :overwrite (false)
20
+ # @return [void]
21
+ def self.register_mime_types!(options = {})
22
+ if defined?(Rack::Mime::MIME_TYPES)
23
+ RDF::Format.each do |format|
24
+ if !Rack::Mime::MIME_TYPES.has_key?(file_ext = ".#{format.to_sym}") || options[:overwrite]
25
+ Rack::Mime::MIME_TYPES.merge!(file_ext => format.content_type.first)
26
+ end
27
+ end
28
+ RDF::Format.file_extensions.each do |file_ext, formats|
29
+ if !Rack::Mime::MIME_TYPES.has_key?(file_ext = ".#{file_ext}") || options[:overwrite]
30
+ Rack::Mime::MIME_TYPES.merge!(file_ext => formats.first.content_type.first)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ Rack::SPARQL.register_mime_types!
@@ -0,0 +1,127 @@
1
+ require 'rack'
2
+ require 'sparql'
3
+
4
+ module Rack; module SPARQL
5
+ ##
6
+ # Rack middleware for SPARQL content negotiation.
7
+ #
8
+ # Uses HTTP Content Negotiation to find an appropriate RDF
9
+ # format to serialize any result with a body being `RDF::Enumerable`.
10
+ #
11
+ # Override content negotiation by setting the :format option to
12
+ # {#initialize}.
13
+ #
14
+ # This endpoint also serves the fuction of Rack::LinkedData, as it will serialize
15
+ # SPARQL results, which may be RDF Graphs
16
+ class ContentNegotiation
17
+ VARY = {'Vary' => 'Accept'}.freeze
18
+
19
+ # @return [#call]
20
+ attr_reader :app
21
+
22
+ # @return [Hash{Symbol => Object}]
23
+ attr_reader :options
24
+
25
+ ##
26
+ # @param [#call] app
27
+ # @param [Hash{Symbol => Object}] options
28
+ # Other options passed to writer.
29
+ # @option options [RDF::Format, #to_sym] :format Specific RDF writer format to use
30
+ def initialize(app, options = {})
31
+ @app, @options = app, options
32
+ end
33
+
34
+ ##
35
+ # Handles a Rack protocol request.
36
+ #
37
+ # If result is `RDF::Literal::Boolean`, `RDF::Query::Results`, or `RDF::Enumerable`
38
+ # The result is serialized using {SPARQL::Results}
39
+ #
40
+ # @param [Hash{String => String}] env
41
+ # @return [Array(Integer, Hash, #each)]
42
+ # @see http://rack.rubyforge.org/doc/SPEC.html
43
+ def call(env)
44
+ response = app.call(env)
45
+ case response[2] # the body
46
+ when RDF::Enumerable, RDF::Query::Solutions, RDF::Literal::Boolean
47
+ serialize(env, *response)
48
+ else response
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Serializes a SPARQL query result into a Rack protocol
54
+ # response using HTTP content negotiation rules or a specified Content-Type.
55
+ #
56
+ # @param [Hash{String => String}] env
57
+ # @param [Integer] status
58
+ # @param [Hash{String => Object}] headers
59
+ # @param [RDF::Enumerable] body
60
+ # @return [Array(Integer, Hash, #each)]
61
+ # @raise [RDF::WriterError] when no results are generated
62
+ def serialize(env, status, headers, body)
63
+ begin
64
+ serialize_options = {}
65
+ serialize_options[:content_types] = parse_accept_header(env['HTTP_ACCEPT']) if env.has_key?('HTTP_ACCEPT')
66
+ serialize_options.merge!(@options)
67
+ results = ::SPARQL.serialize_results(body, serialize_options)
68
+ raise RDF::WriterError, "can't serialize results" unless results
69
+ headers = headers.merge(VARY).merge('Content-Type' => results.content_type) # FIXME: don't overwrite existing Vary headers
70
+ [status, headers, [results]]
71
+ rescue RDF::WriterError => e
72
+ not_acceptable(e.message)
73
+ end
74
+ end
75
+
76
+ protected
77
+
78
+ ##
79
+ # Parses an HTTP `Accept` header, returning an array of MIME content
80
+ # types ordered by the precedence rules defined in HTTP/1.1 Section 14.1.
81
+ #
82
+ # @param [String, #to_s] header
83
+ # @return [Array<String>]
84
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
85
+ def parse_accept_header(header)
86
+ entries = header.to_s.split(',')
87
+ entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
88
+ end
89
+
90
+ def accept_entry(entry)
91
+ type, *options = entry.delete(' ').split(';')
92
+ quality = 0 # we sort smallest first
93
+ options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
94
+ [type, [quality, type.count('*'), 1 - options.size]]
95
+ end
96
+
97
+ ##
98
+ # Outputs an HTTP `406 Not Acceptable` response.
99
+ #
100
+ # @param [String, #to_s] message
101
+ # @return [Array(Integer, Hash, #each)]
102
+ def not_acceptable(message = nil)
103
+ http_error(406, message, VARY)
104
+ end
105
+
106
+ ##
107
+ # Outputs an HTTP `4xx` or `5xx` response.
108
+ #
109
+ # @param [Integer, #to_i] code
110
+ # @param [String, #to_s] message
111
+ # @param [Hash{String => String}] headers
112
+ # @return [Array(Integer, Hash, #each)]
113
+ def http_error(code, message = nil, headers = {})
114
+ message = http_status(code) + (message.nil? ? "\n" : " (#{message})\n")
115
+ [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers), [message]]
116
+ end
117
+
118
+ ##
119
+ # Returns the standard HTTP status message for the given status `code`.
120
+ #
121
+ # @param [Integer, #to_i] code
122
+ # @return [String]
123
+ def http_status(code)
124
+ [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ')
125
+ end
126
+ end # class ContentNegotiation
127
+ end; end # module Rack::SPARQL
@@ -0,0 +1,116 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/sparql/extensions'
3
+ require 'rack/sparql'
4
+
5
+ module Sinatra
6
+ ##
7
+ # The Sinatra::SPARQL module adds Rack::SPARQL} middleware support for responding to
8
+ # SPARQL requests. It is the responsibility of the application to manage
9
+ # the SPARQL endpoint and perform the query. The query results are then sent
10
+ # as the response body. {Rack::SPARQL} middleware uses content negotiation
11
+ # to format the results appropriately.
12
+ # @see http://www.sinatrarb.com/extensions.html
13
+ module SPARQL
14
+ ##
15
+ # Helper methods.
16
+ module Helpers
17
+ ##
18
+ # This is useful when a GET request is performed against a SPARQL endpoint and
19
+ # no query is performed. Provide a set of datasets, including a default dataset
20
+ # along with optional triple count, dump location, and description of the dataset.
21
+ #
22
+ # The results are serialized using content negotiation. For text/html, authors
23
+ # should generate RDFa for the serivce description directly.
24
+ #
25
+ # @param [Hash{Symbol => Object}] options
26
+ # @option options [RDF::Enumerable] :repository
27
+ # An enumerable, typically a type of `RDF::Repository` containing the dataset used for
28
+ # queries against the service.
29
+ # @option options [RDF::URI, #to_s] :endpoint
30
+ # URI of the service endpoint, defaults to "/sparql" in the current realm.
31
+ # @return [RDF::Graph]
32
+ #
33
+ # @see http://www.w3.org/TR/sparql11-service-description
34
+ # @see http://www.w3.org/TR/void/
35
+ def service_description(options = {})
36
+ repository = options[:repository]
37
+
38
+ g = RDF::Graph.new
39
+ sd = RDF::URI("http://www.w3.org/ns/sparql-service-description#")
40
+ void = RDF::URI("http://rdfs.org/ns/void#")
41
+
42
+ node = RDF::Node.new
43
+ g << [node, RDF.type, sd.join("#Service")]
44
+ g << [node, sd.join("#endpoint"), options[:endpoint] || url("/sparql")]
45
+ g << [node, sd.join("#supportedLanguage"), sd.join("#SPARQL10Query")]
46
+
47
+ # Result formats, both RDF and SPARQL Results.
48
+ # FIXME: We should get this from the avaliable serializers
49
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/RDF_XML")]
50
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/Turtle")]
51
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/RDFa")]
52
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/N-Triples")]
53
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_RESULTS_XML")]
54
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_RESULTS_JSON")]
55
+
56
+ # Features
57
+ g << [node, sd.join("#feature"), sd.join("#DereferencesURIs")]
58
+
59
+ # Datasets
60
+ ds = RDF::Node.new
61
+ g << [node, sd.join("#defaultDataset"), ds]
62
+ g << [ds, RDF.type, sd.join("#Dataset")]
63
+
64
+ # Contexts
65
+ if repository.is_a?(RDF::Enumerable)
66
+ contexts = {}
67
+ repository.each do |statement|
68
+ contexts[statement.context] ||= 0
69
+ contexts[statement.context] += 1
70
+ end
71
+
72
+ contexts.each do |name, count|
73
+ bn = RDF::Node.new
74
+ if name
75
+ # Add named contexts as namedGraphs
76
+ g << [ds, sd.join("#namedGraph"), bn]
77
+ g << [bn, RDF.type, sd.join("#NamedGraph")]
78
+ g << [bn, sd.join("#name"), name]
79
+ graph = RDF::Node.new
80
+ g << [bn, sd.join("#graph"), graph]
81
+ bn = graph
82
+ else
83
+ # Default graph
84
+ g << [ds, sd.join("#defaultGraph"), bn]
85
+ g << [bn, RDF.type, sd.join("#Graph")]
86
+ end
87
+ g << [bn, void.join("#triples"), count]
88
+ end
89
+ end
90
+ g
91
+ end
92
+ end
93
+
94
+ ##
95
+ # * Registers Rack::SPARQL::ContentNegotiation
96
+ # * adds helpers
97
+ # * includes SPARQL, RDF and LinkedData
98
+ # * defines `sparql_options`, which are passed to the Rack middleware
99
+ # available as `settings.sparql_options` and as options within
100
+ # the {Rack::SPARQL} middleware.
101
+ #
102
+ # @param [Sinatra::Base] app
103
+ # @return [void]
104
+ def self.registered(app)
105
+ options = {}
106
+ app.set :sparql_options, options
107
+ app.use(Rack::SPARQL::ContentNegotiation, options)
108
+ app.helpers(Sinatra::SPARQL::Helpers)
109
+ app.send(:include, ::SPARQL)
110
+ app.send(:include, ::RDF)
111
+ app.send(:include, ::LinkedData)
112
+ end
113
+ end
114
+ end
115
+
116
+ Sinatra.register(Sinatra::SPARQL)
@@ -0,0 +1,19 @@
1
+ # Patch Sinatra::Response#finish to not calculate Content-Length unless
2
+ # all members of an array are strings
3
+ class Sinatra::Response
4
+ def finish
5
+ if status.to_i / 100 == 1
6
+ headers.delete "Content-Length"
7
+ headers.delete "Content-Type"
8
+ elsif RDF::Query::Solutions === body
9
+ # Don't calculate content-length here
10
+ elsif Array === body and not [204, 304].include?(status.to_i)
11
+ headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
12
+ end
13
+
14
+ # Rack::Response#finish sometimes returns self as response body. We don't want that.
15
+ status, headers, result = super
16
+ result = body if result == self
17
+ [status, headers, result]
18
+ end
19
+ end
data/lib/sparql.rb CHANGED
@@ -44,12 +44,11 @@ module SPARQL
44
44
  # presumed to be existant in `queryable` or are loaded into `queryable` depending
45
45
  # on the presense and value of the :load_datasets option.
46
46
  #
47
- # Attempting to load into an immutable `queryable` will result in a RDF::TypeError.
47
+ # Attempting to load into an immutable `queryable` will result in a TypeError.
48
48
  #
49
49
  # @example
50
50
  # repository = RDF::Repository.new
51
51
  # results = SPARQL.execute("SELECT * WHERE { ?s ?p ?o }", repository)
52
- # result = parser.parse
53
52
  #
54
53
  # @param [IO, StringIO, String, #to_s] query
55
54
  # @param [Hash{Symbol => Object}] options
@@ -80,7 +79,7 @@ module SPARQL
80
79
  solutions = query.execute(queryable)
81
80
  rescue SPARQL::Grammar::Parser::Error => e
82
81
  raise MalformedQuery, e.message
83
- rescue RDF::TypeError => e
82
+ rescue TypeError => e
84
83
  raise QueryRequestRefused, e.message
85
84
  end
86
85
 
@@ -189,7 +189,7 @@ module SPARQL
189
189
  # Some very simple optimizations are currently implemented for `FILTER`
190
190
  # expressions. Use the following to obtain optimized SSE forms:
191
191
  #
192
- # Expression.parse(sse).optimize.to_sse
192
+ # Expression.parse(sse).optimize.to_sxp_bin
193
193
  #
194
194
  # ## Constant comparison folding
195
195
  #
@@ -239,7 +239,7 @@ module SPARQL; module Algebra
239
239
  #
240
240
  # @return [Array] `self`
241
241
  # @see http://openjena.org/wiki/SSE
242
- def to_sse
242
+ def to_sxp_bin
243
243
  self
244
244
  end
245
245
 
@@ -274,7 +274,12 @@ module SPARQL; module Algebra
274
274
  message = args.join(": ")
275
275
  message = message + yield if block_given?
276
276
  depth = options[:depth] || 0
277
- $stderr.puts("#{' ' * depth}#{message}") if options[:debug]
277
+ case options[:debug]
278
+ when Array
279
+ options[:debug] << "#{' ' * depth}#{message}"
280
+ else
281
+ $stderr.puts("#{' ' * depth}#{message}")
282
+ end
278
283
  end
279
284
 
280
285
  def debug(*args, &block)
@@ -4,12 +4,18 @@ require 'json'
4
4
  # Extensions for Ruby's `Object` class.
5
5
  class Object
6
6
  ##
7
- # Returns the SXP representation of this object, defaults to `self'.
7
+ # Returns the SXP binary representation of this object, defaults to `self'.
8
8
  #
9
9
  # @return [String]
10
- def to_sse
10
+ def to_sxp_bin
11
11
  self
12
12
  end
13
+
14
+ ##
15
+ # Make sure the object is in SXP form and transform it to a string form
16
+ def to_sse
17
+ self.to_sxp_bin.to_sxp
18
+ end
13
19
  end
14
20
 
15
21
  ##
@@ -19,8 +25,8 @@ class Array
19
25
  # Returns the SXP representation of this object, defaults to `self'.
20
26
  #
21
27
  # @return [String]
22
- def to_sse
23
- map {|x| x.to_sse}
28
+ def to_sxp_bin
29
+ map {|x| x.to_sxp_bin}
24
30
  end
25
31
 
26
32
  ##
@@ -101,8 +107,8 @@ class RDF::Query
101
107
  # If Query is named, it's treated as a GroupGraphPattern, otherwise, a BGP
102
108
  #
103
109
  # @return [Array]
104
- def to_sse
105
- res = [:bgp] + patterns.map(&:to_sse)
110
+ def to_sxp_bin
111
+ res = [:bgp] + patterns.map(&:to_sxp_bin)
106
112
  (context ? [:graph, context, res] : res)
107
113
  end
108
114
  end
@@ -110,7 +116,7 @@ end
110
116
  class RDF::Query::Pattern
111
117
  # Transform Query Pattern into an SXP
112
118
  # @return [Array]
113
- def to_sse
119
+ def to_sxp_bin
114
120
  [:triple, subject, predicate, object]
115
121
  end
116
122
  end
@@ -323,9 +323,9 @@ module SPARQL; module Algebra
323
323
  #
324
324
  # @return [Array]
325
325
  # @see http://openjena.org/wiki/SSE
326
- def to_sse
326
+ def to_sxp_bin
327
327
  operator = [self.class.const_get(:NAME)].flatten.first
328
- [operator, *(operands || []).map(&:to_sse)]
328
+ [operator, *(operands || []).map(&:to_sxp_bin)]
329
329
  end
330
330
 
331
331
  ##
@@ -340,7 +340,7 @@ module SPARQL; module Algebra
340
340
  end
341
341
  require 'sparql/algebra/sxp_extensions'
342
342
 
343
- to_sse.to_sxp
343
+ to_sxp_bin.to_sxp
344
344
  end
345
345
 
346
346
  ##
@@ -1,3 +1,9 @@
1
+ begin
2
+ require 'linkeddata'
3
+ rescue LoadError => e
4
+ require 'rdf/ntriples'
5
+ end
6
+
1
7
  module SPARQL; module Algebra
2
8
  class Operator
3
9
  ##
@@ -17,6 +23,11 @@ module SPARQL; module Algebra
17
23
  include Query
18
24
 
19
25
  NAME = [:dataset]
26
+ # Selected accept headers, from those available
27
+ ACCEPTS = (%w(text/turtle application/rdf+xml;q=0.8 text/plain;q=0.4).
28
+ select do |content_type|
29
+ RDF::Format.content_types.include?(content_type.split(';').first)
30
+ end << ' */*;q=0.1').join(', ').freeze
20
31
 
21
32
  ##
22
33
  # Executes this query on the given `queryable` graph or repository.
@@ -35,7 +46,9 @@ module SPARQL; module Algebra
35
46
  def execute(queryable, options = {})
36
47
  debug(options) {"Dataset"}
37
48
  operand(0).each do |ds|
38
- load_opts = {}
49
+ load_opts = {
50
+ :headers => {"Accept" => ACCEPTS}
51
+ }
39
52
  case ds
40
53
  when Array
41
54
  # Format is (named <uri>), only need the URI part
@@ -95,7 +95,7 @@ module SPARQL; module Grammar
95
95
  end
96
96
 
97
97
  # @return [String]
98
- def to_sse
98
+ def to_sxp_bin
99
99
  @result
100
100
  end
101
101
 
@@ -260,7 +260,7 @@ module SPARQL; module Grammar
260
260
  #
261
261
  # @return [HRDF::URI]
262
262
  def base_uri
263
- @options[:base_uri]
263
+ RDF::URI(@options[:base_uri])
264
264
  end
265
265
 
266
266
  ##
@@ -1089,7 +1089,12 @@ module SPARQL; module Grammar
1089
1089
  message = args.join(": ")
1090
1090
  message = message + yield if block_given?
1091
1091
  depth = options[:depth] || @productions.length
1092
- $stderr.puts("[#{@lineno}]#{' ' * depth}#{message}") if options[:debug]
1092
+ case options[:debug]
1093
+ when Array
1094
+ options[:debug] << "[#{@lineno}]#{' ' * depth}#{message}"
1095
+ else
1096
+ $stderr.puts("[#{@lineno}]#{' ' * depth}#{message}")
1097
+ end
1093
1098
  end
1094
1099
 
1095
1100
  # [1] Query ::= Prologue ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery )
@@ -118,22 +118,39 @@ module SPARQL
118
118
  # @param [RDF::Query::Solutions, RDF::Queryable, Boolean] solutions
119
119
  # Solutions as either a solution set, a Queryable object (such as a graph) or a Boolean value
120
120
  # @param [Hash{Symbol => Object}] options
121
- # @option options [:format]
121
+ # @option options [#to_sym] :format
122
122
  # Format of results, one of :html, :json or :xml.
123
123
  # May also be an RDF::Writer format to serialize DESCRIBE or CONSTRUCT results
124
- # @option options [:content_type]
124
+ # @option options [String] :content_type
125
125
  # Format of results, one of 'application/sparql-results+json' or 'application/sparql-results+xml'
126
126
  # May also be an RDF::Writer content_type to serialize DESCRIBE or CONSTRUCT results
127
+ # @option options [Array<String>] :content_types
128
+ # Similar to :content_type, but takes an ordered array of appropriate content types,
129
+ # and serializes using the first appropriate type, including wild-cards.
127
130
  # @return [String]
128
131
  # String with serialized results and #content_type
132
+ # @raise [RDF::WriterError] when inappropriate formatting options are used
129
133
  def serialize_results(solutions, options = {})
130
- format = options[:format]
131
- content_type = options[:content_type]
134
+ format = options[:format].to_sym if options[:format]
135
+ content_type = options[:content_type].to_s.split(';').first
136
+ content_types = options[:content_types] || ['*/*']
132
137
  format ||= RDF::Query::Solutions::MIME_TYPES.invert[content_type] if content_type
133
138
 
139
+ if !format && !content_type
140
+ case solutions
141
+ when RDF::Queryable
142
+ content_type = first_content_type(content_types, RDF::Format.content_types.keys) || 'text/plain'
143
+ format = RDF::Writer.for(:content_type => content_type).to_sym
144
+ else
145
+ content_type = first_content_type(content_types, RDF::Query::Solutions::MIME_TYPES.values) || 'application/sparql-results+xml'
146
+ format = RDF::Query::Solutions::MIME_TYPES.invert[content_type]
147
+ end
148
+ end
149
+
134
150
  serialization = case solutions
135
- when TrueClass, FalseClass
136
- case format ||= :xml
151
+ when TrueClass, FalseClass, RDF::Literal::TRUE, RDF::Literal::FALSE
152
+ solutions = solutions.object if solutions.is_a?(RDF::Literal)
153
+ case format
137
154
  when :json
138
155
  require 'json' unless defined?(::JSON)
139
156
  {:boolean => solutions}.to_json
@@ -149,18 +166,29 @@ module SPARQL
149
166
  content_type = "text/html"
150
167
  xml = ::Builder::XmlMarkup.new(:indent => 2)
151
168
  xml.div(solutions.to_s, :class => "sparql")
169
+ else
170
+ raise RDF::WriterError, "Unknown format #{(format || content_type).inspect} for #{solutions.class}"
152
171
  end
153
172
  when RDF::Queryable
173
+ begin
174
+ require 'linkeddata'
175
+ rescue LoadError => e
176
+ require 'rdf/ntriples'
177
+ end
154
178
  fmt = RDF::Format.for(format ? format.to_sym : {:content_type => content_type})
155
179
  fmt ||= RDF::NTriples::Format
156
180
  format ||= fmt.to_sym
157
- content_type ||= fmt.content_types.first
158
- fmt.writer.buffer << solutions
181
+ content_type ||= fmt.content_type.first
182
+ results = solutions.dump(format, options)
183
+ raise RDF::WriterError, "Unknown format #{fmt.inspect} for #{solutions.class}" unless results
184
+ results
159
185
  when RDF::Query::Solutions
160
- case format ||= :xml
186
+ case format
161
187
  when :json then solutions.to_json
162
188
  when :xml then solutions.to_xml
163
189
  when :html then solutions.to_html
190
+ else
191
+ raise RDF::WriterError, "Unknown format #{(format || content_type).inspect} for #{solutions.class}"
164
192
  end
165
193
  end
166
194
 
@@ -184,6 +212,26 @@ module SPARQL
184
212
  </html>
185
213
  ).freeze
186
214
 
215
+ ##
216
+ # Find a content_type from a list using an ordered list of acceptable content types
217
+ # using wildcard matching
218
+ #
219
+ # @param [Array<String>] acceptable
220
+ # @param [Array<String>] available
221
+ # @return [String]
222
+ #
223
+ # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
224
+ def first_content_type(acceptable, available)
225
+ return acceptable.first if available.empty?
226
+ available.flatten!
227
+ acceptable.each do |pattern|
228
+ type = available.detect { |t| File.fnmatch(pattern, t) }
229
+ return type if type
230
+ end
231
+ nil
232
+ end
233
+ module_function :first_content_type
234
+
187
235
  ##
188
236
  # Serialize error results
189
237
  #
metadata CHANGED
@@ -1,33 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Gregg Kellogg
9
9
  - Arto Bendiken
10
- - Ben Levendar
11
10
  - Pius Uzamere
12
11
  autorequire:
13
12
  bindir: bin
14
13
  cert_chain: []
15
- date: 2011-09-13 00:00:00.000000000 Z
14
+ date: 2012-01-31 00:00:00.000000000Z
16
15
  dependencies:
17
16
  - !ruby/object:Gem::Dependency
18
17
  name: rdf
19
- requirement: &2156022280 !ruby/object:Gem::Requirement
18
+ requirement: &2165243520 !ruby/object:Gem::Requirement
20
19
  none: false
21
20
  requirements:
22
21
  - - ! '>='
23
22
  - !ruby/object:Gem::Version
24
- version: 0.3.4
23
+ version: 0.3.5
25
24
  type: :runtime
26
25
  prerelease: false
27
- version_requirements: *2156022280
26
+ version_requirements: *2165243520
28
27
  - !ruby/object:Gem::Dependency
29
28
  name: builder
30
- requirement: &2156020980 !ruby/object:Gem::Requirement
29
+ requirement: &2165242720 !ruby/object:Gem::Requirement
31
30
  none: false
32
31
  requirements:
33
32
  - - ! '>='
@@ -35,10 +34,10 @@ dependencies:
35
34
  version: 3.0.0
36
35
  type: :runtime
37
36
  prerelease: false
38
- version_requirements: *2156020980
37
+ version_requirements: *2165242720
39
38
  - !ruby/object:Gem::Dependency
40
39
  name: json
41
- requirement: &2156018920 !ruby/object:Gem::Requirement
40
+ requirement: &2165241340 !ruby/object:Gem::Requirement
42
41
  none: false
43
42
  requirements:
44
43
  - - ! '>='
@@ -46,10 +45,10 @@ dependencies:
46
45
  version: 1.5.1
47
46
  type: :runtime
48
47
  prerelease: false
49
- version_requirements: *2156018920
48
+ version_requirements: *2165241340
50
49
  - !ruby/object:Gem::Dependency
51
50
  name: sxp
52
- requirement: &2156017400 !ruby/object:Gem::Requirement
51
+ requirement: &2165240320 !ruby/object:Gem::Requirement
53
52
  none: false
54
53
  requirements:
55
54
  - - ! '>='
@@ -57,43 +56,87 @@ dependencies:
57
56
  version: 0.0.14
58
57
  type: :runtime
59
58
  prerelease: false
60
- version_requirements: *2156017400
59
+ version_requirements: *2165240320
61
60
  - !ruby/object:Gem::Dependency
62
61
  name: sparql-client
63
- requirement: &2156015680 !ruby/object:Gem::Requirement
62
+ requirement: &2165237360 !ruby/object:Gem::Requirement
64
63
  none: false
65
64
  requirements:
66
65
  - - ! '>='
67
66
  - !ruby/object:Gem::Version
68
- version: 0.0.9
67
+ version: 0.0.11
69
68
  type: :runtime
70
69
  prerelease: false
71
- version_requirements: *2156015680
70
+ version_requirements: *2165237360
72
71
  - !ruby/object:Gem::Dependency
73
72
  name: rdf-xsd
74
- requirement: &2156014600 !ruby/object:Gem::Requirement
73
+ requirement: &2165236080 !ruby/object:Gem::Requirement
75
74
  none: false
76
75
  requirements:
77
76
  - - ! '>='
78
77
  - !ruby/object:Gem::Version
79
- version: 0.3.4
78
+ version: 0.3.5
80
79
  type: :runtime
81
80
  prerelease: false
82
- version_requirements: *2156014600
81
+ version_requirements: *2165236080
82
+ - !ruby/object:Gem::Dependency
83
+ name: sinatra
84
+ requirement: &2165234460 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *2165234460
93
+ - !ruby/object:Gem::Dependency
94
+ name: rack
95
+ requirement: &2165232960 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: 1.4.1
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: *2165232960
104
+ - !ruby/object:Gem::Dependency
105
+ name: rack-test
106
+ requirement: &2165230920 !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: 0.6.1
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: *2165230920
83
115
  - !ruby/object:Gem::Dependency
84
116
  name: linkeddata
85
- requirement: &2156013760 !ruby/object:Gem::Requirement
117
+ requirement: &2165224020 !ruby/object:Gem::Requirement
86
118
  none: false
87
119
  requirements:
88
120
  - - ! '>='
89
121
  - !ruby/object:Gem::Version
90
- version: 0.3.4
122
+ version: 0.3.5
91
123
  type: :development
92
124
  prerelease: false
93
- version_requirements: *2156013760
125
+ version_requirements: *2165224020
126
+ - !ruby/object:Gem::Dependency
127
+ name: rdf-spec
128
+ requirement: &2165221300 !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: 0.3.5
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: *2165221300
94
137
  - !ruby/object:Gem::Dependency
95
138
  name: open-uri-cached
96
- requirement: &2156011940 !ruby/object:Gem::Requirement
139
+ requirement: &2165218280 !ruby/object:Gem::Requirement
97
140
  none: false
98
141
  requirements:
99
142
  - - ! '>='
@@ -101,32 +144,43 @@ dependencies:
101
144
  version: 0.0.4
102
145
  type: :development
103
146
  prerelease: false
104
- version_requirements: *2156011940
147
+ version_requirements: *2165218280
148
+ - !ruby/object:Gem::Dependency
149
+ name: equivalent-xml
150
+ requirement: &2165215280 !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: 0.2.8
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: *2165215280
105
159
  - !ruby/object:Gem::Dependency
106
160
  name: nokogiri
107
- requirement: &2156010340 !ruby/object:Gem::Requirement
161
+ requirement: &2165202460 !ruby/object:Gem::Requirement
108
162
  none: false
109
163
  requirements:
110
164
  - - ! '>='
111
165
  - !ruby/object:Gem::Version
112
- version: 1.4.4
166
+ version: 1.5.0
113
167
  type: :development
114
168
  prerelease: false
115
- version_requirements: *2156010340
169
+ version_requirements: *2165202460
116
170
  - !ruby/object:Gem::Dependency
117
171
  name: rspec
118
- requirement: &2156009140 !ruby/object:Gem::Requirement
172
+ requirement: &2165199020 !ruby/object:Gem::Requirement
119
173
  none: false
120
174
  requirements:
121
175
  - - ! '>='
122
176
  - !ruby/object:Gem::Version
123
- version: 2.5.0
177
+ version: 2.8.0
124
178
  type: :development
125
179
  prerelease: false
126
- version_requirements: *2156009140
180
+ version_requirements: *2165199020
127
181
  - !ruby/object:Gem::Dependency
128
182
  name: spira
129
- requirement: &2156007540 !ruby/object:Gem::Requirement
183
+ requirement: &2165196200 !ruby/object:Gem::Requirement
130
184
  none: false
131
185
  requirements:
132
186
  - - ! '>='
@@ -134,18 +188,18 @@ dependencies:
134
188
  version: 0.0.12
135
189
  type: :development
136
190
  prerelease: false
137
- version_requirements: *2156007540
191
+ version_requirements: *2165196200
138
192
  - !ruby/object:Gem::Dependency
139
193
  name: yard
140
- requirement: &2156005820 !ruby/object:Gem::Requirement
194
+ requirement: &2165194600 !ruby/object:Gem::Requirement
141
195
  none: false
142
196
  requirements:
143
197
  - - ! '>='
144
198
  - !ruby/object:Gem::Version
145
- version: 0.6.0
199
+ version: 0.7.5
146
200
  type: :development
147
201
  prerelease: false
148
- version_requirements: *2156005820
202
+ version_requirements: *2165194600
149
203
  description: ! "\n Implements SPARQL grammar parsing to SPARQL Algebra, SPARQL
150
204
  Algebra processing\n and includes SPARQL Client for accessing remote repositories."
151
205
  email: public-rdf-ruby@w3.org
@@ -160,6 +214,10 @@ files:
160
214
  - UNLICENSE
161
215
  - VERSION
162
216
  - bin/sparql
217
+ - lib/rack/sparql/conneg.rb
218
+ - lib/rack/sparql.rb
219
+ - lib/sinatra/sparql/extensions.rb
220
+ - lib/sinatra/sparql.rb
163
221
  - lib/sparql/algebra/evaluatable.rb
164
222
  - lib/sparql/algebra/expression.rb
165
223
  - lib/sparql/algebra/extensions.rb
@@ -222,7 +280,7 @@ files:
222
280
  - lib/sparql/results.rb
223
281
  - lib/sparql/version.rb
224
282
  - lib/sparql.rb
225
- homepage: http://sparql.rubyforge.org/
283
+ homepage: http://github.com/gkellogg/sparql
226
284
  licenses:
227
285
  - Public Domain
228
286
  post_install_message:
@@ -234,7 +292,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
234
292
  requirements:
235
293
  - - ! '>='
236
294
  - !ruby/object:Gem::Version
237
- version: 1.8.1
295
+ version: 1.8.7
238
296
  required_rubygems_version: !ruby/object:Gem::Requirement
239
297
  none: false
240
298
  requirements:
@@ -248,3 +306,4 @@ signing_key:
248
306
  specification_version: 3
249
307
  summary: SPARQL library for Ruby.
250
308
  test_files: []
309
+ has_rdoc: false