ldpath 0.3.1 → 1.0.0

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.
@@ -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