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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
class RefShapeRule < Base
|
|
7
|
+
def code = "GLS-305"
|
|
8
|
+
def category = :schema
|
|
9
|
+
def severity = "error"
|
|
10
|
+
def scope = :concept
|
|
11
|
+
|
|
12
|
+
def applicable?(context)
|
|
13
|
+
context.concept.localizations&.any?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check(context)
|
|
17
|
+
concept = context.concept
|
|
18
|
+
fname = context.file_name
|
|
19
|
+
issues = []
|
|
20
|
+
|
|
21
|
+
check_sources(concept, fname, issues)
|
|
22
|
+
check_related(concept, fname, issues)
|
|
23
|
+
|
|
24
|
+
issues
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def check_sources(concept, fname, issues)
|
|
30
|
+
concept.localizations.flat_map(&:all_sources).each_with_index do |source, idx|
|
|
31
|
+
origin = source.origin
|
|
32
|
+
next unless origin
|
|
33
|
+
|
|
34
|
+
ref = origin.ref
|
|
35
|
+
if ref.nil?
|
|
36
|
+
issues << issue(
|
|
37
|
+
"source #{idx + 1} origin has nil ref (expected Citation::Ref hash)",
|
|
38
|
+
location: fname,
|
|
39
|
+
suggestion: "Set origin.ref to { source: ..., id: ... }",
|
|
40
|
+
)
|
|
41
|
+
elsif ref.source.nil? && ref.id.nil?
|
|
42
|
+
issues << issue(
|
|
43
|
+
"source #{idx + 1} origin.ref has neither source nor id",
|
|
44
|
+
location: fname,
|
|
45
|
+
suggestion: "Provide at least origin.ref.source or origin.ref.id",
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def check_related(concept, fname, issues)
|
|
52
|
+
(concept.related || []).each_with_index do |rel, idx|
|
|
53
|
+
ref = rel.ref
|
|
54
|
+
next unless ref
|
|
55
|
+
|
|
56
|
+
if ref.source.nil? && ref.id.nil?
|
|
57
|
+
issues << issue(
|
|
58
|
+
"related concept #{idx + 1} has empty ref (no source or id)",
|
|
59
|
+
location: fname,
|
|
60
|
+
suggestion: "Provide at least ref.source or ref.id",
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
# Verifies that related concept refs point to concepts that exist
|
|
7
|
+
# in the dataset (for local refs) or have valid source/URN (for external).
|
|
8
|
+
class RelatedConceptTargetRule < Base
|
|
9
|
+
URN_RE = %r{\Aurn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,\-.:=@;$_!*'%/?#]+\z}i.freeze
|
|
10
|
+
|
|
11
|
+
def code = "GLS-110"
|
|
12
|
+
def category = :references
|
|
13
|
+
def severity = "warning"
|
|
14
|
+
def scope = :concept
|
|
15
|
+
|
|
16
|
+
def applicable?(context)
|
|
17
|
+
context.concept.related&.any?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check(context)
|
|
21
|
+
concept = context.concept
|
|
22
|
+
fname = context.file_name
|
|
23
|
+
issues = []
|
|
24
|
+
|
|
25
|
+
(concept.related || []).each_with_index do |rel, idx|
|
|
26
|
+
ref = rel.ref
|
|
27
|
+
next unless ref
|
|
28
|
+
|
|
29
|
+
id = ref.id
|
|
30
|
+
source = ref.source
|
|
31
|
+
|
|
32
|
+
if id && local_ref?(source)
|
|
33
|
+
# Local ref — concept_id must exist in dataset
|
|
34
|
+
unless context.concept_ids.include?(id)
|
|
35
|
+
issues << issue(
|
|
36
|
+
"related concept #{idx + 1} references '#{id}' which is not in the dataset",
|
|
37
|
+
location: fname,
|
|
38
|
+
suggestion: "Add concept '#{id}' to the dataset or fix the reference",
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
elsif source && !id
|
|
42
|
+
# Source-only ref — should be a valid URN or known format
|
|
43
|
+
if source.start_with?("urn:") && !URN_RE.match?(source)
|
|
44
|
+
issues << issue(
|
|
45
|
+
"related concept #{idx + 1} has invalid URN '#{source}'",
|
|
46
|
+
location: fname,
|
|
47
|
+
suggestion: "Fix the URN format (e.g. urn:iso:std:iso:ts:14812)",
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
issues
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def local_ref?(source)
|
|
59
|
+
source.nil? || source.strip.empty?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
class SchemaVersionRule < Base
|
|
7
|
+
def code = "GLS-010"
|
|
8
|
+
def category = :schema
|
|
9
|
+
def severity = "warning"
|
|
10
|
+
def scope = :concept
|
|
11
|
+
|
|
12
|
+
def applicable?(context)
|
|
13
|
+
context.concept.is_a?(ManagedConcept)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check(context)
|
|
17
|
+
concept = context.concept
|
|
18
|
+
fname = context.file_name
|
|
19
|
+
issues = []
|
|
20
|
+
|
|
21
|
+
version = concept.schema_version
|
|
22
|
+
if version.nil? || version.to_s.strip.empty?
|
|
23
|
+
issues << issue(
|
|
24
|
+
"concept has no schema_version",
|
|
25
|
+
location: fname,
|
|
26
|
+
suggestion: "Add schema_version: \"3\" to the concept",
|
|
27
|
+
)
|
|
28
|
+
elsif version.to_s != "3"
|
|
29
|
+
issues << issue(
|
|
30
|
+
"concept has schema_version '#{version}', expected '3'",
|
|
31
|
+
location: fname,
|
|
32
|
+
suggestion: "Run schema migration to upgrade to version 3",
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
issues
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -21,7 +21,7 @@ module Glossarist
|
|
|
21
21
|
fname = context.file_name
|
|
22
22
|
issues = []
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
concept.localizations.flat_map(&:all_sources).each_with_index do |source, idx|
|
|
25
25
|
unless VALID_TYPES.include?(source.type)
|
|
26
26
|
issues << issue(
|
|
27
27
|
"source #{idx + 1} has invalid type '#{source.type}'",
|
|
@@ -43,21 +43,7 @@ module Glossarist
|
|
|
43
43
|
|
|
44
44
|
issues
|
|
45
45
|
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def gather_all_sources(concept)
|
|
50
|
-
sources = []
|
|
51
|
-
concept.localizations.each do |l10n|
|
|
52
|
-
(l10n.data&.sources || []).each { |s| sources << s }
|
|
53
|
-
(l10n.data&.definition || []).each { |d| (d.sources || []).each { |s| sources << s } }
|
|
54
|
-
(l10n.data&.notes || []).each { |n| (n.sources || []).each { |s| sources << s } }
|
|
55
|
-
(l10n.data&.examples || []).each { |e| (e.sources || []).each { |s| sources << s } }
|
|
56
|
-
end
|
|
57
|
-
sources
|
|
58
|
-
end
|
|
59
46
|
end
|
|
60
47
|
end
|
|
61
48
|
end
|
|
62
49
|
end
|
|
63
|
-
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
# Validates that every URN-format source in citations and references
|
|
7
|
+
# follows a recognized scheme (iso, iec, itu, etc).
|
|
8
|
+
class SourceUrnFormatRule < Base
|
|
9
|
+
URN_RE = %r{\Aurn:([a-z0-9][a-z0-9-]{0,31}):(.+)\z}i.freeze
|
|
10
|
+
|
|
11
|
+
KNOWN_SCHEMES = %w[
|
|
12
|
+
iso iec itu iso:std:iso iso:std:iec
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
def code = "GLS-310"
|
|
16
|
+
def category = :quality
|
|
17
|
+
def severity = "warning"
|
|
18
|
+
def scope = :concept
|
|
19
|
+
|
|
20
|
+
def applicable?(context)
|
|
21
|
+
context.concept.localizations&.any?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check(context)
|
|
25
|
+
concept = context.concept
|
|
26
|
+
fname = context.file_name
|
|
27
|
+
issues = []
|
|
28
|
+
|
|
29
|
+
all_refs(concept).each_with_index do |ref_str, idx|
|
|
30
|
+
next unless ref_str && ref_str.start_with?("urn:")
|
|
31
|
+
|
|
32
|
+
match = URN_RE.match(ref_str)
|
|
33
|
+
unless match
|
|
34
|
+
issues << issue(
|
|
35
|
+
"source #{idx + 1} has malformed URN '#{ref_str}'",
|
|
36
|
+
location: fname,
|
|
37
|
+
suggestion: "Fix the URN to follow RFC 8141 format",
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
issues
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def all_refs(concept)
|
|
48
|
+
refs = []
|
|
49
|
+
concept.localizations.each do |l10n|
|
|
50
|
+
(l10n.data&.sources || []).each do |s|
|
|
51
|
+
refs << s.origin&.ref&.source if s.origin&.ref&.source&.start_with?("urn:")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
(concept.data&.domains || []).each do |d|
|
|
55
|
+
refs << d.urn if d.urn
|
|
56
|
+
end
|
|
57
|
+
(concept.related || []).each do |r|
|
|
58
|
+
refs << r.ref&.source if r.ref&.source&.start_with?("urn:")
|
|
59
|
+
end
|
|
60
|
+
refs.compact
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
class UuidFormatRule < Base
|
|
7
|
+
UUID_RE = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i.freeze
|
|
8
|
+
|
|
9
|
+
def code = "GLS-016"
|
|
10
|
+
def category = :integrity
|
|
11
|
+
def severity = "error"
|
|
12
|
+
def scope = :concept
|
|
13
|
+
|
|
14
|
+
def check(context)
|
|
15
|
+
concept = context.concept
|
|
16
|
+
fname = context.file_name
|
|
17
|
+
issues = []
|
|
18
|
+
|
|
19
|
+
uuid = concept.uuid
|
|
20
|
+
if uuid && !uuid.to_s.empty? && !UUID_RE.match?(uuid.to_s)
|
|
21
|
+
issues << issue(
|
|
22
|
+
"concept UUID '#{uuid}' is not valid UUID format",
|
|
23
|
+
location: fname,
|
|
24
|
+
suggestion: "Use a valid UUID (e.g. 0ce27901-02ce-531e-8ba5-fdb136139d1a)",
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
issues
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -40,46 +40,13 @@ require_relative "rules/date_type_rule"
|
|
|
40
40
|
require_relative "rules/language_code_format_rule"
|
|
41
41
|
require_relative "rules/designation_type_rule"
|
|
42
42
|
require_relative "rules/date_validity_rule"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
R.register(EntryStatusRule)
|
|
54
|
-
R.register(AsciidocXrefRule)
|
|
55
|
-
R.register(ImageReferenceRule)
|
|
56
|
-
R.register(ConceptMentionRule)
|
|
57
|
-
R.register(ConceptCountRule)
|
|
58
|
-
R.register(LanguageListRule)
|
|
59
|
-
R.register(LanguageCoverageRule)
|
|
60
|
-
R.register(FilenameIdRule)
|
|
61
|
-
R.register(L10nUuidIntegrityRule)
|
|
62
|
-
R.register(OrphanedL10nFilesRule)
|
|
63
|
-
R.register(OrphanedBibliographyRule)
|
|
64
|
-
R.register(OrphanedImagesRule)
|
|
65
|
-
R.register(DefinitionContentRule)
|
|
66
|
-
R.register(PreferredTermRule)
|
|
67
|
-
R.register(DuplicateTermRule)
|
|
68
|
-
R.register(CitationCompletenessRule)
|
|
69
|
-
R.register(AuthoritativeSourceRule)
|
|
70
|
-
R.register(RelatedConceptRule)
|
|
71
|
-
R.register(ConceptStatusRule)
|
|
72
|
-
R.register(SourceEnumRule)
|
|
73
|
-
R.register(TermsPresenceRule)
|
|
74
|
-
R.register(BibliographyYamlRule)
|
|
75
|
-
R.register(ConceptUriRule)
|
|
76
|
-
R.register(RelatedConceptSymmetryRule)
|
|
77
|
-
R.register(RelatedConceptCycleRule)
|
|
78
|
-
R.register(DesignationStatusRule)
|
|
79
|
-
R.register(DateTypeRule)
|
|
80
|
-
R.register(LanguageCodeFormatRule)
|
|
81
|
-
R.register(DesignationTypeRule)
|
|
82
|
-
R.register(DateValidityRule)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
43
|
+
require_relative "rules/schema_version_rule"
|
|
44
|
+
require_relative "rules/ref_shape_rule"
|
|
45
|
+
require_relative "rules/locality_completeness_rule"
|
|
46
|
+
require_relative "rules/domain_ref_rule"
|
|
47
|
+
require_relative "rules/uuid_format_rule"
|
|
48
|
+
require_relative "rules/localization_consistency_rule"
|
|
49
|
+
require_relative "rules/related_concept_target_rule"
|
|
50
|
+
require_relative "rules/domain_target_rule"
|
|
51
|
+
require_relative "rules/source_urn_format_rule"
|
|
52
|
+
require_relative "rules/model_validity_rule"
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Glossarist
|
|
4
4
|
module Validation
|
|
5
|
-
class ValidationIssue
|
|
6
|
-
|
|
5
|
+
class ValidationIssue < Lutaml::Model::Serializable
|
|
6
|
+
attribute :severity, :string
|
|
7
|
+
attribute :code, :string
|
|
8
|
+
attribute :message, :string
|
|
9
|
+
attribute :location, :string
|
|
10
|
+
attribute :suggestion, :string
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@suggestion = suggestion
|
|
12
|
+
key_value do
|
|
13
|
+
map :severity, to: :severity
|
|
14
|
+
map :code, to: :code
|
|
15
|
+
map :message, to: :message
|
|
16
|
+
map :location, to: :location
|
|
17
|
+
map :suggestion, to: :suggestion
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def error?
|
|
@@ -29,9 +32,9 @@ suggestion: nil)
|
|
|
29
32
|
def to_s
|
|
30
33
|
parts = ["[#{severity.upcase}]"]
|
|
31
34
|
parts << "[#{code}]" if code
|
|
32
|
-
parts <<
|
|
35
|
+
parts << "#{location}: " if location
|
|
33
36
|
parts << message
|
|
34
|
-
parts << "
|
|
37
|
+
parts << "(#{suggestion})" if suggestion
|
|
35
38
|
parts.join(" ")
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -1,55 +1,45 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Glossarist
|
|
4
|
-
class ValidationResult
|
|
5
|
-
|
|
4
|
+
class ValidationResult < Lutaml::Model::Serializable
|
|
5
|
+
attribute :issues, Validation::ValidationIssue, collection: true,
|
|
6
|
+
initialize_empty: true
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
errors.each { |e| add_error(e) }
|
|
10
|
-
warnings.each { |w| add_warning(w) }
|
|
11
|
-
issues.each { |i| add_issue(i) }
|
|
8
|
+
key_value do
|
|
9
|
+
map :issues, to: :issues
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
def valid?
|
|
15
|
-
|
|
13
|
+
issues.none?(&:error?)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
def errors
|
|
19
|
-
|
|
17
|
+
issues.select(&:error?).map(&:to_s)
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
def warnings
|
|
23
|
-
|
|
21
|
+
issues.select(&:warning?).map(&:to_s)
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
def add_error(message)
|
|
27
|
-
|
|
25
|
+
issues << Validation::ValidationIssue.new(
|
|
28
26
|
severity: "error", message: message,
|
|
29
27
|
)
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
def add_warning(message)
|
|
33
|
-
|
|
31
|
+
issues << Validation::ValidationIssue.new(
|
|
34
32
|
severity: "warning", message: message,
|
|
35
33
|
)
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
def add_issue(issue)
|
|
39
|
-
|
|
37
|
+
issues << issue
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
def merge(other)
|
|
43
|
-
other.issues.each { |i|
|
|
41
|
+
other.issues.each { |i| issues << i }
|
|
44
42
|
self
|
|
45
43
|
end
|
|
46
|
-
|
|
47
|
-
def to_h
|
|
48
|
-
{
|
|
49
|
-
"valid" => valid?,
|
|
50
|
-
"errors" => errors,
|
|
51
|
-
"warnings" => warnings,
|
|
52
|
-
}
|
|
53
|
-
end
|
|
54
44
|
end
|
|
55
45
|
end
|
data/lib/glossarist/version.rb
CHANGED
data/lib/glossarist.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Glossarist
|
|
|
18
18
|
autoload :Collections, "glossarist/collections"
|
|
19
19
|
autoload :Concept, "glossarist/concept"
|
|
20
20
|
autoload :ConceptData, "glossarist/concept_data"
|
|
21
|
+
autoload :ConceptRef, "glossarist/concept_ref"
|
|
21
22
|
autoload :ConceptReference, "glossarist/concept_reference"
|
|
22
23
|
autoload :ReferenceExtractor, "glossarist/reference_extractor"
|
|
23
24
|
autoload :ReferenceResolver, "glossarist/reference_resolver"
|
|
@@ -28,6 +29,9 @@ module Glossarist
|
|
|
28
29
|
autoload :ConceptSource, "glossarist/concept_source"
|
|
29
30
|
autoload :ConceptValidator, "glossarist/concept_validator"
|
|
30
31
|
autoload :ConceptCollector, "glossarist/concept_collector"
|
|
32
|
+
autoload :ConceptComparator, "glossarist/concept_comparator"
|
|
33
|
+
autoload :ComparisonResult, "glossarist/comparison_result"
|
|
34
|
+
autoload :ConceptDiff, "glossarist/concept_diff"
|
|
31
35
|
autoload :ConceptDocument, "glossarist/concept_document"
|
|
32
36
|
autoload :ConceptEnricher, "glossarist/concept_enricher"
|
|
33
37
|
autoload :Config, "glossarist/config"
|
|
@@ -64,6 +68,8 @@ module Glossarist
|
|
|
64
68
|
autoload :RegisterData, "glossarist/register_data"
|
|
65
69
|
autoload :ValidationResult, "glossarist/validation_result"
|
|
66
70
|
autoload :V1, "glossarist/v1"
|
|
71
|
+
autoload :V2, "glossarist/v2"
|
|
72
|
+
autoload :V3, "glossarist/v3"
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
require_relative "glossarist/version"
|
|
@@ -74,6 +80,9 @@ module Glossarist
|
|
|
74
80
|
LANG_CODES = %w[eng ara deu fra spa ita jpn kor pol por srp swe zho rus fin
|
|
75
81
|
dan nld msa nob nno].freeze
|
|
76
82
|
|
|
83
|
+
SCHEMA_VERSION = "3"
|
|
84
|
+
V3_SCHEMA_VERSION = "3"
|
|
85
|
+
|
|
77
86
|
def self.configure
|
|
78
87
|
config = Glossarist::Config.instance
|
|
79
88
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: V2/V3 Namespace Architecture
|
|
3
|
+
description: lutaml-model mapping inheritance, model register, V2/V3 namespace design
|
|
4
|
+
type: project
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## lutaml-model mapping inheritance
|
|
8
|
+
|
|
9
|
+
lutaml-model `key_value` mappings ARE inherited by subclasses — a subclass's `key_value` block merges on top of the parent's mappings, it does NOT replace them. A subclass cannot "unmap" a parent mapping by omitting it.
|
|
10
|
+
|
|
11
|
+
**Why:** Discovered during V2/V3 namespace implementation. V2::ManagedConcept originally tried to define its own `key_value` block without `related`/`schema_version`/`sources`, but the parent ManagedConcept's mappings for those fields were still active.
|
|
12
|
+
|
|
13
|
+
**How to apply:** For V2/V3 versioning, only V2::ManagedConceptData needs its own `key_value` (to add `related` mapping inside data). V2::ManagedConcept only overrides the `data` attribute to point to V2::ManagedConceptData — it does NOT need its own `key_value`. V2 is only for deserialization; serialization always uses v3 format via inherited base class mappings.
|
|
14
|
+
|
|
15
|
+
## Model register (lutaml-model GlobalContext)
|
|
16
|
+
|
|
17
|
+
Follows the plurimath/mml pattern. Each version has a Configuration module with a unique CONTEXT_ID that extends Glossarist::ContextConfiguration. Models are registered via `Configuration.register_model(ClassName, id: :symbol)`. Type resolution uses `Configuration.resolve_model(:symbol)` which delegates to `Lutaml::Model::GlobalContext.resolve_type`.
|
|
18
|
+
|
|
19
|
+
**Why:** Enables context-based type resolution instead of hardcoded case/when. Each version's registry is isolated — V2::Configuration resolves `:managed_concept` to V2::ManagedConcept, V3::Configuration resolves to V3::ManagedConcept.
|
|
20
|
+
|
|
21
|
+
**How to apply:** `ConceptDocument.for_version(version)` looks up the version's Configuration from a VERSION_CONFIGURATION hash and calls `resolve_model(:concept_document)`. Adding a new version requires only a new Configuration module and register_model calls.
|
|
22
|
+
|
|
23
|
+
## V2 → V3 model-driven migration
|
|
24
|
+
|
|
25
|
+
V2→V3 migration is fully model-driven: V2::ConceptDocument deserializes v2 YAML (data.related → model), then `SchemaMigration.migrate_concept` promotes `data.related` to `concept.related` and sets schema_version to "3". No hash-based transformation needed — `Steps::V2ToV3` was deleted.
|
|
26
|
+
|
|
27
|
+
## RDF / JSON-LD / Turtle: version-agnostic
|
|
28
|
+
|
|
29
|
+
RDF view classes (GlossConcept, GlossLocalizedConcept, etc.) are NOT versioned. They operate on the domain model (ManagedConcept), not on YAML serialization format. schema_version is a YAML metadata field with no SKOS/gloss ontology equivalent. ConceptToGlossTransform takes a domain-model ManagedConcept and produces view-model instances — it is format-agnostic.
|
|
30
|
+
|
|
31
|
+
**Why:** Whether a concept was loaded from v2 or v3 YAML, the RDF output is identical. The domain model normalizes away format differences.
|
|
32
|
+
|
|
33
|
+
**How to apply:** No RDF model changes needed for v2/v3. If a future v4 changes the domain model semantics (not just serialization), RDF view classes would need updating.
|
|
34
|
+
|
|
35
|
+
## README documentation (completed 2026-05-20)
|
|
36
|
+
|
|
37
|
+
The README.adoc now includes a comprehensive "Schema Versioning (v2 / v3)" section covering:
|
|
38
|
+
- V2 vs V3 format differences with YAML examples
|
|
39
|
+
- Namespace architecture diagram
|
|
40
|
+
- Model register (GlobalContext) usage
|
|
41
|
+
- Loading & migration flow diagram
|
|
42
|
+
- Usage examples for v2, v3, migration, and RDF export
|
|
43
|
+
- "Adding a new schema version" guide (Open/Closed Principle)
|