Class: Pubid::Itu::Identifier
Overview
Base class for all ITU identifiers. Canonical name Pubid::Itu::Identifier.
Constant Summary
collapse
- LANGUAGES =
Long-form ↔ ITU single-letter language code map. The parser produces
single-letter codes (E/F/S/R/A/C); API callers (e.g. metanorma-itu)
pass long-form (en/fr/es/ru/ar/zh). Storage is normalized to the
single-letter form. Languages with no canonical letter (e.g. "de")
pass through unchanged and produce no trailing suffix.
{
"fr" => "F", "es" => "S", "ru" => "R",
"ar" => "A", "zh" => "C", "en" => "E",
"F" => "F", "S" => "S", "R" => "R",
"A" => "A", "C" => "C", "E" => "E"
}.freeze
Class Method Summary
collapse
Instance Method Summary
collapse
-
#==(other) ⇒ Object
Class-strict, like Supplement#== — the ITU type is part of the identity, not just the sector/series/number.
-
#base_from_kv(model, value) ⇒ Object
-
#base_to_kv(model, doc) ⇒ Object
Serialize a nested base identifier via its own to_hash so it collapses to the compact shape, and re-dispatch through the flavor base on load so _type resolves to the concrete subclass (a bare polymorphic cast would rebuild a plain Identifier and lose the subclass).
-
#code_for(model) ⇒ Object
-
#common_text_twin_from_kv(model, value) ⇒ Object
-
#common_text_twin_to_kv(model, doc) ⇒ Object
The common-text twin ("| ISO/IEC 13818-1:2022") is another flavor's identifier (ISO/IEC …); serialize it via its own flat to_hash and reconstruct it cross-flavor through the top-level polymorphic router so its concrete class (and custom mappings) are restored by _type.
-
#date_for(model) ⇒ Object
-
#emit_kv(doc, key, value) ⇒ Object
-
#exclude_from_nested(value, args) ⇒ Object
ITU keeps the document number and its edition/part together inside one Code component (like ETSI, unlike ISO's separate part attribute), so a top-level exclude(:part) can't reach the part.
-
#imp_marker_from_kv(model, value) ⇒ Object
-
#imp_marker_to_kv(model, doc) ⇒ Object
-
#initialize(**kwargs) ⇒ Identifier
constructor
A new instance of Identifier.
-
#month_from_kv(model, value) ⇒ Object
-
#month_to_kv(model, doc) ⇒ Object
-
#mr_number_with_part ⇒ Object
-
#mr_publisher ⇒ Object
ITU encodes identity across sector (T/R/CD), series (G/H/J…), and code (number with optional part), not in the inherited number/ typed_stage.
-
#mr_type ⇒ Object
-
#number ⇒ Object
The document number lives on the code component for ITU; surface it at the identifier root so consumers that key on #number — e.g.
-
#number_from_kv(model, value) ⇒ Object
-
#number_to_kv(model, doc) ⇒ Object
-
#parts_from_kv(model, value) ⇒ Object
-
#parts_to_kv(model, doc) ⇒ Object
-
#qualifier_from_kv(model, value) ⇒ Object
-
#qualifier_glued_from_kv(model, value) ⇒ Object
-
#qualifier_glued_to_kv(model, doc) ⇒ Object
-
#qualifier_to_kv(model, doc) ⇒ Object
-
#render_base(**_opts) ⇒ Object
-
#render_date_suffix ⇒ Object
" (MM/YYYY)", or " (YYYY)" when the month is unknown; "" when undated.
-
#render_language_suffix ⇒ Object
-
#sector_from_kv(model, value) ⇒ Object
-
#sector_to_kv(model, doc) ⇒ Object
--- Compact flat key_value converters (shared) -------------------- Collapse the identity components (Sector/Series/Code/Date) to bare top-level scalars so the serialized hash is flat and index-friendly, mirroring ISO/ETSI/JCGM/OIML.
-
#series_from_kv(model, value) ⇒ Object
-
#series_suffix_from_kv(model, value) ⇒ Object
-
#series_suffix_spaced_from_kv(model, value) ⇒ Object
-
#series_suffix_spaced_to_kv(model, doc) ⇒ Object
The three spelling/qualifier fields of Components::Code.
-
#series_suffix_to_kv(model, doc) ⇒ Object
-
#series_to_kv(model, doc) ⇒ Object
-
#subseries_from_kv(model, value) ⇒ Object
-
#subseries_to_kv(model, doc) ⇒ Object
-
#to_s(**opts) ⇒ Object
Render identifier as a string.
-
#year_from_kv(model, value) ⇒ Object
-
#year_to_kv(model, doc) ⇒ Object
apply_mappings, #base, #base_document, concrete_class_for, #dated_version_of?, #draft_of?, #drop_supplements, #edition_of?, #eql?, #exclude, from_hash, #has_supplement?, #hash, #includes?, #matches?, #mr_all_parts, #mr_edition, #mr_languages, #mr_number, #mr_part, #mr_subpart, #mr_supplement_suffix, #mr_year, #new_edition_of?, polymorphic_name, polymorphic_type_map, #related_to?, #render, #resolve_urn_generator, #root, #sibling_of?, #supplement_of?, #to_hash, #to_mr_string, #to_slug, #to_supplement_s, #to_urn, #urn_supplement_type, #urn_type_code, #year
Constructor Details
#initialize(**kwargs) ⇒ Identifier
Returns a new instance of Identifier.
77
78
79
80
81
82
83
84
85
|
# File 'lib/pubid/itu/identifiers/base.rb', line 77
def initialize(**kwargs)
if kwargs[:language]
kwargs = kwargs.merge(language: normalize_language(kwargs[:language]))
end
super
validate_ob_no_sector!
end
|
Class Method Details
.normalize_to_s_opts(opts) ⇒ Object
Translate language: opt to i18n_lang: opt. v1 PR #38 introduced
i18n_lang: to disambiguate "rendering language" from the document
language attribute; language: remains a deprecated alias.
240
241
242
243
244
245
246
247
248
|
# File 'lib/pubid/itu/identifiers/base.rb', line 240
def self.normalize_to_s_opts(opts)
opts = opts.dup
if opts.key?(:language) && !opts.key?(:i18n_lang)
opts[:i18n_lang] = opts.delete(:language)
else
opts.delete(:language)
end
opts
end
|
.parse(identifier) ⇒ Object
Parse an ITU identifier string into an identifier object.
8
9
10
11
12
13
|
# File 'lib/pubid/itu/identifiers/base.rb', line 8
def self.parse(identifier)
parsed = Parser.parse(normalize_whitespace(identifier))
Builder.build(parsed)
rescue Parslet::ParseFailed => e
raise "Failed to parse ITU identifier '#{identifier}': #{e.message}"
end
|
Instance Method Details
#==(other) ⇒ Object
Class-strict, like Supplement#== — the ITU type is part of the
identity, not just the sector/series/number. Report ITU-R BT.2020-1 and
Recommendation ITU-R BT.2020-1 are two different, both-current
documents; an is_a? guard would make them equal (and #matches?
resolve one to the other). Using self.class rather than a hard-coded
class keeps the comparison symmetric in both directions, and closes the
same one-way holes that existed against CombinedIdentifier and
SpecialPublication.
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
|
# File 'lib/pubid/itu/identifiers/base.rb', line 258
def ==(other)
return false unless other.instance_of?(self.class)
sector == other.sector &&
series == other.series &&
code == other.code &&
date == other.date &&
version == other.version &&
series_word == other.series_word &&
series_dash == other.series_dash &&
attachment == other.attachment &&
range_end == other.range_end &&
language == other.language &&
common_text_twin == other.common_text_twin
end
|
#base_from_kv(model, value) ⇒ Object
403
404
405
406
407
|
# File 'lib/pubid/itu/identifiers/base.rb', line 403
def base_from_kv(model, value)
return unless value
model.base = Pubid::Itu::Identifier.from_hash(value)
end
|
#base_to_kv(model, doc) ⇒ Object
Serialize a nested base identifier via its own to_hash so it collapses
to the compact shape, and re-dispatch through the flavor base on load so
_type resolves to the concrete subclass (a bare polymorphic cast would
rebuild a plain Identifier and lose the subclass). Mirrors ETSI/JCGM.
395
396
397
398
399
400
401
|
# File 'lib/pubid/itu/identifiers/base.rb', line 395
def base_to_kv(model, doc)
return unless model.base
doc.add_child(
Lutaml::KeyValue::DataModel::Element.new("base", model.base.to_hash),
)
end
|
#code_for(model) ⇒ Object
437
438
439
|
# File 'lib/pubid/itu/identifiers/base.rb', line 437
def code_for(model)
model.code ||= Components::Code.new
end
|
#common_text_twin_from_kv(model, value) ⇒ Object
423
424
425
426
427
|
# File 'lib/pubid/itu/identifiers/base.rb', line 423
def common_text_twin_from_kv(model, value)
return unless value
model.common_text_twin = ::Pubid::Identifier.from_hash(value)
end
|
#common_text_twin_to_kv(model, doc) ⇒ Object
The common-text twin ("| ISO/IEC 13818-1:2022") is another flavor's
identifier (ISO/IEC …); serialize it via its own flat to_hash and
reconstruct it cross-flavor through the top-level polymorphic router so
its concrete class (and custom mappings) are restored by _type.
413
414
415
416
417
418
419
420
421
|
# File 'lib/pubid/itu/identifiers/base.rb', line 413
def common_text_twin_to_kv(model, doc)
return unless model.common_text_twin
doc.add_child(
Lutaml::KeyValue::DataModel::Element.new(
"common_text_twin", model.common_text_twin.to_hash
),
)
end
|
#date_for(model) ⇒ Object
441
442
443
|
# File 'lib/pubid/itu/identifiers/base.rb', line 441
def date_for(model)
model.date ||= Pubid::Components::Date.new
end
|
#emit_kv(doc, key, value) ⇒ Object
429
430
431
432
433
434
435
|
# File 'lib/pubid/itu/identifiers/base.rb', line 429
def emit_kv(doc, key, value)
return if value.nil? || value.to_s.empty?
doc.add_child(
Lutaml::KeyValue::DataModel::Element.new(key, value.to_s),
)
end
|
#exclude_from_nested(value, args) ⇒ Object
ITU keeps the document number and its edition/part together inside one
Code component (like ETSI, unlike ISO's separate part attribute), so a
top-level exclude(:part) can't reach the part. Handle it during the
nested-exclusion pass: when :part/:parts is excluded, return a copy of
the Code with its parts cleared while keeping number/series/subseries.
This lets a part-less reference (e.g. ITU-R P.838) match every edition
(ITU-R P.838-3) via matches?(other, ignore: [:parts]). Any non-Code
value falls through to super, so nested identifiers (common_text_twin,
supplement base, combined Designations) still recurse.
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
# File 'lib/pubid/itu/identifiers/base.rb', line 110
def exclude_from_nested(value, args)
part_keys = args & %i[part parts]
if value.is_a?(Pubid::Itu::Components::Code) && !part_keys.empty?
return Pubid::Itu::Components::Code.new(
imp_marker: value.imp_marker,
number: value.number,
series_suffix: value.series_suffix,
series_suffix_spaced: value.series_suffix_spaced,
subseries: value.subseries,
parts: [],
qualifier: value.qualifier,
qualifier_glued: value.qualifier_glued,
)
end
super
end
|
#imp_marker_from_kv(model, value) ⇒ Object
303
304
305
|
# File 'lib/pubid/itu/identifiers/base.rb', line 303
def imp_marker_from_kv(model, value)
code_for(model).imp_marker = value.to_s
end
|
#imp_marker_to_kv(model, doc) ⇒ Object
299
300
301
|
# File 'lib/pubid/itu/identifiers/base.rb', line 299
def imp_marker_to_kv(model, doc)
emit_kv(doc, "imp_marker", model.code&.imp_marker)
end
|
#month_from_kv(model, value) ⇒ Object
387
388
389
|
# File 'lib/pubid/itu/identifiers/base.rb', line 387
def month_from_kv(model, value)
date_for(model).month = value.to_s
end
|
#month_to_kv(model, doc) ⇒ Object
383
384
385
|
# File 'lib/pubid/itu/identifiers/base.rb', line 383
def month_to_kv(model, doc)
emit_kv(doc, "month", model.date&.month)
end
|
#mr_number_with_part ⇒ Object
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/pubid/itu/identifiers/base.rb', line 146
def mr_number_with_part
segments = []
segments << series&.series&.to_s&.downcase if series&.series
number = code&.number&.to_s
if number
number += code.series_suffix.to_s if code.series_suffix
number += code.qualifier.to_s.downcase if code.qualifier
segments << number
end
segments << code&.subseries&.to_s if code&.subseries
segments.concat(code&.parts&.map(&:to_s) || [])
segments << "series" if series_word
segments << "attachment" if attachment
segments << "to-#{range_end.to_s.downcase.tr('.', '-')}" if range_end
return nil if segments.empty?
segments.join("-")
end
|
#mr_publisher ⇒ Object
ITU encodes identity across sector (T/R/CD), series (G/H/J…), and
code (number with optional part), not in the inherited number/
typed_stage. The generic MrString renderer would otherwise drop all
three and emit just ITU. Losslessness for issue #142 requires the
sector letter, series letter, and document number to appear in MR.
Lowercased to match the all-lowercase MR convention.
138
139
140
|
# File 'lib/pubid/itu/identifiers/base.rb', line 138
def mr_publisher
publisher&.to_s&.downcase
end
|
#mr_type ⇒ Object
142
143
144
|
# File 'lib/pubid/itu/identifiers/base.rb', line 142
def mr_type
sector&.sector&.to_s&.downcase
end
|
#number ⇒ Object
The document number lives on the code component for ITU; surface it at
the identifier root so consumers that key on #number — e.g.
Relaton::Index's sort/bsearch narrowing on id.root.number.to_s — work
without special-casing ITU. Returns the code.number string (e.g.
"530"); serialization is unaffected because the flat key_value block
maps "number" via number_to_kv/number_from_kv, which read/write
code.number directly and never touch the inherited number attribute.
Supplement/Amendment/Corrigendum/Errata declare their own :string
number (the ordinal), which overrides this reader; their base document
number is still reachable via root.number.
97
98
99
|
# File 'lib/pubid/itu/identifiers/base.rb', line 97
def number
code&.number
end
|
#number_from_kv(model, value) ⇒ Object
311
312
313
|
# File 'lib/pubid/itu/identifiers/base.rb', line 311
def number_from_kv(model, value)
code_for(model).number = value.to_s
end
|
#number_to_kv(model, doc) ⇒ Object
307
308
309
|
# File 'lib/pubid/itu/identifiers/base.rb', line 307
def number_to_kv(model, doc)
emit_kv(doc, "number", model.code&.number)
end
|
#parts_from_kv(model, value) ⇒ Object
371
372
373
|
# File 'lib/pubid/itu/identifiers/base.rb', line 371
def parts_from_kv(model, value)
code_for(model).parts = Array(value).map(&:to_s)
end
|
#parts_to_kv(model, doc) ⇒ Object
362
363
364
365
366
367
368
369
|
# File 'lib/pubid/itu/identifiers/base.rb', line 362
def parts_to_kv(model, doc)
parts = model.code&.parts
return if parts.nil? || parts.empty?
doc.add_child(
Lutaml::KeyValue::DataModel::Element.new("parts", parts.map(&:to_s)),
)
end
|
#qualifier_from_kv(model, value) ⇒ Object
340
341
342
|
# File 'lib/pubid/itu/identifiers/base.rb', line 340
def qualifier_from_kv(model, value)
code_for(model).qualifier = value.to_s
end
|
#qualifier_glued_from_kv(model, value) ⇒ Object
350
351
352
|
# File 'lib/pubid/itu/identifiers/base.rb', line 350
def qualifier_glued_from_kv(model, value)
code_for(model).qualifier_glued = value
end
|
#qualifier_glued_to_kv(model, doc) ⇒ Object
344
345
346
347
348
|
# File 'lib/pubid/itu/identifiers/base.rb', line 344
def qualifier_glued_to_kv(model, doc)
return unless model.code&.qualifier_glued
doc["qualifier_glued"] = true
end
|
#qualifier_to_kv(model, doc) ⇒ Object
336
337
338
|
# File 'lib/pubid/itu/identifiers/base.rb', line 336
def qualifier_to_kv(model, doc)
emit_kv(doc, "qualifier", model.code&.qualifier)
end
|
#render_base(**_opts) ⇒ Object
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
# File 'lib/pubid/itu/identifiers/base.rb', line 193
def render_base(**_opts)
result = "#{publisher}-#{sector}"
result += if series && code
" #{series}#{series_dash ? '-' : '.'}#{code}"
elsif series
" #{series}"
else
" #{code}"
end
result += "-#{range_end}" if range_end
result += " series" if series_word
result += " attachment" if attachment
result += " (V#{version})" if version
result + render_date_suffix
end
|
#render_date_suffix ⇒ Object
" (MM/YYYY)", or " (YYYY)" when the month is unknown; "" when undated.
Shared by every type that prints its date this way (Supplement and its
subclasses, CombinedIdentifier, AnnexOfRecommendation). NOT used by
SpecialPublication, which zero-pads its month.
221
222
223
224
225
|
# File 'lib/pubid/itu/identifiers/base.rb', line 221
def render_date_suffix
return "" unless date
date.month ? " (#{date.month}/#{date.year})" : " (#{date.year})"
end
|
#render_language_suffix ⇒ Object
227
228
229
230
231
232
233
234
235
|
# File 'lib/pubid/itu/identifiers/base.rb', line 227
def render_language_suffix
return "" unless language
return "" unless LANGUAGES.value?(language)
"-#{language}"
end
|
#sector_from_kv(model, value) ⇒ Object
287
288
289
|
# File 'lib/pubid/itu/identifiers/base.rb', line 287
def sector_from_kv(model, value)
model.sector = Components::Sector.new(sector: value.to_s)
end
|
#sector_to_kv(model, doc) ⇒ Object
--- Compact flat key_value converters (shared) --------------------
Collapse the identity components (Sector/Series/Code/Date) to bare
top-level scalars so the serialized hash is flat and index-friendly,
mirroring ISO/ETSI/JCGM/OIML. Used by the per-leaf key_value blocks
(see StandardSerialization) and by the Supplement/Annex/Combined
blocks. publisher ("ITU") is intentionally never mapped — it is
reconstructed from its attribute default.
283
284
285
|
# File 'lib/pubid/itu/identifiers/base.rb', line 283
def sector_to_kv(model, doc)
emit_kv(doc, "sector", model.sector&.sector)
end
|
#series_from_kv(model, value) ⇒ Object
295
296
297
|
# File 'lib/pubid/itu/identifiers/base.rb', line 295
def series_from_kv(model, value)
model.series = Components::Series.new(series: value.to_s)
end
|
#series_suffix_from_kv(model, value) ⇒ Object
319
320
321
|
# File 'lib/pubid/itu/identifiers/base.rb', line 319
def series_suffix_from_kv(model, value)
code_for(model).series_suffix = value.to_s
end
|
#series_suffix_spaced_from_kv(model, value) ⇒ Object
332
333
334
|
# File 'lib/pubid/itu/identifiers/base.rb', line 332
def series_suffix_spaced_from_kv(model, value)
code_for(model).series_suffix_spaced = value
end
|
#series_suffix_spaced_to_kv(model, doc) ⇒ Object
The three spelling/qualifier fields of Components::Code. The two
booleans emit only when true, so no already-published index row gains
a key; qualifier emits only when present.
326
327
328
329
330
|
# File 'lib/pubid/itu/identifiers/base.rb', line 326
def series_suffix_spaced_to_kv(model, doc)
return unless model.code&.series_suffix_spaced
doc["series_suffix_spaced"] = true
end
|
#series_suffix_to_kv(model, doc) ⇒ Object
315
316
317
|
# File 'lib/pubid/itu/identifiers/base.rb', line 315
def series_suffix_to_kv(model, doc)
emit_kv(doc, "series_suffix", model.code&.series_suffix)
end
|
#series_to_kv(model, doc) ⇒ Object
291
292
293
|
# File 'lib/pubid/itu/identifiers/base.rb', line 291
def series_to_kv(model, doc)
emit_kv(doc, "series", model.series&.series)
end
|
#subseries_from_kv(model, value) ⇒ Object
358
359
360
|
# File 'lib/pubid/itu/identifiers/base.rb', line 358
def subseries_from_kv(model, value)
code_for(model).subseries = value.to_s
end
|
#subseries_to_kv(model, doc) ⇒ Object
354
355
356
|
# File 'lib/pubid/itu/identifiers/base.rb', line 354
def subseries_to_kv(model, doc)
emit_kv(doc, "subseries", model.code&.subseries)
end
|
#to_s(**opts) ⇒ Object
Render identifier as a string.
186
187
188
189
190
191
|
# File 'lib/pubid/itu/identifiers/base.rb', line 186
def to_s(**opts)
opts = self.class.normalize_to_s_opts(opts)
result = render_base(**opts) + render_language_suffix
result += " | #{common_text_twin}" if common_text_twin
result
end
|
#year_from_kv(model, value) ⇒ Object
379
380
381
|
# File 'lib/pubid/itu/identifiers/base.rb', line 379
def year_from_kv(model, value)
date_for(model).year = value.to_s
end
|
#year_to_kv(model, doc) ⇒ Object
375
376
377
|
# File 'lib/pubid/itu/identifiers/base.rb', line 375
def year_to_kv(model, doc)
emit_kv(doc, "year", model.date&.year)
end
|