glossarist 2.12.1 → 2.13.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fafa61aa5261ca29216b423006cd0764470a9f8bf59733449ba3b501a63413b
4
- data.tar.gz: b7c5f6148a76feca5dcbe8321e1a49bed99772a415168ddb977786d214c50110
3
+ metadata.gz: 85ff1a35136e9b7ff920830b2bf9a2e4b477bffed405c2386e33739ed6560a45
4
+ data.tar.gz: f7ef8fb96a58eb21a71534fcc8afc1cf7f26053379cfb00e5ec98098620ca6a6
5
5
  SHA512:
6
- metadata.gz: 163a2b94ff599127f8997de745f05ebec13ab19095790bf91ca832e7ea2c79525c65ca93a4e1cbd45ba4fcd9c4f1d4db9cb564bf370497d1e22fd5aa3e28ec73
7
- data.tar.gz: 43be5caab946b3202614ec38db165f6ce72b56137ca1daa87fa0e9c12a853b3c878541c8b8a9b23589719d4239a566ddb621ddee2552e7c284ab435232c03cd1
6
+ metadata.gz: beeecf5f816d5ec10741375d357a1b3602e7d415ebedfc71eff52d414f1d623ed119c7995355231c147020f54b814dc8306d37acdc6eecacdf8ca72ec6ddd65f
7
+ data.tar.gz: 0a7de5bd20f0f7c58c1c08066983b5c490999570249b686f84f60bd87df9b5e436913db66e43b5d1003805bfa33a6613d67bbd8c49373d21a03815dde83a8e42
data/CLAUDE.md CHANGED
@@ -47,23 +47,35 @@ This consolidation closes the legacy trap where writing to
47
47
  `data.related` bypassed `ManagedConcept.detect_schema_version` (which
48
48
  keys off `concept.related`).
49
49
 
50
- ### PartitiveHyperedge (V3 only)
50
+ ### PartitiveRelation (V3 only)
51
51
 
52
- `PartitiveHyperedge` (`v3/partitive_hyperedge.rb`) — a one-to-many partitive decomposition. One comprehensive concept is related to one or more parts as a SINGLE relationship. Captures invariants that binary `RelatedConcept` edges cannot:
52
+ `PartitiveRelation` (`v3/partitive_relation.rb`) — an ISO 704 / ISO 1087-1 / ISO 12620 partitive relation. One comprehensive concept (superordinate concept partitive) is connected to two or more partitive concepts (subordinate concepts partitive) which fitted together constitute the comprehensive.
53
53
 
54
- - which comprehensive owns which parts (set membership)
55
- - diagram notation flags (`PluralityMarker`: `double`, `dashed`)
56
- - enumeration completeness (`PartitiveEnumeration`: `closed`, `open`)
54
+ Each PartitiveRelation carries:
57
55
 
58
- Wired into `V3::ManagedConcept#partitive_hyperedges`. NOT on `ManagedConceptData` (relationships live at the concept level for MECE consistency with `related`).
56
+ - `comprehensive` ConceptRef to the superordinate concept partitive (the whole)
57
+ - `partitives` — `PartitiveMember [2..*]` (ISO 704 "two or more"); each member wraps a ConceptRef plus optional `MemberCertainty` (`confirmed` default, `possible`)
58
+ - `completeness` — `complete` (default; the encoded partitives constitute the whole) or `partial` (others exist beyond those encoded). ISO 704 rake backline notation.
59
+ - `plurality` — optional `TypeSharedPlurality` block (ISO 704 close-set double line promoted to data: `is_shared`, `is_uncertain`, `shared_type`). Replaces the prior opaque `markers` field.
60
+ - `criterion` — optional localized string hash (`{ "eng" => "physical structure" }`). ISO 12620 coordinate-concept coherence: partitives within one relation share the comprehensive AND the criterion.
59
61
 
60
- Enum values are SSOT-loaded from `config.yml` via `GlossaryDefinition::PARTITIVE_ENUMERATION_VALUES` and `PLURALITY_MARKER_VALUES`. The `values:` option on each attribute documents the enum; the model's `initialize` override enforces them at construction (lutaml-model 0.8.17 does not enforce `values:` on assignment). Construction also rejects empty comprehensive, empty parts, self-loops, and duplicate marker values.
62
+ Wired into `V3::ManagedConcept#partitive_relations`. NOT on `ManagedConceptData` (relationships live at the concept level for MECE consistency with `related`).
61
63
 
62
- Semantic checks (defaulted-enumeration warning, defensive walk) live in `Validation::Rules::PartitiveHyperedgeRule` (auto-registered by `Validation::Rules`).
64
+ Enum values are SSOT-loaded from `config.yml` via `GlossaryDefinition::COMPLETENESS_VALUES` and `MEMBER_CERTAINTY_VALUES`. The `values:` option on each attribute documents the enum; the model's `validate!` method enforces structural invariants after construction (empty comprehensive, <2 partitives, self-loops, incoherent plurality, invalid enum values).
63
65
 
64
- RDF emission: `Rdf::GlossHyperedge` view class (per-hyperedge `gloss:PartitiveHyperedge` subject with `gloss:comprehensive`, `gloss:part+`, `gloss:enumeration`, `gloss:hasPluralityMarker*`, `gloss:hyperedgeContent?`), wired into `Transforms::ConceptToGlossTransform` and emitted as `gloss:hasHyperedge` link from `GlossConcept`.
66
+ Semantic checks (≥2 partitives cardinality, duplicate comprehensive+criterion detection, criterion-absent warning, ExternalConcept shape) live in `Validation::Rules::PartitiveRelationRule` (auto-registered by `Validation::Rules`, code GLS-221).
65
67
 
