Class: Pubid::Ieee::Identifier

Inherits:
Pubid::Identifier show all
Defined in:
lib/pubid/ieee/identifiers/base.rb

Overview

Base class for all IEEE identifiers. Canonical name Pubid::Ieee::Identifier. IEEE builds its identifiers as instances of this class directly.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Pubid::Identifier

apply_mappings, #base, #base_document, concrete_class_for, #dated_version_of?, #draft_of?, #drop_supplements, #edition_of?, #eql?, #has_supplement?, #hash, #includes?, #matches?, #mr_all_parts, #mr_edition, #mr_languages, #mr_number, #mr_part, #mr_subpart, #mr_supplement_suffix, #new_edition_of?, polymorphic_name, polymorphic_type_map, #related_to?, #render, #resolve_urn_generator, #root, #sibling_of?, #supplement_of?, #to_mr_string, #to_s, #to_slug, #to_supplement_s, #to_urn, #urn_supplement_type, #urn_type_code, #year

Constructor Details

#initialize(args = {}, **kwargs) ⇒ Identifier

Accept either keyword args (new(number: "802"), the normal path) or a single positional attribute hash (new({number: "802"})). The latter is what the base Pubid::Identifier#exclude/matching machinery uses when it rebuilds via self.class.new(attrs); without it, exclude/matches? raised ArgumentError for every IEEE identifier.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/pubid/ieee/identifiers/base.rb', line 75

def initialize(args = {}, **kwargs)
  args = args.merge(kwargs) unless kwargs.empty?
  super()

  # Handle typed_stage if provided
  if args[:typed_stage]
    self.typed_stage = args[:typed_stage]
  end

  # Handle code as component object. `code` is runtime-only (not a lutaml
  # attribute) — we keep the parsed Components::Code in code_obj; every
  # leaf serializes the split index columns (number/prefix/parts/separator)
  # via the CodeNumber mixin instead of a `code` string. The renderer reads
  # code_obj (base #code returns it).
  if args[:code].is_a?(String)
    self.code_obj = Components::Code.parse(args[:code])
  elsif args[:code]
    self.code_obj = args[:code]
  end

  # Handle draft as component object
  if args[:draft_obj]
    self.draft_obj = args[:draft_obj]
    self.draft = args[:draft_obj].to_s
  elsif args[:draft].is_a?(String)
    self.draft_obj = Components::Draft.parse(args[:draft])
    self.draft = draft_obj.to_s
  elsif args[:draft]
    self.draft_obj = args[:draft]
    self.draft = args[:draft].to_s
  end

  # Set other attributes
  attrs = self.class.attributes
  args.each do |key, value|
    next if %i[code draft draft_obj typed_stage].include?(key)

    setter = :"#{key}="
    public_send(setter, value) if attrs.key?(key)
  end
end

Instance Attribute Details

#code_objObject

Lazily rebuild the parsed component objects from the underlying string attributes. After from_hash, lutaml restores the :code/:draft strings (@code/@draft) but not these objects; the renderer reads code_obj/ draft_obj directly, so rebuild on demand to render a deserialized identifier identically to the parsed one. On the parse path code_obj/ draft_obj are already set, so the ||= returns them unchanged.



68
69
70
# File 'lib/pubid/ieee/identifiers/base.rb', line 68

def code_obj
  @code_obj
end

#draft_objObject

Store actual component objects



68
69
70
# File 'lib/pubid/ieee/identifiers/base.rb', line 68

def draft_obj
  @draft_obj
end

Class Method Details

.additional_identifier_classesObject

Register the identifier classes the automatic Identifiers::* scan cannot see, so Pubid::Ieee::Identifier.from_hash can route their rows back. The scan only looks at classes declared directly under Identifiers, which misses both AIEE and IRE (they live under their own Aiee/Ire namespaces) and the whole NESC family (nested one level deeper, under Identifiers::Nesc).



193
194
195
196
197
198
199
200
201
202
203
# File 'lib/pubid/ieee/identifiers/base.rb', line 193

def self.additional_identifier_classes
  [
    Aiee::Identifier,
    Ire::Identifier,
    Identifiers::Nesc::Draft,
    Identifiers::Nesc::Edition,
    Identifiers::Nesc::Handbook,
    Identifiers::Nesc::Redline,
    Identifiers::Nesc::Standard,
  ]
end

.from_hash(data, options = {}) ⇒ Object

Inverse of the to_hash compaction: expand a scalar stage back into the typed_stage sub-hash (on a deep copy, recursively) before lutaml deserializes, so nested bases rebuild their component too. draft needs no expansion (Draft.parse accepts the slashless form).



182
183
184
185
# File 'lib/pubid/ieee/identifiers/base.rb', line 182

def self.from_hash(data, options = {})
  data = Compaction.expand(Compaction.deep_dup(data)) if data.is_a?(::Hash)
  super
end

.parse(input) ⇒ Object

Parse IEEE identifier string.

