glossarist 2.12.1 → 2.13.1

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: '0898c5d75bdeac6ca104c401a0492061a765079c601cd87c1fa9c134651b52d0'
4
+ data.tar.gz: 3c2cf3896eab10345fea5374ccca555193c31f9ee7773fc4e03ca1f5ea3b6af3
5
5
  SHA512:
6
- metadata.gz: 163a2b94ff599127f8997de745f05ebec13ab19095790bf91ca832e7ea2c79525c65ca93a4e1cbd45ba4fcd9c4f1d4db9cb564bf370497d1e22fd5aa3e28ec73
7
- data.tar.gz: 43be5caab946b3202614ec38db165f6ce72b56137ca1daa87fa0e9c12a853b3c878541c8b8a9b23589719d4239a566ddb621ddee2552e7c284ab435232c03cd1
6
+ metadata.gz: 2eac98effdcbc12e9934df41d9c6c01c19a5c5f6388497be63039a3b50bd6fba5e289ad3eb573c18c4f7740f34d8711f002285f8fb614aef60c2352b90698670
7
+ data.tar.gz: be3178b14bb2a16894e8af117b08f5a34a2910a27a6d289a3298db37647cb8c4cdad4acc78674b6189a4cd3a9d265625dbfc54d3e2ac084447ac875c9ec84749
data/CLAUDE.md CHANGED
@@ -47,23 +47,36 @@ 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 ISO 704:2022 notation metadata:
58
+ - `multiplicity` — one of `compulsory` (1 solid line, default), `optional` (1 dashed line), `compulsory_multiple` (2 solid lines), `optional_multiple` (2 dashed lines), `at_least_one` (1 solid + 1 dashed line)
59
+ - `is_delimiting` — boolean (default false). A delimiting part behaves like a delimiting characteristic: it distinguishes the comprehensive from coordinate concepts. Rendered as a bold 3x-width line in the diagram. Orthogonal to multiplicity.
60
+ - `completeness` — `complete` (default; the encoded partitives constitute the whole) or `partial` (others exist beyond those encoded). ISO 704 rake backline notation.
61
+ - `criterion` — optional localized string hash (`{ "eng" => "physical structure" }`). ISO 12620 coordinate-concept coherence: partitives within one relation share the comprehensive AND the criterion.
59
62
 
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.
63
+ Wired into `V3::ManagedConcept#partitive_relations`. NOT on `ManagedConceptData` (relationships live at the concept level for MECE consistency with `related`).
61
64
 
62
- Semantic checks (defaulted-enumeration warning, defensive walk) live in `Validation::Rules::PartitiveHyperedgeRule` (auto-registered by `Validation::Rules`).
65
+ Enum values are SSOT-loaded from `config.yml` via `GlossaryDefinition::COMPLETENESS_VALUES` and `MULTIPLICITY_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, invalid enum values).
63
66
 
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`.
67
+ Semantic checks (≥2 partitives cardinality, duplicate comprehensive+criterion detection, criterion-absent warning, non-default multiplicity warning, ExternalConcept shape) live in `Validation::Rules::PartitiveRelationRule` (auto-registered by `Validation::Rules`, code GLS-221).
65
68
 
