rdf-n3 3.1.1 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +148 -69
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/lib/rdf/n3.rb +8 -8
- data/lib/rdf/n3/algebra.rb +147 -68
- data/lib/rdf/n3/algebra/builtin.rb +79 -0
- data/lib/rdf/n3/algebra/formula.rb +355 -94
- data/lib/rdf/n3/algebra/list/append.rb +33 -4
- data/lib/rdf/n3/algebra/list/first.rb +24 -0
- data/lib/rdf/n3/algebra/list/in.rb +42 -3
- data/lib/rdf/n3/algebra/list/last.rb +17 -4
- data/lib/rdf/n3/algebra/list/length.rb +24 -0
- data/lib/rdf/n3/algebra/list/member.rb +39 -2
- data/lib/rdf/n3/algebra/list_operator.rb +83 -0
- data/lib/rdf/n3/algebra/log/conclusion.rb +57 -1
- data/lib/rdf/n3/algebra/log/conjunction.rb +28 -1
- data/lib/rdf/n3/algebra/log/content.rb +34 -0
- data/lib/rdf/n3/algebra/log/equal_to.rb +34 -0
- data/lib/rdf/n3/algebra/log/implies.rb +55 -30
- data/lib/rdf/n3/algebra/log/includes.rb +58 -1
- data/lib/rdf/n3/algebra/log/n3_string.rb +34 -0
- data/lib/rdf/n3/algebra/log/not_equal_to.rb +23 -0
- data/lib/rdf/n3/algebra/log/not_includes.rb +27 -0
- data/lib/rdf/n3/algebra/log/output_string.rb +40 -0
- data/lib/rdf/n3/algebra/log/parsed_as_n3.rb +36 -0
- data/lib/rdf/n3/algebra/log/semantics.rb +40 -0
- data/lib/rdf/n3/algebra/math/absolute_value.rb +36 -0
- data/lib/rdf/n3/algebra/math/acos.rb +26 -0
- data/lib/rdf/n3/algebra/math/acosh.rb +26 -0
- data/lib/rdf/n3/algebra/math/asin.rb +26 -0
- data/lib/rdf/n3/algebra/math/asinh.rb +26 -0
- data/lib/rdf/n3/algebra/math/atan.rb +26 -0
- data/lib/rdf/n3/algebra/math/atanh.rb +26 -0
- data/lib/rdf/n3/algebra/math/ceiling.rb +28 -0
- data/lib/rdf/n3/algebra/math/cos.rb +40 -0
- data/lib/rdf/n3/algebra/math/cosh.rb +38 -0
- data/lib/rdf/n3/algebra/math/difference.rb +34 -3
- data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
- data/lib/rdf/n3/algebra/math/exponentiation.rb +29 -3
- data/lib/rdf/n3/algebra/math/floor.rb +28 -0
- data/lib/rdf/n3/algebra/math/greater_than.rb +41 -0
- data/lib/rdf/n3/algebra/math/less_than.rb +41 -0
- data/lib/rdf/n3/algebra/math/negation.rb +31 -2
- data/lib/rdf/n3/algebra/math/not_equal_to.rb +25 -0
- data/lib/rdf/n3/algebra/math/not_greater_than.rb +25 -0
- data/lib/rdf/n3/algebra/math/not_less_than.rb +25 -0
- data/lib/rdf/n3/algebra/math/product.rb +14 -3
- data/lib/rdf/n3/algebra/math/quotient.rb +30 -3
- data/lib/rdf/n3/algebra/math/remainder.rb +29 -3
- data/lib/rdf/n3/algebra/math/rounded.rb +20 -3
- data/lib/rdf/n3/algebra/math/sin.rb +40 -0
- data/lib/rdf/n3/algebra/math/sinh.rb +38 -0
- data/lib/rdf/n3/algebra/math/sum.rb +35 -4
- data/lib/rdf/n3/algebra/math/tan.rb +40 -0
- data/lib/rdf/n3/algebra/math/tanh.rb +38 -0
- data/lib/rdf/n3/algebra/not_implemented.rb +13 -0
- data/lib/rdf/n3/algebra/resource_operator.rb +123 -0
- data/lib/rdf/n3/algebra/str/concatenation.rb +21 -3
- data/lib/rdf/n3/algebra/str/contains.rb +28 -4
- data/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +33 -0
- data/lib/rdf/n3/algebra/str/ends_with.rb +33 -0
- data/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +34 -0
- data/lib/rdf/n3/algebra/str/format.rb +12 -4
- data/lib/rdf/n3/algebra/str/greater_than.rb +38 -0
- data/lib/rdf/n3/algebra/str/less_than.rb +33 -0
- data/lib/rdf/n3/algebra/str/matches.rb +33 -5
- data/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +17 -0
- data/lib/rdf/n3/algebra/str/not_greater_than.rb +17 -0
- data/lib/rdf/n3/algebra/str/not_less_than.rb +17 -0
- data/lib/rdf/n3/algebra/str/not_matches.rb +18 -0
- data/lib/rdf/n3/algebra/str/replace.rb +28 -5
- data/lib/rdf/n3/algebra/str/scrape.rb +31 -5
- data/lib/rdf/n3/algebra/str/starts_with.rb +33 -0
- data/lib/rdf/n3/algebra/time/day.rb +35 -0
- data/lib/rdf/n3/algebra/time/day_of_week.rb +27 -0
- data/lib/rdf/n3/algebra/time/gm_time.rb +29 -0
- data/lib/rdf/n3/algebra/time/hour.rb +35 -0
- data/lib/rdf/n3/algebra/time/in_seconds.rb +59 -0
- data/lib/rdf/n3/algebra/time/local_time.rb +29 -0
- data/lib/rdf/n3/algebra/time/minute.rb +35 -0
- data/lib/rdf/n3/algebra/time/month.rb +35 -0
- data/lib/rdf/n3/algebra/time/second.rb +35 -0
- data/lib/rdf/n3/algebra/time/timezone.rb +36 -0
- data/lib/rdf/n3/algebra/time/year.rb +29 -0
- data/lib/rdf/n3/extensions.rb +180 -21
- data/lib/rdf/n3/format.rb +65 -0
- data/lib/rdf/n3/list.rb +630 -0
- data/lib/rdf/n3/reader.rb +762 -485
- data/lib/rdf/n3/reasoner.rb +57 -68
- data/lib/rdf/n3/refinements.rb +178 -0
- data/lib/rdf/n3/repository.rb +332 -0
- data/lib/rdf/n3/terminals.rb +80 -0
- data/lib/rdf/n3/vocab.rb +35 -7
- data/lib/rdf/n3/writer.rb +208 -148
- metadata +110 -52
- data/AUTHORS +0 -1
- data/History.markdown +0 -99
- data/lib/rdf/n3/algebra/log/equalTo.rb +0 -7
- data/lib/rdf/n3/algebra/log/notEqualTo.rb +0 -7
- data/lib/rdf/n3/algebra/log/notIncludes.rb +0 -12
- data/lib/rdf/n3/algebra/log/outputString.rb +0 -7
- data/lib/rdf/n3/algebra/math/absoluteValue.rb +0 -9
- data/lib/rdf/n3/algebra/math/equalTo.rb +0 -9
- data/lib/rdf/n3/algebra/math/greaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/math/integerQuotient.rb +0 -9
- data/lib/rdf/n3/algebra/math/lessThan.rb +0 -9
- data/lib/rdf/n3/algebra/math/memberCount.rb +0 -9
- data/lib/rdf/n3/algebra/math/notEqualTo.rb +0 -9
- data/lib/rdf/n3/algebra/math/notGreaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/math/notLessThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +0 -9
- data/lib/rdf/n3/algebra/str/endsWith.rb +0 -9
- data/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +0 -9
- data/lib/rdf/n3/algebra/str/greaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/lessThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +0 -9
- data/lib/rdf/n3/algebra/str/notGreaterThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/notLessThan.rb +0 -9
- data/lib/rdf/n3/algebra/str/notMatches.rb +0 -9
- data/lib/rdf/n3/algebra/str/startsWith.rb +0 -56
- data/lib/rdf/n3/patches/array_hacks.rb +0 -53
- data/lib/rdf/n3/reader/meta.rb +0 -641
- data/lib/rdf/n3/reader/parser.rb +0 -239
data/lib/rdf/n3/reasoner.rb
CHANGED
@@ -11,13 +11,13 @@ module RDF::N3
|
|
11
11
|
include RDF::Mutable
|
12
12
|
include RDF::Util::Logger
|
13
13
|
|
14
|
-
# The top-level parsed formula
|
14
|
+
# The top-level parsed formula, including builtins and variables.
|
15
15
|
# @return [RDF::N3::Algebra::Formula]
|
16
16
|
attr_reader :formula
|
17
17
|
|
18
18
|
# Opens a Notation-3 file, and parses it to initialize the reasoner
|
19
19
|
#
|
20
|
-
# @param [String, #to_s]
|
20
|
+
# @param [String, #to_s] file
|
21
21
|
# @yield [reasoner] `self`
|
22
22
|
# @yieldparam [RDF::N3::Reasoner] reasoner
|
23
23
|
# @yieldreturn [void] ignored
|
@@ -50,7 +50,8 @@ module RDF::N3
|
|
50
50
|
# RDF::N3::Reader.open("rules.n3") {|r| reasoner << r}
|
51
51
|
# reasoner.each_triple {}
|
52
52
|
#
|
53
|
-
# @param [RDF::
|
53
|
+
# @param [RDF::Mutable] input (nil)
|
54
|
+
# Input should be parsed N3 using native lists (see `:list_terms` option to {RDF::N3::Reader#initialize})
|
54
55
|
# @param [Hash{Symbol => Object}] options
|
55
56
|
# @option options [#to_s] :base_uri (nil)
|
56
57
|
# the base URI to use when resolving relative URIs (for acessing intermediate parser productions)
|
@@ -59,14 +60,16 @@ module RDF::N3
|
|
59
60
|
# @yieldreturn [void] ignored
|
60
61
|
# @return [RDF::N3::Reasoner]
|
61
62
|
def initialize(input, **options, &block)
|
62
|
-
@options = options
|
63
|
+
@options = options.merge(strings: {}) # for --strings and log:outputString
|
63
64
|
@mutable = case input
|
64
65
|
when RDF::Mutable then input
|
65
|
-
when RDF::Enumerable then RDF::Repository.new {|r| r << input}
|
66
|
-
else RDF::Repository.new
|
66
|
+
when RDF::Enumerable then RDF::N3::Repository.new {|r| r << input}
|
67
|
+
else RDF::N3::Repository.new
|
67
68
|
end
|
68
69
|
|
69
|
-
|
70
|
+
@formula = input if input.is_a?(RDF::N3::Algebra::Formula)
|
71
|
+
|
72
|
+
log_debug("reasoner: expression") {SXP::Generator.string(formula.to_sxp_bin)}
|
70
73
|
|
71
74
|
if block_given?
|
72
75
|
case block.arity
|
@@ -79,7 +82,7 @@ module RDF::N3
|
|
79
82
|
##
|
80
83
|
# Returns a copy of this reasoner
|
81
84
|
def dup
|
82
|
-
repo = RDF::Repository.new {|r| r << @mutable}
|
85
|
+
repo = RDF::N3::Repository.new {|r| r << @mutable}
|
83
86
|
self.class.new(repo) do |reasoner|
|
84
87
|
reasoner.instance_variable_set(:@options, @options.dup)
|
85
88
|
reasoner.instance_variable_set(:@formula, @formula.dup) if @formula
|
@@ -92,6 +95,7 @@ module RDF::N3
|
|
92
95
|
# @param [RDF::Statement] statement
|
93
96
|
# @return [void]
|
94
97
|
def insert_statement(statement)
|
98
|
+
@formula = nil
|
95
99
|
@mutable.insert_statement(statement)
|
96
100
|
end
|
97
101
|
|
@@ -100,6 +104,7 @@ module RDF::N3
|
|
100
104
|
#
|
101
105
|
# @param [Hash{Symbol => Object}] options
|
102
106
|
# @option options [Boolean] :apply
|
107
|
+
# @option options [Boolean] :rules
|
103
108
|
# @option options [Boolean] :think
|
104
109
|
# @yield [statement]
|
105
110
|
# @yieldparam [RDF::Statement] statement
|
@@ -107,29 +112,38 @@ module RDF::N3
|
|
107
112
|
def execute(**options, &block)
|
108
113
|
@options[:logger] = options[:logger] if options.has_key?(:logger)
|
109
114
|
|
115
|
+
# The knowledge base is the non-variable portions of formula
|
116
|
+
knowledge_base = RDF::N3::Repository.new {|r| r << formula}
|
117
|
+
log_debug("reasoner: knowledge_base") {SXP::Generator.string(knowledge_base.statements.to_sxp_bin)}
|
118
|
+
|
110
119
|
# If thinking, continuously execute until results stop growing
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
120
|
+
count = -1
|
121
|
+
log_info("reasoner: start") { "count: #{count}"}
|
122
|
+
solutions = RDF::Query::Solutions(RDF::Query::Solution.new)
|
123
|
+
while knowledge_base.count > count
|
124
|
+
log_info("reasoner: do") { "count: #{count}"}
|
125
|
+
count = knowledge_base.count
|
126
|
+
log_depth {formula.execute(knowledge_base, solutions: solutions, **options)}
|
127
|
+
knowledge_base << formula
|
128
|
+
solutions = RDF::Query::Solutions(RDF::Query::Solution.new) if solutions.empty?
|
129
|
+
log_debug("reasoner: solutions") {SXP::Generator.string solutions.to_sxp_bin}
|
130
|
+
log_debug("reasoner: datastore") {SXP::Generator.string knowledge_base.statements.to_sxp_bin}
|
131
|
+
log_info("reasoner: inferred") {SXP::Generator.string knowledge_base.statements.select(&:inferred?).to_sxp_bin}
|
132
|
+
log_info("reasoner: formula") do
|
133
|
+
SXP::Generator.string RDF::N3::Algebra::Formula.from_enumerable(knowledge_base).to_sxp_bin
|
134
|
+
end
|
135
|
+
@formula = nil # cause formula to be re-calculated from knowledge-base
|
136
|
+
unless options[:think]
|
137
|
+
count = knowledge_base.count
|
138
|
+
break
|
119
139
|
end
|
120
|
-
log_info("reasoner: think end") { "count: #{count}"}
|
121
|
-
else
|
122
|
-
# Run one iteration
|
123
|
-
log_info("reasoner: apply start") { "count: #{count}"}
|
124
|
-
dataset = RDF::Graph.new << @mutable.project_graph(nil)
|
125
|
-
log_depth {formula.execute(dataset, **options)}
|
126
|
-
@mutable << formula
|
127
|
-
log_info("reasoner: apply end") { "count: #{count}"}
|
128
140
|
end
|
141
|
+
log_info("reasoner: end") { "count: #{count}"}
|
129
142
|
|
130
|
-
|
143
|
+
# Add updates back to mutable, containg builtins and variables.
|
144
|
+
@mutable << knowledge_base
|
131
145
|
|
132
|
-
|
146
|
+
each(&block) if block_given?
|
133
147
|
self
|
134
148
|
end
|
135
149
|
alias_method :reason!, :execute
|
@@ -137,7 +151,7 @@ module RDF::N3
|
|
137
151
|
##
|
138
152
|
# Reason with results in a duplicate datastore
|
139
153
|
#
|
140
|
-
# @see
|
154
|
+
# @see execute
|
141
155
|
def reason(**options, &block)
|
142
156
|
self.dup.reason!(**options, &block)
|
143
157
|
end
|
@@ -180,7 +194,7 @@ module RDF::N3
|
|
180
194
|
alias_method :each_datum, :data
|
181
195
|
|
182
196
|
##
|
183
|
-
# Returns an enumerator for {#
|
197
|
+
# Returns an enumerator for {#data}.
|
184
198
|
# FIXME: enum_for doesn't seem to be working properly
|
185
199
|
# in JRuby 1.7, so specs are marked pending
|
186
200
|
#
|
@@ -234,49 +248,24 @@ module RDF::N3
|
|
234
248
|
end
|
235
249
|
|
236
250
|
##
|
237
|
-
# Returns the
|
251
|
+
# Returns the concatenated strings from log:outputString
|
252
|
+
#
|
253
|
+
# @return [String]
|
254
|
+
def strings
|
255
|
+
@options[:strings].
|
256
|
+
sort_by {|k, v| k}.
|
257
|
+
map {|(k,v)| v.join("")}.
|
258
|
+
join("")
|
259
|
+
end
|
260
|
+
|
261
|
+
##
|
262
|
+
# Returns the top-level formula for this file.
|
263
|
+
#
|
264
|
+
# Transforms an RDF dataset into a recursive formula structure.
|
238
265
|
#
|
239
266
|
# @return [RDF::N3::Algebra::Formula]
|
240
267
|
def formula
|
241
|
-
|
242
|
-
require 'sparql' unless defined?(:SPARQL)
|
243
|
-
|
244
|
-
@formula ||= begin
|
245
|
-
# Create formulae from statement graph_names
|
246
|
-
formulae = (@mutable.graph_names.unshift(nil)).inject({}) do |memo, graph_name|
|
247
|
-
memo.merge(graph_name => Algebra::Formula.new(graph_name: graph_name, **@options))
|
248
|
-
end
|
249
|
-
|
250
|
-
# Add patterns to appropiate formula based on graph_name,
|
251
|
-
# and replace subject and object bnodes which identify
|
252
|
-
# named graphs with those formula
|
253
|
-
@mutable.each_statement do |statement|
|
254
|
-
pattern = statement.variable? ? RDF::Query::Pattern.from(statement) : statement
|
255
|
-
|
256
|
-
# A graph name indicates a formula.
|
257
|
-
form = formulae[pattern.graph_name]
|
258
|
-
|
259
|
-
# Formulae may be the subject or object of a known operator
|
260
|
-
if klass = Algebra.for(pattern.predicate)
|
261
|
-
fs = formulae.fetch(pattern.subject, pattern.subject)
|
262
|
-
fo = formulae.fetch(pattern.object, pattern.object)
|
263
|
-
form.operands << klass.new(fs, fo, parent: form, **@options)
|
264
|
-
else
|
265
|
-
# Add formulae as direct operators
|
266
|
-
if formulae.has_key?(pattern.subject)
|
267
|
-
form.operands << formulae[pattern.subject]
|
268
|
-
end
|
269
|
-
if formulae.has_key?(pattern.object)
|
270
|
-
form.operands << formulae[pattern.object]
|
271
|
-
end
|
272
|
-
pattern.graph_name = nil
|
273
|
-
form.operands << pattern
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
# Formula is that without a graph name
|
278
|
-
formulae[nil]
|
279
|
-
end
|
268
|
+
@formula ||= RDF::N3::Algebra::Formula.from_enumerable(@mutable, **@options)
|
280
269
|
end
|
281
270
|
|
282
271
|
##
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Refinements on core RDF class behavior for RDF::N3.
|
2
|
+
module RDF::N3::Refinements
|
3
|
+
# @!parse
|
4
|
+
# # Refinements on RDF::Term
|
5
|
+
# module RDF::Term
|
6
|
+
# ##
|
7
|
+
# # As a term is constant, this returns itself.
|
8
|
+
# #
|
9
|
+
# # @param [Hash{Symbol => RDF::Term}] bindings
|
10
|
+
# # a query solution containing zero or more variable bindings
|
11
|
+
# # @param [Hash{Symbol => Object}] options ({})
|
12
|
+
# # options passed from query
|
13
|
+
# # @return [RDF::Term]
|
14
|
+
# # @see SPARQL::Algebra::Expression.evaluate
|
15
|
+
# def evaluate(bindings, formulae: nil, **options); end
|
16
|
+
# end
|
17
|
+
refine ::RDF::Term do
|
18
|
+
def evaluate(bindings, formulae:, **options)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @!parse
|
24
|
+
# # Refinements on RDF::Node
|
25
|
+
# module RDF::Term
|
26
|
+
# ##
|
27
|
+
# # Blank node may refer to a formula.
|
28
|
+
# #
|
29
|
+
# # @param [Hash{Symbol => RDF::Term}] bindings
|
30
|
+
# # a query solution containing zero or more variable bindings
|
31
|
+
# # @param [Hash{Symbol => Object}] options ({})
|
32
|
+
# # options passed from query
|
33
|
+
# # @return [RDF::Node, RDF::N3::Algebra::Formula]
|
34
|
+
# # @see SPARQL::Algebra::Expression.evaluate
|
35
|
+
# def evaluate(bindings, formulae:, **options); end
|
36
|
+
# end
|
37
|
+
refine ::RDF::Node do
|
38
|
+
##
|
39
|
+
# @return [RDF::Node, RDF::N3::Algebra::Formula]
|
40
|
+
def evaluate(bindings, formulae:, **options)
|
41
|
+
node? ? formulae.fetch(self, self) : self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!parse
|
46
|
+
# # Refinements on RDF::Statement
|
47
|
+
# class ::RDF::Statement
|
48
|
+
# # Refines `valid?` to allow literal subjects and BNode predicates.
|
49
|
+
# # @return [Boolean]
|
50
|
+
# def valid?; end
|
51
|
+
#
|
52
|
+
# # Refines `invalid?` to allow literal subjects and BNode predicates.
|
53
|
+
# # @return [Boolean]
|
54
|
+
# def invalid?; end
|
55
|
+
#
|
56
|
+
# # Refines `validate!` to allow literal subjects and BNode predicates.
|
57
|
+
# # @return [RDF::Value] `self`
|
58
|
+
# # @raise [ArgumentError] if the value is invalid
|
59
|
+
# def validate!; end
|
60
|
+
#
|
61
|
+
# ##
|
62
|
+
# # As a statement is constant, this returns itself.
|
63
|
+
# #
|
64
|
+
# # @param [Hash{Symbol => RDF::Term}] bindings
|
65
|
+
# # a query solution containing zero or more variable bindings
|
66
|
+
# # @param [Hash{Symbol => Object}] options ({})
|
67
|
+
# # options passed from query
|
68
|
+
# # @return [RDF::Statement]
|
69
|
+
# # @see SPARQL::Algebra::Expression.evaluate
|
70
|
+
# def evaluate(bindings, formulae:, **options); end
|
71
|
+
# end
|
72
|
+
refine ::RDF::Statement do
|
73
|
+
##
|
74
|
+
# Override `valid?` terms as subjects and resources as predicates.
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
def valid?
|
78
|
+
has_subject? && subject.term? && subject.valid? &&
|
79
|
+
has_predicate? && predicate.term? && predicate.valid? &&
|
80
|
+
has_object? && object.term? && object.valid? &&
|
81
|
+
(has_graph? ? (graph_name.resource? && graph_name.valid?) : true)
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# @return [Boolean]
|
86
|
+
def invalid?
|
87
|
+
!valid?
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Default validate! implementation, overridden in concrete classes
|
92
|
+
# @return [RDF::Value] `self`
|
93
|
+
# @raise [ArgumentError] if the value is invalid
|
94
|
+
def validate!
|
95
|
+
raise ArgumentError, "#{self.inspect} is not valid" if invalid?
|
96
|
+
self
|
97
|
+
end
|
98
|
+
alias_method :validate, :validate!
|
99
|
+
|
100
|
+
##
|
101
|
+
# @return [RDF::Statement]
|
102
|
+
def evaluate(bindings, formulae:, **options)
|
103
|
+
self
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @!parse
|
108
|
+
# # Refinements on RDF::Query::Pattern
|
109
|
+
# class ::RDF::Query::Pattern
|
110
|
+
# # Refines `#valid?` to allow literal subjects and BNode predicates.
|
111
|
+
# # @return [Boolean]
|
112
|
+
# def valid?; end
|
113
|
+
#
|
114
|
+
# ##
|
115
|
+
# # Evaluates the pattern using the given variable `bindings` by cloning the pattern replacing variables with their bindings recursively. If the resulting pattern is constant, it is cast as a statement.
|
116
|
+
# #
|
117
|
+
# # @param [Hash{Symbol => RDF::Term}] bindings
|
118
|
+
# # a query solution containing zero or more variable bindings
|
119
|
+
# # @param [Hash{Symbol => Object}] options ({})
|
120
|
+
# # options passed from query
|
121
|
+
# # @return [RDF::Statement, RDF::N3::Algebra::Formula]
|
122
|
+
# # @see SPARQL::Algebra::Expression.evaluate
|
123
|
+
# def evaluate(bindings, formulae:, **options); end
|
124
|
+
# end
|
125
|
+
refine ::RDF::Query::Pattern do
|
126
|
+
##
|
127
|
+
# Is this pattern composed only of valid components?
|
128
|
+
#
|
129
|
+
# @return [Boolean] `true` or `false`
|
130
|
+
def valid?
|
131
|
+
(has_subject? ? (subject.term? || subject.variable?) && subject.valid? : true) &&
|
132
|
+
(has_predicate? ? (predicate.term? || predicate.variable?) && predicate.valid? : true) &&
|
133
|
+
(has_object? ? (object.term? || object.variable?) && object.valid? : true) &&
|
134
|
+
(has_graph? ? (graph_name.resource? || graph_name.variable?) && graph_name.valid? : true)
|
135
|
+
rescue NoMethodError
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [RDF::Statement, RDF::N3::Algebra::Formula]
|
140
|
+
def evaluate(bindings, formulae:, **options)
|
141
|
+
elements = self.to_quad.map do |term|
|
142
|
+
term.evaluate(bindings, formulae: formulae, **options)
|
143
|
+
end.compact.map do |term|
|
144
|
+
term.node? ? formulae.fetch(term, term) : term
|
145
|
+
end
|
146
|
+
|
147
|
+
self.class.from(elements)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# @!parse
|
152
|
+
# # Refinements on RDF::Query::Variable
|
153
|
+
# class RDF::Query::Variable
|
154
|
+
# ##
|
155
|
+
# # If variable is bound, replace with the bound value, otherwise, returns itself
|
156
|
+
# #
|
157
|
+
# # @param [Hash{Symbol => RDF::Term}] bindings
|
158
|
+
# # a query solution containing zero or more variable bindings
|
159
|
+
# # @param [Hash{Symbol => Object}] options ({})
|
160
|
+
# # options passed from query
|
161
|
+
# # @return [RDF::Term]
|
162
|
+
# # @see SPARQL::Algebra::Expression.evaluate
|
163
|
+
# def evaluate(bindings, formulae:, **options); end
|
164
|
+
# end
|
165
|
+
refine ::RDF::Query::Variable do
|
166
|
+
##
|
167
|
+
# @return [RDF::Term]
|
168
|
+
def evaluate(bindings, formulae:, **options)
|
169
|
+
value = bindings.has_key?(name) ? bindings[name] : self
|
170
|
+
value.node? ? formulae.fetch(value, value) : value
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
refine ::RDF::Graph do
|
175
|
+
# Allow a graph to be treated as a term in a statement.
|
176
|
+
include ::RDF::Term
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,332 @@
|
|
1
|
+
module RDF::N3
|
2
|
+
##
|
3
|
+
# Sub-class of RDF::Repository which allows for native lists in different positions.
|
4
|
+
class Repository < RDF::Repository
|
5
|
+
DEFAULT_GRAPH = false
|
6
|
+
|
7
|
+
##
|
8
|
+
# Initializes this repository instance.
|
9
|
+
#
|
10
|
+
# @param [URI, #to_s] uri (nil)
|
11
|
+
# @param [String, #to_s] title (nil)
|
12
|
+
# @param [Hash{Symbol => Object}] options
|
13
|
+
# @option options [Boolean] :with_graph_name (true)
|
14
|
+
# Indicates that the repository supports named graphs, otherwise,
|
15
|
+
# only the default graph is supported.
|
16
|
+
# @option options [Boolean] :with_validity (true)
|
17
|
+
# Indicates that the repository supports named validation.
|
18
|
+
# @option options [Boolean] :transaction_class (DEFAULT_TX_CLASS)
|
19
|
+
# Specifies the RDF::Transaction implementation to use in this Repository.
|
20
|
+
# @yield [repository]
|
21
|
+
# @yieldparam [Repository] repository
|
22
|
+
def initialize(uri: nil, title: nil, **options, &block)
|
23
|
+
@data = options.delete(:data) || {}
|
24
|
+
super do
|
25
|
+
if block_given?
|
26
|
+
case block.arity
|
27
|
+
when 1 then block.call(self)
|
28
|
+
else instance_eval(&block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Returns `true` if this respository supports the given `feature`.
|
36
|
+
#
|
37
|
+
# This repository supports list_terms.
|
38
|
+
def supports?(feature)
|
39
|
+
case feature.to_sym
|
40
|
+
when :list_terms then true
|
41
|
+
when :rdfstar then true
|
42
|
+
when :snapshots then false
|
43
|
+
else super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Creates a query from the statements in this repository, turning blank nodes into non-distinguished variables. This can be used to determine if this repository is logically a subset of another repository.
|
49
|
+
#
|
50
|
+
# @return [RDF::Query]
|
51
|
+
def to_query
|
52
|
+
RDF::Query.new do |query|
|
53
|
+
each do |statement|
|
54
|
+
query.pattern RDF::Query::Pattern.from(statement, ndvars: true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# @private
|
61
|
+
# @see RDF::Countable#count
|
62
|
+
def count
|
63
|
+
count = 0
|
64
|
+
@data.each do |_, ss|
|
65
|
+
ss.each do |_, ps|
|
66
|
+
ps.each { |_, os| count += os.size }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
count
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# @private
|
74
|
+
# @see RDF::Enumerable#has_graph?
|
75
|
+
def has_graph?(graph)
|
76
|
+
@data.has_key?(graph)
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# @private
|
81
|
+
# @see RDF::Enumerable#each_graph
|
82
|
+
def graph_names(options = nil, &block)
|
83
|
+
@data.keys.reject { |g| g == DEFAULT_GRAPH }.to_a
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# @private
|
88
|
+
# @see RDF::Enumerable#each_graph
|
89
|
+
def each_graph(&block)
|
90
|
+
if block_given?
|
91
|
+
@data.each_key do |gn|
|
92
|
+
yield RDF::Graph.new(graph_name: (gn == DEFAULT_GRAPH ? nil : gn), data: self)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
enum_graph
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# @private
|
100
|
+
# @see RDF::Enumerable#has_statement?
|
101
|
+
def has_statement?(statement)
|
102
|
+
has_statement_in?(@data, statement)
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# @private
|
107
|
+
# @see RDF::Enumerable#each_statement
|
108
|
+
def each_statement(&block)
|
109
|
+
if block_given?
|
110
|
+
@data.each do |g, ss|
|
111
|
+
ss.each do |s, ps|
|
112
|
+
ps.each do |p, os|
|
113
|
+
os.each do |o, object_options|
|
114
|
+
yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
enum_statement
|
121
|
+
end
|
122
|
+
alias_method :each, :each_statement
|
123
|
+
|
124
|
+
##
|
125
|
+
# Projects statements with lists expanded to first/rest chains
|
126
|
+
#
|
127
|
+
# @yield [RDF::Statement]
|
128
|
+
def each_expanded_statement(&block)
|
129
|
+
if block_given?
|
130
|
+
each_statement do |st|
|
131
|
+
if st.subject.list?
|
132
|
+
st.subject.each_statement(&block)
|
133
|
+
st.subject = st.subject.subject
|
134
|
+
end
|
135
|
+
if st.object.list?
|
136
|
+
st.object.each_statement(&block)
|
137
|
+
st.object = st.object.subject
|
138
|
+
end
|
139
|
+
block.call(st)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
enum_for(:each_expanded_statement) unless block_given?
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Returns the expanded statements for this repository
|
147
|
+
#
|
148
|
+
# @return [Array<RDF::Statement>]
|
149
|
+
def expanded_statements
|
150
|
+
each_expanded_statement.to_a
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# @see Mutable#apply_changeset
|
155
|
+
def apply_changeset(changeset)
|
156
|
+
data = @data
|
157
|
+
changeset.deletes.each do |del|
|
158
|
+
if del.constant?
|
159
|
+
data = delete_from(data, del)
|
160
|
+
else
|
161
|
+
# we need this condition to handle wildcard statements
|
162
|
+
query_pattern(del) { |stmt| data = delete_from(data, stmt) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
changeset.inserts.each { |ins| data = insert_to(data, ins) }
|
166
|
+
@data = data
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# @see RDF::Dataset#isolation_level
|
171
|
+
def isolation_level
|
172
|
+
:serializable
|
173
|
+
end
|
174
|
+
|
175
|
+
protected
|
176
|
+
|
177
|
+
##
|
178
|
+
# Match elements with `eql?`, not `==`
|
179
|
+
#
|
180
|
+
# `graph_name` of `false` matches default graph. Unbound variable matches
|
181
|
+
# non-false graph name.
|
182
|
+
#
|
183
|
+
# Matches terms which are native lists.
|
184
|
+
#
|
185
|
+
# @private
|
186
|
+
# @see RDF::Queryable#query_pattern
|
187
|
+
def query_pattern(pattern, **options, &block)
|
188
|
+
if block_given?
|
189
|
+
graph_name = pattern.graph_name
|
190
|
+
subject = pattern.subject
|
191
|
+
predicate = pattern.predicate
|
192
|
+
object = pattern.object
|
193
|
+
|
194
|
+
cs = @data.has_key?(graph_name) ? { graph_name => @data[graph_name] } : @data
|
195
|
+
|
196
|
+
cs.each do |c, ss|
|
197
|
+
next unless graph_name.nil? ||
|
198
|
+
graph_name == DEFAULT_GRAPH && !c ||
|
199
|
+
graph_name.eql?(c)
|
200
|
+
|
201
|
+
ss = if subject.nil? || subject.is_a?(RDF::Query::Variable)
|
202
|
+
ss
|
203
|
+
elsif subject.is_a?(RDF::N3::List)
|
204
|
+
# Match subjects which are eql lists
|
205
|
+
ss.keys.select {|s| s.list? && subject.eql?(s)}.inject({}) do |memo, li|
|
206
|
+
memo.merge(li => ss[li])
|
207
|
+
end
|
208
|
+
elsif subject.is_a?(RDF::Query::Pattern)
|
209
|
+
# Match subjects which are statements matching this pattern
|
210
|
+
ss.keys.select {|s| s.statement? && subject.eql?(s)}.inject({}) do |memo, st|
|
211
|
+
memo.merge(st => ss[st])
|
212
|
+
end
|
213
|
+
elsif ss.has_key?(subject)
|
214
|
+
{ subject => ss[subject] }
|
215
|
+
else
|
216
|
+
[]
|
217
|
+
end
|
218
|
+
ss.each do |s, ps|
|
219
|
+
ps = if predicate.nil? || predicate.is_a?(RDF::Query::Variable)
|
220
|
+
ps
|
221
|
+
elsif predicate.is_a?(RDF::N3::List)
|
222
|
+
# Match predicates which are eql lists
|
223
|
+
ps.keys.select {|p| p.list? && predicate.eql?(p)}.inject({}) do |memo, li|
|
224
|
+
memo.merge(li => ps[li])
|
225
|
+
end
|
226
|
+
elsif ps.has_key?(predicate)
|
227
|
+
{ predicate => ps[predicate] }
|
228
|
+
else
|
229
|
+
[]
|
230
|
+
end
|
231
|
+
ps.each do |p, os|
|
232
|
+
os.each do |o, object_options|
|
233
|
+
next unless object.nil? || object.eql?(o)
|
234
|
+
yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c))
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
enum_for(:query_pattern, pattern, **options)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# @private
|
246
|
+
# @see RDF::Mutable#insert
|
247
|
+
def insert_statement(statement)
|
248
|
+
@data = insert_to(@data, statement)
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# @private
|
253
|
+
# @see RDF::Mutable#delete
|
254
|
+
def delete_statement(statement)
|
255
|
+
@data = delete_from(@data, statement)
|
256
|
+
end
|
257
|
+
|
258
|
+
##
|
259
|
+
# @private
|
260
|
+
# @see RDF::Mutable#clear
|
261
|
+
def clear_statements
|
262
|
+
@data = @data.clear
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# @private
|
267
|
+
# @return [Hash]
|
268
|
+
def data
|
269
|
+
@data
|
270
|
+
end
|
271
|
+
|
272
|
+
##
|
273
|
+
# @private
|
274
|
+
# @return [Hash]
|
275
|
+
def data=(hash)
|
276
|
+
@data = hash
|
277
|
+
end
|
278
|
+
|
279
|
+
private
|
280
|
+
|
281
|
+
##
|
282
|
+
# @private
|
283
|
+
# @see #has_statement
|
284
|
+
def has_statement_in?(data, statement)
|
285
|
+
s, p, o, g = statement.to_quad
|
286
|
+
g ||= DEFAULT_GRAPH
|
287
|
+
|
288
|
+
data.has_key?(g) &&
|
289
|
+
data[g].has_key?(s) &&
|
290
|
+
data[g][s].has_key?(p) &&
|
291
|
+
data[g][s][p].has_key?(o)
|
292
|
+
end
|
293
|
+
|
294
|
+
##
|
295
|
+
# @private
|
296
|
+
# @return [Hash] a new, updated hash
|
297
|
+
def insert_to(data, statement)
|
298
|
+
raise ArgumentError, "Statement #{statement.inspect} is incomplete" if statement.incomplete?
|
299
|
+
|
300
|
+
s, p, o, c = statement.to_quad
|
301
|
+
c ||= DEFAULT_GRAPH
|
302
|
+
unless has_statement_in?(data, statement)
|
303
|
+
data = data.has_key?(c) ? data.dup : data.merge(c => {})
|
304
|
+
data[c] = data[c].has_key?(s) ? data[c].dup : data[c].merge(s => {})
|
305
|
+
data[c][s] = data[c][s].has_key?(p) ? data[c][s].dup : data[c][s].merge(p => {})
|
306
|
+
data[c][s][p] = data[c][s][p].merge(o => statement.options)
|
307
|
+
end
|
308
|
+
|
309
|
+
# If statement is inferred, make sure that it is marked as inferred in the dataset.
|
310
|
+
data[c][s][p][o][:inferred] = true if statement.options[:inferred]
|
311
|
+
|
312
|
+
data
|
313
|
+
end
|
314
|
+
|
315
|
+
##
|
316
|
+
# @private
|
317
|
+
# @return [Hash] a new, updated hash
|
318
|
+
def delete_from(data, statement)
|
319
|
+
if has_statement_in?(data, statement)
|
320
|
+
s, p, o, g = statement.to_quad
|
321
|
+
g = DEFAULT_GRAPH unless supports?(:graph_name)
|
322
|
+
g ||= DEFAULT_GRAPH
|
323
|
+
|
324
|
+
os = data[g][s][p].dup.delete_if {|k,v| k == o}
|
325
|
+
ps = os.empty? ? data[g][s].dup.delete_if {|k,v| k == p} : data[g][s].merge(p => os)
|
326
|
+
ss = ps.empty? ? data[g].dup.delete_if {|k,v| k == s} : data[g].merge(s => ps)
|
327
|
+
return ss.empty? ? data.dup.delete_if {|k,v| k == g} : data.merge(g => ss)
|
328
|
+
end
|
329
|
+
data
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|