glossarist 2.6.6 → 2.6.7
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/README.adoc +90 -29
- data/glossarist.gemspec +2 -0
- data/lib/glossarist/citation.rb +26 -123
- data/lib/glossarist/cli/compare_command.rb +106 -0
- data/lib/glossarist/cli/export_command.rb +11 -14
- data/lib/glossarist/cli/validate_command.rb +111 -20
- data/lib/glossarist/cli.rb +18 -0
- data/lib/glossarist/collections/bibliography_collection.rb +4 -2
- data/lib/glossarist/collections/localization_collection.rb +2 -0
- data/lib/glossarist/comparison_result.rb +35 -0
- data/lib/glossarist/concept_collector.rb +44 -0
- data/lib/glossarist/concept_comparator.rb +72 -0
- data/lib/glossarist/concept_data.rb +16 -0
- data/lib/glossarist/concept_diff.rb +15 -0
- data/lib/glossarist/concept_document.rb +11 -0
- data/lib/glossarist/concept_manager.rb +19 -5
- data/lib/glossarist/concept_ref.rb +13 -0
- data/lib/glossarist/concept_validator.rb +6 -1
- data/lib/glossarist/context_configuration.rb +90 -0
- data/lib/glossarist/dataset_validator.rb +8 -4
- data/lib/glossarist/designation/prefix.rb +17 -0
- data/lib/glossarist/designation/suffix.rb +17 -0
- data/lib/glossarist/gcr_metadata.rb +7 -14
- data/lib/glossarist/gcr_package.rb +35 -23
- data/lib/glossarist/gcr_validator.rb +38 -17
- data/lib/glossarist/localized_concept.rb +8 -0
- data/lib/glossarist/managed_concept.rb +39 -6
- data/lib/glossarist/managed_concept_data.rb +2 -1
- data/lib/glossarist/rdf/ext/jsonld_transform_ext.rb +208 -0
- data/lib/glossarist/rdf/ext/mapping_ext.rb +37 -0
- data/lib/glossarist/rdf/ext/mapping_rule_ext.rb +27 -0
- data/lib/glossarist/rdf/ext/member_rule_ext.rb +34 -0
- data/lib/glossarist/rdf/ext/turtle_transform_ext.rb +222 -0
- data/lib/glossarist/rdf/ext.rb +39 -0
- data/lib/glossarist/rdf/gloss_citation.rb +36 -0
- data/lib/glossarist/rdf/gloss_concept.rb +58 -0
- data/lib/glossarist/rdf/gloss_concept_date.rb +24 -0
- data/lib/glossarist/rdf/gloss_concept_reference.rb +29 -0
- data/lib/glossarist/rdf/gloss_concept_source.rb +37 -0
- data/lib/glossarist/rdf/gloss_designation.rb +146 -0
- data/lib/glossarist/rdf/gloss_detailed_definition.rb +24 -0
- data/lib/glossarist/rdf/gloss_grammar_info.rb +57 -0
- data/lib/glossarist/rdf/gloss_locality.rb +25 -0
- data/lib/glossarist/rdf/gloss_localized_concept.rb +67 -0
- data/lib/glossarist/rdf/gloss_non_verbal_rep.rb +31 -0
- data/lib/glossarist/rdf/gloss_pronunciation.rb +32 -0
- data/lib/glossarist/rdf/gloss_reference.rb +55 -0
- data/lib/glossarist/rdf/namespaces/glossarist_namespace.rb +12 -0
- data/lib/glossarist/rdf/namespaces/iso_thes_namespace.rb +12 -0
- data/lib/glossarist/rdf/namespaces/owl_namespace.rb +12 -0
- data/lib/glossarist/rdf/namespaces/prov_namespace.rb +12 -0
- data/lib/glossarist/rdf/namespaces/rdf_namespace.rb +12 -0
- data/lib/glossarist/rdf/namespaces/skosxl_namespace.rb +12 -0
- data/lib/glossarist/rdf/namespaces.rb +8 -2
- data/lib/glossarist/rdf/relationships.rb +19 -0
- data/lib/glossarist/rdf/v3/configuration.rb +15 -0
- data/lib/glossarist/rdf/v3.rb +79 -0
- data/lib/glossarist/rdf.rb +22 -2
- data/lib/glossarist/reference_extractor.rb +12 -19
- data/lib/glossarist/reference_resolver.rb +3 -3
- data/lib/glossarist/related_concept.rb +2 -10
- data/lib/glossarist/schema_migration.rb +39 -0
- data/lib/glossarist/sts/term_mapper.rb +2 -2
- data/lib/glossarist/transforms/concept_to_gloss_transform.rb +355 -0
- data/lib/glossarist/transforms.rb +2 -2
- data/lib/glossarist/v1/concept.rb +17 -17
- data/lib/glossarist/v2/citation.rb +36 -0
- data/lib/glossarist/v2/concept_data.rb +46 -0
- data/lib/glossarist/v2/concept_document.rb +18 -0
- data/lib/glossarist/v2/concept_ref.rb +8 -0
- data/lib/glossarist/v2/concept_source.rb +16 -0
- data/lib/glossarist/v2/configuration.rb +13 -0
- data/lib/glossarist/v2/detailed_definition.rb +14 -0
- data/lib/glossarist/v2/localized_concept.rb +9 -0
- data/lib/glossarist/v2/managed_concept.rb +25 -0
- data/lib/glossarist/v2/managed_concept_data.rb +49 -0
- data/lib/glossarist/v2/related_concept.rb +15 -0
- data/lib/glossarist/v2.rb +28 -0
- data/lib/glossarist/v3/bibliography_entry.rb +19 -0
- data/lib/glossarist/v3/bibliography_file.rb +27 -0
- data/lib/glossarist/v3/citation.rb +30 -0
- data/lib/glossarist/v3/concept_data.rb +46 -0
- data/lib/glossarist/v3/concept_document.rb +18 -0
- data/lib/glossarist/v3/concept_ref.rb +8 -0
- data/lib/glossarist/v3/concept_source.rb +16 -0
- data/lib/glossarist/v3/configuration.rb +13 -0
- data/lib/glossarist/v3/detailed_definition.rb +14 -0
- data/lib/glossarist/v3/image_entry.rb +21 -0
- data/lib/glossarist/v3/image_file.rb +31 -0
- data/lib/glossarist/v3/localized_concept.rb +9 -0
- data/lib/glossarist/v3/managed_concept.rb +26 -0
- data/lib/glossarist/v3/managed_concept_data.rb +34 -0
- data/lib/glossarist/v3/related_concept.rb +15 -0
- data/lib/glossarist/v3.rb +36 -0
- data/lib/glossarist/validation/bibliography_index.rb +61 -30
- data/lib/glossarist/validation/rules/asciidoc_xref_rule.rb +2 -15
- data/lib/glossarist/validation/rules/authoritative_source_rule.rb +2 -15
- data/lib/glossarist/validation/rules/base.rb +5 -0
- data/lib/glossarist/validation/rules/bibliography_yaml_rule.rb +2 -3
- data/lib/glossarist/validation/rules/citation_completeness_rule.rb +5 -27
- data/lib/glossarist/validation/rules/dataset_context.rb +8 -3
- data/lib/glossarist/validation/rules/date_validity_rule.rb +1 -1
- data/lib/glossarist/validation/rules/designation_status_rule.rb +0 -1
- data/lib/glossarist/validation/rules/designation_type_rule.rb +1 -5
- data/lib/glossarist/validation/rules/domain_ref_rule.rb +37 -0
- data/lib/glossarist/validation/rules/domain_target_rule.rb +56 -0
- data/lib/glossarist/validation/rules/gcr_context.rb +12 -13
- data/lib/glossarist/validation/rules/image_reference_rule.rb +2 -17
- data/lib/glossarist/validation/rules/locality_completeness_rule.rb +58 -0
- data/lib/glossarist/validation/rules/localization_consistency_rule.rb +72 -0
- data/lib/glossarist/validation/rules/localization_presence_rule.rb +1 -1
- data/lib/glossarist/validation/rules/model_validity_rule.rb +71 -0
- data/lib/glossarist/validation/rules/orphaned_bibliography_rule.rb +1 -13
- data/lib/glossarist/validation/rules/orphaned_images_rule.rb +16 -11
- data/lib/glossarist/validation/rules/ref_shape_rule.rb +68 -0
- data/lib/glossarist/validation/rules/related_concept_cycle_rule.rb +1 -3
- data/lib/glossarist/validation/rules/related_concept_symmetry_rule.rb +1 -3
- data/lib/glossarist/validation/rules/related_concept_target_rule.rb +64 -0
- data/lib/glossarist/validation/rules/schema_version_rule.rb +41 -0
- data/lib/glossarist/validation/rules/source_type_rule.rb +1 -15
- data/lib/glossarist/validation/rules/source_urn_format_rule.rb +65 -0
- data/lib/glossarist/validation/rules/uuid_format_rule.rb +33 -0
- data/lib/glossarist/validation/rules.rb +10 -43
- data/lib/glossarist/validation/validation_issue.rb +14 -11
- data/lib/glossarist/validation_result.rb +12 -22
- data/lib/glossarist/version.rb +1 -1
- data/lib/glossarist.rb +9 -0
- data/memory/project-status.md +43 -0
- data/scripts/migrate_dataset.rb +180 -0
- data/scripts/migrate_isotc204_to_v3.rb +134 -0
- data/scripts/migrate_isotc211_to_v3.rb +153 -0
- data/scripts/migrate_osgeo_to_v3.rb +155 -0
- data/scripts/upgrade_dataset_to_v3.rb +47 -0
- metadata +111 -6
- data/TODO.integration/01-gcr-package-cli.md +0 -180
- data/lib/glossarist/rdf/skos_concept.rb +0 -43
- data/lib/glossarist/rdf/skos_vocabulary.rb +0 -25
- data/lib/glossarist/transforms/concept_to_skos_transform.rb +0 -131
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Glossarist
|
|
4
4
|
class DatasetValidator
|
|
5
|
+
def initialize(on_progress: nil)
|
|
6
|
+
@on_progress = on_progress
|
|
7
|
+
end
|
|
8
|
+
|
|
5
9
|
def validate(path, strict: false, reference_path: nil)
|
|
6
10
|
if File.extname(path).downcase == ".gcr"
|
|
7
11
|
validate_gcr(path, reference_path: reference_path)
|
|
@@ -13,7 +17,7 @@ module Glossarist
|
|
|
13
17
|
private
|
|
14
18
|
|
|
15
19
|
def validate_gcr(path, reference_path: nil)
|
|
16
|
-
result = GcrValidator.new.validate(path)
|
|
20
|
+
result = GcrValidator.new(on_progress: @on_progress).validate(path)
|
|
17
21
|
|
|
18
22
|
if reference_path
|
|
19
23
|
ref_result = validate_gcr_cross_references(path, reference_path)
|
|
@@ -24,7 +28,7 @@ module Glossarist
|
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def validate_directory(path, reference_path: nil)
|
|
27
|
-
result = ConceptValidator.new(path).validate_all
|
|
31
|
+
result = ConceptValidator.new(path, on_progress: @on_progress).validate_all
|
|
28
32
|
|
|
29
33
|
if reference_path
|
|
30
34
|
ref_result = validate_directory_cross_references(path, reference_path)
|
|
@@ -38,7 +42,7 @@ module Glossarist
|
|
|
38
42
|
extractor = ReferenceExtractor.new
|
|
39
43
|
resolver = build_resolver(reference_path)
|
|
40
44
|
pkg = GcrPackage.load(path)
|
|
41
|
-
uri_prefix = pkg.metadata&.
|
|
45
|
+
uri_prefix = pkg.metadata&.uri_prefix || pkg.metadata&.shortname
|
|
42
46
|
resolver.register_self(pkg.concepts)
|
|
43
47
|
resolver.register_package(pkg, uri_prefix: uri_prefix)
|
|
44
48
|
resolver.validate_all(pkg, extractor: extractor)
|
|
@@ -56,7 +60,7 @@ module Glossarist
|
|
|
56
60
|
resolver = ReferenceResolver.new
|
|
57
61
|
Dir.glob(File.join(reference_path, "*.gcr")).each do |gcr_path|
|
|
58
62
|
pkg = GcrPackage.load(gcr_path)
|
|
59
|
-
uri_prefix = pkg.metadata&.
|
|
63
|
+
uri_prefix = pkg.metadata&.uri_prefix || pkg.metadata&.shortname
|
|
60
64
|
resolver.register_package(pkg, uri_prefix: uri_prefix)
|
|
61
65
|
end
|
|
62
66
|
resolver
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Glossarist
|
|
2
|
+
module Designation
|
|
3
|
+
class Prefix < Base
|
|
4
|
+
attribute :type, :string, default: -> { "prefix" }
|
|
5
|
+
|
|
6
|
+
key_value do
|
|
7
|
+
map :type, to: :type, render_default: true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.of_yaml(hash, options = {})
|
|
11
|
+
hash["type"] = "prefix" unless hash["type"]
|
|
12
|
+
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Glossarist
|
|
2
|
+
module Designation
|
|
3
|
+
class Suffix < Base
|
|
4
|
+
attribute :type, :string, default: -> { "suffix" }
|
|
5
|
+
|
|
6
|
+
key_value do
|
|
7
|
+
map :type, to: :type, render_default: true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.of_yaml(hash, options = {})
|
|
11
|
+
hash["type"] = "suffix" unless hash["type"]
|
|
12
|
+
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -12,7 +12,7 @@ module Glossarist
|
|
|
12
12
|
attribute :languages, :string, collection: true
|
|
13
13
|
attribute :created_at, :string
|
|
14
14
|
attribute :glossarist_version, :string
|
|
15
|
-
attribute :schema_version, :string, default: -> {
|
|
15
|
+
attribute :schema_version, :string, default: -> { Glossarist::SCHEMA_VERSION }
|
|
16
16
|
attribute :statistics, GcrStatistics
|
|
17
17
|
attribute :homepage, :string
|
|
18
18
|
attribute :repository, :string
|
|
@@ -46,18 +46,19 @@ module Glossarist
|
|
|
46
46
|
|
|
47
47
|
def self.from_concepts(concepts, register_data: nil, options: {})
|
|
48
48
|
stats = GcrStatistics.from_concepts(concepts)
|
|
49
|
+
rd = register_data
|
|
49
50
|
new(
|
|
50
|
-
shortname: options[:shortname] ||
|
|
51
|
-
version: options[:version] ||
|
|
52
|
-
title: options[:title] ||
|
|
53
|
-
description: options[:description] ||
|
|
51
|
+
shortname: options[:shortname] || rd&.[]("shortname") || rd&.[]("id"),
|
|
52
|
+
version: options[:version] || rd&.[]("version"),
|
|
53
|
+
title: options[:title] || rd&.[]("name"),
|
|
54
|
+
description: options[:description] || rd&.[]("description"),
|
|
54
55
|
owner: options[:owner],
|
|
55
56
|
tags: options[:tags] || [],
|
|
56
57
|
concept_count: concepts.length,
|
|
57
58
|
languages: stats.languages,
|
|
58
59
|
created_at: Time.now.utc.iso8601,
|
|
59
60
|
glossarist_version: Glossarist::VERSION,
|
|
60
|
-
schema_version:
|
|
61
|
+
schema_version: rd&.[]("schema_version") || SchemaMigration::CURRENT_SCHEMA_VERSION,
|
|
61
62
|
statistics: stats,
|
|
62
63
|
uri_prefix: options[:uri_prefix],
|
|
63
64
|
concept_uri_template: options[:concept_uri_template],
|
|
@@ -78,13 +79,5 @@ module Glossarist
|
|
|
78
79
|
end
|
|
79
80
|
sources.map { |uri| { "uri" => uri } }
|
|
80
81
|
end
|
|
81
|
-
|
|
82
|
-
def [](key)
|
|
83
|
-
to_yaml_hash[key]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def dig(*keys)
|
|
87
|
-
to_yaml_hash.dig(*keys)
|
|
88
|
-
end
|
|
89
82
|
end
|
|
90
83
|
end
|
|
@@ -156,31 +156,41 @@ module Glossarist
|
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
def read_file_assets(zip_file)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
entry = zip_file.find_entry(asset[:path])
|
|
163
|
-
next unless entry
|
|
164
|
-
|
|
165
|
-
instance_variable_set("@#{asset[:attr]}", entry.get_input_stream.read)
|
|
166
|
-
end
|
|
159
|
+
entry = zip_file.find_entry("bibliography.yaml")
|
|
160
|
+
@bibliography = entry.get_input_stream.read if entry
|
|
167
161
|
end
|
|
168
162
|
|
|
169
163
|
def read_concepts(zip_file)
|
|
164
|
+
doc_class = concept_document_class_for_read
|
|
170
165
|
zip_file.entries.each do |entry|
|
|
171
166
|
next unless entry.name.start_with?("concepts/") && entry.name.end_with?(".yaml")
|
|
172
167
|
|
|
173
168
|
raw = entry.get_input_stream.read
|
|
174
|
-
doc =
|
|
169
|
+
doc = doc_class.from_yamls(raw)
|
|
175
170
|
@concepts << doc.to_managed_concept
|
|
176
171
|
end
|
|
177
172
|
end
|
|
178
173
|
|
|
179
174
|
private
|
|
180
175
|
|
|
176
|
+
def concept_document_class_for_read
|
|
177
|
+
version = @metadata&.schema_version.to_s
|
|
178
|
+
ConceptDocument.for_version(version)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def concept_document_class_for_write(concept)
|
|
182
|
+
version = if concept.schema_version.to_s == "2"
|
|
183
|
+
"2"
|
|
184
|
+
else
|
|
185
|
+
"3"
|
|
186
|
+
end
|
|
187
|
+
ConceptDocument.for_version(version)
|
|
188
|
+
end
|
|
189
|
+
|
|
181
190
|
def write_concept(zip_file, concept)
|
|
182
191
|
termid = concept.data.id.to_s
|
|
183
|
-
|
|
192
|
+
doc_class = concept_document_class_for_write(concept)
|
|
193
|
+
doc = doc_class.from_managed_concept(concept)
|
|
184
194
|
zip_file.get_output_stream("concepts/#{termid}.yaml") do |f|
|
|
185
195
|
f.write(doc.to_yamls)
|
|
186
196
|
end
|
|
@@ -215,19 +225,21 @@ module Glossarist
|
|
|
215
225
|
end
|
|
216
226
|
|
|
217
227
|
def write_compiled_skos(zip_file, concepts, formats, opts, name) # rubocop:disable Metrics/MethodLength
|
|
218
|
-
require "glossarist/transforms/
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
require "glossarist/transforms/concept_to_gloss_transform"
|
|
229
|
+
|
|
230
|
+
if formats.include?("jsonld") || formats.include?("turtle")
|
|
231
|
+
transform = Transforms::ConceptToGlossTransform.new(nil, opts)
|
|
221
232
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
233
|
+
if formats.include?("jsonld")
|
|
234
|
+
zip_file.get_output_stream("compiled/#{name}.jsonld") do |f|
|
|
235
|
+
f.write(transform.to_jsonld(concepts))
|
|
236
|
+
end
|
|
225
237
|
end
|
|
226
|
-
end
|
|
227
238
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
239
|
+
if formats.include?("turtle")
|
|
240
|
+
zip_file.get_output_stream("compiled/#{name}.ttl") do |f|
|
|
241
|
+
f.write(transform.to_turtle(concepts))
|
|
242
|
+
end
|
|
231
243
|
end
|
|
232
244
|
end
|
|
233
245
|
|
|
@@ -235,8 +247,8 @@ module Glossarist
|
|
|
235
247
|
|
|
236
248
|
zip_file.get_output_stream("compiled/#{name}.jsonl") do |f|
|
|
237
249
|
concepts.each do |concept|
|
|
238
|
-
|
|
239
|
-
f.write(
|
|
250
|
+
transform = Transforms::ConceptToGlossTransform.new(concept, opts)
|
|
251
|
+
f.write(transform.to_jsonl_line)
|
|
240
252
|
f.write("\n")
|
|
241
253
|
end
|
|
242
254
|
end
|
|
@@ -343,7 +355,7 @@ compiled_formats: [], **opts)
|
|
|
343
355
|
languages: languages.sort,
|
|
344
356
|
created_at: Time.now.utc.iso8601,
|
|
345
357
|
glossarist_version: Glossarist::VERSION,
|
|
346
|
-
schema_version: register_data&.
|
|
358
|
+
schema_version: register_data&.[]("schema_version") || SchemaMigration::CURRENT_SCHEMA_VERSION,
|
|
347
359
|
uri_prefix: opts[:uri_prefix],
|
|
348
360
|
concept_uri_template: opts[:concept_uri_template],
|
|
349
361
|
)
|
|
@@ -4,6 +4,10 @@ require "zip"
|
|
|
4
4
|
|
|
5
5
|
module Glossarist
|
|
6
6
|
class GcrValidator
|
|
7
|
+
def initialize(on_progress: nil)
|
|
8
|
+
@on_progress = on_progress
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
def validate(zip_path)
|
|
8
12
|
result = ValidationResult.new
|
|
9
13
|
|
|
@@ -24,27 +28,36 @@ module Glossarist
|
|
|
24
28
|
return result
|
|
25
29
|
end
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
rescue StandardError => e
|
|
30
|
-
result.add_error("Failed to load GCR: #{e.message}")
|
|
31
|
-
return result
|
|
32
|
-
end
|
|
31
|
+
context, all_concepts = load_gcr_context(zip_path, result)
|
|
32
|
+
return result if all_concepts.nil?
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
collection_rules.each do |rule|
|
|
37
|
-
next unless rule.applicable?(context)
|
|
34
|
+
validate_concepts(context, all_concepts, result)
|
|
35
|
+
validate_collection(context, result)
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
result
|
|
38
|
+
end
|
|
41
39
|
|
|
42
|
-
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def load_gcr_context(zip_path, result)
|
|
43
|
+
context = Validation::Rules::GcrContext.new(zip_path)
|
|
44
|
+
pkg = GcrPackage.load(zip_path)
|
|
45
|
+
[context, pkg.concepts]
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
result.add_error("Failed to load GCR: #{e.message}")
|
|
48
|
+
[nil, nil]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_concepts(context, all_concepts, result)
|
|
43
52
|
concept_rules = Validation::Rules::Registry.for_scope(:concept)
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
total = all_concepts.length
|
|
54
|
+
|
|
55
|
+
all_concepts.each_with_index do |concept, idx|
|
|
56
|
+
context.add_concept(concept)
|
|
46
57
|
concept_context = Validation::Rules::ConceptContext.new(
|
|
47
|
-
concept,
|
|
58
|
+
concept,
|
|
59
|
+
file_name: concept.data&.id ? "concepts/#{concept.data.id}.yaml" : "concepts/concept-#{idx}.yaml",
|
|
60
|
+
collection_context: context,
|
|
48
61
|
)
|
|
49
62
|
|
|
50
63
|
concept_rules.each do |rule|
|
|
@@ -52,9 +65,17 @@ module Glossarist
|
|
|
52
65
|
|
|
53
66
|
rule.check(concept_context).each { |i| result.add_issue(i) }
|
|
54
67
|
end
|
|
68
|
+
|
|
69
|
+
@on_progress&.call(idx + 1, total)
|
|
55
70
|
end
|
|
71
|
+
end
|
|
56
72
|
|
|
57
|
-
|
|
73
|
+
def validate_collection(context, result)
|
|
74
|
+
Validation::Rules::Registry.for_scope(:collection).each do |rule|
|
|
75
|
+
next unless rule.applicable?(context)
|
|
76
|
+
|
|
77
|
+
rule.check(context).each { |i| result.add_issue(i) }
|
|
78
|
+
end
|
|
58
79
|
end
|
|
59
80
|
end
|
|
60
81
|
end
|
|
@@ -8,7 +8,7 @@ module Glossarist
|
|
|
8
8
|
|
|
9
9
|
attribute :related, RelatedConcept, collection: true
|
|
10
10
|
attribute :dates, ConceptDate, collection: true
|
|
11
|
-
attribute :sources, ConceptSource
|
|
11
|
+
attribute :sources, ConceptSource, collection: true
|
|
12
12
|
attribute :date_accepted, ConceptDate
|
|
13
13
|
attribute :status, :string,
|
|
14
14
|
values: Glossarist::GlossaryDefinition::CONCEPT_STATUSES
|
|
@@ -19,6 +19,8 @@ module Glossarist
|
|
|
19
19
|
|
|
20
20
|
attribute :uuid, :string
|
|
21
21
|
|
|
22
|
+
attribute :schema_version, :string
|
|
23
|
+
|
|
22
24
|
key_value do
|
|
23
25
|
map :data, to: :data
|
|
24
26
|
map :id, with: { to: :identifier_to_yaml, from: :identifier_from_yaml }
|
|
@@ -26,11 +28,13 @@ module Glossarist
|
|
|
26
28
|
with: { to: :identifier_to_yaml, from: :identifier_from_yaml }
|
|
27
29
|
map :related, to: :related
|
|
28
30
|
map :dates, to: :dates
|
|
31
|
+
map :sources, to: :sources
|
|
29
32
|
map %i[date_accepted dateAccepted],
|
|
30
33
|
with: { from: :date_accepted_from_yaml, to: :date_accepted_to_yaml }
|
|
31
34
|
map :status, to: :status
|
|
32
35
|
|
|
33
36
|
map :uuid, to: :uuid, with: { from: :uuid_from_yaml, to: :uuid_to_yaml }
|
|
37
|
+
map :schema_version, to: :schema_version
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def localizations
|
|
@@ -65,7 +69,7 @@ module Glossarist
|
|
|
65
69
|
def uuid
|
|
66
70
|
@uuid ||= Glossarist::Utilities::UUID.uuid_v5(
|
|
67
71
|
Glossarist::Utilities::UUID::OID_NAMESPACE,
|
|
68
|
-
to_yaml(except: [
|
|
72
|
+
to_yaml(except: %i[uuid schema_version]),
|
|
69
73
|
)
|
|
70
74
|
end
|
|
71
75
|
|
|
@@ -109,18 +113,19 @@ module Glossarist
|
|
|
109
113
|
data.localized_concepts ||= {}
|
|
110
114
|
data.localized_concepts[lang] =
|
|
111
115
|
data.localized_concepts[lang] || localized_concept.uuid
|
|
116
|
+
localized_concept.uuid = data.localized_concepts[lang]
|
|
112
117
|
localizations.store(lang, localized_concept)
|
|
113
118
|
end
|
|
114
119
|
alias :add_l10n :add_localization
|
|
115
120
|
|
|
116
121
|
def to_jsonld
|
|
117
|
-
require "glossarist/transforms/
|
|
118
|
-
Transforms::
|
|
122
|
+
require "glossarist/transforms/concept_to_gloss_transform"
|
|
123
|
+
Transforms::ConceptToGlossTransform.transform(self).to_jsonld
|
|
119
124
|
end
|
|
120
125
|
|
|
121
126
|
def to_turtle
|
|
122
|
-
require "glossarist/transforms/
|
|
123
|
-
Transforms::
|
|
127
|
+
require "glossarist/transforms/concept_to_gloss_transform"
|
|
128
|
+
Transforms::ConceptToGlossTransform.transform(self).to_turtle
|
|
124
129
|
end
|
|
125
130
|
|
|
126
131
|
def default_designation
|
|
@@ -138,6 +143,34 @@ module Glossarist
|
|
|
138
143
|
localization("eng") || localizations.values.first
|
|
139
144
|
end
|
|
140
145
|
|
|
146
|
+
def schema_version
|
|
147
|
+
@schema_version
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def assign_uuid(new_uuid)
|
|
151
|
+
@uuid = new_uuid
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.detect_schema_version(concept) # rubocop:disable Metrics/PerceivedComplexity
|
|
155
|
+
raw = concept.schema_version
|
|
156
|
+
if raw && !%w[legacy nil].include?(raw.to_s)
|
|
157
|
+
return raw.to_s
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
return "3" if concept.related&.any?
|
|
161
|
+
return "3" if concept.sources&.any?
|
|
162
|
+
return "3" if concept.data&.domains&.any?
|
|
163
|
+
return "3" if localization_has_references?(concept)
|
|
164
|
+
|
|
165
|
+
"2"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.localization_has_references?(concept)
|
|
169
|
+
concept.localizations&.any? do |l10n|
|
|
170
|
+
l10n.is_a?(LocalizedConcept) && l10n.data&.references&.any?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
141
174
|
Glossarist::GlossaryDefinition::RELATED_CONCEPT_TYPES.each do |type|
|
|
142
175
|
# List of related concepts of the specified type.
|
|
143
176
|
# @return [Array<RelatedConcept>]
|
|
@@ -10,6 +10,7 @@ module Glossarist
|
|
|
10
10
|
attribute :localizations, LocalizedConcept,
|
|
11
11
|
collection: Collections::LocalizationCollection,
|
|
12
12
|
initialize_empty: true
|
|
13
|
+
attribute :related, RelatedConcept, collection: true
|
|
13
14
|
|
|
14
15
|
key_value do
|
|
15
16
|
map %i[id identifier], to: :id,
|
|
@@ -17,7 +18,7 @@ module Glossarist
|
|
|
17
18
|
map :uri, to: :uri
|
|
18
19
|
map %i[localized_concepts localizedConcepts], to: :localized_concepts
|
|
19
20
|
map %i[domains groups], to: :domains,
|
|
20
|
-
|
|
21
|
+
with: { from: :domains_from_yaml, to: :domains_to_yaml }
|
|
21
22
|
map :sources, to: :sources
|
|
22
23
|
map :localizations, to: :localizations,
|
|
23
24
|
with: { from: :localizations_from_yaml, to: :localizations_to_yaml }
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
# Extends Lutaml::JsonLd::Transform to handle:
|
|
6
|
+
# - Multiple rdf:type values
|
|
7
|
+
# - URI-valued predicates (as: :uri)
|
|
8
|
+
# - Linking predicates for members (link option)
|
|
9
|
+
# - Recursive context/namespace collection
|
|
10
|
+
# - Recursive graph document building
|
|
11
|
+
module Lutaml
|
|
12
|
+
module JsonLd
|
|
13
|
+
class Transform < Lutaml::Rdf::Transform
|
|
14
|
+
def model_to_data(instance, _format, options = {})
|
|
15
|
+
mapping = extract_mapping(options)
|
|
16
|
+
return {} unless mapping
|
|
17
|
+
|
|
18
|
+
if mapping.rdf_members.any?
|
|
19
|
+
build_graph_document(mapping, instance)
|
|
20
|
+
else
|
|
21
|
+
build_resource_object(mapping, instance)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def data_to_model(data, _format, options = {})
|
|
26
|
+
mapping = extract_mapping(options)
|
|
27
|
+
return model_class.new unless mapping
|
|
28
|
+
|
|
29
|
+
hash = data.is_a?(String) ? JSON.parse(data) : data
|
|
30
|
+
|
|
31
|
+
if hash.key?("@graph") && hash["@graph"].is_a?(Array) && !hash["@graph"].empty?
|
|
32
|
+
graph_data = hash["@graph"]
|
|
33
|
+
first = graph_data.first
|
|
34
|
+
hash = first.is_a?(Hash) ? first : {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
hash = strip_jsonld_keywords(hash)
|
|
38
|
+
|
|
39
|
+
attrs = {}
|
|
40
|
+
mapping.rdf_predicates.each do |rule|
|
|
41
|
+
value = hash[rule.predicate_name]
|
|
42
|
+
next if value.nil?
|
|
43
|
+
|
|
44
|
+
attrs[rule.to] = if rule.lang_tagged && value.is_a?(Hash)
|
|
45
|
+
flatten_language_map(value)
|
|
46
|
+
else
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
build_instance(attrs, options)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def extract_mapping(options)
|
|
57
|
+
options[:mappings] || mappings_for(:jsonld, lutaml_register)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_graph_document(mapping, instance)
|
|
61
|
+
context = build_merged_context_recursive(mapping, instance)
|
|
62
|
+
graph = collect_resources(mapping, instance)
|
|
63
|
+
|
|
64
|
+
{ "@context" => context, "@graph" => graph }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def collect_resources(mapping, instance)
|
|
68
|
+
graph = []
|
|
69
|
+
|
|
70
|
+
if mapping.rdf_subject
|
|
71
|
+
resource = build_resource_data(mapping, instance)
|
|
72
|
+
graph << resource unless resource.empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
mapping.rdf_members.each do |member_rule|
|
|
76
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
77
|
+
collection.each do |member|
|
|
78
|
+
member_mapping = member.class.mappings[:jsonld]
|
|
79
|
+
next unless member_mapping
|
|
80
|
+
|
|
81
|
+
resource = build_resource_data(member_mapping, member)
|
|
82
|
+
graph << resource unless resource.empty?
|
|
83
|
+
|
|
84
|
+
# Recurse into child members
|
|
85
|
+
child_resources = collect_resources(member_mapping, member)
|
|
86
|
+
# Skip the first entry (already added above) and any empty resources
|
|
87
|
+
child_resources[1..-1].each { |r| graph << r }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
graph
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_merged_context_recursive(mapping, instance)
|
|
95
|
+
context_hash = build_context_from_mapping(mapping).to_hash
|
|
96
|
+
|
|
97
|
+
mapping.rdf_members.each do |member_rule|
|
|
98
|
+
collection = Array(instance.public_send(member_rule.attr_name))
|
|
99
|
+
next if collection.empty?
|
|
100
|
+
|
|
101
|
+
collection.map(&:class).uniq.each do |klass|
|
|
102
|
+
member_mapping = klass.mappings[:jsonld]
|
|
103
|
+
next unless member_mapping
|
|
104
|
+
|
|
105
|
+
context_hash.merge!(build_context_from_mapping(member_mapping).to_hash)
|
|
106
|
+
|
|
107
|
+
# Recurse into child members
|
|
108
|
+
child_ctx = build_merged_context_recursive(member_mapping, collection.first)
|
|
109
|
+
context_hash.merge!(child_ctx)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context_hash
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def build_context_from_mapping(mapping)
|
|
117
|
+
context = Context.new
|
|
118
|
+
mapping.namespace_set.each { |ns| context.prefix(ns) }
|
|
119
|
+
mapping.rdf_predicates.each do |pred|
|
|
120
|
+
if pred.lang_tagged
|
|
121
|
+
context.term(pred.predicate_name,
|
|
122
|
+
id: pred.uri,
|
|
123
|
+
container: :language)
|
|
124
|
+
else
|
|
125
|
+
context.term(pred.predicate_name, id: pred.uri)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
context
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def build_resource_object(mapping, instance)
|
|
132
|
+
context = build_context_from_mapping(mapping).to_hash
|
|
133
|
+
data = build_resource_data(mapping, instance)
|
|
134
|
+
{ "@context" => context }.merge(data)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_resource_data(mapping, instance)
|
|
138
|
+
result = {}
|
|
139
|
+
|
|
140
|
+
if mapping.rdf_types.any?
|
|
141
|
+
types = mapping.rdf_types.map do |t|
|
|
142
|
+
mapping.namespace_set.resolve_compact_iri(t)
|
|
143
|
+
end
|
|
144
|
+
result["@type"] = types.length == 1 ? types.first : types
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if mapping.rdf_subject
|
|
148
|
+
result["@id"] = resolve_subject_uri(mapping, instance)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
mapping.rdf_predicates.each do |rule|
|
|
152
|
+
value = instance.public_send(rule.to)
|
|
153
|
+
next if value.nil?
|
|
154
|
+
next if value.is_a?(String) && value.empty?
|
|
155
|
+
|
|
156
|
+
result[rule.predicate_name] = if rule.lang_tagged
|
|
157
|
+
build_language_map(value)
|
|
158
|
+
else
|
|
159
|
+
serialize_rdf_value(value, rule, mapping)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Emit variable-predicate relationship triples
|
|
164
|
+
if instance.is_a?(Glossarist::Rdf::Relationships)
|
|
165
|
+
Array(instance.relationship_triples).each do |pred_uri, obj_uri|
|
|
166
|
+
key = pred_uri.split("#").last.split("/").last
|
|
167
|
+
result[key] = { "@id" => obj_uri }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
result
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def build_language_map(values)
|
|
175
|
+
case values
|
|
176
|
+
when Array
|
|
177
|
+
map = {}
|
|
178
|
+
values.each do |v|
|
|
179
|
+
lang = extract_language(v)
|
|
180
|
+
map[lang] = v.to_s if lang
|
|
181
|
+
end
|
|
182
|
+
map.empty? ? nil : map
|
|
183
|
+
else
|
|
184
|
+
lang = extract_language(values)
|
|
185
|
+
lang ? { lang => values.to_s } : values.to_s
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def flatten_language_map(lang_map)
|
|
190
|
+
lang_map.values
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def serialize_rdf_value(value, rule = nil, mapping = nil)
|
|
194
|
+
case value
|
|
195
|
+
when Array then value.map { |v| serialize_rdf_value(v, rule, mapping) }
|
|
196
|
+
when Integer, Float, TrueClass, FalseClass then value
|
|
197
|
+
else value.to_s
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def strip_jsonld_keywords(data)
|
|
202
|
+
return data unless data.is_a?(Hash)
|
|
203
|
+
|
|
204
|
+
data.reject { |key, _| key.start_with?("@") }
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|