Class: Pubid::Nist::CircularSupplementBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/pubid/nist/circular_supplement_builder.rb

Overview

Builder dedicated to CIRC/LCIRC supplement identifier construction.

Extracted from Builder.build_circular_supplement* methods to keep the main Builder focused on orchestration. The circular supplement construction pipeline is a self-contained sub-flow that:

  1. Decides between the wrapper class (CircularSupplement) for V1-compat update forms (slash-year “118supp3/1926” -> “…/Upd1-192603”; implicit revision “145r11/1925”) and the normal Circular / LetterCircular classes for everything else.

  2. Builds the base identifier recursively via Builder#build.

  3. Attaches the supplement as either an isolated component on the normal class, or a structured edition/update on the wrapper.

Single Responsibility: Construct CIRC/LCIRC supplement identifier objects from parsed data, calling back to the parent Builder for base identifier construction.

Instance Method Summary collapse

Constructor Details

#initialize(builder) ⇒ CircularSupplementBuilder

Returns a new instance of CircularSupplementBuilder.

Parameters:

  • builder (Builder)

    the parent builder (for recursive base build)



24
25
26
# File 'lib/pubid/nist/circular_supplement_builder.rb', line 24

def initialize(builder)
  @builder = builder
end

Instance Method Details

#build_circular_supplement(parsed_hash) ⇒ Object

Build CircularSupplement with base_identifier wrapping Build a CIRC/LCIRC supplement. Most forms collapse onto the base identifier’s normal class (Circular / LetterCircular) with isolated supplement attributes, so they share one model with every other series and are queryable by part. The V1-compat update forms (slash-year “118supp3/1926” -> “…/Upd1-192603”; implicit revision “145r11/1925”) render through an Update component the flat supplement model can’t express, so they stay on the CircularSupplement wrapper for now.

Parameters:

  • parsed_hash (Hash)

    the parsed supplement data



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/pubid/nist/circular_supplement_builder.rb', line 37

def build_circular_supplement(parsed_hash)
  if parsed_hash[:supplement_slash_year].is_a?(Hash) ||
      parsed_hash[:implicit_supplement].is_a?(Hash)
    return build_circular_supplement_wrapper(parsed_hash)
  end

  series_value = if parsed_hash[:circ_series].is_a?(Hash)
                   parsed_hash[:circ_series][:series]
                 else
                   parsed_hash[:series]
                 end

  # Date-range supplement (no base document): a plain base identifier with
  # no number, carrying the range. parsed_format is left at the default so
  # a dotted MR input still normalizes to the spaced short form.
  if parsed_hash[:supplement_date_range].is_a?(Hash)
    range = parsed_hash[:supplement_date_range]
    identifier = @builder.build({ series: series_value })
    ms = range[:supp_month_start]&.to_s
    ys = range[:supp_year_start]&.to_s
    me = range[:supp_month_end]&.to_s
    ye = range[:supp_year_end]&.to_s
    identifier.supplement = @builder.supplement_from(
      value: nil, has_revision: false,
      range_start: (ms && ys ? "#{ms}#{ys}" : nil),
      range_end: (me && ye ? "#{me}#{ye}" : nil)
    )
    return identifier
  end

  # Based supplement: build the base, then attach the supplement as an
  # isolated component on that normal class.
  identifier = build_circular_supplement_base(parsed_hash, series_value)
  raw = if parsed_hash[:supplement_month_year]
          parsed_hash[:supplement_month_year].to_s
        elsif parsed_hash[:supplement_year]
          parsed_hash[:supplement_year].to_s
        else
          "" # supplement_empty or bare marker
        end
  identifier.supplement = Components::Supplement.from_raw(raw)
  identifier
end

#build_circular_supplement_base(parsed_hash, series_value) ⇒ Object

Build just the base identifier for a based CIRC/LCIRC supplement, from base_portion (number, optional edition “101e2” or letter suffix “378G”) or the merged first_number fallback. Returns the normal class.



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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/pubid/nist/circular_supplement_builder.rb', line 84

