rdf 0.0.5 → 0.0.6

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.
@@ -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