glossarist 2.5.0 → 2.5.2

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop_todo.yml +50 -146
  4. data/CLAUDE.md +33 -7
  5. data/Gemfile +20 -19
  6. data/README.adoc +383 -7
  7. data/TODO.integration/01-gcr-package-cli.md +180 -0
  8. data/exe/glossarist +1 -53
  9. data/glossarist.gemspec +1 -0
  10. data/lib/glossarist/asset.rb +1 -1
  11. data/lib/glossarist/citation.rb +1 -1
  12. data/lib/glossarist/cli/package_command.rb +32 -0
  13. data/lib/glossarist/cli/upgrade_command.rb +34 -0
  14. data/lib/glossarist/cli/validate_command.rb +56 -0
  15. data/lib/glossarist/cli.rb +105 -0
  16. data/lib/glossarist/collection_config.rb +23 -0
  17. data/lib/glossarist/collections.rb +15 -8
  18. data/lib/glossarist/concept.rb +1 -1
  19. data/lib/glossarist/concept_collector.rb +153 -0
  20. data/lib/glossarist/concept_data.rb +3 -1
  21. data/lib/glossarist/concept_date.rb +1 -1
  22. data/lib/glossarist/concept_document.rb +29 -0
  23. data/lib/glossarist/concept_enricher.rb +34 -0
  24. data/lib/glossarist/concept_manager.rb +31 -49
  25. data/lib/glossarist/concept_reference.rb +45 -0
  26. data/lib/glossarist/concept_source.rb +1 -1
  27. data/lib/glossarist/concept_validator.rb +101 -0
  28. data/lib/glossarist/custom_locality.rb +1 -1
  29. data/lib/glossarist/dataset_validator.rb +69 -0
  30. data/lib/glossarist/designation/abbreviation.rb +1 -1
  31. data/lib/glossarist/designation/base.rb +11 -4
  32. data/lib/glossarist/designation/expression.rb +1 -1
  33. data/lib/glossarist/designation/grammar_info.rb +1 -1
  34. data/lib/glossarist/designation/graphical_symbol.rb +1 -1
  35. data/lib/glossarist/designation/letter_symbol.rb +1 -1
  36. data/lib/glossarist/designation/symbol.rb +2 -2
  37. data/lib/glossarist/detailed_definition.rb +1 -1
  38. data/lib/glossarist/gcr_metadata.rb +87 -0
  39. data/lib/glossarist/gcr_package.rb +223 -0
  40. data/lib/glossarist/gcr_statistics.rb +35 -0
  41. data/lib/glossarist/gcr_validator.rb +98 -0
  42. data/lib/glossarist/locality.rb +1 -1
  43. data/lib/glossarist/localized_concept.rb +12 -1
  44. data/lib/glossarist/managed_concept.rb +1 -1
  45. data/lib/glossarist/managed_concept_data.rb +5 -2
  46. data/lib/glossarist/non_verb_rep.rb +1 -1
  47. data/lib/glossarist/reference_extractor.rb +227 -0
  48. data/lib/glossarist/reference_resolver.rb +169 -0
  49. data/lib/glossarist/register_data.rb +39 -0
  50. data/lib/glossarist/related_concept.rb +1 -1
  51. data/lib/glossarist/resolution_adapter/local.rb +73 -0
  52. data/lib/glossarist/resolution_adapter/package.rb +22 -0
  53. data/lib/glossarist/resolution_adapter/remote.rb +60 -0
  54. data/lib/glossarist/resolution_adapter/route.rb +34 -0
  55. data/lib/glossarist/resolution_adapter.rb +14 -0
  56. data/lib/glossarist/schema_migration.rb +334 -0
  57. data/lib/glossarist/urn_resolver.rb +71 -0
  58. data/lib/glossarist/v1/concept.rb +81 -0
  59. data/lib/glossarist/v1/cross_references.rb +41 -0
  60. data/lib/glossarist/v1/register.rb +50 -0
  61. data/lib/glossarist/v1.rb +9 -0
  62. data/lib/glossarist/validation_result.rb +38 -0
  63. data/lib/glossarist/version.rb +1 -1
  64. data/lib/glossarist.rb +29 -4
  65. data/relaton-bib-2.0.0.gem +0 -0
  66. data/relaton-bib-2.1.0.gem +0 -0
  67. data/relaton-cen-2.0.0.gem +0 -0
  68. data/relaton-iec-2.0.0.gem +0 -0
  69. data/relaton-iso-2.0.0.gem +0 -0
  70. data/relaton-itu-2.0.0.gem +0 -0
  71. metadata +60 -7
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class CLI
5
+ class UpgradeCommand
6
+ def initialize(source_dir, options)
7
+ @source_dir = source_dir
8
+ @options = options
9
+ end
10
+
11
+ def run
12
+ result = SchemaMigration.upgrade_directory(
13
+ @source_dir,
14
+ output: @options[:output],
15
+ target_version: @options[:target_version],
16
+ cross_references: @options[:cross_references],
17
+ dry_run: @options[:dry_run],
18
+ )
19
+ report(result)
20
+ rescue ArgumentError => e
21
+ warn "Error: #{e.message}"
22
+ exit 1
23
+ end
24
+
25
+ private
26
+
27
+ def report(result)
28
+ puts "Upgraded #{result[:count]} concepts " \
29
+ "from schema v#{result[:source_version]} to v#{result[:target_version]}"
30
+ puts "Output: #{result[:output]}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class CLI
5
+ class ValidateCommand
6
+ def initialize(path, options)
7
+ @path = path
8
+ @options = options
9
+ end
10
+
11
+ def run
12
+ result = DatasetValidator.new.validate(
13
+ @path,
14
+ strict: @options[:strict],
15
+ reference_path: @options[:reference_path],
16
+ )
17
+ report(result)
18
+ exit_code = result.errors.any? || (@options[:strict] && result.warnings.any?) ? 1 : 0
19
+ exit(exit_code) unless exit_code.zero?
20
+ end
21
+
22
+ private
23
+
24
+ def report(result)
25
+ case @options[:format]
26
+ when "json"
27
+ require "json"
28
+ puts JSON.pretty_generate(result.to_h)
29
+ when "yaml"
30
+ require "yaml"
31
+ puts YAML.dump(result.to_h)
32
+ else
33
+ report_text(result)
34
+ end
35
+ end
36
+
37
+ def report_text(result)
38
+ if result.valid?
39
+ puts "Valid."
40
+ else
41
+ puts "Invalid."
42
+ result.errors.each { |e| puts " ERROR: #{e}" }
43
+ end
44
+
45
+ if result.warnings.any?
46
+ result.warnings.each do |w|
47
+ puts " WARNING: #{w}"
48
+ end
49
+ end
50
+
51
+ total = result.errors.length + result.warnings.length
52
+ puts "#{total} issue(s) found."
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Glossarist
6
+ class CLI < Thor
7
+ desc "generate_latex", "Convert Concepts to Latex format"
8
+
9
+ option :concepts_path, aliases: :p, required: true,
10
+ desc: "Path to yaml concepts directory"
11
+ option :latex_concepts, aliases: :l,
12
+ desc: "File path having list of concepts that should be converted to LATEX format. If not provided all the concepts will be converted to the latex format"
13
+ option :output_file, aliases: :o,
14
+ desc: "Output file path. By default the output will pe printed to the console"
15
+ option :extra_attributes, aliases: :e, type: :array,
16
+ desc: "List of extra attributes that are not in standard Glossarist Concept model"
17
+ def generate_latex
18
+ assets = []
19
+
20
+ if options[:extra_attributes]
21
+ Glossarist.configure do |config|
22
+ config.register_extension_attributes(options[:extra_attributes])
23
+ end
24
+ end
25
+
26
+ concept_set = Glossarist::ConceptSet.new(options[:concepts_path], assets)
27
+ latex_str = concept_set.to_latex(options[:latex_concepts])
28
+ output_latex(latex_str)
29
+ end
30
+
31
+ desc "upgrade SOURCE_DIR", "Upgrade dataset to current schema version"
32
+ option :output, aliases: :o, required: true,
33
+ desc: "Output directory or .gcr file path"
34
+ option :target_version, type: :string, default: Glossarist::SchemaMigration::CURRENT_SCHEMA_VERSION,
35
+ desc: "Target schema version (default: current)"
36
+ option :cross_references, type: :string,
37
+ desc: "Path to datasets.yml for cross-reference maps"
38
+ option :dry_run, type: :boolean, default: false,
39
+ desc: "Show what would change without writing"
40
+ def upgrade(source_dir)
41
+ require_relative "cli/upgrade_command"
42
+ Glossarist::CLI::UpgradeCommand.new(source_dir, options).run
43
+ end
44
+
45
+ desc "package DIR", "Create a .gcr ZIP archive from a schema v1 dataset"
46
+ option :output, aliases: :o, required: true,
47
+ desc: "Output .gcr file path"
48
+ option :shortname, type: :string, required: true,
49
+ desc: "Machine-readable dataset ID"
50
+ option :version, type: :string, required: true,
51
+ desc: "Semantic version (e.g. 1.0.0)"
52
+ option :uri_prefix, type: :string,
53
+ desc: "URI namespace prefix (e.g. urn:iec:std:iec:60050)"
54
+ option :title, type: :string, desc: "Dataset title"
55
+ option :description, type: :string, desc: "Dataset description"
56
+ option :owner, type: :string, desc: "Dataset owner"
57
+ option :register_yaml, type: :string,
58
+ desc: "Path to register.yaml to include in package"
59
+ option :tags, type: :array, desc: "Tags for the dataset"
60
+ def package(dir)
61
+ require_relative "cli/package_command"
62
+ Glossarist::CLI::PackageCommand.new(dir, options).run
63
+ end
64
+
65
+ desc "validate PATH",
66
+ "Validate dataset directory or .gcr for schema compliance"
67
+ option :strict, type: :boolean, default: false,
68
+ desc: "Treat warnings as errors"
69
+ option :format, type: :string, default: "text",
70
+ enum: %w[text json yaml],
71
+ desc: "Output format for validation results"
72
+ option :reference_path, type: :string,
73
+ desc: "Path to directory of .gcr files for cross-dataset reference validation"
74
+ def validate(path)
75
+ require_relative "cli/validate_command"
76
+ Glossarist::CLI::ValidateCommand.new(path, options).run
77
+ end
78
+
79
+ def method_missing(*args)
80
+ warn "No method found named: #{args[0]}"
81
+ warn "Run with `--help` or `-h` to see available options"
82
+ exit 1
83
+ end
84
+
85
+ def respond_to_missing?
86
+ true
87
+ end
88
+
89
+ def self.exit_on_failure?
90
+ true
91
+ end
92
+
93
+ private
94
+
95
+ def output_latex(latex_str)
96
+ output_file_path = options[:output_file]
97
+
98
+ if output_file_path
99
+ File.open(output_file_path, "w") { |file| file.puts latex_str }
100
+ else
101
+ puts latex_str
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class CollectionConfig < Lutaml::Model::Serializable
5
+ attribute :packages, :hash, collection: true, default: -> { [] }
6
+ attribute :routes, :hash, collection: true, default: -> { [] }
7
+ attribute :remotes, :hash, collection: true, default: -> { [] }
8
+
9
+ yaml do
10
+ map :packages, to: :packages
11
+ map :routes, to: :routes
12
+ map :remote, to: :remotes
13
+ end
14
+
15
+ def self.from_file(path)
16
+ return nil unless File.exist?(path)
17
+
18
+ from_yaml(File.read(path))
19
+ rescue Psych::SyntaxError, Lutaml::Model::InvalidFormatError
20
+ nil
21
+ end
22
+ end
23
+ end
@@ -2,13 +2,20 @@
2
2
 