PreParser owns all regex/dispatch logic; this method is a thin orchestrator that consumes a PreParser::Result and routes to the correct builder.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/pubid/ieee/identifiers/base.rb', line 210

def self.parse(input)
  if input.length > Pubid::MAX_INPUT_LENGTH
    raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
  end

  result = PreParser.preprocess(input)

  case result.dispatch
  when :aiee_simple
    return Aiee::Identifier.parse(result.input)
  when :iec_ieee_copublished
    return parse_single(result.input)
  when :dual_semicolon
    return build_dual(result.parts)
  when :dual_reaffirmed
    return build_reaffirmed(result)
  when :dual_ire
    return build_dual_with_reaffirmed(result)
  when :dual_space_separated
    return build_dual(result.parts)
  when :dual_and
    return build_dual(result.parts)
  when :dual_ampersand
    return build_dual(result.parts)
  when :aiee_asa_adoption
    return build_aiee_asa_adoption(result.parts)
  when :adopted
    return build_adopted(result.parts)
  else
    parse_single(result.input)
  end
rescue Parslet::ParseFailed
  parse_single(input)
end

.parse_single(input) ⇒ Object

Parse a single IEEE identifier



314
315
316
317
318
319
320
321
322
# File 'lib/pubid/ieee/identifiers/base.rb', line 314

def self.parse_single(input)
  # Apply legacy update_codes normalization first, before Parser's extensive preprocessing
  normalized = Core::UpdateCodes.apply(input, :ieee)
  parsed = Parser.parse(normalized) # Use class method for preprocessing
  builder = Builder.new(Identifier)
  # Pass the original input string to builder for context
  builder.original_input = input
  builder.build(parsed)
end

Instance Method Details

#codeObject

Override accessors to return component objects.



118
119
120
# File 'lib/pubid/ieee/identifiers/base.rb', line 118

def code
  code_obj
end

#draftObject



122
123
124
# File 'lib/pubid/ieee/identifiers/base.rb', line 122

def draft
  draft_obj
end

#draft_monthObject

Expose numeric month from draft if available



143
144
145
146
147
# File 'lib/pubid/ieee/identifiers/base.rb', line 143

def draft_month
  return nil unless draft_obj.is_a?(Components::Draft)

  draft_obj.numeric_month
end

#exclude(*args) ⇒ Object

IEEE stores its publication date as separate year/month/day :string attributes, not a Components::Date, so the base #exclude's :year->:date remap can't reach them (it would nil an unused date component and leave the strings). After the base rebuild, nil the whole date cluster when :year/:date is excluded, so a date-less reference matches every date in the bucket (relaton partial-ref matching). NB: the base #exclude also previously raised for every IEEE id (positional self.class.new(attrs) vs the keyword initialize) — fixed by Identifier#initialize accepting a positional hash.



158
159
160
161
162
163
164
165
166
# File 'lib/pubid/ieee/identifiers/base.rb', line 158

def exclude(*args)
  result = super
  if args.intersect?(%i[year date])
    %i[year month day].each do |attr|
      result.public_send("#{attr}=", nil) if result.respond_to?("#{attr}=")
    end
  end
  result
end

#mr_number_with_partObject



339
340
341
# File 'lib/pubid/ieee/identifiers/base.rb', line 339

def mr_number_with_part
  code_obj&.to_s&.downcase
end

#mr_publisherObject

IEEE stores identity in code (prefix/number/parts) rather than the generic number, has its own type string ("Std", "Draft Std"), and carries year as a bare string — none of which the generic MrString renderer knows about. Override the lossless MR template directly so every IEEE identifier round-trips (issue #142). Supplements append _{type}.{number}.{year} recursively via mr_supplement_suffix. Lowercased to match the all-lowercase MR convention.



331
332
333
# File 'lib/pubid/ieee/identifiers/base.rb', line 331

def mr_publisher
  publisher&.to_s&.downcase
end

#mr_typeObject



335
336
337
# File 'lib/pubid/ieee/identifiers/base.rb', line 335

def mr_type
  type&.downcase
end

#mr_yearObject



343
344
345
# File 'lib/pubid/ieee/identifiers/base.rb', line 343

def mr_year
  year&.to_s
end

#publisherString

Generate URN for this identifier

Returns:

  • (String)

    URN representation



17
# File 'lib/pubid/ieee/identifiers/base.rb', line 17

attribute :publisher, :string, default: -> { "IEEE" }

#to_hash(*args) ⇒ Object

Compact the serialized hash (recursively, so nested Corrigendum bases are compacted too) after the normal serialize + canonicalize: collapse typed_stage to a scalar stage and strip the / off draft. See Compaction for why this is a hash transform, not lutaml attributes.



172
173
174
175
176
# File 'lib/pubid/ieee/identifiers/base.rb', line 172

def to_hash(*args)
  hash = super
  Compaction.collapse(hash) if hash.is_a?(::Hash)
  hash
end