rdf 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,7 +27,8 @@ module RDF
27
27
  def self.from(statement, options = {})
28
28
  case statement
29
29
  when Array, Query::Pattern
30
- self.new(statement[0], statement[1], statement[2], options.merge(:context => statement[3] || nil))
30
+ context = statement[3] == false ? nil : statement[3]
31
+ self.new(statement[0], statement[1], statement[2], options.merge(:context => context))
31
32
  when Statement then statement
32
33
  when Hash then self.new(options.merge(statement))
33
34
  else raise ArgumentError, "expected RDF::Statement, Hash, or Array, but got #{statement.inspect}"
@@ -176,7 +177,7 @@ module RDF
176
177
  # @param [Statement] other
177
178
  # @return [Boolean]
178
179
  def eql?(other)
179
- other.is_a?(Statement) && self == other && self.context == other.context
180
+ other.is_a?(Statement) && self == other && (self.context || false) == (other.context || false)
180
181
  end
181
182
 
182
183
  ##
@@ -190,10 +191,10 @@ module RDF
190
191
  # @param [Statement] other
191
192
  # @return [Boolean]
192
193
  def ===(other)
193
- return false if has_context? && context != other.context
194
- return false if has_subject? && subject != other.subject
195
- return false if has_predicate? && predicate != other.predicate
196
- return false if has_object? && object != other.object
194
+ return false if has_context? && !context.eql?(other.context)
195
+ return false if has_subject? && !subject.eql?(other.subject)
196
+ return false if has_predicate? && !predicate.eql?(other.predicate)
197
+ return false if has_object? && !object.eql?(other.object)
197
198
  return true
198
199
  end
199
200
 
@@ -24,6 +24,38 @@ module RDF
24
24
  self.to_s <=> other.to_s
25
25
  end
26
26
 
27
+ ##
28
+ # Compares `self` to `other` to implement RDFterm-equal.
29
+ #
30
+ # Subclasses should override this to provide a more meaningful
31
+ # implementation than the default which simply performs a string
32
+ # comparison based on `#to_s`.
33
+ #
34
+ # @abstract
35
+ # @param [Object] other
36
+ # @return [Integer] `-1`, `0`, or `1`
37
+ #
38
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
39
+ def ==(other)
40
+ super
41
+ end
42
+
43
+ ##
44
+ # Determins if `self` is the same term as `other`.
45
+ #
46
+ # Subclasses should override this to provide a more meaningful
47
+ # implementation than the default which simply performs a string
48
+ # comparison based on `#to_s`.
49
+ #
50
+ # @abstract
51
+ # @param [Object] other
52
+ # @return [Integer] `-1`, `0`, or `1`
53
+ #
54
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-sameTerm
55
+ def eql?(other)
56
+ super
57
+ end
58
+
27
59
  ##
28
60
  # Returns `true` if this term is constant.
29
61
  #
@@ -244,8 +244,8 @@ module RDF
244
244
  # RDF::URI.new('urn:isbn') / 125235111
245
245
  # #=> RDF::URI('urn:isbn:125235111')
246
246
  def /(fragment)
247
- fragment = fragment.respond_to?(:to_uri) ? fragment.to_uri : RDF::URI.intern(fragment.to_s)
248
- raise ArgumentError, "Non-absolute URI or string required, got #{fragment}" unless fragment.relative?
247
+ frag = fragment.respond_to?(:to_uri) ? fragment.to_uri : RDF::URI(fragment.to_s)
248
+ raise ArgumentError, "Non-absolute URI or string required, got #{frag}" unless frag.relative?
249
249
  if urn?
250
250
  RDF::URI.intern(to_s.sub(/:+$/,'') + ':' + fragment.to_s.sub(/^:+/,''))
251
251
  else # !urn?
@@ -435,7 +435,7 @@ module RDF
435
435
  alias_method :ends_with?, :end_with?
436
436
 
437
437
  ##
438
- # Checks whether this URI is equal to `other`.
438
+ # Checks whether this URI the same term as `other'.
439
439
  #
440
440
  # @example
441
441
  # RDF::URI('http://t.co/').eql?(RDF::URI('http://t.co/')) #=> true
@@ -449,7 +449,9 @@ module RDF
449
449
  end
450
450
 
451
451
  ##
