ldpath 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,83 +1,83 @@
1
1
  # rubocop:disable Style/MethodName
2
2
  module Ldpath
3
3
  module Functions
4
- def concat(uri, context, *args)
5
- args.flatten.compact.join
4
+ def concat(_uri, _context, *args)
5
+ deep_flatten_compact(*args).to_a.join
6
6
  end
7
7
 
8
- def first(uri, context, *args)
9
- args.flatten.compact.first
8
+ def first(_uri, _context, *args)
9
+ deep_flatten_compact(*args).first
10
10
  end
11
11
 
12
- def last(uri, context, *args)
13
- args.flatten.compact.last
12
+ def last(_uri, _context, *args)
13
+ deep_flatten_compact(*args).to_a.last
14
14
  end
15
15
 
16
- def count(uri, context, *args)
17
- args.flatten.compact.length
16
+ def count(_uri, _context, *args)
17
+ deep_flatten_compact(*args).count
18
18
  end
19
19
 
20
- def eq(uri, context, *args)
21
- a, b, *rem = args.flatten
22
- unless rem.empty?
23
- raise "Too many arguments to fn:eq"
24
- end
20
+ def eq(_uri, _context, *args)
21
+ a, b, *rem = deep_flatten_compact(*args).first(3)
22
+ raise "Too many arguments to fn:eq" unless rem.empty?
23
+
25
24
  a == b
26
25
  end
27
26
 
28
- def ne(uri, context, *args)
29
- a, b, *rem = args.flatten
30
- unless rem.empty?
31
- raise "Too many arguments to fn:ne"
32
- end
27
+ def ne(_uri, _context, *args)
28
+ a, b, *rem = deep_flatten_compact(*args).first(3)
29
+ raise "Too many arguments to fn:ne" unless rem.empty?
30
+
33
31
  a != b
34
32
  end
35
33
 
36
- def lt(uri, context, *args)
37
- a, b, *rem = args.flatten
38
- unless rem.empty?
39
- raise "Too many arguments to fn:lt"
40
- end
34
+ def lt(_uri, _context, *args)
35
+ a, b, *rem = deep_flatten_compact(*args).first(3)
36
+ raise "Too many arguments to fn:lt" unless rem.empty?
37
+
41
38
  a < b
42
39
  end
43
40
 
44
- def le(uri, context, *args)
45
- a, b, *rem = args.flatten
46
- unless rem.empty?
47
- raise "Too many arguments to fn:le"
48
- end
41
+ def le(_uri, _context, *args)
42
+ a, b, *rem = deep_flatten_compact(*args).first(3)
43
+ raise "Too many arguments to fn:le" unless rem.empty?
44
+
49
45
  a <= b
50
46
  end
51
47
 
52
- def gt(uri, context, *args)
53
- a, b, *rem = args.flatten
54
- unless rem.empty?
55
- raise "Too many arguments to fn:gt"
56
- end
48
+ def gt(_uri, _context, *args)
49
+ a, b, *rem = deep_flatten_compact(*args).first(3)
50
+ raise "Too many arguments to fn:gt" unless rem.empty?
51
+
57
52
  a > b
58
53
  end
59
54
 
60
- def ge(uri, context, *args)
61
- a, b, *rem = args.flatten
62
- unless rem.empty?
63
- raise "Too many arguments to fn:ge"
64
- end
55
+ def ge(_uri, _context, *args)
56
+ a, b, *rem = deep_flatten_compact(*args).first(3)
57
+ raise "Too many arguments to fn:ge" unless rem.empty?
58
+
65
59
  a >= b
66
60
  end
67
61
 
68
62
  # collections
69
63
  def flatten(uri, context, lists)
70
- Array(lists).flatten.map { |x| RDF::List.new(x, context).to_a }.flatten
64
+ return to_enum(:flatten, uri, context, lists) unless block_given?
65
+
66
+ deep_flatten_compact(lists).each do |x|
67
+ RDF::List.new(subject: x, graph: context).to_a.each do |i|
68
+ yield i
69
+ end
70
+ end
71
71
  end
72
72
 
73
73
  def get(uri, context, list, idx)
74
74
  idx = idx.respond_to?(:to_i) ? idx.to_i : idx.to_s.to_i
75
75
 
76
- flatten(uri, context, list)[idx]
76
+ flatten(uri, context, list).to_a[idx]
77
77
  end
78
78
 