3
3
  module Glossarist
4
4
  module Collections
5
- autoload :AssetCollection, "glossarist/collections/asset_collection"
6
- autoload :BibliographyCollection, "glossarist/collections/bibliography_collection"
7
- autoload :Collection, "glossarist/collections/collection"
8
- autoload :ConceptSourceCollection, "glossarist/collections/concept_source_collection"
9
- autoload :DesignationCollection, "glossarist/collections/designation_collection"
10
- autoload :DetailedDefinitionCollection, "glossarist/collections/detailed_definition_collection"
11
- autoload :LocalizationCollection, "glossarist/collections/localization_collection"
12
- autoload :TypedCollection, "glossarist/collections/typed_collection"
5
+ autoload :AssetCollection,
6
+ "glossarist/collections/asset_collection"
7
+ autoload :BibliographyCollection,
8
+ "glossarist/collections/bibliography_collection"
9
+ autoload :Collection, "glossarist/collections/collection"
10
+ autoload :ConceptSourceCollection,
11
+ "glossarist/collections/concept_source_collection"
12
+ autoload :DesignationCollection,
13
+ "glossarist/collections/designation_collection"
14
+ autoload :DetailedDefinitionCollection,
15
+ "glossarist/collections/detailed_definition_collection"
16
+ autoload :LocalizationCollection,
17
+ "glossarist/collections/localization_collection"
18
+ autoload :TypedCollection,
19
+ "glossarist/collections/typed_collection"
13
20
  end
