solis 0.98.0 → 0.99.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.
- checksums.yaml +4 -4
- data/lib/solis/query/run.rb +249 -42
- data/lib/solis/version.rb +1 -1
- metadata +1 -2
- data/lib/solis/query/result_transformer.rb +0 -130
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9c57f5cfabe93ada0f5aa3c008ecafa1b130147dfc7ca5b77930362a330a3457
|
|
4
|
+
data.tar.gz: 7b9854033bb6e4677bde52c4557705f4f48adffe2e0988af33600890c4615984
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f527bc987723ee900f250c8361a7570df65e3f6238c8ccc560c65097f94e0941a43540222e35e26196f4a70ed94d193040d55f0b2455f38f8ea2b2c39466e2f
|
|
7
|
+
data.tar.gz: 404047145bf332a80ee0bee495640071137682f009a1a17de7e21b8d531a2b3cf57aec7c704a423366f778c2cc2259267ed1e67f638fc092c38858c611f72a81
|
data/lib/solis/query/run.rb
CHANGED
|
@@ -1,75 +1,282 @@
|
|
|
1
1
|
require 'solis/store/sparql/client'
|
|
2
|
-
require 'solis/query/result_transformer'
|
|
3
2
|
require 'solis/config_file'
|
|
4
3
|
|
|
5
4
|
class Solis::Query::Runner
|
|
6
5
|
def self.run(entity, query, options = {})
|
|
7
|
-
|
|
8
|
-
Solis::Options.instance.get[:sparql_endpoint],
|
|
9
|
-
graph_name: graph_name
|
|
10
|
-
)
|
|
6
|
+
result = {}
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
c = Solis::Store::Sparql::Client.new(Solis::Options.instance.get[:sparql_endpoint], graph_name: graph_name)
|
|
9
|
+
r = c.query(query, options)
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
if r.is_a?(SPARQL::Client)
|
|
12
|
+
result = direct_transform_with_embedding(r, entity, options)
|
|
13
|
+
else
|
|
14
|
+
t = r.map(&:to_h)
|
|
15
|
+
result = sanitize_result({'@graph' => t})
|
|
16
|
+
end
|
|
17
|
+
result
|
|
16
18
|
rescue StandardError => e
|
|
17
19
|
puts e.message
|
|
18
20
|
raise e
|
|
19
21
|
end
|
|
20
22
|
|
|
23
|
+
def self.direct_transform_with_embedding(client, entity, options = {})
|
|
24
|
+
results = client.query('select * where{?s ?p ?o}')
|
|
25
|
+
|
|
26
|
+
# Step 1: Group all triples by subject
|
|
27
|
+
grouped = group_by_subject(results)
|
|
28
|
+
|
|
29
|
+
# Step 2: Build objects index (without embedding yet)
|
|
30
|
+
objects_index = build_objects_index(grouped)
|
|
31
|
+
|
|
32
|
+
# Step 3: Embed references recursively
|
|
33
|
+
max_depth = options[:max_embed_depth] || 10
|
|
34
|
+
root_subjects = find_root_subjects(grouped, entity)
|
|
35
|
+
|
|
36
|
+
root_subjects.map do |subject|
|
|
37
|
+
embed_references(objects_index[subject], objects_index, max_depth, Set.new)
|
|
38
|
+
end.compact
|
|
39
|
+
end
|
|
40
|
+
|
|
21
41
|
private
|
|
22
42
|
|
|
23
|
-
def self.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
def self.group_by_subject(results)
|
|
44
|
+
results.each_with_object({}) do |solution, acc|
|
|
45
|
+
subject = solution.s.to_s
|
|
46
|
+
acc[subject] ||= []
|
|
47
|
+
acc[subject] << { predicate: solution.p, object: solution.o }
|
|
28
48
|
end
|
|
29
49
|
end
|
|
30
50
|
|
|
31
|
-
def self.
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
def self.build_objects_index(grouped)
|
|
52
|
+
grouped.each_with_object({}) do |(subject, triples), index|
|
|
53
|
+
obj = {
|
|
54
|
+
'_id' => subject, # Full URI for resolution
|
|
55
|
+
'id' => nil, # Will be set from predicate if exists
|
|
56
|
+
'@subject' => subject, # Internal marker for reference resolution
|
|
57
|
+
'@type' => nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
triples.each do |triple|
|
|
61
|
+
predicate = triple[:predicate]
|
|
62
|
+
object = triple[:object]
|
|
63
|
+
|
|
64
|
+
# Handle rdf:type
|
|
65
|
+
if predicate.to_s =~ /type$/i || predicate == RDF::RDFV.type
|
|
66
|
+
obj['@type'] = object.to_s.split('/').last
|
|
67
|
+
next
|
|
68
|
+
end
|
|
34
69
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
70
|
+
# Get predicate name (last part of URI)
|
|
71
|
+
pred_name = predicate.to_s.split('/').last.underscore
|
|
72
|
+
|
|
73
|
+
# Extract value
|
|
74
|
+
value = if object.is_a?(RDF::URI)
|
|
75
|
+
{ '@ref' => object.to_s } # Mark as reference for later resolution
|
|
76
|
+
else
|
|
77
|
+
extract_value(object)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Capture the 'id' predicate value specifically
|
|
81
|
+
if pred_name == 'id'
|
|
82
|
+
obj['id'] = value
|
|
83
|
+
next
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Handle multiple values for same predicate
|
|
87
|
+
if obj.key?(pred_name)
|
|
88
|
+
obj[pred_name] = [obj[pred_name]] unless obj[pred_name].is_a?(Array)
|
|
89
|
+
obj[pred_name] << value
|
|
90
|
+
else
|
|
91
|
+
obj[pred_name] = value
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Fallback: if no 'id' predicate was found, extract from URI
|
|
96
|
+
if obj['id'].nil?
|
|
97
|
+
obj['id'] = subject.split('/').last
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if obj['@type'].nil?
|
|
101
|
+
obj['@type'] = subject.split('/')[-2].classify
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
index[subject] = obj
|
|
38
105
|
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.find_root_subjects(grouped, entity)
|
|
109
|
+
# Find subjects that match the requested entity type
|
|
110
|
+
grouped.select do |subject, triples|
|
|
111
|
+
type_triple = triples.find { |t| t[:predicate].to_s =~ /type$/i || t[:predicate] == RDF::RDFV.type }
|
|
112
|
+
next false unless type_triple
|
|
39
113
|
|
|
40
|
-
|
|
114
|
+
type_name = type_triple[:object].to_s.split('/').last
|
|
115
|
+
type_name.downcase == entity.downcase ||
|
|
116
|
+
type_name.tableize == entity.tableize ||
|
|
117
|
+
type_name == entity
|
|
118
|
+
end.keys
|
|
41
119
|
end
|
|
42
120
|
|
|
43
|
-
def self.
|
|
44
|
-
|
|
45
|
-
|
|
121
|
+
def self.embed_references(obj, objects_index, max_depth, visited, current_depth = 0)
|
|
122
|
+
return nil if obj.nil?
|
|
123
|
+
|
|
124
|
+
subject = obj['@subject']
|
|
125
|
+
|
|
126
|
+
# At max depth, return minimal reference with both IDs
|
|
127
|
+
if current_depth >= max_depth
|
|
128
|
+
#return { '_id' => obj['_id'], 'id' => obj['id'], '@type' => obj['@type'] }
|
|
129
|
+
return { '_id' => obj['_id'], 'id' => obj['id'] }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Circular reference detection
|
|
133
|
+
if visited.include?(subject)
|
|
134
|
+
# Return a reference object instead of embedding
|
|
135
|
+
#return { '_id' => obj['_id'], 'id' => obj['id'], '@type' => obj['@type'] }
|
|
136
|
+
return { '_id' => obj['_id'], 'id' => obj['id'] }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
visited = visited.dup
|
|
140
|
+
visited.add(subject)
|
|
141
|
+
|
|
142
|
+
# Create clean copy without internal markers (except _id)
|
|
143
|
+
result = {
|
|
144
|
+
'_id' => obj['_id'],
|
|
145
|
+
'id' => obj['id']
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
obj.each do |key, value|
|
|
149
|
+
next if key.start_with?('@') # Skip internal markers
|
|
150
|
+
next if key == '_id' || key == 'id' # Already added
|
|
151
|
+
|
|
152
|
+
result[key] = resolve_value(value, objects_index, max_depth, visited, current_depth)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
result
|
|
46
156
|
end
|
|
47
157
|
|
|
48
|
-
def self.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
158
|
+
def self.resolve_value(value, objects_index, max_depth, visited, current_depth)
|
|
159
|
+
case value
|
|
160
|
+
when Array
|
|
161
|
+
value.map { |v| resolve_value(v, objects_index, max_depth, visited, current_depth) }
|
|
162
|
+
when Hash
|
|
163
|
+
if value.key?('@ref')
|
|
164
|
+
# This is a reference - try to embed it
|
|
165
|
+
ref_uri = value['@ref']
|
|
166
|
+
referenced_obj = objects_index[ref_uri]
|
|
167
|
+
|
|
168
|
+
if referenced_obj
|
|
169
|
+
embed_references(referenced_obj, objects_index, max_depth, visited, current_depth + 1)
|
|
170
|
+
else
|
|
171
|
+
# External reference - return both IDs
|
|
172
|
+
{ '_id' => ref_uri, 'id' => ref_uri.split('/').last }
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
# Regular hash - recurse
|
|
176
|
+
value.transform_values { |v| resolve_value(v, objects_index, max_depth, visited, current_depth) }
|
|
177
|
+
end
|
|
178
|
+
else
|
|
179
|
+
value
|
|
52
180
|
end
|
|
53
|
-
graph
|
|
54
181
|
end
|
|
55
182
|
|
|
56
|
-
def self.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
183
|
+
def self.extract_value(literal)
|
|
184
|
+
return literal.to_s if literal.is_a?(RDF::URI)
|
|
185
|
+
|
|
186
|
+
datatype = literal.datatype&.to_s
|
|
187
|
+
|
|
188
|
+
case datatype
|
|
189
|
+
when "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
190
|
+
DateTime.parse(literal.value)
|
|
191
|
+
when "http://www.w3.org/2001/XMLSchema#date"
|
|
192
|
+
Date.parse(literal.value)
|
|
193
|
+
when "http://www.w3.org/2001/XMLSchema#boolean"
|
|
194
|
+
literal.value == "true"
|
|
195
|
+
when "http://www.w3.org/2001/XMLSchema#integer", "http://www.w3.org/2001/XMLSchema#int"
|
|
196
|
+
literal.value.to_i
|
|
197
|
+
when "http://www.w3.org/2001/XMLSchema#float", "http://www.w3.org/2001/XMLSchema#double", "http://www.w3.org/2001/XMLSchema#decimal"
|
|
198
|
+
literal.value.to_f
|
|
199
|
+
when "http://www.w3.org/2006/time#DateTimeInterval"
|
|
200
|
+
ISO8601::TimeInterval.parse(literal.value).to_s
|
|
201
|
+
when "http://www.w3.org/1999/02/22-rdf-syntax-ns#JSON"
|
|
202
|
+
JSON.parse(literal.value) rescue literal.value
|
|
203
|
+
else
|
|
204
|
+
# Handle language-tagged strings
|
|
205
|
+
if literal.respond_to?(:language) && literal.language
|
|
206
|
+
{ '@value' => literal.value, '@language' => literal.language.to_s }
|
|
207
|
+
else
|
|
208
|
+
literal.value
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
rescue StandardError => e
|
|
212
|
+
Solis::LOGGER.warn("Error extracting value: #{e.message}")
|
|
213
|
+
literal.to_s
|
|
67
214
|
end
|
|
68
215
|
|
|
69
216
|
def self.graph_name
|
|
70
|
-
graphs
|
|
71
|
-
|
|
217
|
+
Solis::Options.instance.get.key?(:graphs) ? Solis::Options.instance.get[:graphs].select { |s| s['type'].eql?(:main) }&.first['name'] : ''
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Keep original methods for backward compatibility
|
|
221
|
+
def self.sanitize_result(framed)
|
|
222
|
+
data = framed&.key?('@graph') ? framed['@graph'] : [framed]
|
|
223
|
+
sanitatize_data_in_result(data)
|
|
224
|
+
end
|
|
72
225
|
|
|
73
|
-
|
|
226
|
+
def self.sanitatize_data_in_result(data)
|
|
227
|
+
data.map do |d|
|
|
228
|
+
d.delete_if { |e| e =~ /^@/ }
|
|
229
|
+
if d.is_a?(Hash)
|
|
230
|
+
new_d = {}
|
|
231
|
+
d.each do |k, v|
|
|
232
|
+
if v.is_a?(Hash)
|
|
233
|
+
if v.key?('@type')
|
|
234
|
+
type = v['@type']
|
|
235
|
+
if v.key?('@value')
|
|
236
|
+
value = v['@value']
|
|
237
|
+
case type
|
|
238
|
+
when "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
239
|
+
value = DateTime.parse(value)
|
|
240
|
+
when "http://www.w3.org/2001/XMLSchema#date"
|
|
241
|
+
value = Date.parse(value)
|
|
242
|
+
when "http://www.w3.org/2006/time#DateTimeInterval"
|
|
243
|
+
value = ISO8601::TimeInterval.parse(value)
|
|
244
|
+
when "http://www.w3.org/2001/XMLSchema#boolean"
|
|
245
|
+
value = value == "true"
|
|
246
|
+
end
|
|
247
|
+
v = value
|
|
248
|
+
end
|
|
249
|
+
v = sanitize_result(v) if v.is_a?(Hash)
|
|
250
|
+
end
|
|
251
|
+
if v.is_a?(Hash)
|
|
252
|
+
new_d[k] = v.class.method_defined?(:value) ? v.value : sanitize_result(v)
|
|
253
|
+
else
|
|
254
|
+
new_d[k] = v.class.method_defined?(:value) ? v.value : v
|
|
255
|
+
end
|
|
256
|
+
elsif v.is_a?(Array)
|
|
257
|
+
new_d[k] = []
|
|
258
|
+
v.each do |vt|
|
|
259
|
+
if vt.is_a?(Hash)
|
|
260
|
+
if vt.key?('@value')
|
|
261
|
+
new_d[k] << vt['@value']
|
|
262
|
+
else
|
|
263
|
+
new_d[k] << (vt.is_a?(String) ? vt : sanitize_result(vt))
|
|
264
|
+
end
|
|
265
|
+
else
|
|
266
|
+
new_d[k] << (vt.is_a?(String) ? vt : sanitize_result(vt))
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
new_d[k].flatten!
|
|
270
|
+
else
|
|
271
|
+
new_d[k] = v.class.method_defined?(:value) ? v.value : v
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
d = new_d
|
|
275
|
+
end
|
|
276
|
+
d
|
|
277
|
+
end
|
|
278
|
+
rescue StandardError => e
|
|
279
|
+
Solis::LOGGER.error(e.message)
|
|
280
|
+
data
|
|
74
281
|
end
|
|
75
282
|
end
|
data/lib/solis/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.99.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mehmet Celik
|
|
@@ -299,7 +299,6 @@ files:
|
|
|
299
299
|
- lib/solis/query.rb
|
|
300
300
|
- lib/solis/query/construct.rb
|
|
301
301
|
- lib/solis/query/filter.rb
|
|
302
|
-
- lib/solis/query/result_transformer.rb
|
|
303
302
|
- lib/solis/query/run.rb
|
|
304
303
|
- lib/solis/resource.rb
|
|
305
304
|
- lib/solis/shape.rb
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
class Solis::Query::ResultTransformer
|
|
2
|
-
def initialize(model)
|
|
3
|
-
@model = model
|
|
4
|
-
@type_mappings = load_type_mappings
|
|
5
|
-
@cardinality_map = build_cardinality_map
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def transform(data)
|
|
9
|
-
items = data.key?('@graph') ? data['@graph'] : [data]
|
|
10
|
-
items.map { |item| transform_item(item) }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def transform_item(item)
|
|
16
|
-
clean_item = remove_json_ld_metadata(item)
|
|
17
|
-
cast_and_shape(clean_item)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def cast_and_shape(item)
|
|
21
|
-
item.each_with_object({}) do |(key, value), result|
|
|
22
|
-
value = cast_value(value)
|
|
23
|
-
result[key] = enforce_cardinality(key, value)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# def cast_and_shape(item)
|
|
28
|
-
# item.each_with_object({}) do |(key, value), result|
|
|
29
|
-
# value = cast_value(value)
|
|
30
|
-
# value = enforce_cardinality(key, value)
|
|
31
|
-
# value = transform_nested_entity(key, value)
|
|
32
|
-
# result[key] = value
|
|
33
|
-
# end
|
|
34
|
-
# end
|
|
35
|
-
|
|
36
|
-
def transform_nested_entity(property_key, value)
|
|
37
|
-
return value if @model.nil?
|
|
38
|
-
|
|
39
|
-
metadata = @cardinality_map[property_key]
|
|
40
|
-
return value if metadata.nil?
|
|
41
|
-
|
|
42
|
-
datatype = metadata[:datatype]
|
|
43
|
-
node = metadata[:node]
|
|
44
|
-
|
|
45
|
-
# Check if datatype points to another entity (has a node)
|
|
46
|
-
if node.is_a?(RDF::URI) && value.is_a?(Hash)
|
|
47
|
-
# Recursively transform nested entity if we have its model
|
|
48
|
-
# You'd need a way to resolve the node URI to the model class
|
|
49
|
-
nested_model = resolve_model_from_node(datatype)
|
|
50
|
-
Solis::Query::ResultTransformer.new(nested_model).transform(value) if nested_model
|
|
51
|
-
elsif value.is_a?(Array) && value.all?(Hash)
|
|
52
|
-
# Transform array of nested entities
|
|
53
|
-
value.map { |v| transform_nested_entity(property_key, v) }
|
|
54
|
-
else
|
|
55
|
-
value
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def resolve_model_from_node(datatype_node)
|
|
60
|
-
@model.graph.shape_as_model(datatype_node.to_s)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def remove_json_ld_metadata(item)
|
|
65
|
-
item.reject { |key| key.start_with?('@') }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def cast_value(value)
|
|
69
|
-
case value
|
|
70
|
-
when Hash
|
|
71
|
-
handle_typed_value(value)
|
|
72
|
-
when Array
|
|
73
|
-
value.map { |v| cast_value(v) }
|
|
74
|
-
else
|
|
75
|
-
value
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def handle_typed_value(value)
|
|
80
|
-
return value unless value.key?('@type') && value.key?('@value')
|
|
81
|
-
|
|
82
|
-
type = value['@type']
|
|
83
|
-
raw_value = value['@value']
|
|
84
|
-
|
|
85
|
-
cast_by_type(type, raw_value)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def cast_by_type(type, value)
|
|
89
|
-
caster = @type_mappings[type]
|
|
90
|
-
caster ? caster.call(value) : value
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def enforce_cardinality(property_key, value)
|
|
94
|
-
return value if @model.nil?
|
|
95
|
-
|
|
96
|
-
metadata = @cardinality_map[property_key]
|
|
97
|
-
return value if metadata.nil?
|
|
98
|
-
|
|
99
|
-
maxcount = metadata[:maxcount]
|
|
100
|
-
|
|
101
|
-
# If maxcount is nil or > 1, ensure it's an array
|
|
102
|
-
if maxcount.nil? || maxcount > 1
|
|
103
|
-
value.is_a?(Array) ? value : [value]
|
|
104
|
-
# If maxcount is 0 or 1, ensure it's a single value
|
|
105
|
-
else
|
|
106
|
-
value.is_a?(Array) ? value.first : value
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def build_cardinality_map
|
|
111
|
-
return {} if @model.nil? || @model.metadata.nil?
|
|
112
|
-
|
|
113
|
-
attributes = @model.metadata[:attributes] || {}
|
|
114
|
-
|
|
115
|
-
attributes.each_with_object({}) do |(property_name, property_metadata), map|
|
|
116
|
-
map[property_name.to_s] = property_metadata
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def load_type_mappings
|
|
121
|
-
{
|
|
122
|
-
"http://www.w3.org/2001/XMLSchema#dateTime" => ->(v) { DateTime.parse(v) },
|
|
123
|
-
"http://www.w3.org/2001/XMLSchema#date" => ->(v) { Date.parse(v) },
|
|
124
|
-
"http://www.w3.org/2006/time#DateTimeInterval" => ->(v) { ISO8601::TimeInterval.parse(v) },
|
|
125
|
-
"http://www.w3.org/2001/XMLSchema#boolean" => ->(v) { v == "true" },
|
|
126
|
-
"http://www.w3.org/2001/XMLSchema#integer" => ->(v) { v.to_i },
|
|
127
|
-
"http://www.w3.org/2001/XMLSchema#decimal" => ->(v) { BigDecimal(v) }
|
|
128
|
-
}
|
|
129
|
-
end
|
|
130
|
-
end
|