66
- Binary `broader_partitive`/`narrower_partitive` `RelatedConcept` edges and `PartitiveHyperedge` coexist no automatic consolidation. See `concept-model/TODO.hyperedge/00-design-overview.md`.
68
+ RDF emission: `Rdf::GlossPartitiveRelation` view class with companion `GlossPartitiveMember` and `GlossTypeSharedPlurality`. Wired into `Transforms::ConceptToGlossTransform` and emitted as `gloss:hasPartitiveRelation` link from `GlossConcept`.
69
+
70
+ Binary `broader_partitive`/`narrower_partitive`/`has_part`/`is_part_of` `RelatedConcept` edges and `PartitiveRelation` coexist — no automatic consolidation. Use a binary edge for pairwise relationships; use a PartitiveRelation when membership is jointly significant (≥2 partitives + completeness/plurality/criterion metadata).
71
+
72
+ #### ExternalConcept
73
+
74
+ `status: external` is a value of `ConceptStatus` (not a separate class). A concept with `status: external` is referenced from this dataset but defined elsewhere — ISO 704 "parenthetic term" semantic role. Required fields (definition, sources) are relaxed; at least one designation is still required. Resolves to another dataset's concept via `provided_by` when a defining dataset is loaded.
75
+
76
+ #### Relationship type extensions
77
+
78
+ Two new values in `RelatedConceptType`: `provides` (real concept → external concept) and `provided_by` (external concept → real concept). Inverse pair, established at the collection level.
67
79
 
68
80
  ### Reference Resolution
69
81
 
data/config.yml CHANGED
@@ -101,6 +101,12 @@ related_concept:
101
101
  - version_of
102
102
  - current_version
103
103
  - current_version_of
104
+ # ExternalConcept resolution (Glossarist extension)
105
+ # Real concept `provides` substance for an external concept;
106
+ # external concept `provided_by` real concept. Inverse pair.
107
+ # Established at the collection level when both datasets are loaded.
108
+ - provides
109
+ - provided_by
104
110
 
105
111
  iso12620:
106
112
  term_type:
@@ -178,6 +184,10 @@ concept:
178
184
  - valid
179
185
  - superseded
180
186
  - retired
187
+ # Glossarist extension: concept referenced from this dataset but
188
+ # defined elsewhere. Resolves via `provided_by` when a defining
189
+ # dataset is loaded. ISO 704 "parenthetic term" semantic role.
190
+ - external
181
191
 
182
192
  concept_date:
183
193
  type:
@@ -187,17 +197,23 @@ concept_date:
187
197
  - review
188
198
  - review_decision
189
199
 
190
- partitive_enumeration:
191
- # Whether a PartitiveHyperedge's parts array constitutes the complete
192
- # decomposition of the comprehensive (closed) or only a partial one
193
- # (open). Default: closed.
200
+ # PartitiveRelation redesign (TODO.partitive-relation-v2)
201
+ # Replaces partitive_enumeration + plurality_marker.
202
+
203
+ completeness:
204
+ # Whether the encoded partitives fitted together constitute the
205
+ # whole comprehensive concept (complete) or only some of it
206
+ # (partial — other partitives exist but are not encoded).
207
+ # ISO 704: rake backline ending with a tooth = complete;
208
+ # backline continuing without a tooth = partial.
209
+ # Default: complete.
194
210
  value:
195
- - closed
196
- - open
211
+ - complete
212
+ - partial
197
213
 
198
- plurality_marker:
199
- # Diagram notation flags from VIM concept-relationship diagrams.
200
- # Orthogonal to enumeration both may be set on the same hyperedge.
214
+ member_certainty:
215
+ # Per-partitive certainty. Glossarist extension beyond ISO 704
216
+ # notation. Defaults to confirmed when omitted.
201
217
  value:
202
- - double # close-set double line at receiver; several parts involved
203
- - dashed # broken/dashed line; plurality is uncertain
218
+ - confirmed
219
+ - possible
@@ -34,10 +34,10 @@ module Glossarist
34
34
 
35
35
  ISO12620_TERM_TYPES = config.dig("iso12620", "term_type").freeze
36
36
 
37
- PARTITIVE_ENUMERATION_VALUES =
38
- config.dig("partitive_enumeration", "value").freeze
37
+ # PartitiveRelation redesign (TODO.partitive-relation-v2).
38
+ # Replaces PARTITIVE_ENUMERATION_VALUES + PLURALITY_MARKER_VALUES.
39
+ COMPLETENESS_VALUES = config.dig("completeness", "value").freeze
39
40
 
40
- PLURALITY_MARKER_VALUES =
41
- config.dig("plurality_marker", "value").freeze
41
+ MEMBER_CERTAINTY_VALUES = config.dig("member_certainty", "value").freeze
42
42
  end
43
43
  end
@@ -175,7 +175,7 @@ module Glossarist
175
175
  return "3" if concept.sources&.any?
176
176
  return "3" if concept.data&.domains&.any?
177
177
  return "3" if concept.is_a?(V3::ManagedConcept) &&
178
- concept.partitive_hyperedges&.any?
178
+ concept.partitive_relations&.any?
179
179
  return "3" if localization_has_references?(concept)
180
180
 
181
181
  "2"
@@ -11,7 +11,7 @@ module Glossarist
11
11
  attribute :sources, GlossConceptSource, collection: true
12
12
  attribute :domains, GlossConceptReference, collection: true
13
13
  attribute :dates, GlossConceptDate, collection: true
14
- attribute :hyperedges, GlossHyperedge, collection: true
14
+ attribute :partitive_relations, GlossPartitiveRelation, collection: true
15
15
 
16
16
  RelationshipPredicates::CONCEPT_REL_PREDICATES.each_key do |type|
17
17
  attribute :"#{type}_targets", :string, collection: true
@@ -42,8 +42,8 @@ module Glossarist
42
42
  link: "gloss:hasDomain"
43
43
  members :dates,
44
44
  link: "gloss:hasDate"
45
- members :hyperedges,
46
- link: "gloss:hasPartitiveHyperedge"
45
+ members :partitive_relations,
46
+ link: "gloss:hasPartitiveRelation"
47
47
 
48
48
  RelationshipPredicates::CONCEPT_REL_PREDICATES.each do |type, (ns, name)|
