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.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +148 -69
  3. data/UNLICENSE +1 -1
  4. data/VERSION +1 -1
  5. data/lib/rdf/n3.rb +8 -8
  6. data/lib/rdf/n3/algebra.rb +147 -68
  7. data/lib/rdf/n3/algebra/builtin.rb +79 -0
  8. data/lib/rdf/n3/algebra/formula.rb +355 -94
  9. data/lib/rdf/n3/algebra/list/append.rb +33 -4
  10. data/lib/rdf/n3/algebra/list/first.rb +24 -0
  11. data/lib/rdf/n3/algebra/list/in.rb +42 -3
  12. data/lib/rdf/n3/algebra/list/last.rb +17 -4
  13. data/lib/rdf/n3/algebra/list/length.rb +24 -0
  14. data/lib/rdf/n3/algebra/list/member.rb +39 -2
  15. data/lib/rdf/n3/algebra/list_operator.rb +83 -0
  16. data/lib/rdf/n3/algebra/log/conclusion.rb +57 -1
  17. data/lib/rdf/n3/algebra/log/conjunction.rb +28 -1
  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 +55 -30
  21. data/lib/rdf/n3/algebra/log/includes.rb +58 -1
  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 +34 -3
  39. data/lib/rdf/n3/algebra/math/equal_to.rb +54 -0
  40. data/lib/rdf/n3/algebra/math/exponentiation.rb +29 -3
  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 +31 -2
  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 +14 -3
  49. data/lib/rdf/n3/algebra/math/quotient.rb +30 -3
  50. data/lib/rdf/n3/algebra/math/remainder.rb +29 -3
  51. data/lib/rdf/n3/algebra/math/rounded.rb +20 -3
  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 +35 -4
  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 +21 -3
  60. data/lib/rdf/n3/algebra/str/contains.rb +28 -4
  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 +12 -4
  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 +33 -5
  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 +28 -5
  73. data/lib/rdf/n3/algebra/str/scrape.rb +31 -5
  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 +180 -21
  87. data/lib/rdf/n3/format.rb +65 -0
  88. data/lib/rdf/n3/list.rb +630 -0
  89. data/lib/rdf/n3/reader.rb +762 -485
  90. data/lib/rdf/n3/reasoner.rb +57 -68
  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 +35 -7
  95. data/lib/rdf/n3/writer.rb +208 -148
  96. metadata +110 -52
  97. data/AUTHORS +0 -1
  98. data/History.markdown +0 -99
  99. data/lib/rdf/n3/algebra/log/equalTo.rb +0 -7
  100. data/lib/rdf/n3/algebra/log/notEqualTo.rb +0 -7
  101. data/lib/rdf/n3/algebra/log/notIncludes.rb +0 -12
  102. data/lib/rdf/n3/algebra/log/outputString.rb +0 -7
  103. data/lib/rdf/n3/algebra/math/absoluteValue.rb +0 -9
  104. data/lib/rdf/n3/algebra/math/equalTo.rb +0 -9
  105. data/lib/rdf/n3/algebra/math/greaterThan.rb +0 -9
  106. data/lib/rdf/n3/algebra/math/integerQuotient.rb +0 -9
  107. data/lib/rdf/n3/algebra/math/lessThan.rb +0 -9
  108. data/lib/rdf/n3/algebra/math/memberCount.rb +0 -9
  109. data/lib/rdf/n3/algebra/math/notEqualTo.rb +0 -9
  110. data/lib/rdf/n3/algebra/math/notGreaterThan.rb +0 -9
  111. data/lib/rdf/n3/algebra/math/notLessThan.rb +0 -9
  112. data/lib/rdf/n3/algebra/str/containsIgnoringCase.rb +0 -9
  113. data/lib/rdf/n3/algebra/str/endsWith.rb +0 -9
  114. data/lib/rdf/n3/algebra/str/equalIgnoringCase.rb +0 -9
  115. data/lib/rdf/n3/algebra/str/greaterThan.rb +0 -9
  116. data/lib/rdf/n3/algebra/str/lessThan.rb +0 -9
  117. data/lib/rdf/n3/algebra/str/notEqualIgnoringCase.rb +0 -9
  118. data/lib/rdf/n3/algebra/str/notGreaterThan.rb +0 -9
  119. data/lib/rdf/n3/algebra/str/notLessThan.rb +0 -9
  120. data/lib/rdf/n3/algebra/str/notMatches.rb +0 -9
  121. data/lib/rdf/n3/algebra/str/startsWith.rb +0 -56
  122. data/lib/rdf/n3/patches/array_hacks.rb +0 -53
  123. data/lib/rdf/n3/reader/meta.rb +0 -641
  124. data/lib/rdf/n3/reader/parser.rb +0 -239
@@ -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] filename
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::Enumerable] input (nil)
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
- log_debug("reasoner: expression", **options) {SXP::Generator.string(formula.to_sxp_bin)}
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
- if options[:think]
112
- count = 0
113
- log_info("reasoner: think start") { "count: #{count}"}
114
- while @mutable.count > count
115
- count = @mutable.count
116
- dataset = RDF::Graph.new << @mutable.project_graph(nil)
117
- log_depth {formula.execute(dataset, **options)}
118
- @mutable << formula
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
- log_debug("reasoner: datastore") {@mutable.to_sxp}
143
+ # Add updates back to mutable, containg builtins and variables.
144
+ @mutable << knowledge_base
131
145
 
132
- conclusions(&block) if block_given?
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 {execute}
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 {#conclusions}.
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 top-level formula for this file
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
- # SPARQL used for SSE and algebra functionality
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