Class: Pubid::Core::Identifier::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/pubid/core/identifier/base.rb

Constant Summary collapse

TYPED_STAGES =
{}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(publisher:, number:, copublisher: nil, part: nil, year: nil, edition: nil, language: nil, amendments: nil, corrigendums: nil, stage: nil, all_parts: false) ⇒ Base

Creates new identifier from options provided:

Parameters:

  • publisher (String)

    document’s publisher, eg. “ISO”

  • copublisher (String, Array<String>) (defaults to: nil)

    document’s copublisher, eg. “IEC”

  • number (Integer)

    document’s number, eg. “1234”

  • part (String) (defaults to: nil)

    document’s part and subparts, eg. “1”, “1-1A”, “2-3D”

  • type (String)

    document’s type, eg. “TR”, “TS”

  • year (Integer) (defaults to: nil)

    document’s year, eg. “2020”

  • edition (Integer) (defaults to: nil)

    document’s edition, eg. “1”

  • language (String) (defaults to: nil)

    document’s translation language (available languages: “ru”, “fr”, “en”, “ar”)

  • amendments (Array<Amendment>, Array<Hash>) (defaults to: nil)

    document’s amendments

  • corrigendums (Array<Corrigendum>, Array<Hash>) (defaults to: nil)

    document’s corrigendums

See Also:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/pubid/core/identifier/base.rb', line 23

def initialize(publisher:, number:, copublisher: nil, part: nil,
               year: nil, edition: nil, language: nil, amendments: nil,
               corrigendums: nil, stage: nil, all_parts: false)

  if amendments
    @amendments = amendments.map do |amendment|
      if amendment.is_a?(Hash)
        self.class.get_transformer_class.new.apply(:amendments => [amendment])[:amendments].first
      else
        amendment
      end
    end
  end

  if corrigendums
    @corrigendums = corrigendums.map do |corrigendum|
      if corrigendum.is_a?(Hash)
        self.class.get_transformer_class.new.apply(:corrigendums => [corrigendum])[:corrigendums].first
      else
        corrigendum
      end
    end
  end

  @publisher = publisher.to_s
  @number = number&.to_s
  @copublisher = Array(copublisher).map(&:to_s) if copublisher
  @part = part.to_s if part
  @year = year.to_i if year
  @edition = edition.to_i if edition
  @language = language.to_s if language

  @stage = resolve_stage(stage) if stage
  @all_parts = all_parts if all_parts
end

Instance Attribute Details

#all_partsObject

Returns the value of attribute all_parts.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def all_parts
  @all_parts
end

#amendmentsObject

Returns the value of attribute amendments.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def amendments
  @amendments
end

#copublisherObject

Returns the value of attribute copublisher.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def copublisher
  @copublisher
end

#corrigendumsObject

Returns the value of attribute corrigendums.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def corrigendums
  @corrigendums
end

#editionObject

Returns the value of attribute edition.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def edition
  @edition
end

#languageObject

Returns the value of attribute language.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def language
  @language
end

#numberObject

Returns the value of attribute number.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def number
  @number
end

#partObject

Returns the value of attribute part.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def part
  @part
end

#publisherObject

Returns the value of attribute publisher.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def publisher
  @publisher
end

#stageObject

Returns the value of attribute stage.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def stage
  @stage
end

#yearObject

Returns the value of attribute year.



4
5
6
# File 'lib/pubid/core/identifier/base.rb', line 4

def year
  @year
end

Class Method Details

.array_to_hash(params) ⇒ Object

Converts array of hashes into single hash array like [{ publisher: “ISO” }, { number: 1 }] to hash { publisher: “ISO”, number: 1 }

Parameters:

  • params (Array<Hash>)

    input array of hashes, eg. [{ a: 1 }, { b: 2 }]



218
219
220
221
222
223
224
225
226
# File 'lib/pubid/core/identifier/base.rb', line 218

def array_to_hash(params)
  params.inject({}) do |r, i|
    result = r
    i.each do |k, v|
      result = result.merge(k => r.key?(k) ? [r[k], v].flatten : v)
    end
    result
  end