14
21
  end
@@ -11,7 +11,7 @@ module Glossarist
11
11
  attribute :extension_attributes, :hash
12
12
  attribute :termid, :string
13
13
 
14
- yaml do
14
+ key_value do
15
15
  map :data, to: :data
16
16
  map :termid, to: :termid
17
17
  map :subject, to: :subject
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptCollector
5
+ def self.collect(dir)
6
+ dir = File.expand_path(dir)
7
+ unless File.directory?(dir)
8
+ raise ArgumentError, "#{dir} is not a directory"
9
+ end
10
+
11
+ if v2_concepts?(dir)
12
+ collect_v2_concepts(dir)
13
+ elsif managed_concepts?(dir)
14
+ collect_managed_concepts(dir)
15
+ elsif v1_concepts?(dir)
16
+ collect_v1_concepts(dir)
17
+ else
18
+ []
19
+ end
20
+ end
21
+
22
+ def self.each_concept(dir, &block)
23
+ dir = File.expand_path(dir)
24
+ unless File.directory?(dir)
25
+ raise ArgumentError, "#{dir} is not a directory"
26
+ end
27
+ return enum_for(:each_concept, dir) unless block
28
+
29
+ if v2_concepts?(dir)
30
+ each_v2_concept(dir, &block)
31
+ elsif managed_concepts?(dir)
32
+ each_managed_concept(dir, &block)
33
+ elsif v1_concepts?(dir)
34
+ each_v1_concept(dir, &block)
35
+ end
36
+ end
37
+
38
+ class << self
39
+ private
40
+
41
+ def v1_concepts?(dir)
42
+ concepts_dir = File.join(dir, "concepts")
43
+ File.directory?(concepts_dir) &&
44
+ Dir.glob(File.join(concepts_dir, "*.yaml")).any? do |f|
45
+ V1::Concept.from_file(f)&.termid?
46
+ end
47
+ end
48
+
49
+ def v2_concepts?(dir)
50
+ File.directory?(File.join(dir, "geolexica-v2"))
51
+ end
52
+
53
+ def managed_concepts?(dir)
54
+ concept_dir = File.join(dir, "concepts", "concept")
55
+ File.directory?(concept_dir) &&
56
+ Dir.glob(File.join(concept_dir, "*.yaml")).any?
57
+ end
58
+
59
+ def collect_v1_concepts(dir)
60
+ concepts = []
61
+ each_v1_concept(dir) { |mc| concepts << mc }
62
+ concepts
63
+ end
64
+
65
+ def each_v1_concept(dir)
66
+ concepts_dir = File.join(dir, "concepts")
67
+ files = Dir.glob(File.join(concepts_dir, "*.yaml"))
68
+ files.each do |file|
69
+ v1 = V1::Concept.from_file(file)
70
+ next unless v1
71
+
72
+ yield v1.to_managed_concept
73
+ end
74
+ end
75
+
76
+ def collect_v2_concepts(dir)
77
+ v2_dir = File.join(dir, "geolexica-v2")
78
+ if File.directory?(File.join(v2_dir, "concepts"))
79
+ collect_managed_concepts(v2_dir)
80
+ else
81
+ collect_grouped_v2_concepts(v2_dir)
82
+ end
83
+ end
84
+
85
+ def each_v2_concept(dir, &block)
86
+ v2_dir = File.join(dir, "geolexica-v2")
87
+ if File.directory?(File.join(v2_dir, "concepts"))
88
+ each_managed_concept(v2_dir, &block)
89
+ else
90
+ each_grouped_v2_concept(v2_dir, &block)
91
+ end
92
+ end
93
+
94
+ def each_grouped_v2_concept(v2_dir, &block)
95
+ collection = ManagedConceptCollection.new
96
+ manager = ConceptManager.new(path: v2_dir)
97
+ manager.load_from_files(collection: collection)
98
+ collection.each(&block)
99
+ end
100
+
101
+ def collect_grouped_v2_concepts(v2_dir)
102
+ collection = ManagedConceptCollection.new
103
+ manager = ConceptManager.new(path: v2_dir)
104
+ manager.load_from_files(collection: collection)
105
+ collection.to_a
106
+ end
107
+
108
+ def collect_managed_concepts(dir) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
109
+ concepts = []
110
+ each_managed_concept(dir) { |mc| concepts << mc }
111
+ concepts
112
+ end
113
+
114
+ def each_managed_concept(dir) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
115
+ concepts_dir = File.join(dir, "concepts")
116
+ concept_files = Dir.glob(File.join(concepts_dir, "concept", "*.yaml"))
117
+ return if concept_files.empty?
118
+
119
+ lc_dir = find_localized_concepts_dir(concepts_dir)
120
+ lc_index = build_lc_index(lc_dir) if lc_dir
121
+
122
+ concept_files.each do |f|
123
+ mc = ManagedConcept.from_yaml(File.read(f))
124
+ next unless mc.data&.id
125
+
126
+ lc_map = mc.data.localized_concepts || {}
127
+ lc_map.each_value do |uuid|
128
+ lc_file = lc_index ? lc_index[uuid] : nil
129
+ next unless lc_file
130
+
131
+ l10n = LocalizedConcept.from_yaml(File.read(lc_file))
132
+ mc.add_localization(l10n)
133
+ end
134
+
135
+ yield mc
136
+ end
137
+ end
138
+
139
+ def build_lc_index(lc_dir)
140
+ Dir.glob(File.join(lc_dir, "*.{yaml,yml}"))
141
+ .to_h { |f| [File.basename(f, ".*"), f] }
142
+ end
143
+
144
+ def find_localized_concepts_dir(concepts_dir)
145
+ %w[localized_concept localized-concept].each do |name|
146
+ d = File.join(concepts_dir, name)
147
+ return d if File.directory?(d)
148
+ end
149
+ nil
150
+ end
151
+ end
152
+ end
153
+ end
@@ -20,6 +20,7 @@ module Glossarist
20
20
  initialize_empty: true