452
- # Checks whether this URI is equal to `other`.
452
+ # Checks whether this URI is equal to `other` (type checking).
453
+ #
454
+ # Per SPARQL data-r2/expr-equal/eq-2-2, numeric can't be compared with other types
453
455
  #
454
456
  # @example
455
457
  # RDF::URI('http://t.co/') == RDF::URI('http://t.co/') #=> true
@@ -458,11 +460,15 @@ module RDF
458
460
  #
459
461
  # @param [Object] other
460
462
  # @return [Boolean] `true` or `false`
463
+ # @see http://www.w3.org/TR/rdf-sparql-query/#func-RDFterm-equal
461
464
  def ==(other)
462
465
  case other
463
- when String then to_s == other
464
- when URI, Addressable::URI then to_s == other.to_s
465
- else other.respond_to?(:to_uri) && to_s == other.to_uri.to_s
466
+ when Literal
467
+ # If other is a Literal, reverse test to consolodate complex type checking logic
468
+ other == self
469
+ when String then to_s == other
470
+ when URI, Addressable::URI then to_s == other.to_s
471
+ else other.respond_to?(:to_uri) && to_s == other.to_uri.to_s
466
472
  end
467
473
  end
468
474
 
@@ -119,5 +119,15 @@ module RDF
119
119
  def inspect!
120
120
  warn(inspect)
121
121
  end
122
+
123
+ ##
124
+ # Default implementation of raise_error, which returns false.
125
+ # Classes including RDF::TypeCheck will raise RDF::TypeError
126
+ # instead.
127
+ #
128
+ # @return [false]
129
+ def type_error(message)
130
+ false
131
+ end
122
132
  end # Value
123
133
  end # RDF
@@ -4,6 +4,8 @@ module RDF
4
4
  #
5
5
  # This has not yet been implemented as of RDF.rb 0.3.x.
6
6
  module NQuads
7
+ include NTriples
8
+
7
9
  ##
8
10
  # N-Quads format specification.
9
11
  #
@@ -15,20 +17,135 @@ module RDF
15
17
  # RDF::Format.for(:content_type => "text/x-nquads")
16
18
  #
17
19
  # @see http://sw.deri.org/2008/07/n-quads/#mediatype
20
+ # @since 0.4.0
18
21
  class Format < RDF::Format
19
22
  content_type 'text/x-nquads', :extension => :nq
20
- content_encoding 'ascii'
23
+ content_encoding 'utf-8'
21
24
 
22
25
  reader { RDF::NQuads::Reader }
23
26
  writer { RDF::NQuads::Writer }
