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.
Files changed (101) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +192 -69
  3. data/UNLICENSE +1 -1
  4. data/VERSION +1 -1
  5. data/lib/rdf/n3.rb +11 -8
  6. data/lib/rdf/n3/algebra.rb +204 -0
  7. data/lib/rdf/n3/algebra/builtin.rb +79 -0
  8. data/lib/rdf/n3/algebra/formula.rb +446 -0
  9. data/lib/rdf/n3/algebra/list/append.rb +42 -0
  10. data/lib/rdf/n3/algebra/list/first.rb +24 -0
  11. data/lib/rdf/n3/algebra/list/in.rb +48 -0
  12. data/lib/rdf/n3/algebra/list/last.rb +24 -0
  13. data/lib/rdf/n3/algebra/list/length.rb +24 -0
  14. data/lib/rdf/n3/algebra/list/member.rb +44 -0
  15. data/lib/rdf/n3/algebra/list_operator.rb +83 -0
  16. data/lib/rdf/n3/algebra/log/conclusion.rb +65 -0
  17. data/lib/rdf/n3/algebra/log/conjunction.rb +36 -0
  18. data/lib/rdf/n3/algebra/log/content.rb +34 -0
  19. data/lib/rdf/n3/algebra/log/equal_to.rb +34 -0
  20. data/lib/rdf/n3/algebra/log/implies.rb +102 -0
  21. data/lib/rdf/n3/algebra/log/includes.rb +70 -0
  22. data/lib/rdf/n3/algebra/log/n3_string.rb +34 -0
  23. data/lib/rdf/n3/algebra/log/not_equal_to.rb +23 -0
  24. data/lib/rdf/n3/algebra/log/not_includes.rb +27 -0
  25. data/lib/rdf/n3/algebra/log/output_string.rb +40 -0
  26. data/lib/rdf/n3/algebra/log/parsed_as_n3.rb +36 -0
  27. data/lib/rdf/n3/algebra/log/semantics.rb +40 -0
  28. data/lib/rdf/n3/algebra/math/absolute_value.rb +36 -0
  29. data/lib/rdf/n3/algebra/math/acos.rb +26 -0
  30. data/lib/rdf/n3/algebra/math/acosh.rb +26 -0
  31. data/lib/rdf/n3/algebra/math/asin.rb +26 -0
  32. data/lib/rdf/n3/algebra/math/asinh.rb +26 -0
  33. data/lib/rdf/n3/algebra/math/atan.rb +26 -0
  34. data/lib/rdf/n3/algebra/math/atanh.rb +26 -0
  35. data/lib/rdf/n3/algebra/math/ceiling.rb +28 -0
  36. data/lib/rdf/n3/algebra/math/cos.rb +40 -0
  37. data/lib/rdf/n3/algebra/math/cosh.rb +38 -0
  38. data/lib/rdf/n3/algebra/math/difference.rb +40 -0
  39. data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
  40. data/lib/rdf/n3/algebra/math/exponentiation.rb +35 -0
  41. data/lib/rdf/n3/algebra/math/floor.rb +28 -0
  42. data/lib/rdf/n3/algebra/math/greater_than.rb +41 -0
  43. data/lib/rdf/n3/algebra/math/less_than.rb +41 -0
  44. data/lib/rdf/n3/algebra/math/negation.rb +38 -0
  45. data/lib/rdf/n3/algebra/math/not_equal_to.rb +25 -0
  46. data/lib/rdf/n3/algebra/math/not_greater_than.rb +25 -0
  47. data/lib/rdf/n3/algebra/math/not_less_than.rb +25 -0
  48. data/lib/rdf/n3/algebra/math/product.rb +20 -0
  49. data/lib/rdf/n3/algebra/math/quotient.rb +36 -0
  50. data/lib/rdf/n3/algebra/math/remainder.rb +35 -0
  51. data/lib/rdf/n3/algebra/math/rounded.rb +26 -0
  52. data/lib/rdf/n3/algebra/math/sin.rb +40 -0
  53. data/lib/rdf/n3/algebra/math/sinh.rb +38 -0
  54. data/lib/rdf/n3/algebra/math/sum.rb +40 -0
  55. data/lib/rdf/n3/algebra/math/tan.rb +40 -0
  56. data/lib/rdf/n3/algebra/math/tanh.rb +38 -0
  57. data/lib/rdf/n3/algebra/not_implemented.rb +13 -0
  58. data/lib/rdf/n3/algebra/resource_operator.rb +123 -0
  59. data/lib/rdf/n3/algebra/str/concatenation.rb +27 -0
  60. data/lib/rdf/n3/algebra/str/contains.rb +33 -0
  61. data/lib/rdf/n3/algebra/str/contains_ignoring_case.rb +33 -0
  62. data/lib/rdf/n3/algebra/str/ends_with.rb +33 -0
  63. data/lib/rdf/n3/algebra/str/equal_ignoring_case.rb +34 -0
  64. data/lib/rdf/n3/algebra/str/format.rb +17 -0
  65. data/lib/rdf/n3/algebra/str/greater_than.rb +38 -0
  66. data/lib/rdf/n3/algebra/str/less_than.rb +33 -0
  67. data/lib/rdf/n3/algebra/str/matches.rb +37 -0
  68. data/lib/rdf/n3/algebra/str/not_equal_ignoring_case.rb +17 -0
  69. data/lib/rdf/n3/algebra/str/not_greater_than.rb +17 -0
  70. data/lib/rdf/n3/algebra/str/not_less_than.rb +17 -0
  71. data/lib/rdf/n3/algebra/str/not_matches.rb +18 -0
  72. data/lib/rdf/n3/algebra/str/replace.rb +35 -0
  73. data/lib/rdf/n3/algebra/str/scrape.rb +35 -0
  74. data/lib/rdf/n3/algebra/str/starts_with.rb +33 -0
  75. data/lib/rdf/n3/algebra/time/day.rb +35 -0
  76. data/lib/rdf/n3/algebra/time/day_of_week.rb +27 -0
  77. data/lib/rdf/n3/algebra/time/gm_time.rb +29 -0
  78. data/lib/rdf/n3/algebra/time/hour.rb +35 -0
  79. data/lib/rdf/n3/algebra/time/in_seconds.rb +59 -0
  80. data/lib/rdf/n3/algebra/time/local_time.rb +29 -0
  81. data/lib/rdf/n3/algebra/time/minute.rb +35 -0
  82. data/lib/rdf/n3/algebra/time/month.rb +35 -0
  83. data/lib/rdf/n3/algebra/time/second.rb +35 -0
  84. data/lib/rdf/n3/algebra/time/timezone.rb +36 -0
  85. data/lib/rdf/n3/algebra/time/year.rb +29 -0
  86. data/lib/rdf/n3/extensions.rb +221 -0
  87. data/lib/rdf/n3/format.rb +66 -1
  88. data/lib/rdf/n3/list.rb +630 -0
  89. data/lib/rdf/n3/reader.rb +834 -492
  90. data/lib/rdf/n3/reasoner.rb +282 -0
  91. data/lib/rdf/n3/refinements.rb +178 -0
  92. data/lib/rdf/n3/repository.rb +332 -0
  93. data/lib/rdf/n3/terminals.rb +80 -0
  94. data/lib/rdf/n3/vocab.rb +36 -3
  95. data/lib/rdf/n3/writer.rb +476 -239
  96. metadata +187 -68
  97. data/AUTHORS +0 -1
  98. data/History.markdown +0 -99
  99. data/lib/rdf/n3/patches/array_hacks.rb +0 -53
  100. data/lib/rdf/n3/reader/meta.rb +0 -641
  101. 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
@@ -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 http://www.w3.org/TR/rdf-testcases/#ntriples
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
@@ -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