def build_circular_supplement_base(parsed_hash, series_value)
  base_portion = parsed_hash[:base_portion]
  unless base_portion
    return @builder.build({
                            publisher: parsed_hash[:publisher],
                            series: series_value || parsed_hash[:series],
                            first_number: parsed_hash[:first_number],
                            parsed_format: parsed_hash[:parsed_format],
                          })
  end

  base_number = if base_portion.is_a?(Hash)
                  base_portion[:simple_number] || base_portion[:base_number]
                else
                  base_portion
                end

  letter_suffix = nil
  if base_portion.is_a?(Hash) && base_portion[:letter_suffix]
    letter_suffix = base_portion[:letter_suffix].to_s.upcase
  end

  publisher_value = nil
  if parsed_hash[:circ_series].is_a?(Hash) && parsed_hash[:circ_series][:series]
    series_str = parsed_hash[:circ_series][:series].to_s
    publisher_value = series_str.split.first if series_str.include?(" ")
  end

  has_edition = base_portion.is_a?(Hash) && base_portion[:edition_number]

  base_number_with_suffix = base_number.to_s
  base_number_with_suffix += letter_suffix if letter_suffix
  if has_edition
    base_number_with_suffix += "e#{base_portion[:edition_number]}"
  end

  base_hash = {
    series: series_value,
    first_number: base_number_with_suffix,
    parsed_format: parsed_hash[:parsed_format],
  }
  base_hash[:publisher] = publisher_value if publisher_value
  base_hash[:edition_e] =
    { edition_id: base_portion[:edition_number] } if has_edition

  @builder.build(base_hash)
end

#build_circular_supplement_wrapper(parsed_hash) ⇒ Identifiers::CircularSupplement

Returns the supplement identifier.

Returns:



133
134
135
136
137
138
139
140
141
142
143
144
145
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/pubid/nist/circular_supplement_builder.rb', line 133