21
21
  attribute :terms, Designation::Base, collection: true
22
22
  attribute :related, RelatedConcept, collection: true
23
+ attribute :references, ConceptReference, collection: true
23
24
  attribute :domain, :string
24
25
  attribute :review_date, :date_time
25
26
  attribute :review_decision_date, :date_time
@@ -31,7 +32,7 @@ module Glossarist
31
32
  attribute :language_code, :string, pattern: /^.{3}$/
32
33
  attribute :entry_status, :string
33
34
 
34
- yaml do
35
+ key_value do
35
36
  map :dates, to: :dates
36
37
  map :definition, to: :definition, value_map: { to: { empty: :empty } }
37
38
  map :examples, to: :examples, value_map: { to: { empty: :empty } }
@@ -44,6 +45,7 @@ module Glossarist
44
45
  map :terms, to: :terms,
45
46
  with: { from: :terms_from_yaml, to: :terms_to_yaml }
46
47
  map :related, to: :related
48
+ map :references, to: :references
47
49
  map :domain, to: :domain
48
50
  map %i[language_code languageCode], to: :language_code
49
51
  map %i[entry_status entryStatus], to: :entry_status
@@ -8,7 +8,7 @@ module Glossarist
8
8
  attribute :type, :string,
9
9
  values: Glossarist::GlossaryDefinition::CONCEPT_DATE_TYPES
