solis 0.125.0 → 0.127.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/language_tag.rb +22 -0
- data/lib/solis/model.rb +1 -0
- data/lib/solis/query.rb +58 -6
- data/lib/solis/shape/reader/sheet.rb +73 -33
- data/lib/solis/shape.rb +43 -0
- data/lib/solis/version.rb +1 -1
- data/lib/solis.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7cf89eceec897d7b5fe15ef1669b031480a7aab2850bd5638cfac379e4798ae
|
|
4
|
+
data.tar.gz: 56a9ce9652925e1be2904dc150bba6d31f3f2ca13870641c883fb0dde65b341c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 499ff31724baaa76c0ae960916d5b01c0f25f535e07e89fd0cba72cbc042fe271898b5c422842201463daef4903d1207bf4fdcbd248e3bbff346153f4dba1765
|
|
7
|
+
data.tar.gz: 264f64d77cf8b15d708da87cbf0602a6f8be5282c04a365f52966e865895032e9c08b4952e99abbcb6e5f4aa80fcbd20887e3e4d9c961398e350a4cbaff17b0b
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Solis
|
|
2
|
+
# Normalizes language tags so the same language always ends up under the same key,
|
|
3
|
+
# no matter if it came from a 'Label_XX' sheet column or from a parsed rdfs:label
|
|
4
|
+
# (RDF.rb lowercases language tags, ex. 'nl-BE' is read back as :'nl-be')
|
|
5
|
+
module LanguageTag
|
|
6
|
+
# 'nl' -> :nl, 'nl_be' -> :'nl-BE', 'zh_hant' -> :'zh-Hant'
|
|
7
|
+
def self.normalize(tag)
|
|
8
|
+
return nil if tag.nil? || tag.to_s.strip.empty?
|
|
9
|
+
|
|
10
|
+
primary, *subtags = tag.to_s.strip.split(/[-_]/)
|
|
11
|
+
subtags.map! do |subtag|
|
|
12
|
+
case subtag.length
|
|
13
|
+
when 2 then subtag.upcase
|
|
14
|
+
when 4 then subtag.capitalize
|
|
15
|
+
else subtag.downcase
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
([primary.downcase] + subtags).join('-').to_sym
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/solis/model.rb
CHANGED
|
@@ -583,6 +583,7 @@ values ?s {<#{self.graph_id}>}
|
|
|
583
583
|
end
|
|
584
584
|
|
|
585
585
|
attribute_data = { name: attribute,
|
|
586
|
+
label: attribute_metadata[:label] || {},
|
|
586
587
|
data_type: attribute_metadata[:datatype],
|
|
587
588
|
mandatory: (attribute_metadata[:mincount].to_i > 0),
|
|
588
589
|
repeatable: (attribute_metadata[:maxcount].to_i > 1 || attribute_metadata[:maxcount].nil?),
|
data/lib/solis/query.rb
CHANGED
|
@@ -11,6 +11,15 @@ module Solis
|
|
|
11
11
|
include Enumerable
|
|
12
12
|
include Solis::QueryFilter
|
|
13
13
|
|
|
14
|
+
# XSD numeric datatypes must be ordered by value; wrapping them in STR() would sort
|
|
15
|
+
# them lexically ("100" < "9"). Every other datatype is STR()-wrapped in the outer
|
|
16
|
+
# ORDER BY (see #sort_key_expression) to dodge a Virtuoso collation bug.
|
|
17
|
+
XSD_NUMERIC_DATATYPES = %w[
|
|
18
|
+
integer decimal float double long int short byte
|
|
19
|
+
nonNegativeInteger nonPositiveInteger negativeInteger positiveInteger
|
|
20
|
+
unsignedLong unsignedInt unsignedShort unsignedByte
|
|
21
|
+
].map { |t| "http://www.w3.org/2001/XMLSchema##{t}" }.freeze
|
|
22
|
+
|
|
14
23
|
def self.run(entity, query, options = {})
|
|
15
24
|
Solis::Query::Runner.run(entity, query, options)
|
|
16
25
|
end
|
|
@@ -96,6 +105,13 @@ module Solis
|
|
|
96
105
|
@filter = {values: ["VALUES ?type {#{target_class}}"], concepts: ['?concept a ?type .'] }
|
|
97
106
|
@sort = 'ORDER BY ?concept'
|
|
98
107
|
@sort_select = ''
|
|
108
|
+
# @sort_project carries the sort key(s) out of the inner (paginated) subquery so the
|
|
109
|
+
# outer query can re-sort on them; @sort_outer is that outer ORDER BY. A subquery's
|
|
110
|
+
# ORDER BY only decides which rows survive LIMIT/OFFSET — it does NOT propagate
|
|
111
|
+
# through the enclosing join — so the outer query must sort too. See #sort for the
|
|
112
|
+
# datatype-aware STR() handling that the outer ORDER BY needs.
|
|
113
|
+
@sort_project = ''
|
|
114
|
+
@sort_outer = ''
|
|
99
115
|
@language = Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || 'en'
|
|
100
116
|
@query_cache = self.class.shared_query_cache
|
|
101
117
|
end
|
|
@@ -113,19 +129,31 @@ module Solis
|
|
|
113
129
|
def sort(params)
|
|
114
130
|
@sort = ''
|
|
115
131
|
@sort_select = ''
|
|
132
|
+
@sort_project = ''
|
|
133
|
+
@sort_outer = ''
|
|
116
134
|
if params.key?(:sort)
|
|
117
135
|
i = 0
|
|
136
|
+
outer = ''
|
|
118
137
|
params[:sort].each do |attribute, direction|
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
meta = @model.class.metadata[:attributes][attribute.to_s]
|
|
139
|
+
path = meta[:path]
|
|
140
|
+
@sort_select += "optional {\n" if meta[:mincount] == 0
|
|
121
141
|
@sort_select += "?concept <#{path}> ?__#{attribute} . "
|
|
122
|
-
@sort_select += "}\n" if
|
|
142
|
+
@sort_select += "}\n" if meta[:mincount] == 0
|
|
123
143
|
@sort += ',' if i.positive?
|
|
124
144
|
@sort += "#{direction.to_s.upcase}(?__#{attribute})"
|
|
145
|
+
# Carry the sort key out of the subquery so the outer query can re-sort on it.
|
|
146
|
+
@sort_project += " ?__#{attribute}"
|
|
147
|
+
outer += ',' if i.positive?
|
|
148
|
+
outer += "#{direction.to_s.upcase}(#{sort_key_expression(attribute, meta)})"
|
|
125
149
|
i += 1
|
|
126
150
|
end
|
|
127
151
|
|
|
128
|
-
|
|
152
|
+
if i.positive?
|
|
153
|
+
@sort = "ORDER BY #{@sort}"
|
|
154
|
+
# ?s tiebreaker keeps each subject's triples contiguous and ties deterministic.
|
|
155
|
+
@sort_outer = "ORDER BY #{outer} ?s"
|
|
156
|
+
end
|
|
129
157
|
end
|
|
130
158
|
|
|
131
159
|
self
|
|
@@ -219,6 +247,11 @@ module Solis
|
|
|
219
247
|
# object when the open `?s ?p ?o` pattern is combined with the disjunctive
|
|
220
248
|
# language_filter, returning the rdf:type object for every row. Aliasing through
|
|
221
249
|
# BIND blocks that optimizer rewrite and is a no-op on engines without the bug.
|
|
250
|
+
# Outer ordering: the inner subquery's ORDER BY only selects which rows survive
|
|
251
|
+
# LIMIT/OFFSET; it does NOT propagate through the enclosing join. The outer query
|
|
252
|
+
# therefore re-sorts on the same key(s) (@sort_outer, datatype-aware STR()). When
|
|
253
|
+
# no sort is requested, `order by ?s` gives a cheap deterministic default order.
|
|
254
|
+
outer_order = @sort_outer.empty? ? 'order by ?s' : @sort_outer
|
|
222
255
|
query = %(
|
|
223
256
|
#{prefixes}
|
|
224
257
|
SELECT ?s ?p ?o WHERE {
|
|
@@ -229,7 +262,7 @@ SELECT ?s ?p ?o WHERE {
|
|
|
229
262
|
}
|
|
230
263
|
#{language_filter}
|
|
231
264
|
}
|
|
232
|
-
|
|
265
|
+
#{outer_order}
|
|
233
266
|
)
|
|
234
267
|
|
|
235
268
|
Solis::LOGGER.info(query) if ConfigFile[:debug]
|
|
@@ -274,8 +307,12 @@ order by ?s
|
|
|
274
307
|
end
|
|
275
308
|
|
|
276
309
|
def core_query(relationship)
|
|
310
|
+
# @sort_project re-exposes the sort key(s) bound in @sort_select so the OUTER query
|
|
311
|
+
# can ORDER BY them. NB: for a multi-valued sort attribute this turns DISTINCT into
|
|
312
|
+
# one row per (concept, value) pair, which would skew LIMIT/OFFSET — sort attributes
|
|
313
|
+
# are expected to be single-valued.
|
|
277
314
|
core_query = %(
|
|
278
|
-
SELECT distinct (?concept AS ?s) WHERE {
|
|
315
|
+
SELECT distinct (?concept AS ?s)#{@sort_project} WHERE {
|
|
279
316
|
#{@filter[:values].join("\n")}
|
|
280
317
|
#{relationship}
|
|
281
318
|
#{@filter[:concepts].join("\n")}
|
|
@@ -286,6 +323,21 @@ order by ?s
|
|
|
286
323
|
)
|
|
287
324
|
end
|
|
288
325
|
|
|
326
|
+
# SPARQL expression to sort an attribute by in the OUTER query.
|
|
327
|
+
#
|
|
328
|
+
# The outer (post-join) ORDER BY on this Virtuoso build (08.03.3335) mis-collates
|
|
329
|
+
# language-tagged literals, so string-ish keys are wrapped in STR() — which fixes the
|
|
330
|
+
# ordering and is order-preserving for xsd:string, dates, gYear, booleans and EDTF.
|
|
331
|
+
# Numeric keys must NOT be wrapped: STR() would order them lexically ("100" < "9"),
|
|
332
|
+
# and numbers carry no language tag so they are unaffected by the collation bug.
|
|
333
|
+
def sort_key_expression(attribute, meta = @model.class.metadata[:attributes][attribute.to_s])
|
|
334
|
+
if XSD_NUMERIC_DATATYPES.include?(meta[:datatype_rdf].to_s)
|
|
335
|
+
"?__#{attribute}"
|
|
336
|
+
else
|
|
337
|
+
"STR(?__#{attribute})"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
289
341
|
def prefixes
|
|
290
342
|
"
|
|
291
343
|
PREFIX sh: <http://www.w3.org/ns/shacl#>
|
|
@@ -43,6 +43,11 @@ module Solis
|
|
|
43
43
|
raise "_REFERENCES tab must have ['sheeturl', 'description', 'entityrange'] as a header at row 1" unless (%w[sheeturl description entityrange] - references.header).length == 0
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
if sheets.key?('_CATEGORIES')
|
|
47
|
+
categories = sheets['_CATEGORIES']
|
|
48
|
+
raise "_CATEGORIES tab must have ['name'] as a header at row 1" unless (%w[name] - categories.header).length == 0
|
|
49
|
+
end
|
|
50
|
+
|
|
46
51
|
sheets.each do |sheet_name, sheet|
|
|
47
52
|
if sheet_name !~ /^_/
|
|
48
53
|
entities = sheets[sheet_name]
|
|
@@ -113,6 +118,7 @@ module Solis
|
|
|
113
118
|
category: e['categories'],
|
|
114
119
|
plural: e['nameplural'],
|
|
115
120
|
label: e['name'].to_s.strip,
|
|
121
|
+
labels: parse_labels(e),
|
|
116
122
|
sub_class_of: e['subclassof'].nil? || e['subclassof'].empty? ? [] : [e['subclassof']],
|
|
117
123
|
same_as: e['sameas'],
|
|
118
124
|
properties: entity_data })
|
|
@@ -135,6 +141,26 @@ module Solis
|
|
|
135
141
|
raise Solis::Error::GeneralError, e.message
|
|
136
142
|
end
|
|
137
143
|
|
|
144
|
+
# Matches the 'Label_XX' columns (header is downcased by ::Sheet#header) where
|
|
145
|
+
# XX is a language tag, ex. 'Label_NL', 'Label_EN', 'Label_nl-BE'
|
|
146
|
+
LANGUAGE_LABEL_COLUMN = /\Alabel_(?<language>[a-z]{2,3}([-_][a-z0-9]{2,8})*)\z/.freeze
|
|
147
|
+
|
|
148
|
+
# Collects all 'Label_XX' columns of a row into { nl: 'Publiceren', en: 'Publish' }
|
|
149
|
+
def parse_labels(row)
|
|
150
|
+
labels = {}
|
|
151
|
+
return labels unless row.respond_to?(:each_pair)
|
|
152
|
+
|
|
153
|
+
row.each_pair do |column, value|
|
|
154
|
+
match = LANGUAGE_LABEL_COLUMN.match(column.to_s)
|
|
155
|
+
next if match.nil?
|
|
156
|
+
next if value.nil? || value.to_s.strip.empty?
|
|
157
|
+
|
|
158
|
+
labels[Solis::LanguageTag.normalize(match[:language])] = value.to_s.strip
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
labels
|
|
162
|
+
end
|
|
163
|
+
|
|
138
164
|
def parse_entity_data(entity_name, graph_prefix, _graph_name, e, options = {})
|
|
139
165
|
properties = {}
|
|
140
166
|
entity_data = e
|
|
@@ -168,31 +194,9 @@ module Solis
|
|
|
168
194
|
same_as: p['sameas'],
|
|
169
195
|
order: p['order'],
|
|
170
196
|
category: p['categories'],
|
|
197
|
+
label: parse_labels(p),
|
|
171
198
|
description: p['description']
|
|
172
199
|
}
|
|
173
|
-
|
|
174
|
-
# unless graph_prefix.eql?(datatype_prefix.to_sym)
|
|
175
|
-
# prefixes = options[:prefixes]
|
|
176
|
-
# if prefixes.key?(datatype_prefix.to_sym) && !prefixes[datatype_prefix.to_sym][:sheet_url].empty?
|
|
177
|
-
# tmp = URI(prefixes[datatype_prefix.to_sym][:sheet_url]).path.split('/')
|
|
178
|
-
# spreadsheet_id = tmp[tmp.index('d') + 1]
|
|
179
|
-
#
|
|
180
|
-
# processed_remote_sheet = {}
|
|
181
|
-
# if prefixes[datatype_prefix.to_sym].key?(:data) && !prefixes[datatype_prefix.to_sym][:data].empty?
|
|
182
|
-
# processed_remote_sheet = prefixes[datatype_prefix.to_sym][:data]
|
|
183
|
-
# else
|
|
184
|
-
# if options[:follow]
|
|
185
|
-
# sleep 30
|
|
186
|
-
# remote_sheet = read_sheets(options[:key], spreadsheet_id, { from_cache: true })
|
|
187
|
-
# processed_remote_sheet = process_sheet(options[:key], remote_sheet, {follow: false})
|
|
188
|
-
# prefixes[datatype_prefix.to_sym][:data] = processed_remote_sheet
|
|
189
|
-
# end
|
|
190
|
-
# end
|
|
191
|
-
#
|
|
192
|
-
# processed_remote_sheet
|
|
193
|
-
# end
|
|
194
|
-
# end
|
|
195
|
-
|
|
196
200
|
end
|
|
197
201
|
end
|
|
198
202
|
end
|
|
@@ -266,12 +270,32 @@ hide empty members
|
|
|
266
270
|
datatypes[datatype][as]
|
|
267
271
|
end
|
|
268
272
|
|
|
273
|
+
# Turns { nl: 'Publiceren', en: 'Publish' } into a single rdfs:label statement with
|
|
274
|
+
# one language tagged literal per language, ex.
|
|
275
|
+
# rdfs:label "Publiceren"@nl, "Publish"@en ;
|
|
276
|
+
def build_labels(labels, rdfs_prefix, indent)
|
|
277
|
+
return '' if labels.nil? || labels.empty?
|
|
278
|
+
|
|
279
|
+
literals = labels.map do |language, value|
|
|
280
|
+
%("#{value.to_s.gsub('"', "'").gsub(/\n|\r/, '')}"@#{language})
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
"\n#{indent}#{rdfs_prefix}:label #{literals.join(', ')} ;"
|
|
284
|
+
end
|
|
285
|
+
|
|
269
286
|
def build_shacl(datas)
|
|
270
287
|
shacl_prefix = datas.first[:ontologies][:all].select { |_, v| v[:uri] =~ /shacl/ }.keys.first
|
|
271
288
|
shacl_prefix = 'sh' if shacl_prefix.nil?
|
|
272
289
|
|
|
290
|
+
rdfs_prefix = datas.first[:ontologies][:all].select { |_, v| v[:uri] =~ /rdf-schema/ }.keys.first
|
|
291
|
+
|
|
273
292
|
out = header(datas.first)
|
|
274
293
|
|
|
294
|
+
if rdfs_prefix.nil?
|
|
295
|
+
rdfs_prefix = 'rdfs'
|
|
296
|
+
out += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"
|
|
297
|
+
end
|
|
298
|
+
|
|
275
299
|
datas.each do |data|
|
|
276
300
|
data[:entities].each do |entity_name, metadata|
|
|
277
301
|
graph_prefix = data[:ontologies][:base][:prefix]
|
|
@@ -300,7 +324,7 @@ hide empty members
|
|
|
300
324
|
unless node.nil? || node.empty?
|
|
301
325
|
"\n #{shacl_prefix}:node #{node} ;"
|
|
302
326
|
end}
|
|
303
|
-
#{shacl_prefix}:name "#{label}"
|
|
327
|
+
#{shacl_prefix}:name "#{label}" ;#{build_labels(metadata[:labels], rdfs_prefix, ' ' * 4)}
|
|
304
328
|
)
|
|
305
329
|
metadata[:properties].each do |property, property_metadata|
|
|
306
330
|
attribute = property.to_s.strip
|
|
@@ -314,15 +338,16 @@ hide empty members
|
|
|
314
338
|
order = property_metadata.key?(:order) && property_metadata[:order] ? property_metadata[:order]&.strip : nil
|
|
315
339
|
category = property_metadata.key?(:category) && property_metadata[:category] ? property_metadata[:category]&.strip : nil
|
|
316
340
|
category = category.nil? || category.empty? ? nil : category
|
|
341
|
+
labels = build_labels(property_metadata[:label], rdfs_prefix, ' ' * 17)
|
|
317
342
|
|
|
318
343
|
unless category.nil?
|
|
319
|
-
group = "#{category.
|
|
344
|
+
group = "#{category.camelize}Group"
|
|
320
345
|
groups[group] = category unless groups.key?(group)
|
|
321
346
|
end
|
|
322
347
|
|
|
323
348
|
if datatype =~ /^#{graph_prefix}:/ || datatype =~ /^<#{graph_name}/
|
|
324
349
|
out += %( #{shacl_prefix}:property [#{shacl_prefix}:path #{path} ;
|
|
325
|
-
#{shacl_prefix}:name "#{attribute}"
|
|
350
|
+
#{shacl_prefix}:name "#{attribute}" ;#{labels}
|
|
326
351
|
#{shacl_prefix}:description "#{description}" ;#{order.nil? ? '' : "\n #{shacl_prefix}:order #{order} ;"}
|
|
327
352
|
#{category.nil? ? '' : "\n #{shacl_prefix}:group #{graph_prefix}:#{group} ;"}
|
|
328
353
|
#{shacl_prefix}:nodeKind #{shacl_prefix}:IRI ;
|
|
@@ -332,7 +357,7 @@ hide empty members
|
|
|
332
357
|
else
|
|
333
358
|
if datatype.eql?('rdf:langString') && max_count.eql?('1')
|
|
334
359
|
out += %( #{shacl_prefix}:property [#{shacl_prefix}:path #{path} ;
|
|
335
|
-
#{shacl_prefix}:name "#{attribute}"
|
|
360
|
+
#{shacl_prefix}:name "#{attribute}";#{labels}
|
|
336
361
|
#{shacl_prefix}:description "#{description}" ;#{order.nil? ? '' : "\n #{shacl_prefix}:order #{order} ;"}
|
|
337
362
|
#{shacl_prefix}:uniqueLang true ;
|
|
338
363
|
#{shacl_prefix}:datatype #{datatype} ;#{min_count =~ /\d+/ ? "\n #{shacl_prefix}:minCount #{min_count} ;" : ''}
|
|
@@ -340,7 +365,7 @@ hide empty members
|
|
|
340
365
|
)
|
|
341
366
|
elsif datatype.eql?('rdf:langString')
|
|
342
367
|
out += %( #{shacl_prefix}:property [#{shacl_prefix}:path #{path} ;
|
|
343
|
-
#{shacl_prefix}:name "#{attribute}"
|
|
368
|
+
#{shacl_prefix}:name "#{attribute}";#{labels}
|
|
344
369
|
#{shacl_prefix}:description "#{description}" ;#{order.nil? ? '' : "\n #{shacl_prefix}:order #{order} ;"}
|
|
345
370
|
#{category.nil? ? '' : "\n #{shacl_prefix}:group #{graph_prefix}:#{group} ;"}
|
|
346
371
|
#{shacl_prefix}:datatype #{datatype} ;#{min_count =~ /\d+/ ? "\n #{shacl_prefix}:minCount #{min_count} ;" : ''}#{max_count =~ /\d+/ ? "\n #{shacl_prefix}:maxCount #{max_count} ;" : ''}
|
|
@@ -348,7 +373,7 @@ hide empty members
|
|
|
348
373
|
)
|
|
349
374
|
else
|
|
350
375
|
out += %( #{shacl_prefix}:property [#{shacl_prefix}:path #{path} ;
|
|
351
|
-
#{shacl_prefix}:name "#{attribute}"
|
|
376
|
+
#{shacl_prefix}:name "#{attribute}";#{labels}
|
|
352
377
|
#{shacl_prefix}:description "#{description}" ;#{order.nil? ? '' : "\n #{shacl_prefix}:order #{order} ;"}
|
|
353
378
|
#{shacl_prefix}:datatype #{datatype} ;#{min_count =~ /\d+/ ? "\n #{shacl_prefix}:minCount #{min_count} ;" : ''}#{max_count =~ /\d+/ ? "\n #{shacl_prefix}:maxCount #{max_count} ;" : ''}
|
|
354
379
|
] ;
|
|
@@ -362,8 +387,8 @@ hide empty members
|
|
|
362
387
|
groups.each do |group, category|
|
|
363
388
|
out += %(
|
|
364
389
|
#{graph_prefix}:#{group}
|
|
365
|
-
a
|
|
366
|
-
|
|
390
|
+
a #{shacl_prefix}:PropertyGroup ;
|
|
391
|
+
#{rdfs_prefix}:label "#{category}" .
|
|
367
392
|
|
|
368
393
|
)
|
|
369
394
|
end
|
|
@@ -489,7 +514,7 @@ hide empty members
|
|
|
489
514
|
puts e.message
|
|
490
515
|
end
|
|
491
516
|
|
|
492
|
-
def build_inflections(datas)
|
|
517
|
+
def build_inflections(datas, categories = [])
|
|
493
518
|
inflections = {}
|
|
494
519
|
datas.each do |data|
|
|
495
520
|
data[:entities].each do |entity, metadata|
|
|
@@ -498,6 +523,16 @@ hide empty members
|
|
|
498
523
|
end
|
|
499
524
|
end
|
|
500
525
|
|
|
526
|
+
# categories are uninflected, singular and plural are the same word.
|
|
527
|
+
# an entity by the same name keeps its own plural.
|
|
528
|
+
# categories.each do |category|
|
|
529
|
+
# name = category.to_s.strip
|
|
530
|
+
# next if name.empty?
|
|
531
|
+
#
|
|
532
|
+
# inflections[name.to_sym] = name unless inflections.key?(name.to_sym) || inclections.value?(name.to_s)
|
|
533
|
+
# inflections[name.underscore.to_sym] = name.underscore unless inflections.key?(name.underscore.to_sym) || inclections.value?(name.to_s)
|
|
534
|
+
# end
|
|
535
|
+
|
|
501
536
|
inflections.to_json
|
|
502
537
|
rescue StandardError => e
|
|
503
538
|
puts e.message
|
|
@@ -743,6 +778,11 @@ hide empty members
|
|
|
743
778
|
|
|
744
779
|
options[:prefixes] = prefixes
|
|
745
780
|
options[:metadata] = metadata
|
|
781
|
+
|
|
782
|
+
# like _REFERENCES, _CATEGORIES only lives in the main sheet
|
|
783
|
+
categories = sheet_data.is_a?(Hash) && sheet_data.key?('_CATEGORIES') ? sheet_data['_CATEGORIES'].map { |c| c['name'] } : []
|
|
784
|
+
Solis::LOGGER.info("Found #{categories.length} categories") unless categories.empty?
|
|
785
|
+
|
|
746
786
|
#TODO: cleanup
|
|
747
787
|
if sheet_data.is_a?(Hash)
|
|
748
788
|
raise "No _REFERENCES sheet found" unless sheet_data.key?("_REFERENCES")
|
|
@@ -786,7 +826,7 @@ hide empty members
|
|
|
786
826
|
Solis::LOGGER.info('Generating SCHEMA')
|
|
787
827
|
schema = build_schema(datas)
|
|
788
828
|
Solis::LOGGER.info('Generating INFLECTIONS')
|
|
789
|
-
inflections = build_inflections(datas)
|
|
829
|
+
inflections = build_inflections(datas, categories)
|
|
790
830
|
Solis::LOGGER.info('Generating JSON SCHEMA')
|
|
791
831
|
json_schema = build_json_schema(shacl)
|
|
792
832
|
|
data/lib/solis/shape.rb
CHANGED
|
@@ -14,6 +14,8 @@ module Solis
|
|
|
14
14
|
parse_solution(shapes, solution)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
add_labels(shapes, graph)
|
|
18
|
+
|
|
17
19
|
# shapes = add_missing_attributes(shapes)
|
|
18
20
|
shapes
|
|
19
21
|
rescue Solis::Error::GeneralError => e
|
|
@@ -137,6 +139,47 @@ module Solis
|
|
|
137
139
|
shapes[shape_name] = shape
|
|
138
140
|
end
|
|
139
141
|
|
|
142
|
+
# Reads the rdfs:label statements of every node shape and property shape into
|
|
143
|
+
# { nl: 'Publiceren', en: 'Publish' }. This is a second pass on purpose: adding
|
|
144
|
+
# rdfs:label to the main query would multiply every solution by the number of
|
|
145
|
+
# languages.
|
|
146
|
+
def add_labels(shapes, graph)
|
|
147
|
+
graph.query([nil, RDF.type, RDF::Vocab::SHACL.NodeShape]) do |node_shape|
|
|
148
|
+
shape_name = name_of(graph, node_shape.subject)
|
|
149
|
+
shape = shapes[shape_name]
|
|
150
|
+
next if shape.nil?
|
|
151
|
+
|
|
152
|
+
shape[:label] = labels_of(graph, node_shape.subject)
|
|
153
|
+
|
|
154
|
+
graph.query([node_shape.subject, RDF::Vocab::SHACL.property, nil]) do |property|
|
|
155
|
+
attribute = shape[:attributes][name_of(graph, property.object)]
|
|
156
|
+
next if attribute.nil?
|
|
157
|
+
|
|
158
|
+
attribute[:label] = labels_of(graph, property.object)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
shapes
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def name_of(graph, subject)
|
|
166
|
+
graph.query([subject, RDF::Vocab::SHACL.name, nil]).first&.object&.value
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def labels_of(graph, subject)
|
|
170
|
+
labels = {}
|
|
171
|
+
|
|
172
|
+
graph.query([subject, RDF::RDFS.label, nil]).each do |statement|
|
|
173
|
+
literal = statement.object
|
|
174
|
+
language = Solis::LanguageTag.normalize(literal.language) if literal.is_a?(RDF::Literal)
|
|
175
|
+
next if language.nil?
|
|
176
|
+
|
|
177
|
+
labels[language] = literal.value
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
labels
|
|
181
|
+
end
|
|
182
|
+
|
|
140
183
|
def query
|
|
141
184
|
SPARQL.parse %(
|
|
142
185
|
PREFIX sh: <http://www.w3.org/ns/shacl#>
|
data/lib/solis/version.rb
CHANGED
data/lib/solis.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.127.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mehmet Celik
|
|
@@ -307,6 +307,7 @@ files:
|
|
|
307
307
|
- lib/solis/error/not_found_error.rb
|
|
308
308
|
- lib/solis/error/query_error.rb
|
|
309
309
|
- lib/solis/graph.rb
|
|
310
|
+
- lib/solis/language_tag.rb
|
|
310
311
|
- lib/solis/model.rb
|
|
311
312
|
- lib/solis/model.rb.ok
|
|
312
313
|
- lib/solis/options.rb
|