79
79
  def subList(uri, context, list, idx_start, idx_end = nil)
80
- arr = flatten(uri, context, list)
80
+ arr = flatten(uri, context, list).to_a
81
81
 
82
82
  idx_start = idx_start.respond_to?(:to_i) ? idx_start.to_i : idx_start.to_s.to_i
83
83
  idx_end &&= idx_end.respond_to?(:to_i) ? idx_end.to_i : idx_end.to_s.to_i
@@ -91,102 +91,114 @@ module Ldpath
91
91
 
92
92
  # dates
93
93
 
94
- def earliest(uri, context, *args)
95
- args.flatten.min
94
+ def earliest(_uri, _context, *args)
95
+ deep_flatten_compact(*args).min
96
96
  end
97
97
 
98
- def latest(uri, context, *args)
99
- args.flatten.max
98
+ def latest(_uri, _context, *args)
99
+ deep_flatten_compact(*args).max
100
100
  end
101
101
 
102
102
  # math
103
103
 
104
- def min(uri, context, *args)
105
- args.flatten.min
104
+ def min(_uri, _context, *args)
105
+ deep_flatten_compact(*args).min
106
106
  end
107
107
 
108
- def max(uri, context, *args)
109
- args.flatten.max
108
+ def max(_uri, _context, *args)
109
+ deep_flatten_compact(*args).max
110
110
  end
111
111
 
112
- def round(uri, context, *args)
113
- args.map do |n|
114
- Array(n).map do |i|
115
- i.respond_to? :round ? i.round : i
116
- end
112
+ def round(_uri, _context, *args)
113
+ deep_flatten_compact(*args).map do |i|
114
+ i.respond_to?(:round) ? i.round : i
117
115
  end
118
116
  end
119
117
 
120
- def sum(uri, context, *args)
121
- args.inject(0) { |sum, n| sum + n }
118
+ def sum(_uri, _context, *args)
119
+ args.inject(0) { |acc, elem| acc + elem }
122
120
  end
123
121
 
124
122
  # text
125
123
 
126
- def replace(uri, context, str, pattern, replacement)
124
+ def replace(_uri, _context, str, pattern, replacement)
127
125
  regex = Regexp.parse(pattern)
128
126
  Array(str).map do |x|
129
127
  x.gsub(regex, replacement)
130
128
  end
131
129
  end
132
130
 
133
- def strlen(uri, context, str)
131
+ def strlen(_uri, _context, str)
134
132
  Array(str).map(&:length)
135
133
  end
136
134
 
137
- def wc(uri, context, str)
135
+ def wc(_uri, _context, str)
138
136
  Array(str).map { |x| x.split.length }
139
137
  end
140
138
 
141
- def strLeft(uri, context, str, left)
139
+ def strLeft(_uri, _context, str, left)
142
140
  Array(str).map { |x| x[0..left.to_i] }
143
141
  end
144
142
 
145
- def strRight(uri, context, str, right)
143
+ def strRight(_uri, _context, str, right)
146
144
  Array(str).map { |x| x[right.to_i..x.length] }
147
145
  end
148
146
 
149
- def substr(uri, context, str, left, right)
147
+ def substr(_uri, _context, str, left, right)
150
148
  Array(str).map { |x| x[left.to_i..right.to_i] }
151
149
  end
152
150
 
153
- def strJoin(uri, context, str, sep = "", prefix = "", suffix = "")
151
+ def strJoin(_uri, _context, str, sep = "", prefix = "", suffix = "")
154
152
  prefix + Array(str).join(sep) + suffix
155
153
  end
156
154
 
157
- def equals(uri, context, str, other)
155
+ def equals(_uri, _context, str, other)
158
156
  Array(str).map { |x| x == other }
159
157
  end
160
158
 
161
- def equalsIgnoreCase(uri, context, str, other)
162
- Array(str).map { |x| x.downcase == other.downcase }
159
+ def equalsIgnoreCase(_uri, _context, str, other)
160
+ Array(str).map { |x| x.casecmp(other) }
163
161
  end
164
162
 
165
- def contains(uri, context, str, substr)
163
+ def contains(_uri, _context, str, substr)
166
164
  Array(str).map { |x| x.include? substr }
167
165
  end
168
166
 
169
- def startsWith(uri, context, str, suffix)
167
+ def startsWith(_uri, _context, str, suffix)
170
168
  Array(str).map { |x| x.start_with? suffix }