27
+
28
+ ##
29
+ # Sample detection to see if it matches N-Quads (or N-Triples)
30
+ #
31
+ # Use a text sample to detect the format of an input file. Sub-classes implement
32
+ # a matcher sufficient to detect probably format matches, including disambiguating
33
+ # between other similar formats.
34
+ #
35
+ # @param [String] sample Beginning several bytes (about 1K) of input.
36
+ # @return [Boolean]
37
+ def self.detect(sample)
38
+ !!sample.match(%r(
39
+ (?:\s*(?:<[^>]*>) | (?:_:\w+)) # Subject
40
+ (?:\s*<[^>]*>) # Predicate
41
+ \s*
42
+ (?:(?:<[^>]*>) | (?:_:\w+) | (?:"[^"]*"(?:^^|@\S+)?)) # Object
43
+ (?:\s*<[^>]*>)? # Optional context
44
+ \s*\.
45
+ )mx) && (
46
+ !sample.match(%r(@(base|prefix|keywords))) # Not Turtle/N3
47
+ )
48
+ end
24
49
  end
25
50
 
26
51
  class Reader < NTriples::Reader
27
- # TODO
52
+ ##
53
+ # @param [String] input
54
+ # @return [RDF::Term]
55
+ # @since 0.4.0
56
+ def self.parse_context(input)
57
+ parse_uri(input) || parse_node(input) || parse_literal(input)
58
+ end
59
+
60
+ ##
61
+ # Read a Quad, where the context is optional
62
+ #
63
+ # @return [Array]
64
+ # @see http://sw.deri.org/2008/07/n-quads/#grammar
65
+ # @since 0.4.0
66
+ def read_triple
67
+ loop do
68
+ readline.strip! # EOFError thrown on end of input
69
+ line = @line # for backtracking input in case of parse error
70
+
71
+ begin
72
+ unless blank? || read_comment
73
+ subject = read_uriref || read_node || fail_subject
74
+ predicate = read_uriref(:intern => true) || fail_predicate
75
+ object = read_uriref || read_node || read_literal || fail_object
76
+ context = read_uriref || read_node || read_literal
77
+ return [subject, predicate, object, {:context => context}]
78
+ end
79
+ rescue RDF::ReaderError => e
80
+ @line = line # this allows #read_value to work
81
+ raise e
82
+ end
83
+ end
84
+ end
85
+
28
86
  end # Reader
29
87
 
30
88
  class Writer < NTriples::Writer
31
- # TODO
89
+ ##
90
+ # @param [RDF::Statement] statement
91
+ # @return [void] `self`
92
+ def write_statement(statement)
93
+ write_quad(*statement.to_quad)
94
+ self
95
+ end
96
+ alias_method :insert_statement, :write_statement # support the RDF::Writable interface
97
+
98
+ ##
99
+ # Outputs the N-Quads representation of a statement.
100
+ #
101
+ # @param [RDF::Resource] subject
102
+ # @param [RDF::URI] predicate
103
+ # @param [RDF::Term] object
104
+ # @return [void]
105
+ def write_quad(subject, predicate, object, context)
106
+ puts format_quad(subject, predicate, object, context)
107
+ end
108
+
109
+ ##
110
+ # Returns the N-Quads representation of a statement.
111
+ #
112
+ # @param [RDF::Statement] statement
113
+ # @return [String]
114
+ # @since 0.4.0
115
+ def format_statement(statement)
116
+ format_quad(*statement.to_quad)
117
+ end
118
+
119
+ ##
120
+ # Returns the N-Triples representation of a triple.
121
+ #
122
+ # @param [RDF::Resource] subject
123
+ # @param [RDF::URI] predicate
124
+ # @param [RDF::Term] object
125
+ # @param [RDF::Term] context
126
+ # @return [String]
127
+ def format_quad(subject, predicate, object, context)
128
+ s = "%s %s %s " % [subject, predicate, object].map { |value| format_term(value) }
129
+ s += format_term(context) + " " if context
130
+ s + "."
131
+ end
32
132
  end # Writer
33
133
  end # NQuads
134
+
135
+
136
+ ##
137
+ # Extensions for `RDF::Value`.
138
+ module Value
139
+ ##
140
+ # Returns the N-Triples representation of this value.
141
+ #
142
+ # This method is only available when the 'rdf/ntriples' serializer has
143
+ # been explicitly required.
144
+ #
145
+ # @return [String]
146
+ # @since 0.4.0
147
+ def to_quad
148
+ RDF::NQuads.serialize(self)
149
+ end
150
+ end # Value
34
151
  end # RDF
@@ -2,19 +2,27 @@ module RDF::NTriples
2
2
  ##
3
3
  # N-Triples format specification.
4
4
  #
5
+ # Note: Latest standards activities treat N-Triples as a subset
6
+ # of Turtle. This includes text/ntriples+turtle mime type and a
7
+ # new default encoding of utf-8.
8
+ #
5
9
  # @example Obtaining an NTriples format class
6
10
  # RDF::Format.for(:ntriples) #=> RDF::NTriples::Format
7
11
  # RDF::Format.for("etc/doap.nt")
8
12
  # RDF::Format.for(:file_name => "etc/doap.nt")
9
13
  # RDF::Format.for(:file_extension => "nt")
10
14
  # RDF::Format.for(:content_type => "text/plain")
15
+ # RDF::Format.for(:content_type => "text/ntriples+turtle")
11
16
  #
12
17
  # @see http://www.w3.org/TR/rdf-testcases/#ntriples
13
18
  class Format < RDF::Format
14
19
  content_type 'text/plain', :extension => :nt
15
- content_encoding 'ascii'
20
+ content_type 'text/ntriples+turtle', :extension => :nt
21
+ content_encoding 'utf-8'
16
22
 
17
23
  reader { RDF::NTriples::Reader }
18
24
  writer { RDF::NTriples::Writer }
25
+
26
+ # No format detection, as N-Triples can be parsed by N-Quads
19
27
  end
20
28
  end
@@ -122,6 +122,7 @@ module RDF::NTriples
122
122
  writer = self.new
123
123
  case value
124
124
  when nil then nil
125
+ when FalseClass then value.to_s
125
126
  when RDF::Statement
126
127
  writer.format_statement(value) + "\n"
127
128
  when RDF::Term
@@ -2,6 +2,15 @@ module RDF
2
2
  ##
3
3
  # An RDF basic graph pattern (BGP) query.
4
4
  #
5
+ # Named queries either match against a specifically named
6
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
7
+ # Names that are against unbound variables match either default
8
+ # or named contexts.
9
+ # The name of `false' will only match against the default context.
10
+ #
11
+ # Variable names cause the variable to be added to the solution set
12
+ # elements.
13
+ #
5
14
  # @example Constructing a basic graph pattern query (1)
6
15
  # query = RDF::Query.new do
7
16
  # pattern [:person, RDF.type, FOAF.Person]
@@ -36,6 +45,27 @@ module RDF
36
45
  # }
37
46
  # })
38
47
  #
48
+ # @example In this example, the default graph contains the names of the publishers of two named graphs. The triples in the named graphs are not visible in the default graph in this example.
49
+ # # default graph
50
+ # @prefix dc: <http://purl.org/dc/elements/1.1/
51
+ #
52
+ # <http://example.org/bob> dc:publisher "Bob" .
53
+ # <http://example.org/alice> dc:publisher "Alice" .
54
+ #
55
+ # # Named graph: http://example.org/bob
56
+ # @prefix foaf: <http://xmlns.com/foaf/0.1/> .
57
+ #
58
+ # _:a foaf:name "Bob" .
59
+ # _:a foaf:mbox <mailto:bob@oldcorp.example.org> .
60
+ #
61
+ # # Named graph: http://example.org/alice
62
+ # @prefix foaf: <http://xmlns.com/foaf/0.1/> .
63
+ #
64
+ # _:a foaf:name "Alice" .
65
+ # _:a foaf:mbox <mailto:alice@work.example.org> .
66
+ #
67
+ #
68
+ # @see http://www.w3.org/TR/rdf-sparql-query/#rdfDataset
39
69
  # @since 0.3.0
40
70
  class Query
41
71
  autoload :Pattern, 'rdf/query/pattern'
@@ -95,6 +125,13 @@ module RDF
95
125
  # @param [Hash{Symbol => Object}] options
96
126
  # any additional keyword options
97
127
  # @option options [RDF::Query::Solutions] :solutions (Solutions.new)
128
+ # @option options [RDF::Term, RDF::Query::Variable, Boolean] :context (nil)
129
+ # Default context for matching against queryable.
130
+ # Named queries either match against a specifically named
131
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
132
+ # Names that are against unbound variables match either detault
133
+ # or named contexts.
134
+ # The name of `false' will only match against the default context.
98
135
  # @yield [query]
99
136
  # @yieldparam [RDF::Query] query
100
137
  # @yieldreturn [void] ignored
@@ -105,24 +142,34 @@ module RDF
105
142
  # @param [Hash{Symbol => Object}] options
106
143
  # any additional keyword options
107
144
  # @option options [RDF::Query::Solutions] :solutions (Solutions.new)
145
+ # @option options [RDF::Term, RDF::Query::Variable, Boolean] :context (nil)
146
+ # Default context for matching against queryable.
147
+ # Named queries either match against a specifically named
148
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
149
+ # Names that are against unbound variables match either detault
150
+ # or named contexts.
108
151
  # @yield [query]
109
152
  # @yieldparam [RDF::Query] query
110
153
  # @yieldreturn [void] ignored
111
- def initialize(patterns = nil, options = {}, &block)
112
- @options = options.dup
154
+ def initialize(*patterns, &block)
155
+ @options = patterns.last.is_a?(Hash) ? patterns.pop.dup : {}
156
+ patterns << @options if patterns.empty?
113
157
  @variables = {}
114
158
  @solutions = @options.delete(:solutions) || Solutions.new
159
+ context = @options.delete(:context)
115
160
 
116
- @patterns = case patterns
117
- when Hash then compile_hash_patterns(patterns.dup)
118
- when Array then patterns
119
- else []
161
+ @patterns = case patterns.first
162
+ when Hash then compile_hash_patterns(patterns.first.dup)
163
+ when Array then patterns.first
164
+ else patterns
120
165
  end
121
166
 
167
+ self.context = context
168
+
122
169
  if block_given?
123
170
  case block.arity
124
- when 0 then instance_eval(&block)
125
- else block.call(self)
171
+ when 1 then block.call(self)
172
+ else instance_eval(&block)
126
173
  end
127
174
  end
128
175
  end
@@ -183,10 +230,20 @@ module RDF
183
230
  ##
184
231
  # Executes this query on the given `queryable` graph or repository.
185
232
  #
233
+ # Named queries either match against a specifically named
234
+ # contexts if the name is an RDF::Term or bound RDF::Query::Variable.
235
+ # Names that are against unbound variables match either detault
236
+ # or named contexts.
237
+ # The name of `false' will only match against the default context.
238
+ #
186
239
  # @param [RDF::Queryable] queryable
187
240
  # the graph or repository to query
188
241
  # @param [Hash{Symbol => Object}] options
189
242
  # any additional keyword options
243
+ # @option options [Hash{Symbol => RDF::Term}] bindings
244
+ # optional variable bindings to use
245
+ # @option options [Hash{Symbol => RDF::Term}] solutions
246
+ # optional initial solutions for chained queries
190
247
  # @return [RDF::Query::Solutions]
191
248
  # the resulting solution sequence
192
249
  # @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
@@ -196,13 +253,24 @@ module RDF
196
253
  # just so we can call #keys below without worrying
197
254
  options[:bindings] ||= {}
198
255
 
199
- @solutions = Solutions.new
200
- # A quick empty solution simplifies the logic below; no special case for
256
+ # Use provided solutions to allow for query chaining
257
+ # Otherwise, a quick empty solution simplifies the logic below; no special case for
201
258
  # the first pattern
202
- @solutions << RDF::Query::Solution.new({})
259
+ @solutions = options[:solutions] || (Solutions.new << RDF::Query::Solution.new({}))
260
+
261
+ patterns = @patterns
262
+
263
+ # Add context to pattern, if necessary
264
+ unless self.context.nil?
265
+ if patterns.empty?
266
+ patterns = [Pattern.new(nil, nil, nil, :context => self.context)]
267
+ elsif patterns.first.context.nil?
268
+ patterns.first.context = self.context
269
+ end
270
+ end
271
+
272
+ patterns.each do |pattern|
203
273
 
204
- @patterns.each do |pattern|
205
-
206
274
  old_solutions, @solutions = @solutions, Solutions.new
207
275
 
208
276
  options[:bindings].keys.each do |variable|
@@ -223,6 +291,8 @@ module RDF
223
291
  end
224
292
  end
225
293
 
294
+ #puts "solutions after #{pattern} are #{@solutions.to_a.inspect}"
295
+
226
296
  # It's important to abort failed queries quickly because later patterns
227
297
  # that can have constraints are often broad without them.
228
298
  # We have no solutions at all:
@@ -259,6 +329,44 @@ module RDF
259
329
  !@failed
260
330
  end
261
331
 
332
+ # Add patterns from another query to form a new Query
333
+ # @param [RDF::Query] other
334
+ # @return [RDF::Query]
335
+ def +(other)
336
+ Query.new(self.patterns + other.patterns)
337
+ end
338
+
339
+ # Is this is a named query?
340
+ # @return [Boolean]
341
+ def named?
342
+ !!options[:context]
343
+ end
344
+
345
+ # Is this is an unamed query?
346
+ # @return [Boolean]
347
+ def unnamed?
348
+ !named?
349
+ end
350
+
351
+ # Add name to query
352
+ # @param [RDF::Value] value
353
+ # @return [RDF::Value]
354
+ def context=(value)
355
+ options[:context] = value
356
+ end
357
+
358
+ # Name of this query, if any
359
+ # @return [RDF::Value]
360
+ def context
361
+ options[:context]
362
+ end
363
+
364
+ # Query has no patterns
365
+ # @return [Boolean]
366
+ def empty?
367
+ patterns.empty?
368
+ end
369
+
262
370
  ##
263
371
  # Enumerates over each matching query solution.
264
372
  #