49
49
  predicate name, namespace: ns, to: :"#{type}_targets",
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Glossarist
6
+ module Rdf
7
+ # RDF view for V3::PartitiveMember. Emits a
8
+ # gloss:PartitiveMember subject with ref + certainty properties.
9
+ class GlossPartitiveMember < Lutaml::Model::Serializable
10
+ attribute :ref_id, :string
11
+ attribute :ref_source, :string
12
+ attribute :ref_text, :string
13
+ attribute :certainty, :string
14
+
15
+ rdf do
16
+ namespace Namespaces::GlossaristNamespace
17
+
18
+ subject { |m| "partitiveMember/#{m.ref_source}:#{m.ref_id}" }
19
+
20
+ types "gloss:PartitiveMember"
21
+
22
+ predicate :refSource, namespace: Namespaces::GlossaristNamespace,
23
+ to: :ref_source
24
+ predicate :refId, namespace: Namespaces::GlossaristNamespace,
25
+ to: :ref_id
26
+ predicate :refText, namespace: Namespaces::GlossaristNamespace,
27
+ to: :ref_text
28
+ predicate :certainty, namespace: Namespaces::GlossaristNamespace,
29
+ to: :certainty
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Glossarist
6
+ module Rdf
7
+ # RDF view for V3::PartitiveRelation. Emits a
8
+ # gloss:PartitiveRelation subject with comprehensive, hasPartitive,
9
+ # completeness, hasPlurality, criterion properties.
10
+ class GlossPartitiveRelation < Lutaml::Model::Serializable
11
+ attribute :identifier, :string
12
+ attribute :comprehensive_uri, :string
13
+ attribute :partitive_member_ids, :string, collection: true
14
+ attribute :completeness, :string
15
+ attribute :has_plurality, GlossTypeSharedPlurality
16
+ attribute :criterion, :hash
17
+
18
+ rdf do
19
+ namespace Namespaces::GlossaristNamespace
20
+
21
+ subject { |r| "partitiveRelation/#{r.identifier}/#{r.object_id}" }
22
+
23
+ types "gloss:PartitiveRelation"
24
+
25
+ predicate :comprehensive, namespace: Namespaces::GlossaristNamespace,
26
+ to: :comprehensive_uri, uri_reference: true
27
+ predicate :hasPartitive, namespace: Namespaces::GlossaristNamespace,
28
+ to: :partitive_member_ids, uri_reference: true
29
+ predicate :completeness, namespace: Namespaces::GlossaristNamespace,
30
+ to: :completeness, uri_reference: true
31
+ members :has_plurality,
32
+ link: "gloss:hasPlurality"
33
+ predicate :criterion, namespace: Namespaces::GlossaristNamespace,
34
+ to: :criterion
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Glossarist
6
+ module Rdf
7
+ # RDF view for V3::TypeSharedPlurality. Emits a
8
+ # gloss:TypeSharedPlurality subject with is_shared, is_uncertain,
9
+ # shared_type properties.
10
+ class GlossTypeSharedPlurality < Lutaml::Model::Serializable
11
+ attribute :is_shared, :boolean
12
+ attribute :is_uncertain, :boolean
13
+ attribute :shared_type_uri, :string
14
+
15
+ rdf do
16
+ namespace Namespaces::GlossaristNamespace
17
+
18
+ subject { |p| "plurality/#{p.object_id}" }
19
+
20
+ types "gloss:TypeSharedPlurality"
21
+
22
+ predicate :isShared, namespace: Namespaces::GlossaristNamespace,
23
+ to: :is_shared
24
+ predicate :isUncertain, namespace: Namespaces::GlossaristNamespace,
25
+ to: :is_uncertain
26
+ predicate :sharedType, namespace: Namespaces::GlossaristNamespace,
27
+ to: :shared_type_uri, uri_reference: true
28
+ end
29
+ end
30
+ end
31
+ end
@@ -29,7 +29,9 @@ module Glossarist
29
29
  GlossFigureImage
30
30
  GlossTable
31
31
  GlossFormula
32
- GlossHyperedge
32
+ GlossPartitiveRelation
33
+ GlossPartitiveMember
34
+ GlossTypeSharedPlurality
33
35
  ].freeze
34
36
 
35
37
  VIEW_CLASS_NAMES.each do |name|
@@ -42,7 +42,10 @@ module Glossarist
42
42
  autoload :GlossFigureImage, "#{__dir__}/rdf/gloss_figure_image"
43
43
  autoload :GlossTable, "#{__dir__}/rdf/gloss_table"
44
44
  autoload :GlossFormula, "#{__dir__}/rdf/gloss_formula"
45
- autoload :GlossHyperedge, "#{__dir__}/rdf/gloss_hyperedge"
45
+ autoload :GlossPartitiveRelation, "#{__dir__}/rdf/gloss_partitive_relation"
46
+ autoload :GlossPartitiveMember, "#{__dir__}/rdf/gloss_partitive_member"
47
+ autoload :GlossTypeSharedPlurality,
48
+ "#{__dir__}/rdf/gloss_type_shared_plurality"
46
49
  autoload :V3, "#{__dir__}/rdf/v3"
47
50
  end
48
51
  end
@@ -130,42 +130,64 @@ module Glossarist
130
130
  domains: build_gloss_domains(managed_concept.data&.domains,
131
131
  identifier),
132
132
  dates: build_gloss_dates(managed_concept.dates, identifier),
133
- hyperedges: build_gloss_hyperedges(v3_hyperedges(managed_concept),
134
- identifier),
133
+ partitive_relations: build_gloss_partitive_relations(
134
+ v3_partitive_relations(managed_concept), identifier,
135
+ ),
135
136
  **rel_targets,
136
137
  )
137
138
  end
138
139
 
139
- # Hyperedges are a V3-only attribute. The transform accepts both
140
- # V1/V2 and V3 concepts; for V1/V2 concepts the hyperedge list
141
- # is always empty.
142
- def v3_hyperedges(managed_concept)
140
+ # PartitiveRelations are a V3-only attribute. The transform
141
+ # accepts both V1/V2 and V3 concepts; for V1/V2 concepts the
142
+ # relation list is always empty.
143
+ def v3_partitive_relations(managed_concept)
143
144
  return [] unless managed_concept.is_a?(V3::ManagedConcept)
144
145
 
145
- managed_concept.partitive_hyperedges
146
+ managed_concept.partitive_relations
146
147
  end
147
148
 
148
- def build_gloss_hyperedges(hyperedges, identifier)
149
- return [] unless hyperedges
149
+ def build_gloss_partitive_relations(relations, identifier)
150
+ return [] unless relations
150
151
 
