ldpath 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +17 -2
- data/ldpath.gemspec +1 -0
- data/lib/ldpath.rb +13 -0
- data/lib/ldpath/field_mapping.rb +2 -0
- data/lib/ldpath/functions.rb +144 -0
- data/lib/ldpath/parser.rb +358 -0
- data/lib/ldpath/program.rb +63 -252
- data/lib/ldpath/selectors.rb +124 -0
- data/lib/ldpath/tests.rb +102 -0
- data/lib/ldpath/transform.rb +143 -0
- data/lib/ldpath/version.rb +1 -1
- data/spec/fixtures/foaf_example.program +9 -0
- data/spec/fixtures/namespaces.ldpath +21 -0
- data/spec/fixtures/program.ldpath +41 -0
- data/spec/ldpath_parser_spec.rb +162 -0
- data/spec/ldpath_program_spec.rb +115 -0
- data/spec/ldpath_transform_spec.rb +62 -0
- metadata +34 -4
- data/spec/ldpath_program_parser_spec.rb +0 -34
data/lib/ldpath/program.rb
CHANGED
@@ -1,259 +1,70 @@
|
|
1
|
-
|
1
|
+
module Ldpath
|
2
|
+
class Program
|
2
3
|
|
3
|
-
|
4
|
-
root :lines
|
5
|
-
rule(:lines) { line.repeat }
|
6
|
-
rule(:line) { expression >> newline }
|
7
|
-
rule(:newline) { any.absent? | str("\n") >> str("\r").maybe }
|
8
|
-
rule(:expression) { wsp | multiline_comment | namespace | mapping }
|
9
|
-
|
10
|
-
rule(:multiline_comment) { (str('/*') >> (str('*/').absent? >> any).repeat >> str('*/')) }
|
11
|
-
|
12
|
-
rule(:comma) { str(",") }
|
13
|
-
rule(:scolon) { str(";") }
|
14
|
-
rule(:colon) { str(":") }
|
15
|
-
rule(:dcolon) { str("::") }
|
16
|
-
rule(:assign) { str("=") }
|
17
|
-
rule(:k_prefix) { str("@prefix")}
|
18
|
-
rule(:k_graph) { str("@graph")}
|
19
|
-
|
20
|
-
rule(:self_op) { str(".") }
|
21
|
-
rule(:and_op) { str("&") }
|
22
|
-
rule(:or_op) { str("|") }
|
23
|
-
rule(:p_sep) { str("/") }
|
24
|
-
rule(:plus) { str("+") }
|
25
|
-
rule(:star) { str("*") }
|
26
|
-
rule(:not_op) { str("!") }
|
27
|
-
rule(:inverse) { str("^") }
|
28
|
-
rule(:is) { str "is" }
|
29
|
-
rule(:is_a) { str "is-a" }
|
30
|
-
rule(:func) { str "fn:"}
|
31
|
-
rule(:type) { str "^^" }
|
32
|
-
rule(:lang) { str "@" }
|
33
|
-
|
34
|
-
rule(:wsp) { (match["\s"] | str("\n")).repeat(1) }
|
35
|
-
rule(:wsp?) { wsp.maybe }
|
36
|
-
|
37
|
-
# todo: fixme
|
38
|
-
rule(:uri) { (str("<") >> (str(">").absent? >> any).repeat >> str(">")) | (identifier.as(:prefix) >> str(":") >> identifier.as(:localName) )}
|
39
|
-
rule(:identifier) { match["a-zA-Z0-9_"] >> (match["a-zA-Z0-9_'\\.-"]).repeat }
|
4
|
+
include Ldpath::Functions
|
40
5
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
identifier.as(:name) >>
|
59
|
-
wsp? >>
|
60
|
-
assign >>
|
61
|
-
wsp? >>
|
62
|
-
selector.as(:selector) >>
|
63
|
-
wsp? >>
|
64
|
-
(
|
65
|
-
dcolon >> wsp? >>
|
66
|
-
uri.as(:type)
|
67
|
-
).maybe >>
|
68
|
-
wsp? >>
|
69
|
-
scolon
|
70
|
-
}
|
71
|
-
|
72
|
-
rule(:selector) {
|
73
|
-
(
|
74
|
-
compound_selector |
|
75
|
-
testing_selector |
|
76
|
-
atomic_selector
|
77
|
-
).as(:result)
|
78
|
-
}
|
79
|
-
|
80
|
-
rule(:compound_selector) {
|
81
|
-
(
|
82
|
-
union_selector |
|
83
|
-
intersection_selector |
|
84
|
-
path_selector
|
85
|
-
).as(:result)
|
86
|
-
}
|
87
|
-
|
88
|
-
rule(:grouped_selector) {
|
89
|
-
wsp? >>
|
90
|
-
str("(") >> wsp? >> selector.as(:result) >> wsp? >> str(")") >> wsp?
|
91
|
-
}
|
92
|
-
|
93
|
-
rule(:path_selector) {
|
94
|
-
wsp? >>
|
95
|
-
atomic_or_testing_selector.as(:left) >>
|
96
|
-
wsp? >>
|
97
|
-
p_sep >>
|
98
|
-
wsp? >>
|
99
|
-
atomic_or_testing_or_path_selector.as(:right) >>
|
100
|
-
wsp?
|
101
|
-
}
|
102
|
-
|
103
|
-
rule(:intersection_selector) {
|
104
|
-
wsp? >>
|
105
|
-
atomic_or_testing_or_path_selector.as(:left) >>
|
106
|
-
wsp? >>
|
107
|
-
and_op >>
|
108
|
-
wsp? >>
|
109
|
-
selector.as(:right) >>
|
110
|
-
wsp?
|
111
|
-
}
|
112
|
-
|
113
|
-
rule(:union_selector) {
|
114
|
-
wsp? >>
|
115
|
-
atomic_or_testing_or_path_selector.as(:left) >>
|
6
|
+
class << self
|
7
|
+
def parse program, transform_context = {}
|
8
|
+
parsed = parser.parse(program)
|
9
|
+
ast = transform.apply parsed, transform_context
|
10
|
+
|
11
|
+
Ldpath::Program.new ast.compact
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def transform
|
16
|
+
Ldpath::Transform.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def parser
|
20
|
+
@parser ||= Ldpath::Parser.new
|
21
|
+
end
|
22
|
+
end
|
116
23
|
|
117
|
-
|
118
|
-
|
24
|
+
attr_reader :mappings
|
25
|
+
def initialize mappings
|
26
|
+
@mappings ||= mappings
|
27
|
+
@cache = {}
|
28
|
+
end
|
119
29
|
|
120
|
-
|
121
|
-
|
30
|
+
def loading uri, context
|
31
|
+
if uri.to_s =~ /^http/ and !context.has_subject?(uri)
|
32
|
+
@cache[uri] ||= RDF::Graph.load(uri)
|
33
|
+
context << @cache[uri]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def evaluate uri, context = nil
|
38
|
+
h = {}
|
39
|
+
context ||= RDF::Graph.load uri.to_s
|
40
|
+
|
41
|
+
mappings.each do |m|
|
42
|
+
h[m.name] = case m.selector
|
43
|
+
when Selector
|
44
|
+
m.selector.evaluate(self, uri, context).map do |x|
|
45
|
+
next x unless m.field_type
|
46
|
+
RDF::Literal.new(x.to_s, datatype: m.field_type).canonicalize.object
|
47
|
+
end
|
48
|
+
else
|
49
|
+
Array(m.selector)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
h
|
54
|
+
end
|
122
55
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
(
|
136
|
-
self_selector |
|
137
|
-
property_selector |
|
138
|
-
wildcard_selector |
|
139
|
-
reverse_property_selector |
|
140
|
-
function_selector |
|
141
|
-
# string_constant_selector |
|
142
|
-
# recursive_path_selector |
|
143
|
-
grouped_selector
|
144
|
-
).as(:result)
|
145
|
-
}
|
146
|
-
|
147
|
-
rule(:self_selector) {
|
148
|
-
wsp? >> self_op >> wsp?
|
149
|
-
}
|
150
|
-
|
151
|
-
rule(:property_selector) {
|
152
|
-
wsp? >> uri >> wsp?
|
153
|
-
}
|
154
|
-
|
155
|
-
rule(:reverse_property_selector) {
|
156
|
-
wsp? >> inverse >> uri.as(:uri) >> wsp?
|
157
|
-
}
|
158
|
-
|
159
|
-
rule(:wildcard_selector) {
|
160
|
-
wsp? >> star >> wsp?
|
161
|
-
}
|
162
|
-
|
163
|
-
rule(:testing_selector) {
|
164
|
-
wsp? >>
|
165
|
-
atomic_selector.as(:delegate) >>
|
166
|
-
str("[") >>
|
167
|
-
wsp? >>
|
168
|
-
node_test.as(:test) >>
|
169
|
-
wsp? >>
|
170
|
-
str("]") >> wsp?
|
171
|
-
}
|
172
|
-
|
173
|
-
rule(:node_test) {
|
174
|
-
grouped_test |
|
175
|
-
not_test |
|
176
|
-
and_test |
|
177
|
-
or_test |
|
178
|
-
atomic_node_test
|
179
|
-
}
|
180
|
-
|
181
|
-
rule(:grouped_test) {
|
182
|
-
wsp? >> str("(") >> wsp? >> node_test >> wsp? >> str(")") >> wsp?
|
183
|
-
}
|
184
|
-
|
185
|
-
rule(:atomic_node_test) {
|
186
|
-
# literal_language_test |
|
187
|
-
# literal_type_test |
|
188
|
-
is_a_test |
|
189
|
-
path_equality_test |
|
190
|
-
function_test |
|
191
|
-
path_test
|
192
|
-
}
|
193
|
-
|
194
|
-
rule(:not_test) {
|
195
|
-
wsp? >> not_op >> node_test.as(:delegate) >> wsp?
|
196
|
-
}
|
197
|
-
|
198
|
-
rule(:and_test) {
|
199
|
-
wsp? >> atomic_node_test >> wsp? >> and_op >> wsp? >> node_test >> wsp?
|
200
|
-
}
|
201
|
-
|
202
|
-
rule(:or_test) {
|
203
|
-
wsp? >> atomic_node_test >> wsp? >> or_op >> wsp? >> node_test >> wsp?
|
204
|
-
}
|
205
|
-
|
206
|
-
rule(:strlit) {
|
207
|
-
wsp? >> str('"') >> (str('"').absent? >> any).repeat >> str('"') >> wsp?
|
208
|
-
}
|
209
|
-
|
210
|
-
rule(:node) {
|
211
|
-
uri.as(:uri) | strlit.as(:literal)
|
212
|
-
}
|
213
|
-
|
214
|
-
rule(:is_a_test) {
|
215
|
-
wsp? >> is_a >> wsp? >> node.as(:node) >> wsp?
|
216
|
-
}
|
217
|
-
|
218
|
-
rule(:path_equality_test) {
|
219
|
-
selector.as(:path) >> is >> node.as(:node)
|
220
|
-
}
|
221
|
-
|
222
|
-
rule(:function_selector) {
|
223
|
-
(
|
224
|
-
func >> identifier.as(:fname) >> str("()") |
|
225
|
-
func >> identifier.as(:fname) >> str("(") >>
|
226
|
-
wsp? >>
|
227
|
-
(selector.as(:argument)) >>
|
228
|
-
(
|
229
|
-
(wsp? >> str(",") >> wsp? >> selector.as(:argument)).repeat
|
230
|
-
).maybe >>
|
231
|
-
wsp? >>
|
232
|
-
str(")")
|
233
|
-
)
|
234
|
-
}
|
235
|
-
|
236
|
-
rule(:function_test) {
|
237
|
-
(
|
238
|
-
func >> identifier.as(:fname) >> str("()") |
|
239
|
-
func >> identifier.as(:fname) >> str("(") >>
|
240
|
-
wsp? >>
|
241
|
-
(selector.as(:argument)) >>
|
242
|
-
(
|
243
|
-
(wsp? >> str(",") >> wsp? >> selector.as(:argument)).repeat
|
244
|
-
).maybe >>
|
245
|
-
wsp? >>
|
246
|
-
str(")")
|
247
|
-
)
|
248
|
-
}
|
249
|
-
|
250
|
-
rule(:path_test) {
|
251
|
-
(
|
252
|
-
path_selector |
|
253
|
-
testing_selector |
|
254
|
-
atomic_selector
|
255
|
-
).as(:path)
|
256
|
-
}
|
257
|
-
|
56
|
+
def func_call fname, uri, context, *arguments
|
57
|
+
if function_method? fname
|
58
|
+
public_send(fname, uri, context, *arguments)
|
59
|
+
else
|
60
|
+
raise "No such function: #{fname}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def function_method? function
|
66
|
+
Functions.public_instance_methods(false).include? function.to_sym
|
67
|
+
end
|
258
68
|
|
69
|
+
end
|
259
70
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Ldpath
|
2
|
+
class Selector
|
3
|
+
def evaluate program, uris, context
|
4
|
+
Array(uris).map do |uri|
|
5
|
+
loading program, uri, context
|
6
|
+
evaluate_one uri, context
|
7
|
+
end.flatten.compact
|
8
|
+
end
|
9
|
+
|
10
|
+
def loading program, uri, context
|
11
|
+
program.loading uri, context
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class SelfSelector < Selector
|
16
|
+
def evaluate_one uri, context
|
17
|
+
uri
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class FunctionSelector < Selector
|
22
|
+
attr_reader :fname, :arguments
|
23
|
+
|
24
|
+
def initialize fname, arguments
|
25
|
+
@fname = fname
|
26
|
+
@arguments = arguments
|
27
|
+
end
|
28
|
+
|
29
|
+
def evaluate program, uris, context
|
30
|
+
Array(uris).map do |uri|
|
31
|
+
loading program, uri, context
|
32
|
+
args = arguments.map do |i|
|
33
|
+
case i
|
34
|
+
when Selector
|
35
|
+
i.evaluate(program, uri, context)
|
36
|
+
else
|
37
|
+
i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
program.func_call fname, uri, context, *arguments
|
41
|
+
end.flatten.compact
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class PropertySelector < Selector
|
46
|
+
attr_reader :property
|
47
|
+
def initialize property
|
48
|
+
@property = property
|
49
|
+
end
|
50
|
+
|
51
|
+
def evaluate_one uri, context
|
52
|
+
context.query([uri, property, nil]).map(&:object)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class WildcardSelector < Selector
|
57
|
+
def evaluate_one uri, context
|
58
|
+
context.query([uri, nil, nil]).map(&:object)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ReversePropertySelector < Selector
|
63
|
+
attr_reader :property
|
64
|
+
def initialize property
|
65
|
+
@property = property
|
66
|
+
end
|
67
|
+
|
68
|
+
def evaluate_one uri, context
|
69
|
+
context.query([nil, property, uri]).map(&:subject)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class RecursivePathSelector < Selector
|
74
|
+
attr_reader :property, :repeat
|
75
|
+
def initialize property, repeat
|
76
|
+
@property = property
|
77
|
+
@repeat = repeat
|
78
|
+
end
|
79
|
+
|
80
|
+
def evaluate program, uris, context
|
81
|
+
result = []
|
82
|
+
input = Array(uris)
|
83
|
+
|
84
|
+
Range.new(0,repeat.min,true).each do
|
85
|
+
input = property.evaluate program, input, context
|
86
|
+
end
|
87
|
+
|
88
|
+
repeat.each_with_index do |i, idx|
|
89
|
+
break if input.empty? or idx > 25 # we're probably lost..
|
90
|
+
input = property.evaluate program, input, context
|
91
|
+
result |= input
|
92
|
+
end
|
93
|
+
result.flatten.compact
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class CompoundSelector < Selector
|
98
|
+
attr_reader :left, :right
|
99
|
+
def initialize left, right
|
100
|
+
@left = left
|
101
|
+
@right = right
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class PathSelector < CompoundSelector
|
106
|
+
def evaluate program, uris, context
|
107
|
+
output = left.evaluate(program, uris, context)
|
108
|
+
right.evaluate(program, output, context)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class UnionSelector < CompoundSelector
|
113
|
+
def evaluate program, uris, context
|
114
|
+
left.evaluate(program, uris, context) | right.evaluate(program, uris, context)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class IntersectionSelector < CompoundSelector
|
119
|
+
def evaluate program, uris, context
|
120
|
+
left.evaluate(program, uris, context) & right.evaluate(program, uris, context)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/lib/ldpath/tests.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Ldpath
|
2
|
+
|
3
|
+
class TestSelector < Selector
|
4
|
+
attr_reader :delegate, :test
|
5
|
+
|
6
|
+
def initialize delegate, test
|
7
|
+
@delegate = delegate
|
8
|
+
@test = test
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate program, uris, context
|
12
|
+
entries = delegate.evaluate program, uris, context
|
13
|
+
entries.select do |uri|
|
14
|
+
Array(test.evaluate(program, uri, context)).any? do |x|
|
15
|
+
x
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class LanguageTest < TestSelector
|
22
|
+
attr_reader :lang
|
23
|
+
def initialize lang
|
24
|
+
@lang = lang
|
25
|
+
end
|
26
|
+
|
27
|
+
def evaluate program, uri, context
|
28
|
+
return unless uri.literal?
|
29
|
+
if (lang == "none" && !uri.has_language?) or uri.language == lang
|
30
|
+
uri
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TypeTest < TestSelector
|
36
|
+
attr_reader :type
|
37
|
+
def initialize type
|
38
|
+
@type = type
|
39
|
+
end
|
40
|
+
|
41
|
+
def evaluate program, uri, context
|
42
|
+
return unless uri.literal?
|
43
|
+
if uri.has_datatype? and uri.datatype == type
|
44
|
+
uri
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class NotTest < TestSelector
|
50
|
+
attr_reader :delegate
|
51
|
+
|
52
|
+
def initialize delegate
|
53
|
+
@delegate = delegate
|
54
|
+
end
|
55
|
+
|
56
|
+
def evaluate program, uri, context
|
57
|
+
!Array(delegate.evaluate(program, uri, context)).any? { |x| x }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class OrTest < TestSelector
|
62
|
+
attr_reader :left, :right
|
63
|
+
|
64
|
+
def initialize left, right
|
65
|
+
@left = left
|
66
|
+
@right = right
|
67
|
+
end
|
68
|
+
|
69
|
+
def evaluate program, uri, context
|
70
|
+
left.evaluate(program, uri, context).any? || right.evaluate(program, uri, context).any?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class AndTest < TestSelector
|
75
|
+
|
76
|
+
attr_reader :left, :right
|
77
|
+
|
78
|
+
def initialize left, right
|
79
|
+
@left = left
|
80
|
+
@right = right
|
81
|
+
end
|
82
|
+
|
83
|
+
def evaluate program, uri, context
|
84
|
+
left.evaluate(program, uri, context).compact.any? &&
|
85
|
+
right.evaluate(program, uri, context).compact.any?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class IsTest < TestSelector
|
90
|
+
|
91
|
+
attr_reader :left, :right
|
92
|
+
|
93
|
+
def initialize left, right
|
94
|
+
@left = left
|
95
|
+
@right = right
|
96
|
+
end
|
97
|
+
|
98
|
+
def evaluate program, uri, context
|
99
|
+
left.evaluate(program, uri, context).compact.include?(right)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|