171
169
  end
172
170
 
173
- def endsWith(uri, context, str, suffix)
171
+ def endsWith(_uri, _context, str, suffix)
174
172
  Array(str).map { |x| x.end_with? suffix }
175
173
  end
176
174
 
177
- def isEmpty(uri, context, str)
175
+ def isEmpty(_uri, _context, str)
178
176
  Array(str).map(&:empty?)
179
177
  end
180
178
 
181
- def predicates(uri, context, *args)
179
+ def predicates(uri, context, *_args)
182
180
  context.query([uri, nil, nil]).map(&:predicate).uniq
183
181
  end
184
182
 
185
- def xpath(uri, context, xpath, node)
183
+ def xpath(_uri, _context, xpath, node)
186
184
  x = Array(xpath).flatten.first
187
185
  Array(node).flatten.compact.map do |n|
188
186
  Nokogiri::XML(n.to_s).xpath(x.to_s, prefixes.map { |k, v| [k, v.to_s] }).map(&:text)
189
187
  end
190
188
  end
189
+
190
+ private
191
+
192
+ def deep_flatten_compact(*args)
193
+ return to_enum(:deep_flatten_compact, *args) unless block_given?
194
+
195
+ args.each do |x|
196
+ if x.is_a? Enumerable
197
+ x.each { |y| yield y unless y.nil? }
198
+ else
199
+ yield x unless x.nil?
200
+ end
201
+ end
202
+ end
191
203
  end
192
204
  end
data/lib/ldpath/parser.rb CHANGED
@@ -116,8 +116,7 @@ module Ldpath
116
116
  rule(:identifier) { pn_chars_base >> (str(".").maybe >> pn_chars).repeat }
117
117
 
118
118
  rule(:pn_chars_base) do
119
- # also \u10000-\uEFFFF
120
- match("[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]")
119
+ match("[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]")
121
120
  end
122
121
 
123
122
  rule(:pn_chars) do
@@ -203,7 +202,7 @@ module Ldpath
203
202
 
204
203
  # ( x = "xyz", y = "abc" )
205
204
  rule(:field_type_options) do
206
- str("(") >> wsp? >> (field_type_option >> (wsp? >> comma >> wsp? >> field_type_option).repeat).as(:options) >> wsp? >> str(")")
205
+ group(field_type_option >> (wsp? >> comma >> wsp? >> field_type_option).repeat).as(:options)
207
206
  end
208
207
 
209
208
  # x = "xyz"
@@ -261,7 +260,8 @@ module Ldpath
261
260
  literal_selector |
262
261
  recursive_path_selector |
263
262
  grouped_selector |
264
- tap_selector
263
+ tap_selector |
264
+ not_property_selector
265
265
  )
266
266
  end
267
267
 
@@ -284,11 +284,11 @@ module Ldpath
284
284
  end
285
285
 
286
286
  rule(:function_without_args) do
287
- func >> identifier.as(:fname) >> str("()")
287
+ func >> identifier.as(:fname) >> group(wsp?)
288
288
  end
289
289
 
290
290
  rule(:function_with_arglist) do
291
- func >> identifier.as(:fname) >> str("(") >> wsp? >> arglist.as(:arglist) >> wsp? >> str(")")
291
+ func >> identifier.as(:fname) >> group(arglist.as(:arglist))
292
292
  end
293
293
 
294
294
  rule(:arglist) do
@@ -322,15 +322,21 @@ module Ldpath
322
322
  inverse.as(:reverse) >> iri.as(:property)
323
323
  end
324
324
 
325
+ rule(:not_property_selector) do
326
+ not_one_property_selector
327
+ end
328
+
329
+ rule(:not_one_property_selector) do
330
+ not_op.as(:not) >> iri.repeat(1, 1).as(:property)
331
+ end
332
+
325
333
  rule(:literal_selector) do
326
334
  literal.as(:literal)
327
335
  end
328
336
 
329
337
  # (x)?; (x)*; (x)+; (x){3,5}
330
338
  rule(:recursive_path_selector) do
331
- str("(") >> wsp? >>
332
- selector.as(:delegate) >> wsp? >>
333
- str(")") >>
339
+ group(selector.as(:delegate)) >>
334
340
  range.as(:repeat)
335
341
  end
336
342
 
@@ -346,18 +352,16 @@ module Ldpath
346
352
 
347
353
  # (<info:a>)
