pubid 2.0.1 → 2.0.3
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 +4 -4
- data/data/ieee/update_codes.yaml +2 -2
- data/data/parg/adobe.json +1 -1
- data/data/parg/aiee.json +1 -1
- data/data/parg/aiee.parg +99 -9
- data/data/parg/amca.json +1 -1
- data/data/parg/ansi.json +1 -1
- data/data/parg/api.json +1 -1
- data/data/parg/ashrae.json +1 -1
- data/data/parg/asme.json +1 -1
- data/data/parg/astm.json +1 -1
- data/data/parg/bipm.json +1 -1
- data/data/parg/bsi.json +1 -1
- data/data/parg/ccsds.json +1 -1
- data/data/parg/cen_cenelec.json +1 -1
- data/data/parg/cie.json +1 -1
- data/data/parg/csa.json +1 -1
- data/data/parg/ecma.json +1 -1
- data/data/parg/etsi.json +1 -1
- data/data/parg/evs.json +1 -1
- data/data/parg/gost.json +1 -1
- data/data/parg/iala.json +1 -1
- data/data/parg/iana.json +1 -1
- data/data/parg/idf.json +1 -1
- data/data/parg/iec.json +1 -1
- data/data/parg/ieee.json +1 -1
- data/data/parg/iho.json +1 -0
- data/data/parg/ire.json +1 -1
- data/data/parg/ire.parg +51 -12
- data/data/parg/isbn.json +1 -1
- data/data/parg/iso.json +1 -1
- data/data/parg/iso.parg +751 -0
- data/data/parg/itu.json +1 -1
- data/data/parg/jcgm.json +1 -1
- data/data/parg/jis.json +1 -1
- data/data/parg/nesc.json +1 -1
- data/data/parg/nesc.parg +97 -12
- data/data/parg/nist.json +1 -1
- data/data/parg/oiml.json +1 -1
- data/data/parg/omg.json +1 -1
- data/data/parg/parg.json +1 -1
- data/data/parg/plateau.json +1 -1
- data/data/parg/tgpp.json +1 -1
- data/data/parg/un.json +1 -1
- data/lib/pubid/adobe/identifier.rb +1 -1
- data/lib/pubid/adobe/parser.rb +2 -113
- data/lib/pubid/amca/identifiers/base.rb +1 -1
- data/lib/pubid/amca/parser.rb +1 -145
- data/lib/pubid/ansi/identifier.rb +1 -1
- data/lib/pubid/ansi/parser.rb +1 -82
- data/lib/pubid/api/builder.rb +32 -1
- data/lib/pubid/api/identifier.rb +1 -1
- data/lib/pubid/api/parser.rb +1 -131
- data/lib/pubid/ashrae/identifiers/base.rb +1 -1
- data/lib/pubid/ashrae/parser.rb +3 -710
- data/lib/pubid/asme/builder.rb +6 -4
- data/lib/pubid/asme/identifier.rb +1 -2
- data/lib/pubid/asme/parser.rb +1 -328
- data/lib/pubid/astm/builder.rb +12 -3
- data/lib/pubid/astm/identifier.rb +1 -2
- data/lib/pubid/astm/parser.rb +1 -227
- data/lib/pubid/bipm/identifier.rb +8 -1
- data/lib/pubid/bipm/parser.rb +2 -224
- data/lib/pubid/bsi/builder.rb +2 -4
- data/lib/pubid/bsi/parser.rb +1 -666
- data/lib/pubid/bsi/single_identifier.rb +38 -1
- data/lib/pubid/calconnect/parser.rb +2 -40
- data/lib/pubid/ccsds/identifier.rb +1 -1
- data/lib/pubid/ccsds/parser.rb +1 -66
- data/lib/pubid/cen_cenelec/identifier.rb +16 -1
- data/lib/pubid/cen_cenelec/parser.rb +3 -136
- data/lib/pubid/cie/identifier.rb +1 -1
- data/lib/pubid/cie/parser.rb +3 -378
- data/lib/pubid/conformance.rb +1 -1
- data/lib/pubid/csa/builder.rb +41 -18
- data/lib/pubid/csa/identifier.rb +12 -4
- data/lib/pubid/csa/identifiers/amendment.rb +28 -0
- data/lib/pubid/csa/identifiers.rb +1 -0
- data/lib/pubid/csa/parser.rb +16 -397
- data/lib/pubid/doi/parser.rb +2 -37
- data/lib/pubid/easc/builder.rb +1 -1
- data/lib/pubid/easc/parser.rb +2 -70
- data/lib/pubid/ecma/CLAUDE.md +8 -1
- data/lib/pubid/ecma/identifier.rb +13 -8
- data/lib/pubid/ecma/parser.rb +2 -54
- data/lib/pubid/ecma/renderer.rb +3 -7
- data/lib/pubid/errors.rb +18 -13
- data/lib/pubid/etsi/builder.rb +8 -5
- data/lib/pubid/etsi/identifiers/base.rb +1 -1
- data/lib/pubid/etsi/parser.rb +1 -132
- data/lib/pubid/evs/identifier.rb +7 -0
- data/lib/pubid/evs/parser.rb +1 -8
- data/lib/pubid/evs.rb +1 -1
- data/lib/pubid/gb/identifier.rb +6 -0
- data/lib/pubid/gb/parser.rb +2 -51
- data/lib/pubid/gost/identifier.rb +9 -1
- data/lib/pubid/gost/parser.rb +4 -81
- data/lib/pubid/iala/builder.rb +48 -39
- data/lib/pubid/iala/identifier.rb +1 -1
- data/lib/pubid/iala/parser.rb +2 -121
- data/lib/pubid/iana/identifier.rb +1 -1
- data/lib/pubid/iana/parser.rb +2 -24
- data/lib/pubid/identifier.rb +78 -1
- data/lib/pubid/idf/builder.rb +42 -0
- data/lib/pubid/idf/identifier.rb +1 -1
- data/lib/pubid/idf/parser.rb +1 -137
- data/lib/pubid/idf.rb +1 -2
- data/lib/pubid/iec/builder.rb +158 -50
- data/lib/pubid/iec/identifier.rb +1 -1
- data/lib/pubid/iec/parser.rb +3 -430
- data/lib/pubid/ieee/CLAUDE.md +2 -0
- data/lib/pubid/ieee/aiee/identifier.rb +2 -1
- data/lib/pubid/ieee/aiee.rb +0 -1
- data/lib/pubid/ieee/builder.rb +177 -30
- data/lib/pubid/ieee/components/code.rb +46 -7
- data/lib/pubid/ieee/identifiers/base.rb +5 -2
- data/lib/pubid/ieee/identifiers/joint_development.rb +7 -3
- data/lib/pubid/ieee/ire/identifier.rb +2 -1
- data/lib/pubid/ieee/ire.rb +0 -1
- data/lib/pubid/ieee/nesc/builder.rb +1 -1
- data/lib/pubid/ieee/nesc.rb +0 -1
- data/lib/pubid/ieee/parser.rb +1 -1257
- data/lib/pubid/ieee/project_renderer.rb +5 -1
- data/lib/pubid/ieee/renderer.rb +4 -2
- data/lib/pubid/ieee/urn_generator.rb +4 -1
- data/lib/pubid/ietf/identifiers/base.rb +17 -1
- data/lib/pubid/ietf/parser.rb +2 -47
- data/lib/pubid/ietf/renderer.rb +17 -2
- data/lib/pubid/iho/CLAUDE.md +98 -0
- data/lib/pubid/iho/builder.rb +4 -1
- data/lib/pubid/iho/identifiers/base.rb +28 -2
- data/lib/pubid/iho/renderer.rb +8 -0
- data/lib/pubid/iho/urn_generator.rb +14 -1
- data/lib/pubid/iho/urn_parser.rb +45 -32
- data/lib/pubid/iho.rb +0 -1
- data/lib/pubid/isbn/identifier.rb +1 -1
- data/lib/pubid/isbn/parser.rb +2 -31
- data/lib/pubid/iso/CLAUDE.md +66 -0
- data/lib/pubid/iso/builder.rb +229 -15
- data/lib/pubid/iso/identifier.rb +22 -12
- data/lib/pubid/iso/identifiers/directives.rb +6 -3
- data/lib/pubid/iso/identifiers/guide.rb +6 -3
- data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
- data/lib/pubid/iso/normalizer.rb +5 -4
- data/lib/pubid/iso/parser.rb +1 -539
- data/lib/pubid/iso/supplement_identifier.rb +6 -3
- data/lib/pubid/iso/urn_parser.rb +13 -8
- data/lib/pubid/iso/utilities.rb +4 -5
- data/lib/pubid/iso.rb +1 -19
- data/lib/pubid/itu/builder.rb +36 -20
- data/lib/pubid/itu/identifiers/base.rb +15 -2
- data/lib/pubid/itu/parser.rb +1 -775
- data/lib/pubid/jcgm/builder.rb +35 -0
- data/lib/pubid/jcgm/parser.rb +1 -151
- data/lib/pubid/jcgm.rb +1 -2
- data/lib/pubid/jis/CLAUDE.md +36 -0
- data/lib/pubid/jis/builder.rb +30 -41
- data/lib/pubid/jis/identifier.rb +12 -2
- data/lib/pubid/jis/identifiers/welding_engineering_standard.rb +35 -0
- data/lib/pubid/jis/identifiers.rb +2 -0
- data/lib/pubid/jis/parser.rb +1 -128
- data/lib/pubid/jis/renderer.rb +1 -1
- data/lib/pubid/jis/supplement_identifier.rb +5 -5
- data/lib/pubid/jis/urn_parser.rb +2 -1
- data/lib/pubid/jis.rb +3 -2
- data/lib/pubid/nist/builder.rb +5 -1
- data/lib/pubid/nist/caster.rb +13 -3
- data/lib/pubid/nist/identifiers/base.rb +37 -2
- data/lib/pubid/nist/identifiers/circular.rb +2 -2
- data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -2
- data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +2 -2
- data/lib/pubid/nist/identifiers/crpl_report.rb +2 -2
- data/lib/pubid/nist/identifiers/handbook.rb +2 -2
- data/lib/pubid/nist/identifiers/internal_report.rb +2 -2
- data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
- data/lib/pubid/nist/identifiers/monograph.rb +2 -2
- data/lib/pubid/nist/identifiers/report.rb +2 -2
- data/lib/pubid/nist/parser.rb +19 -775
- data/lib/pubid/nist/router.rb +4 -1
- data/lib/pubid/nist/supplement_identifier.rb +2 -2
- data/lib/pubid/oasis/parser.rb +2 -16
- data/lib/pubid/ogc/parser.rb +2 -26
- data/lib/pubid/oiml/identifiers/certification_system.rb +7 -0
- data/lib/pubid/oiml/identifiers/dual_published.rb +2 -2
- data/lib/pubid/oiml/parser.rb +1 -303
- data/lib/pubid/oiml.rb +1 -2
- data/lib/pubid/omg/identifier.rb +6 -1
- data/lib/pubid/omg/parser.rb +2 -76
- data/lib/pubid/parser.rb +5 -4
- data/lib/pubid/parsers/mr_string.rb +1 -1
- data/lib/pubid/plateau/parser.rb +1 -66
- data/lib/pubid/plateau.rb +1 -2
- data/lib/pubid/renderers/directives_renderer.rb +1 -1
- data/lib/pubid/renderers/iwa_renderer.rb +1 -1
- data/lib/pubid/renderers/publisher_stripper.rb +82 -0
- data/lib/pubid/renderers/supplement_renderer.rb +1 -1
- data/lib/pubid/renderers.rb +1 -0
- data/lib/pubid/sae/parser.rb +1 -48
- data/lib/pubid/tgpp/identifier.rb +1 -1
- data/lib/pubid/tgpp/parser.rb +2 -59
- data/lib/pubid/un/identifier.rb +1 -1
- data/lib/pubid/un/parser.rb +1 -18
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid/w3c/parser.rb +2 -27
- data/lib/pubid/xsf/identifier.rb +10 -6
- data/lib/pubid/xsf/parser.rb +3 -31
- data/lib/pubid/xsf/renderer.rb +2 -4
- data/lib/pubid.rb +8 -9
- metadata +9 -23
- data/lib/pubid/ieee/aiee/parser.rb +0 -130
- data/lib/pubid/ieee/ire/parser.rb +0 -109
- data/lib/pubid/ieee/nesc/parser.rb +0 -155
- data/lib/pubid/iho/parser.rb +0 -68
- data/lib/pubid/parser/common_parse_methods.rb +0 -13
- data/lib/pubid/parser/common_parse_rules.rb +0 -56
- data/lib/pubid/parser/grammar.rb +0 -96
data/lib/pubid/ieee/builder.rb
CHANGED
|
@@ -146,7 +146,7 @@ module Pubid
|
|
|
146
146
|
related =
|
|
147
147
|
begin
|
|
148
148
|
Identifier.parse(m[1])
|
|
149
|
-
rescue
|
|
149
|
+
rescue Pubid::Errors::ParseError
|
|
150
150
|
Identifier.new(parenthetical_content: m[1])
|
|
151
151
|
end
|
|
152
152
|
relationships = [Components::Relationship.new(
|
|
@@ -283,21 +283,90 @@ module Pubid
|
|
|
283
283
|
# Parslet can return array of hashes - merge them
|
|
284
284
|
parsed_hash = parsed.is_a?(Array) ? merge_parsed_array(parsed) : parsed
|
|
285
285
|
|
|
286
|
+
# The grammar binds the bare top-level draft clause as
|
|
287
|
+
# :draft_version_scalar (string) — the only cross-engine-stable
|
|
288
|
+
# scalar — and the composite draft rule as :draft_version_composite
|
|
289
|
+
# (raw). Surface the scalar under the top-level key the rest of the
|
|
290
|
+
# builder has always read, and the composite under the interior key
|
|
291
|
+
# the draft-element walks read.
|
|
292
|
+
if parsed_hash[:draft_version].nil? && parsed_hash[:draft_version_scalar]
|
|
293
|
+
parsed_hash[:draft_version] = parsed_hash.delete(:draft_version_scalar)
|
|
294
|
+
end
|
|
295
|
+
draft_elems = parsed_hash[:draft].is_a?(Array) ? parsed_hash[:draft] : [parsed_hash[:draft]]
|
|
296
|
+
draft_elems.each do |e|
|
|
297
|
+
next unless e.is_a?(Hash) && e[:draft_version].nil? && e[:draft_version_composite]
|
|
298
|
+
|
|
299
|
+
e[:draft_version] = e.delete(:draft_version_composite)
|
|
300
|
+
end
|
|
301
|
+
# The subpart run ("7.4-3-2" carries two) rides the raw
|
|
302
|
+
# part_subpart_year wrapper — the subparts[] array harvest emitted
|
|
303
|
+
# empty hashes for non-firing rep iterations on wasm
|
|
304
|
+
# (parsanol-ruby#399), so the run no longer binds top-level.
|
|
305
|
+
# Hoist the wrapper's part/year and deep-collect its interior
|
|
306
|
+
# subparts so every consumer — and every raw subtree built through
|
|
307
|
+
# here (the CSA dual's ieee_portion, the multi-numbered primary,
|
|
308
|
+
# the supplement bases) — sees the flat keys it has always read.
|
|
309
|
+
parsed_hash = unwrap_part_subpart_year(parsed_hash)
|
|
310
|
+
|
|
286
311
|
# Grammar-captured dash dates (relaton's "/D-3-2017-07" and
|
|
287
312
|
# "/R-x-2011-04" tails) land on the identifier's year/month exactly
|
|
288
313
|
# like the number's own date clause. The revision-suffix form sits at
|
|
289
314
|
# the top level; the draft tail nests under :draft — either as its
|
|
290
315
|
# own element or wrapped in a draft_version hash.
|
|
316
|
+
# A revision element INSIDE the draft array is the draft rule's own
|
|
317
|
+
# split artifact (the version's trailing ".1" segment) — the spine
|
|
318
|
+
# captured the same value, and leaving it there makes the REV-date
|
|
319
|
+
# embed glue the draft date into the designator. A rev_inline
|
|
320
|
+
# capture never lands inside the draft array, so this only ever
|
|
321
|
+
# drops the artifact.
|
|
322
|
+
if parsed_hash[:draft].is_a?(Array) && parsed_hash[:revision]
|
|
323
|
+
artifact = parsed_hash[:draft].any? do |e|
|
|
324
|
+
e.is_a?(Hash) && e.key?(:revision) && e[:revision].to_s == parsed_hash[:revision].to_s
|
|
325
|
+
end
|
|
326
|
+
parsed_hash.delete(:revision) if artifact
|
|
327
|
+
end
|
|
328
|
+
# The edition-2 tree materializes a multi-segment draft version
|
|
329
|
+
# ("D2.1.1") as per-token nodes carrying no :draft_version key. The
|
|
330
|
+
# v1 tree degraded such a clause to a whole-clause LEAF string when
|
|
331
|
+
# no draft date follows (the designator survives through the
|
|
332
|
+
# Draft.parse inverse), and to a date-only hash when it does (the
|
|
333
|
+
# version is dropped, no draft built). Normalize to the v1 shape
|
|
334
|
+
# here — before the stage derivation and draft handling read it —
|
|
335
|
+
# so every consumer sees what v1 saw.
|
|
336
|
+
if parsed_hash[:draft].is_a?(Array)
|
|
337
|
+
draft_elems = parsed_hash[:draft].select { |e| e.is_a?(Hash) }
|
|
338
|
+
if !draft_elems.empty? && draft_elems.none? { |e| e.key?(:draft_version) }
|
|
339
|
+
has_date = draft_elems.any? { |e| e.key?(:draft_date_month) || e.key?(:draft_date_year) }
|
|
340
|
+
if has_date
|
|
341
|
+
date_elems = draft_elems.select do |e|
|
|
342
|
+
e.key?(:draft_date_month) || e.key?(:draft_date_year) || e.key?(:draft_date_day)
|
|
343
|
+
end
|
|
344
|
+
parsed_hash[:draft] = date_elems.inject({}, :merge)
|
|
345
|
+
else
|
|
346
|
+
parsed_hash[:draft] = draft_elems.flat_map { |e| e.values }.join
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
291
350
|
drafts = parsed_hash[:draft].is_a?(Array) ? parsed_hash[:draft] : [parsed_hash[:draft]].compact
|
|
292
351
|
drafts.each do |d|
|
|
293
352
|
next unless d.is_a?(Hash) && d[:draft_version].is_a?(Hash)
|
|
294
353
|
dv = d[:draft_version]
|
|
295
|
-
|
|
296
|
-
|
|
354
|
+
# The edition-2 grammar renames the nested draft-version dash
|
|
355
|
+
# captures (draft_version_dash_year/month) to keep them out of the
|
|
356
|
+
# flat bound map; the v1 tree spelling stays as the fallback.
|
|
357
|
+
d[:dash_year] = (dv[:draft_version_dash_year] || dv[:dash_year]) if
|
|
358
|
+
dv[:draft_version_dash_year] || dv[:dash_year]
|
|
359
|
+
d[:dash_month] = (dv[:draft_version_dash_month] || dv[:dash_month]) if
|
|
360
|
+
dv[:draft_version_dash_month] || dv[:dash_month]
|
|
297
361
|
d.delete(:draft_version)
|
|
298
362
|
end
|
|
299
363
|
dash = parsed_hash[:dash_year] ? parsed_hash : nil
|
|
300
|
-
|
|
364
|
+
# The draft_date captures rename with the draft_date_ prefix on the
|
|
365
|
+
# edition-2 grammar (nested-only, kept off the identity keys); v1
|
|
366
|
+
# spelling stays as the fallback.
|
|
367
|
+
dash ||= drafts.find do |d|
|
|
368
|
+
d.is_a?(Hash) && (d[:draft_date_dash_year] || d[:dash_year])
|
|
369
|
+
end
|
|
301
370
|
if dash.nil? && parsed_hash[:draft]
|
|
302
371
|
# A numeric month trailing a draft is the draft's own date: it
|
|
303
372
|
# embeds in the designator ("D09, 04 2011" -> "D09-2011-04")
|
|
@@ -323,25 +392,32 @@ module Pubid
|
|
|
323
392
|
if dash
|
|
324
393
|
joined = drafts.filter_map { |d| d.is_a?(Hash) ? d[:draft_version] : nil }
|
|
325
394
|
.map { |v| v.is_a?(Hash) ? v[:value] : v.to_s }.join
|
|
395
|
+
# dash is either the top-level spine (dash_year/dash_month) or the
|
|
396
|
+
# draft's own hash (draft_date_dash_year/month on the edition-2
|
|
397
|
+
# grammar, dash_year/month on v1) — read both spellings.
|
|
398
|
+
dash_y = dash[:dash_year] || dash[:draft_date_dash_year]
|
|
399
|
+
dash_m = dash[:dash_month] || dash[:draft_date_dash_month]
|
|
326
400
|
bare_joint_draft = parsed_hash[:joint_publishers] &&
|
|
327
401
|
parsed_hash[:part].nil? &&
|
|
328
402
|
parsed_hash[:iso_stage].nil? &&
|
|
329
403
|
drafts.any? { |d| d.is_a?(Hash) && d[:draft_version] }
|
|
330
404
|
dash_led = joined.start_with?("-")
|
|
331
405
|
if bare_joint_draft && dash_led
|
|
332
|
-
parsed_hash[:part] = { value:
|
|
333
|
-
elsif
|
|
406
|
+
parsed_hash[:part] = { value: dash_y }
|
|
407
|
+
elsif dash_m &&
|
|
334
408
|
(host = drafts.find { |d| d.is_a?(Hash) && d[:draft_version] })
|
|
335
409
|
# The draft's own numeric date embeds in the designator
|
|
336
410
|
# ("D2-2015-04") — the "D2, 04 2015" comma spelling
|
|
337
411
|
# normalizes onto it (pubid ruling 2026-10-13).
|
|
338
412
|
host[:draft_version] =
|
|
339
|
-
[joined,
|
|
413
|
+
[joined, dash_y, dash_m].compact.join("-")
|
|
340
414
|
dash.delete(:dash_year)
|
|
341
415
|
dash.delete(:dash_month)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
416
|
+
dash.delete(:draft_date_dash_year)
|
|
417
|
+
dash.delete(:draft_date_dash_month)
|
|
418
|
+
elsif (dash_m || dash_led) && parsed_hash[:year].nil?
|
|
419
|
+
parsed_hash[:year] = dash_y
|
|
420
|
+
parsed_hash[:month] = dash_m
|
|
345
421
|
else
|
|
346
422
|
# Year-only tail embeds in the draft face ("D9-2006"), the model
|
|
347
423
|
# the relaton-pinned renders use for corrupted update_codes
|
|
@@ -353,15 +429,17 @@ module Pubid
|
|
|
353
429
|
# ("D9-2006", and with its month "D2-2015-04") — the
|
|
354
430
|
# "D2, 04 2015" comma spelling normalizes onto it.
|
|
355
431
|
host[:draft_version] =
|
|
356
|
-
[joined,
|
|
432
|
+
[joined, dash_y, dash_m].compact.join("-")
|
|
357
433
|
dash.delete(:dash_year)
|
|
358
434
|
dash.delete(:dash_month)
|
|
435
|
+
dash.delete(:draft_date_dash_year)
|
|
436
|
+
dash.delete(:draft_date_dash_month)
|
|
359
437
|
# the hyphenated "/D-2-2007" spelling's year the spine
|
|
360
438
|
# captured at top level is the draft's own — vacate it
|
|
361
|
-
parsed_hash.delete(:year) if parsed_hash[:year] ==
|
|
439
|
+
parsed_hash.delete(:year) if parsed_hash[:year] == dash_y
|
|
362
440
|
else
|
|
363
|
-
parsed_hash[:year] =
|
|
364
|
-
parsed_hash[:month] =
|
|
441
|
+
parsed_hash[:year] = dash_y
|
|
442
|
+
parsed_hash[:month] = dash_m
|
|
365
443
|
end
|
|
366
444
|
end
|
|
367
445
|
end
|
|
@@ -456,8 +534,11 @@ module Pubid
|
|
|
456
534
|
# "IEEE P15026.2/D=FDIS-201008" (pubid#203 ruling). The
|
|
457
535
|
# stage and date ride the designator; the ISO face is not
|
|
458
536
|
# elected.
|
|
459
|
-
year
|
|
460
|
-
|
|
537
|
+
# fdraft's month/year rename with the fdraft_ prefix on the
|
|
538
|
+
# edition-2 grammar (nested-only); v1 spelling stays as the
|
|
539
|
+
# fallback.
|
|
540
|
+
year = extract_value(fd[:fdraft_year] || fd[:year])
|
|
541
|
+
month = extract_value(fd[:fdraft_month] || fd[:month])
|
|
461
542
|
date = ""
|
|
462
543
|
if year
|
|
463
544
|
date = "-#{year}"
|
|
@@ -500,6 +581,11 @@ module Pubid
|
|
|
500
581
|
def reconstruct_base_string(base_data)
|
|
501
582
|
base_parts = []
|
|
502
583
|
|
|
584
|
+
# The part/subpart/year captures sit one level down, inside the
|
|
585
|
+
# raw part_subpart_year wrapper (parsanol-ruby#399) — hoist them
|
|
586
|
+
# before the flat reads below.
|
|
587
|
+
base_data = unwrap_part_subpart_year(base_data)
|
|
588
|
+
|
|
503
589
|
# Extract publisher
|
|
504
590
|
if base_data[:publishers]
|
|
505
591
|
pub_data = base_data[:publishers]
|
|
@@ -536,11 +622,14 @@ module Pubid
|
|
|
536
622
|
number_str += part_val if part_val
|
|
537
623
|
end
|
|
538
624
|
|
|
539
|
-
# Add subpart if present
|
|
625
|
+
# Add subpart if present — the bound run arrives as subparts[]
|
|
626
|
+
# harvest hashes ({subpart: …}); v1's flat spelling stays as the
|
|
627
|
+
# fallback.
|
|
540
628
|
if base_data[:subpart]
|
|
541
629
|
subparts = base_data[:subpart]
|
|
542
630
|
subparts = [subparts] unless subparts.is_a?(Array)
|
|
543
631
|
subparts.each do |sp|
|
|
632
|
+
sp = sp[:subpart] if sp.is_a?(Hash) && sp.key?(:subpart)
|
|
544
633
|
subpart_val = extract_value(sp)
|
|
545
634
|
number_str += subpart_val if subpart_val
|
|
546
635
|
end
|
|
@@ -681,7 +770,7 @@ module Pubid
|
|
|
681
770
|
def build_interpretation_supplement(parsed_hash)
|
|
682
771
|
# Reconstruct base identifier string from parsed components (same logic as corrigendum)
|
|
683
772
|
base_parts = []
|
|
684
|
-
base_data = parsed_hash[:base]
|
|
773
|
+
base_data = unwrap_part_subpart_year(parsed_hash[:base])
|
|
685
774
|
|
|
686
775
|
# Extract publisher
|
|
687
776
|
if base_data[:publishers]
|
|
@@ -714,11 +803,14 @@ module Pubid
|
|
|
714
803
|
number_str += part_val if part_val
|
|
715
804
|
end
|
|
716
805
|
|
|
717
|
-
# Add subpart if present
|
|
806
|
+
# Add subpart if present — the bound run arrives as subparts[]
|
|
807
|
+
# harvest hashes ({subpart: …}); v1's flat spelling stays as the
|
|
808
|
+
# fallback.
|
|
718
809
|
if base_data[:subpart]
|
|
719
810
|
subparts = base_data[:subpart]
|
|
720
811
|
subparts = [subparts] unless subparts.is_a?(Array)
|
|
721
812
|
subparts.each do |sp|
|
|
813
|
+
sp = sp[:subpart] if sp.is_a?(Hash) && sp.key?(:subpart)
|
|
722
814
|
subpart_val = extract_value(sp)
|
|
723
815
|
number_str += subpart_val if subpart_val
|
|
724
816
|
end
|
|
@@ -754,7 +846,7 @@ module Pubid
|
|
|
754
846
|
def build_conformance_supplement(parsed_hash)
|
|
755
847
|
# Reconstruct base identifier string from parsed components (same logic as corrigendum)
|
|
756
848
|
base_parts = []
|
|
757
|
-
base_data = parsed_hash[:base]
|
|
849
|
+
base_data = unwrap_part_subpart_year(parsed_hash[:base])
|
|
758
850
|
|
|
759
851
|
# Extract publisher
|
|
760
852
|
if base_data[:publishers]
|
|
@@ -787,11 +879,14 @@ module Pubid
|
|
|
787
879
|
number_str += part_val if part_val
|
|
788
880
|
end
|
|
789
881
|
|
|
790
|
-
# Add subpart if present
|
|
882
|
+
# Add subpart if present — the bound run arrives as subparts[]
|
|
883
|
+
# harvest hashes ({subpart: …}); v1's flat spelling stays as the
|
|
884
|
+
# fallback.
|
|
791
885
|
if base_data[:subpart]
|
|
792
886
|
subparts = base_data[:subpart]
|
|
793
887
|
subparts = [subparts] unless subparts.is_a?(Array)
|
|
794
888
|
subparts.each do |sp|
|
|
889
|
+
sp = sp[:subpart] if sp.is_a?(Hash) && sp.key?(:subpart)
|
|
795
890
|
subpart_val = extract_value(sp)
|
|
796
891
|
number_str += subpart_val if subpart_val
|
|
797
892
|
end
|
|
@@ -1320,9 +1415,12 @@ module Pubid
|
|
|
1320
1415
|
# Extract SI type (SI or PSI)
|
|
1321
1416
|
si_type = extract_value(parsed[:si_type])
|
|
1322
1417
|
|
|
1323
|
-
# Extract publishers (should be "IEEE/ASTM")
|
|
1324
|
-
|
|
1325
|
-
|
|
1418
|
+
# Extract publishers (should be "IEEE/ASTM") — the SI rule's bare
|
|
1419
|
+
# publishers clause binds as publishers_scalar (string); the
|
|
1420
|
+
# composite joint rows deliver :publishers (raw).
|
|
1421
|
+
pub_src = parsed[:publishers] || parsed[:publishers_scalar]
|
|
1422
|
+
if pub_src
|
|
1423
|
+
publishers_str = extract_value(pub_src)
|
|
1326
1424
|
# Split by slash to get individual publishers
|
|
1327
1425
|
pubs = publishers_str.split("/")
|
|
1328
1426
|
attributes[:publisher] = pubs.first
|
|
@@ -1334,6 +1432,10 @@ module Pubid
|
|
|
1334
1432
|
|
|
1335
1433
|
# For PSI (draft), create proper Draft component object
|
|
1336
1434
|
if si_type == "PSI"
|
|
1435
|
+
# The bare top-level draft clause binds as :draft_version_scalar
|
|
1436
|
+
# (string) — surface it under the key this builder reads.
|
|
1437
|
+
parsed[:draft_version] = parsed.delete(:draft_version_scalar) if
|
|
1438
|
+
parsed[:draft_version].nil? && parsed[:draft_version_scalar]
|
|
1337
1439
|
if parsed[:draft_version]
|
|
1338
1440
|
draft_version = extract_value(parsed[:draft_version])
|
|
1339
1441
|
# Create Draft component (Lutaml::Model object)
|
|
@@ -1904,6 +2006,42 @@ module Pubid
|
|
|
1904
2006
|
str_value.length.positive? ? str_value : nil
|
|
1905
2007
|
end
|
|
1906
2008
|
|
|
2009
|
+
# Deep-first collection of every :subpart node under a raw
|
|
2010
|
+
# part_subpart_year wrapper (Hash or Array, any nesting).
|
|
2011
|
+
def collect_subparts(node, out = [])
|
|
2012
|
+
case node
|
|
2013
|
+
when Hash
|
|
2014
|
+
node.each do |key, value|
|
|
2015
|
+
if key.to_sym == :subpart
|
|
2016
|
+
out << value
|
|
2017
|
+
else
|
|
2018
|
+
collect_subparts(value, out)
|
|
2019
|
+
end
|
|
2020
|
+
end
|
|
2021
|
+
when Array then node.each { |item| collect_subparts(item, out) }
|
|
2022
|
+
end
|
|
2023
|
+
out
|
|
2024
|
+
end
|
|
2025
|
+
|
|
2026
|
+
# The part/subpart/year captures ride the raw part_subpart_year
|
|
2027
|
+
# wrapper inside raw subtrees (parsanol-ruby#399: the subparts[]
|
|
2028
|
+
# array harvest is not cross-engine stable, so the run does not
|
|
2029
|
+
# bind top-level). Hoist the wrapper's part/year onto a copy of the
|
|
2030
|
+
# subtree and surface the interior subpart run under :subpart, so
|
|
2031
|
+
# the flat reads downstream see the shapes they always read.
|
|
2032
|
+
def unwrap_part_subpart_year(node)
|
|
2033
|
+
return node unless node.is_a?(Hash) && (run = node[:part_subpart_year])
|
|
2034
|
+
|
|
2035
|
+
nodes = run.is_a?(Array) ? run : [run]
|
|
2036
|
+
flat = nodes.select { |item| item.is_a?(Hash) }.inject({}, :merge)
|
|
2037
|
+
node = node.merge(flat.slice(:part, :year))
|
|
2038
|
+
unless node[:subpart]
|
|
2039
|
+
collected = collect_subparts(run)
|
|
2040
|
+
node = node.merge(subpart: collected) unless collected.empty?
|
|
2041
|
+
end
|
|
2042
|
+
node
|
|
2043
|
+
end
|
|
2044
|
+
|
|
1907
2045
|
# Extract optional attribute if present
|
|
1908
2046
|
def extract_optional(parsed, attributes, key)
|
|
1909
2047
|
value = extract_value(parsed[key])
|
|
@@ -2004,15 +2142,20 @@ module Pubid
|
|
|
2004
2142
|
iso_stage ||= extract_value(draft_data[:draft_iso_stage]) if draft_data[:draft_iso_stage]
|
|
2005
2143
|
iso_iteration = extract_value(draft_data[:draft_iso_iteration]) if draft_data[:draft_iso_iteration]
|
|
2006
2144
|
|
|
2007
|
-
# Extract date information from draft data
|
|
2008
|
-
|
|
2145
|
+
# Extract date information from draft data. The edition-2 grammar
|
|
2146
|
+
# renames draft_date's nested month/year/day captures with the
|
|
2147
|
+
# draft_date_ prefix (kept off the identity keys); the v1
|
|
2148
|
+
# spellings stay as the fallback.
|
|
2149
|
+
month_slice = draft_data[:draft_date_month] || draft_data[:month]
|
|
2009
2150
|
month = extract_value(month_slice) if month_slice
|
|
2010
|
-
year = extract_value(draft_data[:
|
|
2151
|
+
year = extract_value(draft_data[:draft_date_year] || draft_data[:year]) if
|
|
2152
|
+
draft_data[:draft_date_year] || draft_data[:year]
|
|
2011
2153
|
# The apostrophe-year catalogue convention ("P1484.20.1/D7,
|
|
2012
2154
|
# May 07"): the 2-digit year expands with the <30 pivot so the
|
|
2013
2155
|
# hash carries an unambiguous year.
|
|
2014
2156
|
year = "#{year.to_i < 30 ? 20 : 19}#{year}" if year.to_s.match?(/\A\d{2}\z/)
|
|
2015
|
-
day = extract_value(draft_data[:
|
|
2157
|
+
day = extract_value(draft_data[:draft_date_day] || draft_data[:day]) if
|
|
2158
|
+
draft_data[:draft_date_day] || draft_data[:day]
|
|
2016
2159
|
|
|
2017
2160
|
# A REV-carrying draft embeds its print date into the designator
|
|
2018
2161
|
# with the month canonicalized numeric (pubid ruling 2026-10-14):
|
|
@@ -2103,7 +2246,11 @@ module Pubid
|
|
|
2103
2246
|
def handle_reaffirmed(parsed, attributes)
|
|
2104
2247
|
if parsed[:reaffirmed].is_a?(Hash)
|
|
2105
2248
|
reaf_data = parsed[:reaffirmed]
|
|
2106
|
-
|
|
2249
|
+
# The edition-2 grammar renames the nested year capture
|
|
2250
|
+
# (reaffirmed_year) so the reaffirmation year cannot leak onto
|
|
2251
|
+
# the identity year key; v1 spelling stays as the fallback.
|
|
2252
|
+
attributes[:reaffirmed] =
|
|
2253
|
+
extract_value(reaf_data[:reaffirmed_year] || reaf_data[:year])
|
|
2107
2254
|
elsif parsed[:reaffirmed]
|
|
2108
2255
|
attributes[:reaffirmed] = extract_value(parsed[:reaffirmed])
|
|
2109
2256
|
end
|
|
@@ -2278,7 +2425,7 @@ module Pubid
|
|
|
2278
2425
|
id_strings.map do |id_str|
|
|
2279
2426
|
# Use Identifier.parse for recursive parsing
|
|
2280
2427
|
Identifier.parse(id_str)
|
|
2281
|
-
rescue
|
|
2428
|
+
rescue Pubid::Errors::ParseError
|
|
2282
2429
|
# If parsing fails, create minimal identifier with just the string
|
|
2283
2430
|
# This maintains graceful degradation
|
|
2284
2431
|
Identifier.new(parenthetical_content: id_str)
|
|
@@ -23,13 +23,21 @@ module Pubid
|
|
|
23
23
|
attribute :parts, :string, collection: true # Array of part strings
|
|
24
24
|
attribute :original_separator, :string # Original separator (. or -) from parsing
|
|
25
25
|
|
|
26
|
+
# The separator BEFORE each part, when the source mixed them
|
|
27
|
+
# ("7.4-3-2" — dot after the number, dashes before the subparts).
|
|
28
|
+
# Runtime-only (a plain accessor, not a lutaml attribute): the
|
|
29
|
+
# serialized columns keep the single original_separator, and only
|
|
30
|
+
# a fresh parse knows the per-part spelling.
|
|
31
|
+
attr_accessor :original_separators
|
|
32
|
+
|
|
26
33
|
def initialize(prefix: nil, number: nil, parts: nil,
|
|
27
|
-
original_separator: nil)
|
|
34
|
+
original_separator: nil, original_separators: nil)
|
|
28
35
|
super()
|
|
29
36
|
self.prefix = prefix
|
|
30
37
|
self.number = number
|
|
31
38
|
self.parts = parts || []
|
|
32
39
|
self.original_separator = original_separator
|
|
40
|
+
self.original_separators = original_separators
|
|
33
41
|
end
|
|
34
42
|
|
|
35
43
|
# Parse code string into components
|
|
@@ -55,15 +63,45 @@ original_separator: nil)
|
|
|
55
63
|
components = remainder.split(/[.-]/)
|
|
56
64
|
number = components.shift
|
|
57
65
|
|
|
66
|
+
# The separators AS PRINTED, one per part boundary — a code may
|
|
67
|
+
# mix them ("7.4-3-2": dot after the number, dashes before the
|
|
68
|
+
# subparts), and to_s re-joins with these instead of the uniform
|
|
69
|
+
# original_separator when they were captured.
|
|
70
|
+
separators = remainder.scan(/[.-]/)
|
|
71
|
+
per_part = separators.length == components.length ? separators : nil
|
|
72
|
+
|
|
58
73
|
new(
|
|
59
74
|
prefix: prefix,
|
|
60
75
|
number: number,
|
|
61
76
|
parts: components.empty? ? nil : components,
|
|
62
77
|
original_separator: separator,
|
|
78
|
+
original_separators: per_part,
|
|
63
79
|
)
|
|
64
80
|
end
|
|
65
81
|
|
|
82
|
+
# The printed face: keeps the separator each part was printed
|
|
83
|
+
# with ("7.4-3-2"), per Pubid::SubsetMatch. Spellings that differ
|
|
84
|
+
# only in part separators denote the same document, so faces that
|
|
85
|
+
# must be spelling-stable — the URN and the joint IEEE/ISO faces —
|
|
86
|
+
# render through #canonical_to_s instead.
|
|
66
87
|
def to_s
|
|
88
|
+
render_code(per_part: true)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The canonical face: one uniform separator across every part
|
|
92
|
+
# boundary (the first distinct one the source printed, the same
|
|
93
|
+
# face this class rendered before per-part separators were kept).
|
|
94
|
+
def canonical_to_s
|
|
95
|
+
render_code(per_part: false)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def render(context: nil)
|
|
99
|
+
to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def render_code(per_part:)
|
|
67
105
|
result = ""
|
|
68
106
|
result += prefix if prefix
|
|
69
107
|
result += number if number # Guard against nil number (e.g., "ANSI X")
|
|
@@ -95,17 +133,18 @@ original_separator: nil)
|
|
|
95
133
|
separator = "."
|
|
96
134
|
end
|
|
97
135
|
|
|
98
|
-
parts.
|
|
99
|
-
|
|
136
|
+
parts.each_with_index do |part, idx|
|
|
137
|
+
# A fresh parse of mixed-separator codes ("7.4-3-2") carries
|
|
138
|
+
# the separator each part was printed with; the canonical
|
|
139
|
+
# face (and an unknown per-part spelling) falls back to the
|
|
140
|
+
# uniform original separator (the serialized shape).
|
|
141
|
+
sep = (per_part && original_separators&.at(idx)) || separator
|
|
142
|
+
result += sep + part
|
|
100
143
|
end
|
|
101
144
|
end
|
|
102
145
|
|
|
103
146
|
result
|
|
104
147
|
end
|
|
105
|
-
|
|
106
|
-
def render(context: nil)
|
|
107
|
-
to_s
|
|
108
|
-
end
|
|
109
148
|
end
|
|
110
149
|
end
|
|
111
150
|
end
|
|
@@ -312,7 +312,7 @@ module Pubid
|
|
|
312
312
|
else
|
|
313
313
|
parse_single(result.input)
|
|
314
314
|
end
|
|
315
|
-
rescue
|
|
315
|
+
rescue Pubid::Errors::ParseError
|
|
316
316
|
parse_single(input)
|
|
317
317
|
end
|
|
318
318
|
|
|
@@ -403,7 +403,10 @@ module Pubid
|
|
|
403
403
|
return build_dual(result.parts) if result.dispatch == :dual_semicolon
|
|
404
404
|
end
|
|
405
405
|
parser_input = Parser.normalize_input(normalized)
|
|
406
|
-
|
|
406
|
+
# The edition-2 artifact's bindings block produces the builder-ready
|
|
407
|
+
# hash at the engine (typed scalars + raw wrappers); the tree
|
|
408
|
+
# backend's normalization it replaces read the edition-1 shapes.
|
|
409
|
+
parsed = Pubid::Parg::Model.bound(:ieee, parser_input)
|
|
407
410
|
builder = Builder.new(Identifier)
|
|
408
411
|
# Pass the original input string to builder for context
|
|
409
412
|
builder.original_input = input
|
|
@@ -164,7 +164,10 @@ module Pubid
|
|
|
164
164
|
# P11073.10418", #216); a plain ISO/IEC number keeps the
|
|
165
165
|
# printed separator ("IEC/IEEE FDIS 60079-30-2", "ISO/IEC
|
|
166
166
|
# 8802-3").
|
|
167
|
-
|
|
167
|
+
# Canonical (uniform-separator) code: a joint document parses
|
|
168
|
+
# from both the dotted IEEE face and the dashed IEC face, and
|
|
169
|
+
# both spellings must render — and URN — identically.
|
|
170
|
+
code_str = code.canonical_to_s
|
|
168
171
|
code_str = code_str.tr("-", ".") if code_str.start_with?("P")
|
|
169
172
|
code_str += mark unless code_str.empty?
|
|
170
173
|
parts << code_str if code_str && !code_str.empty?
|
|
@@ -213,8 +216,9 @@ module Pubid
|
|
|
213
216
|
parts << draft_status if draft_status
|
|
214
217
|
|
|
215
218
|
# Build code part — the P-state prints as spelled (P = project
|
|
216
|
-
# draft; no P = standard). Never added or stripped.
|
|
217
|
-
|
|
219
|
+
# draft; no P = standard). Never added or stripped. Canonical
|
|
220
|
+
# code for the same both-faces reason as the ISO face above.
|
|
221
|
+
code_str = code.canonical_to_s
|
|
218
222
|
|
|
219
223
|
# Mark after the number, before the draft and the year
|
|
220
224
|
code_str += mark unless code_str.empty?
|
data/lib/pubid/ieee/ire.rb
CHANGED
|
@@ -94,7 +94,7 @@ module Pubid
|
|
|
94
94
|
return Identifiers::Nesc::Preprint if parsed_hash[:preprint]
|
|
95
95
|
|
|
96
96
|
# Draft identifiers
|
|
97
|
-
return Identifiers::Nesc::Draft if parsed_hash[:draft]
|
|
97
|
+
return Identifiers::Nesc::Draft if parsed_hash[:draft] || parsed_hash[:nesc_draft_marker]
|
|
98
98
|
|
|
99
99
|
# Variant-based identifiers
|
|
100
100
|
if parsed_hash[:variant]
|