66
- Binary `broader_partitive`/`narrower_partitive` `RelatedConcept` edges and `PartitiveHyperedge` coexist no automatic consolidation. See `concept-model/TODO.hyperedge/00-design-overview.md`.
69
+ RDF emission: `Rdf::GlossPartitiveRelation` view class with companion `GlossPartitiveMember`. Wired into `Transforms::ConceptToGlossTransform` and emitted as `gloss:hasPartitiveRelation` link from `GlossConcept`.
70
+
71
+ 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/multiplicity/delimiting/criterion metadata).
72
+
73
+ #### ExternalConcept
74
+
75
+ `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.
76
+
77
+ #### Relationship type extensions
78
+
79
+ 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
80
 
68
81
  ### Reference Resolution
69
82
 
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,32 @@ 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
+ multiplicity:
215
+ # ISO 704:2022 partitive member multiplicity. Encodes the diagram
216
+ # line notation as structured data:
217
+ # compulsory — 1 solid line (must exist in every instance)
218
+ # optional — 1 dashed line (exists in some instances only)
219
+ # compulsory_multiple — 2 solid lines (multiple must exist)
220
+ # optional_multiple — 2 dashed lines (multiple may exist)
221
+ # at_least_one — 1 solid + 1 dashed line (at least one must exist)
222
+ # Default: compulsory.
201
223
  value:
202
- - double # close-set double line at receiver; several parts involved
203
- - dashed # broken/dashed line; plurality is uncertain
224
+ - compulsory
225
+ - optional
226
+ - compulsory_multiple
227
+ - optional_multiple
228
+ - at_least_one
@@ -34,10 +34,9 @@ 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
+ COMPLETENESS_VALUES = config.dig("completeness", "value").freeze
39
39
 
40
- PLURALITY_MARKER_VALUES =
41
- config.dig("plurality_marker", "value").freeze
40
+ MULTIPLICITY_VALUES = config.dig("multiplicity", "value").freeze
42
41
  end
43
42
  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,37 @@
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, multiplicity, and
9
+ # is_delimiting properties.
10
+ class GlossPartitiveMember < Lutaml::Model::Serializable
11
+ attribute :ref_id, :string
12
+ attribute :ref_source, :string
13
+ attribute :ref_text, :string
14
+ attribute :multiplicity, :string
15
+ attribute :is_delimiting, :boolean
16
+
17
+ rdf do
18
+ namespace Namespaces::GlossaristNamespace
19
+
20
+ subject { |m| "partitiveMember/#{m.ref_source}:#{m.ref_id}" }
21
+
22
+ types "gloss:PartitiveMember"
23
+
24
+ predicate :refSource, namespace: Namespaces::GlossaristNamespace,
25
+ to: :ref_source
26
+ predicate :refId, namespace: Namespaces::GlossaristNamespace,
27
+ to: :ref_id
28
+ predicate :refText, namespace: Namespaces::GlossaristNamespace,
29
+ to: :ref_text
30
+ predicate :multiplicity, namespace: Namespaces::GlossaristNamespace,
31
+ to: :multiplicity
32
+ predicate :isDelimiting, namespace: Namespaces::GlossaristNamespace,
33
+ to: :is_delimiting
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
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, 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 :criterion, :hash
16
+
17
+ rdf do
18
+ namespace Namespaces::GlossaristNamespace
19
+
20
+ subject { |r| "partitiveRelation/#{r.identifier}/#{r.object_id}" }
21
+
22
+ types "gloss:PartitiveRelation"
23
+
24
+ predicate :comprehensive, namespace: Namespaces::GlossaristNamespace,
25
+ to: :comprehensive_uri, uri_reference: true
26
+ predicate :hasPartitive, namespace: Namespaces::GlossaristNamespace,
27
+ to: :partitive_member_ids, uri_reference: true
28
+ predicate :completeness, namespace: Namespaces::GlossaristNamespace,
29
+ to: :completeness, uri_reference: true
30
+ predicate :criterion, namespace: Namespaces::GlossaristNamespace,
31
+ to: :criterion
32
+ end
33
+ end
34
+ end
35
+ end
@@ -29,7 +29,8 @@ module Glossarist
29
29
  GlossFigureImage
30
30
  GlossTable
31
31
  GlossFormula
32
- GlossHyperedge
32
+ GlossPartitiveRelation
33
+ GlossPartitiveMember
33
34
  ].freeze
34
35
 
35
36
  VIEW_CLASS_NAMES.each do |name|
@@ -42,7 +42,8 @@ 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"
46
47
  autoload :V3, "#{__dir__}/rdf/v3"
47
48
  end
48
49
  end
@@ -130,42 +130,53 @@ 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
+ criterion: rel.criterion,
160
161
  )
161
162
  end
162
163
  end
163
164
 
164
- def hyperedge_concept_uri(ref)
165
+ def partitive_concept_uri(ref)
165
166
  return nil unless ref.is_a?(Glossarist::ConceptRef)
166
167
 
167
168
  Glossarist::Rdf::Namespaces::GlossaristNamespace.uri +
168
- "concept/#{ref.id}"
169
+ "concept/#{ref.id || ref.text}"
170
+ end
171
+
172
+ def partitive_member_uri(member)
173
+ return nil unless member.is_a?(V3::PartitiveMember)
174
+
175
+ ref = member.ref
176
+ return nil unless ref.is_a?(Glossarist::ConceptRef)
177
+
178
+ Glossarist::Rdf::Namespaces::GlossaristNamespace.uri +
179
+ "concept/#{ref.id || ref.text}"
169
180
  end
170
181
 
171
182
  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,82 @@
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 ISO 704:2022
7
+ # multiplicity and delimiting metadata.
8
+ #
9
+ # ISO 704:2022 partitive member notation:
10
+ #
11
+ # multiplicity (diagram line notation):
12
+ # compulsory — 1 solid line (must exist in every instance)
13
+ # optional — 1 dashed line (exists in some instances only)
14
+ # compulsory_multiple — 2 solid lines (multiple must exist)
15
+ # optional_multiple — 2 dashed lines (multiple may exist)
16
+ # at_least_one — 1 solid + 1 dashed line (≥1 must exist)
17
+ #
18
+ # is_delimiting (orthogonal, bold 3x-width line in diagram):
19
+ # A delimiting part behaves like a delimiting characteristic:
20
+ # it distinguishes the comprehensive from coordinate concepts.
21
+ # Example (ISO 704 §5.5.4.2.2): for "optomechanical mouse",
22
+ # the delimiting parts are mouse ball, x/y-axis rollers,
23
+ # infrared emitter/sensor — they distinguish it from
24
+ # "mechanical mouse" and "optical mouse". Mouse button is
25
+ # NOT delimiting (all computer mice have buttons).
26
+ class PartitiveMember < Lutaml::Model::Serializable
27
+ DEFAULT_MULTIPLICITY = "compulsory"
28
+
29
+ attribute :ref, ConceptRef
30
+ attribute :multiplicity, :string,
31
+ values: Glossarist::GlossaryDefinition::MULTIPLICITY_VALUES,
32
+ default: -> { DEFAULT_MULTIPLICITY }
33
+ attribute :is_delimiting, :boolean, default: -> { false }
34
+
35
+ key_value do
36
+ map :ref, to: :ref
37
+ map :multiplicity, to: :multiplicity
38
+ map :is_delimiting, to: :is_delimiting
39
+ end
40
+
41
+ def validate!
42
+ validate_ref!
43
+ validate_multiplicity!
44
+ self
45
+ end
46
+
47
+ def compulsory?
48
+ multiplicity == "compulsory"
49
+ end
50
+
51
+ def optional?
52
+ multiplicity == "optional"
53
+ end
54
+
55
+ def delimiting?
56
+ is_delimiting == true
57
+ end
58
+
59
+ private
60
+
61
+ def validate_ref!
62
+ return if ref.is_a?(ConceptRef) && (ref.source || ref.id || ref.text)
63
+
64
+ raise ArgumentError,
65
+ "PartitiveMember#ref must be a non-empty ConceptRef " \
66
+ "(source, id, or text required)"
67
+ end
68
+
69
+ def validate_multiplicity!
70
+ return if multiplicity.nil?
71
+
72
+ unless Glossarist::GlossaryDefinition::MULTIPLICITY_VALUES
73
+ .include?(multiplicity)
74
+ raise ArgumentError,
75
+ "PartitiveMember#multiplicity has invalid value " \
76
+ "#{multiplicity.inspect}; must be one of " \
77
+ "#{GlossaryDefinition::MULTIPLICITY_VALUES.join(', ')}"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,115 @@
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):
16
+ # - multiplicity (compulsory, optional, compulsory_multiple,
17
+ # optional_multiple, at_least_one) — encodes the diagram
18
+ # line notation as data
19
+ # - is_delimiting — orthogonal flag; a delimiting part behaves
20
+ # like a delimiting characteristic (distinguishes the
21
+ # comprehensive from coordinate concepts)
22
+ #
23
+ # Replaces the prior PartitiveHyperedge class. The "hyperedge"
24
+ # framing was graph-theoretic; ISO calls this a *relation*.
25
+ class PartitiveRelation < Lutaml::Model::Serializable
26
+ DEFAULT_COMPLETENESS = "complete"
27
+
28
+ attribute :comprehensive, ConceptRef
29
+ attribute :partitives, PartitiveMember, collection: true
30
+ attribute :completeness, :string,
31
+ values: Glossarist::GlossaryDefinition::COMPLETENESS_VALUES,
32
+ default: -> { DEFAULT_COMPLETENESS }
33
+ attribute :criterion, :hash
34
+
35
+ key_value do
36
+ map :comprehensive, to: :comprehensive
37
+ map :partitives, to: :partitives
38
+ map :completeness, to: :completeness
39
+ map :criterion, to: :criterion
40
+ end
41
+
42
+ def validate!
43
+ validate_comprehensive!
44
+ validate_partitives!
45
+ validate_self_loop!
46
+ validate_completeness!
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
+ end
114
+ end
115
+ end
data/lib/glossarist/v3.rb CHANGED
@@ -9,7 +9,8 @@ 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"
13
14
  autoload :ConceptData, "glossarist/v3/concept_data"
14
15
  autoload :LocalizedConcept, "glossarist/v3/localized_concept"
15
16
  autoload :ManagedConceptData, "glossarist/v3/managed_concept_data"
@@ -24,7 +25,8 @@ module Glossarist
24
25
  Configuration.register_model(LocalizedConcept, id: :localized_concept)
25
26
  Configuration.register_model(ConceptRef, id: :concept_ref)
26
27
  Configuration.register_model(RelatedConcept, id: :related_concept)
27
- Configuration.register_model(PartitiveHyperedge, id: :partitive_hyperedge)
28
+ Configuration.register_model(PartitiveRelation, id: :partitive_relation)
29
+ Configuration.register_model(PartitiveMember, id: :partitive_member)
28
30
  Configuration.register_model(ManagedConceptData, id: :managed_concept_data)
29
31
  Configuration.register_model(ManagedConcept, id: :managed_concept)
30
32
  Configuration.register_model(ConceptDocument, id: :concept_document)
@@ -0,0 +1,161 @@
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
+ # - warning when multiplicity is not the default compulsory
18
+ # (encourages explicit statement of optional/multiple)
19
+ # - error when ExternalConcept (status: external) lacks
20
+ # at least one designation
21
+ #
22
+ # The model constructor already rejects empty comprehensive,
23
+ # empty partitives list, self-loops, invalid enum values.
24
+ class PartitiveRelationRule < Base
25
+ def code = "GLS-221"
26
+ def category = :schema
27
+ def severity = "error"
28
+ def scope = :concept
29
+
30
+ def applicable?(context)
31
+ concept = context.concept
32
+ return false unless concept.is_a?(V3::ManagedConcept)
33
+
34
+ concept.partitive_relations&.any? || external?(concept)
35
+ end
36
+
37
+ def check(context)
38
+ concept = context.concept
39
+ fname = context.file_name
40
+ issues = []
41
+
42
+ return issues unless concept.is_a?(V3::ManagedConcept)
43
+
44
+ relations = Array(concept.partitive_relations)
45
+
46
+ relations.each_with_index do |rel, idx|
47
+ check_cardinality(rel, idx, fname, issues)
48
+ check_criterion_present(rel, idx, fname, issues)
49
+ check_member_multiplicity(rel, idx, fname, issues)
50
+ end
51
+
52
+ check_duplicate_decomposition(relations, fname, issues)
53
+ check_external_concept(concept, fname, issues)
54
+
55
+ issues
56
+ end
57
+
58
+ private
59
+
60
+ def external?(concept)
61
+ concept.status == "external"
62
+ end
63
+
64
+ def check_cardinality(rel, idx, fname, issues)
65
+ return if rel.partitives.length >= 2
66
+
67
+ issues << issue(
68
+ "partitive_relation #{idx + 1} has fewer than 2 partitives " \
69
+ "(ISO 704 requires two or more); a single binary has_part edge " \
70
+ "should be used instead",
71
+ location: fname,
72
+ )
73
+ end
74
+
75
+ def check_criterion_present(rel, idx, fname, issues)
76
+ return if rel.criterion && !rel.criterion.empty?
77
+
78
+ issues << issue(
79
+ "partitive_relation #{idx + 1} has no criterion; cannot verify " \
80
+ "distinctness from sibling relations sharing the comprehensive " \
81
+ "(ISO 12620 coordinate-concept coherence)",
82
+ severity: "warning",
83
+ location: fname,
84
+ suggestion: "Add a criterion: { eng: '...' } field",
85
+ )
86
+ end
87
+
88
+ def check_member_multiplicity(rel, idx, fname, issues)
89
+ rel.partitives.each_with_index do |member, mi|
90
+ unless member.compulsory?
91
+ issues << issue(
92
+ "partitive_relation #{idx + 1}.partitives[#{mi}] has " \
93
+ "non-default multiplicity '#{member.multiplicity}'; " \
94
+ "ISO 704 diagram uses dashed/multiple-line notation for " \
95
+ "this — confirm the optionality is intentional",
96
+ severity: "warning",
97
+ location: fname,
98
+ )
99
+ end
100
+
101
+ next unless member.delimiting?
102
+
103
+ issues << issue(
104
+ "partitive_relation #{idx + 1}.partitives[#{mi}] is marked " \
105
+ "delimiting (ISO 704 bold 3x-width line); behaves like a " \
106
+ "delimiting characteristic distinguishing the comprehensive " \
107
+ "from coordinate concepts",
108
+ severity: "info",
109
+ location: fname,
110
+ )
111
+ end
112
+ end
113
+
114
+ def check_duplicate_decomposition(relations, fname, issues)
115
+ grouped = {}
116
+ relations.each_with_index do |rel, idx|
117
+ next unless rel.criterion && !rel.criterion.empty?
118
+
119
+ key = criterion_key(rel)
120
+ (grouped[key] ||= []) << idx
121
+ end
122
+
123
+ grouped.each do |key, idxs|
124
+ next if idxs.length == 1
125
+
126
+ issues << issue(
127
+ "duplicate PartitiveRelation for comprehensive " \
128
+ "#{key.first.inspect} with criterion #{key.last.inspect} " \
129
+ "(relations ##{idxs.map { |i| i + 1 }.join(', ')}); " \
130
+ "two relations sharing comprehensive AND criterion are the " \
131
+ "same decomposition",
132
+ location: fname,
133
+ )
134
+ end
135
+ end
136
+
137
+ def criterion_key(rel)
138
+ comp = rel.comprehensive
139
+ comp_id = comp.is_a?(ConceptRef) ? [comp.source, comp.id] : nil
140
+ [comp_id, rel.criterion]
141
+ end
142
+
143
+ def check_external_concept(concept, fname, issues)
144
+ return unless external?(concept)
145
+
146
+ has_designation = concept.localizations.any? do |_, lc|
147
+ lc.is_a?(LocalizedConcept) && lc.terms&.any?
148
+ end
149
+
150
+ return if has_designation
151
+
152
+ issues << issue(
153
+ "ExternalConcept (status: external) must have at least one " \
154
+ "designation — even external concepts have a name",
155
+ location: fname,
156
+ )
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -4,5 +4,5 @@
4
4
  #
5
5
 
6
6
  module Glossarist
7
- VERSION = "2.12.1"
7
+ VERSION = "2.13.1"
8
8
  end
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.12.1
4
+ version: 2.13.1
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-23 00:00:00.000000000 Z
11
+ date: 2026-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -307,10 +307,11 @@ 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
@@ -395,7 +396,8 @@ files:
395
396
  - lib/glossarist/v3/localized_concept.rb
396
397
  - lib/glossarist/v3/managed_concept.rb
397
398
  - lib/glossarist/v3/managed_concept_data.rb
398
- - lib/glossarist/v3/partitive_hyperedge.rb
399
+ - lib/glossarist/v3/partitive_member.rb
400
+ - lib/glossarist/v3/partitive_relation.rb
399
401
  - lib/glossarist/v3/related_concept.rb
400
402
  - lib/glossarist/validation.rb
401
403
  - lib/glossarist/validation/asset_index.rb
@@ -438,7 +440,7 @@ files:
438
440
  - lib/glossarist/validation/rules/orphaned_bibliography_rule.rb
439
441
  - lib/glossarist/validation/rules/orphaned_images_rule.rb
440
442
  - lib/glossarist/validation/rules/orphaned_l10n_files_rule.rb
441
- - lib/glossarist/validation/rules/partitive_hyperedge_rule.rb
443
+ - lib/glossarist/validation/rules/partitive_relation_rule.rb
442
444
  - lib/glossarist/validation/rules/preferred_term_rule.rb
443
445
  - lib/glossarist/validation/rules/ref_shape_rule.rb
444
446
  - 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