10
10
 
11
- yaml do
11
+ key_value do
12
12
  map :date, to: :date
13
13
  map :type, to: :type
14
14
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptDocument < Lutaml::Model::Serializable
5
+ attribute :concept, ManagedConcept
6
+ attribute :localizations, LocalizedConcept, collection: true
7
+
8
+ yamls do
9
+ sequence do
10
+ map_document 0, to: :concept, type: ManagedConcept
11
+ map_document 1.., to: :localizations, type: LocalizedConcept,
12
+ collection: true
13
+ end
14
+ end
15
+
16
+ def self.from_managed_concept(managed_concept)
17
+ new(
18
+ concept: managed_concept,
19
+ localizations: managed_concept.localizations&.values || [],
20
+ )
21
+ end
22
+
23
+ def to_managed_concept
24
+ mc = concept
25
+ localizations.each { |l10n| mc.add_localization(l10n) }
26
+ mc
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptEnricher
5
+ def inject_references(concepts) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
6
+ extractor = ReferenceExtractor.new
7
+
8
+ concepts.each do |mc|
9
+ mc.localizations.each do |l10n|
10
+ refs = extractor.extract_from_localized_concept(l10n)
11
+ next if refs.empty?
12
+
13
+ existing = l10n.data.references || []
14
+ seen_keys = existing.to_set { |r| [r.source, r.concept_id] }
15
+
16
+ refs.each do |ref|
17
+ key = [ref.source, ref.concept_id]
18
+ next if seen_keys.include?(key)
19
+
20
+ seen_keys.add(key)
21
+ existing << ref
22
+ end
23
+ l10n.data.references = existing
24
+ end
25
+ end
26
+ end
27
+
28
+ def apply_uri_template(concepts, template)
29
+ concepts.each do |mc|
30
+ mc.data.uri = template.sub("{id}", mc.data.id.to_s)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -3,7 +3,7 @@ module Glossarist
3
3
  attribute :path, :string