348
354
  rule(:grouped_selector) do
349
- str("(") >> wsp? >>
350
- selector >> wsp? >>
351
- str(")")
355
+ group(selector)
352
356
  end
353
357
 
354
358
  # ?<a>(<info:a>)
355
359
  rule(:tap_selector) do
356
360
  question >>
357
361
  str("<") >> wsp? >>
358
- identifier.as(:identifier) >> wsp? >>
362
+ (str(">").absent? >> any).repeat(1).as(:identifier) >> wsp? >>
359
363
  str(">") >> wsp? >>
360
- (atomic_selector).as(:tap)
364
+ atomic_selector.as(:tap)
361
365
  end
362
366
 
363
367
  # Testing Selectors
@@ -365,8 +369,7 @@ module Ldpath
365
369
  rule(:node_test) do
366
370
  grouped_test |
367
371
  not_test |
368
- and_test |
369
- or_test |
372
+ compound_test |
370
373
  atomic_node_test
371
374
  end
372
375
 
@@ -380,31 +383,17 @@ module Ldpath
380
383
  end
381
384
 
382
385
  rule(:grouped_test) do
383
- str("(") >> wsp? >>
384
- node_test >> wsp? >>
385
- str(")")
386
+ group(node_test)
386
387
  end
387
388
 
388
389
  rule(:not_test) do
389
- (
390
- not_op >> node_test.as(:delegate)
391
- ).as(:not)
390
+ not_op.as(:not) >> node_test.as(:delegate)
392
391
  end
393
392
 
394
- rule(:and_test) do
395
- (
396
- atomic_node_test.as(:left) >> wsp? >>
397
- and_op >> wsp? >>
398
- node_test.as(:right)
399
- ).as(:and)
400
- end
401
-
402
- rule(:or_test) do
403
- (
404
- atomic_node_test.as(:left) >> wsp? >>
405
- or_op >> wsp? >>
406
- node_test.as(:right)
407
- ).as(:or)
393
+ rule(:compound_test) do
394
+ atomic_node_test.as(:left_test) >> wsp? >>
395
+ compound_operator.as(:op) >> wsp? >>
396
+ node_test.as(:right_test)
408
397
  end
409
398
 
410
399
  # @en
@@ -443,5 +432,11 @@ module Ldpath
443
432
  atomic_selector
444
433
  )
445
434
  end
435
+
436
+ def group(atom)
437
+ str("(") >> wsp? >>
438
+ atom >> wsp? >>
439
+ str(")")
440
+ end
446
441
  end
447
442
  end
@@ -1,8 +1,6 @@
1
1
  module Ldpath
2
2
  class Program
3
- include Ldpath::Functions
4
-
5
- ParseError = Class.new StandardError
3
+ ParseError = Class.new StandardError
6
4
 
7
5
  class << self
8
6
  def parse(program, transform_context = {})
@@ -14,7 +12,7 @@ module Ldpath
14
12
  def load(program)
15
13
  parser.parse(program, reporter: Parslet::ErrorReporter::Deepest.new)
16
14
  rescue Parslet::ParseFailed => e
17
- fail ParseError, e.cause.ascii_tree
15
+ raise ParseError, e.parse_failure_cause.ascii_tree
18
16
  end
19
17
 
20
18
  private
@@ -28,73 +26,20 @@ module Ldpath
28
26
  end
29
27
  end
30
28
 
31
- attr_reader :mappings, :cache, :loaded, :prefixes, :filters
29
+ attr_reader :mappings, :prefixes, :filters
32
30
  def initialize(mappings, options = {})
33
31
  @mappings ||= mappings
34
- @cache = options[:cache] || RDF::Util::Cache.new
35
32
  @prefixes = options[:prefixes] || {}
36
33
  @filters = options[:filters] || []
37
- @loaded = {}
38
- end
39
-
40
- def loading(uri, context)
41
- if uri.to_s =~ /^http/ && !loaded[uri.to_s]
42
- context << load_graph(uri.to_s)
43
- end
44
- end
45
-
46
- def load_graph(uri)
47
- cache[uri] ||= begin
48
- Ldpath.logger.debug "[#{object_id}] Loading #{uri.inspect}"
49
-
50
- reader_types = RDF::Format.reader_types.reject { |t| t.to_s =~ /html/ }.map do |t|
51
- t.to_s =~ /text\/(?:plain|html)/ ? "#{t};q=0.5" : t
52
- end
53
-
54
- RDF::Graph.load(uri, headers: { 'Accept' => reader_types.join(", ") }).tap { loaded[uri] = true }
55
- end
56
34
  end
