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
|
@@ -18,9 +18,8 @@ module Glossarist
|
|
|
18
18
|
return [] unless bib_content
|
|
19
19
|
|
|
20
20
|
begin
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
rescue Psych::SyntaxError => e
|
|
21
|
+
V3::BibliographyFile.from_yaml(bib_content)
|
|
22
|
+
rescue StandardError => e
|
|
24
23
|
return [issue(
|
|
25
24
|
"bibliography.yaml is invalid YAML: #{e.message}",
|
|
26
25
|
code: code, severity: severity,
|
|
@@ -18,46 +18,24 @@ module Glossarist
|
|
|
18
18
|
fname = context.file_name
|
|
19
19
|
issues = []
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
concept.localizations.flat_map(&:all_sources).each_with_index do |source, idx|
|
|
22
22
|
origin = source.origin
|
|
23
23
|
next unless origin
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
ref = origin.ref
|
|
26
|
+
if ref.nil? || (ref.source.nil? && ref.id.nil?)
|
|
26
27
|
issues << issue(
|
|
27
|
-
"source #{idx + 1} has empty origin (no
|
|
28
|
+
"source #{idx + 1} has empty origin (no ref source or id)",
|
|
28
29
|
code: "GLS-304", severity: severity,
|
|
29
30
|
location: fname,
|
|
30
|
-
suggestion: "Add at minimum an origin.
|
|
31
|
+
suggestion: "Add at minimum an origin.ref with source or id",
|
|
31
32
|
)
|
|
32
33
|
end
|
|
33
|
-
|
|
34
|
-
next unless origin.structured? && origin.source.nil?
|
|
35
|
-
|
|
36
|
-
issues << issue(
|
|
37
|
-
"source #{idx + 1} is structured but missing source field",
|
|
38
|
-
code: "GLS-304", severity: severity,
|
|
39
|
-
location: fname,
|
|
40
|
-
suggestion: "Add origin.source to the citation",
|
|
41
|
-
)
|
|
42
34
|
end
|
|
43
35
|
|
|
44
36
|
issues
|
|
45
37
|
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
38
|
end
|
|
60
39
|
end
|
|
61
40
|
end
|
|
62
41
|
end
|
|
63
|
-
|
|
@@ -8,14 +8,19 @@ module Glossarist
|
|
|
8
8
|
|
|
9
9
|
def initialize(path)
|
|
10
10
|
@path = File.expand_path(path)
|
|
11
|
-
@
|
|
11
|
+
@accumulated_concepts = []
|
|
12
12
|
@bibliography_index = nil
|
|
13
13
|
@asset_index = nil
|
|
14
14
|
@declared_languages = nil
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def add_concept(concept)
|
|
18
|
+
@accumulated_concepts << concept
|
|
19
|
+
@concept_ids = nil
|
|
20
|
+
end
|
|
21
|
+
|
|
17
22
|
def concepts
|
|
18
|
-
@
|
|
23
|
+
@accumulated_concepts
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def concept_ids
|
|
@@ -77,7 +82,7 @@ module Glossarist
|
|
|
77
82
|
reg_path = File.join(@path, "register.yaml")
|
|
78
83
|
return nil unless File.exist?(reg_path)
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
RegisterData.from_file(reg_path)
|
|
81
86
|
end
|
|
82
87
|
|
|
83
88
|
def build_localization_index
|
|
@@ -48,7 +48,7 @@ module Glossarist
|
|
|
48
48
|
|
|
49
49
|
return if date_value.nil?
|
|
50
50
|
|
|
51
|
-
str = date_value.
|
|
51
|
+
str = date_value.is_a?(Date) || date_value.is_a?(Time) ? date_value.iso8601 : date_value.to_s
|
|
52
52
|
|
|
53
53
|
begin
|
|
54
54
|
DateTime.parse(str)
|
|
@@ -24,7 +24,6 @@ module Glossarist
|
|
|
24
24
|
lang = l10n.language_code || "unknown"
|
|
25
25
|
terms = l10n.data&.terms || []
|
|
26
26
|
terms.each_with_index do |term, idx|
|
|
27
|
-
next unless term.respond_to?(:normative_status)
|
|
28
27
|
next if term.normative_status.nil? || term.normative_status.to_s.strip.empty?
|
|
29
28
|
|
|
30
29
|
unless VALID_STATUSES.include?(term.normative_status.to_s)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
class DomainRefRule < Base
|
|
7
|
+
def code = "GLS-309"
|
|
8
|
+
def category = :quality
|
|
9
|
+
def severity = "warning"
|
|
10
|
+
def scope = :concept
|
|
11
|
+
|
|
12
|
+
def applicable?(context)
|
|
13
|
+
context.concept.data&.domains&.any?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check(context)
|
|
17
|
+
concept = context.concept
|
|
18
|
+
fname = context.file_name
|
|
19
|
+
issues = []
|
|
20
|
+
|
|
21
|
+
(concept.data.domains || []).each_with_index do |domain, idx|
|
|
22
|
+
has_ref = domain.concept_id || domain.urn
|
|
23
|
+
unless has_ref
|
|
24
|
+
issues << issue(
|
|
25
|
+
"domain #{idx + 1} has neither concept_id nor urn",
|
|
26
|
+
location: fname,
|
|
27
|
+
suggestion: "Provide at least concept_id or urn for the domain reference",
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
issues
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
# Validates that domain references point to concepts that exist in the
|
|
7
|
+
# dataset (for local refs with concept_id) or have a valid URN.
|
|
8
|
+
class DomainTargetRule < 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-111"
|
|
12
|
+
def category = :references
|
|
13
|
+
def severity = "warning"
|
|
14
|
+
def scope = :concept
|
|
15
|
+
|
|
16
|
+
def applicable?(context)
|
|
17
|
+
context.concept.data&.domains&.any?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check(context)
|
|
21
|
+
concept = context.concept
|
|
22
|
+
fname = context.file_name
|
|
23
|
+
issues = []
|
|
24
|
+
|
|
25
|
+
(concept.data.domains || []).each_with_index do |domain, idx|
|
|
26
|
+
if domain.concept_id && local_domain?(domain)
|
|
27
|
+
unless context.concept_ids.include?(domain.concept_id)
|
|
28
|
+
issues << issue(
|
|
29
|
+
"domain #{idx + 1} references '#{domain.concept_id}' not in dataset",
|
|
30
|
+
location: fname,
|
|
31
|
+
suggestion: "Add concept '#{domain.concept_id}' or fix the domain ref",
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
elsif domain.urn
|
|
35
|
+
if domain.urn.start_with?("urn:") && !URN_RE.match?(domain.urn)
|
|
36
|
+
issues << issue(
|
|
37
|
+
"domain #{idx + 1} has invalid URN '#{domain.urn}'",
|
|
38
|
+
location: fname,
|
|
39
|
+
suggestion: "Fix the URN format",
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
issues
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def local_domain?(domain)
|
|
51
|
+
domain.source.nil? || domain.source.strip.empty?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -10,21 +10,20 @@ module Glossarist
|
|
|
10
10
|
|
|
11
11
|
def initialize(zip_path)
|
|
12
12
|
@zip_path = zip_path
|
|
13
|
+
@accumulated_concepts = []
|
|
13
14
|
@metadata = nil
|
|
14
|
-
@concepts = nil
|
|
15
15
|
@bibliography_index = nil
|
|
16
16
|
@asset_index = nil
|
|
17
17
|
@zip_entries = nil
|
|
18
|
-
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_concept(concept)
|
|
21
|
+
@accumulated_concepts << concept
|
|
22
|
+
@concept_ids = nil
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
def concepts
|
|
22
|
-
@
|
|
23
|
-
pkg = GcrPackage.load(@zip_path)
|
|
24
|
-
pkg.concepts
|
|
25
|
-
rescue StandardError
|
|
26
|
-
[]
|
|
27
|
-
end
|
|
26
|
+
@accumulated_concepts
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def concept_ids
|
|
@@ -41,11 +40,11 @@ module Glossarist
|
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
def bibliography_index
|
|
44
|
-
@bibliography_index ||=
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
@bibliography_index ||= BibliographyIndex.build_from_yaml(
|
|
44
|
+
concepts,
|
|
45
|
+
bibliography_yaml: read_zip_file("bibliography.yaml"),
|
|
46
|
+
images_yaml: read_zip_file("images.yaml"),
|
|
47
|
+
)
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
def asset_index
|
|
@@ -19,15 +19,12 @@ module Glossarist
|
|
|
19
19
|
extractor = ReferenceExtractor.new
|
|
20
20
|
issues = []
|
|
21
21
|
|
|
22
|
-
# Text-embedded image references (image::path[])
|
|
23
22
|
concept.localizations.each do |l10n|
|
|
24
23
|
lang = l10n.language_code || "unknown"
|
|
25
|
-
texts = extract_texts(l10n)
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
l10n.text_content.each do |text|
|
|
28
26
|
next unless text
|
|
29
|
-
|
|
30
|
-
refs.each do |ref|
|
|
27
|
+
extractor.extract_from_text(text).each do |ref|
|
|
31
28
|
next unless ref.is_a?(AssetReference)
|
|
32
29
|
next if context.asset_index.resolve?(ref.path)
|
|
33
30
|
|
|
@@ -41,7 +38,6 @@ module Glossarist
|
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
40
|
|
|
44
|
-
# Model-level asset references (NonVerbRep, GraphicalSymbol)
|
|
45
41
|
asset_refs = extractor.extract_asset_refs_from_concept(concept)
|
|
46
42
|
asset_refs.each do |ref|
|
|
47
43
|
next if context.asset_index.resolve?(ref.path)
|
|
@@ -56,18 +52,7 @@ module Glossarist
|
|
|
56
52
|
|
|
57
53
|
issues
|
|
58
54
|
end
|
|
59
|
-
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
def extract_texts(l10n)
|
|
63
|
-
texts = []
|
|
64
|
-
(l10n.data&.definition || []).each { |d| texts << d.content if d.content }
|
|
65
|
-
(l10n.data&.notes || []).each { |n| texts << n.content if n.content }
|
|
66
|
-
(l10n.data&.examples || []).each { |e| texts << e.content if e.content }
|
|
67
|
-
texts
|
|
68
|
-
end
|
|
69
55
|
end
|
|
70
56
|
end
|
|
71
57
|
end
|
|
72
58
|
end
|
|
73
|
-
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
class LocalityCompletenessRule < Base
|
|
7
|
+
def code = "GLS-308"
|
|
8
|
+
def category = :quality
|
|
9
|
+
def severity = "warning"
|
|
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
|
+
all_origins(concept).each_with_index do |origin, idx|
|
|
22
|
+
next unless origin
|
|
23
|
+
next unless origin.locality
|
|
24
|
+
|
|
25
|
+
loc = origin.locality
|
|
26
|
+
if loc.type.nil? || loc.type.to_s.strip.empty?
|
|
27
|
+
issues << issue(
|
|
28
|
+
"source #{idx + 1} locality has no type",
|
|
29
|
+
location: fname,
|
|
30
|
+
suggestion: "Add locality type (e.g. 'clause')",
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if loc.reference_from.nil? || loc.reference_from.to_s.strip.empty?
|
|
35
|
+
issues << issue(
|
|
36
|
+
"source #{idx + 1} locality has no reference_from",
|
|
37
|
+
location: fname,
|
|
38
|
+
suggestion: "Add locality.reference_from (e.g. '3.1.3.10')",
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
issues
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def all_origins(concept)
|
|
49
|
+
origins = []
|
|
50
|
+
concept.localizations.each do |l10n|
|
|
51
|
+
(l10n.data&.sources || []).each { |s| origins << s.origin if s.origin }
|
|
52
|
+
end
|
|
53
|
+
origins
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
# Verifies that every entry in localized_concepts map points to a loaded
|
|
7
|
+
# localization, and that every loaded localization has a corresponding
|
|
8
|
+
# entry in the map.
|
|
9
|
+
class LocalizationConsistencyRule < Base
|
|
10
|
+
def code = "GLS-017"
|
|
11
|
+
def category = :integrity
|
|
12
|
+
def severity = "error"
|
|
13
|
+
def scope = :concept
|
|
14
|
+
|
|
15
|
+
def applicable?(context)
|
|
16
|
+
context.concept.localizations&.any? ||
|
|
17
|
+
context.concept.data&.localized_concepts&.any?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check(context)
|
|
21
|
+
concept = context.concept
|
|
22
|
+
fname = context.file_name
|
|
23
|
+
issues = []
|
|
24
|
+
|
|
25
|
+
lc_map = concept.data&.localized_concepts || {}
|
|
26
|
+
loaded_langs = concept.localizations&.map(&:language_code)&.compact || []
|
|
27
|
+
|
|
28
|
+
# Map has entry but no loaded localization
|
|
29
|
+
lc_map.each_key do |lang|
|
|
30
|
+
next if loaded_langs.include?(lang)
|
|
31
|
+
|
|
32
|
+
issues << issue(
|
|
33
|
+
"localized_concepts map has '#{lang}' but no localization loaded",
|
|
34
|
+
location: fname,
|
|
35
|
+
suggestion: "Add a localization for '#{lang}' or remove it from the map",
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Loaded localization not in map
|
|
40
|
+
loaded_langs.each do |lang|
|
|
41
|
+
next if lc_map.key?(lang)
|
|
42
|
+
|
|
43
|
+
issues << issue(
|
|
44
|
+
"localization '#{lang}' is loaded but not in localized_concepts map",
|
|
45
|
+
location: fname,
|
|
46
|
+
suggestion: "Add '#{lang}' to the localized_concepts map",
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# UUID mismatch between map and loaded localization
|
|
51
|
+
concept.localizations.each do |l10n|
|
|
52
|
+
lang = l10n.language_code
|
|
53
|
+
next unless lang
|
|
54
|
+
|
|
55
|
+
expected_uuid = lc_map[lang]
|
|
56
|
+
actual_uuid = l10n.uuid
|
|
57
|
+
next unless expected_uuid && actual_uuid
|
|
58
|
+
next if expected_uuid == actual_uuid
|
|
59
|
+
|
|
60
|
+
issues << issue(
|
|
61
|
+
"UUID mismatch for '#{lang}': map says '#{expected_uuid}', localization is '#{actual_uuid}'",
|
|
62
|
+
location: fname,
|
|
63
|
+
suggestion: "Ensure the UUID in the map matches the localization file",
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
issues
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glossarist
|
|
4
|
+
module Validation
|
|
5
|
+
module Rules
|
|
6
|
+
class ModelValidityRule < Base
|
|
7
|
+
def code = "GLS-050"
|
|
8
|
+
def category = :structure
|
|
9
|
+
def severity = "error"
|
|
10
|
+
def scope = :concept
|
|
11
|
+
|
|
12
|
+
def applicable?(context)
|
|
13
|
+
context.concept.is_a?(Lutaml::Model::Serializable)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check(context)
|
|
17
|
+
validate_recursive(context.concept, context.file_name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def validate_recursive(model, location, path = "")
|
|
23
|
+
return [] unless model.is_a?(Lutaml::Model::Serializable)
|
|
24
|
+
|
|
25
|
+
issues = collect_model_errors(model, location, path)
|
|
26
|
+
issues.concat(recurse_attributes(model, location, path))
|
|
27
|
+
issues
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def collect_model_errors(model, location, path)
|
|
31
|
+
errors = model.validate
|
|
32
|
+
return [] if errors.empty?
|
|
33
|
+
|
|
34
|
+
prefix = path.empty? ? "" : "#{path}: "
|
|
35
|
+
errors.map { |e| issue("#{prefix}#{e}", location: location) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def recurse_attributes(model, location, path)
|
|
39
|
+
issues = []
|
|
40
|
+
|
|
41
|
+
model.class.attributes.each do |name, _|
|
|
42
|
+
value = model.public_send(name)
|
|
43
|
+
next if value.nil?
|
|
44
|
+
|
|
45
|
+
child_path = build_path(path, name)
|
|
46
|
+
issues.concat(validate_collection(value, location, child_path))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
issues
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def validate_collection(value, location, path)
|
|
53
|
+
case value
|
|
54
|
+
when Array
|
|
55
|
+
value.each_with_index.flat_map do |item, idx|
|
|
56
|
+
validate_recursive(item, location, "#{path}[#{idx}]")
|
|
57
|
+
end
|
|
58
|
+
when Lutaml::Model::Serializable
|
|
59
|
+
validate_recursive(value, location, path)
|
|
60
|
+
else
|
|
61
|
+
[]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def build_path(parent, name)
|
|
66
|
+
parent.empty? ? name.to_s : "#{parent}.#{name}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -20,8 +20,7 @@ module Glossarist
|
|
|
20
20
|
|
|
21
21
|
context.concepts.each do |concept|
|
|
22
22
|
concept.localizations.each do |l10n|
|
|
23
|
-
|
|
24
|
-
texts.each do |text|
|
|
23
|
+
l10n.text_content.each do |text|
|
|
25
24
|
next unless text
|
|
26
25
|
extractor.extract_from_text(text).each do |ref|
|
|
27
26
|
if ref.is_a?(BibliographicReference)
|
|
@@ -47,18 +46,7 @@ module Glossarist
|
|
|
47
46
|
|
|
48
47
|
issues
|
|
49
48
|
end
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
def extract_texts(l10n)
|
|
54
|
-
texts = []
|
|
55
|
-
(l10n.data&.definition || []).each { |d| texts << d.content if d.content }
|
|
56
|
-
(l10n.data&.notes || []).each { |n| texts << n.content if n.content }
|
|
57
|
-
(l10n.data&.examples || []).each { |e| texts << e.content if e.content }
|
|
58
|
-
texts
|
|
59
|
-
end
|
|
60
49
|
end
|
|
61
50
|
end
|
|
62
51
|
end
|
|
63
52
|
end
|
|
64
|
-
|
|
@@ -18,10 +18,8 @@ module Glossarist
|
|
|
18
18
|
referenced_paths = Set.new
|
|
19
19
|
|
|
20
20
|
context.concepts.each do |concept|
|
|
21
|
-
# Text-embedded image refs
|
|
22
21
|
concept.localizations.each do |l10n|
|
|
23
|
-
|
|
24
|
-
texts.each do |text|
|
|
22
|
+
l10n.text_content.each do |text|
|
|
25
23
|
next unless text
|
|
26
24
|
extractor.extract_from_text(text).each do |ref|
|
|
27
25
|
if ref.is_a?(AssetReference)
|
|
@@ -31,12 +29,20 @@ module Glossarist
|
|
|
31
29
|
end
|
|
32
30
|
end
|
|
33
31
|
|
|
34
|
-
# Model-level asset refs
|
|
35
32
|
extractor.extract_asset_refs_from_concept(concept).each do |ref|
|
|
36
33
|
referenced_paths.add(ref.path)
|
|
37
34
|
end
|
|
38
35
|
end
|
|
39
36
|
|
|
37
|
+
images_file = load_images_file(context)
|
|
38
|
+
if images_file
|
|
39
|
+
context.bibliography_index.entries.each_value do |entry|
|
|
40
|
+
next unless entry[:source].is_a?(V3::ImageEntry)
|
|
41
|
+
path = entry[:source].path
|
|
42
|
+
referenced_paths.add(path) if path
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
40
46
|
issues = []
|
|
41
47
|
context.asset_index.each_path do |path|
|
|
42
48
|
next if referenced_paths.include?(path)
|
|
@@ -54,15 +60,14 @@ module Glossarist
|
|
|
54
60
|
|
|
55
61
|
private
|
|
56
62
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
def load_images_file(context)
|
|
64
|
+
return @images_file if defined?(@images_file)
|
|
65
|
+
|
|
66
|
+
@images_file = V3::ImageFile.from_file(
|
|
67
|
+
File.join(context.path, "images.yaml")
|
|
68
|
+
)
|
|
63
69
|
end
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
|
-
|