/
graph.rb
356 lines (322 loc) · 9.81 KB
/
graph.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
module RDF
##
# An RDF graph.
#
# An {RDF::Graph} contains a unique set of {RDF::Statement}. It is
# based on an underlying data object, which may be specified when the
# graph is initialized, and will default to a {RDF::Repository} without
# support for named graphs otherwise.
#
# Note that in RDF 1.1, graphs are not named, but are associated with
# a graph name in a Dataset, as a pair of <name, graph>.
# This class allows a name to be associated with a graph when it is
# a projection of an underlying {RDF::Repository} supporting graph_names.
#
# @example Creating an empty unnamed graph
# graph = RDF::Graph.new
#
# @example Loading graph data from a URL
# graph = RDF::Graph.load("http://ruby-rdf.github.io/rdf/etc/doap.nt")
#
# @example Loading graph data from a URL
# require 'rdf/rdfxml' # for RDF/XML support
#
# graph = RDF::Graph.load("http://www.bbc.co.uk/programmes/b0081dq5.rdf")
#
# @example Accessing a specific named graph within a {RDF::Repository}
# require 'rdf/trig' # for TriG support
#
# repository = graph = RDF::Repository.load("https://raw.githubusercontent.com/ruby-rdf/rdf-trig/develop/etc/doap.trig", format: :trig))
# graph = RDF::Graph.new(graph_name: RDF::URI("http://greggkellogg.net/foaf#me"), data: repository)
class Graph
include RDF::Value
include RDF::Countable
include RDF::Durable
include RDF::Enumerable
include RDF::Queryable
include RDF::Mutable
include RDF::Transactable
##
# Returns the options passed to this graph when it was constructed.
#
# @!attribute [r] options
# @return [Hash{Symbol => Object}]
attr_reader :options
##
# Name of this graph, if it is part of an {RDF::Repository}
# @!attribute [rw] graph_name
# @return [RDF::Resource]
# @since 1.1.18
attr_accessor :graph_name
alias_method :name, :graph_name
alias_method :name=, :graph_name=
##
# {RDF::Queryable} backing this graph.
# @!attribute [rw] data
# @return [RDF::Queryable]
attr_accessor :data
##
# Creates a new `Graph` instance populated by the RDF data returned by
# dereferencing the given graph_name Resource.
#
# @param [String, #to_s] url
# @param [RDF::Resource] graph_name
# Set set graph name of each loaded statement
# @param [Hash{Symbol => Object}] options
# Options from {RDF::Reader.open}
# @yield [graph]
# @yieldparam [Graph] graph
# @return [Graph]
# @since 0.1.7
def self.load(url, graph_name: nil, **options, &block)
self.new(graph_name: graph_name, **options) do |graph|
graph.load(url, graph_name: graph_name, **options)
if block_given?
case block.arity
when 1 then block.call(graph)
else graph.instance_eval(&block)
end
end
end
end
##
# @param [RDF::Resource] graph_name
# The graph_name from the associated {RDF::Queryable} associated
# with this graph as provided with the `:data` option
# (only for {RDF::Queryable} instances supporting
# named graphs).
# @param [RDF::Queryable] data (RDF::Repository.new)
# Storage behind this graph.
#
# @raise [ArgumentError] if a `data` does not support named graphs.
# @note
# Graph names are only useful when used as a projection
# on a `:data` which supports named graphs. Otherwise, there is no
# such thing as a named graph in RDF 1.1, a repository may have
# graphs which are named, but the name is not a property of the graph.
# @yield [graph]
# @yieldparam [Graph]
def initialize(graph_name: nil, data: nil, **options, &block)
@graph_name = case graph_name
when nil then nil
when RDF::Resource then graph_name
else RDF::URI.new(graph_name)
end
@options = options.dup
@data = data || RDF::Repository.new(with_graph_name: false)
raise ArgumentError, "Can't apply graph_name unless initialized with `data` supporting graph_names" if
@graph_name && !@data.supports?(:graph_name)
if block_given?
case block.arity
when 1 then block.call(self)
else instance_eval(&block)
end
end
end
##
# (re)loads the graph from the specified location, or from the location associated with the graph name, if any
# @return [void]
# @see RDF::Mutable#load
def load!(*args)
case
when args.empty?
raise ArgumentError, "Can't reload graph without a graph_name" unless graph_name.is_a?(RDF::URI)
load(graph_name.to_s, base_uri: graph_name)
else super
end
end
##
# Returns `true` to indicate that this is a graph.
#
# @return [Boolean]
def graph?
true
end
##
# Returns `true` if this is a named graph.
#
# @return [Boolean]
# @note The next release, graphs will not be named, this will return false
def named?
!unnamed?
end
##
# Returns `true` if this is a unnamed graph.
#
# @return [Boolean]
# @note The next release, graphs will not be named, this will return true
def unnamed?
graph_name.nil?
end
##
# A graph is durable if it's underlying data model is durable
#
# @return [Boolean]
# @see RDF::Durable#durable?
def durable?
@data.durable?
end
##
# Returns all unique RDF names for this graph.
#
# @return [Enumerator<RDF::Resource>]
def graph_names(unique: true)
(named? ? [graph_name] : []).extend(RDF::Countable)
end
##
# Returns the {RDF::Resource} representation of this graph.
#
# @return [RDF::Resource]
def to_uri
graph_name
end
##
# Returns a string representation of this graph.
#
# @return [String]
def to_s
named? ? graph_name.to_s : "default"
end
##
# Returns `true` if this graph has an anonymous graph, `false` otherwise.
#
# @return [Boolean]
# @note The next release, graphs will not be named, this will return true
def anonymous?
graph_name.nil? ? false : graph_name.anonymous?
end
##
# Returns the number of RDF statements in this graph.
#
# @return [Integer]
# @see RDF::Enumerable#count
def count
@data.query(graph_name: graph_name || false).count
end
##
# Returns `true` if this graph contains the given RDF statement.
#
# A statement is in a graph if the statement if it has the same triples without regard to graph_name.
#
# @param [Statement] statement
# @return [Boolean]
# @see RDF::Enumerable#has_statement?
def has_statement?(statement)
statement = statement.dup
statement.graph_name = graph_name
@data.has_statement?(statement)
end
##
# Enumerates each RDF statement in this graph.
#
# @yield [statement]
# @yieldparam [Statement] statement
# @return [Enumerator]
# @see RDF::Enumerable#each_statement
def each(&block)
if @data.respond_to?(:query)
@data.query(graph_name: graph_name || false, &block)
elsif @data.respond_to?(:each)
@data.each(&block)
else
@data.to_a.each(&block)
end
end
##
# @private
# @see RDF::Enumerable#project_graph
def project_graph(graph_name, &block)
if block_given?
self.each(&block) if graph_name == self.graph_name
else
graph_name == self.graph_name ? self : RDF::Graph.new
end
end
##
# Graph equivalence based on the contents of each graph being _exactly_
# the same. To determine if the have the same _meaning_, consider
# [rdf-isomorphic](http://rubygems.org/gems/rdf-isomorphic).
#
# @param [RDF::Graph] other
# @return [Boolean]
# @see http://rubygems.org/gems/rdf-isomorphic
def ==(other)
other.is_a?(RDF::Graph) &&
graph_name == other.graph_name &&
statements.to_a == other.statements.to_a
end
##
# @private
# @see RDF::Queryable#query_pattern
def query_pattern(pattern, **options, &block)
pattern = pattern.dup
pattern.graph_name = graph_name || false
@data.query(pattern, &block)
end
##
# @private
# @see RDF::Mutable#insert
def insert_statement(statement)
statement = statement.dup
statement.graph_name = graph_name
@data.insert(statement)
end
##
# @private
# @see RDF::Mutable#insert_statements
def insert_statements(statements)
enum = Enumerable::Enumerator.new do |yielder|
statements.send(statements.respond_to?(:each_statement) ? :each_statement : :each) do |s|
s = s.dup
s.graph_name = graph_name
yielder << s
end
end
@data.insert(enum)
end
##
# @private
# @see RDF::Mutable#delete
def delete_statement(statement)
statement = statement.dup
statement.graph_name = graph_name
@data.delete(statement)
end
##
# @private
# @see RDF::Mutable#clear
def clear_statements
@data.delete(graph_name: graph_name || false)
end
##
# @private
# Opens a transaction over the graph
# @see RDF::Transactable#begin_transaction
def begin_transaction(mutable: false, graph_name: @graph_name)
@data.send(:begin_transaction, mutable: mutable, graph_name: graph_name)
end
protected :query_pattern
protected :insert_statement
protected :delete_statement
protected :clear_statements
protected :begin_transaction
##
# @private
# @see RDF::Enumerable#graphs
# @since 0.2.0
def graphs
Array(enum_graph)
end
##
# @private
# @see RDF::Enumerable#each_graph
# @since 0.2.0
def each_graph
if block_given?
yield self
else
enum_graph
end
end
end
end