end

.find_typed_stage(typed_stage) ⇒ [Symbol, Stage]

Returns typed stage and stage with assigned harmonized codes.

Parameters:

  • typed_stage (String, Symbol)

    eg. “DTR” or :dtr

Returns:

  • ([Symbol, Stage])

    typed stage and stage with assigned harmonized codes



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/pubid/core/identifier/base.rb', line 310

def find_typed_stage(typed_stage)
  if typed_stage.is_a?(Symbol)
    return get_identifier
        .build_typed_stage(
          harmonized_code:
            get_identifier.build_harmonized_stage_code(self::TYPED_STAGES[typed_stage][:harmonized_stages]),
          abbr: typed_stage,
        )
  end

  typed_stage = self::TYPED_STAGES.find do |_, v|
    if v[:abbr].is_a?(Hash)
      v[:abbr].value?(typed_stage)
    elsif v.key?(:legacy_abbr)
      v[:legacy_abbr].include?(typed_stage) || v[:abbr] == typed_stage
    else
      v[:abbr] == typed_stage
    end
  end

  get_identifier.build_typed_stage(harmonized_code:
                               get_identifier.build_harmonized_stage_code(typed_stage[1][:harmonized_stages]),
                             abbr: typed_stage.first)
end

.get_amendment_classObject



251
252
253
# File 'lib/pubid/core/identifier/base.rb', line 251

def get_amendment_class
  Amendment
end

.get_corrigendum_classObject



255
256
257
# File 'lib/pubid/core/identifier/base.rb', line 255

def get_corrigendum_class
  Corrigendum
end

.get_identifierObject



272
273
274
# File 'lib/pubid/core/identifier/base.rb', line 272

def get_identifier
  Identifier
end

.get_renderer_classObject



259
260
261
# File 'lib/pubid/core/identifier/base.rb', line 259

def get_renderer_class
  Renderer::Base
end

.get_transformer_classObject



263
264
265
# File 'lib/pubid/core/identifier/base.rb', line 263

def get_transformer_class
  Transformer
end

.get_update_codesHash?

Returns replacement patterns.

Returns:

  • (Hash, nil)

    replacement patterns



268
269
270
# File 'lib/pubid/core/identifier/base.rb', line 268

def get_update_codes
  nil
end

.has_type?(type) ⇒ Boolean

Returns true if provided type matches with identifier’s class type.

Parameters:

  • type (Symbol, String)

    eg. :tr, :ts, “TS”

Returns:

  • (Boolean)

    true if provided type matches with identifier’s class type



245
246
247
248
249
# File 'lib/pubid/core/identifier/base.rb', line 245

def has_type?(type)
  return type == self.type[:key] if type.is_a?(Symbol)

  self.type.key?(:values) ? self.type[:values].include?(type) : type.to_s.downcase.to_sym == self.type[:key]
end

.has_typed_stage?(typed_stage) ⇒ Boolean

Returns true when identifier has associated typed stage.

Parameters:

  • typed_stage (String, Symbol)

    typed stage, eg. “DTR” or :dtr

Returns:

  • (Boolean)

    true when identifier has associated typed stage



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/pubid/core/identifier/base.rb', line 287

def has_typed_stage?(typed_stage)
  return self::TYPED_STAGES.key?(typed_stage) if typed_stage.is_a?(Symbol)

  self::TYPED_STAGES.any? do |_, v|
    if v[:abbr].is_a?(Hash)
      v[:abbr].value?(typed_stage)
    else
      if v.key?(:legacy_abbr)
        v[:legacy_abbr].include?(typed_stage) || v[:abbr] == typed_stage
      else
        v[:abbr] == typed_stage
      end
    end
  end
end

.parse(code_or_params) ⇒ Pubid::Core::Identifier

Parses given identifier

Parameters:

  • code_or_params (String, Hash)

    code or hash from parser eg. “ISO 1234”, { }

Returns:



207
208
209
210
211
212
213
# File 'lib/pubid/core/identifier/base.rb', line 207

