glossarist 2.13.11 → 2.13.12

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: 91c65595f05c44c51808471afe8444211ea1f6dec412d8fe0ad74eee187b11b2
4
- data.tar.gz: 9354da2e19de5935ed2e6415436f2b7344e8ad841520a7ee9b70be7c4ddd49da
3
+ metadata.gz: 5b15e9fa1ab722c42698d7b4f87a9517557aaeffbf44ed56907d20db28a678d3
4
+ data.tar.gz: 92743d7653a3a64f09da6098aea9308ca809db2a2ac4108766590cf5dbea791c
5
5
  SHA512:
6
- metadata.gz: a2288cbc327785082b55fa370c622877e7dd41e90545edb8db7063e3fd29d57d974b040197fb015e4a83a6bc99f1ce44c583fbe692aced5b5f2251dbd542382d
7
- data.tar.gz: 919ec57825c62a7749afce56ec9bf5f49e7f18a89d537c60290d92e6bf35b97eeb4118f40369e81119113ebf945b371d0fccc942d4416df14e31d2af2359bd7d
6
+ metadata.gz: '02780e9a932c36e5d769d8645b37a36c372fabaa3acee9cc85760d5b6734013b524d0a41d06c9dbb06ace71aba2186797dd022f9f055fc761ffa5c605a42a7bb'
7
+ data.tar.gz: b41226d8877739287fafa314e1fd7b5be6574475e839dcff4a218f8a003a618549e0b01a89b7bc38a76d3a708390bd6b7a8f8d2f15afea62b181b41add392fc5
@@ -4,39 +4,44 @@ module Glossarist
4
4
  # A typed reference to another concept, used inside n-ary relation
5
5
  # members and other internal pointer shapes.
6
6
  #
7
- # The base ConceptRef carries `source`, `id`, and `text`. The richer
8
- # `ConceptReference` (in `glossarist/concept_reference.rb`) extends
9
- # this with `term`, `urn`, `version`, `ref_type` for use in
10
- # cross-vocabulary references and relevance contexts.
7
+ # The base ConceptRef carries `source`, `id`, `text`, `external`, and
8
+ # `ellipsis`. The richer `ConceptReference` (in
9
+ # `glossarist/concept_reference.rb`) extends this with `term`, `urn`,
10
+ # `version`, `ref_type` for use in cross-vocabulary references and
11
+ # relevance contexts.
11
12
  #
12
13
  # A ConceptRef is identified by `source:id` for external references
13
14
  # or just `id` for local references. The `qualified_id` class method
14
15
  # is the single SSOT for this string format — used by RelationLoader,
15
16
  # the RDF transform, and ConceptRef-bearing data structures.
17
+ #
18
+ # == External concepts (parenthetical notation)
19
+ #
20
+ # ISO 704:2022 uses parenthetical notation for external concepts —
21
+ # concepts referenced from this dataset but defined elsewhere. The
22
+ # `external: true` flag marks such a ref. External refs carry `text`
23
+ # (the parenthetical label) but NOT `source`/`id`.
24
+ #
25
+ # == Ellipsis (structural marker)
26
+ #
27
+ # An ellipsis ref marks "further members exist but are not encoded."
28
+ # It is NOT a concept — it's a structural marker on the hyperedge.
29
+ # An ellipsis ref carries NO other fields (no source, id, text).
16
30
  class ConceptRef < Lutaml::Model::Serializable
17
31
  attribute :source, :string
18
32
  attribute :id, :string
19
33
  attribute :text, :string
34
+ attribute :external, :boolean, default: -> { false }
35
+ attribute :ellipsis, :boolean, default: -> { false }
20
36
 
21
37
  key_value do
22
38
  map :source, to: :source
23
39
  map :id, to: :id
24
40
  map :text, to: :text
41
+ map :external, to: :external
42
+ map :ellipsis, to: :ellipsis
25
43
  end
26
44
 
27
- # SSOT for the canonical "qualified identifier" string of a
28
- # ConceptRef. Returns:
29
- #
30
- # - "{source}:{id}" when both source and id are present
31
- # - "{id}" when only id is present (local ref)
32
- # - "{source}:{text}" when source and text are present (no id)
33
- # - "{text}" when only text is present (external
34
- # concept form, no resolvable id)
35
- # - nil when the ref is empty / not a ConceptRef
36
- #
37
- # Two refs are considered to point at the same concept iff their
38
- # qualified_id is equal. Tables and registries can use this
39
- # directly as a hash key.
40
45
  def self.qualified_id(ref)
41
46
  return nil unless ref.is_a?(ConceptRef)
42
47
 
@@ -51,14 +56,50 @@ module Glossarist
51
56
  end
52
57
  end
53
58
 
54
- # Per-instance convenience delegating to the class method.
55
59
  def qualified_id
56
60
  self.class.qualified_id(self)
57
61
  end
58
62
 
59
- # True iff +other+ refers to the same concept as self.
60
63
  def same_concept?(other)
61
64
  qualified_id == self.class.qualified_id(other)
62
65
  end
