solis 0.126.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/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?),
|
|
@@ -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
|