4
4
  attribute :localized_concepts_path, :string
5
5
 
6
- yaml do
6
+ key_value do
7
7
  map :path, to: :path
8
8
  map %i[localized_concepts_path localizedConceptsPath],
9
9
  to: :localized_concepts_path
@@ -33,62 +33,43 @@ module Glossarist
33
33
  end
34
34
  end
35
35
 
36
- def group_concept_hashes(mixed_hashes)
37
- concept_hashes = mixed_hashes.select do |concept_hash|
38
- !concept_hash["data"]["localized_concepts"].nil? ||
39
- !concept_hash["data"]["localizedConcepts"].nil?
36
+ def load_concept_from_file(filename) # rubocop:disable Metrics/CyclomaticComplexity
37
+ raw = File.read(filename, encoding: "utf-8")
38
+ doc = ConceptDocument.from_yamls(raw)
39
+ concept = doc.concept
40
+ unless concept
41
+ raise Glossarist::ParseError.new(filename: filename)
40
42
  end
41
43
 
42
- localized_concept_hashes = mixed_hashes.select do |concept_hash|
43
- concept_hash["data"]["localized_concepts"].nil? &&
44
- concept_hash["data"]["localizedConcepts"].nil?
45
- end
46
-
47
- [concept_hashes, localized_concept_hashes]
48
- end
49
-
50
- def load_concept_from_file(filename)
51
- mixed_hashes = YAML.load_stream(File.read(filename, encoding: "utf-8"))
52
- concepts = []
53
-
54
- concept_hashes, localized_concept_hashes =
55
- group_concept_hashes(mixed_hashes)
56
-
57
- concept_hashes.each do |concept_hash|
58
- concept_hash["uuid"] = concept_hash["id"] ||
59
- File.basename(filename, ".*")
60
- concept = Config.class_for(:managed_concept).of_yaml(concept_hash)
44
+ concept_uuid = concept.identifier || concept.data&.id || File.basename(
45
+ filename, ".*"
46
+ )
47
+ concept.instance_variable_set(:@uuid, concept_uuid)
61
48
 
