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,146 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module RDF
|
4
|
+
module Graph
|
5
|
+
# An in-memory, pure Ruby implementation of an RDF graph. This graph implementation also serves as an example of the interface that every graph implementation should have.
|
6
|
+
class Memory < Base
|
7
|
+
include RDF::Query::Executer
|
8
|
+
|
9
|
+
# Creates a new Graph.
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
@triples = Set.new
|
13
|
+
@idx_s = Hash.new{|h, k| h[k] = Set.new}
|
14
|
+
@idx_sp = Hash.new{|h, k| h[k] = Set.new}
|
15
|
+
@idx_blank = Hash.new(0)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds a triple to the graph. Will accept an instance of Triple, or three parameters for the subject, predicate, and object.
|
19
|
+
def add(*triple)
|
20
|
+
triple = Triple.construct(*triple)
|
21
|
+
|
22
|
+
# perform some syntax checks
|
23
|
+
raise RDF::UnassociatedBlankNodeError, "#{triple.subject} is not associated with this graph" if RDF::BlankNode?(triple.subject) && triple.subject.graph != self
|
24
|
+
raise RDF::UnassociatedBlankNodeError, "#{triple.object} is not associated with this graph" if RDF::BlankNode?(triple.object) && triple.object.graph != self
|
25
|
+
|
26
|
+
index_subject(triple)
|
27
|
+
index_subject_and_predicate(triple)
|
28
|
+
index_blank(triple.subject) if RDF::BlankNode?(triple.subject)
|
29
|
+
index_blank(triple.object) if RDF::BlankNode?(triple.object)
|
30
|
+
@triples << triple
|
31
|
+
end
|
32
|
+
|
33
|
+
# Deletes +triple+ from the graph. Will accept an instance of Triple, or three parameters for the subject, predicate, and object.
|
34
|
+
def delete(*triple)
|
35
|
+
triple = Triple.construct(*triple)
|
36
|
+
unindex_subject(triple)
|
37
|
+
unindex_subject_and_predicate(triple)
|
38
|
+
unindex_blank(triple.subject) if RDF::BlankNode?(triple.subject)
|
39
|
+
unindex_blank(triple.object) if RDF::BlankNode?(triple.object)
|
40
|
+
@triples.delete(triple)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns true if graph contains +triple+. Will accept an instance of Triple, or three parameters for the subject, predicate, and object.
|
44
|
+
def include?(*triple)
|
45
|
+
triple = Triple.construct(*triple)
|
46
|
+
@triples.include?(triple)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Iterates through all of the triples in this graph.
|
50
|
+
def each(&b)
|
51
|
+
@triples.each(&b)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the number of triples in this graph.
|
55
|
+
def size
|
56
|
+
@triples.size
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def index_subject(triple)
|
61
|
+
@idx_s[triple.subject] ||= Set.new
|
62
|
+
@idx_s[triple.subject] << triple.predicate
|
63
|
+
end
|
64
|
+
|
65
|
+
def index_subject_and_predicate(triple)
|
66
|
+
@idx_sp[[triple.subject, triple.predicate]] ||= Set.new
|
67
|
+
@idx_sp[[triple.subject, triple.predicate]] << triple.object
|
68
|
+
end
|
69
|
+
|
70
|
+
def index_blank(node)
|
71
|
+
@idx_blank[node] += 1
|
72
|
+
end
|
73
|
+
|
74
|
+
def unindex_subject(triple)
|
75
|
+
if @idx_s[triple.subject]
|
76
|
+
@idx_s[triple.subject].delete(triple.predicate)
|
77
|
+
@idx_s.delete(triple.subject) if @idx_s[triple.subject].empty?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def unindex_subject_and_predicate(triple)
|
82
|
+
sp = [triple.subject, triple.predicate]
|
83
|
+
if @idx_sp[sp]
|
84
|
+
@idx_sp[sp].delete(triple.object)
|
85
|
+
@idx_sp.delete(sp) if @idx_sp[sp].empty?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def unindex_blank(node)
|
90
|
+
if @idx_blank[node] > 0
|
91
|
+
@idx_blank[node] -= 1
|
92
|
+
@idx_blank.delete(node) if @idx_blank[node] == 0 && @idx_blank.key?(node)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def match_binding(s, p, o, b)
|
97
|
+
if (s.is_a?(Symbol) || (RDF::BlankNode?(s) && !@idx_blank.key?(s))) && !b.bound?(s)
|
98
|
+
result = @idx_s.keys.inject(RDF::Query::Result.new) do |r, node|
|
99
|
+
binding = b.clone
|
100
|
+
binding[s] = node
|
101
|
+
r.bindings << binding
|
102
|
+
r
|
103
|
+
end
|
104
|
+
result = match(s, p, o, result)
|
105
|
+
result
|
106
|
+
else
|
107
|
+
bind_predicate(s, p, o, b)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def bind_predicate(s, p, o, b)
|
112
|
+
if p.is_a?(Symbol) && !b.bound?(p)
|
113
|
+
result = @idx_s[b[s]].inject(RDF::Query::Result.new) do |r, node|
|
114
|
+
binding = b.clone
|
115
|
+
binding[p] = node
|
116
|
+
r.bindings << binding
|
117
|
+
r
|
118
|
+
end
|
119
|
+
result = match(s, p, o, result)
|
120
|
+
result
|
121
|
+
else
|
122
|
+
bind_object(s, p, o, b)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def bind_object(s, p, o, b)
|
127
|
+
if (o.is_a?(Symbol) || (RDF::BlankNode?(o) && !@idx_blank.key?(o))) && !b.bound?(o)
|
128
|
+
result = @idx_sp[[b[s],b[p]]].inject(RDF::Query::Result.new) do |r, node|
|
129
|
+
binding = b.clone
|
130
|
+
binding[o] = node
|
131
|
+
r.bindings << binding
|
132
|
+
r
|
133
|
+
end
|
134
|
+
|
135
|
+
match(s, p, o, result)
|
136
|
+
else
|
137
|
+
if @triples.include?(Triple.new(b[s], b[p], b[o]))
|
138
|
+
RDF::Query::Result.success!(b)
|
139
|
+
else
|
140
|
+
RDF::Query::Result.new
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module RDF
|
2
|
+
module Graph
|
3
|
+
module Tests
|
4
|
+
##== add ==##
|
5
|
+
def test_add_should_insert_triple
|
6
|
+
@graph.add(RDF::Triple.new(EX::a, EX::b, EX::c))
|
7
|
+
assert @graph.include?(RDF::Triple.new(EX::a, EX::b, EX::c)),
|
8
|
+
"Add should accept an RDF::Triple as a parameter"
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_add_should_insert_spo
|
12
|
+
@graph.add(EX::a, EX::b, EX::c)
|
13
|
+
assert @graph.include?(RDF::Triple.new(EX::a, EX::b, EX::c)),
|
14
|
+
"Add should accept three nodes as parameters"
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_add_should_raise_error_for_unassociated_blank_node_subject
|
18
|
+
assert_raise(RDF::UnassociatedBlankNodeError) {
|
19
|
+
@graph.add(RDF::Graph::Memory.new.new_blank_node('x'), EX::b, EX::c)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_add_should_raise_error_for_unassociated_blank_node_object
|
24
|
+
assert_raise(RDF::UnassociatedBlankNodeError) {
|
25
|
+
@graph.add(EX::a, EX::b, RDF::Graph::Memory.new.new_blank_node('x'))
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
##== include? ==##
|
30
|
+
def test_include_should_accept_spo
|
31
|
+
@graph.add(RDF::Triple.new(EX::a, EX::b, EX::c))
|
32
|
+
assert @graph.include?(EX::a, EX::b, EX::c)
|
33
|
+
end
|
34
|
+
|
35
|
+
##== delete ==##
|
36
|
+
def test_delete_should_remove_triple
|
37
|
+
@graph.add(RDF::Triple.new(EX::a, EX::b, EX::c))
|
38
|
+
@graph.delete(RDF::Triple.new(EX::a, EX::b, EX::c))
|
39
|
+
assert !@graph.include?(RDF::Triple.new(EX::a, EX::b, EX::c))
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_delete_should_remove_spo
|
43
|
+
@graph.add(RDF::Triple.new(EX::a, EX::b, EX::c))
|
44
|
+
@graph.delete(EX::a, EX::b, EX::c)
|
45
|
+
assert !@graph.include?(RDF::Triple.new(EX::a, EX::b, EX::c))
|
46
|
+
end
|
47
|
+
|
48
|
+
##== empty? ==##
|
49
|
+
def test_empty_should_be_true_for_empty_graph
|
50
|
+
assert @graph.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_empty_should_not_be_true_for_non_empty_graph
|
54
|
+
@graph.add(RDF::Triple.new(EX::a, EX::b, EX::c))
|
55
|
+
assert !@graph.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
##== new_blank_node ==##
|
59
|
+
def test_new_blank_node_should_be_a_blank_node
|
60
|
+
assert RDF::BlankNode?(@graph.new_blank_node('test'))
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_new_blank_node_should_set_graph
|
64
|
+
assert_equal @graph, @graph.new_blank_node('test').graph
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_new_blank_node_should_set_name
|
68
|
+
assert_equal 'test', @graph.new_blank_node('test').name
|
69
|
+
end
|
70
|
+
|
71
|
+
##== each ==##
|
72
|
+
def test_each_should_iterate_all_nodes
|
73
|
+
triples = [RDF::Triple.new(EX::a, EX::b, EX::c),
|
74
|
+
RDF::Triple.new(EX::d, EX::e, EX::f),
|
75
|
+
RDF::Triple.new(EX::g, EX::h, EX::i)]
|
76
|
+
@graph.add(triples[0])
|
77
|
+
@graph.add(triples[1])
|
78
|
+
@graph.add(triples[2])
|
79
|
+
|
80
|
+
@graph.each do |t|
|
81
|
+
assert triples.delete(t)
|
82
|
+
end
|
83
|
+
assert_equal [], triples
|
84
|
+
end
|
85
|
+
|
86
|
+
##== size ==##
|
87
|
+
def test_size_should_be_zero_for_empty_graph
|
88
|
+
assert_equal 0, @graph.size
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_size_should_be_number_of_triples
|
92
|
+
@graph.add(EX::a, EX::b, EX::c)
|
93
|
+
@graph.add(EX::d, EX::e, EX::f)
|
94
|
+
assert 2, @graph.size
|
95
|
+
end
|
96
|
+
|
97
|
+
##== merge ==##
|
98
|
+
def test_merge_should_copy_triples
|
99
|
+
new_graph = RDF::Graph::Memory.new
|
100
|
+
new_graph.add(EX::a, EX::b, new_graph.new_blank_node('x'))
|
101
|
+
@graph.merge(new_graph)
|
102
|
+
assert @graph.execute(RDF::Query.new.where(EX::a, EX::b, :a)).success?
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_merge_should_associate_blank_nodes_with_new_graph
|
106
|
+
new_graph = RDF::Graph::Memory.new
|
107
|
+
x = new_graph.new_blank_node('x')
|
108
|
+
new_graph.add(EX::a, EX::b, x)
|
109
|
+
@graph.merge(new_graph)
|
110
|
+
assert_equal @graph, @graph.execute(RDF::Query.new.where(EX::a, EX::b, :a)).bindings.to_a.first[:a].graph
|
111
|
+
end
|
112
|
+
|
113
|
+
##== execute ==##
|
114
|
+
def query_executer_test_case
|
115
|
+
@graph.add(EX::a, EX::b, EX::c)
|
116
|
+
@graph.add(EX::c, EX::e, EX::f)
|
117
|
+
q = RDF::Query.new
|
118
|
+
q.where(EX::a, EX::b, :a)
|
119
|
+
q.where(:a, EX::e, EX::f)
|
120
|
+
|
121
|
+
@graph.execute(q)
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_execute_should_return_result
|
125
|
+
assert query_executer_test_case.is_a?(RDF::Query::Result)
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_execute_should_succeed
|
129
|
+
assert query_executer_test_case.success?
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_execute_should_assign_variable
|
133
|
+
assert_equal EX::c, query_executer_test_case.bindings.to_a.first[:a]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module RDF
|
2
|
+
# An RDF namespace that can be used to generate URI references easily.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# RDF::Namespace.register(:ex, 'http://example.com/')
|
7
|
+
# EX::Test #=> 'http://example.com/Test'
|
8
|
+
# EX::test #=> 'http://example.com/test'
|
9
|
+
module Namespace
|
10
|
+
@@prefixes = {}
|
11
|
+
@@bases = {}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# Returns an array of all registered prefixes.
|
15
|
+
def prefixes
|
16
|
+
@@prefixes.keys
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns true if the specified +prefix+ is registered.
|
20
|
+
def registered?(prefix)
|
21
|
+
@@prefixes.key?(prefix)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Registers +prefix+ as a nickname for +base+, and either finds an existing module,
|
25
|
+
# or creates a new module named +prefix+ that can be used for expansions.
|
26
|
+
def register(prefix, base)
|
27
|
+
raise ArgumentError, "prefix cannot be nil" if prefix.to_s.empty?
|
28
|
+
|
29
|
+
prefix, base = prefix.to_s.to_sym, base.to_s
|
30
|
+
|
31
|
+
raise ArgumentError, "base is invalid it should end with '/' or '#'" unless ['/', '#'].include?(base[-1,1])
|
32
|
+
if @@prefixes.has_key?(prefix)
|
33
|
+
$stderr.puts "Warning: Namespace #{prefix} is already defined"
|
34
|
+
return find_or_create_module(prefix)
|
35
|
+
end
|
36
|
+
|
37
|
+
@@prefixes[prefix] = base
|
38
|
+
@@bases[base] = prefix
|
39
|
+
|
40
|
+
mod = find_or_create_module(prefix)
|
41
|
+
class << mod
|
42
|
+
def method_missing(sym, *a, &b)
|
43
|
+
raise ArgumentError, "Unexpected arguments for Namespace.expand" if a.size > 0
|
44
|
+
RDF::UriNode.new(Namespace.expand(self, sym))
|
45
|
+
end
|
46
|
+
|
47
|
+
def const_missing(sym)
|
48
|
+
RDF::UriNode.new(Namespace.expand(self, sym))
|
49
|
+
end
|
50
|
+
|
51
|
+
[:type, :name, :id].each{|a| private(a)}
|
52
|
+
end
|
53
|
+
mod
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the string resulting from looking up the prefix and appending the name
|
57
|
+
def expand(prefix, name)
|
58
|
+
prefix, name = prefix.to_s.downcase.to_sym, name.to_s
|
59
|
+
@@prefixes[prefix] + name
|
60
|
+
end
|
61
|
+
|
62
|
+
# Finds the last '/' or '#' and uses it to split the uri into base and name parts.
|
63
|
+
def split(uri)
|
64
|
+
uri = uri.to_s
|
65
|
+
i = uri.rindex(/\/|#/) + 1
|
66
|
+
[uri[0...i].to_s, uri[i..-1].to_s]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the assigned prefix name for a uri, if one exists
|
70
|
+
def prefix(uri)
|
71
|
+
@@bases[split(uri).first]
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def find_or_create_module(name)
|
76
|
+
name = name.to_s.upcase
|
77
|
+
|
78
|
+
unless Object.const_defined?(name)
|
79
|
+
Object.const_set(name, Module.new)
|
80
|
+
end
|
81
|
+
Object.const_get(name)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Namespace.register(:rdf, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#')
|
87
|
+
Namespace.register(:rdfs, 'http://www.w3.org/2000/01/rdf-schema#')
|
88
|
+
Namespace.register(:owl, 'http://www.w3.org/2002/07/owl#')
|
89
|
+
Namespace.register(:xsd, 'http://www.w3.org/2001/XMLSchema#')
|
90
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module RDF
|
2
|
+
# A plain literal node.
|
3
|
+
class PlainLiteralNode
|
4
|
+
# the lexical form of this LiteralNode.
|
5
|
+
attr_reader :lexical_form
|
6
|
+
# the language tag (if any) of this LiteralNode.
|
7
|
+
attr_reader :language_tag
|
8
|
+
|
9
|
+
# Creates a new literal node with the specified +lexical_form+ and +language_tag+.
|
10
|
+
def initialize(lexical_form, language_tag = nil)
|
11
|
+
@lexical_form = lexical_form
|
12
|
+
@language_tag = language_tag
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns true if +o+ is a PlainLiteralNode with the same +lexical_form+ and
|
16
|
+
# +language_tag+ (if any).
|
17
|
+
def ==(o)
|
18
|
+
if RDF::PlainLiteralNode?(o)
|
19
|
+
lexical_form == o.lexical_form &&
|
20
|
+
language_tag.to_s.downcase == o.language_tag.to_s.downcase
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
alias :eql? :==
|
25
|
+
|
26
|
+
# Returns a hash value for this PlainLiteralNode.
|
27
|
+
def hash
|
28
|
+
[-1025818701, lexical_form.hash, language_tag.to_s.downcase.hash].hash
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the NTriples representation of this PlainLiteralNode.
|
32
|
+
def to_s
|
33
|
+
Format::NTriples.export_node(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RDF
|
2
|
+
class Query
|
3
|
+
# A single binding of variables in an Graph.
|
4
|
+
class Binding
|
5
|
+
# Creates a new instange of Binding initializing it with +hash+, or an empty Hash if +hash+ is nil.
|
6
|
+
def initialize(hash = nil)
|
7
|
+
@hash = hash || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Copy constructor that creates a deep copy of this binding.
|
11
|
+
def initialize_copy(o)
|
12
|
+
@hash = o.instance_variable_get('@hash').clone
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns true if +key+ is bound in this binding.
|
16
|
+
def bound?(key)
|
17
|
+
@hash.key?(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the value bound to +key+ if +key+ is bound in this binding, or +key+ otherwise.
|
21
|
+
def [](key)
|
22
|
+
@hash.key?(key) ? @hash[key] : key
|
23
|
+
end
|
24
|
+
|
25
|
+
# Binds +key+ to +value+ in this binding.
|
26
|
+
def []=(key, value)
|
27
|
+
@hash[key] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns true if this binding is empty, false otherwise.
|
31
|
+
def empty?
|
32
|
+
@hash.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if +o+ is a Binding and it has the same bindings.
|
36
|
+
def ==(o)
|
37
|
+
if o.is_a?(Binding)
|
38
|
+
@hash.each_pair do |k,v|
|
39
|
+
return false unless o[k] == v
|
40
|
+
end
|
41
|
+
|
42
|
+
ohash = o.instance_variable_get('@hash')
|
43
|
+
ohash.each_pair do |k,v|
|
44
|
+
return false unless @hash[k] == v
|
45
|
+
end
|
46
|
+
|
47
|
+
true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
alias :eql? :==
|
52
|
+
|
53
|
+
# Returns a hash value for this binding.
|
54
|
+
def hash
|
55
|
+
h = -901662846
|
56
|
+
@hash.each_pair do |k,v|
|
57
|
+
h ^= (h << 5) + k.hash + (h >> 2)
|
58
|
+
h ^= (h << 5) + v.hash + (h >> 2)
|
59
|
+
end
|
60
|
+
h
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"{#{@hash.collect{|k,v| "#{k} => #{v}"}.join(", ")}}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RDF
|
2
|
+
class Query
|
3
|
+
# A mix-in module for graph implementations. If a Graph implementation
|
4
|
+
# cannot delegate the +execute+ method to the underlying triplestore, then
|
5
|
+
# this module can be mixed in to provide Query execution. The graph must
|
6
|
+
# only implement the +match+ method.
|
7
|
+
module Executer
|
8
|
+
# Returns the result of executing +query+ on this graph.
|
9
|
+
def execute(query)
|
10
|
+
result = nil
|
11
|
+
|
12
|
+
where_clause = query.where_clause
|
13
|
+
begin
|
14
|
+
s, p, o = where_clause.shift
|
15
|
+
result = match(s, p, o, result)
|
16
|
+
end while where_clause.any? && result.success?
|
17
|
+
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
# Performs a match of +s+, +p+, and +o+ and returns a set of bindings.
|
22
|
+
#
|
23
|
+
# If +s+, +p+, or +o+ are Ruby symbols, then they will be considered
|
24
|
+
# variables, and they will be bound to all possible nodes in the result.
|
25
|
+
#
|
26
|
+
# If +s+, +p+, or +o+ are BlankNodes that are unassociated with this
|
27
|
+
# graph, then they will be bound to all possible nodes in the result.
|
28
|
+
def match(s, p, o, result = nil)
|
29
|
+
if result.nil?
|
30
|
+
result = match_binding(s, p, o, RDF::Query::Binding.new)
|
31
|
+
result
|
32
|
+
else
|
33
|
+
result = result.bindings.inject(RDF::Query::Result.new) do |r, b|
|
34
|
+
r << match_binding(s, p, o, b)
|
35
|
+
r
|
36
|
+
end
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RDF
|
2
|
+
class Query
|
3
|
+
# A set of bindings for a graph.
|
4
|
+
class Result
|
5
|
+
def self.success!(*a)
|
6
|
+
r = Result.new(*a)
|
7
|
+
r.success!
|
8
|
+
r
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :bindings
|
12
|
+
|
13
|
+
# Creates an instance of Result with the specified bindings.
|
14
|
+
def initialize(*bindings)
|
15
|
+
@success = false
|
16
|
+
@bindings = Set.new((bindings || []).flatten.reject{|b| b.empty?})
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns true if the executed query failed.
|
20
|
+
def failure?
|
21
|
+
!@success
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if the executed query succeeded.
|
25
|
+
def success?
|
26
|
+
@success
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the success indicator to true.
|
30
|
+
def success!
|
31
|
+
@success = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# Merges the Result +o+ into this Result.
|
35
|
+
def <<(o)
|
36
|
+
raise ArgumentError, "Must be RDF::Query::Result" unless o.is_a?(Result)
|
37
|
+
if o.success?
|
38
|
+
if failure?
|
39
|
+
success!
|
40
|
+
bindings.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
o.bindings.each do |b|
|
44
|
+
bindings << b unless b.empty?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"{#{bindings.collect{|b| b.to_s}.join(', ')}}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/rdf/query.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module RDF
|
2
|
+
# An RDF graph query. This represents a complex query that joins several triples. To query for
|
3
|
+
# a simple triple, see the +match+ method on RDF::Graph::Memory.
|
4
|
+
#
|
5
|
+
# A Query is created then passed to the +execute+ method on a graph. In some graph
|
6
|
+
# implementations the execute method may delegate to the underlying triple store, but
|
7
|
+
# in other cases the +execute+ method may be mixed in from RDF::Query::Executer.
|
8
|
+
#
|
9
|
+
# At some point I'd like to steal the {query DSL}[http://projects.semwebcentral.org/cgi-bin/viewcvs.cgi/semitar/test/test_query.rb?rev=1.1.1.1&cvsroot=semitar&content-type=text/vnd.viewcvs-markup] from Semitar[http://semitar.projects.semwebcentral.org/].
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
# q = RDF::Query.new.select(:a, :c).
|
13
|
+
# where(:a, RDF::type, :b).
|
14
|
+
# and(:b, RDFS::subClassOf, RDF::Property).
|
15
|
+
# and(:b, RDFS::comment, :c)
|
16
|
+
# result = some_graph.execute(q)
|
17
|
+
class Query
|
18
|
+
class << self
|
19
|
+
alias :select :new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a new Query.
|
23
|
+
def initialize
|
24
|
+
@select_clause = []
|
25
|
+
@where_clause = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Adds items to the select clause of this query. When this query is executed, the results will include
|
29
|
+
# bindings for these variables.
|
30
|
+
def select(*a)
|
31
|
+
@select_clause = a
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the select clause for this query.
|
36
|
+
def select_clause
|
37
|
+
@select_clause.clone
|
38
|
+
end
|
39
|
+
|
40
|
+
# Adds the triple +s p o+ to the where clause of this query. When the query is executed, the where clause
|
41
|
+
# will constrain the results.
|
42
|
+
def where(s, p, o)
|
43
|
+
@where_clause << [s, p, o]
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the where clause for this query.
|
48
|
+
def where_clause
|
49
|
+
@where_clause.clone
|
50
|
+
end
|
51
|
+
|
52
|
+
alias :and :where
|
53
|
+
end
|
54
|
+
end
|
data/lib/rdf/triple.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module RDF
|
2
|
+
# A triple.
|
3
|
+
class Triple
|
4
|
+
def self.construct(*a)
|
5
|
+
if a.size == 1
|
6
|
+
a[0] if a[0].is_a?(Triple)
|
7
|
+
elsif a.size == 3
|
8
|
+
Triple.new(*a)
|
9
|
+
else
|
10
|
+
raise ArgumentError, 'Expected triple or subject, predicate, and object'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# the subject of this triple
|
15
|
+
attr_reader :subject
|
16
|
+
# the predicate of this triple
|
17
|
+
attr_reader :predicate
|
18
|
+
# the object of this triple
|
19
|
+
attr_reader :object
|
20
|
+
|
21
|
+
# Creates a new triple with the specified +subject+, +predicate+, and +object+.
|
22
|
+
#
|
23
|
+
# Raises:
|
24
|
+
# ArgumentError:: if any of the parameters is nil.
|
25
|
+
# LiteralNodeSubjectError:: if +subject+ is a LiteralNode.
|
26
|
+
# LiteralNodePredicateError:: if +predicate+ is a LiteralNode.
|
27
|
+
# BlankNodePredicateError:: if +predicate+ is a BlankNode.
|
28
|
+
def initialize(subject, predicate, object)
|
29
|
+
@subject = subject
|
30
|
+
@predicate = predicate
|
31
|
+
@object = object
|
32
|
+
|
33
|
+
raise ArgumentError, 'subject must be a node' unless RDF::Node?(@subject)
|
34
|
+
raise ArgumentError, 'predicate must be a node' unless RDF::Node?(@predicate)
|
35
|
+
raise ArgumentError, 'object must be a node' unless RDF::Node?(@object)
|
36
|
+
|
37
|
+
raise RDF::InvalidSubjectError, "#{@subject} must be a blank or uri node" unless RDF::BlankNode?(@subject) || RDF::UriNode?(@subject)
|
38
|
+
raise RDF::InvalidPredicateError, "#{@predicate} must be a uri node" unless RDF::UriNode?(@predicate)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns true if +o+ is a Triple with the same +subject+, +object+, and +predicate+.
|
42
|
+
def ==(o)
|
43
|
+
if RDF::Triple?(o)
|
44
|
+
subject == o.subject &&
|
45
|
+
predicate == o.predicate &&
|
46
|
+
object == o.object
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias :eql? :==
|
51
|
+
|
52
|
+
# Returns a hash value for this Triple.
|
53
|
+
def hash
|
54
|
+
[-78640401, subject.hash, predicate.hash, object.hash].hash
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
"#{subject} #{predicate} #{object}."
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|