def parse(code_or_params)
  params = code_or_params.is_a?(String) ?
             get_parser_class.new.parse(update_old_code(code_or_params)) : code_or_params
  transform(params.is_a?(Array) ? array_to_hash(params) : params)
rescue Parslet::ParseFailed => failure
  raise Errors::ParseError, "#{failure.message}\ncause: #{failure.parse_failure_cause.ascii_tree}"
end

.resolve_typed_stage(harmonized_code) ⇒ Symbol?

Resolve typed stage using stage harmonized stage code

Parameters:

Returns:

  • (Symbol, nil)

    typed stage or nil



338
339
340
341
342
343
344
345
# File 'lib/pubid/core/identifier/base.rb', line 338

def resolve_typed_stage(harmonized_code)
  self::TYPED_STAGES.each do |k, v|
    if (v[:harmonized_stages] & harmonized_code.stages) == harmonized_code.stages
      return get_identifier.build_typed_stage(abbr: k, harmonized_code: harmonized_code)
    end
  end
  nil
end

.transform(params) ⇒ Object

Transform parameters hash or array or hashes to identifier



229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/pubid/core/identifier/base.rb', line 229

def transform(params)
  # run transform through each element,
  # like running transformer.apply(number: 1) and transformer.apply(year: 1999)
  # instead of running transformer on whole hash, like running transformer.apply({ number: 1, year: 1999 })
  # where rule for number or year only will be not applied
  # transformation only applied to rules matching the whole hash

  identifier_params = params.map do |k, v|
                        get_transformer_class.new.apply(k => v).to_a.first
                      end.to_h

  new(**identifier_params)
end

.type_match?(parameters) ⇒ Boolean

Returns true when identifier’s type match with provided parameters

Returns:

  • (Boolean)


304
305
306
# File 'lib/pubid/core/identifier/base.rb', line 304

def type_match?(parameters)
  parameters[:type] ? has_type?(parameters[:type]) : has_typed_stage?(parameters[:stage])
end

.update_old_code(code) ⇒ Object



276
277
278
279
280
281
282
283
# File 'lib/pubid/core/identifier/base.rb', line 276

