ldpath 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|