57
35
 
58
- def evaluate(uri, context = nil)
59
- h = {}
60
- context ||= load_graph(uri.to_s)
61
-
36
+ def evaluate(uri, context: nil, limit_to_context: false)
37
+ result = Ldpath::Result.new(self, uri, context: context, limit_to_context: limit_to_context)
62
38
  unless filters.empty?
63
- return h unless filters.all? { |f| f.evaluate(self, uri, context) }
39
+ return {} unless filters.all? { |f| f.evaluate(result, uri, result.context) }
64
40
  end
65
41
 
66
- mappings.each do |m|
67
- h[m.name] ||= []
68
- h[m.name] += case m.selector
69
- when Selector
70
- m.selector.evaluate(self, uri, context).map do |x|
71
- next x unless m.field_type
72
- RDF::Literal.new(x.to_s, datatype: m.field_type).canonicalize.object
73
- end
74
- else
75
- Array(m.selector)
76
- end
77
- end
78
-
79
- h.merge(meta)
80
- end
81
-
82
- def meta
83
- @meta ||= {}
84
- end
85
-
86
- def func_call(fname, uri, context, *arguments)
87
- if function_method? fname
88
- public_send(fname, uri, context, *arguments)
89
- else
90
- raise "No such function: #{fname}"
91
- end
92
- end
93
-
94
- private
95
-
96
- def function_method?(function)
97
- Functions.public_instance_methods(false).include? function.to_sym
42
+ result.to_hash
98
43
  end
99
44
  end
100
45
  end
@@ -0,0 +1,83 @@
1
+ module Ldpath
2
+ class Result
3
+ include Ldpath::Functions
4
+ attr_reader :program, :uri, :cache, :loaded
5
+
6
+ def initialize(program, uri, cache: RDF::Util::Cache.new, context: nil, limit_to_context: false)
7
+ @program = program
8
+ @uri = uri
9
+ @cache = cache
10
+ @loaded = {}
11
+ @context = context
12
+ @limit_to_context = limit_to_context
13
+ end
14
+
15
+ def loading(uri, context)
16
+ return unless uri.to_s =~ /^http/
17
+ return if loaded[uri.to_s]
18
+
19
+ context << load_graph(uri.to_s) unless limit_to_context?
20
+ context
21
+ end
22
+
23
+ def load_graph(uri)
24
+ cache[uri] ||= begin
25
+ Ldpath.logger.debug "[#{object_id}] Loading #{uri.inspect}"
26
+
27
+ reader_types = RDF::Format.reader_types.reject { |t| t.to_s =~ /html/ }.map do |t|
28
+ t.to_s =~ %r{text/(?:plain|html)} ? "#{t};q=0.5" : t
29
+ end
30
+
31
+ RDF::Graph.load(uri, headers: { 'Accept' => reader_types.join(", ") }).tap { loaded[uri] = true }
32
+ end
33
+ end
34
+
35
+ def [](key)
36
+ evaluate(mappings.find { |x| x.name == key })
37
+ end
38
+
39
+ def to_hash
40
+ h = mappings.each_with_object({}) do |mapping, hash|
41
+ hash[mapping.name] = evaluate(mapping).to_a
42
+ end
43
+
44
+ h.merge(meta)
45
+ end
46
+
47
+ def func_call(fname, uri, context, *arguments)
48
+ raise "No such function: #{fname}" unless function_method? fname
49
+
50
+ public_send(fname, uri, context, *arguments)
51
+ end
52
+
53
+ def context
54
+ @context ||= load_graph(uri.to_s)
55
+ end
56
+
57
+ def prefixes
58
+ program.prefixes
59
+ end
60
+
61
+ def meta
62
+ @meta ||= {}
63
+ end
64
+
65
+ private
66
+
67
+ def evaluate(mapping)
68
+ mapping.evaluate(self, uri, context)
69
+ end
70
+
71
+ def function_method?(function)
72
+ Functions.public_instance_methods(false).include? function.to_sym
73
+ end
74
+
75
+ def mappings
76
+ program.mappings
77
+ end
78
+
79
+ def limit_to_context?
80
+ @limit_to_context
81
+ end
82
+ end
83
+ end