rdf 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ module RDF module NTriples
2
+ ##
3
+ # N-Triples parser.
4
+ #
5
+ # @example Reading N-Triples data
6
+ # RDF::NTriples::Reader.open("spec/data/test.nt") do |reader|
7
+ # reader.each_statement do |statement|
8
+ # puts statement.inspect
9
+ # end
10
+ # end
11
+ #
12
+ # @see http://www.w3.org/TR/rdf-testcases/#ntriples
13
+ class Reader < RDF::Reader
14
+ format RDF::NTriples::Format
15
+
16
+ ##
17
+ # @return [Array, nil]
18
+ # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar
19
+ def read_triple
20
+ loop do
21
+ readline.strip! # EOFError thrown on end of input
22
+
23
+ unless blank? || read_comment
24
+ subject = read_uriref || read_bnode || fail_subject
25
+ predicate = read_uriref || fail_predicate
26
+ object = read_uriref || read_bnode || read_literal || fail_object
27
+ return [subject, predicate, object]
28
+ end
29
+ end
30
+ end
31
+
32
+ ##
33
+ # @return [Boolean]
34
+ # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (comment)
35
+ def read_comment
36
+ match(/^#\s*(.*)$/)
37
+ end
38
+
39
+ ##
40
+ # @return [URI, nil]
41
+ # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (uriref)
42
+ def read_uriref
43
+ if uri = match(/^<([^>]+)>/)
44
+ RDF::URI.parse(uri)
45
+ end
46
+ end
47
+
48
+ ##
49
+ # @return [Node, nil]
50
+ # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (nodeID)
51
+ def read_bnode
52
+ if node_id = match(/^_:([A-Za-z][A-Za-z0-9]*)/)
53
+ @nodes[node_id] ||= RDF::Node.new(node_id)
54
+ end
55
+ end
56
+
57
+ ##
58
+ # @return [String, Literal, nil]
59
+ # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (literal)
60
+ def read_literal
61
+ if literal = match(/^"((?:\\"|[^"])*)"/)
62
+ literal = unescaped(literal)
63
+
64
+ if language = match(/^@([a-z]+[\-a-z0-9]*)/)
65
+ RDF::Literal.new(literal, :language => language)
66
+ elsif datatype = match(/^(\^\^)/)
67
+ RDF::Literal.new(literal, :datatype => read_uriref || fail_object)
68
+ else
69
+ literal # plain string literal
70
+ end
71
+ end
72
+ end
73
+
74
+ ##
75
+ # @param [String] string
76
+ # @return [String]
77
+ # @see http://www.w3.org/TR/rdf-testcases/#ntrip_strings
78
+ def unescaped(string)
79
+ ["\t", "\n", "\r", "\"", "\\"].each do |escape|
80
+ string.gsub!(escape.inspect[1...-1], escape)
81
+ end
82
+ string.gsub!(/\\u([0-9A-Fa-f]{4,4})/u) { [$1.hex].pack('U*') }
83
+ string.gsub!(/\\U([0-9A-Fa-f]{8,8})/u) { [$1.hex].pack('U*') }
84
+ string
85
+ end
86
+ end
87
+ end end
@@ -0,0 +1,50 @@
1
+ module RDF module NTriples
2
+ ##
3
+ # N-Triples serializer.
4
+ #
5
+ # @see <http://www.w3.org/TR/rdf-testcases/#ntriples>
6
+ class Writer < RDF::Writer
7
+ format RDF::NTriples::Format
8
+
9
+ ##
10
+ # @param [String] text
11
+ # @return [void]
12
+ def write_comment(text)
13
+ puts "# #{text}"
14
+ end
15
+
16
+ ##
17
+ # @param [Resource] subject
18
+ # @param [URI] predicate
19
+ # @param [Value] object
20
+ # @return [void]
21
+ def write_triple(subject, predicate, object)
22
+ s = format_uri(subject)
23
+ p = format_uri(predicate)
24
+ o = object.kind_of?(RDF::URI) ? format_uri(object) : format_literal(object)
25
+ puts "%s %s %s ." % [s, p, o]
26
+ end
27
+
28
+ ##
29
+ # @param [node] Resource
30
+ # @return [void]
31
+ def format_uri(node)
32
+ "<%s>" % uri_for(node)
33
+ end
34
+
35
+ ##
36
+ # @param [String, Literal] literal
37
+ # @return [void]
38
+ def format_literal(literal)
39
+ case literal
40
+ when RDF::Literal
41
+ text = quoted(escaped(literal.value))
42
+ text << "@#{literal.language}" if literal.language
43
+ text << "^^<#{uri_for(literal.datatype)}>" if literal.datatype
44
+ text
45
+ else
46
+ quoted(escaped(literal.to_s))
47
+ end
48
+ end
49
+ end
50
+ end end
data/lib/rdf/query.rb ADDED
@@ -0,0 +1,212 @@
1
+ module RDF
2
+ ##
3
+ # An RDF basic graph pattern query.
4
+ #
5
+ # @example Filtering solutions using a hash
6
+ # query.filter(:author => RDF::URI.new("http://ar.to/#self"))
7
+ # query.filter(:author => "Arto Bendiken")
8
+ # query.filter(:author => [RDF::URI.new("http://ar.to/#self"), "Arto Bendiken"])
9
+ # query.filter(:updated => RDF::Literal.new(Date.today))
10
+ #
11
+ # @example Filtering solutions using a block
12
+ # query.filter { |solution| solution.author.literal? }
13
+ # query.filter { |solution| solution.title =~ /^SPARQL/ }
14
+ # query.filter { |solution| solution.price < 30.5 }
15
+ # query.filter { |solution| solution.bound?(:date) }
16
+ # query.filter { |solution| solution.age.datatype == RDF::XSD.integer }
17
+ # query.filter { |solution| solution.name.language == :es }
18
+ #
19
+ # @example Reordering solutions based on a variable
20
+ # query.order_by(:updated)
21
+ # query.order_by(:updated, :created)
22
+ #
23
+ # @example Selecting particular variables only
24
+ # query.select(:title)
25
+ # query.select(:title, :description)
26
+ #
27
+ # @example Eliminating duplicate solutions
28
+ # query.distinct!
29
+ #
30
+ # @example Limiting the number of solutions
31
+ # query.offset(25).limit(10)
32
+ #
33
+ # @example Counting the number of solutions
34
+ # query.count
35
+ #
36
+ # @example Iterating over all found solutions
37
+ # query.each_solution { |solution| puts solution.inspect }
38
+ #
39
+ class Query
40
+ autoload :Pattern, 'rdf/query/pattern'
41
+ autoload :Solution, 'rdf/query/solution'
42
+ autoload :Variable, 'rdf/query/variable'
43
+
44
+ include Enumerable
45
+
46
+ # @return [Hash{Symbol => Variable}]
47
+ attr_reader :variables
48
+
49
+ # @return [Array<Pattern>]
50
+ attr_reader :patterns
51
+
52
+ # @return [Array<Hash{Symbol => Value}>] An unordered sequence of query solutions.
53
+ attr_accessor :solutions
54
+
55
+ ##
56
+ # @param [Hash{Symbol => Object}] options
57
+ # @yield [query]
58
+ # @yieldparam [Query]
59
+ def initialize(options = {}, &block)
60
+ @variables = options.delete(:variables) || {}
61
+ @patterns = options.delete(:patterns) || []
62
+ @solutions = options.delete(:solutions) || []
63
+ @options = options
64
+
65
+ if block_given?
66
+ case block.arity
67
+ when 1 then block.call(self)
68
+ else instance_eval(&block)
69
+ end
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Enumerates over each query solution.
75
+ #
76
+ # @yield [solution]
77
+ # @yieldparam [Solution]
78
+ # @return [Enumerable]
79
+ def each_solution(&block)
80
+ solutions.each do |bindings|
81
+ block.call(Solution.new(bindings))
82
+ end
83
+ end
84
+
85
+ alias_method :each, :each_solution
86
+
87
+ ##
88
+ # Returns the number of query solutions.
89
+ #
90
+ # @return [Integer]
91
+ def size
92
+ solutions.size
93
+ end
94
+
95
+ alias_method :count, :size
96
+
97
+ ##
98
+ # Filters the solution sequence by the given criteria.
99
+ #
100
+ # @param [Hash{Symbol => Object}] criteria
101
+ # @yield [solution]
102
+ # @yieldparam [Solution] solution
103
+ # @yieldreturn [Boolean]
104
+ # @return [Query]
105
+ def filter(criteria = {}, &block)
106
+ if block_given?
107
+ solutions.reject! do |bindings|
108
+ !block.call(Solution.new(bindings))
109
+ end
110
+ else
111
+ solutions.reject! do |bindings|
112
+ results = criteria.map do |name, value|
113
+ bindings[name] == value
114
+ end
115
+ !results.all?
116
+ end
117
+ end
118
+ self
119
+ end
120
+
121
+ alias_method :filter!, :filter
122
+
123
+ ##
124
+ # Reorders the solution sequence based on `variables`.
125
+ #
126
+ # @param [Enumerable<Symbol>] variables
127
+ # @return [Query]
128
+ def order(*variables)
129
+ if variables.empty?
130
+ raise ArgumentError.new("wrong number of arguments (0 for 1)")
131
+ else
132
+ # TODO: support for descending sort, e.g. order(:s => :asc, :p => :desc)
133
+ variables.map! { |variable| variable.to_sym }
134
+ solutions.sort! do |a, b|
135
+ a = variables.map { |variable| a[variable].to_s }
136
+ b = variables.map { |variable| b[variable].to_s }
137
+ a <=> b
138
+ end
139
+ end
140
+ self
141
+ end
142
+
143
+ alias_method :order_by, :order
144
+
145
+ ##
146
+ # Restricts the the solution sequence to the given `variables` only.
147
+ #
148
+ # @param [Enumerable<Symbol>] variables
149
+ # @return [Query]
150
+ def project(*variables)
151
+ unless variables.empty?
152
+ variables.map! { |variable| variable.to_sym }
153
+ solutions.each do |bindings|
154
+ bindings.delete_if { |k, v| !variables.include?(k) }
155
+ end
156
+ end
157
+ self
158
+ end
159
+
160
+ alias_method :select, :project
161
+
162
+ ##
163
+ # Ensures solutions in the solution sequence are unique.
164
+ #
165
+ # @return [Query]
166
+ def distinct
167
+ solutions.uniq!
168
+ self
169
+ end
170
+
171
+ alias_method :distinct!, :distinct
172
+ alias_method :reduced, :distinct
173
+ alias_method :reduced!, :distinct
174
+
175
+ ##
176
+ # Limits the solution sequence to bindings starting from the `start`
177
+ # offset in the overall solution sequence.
178
+ #
179
+ # @param [Integer] start
180
+ # @return [Query]
181
+ def offset(start)
182
+ slice(start, solutions.size - start)
183
+ end
184
+
185
+ ##
186
+ # Limits the number of solutions to `length`.
187
+ #
188
+ # @param [Integer] length
189
+ # @return [Query]
190
+ def limit(length)
191
+ slice(0, length)
192
+ end
193
+
194
+ ##
195
+ # Limits the solution sequence to `length` bindings starting from the
196
+ # `start` offset in the overall solution sequence.
197
+ #
198
+ # @param [Integer] start
199
+ # @param [Integer] length
200
+ # @return [Query]
201
+ def slice(start, length)
202
+ if start < solutions.size
203
+ solutions.slice!(start, length)
204
+ else
205
+ solutions = []
206
+ end
207
+ self
208
+ end
209
+
210
+ alias_method :slice!, :slice
211
+ end
212
+ end
@@ -0,0 +1,152 @@
1
+ module RDF class Query
2
+ ##
3
+ # An RDF query pattern.
4
+ class Pattern < Statement
5
+ # @return [Hash{Symbol => Object}]
6
+ attr_reader :options
7
+
8
+ ##
9
+ # @overload initialize(options = {})
10
+ # @param [Hash{Symbol => Object}] options
11
+ # @option options [Variable, Resource] :subject (nil)
12
+ # @option options [Variable, URI] :predicate (nil)
13
+ # @option options [Variable, Value] :object (nil)
14
+ # @option options [Boolean] :optional (false)
15
+ #
16
+ # @overload initialize(subject, predicate, object, options = {})
17
+ # @param [Variable, Resource] subject
18
+ # @param [Variable, URI] predicate
19
+ # @param [Variable, Value] object
20
+ # @param [Hash{Symbol => Object}] options
21
+ # @option options [Boolean] :optional (false)
22
+ def initialize(subject = nil, predicate = nil, object = nil, options = {})
23
+ case subject
24
+ when Hash
25
+ options = subject
26
+ subject = options.delete(:subject)
27
+ predicate = options.delete(:predicate)
28
+ object = options.delete(:object)
29
+ initialize(subject, predicate, object, options)
30
+ else
31
+ @options = options || {}
32
+ @subject = subject.is_a?(Symbol) ? Variable.new(subject) : subject
33
+ @predicate = predicate.is_a?(Symbol) ? Variable.new(predicate) : predicate
34
+ @object = object.is_a?(Symbol) ? Variable.new(object) : object
35
+ end
36
+ end
37
+
38
+ ##
39
+ # @param [Graph, Repository] graph
40
+ # @return [Enumerator]
41
+ def execute(graph, &block)
42
+ graph.query(self) # FIXME
43
+ end
44
+
45
+ ##
46
+ # Returns `true` if this pattern contains variables.
47
+ #
48
+ # @return [Boolean]
49
+ def variables?
50
+ subject.is_a?(Variable) ||
51
+ predicate.is_a?(Variable) ||
52
+ object.is_a?(Variable)
53
+ end
54
+
55
+ ##
56
+ # Returns the number of variables in this pattern.
57
+ #
58
+ # @return [Integer] (0..3)
59
+ def variable_count
60
+ variables.size
61
+ end
62
+
63
+ alias_method :cardinality, :variable_count
64
+ alias_method :arity, :variable_count
65
+
66
+ ##
67
+ # Returns all variables in this pattern.
68
+ #
69
+ # @return [Hash{Symbol => Variable}]
70
+ def variables
71
+ variables = {}
72
+ variables.merge!(subject.variables) if subject.is_a?(Variable)
73
+ variables.merge!(predicate.variables) if predicate.is_a?(Variable)
74
+ variables.merge!(object.variables) if object.is_a?(Variable)
75
+ variables
76
+ end
77
+
78
+ ##
79
+ # Returns `true` if this pattern contains bindings.
80
+ #
81
+ # @return [Boolean]
82
+ def bindings?
83
+ !bindings.empty?
84
+ end
85
+
86
+ ##
87
+ # Returns the number of bindings in this pattern.
88
+ #
89
+ # @return [Integer] (0..3)
90
+ def binding_count
91
+ bindings.size
92
+ end
93
+
94
+ ##
95
+ # Returns all bindings in this pattern.
96
+ #
97
+ # @return [Hash{Symbol => Value}]
98
+ def bindings
99
+ bindings = {}
100
+ bindings.merge!(subject.bindings) if subject.is_a?(Variable)
101
+ bindings.merge!(predicate.bindings) if predicate.is_a?(Variable)
102
+ bindings.merge!(object.bindings) if object.is_a?(Variable)
103
+ bindings
104
+ end
105
+
106
+ ##
107
+ # Returns `true` if all variables in this pattern are bound.
108
+ #
109
+ # @return [Boolean]
110
+ def bound?
111
+ !variables.empty? && variables.values.all?(&:bound?)
112
+ end
113
+
114
+ ##
115
+ # Returns all bound variables in this pattern.
116
+ #
117
+ # @return [Hash{Symbol => Variable}]
118
+ def bound_variables
119
+ variables.reject { |name, variable| variable.unbound? }
120
+ end
121
+
122
+ ##
123
+ # Returns `true` if all variables in this pattern are unbound.
124
+ #
125
+ # @return [Boolean]
126
+ def unbound?
127
+ !variables.empty? && variables.values.all?(&:unbound?)
128
+ end
129
+
130
+ ##
131
+ # Returns all unbound variables in this pattern.
132
+ #
133
+ # @return [Hash{Symbol => Variable}]
134
+ def unbound_variables
135
+ variables.reject { |name, variable| variable.bound? }
136
+ end
137
+
138
+ ##
139
+ # Returns the string representation of this pattern.
140
+ #
141
+ # @return [String]
142
+ def to_s
143
+ require 'stringio' unless defined?(StringIO)
144
+ StringIO.open do |buffer| # FIXME in RDF::Statement
145
+ buffer << (subject.is_a?(Variable) ? subject.to_s : "<#{subject}>") << ' '
146
+ buffer << (predicate.is_a?(Variable) ? predicate.to_s : "<#{predicate}>") << ' '
147
+ buffer << (object.is_a?(Variable) ? object.to_s : "<#{object}>") << ' .'
148
+ buffer.string
149
+ end
150
+ end
151
+ end
152
+ end end