def update_old_code(code)
  return code unless get_update_codes

  get_update_codes.each do |from, to|
    code = code.gsub(from.match?(/^\/.*\/$/) ? Regexp.new(from[1..-2]) : /^#{Regexp.escape(from)}$/, to)
  end
  code
end

Instance Method Details

#==(other) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/pubid/core/identifier/base.rb', line 92

def ==(other)
  case other
  when String
    to_s == other
  when Identifier::Base
    to_h == other.to_h
  when Hash
    to_h == other
  else
    raise Errors::WrongTypeError, "cannot compare with #{other.class} type"
  end
end

#exclude(*args) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/pubid/core/identifier/base.rb', line 111

def exclude(*args)
  nested_exclusions, top_level_exclusions = args.partition { |arg| arg.is_a?(Hash) }

  nested_exclusions = nested_exclusions.reduce({}, :merge)

  excluded_hash = to_h(add_type: false)
    .reject { |k, v| top_level_exclusions.include?(k) }
    .each_with_object({}) do |(k, v), memo|
      memo[k] = if v.is_a?(Hash) && nested_exclusions.key?(k)
                  v.reject { |key, _| nested_exclusions[k].include?(key) }
                else
                  v
                end
    end

  self.class.new(**excluded_hash)
end

#new_edition_of?(other) ⇒ Boolean

Checks if another identifier is newer edition of the same document

Parameters:

Returns:

  • (Boolean)

    true if another identifier is newer edition



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/pubid/core/identifier/base.rb', line 175

def new_edition_of?(other)
  if exclude(:year, :edition) != other.exclude(:year, :edition)
    raise Errors::AnotherDocumentError, "cannot compare edition with #{other}"
  end

  if year.nil? || other.year.nil?
    raise Errors::CannotCompareError, "cannot compare identifier without edition year"
  end

  if year == other.year && (edition || other.edition)
    return false if other.edition.nil?

    return true if edition.nil?

    return edition > other.edition
  end

  year > other.year
end

#resolve_stage(stage) ⇒ [nil, Stage], [Symbol, Stage]

Returns typed stage and stage values.

Parameters:

  • stage (Stage, Symbol, String)

    stage or typed stage, e.g. “PWI”, “NP”, “50.00”, Stage.new(abbr: :WD), “DTR”

Returns:

  • ([nil, Stage], [Symbol, Stage])

    typed stage and stage values



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/core/identifier/base.rb', line 148

def resolve_stage(stage)
  if stage.is_a?(Stage)
    return self.class.resolve_typed_stage(stage.harmonized_code) || stage unless stage.abbr

    return stage
  end

  if self.class.has_typed_stage?(stage)
    return self.class.find_typed_stage(stage)
  end

  parsed_stage = self.class.get_identifier.parse_stage(stage)
  # resolve typed stage when harmonized code provided as stage
  # or stage abbreviation was not resolved
  if /\A[\d.]+\z/.match?(stage) || parsed_stage.empty_abbr?(with_prf: true)
    return self.class.resolve_typed_stage(parsed_stage.harmonized_code) || parsed_stage
  end

  parsed_stage

  # from IEC
  # @typed_stage = self.class::TYPED_STAGES[@typed_stage][:abbr] if @typed_stage
end

#rootObject

returns root identifier



196
197
198
199
200
# File 'lib/pubid/core/identifier/base.rb', line 196

def root
  return base.base if base&.class&.method_defined?(:base) && base&.base

  base || self
end

#to_h(deep: true, add_type: true) ⇒ Hash

Returns Identifier’s parameters.

Returns:

  • (Hash)

    Identifier’s parameters



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/pubid/core/identifier/base.rb', line 65

def to_h(deep: true, add_type: true)
  result = instance_variables.map do |var|
    value = instance_variable_get(var)

    [var.to_s.gsub("@", "").to_sym,
     if value.is_a?(Array)
       value.map { |v| (v.respond_to?(:to_h) && deep) ? v.to_h : v }
     elsif value.nil?
       nil
     else
       (value.respond_to?(:to_h) && deep) ? value.to_h : value
     end
    ]
  end.to_h

  if add_type && respond_to?(:type) && type[:short]
    result[:type] = type[:short]
  end

  result.reject { |k, v| k != :number && v.nil? }
end

#to_s(annotated: false) ⇒ Object

Render identifier using default renderer

Parameters:

  • annotated (Boolean) (defaults to: false)

    wrap semantic components in <span> tags



107
108
109
# File 'lib/pubid/core/identifier/base.rb', line 107

def to_s(annotated: false)
  self.class.get_renderer_class.new(to_h(deep: false)).render(annotated: annotated)
end

#to_yamlObject



87
88
89
90
# File 'lib/pubid/core/identifier/base.rb', line 87

def to_yaml
  # use #to_h for serialization to avoid !ruby/object in output
  to_h.to_yaml
end

#typed_stage_abbrevObject



129
130
131
132
133
134
135
# File 'lib/pubid/core/identifier/base.rb', line 129

def typed_stage_abbrev
  if stage.is_a?(TypedStage)
    return stage.to_s
  end

  stage ? "#{stage.abbr} #{self.class.type[:key].to_s.upcase}" : self.class.type[:key].to_s.upcase
end

#typed_stage_nameObject

Return typed stage name, eg. “Final Draft Technical Report” for “FDTR”



138
139
140
141
142
143
144
# File 'lib/pubid/core/identifier/base.rb', line 138

def typed_stage_name
  if stage.is_a?(TypedStage) && self.class::TYPED_STAGES.key?(stage.abbr)
    return self.class::TYPED_STAGES[stage.abbr][:name]
  end

  stage ? "#{stage.name} #{self.class.type[:title]}" : self.class.type[:title]
end

#urnString

Returns Rendered URN identifier.

Returns:

  • (String)

    Rendered URN identifier



60
61
62
# File 'lib/pubid/core/identifier/base.rb', line 60

def urn
  Renderer::Urn.new(to_h).render
end