66
+
67
+ def resolved?
68
+ !source.to_s.empty? && !id.to_s.empty?
69
+ end
70
+
71
+ def external?
72
+ external == true
73
+ end
74
+
75
+ def ellipsis?
76
+ ellipsis == true
77
+ end
78
+
79
+ def text_only?
80
+ !text.to_s.empty? && !resolved? && !external?
81
+ end
82
+
83
+ def validate_ref!
84
+ if ellipsis? && (source || id || text || external?)
85
+ raise ArgumentError,
86
+ "ConceptRef with ellipsis: true must not carry any " \
87
+ "other field (source, id, text, external)"
88
+ end
89
+
90
+ if external? && (source || id)
91
+ raise ArgumentError,
92
+ "ConceptRef with external: true must not carry " \
93
+ "source/id — use text for the parenthetical label"
94
+ end
95
+
96
+ if !source && !id && !text && !external? && !ellipsis?
97
+ raise ArgumentError,
98
+ "ConceptRef must have at least one of: source+id, text, " \
99
+ "external, or ellipsis"
100
+ end
101
+
102
+ self
103
+ end
63
104
  end
64
105
  end
@@ -89,8 +89,10 @@ module Glossarist
89
89
  # Consumers inject the resolver at query time.
90
90
 
91
91
  # True if the comprehensive resolves (via the supplied resolver)
92
- # to a concept with status: external.
92
+ # to a concept with status: external. Also true when the
93
+ # comprehensive ConceptRef itself has external: true (inline form).
93
94
  def external_comprehensive?(resolver = nil)
95
+ return true if comprehensive.is_a?(ConceptRef) && comprehensive.external?
94
96
  return false unless comprehensive.is_a?(ConceptRef)
95
97
  return false unless resolver
96
98
 
@@ -98,8 +100,8 @@ module Glossarist
98
100
  concept_is_external?(concept)
99
101
  end
100
102
 
101
- # Array of members whose refs resolve to status: external.
102
- # Empty when no resolver is supplied (detection is opt-in).
103
+ # Array of members whose refs are marked external: true (inline
104
+ # form) OR resolve (via the supplied resolver) to status: external.
103
105
  def external_members(resolver = nil)
104
106
  return [] unless resolver
105
107
 
@@ -125,6 +127,33 @@ module Glossarist
125
127
  candidates.any? { |ref| !has_provided_by?(ref, resolver) }
126
128
  end
127
129
 
130
+ # ── Ellipsis / external inline helpers (no resolver needed) ──
131
+
132
+ # True if any member has a ConceptRef with external: true.
133
+ def has_external_members?
134
+ members.any? { |m| m.is_a?(HyperedgeMember) && m.ref&.external? }
135
+ end
136
+
137
+ # True if the comprehensive ConceptRef has external: true.
138
+ def has_external_comprehensive?
139
+ comprehensive.is_a?(ConceptRef) && comprehensive.external?
140
+ end
141
+
142
+ # True if any member has a ConceptRef with ellipsis: true.
143
+ def has_ellipsis_member?
144
+ members.any? { |m| m.is_a?(HyperedgeMember) && m.ref&.ellipsis? }
145
+ end
146
+
147
+ # Array of members whose refs are marked external: true.
148
+ def inline_external_members
149
+ members.select { |m| m.is_a?(HyperedgeMember) && m.ref&.external? }
150
+ end
151
+
152
+ # Array of members whose refs are marked ellipsis: true.
153
+ def ellipsis_members
154
+ members.select { |m| m.is_a?(HyperedgeMember) && m.ref&.ellipsis? }
155
+ end
156
+
128
157
  # Per-file identity derived from comprehensive + criterion.
129
158
  # Format: `<comprehensive-id-dir>/<criterion-slug>` where the
130
159
  # dir is `<source>-<id>` (downcased, kebab-case) and the slug
@@ -68,7 +68,7 @@ module Glossarist
68
68
  hash = hyperedge.to_hash
69
69
  hash["$id"] = hyperedge.file_id || hyperedge.derived_file_id
70
70
  hash["type"] = hyperedge.class::TYPE_TAG
71
- # Re-order for stable output.
71
+ strip_falsey_concept_ref_flags!(hash)
72
72
  ordered = {}
73
73
  ordered["$id"] = hash.delete("$id")
74
74
  ordered["type"] = hash.delete("type")
@@ -79,11 +79,28 @@ module Glossarist
79
79
  ordered["criterion"] = hash.delete("criterion") if hash.key?("criterion")
80
80
  ordered["sources"] = hash.delete("sources") if hash.key?("sources")
81
81
  ordered["notes"] = hash.delete("notes") if hash.key?("notes")
82
- ordered.merge!(hash) # any future fields append in declaration order
82
+ ordered.merge!(hash)
83
+ strip_falsey_concept_ref_flags!(ordered)
83
84
  ordered.compact!
84
85
 
85
86
  YAML.dump(ordered).gsub(/^---\s*\n/, "---\n")
86
87
  end
88
+
89
+ # Recursively strip `external: false` and `ellipsis: false` from
90
+ # ConceptRef hashes inside the hyperedge's wire output. Keeps
91
+ # YAML clean — these flags only appear when true.
92
+ def strip_falsey_concept_ref_flags!(hash)
93
+ hash.each_value do |value|
94
+ case value
95
+ when Hash
96
+ value.delete("external") if value["external"] == false
97
+ value.delete("ellipsis") if value["ellipsis"] == false
98
+ strip_falsey_concept_ref_flags!(value)
99
+ when Array
100
+ value.each { |item| strip_falsey_concept_ref_flags!(item) if item.is_a?(Hash) }
101
+ end
102
+ end
103
+ end
87
104
  end
88
105
  end
89
106
  end
@@ -4,5 +4,5 @@
4
4
  #
5
5
 
6
6
  module Glossarist
7
- VERSION = "2.13.11"
7
+ VERSION = "2.13.12"
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.13.11
4
+ version: 2.13.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-03 00:00:00.000000000 Z
11
+ date: 2026-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model