rubyrdf 0.0.1
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/History.txt +4 -0
- data/License.txt +24 -0
- data/Manifest.txt +52 -0
- data/README.txt +79 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +15 -0
- data/lib/rdf/blank_node.rb +41 -0
- data/lib/rdf/exceptions.rb +26 -0
- data/lib/rdf/format/ntriples.rb +493 -0
- data/lib/rdf/graph/base.rb +118 -0
- data/lib/rdf/graph/memory.rb +146 -0
- data/lib/rdf/graph/tests.rb +137 -0
- data/lib/rdf/namespace.rb +90 -0
- data/lib/rdf/plain_literal_node.rb +36 -0
- data/lib/rdf/query/binding.rb +68 -0
- data/lib/rdf/query/executer.rb +42 -0
- data/lib/rdf/query/result.rb +54 -0
- data/lib/rdf/query.rb +54 -0
- data/lib/rdf/triple.rb +61 -0
- data/lib/rdf/typed_literal_node.rb +39 -0
- data/lib/rdf/uri_node.rb +35 -0
- data/lib/rdf/version.rb +9 -0
- data/lib/rubyrdf.rb +59 -0
- data/log/debug.log +0 -0
- data/script/console +11 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/test/helper.rb +14 -0
- data/test/test_blank_node.rb +67 -0
- data/test/test_format_ntriples.rb +247 -0
- data/test/test_graph_base.rb +71 -0
- data/test/test_graph_memory.rb +146 -0
- data/test/test_namespace.rb +130 -0
- data/test/test_plain_literal_node.rb +83 -0
- data/test/test_query.rb +49 -0
- data/test/test_query_binding.rb +84 -0
- data/test/test_query_result.rb +111 -0
- data/test/test_rdf.rb +56 -0
- data/test/test_triple.rb +147 -0
- data/test/test_typed_literal_node.rb +61 -0
- data/test/test_uri_node.rb +45 -0
- data/website/index.html +169 -0
- data/website/index.txt +92 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- metadata +139 -0
@@ -0,0 +1,493 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module RDF
|
4
|
+
module Format
|
5
|
+
# Import/Exports a graph to NTriples format.
|
6
|
+
class NTriples
|
7
|
+
# Raised when there is a syntax error in an input NTriples file.
|
8
|
+
class SyntaxError < RDF::Error; end
|
9
|
+
|
10
|
+
# Exports a +graph+ to +file+ in NTriples format.
|
11
|
+
def self.export(graph, file)
|
12
|
+
graph.each do |t|
|
13
|
+
file.write(export_node(t.subject))
|
14
|
+
file.write(' ')
|
15
|
+
file.write(export_node(t.predicate))
|
16
|
+
file.write(' ')
|
17
|
+
file.write(export_node(t.object))
|
18
|
+
file.puts('.')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a string NTriples representation of +node+.
|
23
|
+
def self.export_node(node)
|
24
|
+
case
|
25
|
+
when RDF::UriNode?(node)
|
26
|
+
"<#{node.uri}>"
|
27
|
+
when RDF::BlankNode?(node)
|
28
|
+
"_:#{node.name}"
|
29
|
+
when RDF::PlainLiteralNode?(node)
|
30
|
+
"\"#{node.lexical_form}\"" + (node.language_tag.to_s.empty? ? '' : "@#{node.language_tag}")
|
31
|
+
when RDF::TypedLiteralNode?(node)
|
32
|
+
"\"#{node.lexical_form}\"^^<#{node.datatype_uri}>"
|
33
|
+
else
|
34
|
+
raise SyntaxError, "Unknown node type"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Imports NTriples input +file+ to +graph+ or creates a new graph if +graph+ is nil.
|
39
|
+
def self.import(file, graph = nil)
|
40
|
+
NTriples.new(file, graph).import
|
41
|
+
end
|
42
|
+
|
43
|
+
# Imports a string NTriples representation of a node into +graph+ or creates a new graph if +graph+ is nil.
|
44
|
+
def self.import_node(str, graph = nil)
|
45
|
+
NTriples.new(StringIO.new(str), graph).import_node
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates an instance of NTriples for importing an NTriples +file+ into +graph+.
|
49
|
+
def initialize(file, graph = nil)
|
50
|
+
@lineno = 1
|
51
|
+
@charno = 1
|
52
|
+
@file = file.respond_to?(:getc) ? file : StringIO.new(file.to_s)
|
53
|
+
stretch_buf_to(1)
|
54
|
+
@graph = graph.nil? ? RDF::Graph::Memory.new : graph
|
55
|
+
end
|
56
|
+
|
57
|
+
# Runs the import for this NTriples instance, and returns the graph.
|
58
|
+
def import
|
59
|
+
lines unless @file.eof?
|
60
|
+
@graph
|
61
|
+
end
|
62
|
+
|
63
|
+
# Runs the import for a single node for this NTriples instance, and returns the node.
|
64
|
+
def import_node
|
65
|
+
if ws?
|
66
|
+
wses
|
67
|
+
end
|
68
|
+
|
69
|
+
object
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def position
|
74
|
+
"line: #{@lineno}, char: #{@charno}"
|
75
|
+
end
|
76
|
+
|
77
|
+
# All of the internal parsing methods follow
|
78
|
+
def stretch_buf_to(x)
|
79
|
+
@buf ||= ''
|
80
|
+
goal = x - @buf.length
|
81
|
+
while goal > 0 && !@file.eof?
|
82
|
+
@buf << @file.getc.chr
|
83
|
+
goal -= 1
|
84
|
+
@charno += 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def consume(str = nil)
|
89
|
+
if str.nil?
|
90
|
+
stretch_buf_to(2)
|
91
|
+
@buf.slice!(0).chr
|
92
|
+
elsif test(str)
|
93
|
+
stretch_buf_to(str.length + 1)
|
94
|
+
|
95
|
+
if @buf.length >= str.length
|
96
|
+
@buf.slice!(0, str.length)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise SyntaxError, "Expected #{str} at #{position}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def test(str)
|
104
|
+
stretch_buf_to(str.length)
|
105
|
+
@buf[0, str.length] == str
|
106
|
+
end
|
107
|
+
|
108
|
+
def peek
|
109
|
+
@buf[0, 1]
|
110
|
+
end
|
111
|
+
|
112
|
+
def unescape(str)
|
113
|
+
str
|
114
|
+
end
|
115
|
+
|
116
|
+
## Parsing Methods
|
117
|
+
def lines
|
118
|
+
line
|
119
|
+
while line?
|
120
|
+
line
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def line
|
125
|
+
@charno = 1
|
126
|
+
if ws?
|
127
|
+
wses
|
128
|
+
end
|
129
|
+
|
130
|
+
case
|
131
|
+
when comment?
|
132
|
+
comment
|
133
|
+
eoln
|
134
|
+
when triple?
|
135
|
+
triple
|
136
|
+
eoln
|
137
|
+
when eoln? || @file.eof?
|
138
|
+
eoln
|
139
|
+
else
|
140
|
+
raise SyntaxError, "Expected comment, triple, or empty line at #{position}"
|
141
|
+
end
|
142
|
+
@lineno += 1
|
143
|
+
end
|
144
|
+
|
145
|
+
def wses
|
146
|
+
str = ws
|
147
|
+
while ws?
|
148
|
+
str << ws
|
149
|
+
end
|
150
|
+
str
|
151
|
+
end
|
152
|
+
|
153
|
+
def ws
|
154
|
+
if space? || tab?
|
155
|
+
consume
|
156
|
+
else
|
157
|
+
raise SyntaxError, "Expected space or tab at #{position}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def comment
|
162
|
+
str = consume('#')
|
163
|
+
while character_no_cr_or_lf?
|
164
|
+
str = consume
|
165
|
+
end
|
166
|
+
str
|
167
|
+
end
|
168
|
+
|
169
|
+
def triple
|
170
|
+
sub = subject
|
171
|
+
wses
|
172
|
+
pred = predicate
|
173
|
+
wses
|
174
|
+
obj = object
|
175
|
+
if ws?
|
176
|
+
wses
|
177
|
+
end
|
178
|
+
consume('.')
|
179
|
+
if ws?
|
180
|
+
wses
|
181
|
+
end
|
182
|
+
@graph.add(RDF::Triple.new(sub, pred, obj))
|
183
|
+
end
|
184
|
+
|
185
|
+
def subject
|
186
|
+
if uriRef?
|
187
|
+
uriRef
|
188
|
+
elsif nodeId?
|
189
|
+
nodeId
|
190
|
+
else
|
191
|
+
raise SyntaxError, "Expected uriRef or nodeId at #{position}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def predicate
|
196
|
+
uriRef
|
197
|
+
end
|
198
|
+
|
199
|
+
def object
|
200
|
+
if uriRef?
|
201
|
+
uriRef
|
202
|
+
elsif nodeId?
|
203
|
+
nodeId
|
204
|
+
elsif lit_string?
|
205
|
+
literal
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def uriRef
|
210
|
+
consume('<')
|
211
|
+
node = RDF::UriNode.new(absoluteUri)
|
212
|
+
consume('>')
|
213
|
+
node
|
214
|
+
end
|
215
|
+
|
216
|
+
def absoluteUri
|
217
|
+
str = character
|
218
|
+
while character? && !test('>')
|
219
|
+
str << character
|
220
|
+
end
|
221
|
+
unescape(str)
|
222
|
+
end
|
223
|
+
|
224
|
+
def nodeId
|
225
|
+
consume('_:')
|
226
|
+
RDF::BlankNode.new(name, @graph)
|
227
|
+
end
|
228
|
+
|
229
|
+
def name
|
230
|
+
str = ''
|
231
|
+
if cap_az? || az?
|
232
|
+
str << consume
|
233
|
+
else
|
234
|
+
raise SyntaxError, "Expected A-Z or a-z at #{position}"
|
235
|
+
end
|
236
|
+
|
237
|
+
while cap_az? || az? || num?
|
238
|
+
str << consume
|
239
|
+
end
|
240
|
+
str
|
241
|
+
end
|
242
|
+
|
243
|
+
def literal
|
244
|
+
str = lit_string
|
245
|
+
if lang?
|
246
|
+
lit_lang = lang
|
247
|
+
elsif datatype?
|
248
|
+
lit_dt = datatype
|
249
|
+
end
|
250
|
+
|
251
|
+
if lit_dt
|
252
|
+
RDF::TypedLiteralNode.new(str, lit_dt)
|
253
|
+
else
|
254
|
+
RDF::PlainLiteralNode.new(str, lit_lang)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def lit_string
|
259
|
+
consume('"')
|
260
|
+
str = string
|
261
|
+
consume('"')
|
262
|
+
unescape(str)
|
263
|
+
end
|
264
|
+
|
265
|
+
def string
|
266
|
+
str = ''
|
267
|
+
backslash = false
|
268
|
+
while character? && (!test('"') || backslash)
|
269
|
+
char = character
|
270
|
+
if char == '\\' && !backslash
|
271
|
+
backslash = true
|
272
|
+
else
|
273
|
+
if backslash
|
274
|
+
backslash = false
|
275
|
+
char = convert_backslash(char)
|
276
|
+
end
|
277
|
+
|
278
|
+
str << char
|
279
|
+
end
|
280
|
+
end
|
281
|
+
str
|
282
|
+
end
|
283
|
+
|
284
|
+
def convert_backslash(char)
|
285
|
+
case char
|
286
|
+
when 'n'
|
287
|
+
"\n"
|
288
|
+
when 'r'
|
289
|
+
"\r"
|
290
|
+
when 't'
|
291
|
+
"\t"
|
292
|
+
when 'u'
|
293
|
+
small_unicode_value
|
294
|
+
when 'U'
|
295
|
+
long_unicode_value
|
296
|
+
else
|
297
|
+
char
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def small_unicode_value
|
302
|
+
value = ''
|
303
|
+
while peek =~ /[A-Fa-f0-9]/
|
304
|
+
value << consume
|
305
|
+
end
|
306
|
+
|
307
|
+
if value.size <= 4
|
308
|
+
value.hex.utf8
|
309
|
+
else
|
310
|
+
raise SyntaxError, 'Expected no more than four hexadecimal characters'
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def long_unicode_value
|
315
|
+
value = ''
|
316
|
+
while peek =~ /[A-Fa-f0-9]/
|
317
|
+
value << consume
|
318
|
+
end
|
319
|
+
|
320
|
+
if value.size >= 5 && value.size <= 8
|
321
|
+
value.hex.utf8
|
322
|
+
else
|
323
|
+
raise SyntaxError, 'Expected between five and eight hexadecimal characters'
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def lang
|
328
|
+
consume('@')
|
329
|
+
language
|
330
|
+
end
|
331
|
+
|
332
|
+
def language
|
333
|
+
str = ''
|
334
|
+
if az?
|
335
|
+
str << consume
|
336
|
+
else
|
337
|
+
raise SyntaxError, "Expected a-z at #{position}"
|
338
|
+
end
|
339
|
+
|
340
|
+
while az? && !test('-')
|
341
|
+
str << consume
|
342
|
+
end
|
343
|
+
|
344
|
+
while test('-')
|
345
|
+
str << consume
|
346
|
+
if az? || num?
|
347
|
+
str << consume
|
348
|
+
else
|
349
|
+
raise SyntaxError, "Expected a-z or number at #{position}"
|
350
|
+
end
|
351
|
+
|
352
|
+
while az? || num?
|
353
|
+
str << consume
|
354
|
+
end
|
355
|
+
end
|
356
|
+
str
|
357
|
+
end
|
358
|
+
|
359
|
+
def datatype
|
360
|
+
consume('^^')
|
361
|
+
uriRef.uri
|
362
|
+
end
|
363
|
+
|
364
|
+
def character
|
365
|
+
if character?
|
366
|
+
consume
|
367
|
+
else
|
368
|
+
raise SyntaxError, "Expected character at #{position}"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def eoln
|
373
|
+
str = ''
|
374
|
+
if cr?
|
375
|
+
str = consume
|
376
|
+
if lf?
|
377
|
+
str << consume
|
378
|
+
end
|
379
|
+
elsif lf?
|
380
|
+
str = consume
|
381
|
+
elsif !@file.eof?
|
382
|
+
raise SyntaxError, "Expected cr, lf, or crlf at #{position}"
|
383
|
+
end
|
384
|
+
str
|
385
|
+
end
|
386
|
+
|
387
|
+
## Test Methods
|
388
|
+
def line?
|
389
|
+
ws? || comment? || uriRef? || nodeId? || eoln?
|
390
|
+
end
|
391
|
+
|
392
|
+
def comment?
|
393
|
+
test('#')
|
394
|
+
end
|
395
|
+
|
396
|
+
def triple?
|
397
|
+
subject?
|
398
|
+
end
|
399
|
+
|
400
|
+
def subject?
|
401
|
+
uriRef? || nodeId?
|
402
|
+
end
|
403
|
+
|
404
|
+
def predicate?
|
405
|
+
uriRef?
|
406
|
+
end
|
407
|
+
|
408
|
+
def object?
|
409
|
+
uriRef? || nodeId? || lit_string?
|
410
|
+
end
|
411
|
+
|
412
|
+
def uriRef?
|
413
|
+
test('<')
|
414
|
+
end
|
415
|
+
|
416
|
+
def nodeId?
|
417
|
+
test("_:")
|
418
|
+
end
|
419
|
+
|
420
|
+
def lit_string?
|
421
|
+
test('"')
|
422
|
+
end
|
423
|
+
|
424
|
+
def lang?
|
425
|
+
test("@")
|
426
|
+
end
|
427
|
+
|
428
|
+
def datatype?
|
429
|
+
test("^^")
|
430
|
+
end
|
431
|
+
|
432
|
+
def language_tag?
|
433
|
+
('a'..'z').include?(peek)
|
434
|
+
end
|
435
|
+
|
436
|
+
def ws?
|
437
|
+
space? || tab?
|
438
|
+
end
|
439
|
+
|
440
|
+
def eoln?
|
441
|
+
cr? || lf?
|
442
|
+
end
|
443
|
+
|
444
|
+
def space?
|
445
|
+
peek == 0x20.chr
|
446
|
+
end
|
447
|
+
|
448
|
+
def cr?
|
449
|
+
peek == 0xD.chr
|
450
|
+
end
|
451
|
+
|
452
|
+
def lf?
|
453
|
+
peek == 0xA.chr
|
454
|
+
end
|
455
|
+
|
456
|
+
def tab?
|
457
|
+
peek == 0x9.chr
|
458
|
+
end
|
459
|
+
|
460
|
+
def string?
|
461
|
+
@file.eof? || character?
|
462
|
+
end
|
463
|
+
|
464
|
+
def cap_az?
|
465
|
+
('A'..'Z').include?(peek)
|
466
|
+
end
|
467
|
+
|
468
|
+
def az?
|
469
|
+
('a'..'z').include?(peek)
|
470
|
+
end
|
471
|
+
|
472
|
+
def num?
|
473
|
+
('0'..'9').include?(peek)
|
474
|
+
end
|
475
|
+
|
476
|
+
def name?
|
477
|
+
cap_az? || az?
|
478
|
+
end
|
479
|
+
|
480
|
+
def absoluteUri?
|
481
|
+
character?
|
482
|
+
end
|
483
|
+
|
484
|
+
def character_no_cr_or_lf?
|
485
|
+
character? && !(cr? || lf?)
|
486
|
+
end
|
487
|
+
|
488
|
+
def character?
|
489
|
+
(0x20.chr .. 0x7E.chr).include?(peek)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module RDF
|
2
|
+
module Graph
|
3
|
+
# Base class for graph implementations. This class provides basic
|
4
|
+
# functionality and common interface for all graphs.
|
5
|
+
class Base
|
6
|
+
# Exports the triples for this graph in +format+ to +file+. If +file+ is
|
7
|
+
# nil, then the export is returned as a string.
|
8
|
+
#
|
9
|
+
# There is one format provided by default (:ntriples), but the supported
|
10
|
+
# formats are depended upon the particular graph implementation, and graph
|
11
|
+
# implementations can override this method to use a (presumably) faster
|
12
|
+
# export function from the underlying triple store--as long as it
|
13
|
+
# preserves the interface expectations.
|
14
|
+
#
|
15
|
+
# If a graph implementation overrides this method, and the underlying
|
16
|
+
# triple store does not support one of the default formats
|
17
|
+
# (see RDF::Format), then it is recommended that the implementation make
|
18
|
+
# use of the RDF::Format classes to provide exports for the default
|
19
|
+
# formats.
|
20
|
+
#
|
21
|
+
# *Raises*:: <tt></tt>
|
22
|
+
# UnsupportedFormatError:: if +format+ is not supported for export
|
23
|
+
def export(format = :ntriples, file = nil)
|
24
|
+
file ||= StringIO.new
|
25
|
+
|
26
|
+
case format
|
27
|
+
when :ntriples
|
28
|
+
RDF::Format::NTriples.export(self, file)
|
29
|
+
else
|
30
|
+
raise UnsupportedFormatError
|
31
|
+
end
|
32
|
+
|
33
|
+
file.string if file.is_a?(StringIO)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Imports the triples from +file+ in +format+ to this graph.
|
37
|
+
#
|
38
|
+
# This method will not merge the graphs, meaning the blank nodes from
|
39
|
+
# +file+ are not renamed as they are imported. If you want to merge two
|
40
|
+
# graphs, then use merge.
|
41
|
+
#
|
42
|
+
# There is one format provided by default (:ntriples), but the supported
|
43
|
+
# formats are depended upon the particular graph implementation, and graph
|
44
|
+
# implementations can override this method to use a (presumably) faster
|
45
|
+
# import function from the underlying triple store--as long as it
|
46
|
+
# preserves the interface expectations.
|
47
|
+
#
|
48
|
+
# If a graph implementation overrides this method, and the underlying
|
49
|
+
# triple store does not support one of the default formats
|
50
|
+
# (see RDF::Format), then it is recommended that the implementation make
|
51
|
+
# use of the RDF::Format classes to provide imports for the default
|
52
|
+
# formats.
|
53
|
+
#
|
54
|
+
# *Raises*:: <tt></tt>
|
55
|
+
# ArgumentError:: if +file+ is nil
|
56
|
+
# UnsupportedFormatError:: if +format+ is not supported for import
|
57
|
+
def import(file, format = :ntriples)
|
58
|
+
raise ArgumentError, "file cannot be blank" if file.nil?
|
59
|
+
|
60
|
+
case format
|
61
|
+
when :ntriples
|
62
|
+
RDF::Format::NTriples.import(file, self)
|
63
|
+
else
|
64
|
+
raise UnsupportedFormatError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Merges +graph+ into the current graph. In doing so it will rename all
|
69
|
+
# of the blank nodes so they don't collide with any existing blank nodes.
|
70
|
+
def merge(graph)
|
71
|
+
map = {}
|
72
|
+
graph.each do |t|
|
73
|
+
s, p, o = t.subject, t.predicate, t.object
|
74
|
+
if RDF::BlankNode?(s)
|
75
|
+
map[s] = new_blank_node(s.name) unless map.key?(s)
|
76
|
+
s = map[s]
|
77
|
+
end
|
78
|
+
if RDF::BlankNode?(o)
|
79
|
+
map[o] = new_blank_node(o.name) unless map.key?(o)
|
80
|
+
o = map[o]
|
81
|
+
end
|
82
|
+
|
83
|
+
add(s, p, o)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns true if the triple (+s+, +p+, +o+) is in this graph, false
|
88
|
+
# otherwise.
|
89
|
+
def include?(*triple)
|
90
|
+
triple = Triple.construct(*triple)
|
91
|
+
return false if variable?(triple.subject) || variable?(triple.object)
|
92
|
+
execute(RDF::Query.new.where(triple.subject, triple.predicate, triple.object)).success?
|
93
|
+
end
|
94
|
+
|
95
|
+
def each
|
96
|
+
result = execute(RDF::Query.new.where(:s, :p, :o))
|
97
|
+
result.bindings.each do |b|
|
98
|
+
yield Triple.new(b[:s], b[:p], b[:o])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns true if this graph is empty, false otherwise.
|
103
|
+
def empty?
|
104
|
+
size == 0
|
105
|
+
end
|
106
|
+
|
107
|
+
# Creates a new BlankNode associated with this graph.
|
108
|
+
def new_blank_node(name)
|
109
|
+
RDF::BlankNode.new(name, self)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def variable?(x)
|
114
|
+
RDF::BlankNode?(x) && x.graph != self
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|