pubid 2.0.0.pre.alpha.2 → 2.0.0.pre.alpha.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/README.adoc +5 -1
- data/data/nist/update_codes.yaml +25 -0
- data/lib/pubid/amca/builder.rb +2 -2
- data/lib/pubid/amca/identifier.rb +7 -39
- data/lib/pubid/amca/identifiers/base.rb +0 -26
- data/lib/pubid/amca/identifiers/interpretation.rb +0 -17
- data/lib/pubid/amca/identifiers/publication.rb +0 -13
- data/lib/pubid/amca/renderer.rb +82 -0
- data/lib/pubid/amca/single_identifier.rb +0 -23
- data/lib/pubid/amca/urn_parser.rb +28 -0
- data/lib/pubid/amca.rb +42 -1
- data/lib/pubid/ansi/builder.rb +5 -3
- data/lib/pubid/ansi/identifier.rb +1 -43
- data/lib/pubid/ansi/identifiers/american_national_standard.rb +2 -1
- data/lib/pubid/ansi/identifiers/standard.rb +2 -3
- data/lib/pubid/ansi/renderer.rb +53 -0
- data/lib/pubid/ansi/single_identifier.rb +2 -31
- data/lib/pubid/ansi/urn_generator.rb +3 -38
- data/lib/pubid/ansi/urn_parser.rb +23 -0
- data/lib/pubid/ansi.rb +38 -3
- data/lib/pubid/api/builder.rb +29 -74
- data/lib/pubid/api/identifier.rb +0 -51
- data/lib/pubid/api/identifiers/base.rb +0 -2
- data/lib/pubid/api/identifiers/bulletin.rb +0 -2
- data/lib/pubid/api/identifiers/continuous_operations_standard.rb +0 -2
- data/lib/pubid/api/identifiers/mpms.rb +1 -17
- data/lib/pubid/api/identifiers/publication.rb +0 -2
- data/lib/pubid/api/identifiers/recommended_practice.rb +0 -2
- data/lib/pubid/api/identifiers/specification.rb +0 -2
- data/lib/pubid/api/identifiers/standard.rb +0 -2
- data/lib/pubid/api/identifiers/technical_report.rb +0 -2
- data/lib/pubid/api/identifiers/typeless_standard.rb +1 -14
- data/lib/pubid/api/identifiers.rb +18 -0
- data/lib/pubid/api/renderer.rb +89 -0
- data/lib/pubid/api/single_identifier.rb +1 -13
- data/lib/pubid/api/urn_generator.rb +0 -18
- data/lib/pubid/api/urn_parser.rb +35 -0
- data/lib/pubid/api.rb +51 -5
- data/lib/pubid/ashrae/builder.rb +3 -3
- data/lib/pubid/ashrae/identifier.rb +6 -39
- data/lib/pubid/ashrae/identifiers/addenda_package.rb +0 -10
- data/lib/pubid/ashrae/identifiers/addendum.rb +0 -19
- data/lib/pubid/ashrae/identifiers/base.rb +3 -0
- data/lib/pubid/ashrae/identifiers/combined_addenda.rb +0 -15
- data/lib/pubid/ashrae/identifiers/errata.rb +0 -10
- data/lib/pubid/ashrae/identifiers/interpretation.rb +0 -10
- data/lib/pubid/ashrae/renderer.rb +117 -0
- data/lib/pubid/ashrae/single_identifier.rb +0 -13
- data/lib/pubid/ashrae/urn_generator.rb +0 -8
- data/lib/pubid/ashrae/urn_parser.rb +27 -0
- data/lib/pubid/ashrae.rb +42 -1
- data/lib/pubid/asme/components/code.rb +10 -2
- data/lib/pubid/asme/identifier.rb +0 -46
- data/lib/pubid/asme/identifiers/base.rb +0 -60
- data/lib/pubid/asme/renderer.rb +66 -0
- data/lib/pubid/asme/urn_parser.rb +31 -0
- data/lib/pubid/asme.rb +42 -1
- data/lib/pubid/astm/components/code.rb +9 -0
- data/lib/pubid/{jis → astm}/components.rb +1 -1
- data/lib/pubid/astm/identifier.rb +0 -77
- data/lib/pubid/astm/identifiers/adjunct.rb +0 -8
- data/lib/pubid/astm/identifiers/data_series.rb +0 -14
- data/lib/pubid/astm/identifiers/iso_dual_published.rb +9 -34
- data/lib/pubid/astm/identifiers/manual.rb +0 -27
- data/lib/pubid/astm/identifiers/monograph.rb +0 -14
- data/lib/pubid/astm/identifiers/research_report.rb +0 -7
- data/lib/pubid/astm/identifiers/standard.rb +0 -39
- data/lib/pubid/astm/identifiers/technical_report.rb +0 -13
- data/lib/pubid/astm/identifiers/work_in_progress.rb +0 -11
- data/lib/pubid/astm/identifiers.rb +18 -0
- data/lib/pubid/astm/renderer.rb +172 -0
- data/lib/pubid/astm/single_identifier.rb +0 -10
- data/lib/pubid/astm/urn_parser.rb +30 -0
- data/lib/pubid/astm.rb +39 -27
- data/lib/pubid/bsi/builder.rb +21 -12
- data/lib/pubid/bsi/identifier.rb +8 -62
- data/lib/pubid/bsi/identifiers/addendum_document.rb +3 -33
- data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +11 -47
- data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +11 -38
- data/lib/pubid/bsi/identifiers/aerospace_standard.rb +3 -53
- data/lib/pubid/bsi/identifiers/amendment.rb +3 -19
- data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +2 -4
- data/lib/pubid/bsi/identifiers/british_standard.rb +2 -1
- data/lib/pubid/bsi/identifiers/bundled_identifier.rb +3 -84
- data/lib/pubid/bsi/identifiers/committee_document.rb +1 -14
- data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +3 -84
- data/lib/pubid/bsi/identifiers/corrigendum.rb +3 -7
- data/lib/pubid/bsi/identifiers/detailed_specification.rb +1 -34
- data/lib/pubid/bsi/identifiers/disc.rb +1 -27
- data/lib/pubid/bsi/identifiers/draft_document.rb +3 -44
- data/lib/pubid/bsi/identifiers/electronic_book.rb +3 -36
- data/lib/pubid/bsi/identifiers/expert_commentary.rb +3 -15
- data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +1 -45
- data/lib/pubid/bsi/identifiers/flex.rb +1 -33
- data/lib/pubid/bsi/identifiers/handbook.rb +2 -13
- data/lib/pubid/bsi/identifiers/index.rb +1 -30
- data/lib/pubid/bsi/identifiers/method.rb +1 -39
- data/lib/pubid/bsi/identifiers/national_annex.rb +5 -27
- data/lib/pubid/bsi/identifiers/practice_guide.rb +2 -4
- data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +3 -52
- data/lib/pubid/bsi/identifiers/published_document.rb +3 -52
- data/lib/pubid/bsi/identifiers/section.rb +1 -28
- data/lib/pubid/bsi/identifiers/set.rb +3 -17
- data/lib/pubid/bsi/identifiers/standalone_amendment.rb +1 -7
- data/lib/pubid/bsi/identifiers/supplement_document.rb +3 -21
- data/lib/pubid/bsi/identifiers/supplementary_index.rb +1 -44
- data/lib/pubid/bsi/identifiers/technical_specification.rb +3 -45
- data/lib/pubid/bsi/identifiers/test_method.rb +1 -30
- data/lib/pubid/bsi/identifiers/value_added_publication.rb +3 -14
- data/lib/pubid/bsi/identifiers.rb +0 -1
- data/lib/pubid/bsi/renderer.rb +1050 -0
- data/lib/pubid/bsi/single_identifier.rb +6 -70
- data/lib/pubid/bsi/urn_generator.rb +2 -3
- data/lib/pubid/bsi/urn_parser.rb +52 -0
- data/lib/pubid/bsi.rb +224 -1
- data/lib/pubid/builder/base.rb +57 -10
- data/lib/pubid/bundled_identifier.rb +0 -1
- data/lib/pubid/ccsds/builder.rb +4 -3
- data/lib/pubid/ccsds/identifier.rb +63 -66
- data/lib/pubid/ccsds/identifiers/base.rb +11 -61
- data/lib/pubid/ccsds/identifiers/corrigendum.rb +7 -6
- data/lib/pubid/ccsds/parser.rb +4 -2
- data/lib/pubid/ccsds/supplement_identifier.rb +15 -11
- data/lib/pubid/ccsds/urn_generator.rb +3 -3
- data/lib/pubid/ccsds/urn_parser.rb +20 -0
- data/lib/pubid/ccsds.rb +39 -1
- data/lib/pubid/cen_cenelec/builder.rb +12 -14
- data/lib/pubid/cen_cenelec/identifier.rb +7 -38
- data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +13 -4
- data/lib/pubid/cen_cenelec/identifiers/amendment.rb +2 -8
- data/lib/pubid/cen_cenelec/identifiers/base.rb +5 -41
- data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +2 -1
- data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +2 -1
- data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +2 -25
- data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +2 -13
- data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +2 -1
- data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +4 -7
- data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +2 -1
- data/lib/pubid/cen_cenelec/identifiers/fragment.rb +2 -2
- data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +2 -1
- data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +2 -1
- data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +2 -1
- data/lib/pubid/cen_cenelec/renderer.rb +261 -0
- data/lib/pubid/cen_cenelec/single_identifier.rb +11 -89
- data/lib/pubid/cen_cenelec/urn_generator.rb +6 -6
- data/lib/pubid/cen_cenelec/urn_parser.rb +28 -0
- data/lib/pubid/cen_cenelec.rb +168 -1
- data/lib/pubid/cie/components/code.rb +8 -0
- data/lib/pubid/cie/identifier.rb +6 -57
- data/lib/pubid/cie/urn_parser.rb +28 -0
- data/lib/pubid/cie.rb +43 -1
- data/lib/pubid/components/adoption.rb +104 -0
- data/lib/pubid/components/code.rb +22 -8
- data/lib/pubid/components/date.rb +23 -16
- data/lib/pubid/components/edition.rb +9 -6
- data/lib/pubid/components/iteration.rb +32 -0
- data/lib/pubid/components/language.rb +6 -4
- data/lib/pubid/components/locality.rb +10 -1
- data/lib/pubid/components/publisher.rb +9 -6
- data/lib/pubid/components/relationship.rb +151 -0
- data/lib/pubid/components/stage.rb +5 -14
- data/lib/pubid/components/supplement.rb +184 -0
- data/lib/pubid/components/type.rb +5 -15
- data/lib/pubid/components/typed_stage.rb +10 -11
- data/lib/pubid/components.rb +4 -1
- data/lib/pubid/core/update_codes.rb +28 -7
- data/lib/pubid/csa/identifier.rb +0 -59
- data/lib/pubid/csa/identifiers/base.rb +2 -122
- data/lib/pubid/csa/identifiers/cec.rb +2 -101
- data/lib/pubid/csa/identifiers/series.rb +2 -102
- data/lib/pubid/csa/renderer.rb +292 -0
- data/lib/pubid/csa/urn_generator.rb +1 -1
- data/lib/pubid/csa/urn_parser.rb +33 -0
- data/lib/pubid/csa.rb +42 -1
- data/lib/pubid/etsi/components/code.rb +9 -2
- data/lib/pubid/etsi/identifier.rb +0 -43
- data/lib/pubid/etsi/identifiers/base.rb +1 -4
- data/lib/pubid/etsi/identifiers/supplement_identifier.rb +2 -9
- data/lib/pubid/etsi/renderer.rb +42 -0
- data/lib/pubid/etsi/urn_parser.rb +34 -0
- data/lib/pubid/etsi.rb +42 -1
- data/lib/pubid/export/exporter.rb +4 -46
- data/lib/pubid/export/flavor_exporter.rb +111 -278
- data/lib/pubid/export.rb +0 -6
- data/lib/pubid/identifier.rb +2 -17
- data/lib/pubid/identifier_facade.rb +114 -0
- data/lib/pubid/identifier_metadata.rb +1 -1
- data/lib/pubid/idf/builder.rb +3 -3
- data/lib/pubid/idf/identifier.rb +3 -66
- data/lib/pubid/idf/identifiers/amendment.rb +2 -1
- data/lib/pubid/idf/identifiers/corrigendum.rb +2 -1
- data/lib/pubid/idf/identifiers/international_standard.rb +2 -1
- data/lib/pubid/idf/identifiers/reviewed_method.rb +2 -1
- data/lib/pubid/idf/parser.rb +3 -2
- data/lib/pubid/idf/renderer.rb +84 -0
- data/lib/pubid/idf/supplement_identifier.rb +2 -10
- data/lib/pubid/idf/urn_generator.rb +4 -39
- data/lib/pubid/idf/urn_parser.rb +25 -0
- data/lib/pubid/idf.rb +51 -1
- data/lib/pubid/iec/builder.rb +46 -64
- data/lib/pubid/iec/components/code.rb +8 -32
- data/lib/pubid/iec/components/publisher.rb +0 -1
- data/lib/pubid/iec/components.rb +14 -0
- data/lib/pubid/iec/identifier.rb +251 -213
- data/lib/pubid/iec/identifiers/amendment.rb +2 -3
- data/lib/pubid/iec/identifiers/base.rb +8 -32
- data/lib/pubid/iec/identifiers/component_specification.rb +3 -3
- data/lib/pubid/iec/identifiers/conformity_assessment.rb +1 -2
- data/lib/pubid/iec/identifiers/consolidated_identifier.rb +27 -26
- data/lib/pubid/iec/identifiers/corrigendum.rb +2 -3
- data/lib/pubid/iec/identifiers/fragment_identifier.rb +37 -22
- data/lib/pubid/iec/identifiers/guide.rb +0 -2
- data/lib/pubid/iec/identifiers/international_standard.rb +2 -3
- data/lib/pubid/iec/identifiers/interpretation_sheet.rb +2 -3
- data/lib/pubid/iec/identifiers/operational_document.rb +3 -3
- data/lib/pubid/iec/identifiers/publicly_available_specification.rb +2 -3
- data/lib/pubid/iec/identifiers/sheet_identifier.rb +21 -11
- data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +3 -3
- data/lib/pubid/iec/identifiers/systems_reference_document.rb +2 -3
- data/lib/pubid/iec/identifiers/technical_report.rb +2 -3
- data/lib/pubid/iec/identifiers/technical_specification.rb +2 -3
- data/lib/pubid/iec/identifiers/technology_report.rb +1 -2
- data/lib/pubid/iec/identifiers/test_report_form.rb +5 -34
- data/lib/pubid/iec/identifiers/vap_identifier.rb +26 -19
- data/lib/pubid/iec/identifiers/white_paper.rb +3 -3
- data/lib/pubid/iec/identifiers/working_document.rb +4 -48
- data/lib/pubid/iec/identifiers.rb +30 -0
- data/lib/pubid/iec/parser.rb +13 -12
- data/lib/pubid/iec/renderer.rb +254 -0
- data/lib/pubid/iec/single_identifier.rb +6 -12
- data/lib/pubid/iec/supplement_identifier.rb +58 -54
- data/lib/pubid/iec/urn_generator.rb +3 -3
- data/lib/pubid/iec/urn_parser.rb +3 -3
- data/lib/pubid/iec.rb +40 -68
- data/lib/pubid/ieee/builder.rb +12 -12
- data/lib/pubid/ieee/components/code.rb +8 -0
- data/lib/pubid/ieee/components/draft.rb +14 -0
- data/lib/pubid/ieee/components/relationship.rb +5 -149
- data/lib/pubid/ieee/identifier.rb +6 -41
- data/lib/pubid/ieee/identifiers/adopted_standard.rb +1 -6
- data/lib/pubid/ieee/identifiers/base.rb +101 -458
- data/lib/pubid/ieee/identifiers/conformance_identifier.rb +1 -7
- data/lib/pubid/ieee/identifiers/corrigendum.rb +1 -9
- data/lib/pubid/ieee/identifiers/csa_dual_published.rb +1 -7
- data/lib/pubid/ieee/identifiers/dual_identifier.rb +1 -1
- data/lib/pubid/ieee/identifiers/dual_published.rb +1 -1
- data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +1 -6
- data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +1 -7
- data/lib/pubid/ieee/identifiers/joint_development.rb +2 -0
- data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +1 -15
- data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +1 -3
- data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +15 -0
- data/lib/pubid/ieee/identifiers/redlined_standard.rb +1 -4
- data/lib/pubid/ieee/identifiers/si_standard.rb +1 -35
- data/lib/pubid/ieee/identifiers/standard.rb +1 -1
- data/lib/pubid/ieee/pre_parser.rb +301 -0
- data/lib/pubid/ieee/renderer.rb +307 -0
- data/lib/pubid/ieee/urn_parser.rb +34 -0
- data/lib/pubid/ieee.rb +62 -1
- data/lib/pubid/ieee_debug.rb +0 -1
- data/lib/pubid/iho/builder.rb +2 -2
- data/lib/pubid/iho/identifier.rb +8 -42
- data/lib/pubid/iho/identifiers/base.rb +49 -10
- data/lib/pubid/iho/parser.rb +3 -3
- data/lib/pubid/iho/renderer.rb +30 -0
- data/lib/pubid/iho/urn_generator.rb +2 -2
- data/lib/pubid/iho/urn_parser.rb +58 -0
- data/lib/pubid/iho.rb +50 -1
- data/lib/pubid/iso/builder.rb +55 -53
- data/lib/pubid/iso/bundled_identifier.rb +51 -0
- data/lib/pubid/iso/components/code.rb +7 -19
- data/lib/pubid/iso/components/publisher.rb +10 -8
- data/lib/pubid/iso/components.rb +2 -4
- data/lib/pubid/iso/identifier.rb +218 -252
- data/lib/pubid/iso/identifiers/addendum.rb +9 -6
- data/lib/pubid/iso/identifiers/amendment.rb +8 -4
- data/lib/pubid/iso/identifiers/corrigendum.rb +4 -4
- data/lib/pubid/iso/identifiers/data.rb +0 -1
- data/lib/pubid/iso/identifiers/directives.rb +8 -2
- data/lib/pubid/iso/identifiers/directives_supplement.rb +43 -14
- data/lib/pubid/iso/identifiers/extract.rb +2 -2
- data/lib/pubid/iso/identifiers/guide.rb +0 -1
- data/lib/pubid/iso/identifiers/international_standard.rb +4 -4
- data/lib/pubid/iso/identifiers/international_standardized_profile.rb +4 -4
- data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +10 -4
- data/lib/pubid/iso/identifiers/pas.rb +2 -2
- data/lib/pubid/iso/identifiers/recommendation.rb +2 -2
- data/lib/pubid/iso/identifiers/supplement.rb +11 -3
- data/lib/pubid/iso/identifiers/tc_document.rb +44 -15
- data/lib/pubid/iso/identifiers/technical_report.rb +4 -4
- data/lib/pubid/iso/identifiers/technical_specification.rb +2 -2
- data/lib/pubid/iso/identifiers/technology_trends_assessments.rb +2 -2
- data/lib/pubid/iso/identifiers.rb +0 -1
- data/lib/pubid/iso/normalizer.rb +89 -0
- data/lib/pubid/iso/parser.rb +22 -4
- data/lib/pubid/iso/supplement_identifier.rb +15 -2
- data/lib/pubid/iso/urn_generator.rb +66 -182
- data/lib/pubid/iso/urn_parser.rb +12 -7
- data/lib/pubid/iso.rb +173 -2
- data/lib/pubid/itu/builder.rb +0 -12
- data/lib/pubid/itu/components/code.rb +8 -0
- data/lib/pubid/itu/components.rb +11 -0
- data/lib/pubid/itu/identifier.rb +6 -104
- data/lib/pubid/itu/identifiers/amendment.rb +0 -2
- data/lib/pubid/itu/identifiers/annex.rb +0 -2
- data/lib/pubid/itu/identifiers/base.rb +0 -6
- data/lib/pubid/itu/identifiers/combined_identifier.rb +0 -2
- data/lib/pubid/itu/identifiers/corrigendum.rb +0 -2
- data/lib/pubid/itu/identifiers/recommendation.rb +0 -2
- data/lib/pubid/itu/identifiers/special_publication.rb +0 -2
- data/lib/pubid/itu/identifiers/supplement.rb +0 -2
- data/lib/pubid/itu/urn_parser.rb +23 -0
- data/lib/pubid/itu.rb +42 -1
- data/lib/pubid/jcgm/builder.rb +16 -8
- data/lib/pubid/jcgm/identifier.rb +0 -43
- data/lib/pubid/jcgm/identifiers/amendment.rb +2 -7
- data/lib/pubid/jcgm/identifiers/gum_guide.rb +2 -10
- data/lib/pubid/jcgm/renderer.rb +68 -0
- data/lib/pubid/jcgm/single_identifier.rb +1 -5
- data/lib/pubid/jcgm/urn_generator.rb +4 -6
- data/lib/pubid/jcgm/urn_parser.rb +23 -0
- data/lib/pubid/jcgm.rb +43 -2
- data/lib/pubid/jis/builder.rb +44 -52
- data/lib/pubid/jis/identifier.rb +132 -46
- data/lib/pubid/jis/identifiers/amendment.rb +1 -1
- data/lib/pubid/jis/identifiers/corrigendum.rb +16 -0
- data/lib/pubid/jis/identifiers/standard.rb +2 -1
- data/lib/pubid/jis/identifiers/technical_report.rb +2 -1
- data/lib/pubid/jis/identifiers/technical_specification.rb +2 -1
- data/lib/pubid/jis/identifiers.rb +1 -1
- data/lib/pubid/jis/parser.rb +31 -5
- data/lib/pubid/jis/renderer.rb +69 -0
- data/lib/pubid/jis/single_identifier.rb +6 -12
- data/lib/pubid/jis/supplement_identifier.rb +17 -14
- data/lib/pubid/jis/urn_parser.rb +23 -0
- data/lib/pubid/jis.rb +42 -2
- data/lib/pubid/nist/builder.rb +63 -1871
- data/lib/pubid/nist/caster.rb +1272 -0
- data/lib/pubid/nist/circular_supplement_builder.rb +291 -0
- data/lib/pubid/nist/components/code.rb +9 -20
- data/lib/pubid/nist/components/supplement.rb +2 -2
- data/lib/pubid/nist/components.rb +0 -1
- data/lib/pubid/nist/identifier.rb +11 -48
- data/lib/pubid/nist/identifiers/base.rb +110 -47
- data/lib/pubid/nist/identifiers/circular.rb +7 -2
- data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -1
- data/lib/pubid/nist/identifiers/commercial_standard.rb +2 -1
- data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +6 -4
- data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +10 -3
- data/lib/pubid/nist/identifiers/crpl_report.rb +8 -8
- data/lib/pubid/nist/identifiers/dated_document.rb +49 -0
- data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +15 -24
- data/lib/pubid/nist/identifiers/grant_contractor_report.rb +2 -1
- data/lib/pubid/nist/identifiers/handbook.rb +2 -1
- data/lib/pubid/nist/identifiers/internal_report.rb +2 -1
- data/lib/pubid/nist/identifiers/letter_circular.rb +2 -1
- data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +5 -4
- data/lib/pubid/nist/identifiers/monograph.rb +7 -3
- data/lib/pubid/nist/identifiers/report.rb +4 -2
- data/lib/pubid/nist/identifiers/special_publication.rb +2 -1
- data/lib/pubid/nist/identifiers/technical_note.rb +3 -2
- data/lib/pubid/nist/identifiers.rb +1 -0
- data/lib/pubid/nist/parser.rb +62 -452
- data/lib/pubid/nist/parser_output_normalizer.rb +233 -0
- data/lib/pubid/nist/preprocessor.rb +416 -0
- data/lib/pubid/nist/renderer.rb +43 -0
- data/lib/pubid/nist/router.rb +148 -0
- data/lib/pubid/nist/series/base.rb +58 -0
- data/lib/pubid/nist/series/crpl.rb +13 -0
- data/lib/pubid/nist/series/fips.rb +14 -0
- data/lib/pubid/nist/series/ir.rb +60 -0
- data/lib/pubid/nist/series/letter_preserving.rb +15 -0
- data/lib/pubid/nist/series/mono.rb +19 -0
- data/lib/pubid/nist/series/ncstar.rb +20 -0
- data/lib/pubid/nist/series.rb +49 -0
- data/lib/pubid/nist/supplement_identifier.rb +3 -1
- data/lib/pubid/nist/urn_parser.rb +67 -0
- data/lib/pubid/nist.rb +82 -4
- data/lib/pubid/oiml/components/code.rb +10 -0
- data/lib/pubid/oiml/identifier.rb +0 -50
- data/lib/pubid/oiml/identifiers/annex.rb +3 -45
- data/lib/pubid/oiml/identifiers/base.rb +2 -17
- data/lib/pubid/oiml/renderer.rb +161 -0
- data/lib/pubid/oiml/single_identifier.rb +6 -45
- data/lib/pubid/oiml/supplement_identifier.rb +4 -19
- data/lib/pubid/oiml/urn_generator.rb +0 -8
- data/lib/pubid/oiml/urn_parser.rb +22 -0
- data/lib/pubid/oiml.rb +42 -1
- data/lib/pubid/plateau/identifier.rb +7 -41
- data/lib/pubid/plateau/identifiers/handbook.rb +1 -3
- data/lib/pubid/plateau/identifiers/technical_report.rb +1 -1
- data/lib/pubid/plateau/renderer.rb +51 -0
- data/lib/pubid/plateau/supplement_identifier.rb +1 -1
- data/lib/pubid/plateau/urn_parser.rb +43 -0
- data/lib/pubid/plateau.rb +43 -1
- data/lib/pubid/renderers/directives_renderer.rb +22 -8
- data/lib/pubid/renderers/guide_renderer.rb +4 -2
- data/lib/pubid/renderers/human_readable.rb +18 -7
- data/lib/pubid/rendering/context.rb +28 -19
- data/lib/pubid/rendering.rb +0 -3
- data/lib/pubid/sae/components/date.rb +8 -0
- data/lib/pubid/sae/components/type.rb +5 -1
- data/lib/pubid/sae/identifier.rb +0 -23
- data/lib/pubid/sae/identifiers/base.rb +2 -16
- data/lib/pubid/sae/renderer.rb +36 -0
- data/lib/pubid/sae/urn_generator.rb +2 -10
- data/lib/pubid/sae/urn_parser.rb +36 -0
- data/lib/pubid/sae.rb +42 -1
- data/lib/pubid/urn_generator/base.rb +12 -12
- data/lib/pubid/urn_parser/base.rb +81 -0
- data/lib/pubid/urn_parser/errors.rb +9 -0
- data/lib/pubid/urn_parser.rb +14 -0
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid.rb +29 -7
- data/lib/tasks/website-data.json +1940 -1882
- metadata +75 -44
- data/lib/pubid/amca/scheme.rb +0 -16
- data/lib/pubid/ansi/scheme.rb +0 -15
- data/lib/pubid/api/scheme.rb +0 -66
- data/lib/pubid/ashrae/scheme.rb +0 -53
- data/lib/pubid/asme/scheme.rb +0 -37
- data/lib/pubid/astm/scheme.rb +0 -55
- data/lib/pubid/bsi/identifiers/base.rb +0 -11
- data/lib/pubid/bsi/scheme.rb +0 -243
- data/lib/pubid/ccsds/scheme.rb +0 -57
- data/lib/pubid/cen_cenelec/scheme.rb +0 -164
- data/lib/pubid/cie/scheme.rb +0 -64
- data/lib/pubid/components/factory.rb +0 -50
- data/lib/pubid/csa/scheme.rb +0 -44
- data/lib/pubid/etsi/scheme.rb +0 -42
- data/lib/pubid/export/data_class_exporter.rb +0 -59
- data/lib/pubid/export/ieee_exporter.rb +0 -78
- data/lib/pubid/export/itu_exporter.rb +0 -66
- data/lib/pubid/export/nist_exporter.rb +0 -64
- data/lib/pubid/export/registry_exporter.rb +0 -90
- data/lib/pubid/export/scheme_exporter.rb +0 -70
- data/lib/pubid/identifier_registry.rb +0 -198
- data/lib/pubid/idf/scheme.rb +0 -61
- data/lib/pubid/iec/scheme.rb +0 -71
- data/lib/pubid/ieee/scheme.rb +0 -90
- data/lib/pubid/iho/scheme.rb +0 -29
- data/lib/pubid/iso/identifiers/base.rb +0 -115
- data/lib/pubid/iso/scheme.rb +0 -193
- data/lib/pubid/itu/scheme.rb +0 -174
- data/lib/pubid/jcgm/scheme.rb +0 -60
- data/lib/pubid/jis/components/code.rb +0 -59
- data/lib/pubid/jis/identifiers/base.rb +0 -72
- data/lib/pubid/jis/scheme.rb +0 -49
- data/lib/pubid/nist/components/publisher.rb +0 -24
- data/lib/pubid/nist/scheme.rb +0 -199
- data/lib/pubid/oiml/scheme.rb +0 -46
- data/lib/pubid/plateau/scheme.rb +0 -45
- data/lib/pubid/rendering/base.rb +0 -73
- data/lib/pubid/rendering/common.rb +0 -211
- data/lib/pubid/rendering/format.rb +0 -25
- data/lib/pubid/sae/scheme.rb +0 -47
- data/lib/pubid/scheme.rb +0 -219
data/lib/pubid/nist/builder.rb
CHANGED
|
@@ -3,26 +3,21 @@
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Nist
|
|
5
5
|
# Builder class for constructing NIST identifier objects from parsed data
|
|
6
|
-
# Single Responsibility:
|
|
6
|
+
# Single Responsibility: Orchestrate parsing pipeline (pre-processing -> routing -> casting -> construction)
|
|
7
7
|
#
|
|
8
8
|
# CRITICAL ARCHITECTURE PRINCIPLE:
|
|
9
9
|
# Builder NEVER makes business logic decisions.
|
|
10
10
|
# Builder ONLY casts parsed data to domain objects.
|
|
11
|
+
#
|
|
12
|
+
# Delegates:
|
|
13
|
+
# - Router: series-to-class mapping (which identifier class to instantiate)
|
|
14
|
+
# - Caster: type coercion (parsed values -> domain component objects)
|
|
11
15
|
class Builder < Pubid::Builder::Base
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"id" => "ind",
|
|
18
|
-
"chi" => "zho",
|
|
19
|
-
"viet" => "vie",
|
|
20
|
-
"port" => "por",
|
|
21
|
-
"esp" => "spa",
|
|
22
|
-
}.freeze
|
|
23
|
-
|
|
24
|
-
def initialize(scheme)
|
|
25
|
-
@scheme = scheme
|
|
16
|
+
def initialize
|
|
17
|
+
@router = Router.new
|
|
18
|
+
@caster = Caster.new
|
|
19
|
+
@normalizer = ParserOutputNormalizer.new
|
|
20
|
+
@circular_supplement_builder = CircularSupplementBuilder.new(self)
|
|
26
21
|
end
|
|
27
22
|
|
|
28
23
|
# Build an identifier object from parsed data
|
|
@@ -32,16 +27,10 @@ module Pubid
|
|
|
32
27
|
# Parslet can return array of hashes - merge them
|
|
33
28
|
parsed_hash = parsed.is_a?(Array) ? flatten_array(parsed) : parsed
|
|
34
29
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
# Move edition_e to update_year
|
|
40
|
-
edition_id = parsed_hash[:edition_e][:edition_id]
|
|
41
|
-
parsed_hash[:update] =
|
|
42
|
-
parsed_hash[:update].merge(update_year: edition_id)
|
|
43
|
-
parsed_hash.delete(:edition_e) # Remove the edition_e hash
|
|
44
|
-
end
|
|
30
|
+
# Pure in-place shape corrections: parser-output normalization that
|
|
31
|
+
# only mutates parsed_hash. Extraction logic that needs to surface
|
|
32
|
+
# components to the construction phase stays below.
|
|
33
|
+
@normalizer.normalize(parsed_hash)
|
|
45
34
|
|
|
46
35
|
# NEW: Fix for letter suffix in number with edition_dash_year
|
|
47
36
|
# Pattern: "304a-2017" where parser returns first_number="304a" and edition_dash_year="2017"
|
|
@@ -59,7 +48,7 @@ module Pubid
|
|
|
59
48
|
|
|
60
49
|
# Update first_number to exclude letter suffix
|
|
61
50
|
parsed_hash[:first_number] =
|
|
62
|
-
Components::Code.new(
|
|
51
|
+
Components::Code.new(value: base_number)
|
|
63
52
|
|
|
64
53
|
# Store Part component for later (after identifier is initialized)
|
|
65
54
|
letter_suffix_part = Components::Part.new(type: "",
|
|
@@ -73,232 +62,12 @@ module Pubid
|
|
|
73
62
|
end
|
|
74
63
|
end
|
|
75
64
|
|
|
76
|
-
# NEW: Fix for edition_dash_year with embedded edition in first_number
|
|
77
|
-
# Pattern: "44e2-1955" where first_number="44e2" and edition_dash_year="1955"
|
|
78
|
-
# Expected: edition extracted from "44e2" (type: "e", id: "2") with additional_text="1955"
|
|
79
|
-
#
|
|
80
|
-
# We need to check for this pattern BEFORE the simple year-as-edition pattern below
|
|
81
|
-
if parsed_hash[:first_number]&.to_s&.match?(/^[0-9]+[a-zA-Z]\d+$/) && parsed_hash[:edition_dash_year]
|
|
82
|
-
# Extract edition from embedded pattern (e.g., "44e2" -> type="e", id="2")
|
|
83
|
-
number_str = parsed_hash[:first_number].to_s
|
|
84
|
-
if match_data = number_str.match(/^(\d+)([a-zA-Z])(\d+)$/)
|
|
85
|
-
base_number = match_data[1]
|
|
86
|
-
edition_type = match_data[2].downcase
|
|
87
|
-
edition_id = match_data[3]
|
|
88
|
-
|
|
89
|
-
# Update first_number to base number only
|
|
90
|
-
parsed_hash[:first_number] =
|
|
91
|
-
Components::Code.new(number: base_number)
|
|
92
|
-
|
|
93
|
-
# Create Edition with additional_text from edition_dash_year
|
|
94
|
-
dash_year = parsed_hash[:edition_dash_year][:dash_year]
|
|
95
|
-
edition_obj = Components::Edition.new(
|
|
96
|
-
type: edition_type,
|
|
97
|
-
id: edition_id,
|
|
98
|
-
additional_text: dash_year,
|
|
99
|
-
)
|
|
100
|
-
parsed_hash[:edition_with_year] = edition_obj
|
|
101
|
-
parsed_hash.delete(:edition_dash_year)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# NEW: Fix for edition embedded in first_number WITHOUT edition_dash_year
|
|
106
|
-
# Pattern: "8115r1" where parser returns first_number="8115r1" with NO edition_dash_year
|
|
107
|
-
# Expected: first_number="8115", edition with type="r", id="1"
|
|
108
|
-
# This handles patterns like IR "8115r1/upd" where r1 is the edition
|
|
109
|
-
# CRITICAL: Only process if NO second_number is present!
|
|
110
|
-
# Otherwise, the compound number logic (lines 418-437) will handle edition+year patterns
|
|
111
|
-
if !parsed_hash[:second_number] &&
|
|
112
|
-
parsed_hash[:first_number]&.to_s&.match?(/^[0-9]+[a-zA-Z]\d+$/) &&
|
|
113
|
-
!parsed_hash[:edition_dash_year]
|
|
114
|
-
number_str = parsed_hash[:first_number].to_s
|
|
115
|
-
if match_data = number_str.match(/^(\d+)([a-zA-Z])(\d+)$/)
|
|
116
|
-
base_number = match_data[1]
|
|
117
|
-
edition_type = match_data[2].downcase
|
|
118
|
-
edition_id = match_data[3]
|
|
119
|
-
|
|
120
|
-
# Update first_number to base number only
|
|
121
|
-
parsed_hash[:first_number] =
|
|
122
|
-
Components::Code.new(number: base_number)
|
|
123
|
-
|
|
124
|
-
# Create Edition without additional_text (no year in this pattern)
|
|
125
|
-
edition_obj = Components::Edition.new(
|
|
126
|
-
type: edition_type,
|
|
127
|
-
id: edition_id,
|
|
128
|
-
)
|
|
129
|
-
parsed_hash[:edition_with_year] = edition_obj
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# REMOVED: edition_r_with_space pre-processing
|
|
134
|
-
# Now handled by cast_edition method with original_prefix preservation
|
|
135
|
-
|
|
136
|
-
# NEW: Fix for second_number_edition_year pattern
|
|
137
|
-
# Pattern: "105-1-1990" where parser returns second_number_edition_year={second_number="1", dash_year="1990"}
|
|
138
|
-
# Expected: number="105-1", edition with type="e", id="1990", original_prefix="-"
|
|
139
|
-
if parsed_hash[:second_number_edition_year]
|
|
140
|
-
second_num_value = parsed_hash[:second_number_edition_year][:second_number]
|
|
141
|
-
dash_year = parsed_hash[:second_number_edition_year][:dash_year]
|
|
142
|
-
|
|
143
|
-
# Extract second_number and edition_dash_year from the combined hash
|
|
144
|
-
parsed_hash[:second_number] = second_num_value
|
|
145
|
-
|
|
146
|
-
# For HB (Handbook) series, create year-only edition with dash rendering
|
|
147
|
-
is_handbook = begin
|
|
148
|
-
parsed_hash[:series].to_s == "HB"
|
|
149
|
-
rescue StandardError
|
|
150
|
-
false
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
if is_handbook && dash_year.to_s.match?(/^\d{4}$/)
|
|
154
|
-
# Create Edition with type="e" and id=dash_year for HB series
|
|
155
|
-
edition_obj = Components::Edition.new(type: "e", id: dash_year)
|
|
156
|
-
parsed_hash[:edition_from_year] = edition_obj
|
|
157
|
-
else
|
|
158
|
-
# For other series, treat dash_year as edition_dash_year for further processing
|
|
159
|
-
parsed_hash[:edition_dash_year] = { dash_year: dash_year }
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
parsed_hash.delete(:second_number_edition_year)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# NEW: Fix for fips_month_year_after_part pattern
|
|
166
|
-
# Pattern: "11-1-Sep1977" where parser returns fips_month_year_after_part={second_number="1", edition_month="Sep", edition_year="1977"}
|
|
167
|
-
# Expected: number="11-1", edition with type="e", id="197709" (year + month number)
|
|
168
|
-
if parsed_hash[:fips_month_year_after_part]
|
|
169
|
-
second_num_value = parsed_hash[:fips_month_year_after_part][:second_number]
|
|
170
|
-
month_str = parsed_hash[:fips_month_year_after_part][:edition_month]
|
|
171
|
-
year_str = parsed_hash[:fips_month_year_after_part][:edition_year]
|
|
172
|
-
|
|
173
|
-
# Extract second_number
|
|
174
|
-
parsed_hash[:second_number] = second_num_value
|
|
175
|
-
|
|
176
|
-
# Convert month abbreviation to month number and combine with year
|
|
177
|
-
month_num = Date::ABBR_MONTHNAMES.index(month_str) ||
|
|
178
|
-
Date::MONTHNAMES.index(month_str) ||
|
|
179
|
-
month_str.to_i
|
|
180
|
-
|
|
181
|
-
edition_id = if month_num&.positive?
|
|
182
|
-
"#{year_str}#{format('%02d', month_num)}"
|
|
183
|
-
else
|
|
184
|
-
year_str
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Create Edition with type="e" and combined ID
|
|
188
|
-
edition_obj = Components::Edition.new(type: "e", id: edition_id)
|
|
189
|
-
parsed_hash[:edition_from_year] = edition_obj
|
|
190
|
-
|
|
191
|
-
parsed_hash.delete(:fips_month_year_after_part)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# NEW: Fix for IR compound number vs edition pattern
|
|
195
|
-
# Pattern: "84-2946" where parser returns first_number="84", edition_dash_year={dash_year="2946"}
|
|
196
|
-
# For IR (InteragencyReport), 4-digit second numbers >= 1000 are typically editions
|
|
197
|
-
# EXCEPT for:
|
|
198
|
-
# - Numbers > 2699 which are clearly not valid years (like 2946)
|
|
199
|
-
# - Patterns with embedded edition_e (like "76-1094e2") which should be compound
|
|
200
|
-
if parsed_hash[:series]&.to_s == "IR" && parsed_hash[:first_number] && parsed_hash[:edition_dash_year]
|
|
201
|
-
first_num = parsed_hash[:first_number].to_s
|
|
202
|
-
dash_year = parsed_hash[:edition_dash_year][:dash_year].to_s
|
|
203
|
-
|
|
204
|
-
# Check if first_number looks like a 2-digit year (00-99)
|
|
205
|
-
if first_num.match?(/^\d{2}$/) && dash_year.match?(/^\d{4}$/)
|
|
206
|
-
dash_year_num = dash_year.to_i
|
|
207
|
-
# Valid year range for IR: 1901-2026 (NBS establishment to present)
|
|
208
|
-
is_valid_year = dash_year_num.between?(1901, 2026)
|
|
209
|
-
# If there's an embedded edition (e2, e3, etc.), treat as compound, not edition
|
|
210
|
-
has_embedded_edition = parsed_hash[:edition_e]
|
|
211
|
-
if is_valid_year && !has_embedded_edition
|
|
212
|
-
# Edition format: "76e1000", "76e2013", "76e1100", "80e2100", "81e2300", "81e2400", "82e2500", "82e2600"
|
|
213
|
-
parsed_hash[:first_number] =
|
|
214
|
-
Components::Code.new(number: first_num)
|
|
215
|
-
parsed_hash[:edition] =
|
|
216
|
-
Components::Edition.new(type: "e", id: dash_year)
|
|
217
|
-
else
|
|
218
|
-
# Compound number: "84-2946" or "76-1094e2"
|
|
219
|
-
parsed_hash[:first_number] =
|
|
220
|
-
Components::Code.new(number: "#{first_num}-#{dash_year}")
|
|
221
|
-
end
|
|
222
|
-
parsed_hash.delete(:edition_dash_year)
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# NEW: Fix for edition_dash_year that's actually a second_number
|
|
227
|
-
# Pattern: "250-1039" where parser returns edition_dash_year="1039"
|
|
228
|
-
# Also handles RPT date ranges: "1946-1947" where edition_dash_year="1947"
|
|
229
|
-
# but it should be second_number="1039" (not a year - years are 1900+ or 2000+)
|
|
230
|
-
if parsed_hash[:first_number] && parsed_hash[:edition_dash_year] && !parsed_hash[:first_number].to_s.match?(/^[0-9]+[a-zA-Z]\d+$/)
|
|
231
|
-
dash_year = parsed_hash[:edition_dash_year][:dash_year].to_s
|
|
232
|
-
series = begin
|
|
233
|
-
parsed_hash[:series].to_s
|
|
234
|
-
rescue StandardError
|
|
235
|
-
""
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
# Check if dash_year is a valid year (1901-2026)
|
|
239
|
-
dash_year_num = dash_year.to_i
|
|
240
|
-
is_valid_year = dash_year_num.between?(1901, 2026)
|
|
241
|
-
|
|
242
|
-
# Check if series uses dash-year as edition (HB, CS, FIPS)
|
|
243
|
-
# These series convert dash-year to edition ONLY for valid years
|
|
244
|
-
uses_dash_year_as_edition = ["HB", "CS", "FIPS"].include?(series)
|
|
245
|
-
|
|
246
|
-
# GCR always converts dash-year to edition (e.g., "15-1000" → "15e1000")
|
|
247
|
-
is_gcr = series == "GCR"
|
|
248
|
-
|
|
249
|
-
# IR only converts dash-year to edition for valid years (e.g., "76-1100" → "76e1100")
|
|
250
|
-
# but NOT for non-years like 2946 (e.g., "84-2946" stays as "84-2946")
|
|
251
|
-
is_ir = series == "IR"
|
|
252
|
-
|
|
253
|
-
# Check if series uses compound numbers with dash-year (RPT date ranges)
|
|
254
|
-
is_rpt = series == "RPT"
|
|
255
|
-
|
|
256
|
-
if is_rpt
|
|
257
|
-
# For RPT (Report) series with date ranges, create compound number: "1946-1947"
|
|
258
|
-
parsed_hash[:first_number] =
|
|
259
|
-
Components::Code.new(number: "#{parsed_hash[:first_number]}-#{dash_year}")
|
|
260
|
-
parsed_hash.delete(:edition_dash_year)
|
|
261
|
-
elsif is_gcr
|
|
262
|
-
# GCR always converts dash_year to edition regardless of whether it's a valid year
|
|
263
|
-
# e.g., "15-1000" → "15e1000", "15-1001" → "15e1001"
|
|
264
|
-
edition_obj = Components::Edition.new(type: "e", id: dash_year)
|
|
265
|
-
parsed_hash[:edition_from_year] = edition_obj
|
|
266
|
-
parsed_hash.delete(:edition_dash_year)
|
|
267
|
-
elsif is_ir && is_valid_year
|
|
268
|
-
# IR only converts valid years to edition
|
|
269
|
-
# e.g., "76-1100" → "76e1100", but "84-2946" stays as "84-2946"
|
|
270
|
-
edition_obj = Components::Edition.new(type: "e", id: dash_year)
|
|
271
|
-
parsed_hash[:edition_from_year] = edition_obj
|
|
272
|
-
parsed_hash.delete(:edition_dash_year)
|
|
273
|
-
elsif uses_dash_year_as_edition && is_valid_year
|
|
274
|
-
# For HB, CS, FIPS: convert dash_year to edition ONLY if it's a valid year
|
|
275
|
-
edition_obj = Components::Edition.new(type: "e", id: dash_year)
|
|
276
|
-
parsed_hash[:edition_from_year] = edition_obj
|
|
277
|
-
parsed_hash.delete(:edition_dash_year)
|
|
278
|
-
elsif dash_year.to_i < 1900
|
|
279
|
-
# For other series with dash_year < 1900, treat as second_number
|
|
280
|
-
parsed_hash[:second_number] = dash_year
|
|
281
|
-
parsed_hash.delete(:edition_dash_year)
|
|
282
|
-
elsif is_valid_year
|
|
283
|
-
# For remaining cases with valid year (>= 1901), check if series uses it as edition
|
|
284
|
-
is_handbook = series == "HB"
|
|
285
|
-
is_commercial_standard = series == "CS"
|
|
286
|
-
is_fips = series == "FIPS"
|
|
287
|
-
|
|
288
|
-
if is_handbook || is_commercial_standard || is_fips
|
|
289
|
-
edition_obj = Components::Edition.new(type: "e", id: dash_year)
|
|
290
|
-
parsed_hash[:edition_from_year] = edition_obj
|
|
291
|
-
parsed_hash.delete(:edition_dash_year)
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
|
|
296
65
|
# NEW: Fix for edition embedded in second_number
|
|
297
66
|
# Pattern: "53e5" where second_number="53e5" with edition "e5" embedded
|
|
298
67
|
# Expected: second_number="53", edition with type="e" and id="5"
|
|
299
68
|
if parsed_hash[:second_number]&.to_s&.match?(/^\d+[a-zA-Z]\d+$/)
|
|
300
69
|
second_str = parsed_hash[:second_number].to_s
|
|
301
|
-
# Extract edition from second_number (e.g., "53e5"
|
|
70
|
+
# Extract edition from second_number (e.g., "53e5" -> "53" + edition "e5")
|
|
302
71
|
if match_data = second_str.match(/^(\d+)([a-zA-Z])(\d+)$/)
|
|
303
72
|
base_number = match_data[1]
|
|
304
73
|
edition_letter = match_data[2]
|
|
@@ -306,7 +75,7 @@ module Pubid
|
|
|
306
75
|
|
|
307
76
|
# Update second_number and create Edition component
|
|
308
77
|
parsed_hash[:second_number] =
|
|
309
|
-
Components::Code.new(
|
|
78
|
+
Components::Code.new(value: base_number)
|
|
310
79
|
# Store Edition component for later (after identifier is initialized)
|
|
311
80
|
edition_from_embedded = Components::Edition.new(
|
|
312
81
|
type: edition_letter, id: edition_id,
|
|
@@ -322,8 +91,12 @@ module Pubid
|
|
|
322
91
|
return build_circular_supplement(parsed_hash)
|
|
323
92
|
end
|
|
324
93
|
|
|
325
|
-
# Locate the appropriate identifier class via
|
|
326
|
-
identifier = @
|
|
94
|
+
# Locate the appropriate identifier class via Router
|
|
95
|
+
identifier = @router.locate_identifier_klass(parsed_hash).new
|
|
96
|
+
|
|
97
|
+
# Resolve the series policy once — every series-specific decision
|
|
98
|
+
# flows through this object instead of branching on the parsed shape.
|
|
99
|
+
series = Series.for(parsed_hash)
|
|
327
100
|
|
|
328
101
|
# NEW: If we extracted a letter suffix Part, assign it now (after identifier initialization)
|
|
329
102
|
if letter_suffix_part
|
|
@@ -390,9 +163,11 @@ module Pubid
|
|
|
390
163
|
|
|
391
164
|
# Cast and assign all attributes
|
|
392
165
|
parsed_hash.each_pair do |key, value|
|
|
393
|
-
realized_components = cast(key.to_sym, value, parsed_hash) # Pass parsed_hash for context
|
|
166
|
+
realized_components = @caster.cast(key.to_sym, value, parsed_hash) # Pass parsed_hash for context
|
|
394
167
|
next if realized_components.nil?
|
|
395
|
-
next if !realized_components.is_a?(Hash) && capture_supplement.call(
|
|
168
|
+
next if !realized_components.is_a?(Hash) && capture_supplement.call(
|
|
169
|
+
key.to_sym, realized_components
|
|
170
|
+
)
|
|
396
171
|
|
|
397
172
|
# Track number components
|
|
398
173
|
if key == :first_number && realized_components.is_a?(Components::Code)
|
|
@@ -478,33 +253,25 @@ module Pubid
|
|
|
478
253
|
elsif decimal_num
|
|
479
254
|
decimal_base = decimal_num[:decimal_base].to_s
|
|
480
255
|
decimal_suffix = decimal_num[:decimal_suffix].to_s
|
|
481
|
-
identifier.number = Components::Code.new(
|
|
256
|
+
identifier.number = Components::Code.new(value: "#{first_num.value}-#{decimal_base}.#{decimal_suffix}")
|
|
482
257
|
# NEW: Handle letter number pattern (e.g., 1-1A, 1-3B for NCSTAR identifiers)
|
|
483
258
|
# letter_num is {:letter_base => "1", :letter_suffix => "A"}
|
|
484
|
-
# Also handles IR series "R" suffix: "79-1786R"
|
|
259
|
+
# Also handles IR series "R" suffix: "79-1786R" -> "79-1786r1"
|
|
485
260
|
elsif letter_num
|
|
486
261
|
letter_base = letter_num[:letter_base].to_s
|
|
487
262
|
letter_suffix = letter_num[:letter_suffix].to_s
|
|
488
263
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
# IR "R"
|
|
494
|
-
identifier.number = Components::Code.new(number: "#{first_num.value}-#{letter_base}")
|
|
495
|
-
edition_obj = Components::Edition.new(type: "r", id: "1")
|
|
496
|
-
identifier.edition = edition_obj
|
|
497
|
-
identifier.edition_component = edition_obj
|
|
498
|
-
identifier.revision = "r1"
|
|
499
|
-
# If a Part component was already set (from cast handler), the letter_suffix
|
|
500
|
-
# is a separate Part component (e.g., SpecialPublication "800-56A" → number="800-56", part="A")
|
|
501
|
-
# Otherwise, letter_suffix is part of the number (e.g., NCSTAR "1-1A" → number="1-1A")
|
|
264
|
+
if series.handle_letter_num_compound?(identifier,
|
|
265
|
+
first_num: first_num,
|
|
266
|
+
letter_base: letter_base,
|
|
267
|
+
letter_suffix: letter_suffix)
|
|
268
|
+
# Series-specific handler took ownership (e.g., IR "R" → r1)
|
|
502
269
|
elsif identifier.part
|
|
503
270
|
# SpecialPublication pattern: letter_suffix is separate Part component
|
|
504
|
-
identifier.number = Components::Code.new(
|
|
271
|
+
identifier.number = Components::Code.new(value: "#{first_num.value}-#{letter_base}")
|
|
505
272
|
else
|
|
506
273
|
# NCSTAR pattern: letter_suffix is part of the number
|
|
507
|
-
identifier.number = Components::Code.new(
|
|
274
|
+
identifier.number = Components::Code.new(value: "#{first_num.value}-#{letter_base}#{letter_suffix}")
|
|
508
275
|
end
|
|
509
276
|
elsif second_num
|
|
510
277
|
# Check for special patterns first
|
|
@@ -518,11 +285,11 @@ module Pubid
|
|
|
518
285
|
# Create Edition component
|
|
519
286
|
edition_obj = Components::Edition.new(type: "r", id: edition_id)
|
|
520
287
|
|
|
521
|
-
identifier.number = Components::Code.new(
|
|
288
|
+
identifier.number = Components::Code.new(value: "#{first_num.value}-#{number_part}")
|
|
522
289
|
identifier.edition = edition_obj
|
|
523
290
|
identifier.edition_component = edition_obj
|
|
524
291
|
identifier.revision = "r#{edition_id}"
|
|
525
|
-
# CS Emergency pattern: e104-43
|
|
292
|
+
# CS Emergency pattern: e104-43 -> number=104, edition_year=1943
|
|
526
293
|
# Logic: e104-43 means "emergency 104 from 1943" (43 = 1943)
|
|
527
294
|
elsif first_num.value.to_s.match?(/^e(\d{3})$/) &&
|
|
528
295
|
second_num.value.to_s.match?(/^\d{2}$/)
|
|
@@ -535,7 +302,7 @@ module Pubid
|
|
|
535
302
|
# Create Edition component
|
|
536
303
|
edition_obj = Components::Edition.new(type: "e", id: edition_year)
|
|
537
304
|
|
|
538
|
-
identifier.number = Components::Code.new(
|
|
305
|
+
identifier.number = Components::Code.new(value: number_part)
|
|
539
306
|
identifier.edition = edition_obj
|
|
540
307
|
identifier.edition_component = edition_obj
|
|
541
308
|
elsif first_num.value.to_s.match?(/^(\d+)e(\d+)$/) &&
|
|
@@ -547,13 +314,13 @@ module Pubid
|
|
|
547
314
|
edition_id = match_data[2]
|
|
548
315
|
year_part = second_num.value.to_s
|
|
549
316
|
|
|
550
|
-
# Expand 2-digit year to 4-digit (50
|
|
317
|
+
# Expand 2-digit year to 4-digit (50 -> 1950)
|
|
551
318
|
year_part = "19#{year_part}" if year_part.length == 2
|
|
552
319
|
|
|
553
|
-
identifier.number = Components::Code.new(
|
|
320
|
+
identifier.number = Components::Code.new(value: number_part)
|
|
554
321
|
|
|
555
322
|
# For edition+year patterns, handling depends on identifier type:
|
|
556
|
-
# - CIRC: edition number + year as additional_text, rendered with dot ("11e2-1915"
|
|
323
|
+
# - CIRC: edition number + year as additional_text, rendered with dot ("11e2-1915" -> "11e2.1915")
|
|
557
324
|
# - HB, others: edition number + year as additional_text, rendered with dash ("44e2-1955")
|
|
558
325
|
# Both use the same Edition component structure, only rendering differs
|
|
559
326
|
edition_obj = Components::Edition.new(type: "e",
|
|
@@ -566,7 +333,7 @@ module Pubid
|
|
|
566
333
|
number_part = first_num.value.to_s.match(/^(\d+)supp?$/)[1]
|
|
567
334
|
year_part = second_num.value.to_s
|
|
568
335
|
|
|
569
|
-
identifier.number = Components::Code.new(
|
|
336
|
+
identifier.number = Components::Code.new(value: number_part)
|
|
570
337
|
supp[:value] = year_part
|
|
571
338
|
supp[:present] = true
|
|
572
339
|
elsif second_num.value.to_s.match?(/^(\d+)supp?$/)
|
|
@@ -574,7 +341,7 @@ module Pubid
|
|
|
574
341
|
# second number. Strip it and isolate as supplement="" (single-p).
|
|
575
342
|
second_part = second_num.value.to_s.match(/^(\d+)supp?$/)[1]
|
|
576
343
|
compound = "#{first_num.value}-#{second_part}"
|
|
577
|
-
identifier.number = Components::Code.new(
|
|
344
|
+
identifier.number = Components::Code.new(value: compound)
|
|
578
345
|
supp[:value] = ""
|
|
579
346
|
supp[:present] = true
|
|
580
347
|
elsif identifier.is_a?(Identifiers::TechnicalNote) &&
|
|
@@ -587,18 +354,17 @@ module Pubid
|
|
|
587
354
|
identifier.edition_component = edition_obj
|
|
588
355
|
identifier.edition = edition_obj
|
|
589
356
|
identifier.edition_year = second_num.value.to_s
|
|
590
|
-
elsif part_num &&
|
|
591
|
-
#
|
|
592
|
-
#
|
|
357
|
+
elsif part_num && series.part_num_as_component?
|
|
358
|
+
# IR pattern: part_num becomes a Part component (type="pt"),
|
|
359
|
+
# not folded into the compound number.
|
|
593
360
|
identifier.part = Components::Part.new(type: "pt",
|
|
594
361
|
value: part_num)
|
|
595
|
-
identifier.number = Components::Code.new(
|
|
596
|
-
# For IR, create Part component with type="pt"
|
|
362
|
+
identifier.number = Components::Code.new(value: "#{first_num.value}-#{second_num.value}")
|
|
597
363
|
else
|
|
598
364
|
# For GCR and others, include part number in compound number
|
|
599
365
|
compound_value = "#{first_num.value}-#{second_num.value}"
|
|
600
366
|
compound_value += "-#{part_num}" if part_num
|
|
601
|
-
identifier.number = Components::Code.new(
|
|
367
|
+
identifier.number = Components::Code.new(value: compound_value)
|
|
602
368
|
end
|
|
603
369
|
else
|
|
604
370
|
# No second_num, use first_num directly
|
|
@@ -613,31 +379,15 @@ module Pubid
|
|
|
613
379
|
id: extracted_revision.to_s)
|
|
614
380
|
end
|
|
615
381
|
|
|
616
|
-
#
|
|
617
|
-
#
|
|
618
|
-
|
|
619
|
-
is_ir = begin
|
|
620
|
-
parsed_hash[:series].to_s.include?("IR")
|
|
621
|
-
rescue StandardError
|
|
622
|
-
false
|
|
623
|
-
end
|
|
624
|
-
if is_ir && identifier.number && identifier.number.value.to_s.match?(/^(\d+)e(\d{4})$/)
|
|
625
|
-
# Extract the compound number parts from the edition+year format
|
|
626
|
-
match_data = identifier.number.value.to_s.match(/^(\d+)e(\d{4})$/)
|
|
627
|
-
number_part = match_data[1] # "84"
|
|
628
|
-
year_part = match_data[2] # "2946"
|
|
629
|
-
|
|
630
|
-
# Convert to compound number format
|
|
631
|
-
identifier.number = Components::Code.new(number: "#{number_part}-#{year_part}")
|
|
632
|
-
|
|
633
|
-
# Clear the edition that was incorrectly set from the year
|
|
634
|
-
identifier.edition = nil
|
|
635
|
-
identifier.edition_component = nil
|
|
636
|
-
end
|
|
382
|
+
# Series-specific post-processing (e.g., IR reverses the "84e2946"
|
|
383
|
+
# form that preprocessing produced back to "84-2946").
|
|
384
|
+
series.finalize_identifier(identifier, parsed_hash)
|
|
637
385
|
|
|
638
|
-
#
|
|
639
|
-
#
|
|
640
|
-
|
|
386
|
+
# publisher_was_parsed defaults to true (see Identifiers::Base), so only
|
|
387
|
+
# the prefix-less case needs recording: assign false when no publisher
|
|
388
|
+
# was parsed/extracted, and leave it unset (default true, omitted from
|
|
389
|
+
# to_hash) when one was. Keeps the common publisher-bearing id lean.
|
|
390
|
+
identifier.publisher_was_parsed = false unless identifier.publisher
|
|
641
391
|
|
|
642
392
|
# NEW: Convert revision with month+year to update component (V1 compatibility)
|
|
643
393
|
# Patterns like "NIST IR 4743rJun1992" should be rendered as "NIST IR 4743/Upd1-199206"
|
|
@@ -646,8 +396,8 @@ module Pubid
|
|
|
646
396
|
month_str = parsed_hash[:revision_month].to_s
|
|
647
397
|
year_str = parsed_hash[:revision_year].to_s
|
|
648
398
|
|
|
649
|
-
# Convert month name to number (Jun
|
|
650
|
-
month_num = month_name_to_number(month_str)
|
|
399
|
+
# Convert month name to number (Jun -> 06, Nov -> 11, etc.)
|
|
400
|
+
month_num = @caster.month_name_to_number(month_str)
|
|
651
401
|
|
|
652
402
|
# Create update component with default number=1, converted year and month
|
|
653
403
|
update_obj = Components::Update.new(
|
|
@@ -669,7 +419,7 @@ module Pubid
|
|
|
669
419
|
# Fold the accumulated supplement signals into the single structured
|
|
670
420
|
# supplement component (the source of truth).
|
|
671
421
|
if (supp[:present] || supp[:has_revision]) &&
|
|
672
|
-
identifier.
|
|
422
|
+
identifier.class.attributes.key?(:supplement)
|
|
673
423
|
identifier.supplement = supplement_from(
|
|
674
424
|
value: supp[:value], has_revision: supp[:has_revision],
|
|
675
425
|
range_start: supp[:range_start], range_end: supp[:range_end]
|
|
@@ -701,1568 +451,10 @@ module Pubid
|
|
|
701
451
|
end
|
|
702
452
|
end
|
|
703
453
|
|
|
704
|
-
#
|
|
705
|
-
#
|
|
706
|
-
# @return [Integer] month number (1-12)
|
|
707
|
-
def month_name_to_number(month_name)
|
|
708
|
-
month_map = {
|
|
709
|
-
"Jan" => 1, "January" => 1,
|
|
710
|
-
"Feb" => 2, "February" => 2,
|
|
711
|
-
"Mar" => 3, "March" => 3,
|
|
712
|
-
"Apr" => 4, "April" => 4,
|
|
713
|
-
"May" => 5,
|
|
714
|
-
"Jun" => 6, "June" => 6,
|
|
715
|
-
"Jul" => 7, "July" => 7,
|
|
716
|
-
"Aug" => 8, "August" => 8,
|
|
717
|
-
"Sep" => 9, "Sept" => 9, "September" => 9,
|
|
718
|
-
"Oct" => 10, "October" => 10,
|
|
719
|
-
"Nov" => 11, "November" => 11,
|
|
720
|
-
"Dec" => 12, "December" => 12
|
|
721
|
-
}
|
|
722
|
-
month_map[month_name] || 1 # Default to January if not found
|
|
723
|
-
end
|
|
724
|
-
|
|
725
|
-
# Build CircularSupplement with base_identifier wrapping
|
|
726
|
-
# @param parsed_hash [Hash] the parsed supplement data
|
|
727
|
-
# Build a CIRC/LCIRC supplement. Most forms collapse onto the base
|
|
728
|
-
# identifier's normal class (Circular / LetterCircular) with isolated
|
|
729
|
-
# supplement attributes, so they share one model with every other series
|
|
730
|
-
# and are queryable by part. The V1-compat update forms (slash-year
|
|
731
|
-
# "118supp3/1926" → ".../Upd1-192603"; implicit revision "145r11/1925")
|
|
732
|
-
# render through an Update component the flat supplement model can't
|
|
733
|
-
# express, so they stay on the CircularSupplement wrapper for now.
|
|
454
|
+
# Delegate CIRC/LCIRC supplement construction to the dedicated builder.
|
|
455
|
+
# See CircularSupplementBuilder for the construction pipeline.
|
|
734
456
|
def build_circular_supplement(parsed_hash)
|
|
735
|
-
|
|
736
|
-
parsed_hash[:implicit_supplement].is_a?(Hash)
|
|
737
|
-
return build_circular_supplement_wrapper(parsed_hash)
|
|
738
|
-
end
|
|
739
|
-
|
|
740
|
-
series_value = if parsed_hash[:circ_series].is_a?(Hash)
|
|
741
|
-
parsed_hash[:circ_series][:series]
|
|
742
|
-
else
|
|
743
|
-
parsed_hash[:series]
|
|
744
|
-
end
|
|
745
|
-
|
|
746
|
-
# Date-range supplement (no base document): a plain base identifier with
|
|
747
|
-
# no number, carrying the range. parsed_format is left at the default so
|
|
748
|
-
# a dotted MR input still normalizes to the spaced short form.
|
|
749
|
-
if parsed_hash[:supplement_date_range].is_a?(Hash)
|
|
750
|
-
range = parsed_hash[:supplement_date_range]
|
|
751
|
-
identifier = build({ series: series_value })
|
|
752
|
-
ms = range[:supp_month_start]&.to_s
|
|
753
|
-
ys = range[:supp_year_start]&.to_s
|
|
754
|
-
me = range[:supp_month_end]&.to_s
|
|
755
|
-
ye = range[:supp_year_end]&.to_s
|
|
756
|
-
identifier.supplement = supplement_from(
|
|
757
|
-
value: nil, has_revision: false,
|
|
758
|
-
range_start: (ms && ys ? "#{ms}#{ys}" : nil),
|
|
759
|
-
range_end: (me && ye ? "#{me}#{ye}" : nil)
|
|
760
|
-
)
|
|
761
|
-
return identifier
|
|
762
|
-
end
|
|
763
|
-
|
|
764
|
-
# Based supplement: build the base, then attach the supplement as an
|
|
765
|
-
# isolated component on that normal class.
|
|
766
|
-
identifier = build_circular_supplement_base(parsed_hash, series_value)
|
|
767
|
-
raw = if parsed_hash[:supplement_month_year]
|
|
768
|
-
parsed_hash[:supplement_month_year].to_s
|
|
769
|
-
elsif parsed_hash[:supplement_year]
|
|
770
|
-
parsed_hash[:supplement_year].to_s
|
|
771
|
-
else
|
|
772
|
-
"" # supplement_empty or bare marker
|
|
773
|
-
end
|
|
774
|
-
identifier.supplement = Components::Supplement.from_raw(raw)
|
|
775
|
-
identifier
|
|
776
|
-
end
|
|
777
|
-
|
|
778
|
-
# Build just the base identifier for a based CIRC/LCIRC supplement, from
|
|
779
|
-
# base_portion (number, optional edition "101e2" or letter suffix "378G")
|
|
780
|
-
# or the merged first_number fallback. Returns the normal class.
|
|
781
|
-
def build_circular_supplement_base(parsed_hash, series_value)
|
|
782
|
-
base_portion = parsed_hash[:base_portion]
|
|
783
|
-
unless base_portion
|
|
784
|
-
return build({
|
|
785
|
-
publisher: parsed_hash[:publisher],
|
|
786
|
-
series: series_value || parsed_hash[:series],
|
|
787
|
-
first_number: parsed_hash[:first_number],
|
|
788
|
-
parsed_format: parsed_hash[:parsed_format],
|
|
789
|
-
})
|
|
790
|
-
end
|
|
791
|
-
|
|
792
|
-
base_number = if base_portion.is_a?(Hash)
|
|
793
|
-
base_portion[:simple_number] || base_portion[:base_number]
|
|
794
|
-
else
|
|
795
|
-
base_portion
|
|
796
|
-
end
|
|
797
|
-
|
|
798
|
-
letter_suffix = nil
|
|
799
|
-
if base_portion.is_a?(Hash) && base_portion[:letter_suffix]
|
|
800
|
-
letter_suffix = base_portion[:letter_suffix].to_s.upcase
|
|
801
|
-
end
|
|
802
|
-
|
|
803
|
-
publisher_value = nil
|
|
804
|
-
if parsed_hash[:circ_series].is_a?(Hash) && parsed_hash[:circ_series][:series]
|
|
805
|
-
series_str = parsed_hash[:circ_series][:series].to_s
|
|
806
|
-
publisher_value = series_str.split.first if series_str.include?(" ")
|
|
807
|
-
end
|
|
808
|
-
|
|
809
|
-
has_edition = base_portion.is_a?(Hash) && base_portion[:edition_number]
|
|
810
|
-
|
|
811
|
-
base_number_with_suffix = base_number.to_s
|
|
812
|
-
base_number_with_suffix += letter_suffix if letter_suffix
|
|
813
|
-
if has_edition
|
|
814
|
-
base_number_with_suffix += "e#{base_portion[:edition_number]}"
|
|
815
|
-
end
|
|
816
|
-
|
|
817
|
-
base_hash = {
|
|
818
|
-
series: series_value,
|
|
819
|
-
first_number: base_number_with_suffix,
|
|
820
|
-
parsed_format: parsed_hash[:parsed_format],
|
|
821
|
-
}
|
|
822
|
-
base_hash[:publisher] = publisher_value if publisher_value
|
|
823
|
-
base_hash[:edition_e] = { edition_id: base_portion[:edition_number] } if has_edition
|
|
824
|
-
|
|
825
|
-
build(base_hash)
|
|
826
|
-
end
|
|
827
|
-
|
|
828
|
-
# @return [Identifiers::CircularSupplement] the supplement identifier
|
|
829
|
-
def build_circular_supplement_wrapper(parsed_hash)
|
|
830
|
-
supplement = Identifiers::CircularSupplement.new
|
|
831
|
-
|
|
832
|
-
# Extract series from circ_series if present (nested structure from parser)
|
|
833
|
-
series_value = nil
|
|
834
|
-
if parsed_hash[:circ_series].is_a?(Hash)
|
|
835
|
-
series_value = parsed_hash[:circ_series][:series]
|
|
836
|
-
elsif parsed_hash[:series]
|
|
837
|
-
series_value = parsed_hash[:series]
|
|
838
|
-
end
|
|
839
|
-
|
|
840
|
-
# Handle date range supplement (no base)
|
|
841
|
-
if parsed_hash[:supplement_date_range].is_a?(Hash)
|
|
842
|
-
range = parsed_hash[:supplement_date_range]
|
|
843
|
-
month_start = range[:supp_month_start]&.to_s
|
|
844
|
-
year_start = range[:supp_year_start]&.to_s
|
|
845
|
-
month_end = range[:supp_month_end]&.to_s
|
|
846
|
-
year_end = range[:supp_year_end]&.to_s
|
|
847
|
-
|
|
848
|
-
supplement.supplement_date_range_start = "#{month_start}#{year_start}" if month_start && year_start
|
|
849
|
-
supplement.supplement_date_range_end = "#{month_end}#{year_end}" if month_end && year_end
|
|
850
|
-
|
|
851
|
-
return supplement
|
|
852
|
-
end
|
|
853
|
-
|
|
854
|
-
# Build base identifier from base_portion (if present)
|
|
855
|
-
# If not present (because it was merged during parsing), use first_number
|
|
856
|
-
if parsed_hash[:base_portion]
|
|
857
|
-
# Extract the actual number value from base_portion hash
|
|
858
|
-
# base_portion can be: {:simple_number=>"118"}, {:base_number=>"145", :revision_letter=>"r", :revision_number=>"11"}, etc.
|
|
859
|
-
base_portion = parsed_hash[:base_portion]
|
|
860
|
-
base_number = if base_portion.is_a?(Hash)
|
|
861
|
-
# Extract the value from whichever key is present
|
|
862
|
-
base_portion[:simple_number] || base_portion[:base_number]
|
|
863
|
-
else
|
|
864
|
-
base_portion
|
|
865
|
-
end
|
|
866
|
-
|
|
867
|
-
# Check for letter suffix in base_portion (e.g., "378G")
|
|
868
|
-
letter_suffix = nil
|
|
869
|
-
if base_portion.is_a?(Hash) && base_portion[:letter_suffix]
|
|
870
|
-
letter_suffix = base_portion[:letter_suffix].to_s.upcase
|
|
871
|
-
end
|
|
872
|
-
|
|
873
|
-
# Extract publisher from circ_series if present
|
|
874
|
-
publisher_value = nil
|
|
875
|
-
if parsed_hash[:circ_series].is_a?(Hash) && parsed_hash[:circ_series][:series]
|
|
876
|
-
series_str = parsed_hash[:circ_series][:series].to_s
|
|
877
|
-
# Extract publisher from series (e.g., "NBS LCIRC" -> "NBS")
|
|
878
|
-
publisher_value = series_str.split.first if series_str.include?(" ")
|
|
879
|
-
end
|
|
880
|
-
|
|
881
|
-
# Check if base_portion has revision (for patterns like "145r11/1925")
|
|
882
|
-
has_revision = base_portion.is_a?(Hash) && base_portion[:revision_letter] && base_portion[:revision_number]
|
|
883
|
-
|
|
884
|
-
# NEW: Check if base_portion has edition_number (for patterns like "101e2")
|
|
885
|
-
has_edition = base_portion.is_a?(Hash) && base_portion[:edition_number]
|
|
886
|
-
|
|
887
|
-
# Include letter suffix in base_number if present
|
|
888
|
-
# Also include edition_number if present (for "101e2" pattern)
|
|
889
|
-
base_number_with_suffix = base_number.to_s
|
|
890
|
-
if letter_suffix
|
|
891
|
-
base_number_with_suffix += letter_suffix
|
|
892
|
-
end
|
|
893
|
-
if has_edition
|
|
894
|
-
base_number_with_suffix += "e#{base_portion[:edition_number]}"
|
|
895
|
-
end
|
|
896
|
-
|
|
897
|
-
# Reconstruct parse hash for base identifier
|
|
898
|
-
base_hash = {
|
|
899
|
-
series: series_value,
|
|
900
|
-
first_number: base_number_with_suffix,
|
|
901
|
-
parsed_format: parsed_hash[:parsed_format],
|
|
902
|
-
}
|
|
903
|
-
base_hash[:publisher] = publisher_value if publisher_value
|
|
904
|
-
|
|
905
|
-
# NEW: Add edition_number to base_hash for patterns like "101e2"
|
|
906
|
-
# This will be processed by the normal build() logic to create Edition component
|
|
907
|
-
if has_edition
|
|
908
|
-
# Create edition_e hash that will be converted to Edition with type="e"
|
|
909
|
-
base_hash[:edition_e] =
|
|
910
|
-
{ edition_id: base_portion[:edition_number] }
|
|
911
|
-
end
|
|
912
|
-
|
|
913
|
-
# Recursively build base identifier
|
|
914
|
-
# This will go through normal build() process which extracts edition from "101e2"
|
|
915
|
-
supplement.base_identifier = build(base_hash)
|
|
916
|
-
|
|
917
|
-
# NEW: Handle revision + implicit supplement pattern (e.g., "145r11/1925")
|
|
918
|
-
# Create update format: "Upd1-{year}{revision_number}"
|
|
919
|
-
if has_revision && parsed_hash[:implicit_supplement].is_a?(Hash)
|
|
920
|
-
revision_number = base_portion[:revision_number].to_s
|
|
921
|
-
supplement_year = parsed_hash[:implicit_supplement][:implicit_supplement_year].to_s
|
|
922
|
-
|
|
923
|
-
# Create Update component for revision+supplement pattern
|
|
924
|
-
# Format: Upd1-{year}{revision_number} (always use "1" and concatenate year+revision)
|
|
925
|
-
update_value = "Upd1-#{supplement_year}#{revision_number}"
|
|
926
|
-
supplement.update = update_value
|
|
927
|
-
supplement.implicit_supplement = true # Mark as implicit supplement for rendering
|
|
928
|
-
end
|
|
929
|
-
elsif parsed_hash[:first_number]
|
|
930
|
-
# base_portion was lost during merge, use first_number to build base identifier
|
|
931
|
-
base_hash = {
|
|
932
|
-
publisher: parsed_hash[:publisher],
|
|
933
|
-
series: series_value || parsed_hash[:series],
|
|
934
|
-
first_number: parsed_hash[:first_number],
|
|
935
|
-
parsed_format: parsed_hash[:parsed_format],
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
# Recursively build base identifier
|
|
939
|
-
supplement.base_identifier = build(base_hash)
|
|
940
|
-
end
|
|
941
|
-
|
|
942
|
-
# Build supplement edition from captured data
|
|
943
|
-
if parsed_hash[:supplement_month_year]
|
|
944
|
-
# Parse month+year format like "Jan1924"
|
|
945
|
-
month_year = parsed_hash[:supplement_month_year].to_s
|
|
946
|
-
supplement.edition = Components::Edition.new(type: "s",
|
|
947
|
-
id: month_year)
|
|
948
|
-
elsif parsed_hash[:supplement_year]
|
|
949
|
-
# Just year: 1924
|
|
950
|
-
supplement.edition = Components::Edition.new(type: "s",
|
|
951
|
-
id: parsed_hash[:supplement_year].to_s)
|
|
952
|
-
elsif parsed_hash[:supplement_slash_year].is_a?(Hash)
|
|
953
|
-
# NEW: Handle supplement_slash_year pattern (e.g., "sup12/1926", "sup1/1927")
|
|
954
|
-
# V1 format: "Upd1-192612" where "1" is fixed and "192612" is year+number concatenated
|
|
955
|
-
# Single digit numbers are zero-padded: "sup1/1927" → "Upd1-192701"
|
|
956
|
-
supp_hash = parsed_hash[:supplement_slash_year]
|
|
957
|
-
supp_number = supp_hash[:supp_number]&.to_s
|
|
958
|
-
supp_year = supp_hash[:supp_year]&.to_s
|
|
959
|
-
|
|
960
|
-
# Pad supplement number to 2 digits for single-digit numbers
|
|
961
|
-
supp_number_padded = supp_number.rjust(2, "0")
|
|
962
|
-
|
|
963
|
-
# Create Update component for supplement (V1 compatibility uses Update for supplements)
|
|
964
|
-
# Format: Upd1-{year}{padded_number} (always use "1" and concatenate year+padded_number)
|
|
965
|
-
update_value = "Upd1-#{supp_year}#{supp_number_padded}"
|
|
966
|
-
supplement.update = update_value
|
|
967
|
-
elsif parsed_hash[:supplement_empty]
|
|
968
|
-
# Empty supplement - no edition
|
|
969
|
-
# supplement.edition remains nil
|
|
970
|
-
end
|
|
971
|
-
|
|
972
|
-
supplement
|
|
973
|
-
end
|
|
974
|
-
|
|
975
|
-
private
|
|
976
|
-
|
|
977
|
-
# Cast parsed value to appropriate component type
|
|
978
|
-
# ALL conversions happen in this single method
|
|
979
|
-
# @param type [Symbol] the parameter type
|
|
980
|
-
# @param value [Object] the parsed value
|
|
981
|
-
# @param parsed_hash [Hash] the full parsed hash for context
|
|
982
|
-
# @return [Object, Hash, nil] the cast component(s)
|
|
983
|
-
def cast(type, value, parsed_hash = {})
|
|
984
|
-
case type
|
|
985
|
-
when :publisher
|
|
986
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
987
|
-
|
|
988
|
-
Components::Publisher.new(publisher: value.to_s)
|
|
989
|
-
|
|
990
|
-
when :series
|
|
991
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
992
|
-
|
|
993
|
-
str_value = value.to_s
|
|
994
|
-
publisher_extracted = nil
|
|
995
|
-
|
|
996
|
-
# For compound series like "NBS CIRC", extract publisher and series separately
|
|
997
|
-
if str_value.start_with?("NBS ")
|
|
998
|
-
publisher_extracted = "NBS"
|
|
999
|
-
str_value = str_value.sub("NBS ", "")
|
|
1000
|
-
end
|
|
1001
|
-
|
|
1002
|
-
# Return composite hash with both publisher and series if extracted
|
|
1003
|
-
if publisher_extracted
|
|
1004
|
-
{
|
|
1005
|
-
publisher: Components::Publisher.new(publisher: publisher_extracted),
|
|
1006
|
-
series: Components::Code.new(number: str_value),
|
|
1007
|
-
}
|
|
1008
|
-
else
|
|
1009
|
-
Components::Code.new(number: str_value)
|
|
1010
|
-
end
|
|
1011
|
-
|
|
1012
|
-
when :volume_number
|
|
1013
|
-
# Volume from v#n# pattern - return Volume component
|
|
1014
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1015
|
-
|
|
1016
|
-
{ volume: Components::Volume.new(value: value.to_s) }
|
|
1017
|
-
|
|
1018
|
-
when :issue_number
|
|
1019
|
-
# Issue number from v#n# pattern - return Part component
|
|
1020
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1021
|
-
|
|
1022
|
-
{ part: Components::Part.new(type: "n", value: value.to_s) }
|
|
1023
|
-
|
|
1024
|
-
when :part_number
|
|
1025
|
-
# Part number from GCR pattern (e.g., 85-3273-37)
|
|
1026
|
-
# Return raw value for inclusion in compound number
|
|
1027
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1028
|
-
|
|
1029
|
-
value # Return raw value to be tracked in builder
|
|
1030
|
-
|
|
1031
|
-
when :letter_number
|
|
1032
|
-
# Letter number pattern (e.g., 800-56A, 1-1A for NCSTAR, 73-197Ur for IR)
|
|
1033
|
-
# Parser returns: {:letter_base=>"56", :letter_suffix=>"A"} or
|
|
1034
|
-
# {:letter_base=>"197", :letter_suffix=>"U", :letter_suffix_extra=>"r"}
|
|
1035
|
-
# For SpecialPublication, create Part component with letter suffix as value
|
|
1036
|
-
# For MONO and NCSTAR, preserve letter suffix as part of the number (return raw value)
|
|
1037
|
-
return nil if value.nil? || !value.is_a?(Hash)
|
|
1038
|
-
|
|
1039
|
-
letter_suffix = value[:letter_suffix]&.to_s&.strip
|
|
1040
|
-
letter_suffix_extra = value[:letter_suffix_extra]&.to_s&.strip
|
|
1041
|
-
|
|
1042
|
-
# Combine letter_suffix and letter_suffix_extra (e.g., "U" + "r" = "Ur")
|
|
1043
|
-
full_suffix = if letter_suffix_extra && !letter_suffix_extra.empty?
|
|
1044
|
-
letter_suffix + letter_suffix_extra
|
|
1045
|
-
else
|
|
1046
|
-
letter_suffix
|
|
1047
|
-
end
|
|
1048
|
-
|
|
1049
|
-
return nil if full_suffix.nil? || full_suffix.empty?
|
|
1050
|
-
|
|
1051
|
-
# Check if this is a MONO or NCSTAR series
|
|
1052
|
-
# For these series, the letter suffix should be part of the number, not a separate Part component
|
|
1053
|
-
# For IR with "R" or "Ur" suffix, also return raw value so builder can convert to edition "r1"
|
|
1054
|
-
is_mono = begin
|
|
1055
|
-
parsed_hash[:series].to_s.include?("MONO")
|
|
1056
|
-
rescue StandardError
|
|
1057
|
-
false
|
|
1058
|
-
end
|
|
1059
|
-
is_ncstar = begin
|
|
1060
|
-
parsed_hash[:series].to_s.include?("NCSTAR")
|
|
1061
|
-
rescue StandardError
|
|
1062
|
-
false
|
|
1063
|
-
end
|
|
1064
|
-
# IR with "R" suffix needs special handling (convert to edition "r1")
|
|
1065
|
-
# Also handle "Ur" which combines uppercase U with lowercase r
|
|
1066
|
-
is_ir_with_r = begin
|
|
1067
|
-
parsed_hash[:series].to_s.include?("IR") && (letter_suffix == "R" || full_suffix == "Ur")
|
|
1068
|
-
rescue StandardError
|
|
1069
|
-
false
|
|
1070
|
-
end
|
|
1071
|
-
|
|
1072
|
-
if is_mono || is_ncstar || is_ir_with_r
|
|
1073
|
-
# For MONO and NCSTAR, preserve letter suffix as part of the number
|
|
1074
|
-
# For IR with "R" or "Ur", return raw value so builder can convert "79-1786R" to "79-1786r1"
|
|
1075
|
-
# Return raw value so builder can construct proper format
|
|
1076
|
-
value[:letter_suffix] = full_suffix
|
|
1077
|
-
value
|
|
1078
|
-
else
|
|
1079
|
-
# For SpecialPublication and others, create Part component
|
|
1080
|
-
{ part: Components::Part.new(type: "", value: full_suffix.upcase) }
|
|
1081
|
-
end
|
|
1082
|
-
|
|
1083
|
-
when :fips_part
|
|
1084
|
-
# Part number from FIPS date pattern (e.g., 11-1-Sep30/1977)
|
|
1085
|
-
# Return Part component with pt type
|
|
1086
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1087
|
-
|
|
1088
|
-
{ part: Components::Part.new(type: "pt", value: value.to_s) }
|
|
1089
|
-
|
|
1090
|
-
when :owmwp_date_number
|
|
1091
|
-
# OWMWP date-based number format (MM-DD-YYYY)
|
|
1092
|
-
# Parser returns: {:owmwp_month=>"06", :owmwp_day=>"13", :owmwp_year=>"2018"}
|
|
1093
|
-
# Convert to number + edition: "06-13" + edition "e2018"
|
|
1094
|
-
return nil if value.nil?
|
|
1095
|
-
|
|
1096
|
-
number_part = "#{value[:owmwp_month]}-#{value[:owmwp_day]}"
|
|
1097
|
-
edition_part = Components::Edition.new(type: "e",
|
|
1098
|
-
id: value[:owmwp_year])
|
|
1099
|
-
{ first_number: Components::Code.new(number: number_part), edition: edition_part }
|
|
1100
|
-
|
|
1101
|
-
when :first_number, :second_number
|
|
1102
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1103
|
-
|
|
1104
|
-
# NEW: Handle OWMWP date-based number (nested hash structure)
|
|
1105
|
-
# Parser returns: {:owmwp_date_number=>{:owmwp_month=>"06", :owmwp_day=>"13", :owmwp_year=>"2018"}}
|
|
1106
|
-
# Convert to number + edition: "06-13" + edition "e2018"
|
|
1107
|
-
if value.is_a?(Hash) && value[:owmwp_date_number]
|
|
1108
|
-
owmwp_hash = value[:owmwp_date_number]
|
|
1109
|
-
number_part = "#{owmwp_hash[:owmwp_month]}-#{owmwp_hash[:owmwp_day]}"
|
|
1110
|
-
edition_part = Components::Edition.new(type: "e",
|
|
1111
|
-
id: owmwp_hash[:owmwp_year])
|
|
1112
|
-
return { type => Components::Code.new(number: number_part), edition: edition_part }
|
|
1113
|
-
end
|
|
1114
|
-
|
|
1115
|
-
# NEW: Handle second_number with edition (hash with :number_only and :edition_id)
|
|
1116
|
-
# This handles "126r2013" pattern where parser returns {:number_only=>"126", :edition_id=>"2013"}
|
|
1117
|
-
# CRITICAL: Wrap in a structure that builder loop can recognize
|
|
1118
|
-
# The builder loop expects keys like :second_number to be present in the hash
|
|
1119
|
-
if type == :second_number && value.is_a?(Hash) && value[:number_only] && value[:edition_id]
|
|
1120
|
-
# Return wrapped hash so builder loop finds :second_number key
|
|
1121
|
-
return { second_number: value }
|
|
1122
|
-
end
|
|
1123
|
-
|
|
1124
|
-
# NEW: Handle second_number with revision_letter (hash with :revision_letter containing :number_only and :letter)
|
|
1125
|
-
# This handles "27ra" pattern where parser returns {revision_letter: {number_only: "27", letter: "a"}}
|
|
1126
|
-
# Should be combined to "27rA" format
|
|
1127
|
-
if type == :second_number && value.is_a?(Hash) && value[:revision_letter]
|
|
1128
|
-
revision_data = value[:revision_letter]
|
|
1129
|
-
number_only = revision_data[:number_only].to_s
|
|
1130
|
-
letter = revision_data[:letter].to_s.upcase
|
|
1131
|
-
# Return as second_number with combined format "27rA"
|
|
1132
|
-
return { second_number: Components::Code.new(number: "#{number_only}r#{letter}") }
|
|
1133
|
-
end
|
|
1134
|
-
|
|
1135
|
-
# Handle v#n# pattern (CSM series) - comes as hash from parser
|
|
1136
|
-
# Return Volume and Part components separately
|
|
1137
|
-
if value.is_a?(Hash) && value[:volume_number] && value[:issue_number]
|
|
1138
|
-
volume_num = value[:volume_number].to_s
|
|
1139
|
-
issue_num = value[:issue_number].to_s
|
|
1140
|
-
return {
|
|
1141
|
-
volume: Components::Volume.new(value: volume_num),
|
|
1142
|
-
part: Components::Part.new(type: "n", value: issue_num),
|
|
1143
|
-
}
|
|
1144
|
-
end
|
|
1145
|
-
|
|
1146
|
-
str_value = value.to_s
|
|
1147
|
-
|
|
1148
|
-
# Handle special patterns embedded in first_number
|
|
1149
|
-
if type == :first_number
|
|
1150
|
-
|
|
1151
|
-
# NEW: Handle first_number hash with number_with_rev_year (e.g., "1013rv1953")
|
|
1152
|
-
# Parser returns: {:number_with_rev_year=>{:number=>"1013", :revision_year=>"1953"}}
|
|
1153
|
-
if value.is_a?(Hash) && value[:number_with_rev_year]
|
|
1154
|
-
number_part = value[:number_with_rev_year][:number].to_s
|
|
1155
|
-
revision_year = value[:number_with_rev_year][:revision_year].to_s
|
|
1156
|
-
return {
|
|
1157
|
-
first_number: Components::Code.new(number: number_part),
|
|
1158
|
-
edition: Components::Edition.new(type: "rv", id: revision_year),
|
|
1159
|
-
}
|
|
1160
|
-
end
|
|
1161
|
-
|
|
1162
|
-
# NEW: Handle first_number hash with language_code (e.g., "1262es")
|
|
1163
|
-
# Parser returns: {:number=>"1262", :language_code=>"es"}
|
|
1164
|
-
if value.is_a?(Hash) && value[:number] && value[:language_code]
|
|
1165
|
-
number_part = value[:number].to_s
|
|
1166
|
-
language_code = value[:language_code].to_s.strip.downcase
|
|
1167
|
-
# Apply normalization map (es → spa, pt → por, etc.)
|
|
1168
|
-
normalized_code = TRANSLATION_MAP[language_code] || language_code
|
|
1169
|
-
return {
|
|
1170
|
-
first_number: Components::Code.new(number: number_part),
|
|
1171
|
-
translation_component: Components::Translation.new(code: normalized_code),
|
|
1172
|
-
}
|
|
1173
|
-
end
|
|
1174
|
-
|
|
1175
|
-
# NEW: Handle first_number hash with number, part_number, and edition_year (MR format)
|
|
1176
|
-
# Parser returns: {:number=>"28", :part_number=>"1", :edition_year=>"1969"}
|
|
1177
|
-
# For "NBS.HB.28pt1e1969" MR format input
|
|
1178
|
-
if value.is_a?(Hash) && value[:number] && value[:part_number] && value[:edition_year]
|
|
1179
|
-
number_part = value[:number].to_s
|
|
1180
|
-
part_number = value[:part_number].to_s
|
|
1181
|
-
edition_year = value[:edition_year].to_s
|
|
1182
|
-
return {
|
|
1183
|
-
first_number: Components::Code.new(number: number_part),
|
|
1184
|
-
part: Components::Part.new(type: "pt", value: part_number),
|
|
1185
|
-
edition: Components::Edition.new(type: "e", id: edition_year),
|
|
1186
|
-
}
|
|
1187
|
-
end
|
|
1188
|
-
|
|
1189
|
-
# NEW: Check for edition_year_separate in parsed_hash context
|
|
1190
|
-
# This handles "11e2-1915" where first_number="11e2" and edition_year_separate="1915"
|
|
1191
|
-
if parsed_hash[:edition_year_separate] && str_value =~ /^(\d+)e(\d+)$/
|
|
1192
|
-
number_part = $1
|
|
1193
|
-
edition_id = $2
|
|
1194
|
-
year_part = parsed_hash[:edition_year_separate].to_s
|
|
1195
|
-
return {
|
|
1196
|
-
first_number: Components::Code.new(number: number_part),
|
|
1197
|
-
edition: Components::Edition.new(type: "e", id: edition_id,
|
|
1198
|
-
additional_text: year_part),
|
|
1199
|
-
}
|
|
1200
|
-
end
|
|
1201
|
-
|
|
1202
|
-
# NEW: Check for number_with_volume in value hash (for first_number)
|
|
1203
|
-
# This handles "539v10" where parser captures :number and :volume_suffix separately
|
|
1204
|
-
# Parse tree: value = {:number_with_volume => {:number => "539", :volume_suffix => "10"}}
|
|
1205
|
-
if value.is_a?(Hash) && value[:number_with_volume] && value[:number_with_volume][:volume_suffix]
|
|
1206
|
-
number_part = value[:number_with_volume][:number].to_s
|
|
1207
|
-
volume_value = value[:number_with_volume][:volume_suffix].to_s
|
|
1208
|
-
return {
|
|
1209
|
-
first_number: Components::Code.new(number: number_part),
|
|
1210
|
-
volume: Components::Volume.new(value: volume_value),
|
|
1211
|
-
}
|
|
1212
|
-
end
|
|
1213
|
-
|
|
1214
|
-
# NEW: Check for historical_month and historical_year in parsed_hash context
|
|
1215
|
-
# This handles "-April1909" where it's captured as separate month/year
|
|
1216
|
-
if parsed_hash[:historical_month] && parsed_hash[:historical_year]
|
|
1217
|
-
month_part = parsed_hash[:historical_month].to_s
|
|
1218
|
-
year_part = parsed_hash[:historical_year].to_s
|
|
1219
|
-
# Check if str_value is just a number (the part before dash)
|
|
1220
|
-
if /^\d+$/.match?(str_value)
|
|
1221
|
-
return {
|
|
1222
|
-
first_number: Components::Code.new(number: str_value),
|
|
1223
|
-
edition: Components::Edition.new(type: "-",
|
|
1224
|
-
additional_text: "#{month_part}#{year_part}"),
|
|
1225
|
-
}
|
|
1226
|
-
else
|
|
1227
|
-
# No number, just historical edition
|
|
1228
|
-
return {
|
|
1229
|
-
edition: Components::Edition.new(type: "-",
|
|
1230
|
-
additional_text: "#{month_part}#{year_part}"),
|
|
1231
|
-
}
|
|
1232
|
-
end
|
|
1233
|
-
end
|
|
1234
|
-
|
|
1235
|
-
# Pattern "9350sup"/"5893supp" - number with bare supplement marker
|
|
1236
|
-
# (no trailing payload). Accept both single-p "sup" and double-p
|
|
1237
|
-
# "supp" so the marker is isolated as supplement="" and rendered as
|
|
1238
|
-
# canonical single-p "sup", instead of staying baked into the number
|
|
1239
|
-
# as an opaque suffix. E.g. "NBS RPT 5893supp", "NBS MONO 32supp".
|
|
1240
|
-
if str_value =~ /^(\d+)supp?$/
|
|
1241
|
-
return {
|
|
1242
|
-
first_number: Components::Code.new(number: $1),
|
|
1243
|
-
supplement: "",
|
|
1244
|
-
}
|
|
1245
|
-
end
|
|
1246
|
-
|
|
1247
|
-
# NEW: Check for supplement_year in parsed_hash context
|
|
1248
|
-
# This handles "25supp-1924" where first_number="25supp" and supplement_year="1924"
|
|
1249
|
-
if parsed_hash[:supplement_year] && str_value =~ /^(\d+)supp?$/
|
|
1250
|
-
number_part = $1
|
|
1251
|
-
year_part = parsed_hash[:supplement_year].to_s
|
|
1252
|
-
return {
|
|
1253
|
-
first_number: Components::Code.new(number: number_part),
|
|
1254
|
-
supplement: year_part,
|
|
1255
|
-
}
|
|
1256
|
-
end
|
|
1257
|
-
|
|
1258
|
-
# Pattern: "154supprev" - supplement with revision
|
|
1259
|
-
if str_value =~ /^(\d+)supprev$/
|
|
1260
|
-
return {
|
|
1261
|
-
first_number: Components::Code.new(number: $1),
|
|
1262
|
-
supplement: "",
|
|
1263
|
-
supplement_has_revision: true,
|
|
1264
|
-
}
|
|
1265
|
-
# NEW: Pattern "11e2-1915" - edition with separate year (inline match)
|
|
1266
|
-
# Creates: number="11", Edition(type: "e", id: "2", additional_text: "1915")
|
|
1267
|
-
# Renders: "NBS CIRC 11e2.1915"
|
|
1268
|
-
elsif str_value =~ /^(\d+)e(\d+)-(\d{4})$/
|
|
1269
|
-
number_part = $1
|
|
1270
|
-
edition_id = $2
|
|
1271
|
-
year_part = $3
|
|
1272
|
-
return {
|
|
1273
|
-
first_number: Components::Code.new(number: number_part),
|
|
1274
|
-
edition: Components::Edition.new(type: "e", id: edition_id,
|
|
1275
|
-
additional_text: year_part),
|
|
1276
|
-
}
|
|
1277
|
-
# NEW: Pattern "-April1909" - historical edition with month+year (inline match)
|
|
1278
|
-
# Creates: Edition(type: "-", additional_text: "April1909")
|
|
1279
|
-
# Renders: "NBS CIRC -April1909"
|
|
1280
|
-
elsif str_value =~ /^-([A-Za-z]{3,9})(\d{4})$/
|
|
1281
|
-
month_part = $1
|
|
1282
|
-
year_part = $2
|
|
1283
|
-
return {
|
|
1284
|
-
edition: Components::Edition.new(type: "-",
|
|
1285
|
-
additional_text: "#{month_part}#{year_part}"),
|
|
1286
|
-
}
|
|
1287
|
-
# NEW: CS Emergency pattern "e104" or "e104-43" → extract number
|
|
1288
|
-
# This must come BEFORE bare edition check to avoid conflict
|
|
1289
|
-
# CS emergency always has 3+ digit number (e104, not e2)
|
|
1290
|
-
# NOTE: If second_number exists (e104-43 pattern), defer to compound number logic
|
|
1291
|
-
elsif /^e(\d{3,})$/.match?(str_value) && !parsed_hash[:second_number]
|
|
1292
|
-
# Extract emergency number: e104 → 104 (only when no second_number)
|
|
1293
|
-
emergency_num = str_value.sub(/^e/, "")
|
|
1294
|
-
return {
|
|
1295
|
-
first_number: Components::Code.new(number: emergency_num),
|
|
1296
|
-
}
|
|
1297
|
-
# If e104-43 pattern (with second_number), keep e prefix for compound number logic
|
|
1298
|
-
elsif /^e(\d{3,})$/.match?(str_value) && parsed_hash[:second_number]
|
|
1299
|
-
# Keep e104 as-is, let compound number logic handle it
|
|
1300
|
-
return {
|
|
1301
|
-
first_number: Components::Code.new(number: str_value),
|
|
1302
|
-
}
|
|
1303
|
-
# NEW: Bare edition pattern like "100e1" (CS series without year)
|
|
1304
|
-
# ONLY when NO second_number present (to avoid conflict with "123e2-50")
|
|
1305
|
-
# Creates: number="100", Edition(type: "e", id: "1")
|
|
1306
|
-
# Renders: "NBS CS 100e1"
|
|
1307
|
-
# CRITICAL: Skip if edition_dash_year is present - let that handler create Edition with additional_text
|
|
1308
|
-
elsif str_value =~ /^(\d+)e(\d+)$/ && !parsed_hash[:second_number] && !parsed_hash[:edition_dash_year]
|
|
1309
|
-
number_part = $1
|
|
1310
|
-
edition_id = $2
|
|
1311
|
-
|
|
1312
|
-
return {
|
|
1313
|
-
first_number: Components::Code.new(number: number_part),
|
|
1314
|
-
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1315
|
-
}
|
|
1316
|
-
# NEW: Bare edition pattern "e2" - just edition without number prefix
|
|
1317
|
-
# Creates: Edition(type: "e", id: "2")
|
|
1318
|
-
# Renders: "NBS CIRC e2"
|
|
1319
|
-
# Only matches single or double digit (e1, e2, not e104 which is emergency)
|
|
1320
|
-
elsif str_value =~ /^e(\d{1,2})$/
|
|
1321
|
-
edition_id = $1
|
|
1322
|
-
return {
|
|
1323
|
-
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1324
|
-
}
|
|
1325
|
-
# Pattern: "13e2rev1908" - edition with revision year-only (NO month)
|
|
1326
|
-
# Creates: Edition(type: "e", id: "2", additional_text: "1908")
|
|
1327
|
-
# Renders: "e2.1908" (DOT separator)
|
|
1328
|
-
elsif str_value =~ /^(\d+)e(\d+)rev(\d{4})$/
|
|
1329
|
-
# CRITICAL: Capture BEFORE any regex method calls!
|
|
1330
|
-
number_part = $1
|
|
1331
|
-
edition_id_part = $2
|
|
1332
|
-
year_part = $3
|
|
1333
|
-
return {
|
|
1334
|
-
first_number: Components::Code.new(number: number_part),
|
|
1335
|
-
edition: Components::Edition.new(type: "e",
|
|
1336
|
-
id: edition_id_part, additional_text: year_part),
|
|
1337
|
-
}
|
|
1338
|
-
# Pattern: "13e2revJune1908" - edition with revision month+year
|
|
1339
|
-
# Creates: Edition(type: "e", id: "2", additional_text: "June1908")
|
|
1340
|
-
# Renders: "e2.June1908" (DOT separator)
|
|
1341
|
-
elsif str_value =~ /^(\d+)e(\d+)(rev.+)$/
|
|
1342
|
-
# CRITICAL: Capture $1, $2, $3 BEFORE calling .sub() which resets them!
|
|
1343
|
-
number_part = $1
|
|
1344
|
-
edition_id_part = $2
|
|
1345
|
-
rev_part = $3
|
|
1346
|
-
# Strip "rev" prefix from additional_text - store only "June1908" or "1908"
|
|
1347
|
-
additional_text = rev_part.sub(/^rev/, "")
|
|
1348
|
-
return {
|
|
1349
|
-
first_number: Components::Code.new(number: number_part),
|
|
1350
|
-
edition: Components::Edition.new(type: "e",
|
|
1351
|
-
id: edition_id_part, additional_text: additional_text),
|
|
1352
|
-
}
|
|
1353
|
-
# NEW: Pattern "24suppJan1924" - supplement with month and year in first_number
|
|
1354
|
-
# Creates: number="24", supplement="Jan1924"
|
|
1355
|
-
elsif str_value =~ /^(\d+)supp([A-Za-z]{3,9})(\d{4})$/
|
|
1356
|
-
number_part = $1
|
|
1357
|
-
month_part = $2
|
|
1358
|
-
year_part = $3
|
|
1359
|
-
return {
|
|
1360
|
-
first_number: Components::Code.new(number: number_part),
|
|
1361
|
-
supplement: "#{month_part}#{year_part}",
|
|
1362
|
-
}
|
|
1363
|
-
# NEW: Pattern "25supp1924" - supplement with year (no dash, no month)
|
|
1364
|
-
# Creates: number="25", supplement="1924"
|
|
1365
|
-
# Renders: "NBS SP 25supp1924"
|
|
1366
|
-
elsif str_value =~ /^(\d+)supp(\d{4})$/
|
|
1367
|
-
number_part = $1
|
|
1368
|
-
year_part = $2
|
|
1369
|
-
return {
|
|
1370
|
-
first_number: Components::Code.new(number: number_part),
|
|
1371
|
-
supplement: year_part,
|
|
1372
|
-
}
|
|
1373
|
-
# NEW: Pattern "25supp-1924" - supplement with dash-year (inline match)
|
|
1374
|
-
# Creates: number="25", supplement="1924"
|
|
1375
|
-
# Renders: "NBS CIRC 25supp-1924"
|
|
1376
|
-
elsif str_value =~ /^(\d+)supp-(\d{4})$/
|
|
1377
|
-
number_part = $1
|
|
1378
|
-
year_part = $2
|
|
1379
|
-
return {
|
|
1380
|
-
first_number: Components::Code.new(number: number_part),
|
|
1381
|
-
supplement: year_part,
|
|
1382
|
-
}
|
|
1383
|
-
# NEW: Pattern "101e2supp" - edition + supplement
|
|
1384
|
-
# Creates: number="101", Edition(type: "e", id: "2"), supplement=""
|
|
1385
|
-
# Renders: "NBS CIRC 101e2supp"
|
|
1386
|
-
elsif str_value =~ /^(\d+)e(\d+)supp$/
|
|
1387
|
-
number_part = $1
|
|
1388
|
-
edition_id = $2
|
|
1389
|
-
return {
|
|
1390
|
-
first_number: Components::Code.new(number: number_part),
|
|
1391
|
-
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1392
|
-
supplement: "",
|
|
1393
|
-
}
|
|
1394
|
-
end
|
|
1395
|
-
elsif type == :second_number && value.is_a?(Hash) && value[:first_number]
|
|
1396
|
-
# Handle second_number as a hash with first_number context
|
|
1397
|
-
# e.g., for pattern 800-57pt1r4
|
|
1398
|
-
number_part = value[:first_number].to_s
|
|
1399
|
-
part_value = value[:part_value]&.to_s
|
|
1400
|
-
revision_value = value[:revision_value]&.to_s
|
|
1401
|
-
return {
|
|
1402
|
-
first_number: Components::Code.new(number: number_part),
|
|
1403
|
-
part: Components::Part.new(value: part_value),
|
|
1404
|
-
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
1405
|
-
}
|
|
1406
|
-
end
|
|
1407
|
-
|
|
1408
|
-
# Extract revision suffix from number (e.g., "53r5" → "53" + Edition(r, 5))
|
|
1409
|
-
# ENHANCED: Also extract revision with slash-year (e.g., "53r5/1917" → "53" + Edition)
|
|
1410
|
-
# ENHANCED: Also extract revision with 4-digit year (e.g., "1019r1963" → "1019" + Edition)
|
|
1411
|
-
# ENHANCED: Also extract revision with month+year (e.g., "4743rJun1992" → "4743" + Edition)
|
|
1412
|
-
|
|
1413
|
-
# NEW: Extract part suffix from number (e.g., "800-57pt1" → "800-57" + Part(1))
|
|
1414
|
-
# This handles SP series part notation
|
|
1415
|
-
# IMPORTANT: Handle combined part+revision first (e.g., "800-57pt1r4")
|
|
1416
|
-
if str_value =~ /^(.+?)pt(\d+)r(\d+[a-z]?)$/
|
|
1417
|
-
number_part = $1
|
|
1418
|
-
part_value = $2
|
|
1419
|
-
revision_value = $3
|
|
1420
|
-
return {
|
|
1421
|
-
type => Components::Code.new(number: number_part),
|
|
1422
|
-
part: Components::Part.new(type: "pt", value: part_value),
|
|
1423
|
-
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
1424
|
-
}
|
|
1425
|
-
elsif str_value =~ /^(.+?)pt(\d+)$/
|
|
1426
|
-
number_part = $1
|
|
1427
|
-
part_value = $2
|
|
1428
|
-
return {
|
|
1429
|
-
type => Components::Code.new(number: number_part),
|
|
1430
|
-
part: Components::Part.new(type: "pt", value: part_value),
|
|
1431
|
-
}
|
|
1432
|
-
end
|
|
1433
|
-
|
|
1434
|
-
# NEW: Extract volume suffix from number (e.g., "539v10" → "539" + volume="10")
|
|
1435
|
-
# This handles CIRC volume notation
|
|
1436
|
-
if str_value =~ /^(\d+)v(\d+)$/
|
|
1437
|
-
number_part = $1
|
|
1438
|
-
volume_part = $2
|
|
1439
|
-
return {
|
|
1440
|
-
type => Components::Code.new(number: number_part),
|
|
1441
|
-
volume: volume_part,
|
|
1442
|
-
}
|
|
1443
|
-
end
|
|
1444
|
-
|
|
1445
|
-
# REVISION PATTERNS - These must come BEFORE letter suffix to avoid conflicts
|
|
1446
|
-
case str_value
|
|
1447
|
-
when /^(.+?)(r\d+\/\d{4})$/i
|
|
1448
|
-
# Pattern: r6/1925 (revision with slash-year)
|
|
1449
|
-
number_part = $1
|
|
1450
|
-
revision_with_year = $2 # e.g., "r6/1925"
|
|
1451
|
-
# Extract revision and year
|
|
1452
|
-
if revision_with_year =~ /^r(\d+)\/(\d{4})$/
|
|
1453
|
-
revision_id = $1
|
|
1454
|
-
year_part = $2
|
|
1455
|
-
return {
|
|
1456
|
-
type => Components::Code.new(number: number_part),
|
|
1457
|
-
edition: Components::Edition.new(type: "r", id: revision_id,
|
|
1458
|
-
additional_text: year_part),
|
|
1459
|
-
}
|
|
1460
|
-
end
|
|
1461
|
-
when /^(.+?)(r\d{4})$/i
|
|
1462
|
-
# Pattern: r1963 (revision as 4-digit year)
|
|
1463
|
-
number_part = $1
|
|
1464
|
-
year_value = $2.sub(/^r/, "") # Strip 'r' prefix
|
|
1465
|
-
return {
|
|
1466
|
-
type => Components::Code.new(number: number_part),
|
|
1467
|
-
edition: Components::Edition.new(type: "r", id: year_value),
|
|
1468
|
-
}
|
|
1469
|
-
when /^(.+?)(r[A-Za-z]{3,9}\d{4})$/i
|
|
1470
|
-
# Pattern: rJun1992 (revision with month and year)
|
|
1471
|
-
number_part = $1
|
|
1472
|
-
revision_with_date = $2 # e.g., "rJun1992"
|
|
1473
|
-
# Extract month and year
|
|
1474
|
-
if revision_with_date =~ /^r([A-Za-z]{3,9})(\d{4})$/
|
|
1475
|
-
month_part = $1
|
|
1476
|
-
year_part = $2
|
|
1477
|
-
return {
|
|
1478
|
-
type => Components::Code.new(number: number_part),
|
|
1479
|
-
edition: Components::Edition.new(type: "r",
|
|
1480
|
-
id: "#{month_part}#{year_part}"),
|
|
1481
|
-
}
|
|
1482
|
-
end
|
|
1483
|
-
when /^(.+?)(r\d+[a-z]?)$/i
|
|
1484
|
-
# Pattern: r5, r1a (simple revision)
|
|
1485
|
-
number_part = $1
|
|
1486
|
-
revision_value = $2.sub(/^r/, "") # Strip 'r' prefix
|
|
1487
|
-
return {
|
|
1488
|
-
type => Components::Code.new(number: number_part),
|
|
1489
|
-
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
1490
|
-
}
|
|
1491
|
-
when /^(.+?)(?<![a-zA-Z])(r)$/i
|
|
1492
|
-
# Pattern: bare r with no digits (e.g., "800-90r")
|
|
1493
|
-
# Negative lookbehind ensures r is NOT preceded by a letter (avoids matching Ur, Ua, etc.)
|
|
1494
|
-
number_part = $1
|
|
1495
|
-
return {
|
|
1496
|
-
type => Components::Code.new(number: number_part),
|
|
1497
|
-
edition: Components::Edition.new(type: "r", id: "1"),
|
|
1498
|
-
}
|
|
1499
|
-
end
|
|
1500
|
-
|
|
1501
|
-
# NEW: Extract UPPERCASE letter suffix as Part component (e.g., "800-56A" → "800-56" + Part)
|
|
1502
|
-
# IMPORTANT: These patterns come AFTER revision patterns to avoid conflicts
|
|
1503
|
-
# Letter suffixes are UPPERCASE letters A-Z only (no lowercase to avoid revision markers)
|
|
1504
|
-
|
|
1505
|
-
# Pattern: UPPERCASE letter + revision (e.g., "800-56Ar2" → number + Part("", "A") + Edition(r, 2))
|
|
1506
|
-
# NO /i flag - only match uppercase letters!
|
|
1507
|
-
if str_value =~ /^(.+?)([A-Z])(r\d+[a-z]?)$/
|
|
1508
|
-
number_part = $1
|
|
1509
|
-
letter_part = $2
|
|
1510
|
-
revision_part = $3.sub(/^r/, "")
|
|
1511
|
-
return {
|
|
1512
|
-
type => Components::Code.new(number: number_part),
|
|
1513
|
-
part: Components::Part.new(type: "", value: letter_part),
|
|
1514
|
-
edition: Components::Edition.new(type: "r", id: revision_part),
|
|
1515
|
-
}
|
|
1516
|
-
# Pattern: bare UPPERCASE letter suffix (e.g., "800-56A" → number + Part("", "A"))
|
|
1517
|
-
# Only matches uppercase letters - won't match revision markers
|
|
1518
|
-
# IMPORTANT: For MR format preservation, keep letter suffix as part of number
|
|
1519
|
-
# IMPORTANT: For Report, FIPS, IR, and LC series, preserve letter suffix as part of number
|
|
1520
|
-
elsif str_value =~ /^(.+?)([A-Z])$/
|
|
1521
|
-
number_part = $1
|
|
1522
|
-
letter_part = $2
|
|
1523
|
-
# Check if we should preserve letter suffix in number
|
|
1524
|
-
# Check for specific series that need letter suffix preserved
|
|
1525
|
-
is_report = begin
|
|
1526
|
-
parsed_hash[:series].to_s.include?("RPT")
|
|
1527
|
-
rescue StandardError
|
|
1528
|
-
false
|
|
1529
|
-
end
|
|
1530
|
-
is_fips = begin
|
|
1531
|
-
parsed_hash[:series].to_s.include?("FIPS")
|
|
1532
|
-
rescue StandardError
|
|
1533
|
-
false
|
|
1534
|
-
end
|
|
1535
|
-
is_ir = begin
|
|
1536
|
-
parsed_hash[:series].to_s.include?("IR")
|
|
1537
|
-
rescue StandardError
|
|
1538
|
-
false
|
|
1539
|
-
end
|
|
1540
|
-
is_crpl = begin
|
|
1541
|
-
parsed_hash[:series].to_s.include?("CRPL")
|
|
1542
|
-
rescue StandardError
|
|
1543
|
-
false
|
|
1544
|
-
end
|
|
1545
|
-
is_mono = begin
|
|
1546
|
-
parsed_hash[:series].to_s.include?("MONO")
|
|
1547
|
-
rescue StandardError
|
|
1548
|
-
false
|
|
1549
|
-
end
|
|
1550
|
-
is_mp = begin
|
|
1551
|
-
parsed_hash[:series].to_s.include?("MP")
|
|
1552
|
-
rescue StandardError
|
|
1553
|
-
false
|
|
1554
|
-
end
|
|
1555
|
-
# Check for LC but exclude LCIRC (Letter Circular uses LC, not LCIRC)
|
|
1556
|
-
is_lc = begin
|
|
1557
|
-
parsed_hash[:series].to_s.include?("LC") && !parsed_hash[:series].to_s.include?("LCIRC")
|
|
1558
|
-
rescue StandardError
|
|
1559
|
-
false
|
|
1560
|
-
end
|
|
1561
|
-
|
|
1562
|
-
if parsed_hash[:parsed_format] == :mr || is_report || is_fips || is_ir || is_crpl || is_lc || is_mono || is_mp
|
|
1563
|
-
# For MR format, Report, FIPS, IR, CRPL, LC, MONO, and MP, preserve letter suffix as part of number
|
|
1564
|
-
return { type => Components::Code.new(number: str_value) }
|
|
1565
|
-
else
|
|
1566
|
-
# For other formats, extract letter suffix as separate Part component
|
|
1567
|
-
return {
|
|
1568
|
-
type => Components::Code.new(number: number_part),
|
|
1569
|
-
part: Components::Part.new(type: "", value: letter_part),
|
|
1570
|
-
}
|
|
1571
|
-
end
|
|
1572
|
-
end
|
|
1573
|
-
|
|
1574
|
-
Components::Code.new(number: str_value)
|
|
1575
|
-
|
|
1576
|
-
when :crpl_range
|
|
1577
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1578
|
-
|
|
1579
|
-
# For CRPL range patterns like "2_3-1" or "2_3-1A" (with supplement)
|
|
1580
|
-
# Format: X_Y-Z where X,Y,Z are digits, optional trailing letter is supplement
|
|
1581
|
-
# This should split into:
|
|
1582
|
-
# - X → second_number (to combine with first_number as "1-X")
|
|
1583
|
-
# - Y-Z → Part component (with type "pt" for CRPL)
|
|
1584
|
-
# - trailing letter (if present) → Supplement
|
|
1585
|
-
str_value = value.to_s
|
|
1586
|
-
|
|
1587
|
-
# Check for supplement letter suffix (e.g., "2_3-1A" → supplement="A")
|
|
1588
|
-
if str_value =~ /^(\d+)_(\d+-\d+)([A-Z])$/
|
|
1589
|
-
second_num_part = $1 # "2"
|
|
1590
|
-
part_value = $2 # "3-1"
|
|
1591
|
-
supplement_letter = $3 # "A"
|
|
1592
|
-
|
|
1593
|
-
# Return second_number, Part, and Supplement
|
|
1594
|
-
{
|
|
1595
|
-
second_number: Components::Code.new(number: second_num_part),
|
|
1596
|
-
part: Components::Part.new(type: "pt", value: part_value),
|
|
1597
|
-
supplement: supplement_letter,
|
|
1598
|
-
}
|
|
1599
|
-
elsif str_value =~ /^(\d+)_(\d+-\d+)$/
|
|
1600
|
-
# No supplement letter
|
|
1601
|
-
second_num_part = $1 # "2"
|
|
1602
|
-
part_value = $2 # "3-1"
|
|
1603
|
-
|
|
1604
|
-
# Return second_number and Part
|
|
1605
|
-
{
|
|
1606
|
-
second_number: Components::Code.new(number: second_num_part),
|
|
1607
|
-
part: Components::Part.new(type: "pt", value: part_value),
|
|
1608
|
-
}
|
|
1609
|
-
else
|
|
1610
|
-
# Fallback: treat entire value as second_number (shouldn't happen with valid CRPL patterns)
|
|
1611
|
-
Components::Code.new(number: str_value)
|
|
1612
|
-
end
|
|
1613
|
-
|
|
1614
|
-
# ========== V2 COMPONENT CASTING ==========
|
|
1615
|
-
|
|
1616
|
-
when :stage
|
|
1617
|
-
# Stage from nested hash with id and type
|
|
1618
|
-
return nil unless value.is_a?(Hash)
|
|
1619
|
-
|
|
1620
|
-
stage_id = value[:stage_id]&.to_s&.downcase
|
|
1621
|
-
stage_type = value[:stage_type]&.to_s&.downcase
|
|
1622
|
-
return nil if stage_id.nil? || stage_type.nil? || stage_id.empty? || stage_type.empty?
|
|
1623
|
-
|
|
1624
|
-
# Return as hash to set the stage attribute
|
|
1625
|
-
{ stage: Components::Stage.new(id: stage_id, type: stage_type) }
|
|
1626
|
-
|
|
1627
|
-
when :stage_id, :stage_type
|
|
1628
|
-
# These are captured by :stage, so skip individual processing
|
|
1629
|
-
nil
|
|
1630
|
-
|
|
1631
|
-
when :parsed_format
|
|
1632
|
-
# Format detection result from parser
|
|
1633
|
-
value&.to_s
|
|
1634
|
-
|
|
1635
|
-
when :translation
|
|
1636
|
-
# V1 TRANSLATION NORMALIZATION
|
|
1637
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1638
|
-
|
|
1639
|
-
code = value.to_s.strip.downcase
|
|
1640
|
-
# Apply normalization map (es → spa, pt → por, etc.)
|
|
1641
|
-
normalized_code = TRANSLATION_MAP[code] || code
|
|
1642
|
-
|
|
1643
|
-
# Return as hash to set translation_component attribute
|
|
1644
|
-
{ translation_component: Components::Translation.new(code: normalized_code) }
|
|
1645
|
-
|
|
1646
|
-
when :version
|
|
1647
|
-
# Version component with dotted notation
|
|
1648
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1649
|
-
|
|
1650
|
-
# Return as hash to set version_component attribute
|
|
1651
|
-
{ version_component: Components::Version.new(value: value.to_s) }
|
|
1652
|
-
|
|
1653
|
-
when :update
|
|
1654
|
-
# Update component with number, year, and optional month
|
|
1655
|
-
if value.is_a?(Hash)
|
|
1656
|
-
# Convert Parslet slice to regular Hash for reliable key access
|
|
1657
|
-
value_hash = value.to_h
|
|
1658
|
-
|
|
1659
|
-
number = value_hash[:update_number]&.to_s # Don't default to "1"
|
|
1660
|
-
year = value_hash[:update_year]&.to_s # String not integer
|
|
1661
|
-
month = value_hash[:update_month]&.to_s # String not integer
|
|
1662
|
-
|
|
1663
|
-
# Determine prefix from update_prefix key (captured by parser)
|
|
1664
|
-
# If not present, default to "slash" (/Upd format)
|
|
1665
|
-
prefix_str = value_hash[:update_prefix]&.to_s
|
|
1666
|
-
prefix_value = if prefix_str&.include?("-") || prefix_str == "-upd"
|
|
1667
|
-
"dash"
|
|
1668
|
-
else
|
|
1669
|
-
"slash"
|
|
1670
|
-
end
|
|
1671
|
-
|
|
1672
|
-
# Create update with at least number
|
|
1673
|
-
update_obj = Components::Update.new(number: number, year: year,
|
|
1674
|
-
month: month, prefix: prefix_value)
|
|
1675
|
-
{
|
|
1676
|
-
update: update_obj, # Main attribute for tests
|
|
1677
|
-
update_component: update_obj, # V2 component
|
|
1678
|
-
}
|
|
1679
|
-
elsif value.to_s.strip.empty?
|
|
1680
|
-
# Empty update string means "-upd" or "/upd" with no details
|
|
1681
|
-
# Create Update with default number="1" (no year/month)
|
|
1682
|
-
# Check update_prefix key to determine correct prefix format
|
|
1683
|
-
prefix_str = parsed_hash[:update_prefix]&.to_s
|
|
1684
|
-
prefix_value = if prefix_str&.include?("-") || prefix_str == "-upd"
|
|
1685
|
-
"dash"
|
|
1686
|
-
else
|
|
1687
|
-
"slash"
|
|
1688
|
-
end
|
|
1689
|
-
update_obj = Components::Update.new(number: "1", year: nil,
|
|
1690
|
-
month: nil, prefix: prefix_value)
|
|
1691
|
-
{
|
|
1692
|
-
update: update_obj,
|
|
1693
|
-
update_component: update_obj,
|
|
1694
|
-
}
|
|
1695
|
-
else
|
|
1696
|
-
# Simple string value - shouldn't reach here
|
|
1697
|
-
{ update: value.to_s.strip } unless value.to_s.strip.empty?
|
|
1698
|
-
end
|
|
1699
|
-
|
|
1700
|
-
when :update_prefix, :update_number, :update_year, :update_month
|
|
1701
|
-
# Captured as part of :update processing
|
|
1702
|
-
nil
|
|
1703
|
-
|
|
1704
|
-
# ========== END V2 COMPONENTS ==========
|
|
1705
|
-
|
|
1706
|
-
when :volume, :section, :appendix, :translation,
|
|
1707
|
-
:errata, :index, :insert, :version
|
|
1708
|
-
return nil if value.nil?
|
|
1709
|
-
return nil if value.is_a?(Array) && value.empty?
|
|
1710
|
-
|
|
1711
|
-
str_value = value.to_s.strip
|
|
1712
|
-
return nil if str_value.empty?
|
|
1713
|
-
|
|
1714
|
-
# For volume, create Volume component from string value
|
|
1715
|
-
# This handles patterns like "v1" that come from parser as simple strings
|
|
1716
|
-
if type == :volume
|
|
1717
|
-
{ volume: Components::Volume.new(value: str_value) }
|
|
1718
|
-
else
|
|
1719
|
-
str_value
|
|
1720
|
-
end
|
|
1721
|
-
|
|
1722
|
-
when :revision
|
|
1723
|
-
# Revision MUST be Edition component with type "r"
|
|
1724
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1725
|
-
|
|
1726
|
-
# Handle new structure with :revision_prefix and :revision_id (format preservation)
|
|
1727
|
-
if value.is_a?(Hash) && value[:revision_prefix] && value[:revision_id]
|
|
1728
|
-
prefix = value[:revision_prefix].to_s
|
|
1729
|
-
id = value[:revision_id].to_s.strip
|
|
1730
|
-
|
|
1731
|
-
# Normalize bare "r" → "r1"
|
|
1732
|
-
revision_id = if id.empty? || id == "r" || id == "R"
|
|
1733
|
-
"1"
|
|
1734
|
-
# Handle "r4", "R5", "4" etc. (but prefix already has the r/rev/etc.)
|
|
1735
|
-
elsif id =~ /^(\d+[a-z]?)$/
|
|
1736
|
-
$1
|
|
1737
|
-
else
|
|
1738
|
-
id
|
|
1739
|
-
end
|
|
1740
|
-
|
|
1741
|
-
# Return Edition component with original_prefix for format preservation
|
|
1742
|
-
{
|
|
1743
|
-
edition: Components::Edition.new(type: "r", id: revision_id,
|
|
1744
|
-
original_prefix: prefix),
|
|
1745
|
-
}
|
|
1746
|
-
else
|
|
1747
|
-
# Legacy handling: revision as simple string value
|
|
1748
|
-
str_value = value.to_s.strip
|
|
1749
|
-
|
|
1750
|
-
# Handle bare "r" → normalize to "r1"
|
|
1751
|
-
revision_id = if str_value.empty? || str_value == "r" || str_value == "R"
|
|
1752
|
-
"1"
|
|
1753
|
-
# Handle "r4", "R5", "4" etc.
|
|
1754
|
-
elsif str_value =~ /^[rR]?(\d+[a-z]?)$/
|
|
1755
|
-
$1
|
|
1756
|
-
else
|
|
1757
|
-
str_value
|
|
1758
|
-
end
|
|
1759
|
-
|
|
1760
|
-
# Return Edition component (no original_prefix available)
|
|
1761
|
-
{
|
|
1762
|
-
edition: Components::Edition.new(type: "r", id: revision_id),
|
|
1763
|
-
}
|
|
1764
|
-
end
|
|
1765
|
-
|
|
1766
|
-
when :revision_year, :revision_month
|
|
1767
|
-
# When revision_year comes from parser as separate element (e.g., "1019 r1963")
|
|
1768
|
-
# Create Edition component
|
|
1769
|
-
if type == :revision_year
|
|
1770
|
-
year_value = value.to_s.strip
|
|
1771
|
-
# Check if this should be an Edition component or legacy revision_year
|
|
1772
|
-
# If revision_month is also present, use legacy attributes for "revJune1908" pattern
|
|
1773
|
-
if parsed_hash[:revision_month]
|
|
1774
|
-
# Legacy: revision with month - keep as revision_year/revision_month
|
|
1775
|
-
year_value
|
|
1776
|
-
else
|
|
1777
|
-
# V2: revision with year only - create Edition component
|
|
1778
|
-
{
|
|
1779
|
-
edition: Components::Edition.new(type: "r", id: year_value),
|
|
1780
|
-
}
|
|
1781
|
-
end
|
|
1782
|
-
else
|
|
1783
|
-
# revision_month - preserve as string for legacy rendering
|
|
1784
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1785
|
-
|
|
1786
|
-
value.to_s.strip
|
|
1787
|
-
end
|
|
1788
|
-
|
|
1789
|
-
when :edition_year_separate
|
|
1790
|
-
# NEW: Edition year from "e2-1915" pattern (captured separately by parser)
|
|
1791
|
-
# This comes with first_number like "11e2" and separate year "1915"
|
|
1792
|
-
# Already handled in first_number regex matching above, but if it reaches here
|
|
1793
|
-
# as a separate capture, we need to process it
|
|
1794
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1795
|
-
|
|
1796
|
-
value.to_s # Return as string for potential use
|
|
1797
|
-
|
|
1798
|
-
when :historical_month
|
|
1799
|
-
# NEW: Historical month from "-April1909" pattern
|
|
1800
|
-
# Handled in first_number pattern matching, but return as string if separate
|
|
1801
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1802
|
-
|
|
1803
|
-
value.to_s
|
|
1804
|
-
|
|
1805
|
-
when :historical_year
|
|
1806
|
-
# NEW: Historical year from "-April1909" pattern
|
|
1807
|
-
# Handled in first_number pattern matching, but return as string if separate
|
|
1808
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1809
|
-
|
|
1810
|
-
value.to_s
|
|
1811
|
-
|
|
1812
|
-
when :supplement_year
|
|
1813
|
-
# NEW: Supplement year from "supp-1924" pattern (captured separately by parser)
|
|
1814
|
-
# This comes with first_number like "25supp" and separate year "1924"
|
|
1815
|
-
# Already handled in first_number regex matching above, but if it reaches here
|
|
1816
|
-
# as a separate capture, return as supplement value
|
|
1817
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
1818
|
-
|
|
1819
|
-
{ supplement: value.to_s } # Return as supplement attribute
|
|
1820
|
-
|
|
1821
|
-
when :supplement
|
|
1822
|
-
handle_supplement_cast(value)
|
|
1823
|
-
|
|
1824
|
-
when :supplement_date_range
|
|
1825
|
-
return nil unless value.is_a?(Hash)
|
|
1826
|
-
|
|
1827
|
-
month_start = value[:supp_month_start]&.to_s
|
|
1828
|
-
year_start = value[:supp_year_start]&.to_s
|
|
1829
|
-
month_end = value[:supp_month_end]&.to_s
|
|
1830
|
-
year_end = value[:supp_year_end]&.to_s
|
|
1831
|
-
|
|
1832
|
-
{
|
|
1833
|
-
supplement_date_range_start: (month_start && year_start ? "#{month_start}#{year_start}" : nil),
|
|
1834
|
-
supplement_date_range_end: (month_end && year_end ? "#{month_end}#{year_end}" : nil),
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
when :supplement_date
|
|
1838
|
-
return nil unless value.is_a?(Hash)
|
|
1839
|
-
|
|
1840
|
-
month = value[:supp_month]&.to_s
|
|
1841
|
-
year = value[:supp_year]&.to_s
|
|
1842
|
-
|
|
1843
|
-
month && year ? "#{month}#{year}" : nil
|
|
1844
|
-
|
|
1845
|
-
when :supplement_slash_year
|
|
1846
|
-
return nil unless value.is_a?(Hash)
|
|
1847
|
-
|
|
1848
|
-
number = value[:supp_number]&.to_s
|
|
1849
|
-
year = value[:supp_year]&.to_s
|
|
1850
|
-
|
|
1851
|
-
number && year ? "#{number}/#{year}" : nil
|
|
1852
|
-
|
|
1853
|
-
when :supplement_with_rev
|
|
1854
|
-
{ supplement: "", supplement_has_revision: true }
|
|
1855
|
-
|
|
1856
|
-
when :supp_year
|
|
1857
|
-
# Parser extracts supplement year from patterns like "187supp1924"
|
|
1858
|
-
# This should set the supplement attribute with the year value
|
|
1859
|
-
{ supplement: value.to_s }
|
|
1860
|
-
|
|
1861
|
-
# ========== V2 EDITION COMPONENT ==========
|
|
1862
|
-
|
|
1863
|
-
when :edition_e_date
|
|
1864
|
-
# Edition with "e" prefix + 6-digit date (YYYYMM): e199206, e202103
|
|
1865
|
-
# Used for IR revision+month patterns after preprocessing: "4743rJun1992" → "4743e199206"
|
|
1866
|
-
return nil unless value.is_a?(Hash) && value[:edition_date]
|
|
1867
|
-
|
|
1868
|
-
edition_date = value[:edition_date].to_s
|
|
1869
|
-
# Parse 6-digit date as YYYYMM
|
|
1870
|
-
# Store as id directly - renders as "e199206"
|
|
1871
|
-
{
|
|
1872
|
-
edition: Components::Edition.new(type: "e", id: edition_date),
|
|
1873
|
-
edition_component: Components::Edition.new(type: "e",
|
|
1874
|
-
id: edition_date),
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
when :edition_e
|
|
1878
|
-
# Edition with "e" prefix: e2, e2021
|
|
1879
|
-
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1880
|
-
|
|
1881
|
-
edition_id = value[:edition_id].to_s
|
|
1882
|
-
|
|
1883
|
-
{
|
|
1884
|
-
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1885
|
-
edition_component: Components::Edition.new(type: "e",
|
|
1886
|
-
id: edition_id),
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
when :edition_r
|
|
1890
|
-
# Revision with "r" prefix: r5, r2021
|
|
1891
|
-
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1892
|
-
|
|
1893
|
-
edition_id = value[:edition_id].to_s
|
|
1894
|
-
|
|
1895
|
-
{
|
|
1896
|
-
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1897
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1898
|
-
id: edition_id),
|
|
1899
|
-
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
when :edition_r_no_space
|
|
1903
|
-
# Revision with "r" prefix (no space pattern): r2, r5
|
|
1904
|
-
# Used for patterns like "800-56Ar2" where edition is "r2"
|
|
1905
|
-
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1906
|
-
|
|
1907
|
-
edition_id = value[:edition_id].to_s
|
|
1908
|
-
|
|
1909
|
-
{
|
|
1910
|
-
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1911
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1912
|
-
id: edition_id),
|
|
1913
|
-
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
when :edition_rev
|
|
1917
|
-
# Revision with "rev" prefix (verbose): rev2013, rev 2013
|
|
1918
|
-
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1919
|
-
|
|
1920
|
-
edition_id = value[:edition_id].to_s
|
|
1921
|
-
|
|
1922
|
-
{
|
|
1923
|
-
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1924
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1925
|
-
id: edition_id),
|
|
1926
|
-
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
when :edition_r_letter
|
|
1930
|
-
# Revision with "r" prefix and letter suffix: r1a, r2b (for SP patterns like 800-22r1a)
|
|
1931
|
-
return nil unless value.is_a?(Hash) && value[:edition_id] && value[:edition_letter]
|
|
1932
|
-
|
|
1933
|
-
edition_id = value[:edition_id].to_s
|
|
1934
|
-
edition_letter = value[:edition_letter].to_s.downcase
|
|
1935
|
-
|
|
1936
|
-
{
|
|
1937
|
-
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1938
|
-
additional_text: edition_letter),
|
|
1939
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1940
|
-
id: edition_id,
|
|
1941
|
-
additional_text: edition_letter),
|
|
1942
|
-
revision: "r#{edition_id}#{edition_letter}", # Also set revision string attribute for compatibility
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
when :edition_r_letter_only
|
|
1946
|
-
# Revision with "r" prefix and only letter (no digit): ra, rb (for SP patterns like 800-27ra)
|
|
1947
|
-
return nil unless value.is_a?(Hash) && value[:edition_letter]
|
|
1948
|
-
|
|
1949
|
-
edition_letter = value[:edition_letter].to_s.downcase
|
|
1950
|
-
|
|
1951
|
-
{
|
|
1952
|
-
edition: Components::Edition.new(type: "r", id: edition_letter),
|
|
1953
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1954
|
-
id: edition_letter),
|
|
1955
|
-
revision: "r#{edition_letter}", # Also set revision string attribute for compatibility
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
when :edition_historical
|
|
1959
|
-
# Historical with "-" prefix: -3, -4
|
|
1960
|
-
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1961
|
-
|
|
1962
|
-
edition_id = value[:edition_id].to_s
|
|
1963
|
-
|
|
1964
|
-
{
|
|
1965
|
-
edition: Components::Edition.new(type: "-", id: edition_id),
|
|
1966
|
-
edition_component: Components::Edition.new(type: "-",
|
|
1967
|
-
id: edition_id),
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
when :edition_r_with_space_letter
|
|
1971
|
-
# Revision with "r" prefix, space, and letter: r 5A (format preservation)
|
|
1972
|
-
# Used for patterns like "NIST SP 800-53 r5A"
|
|
1973
|
-
# NOTE: If there's an update component, the space was added by preprocessing
|
|
1974
|
-
return nil unless value.is_a?(Hash) && value[:edition_id] && value[:edition_letter]
|
|
1975
|
-
|
|
1976
|
-
edition_id = value[:edition_id].to_s
|
|
1977
|
-
edition_letter = value[:edition_letter].to_s.upcase
|
|
1978
|
-
|
|
1979
|
-
# Check if this is an embedded edition with update (space added by preprocessing)
|
|
1980
|
-
has_update = parsed_hash[:update_prefix] || parsed_hash[:update]
|
|
1981
|
-
|
|
1982
|
-
if has_update
|
|
1983
|
-
# No original_prefix - space was added by preprocessing
|
|
1984
|
-
{
|
|
1985
|
-
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1986
|
-
additional_text: edition_letter),
|
|
1987
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1988
|
-
id: edition_id,
|
|
1989
|
-
additional_text: edition_letter),
|
|
1990
|
-
revision: "r#{edition_id}#{edition_letter}",
|
|
1991
|
-
}
|
|
1992
|
-
else
|
|
1993
|
-
# Space was in original input - preserve format
|
|
1994
|
-
{
|
|
1995
|
-
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1996
|
-
additional_text: edition_letter,
|
|
1997
|
-
original_prefix: " r"),
|
|
1998
|
-
edition_component: Components::Edition.new(type: "r",
|
|
1999
|
-
id: edition_id,
|
|
2000
|
-
additional_text: edition_letter,
|
|
2001
|
-
original_prefix: " r"),
|
|
2002
|
-
revision: "r#{edition_id}#{edition_letter}",
|
|
2003
|
-
}
|
|
2004
|
-
end
|
|
2005
|
-
|
|
2006
|
-
when :edition_r_with_space
|
|
2007
|
-
# Revision with "r" prefix and space: r 5 (format preservation)
|
|
2008
|
-
# Used for patterns like "NIST SP 800-53 r5"
|
|
2009
|
-
# NOTE: If there's an update component, the space was added by preprocessing
|
|
2010
|
-
# for patterns like "8115r1/upd" → "8115 r1/upd", so don't set original_prefix
|
|
2011
|
-
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
2012
|
-
|
|
2013
|
-
edition_id = value[:edition_id].to_s
|
|
2014
|
-
|
|
2015
|
-
# Check if this is an embedded edition with update (space added by preprocessing)
|
|
2016
|
-
# Patterns like "8115r1/upd" become "8115 r1/upd" after preprocessing
|
|
2017
|
-
has_update = parsed_hash[:update_prefix] || parsed_hash[:update]
|
|
2018
|
-
|
|
2019
|
-
if has_update
|
|
2020
|
-
# No original_prefix - space was added by preprocessing
|
|
2021
|
-
{
|
|
2022
|
-
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
2023
|
-
edition_component: Components::Edition.new(type: "r",
|
|
2024
|
-
id: edition_id),
|
|
2025
|
-
revision: "r#{edition_id}",
|
|
2026
|
-
}
|
|
2027
|
-
else
|
|
2028
|
-
# Space was in original input - preserve format
|
|
2029
|
-
{
|
|
2030
|
-
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
2031
|
-
original_prefix: " r"),
|
|
2032
|
-
edition_component: Components::Edition.new(type: "r",
|
|
2033
|
-
id: edition_id,
|
|
2034
|
-
original_prefix: " r"),
|
|
2035
|
-
revision: "r#{edition_id}",
|
|
2036
|
-
}
|
|
2037
|
-
end
|
|
2038
|
-
|
|
2039
|
-
when :edition_id
|
|
2040
|
-
# Captured by edition_e, edition_r, edition_rev, edition_historical
|
|
2041
|
-
nil
|
|
2042
|
-
|
|
2043
|
-
when :edition_date
|
|
2044
|
-
# Captured by edition_e_date
|
|
2045
|
-
nil
|
|
2046
|
-
|
|
2047
|
-
# ========== LEGACY EDITION (for migration) ==========
|
|
2048
|
-
|
|
2049
|
-
when :legacy_edition
|
|
2050
|
-
# Legacy edition patterns - will be phased out
|
|
2051
|
-
# For now, map to old edition_year/edition_month attributes
|
|
2052
|
-
nil # Handled by existing edition_year logic below
|
|
2053
|
-
|
|
2054
|
-
when :edition_month, :edition_year, :edition_day, :edition_has_rev
|
|
2055
|
-
# These work together: edition_month + edition_year → single edition ID
|
|
2056
|
-
# Skip processing if this is edition_month alone (will be processed with edition_year)
|
|
2057
|
-
return nil if type == :edition_month
|
|
2058
|
-
|
|
2059
|
-
# Process edition_year, combining with edition_month if present
|
|
2060
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
2061
|
-
|
|
2062
|
-
# Build the edition ID from year and optional month
|
|
2063
|
-
edition_id = value.to_s # Start with year (e.g., "1985")
|
|
2064
|
-
|
|
2065
|
-
# Add month if present (e.g., "Mar" → "03", so "1985" + "03" = "198503")
|
|
2066
|
-
# For FIPS with day: "Sep30/1977" → "19770930" (year + month + day)
|
|
2067
|
-
if parsed_hash[:edition_month]
|
|
2068
|
-
month_str = parsed_hash[:edition_month].to_s
|
|
2069
|
-
month_num = Date::ABBR_MONTHNAMES.index(month_str) ||
|
|
2070
|
-
Date::MONTHNAMES.index(month_str) ||
|
|
2071
|
-
month_str.to_i
|
|
2072
|
-
if month_num&.positive?
|
|
2073
|
-
# Check if this is FIPS series - FIPS uses number format (e198503), not month abbreviations
|
|
2074
|
-
# For historical NBS documents, preserve month name: "April1909" not "190904"
|
|
2075
|
-
is_fips = parsed_hash[:series]&.to_s == "FIPS"
|
|
2076
|
-
if !is_fips && month_str.match?(/^[A-Z][a-z]+/) && edition_id.to_s.match?(/^\d{4}$/)
|
|
2077
|
-
# Historical NBS month+year format: preserve month name, use "-" type for special rendering
|
|
2078
|
-
edition_obj = Components::Edition.new(
|
|
2079
|
-
type: "-",
|
|
2080
|
-
id: "",
|
|
2081
|
-
additional_text: "#{month_str}#{edition_id}",
|
|
2082
|
-
)
|
|
2083
|
-
return {
|
|
2084
|
-
edition: edition_obj,
|
|
2085
|
-
edition_component: edition_obj,
|
|
2086
|
-
edition_year: edition_id.to_s,
|
|
2087
|
-
}
|
|
2088
|
-
else
|
|
2089
|
-
# Modern format (and FIPS): combine year and month as single number: 1985 + 03 = 198503
|
|
2090
|
-
edition_id = "#{edition_id}#{format('%02d', month_num)}"
|
|
2091
|
-
|
|
2092
|
-
# For FIPS with day, append day as well: "Sep30/1977" → "19770930"
|
|
2093
|
-
if is_fips && parsed_hash[:edition_day]
|
|
2094
|
-
day_num = parsed_hash[:edition_day].to_s.to_i
|
|
2095
|
-
if day_num.positive? && day_num <= 31
|
|
2096
|
-
edition_id = "#{edition_id}#{format('%02d', day_num)}"
|
|
2097
|
-
end
|
|
2098
|
-
end
|
|
2099
|
-
end
|
|
2100
|
-
end
|
|
2101
|
-
end
|
|
2102
|
-
|
|
2103
|
-
# Create Edition component with type="e" (edition) and combined ID
|
|
2104
|
-
edition_obj = Components::Edition.new(type: "e", id: edition_id)
|
|
2105
|
-
|
|
2106
|
-
# Return as hash to set edition and edition_year
|
|
2107
|
-
{
|
|
2108
|
-
edition: edition_obj, # Main attribute for tests
|
|
2109
|
-
edition_component: edition_obj, # V2 component
|
|
2110
|
-
edition_year: value.to_s, # Keep string for render logic
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
when :part
|
|
2114
|
-
# Part component - handle part number with optional addendum
|
|
2115
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
2116
|
-
|
|
2117
|
-
str_value = value.to_s.strip
|
|
2118
|
-
|
|
2119
|
-
# Pattern: "1adde1" → Part(value: "1"), addendum=true
|
|
2120
|
-
# Note: eN after add is discarded (not included in output per fixture)
|
|
2121
|
-
if str_value =~ /^(\d+)add(e\d+)$/
|
|
2122
|
-
{
|
|
2123
|
-
part: Components::Part.new(type: "pt", value: $1),
|
|
2124
|
-
addendum: "true",
|
|
2125
|
-
}
|
|
2126
|
-
elsif str_value =~ /^(\d+)add/
|
|
2127
|
-
{
|
|
2128
|
-
part: Components::Part.new(type: "pt", value: $1),
|
|
2129
|
-
addendum: "true",
|
|
2130
|
-
}
|
|
2131
|
-
else
|
|
2132
|
-
# Just a part number - return Part component with pt type
|
|
2133
|
-
{ part: Components::Part.new(type: "pt", value: str_value) }
|
|
2134
|
-
end
|
|
2135
|
-
|
|
2136
|
-
when :part_extracted
|
|
2137
|
-
# Legacy - this is now handled by :part
|
|
2138
|
-
nil
|
|
2139
|
-
|
|
2140
|
-
when :edition_letter
|
|
2141
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
2142
|
-
|
|
2143
|
-
value.to_s
|
|
2144
|
-
|
|
2145
|
-
when :public_draft
|
|
2146
|
-
return nil if value.nil?
|
|
2147
|
-
|
|
2148
|
-
value.to_s
|
|
2149
|
-
|
|
2150
|
-
when :draft
|
|
2151
|
-
# Extract draft number from "-draft N" pattern for pd rendering
|
|
2152
|
-
return nil if value.nil?
|
|
2153
|
-
|
|
2154
|
-
str_value = value.to_s.strip
|
|
2155
|
-
return nil if str_value.empty?
|
|
2156
|
-
|
|
2157
|
-
# Pattern: " -draft 2" or "-draft 2" → extract "2" for pd rendering
|
|
2158
|
-
if str_value =~ /^\s*-draft\s+(\d+)$/
|
|
2159
|
-
{ draft_number: $1 }
|
|
2160
|
-
# Pattern: " 2pd" → already in pd format
|
|
2161
|
-
elsif str_value =~ /^\s*(\d+)pd$/
|
|
2162
|
-
{ public_draft: $1 }
|
|
2163
|
-
# Other patterns (parenthetical, simple -draft)
|
|
2164
|
-
else
|
|
2165
|
-
str_value
|
|
2166
|
-
end
|
|
2167
|
-
|
|
2168
|
-
when :update
|
|
2169
|
-
handle_update_cast(value)
|
|
2170
|
-
|
|
2171
|
-
when :update_number, :update_year
|
|
2172
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
2173
|
-
|
|
2174
|
-
value.to_s
|
|
2175
|
-
|
|
2176
|
-
when :addendum
|
|
2177
|
-
handle_addendum_cast(value)
|
|
2178
|
-
|
|
2179
|
-
when :addendum_number
|
|
2180
|
-
return nil if value.nil? || value.to_s.strip.empty?
|
|
2181
|
-
|
|
2182
|
-
value.to_s
|
|
2183
|
-
|
|
2184
|
-
when :supplement_suffix
|
|
2185
|
-
# Return as hash to set supplement attribute (not supplement_suffix)
|
|
2186
|
-
{ supplement: value.to_s }
|
|
2187
|
-
|
|
2188
|
-
when :date
|
|
2189
|
-
# Date component per NIST spec
|
|
2190
|
-
return nil unless value.is_a?(Hash)
|
|
2191
|
-
|
|
2192
|
-
# NEW: Check if this is historical edition pattern ("-April1909")
|
|
2193
|
-
# Parser captures as date with month + year, but semantically it's an edition
|
|
2194
|
-
if value[:date_month] && value[:date_year] && !value[:date_day]
|
|
2195
|
-
month_str = value[:date_month].to_s
|
|
2196
|
-
year_str = value[:date_year].to_s
|
|
2197
|
-
# If month is a word like "April", this is historical edition format
|
|
2198
|
-
if month_str.match?(/^[A-Za-z]+$/)
|
|
2199
|
-
return {
|
|
2200
|
-
edition: Components::Edition.new(type: "-",
|
|
2201
|
-
additional_text: "#{month_str}#{year_str}"),
|
|
2202
|
-
}
|
|
2203
|
-
end
|
|
2204
|
-
end
|
|
2205
|
-
|
|
2206
|
-
# Regular date processing
|
|
2207
|
-
value[:date_year]&.to_s
|
|
2208
|
-
value[:date_month]&.to_s
|
|
2209
|
-
value[:date_day]&.to_s
|
|
2210
|
-
|
|
2211
|
-
else
|
|
2212
|
-
# Unknown types: return the original value for default processing
|
|
2213
|
-
# This allows hashes with arbitrary structures to be processed
|
|
2214
|
-
# e.g., second_number hash with number_only and edition_id
|
|
2215
|
-
value.is_a?(Hash) ? value : nil
|
|
2216
|
-
end
|
|
2217
|
-
end
|
|
2218
|
-
|
|
2219
|
-
# Handle supplement casting with all its variants
|
|
2220
|
-
def handle_supplement_cast(value)
|
|
2221
|
-
return nil unless value
|
|
2222
|
-
|
|
2223
|
-
if value.is_a?(Array) && value.empty?
|
|
2224
|
-
# Empty array means "supp" was present but no suffix
|
|
2225
|
-
""
|
|
2226
|
-
else
|
|
2227
|
-
str_value = value.to_s.strip
|
|
2228
|
-
str_value.empty? ? nil : str_value
|
|
2229
|
-
end
|
|
2230
|
-
end
|
|
2231
|
-
|
|
2232
|
-
# Handle update casting (number and year)
|
|
2233
|
-
def handle_update_cast(value)
|
|
2234
|
-
if value.is_a?(Hash)
|
|
2235
|
-
{
|
|
2236
|
-
update_number: value[:update_number]&.to_s,
|
|
2237
|
-
update_year: value[:update_year]&.to_s,
|
|
2238
|
-
}.compact
|
|
2239
|
-
elsif value.to_s.strip.empty?
|
|
2240
|
-
# Empty update string (just "-upd" with no details)
|
|
2241
|
-
# Don't create update component - not enough data
|
|
2242
|
-
nil
|
|
2243
|
-
else
|
|
2244
|
-
str_value = value.to_s.strip
|
|
2245
|
-
str_value.empty? ? nil : str_value
|
|
2246
|
-
end
|
|
2247
|
-
end
|
|
2248
|
-
|
|
2249
|
-
# Handle addendum casting (number)
|
|
2250
|
-
def handle_addendum_cast(value)
|
|
2251
|
-
if value.is_a?(Hash)
|
|
2252
|
-
addendum_num = value[:addendum_number]&.to_s&.strip
|
|
2253
|
-
if addendum_num && !addendum_num.empty?
|
|
2254
|
-
{ addendum_number: addendum_num }
|
|
2255
|
-
else
|
|
2256
|
-
{ addendum: "true" }
|
|
2257
|
-
end
|
|
2258
|
-
else
|
|
2259
|
-
str_value = value.to_s.strip
|
|
2260
|
-
if str_value.empty?
|
|
2261
|
-
{ addendum: "true" }
|
|
2262
|
-
else
|
|
2263
|
-
{ addendum_number: str_value }
|
|
2264
|
-
end
|
|
2265
|
-
end
|
|
457
|
+
@circular_supplement_builder.build_circular_supplement(parsed_hash)
|
|
2266
458
|
end
|
|
2267
459
|
end
|
|
2268
460
|
end
|