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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce5b23eb71b6b7189377d4981fa8e6dc076128d60aec207e58310fbd162b8a16
4
- data.tar.gz: 57c514df94596d89d76ea012cc946b2b521869f585f1d495609f8e0ec5cbdf71
3
+ metadata.gz: d7cf89eceec897d7b5fe15ef1669b031480a7aab2850bd5638cfac379e4798ae
4
+ data.tar.gz: 56a9ce9652925e1be2904dc150bba6d31f3f2ca13870641c883fb0dde65b341c
5
5
  SHA512:
6
- metadata.gz: 912773a18a5b44da59e1bbb6b7e3472c5d8be9a8044df0b64acbb7da501a3156ead5615363bd23fbd595d42db1b862f3d776235758d92acf8d0942f42f2fde00
7
- data.tar.gz: 90c434c26f367159078555cf9bfbc715d4179ad8f3ebffba12ba379000e7696bf25020cd9a033d90b1b1e95375574bd4600eb3492d67e703a07aaff901d6b251
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.classify}Group"
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 sh:PropertyGroup ;
366
- rdfs:label "#{category}" .
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
@@ -1,3 +1,3 @@
1
1
  module Solis
2
- VERSION = "0.126.0"
2
+ VERSION = "0.127.0"
3
3
  end
data/lib/solis.rb CHANGED
@@ -7,6 +7,7 @@ require "solis/version"
7
7
  require 'solis/config_file'
8
8
  require "solis/error"
9
9
  require 'solis/rdf_edtf_literal'
10
+ require 'solis/language_tag'
10
11
  require 'solis/graph'
11
12
  require 'solis/shape'
12
13
 
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.126.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