glossarist 2.13.5 → 2.13.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/config.yml +15 -0
- data/lib/glossarist/cli/export_command.rb +5 -5
- data/lib/glossarist/concept_ref.rb +49 -0
- data/lib/glossarist/gcr_package.rb +2 -2
- data/lib/glossarist/gcr_validator.rb +19 -1
- data/lib/glossarist/glossary_definition.rb +16 -3
- data/lib/glossarist/glossary_store.rb +70 -0
- data/lib/glossarist/managed_concept.rb +2 -2
- data/lib/glossarist/rdf/deterministic_slug.rb +33 -0
- data/lib/glossarist/rdf/gloss_citation.rb +1 -1
- data/lib/glossarist/rdf/gloss_concept.rb +3 -0
- data/lib/glossarist/rdf/gloss_concept_source.rb +1 -1
- data/lib/glossarist/rdf/gloss_detailed_definition.rb +1 -2
- data/lib/glossarist/rdf/gloss_generic_member.rb +34 -0
- data/lib/glossarist/rdf/gloss_generic_relation.rb +44 -0
- data/lib/glossarist/rdf/gloss_nary_member.rb +43 -0
- data/lib/glossarist/rdf/gloss_nary_relation.rb +56 -0
- data/lib/glossarist/rdf/gloss_partitive_member.rb +5 -28
- data/lib/glossarist/rdf/gloss_partitive_relation.rb +9 -23
- data/lib/glossarist/rdf/gloss_reference.rb +1 -2
- data/lib/glossarist/rdf.rb +5 -0
- data/lib/glossarist/tasks/shacl.rake +4 -1
- data/lib/glossarist/tasks/sync.rake +4 -1
- data/lib/glossarist/tasks.rb +9 -0
- data/lib/glossarist/transforms/concept_to_gloss_transform.rb +72 -52
- data/lib/glossarist/v3/abstract_hyperedge.rb +187 -0
- data/lib/glossarist/v3/definition_type.rb +30 -0
- data/lib/glossarist/v3/detailed_definition.rb +4 -0
- data/lib/glossarist/v3/generic_hyperedge.rb +34 -0
- data/lib/glossarist/v3/generic_member.rb +17 -0
- data/lib/glossarist/v3/hyperedge_index.rb +89 -0
- data/lib/glossarist/v3/hyperedge_member.rb +97 -0
- data/lib/glossarist/v3/hyperedge_registry.rb +70 -0
- data/lib/glossarist/v3/hyperedge_writer.rb +87 -0
- data/lib/glossarist/v3/managed_concept.rb +53 -2
- data/lib/glossarist/v3/managed_concept_data.rb +0 -1
- data/lib/glossarist/v3/partitive_hyperedge.rb +53 -0
- data/lib/glossarist/v3/partitive_member.rb +8 -105
- data/lib/glossarist/v3/relation_loader.rb +98 -0
- data/lib/glossarist/v3.rb +25 -8
- data/lib/glossarist/validation/rules/concept_context.rb +10 -2
- data/lib/glossarist/validation/rules/{partitive_relation_rule.rb → hyperedge_coherence_rule.rb} +22 -16
- data/lib/glossarist/version.rb +1 -1
- data/lib/glossarist.rb +1 -0
- metadata +19 -4
- data/lib/glossarist/v3/partitive_relation.rb +0 -114
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module Glossarist
|
|
7
|
+
module V3
|
|
8
|
+
# RelationLoader — scans a directory for per-file hyperedge files
|
|
9
|
+
# and returns typed instances.
|
|
10
|
+
#
|
|
11
|
+
# Files live at `relations/<comprehensive-id>/<criterion-slug>.yaml`.
|
|
12
|
+
# The `type` field on each file discriminates which concrete
|
|
13
|
+
# hyperedge class to instantiate. Dispatch is via HyperedgeRegistry
|
|
14
|
+
# — adding a new hyperedge type requires no edit here.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# relations = RelationLoader.load_all("path/to/dataset/relations")
|
|
18
|
+
# partitive = RelationLoader.load_for_concept("path/to/dataset", "5-1")
|
|
19
|
+
class RelationLoader
|
|
20
|
+
class LoadError < ::StandardError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# Load every relation file under `dir`. Returns a hash keyed
|
|
25
|
+
# by comprehensive id, value = array of typed hyperedges.
|
|
26
|
+
def load_all(dir)
|
|
27
|
+
new(dir).load_all
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Load all relation files for a single comprehensive id.
|
|
31
|
+
# `dataset_root` is the path containing the `relations/` directory.
|
|
32
|
+
def load_for_concept(dataset_root, comprehensive_id)
|
|
33
|
+
new(File.join(dataset_root, "relations")).load_for_comprehensive(comprehensive_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Load a single relation file. Returns a typed hyperedge
|
|
37
|
+
# instance (PartitiveHyperedge, GenericHyperedge, etc.).
|
|
38
|
+
def load_file(path)
|
|
39
|
+
new(File.dirname(path, 2)).load_path(path)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(relations_dir)
|
|
44
|
+
@relations_dir = Pathname.new(relations_dir)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def load_all
|
|
48
|
+
each_relation_path.with_object({}) do |path, h|
|
|
49
|
+
rel = load_path(path)
|
|
50
|
+
comp_id = Glossarist::ConceptRef.qualified_id(rel.comprehensive)
|
|
51
|
+
(h[comp_id] ||= []) << rel
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_for_comprehensive(comprehensive_id)
|
|
56
|
+
dir = @relations_dir.join(comprehensive_id.to_s)
|
|
57
|
+
return [] unless dir.exist?
|
|
58
|
+
|
|
59
|
+
Dir.glob("#{dir}/*.yaml").map { |p| load_path(Pathname.new(p)) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def load_path(path)
|
|
63
|
+
path = Pathname.new(path)
|
|
64
|
+
doc = YAML.load_file(path)
|
|
65
|
+
unless doc.is_a?(Hash) && doc["type"]
|
|
66
|
+
raise LoadError, "#{path} missing required `type` field"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
klass = HyperedgeRegistry.for_type_tag(doc["type"])
|
|
70
|
+
unless klass
|
|
71
|
+
known = HyperedgeRegistry.all_classes.map { |c| c::TYPE_TAG }.join(", ")
|
|
72
|
+
raise LoadError, "#{path} has unknown type #{doc['type'].inspect}; " \
|
|
73
|
+
"expected one of #{known}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Preserve the source $id so round-trip writes go to the same
|
|
77
|
+
# file path. Without this, write-back would derive a new path
|
|
78
|
+
# from comprehensive + criterion and could fragment files.
|
|
79
|
+
file_id = doc["$id"]
|
|
80
|
+
instance = klass.from_hash(doc)
|
|
81
|
+
instance.file_id = file_id if file_id && instance.respond_to?(:file_id=)
|
|
82
|
+
instance
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def each_relation_path
|
|
88
|
+
return enum_for(:each_relation_path) unless block_given?
|
|
89
|
+
|
|
90
|
+
return unless @relations_dir.exist?
|
|
91
|
+
|
|
92
|
+
Dir.glob("#{@relations_dir}/**/*.yaml").each do |p|
|
|
93
|
+
yield Pathname.new(p)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
data/lib/glossarist/v3.rb
CHANGED
|
@@ -9,14 +9,17 @@ module Glossarist
|
|
|
9
9
|
autoload :DetailedDefinition, "glossarist/v3/detailed_definition"
|
|
10
10
|
autoload :ConceptRef, "glossarist/v3/concept_ref"
|
|
11
11
|
autoload :RelatedConcept, "glossarist/v3/related_concept"
|
|
12
|
-
autoload :
|
|
12
|
+
autoload :HyperedgeMember, "glossarist/v3/hyperedge_member"
|
|
13
|
+
autoload :AbstractHyperedge, "glossarist/v3/abstract_hyperedge"
|
|
14
|
+
autoload :HyperedgeRegistry, "glossarist/v3/hyperedge_registry"
|
|
15
|
+
autoload :HyperedgeIndex, "glossarist/v3/hyperedge_index"
|
|
16
|
+
autoload :PartitiveHyperedge, "glossarist/v3/partitive_hyperedge"
|
|
13
17
|
autoload :PartitiveMember, "glossarist/v3/partitive_member"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# carries the orthogonal presence + count dimensions.
|
|
18
|
+
autoload :GenericHyperedge, "glossarist/v3/generic_hyperedge"
|
|
19
|
+
autoload :GenericMember, "glossarist/v3/generic_member"
|
|
20
|
+
autoload :DefinitionType, "glossarist/v3/definition_type"
|
|
21
|
+
autoload :RelationLoader, "glossarist/v3/relation_loader"
|
|
22
|
+
autoload :HyperedgeWriter, "glossarist/v3/hyperedge_writer"
|
|
20
23
|
autoload :Multiplicity, "glossarist/v3/multiplicity"
|
|
21
24
|
autoload :ConceptData, "glossarist/v3/concept_data"
|
|
22
25
|
autoload :LocalizedConcept, "glossarist/v3/localized_concept"
|
|
@@ -32,10 +35,24 @@ module Glossarist
|
|
|
32
35
|
Configuration.register_model(LocalizedConcept, id: :localized_concept)
|
|
33
36
|
Configuration.register_model(ConceptRef, id: :concept_ref)
|
|
34
37
|
Configuration.register_model(RelatedConcept, id: :related_concept)
|
|
35
|
-
|
|
38
|
+
# HyperedgeMember and AbstractHyperedge are abstract base classes —
|
|
39
|
+
# they are NOT registered as Lutaml models because they must not be
|
|
40
|
+
# instantiable directly. Concrete leaves (PartitiveHyperedge,
|
|
41
|
+
# GenericHyperedge, PartitiveMember, GenericMember) are registered
|
|
42
|
+
# below and also auto-registered with HyperedgeRegistry.
|
|
43
|
+
Configuration.register_model(PartitiveHyperedge, id: :partitive_hyperedge)
|
|
36
44
|
Configuration.register_model(PartitiveMember, id: :partitive_member)
|
|
45
|
+
Configuration.register_model(GenericHyperedge, id: :generic_hyperedge)
|
|
46
|
+
Configuration.register_model(GenericMember, id: :generic_member)
|
|
37
47
|
Configuration.register_model(ManagedConceptData, id: :managed_concept_data)
|
|
38
48
|
Configuration.register_model(ManagedConcept, id: :managed_concept)
|
|
39
49
|
Configuration.register_model(ConceptDocument, id: :concept_document)
|
|
50
|
+
|
|
51
|
+
# Eager-load concrete hyperedge leaves so HyperedgeRegistry
|
|
52
|
+
# auto-populates via AbstractHyperedge.inherited. Without this,
|
|
53
|
+
# autoload defers leaf definition until first reference, leaving
|
|
54
|
+
# the registry empty at boot.
|
|
55
|
+
PartitiveHyperedge
|
|
56
|
+
GenericHyperedge
|
|
40
57
|
end
|
|
41
58
|
end
|
|
@@ -9,13 +9,21 @@ module Glossarist
|
|
|
9
9
|
# rules examining the same concept share one extraction pass (DRY,
|
|
10
10
|
# single source of truth). Rules ask the context for references rather
|
|
11
11
|
# than instantiating their own ReferenceExtractor.
|
|
12
|
+
#
|
|
13
|
+
# In V3, n-ary relations (PartitiveHyperedge, GenericHyperedge) are
|
|
14
|
+
# per-file (see Glossarist::V3::RelationLoader). Each concept's
|
|
15
|
+
# relations are looked up via the relations lookup passed here.
|
|
16
|
+
# Pass an explicit `relations:` list when constructing the context
|
|
17
|
+
# — the loader does not run on demand so the validator behaviour
|
|
18
|
+
# is fully deterministic given the load.
|
|
12
19
|
class ConceptContext
|
|
13
|
-
attr_reader :concept, :file_name, :collection_context
|
|
20
|
+
attr_reader :concept, :file_name, :collection_context, :relations
|
|
14
21
|
|
|
15
|
-
def initialize(concept, file_name:, collection_context:)
|
|
22
|
+
def initialize(concept, file_name:, collection_context:, relations: [])
|
|
16
23
|
@concept = concept
|
|
17
24
|
@file_name = file_name
|
|
18
25
|
@collection_context = collection_context
|
|
26
|
+
@relations = Array(relations)
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
def concept_id
|
data/lib/glossarist/validation/rules/{partitive_relation_rule.rb → hyperedge_coherence_rule.rb}
RENAMED
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
module Glossarist
|
|
4
4
|
module Validation
|
|
5
5
|
module Rules
|
|
6
|
-
# Validates semantic invariants of
|
|
7
|
-
# that the model constructor
|
|
6
|
+
# Validates semantic invariants of n-ary relation entries
|
|
7
|
+
# (PartitiveHyperedge, GenericHyperedge) that the model constructor
|
|
8
|
+
# does NOT enforce. The relations are passed in via the
|
|
9
|
+
# ConceptContext (per-file storage — see Glossarist::V3::RelationLoader).
|
|
8
10
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
+
# Checks:
|
|
12
|
+
# - error when a relation has fewer than 2 members
|
|
13
|
+
# (ISO 704: "two or more"; single binary should use a
|
|
11
14
|
# has_part edge instead)
|
|
12
15
|
# - error when two relations share the same comprehensive
|
|
13
|
-
# AND the same non-
|
|
16
|
+
# AND the same non-empty criterion (duplicate decomposition;
|
|
14
17
|
# ISO 12620 coordinate-concept coherence)
|
|
15
18
|
# - warning when a relation has no criterion (cannot
|
|
16
19
|
# distinguish from siblings sharing the comprehensive)
|
|
@@ -21,12 +24,16 @@ module Glossarist
|
|
|
21
24
|
# at least one designation
|
|
22
25
|
#
|
|
23
26
|
# The model constructor already rejects empty comprehensive,
|
|
24
|
-
# empty
|
|
27
|
+
# empty members list, self-loops, invalid enum values, and
|
|
25
28
|
# the optional + at_least_one combination.
|
|
26
|
-
class
|
|
29
|
+
class HyperedgeCoherenceRule < Base
|
|
27
30
|
DEFAULT_PRESENCE = "required"
|
|
28
31
|
DEFAULT_COUNT = "exactly_one"
|
|
29
32
|
|
|
33
|
+
# Stable identifier for downstream issue trackers / config.
|
|
34
|
+
# Originally assigned when the rule was partitive-only; kept
|
|
35
|
+
# after the rename to GenericHyperedge + n-ary generalization
|
|
36
|
+
# so existing suppression configs continue to work.
|
|
30
37
|
def code = "GLS-221"
|
|
31
38
|
def category = :schema
|
|
32
39
|
def severity = "error"
|
|
@@ -36,18 +43,17 @@ module Glossarist
|
|
|
36
43
|
concept = context.concept
|
|
37
44
|
return false unless concept.is_a?(V3::ManagedConcept)
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
context.relations&.any? || external?(concept)
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def check(context)
|
|
43
50
|
concept = context.concept
|
|
44
51
|
fname = context.file_name
|
|
52
|
+
relations = context.relations
|
|
45
53
|
issues = []
|
|
46
54
|
|
|
47
55
|
return issues unless concept.is_a?(V3::ManagedConcept)
|
|
48
56
|
|
|
49
|
-
relations = Array(concept.partitive_relations)
|
|
50
|
-
|
|
51
57
|
relations.each_with_index do |rel, idx|
|
|
52
58
|
check_cardinality(rel, idx, fname, issues)
|
|
53
59
|
check_criterion_present(rel, idx, fname, issues)
|
|
@@ -67,10 +73,10 @@ module Glossarist
|
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
def check_cardinality(rel, idx, fname, issues)
|
|
70
|
-
return if rel.
|
|
76
|
+
return if rel.members.length >= 2
|
|
71
77
|
|
|
72
78
|
issues << issue(
|
|
73
|
-
"
|
|
79
|
+
"relation #{idx + 1} has fewer than 2 members " \
|
|
74
80
|
"(ISO 704 requires two or more); a single binary has_part edge " \
|
|
75
81
|
"should be used instead",
|
|
76
82
|
location: fname,
|
|
@@ -81,7 +87,7 @@ module Glossarist
|
|
|
81
87
|
return if rel.criterion && !rel.criterion.empty?
|
|
82
88
|
|
|
83
89
|
issues << issue(
|
|
84
|
-
"
|
|
90
|
+
"relation #{idx + 1} has no criterion; cannot verify " \
|
|
85
91
|
"distinctness from sibling relations sharing the comprehensive " \
|
|
86
92
|
"(ISO 12620 coordinate-concept coherence)",
|
|
87
93
|
severity: "warning",
|
|
@@ -97,14 +103,14 @@ module Glossarist
|
|
|
97
103
|
# legal; the warning encourages an explicit, reviewed choice
|
|
98
104
|
# rather than an accidental non-default.
|
|
99
105
|
def check_member_dimensions(rel, idx, fname, issues)
|
|
100
|
-
rel.
|
|
106
|
+
rel.members.each_with_index do |member, mi|
|
|
101
107
|
non_default = []
|
|
102
108
|
non_default << "presence='#{member.presence}'" if member.presence != DEFAULT_PRESENCE
|
|
103
109
|
non_default << "count='#{member.count}'" if member.count != DEFAULT_COUNT
|
|
104
110
|
next if non_default.empty?
|
|
105
111
|
|
|
106
112
|
issues << issue(
|
|
107
|
-
"
|
|
113
|
+
"relation #{idx + 1}.members[#{mi}] uses " \
|
|
108
114
|
"non-default #{non_default.join(' ')} " \
|
|
109
115
|
"(defaults: presence=#{DEFAULT_PRESENCE}, count=#{DEFAULT_COUNT}) " \
|
|
110
116
|
"— confirm the dimensions are intentional",
|
|
@@ -129,7 +135,7 @@ module Glossarist
|
|
|
129
135
|
next if idxs.length == 1
|
|
130
136
|
|
|
131
137
|
issues << issue(
|
|
132
|
-
"duplicate
|
|
138
|
+
"duplicate relation for comprehensive " \
|
|
133
139
|
"#{key.first.inspect} with criterion #{key.last.inspect} " \
|
|
134
140
|
"(relations ##{idxs.map { |i| i + 1 }.join(', ')}); " \
|
|
135
141
|
"two relations sharing comprehensive AND criterion are the " \
|
data/lib/glossarist/version.rb
CHANGED
data/lib/glossarist.rb
CHANGED
|
@@ -85,6 +85,7 @@ module Glossarist
|
|
|
85
85
|
autoload :VERSION, "glossarist/version"
|
|
86
86
|
autoload :GlossaryDefinition, "glossarist/glossary_definition"
|
|
87
87
|
autoload :GlossaryStore, "glossarist/glossary_store"
|
|
88
|
+
autoload :Tasks, "glossarist/tasks"
|
|
88
89
|
|
|
89
90
|
LANG_CODES = %w[eng ara deu fra spa ita jpn kor pol por srp swe zho rus fin
|
|
90
91
|
dan nld msa nob nno].freeze
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glossarist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.13.
|
|
4
|
+
version: 2.13.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -296,6 +296,7 @@ files:
|
|
|
296
296
|
- lib/glossarist/non_verbal_reference.rb
|
|
297
297
|
- lib/glossarist/pronunciation.rb
|
|
298
298
|
- lib/glossarist/rdf.rb
|
|
299
|
+
- lib/glossarist/rdf/deterministic_slug.rb
|
|
299
300
|
- lib/glossarist/rdf/gloss_citation.rb
|
|
300
301
|
- lib/glossarist/rdf/gloss_concept.rb
|
|
301
302
|
- lib/glossarist/rdf/gloss_concept_date.rb
|
|
@@ -306,9 +307,13 @@ files:
|
|
|
306
307
|
- lib/glossarist/rdf/gloss_figure.rb
|
|
307
308
|
- lib/glossarist/rdf/gloss_figure_image.rb
|
|
308
309
|
- lib/glossarist/rdf/gloss_formula.rb
|
|
310
|
+
- lib/glossarist/rdf/gloss_generic_member.rb
|
|
311
|
+
- lib/glossarist/rdf/gloss_generic_relation.rb
|
|
309
312
|
- lib/glossarist/rdf/gloss_grammar_info.rb
|
|
310
313
|
- lib/glossarist/rdf/gloss_locality.rb
|
|
311
314
|
- lib/glossarist/rdf/gloss_localized_concept.rb
|
|
315
|
+
- lib/glossarist/rdf/gloss_nary_member.rb
|
|
316
|
+
- lib/glossarist/rdf/gloss_nary_relation.rb
|
|
312
317
|
- lib/glossarist/rdf/gloss_non_verbal_rep.rb
|
|
313
318
|
- lib/glossarist/rdf/gloss_partitive_member.rb
|
|
314
319
|
- lib/glossarist/rdf/gloss_partitive_relation.rb
|
|
@@ -358,6 +363,7 @@ files:
|
|
|
358
363
|
- lib/glossarist/sts/term_mapper.rb
|
|
359
364
|
- lib/glossarist/table.rb
|
|
360
365
|
- lib/glossarist/table_reference.rb
|
|
366
|
+
- lib/glossarist/tasks.rb
|
|
361
367
|
- lib/glossarist/tasks/shacl.rake
|
|
362
368
|
- lib/glossarist/tasks/sync.rake
|
|
363
369
|
- lib/glossarist/tasks/sync_model.rb
|
|
@@ -385,6 +391,7 @@ files:
|
|
|
385
391
|
- lib/glossarist/v2/managed_concept_data.rb
|
|
386
392
|
- lib/glossarist/v2/related_concept.rb
|
|
387
393
|
- lib/glossarist/v3.rb
|
|
394
|
+
- lib/glossarist/v3/abstract_hyperedge.rb
|
|
388
395
|
- lib/glossarist/v3/citation.rb
|
|
389
396
|
- lib/glossarist/v3/concept_data.rb
|
|
390
397
|
- lib/glossarist/v3/concept_date.rb
|
|
@@ -392,14 +399,22 @@ files:
|
|
|
392
399
|
- lib/glossarist/v3/concept_ref.rb
|
|
393
400
|
- lib/glossarist/v3/concept_source.rb
|
|
394
401
|
- lib/glossarist/v3/configuration.rb
|
|
402
|
+
- lib/glossarist/v3/definition_type.rb
|
|
395
403
|
- lib/glossarist/v3/detailed_definition.rb
|
|
404
|
+
- lib/glossarist/v3/generic_hyperedge.rb
|
|
405
|
+
- lib/glossarist/v3/generic_member.rb
|
|
406
|
+
- lib/glossarist/v3/hyperedge_index.rb
|
|
407
|
+
- lib/glossarist/v3/hyperedge_member.rb
|
|
408
|
+
- lib/glossarist/v3/hyperedge_registry.rb
|
|
409
|
+
- lib/glossarist/v3/hyperedge_writer.rb
|
|
396
410
|
- lib/glossarist/v3/localized_concept.rb
|
|
397
411
|
- lib/glossarist/v3/managed_concept.rb
|
|
398
412
|
- lib/glossarist/v3/managed_concept_data.rb
|
|
399
413
|
- lib/glossarist/v3/multiplicity.rb
|
|
414
|
+
- lib/glossarist/v3/partitive_hyperedge.rb
|
|
400
415
|
- lib/glossarist/v3/partitive_member.rb
|
|
401
|
-
- lib/glossarist/v3/partitive_relation.rb
|
|
402
416
|
- lib/glossarist/v3/related_concept.rb
|
|
417
|
+
- lib/glossarist/v3/relation_loader.rb
|
|
403
418
|
- lib/glossarist/validation.rb
|
|
404
419
|
- lib/glossarist/validation/asset_index.rb
|
|
405
420
|
- lib/glossarist/validation/bibliography_index.rb
|
|
@@ -429,6 +444,7 @@ files:
|
|
|
429
444
|
- lib/glossarist/validation/rules/entry_status_rule.rb
|
|
430
445
|
- lib/glossarist/validation/rules/filename_id_rule.rb
|
|
431
446
|
- lib/glossarist/validation/rules/gcr_context.rb
|
|
447
|
+
- lib/glossarist/validation/rules/hyperedge_coherence_rule.rb
|
|
432
448
|
- lib/glossarist/validation/rules/image_reference_rule.rb
|
|
433
449
|
- lib/glossarist/validation/rules/l10n_uuid_integrity_rule.rb
|
|
434
450
|
- lib/glossarist/validation/rules/language_code_format_rule.rb
|
|
@@ -441,7 +457,6 @@ files:
|
|
|
441
457
|
- lib/glossarist/validation/rules/orphaned_bibliography_rule.rb
|
|
442
458
|
- lib/glossarist/validation/rules/orphaned_images_rule.rb
|
|
443
459
|
- lib/glossarist/validation/rules/orphaned_l10n_files_rule.rb
|
|
444
|
-
- lib/glossarist/validation/rules/partitive_relation_rule.rb
|
|
445
460
|
- lib/glossarist/validation/rules/preferred_term_rule.rb
|
|
446
461
|
- lib/glossarist/validation/rules/ref_shape_rule.rb
|
|
447
462
|
- lib/glossarist/validation/rules/registry.rb
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Glossarist
|
|
4
|
-
module V3
|
|
5
|
-
# PartitiveRelation — an ISO 704 / ISO 1087-1 / ISO 12620
|
|
6
|
-
# partitive relation connecting a comprehensive concept
|
|
7
|
-
# (superordinate concept partitive) to two or more partitive
|
|
8
|
-
# concepts (subordinate concepts partitive) which fitted together
|
|
9
|
-
# constitute the comprehensive.
|
|
10
|
-
#
|
|
11
|
-
# Shown as a rake or bracket in source diagrams. All partitives
|
|
12
|
-
# within one relation are coordinate concepts: they share the
|
|
13
|
-
# comprehensive AND share the criterion of subdivision.
|
|
14
|
-
#
|
|
15
|
-
# Per-partitive metadata (ISO 704:2022, MECE decomposition):
|
|
16
|
-
# - presence (required, optional) — line style: solid vs dashed
|
|
17
|
-
# - count (exactly_one, at_least_one, multiple) — line count
|
|
18
|
-
# - is_delimiting — orthogonal flag; a delimiting part behaves
|
|
19
|
-
# like a delimiting characteristic (distinguishes the
|
|
20
|
-
# comprehensive from coordinate concepts)
|
|
21
|
-
#
|
|
22
|
-
# Replaces the prior PartitiveHyperedge class. The "hyperedge"
|
|
23
|
-
# framing was graph-theoretic; ISO calls this a *relation*.
|
|
24
|
-
class PartitiveRelation < Lutaml::Model::Serializable
|
|
25
|
-
DEFAULT_COMPLETENESS = "complete"
|
|
26
|
-
|
|
27
|
-
attribute :comprehensive, ConceptRef
|
|
28
|
-
attribute :partitives, PartitiveMember, collection: true
|
|
29
|
-
attribute :completeness, :string,
|
|
30
|
-
values: Glossarist::GlossaryDefinition::COMPLETENESS_VALUES,
|
|
31
|
-
default: -> { DEFAULT_COMPLETENESS }
|
|
32
|
-
attribute :criterion, :hash
|
|
33
|
-
|
|
34
|
-
key_value do
|
|
35
|
-
map :comprehensive, to: :comprehensive
|
|
36
|
-
map :partitives, to: :partitives
|
|
37
|
-
map :completeness, to: :completeness
|
|
38
|
-
map :criterion, to: :criterion
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def validate!
|
|
42
|
-
validate_comprehensive!
|
|
43
|
-
validate_partitives!
|
|
44
|
-
validate_self_loop!
|
|
45
|
-
validate_completeness!
|
|
46
|
-
self
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def complete?
|
|
50
|
-
completeness == "complete"
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def partial?
|
|
54
|
-
completeness == "partial"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# ISO 704: a partitive relation connects to two or more
|
|
58
|
-
# partitives. A single binary has_part edge is not a
|
|
59
|
-
# PartitiveRelation.
|
|
60
|
-
def coordinate?
|
|
61
|
-
partitives.length >= 2
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
private
|
|
65
|
-
|
|
66
|
-
def validate_comprehensive!
|
|
67
|
-
return if comprehensive.is_a?(ConceptRef) &&
|
|
68
|
-
(comprehensive.source || comprehensive.id || comprehensive.text)
|
|
69
|
-
|
|
70
|
-
raise ArgumentError,
|
|
71
|
-
"PartitiveRelation#comprehensive must be a non-empty " \
|
|
72
|
-
"ConceptRef (source, id, or text required)"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def validate_partitives!
|
|
76
|
-
if partitives.empty?
|
|
77
|
-
raise ArgumentError, "PartitiveRelation requires at least one partitive"
|
|
78
|
-
end
|
|
79
|
-
unless coordinate?
|
|
80
|
-
raise ArgumentError,
|
|
81
|
-
"PartitiveRelation requires ≥2 partitives (ISO 704); a single " \
|
|
82
|
-
"binary has_part edge should be used instead"
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
partitives.each(&:validate!)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def validate_self_loop!
|
|
89
|
-
return unless comprehensive.is_a?(ConceptRef)
|
|
90
|
-
|
|
91
|
-
comp_key = [comprehensive.source, comprehensive.id]
|
|
92
|
-
partitives.each do |member|
|
|
93
|
-
next unless member.ref.is_a?(ConceptRef)
|
|
94
|
-
next unless [member.ref.source, member.ref.id] == comp_key
|
|
95
|
-
|
|
96
|
-
raise ArgumentError,
|
|
97
|
-
"PartitiveRelation#partitives cannot include the comprehensive"
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def validate_completeness!
|
|
102
|
-
return if completeness.nil?
|
|
103
|
-
|
|
104
|
-
unless Glossarist::GlossaryDefinition::COMPLETENESS_VALUES
|
|
105
|
-
.include?(completeness)
|
|
106
|
-
raise ArgumentError,
|
|
107
|
-
"PartitiveRelation#completeness has invalid value " \
|
|
108
|
-
"#{completeness.inspect}; must be one of " \
|
|
109
|
-
"#{GlossaryDefinition::COMPLETENESS_VALUES.join(', ')}"
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|