151
- Array(hyperedges).map do |he|
152
- Rdf::GlossHyperedge.new(
152
+ Array(relations).map do |rel|
153
+ Rdf::GlossPartitiveRelation.new(
153
154
  identifier: identifier.to_s,
154
- comprehensive_id: he.comprehensive.id.to_s,
155
- comprehensive_uri: hyperedge_concept_uri(he.comprehensive),
156
- part_uris: Array(he.parts).map { |p| hyperedge_concept_uri(p) },
157
- enumeration: he.enumeration,
158
- markers: Array(he.markers),
159
- content: he.content,
155
+ comprehensive_uri: partitive_concept_uri(rel.comprehensive),
156
+ partitive_member_ids: Array(rel.partitives).map do |m|
157
+ partitive_member_uri(m)
158
+ end,
159
+ completeness: rel.completeness,
160
+ has_plurality: build_gloss_plurality(rel.plurality),
161
+ criterion: rel.criterion,
160
162
  )
161
163
  end
162
164
  end
163
165
 
164
- def hyperedge_concept_uri(ref)
166
+ def build_gloss_plurality(plurality)
167
+ return nil unless plurality
168
+
169
+ Rdf::GlossTypeSharedPlurality.new(
170
+ is_shared: plurality.is_shared,
171
+ is_uncertain: plurality.is_uncertain,
172
+ shared_type_uri: partitive_concept_uri(plurality.shared_type),
173
+ )
174
+ end
175
+
176
+ def partitive_concept_uri(ref)
177
+ return nil unless ref.is_a?(Glossarist::ConceptRef)
178
+
179
+ Glossarist::Rdf::Namespaces::GlossaristNamespace.uri +
180
+ "concept/#{ref.id || ref.text}"
181
+ end
182
+
183
+ def partitive_member_uri(member)
184
+ return nil unless member.is_a?(V3::PartitiveMember)
185
+
186
+ ref = member.ref
165
187
  return nil unless ref.is_a?(Glossarist::ConceptRef)
166
188
 
167
189
  Glossarist::Rdf::Namespaces::GlossaristNamespace.uri +
168
- "concept/#{ref.id}"
190
+ "concept/#{ref.id || ref.text}"
169
191
  end
170
192
 
171
193
  def build_gloss_localized_concept(l10n, concept_id)
@@ -5,7 +5,7 @@ module Glossarist
5
5
  class ManagedConcept < Glossarist::ManagedConcept
6
6
  attribute :data, V3::ManagedConceptData, default: -> { V3::ManagedConceptData.new }
7
7
  attribute :related, V3::RelatedConcept, collection: true
8
- attribute :partitive_hyperedges, V3::PartitiveHyperedge, collection: true
8
+ attribute :partitive_relations, V3::PartitiveRelation, collection: true
9
9
  attribute :dates, V3::ConceptDate, collection: true
10
10
  attribute :date_accepted, V3::ConceptDate
11
11
  attribute :sources, V3::ConceptSource, collection: true
@@ -13,7 +13,7 @@ module Glossarist
13
13
  key_value do
14
14
  map :data, to: :data
15
15
  map :related, to: :related
16
- map :partitive_hyperedges, to: :partitive_hyperedges
16
+ map :partitive_relations, to: :partitive_relations
17
17
  map :dates, to: :dates
18
18
  map %i[date_accepted dateAccepted],
19
19
  with: { from: :date_accepted_from_yaml, to: :date_accepted_to_yaml }
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module V3
5
+ # PartitiveMember — one member of a PartitiveRelation, carrying
6
+ # a ConceptRef to the partitive concept plus optional certainty
7
+ # metadata (Glossarist extension beyond ISO 704 notation).
8
+ #
9
+ # A PartitiveMember with no certainty field is implicitly
10
+ # confirmed.
11
+ class PartitiveMember < Lutaml::Model::Serializable
12
+ DEFAULT_CERTAINTY = "confirmed"
13
+
14
+ attribute :ref, ConceptRef
15
+ attribute :certainty, :string,
16
+ values: Glossarist::GlossaryDefinition::MEMBER_CERTAINTY_VALUES,
17
+ default: -> { DEFAULT_CERTAINTY }
18
+
19
+ key_value do
20
+ map :ref, to: :ref
21
+ map :certainty, to: :certainty
22
+ end
23
+
24
+ def validate!
25
+ validate_ref!
26
+ validate_certainty!
27
+ self
28
+ end
29
+
30
+ def confirmed?
31
+ certainty == DEFAULT_CERTAINTY
32
+ end
33
+
34
+ def possible?
35
+ certainty == "possible"
36
+ end
37
+
38
+ private
39
+
40
+ def validate_ref!
41
+ return if ref.is_a?(ConceptRef) && (ref.source || ref.id || ref.text)
42
+
43
+ raise ArgumentError,
44
+ "PartitiveMember#ref must be a non-empty ConceptRef " \
45
+ "(source, id, or text required)"
46
+ end
47
+
48
+ def validate_certainty!
49
+ return if certainty.nil?
50
+
51
+ unless Glossarist::GlossaryDefinition::MEMBER_CERTAINTY_VALUES
52
+ .include?(certainty)
53
+ raise ArgumentError,
54
+ "PartitiveMember#certainty has invalid value " \
55
+ "#{certainty.inspect}; must be one of " \
56
+ "#{GlossaryDefinition::MEMBER_CERTAINTY_VALUES.join(', ')}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,119 @@
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
+ # Glossarist extensions beyond ISO notation:
16
+ # - Per-partitive certainty (MemberCertainty)
17
+ # - plurality.shared_type (the type the partitives share)
18
+ # - criterion as a structured field (not just diagrammatic)
19
+ #
20
+ # Replaces the prior PartitiveHyperedge class. The "hyperedge"
21
+ # framing was graph-theoretic; ISO calls this a *relation*.
22
+ class PartitiveRelation < Lutaml::Model::Serializable
23
+ DEFAULT_COMPLETENESS = "complete"
24
+
25
+ attribute :comprehensive, ConceptRef
26
+ attribute :partitives, PartitiveMember, collection: true
27
+ attribute :completeness, :string,
28
+ values: Glossarist::GlossaryDefinition::COMPLETENESS_VALUES,
29
+ default: -> { DEFAULT_COMPLETENESS }
30
+ attribute :plurality, TypeSharedPlurality
31
+ attribute :criterion, :hash
32
+
33
+ key_value do
34
+ map :comprehensive, to: :comprehensive
35
+ map :partitives, to: :partitives
36
+ map :completeness, to: :completeness
37
+ map :plurality, to: :plurality
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
+ validate_plurality!
47
+ self
48
+ end
49
+
50
+ def complete?
51
+ completeness == "complete"
52
+ end
53
+
54
+ def partial?
55
+ completeness == "partial"
56
+ end
57
+
58
+ # ISO 704: a partitive relation connects to two or more
59
+ # partitives. A single binary has_part edge is not a
60
+ # PartitiveRelation.
61
+ def coordinate?
62
+ partitives.length >= 2
63
+ end
64
+
65
+ private
66
+
67
+ def validate_comprehensive!
68
+ return if comprehensive.is_a?(ConceptRef) &&
69
+ (comprehensive.source || comprehensive.id || comprehensive.text)
70
+
71
+ raise ArgumentError,
72
+ "PartitiveRelation#comprehensive must be a non-empty " \
73
+ "ConceptRef (source, id, or text required)"
74
+ end
75
+
76
+ def validate_partitives!
77
+ if partitives.empty?
78
+ raise ArgumentError, "PartitiveRelation requires at least one partitive"
79
+ end
80
+ unless coordinate?
81
+ raise ArgumentError,
82
+ "PartitiveRelation requires ≥2 partitives (ISO 704); a single " \
83
+ "binary has_part edge should be used instead"
84
+ end
85
+
86
+ partitives.each(&:validate!)
87
+ end
88
+
89
+ def validate_self_loop!
90
+ return unless comprehensive.is_a?(ConceptRef)
91
+
92
+ comp_key = [comprehensive.source, comprehensive.id]
93
+ partitives.each do |member|
94
+ next unless member.ref.is_a?(ConceptRef)
95
+ next unless [member.ref.source, member.ref.id] == comp_key
96
+
97
+ raise ArgumentError,
98
+ "PartitiveRelation#partitives cannot include the comprehensive"
99
+ end
100
+ end
101
+
102
+ def validate_completeness!
103
+ return if completeness.nil?
104
+
105
+ unless Glossarist::GlossaryDefinition::COMPLETENESS_VALUES
106
+ .include?(completeness)
107
+ raise ArgumentError,
108
+ "PartitiveRelation#completeness has invalid value " \
109
+ "#{completeness.inspect}; must be one of " \
110
+ "#{GlossaryDefinition::COMPLETENESS_VALUES.join(', ')}"
111
+ end
112
+ end
113
+
114
+ def validate_plurality!
115
+ plurality&.validate!
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module V3
5
+ # TypeSharedPlurality — semantic claims encoded by ISO 704's
6
+ # close-set double line and broken line notation, promoted from
7
+ # diagram-notation flags to structured data so tools can reason
8
+ # about plurality directly.
9
+ #
10
+ # A PartitiveRelation has at most one TypeSharedPlurality block.
11
+ # Absent means: no type-shared plurality claim is being made.
12
+ #
13
+ # Replaces the prior `markers` field (which encoded the same
14
+ # information as opaque strings like "double" and "dashed").
15
+ class TypeSharedPlurality < Lutaml::Model::Serializable
16
+ attribute :is_shared, :boolean
17
+ attribute :is_uncertain, :boolean, default: -> { false }
18
+ attribute :shared_type, ConceptRef
19
+
20
+ key_value do
21
+ map :is_shared, to: :is_shared
22
+ map :is_uncertain, to: :is_uncertain
23
+ map :shared_type, to: :shared_type
24
+ end
25
+
26
+ def validate!
27
+ unless is_shared.is_a?(TrueClass) || is_shared.is_a?(FalseClass)
28
+ raise ArgumentError,
29
+ "TypeSharedPlurality#is_shared is required (boolean)"
30
+ end
31
+
32
+ if is_uncertain == true && is_shared == false
33
+ raise ArgumentError,
34
+ "TypeSharedPlurality#is_uncertain requires is_shared: true " \
35
+ "(ISO 704 broken line qualifies the close-set double line claim)"
36
+ end
37
+
38
+ self
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/glossarist/v3.rb CHANGED
@@ -9,7 +9,9 @@ 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 :PartitiveHyperedge, "glossarist/v3/partitive_hyperedge"
12
+ autoload :PartitiveRelation, "glossarist/v3/partitive_relation"
13
+ autoload :PartitiveMember, "glossarist/v3/partitive_member"
14
+ autoload :TypeSharedPlurality, "glossarist/v3/type_shared_plurality"
13
15
  autoload :ConceptData, "glossarist/v3/concept_data"
14
16
  autoload :LocalizedConcept, "glossarist/v3/localized_concept"
15
17
  autoload :ManagedConceptData, "glossarist/v3/managed_concept_data"
@@ -24,7 +26,9 @@ module Glossarist
24
26
  Configuration.register_model(LocalizedConcept, id: :localized_concept)
25
27
  Configuration.register_model(ConceptRef, id: :concept_ref)
26
28
  Configuration.register_model(RelatedConcept, id: :related_concept)
27
- Configuration.register_model(PartitiveHyperedge, id: :partitive_hyperedge)
29
+ Configuration.register_model(PartitiveRelation, id: :partitive_relation)
30
+ Configuration.register_model(PartitiveMember, id: :partitive_member)
31
+ Configuration.register_model(TypeSharedPlurality, id: :type_shared_plurality)
28
32
  Configuration.register_model(ManagedConceptData, id: :managed_concept_data)
29
33
  Configuration.register_model(ManagedConcept, id: :managed_concept)
30
34
  Configuration.register_model(ConceptDocument, id: :concept_document)
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Glossarist
4
+ module Validation
5
+ module Rules
6
+ # Validates semantic invariants of PartitiveRelation entries
7
+ # that the model constructor does NOT enforce. Specifically:
8
+ #
9
+ # - error when a relation has fewer than 2 partitives
10
+ # (ISO 704: "two or more"; single binary should use
11
+ # has_part edge instead)
12
+ # - error when two relations share the same comprehensive
13
+ # AND the same non-nil criterion (duplicate decomposition;
14
+ # ISO 12620 coordinate-concept coherence)
15
+ # - warning when a relation has no criterion (cannot
16
+ # distinguish from siblings sharing the comprehensive)
17
+ # - error when plurality.is_uncertain is set without
18
+ # plurality.is_shared: true (broken-line qualifies
19
+ # close-set double line)
20
+ # - error when ExternalConcept (status: external) lacks
21
+ # at least one designation
22
+ #
23
+ # The model constructor already rejects empty comprehensive,
24
+ # empty partitives list, self-loops, invalid enum values.
25
+ class PartitiveRelationRule < Base
26
+ def code = "GLS-221"
27
+ def category = :schema
28
+ def severity = "error"
29
+ def scope = :concept
30
+
31
+ def applicable?(context)
32
+ concept = context.concept
33
+ return false unless concept.is_a?(V3::ManagedConcept)
34
+
35
+ concept.partitive_relations&.any? || external?(concept)
36
+ end
37
+
38
+ def check(context)
39
+ concept = context.concept
40
+ fname = context.file_name
41
+ issues = []
42
+
43
+ return issues unless concept.is_a?(V3::ManagedConcept)
44
+
45
+ relations = Array(concept.partitive_relations)
46
+
47
+ relations.each_with_index do |rel, idx|
48
+ check_cardinality(rel, idx, fname, issues)
49
+ check_criterion_present(rel, idx, fname, issues)
50
+ check_plurality_coherence(rel, idx, fname, issues)
51
+ end
52
+
53
+ check_duplicate_decomposition(relations, fname, issues)
54
+ check_external_concept(concept, fname, issues)
55
+
56
+ issues
57
+ end
58
+
59
+ private
60
+
61
+ def external?(concept)
62
+ concept.status == "external"
63
+ end
64
+
65
+ def check_cardinality(rel, idx, fname, issues)
66
+ return if rel.partitives.length >= 2
67
+
68
+ issues << issue(
69
+ "partitive_relation #{idx + 1} has fewer than 2 partitives " \
70
+ "(ISO 704 requires two or more); a single binary has_part edge " \
71
+ "should be used instead",
72
+ location: fname,
73
+ )
74
+ end
75
+
76
+ def check_criterion_present(rel, idx, fname, issues)
77
+ return if rel.criterion && !rel.criterion.empty?
78
+
79
+ issues << issue(
80
+ "partitive_relation #{idx + 1} has no criterion; cannot verify " \
81
+ "distinctness from sibling relations sharing the comprehensive " \
82
+ "(ISO 12620 coordinate-concept coherence)",
83
+ severity: "warning",
84
+ location: fname,
85
+ suggestion: "Add a criterion: { eng: '...' } field",
86
+ )
87
+ end
88
+
89
+ def check_plurality_coherence(rel, idx, fname, issues)
90
+ plural = rel.plurality
91
+ return unless plural
92
+ return unless plural.is_uncertain
93
+ return if plural.is_shared
94
+
95
+ issues << issue(
96
+ "partitive_relation #{idx + 1}.plurality: is_uncertain requires " \
97
+ "is_shared: true (ISO 704 broken line qualifies the close-set " \
98
+ "double line claim)",
99
+ location: fname,
100
+ )
101
+ end
102
+
103
+ def check_duplicate_decomposition(relations, fname, issues)
104
+ grouped = {}
105
+ relations.each_with_index do |rel, idx|
106
+ next unless rel.criterion && !rel.criterion.empty?
107
+
108
+ key = criterion_key(rel)
109
+ (grouped[key] ||= []) << idx
110
+ end
111
+
112
+ grouped.each do |key, idxs|
113
+ next if idxs.length == 1
114
+
115
+ issues << issue(
116
+ "duplicate PartitiveRelation for comprehensive " \
117
+ "#{key.first.inspect} with criterion #{key.last.inspect} " \
118
+ "(relations ##{idxs.map { |i| i + 1 }.join(', ')}); " \
119
+ "two relations sharing comprehensive AND criterion are the " \
120
+ "same decomposition",
121
+ location: fname,
122
+ )
123
+ end
124
+ end
125
+
126
+ def criterion_key(rel)
127
+ comp = rel.comprehensive
128
+ comp_id = comp.is_a?(ConceptRef) ? [comp.source, comp.id] : nil
129
+ [comp_id, rel.criterion]
130
+ end
131
+
132
+ def check_external_concept(concept, fname, issues)
133
+ return unless external?(concept)
134
+
135
+ has_designation = concept.localizations.any? do |_, lc|
136
+ lc.is_a?(LocalizedConcept) && lc.terms&.any?
137
+ end
138
+
139
+ return if has_designation
140
+
141
+ issues << issue(
142
+ "ExternalConcept (status: external) must have at least one " \
143
+ "designation — even external concepts have a name",
144
+ location: fname,
145
+ )
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -4,5 +4,5 @@
4
4
  #
5
5
 
6
6
  module Glossarist
7
- VERSION = "2.12.1"
7
+ VERSION = "2.13.0"
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glossarist
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.1
4
+ version: 2.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -307,13 +307,15 @@ files:
307
307
  - lib/glossarist/rdf/gloss_figure_image.rb
308
308
  - lib/glossarist/rdf/gloss_formula.rb
309
309
  - lib/glossarist/rdf/gloss_grammar_info.rb
310
- - lib/glossarist/rdf/gloss_hyperedge.rb
311
310
  - lib/glossarist/rdf/gloss_locality.rb
312
311
  - lib/glossarist/rdf/gloss_localized_concept.rb
313
312
  - lib/glossarist/rdf/gloss_non_verbal_rep.rb
313
+ - lib/glossarist/rdf/gloss_partitive_member.rb
314
+ - lib/glossarist/rdf/gloss_partitive_relation.rb
314
315
  - lib/glossarist/rdf/gloss_pronunciation.rb
315
316
  - lib/glossarist/rdf/gloss_reference.rb
316
317
  - lib/glossarist/rdf/gloss_table.rb
318
+ - lib/glossarist/rdf/gloss_type_shared_plurality.rb
317
319
  - lib/glossarist/rdf/localized_literal.rb
318
320
  - lib/glossarist/rdf/lutaml_ext.rb
319
321
  - lib/glossarist/rdf/namespaces.rb
@@ -395,8 +397,10 @@ files:
395
397
  - lib/glossarist/v3/localized_concept.rb
396
398
  - lib/glossarist/v3/managed_concept.rb
397
399
  - lib/glossarist/v3/managed_concept_data.rb
398
- - lib/glossarist/v3/partitive_hyperedge.rb
400
+ - lib/glossarist/v3/partitive_member.rb
401
+ - lib/glossarist/v3/partitive_relation.rb
399
402
  - lib/glossarist/v3/related_concept.rb
403
+ - lib/glossarist/v3/type_shared_plurality.rb
400
404
  - lib/glossarist/validation.rb
401
405
  - lib/glossarist/validation/asset_index.rb
402
406
  - lib/glossarist/validation/bibliography_index.rb
@@ -438,7 +442,7 @@ files:
438
442
  - lib/glossarist/validation/rules/orphaned_bibliography_rule.rb
439
443
  - lib/glossarist/validation/rules/orphaned_images_rule.rb
440
444
  - lib/glossarist/validation/rules/orphaned_l10n_files_rule.rb
441
- - lib/glossarist/validation/rules/partitive_hyperedge_rule.rb
445
+ - lib/glossarist/validation/rules/partitive_relation_rule.rb
442
446
  - lib/glossarist/validation/rules/preferred_term_rule.rb
443
447
  - lib/glossarist/validation/rules/ref_shape_rule.rb
444
448
  - lib/glossarist/validation/rules/registry.rb
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "lutaml/model"
4
-
5
- module Glossarist
6
- module Rdf
7
- # RDF view for a PartitiveHyperedge (concept-model v3.1.0).
8
- #
9
- # Emits one `gloss:PartitiveHyperedge` subject per hyperedge with:
10
- #
11
- # - gloss:comprehensive — IRI of the comprehensive concept
12
- # - gloss:part — IRI of each part concept (zero or more)
13
- # - gloss:enumeration — xsd:string ("closed" | "open")
14
- # - gloss:hasPluralityMarker — xsd:string per marker ("double" |
15
- # "dashed"). One triple per marker.
16
- # - gloss:hyperedgeContent — xsd:string (omitted if absent)
17
- #
18
- # The hyperedge is anchored to the concept that carries it via
19
- # `gloss:hasHyperedge` (an inverse of `gloss:comprehensive`). The
20
- # subject URI is derived from the carrying concept's identifier
21
- # and the comprehensive's id, so it is stable across builds.
22
- class GlossHyperedge < Lutaml::Model::Serializable
23
- attribute :identifier, :string
24
- attribute :comprehensive_id, :string
25
- attribute :comprehensive_uri, :string
26
- attribute :part_uris, :string, collection: true
27
- attribute :enumeration, :string
28
- attribute :markers, :string, collection: true
29
- attribute :content, :hash
30
-
31
- rdf do
32
- namespace Namespaces::GlossaristNamespace
33
-
34
- subject { |h| "hyperedge/#{h.identifier}/#{h.comprehensive_id}" }
35
-
36
- types "gloss:PartitiveHyperedge"
37
-
38
- predicate :comprehensive, namespace: Namespaces::GlossaristNamespace,
39
- to: :comprehensive_uri
40
- predicate :part, namespace: Namespaces::GlossaristNamespace,
41
- to: :part_uris
42
- predicate :enumeration, namespace: Namespaces::GlossaristNamespace,
43
- to: :enumeration
44
- predicate :hasPluralityMarker,
45
- namespace: Namespaces::GlossaristNamespace,
46
- to: :markers
47
- predicate :hyperedgeContent,
48
- namespace: Namespaces::GlossaristNamespace,
49
- to: :content
50
- end
51
- end
52
- end
53
- end
@@ -1,132 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Glossarist
4
- module V3
5
- # PartitiveHyperedge — a one-to-many partitive decomposition.
6
- #
7
- # One comprehensive concept (the whole) is related to one or more
8
- # parts as a SINGLE relationship. Captures invariants that binary
9
- # RelatedConcept edges cannot:
10
- #
11
- # - which comprehensive owns which parts (set membership)
12
- # - plurality markers from the source diagram (double / dashed)
13
- # - enumeration completeness (closed: all parts listed; open:
14
- # other parts may exist)
15
- #
16
- # Enum values are SSOT-loaded from `config.yml` via
17
- # `GlossaryDefinition`. The `values:` option on each attribute
18
- # documents the enum for schema generation. lutaml-model 0.8.x
19
- # does NOT enforce `values:` on assignment — semantic checks live
20
- # in `Validation::Rules::PartitiveHyperedgeRule` (auto-registered
21
- # via `Validation::Rules`). The model's `validate!` method covers
22
- # type-shape invariants that fail loudly at construction (empty
23
- # comprehensive, empty parts, self-loop, invalid enum values).
24
- #
25
- # Note on duplicates: lutaml-model's enum-collection setter
26
- # dedupes silently via the getter, so duplicate markers at
27
- # construction time are silently coerced to a unique set. We do
28
- # not attempt to override this; the framework's Set semantics are
29
- # the data contract.
30
- #
31
- # `content` is a localized string hash `{ "eng" => "...", "fra" => "..." }`
32
- # keyed by ISO 639 language code. All free-form text fields in
33
- # Glossarist MUST be localized — the project is multi-language by
34
- # domain. Read values via the `LocalizedString` helper module:
35
- #
36
- # Glossarist::LocalizedString.fetch(he.content, "eng")
37
- #
38
- # Matches the pattern used by `Section#names`, `DatasetRegister#description`,
39
- # `Formula#expression`, `Table#content`. The concept-model schema and
40
- # `RelatedConcept#content` still declare `type: string` today — those
41
- # are bugs to fix, not patterns to copy.
42
- class PartitiveHyperedge < Lutaml::Model::Serializable
43
- DEFAULT_ENUMERATION = "closed"
44
-
45
- attribute :comprehensive, ConceptRef
46
- attribute :parts, ConceptRef, collection: true
47
- attribute :enumeration, :string,
48
- values: Glossarist::GlossaryDefinition::PARTITIVE_ENUMERATION_VALUES,
49
- default: -> { DEFAULT_ENUMERATION }
50
- attribute :markers, :string,
51
- values: Glossarist::GlossaryDefinition::PLURALITY_MARKER_VALUES,
52
- collection: true
53
- attribute :content, :hash
54
-
55
- key_value do
56
- map :comprehensive, to: :comprehensive
57
- map :parts, to: :parts
58
- map :enumeration, to: :enumeration
59
- map :markers, to: :markers
60
- map :content, to: :content
61
- end
62
-
63
- # Type-shape validators — invoke via `validate!` after
64
- # construction. We do NOT call these from `initialize` because
65
- # lutaml-model deserializes attributes AFTER `new` returns (see
66
- # `Lutaml::KeyValue::Transform#data_to_model`); validating in
67
- # `initialize` sees a half-built instance and raises spuriously.
68
-
69
- def validate!
70
- validate_comprehensive!
71
- validate_parts!
72
- validate_self_loop!
73
- validate_markers!
74
- validate_enumeration!
75
- self
76
- end
77
-
78
- private
79
-
80
- def validate_comprehensive!
81
- return if comprehensive.is_a?(ConceptRef) &&
82
- (comprehensive.source || comprehensive.id)
83
-
84
- raise ArgumentError,
85
- "PartitiveHyperedge#comprehensive must be a non-empty " \
86
- "ConceptRef (source or id required)"
87
- end
88
-
89
- def validate_parts!
90
- return if Array(parts).any?
91
-
92
- raise ArgumentError, "PartitiveHyperedge requires at least one part"
93
- end
94
-
95
- def validate_self_loop!
96
- return unless comprehensive.is_a?(ConceptRef)
97
-
98
- comp_key = [comprehensive.source, comprehensive.id]
99
- parts.each do |p|
100
- next unless p.is_a?(ConceptRef)
101
- next unless [p.source, p.id] == comp_key
102
-
103
- raise ArgumentError,
104
- "PartitiveHyperedge#parts cannot include the comprehensive"
105
- end
106
- end
107
-
108
- def validate_markers!
109
- Array(markers).each do |m|
110
- unless Glossarist::GlossaryDefinition::PLURALITY_MARKER_VALUES.include?(m)
111
- raise ArgumentError,
112
- "PartitiveHyperedge#markers contains invalid value #{m.inspect}; " \
113
- "must be one of " \
114
- "#{GlossaryDefinition::PLURALITY_MARKER_VALUES.join(', ')}"
115
- end
116
- end
117
- end
118
-
119
- def validate_enumeration!
120
- return if enumeration.nil?
121
-
122
- unless Glossarist::GlossaryDefinition::PARTITIVE_ENUMERATION_VALUES
123
- .include?(enumeration)
124
- raise ArgumentError,
125
- "PartitiveHyperedge#enumeration has invalid value " \
126
- "#{enumeration.inspect}; must be one of " \
127
- "#{GlossaryDefinition::PARTITIVE_ENUMERATION_VALUES.join(', ')}"
128
- end
129
- end
130
- end
131
- end
132
- end
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Glossarist
4
- module Validation
5
- module Rules
6
- # Validates semantic invariants of PartitiveHyperedge entries that
7
- # the model constructor does NOT enforce. Specifically:
8
- #
9
- # - warning when `enumeration` was filled by lutaml-model's
10
- # default proc (i.e. the source YAML omitted it) — author is
11
- # *encouraged* to set explicitly per the design doc
12
- # - error when markers contain values not in
13
- # PLURALITY_MARKER_VALUES
14
- #
15
- # The model constructor already rejects empty comprehensive,
16
- # empty parts, self-loops, and (lutaml-model auto-dedupes
17
- # duplicate markers on enum-collection assignment, so no
18
- # explicit check is needed).
19
- class PartitiveHyperedgeRule < Base
20
- def code = "GLS-220"
21
- def category = :schema
22
- def severity = "error"
23
- def scope = :concept
24
-
25
- def applicable?(context)
26
- concept = context.concept
27
- return false unless concept.is_a?(V3::ManagedConcept)
28
-
29
- concept.partitive_hyperedges&.any?
30
- end
31
-
32
- def check(context)
33
- concept = context.concept
34
- fname = context.file_name
35
- issues = []
36
-
37
- return issues unless concept.is_a?(V3::ManagedConcept)
38
-
39
- concept.partitive_hyperedges.each_with_index do |he, idx|
40
- check_comprehensive(he, idx, fname, issues)
41
- check_parts(he, idx, fname, issues)
42
- check_markers(he, idx, fname, issues)
43
- check_enumeration_explicit(he, idx, fname, issues)
44
- end
45
-
46
- issues
47
- end
48
-
49
- private
50
-
51
- def check_comprehensive(he, idx, fname, issues)
52
- ref = he.comprehensive
53
- return if ref.is_a?(Glossarist::ConceptRef) && (ref.source || ref.id)
54
-
55
- issues << issue(
56
- "partitive_hyperedge #{idx + 1} has empty comprehensive",
57
- location: fname,
58
- )
59
- end
60
-
61
- def check_parts(he, idx, fname, issues)
62
- parts = Array(he.parts)
63
- if parts.empty?
64
- issues << issue(
65
- "partitive_hyperedge #{idx + 1} has no parts",
66
- location: fname,
67
- )
68
- return
69
- end
70
-
71
- parts.each_with_index do |part, pi|
72
- next if part.is_a?(Glossarist::ConceptRef) && (part.source || part.id)
73
-
74
- issues << issue(
75
- "partitive_hyperedge #{idx + 1} part #{pi + 1} has empty ref",
76
- location: fname,
77
- )
78
- end
79
- end
80
-
81
- def check_markers(he, idx, fname, issues)
82
- # `using_default?` reports whether an attribute was set by
83
- # the user (false) or filled by the default proc (true).
84
- # `markers` has no default, so absence yields nil/[] and
85
- # `using_default?(:markers)` is true. We still walk for
86
- # invalid values because `values:` is informational.
87
- Array(he.markers).each_with_index do |m, mi|
88
- unless Glossarist::GlossaryDefinition::PLURALITY_MARKER_VALUES.include?(m)
89
- issues << issue(
90
- "partitive_hyperedge #{idx + 1} marker #{mi + 1} has invalid value #{m.inspect}",
91
- location: fname,
92
- )
93
- end
94
- end
95
- end
96
-
97
- def check_enumeration_explicit(he, idx, fname, issues)
98
- # `using_default?(:enumeration)` is true iff the value was
99
- # NOT explicitly set by the caller (filled by default proc).
100
- return unless he.using_default?(:enumeration)
101
-
102
- issues << issue(
103
- "partitive_hyperedge #{idx + 1} has implicit enumeration (default '#{Glossarist::V3::PartitiveHyperedge::DEFAULT_ENUMERATION}')",
104
- severity: "warning",
105
- location: fname,
106
- suggestion: "Set 'enumeration: closed' or 'enumeration: open' explicitly",
107
- )
108
- end
109
- end
110
- end
111
- end
112
- end