rdf-n3 2.2.0 → 3.1.2
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.
- checksums.yaml +5 -5
- data/README.md +192 -69
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/lib/rdf/n3.rb +11 -8
- data/lib/rdf/n3/algebra.rb +204 -0
- data/lib/rdf/n3/algebra/builtin.rb +79 -0
- data/lib/rdf/n3/algebra/formula.rb +446 -0
- data/lib/rdf/n3/algebra/list/append.rb +42 -0
- data/lib/rdf/n3/algebra/list/first.rb +24 -0
- data/lib/rdf/n3/algebra/list/in.rb +48 -0
- data/lib/rdf/n3/algebra/list/last.rb +24 -0
- data/lib/rdf/n3/algebra/list/length.rb +24 -0
- data/lib/rdf/n3/algebra/list/member.rb +44 -0
- data/lib/rdf/n3/algebra/list_operator.rb +83 -0
- data/lib/rdf/n3/algebra/log/conclusion.rb +65 -0
- data/lib/rdf/n3/algebra/log/conjunction.rb +36 -0
- 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 +102 -0
- data/lib/rdf/n3/algebra/log/includes.rb +70 -0
- 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 +40 -0
- data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
- data/lib/rdf/n3/algebra/math/exponentiation.rb +35 -0
- 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 +38 -0
- 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 +20 -0
- data/lib/rdf/n3/algebra/math/quotient.rb +36 -0
- data/lib/rdf/n3/algebra/math/remainder.rb +35 -0
- data/lib/rdf/n3/algebra/math/rounded.rb +26 -0
- 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 +40 -0
- 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 +27 -0
- data/lib/rdf/n3/algebra/str/contains.rb +33 -0
- 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 +17 -0
- 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 +37 -0
- 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 +35 -0
- data/lib/rdf/n3/algebra/str/scrape.rb +35 -0
- 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 +221 -0
- data/lib/rdf/n3/format.rb +66 -1
- data/lib/rdf/n3/list.rb +630 -0
- data/lib/rdf/n3/reader.rb +834 -492
- data/lib/rdf/n3/reasoner.rb +282 -0
- 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 +36 -3
- data/lib/rdf/n3/writer.rb +476 -239
- metadata +187 -68
- data/AUTHORS +0 -1
- data/History.markdown +0 -99
- 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 -237
@@ -0,0 +1,35 @@
|
|
1
|
+
module RDF::N3::Algebra::Time
|
2
|
+
##
|
3
|
+
# For a date-time, its time:second is the seconds component.
|
4
|
+
#
|
5
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-seconds-from-dateTime
|
6
|
+
class Second < RDF::N3::Algebra::ResourceOperator
|
7
|
+
NAME = :timeSecond
|
8
|
+
URI = RDF::N3::Time.second
|
9
|
+
|
10
|
+
##
|
11
|
+
# The time:second operator takes string or dateTime and extracts the seconds component.
|
12
|
+
#
|
13
|
+
# @param [RDF::Term] resource
|
14
|
+
# @param [:subject, :object] position
|
15
|
+
# @return [RDF::Term]
|
16
|
+
# @see RDF::N3::ResourceOperator#evaluate
|
17
|
+
def resolve(resource, position:)
|
18
|
+
case position
|
19
|
+
when :subject
|
20
|
+
return nil unless resource.literal?
|
21
|
+
resource = resource.as_datetime
|
22
|
+
RDF::Literal(resource.object.strftime("%S").to_i)
|
23
|
+
when :object
|
24
|
+
return nil unless resource.literal? || resource.variable?
|
25
|
+
resource
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# There is no second unless it was specified in the lexical form
|
31
|
+
def valid?(subject, object)
|
32
|
+
subject.value.match?(%r(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module RDF::N3::Algebra::Time
|
2
|
+
##
|
3
|
+
# For a date-time, its time:timeZone is the trailing timezone offset part, e.g. "-05:00".
|
4
|
+
#
|
5
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-timezone-from-dateTime
|
6
|
+
class Timezone < RDF::N3::Algebra::ResourceOperator
|
7
|
+
NAME = :timeTimezone
|
8
|
+
URI = RDF::N3::Time.timeZone
|
9
|
+
|
10
|
+
##
|
11
|
+
# The time:timeZone operator takes string or dateTime and extracts the timeZone component.
|
12
|
+
#
|
13
|
+
# @param [RDF::Term] resource
|
14
|
+
# @param [:subject, :object] position
|
15
|
+
# @return [RDF::Term]
|
16
|
+
# @see RDF::N3::ResourceOperator#evaluate
|
17
|
+
def resolve(resource, position:)
|
18
|
+
case position
|
19
|
+
when :subject
|
20
|
+
return nil unless resource.literal?
|
21
|
+
resource = resource.as_datetime
|
22
|
+
RDF::Literal(resource.object.strftime("%Z"))
|
23
|
+
when :object
|
24
|
+
return nil unless resource.literal? || resource.variable?
|
25
|
+
resource
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# There is no timezone unless it was specified in the lexical form and is not "Z"
|
31
|
+
def valid?(subject, object)
|
32
|
+
md = subject.value.match(%r(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[\+-][\d-]+)))
|
33
|
+
md && md[1].to_s != 'Z'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RDF::N3::Algebra::Time
|
2
|
+
##
|
3
|
+
# For a date-time, its time:year is the year component.
|
4
|
+
#
|
5
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-year-from-dateTime
|
6
|
+
class Year < RDF::N3::Algebra::ResourceOperator
|
7
|
+
NAME = :timeYear
|
8
|
+
URI = RDF::N3::Time.year
|
9
|
+
|
10
|
+
##
|
11
|
+
# The time:year operator takes string or dateTime and extracts the year component.
|
12
|
+
#
|
13
|
+
# @param [RDF::Term] resource
|
14
|
+
# @param [:subject, :object] position
|
15
|
+
# @return [RDF::Term]
|
16
|
+
# @see RDF::N3::ResourceOperator#evaluate
|
17
|
+
def resolve(resource, position:)
|
18
|
+
case position
|
19
|
+
when :subject
|
20
|
+
return nil unless resource.literal?
|
21
|
+
resource = resource.as_datetime
|
22
|
+
RDF::Literal(resource.object.strftime("%Y").to_i)
|
23
|
+
when :object
|
24
|
+
return nil unless resource.literal? || resource.variable?
|
25
|
+
resource
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rdf'
|
3
|
+
require 'rdf/n3/terminals'
|
4
|
+
|
5
|
+
# Monkey-patch RDF::Enumerable to add `:existentials` and `:univerals` accessors
|
6
|
+
module RDF
|
7
|
+
module Enumerable
|
8
|
+
# Existential quantifiers defined on this enumerable
|
9
|
+
# @return [Array<RDF::Query::Variable>]
|
10
|
+
attr_accessor :existentials
|
11
|
+
|
12
|
+
# Universal quantifiers defined on this enumerable
|
13
|
+
# @return [Array<RDF::Query::Variable>]
|
14
|
+
attr_accessor :universals
|
15
|
+
end
|
16
|
+
|
17
|
+
class List
|
18
|
+
##
|
19
|
+
# A list is variable if any of its members are variable?
|
20
|
+
#
|
21
|
+
# @return [Boolean]
|
22
|
+
def variable?
|
23
|
+
to_a.any?(&:variable?)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Transform Statement into an SXP
|
27
|
+
# @return [Array]
|
28
|
+
def to_sxp_bin
|
29
|
+
to_a.to_sxp_bin
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Returns an S-Expression (SXP) representation
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
def to_sxp
|
37
|
+
to_a.to_sxp_bin.to_sxp
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Value
|
42
|
+
##
|
43
|
+
# Returns `true` if `self` is a {RDF::N3::Algebra::Formula}.
|
44
|
+
#
|
45
|
+
# @return [Boolean]
|
46
|
+
def formula?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
# By default, returns itself. Can be used for terms such as blank nodes to be turned into non-disinguished variables.
|
51
|
+
#
|
52
|
+
# @param [RDF::Node] scope
|
53
|
+
# return [RDF::Query::Variable]
|
54
|
+
def to_ndvar(scope)
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Term
|
60
|
+
##
|
61
|
+
# Is this the same term? Like `#eql?`, but no variable matching
|
62
|
+
def sameTerm?(other)
|
63
|
+
eql?(other)
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Parse the value as a numeric literal, or return 0.
|
68
|
+
#
|
69
|
+
# @return [RDF::Literal::Numeric]
|
70
|
+
def as_number
|
71
|
+
RDF::Literal(0)
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Parse the value as a dateTime literal, or return now.
|
76
|
+
#
|
77
|
+
# @return [RDF::Literal::DateTime]
|
78
|
+
def as_datetime
|
79
|
+
RDF::Literal::DateTime.new(DateTime.now)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Literal
|
84
|
+
include RDF::N3::Terminals
|
85
|
+
|
86
|
+
##
|
87
|
+
# Parse the value as a numeric literal, or return 0.
|
88
|
+
#
|
89
|
+
# @return [RDF::Literal::Numeric]
|
90
|
+
def as_number
|
91
|
+
return self if self.is_a?(RDF::Literal::Numeric)
|
92
|
+
case value
|
93
|
+
when DOUBLE then RDF::Literal::Double.new(value)
|
94
|
+
when DECIMAL then RDF::Literal::Decimal.new(value)
|
95
|
+
when INTEGER then RDF::Literal::Integer.new(value)
|
96
|
+
else
|
97
|
+
RDF::Literal(0)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Parse the value as a dateTime literal, or return now.
|
103
|
+
#
|
104
|
+
# @return [RDF::Literal::DateTime]
|
105
|
+
def as_datetime
|
106
|
+
return self if is_a?(RDF::Literal::DateTime)
|
107
|
+
mvalue = value
|
108
|
+
mvalue = "#{mvalue}-01" if mvalue.match?(%r(^\d{4}$))
|
109
|
+
mvalue = "#{mvalue}-01" if mvalue.match?(%r(^\d{4}-\d{2}$))
|
110
|
+
RDF::Literal::DateTime.new(::DateTime.iso8601(mvalue), lexical: value)
|
111
|
+
rescue
|
112
|
+
RDF::Literal(0)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Node
|
117
|
+
# Transform to a nondistinguished exisetntial variable in a formula scope
|
118
|
+
#
|
119
|
+
# @param [RDF::Node] scope
|
120
|
+
# return [RDF::Query::Variable]
|
121
|
+
def to_ndvar(scope)
|
122
|
+
label = "#{id}_#{scope ? scope.id : 'base'}_undext"
|
123
|
+
RDF::Query::Variable.new(label, existential: true, distinguished: false)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Query
|
128
|
+
class Pattern
|
129
|
+
##
|
130
|
+
# Overrides `#initialize!` to turn blank nodes into non-distinguished variables, if the `:ndvars` option is set.
|
131
|
+
alias_method :orig_initialize!, :initialize!
|
132
|
+
def initialize!
|
133
|
+
if @options[:ndvars]
|
134
|
+
@graph_name = @graph_name.to_ndvar(nil) if @graph_name
|
135
|
+
@subject = @subject.to_ndvar(@graph_name)
|
136
|
+
@predicate = @predicate.to_ndvar(@graph_name)
|
137
|
+
@object = @object.to_ndvar(@graph_name)
|
138
|
+
end
|
139
|
+
orig_initialize!
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Checks pattern equality against a statement, considering nesting an lists.
|
144
|
+
#
|
145
|
+
# * A pattern which has a pattern as a subject or an object, matches
|
146
|
+
# a statement having a statement as a subject or an object using {#eql?}.
|
147
|
+
#
|
148
|
+
# @param [Statement] other
|
149
|
+
# @return [Boolean]
|
150
|
+
#
|
151
|
+
# @see RDF::URI#==
|
152
|
+
# @see RDF::Node#==
|
153
|
+
# @see RDF::Literal#==
|
154
|
+
# @see RDF::Query::Variable#==
|
155
|
+
def eql?(other)
|
156
|
+
return false unless other.is_a?(RDF::Statement) && (self.graph_name || false) == (other.graph_name || false)
|
157
|
+
|
158
|
+
[:subject, :predicate, :object].each do |part|
|
159
|
+
case o = self.send(part)
|
160
|
+
when RDF::Query::Pattern, RDF::List
|
161
|
+
return false unless o.eql?(other.send(part))
|
162
|
+
else
|
163
|
+
return false unless o == other.send(part)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
true
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class Solution
|
171
|
+
# Transform Statement into an SXP
|
172
|
+
# @return [Array]
|
173
|
+
def to_sxp_bin
|
174
|
+
[:solution] + bindings.map do |k, v|
|
175
|
+
existential = k.to_s.end_with?('ext')
|
176
|
+
k = k.to_s.sub(/_(?:und)?ext$/, '').to_sym
|
177
|
+
distinguished = !k.to_s.end_with?('undext')
|
178
|
+
Query::Variable.new(k, v, existential: existential, distinguished: distinguished).to_sxp_bin
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class Variable
|
184
|
+
##
|
185
|
+
# True if the other is the same variable
|
186
|
+
def sameTerm?(other)
|
187
|
+
other.is_a?(::RDF::Query::Variable) && name.eql?(other.name)
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Parse the value as a numeric literal, or return 0.
|
192
|
+
#
|
193
|
+
# @return [RDF::Literal::Numeric]
|
194
|
+
def as_number
|
195
|
+
RDF::Literal(0)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
module SPARQL
|
202
|
+
module Algebra
|
203
|
+
class Operator
|
204
|
+
##
|
205
|
+
# Map of related formulae, indexed by graph name.
|
206
|
+
#
|
207
|
+
# @return [Hash{RDF::Resource => RDF::N3::Algebra::Formula}]
|
208
|
+
def formulae
|
209
|
+
@options.fetch(:formulae, {})
|
210
|
+
end
|
211
|
+
|
212
|
+
# Updates the operands for this operator.
|
213
|
+
#
|
214
|
+
# @param [Array] ary
|
215
|
+
# @return [Array]
|
216
|
+
def operands=(ary)
|
217
|
+
@operands = ary
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
data/lib/rdf/n3/format.rb
CHANGED
@@ -15,7 +15,7 @@ module RDF::N3
|
|
15
15
|
# @example Obtaining serialization format file extension mappings
|
16
16
|
# RDF::Format.file_extensions #=> {n3: "text/n3"}
|
17
17
|
#
|
18
|
-
# @see
|
18
|
+
# @see https://www.w3.org/TR/rdf-testcases/#ntriples
|
19
19
|
class Format < RDF::Format
|
20
20
|
content_type 'text/n3', extension: :n3, aliases: %w(text/rdf+n3;q=0.2 application/rdf+n3;q=0.2)
|
21
21
|
content_encoding 'utf-8'
|
@@ -27,5 +27,70 @@ module RDF::N3
|
|
27
27
|
def self.symbols
|
28
28
|
[:n3, :notation3]
|
29
29
|
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Hash of CLI commands appropriate for this format
|
33
|
+
# @return [Hash{Symbol => Hash}]
|
34
|
+
def self.cli_commands
|
35
|
+
{
|
36
|
+
reason: {
|
37
|
+
description: "Reason over formulae.",
|
38
|
+
help: "reason [--think] file\nPerform Notation-3 reasoning.",
|
39
|
+
parse: false,
|
40
|
+
# Only shows when input and output format set
|
41
|
+
filter: {format: :n3},
|
42
|
+
repository: RDF::N3::Repository.new,
|
43
|
+
lambda: ->(argv, **options) do
|
44
|
+
repository = options[:repository]
|
45
|
+
result_repo = RDF::N3::Repository.new
|
46
|
+
RDF::CLI.parse(argv, format: :n3, list_terms: true, **options) do |reader|
|
47
|
+
reasoner = RDF::N3::Reasoner.new(reader, **options)
|
48
|
+
reasoner.reason!(**options)
|
49
|
+
if options[:conclusions]
|
50
|
+
result_repo << reasoner.conclusions
|
51
|
+
elsif options[:data]
|
52
|
+
result_repo << reasoner.data
|
53
|
+
else
|
54
|
+
result_repo << reasoner
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Replace input repository with results
|
59
|
+
repository.clear!
|
60
|
+
repository << result_repo
|
61
|
+
end,
|
62
|
+
options: [
|
63
|
+
RDF::CLI::Option.new(
|
64
|
+
symbol: :conclusions,
|
65
|
+
datatype: TrueClass,
|
66
|
+
control: :checkbox,
|
67
|
+
use: :optional,
|
68
|
+
on: ["--conclusions"],
|
69
|
+
description: "Exclude formulae and statements in the original dataset."),
|
70
|
+
RDF::CLI::Option.new(
|
71
|
+
symbol: :data,
|
72
|
+
datatype: TrueClass,
|
73
|
+
control: :checkbox,
|
74
|
+
use: :optional,
|
75
|
+
on: ["--data"],
|
76
|
+
description: "Only results from default graph, excluding formulae or variables."),
|
77
|
+
RDF::CLI::Option.new(
|
78
|
+
symbol: :strings,
|
79
|
+
datatype: TrueClass,
|
80
|
+
control: :checkbox,
|
81
|
+
use: :optional,
|
82
|
+
on: ["--strings"],
|
83
|
+
description: "Returns the concatenated strings from log:outputString."),
|
84
|
+
RDF::CLI::Option.new(
|
85
|
+
symbol: :think,
|
86
|
+
datatype: TrueClass,
|
87
|
+
control: :checkbox,
|
88
|
+
use: :optional,
|
89
|
+
on: ["--think"],
|
90
|
+
description: "Continuously execute until results stop growing."),
|
91
|
+
]
|
92
|
+
},
|
93
|
+
}
|
94
|
+
end
|
30
95
|
end
|
31
96
|
end
|
data/lib/rdf/n3/list.rb
ADDED
@@ -0,0 +1,630 @@
|
|
1
|
+
module RDF::N3
|
2
|
+
##
|
3
|
+
# Sub-class of RDF::List which uses a native representation of values and allows recursive lists.
|
4
|
+
#
|
5
|
+
# Also serves as the vocabulary URI for expanding other methods
|
6
|
+
class List < RDF::List
|
7
|
+
# Allow a list to be treated as a term in a statement.
|
8
|
+
include ::RDF::Term
|
9
|
+
|
10
|
+
URI = RDF::URI("http://www.w3.org/2000/10/swap/list#")
|
11
|
+
|
12
|
+
# Returns a vocubulary term
|
13
|
+
def self.method_missing(property, *args, &block)
|
14
|
+
property = RDF::Vocabulary.camelize(property.to_s)
|
15
|
+
if args.empty? && !to_s.empty?
|
16
|
+
RDF::Vocabulary::Term.intern("#{URI}#{property}", attributes: {})
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Returns the base URI for this vocabulary.
|
24
|
+
#
|
25
|
+
# @return [URI]
|
26
|
+
def self.to_uri
|
27
|
+
URI
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Attempts to create an RDF::N3::List from subject, or returns the node as is, if unable.
|
32
|
+
#
|
33
|
+
# @param [RDF::Resource] subject
|
34
|
+
# @return [RDF::List, RDF::Resource] returns either the original resource, or a list based on that resource
|
35
|
+
def self.try_list(subject, graph)
|
36
|
+
return subject unless subject && (subject.node? || subject.uri? && subject == RDF.nil)
|
37
|
+
ln = RDF::List.new(subject: subject, graph: graph)
|
38
|
+
return subject unless ln.valid?
|
39
|
+
|
40
|
+
# Return a new list, outside of this queryable, with any embedded lists also expanded
|
41
|
+
values = ln.to_a.map {|li| try_list(li, graph)}
|
42
|
+
RDF::N3::List.new(subject: subject, graph: graph, values: values)
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Initializes a newly-constructed list.
|
47
|
+
#
|
48
|
+
# Instantiates a new list based at `subject`, which **must** be an RDF::Node. List may be initialized using passed `values`.
|
49
|
+
#
|
50
|
+
# @example add constructed list to existing graph
|
51
|
+
# l = RDF::N3::List(values: (1, 2, 3))
|
52
|
+
# g = RDF::Graph.new << l
|
53
|
+
# g.count # => l.count
|
54
|
+
#
|
55
|
+
# If values is not provided, but subject and graph are, then will attempt to recursively represent lists.
|
56
|
+
#
|
57
|
+
# @param [RDF::Resource] subject (RDF.nil)
|
58
|
+
# Subject should be an {RDF::Node}, not a {RDF::URI}. A list with an IRI head will not validate, but is commonly used to detect if a list is valid.
|
59
|
+
# @param [RDF::Graph] graph (RDF::Graph.new)
|
60
|
+
# @param [Array<RDF::Term>] values
|
61
|
+
# Any values which are not terms are coerced to `RDF::Literal`.
|
62
|
+
# @yield [list]
|
63
|
+
# @yieldparam [RDF::List] list
|
64
|
+
def initialize(subject: nil, graph: nil, values: nil, &block)
|
65
|
+
@subject = subject || (Array(values).empty? ? RDF.nil : RDF::Node.new)
|
66
|
+
@graph = graph
|
67
|
+
@valid = true
|
68
|
+
|
69
|
+
@values = case
|
70
|
+
when values
|
71
|
+
values.map do |v|
|
72
|
+
# Convert values, as necessary.
|
73
|
+
case v
|
74
|
+
when RDF::Value then v.to_term
|
75
|
+
when Symbol then RDF::Node.intern(v)
|
76
|
+
when Array then RDF::N3::List.new(values: v)
|
77
|
+
when nil then RDF.nil
|
78
|
+
else RDF::Literal.new(v)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
when subject && graph
|
82
|
+
ln = RDF::List.new(subject: subject, graph: graph)
|
83
|
+
@valid = ln.valid?
|
84
|
+
ln.to_a.map {|li| self.class.try_list(li, graph)}
|
85
|
+
else
|
86
|
+
[]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Lists are valid, unless established via RDF::List, in which case they are only valid if the RDF::List is valid.
|
92
|
+
#
|
93
|
+
# @return [Boolean]
|
94
|
+
def valid?; @valid; end
|
95
|
+
|
96
|
+
##
|
97
|
+
# @see RDF::Value#==
|
98
|
+
def ==(other)
|
99
|
+
case other
|
100
|
+
when Array, RDF::List then to_a == other.to_a
|
101
|
+
else
|
102
|
+
false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# The list hash is the hash of it's members.
|
108
|
+
#
|
109
|
+
# @see RDF::Value#hash
|
110
|
+
def hash
|
111
|
+
to_a.hash
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Element Assignment — Sets the element at `index`, or replaces a subarray from the `start` index for `length` elements, or replaces a subarray specified by the `range` of indices.
|
116
|
+
#
|
117
|
+
# @overload []=(index, term)
|
118
|
+
# Replaces the element at `index` with `term`.
|
119
|
+
# @param [Integer] index
|
120
|
+
# @param [RDF::Term] term
|
121
|
+
# A non-RDF::Term is coerced to a Literal.
|
122
|
+
# @return [RDF::Term]
|
123
|
+
# @raise [IndexError]
|
124
|
+
#
|
125
|
+
# @overload []=(start, length, value)
|
126
|
+
# Replaces a subarray from the `start` index for `length` elements with `value`. Value is a {RDF::Term}, Array of {RDF::Term}, or {RDF::List}.
|
127
|
+
# @param [Integer] start
|
128
|
+
# @param [Integer] length
|
129
|
+
# @param [RDF::Term, Array<RDF::Term>, RDF::List] value
|
130
|
+
# A non-RDF::Term is coerced to a Literal.
|
131
|
+
# @return [RDF::Term, RDF::List]
|
132
|
+
# @raise [IndexError]
|
133
|
+
#
|
134
|
+
# @overload []=(range, value)
|
135
|
+
# Replaces a subarray from the `start` index for `length` elements with `value`. Value is a {RDF::Term}, Array of {RDF::Term}, or {RDF::List}.
|
136
|
+
# @param [Range] range
|
137
|
+
# @param [RDF::Term, Array<RDF::Term>, RDF::List] value
|
138
|
+
# A non-RDF::Term is coerced to a Literal.
|
139
|
+
# @return [RDF::Term, RDF::List]
|
140
|
+
# @raise [IndexError]
|
141
|
+
def []=(*args)
|
142
|
+
value = case args.last
|
143
|
+
when Array then args.last
|
144
|
+
when RDF::List then args.last.to_a
|
145
|
+
else [args.last]
|
146
|
+
end.map do |v|
|
147
|
+
# Convert values, as necessary.
|
148
|
+
case v
|
149
|
+
when RDF::Value then v.to_term
|
150
|
+
when Symbol then RDF::Node.intern(v)
|
151
|
+
when Array then RDF::N3::List.new(values: v)
|
152
|
+
when nil then RDF.nil
|
153
|
+
else RDF::Literal.new(v)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
ret = case args.length
|
158
|
+
when 3
|
159
|
+
start, length = args[0], args[1]
|
160
|
+
@subject = nil if start == 0
|
161
|
+
@values[start, length] = value
|
162
|
+
when 2
|
163
|
+
case args.first
|
164
|
+
when Integer
|
165
|
+
raise ArgumentError, "Index form of []= takes a single term" if args.last.is_a?(Array)
|
166
|
+
@values[args.first] = value.first
|
167
|
+
when Range
|
168
|
+
@values[args.first] = value
|
169
|
+
else
|
170
|
+
raise ArgumentError, "Index form of must use an integer or range"
|
171
|
+
end
|
172
|
+
else
|
173
|
+
raise ArgumentError, "List []= takes one or two index values"
|
174
|
+
end
|
175
|
+
|
176
|
+
# Fill any nil entries in @values with rdf:nil
|
177
|
+
@values.map! {|v| v || RDF.nil}
|
178
|
+
|
179
|
+
@subject = RDF.nil if @values.empty?
|
180
|
+
@subject ||= RDF::Node.new
|
181
|
+
ret # Returns inserted values
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Appends an element to the head of this list. Existing references are not updated, as the list subject changes as a side-effect.
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# RDF::List[].unshift(1).unshift(2).unshift(3) #=> RDF::List[3, 2, 1]
|
189
|
+
#
|
190
|
+
# @param [RDF::Term, Array<RDF::Term>, RDF::List] value
|
191
|
+
# A non-RDF::Term is coerced to a Literal
|
192
|
+
# @return [RDF::List]
|
193
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-unshift
|
194
|
+
#
|
195
|
+
def unshift(value)
|
196
|
+
value = normalize_value(value)
|
197
|
+
@values.unshift(value)
|
198
|
+
@subject = nil
|
199
|
+
|
200
|
+
return self
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Removes and returns the element at the head of this list.
|
205
|
+
#
|
206
|
+
# @example
|
207
|
+
# RDF::List[1,2,3].shift #=> 1
|
208
|
+
#
|
209
|
+
# @return [RDF::Term]
|
210
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-shift
|
211
|
+
def shift
|
212
|
+
return nil if empty?
|
213
|
+
@subject = nil
|
214
|
+
@values.shift
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Empties this list
|
219
|
+
#
|
220
|
+
# @example
|
221
|
+
# RDF::List[1, 2, 2, 3].clear #=> RDF::List[]
|
222
|
+
#
|
223
|
+
# @return [RDF::List]
|
224
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-clear
|
225
|
+
def clear
|
226
|
+
@values.clear
|
227
|
+
@subject = nil
|
228
|
+
self
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Appends an element to the tail of this list.
|
233
|
+
#
|
234
|
+
# @example
|
235
|
+
# RDF::List[] << 1 << 2 << 3 #=> RDF::List[1, 2, 3]
|
236
|
+
#
|
237
|
+
# @param [RDF::Term] value
|
238
|
+
# @return [RDF::List]
|
239
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-3C-3C
|
240
|
+
def <<(value)
|
241
|
+
value = normalize_value(value)
|
242
|
+
@subject = nil
|
243
|
+
@values << value
|
244
|
+
self
|
245
|
+
end
|
246
|
+
|
247
|
+
##
|
248
|
+
# Returns `true` if this list is empty.
|
249
|
+
#
|
250
|
+
# @example
|
251
|
+
# RDF::List[].empty? #=> true
|
252
|
+
# RDF::List[1, 2, 3].empty? #=> false
|
253
|
+
#
|
254
|
+
# @return [Boolean]
|
255
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-empty-3F
|
256
|
+
def empty?
|
257
|
+
@values.empty?
|
258
|
+
end
|
259
|
+
|
260
|
+
##
|
261
|
+
# Returns the length of this list.
|
262
|
+
#
|
263
|
+
# @example
|
264
|
+
# RDF::List[].length #=> 0
|
265
|
+
# RDF::List[1, 2, 3].length #=> 3
|
266
|
+
#
|
267
|
+
# @return [Integer]
|
268
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-length
|
269
|
+
def length
|
270
|
+
@values.length
|
271
|
+
end
|
272
|
+
|
273
|
+
##
|
274
|
+
# Returns the index of the first element equal to `value`, or `nil` if
|
275
|
+
# no match was found.
|
276
|
+
#
|
277
|
+
# @example
|
278
|
+
# RDF::List['a', 'b', 'c'].index('a') #=> 0
|
279
|
+
# RDF::List['a', 'b', 'c'].index('d') #=> nil
|
280
|
+
#
|
281
|
+
# @param [RDF::Term] value
|
282
|
+
# @return [Integer]
|
283
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-index
|
284
|
+
def index(value)
|
285
|
+
@values.index(value)
|
286
|
+
end
|
287
|
+
|
288
|
+
##
|
289
|
+
# Returns element at `index` with default.
|
290
|
+
#
|
291
|
+
# @example
|
292
|
+
# RDF::List[1, 2, 3].fetch(0) #=> RDF::Literal(1)
|
293
|
+
# RDF::List[1, 2, 3].fetch(4) #=> IndexError
|
294
|
+
# RDF::List[1, 2, 3].fetch(4, nil) #=> nil
|
295
|
+
# RDF::List[1, 2, 3].fetch(4) { |n| n*n } #=> 16
|
296
|
+
#
|
297
|
+
# @return [RDF::Term, nil]
|
298
|
+
# @see http://ruby-doc.org/core-1.9/classes/Array.html#M000420
|
299
|
+
def fetch(*args, &block)
|
300
|
+
@values.fetch(*args, &block)
|
301
|
+
end
|
302
|
+
|
303
|
+
##
|
304
|
+
# Returns the element at `index`.
|
305
|
+
#
|
306
|
+
# @example
|
307
|
+
# RDF::List[1, 2, 3].at(0) #=> 1
|
308
|
+
# RDF::List[1, 2, 3].at(4) #=> nil
|
309
|
+
#
|
310
|
+
# @return [RDF::Term, nil]
|
311
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-at
|
312
|
+
def at(index)
|
313
|
+
@values.at(index)
|
314
|
+
end
|
315
|
+
|
316
|
+
##
|
317
|
+
# Returns the first element in this list.
|
318
|
+
#
|
319
|
+
# @example
|
320
|
+
# RDF::List[*(1..10)].first #=> RDF::Literal(1)
|
321
|
+
#
|
322
|
+
# @return [RDF::Term]
|
323
|
+
def first
|
324
|
+
@values.first
|
325
|
+
end
|
326
|
+
|
327
|
+
##
|
328
|
+
# Returns the last element in this list.
|
329
|
+
#
|
330
|
+
# @example
|
331
|
+
# RDF::List[*(1..10)].last #=> RDF::Literal(10)
|
332
|
+
#
|
333
|
+
# @return [RDF::Term]
|
334
|
+
# @see http://ruby-doc.org/core-2.2.2/Array.html#method-i-last
|
335
|
+
def last
|
336
|
+
@values.last
|
337
|
+
end
|
338
|
+
|
339
|
+
##
|
340
|
+
# Returns a list containing all but the first element of this list.
|
341
|
+
#
|
342
|
+
# @example
|
343
|
+
# RDF::List[1, 2, 3].rest #=> RDF::List[2, 3]
|
344
|
+
#
|
345
|
+
# @return [RDF::List]
|
346
|
+
def rest
|
347
|
+
self.class.new(values: @values[1..-1])
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Returns a list containing the last element of this list.
|
352
|
+
#
|
353
|
+
# @example
|
354
|
+
# RDF::List[1, 2, 3].tail #=> RDF::List[3]
|
355
|
+
#
|
356
|
+
# @return [RDF::List]
|
357
|
+
def tail
|
358
|
+
self.class.new(values: @values[-1..-1])
|
359
|
+
end
|
360
|
+
|
361
|
+
##
|
362
|
+
# Yields each element in this list.
|
363
|
+
#
|
364
|
+
# @example
|
365
|
+
# RDF::List[1, 2, 3].each do |value|
|
366
|
+
# puts value.inspect
|
367
|
+
# end
|
368
|
+
#
|
369
|
+
# @return [Enumerator]
|
370
|
+
# @see http://ruby-doc.org/core-1.9/classes/Enumerable.html
|
371
|
+
def each(&block)
|
372
|
+
return to_enum unless block_given?
|
373
|
+
|
374
|
+
@values.each(&block)
|
375
|
+
end
|
376
|
+
|
377
|
+
##
|
378
|
+
# Yields each statement constituting this list. Uses actual statements if a graph was set, otherwise, the saved values.
|
379
|
+
#
|
380
|
+
# This will recursively get statements for sub-lists as well.
|
381
|
+
#
|
382
|
+
# @example
|
383
|
+
# RDF::List[1, 2, 3].each_statement do |statement|
|
384
|
+
# puts statement.inspect
|
385
|
+
# end
|
386
|
+
#
|
387
|
+
# @return [Enumerator]
|
388
|
+
# @see RDF::Enumerable#each_statement
|
389
|
+
def each_statement(&block)
|
390
|
+
return enum_statement unless block_given?
|
391
|
+
|
392
|
+
if graph
|
393
|
+
RDF::List.new(subject: subject, graph: graph).each_statement(&block)
|
394
|
+
elsif @values.length > 0
|
395
|
+
# Create a subject for each entry based on the subject bnode
|
396
|
+
subjects = (0..(@values.count-1)).map {|ndx| ndx > 0 ? RDF::Node.intern("#{subject.id}_#{ndx}") : subject}
|
397
|
+
*values, last = @values
|
398
|
+
while !values.empty?
|
399
|
+
subj = subjects.shift
|
400
|
+
value = values.shift
|
401
|
+
block.call(RDF::Statement(subj, RDF.first, value.list? ? value.subject : value))
|
402
|
+
block.call(RDF::Statement(subj, RDF.rest, subjects.first))
|
403
|
+
end
|
404
|
+
subj = subjects.shift
|
405
|
+
block.call(RDF::Statement(subj, RDF.first, last.list? ? last.subject : last))
|
406
|
+
block.call(RDF::Statement(subj, RDF.rest, RDF.nil))
|
407
|
+
end
|
408
|
+
|
409
|
+
# If a graph was used, also get statements from sub-lists
|
410
|
+
@values.select(&:list?).each {|li| li.each_statement(&block)}
|
411
|
+
end
|
412
|
+
|
413
|
+
##
|
414
|
+
# Yields each subject term constituting this list along with sub-lists.
|
415
|
+
#
|
416
|
+
# @example
|
417
|
+
# RDF::List[1, 2, 3].each_subject do |subject|
|
418
|
+
# puts subject.inspect
|
419
|
+
# end
|
420
|
+
#
|
421
|
+
# @return [Enumerator]
|
422
|
+
# @see RDF::Enumerable#each
|
423
|
+
def each_subject(&block)
|
424
|
+
return enum_subject unless block_given?
|
425
|
+
|
426
|
+
each_statement {|st| block.call(st.subject) if st.predicate == RDF.rest}
|
427
|
+
end
|
428
|
+
|
429
|
+
##
|
430
|
+
# Enumerate via depth-first recursive descent over list members, yielding each member
|
431
|
+
# @yield term
|
432
|
+
# @yieldparam [RDF::Term] term
|
433
|
+
# @return [Enumerator]
|
434
|
+
def each_descendant(&block)
|
435
|
+
if block_given?
|
436
|
+
each do |term|
|
437
|
+
term.each_descendant(&block) if term.list?
|
438
|
+
block.call(term)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
enum_for(:each_descendant)
|
442
|
+
end
|
443
|
+
|
444
|
+
##
|
445
|
+
# Does this list, or any recusive list have any blank node members?
|
446
|
+
#
|
447
|
+
# @return [Boolean]
|
448
|
+
def has_nodes?
|
449
|
+
@values.any? {|e| e.node? || e.list? && e.has_nodes?}
|
450
|
+
end
|
451
|
+
|
452
|
+
##
|
453
|
+
# Substitutes blank node members with existential variables, recusively.
|
454
|
+
#
|
455
|
+
# @param [RDF::Node] scope
|
456
|
+
# @return [RDF::N3::List]
|
457
|
+
def to_ndvar(scope)
|
458
|
+
values = @values.map do |e|
|
459
|
+
case e
|
460
|
+
when RDF::Node then e.to_ndvar(scope)
|
461
|
+
when RDF::N3::List then e.to_ndvar(scope)
|
462
|
+
else e
|
463
|
+
end
|
464
|
+
end
|
465
|
+
RDF::N3::List.new(values: values)
|
466
|
+
end
|
467
|
+
|
468
|
+
##
|
469
|
+
# Returns the elements in this list as an array.
|
470
|
+
#
|
471
|
+
# @example
|
472
|
+
# RDF::List[].to_a #=> []
|
473
|
+
# RDF::List[1, 2, 3].to_a #=> [RDF::Literal(1), RDF::Literal(2), RDF::Literal(3)]
|
474
|
+
#
|
475
|
+
# @return [Array]
|
476
|
+
def to_a
|
477
|
+
@values
|
478
|
+
end
|
479
|
+
|
480
|
+
##
|
481
|
+
# Checks pattern equality against another list, considering nesting.
|
482
|
+
#
|
483
|
+
# @param [List, Array] other
|
484
|
+
# @return [Boolean]
|
485
|
+
def eql?(other)
|
486
|
+
other = RDF::N3::List[*other] if other.is_a?(Array)
|
487
|
+
return false if !other.is_a?(RDF::List) || count != other.count
|
488
|
+
@values.each_with_index do |li, ndx|
|
489
|
+
case li
|
490
|
+
when RDF::Query::Pattern, RDF::N3::List
|
491
|
+
return false unless li.eql?(other.at(ndx))
|
492
|
+
else
|
493
|
+
return false unless li == other.at(ndx)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
true
|
497
|
+
end
|
498
|
+
|
499
|
+
##
|
500
|
+
# A list is variable if any of its members are variable?
|
501
|
+
#
|
502
|
+
# @return [Boolean]
|
503
|
+
def variable?
|
504
|
+
@values.any?(&:variable?)
|
505
|
+
end
|
506
|
+
|
507
|
+
##
|
508
|
+
# Return the variables contained this list
|
509
|
+
# @return [Array<RDF::Query::Variable>]
|
510
|
+
def vars
|
511
|
+
@values.vars
|
512
|
+
end
|
513
|
+
|
514
|
+
##
|
515
|
+
# Returns all variables in this list.
|
516
|
+
#
|
517
|
+
# Note: this returns a hash containing distinct variables only.
|
518
|
+
#
|
519
|
+
# @return [Hash{Symbol => Variable}]
|
520
|
+
def variables
|
521
|
+
@values.inject({}) do |hash, li|
|
522
|
+
li.respond_to?(:variables) ? hash.merge(li.variables) : hash
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
##
|
527
|
+
# Returns the number of variables in this list, recursively.
|
528
|
+
#
|
529
|
+
# @return [Integer]
|
530
|
+
def variable_count
|
531
|
+
variables.length
|
532
|
+
end
|
533
|
+
|
534
|
+
##
|
535
|
+
# Returns all values the list in the same pattern position
|
536
|
+
#
|
537
|
+
# @param [Symbol] var
|
538
|
+
# @param [RDF::N3::List] list
|
539
|
+
# @return [Array<RDF::Term>]
|
540
|
+
def var_values(var, list)
|
541
|
+
results = []
|
542
|
+
@values.each_index do |ndx|
|
543
|
+
maybe_var = @values[ndx]
|
544
|
+
next unless maybe_var.respond_to?(:var_values)
|
545
|
+
results.push(*Array(maybe_var.var_values(var, list.at(ndx))))
|
546
|
+
end
|
547
|
+
results.flatten.compact
|
548
|
+
end
|
549
|
+
|
550
|
+
##
|
551
|
+
# Evaluates the list using the given variable `bindings`.
|
552
|
+
#
|
553
|
+
# @param [Hash{Symbol => RDF::Term}] bindings
|
554
|
+
# a query solution containing zero or more variable bindings
|
555
|
+
# @param [Hash{Symbol => Object}] options ({})
|
556
|
+
# options passed from query
|
557
|
+
# @return [RDF::N3::List]
|
558
|
+
# @see SPARQL::Algebra::Expression.evaluate
|
559
|
+
def evaluate(bindings, formulae: {}, **options)
|
560
|
+
# if values are constant, simply return ourselves
|
561
|
+
return self if to_a.none? {|li| li.node? || li.variable?}
|
562
|
+
bindings = bindings.to_h unless bindings.is_a?(Hash)
|
563
|
+
# Create a new list subject using a combination of the current subject and a hash of the binding values
|
564
|
+
subj = "#{subject.id}_#{bindings.values.sort.hash}"
|
565
|
+
values = to_a.map do |o|
|
566
|
+
o = o.evaluate(bindings, formulae: formulae, **options) || o
|
567
|
+
end
|
568
|
+
RDF::N3::List.new(subject: RDF::Node.intern(subj), values: values)
|
569
|
+
end
|
570
|
+
|
571
|
+
##
|
572
|
+
# Returns a query solution constructed by binding any variables in this list with the corresponding terms in the given `list`.
|
573
|
+
#
|
574
|
+
# @param [RDF::N3::List] list
|
575
|
+
# a native list with patterns to bind.
|
576
|
+
# @return [RDF::Query::Solution]
|
577
|
+
# @see RDF::Query::Pattern#solution
|
578
|
+
def solution(list)
|
579
|
+
RDF::Query::Solution.new do |solution|
|
580
|
+
@values.each_with_index do |li, ndx|
|
581
|
+
if li.respond_to?(:solution)
|
582
|
+
solution.merge!(li.solution(list[ndx]))
|
583
|
+
elsif li.is_a?(RDF::Query::Variable)
|
584
|
+
solution[li.to_sym] = list[ndx]
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
##
|
591
|
+
# Returns the base representation of this term.
|
592
|
+
#
|
593
|
+
# @return [Sring]
|
594
|
+
def to_base
|
595
|
+
"(#{@values.map(&:to_base).join(' ')})"
|
596
|
+
end
|
597
|
+
|
598
|
+
# Transform Statement into an SXP
|
599
|
+
# @return [Array]
|
600
|
+
def to_sxp_bin
|
601
|
+
to_a.to_sxp_bin
|
602
|
+
end
|
603
|
+
|
604
|
+
##
|
605
|
+
# Creates a new list by recusively mapping the values of the list
|
606
|
+
#
|
607
|
+
# @return [RDF::N3::list]
|
608
|
+
def transform(&block)
|
609
|
+
values = self.to_a.map {|v| v.list? ? v.map(&block) : block.call(v)}
|
610
|
+
RDF::N3::List.new(values: values)
|
611
|
+
end
|
612
|
+
|
613
|
+
private
|
614
|
+
|
615
|
+
##
|
616
|
+
# Normalizes `Array` to `RDF::List` and `nil` to `RDF.nil`.
|
617
|
+
#
|
618
|
+
# @param value [Object]
|
619
|
+
# @return [RDF::Value, Object] normalized value
|
620
|
+
def normalize_value(value)
|
621
|
+
case value
|
622
|
+
when RDF::Value then value.to_term
|
623
|
+
when Array then RDF::N3::List.new(values: value)
|
624
|
+
when Symbol then RDF::Node.intern(value)
|
625
|
+
when nil then RDF.nil
|
626
|
+
else RDF::Literal.new(value)
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|