def build_circular_supplement_wrapper(parsed_hash)
  supplement = Identifiers::CircularSupplement.new

  # Extract series from circ_series if present (nested structure from parser)
  series_value = nil
  if parsed_hash[:circ_series].is_a?(Hash)
    series_value = parsed_hash[:circ_series][:series]
  elsif parsed_hash[:series]
    series_value = parsed_hash[:series]
  end

  # Handle date range supplement (no base)
  if parsed_hash[:supplement_date_range].is_a?(Hash)
    range = parsed_hash[:supplement_date_range]
    month_start = range[:supp_month_start]&.to_s
    year_start = range[:supp_year_start]&.to_s
    month_end = range[:supp_month_end]&.to_s
    year_end = range[:supp_year_end]&.to_s

    supplement.supplement_date_range_start = "#{month_start}#{year_start}" if month_start && year_start
    supplement.supplement_date_range_end = "#{month_end}#{year_end}" if month_end && year_end

    return supplement
  end

  # Build base identifier from base_portion (if present)
  # If not present (because it was merged during parsing), use first_number
  if parsed_hash[:base_portion]
    # Extract the actual number value from base_portion hash
    # base_portion can be: {:simple_number=>"118"}, {:base_number=>"145", :revision_letter=>"r", :revision_number=>"11"}, etc.
    base_portion = parsed_hash[:base_portion]
    base_number = if base_portion.is_a?(Hash)
                    # Extract the value from whichever key is present
                    base_portion[:simple_number] || base_portion[:base_number]
                  else
                    base_portion
                  end

    # Check for letter suffix in base_portion (e.g., "378G")
    letter_suffix = nil
    if base_portion.is_a?(Hash) && base_portion[:letter_suffix]
      letter_suffix = base_portion[:letter_suffix].to_s.upcase
    end

    # Extract publisher from circ_series if present
    publisher_value = nil
    if parsed_hash[:circ_series].is_a?(Hash) && parsed_hash[:circ_series][:series]
      series_str = parsed_hash[:circ_series][:series].to_s
      # Extract publisher from series (e.g., "NBS LCIRC" -> "NBS")
      publisher_value = series_str.split.first if series_str.include?(" ")
    end

    # Check if base_portion has revision (for patterns like "145r11/1925")
    has_revision = base_portion.is_a?(Hash) && base_portion[:revision_letter] && base_portion[:revision_number]

    # NEW: Check if base_portion has edition_number (for patterns like "101e2")
    has_edition = base_portion.is_a?(Hash) && base_portion[:edition_number]

    # Include letter suffix in base_number if present
    # Also include edition_number if present (for "101e2" pattern)
    base_number_with_suffix = base_number.to_s
    if letter_suffix
      base_number_with_suffix += letter_suffix
    end
    if has_edition
      base_number_with_suffix += "e#{base_portion[:edition_number]}"
    end

    # Reconstruct parse hash for base identifier
    base_hash = {
      series: series_value,
      first_number: base_number_with_suffix,
      parsed_format: parsed_hash[:parsed_format],
    }
    base_hash[:publisher] = publisher_value if publisher_value

    # NEW: Add edition_number to base_hash for patterns like "101e2"
    # This will be processed by the normal build() logic to create Edition component
    if has_edition
      # Create edition_e hash that will be converted to Edition with type="e"
      base_hash[:edition_e] =
        { edition_id: base_portion[:edition_number] }
    end

    # Recursively build base identifier
    # This will go through normal build() process which extracts edition from "101e2"
    supplement.base_identifier = @builder.build(base_hash)

    # NEW: Handle revision + implicit supplement pattern (e.g., "145r11/1925")
    # Create update format: "Upd1-{year}{revision_number}"
    if has_revision && parsed_hash[:implicit_supplement].is_a?(Hash)
      revision_number = base_portion[:revision_number].to_s
      supplement_year = parsed_hash[:implicit_supplement][:implicit_supplement_year].to_s

      # Create Update component for revision+supplement pattern.
      # Renders "/Upd1-{year}{revision_number}" via Update#to_s(:short).
      # The year+revision are fused into the year field (month left nil) so
      # the revision is NOT zero-padded (e.g. "145r6/1925" -> "Upd1-19256",
      # not "192506"). Must be a Components::Update (not a String) so
      # :update serializes.
      supplement.update = Components::Update.new(
        number: "1", year: "#{supplement_year}#{revision_number}",
        prefix: "slash"
      )
      supplement.implicit_supplement = true # Mark as implicit supplement for rendering
    end
  elsif parsed_hash[:first_number]
    # base_portion was lost during merge, use first_number to build base identifier
    base_hash = {
      publisher: parsed_hash[:publisher],
      series: series_value || parsed_hash[:series],
      first_number: parsed_hash[:first_number],
      parsed_format: parsed_hash[:parsed_format],
    }

    # Recursively build base identifier
    supplement.base_identifier = @builder.build(base_hash)
  end

  # Build supplement edition from captured data
  if parsed_hash[:supplement_month_year]
    # Parse month+year format like "Jan1924"
    month_year = parsed_hash[:supplement_month_year].to_s
    supplement.edition = Components::Edition.new(type: "s",
                                                 id: month_year)
  elsif parsed_hash[:supplement_year]
    # Just year: 1924
    supplement.edition = Components::Edition.new(type: "s",
                                                 id: parsed_hash[:supplement_year].to_s)
  elsif parsed_hash[:supplement_slash_year].is_a?(Hash)
    # NEW: Handle supplement_slash_year pattern (e.g., "sup12/1926", "sup1/1927")
    # V1 format: "Upd1-192612" where "1" is fixed and "192612" is year+number concatenated
    # Single digit numbers are zero-padded: "sup1/1927" -> "Upd1-192701"
    supp_hash = parsed_hash[:supplement_slash_year]
    supp_number = supp_hash[:supp_number]&.to_s
    supp_year = supp_hash[:supp_year]&.to_s

    # Pad supplement number to 2 digits for single-digit numbers
    supp_number_padded = supp_number.rjust(2, "0")

    # Create Update component for supplement (V1 compatibility uses Update for supplements).
    # Renders "/Upd1-{year}{padded_number}" via Update#to_s(:short). The
    # year and already-padded number are fused into the year field (month
    # left nil) so rendering reproduces the V1 string exactly. Must be a
    # Components::Update (not a String) so :update serializes.
    supplement.update = Components::Update.new(
      number: "1", year: "#{supp_year}#{supp_number_padded}",
      prefix: "slash"
    )
  elsif parsed_hash[:supplement_empty]
    # Empty supplement - no edition
    # supplement.edition remains nil
  end

  supplement
end