sparql 0.0.2 → 0.1.0

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/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