62
- concept.data.localized_concepts.each_value do |id|
63
- localized_concept =
64
- load_localized_concept(id, localized_concept_hashes)
65
- concept.add_l10n(localized_concept)
66
- end
67
-
68
- concepts << concept
49
+ concept.data.localized_concepts.each_value do |id|
50
+ localized_concept = load_localized_concept(id, doc.localizations)
51
+ concept.add_l10n(localized_concept)
69
52
  end
70
53
 
71
- concepts
54
+ [concept]
72
55
  rescue Psych::SyntaxError => e
73
56
  raise Glossarist::ParseError.new(filename: filename, line: e.line)
74
57
  end
75
58
 
76
- def load_localized_concept(id, localized_concept_hashes = [])
77
- {}
78
-
79
- concept_hash = if localized_concept_hashes.empty?
80
- Psych.safe_load(
81
- File.read(localized_concept_path(id), encoding: "utf-8"),
82
- permitted_classes: [Date, Time],
83
- )
84
- else
85
- localized_concept_hashes.find do |hash|
86
- hash["id"] == id
87
- end
88
- end
89
-
90
- concept_hash["uuid"] = id
91
- Config.class_for(:localized_concept).of_yaml(concept_hash)
59
+ def load_localized_concept(id, inline_localizations = nil)
60
+ if inline_localizations
61
+ l10n = inline_localizations.find { |l| l.id == id }
62
+ if l10n
63
+ l10n.instance_variable_set(:@uuid, id)
64
+ return l10n
65
+ end
66
+ end
67
+
68
+ l10n = LocalizedConcept.from_yaml(
69
+ File.read(localized_concept_path(id), encoding: "utf-8"),
70
+ )
71
+ l10n.instance_variable_set(:@uuid, id)
72
+ l10n
92
73
  rescue Psych::SyntaxError => e
93
74
  raise Glossarist::ParseError.new(filename: filename, line: e.line)
94
75
  end
@@ -107,7 +88,8 @@ module Glossarist
107
88
 
108
89
  concept.localized_concepts.each do |lang, uuid|
109
90
  filename = File.join(localized_concept_dir, "#{uuid}.yaml")
110
- File.write(filename, concept.localization(lang).to_yaml, encoding: "utf-8")
91
+ File.write(filename, concept.localization(lang).to_yaml,
92
+ encoding: "utf-8")
111
93
  end
112
94
  end
113
95
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ class ConceptReference < Lutaml::Model::Serializable
5
+ attribute :term, :string
6
+ attribute :concept_id, :string
7
+ attribute :source, :string
8
+ attribute :ref_type, :string
9
+
10
+ key_value do
11
+ map :term, to: :term
12
+ map :concept_id, to: :concept_id
13
+ map :source, to: :source
14
+ map :ref_type, to: :ref_type
15
+ end
16
+
17
+ def local?
18
+ %w[local designation].include?(ref_type) ||
19
+ (ref_type.nil? && (source.nil? || source.empty?))
20
+ end
21
+
22
+ def external?
23
+ !local?
24
+ end
25
+
26
+ def to_urn
27
+ return nil unless external?
28
+ return nil unless source && concept_id
29
+
30
+ case source
31
+ when /\Aurn:iec/ then "#{source}-#{concept_id}"
32
+ when /\Aurn:iso/ then "#{source}:term:#{concept_id}"
33
+ else "#{source}/#{concept_id}"
34
+ end
35
+ end
36
+
37
+ def to_gcr_hash
38
+ h = { "term" => term }
39
+ h["concept_id"] = concept_id if concept_id
40
+ h["source"] = source if source
41
+ h["ref_type"] = ref_type if ref_type
42
+ h.compact
43
+ end
44
+ end
45
+ end
@@ -7,7 +7,7 @@ module Glossarist
7
7
  attribute :origin, Citation
8
8
  attribute :modification, :string
9
9
 
10
- yaml do
10
+ key_value do
11
11
  # TODO: change to `map [:ref, :origin], to: :origin
12
12
  # when multiple key mapping is supported in lutaml-model
13
13
  map :origin, to: :origin