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
|
@@ -0,0 +1,1272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Nist
|
|
5
|
+
# Caster class for NIST type coercion
|
|
6
|
+
# Single Responsibility: Convert parsed values to domain component objects
|
|
7
|
+
#
|
|
8
|
+
# Extracted from Builder to isolate the 1200+ line type coercion switch
|
|
9
|
+
# from orchestration logic. Caster is stateless — each call to #cast
|
|
10
|
+
# receives all the context it needs via parameters.
|
|
11
|
+
class Caster
|
|
12
|
+
# Translation normalization map (V1 compatibility)
|
|
13
|
+
TRANSLATION_MAP = {
|
|
14
|
+
"es" => "spa",
|
|
15
|
+
"sp" => "spa",
|
|
16
|
+
"pt" => "por",
|
|
17
|
+
"id" => "ind",
|
|
18
|
+
"chi" => "zho",
|
|
19
|
+
"viet" => "vie",
|
|
20
|
+
"port" => "por",
|
|
21
|
+
"esp" => "spa",
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# Cast parsed value to appropriate component type
|
|
25
|
+
# ALL conversions happen in this single method
|
|
26
|
+
# @param type [Symbol] the parameter type
|
|
27
|
+
# @param value [Object] the parsed value
|
|
28
|
+
# @param parsed_hash [Hash] the full parsed hash for context
|
|
29
|
+
# @return [Object, Hash, nil] the cast component(s)
|
|
30
|
+
def cast(type, value, parsed_hash = {})
|
|
31
|
+
case type
|
|
32
|
+
when :publisher
|
|
33
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
34
|
+
|
|
35
|
+
# publisher is a plain string attribute (see Identifiers::Base).
|
|
36
|
+
value.to_s
|
|
37
|
+
|
|
38
|
+
when :dated_date
|
|
39
|
+
# Date-style identifier (DatedDocument): carry the YYYY-MM-DD parts
|
|
40
|
+
# as string attributes.
|
|
41
|
+
return nil unless value.is_a?(Hash)
|
|
42
|
+
|
|
43
|
+
{ date_year: value[:date_year]&.to_s, date_month: value[:date_month]&.to_s,
|
|
44
|
+
date_day: value[:date_day]&.to_s }
|
|
45
|
+
|
|
46
|
+
when :dated_seq
|
|
47
|
+
return nil if value.to_s.strip.empty?
|
|
48
|
+
|
|
49
|
+
{ dated_seq: value.to_s }
|
|
50
|
+
|
|
51
|
+
when :series
|
|
52
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
53
|
+
|
|
54
|
+
str_value = value.to_s
|
|
55
|
+
publisher_extracted = nil
|
|
56
|
+
|
|
57
|
+
# Compound series carry the publisher inside the series token (e.g.
|
|
58
|
+
# "NBS CIRC", "NIST DCI") because the bare series code isn't recognized
|
|
59
|
+
# by the grammar on its own. Split the leading publisher out so
|
|
60
|
+
# `series` holds just the code and `publisher` is populated — matching
|
|
61
|
+
# how standalone series (NIST SP, NBS CS) already parse, and avoiding a
|
|
62
|
+
# nil publisher (which renders fine via the series string but would
|
|
63
|
+
# otherwise force a misleading publisher_was_parsed: false). Routing
|
|
64
|
+
# already ran on the raw compound series, so stripping here can't
|
|
65
|
+
# change the identifier class.
|
|
66
|
+
if (m = str_value.match(/\A(NBS|NIST) (.+)\z/))
|
|
67
|
+
publisher_extracted = m[1]
|
|
68
|
+
str_value = m[2]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Return composite hash with both publisher and series if extracted
|
|
72
|
+
if publisher_extracted
|
|
73
|
+
{
|
|
74
|
+
publisher: publisher_extracted,
|
|
75
|
+
series: Components::Code.new(value: str_value),
|
|
76
|
+
}
|
|
77
|
+
else
|
|
78
|
+
Components::Code.new(value: str_value)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
when :volume_number
|
|
82
|
+
# Volume from v#n# pattern - return Volume component
|
|
83
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
84
|
+
|
|
85
|
+
{ volume: Components::Volume.new(value: value.to_s) }
|
|
86
|
+
|
|
87
|
+
when :issue_number
|
|
88
|
+
# Issue number from v#n# pattern - return Part component
|
|
89
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
90
|
+
|
|
91
|
+
{ part: Components::Part.new(type: "n", value: value.to_s) }
|
|
92
|
+
|
|
93
|
+
when :part_number
|
|
94
|
+
# Part number from GCR pattern (e.g., 85-3273-37)
|
|
95
|
+
# Return raw value for inclusion in compound number
|
|
96
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
97
|
+
|
|
98
|
+
value # Return raw value to be tracked in builder
|
|
99
|
+
|
|
100
|
+
when :letter_number
|
|
101
|
+
# Letter suffix from a dashed pattern (e.g., 800-56A → {:letter_base=>"56", :letter_suffix=>"A"}).
|
|
102
|
+
# Series policy decides whether the suffix becomes a Part component
|
|
103
|
+
# (default) or stays in the number (MONO/NCSTAR/IR-with-R-or-Ur).
|
|
104
|
+
return nil if value.nil? || !value.is_a?(Hash)
|
|
105
|
+
|
|
106
|
+
Series.for(parsed_hash).cast_letter_number(value, parsed_hash)
|
|
107
|
+
|
|
108
|
+
when :fips_part
|
|
109
|
+
# Part number from FIPS date pattern (e.g., 11-1-Sep30/1977)
|
|
110
|
+
# Return Part component with pt type
|
|
111
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
112
|
+
|
|
113
|
+
{ part: Components::Part.new(type: "pt", value: value.to_s) }
|
|
114
|
+
|
|
115
|
+
when :owmwp_date_number
|
|
116
|
+
# OWMWP date-based number format (MM-DD-YYYY)
|
|
117
|
+
# Parser returns: {:owmwp_month=>"06", :owmwp_day=>"13", :owmwp_year=>"2018"}
|
|
118
|
+
# Convert to number + edition: "06-13" + edition "e2018"
|
|
119
|
+
return nil if value.nil?
|
|
120
|
+
|
|
121
|
+
number_part = "#{value[:owmwp_month]}-#{value[:owmwp_day]}"
|
|
122
|
+
edition_part = Components::Edition.new(type: "e",
|
|
123
|
+
id: value[:owmwp_year])
|
|
124
|
+
{ first_number: Components::Code.new(value: number_part), edition: edition_part }
|
|
125
|
+
|
|
126
|
+
when :first_number, :second_number
|
|
127
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
128
|
+
|
|
129
|
+
# NEW: Handle OWMWP date-based number (nested hash structure)
|
|
130
|
+
# Parser returns: {:owmwp_date_number=>{:owmwp_month=>"06", :owmwp_day=>"13", :owmwp_year=>"2018"}}
|
|
131
|
+
# Convert to number + edition: "06-13" + edition "e2018"
|
|
132
|
+
if value.is_a?(Hash) && value[:owmwp_date_number]
|
|
133
|
+
owmwp_hash = value[:owmwp_date_number]
|
|
134
|
+
number_part = "#{owmwp_hash[:owmwp_month]}-#{owmwp_hash[:owmwp_day]}"
|
|
135
|
+
edition_part = Components::Edition.new(type: "e",
|
|
136
|
+
id: owmwp_hash[:owmwp_year])
|
|
137
|
+
return { type => Components::Code.new(value: number_part), edition: edition_part }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# NEW: Handle second_number with edition (hash with :number_only and :edition_id)
|
|
141
|
+
# This handles "126r2013" pattern where parser returns {:number_only=>"126", :edition_id=>"2013"}
|
|
142
|
+
# CRITICAL: Wrap in a structure that builder loop can recognize
|
|
143
|
+
# The builder loop expects keys like :second_number to be present in the hash
|
|
144
|
+
if type == :second_number && value.is_a?(Hash) && value[:number_only] && value[:edition_id]
|
|
145
|
+
# Return wrapped hash so builder loop finds :second_number key
|
|
146
|
+
return { second_number: value }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# NEW: Handle second_number with revision_letter (hash with :revision_letter containing :number_only and :letter)
|
|
150
|
+
# This handles "27ra" pattern where parser returns {revision_letter: {number_only: "27", letter: "a"}}
|
|
151
|
+
# Should be combined to "27rA" format
|
|
152
|
+
if type == :second_number && value.is_a?(Hash) && value[:revision_letter]
|
|
153
|
+
revision_data = value[:revision_letter]
|
|
154
|
+
number_only = revision_data[:number_only].to_s
|
|
155
|
+
letter = revision_data[:letter].to_s.upcase
|
|
156
|
+
# Return as second_number with combined format "27rA"
|
|
157
|
+
return { second_number: Components::Code.new(value: "#{number_only}r#{letter}") }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Handle v#n# pattern (CSM series) - comes as hash from parser
|
|
161
|
+
# Return Volume and Part components separately
|
|
162
|
+
if value.is_a?(Hash) && value[:volume_number] && value[:issue_number]
|
|
163
|
+
volume_num = value[:volume_number].to_s
|
|
164
|
+
issue_num = value[:issue_number].to_s
|
|
165
|
+
return {
|
|
166
|
+
volume: Components::Volume.new(value: volume_num),
|
|
167
|
+
part: Components::Part.new(type: "n", value: issue_num),
|
|
168
|
+
}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
str_value = value.to_s
|
|
172
|
+
|
|
173
|
+
# Handle special patterns embedded in first_number
|
|
174
|
+
if type == :first_number
|
|
175
|
+
|
|
176
|
+
# NEW: Handle first_number hash with number_with_rev_year (e.g., "1013rv1953")
|
|
177
|
+
# Parser returns: {:number_with_rev_year=>{:number=>"1013", :revision_year=>"1953"}}
|
|
178
|
+
if value.is_a?(Hash) && value[:number_with_rev_year]
|
|
179
|
+
number_part = value[:number_with_rev_year][:number].to_s
|
|
180
|
+
revision_year = value[:number_with_rev_year][:revision_year].to_s
|
|
181
|
+
return {
|
|
182
|
+
first_number: Components::Code.new(value: number_part),
|
|
183
|
+
edition: Components::Edition.new(type: "rv", id: revision_year),
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Handle first_number with letter suffix and revision (e.g., "8278Ar1", "256Ar1930")
|
|
188
|
+
# Parser returns: {:number_with_letter_revision=>{:number=>"8278", :letter_suffix=>"A", :revision_id=>"1"}}
|
|
189
|
+
# Splits into number + Part(letter) + Edition(r, id) to mirror how :letter_number
|
|
190
|
+
# (dash-separated "56Ar2") is decomposed for SpecialPublication and similar series.
|
|
191
|
+
if value.is_a?(Hash) && value[:number_with_letter_revision]
|
|
192
|
+
data = value[:number_with_letter_revision]
|
|
193
|
+
return {
|
|
194
|
+
first_number: Components::Code.new(value: data[:number].to_s),
|
|
195
|
+
part: Components::Part.new(type: "",
|
|
196
|
+
value: data[:letter_suffix].to_s.upcase),
|
|
197
|
+
edition: Components::Edition.new(type: "r",
|
|
198
|
+
id: data[:revision_id].to_s),
|
|
199
|
+
edition_component: Components::Edition.new(type: "r",
|
|
200
|
+
id: data[:revision_id].to_s),
|
|
201
|
+
revision: "r#{data[:revision_id]}",
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# NEW: Handle first_number hash with language_code (e.g., "1262es")
|
|
206
|
+
# Parser returns: {:number=>"1262", :language_code=>"es"}
|
|
207
|
+
if value.is_a?(Hash) && value[:number] && value[:language_code]
|
|
208
|
+
number_part = value[:number].to_s
|
|
209
|
+
language_code = value[:language_code].to_s.strip.downcase
|
|
210
|
+
# Apply normalization map (es -> spa, pt -> por, etc.)
|
|
211
|
+
normalized_code = TRANSLATION_MAP[language_code] || language_code
|
|
212
|
+
return {
|
|
213
|
+
first_number: Components::Code.new(value: number_part),
|
|
214
|
+
translation_component: Components::Translation.new(code: normalized_code),
|
|
215
|
+
}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# NEW: Handle first_number hash with number, part_number, and edition_year (MR format)
|
|
219
|
+
# Parser returns: {:number=>"28", :part_number=>"1", :edition_year=>"1969"}
|
|
220
|
+
# For "NBS.HB.28pt1e1969" MR format input
|
|
221
|
+
if value.is_a?(Hash) && value[:number] && value[:part_number] && value[:edition_year]
|
|
222
|
+
number_part = value[:number].to_s
|
|
223
|
+
part_number = value[:part_number].to_s
|
|
224
|
+
edition_year = value[:edition_year].to_s
|
|
225
|
+
return {
|
|
226
|
+
first_number: Components::Code.new(value: number_part),
|
|
227
|
+
part: Components::Part.new(type: "pt", value: part_number),
|
|
228
|
+
edition: Components::Edition.new(type: "e", id: edition_year),
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# NEW: Check for edition_year_separate in parsed_hash context
|
|
233
|
+
# This handles "11e2-1915" where first_number="11e2" and edition_year_separate="1915"
|
|
234
|
+
if parsed_hash[:edition_year_separate] && str_value =~ /^(\d+)e(\d+)$/
|
|
235
|
+
number_part = $1
|
|
236
|
+
edition_id = $2
|
|
237
|
+
year_part = parsed_hash[:edition_year_separate].to_s
|
|
238
|
+
return {
|
|
239
|
+
first_number: Components::Code.new(value: number_part),
|
|
240
|
+
edition: Components::Edition.new(type: "e", id: edition_id,
|
|
241
|
+
additional_text: year_part),
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# NEW: Check for number_with_volume in value hash (for first_number)
|
|
246
|
+
# This handles "539v10" where parser captures :number and :volume_suffix separately
|
|
247
|
+
# Parse tree: value = {:number_with_volume => {:number => "539", :volume_suffix => "10"}}
|
|
248
|
+
if value.is_a?(Hash) && value[:number_with_volume] && value[:number_with_volume][:volume_suffix]
|
|
249
|
+
number_part = value[:number_with_volume][:number].to_s
|
|
250
|
+
volume_value = value[:number_with_volume][:volume_suffix].to_s
|
|
251
|
+
return {
|
|
252
|
+
first_number: Components::Code.new(value: number_part),
|
|
253
|
+
volume: Components::Volume.new(value: volume_value),
|
|
254
|
+
}
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# NEW: Check for historical_month and historical_year in parsed_hash context
|
|
258
|
+
# This handles "-April1909" where it's captured as separate month/year
|
|
259
|
+
if parsed_hash[:historical_month] && parsed_hash[:historical_year]
|
|
260
|
+
month_part = parsed_hash[:historical_month].to_s
|
|
261
|
+
year_part = parsed_hash[:historical_year].to_s
|
|
262
|
+
# Check if str_value is just a number (the part before dash)
|
|
263
|
+
if /^\d+$/.match?(str_value)
|
|
264
|
+
return {
|
|
265
|
+
first_number: Components::Code.new(value: str_value),
|
|
266
|
+
edition: Components::Edition.new(type: "-",
|
|
267
|
+
additional_text: "#{month_part}#{year_part}"),
|
|
268
|
+
}
|
|
269
|
+
else
|
|
270
|
+
# No number, just historical edition
|
|
271
|
+
return {
|
|
272
|
+
edition: Components::Edition.new(type: "-",
|
|
273
|
+
additional_text: "#{month_part}#{year_part}"),
|
|
274
|
+
}
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Pattern "9350sup"/"5893supp" - number with bare supplement marker
|
|
279
|
+
# (no trailing payload). Accept both single-p "sup" and double-p
|
|
280
|
+
# "supp" so the marker is isolated as supplement="" and rendered as
|
|
281
|
+
# canonical single-p "sup", instead of staying baked into the number
|
|
282
|
+
# as an opaque suffix. E.g. "NBS RPT 5893supp", "NBS MONO 32supp".
|
|
283
|
+
if str_value =~ /^(\d+)supp?$/
|
|
284
|
+
return {
|
|
285
|
+
first_number: Components::Code.new(value: $1),
|
|
286
|
+
supplement: "",
|
|
287
|
+
}
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# NEW: Check for supplement_year in parsed_hash context
|
|
291
|
+
# This handles "25supp-1924" where first_number="25supp" and supplement_year="1924"
|
|
292
|
+
if parsed_hash[:supplement_year] && str_value =~ /^(\d+)supp?$/
|
|
293
|
+
number_part = $1
|
|
294
|
+
year_part = parsed_hash[:supplement_year].to_s
|
|
295
|
+
return {
|
|
296
|
+
first_number: Components::Code.new(value: number_part),
|
|
297
|
+
supplement: year_part,
|
|
298
|
+
}
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Pattern: "154supprev" - supplement with revision
|
|
302
|
+
if str_value =~ /^(\d+)supprev$/
|
|
303
|
+
return {
|
|
304
|
+
first_number: Components::Code.new(value: $1),
|
|
305
|
+
supplement: "",
|
|
306
|
+
supplement_has_revision: true,
|
|
307
|
+
}
|
|
308
|
+
# NEW: Pattern "11e2-1915" - edition with separate year (inline match)
|
|
309
|
+
# Creates: number="11", Edition(type: "e", id: "2", additional_text: "1915")
|
|
310
|
+
# Renders: "NBS CIRC 11e2.1915"
|
|
311
|
+
elsif str_value =~ /^(\d+)e(\d+)-(\d{4})$/
|
|
312
|
+
number_part = $1
|
|
313
|
+
edition_id = $2
|
|
314
|
+
year_part = $3
|
|
315
|
+
return {
|
|
316
|
+
first_number: Components::Code.new(value: number_part),
|
|
317
|
+
edition: Components::Edition.new(type: "e", id: edition_id,
|
|
318
|
+
additional_text: year_part),
|
|
319
|
+
}
|
|
320
|
+
# NEW: Pattern "-April1909" - historical edition with month+year (inline match)
|
|
321
|
+
# Creates: Edition(type: "-", additional_text: "April1909")
|
|
322
|
+
# Renders: "NBS CIRC -April1909"
|
|
323
|
+
elsif str_value =~ /^-([A-Za-z]{3,9})(\d{4})$/
|
|
324
|
+
month_part = $1
|
|
325
|
+
year_part = $2
|
|
326
|
+
return {
|
|
327
|
+
edition: Components::Edition.new(type: "-",
|
|
328
|
+
additional_text: "#{month_part}#{year_part}"),
|
|
329
|
+
}
|
|
330
|
+
# NEW: CS Emergency pattern "e104" or "e104-43" -> extract number
|
|
331
|
+
# This must come BEFORE bare edition check to avoid conflict
|
|
332
|
+
# CS emergency always has 3+ digit number (e104, not e2)
|
|
333
|
+
# NOTE: If second_number exists (e104-43 pattern), defer to compound number logic
|
|
334
|
+
elsif /^e(\d{3,})$/.match?(str_value) && !parsed_hash[:second_number]
|
|
335
|
+
# Extract emergency number: e104 -> 104 (only when no second_number)
|
|
336
|
+
emergency_num = str_value.sub(/^e/, "")
|
|
337
|
+
return {
|
|
338
|
+
first_number: Components::Code.new(value: emergency_num),
|
|
339
|
+
}
|
|
340
|
+
# If e104-43 pattern (with second_number), keep e prefix for compound number logic
|
|
341
|
+
elsif /^e(\d{3,})$/.match?(str_value) && parsed_hash[:second_number]
|
|
342
|
+
# Keep e104 as-is, let compound number logic handle it
|
|
343
|
+
return {
|
|
344
|
+
first_number: Components::Code.new(value: str_value),
|
|
345
|
+
}
|
|
346
|
+
# NEW: Bare edition pattern like "100e1" (CS series without year)
|
|
347
|
+
# ONLY when NO second_number present (to avoid conflict with "123e2-50")
|
|
348
|
+
# Creates: number="100", Edition(type: "e", id: "1")
|
|
349
|
+
# Renders: "NBS CS 100e1"
|
|
350
|
+
# CRITICAL: Skip if edition_dash_year is present - let that handler create Edition with additional_text
|
|
351
|
+
elsif str_value =~ /^(\d+)e(\d+)$/ && !parsed_hash[:second_number] && !parsed_hash[:edition_dash_year]
|
|
352
|
+
number_part = $1
|
|
353
|
+
edition_id = $2
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
first_number: Components::Code.new(value: number_part),
|
|
357
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
358
|
+
}
|
|
359
|
+
# NEW: Bare edition pattern "e2" - just edition without number prefix
|
|
360
|
+
# Creates: Edition(type: "e", id: "2")
|
|
361
|
+
# Renders: "NBS CIRC e2"
|
|
362
|
+
# Only matches single or double digit (e1, e2, not e104 which is emergency)
|
|
363
|
+
elsif str_value =~ /^e(\d{1,2})$/
|
|
364
|
+
edition_id = $1
|
|
365
|
+
return {
|
|
366
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
367
|
+
}
|
|
368
|
+
# Pattern: "13e2rev1908" - edition with revision year-only (NO month)
|
|
369
|
+
# Creates: Edition(type: "e", id: "2", additional_text: "1908")
|
|
370
|
+
# Renders: "e2.1908" (DOT separator)
|
|
371
|
+
elsif str_value =~ /^(\d+)e(\d+)rev(\d{4})$/
|
|
372
|
+
# CRITICAL: Capture BEFORE any regex method calls!
|
|
373
|
+
number_part = $1
|
|
374
|
+
edition_id_part = $2
|
|
375
|
+
year_part = $3
|
|
376
|
+
return {
|
|
377
|
+
first_number: Components::Code.new(value: number_part),
|
|
378
|
+
edition: Components::Edition.new(type: "e",
|
|
379
|
+
id: edition_id_part, additional_text: year_part),
|
|
380
|
+
}
|
|
381
|
+
# Pattern: "13e2revJune1908" - edition with revision month+year
|
|
382
|
+
# Creates: Edition(type: "e", id: "2", additional_text: "June1908")
|
|
383
|
+
# Renders: "e2.June1908" (DOT separator)
|
|
384
|
+
elsif str_value =~ /^(\d+)e(\d+)(rev.+)$/
|
|
385
|
+
# CRITICAL: Capture $1, $2, $3 BEFORE calling .sub() which resets them!
|
|
386
|
+
number_part = $1
|
|
387
|
+
edition_id_part = $2
|
|
388
|
+
rev_part = $3
|
|
389
|
+
# Strip "rev" prefix from additional_text - store only "June1908" or "1908"
|
|
390
|
+
additional_text = rev_part.sub(/^rev/, "")
|
|
391
|
+
return {
|
|
392
|
+
first_number: Components::Code.new(value: number_part),
|
|
393
|
+
edition: Components::Edition.new(type: "e",
|
|
394
|
+
id: edition_id_part, additional_text: additional_text),
|
|
395
|
+
}
|
|
396
|
+
# NEW: Pattern "24suppJan1924" - supplement with month and year in first_number
|
|
397
|
+
# Creates: number="24", supplement="Jan1924"
|
|
398
|
+
elsif str_value =~ /^(\d+)supp([A-Za-z]{3,9})(\d{4})$/
|
|
399
|
+
number_part = $1
|
|
400
|
+
month_part = $2
|
|
401
|
+
year_part = $3
|
|
402
|
+
return {
|
|
403
|
+
first_number: Components::Code.new(value: number_part),
|
|
404
|
+
supplement: "#{month_part}#{year_part}",
|
|
405
|
+
}
|
|
406
|
+
# NEW: Pattern "25supp1924" - supplement with year (no dash, no month)
|
|
407
|
+
# Creates: number="25", supplement="1924"
|
|
408
|
+
# Renders: "NBS SP 25supp1924"
|
|
409
|
+
elsif str_value =~ /^(\d+)supp(\d{4})$/
|
|
410
|
+
number_part = $1
|
|
411
|
+
year_part = $2
|
|
412
|
+
return {
|
|
413
|
+
first_number: Components::Code.new(value: number_part),
|
|
414
|
+
supplement: year_part,
|
|
415
|
+
}
|
|
416
|
+
# NEW: Pattern "25supp-1924" - supplement with dash-year (inline match)
|
|
417
|
+
# Creates: number="25", supplement="1924"
|
|
418
|
+
# Renders: "NBS CIRC 25supp-1924"
|
|
419
|
+
elsif str_value =~ /^(\d+)supp-(\d{4})$/
|
|
420
|
+
number_part = $1
|
|
421
|
+
year_part = $2
|
|
422
|
+
return {
|
|
423
|
+
first_number: Components::Code.new(value: number_part),
|
|
424
|
+
supplement: year_part,
|
|
425
|
+
}
|
|
426
|
+
# NEW: Pattern "101e2supp" - edition + supplement
|
|
427
|
+
# Creates: number="101", Edition(type: "e", id: "2"), supplement=""
|
|
428
|
+
# Renders: "NBS CIRC 101e2supp"
|
|
429
|
+
elsif str_value =~ /^(\d+)e(\d+)supp$/
|
|
430
|
+
number_part = $1
|
|
431
|
+
edition_id = $2
|
|
432
|
+
return {
|
|
433
|
+
first_number: Components::Code.new(value: number_part),
|
|
434
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
435
|
+
supplement: "",
|
|
436
|
+
}
|
|
437
|
+
end
|
|
438
|
+
elsif type == :second_number && value.is_a?(Hash) && value[:first_number]
|
|
439
|
+
# Handle second_number as a hash with first_number context
|
|
440
|
+
# e.g., for pattern 800-57pt1r4
|
|
441
|
+
number_part = value[:first_number].to_s
|
|
442
|
+
part_value = value[:part_value]&.to_s
|
|
443
|
+
revision_value = value[:revision_value]&.to_s
|
|
444
|
+
return {
|
|
445
|
+
first_number: Components::Code.new(value: number_part),
|
|
446
|
+
part: Components::Part.new(value: part_value),
|
|
447
|
+
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
448
|
+
}
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Extract revision suffix from number (e.g., "53r5" -> "53" + Edition(r, 5))
|
|
452
|
+
# ENHANCED: Also extract revision with slash-year (e.g., "53r5/1917" -> "53" + Edition)
|
|
453
|
+
# ENHANCED: Also extract revision with 4-digit year (e.g., "1019r1963" -> "1019" + Edition)
|
|
454
|
+
# ENHANCED: Also extract revision with month+year (e.g., "4743rJun1992" -> "4743" + Edition)
|
|
455
|
+
|
|
456
|
+
# NEW: Extract part suffix from number (e.g., "800-57pt1" -> "800-57" + Part(1))
|
|
457
|
+
# This handles SP series part notation
|
|
458
|
+
# IMPORTANT: Handle combined part+revision first (e.g., "800-57pt1r4")
|
|
459
|
+
if str_value =~ /^(.+?)pt(\d+)r(\d+[a-z]?)$/
|
|
460
|
+
number_part = $1
|
|
461
|
+
part_value = $2
|
|
462
|
+
revision_value = $3
|
|
463
|
+
return {
|
|
464
|
+
type => Components::Code.new(value: number_part),
|
|
465
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
466
|
+
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
467
|
+
}
|
|
468
|
+
elsif str_value =~ /^(.+?)pt(\d+)$/
|
|
469
|
+
number_part = $1
|
|
470
|
+
part_value = $2
|
|
471
|
+
return {
|
|
472
|
+
type => Components::Code.new(value: number_part),
|
|
473
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
474
|
+
}
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# NEW: Extract volume suffix from number (e.g., "539v10" -> "539" + volume="10")
|
|
478
|
+
# This handles CIRC volume notation
|
|
479
|
+
if str_value =~ /^(\d+)v(\d+)$/
|
|
480
|
+
number_part = $1
|
|
481
|
+
volume_part = $2
|
|
482
|
+
return {
|
|
483
|
+
type => Components::Code.new(value: number_part),
|
|
484
|
+
volume: volume_part,
|
|
485
|
+
}
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# REVISION PATTERNS - These must come BEFORE letter suffix to avoid conflicts
|
|
489
|
+
case str_value
|
|
490
|
+
when /^(.+?)(r\d+\/\d{4})$/i
|
|
491
|
+
# Pattern: r6/1925 (revision with slash-year)
|
|
492
|
+
number_part = $1
|
|
493
|
+
revision_with_year = $2 # e.g., "r6/1925"
|
|
494
|
+
# Extract revision and year
|
|
495
|
+
if revision_with_year =~ /^r(\d+)\/(\d{4})$/
|
|
496
|
+
revision_id = $1
|
|
497
|
+
year_part = $2
|
|
498
|
+
return {
|
|
499
|
+
type => Components::Code.new(value: number_part),
|
|
500
|
+
edition: Components::Edition.new(type: "r", id: revision_id,
|
|
501
|
+
additional_text: year_part),
|
|
502
|
+
}
|
|
503
|
+
end
|
|
504
|
+
when /^(.*\d)(r\d{4})$/i
|
|
505
|
+
# Pattern: r1963 (revision as 4-digit year)
|
|
506
|
+
number_part = $1
|
|
507
|
+
year_value = $2.sub(/^r/, "") # Strip 'r' prefix
|
|
508
|
+
return {
|
|
509
|
+
type => Components::Code.new(value: number_part),
|
|
510
|
+
edition: Components::Edition.new(type: "r", id: year_value),
|
|
511
|
+
}
|
|
512
|
+
when /^(.+?)(r[A-Za-z]{3,9}\d{4})$/i
|
|
513
|
+
# Pattern: rJun1992 (revision with month and year)
|
|
514
|
+
number_part = $1
|
|
515
|
+
revision_with_date = $2 # e.g., "rJun1992"
|
|
516
|
+
# Extract month and year
|
|
517
|
+
if revision_with_date =~ /^r([A-Za-z]{3,9})(\d{4})$/
|
|
518
|
+
month_part = $1
|
|
519
|
+
year_part = $2
|
|
520
|
+
return {
|
|
521
|
+
type => Components::Code.new(value: number_part),
|
|
522
|
+
edition: Components::Edition.new(type: "r",
|
|
523
|
+
id: "#{month_part}#{year_part}"),
|
|
524
|
+
}
|
|
525
|
+
end
|
|
526
|
+
when /^(.*\d)(r\d+[a-z]?)$/i
|
|
527
|
+
# Pattern: r5, r1a (simple revision)
|
|
528
|
+
number_part = $1
|
|
529
|
+
revision_value = $2.sub(/^r/, "") # Strip 'r' prefix
|
|
530
|
+
return {
|
|
531
|
+
type => Components::Code.new(value: number_part),
|
|
532
|
+
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
533
|
+
}
|
|
534
|
+
when /^(.+?)(?<![a-zA-Z])(r)$/i
|
|
535
|
+
# Pattern: bare r with no digits (e.g., "800-90r")
|
|
536
|
+
# Negative lookbehind ensures r is NOT preceded by a letter (avoids matching Ur, Ua, etc.)
|
|
537
|
+
number_part = $1
|
|
538
|
+
return {
|
|
539
|
+
type => Components::Code.new(value: number_part),
|
|
540
|
+
edition: Components::Edition.new(type: "r", id: "1"),
|
|
541
|
+
}
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# NEW: Extract UPPERCASE letter suffix as Part component (e.g., "800-56A" -> "800-56" + Part)
|
|
545
|
+
# IMPORTANT: These patterns come AFTER revision patterns to avoid conflicts
|
|
546
|
+
# Letter suffixes are UPPERCASE letters A-Z only (no lowercase to avoid revision markers)
|
|
547
|
+
|
|
548
|
+
# Pattern: UPPERCASE letter + revision (e.g., "800-56Ar2" -> number + Part("", "A") + Edition(r, 2))
|
|
549
|
+
# NO /i flag - only match uppercase letters!
|
|
550
|
+
if str_value =~ /^(.+?)([A-Z])(r\d+[a-z]?)$/
|
|
551
|
+
number_part = $1
|
|
552
|
+
letter_part = $2
|
|
553
|
+
revision_part = $3.sub(/^r/, "")
|
|
554
|
+
return {
|
|
555
|
+
type => Components::Code.new(value: number_part),
|
|
556
|
+
part: Components::Part.new(type: "", value: letter_part),
|
|
557
|
+
edition: Components::Edition.new(type: "r", id: revision_part),
|
|
558
|
+
}
|
|
559
|
+
# Pattern: bare UPPERCASE letter suffix (e.g., "800-56A" -> number + Part("", "A"))
|
|
560
|
+
# Only matches uppercase letters - won't match revision markers.
|
|
561
|
+
# Series policy decides whether the letter stays in the number
|
|
562
|
+
# (MR format, RPT/FIPS/IR/CRPL/LC/MONO/MP) or becomes a Part.
|
|
563
|
+
elsif str_value =~ /^(.+?)([A-Z])$/
|
|
564
|
+
number_part = $1
|
|
565
|
+
letter_part = $2
|
|
566
|
+
|
|
567
|
+
if Series.for(parsed_hash).preserve_letter_suffix?(parsed_hash)
|
|
568
|
+
return { type => Components::Code.new(value: str_value) }
|
|
569
|
+
else
|
|
570
|
+
return {
|
|
571
|
+
type => Components::Code.new(value: number_part),
|
|
572
|
+
part: Components::Part.new(type: "", value: letter_part),
|
|
573
|
+
}
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
Components::Code.new(value: str_value)
|
|
578
|
+
|
|
579
|
+
when :crpl_range
|
|
580
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
581
|
+
|
|
582
|
+
# For CRPL range patterns like "2_3-1" or "2_3-1A" (with supplement)
|
|
583
|
+
# Format: X_Y-Z where X,Y,Z are digits, optional trailing letter is supplement
|
|
584
|
+
# This should split into:
|
|
585
|
+
# - X -> second_number (to combine with first_number as "1-X")
|
|
586
|
+
# - Y-Z -> Part component (with type "pt" for CRPL)
|
|
587
|
+
# - trailing letter (if present) -> Supplement
|
|
588
|
+
str_value = value.to_s
|
|
589
|
+
|
|
590
|
+
# Check for supplement letter suffix (e.g., "2_3-1A" -> supplement="A")
|
|
591
|
+
if str_value =~ /^(\d+)_(\d+-\d+)([A-Z])$/
|
|
592
|
+
second_num_part = $1 # "2"
|
|
593
|
+
part_value = $2 # "3-1"
|
|
594
|
+
supplement_letter = $3 # "A"
|
|
595
|
+
|
|
596
|
+
# Return second_number, Part, and Supplement
|
|
597
|
+
{
|
|
598
|
+
second_number: Components::Code.new(value: second_num_part),
|
|
599
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
600
|
+
supplement: supplement_letter,
|
|
601
|
+
}
|
|
602
|
+
elsif str_value =~ /^(\d+)_(\d+-\d+)$/
|
|
603
|
+
# No supplement letter
|
|
604
|
+
second_num_part = $1 # "2"
|
|
605
|
+
part_value = $2 # "3-1"
|
|
606
|
+
|
|
607
|
+
# Return second_number and Part
|
|
608
|
+
{
|
|
609
|
+
second_number: Components::Code.new(value: second_num_part),
|
|
610
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
611
|
+
}
|
|
612
|
+
else
|
|
613
|
+
# Fallback: treat entire value as second_number (shouldn't happen with valid CRPL patterns)
|
|
614
|
+
Components::Code.new(value: str_value)
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# ========== V2 COMPONENT CASTING ==========
|
|
618
|
+
|
|
619
|
+
when :stage
|
|
620
|
+
# Stage from nested hash with id and type
|
|
621
|
+
return nil unless value.is_a?(Hash)
|
|
622
|
+
|
|
623
|
+
stage_id = value[:stage_id]&.to_s&.downcase
|
|
624
|
+
stage_type = value[:stage_type]&.to_s&.downcase
|
|
625
|
+
return nil if stage_id.nil? || stage_type.nil? || stage_id.empty? || stage_type.empty?
|
|
626
|
+
|
|
627
|
+
# Return as hash to set the stage attribute
|
|
628
|
+
{ stage: Components::Stage.new(id: stage_id, type: stage_type) }
|
|
629
|
+
|
|
630
|
+
when :stage_id, :stage_type
|
|
631
|
+
# These are captured by :stage, so skip individual processing
|
|
632
|
+
nil
|
|
633
|
+
|
|
634
|
+
when :parsed_format
|
|
635
|
+
# Format detection result from parser. :short is the render default
|
|
636
|
+
# (a nil parsed_format renders short — see Identifiers::Base#to_s), so
|
|
637
|
+
# store only non-default formats (e.g. "mr"); "short" stays unset and
|
|
638
|
+
# is omitted from to_hash. detect_format only emits :mr or :short.
|
|
639
|
+
v = value&.to_s
|
|
640
|
+
v unless v == "short"
|
|
641
|
+
|
|
642
|
+
when :translation
|
|
643
|
+
# V1 TRANSLATION NORMALIZATION
|
|
644
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
645
|
+
|
|
646
|
+
code = value.to_s.strip.downcase
|
|
647
|
+
# Apply normalization map (es -> spa, pt -> por, etc.)
|
|
648
|
+
normalized_code = TRANSLATION_MAP[code] || code
|
|
649
|
+
|
|
650
|
+
# Return as hash to set translation_component attribute
|
|
651
|
+
{ translation_component: Components::Translation.new(code: normalized_code) }
|
|
652
|
+
|
|
653
|
+
when :version
|
|
654
|
+
# Version component with dotted notation
|
|
655
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
656
|
+
|
|
657
|
+
# Return as hash to set version_component attribute
|
|
658
|
+
{ version_component: Components::Version.new(value: value.to_s) }
|
|
659
|
+
|
|
660
|
+
when :update
|
|
661
|
+
# Update component with number, year, and optional month
|
|
662
|
+
if value.is_a?(Hash)
|
|
663
|
+
# Convert Parslet slice to regular Hash for reliable key access
|
|
664
|
+
value_hash = value.to_h
|
|
665
|
+
|
|
666
|
+
number = value_hash[:update_number]&.to_s # Don't default to "1"
|
|
667
|
+
year = value_hash[:update_year]&.to_s # String not integer
|
|
668
|
+
month = value_hash[:update_month]&.to_s # String not integer
|
|
669
|
+
|
|
670
|
+
# Determine prefix from update_prefix key (captured by parser)
|
|
671
|
+
# If not present, default to "slash" (/Upd format)
|
|
672
|
+
prefix_str = value_hash[:update_prefix]&.to_s
|
|
673
|
+
prefix_value = if prefix_str&.include?("-") || prefix_str == "-upd"
|
|
674
|
+
"dash"
|
|
675
|
+
else
|
|
676
|
+
"slash"
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Create update with at least number
|
|
680
|
+
update_obj = Components::Update.new(number: number, year: year,
|
|
681
|
+
month: month, prefix: prefix_value)
|
|
682
|
+
{
|
|
683
|
+
update: update_obj, # Main attribute for tests
|
|
684
|
+
update_component: update_obj, # V2 component
|
|
685
|
+
}
|
|
686
|
+
elsif value.to_s.strip.empty?
|
|
687
|
+
# Empty update string means "-upd" or "/upd" with no details
|
|
688
|
+
# Create Update with default number="1" (no year/month)
|
|
689
|
+
# Check update_prefix key to determine correct prefix format
|
|
690
|
+
prefix_str = parsed_hash[:update_prefix]&.to_s
|
|
691
|
+
prefix_value = if prefix_str&.include?("-") || prefix_str == "-upd"
|
|
692
|
+
"dash"
|
|
693
|
+
else
|
|
694
|
+
"slash"
|
|
695
|
+
end
|
|
696
|
+
update_obj = Components::Update.new(number: "1", year: nil,
|
|
697
|
+
month: nil, prefix: prefix_value)
|
|
698
|
+
{
|
|
699
|
+
update: update_obj,
|
|
700
|
+
update_component: update_obj,
|
|
701
|
+
}
|
|
702
|
+
else
|
|
703
|
+
# Simple string value - shouldn't reach here
|
|
704
|
+
{ update: value.to_s.strip } unless value.to_s.strip.empty?
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
when :update_prefix, :update_number, :update_year, :update_month
|
|
708
|
+
# Captured as part of :update processing
|
|
709
|
+
nil
|
|
710
|
+
|
|
711
|
+
# ========== END V2 COMPONENTS ==========
|
|
712
|
+
|
|
713
|
+
when :volume, :section, :appendix, :translation,
|
|
714
|
+
:errata, :index, :insert, :version
|
|
715
|
+
return nil if value.nil?
|
|
716
|
+
return nil if value.is_a?(Array) && value.empty?
|
|
717
|
+
|
|
718
|
+
str_value = value.to_s.strip
|
|
719
|
+
return nil if str_value.empty?
|
|
720
|
+
|
|
721
|
+
# For volume, create Volume component from string value
|
|
722
|
+
# This handles patterns like "v1" that come from parser as simple strings
|
|
723
|
+
if type == :volume
|
|
724
|
+
{ volume: Components::Volume.new(value: str_value) }
|
|
725
|
+
else
|
|
726
|
+
str_value
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
when :revision
|
|
730
|
+
# Revision MUST be Edition component with type "r"
|
|
731
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
732
|
+
|
|
733
|
+
# Handle new structure with :revision_prefix and :revision_id (format preservation)
|
|
734
|
+
if value.is_a?(Hash) && value[:revision_prefix] && value[:revision_id]
|
|
735
|
+
prefix = value[:revision_prefix].to_s
|
|
736
|
+
id = value[:revision_id].to_s.strip
|
|
737
|
+
|
|
738
|
+
# Normalize bare "r" -> "r1"
|
|
739
|
+
revision_id = if id.empty? || id == "r" || id == "R"
|
|
740
|
+
"1"
|
|
741
|
+
# Handle "r4", "R5", "4" etc. (but prefix already has the r/rev/etc.)
|
|
742
|
+
elsif id =~ /^(\d+[a-z]?)$/
|
|
743
|
+
$1
|
|
744
|
+
else
|
|
745
|
+
id
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# Return Edition component with original_prefix for format preservation
|
|
749
|
+
{
|
|
750
|
+
edition: Components::Edition.new(type: "r", id: revision_id,
|
|
751
|
+
original_prefix: prefix),
|
|
752
|
+
}
|
|
753
|
+
else
|
|
754
|
+
# Legacy handling: revision as simple string value
|
|
755
|
+
str_value = value.to_s.strip
|
|
756
|
+
|
|
757
|
+
# Handle bare "r" -> normalize to "r1"
|
|
758
|
+
revision_id = if str_value.empty? || str_value == "r" || str_value == "R"
|
|
759
|
+
"1"
|
|
760
|
+
# Handle "r4", "R5", "4" etc.
|
|
761
|
+
elsif str_value =~ /^[rR]?(\d+[a-z]?)$/
|
|
762
|
+
$1
|
|
763
|
+
else
|
|
764
|
+
str_value
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
# Return Edition component (no original_prefix available)
|
|
768
|
+
{
|
|
769
|
+
edition: Components::Edition.new(type: "r", id: revision_id),
|
|
770
|
+
}
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
when :revision_year, :revision_month
|
|
774
|
+
# When revision_year comes from parser as separate element (e.g., "1019 r1963")
|
|
775
|
+
# Create Edition component
|
|
776
|
+
if type == :revision_year
|
|
777
|
+
year_value = value.to_s.strip
|
|
778
|
+
# Check if this should be an Edition component or legacy revision_year
|
|
779
|
+
# If revision_month is also present, use legacy attributes for "revJune1908" pattern
|
|
780
|
+
if parsed_hash[:revision_month]
|
|
781
|
+
# Legacy: revision with month - keep as revision_year/revision_month
|
|
782
|
+
year_value
|
|
783
|
+
else
|
|
784
|
+
# V2: revision with year only - create Edition component
|
|
785
|
+
{
|
|
786
|
+
edition: Components::Edition.new(type: "r", id: year_value),
|
|
787
|
+
}
|
|
788
|
+
end
|
|
789
|
+
else
|
|
790
|
+
# revision_month - preserve as string for legacy rendering
|
|
791
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
792
|
+
|
|
793
|
+
value.to_s.strip
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
when :edition_year_separate
|
|
797
|
+
# NEW: Edition year from "e2-1915" pattern (captured separately by parser)
|
|
798
|
+
# This comes with first_number like "11e2" and separate year "1915"
|
|
799
|
+
# Already handled in first_number regex matching above, but if it reaches here
|
|
800
|
+
# as a separate capture, we need to process it
|
|
801
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
802
|
+
|
|
803
|
+
value.to_s # Return as string for potential use
|
|
804
|
+
|
|
805
|
+
when :historical_month
|
|
806
|
+
# NEW: Historical month from "-April1909" pattern
|
|
807
|
+
# Handled in first_number pattern matching, but return as string if separate
|
|
808
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
809
|
+
|
|
810
|
+
value.to_s
|
|
811
|
+
|
|
812
|
+
when :historical_year
|
|
813
|
+
# NEW: Historical year from "-April1909" pattern
|
|
814
|
+
# Handled in first_number pattern matching, but return as string if separate
|
|
815
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
816
|
+
|
|
817
|
+
value.to_s
|
|
818
|
+
|
|
819
|
+
when :supplement_year
|
|
820
|
+
# NEW: Supplement year from "supp-1924" pattern (captured separately by parser)
|
|
821
|
+
# This comes with first_number like "25supp" and separate year "1924"
|
|
822
|
+
# Already handled in first_number regex matching above, but if it reaches here
|
|
823
|
+
# as a separate capture, return as supplement value
|
|
824
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
825
|
+
|
|
826
|
+
{ supplement: value.to_s } # Return as supplement attribute
|
|
827
|
+
|
|
828
|
+
when :supplement
|
|
829
|
+
handle_supplement_cast(value)
|
|
830
|
+
|
|
831
|
+
when :supplement_date_range
|
|
832
|
+
return nil unless value.is_a?(Hash)
|
|
833
|
+
|
|
834
|
+
month_start = value[:supp_month_start]&.to_s
|
|
835
|
+
year_start = value[:supp_year_start]&.to_s
|
|
836
|
+
month_end = value[:supp_month_end]&.to_s
|
|
837
|
+
year_end = value[:supp_year_end]&.to_s
|
|
838
|
+
|
|
839
|
+
{
|
|
840
|
+
supplement_date_range_start: (month_start && year_start ? "#{month_start}#{year_start}" : nil),
|
|
841
|
+
supplement_date_range_end: (month_end && year_end ? "#{month_end}#{year_end}" : nil),
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
when :supplement_date
|
|
845
|
+
return nil unless value.is_a?(Hash)
|
|
846
|
+
|
|
847
|
+
month = value[:supp_month]&.to_s
|
|
848
|
+
year = value[:supp_year]&.to_s
|
|
849
|
+
|
|
850
|
+
month && year ? "#{month}#{year}" : nil
|
|
851
|
+
|
|
852
|
+
when :supplement_slash_year
|
|
853
|
+
return nil unless value.is_a?(Hash)
|
|
854
|
+
|
|
855
|
+
number = value[:supp_number]&.to_s
|
|
856
|
+
year = value[:supp_year]&.to_s
|
|
857
|
+
|
|
858
|
+
number && year ? "#{number}/#{year}" : nil
|
|
859
|
+
|
|
860
|
+
when :supplement_with_rev
|
|
861
|
+
{ supplement: "", supplement_has_revision: true }
|
|
862
|
+
|
|
863
|
+
when :supp_year
|
|
864
|
+
# Parser extracts supplement year from patterns like "187supp1924"
|
|
865
|
+
# This should set the supplement attribute with the year value
|
|
866
|
+
{ supplement: value.to_s }
|
|
867
|
+
|
|
868
|
+
# ========== V2 EDITION COMPONENT ==========
|
|
869
|
+
|
|
870
|
+
when :edition_e_date
|
|
871
|
+
# Edition with "e" prefix + 6-digit date (YYYYMM): e199206, e202103
|
|
872
|
+
# Used for IR revision+month patterns after preprocessing: "4743rJun1992" -> "4743e199206"
|
|
873
|
+
return nil unless value.is_a?(Hash) && value[:edition_date]
|
|
874
|
+
|
|
875
|
+
edition_date = value[:edition_date].to_s
|
|
876
|
+
# Parse 6-digit date as YYYYMM
|
|
877
|
+
# Store as id directly - renders as "e199206"
|
|
878
|
+
{
|
|
879
|
+
edition: Components::Edition.new(type: "e", id: edition_date),
|
|
880
|
+
edition_component: Components::Edition.new(type: "e",
|
|
881
|
+
id: edition_date),
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
when :edition_e
|
|
885
|
+
# Edition with "e" prefix: e2, e2021
|
|
886
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
887
|
+
|
|
888
|
+
edition_id = value[:edition_id].to_s
|
|
889
|
+
|
|
890
|
+
{
|
|
891
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
892
|
+
edition_component: Components::Edition.new(type: "e",
|
|
893
|
+
id: edition_id),
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
when :edition_r
|
|
897
|
+
# Revision with "r" prefix: r5, r2021
|
|
898
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
899
|
+
|
|
900
|
+
edition_id = value[:edition_id].to_s
|
|
901
|
+
|
|
902
|
+
{
|
|
903
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
904
|
+
edition_component: Components::Edition.new(type: "r",
|
|
905
|
+
id: edition_id),
|
|
906
|
+
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
when :edition_r_no_space
|
|
910
|
+
# Revision with "r" prefix (no space pattern): r2, r5
|
|
911
|
+
# Used for patterns like "800-56Ar2" where edition is "r2"
|
|
912
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
913
|
+
|
|
914
|
+
edition_id = value[:edition_id].to_s
|
|
915
|
+
|
|
916
|
+
{
|
|
917
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
918
|
+
edition_component: Components::Edition.new(type: "r",
|
|
919
|
+
id: edition_id),
|
|
920
|
+
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
when :edition_rev
|
|
924
|
+
# Revision with "rev" prefix (verbose): rev2013, rev 2013
|
|
925
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
926
|
+
|
|
927
|
+
edition_id = value[:edition_id].to_s
|
|
928
|
+
|
|
929
|
+
{
|
|
930
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
931
|
+
edition_component: Components::Edition.new(type: "r",
|
|
932
|
+
id: edition_id),
|
|
933
|
+
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
when :edition_r_letter
|
|
937
|
+
# Revision with "r" prefix and letter suffix: r1a, r2b (for SP patterns like 800-22r1a)
|
|
938
|
+
return nil unless value.is_a?(Hash) && value[:edition_id] && value[:edition_letter]
|
|
939
|
+
|
|
940
|
+
edition_id = value[:edition_id].to_s
|
|
941
|
+
edition_letter = value[:edition_letter].to_s.downcase
|
|
942
|
+
|
|
943
|
+
{
|
|
944
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
945
|
+
additional_text: edition_letter),
|
|
946
|
+
edition_component: Components::Edition.new(type: "r",
|
|
947
|
+
id: edition_id,
|
|
948
|
+
additional_text: edition_letter),
|
|
949
|
+
revision: "r#{edition_id}#{edition_letter}", # Also set revision string attribute for compatibility
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
when :edition_r_letter_only
|
|
953
|
+
# Revision with "r" prefix and only letter (no digit): ra, rb (for SP patterns like 800-27ra)
|
|
954
|
+
return nil unless value.is_a?(Hash) && value[:edition_letter]
|
|
955
|
+
|
|
956
|
+
edition_letter = value[:edition_letter].to_s.downcase
|
|
957
|
+
|
|
958
|
+
{
|
|
959
|
+
edition: Components::Edition.new(type: "r", id: edition_letter),
|
|
960
|
+
edition_component: Components::Edition.new(type: "r",
|
|
961
|
+
id: edition_letter),
|
|
962
|
+
revision: "r#{edition_letter}", # Also set revision string attribute for compatibility
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
when :edition_historical
|
|
966
|
+
# Historical with "-" prefix: -3, -4
|
|
967
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
968
|
+
|
|
969
|
+
edition_id = value[:edition_id].to_s
|
|
970
|
+
|
|
971
|
+
{
|
|
972
|
+
edition: Components::Edition.new(type: "-", id: edition_id),
|
|
973
|
+
edition_component: Components::Edition.new(type: "-",
|
|
974
|
+
id: edition_id),
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
when :edition_r_with_space_letter
|
|
978
|
+
# Revision with "r" prefix, space, and letter: r 5A (format preservation)
|
|
979
|
+
# Used for patterns like "NIST SP 800-53 r5A"
|
|
980
|
+
# NOTE: If there's an update component, the space was added by preprocessing
|
|
981
|
+
return nil unless value.is_a?(Hash) && value[:edition_id] && value[:edition_letter]
|
|
982
|
+
|
|
983
|
+
edition_id = value[:edition_id].to_s
|
|
984
|
+
edition_letter = value[:edition_letter].to_s.upcase
|
|
985
|
+
|
|
986
|
+
# Check if this is an embedded edition with update (space added by preprocessing)
|
|
987
|
+
has_update = parsed_hash[:update_prefix] || parsed_hash[:update]
|
|
988
|
+
|
|
989
|
+
if has_update
|
|
990
|
+
# No original_prefix - space was added by preprocessing
|
|
991
|
+
{
|
|
992
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
993
|
+
additional_text: edition_letter),
|
|
994
|
+
edition_component: Components::Edition.new(type: "r",
|
|
995
|
+
id: edition_id,
|
|
996
|
+
additional_text: edition_letter),
|
|
997
|
+
revision: "r#{edition_id}#{edition_letter}",
|
|
998
|
+
}
|
|
999
|
+
else
|
|
1000
|
+
# Space was in original input - preserve format
|
|
1001
|
+
{
|
|
1002
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1003
|
+
additional_text: edition_letter,
|
|
1004
|
+
original_prefix: " r"),
|
|
1005
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1006
|
+
id: edition_id,
|
|
1007
|
+
additional_text: edition_letter,
|
|
1008
|
+
original_prefix: " r"),
|
|
1009
|
+
revision: "r#{edition_id}#{edition_letter}",
|
|
1010
|
+
}
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
when :edition_r_with_space
|
|
1014
|
+
# Revision with "r" prefix and space: r 5 (format preservation)
|
|
1015
|
+
# Used for patterns like "NIST SP 800-53 r5"
|
|
1016
|
+
# NOTE: If there's an update component, the space was added by preprocessing
|
|
1017
|
+
# for patterns like "8115r1/upd" -> "8115 r1/upd", so don't set original_prefix
|
|
1018
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1019
|
+
|
|
1020
|
+
edition_id = value[:edition_id].to_s
|
|
1021
|
+
|
|
1022
|
+
# Check if this is an embedded edition with update (space added by preprocessing)
|
|
1023
|
+
# Patterns like "8115r1/upd" become "8115 r1/upd" after preprocessing
|
|
1024
|
+
has_update = parsed_hash[:update_prefix] || parsed_hash[:update]
|
|
1025
|
+
|
|
1026
|
+
if has_update
|
|
1027
|
+
# No original_prefix - space was added by preprocessing
|
|
1028
|
+
{
|
|
1029
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1030
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1031
|
+
id: edition_id),
|
|
1032
|
+
revision: "r#{edition_id}",
|
|
1033
|
+
}
|
|
1034
|
+
else
|
|
1035
|
+
# Space was in original input - preserve format
|
|
1036
|
+
{
|
|
1037
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1038
|
+
original_prefix: " r"),
|
|
1039
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1040
|
+
id: edition_id,
|
|
1041
|
+
original_prefix: " r"),
|
|
1042
|
+
revision: "r#{edition_id}",
|
|
1043
|
+
}
|
|
1044
|
+
end
|
|
1045
|
+
|
|
1046
|
+
when :edition_id
|
|
1047
|
+
# Captured by edition_e, edition_r, edition_rev, edition_historical
|
|
1048
|
+
nil
|
|
1049
|
+
|
|
1050
|
+
when :edition_date
|
|
1051
|
+
# Captured by edition_e_date
|
|
1052
|
+
nil
|
|
1053
|
+
|
|
1054
|
+
# ========== LEGACY EDITION (for migration) ==========
|
|
1055
|
+
|
|
1056
|
+
when :legacy_edition
|
|
1057
|
+
# Legacy edition patterns - will be phased out
|
|
1058
|
+
# For now, map to old edition_year/edition_month attributes
|
|
1059
|
+
nil # Handled by existing edition_year logic below
|
|
1060
|
+
|
|
1061
|
+
when :edition_month, :edition_year, :edition_day, :edition_has_rev
|
|
1062
|
+
# These work together: edition_month + edition_year -> single edition ID
|
|
1063
|
+
# Skip processing if this is edition_month alone (will be processed with edition_year)
|
|
1064
|
+
return nil if type == :edition_month
|
|
1065
|
+
|
|
1066
|
+
# Process edition_year, combining with edition_month if present
|
|
1067
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1068
|
+
|
|
1069
|
+
# Build the edition ID from year and optional month
|
|
1070
|
+
edition_id = value.to_s # Start with year (e.g., "1985")
|
|
1071
|
+
|
|
1072
|
+
# Add month if present (e.g., "Mar" -> "03", so "1985" + "03" = "198503")
|
|
1073
|
+
# For FIPS with day: "Sep30/1977" -> "19770930" (year + month + day)
|
|
1074
|
+
if parsed_hash[:edition_month]
|
|
1075
|
+
month_str = parsed_hash[:edition_month].to_s
|
|
1076
|
+
month_num = Date::ABBR_MONTHNAMES.index(month_str) ||
|
|
1077
|
+
Date::MONTHNAMES.index(month_str) ||
|
|
1078
|
+
month_str.to_i
|
|
1079
|
+
if month_num&.positive?
|
|
1080
|
+
# FIPS uses modern (numeric) date format; historical NBS keeps
|
|
1081
|
+
# month names like "April1909" instead of "190904".
|
|
1082
|
+
modern = Series.for(parsed_hash).modern_edition_date?
|
|
1083
|
+
if !modern && month_str.match?(/^[A-Z][a-z]+/) && edition_id.to_s.match?(/^\d{4}$/)
|
|
1084
|
+
# Historical NBS month+year format: preserve month name, use "-" type for special rendering
|
|
1085
|
+
edition_obj = Components::Edition.new(
|
|
1086
|
+
type: "-",
|
|
1087
|
+
id: "",
|
|
1088
|
+
additional_text: "#{month_str}#{edition_id}",
|
|
1089
|
+
)
|
|
1090
|
+
return {
|
|
1091
|
+
edition: edition_obj,
|
|
1092
|
+
edition_component: edition_obj,
|
|
1093
|
+
edition_year: edition_id.to_s,
|
|
1094
|
+
}
|
|
1095
|
+
else
|
|
1096
|
+
# Modern format (and FIPS): combine year and month as single number: 1985 + 03 = 198503
|
|
1097
|
+
edition_id = "#{edition_id}#{format('%02d', month_num)}"
|
|
1098
|
+
|
|
1099
|
+
# For FIPS with day, append day as well: "Sep30/1977" -> "19770930"
|
|
1100
|
+
if modern && parsed_hash[:edition_day]
|
|
1101
|
+
day_num = parsed_hash[:edition_day].to_s.to_i
|
|
1102
|
+
if day_num.positive? && day_num <= 31
|
|
1103
|
+
edition_id = "#{edition_id}#{format('%02d', day_num)}"
|
|
1104
|
+
end
|
|
1105
|
+
end
|
|
1106
|
+
end
|
|
1107
|
+
end
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
# Create Edition component with type="e" (edition) and combined ID
|
|
1111
|
+
edition_obj = Components::Edition.new(type: "e", id: edition_id)
|
|
1112
|
+
|
|
1113
|
+
# Return as hash to set edition and edition_year
|
|
1114
|
+
{
|
|
1115
|
+
edition: edition_obj, # Main attribute for tests
|
|
1116
|
+
edition_component: edition_obj, # V2 component
|
|
1117
|
+
edition_year: value.to_s, # Keep string for render logic
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
when :part
|
|
1121
|
+
# Part component - handle part number with optional addendum
|
|
1122
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1123
|
+
|
|
1124
|
+
str_value = value.to_s.strip
|
|
1125
|
+
|
|
1126
|
+
# Pattern: "1adde1" -> Part(value: "1"), addendum=true
|
|
1127
|
+
# Note: eN after add is discarded (not included in output per fixture)
|
|
1128
|
+
if str_value =~ /^(\d+)add(e\d+)$/
|
|
1129
|
+
{
|
|
1130
|
+
part: Components::Part.new(type: "pt", value: $1),
|
|
1131
|
+
addendum: "true",
|
|
1132
|
+
}
|
|
1133
|
+
elsif str_value =~ /^(\d+)add/
|
|
1134
|
+
{
|
|
1135
|
+
part: Components::Part.new(type: "pt", value: $1),
|
|
1136
|
+
addendum: "true",
|
|
1137
|
+
}
|
|
1138
|
+
else
|
|
1139
|
+
# Just a part number - return Part component with pt type
|
|
1140
|
+
{ part: Components::Part.new(type: "pt", value: str_value) }
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
when :part_extracted
|
|
1144
|
+
# Legacy - this is now handled by :part
|
|
1145
|
+
nil
|
|
1146
|
+
|
|
1147
|
+
when :edition_letter
|
|
1148
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1149
|
+
|
|
1150
|
+
value.to_s
|
|
1151
|
+
|
|
1152
|
+
when :public_draft
|
|
1153
|
+
return nil if value.nil?
|
|
1154
|
+
|
|
1155
|
+
value.to_s
|
|
1156
|
+
|
|
1157
|
+
when :draft
|
|
1158
|
+
# Extract draft number from "-draft N" pattern for pd rendering
|
|
1159
|
+
return nil if value.nil?
|
|
1160
|
+
|
|
1161
|
+
str_value = value.to_s.strip
|
|
1162
|
+
return nil if str_value.empty?
|
|
1163
|
+
|
|
1164
|
+
# Pattern: " -draft 2" or "-draft 2" -> extract "2" for pd rendering
|
|
1165
|
+
if str_value =~ /^\s*-draft\s+(\d+)$/
|
|
1166
|
+
{ draft_number: $1 }
|
|
1167
|
+
# Pattern: " 2pd" -> already in pd format
|
|
1168
|
+
elsif str_value =~ /^\s*(\d+)pd$/
|
|
1169
|
+
{ public_draft: $1 }
|
|
1170
|
+
# Other patterns (parenthetical, simple -draft)
|
|
1171
|
+
else
|
|
1172
|
+
str_value
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
when :addendum
|
|
1176
|
+
handle_addendum_cast(value)
|
|
1177
|
+
|
|
1178
|
+
when :addendum_number
|
|
1179
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1180
|
+
|
|
1181
|
+
value.to_s
|
|
1182
|
+
|
|
1183
|
+
when :supplement_suffix
|
|
1184
|
+
# Return as hash to set supplement attribute (not supplement_suffix)
|
|
1185
|
+
{ supplement: value.to_s }
|
|
1186
|
+
|
|
1187
|
+
when :date
|
|
1188
|
+
# Date component per NIST spec
|
|
1189
|
+
return nil unless value.is_a?(Hash)
|
|
1190
|
+
|
|
1191
|
+
# NEW: Check if this is historical edition pattern ("-April1909")
|
|
1192
|
+
# Parser captures as date with month + year, but semantically it's an edition
|
|
1193
|
+
if value[:date_month] && value[:date_year] && !value[:date_day]
|
|
1194
|
+
month_str = value[:date_month].to_s
|
|
1195
|
+
year_str = value[:date_year].to_s
|
|
1196
|
+
# If month is a word like "April", this is historical edition format
|
|
1197
|
+
if month_str.match?(/^[A-Za-z]+$/)
|
|
1198
|
+
return {
|
|
1199
|
+
edition: Components::Edition.new(type: "-",
|
|
1200
|
+
additional_text: "#{month_str}#{year_str}"),
|
|
1201
|
+
}
|
|
1202
|
+
end
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1205
|
+
# Regular date processing
|
|
1206
|
+
value[:date_year]&.to_s
|
|
1207
|
+
value[:date_month]&.to_s
|
|
1208
|
+
value[:date_day]&.to_s
|
|
1209
|
+
|
|
1210
|
+
else
|
|
1211
|
+
# Unknown types: return the original value for default processing
|
|
1212
|
+
# This allows hashes with arbitrary structures to be processed
|
|
1213
|
+
# e.g., second_number hash with number_only and edition_id
|
|
1214
|
+
value.is_a?(Hash) ? value : nil
|
|
1215
|
+
end
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
# Convert month name to month number
|
|
1219
|
+
# @param month_name [String] month abbreviation (Jan, Feb, Mar, etc.)
|
|
1220
|
+
# @return [Integer] month number (1-12)
|
|
1221
|
+
def month_name_to_number(month_name)
|
|
1222
|
+
month_map = {
|
|
1223
|
+
"Jan" => 1, "January" => 1,
|
|
1224
|
+
"Feb" => 2, "February" => 2,
|
|
1225
|
+
"Mar" => 3, "March" => 3,
|
|
1226
|
+
"Apr" => 4, "April" => 4,
|
|
1227
|
+
"May" => 5,
|
|
1228
|
+
"Jun" => 6, "June" => 6,
|
|
1229
|
+
"Jul" => 7, "July" => 7,
|
|
1230
|
+
"Aug" => 8, "August" => 8,
|
|
1231
|
+
"Sep" => 9, "Sept" => 9, "September" => 9,
|
|
1232
|
+
"Oct" => 10, "October" => 10,
|
|
1233
|
+
"Nov" => 11, "November" => 11,
|
|
1234
|
+
"Dec" => 12, "December" => 12,
|
|
1235
|
+
}
|
|
1236
|
+
month_map[month_name] || 1 # Default to January if not found
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
# Handle supplement casting with all its variants
|
|
1240
|
+
def handle_supplement_cast(value)
|
|
1241
|
+
return nil unless value
|
|
1242
|
+
|
|
1243
|
+
if value.is_a?(Array) && value.empty?
|
|
1244
|
+
# Empty array means "supp" was present but no suffix
|
|
1245
|
+
""
|
|
1246
|
+
else
|
|
1247
|
+
str_value = value.to_s.strip
|
|
1248
|
+
str_value.empty? ? nil : str_value
|
|
1249
|
+
end
|
|
1250
|
+
end
|
|
1251
|
+
|
|
1252
|
+
# Handle addendum casting (number)
|
|
1253
|
+
def handle_addendum_cast(value)
|
|
1254
|
+
if value.is_a?(Hash)
|
|
1255
|
+
addendum_num = value[:addendum_number]&.to_s&.strip
|
|
1256
|
+
if addendum_num && !addendum_num.empty?
|
|
1257
|
+
{ addendum_number: addendum_num }
|
|
1258
|
+
else
|
|
1259
|
+
{ addendum: "true" }
|
|
1260
|
+
end
|
|
1261
|
+
else
|
|
1262
|
+
str_value = value.to_s.strip
|
|
1263
|
+
if str_value.empty?
|
|
1264
|
+
{ addendum: "true" }
|
|
1265
|
+
else
|
|
1266
|
+
{ addendum_number: str_value }
|
|
1267
|
+
end
|
|
1268
|
+
end
|
|
1269
|
+
end
|
|
1270
|
+
end
|
|
1271
|
+
end
|
|
1272
|
+
end
|