pubid 1.15.19 → 2.0.0.pre.alpha.1
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/LICENSE.txt +1 -1
- data/README.adoc +2041 -53
- data/archived-gems/pubid-ccsds/update_codes.yaml +1 -0
- data/archived-gems/pubid-iec/stages.yaml +129 -0
- data/archived-gems/pubid-iec/update_codes.yaml +67 -0
- data/archived-gems/pubid-ieee/update_codes.yaml +104 -0
- data/archived-gems/pubid-iso/stages.yaml +106 -0
- data/archived-gems/pubid-iso/update_codes.yaml +4 -0
- data/archived-gems/pubid-itu/i18n.yaml +13 -0
- data/archived-gems/pubid-itu/series.yaml +42 -0
- data/archived-gems/pubid-nist/publishers.yaml +6 -0
- data/archived-gems/pubid-nist/series.yaml +121 -0
- data/archived-gems/pubid-nist/stages.yaml +16 -0
- data/archived-gems/pubid-nist/update_codes.yaml +93 -0
- data/archived-gems/pubid-plateau/update_codes.yaml +6 -0
- data/data/ccsds/update_codes.yaml +1 -0
- data/data/iec/update_codes.yaml +67 -0
- data/data/ieee/update_codes.yaml +104 -0
- data/data/iso/update_codes.yaml +21 -0
- data/data/nist/update_codes.yaml +87 -0
- data/data/plateau/update_codes.yaml +6 -0
- data/lib/pubid/amca/builder.rb +176 -0
- data/lib/pubid/amca/identifier.rb +18 -0
- data/lib/pubid/amca/identifiers/base.rb +64 -0
- data/lib/pubid/amca/identifiers/interpretation.rb +51 -0
- data/lib/pubid/amca/identifiers/publication.rb +47 -0
- data/lib/pubid/amca/identifiers/standard.rb +22 -0
- data/lib/pubid/amca/identifiers.rb +12 -0
- data/lib/pubid/amca/parser.rb +153 -0
- data/lib/pubid/amca/scheme.rb +16 -0
- data/lib/pubid/amca/single_identifier.rb +33 -0
- data/lib/pubid/amca/urn_generator.rb +50 -0
- data/lib/pubid/amca.rb +26 -0
- data/lib/pubid/ansi/builder.rb +52 -0
- data/lib/pubid/ansi/identifier.rb +13 -0
- data/lib/pubid/ansi/identifiers/american_national_standard.rb +12 -0
- data/lib/pubid/ansi/identifiers/standard.rb +16 -0
- data/lib/pubid/ansi/identifiers.rb +11 -0
- data/lib/pubid/ansi/parser.rb +91 -0
- data/lib/pubid/ansi/scheme.rb +15 -0
- data/lib/pubid/ansi/single_identifier.rb +45 -0
- data/lib/pubid/ansi/urn_generator.rb +76 -0
- data/lib/pubid/ansi.rb +27 -0
- data/lib/pubid/api/builder.rb +85 -0
- data/lib/pubid/api/components/code.rb +9 -0
- data/lib/pubid/api/identifier.rb +21 -0
- data/lib/pubid/api/identifiers/base.rb +24 -0
- data/lib/pubid/api/identifiers/bulletin.rb +15 -0
- data/lib/pubid/api/identifiers/continuous_operations_standard.rb +15 -0
- data/lib/pubid/api/identifiers/mpms.rb +44 -0
- data/lib/pubid/api/identifiers/publication.rb +15 -0
- data/lib/pubid/api/identifiers/recommended_practice.rb +15 -0
- data/lib/pubid/api/identifiers/specification.rb +15 -0
- data/lib/pubid/api/identifiers/standard.rb +15 -0
- data/lib/pubid/api/identifiers/technical_report.rb +15 -0
- data/lib/pubid/api/identifiers/typeless_standard.rb +27 -0
- data/lib/pubid/api/parser.rb +140 -0
- data/lib/pubid/api/scheme.rb +66 -0
- data/lib/pubid/api/single_identifier.rb +46 -0
- data/lib/pubid/api/urn_generator.rb +41 -0
- data/lib/pubid/api.rb +17 -0
- data/lib/pubid/ashrae/builder.rb +498 -0
- data/lib/pubid/ashrae/identifier.rb +18 -0
- data/lib/pubid/ashrae/identifiers/addenda_package.rb +46 -0
- data/lib/pubid/ashrae/identifiers/addendum.rb +55 -0
- data/lib/pubid/ashrae/identifiers/base.rb +23 -0
- data/lib/pubid/ashrae/identifiers/combined_addenda.rb +51 -0
- data/lib/pubid/ashrae/identifiers/errata.rb +40 -0
- data/lib/pubid/ashrae/identifiers/guideline.rb +38 -0
- data/lib/pubid/ashrae/identifiers/interpretation.rb +39 -0
- data/lib/pubid/ashrae/identifiers/standard.rb +38 -0
- data/lib/pubid/ashrae/identifiers.rb +16 -0
- data/lib/pubid/ashrae/parser.rb +724 -0
- data/lib/pubid/ashrae/scheme.rb +53 -0
- data/lib/pubid/ashrae/single_identifier.rb +23 -0
- data/lib/pubid/ashrae/supplement_identifier.rb +23 -0
- data/lib/pubid/ashrae/urn_generator.rb +59 -0
- data/lib/pubid/ashrae.rb +21 -0
- data/lib/pubid/asme/builder.rb +153 -0
- data/lib/pubid/asme/components/code.rb +18 -0
- data/lib/pubid/asme/identifier.rb +15 -0
- data/lib/pubid/asme/identifiers/base.rb +70 -0
- data/lib/pubid/asme/identifiers/standard.rb +12 -0
- data/lib/pubid/asme/identifiers.rb +10 -0
- data/lib/pubid/asme/parser.rb +308 -0
- data/lib/pubid/asme/scheme.rb +37 -0
- data/lib/pubid/asme/single_identifier.rb +29 -0
- data/lib/pubid/asme/urn_generator.rb +133 -0
- data/lib/pubid/asme.rb +21 -0
- data/lib/pubid/astm/builder.rb +159 -0
- data/lib/pubid/astm/components/code.rb +33 -0
- data/lib/pubid/astm/identifier.rb +15 -0
- data/lib/pubid/astm/identifiers/adjunct.rb +21 -0
- data/lib/pubid/astm/identifiers/base.rb +13 -0
- data/lib/pubid/astm/identifiers/data_series.rb +25 -0
- data/lib/pubid/astm/identifiers/iso_dual_published.rb +74 -0
- data/lib/pubid/astm/identifiers/manual.rb +40 -0
- data/lib/pubid/astm/identifiers/monograph.rb +25 -0
- data/lib/pubid/astm/identifiers/research_report.rb +18 -0
- data/lib/pubid/astm/identifiers/standard.rb +52 -0
- data/lib/pubid/astm/identifiers/technical_report.rb +23 -0
- data/lib/pubid/astm/identifiers/work_in_progress.rb +21 -0
- data/lib/pubid/astm/parser.rb +244 -0
- data/lib/pubid/astm/scheme.rb +55 -0
- data/lib/pubid/astm/single_identifier.rb +25 -0
- data/lib/pubid/astm/urn_generator.rb +99 -0
- data/lib/pubid/astm.rb +38 -0
- data/lib/pubid/bsi/builder.rb +1483 -0
- data/lib/pubid/bsi/components/code.rb +11 -0
- data/lib/pubid/bsi/components/date.rb +11 -0
- data/lib/pubid/bsi/components/publisher.rb +11 -0
- data/lib/pubid/bsi/components/type.rb +11 -0
- data/lib/pubid/bsi/identifier.rb +27 -0
- data/lib/pubid/bsi/identifiers/addendum_document.rb +64 -0
- data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +95 -0
- data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +82 -0
- data/lib/pubid/bsi/identifiers/aerospace_standard.rb +118 -0
- data/lib/pubid/bsi/identifiers/amendment.rb +40 -0
- data/lib/pubid/bsi/identifiers/base.rb +11 -0
- data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +27 -0
- data/lib/pubid/bsi/identifiers/british_standard.rb +33 -0
- data/lib/pubid/bsi/identifiers/bundled_identifier.rb +114 -0
- data/lib/pubid/bsi/identifiers/committee_document.rb +51 -0
- data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +152 -0
- data/lib/pubid/bsi/identifiers/corrigendum.rb +28 -0
- data/lib/pubid/bsi/identifiers/detailed_specification.rb +69 -0
- data/lib/pubid/bsi/identifiers/disc.rb +56 -0
- data/lib/pubid/bsi/identifiers/draft_document.rb +71 -0
- data/lib/pubid/bsi/identifiers/electronic_book.rb +52 -0
- data/lib/pubid/bsi/identifiers/expert_commentary.rb +47 -0
- data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +82 -0
- data/lib/pubid/bsi/identifiers/flex.rb +61 -0
- data/lib/pubid/bsi/identifiers/handbook.rb +39 -0
- data/lib/pubid/bsi/identifiers/index.rb +62 -0
- data/lib/pubid/bsi/identifiers/method.rb +76 -0
- data/lib/pubid/bsi/identifiers/national_annex.rb +73 -0
- data/lib/pubid/bsi/identifiers/practice_guide.rb +27 -0
- data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +79 -0
- data/lib/pubid/bsi/identifiers/published_document.rb +79 -0
- data/lib/pubid/bsi/identifiers/section.rb +62 -0
- data/lib/pubid/bsi/identifiers/set.rb +46 -0
- data/lib/pubid/bsi/identifiers/standalone_amendment.rb +40 -0
- data/lib/pubid/bsi/identifiers/supplement_document.rb +51 -0
- data/lib/pubid/bsi/identifiers/supplementary_index.rb +81 -0
- data/lib/pubid/bsi/identifiers/technical_specification.rb +79 -0
- data/lib/pubid/bsi/identifiers/test_method.rb +67 -0
- data/lib/pubid/bsi/identifiers/value_added_publication.rb +52 -0
- data/lib/pubid/bsi/identifiers.rb +52 -0
- data/lib/pubid/bsi/model.rb +196 -0
- data/lib/pubid/bsi/parser.rb +659 -0
- data/lib/pubid/bsi/scheme.rb +243 -0
- data/lib/pubid/bsi/single_identifier.rb +129 -0
- data/lib/pubid/bsi/urn_generator.rb +84 -0
- data/lib/pubid/bsi.rb +32 -0
- data/lib/pubid/builder/base.rb +138 -0
- data/lib/pubid/bundled_identifier.rb +126 -0
- data/lib/pubid/ccsds/builder.rb +56 -0
- data/lib/pubid/ccsds/identifier.rb +16 -0
- data/lib/pubid/ccsds/identifiers/base.rb +78 -0
- data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +70 -0
- data/lib/pubid/ccsds/identifiers/corrigendum.rb +39 -0
- data/lib/pubid/ccsds/identifiers.rb +10 -0
- data/lib/pubid/ccsds/parser.rb +71 -0
- data/lib/pubid/ccsds/scheme.rb +57 -0
- data/lib/pubid/ccsds/single_identifier.rb +74 -0
- data/lib/pubid/ccsds/supplement_identifier.rb +33 -0
- data/lib/pubid/ccsds/urn_generator.rb +115 -0
- data/lib/pubid/ccsds.rb +21 -0
- data/lib/pubid/cen_cenelec/builder.rb +330 -0
- data/lib/pubid/cen_cenelec/identifier.rb +15 -0
- data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +40 -0
- data/lib/pubid/cen_cenelec/identifiers/amendment.rb +29 -0
- data/lib/pubid/cen_cenelec/identifiers/base.rb +75 -0
- data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +28 -0
- data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +27 -0
- data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +28 -0
- data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +61 -0
- data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +35 -0
- data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +41 -0
- data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +37 -0
- data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +28 -0
- data/lib/pubid/cen_cenelec/identifiers/fragment.rb +22 -0
- data/lib/pubid/cen_cenelec/identifiers/guide.rb +27 -0
- data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +27 -0
- data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +27 -0
- data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +35 -0
- data/lib/pubid/cen_cenelec/identifiers.rb +32 -0
- data/lib/pubid/cen_cenelec/parser.rb +144 -0
- data/lib/pubid/cen_cenelec/scheme.rb +164 -0
- data/lib/pubid/cen_cenelec/single_identifier.rb +130 -0
- data/lib/pubid/cen_cenelec/supplement_identifier.rb +48 -0
- data/lib/pubid/cen_cenelec/urn_generator.rb +129 -0
- data/lib/pubid/cen_cenelec.rb +21 -0
- data/lib/pubid/cie/builder.rb +399 -0
- data/lib/pubid/cie/components/code.rb +72 -0
- data/lib/pubid/cie/components/language.rb +58 -0
- data/lib/pubid/cie/identifier.rb +18 -0
- data/lib/pubid/cie/identifiers/bundle.rb +20 -0
- data/lib/pubid/cie/identifiers/conference.rb +32 -0
- data/lib/pubid/cie/identifiers/corrigendum.rb +40 -0
- data/lib/pubid/cie/identifiers/dual_published.rb +41 -0
- data/lib/pubid/cie/identifiers/identical.rb +64 -0
- data/lib/pubid/cie/identifiers/joint_published.rb +52 -0
- data/lib/pubid/cie/identifiers/standard.rb +58 -0
- data/lib/pubid/cie/identifiers/supplement.rb +45 -0
- data/lib/pubid/cie/identifiers/tutorial_bundle.rb +20 -0
- data/lib/pubid/cie/identifiers.rb +17 -0
- data/lib/pubid/cie/parser.rb +347 -0
- data/lib/pubid/cie/scheme.rb +64 -0
- data/lib/pubid/cie/single_identifier.rb +30 -0
- data/lib/pubid/cie/supplement_identifier.rb +26 -0
- data/lib/pubid/cie/urn_generator.rb +123 -0
- data/lib/pubid/cie.rb +28 -0
- data/lib/pubid/components/code.rb +33 -0
- data/lib/pubid/components/date.rb +49 -0
- data/lib/pubid/components/edition.rb +32 -0
- data/lib/pubid/components/language.rb +37 -0
- data/lib/pubid/components/locality.rb +10 -0
- data/lib/pubid/components/publisher.rb +36 -0
- data/lib/pubid/components/stage.rb +54 -0
- data/lib/pubid/components/type.rb +58 -0
- data/lib/pubid/components/typed_stage.rb +55 -0
- data/lib/pubid/components.rb +15 -0
- data/lib/pubid/core/pattern_doc_generator.rb +272 -0
- data/lib/pubid/core/update_codes.rb +77 -0
- data/lib/pubid/core.rb +8 -0
- data/lib/pubid/csa/builder.rb +671 -0
- data/lib/pubid/csa/components/code.rb +9 -0
- data/lib/pubid/csa/components.rb +9 -0
- data/lib/pubid/csa/composite_identifier.rb +27 -0
- data/lib/pubid/csa/identifier.rb +457 -0
- data/lib/pubid/csa/identifiers/base.rb +133 -0
- data/lib/pubid/csa/identifiers/bundled.rb +125 -0
- data/lib/pubid/csa/identifiers/canadian_adopted.rb +82 -0
- data/lib/pubid/csa/identifiers/cec.rb +129 -0
- data/lib/pubid/csa/identifiers/combined.rb +130 -0
- data/lib/pubid/csa/identifiers/csa_adopted.rb +78 -0
- data/lib/pubid/csa/identifiers/package.rb +65 -0
- data/lib/pubid/csa/identifiers/series.rb +127 -0
- data/lib/pubid/csa/identifiers/standard.rb +10 -0
- data/lib/pubid/csa/identifiers.rb +17 -0
- data/lib/pubid/csa/parser.rb +445 -0
- data/lib/pubid/csa/scheme.rb +44 -0
- data/lib/pubid/csa/single_identifier.rb +30 -0
- data/lib/pubid/csa/urn_generator.rb +80 -0
- data/lib/pubid/csa/wrapper_identifier.rb +31 -0
- data/lib/pubid/csa.rb +25 -0
- data/lib/pubid/etsi/builder.rb +133 -0
- data/lib/pubid/etsi/components/code.rb +42 -0
- data/lib/pubid/etsi/components/version.rb +37 -0
- data/lib/pubid/etsi/components.rb +10 -0
- data/lib/pubid/etsi/identifier.rb +14 -0
- data/lib/pubid/etsi/identifiers/amendment.rb +15 -0
- data/lib/pubid/etsi/identifiers/base.rb +38 -0
- data/lib/pubid/etsi/identifiers/corrigendum.rb +15 -0
- data/lib/pubid/etsi/identifiers/etsi_standard.rb +19 -0
- data/lib/pubid/etsi/identifiers/supplement_identifier.rb +91 -0
- data/lib/pubid/etsi/identifiers.rb +14 -0
- data/lib/pubid/etsi/parser.rb +133 -0
- data/lib/pubid/etsi/scheme.rb +42 -0
- data/lib/pubid/etsi/urn_generator.rb +76 -0
- data/lib/pubid/etsi.rb +21 -0
- data/lib/pubid/export/auditor.rb +89 -0
- data/lib/pubid/export/data_class_exporter.rb +59 -0
- data/lib/pubid/export/exporter.rb +74 -0
- data/lib/pubid/export/flavor_exporter.rb +402 -0
- data/lib/pubid/export/ieee_exporter.rb +78 -0
- data/lib/pubid/export/itu_exporter.rb +66 -0
- data/lib/pubid/export/nist_exporter.rb +64 -0
- data/lib/pubid/export/registry_exporter.rb +90 -0
- data/lib/pubid/export/result.rb +97 -0
- data/lib/pubid/export/scheme_exporter.rb +70 -0
- data/lib/pubid/export.rb +18 -0
- data/lib/pubid/format_detector.rb +16 -0
- data/lib/pubid/format_registry.rb +42 -0
- data/lib/pubid/identifier.rb +235 -0
- data/lib/pubid/identifier_metadata.rb +148 -0
- data/lib/pubid/identifier_registry.rb +198 -0
- data/lib/pubid/idf/builder.rb +82 -0
- data/lib/pubid/idf/identifier.rb +69 -0
- data/lib/pubid/idf/identifiers/amendment.rb +27 -0
- data/lib/pubid/idf/identifiers/corrigendum.rb +27 -0
- data/lib/pubid/idf/identifiers/international_standard.rb +123 -0
- data/lib/pubid/idf/identifiers/reviewed_method.rb +100 -0
- data/lib/pubid/idf/identifiers.rb +13 -0
- data/lib/pubid/idf/parser.rb +143 -0
- data/lib/pubid/idf/scheme.rb +61 -0
- data/lib/pubid/idf/single_identifier.rb +19 -0
- data/lib/pubid/idf/supplement_identifier.rb +43 -0
- data/lib/pubid/idf/urn_generator.rb +84 -0
- data/lib/pubid/idf.rb +25 -0
- data/lib/pubid/iec/builder.rb +457 -0
- data/lib/pubid/iec/components/code.rb +59 -0
- data/lib/pubid/iec/components/consolidated_amendment.rb +59 -0
- data/lib/pubid/iec/components/publisher.rb +35 -0
- data/lib/pubid/iec/components/sheet.rb +32 -0
- data/lib/pubid/iec/components/trf_info.rb +38 -0
- data/lib/pubid/iec/components/vap_suffix.rb +41 -0
- data/lib/pubid/iec/identifier.rb +21 -0
- data/lib/pubid/iec/identifiers/amendment.rb +94 -0
- data/lib/pubid/iec/identifiers/base.rb +78 -0
- data/lib/pubid/iec/identifiers/component_specification.rb +39 -0
- data/lib/pubid/iec/identifiers/conformity_assessment.rb +39 -0
- data/lib/pubid/iec/identifiers/consolidated_identifier.rb +86 -0
- data/lib/pubid/iec/identifiers/corrigendum.rb +94 -0
- data/lib/pubid/iec/identifiers/fragment_identifier.rb +141 -0
- data/lib/pubid/iec/identifiers/guide.rb +104 -0
- data/lib/pubid/iec/identifiers/international_standard.rb +147 -0
- data/lib/pubid/iec/identifiers/interpretation_sheet.rb +104 -0
- data/lib/pubid/iec/identifiers/operational_document.rb +39 -0
- data/lib/pubid/iec/identifiers/publicly_available_specification.rb +101 -0
- data/lib/pubid/iec/identifiers/sheet_identifier.rb +66 -0
- data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +40 -0
- data/lib/pubid/iec/identifiers/systems_reference_document.rb +40 -0
- data/lib/pubid/iec/identifiers/technical_report.rb +132 -0
- data/lib/pubid/iec/identifiers/technical_specification.rb +132 -0
- data/lib/pubid/iec/identifiers/technology_report.rb +39 -0
- data/lib/pubid/iec/identifiers/test_report_form.rb +78 -0
- data/lib/pubid/iec/identifiers/vap_identifier.rb +77 -0
- data/lib/pubid/iec/identifiers/white_paper.rb +39 -0
- data/lib/pubid/iec/identifiers/working_document.rb +96 -0
- data/lib/pubid/iec/parser.rb +412 -0
- data/lib/pubid/iec/rendering_style.rb +113 -0
- data/lib/pubid/iec/scheme.rb +71 -0
- data/lib/pubid/iec/single_identifier.rb +80 -0
- data/lib/pubid/iec/supplement_identifier.rb +161 -0
- data/lib/pubid/iec/urn_generator.rb +193 -0
- data/lib/pubid/iec/urn_parser.rb +289 -0
- data/lib/pubid/iec.rb +85 -0
- data/lib/pubid/ieee/aiee/builder.rb +71 -0
- data/lib/pubid/ieee/aiee/identifier.rb +105 -0
- data/lib/pubid/ieee/aiee/parser.rb +130 -0
- data/lib/pubid/ieee/aiee.rb +11 -0
- data/lib/pubid/ieee/builder.rb +1237 -0
- data/lib/pubid/ieee/components/code.rb +102 -0
- data/lib/pubid/ieee/components/draft.rb +93 -0
- data/lib/pubid/ieee/components/relationship.rb +157 -0
- data/lib/pubid/ieee/components/typed_stage.rb +100 -0
- data/lib/pubid/ieee/identifier.rb +13 -0
- data/lib/pubid/ieee/identifiers/adopted_standard.rb +33 -0
- data/lib/pubid/ieee/identifiers/base.rb +591 -0
- data/lib/pubid/ieee/identifiers/conformance_identifier.rb +35 -0
- data/lib/pubid/ieee/identifiers/corrigendum.rb +37 -0
- data/lib/pubid/ieee/identifiers/csa_dual_published.rb +51 -0
- data/lib/pubid/ieee/identifiers/dual_identifier.rb +18 -0
- data/lib/pubid/ieee/identifiers/dual_published.rb +28 -0
- data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +27 -0
- data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +34 -0
- data/lib/pubid/ieee/identifiers/joint_development.rb +172 -0
- data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +51 -0
- data/lib/pubid/ieee/identifiers/nesc/base.rb +56 -0
- data/lib/pubid/ieee/identifiers/nesc/draft.rb +28 -0
- data/lib/pubid/ieee/identifiers/nesc/handbook.rb +32 -0
- data/lib/pubid/ieee/identifiers/nesc/redline.rb +26 -0
- data/lib/pubid/ieee/identifiers/nesc/standard.rb +26 -0
- data/lib/pubid/ieee/identifiers/nesc.rb +15 -0
- data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +20 -0
- data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +26 -0
- data/lib/pubid/ieee/identifiers/redlined_standard.rb +33 -0
- data/lib/pubid/ieee/identifiers/si_standard.rb +73 -0
- data/lib/pubid/ieee/identifiers/standard.rb +41 -0
- data/lib/pubid/ieee/identifiers/supplement_identifier.rb +23 -0
- data/lib/pubid/ieee/identifiers.rb +33 -0
- data/lib/pubid/ieee/ire/builder.rb +61 -0
- data/lib/pubid/ieee/ire/identifier.rb +58 -0
- data/lib/pubid/ieee/ire/parser.rb +91 -0
- data/lib/pubid/ieee/ire.rb +11 -0
- data/lib/pubid/ieee/nesc/builder.rb +101 -0
- data/lib/pubid/ieee/nesc/parser.rb +154 -0
- data/lib/pubid/ieee/nesc.rb +10 -0
- data/lib/pubid/ieee/parser.rb +1226 -0
- data/lib/pubid/ieee/scheme.rb +90 -0
- data/lib/pubid/ieee/typed_stages.rb +172 -0
- data/lib/pubid/ieee/urn_generator.rb +188 -0
- data/lib/pubid/ieee.rb +32 -0
- data/lib/pubid/ieee_debug.rb +31 -0
- data/lib/pubid/iho/builder.rb +37 -0
- data/lib/pubid/iho/identifier.rb +19 -0
- data/lib/pubid/iho/identifiers/base.rb +41 -0
- data/lib/pubid/iho/identifiers/bibliographic.rb +20 -0
- data/lib/pubid/iho/identifiers/circular_letter.rb +19 -0
- data/lib/pubid/iho/identifiers/miscellaneous.rb +20 -0
- data/lib/pubid/iho/identifiers/publication.rb +19 -0
- data/lib/pubid/iho/identifiers/standard.rb +19 -0
- data/lib/pubid/iho/identifiers.rb +14 -0
- data/lib/pubid/iho/parser.rb +68 -0
- data/lib/pubid/iho/scheme.rb +29 -0
- data/lib/pubid/iho/urn_generator.rb +29 -0
- data/lib/pubid/iho.rb +21 -0
- data/lib/pubid/iso/builder.rb +305 -0
- data/lib/pubid/iso/bundled_identifier.rb +85 -0
- data/lib/pubid/iso/combined_identifier.rb +22 -0
- data/lib/pubid/iso/components/code.rb +36 -0
- data/lib/pubid/iso/components/publisher.rb +60 -0
- data/lib/pubid/iso/components.rb +12 -0
- data/lib/pubid/iso/format_resolver.rb +45 -0
- data/lib/pubid/iso/identifier.rb +69 -0
- data/lib/pubid/iso/identifiers/addendum.rb +104 -0
- data/lib/pubid/iso/identifiers/amendment.rb +128 -0
- data/lib/pubid/iso/identifiers/base.rb +115 -0
- data/lib/pubid/iso/identifiers/corrigendum.rb +108 -0
- data/lib/pubid/iso/identifiers/data.rb +76 -0
- data/lib/pubid/iso/identifiers/directives.rb +59 -0
- data/lib/pubid/iso/identifiers/directives_supplement.rb +119 -0
- data/lib/pubid/iso/identifiers/extract.rb +30 -0
- data/lib/pubid/iso/identifiers/guide.rb +100 -0
- data/lib/pubid/iso/identifiers/international_standard.rb +168 -0
- data/lib/pubid/iso/identifiers/international_standardized_profile.rb +94 -0
- data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +89 -0
- data/lib/pubid/iso/identifiers/pas.rb +93 -0
- data/lib/pubid/iso/identifiers/recommendation.rb +45 -0
- data/lib/pubid/iso/identifiers/supplement.rb +87 -0
- data/lib/pubid/iso/identifiers/tc_document.rb +108 -0
- data/lib/pubid/iso/identifiers/technical_report.rb +103 -0
- data/lib/pubid/iso/identifiers/technical_specification.rb +102 -0
- data/lib/pubid/iso/identifiers/technology_trends_assessments.rb +95 -0
- data/lib/pubid/iso/identifiers.rb +33 -0
- data/lib/pubid/iso/parser.rb +510 -0
- data/lib/pubid/iso/rendering_style.rb +120 -0
- data/lib/pubid/iso/scheme.rb +187 -0
- data/lib/pubid/iso/single_identifier.rb +61 -0
- data/lib/pubid/iso/supplement_identifier.rb +27 -0
- data/lib/pubid/iso/urn_generator.rb +412 -0
- data/lib/pubid/iso/urn_parser.rb +423 -0
- data/lib/pubid/iso/utilities.rb +86 -0
- data/lib/pubid/iso.rb +50 -0
- data/lib/pubid/itu/builder.rb +171 -0
- data/lib/pubid/itu/components/code.rb +39 -0
- data/lib/pubid/itu/components/sector.rb +35 -0
- data/lib/pubid/itu/components/series.rb +29 -0
- data/lib/pubid/itu/i18n.rb +9 -0
- data/lib/pubid/itu/i18n.yaml +30 -0
- data/lib/pubid/itu/identifier.rb +53 -0
- data/lib/pubid/itu/identifiers/amendment.rb +43 -0
- data/lib/pubid/itu/identifiers/annex.rb +74 -0
- data/lib/pubid/itu/identifiers/base.rb +154 -0
- data/lib/pubid/itu/identifiers/combined_identifier.rb +47 -0
- data/lib/pubid/itu/identifiers/corrigendum.rb +44 -0
- data/lib/pubid/itu/identifiers/recommendation.rb +16 -0
- data/lib/pubid/itu/identifiers/special_publication.rb +31 -0
- data/lib/pubid/itu/identifiers/supplement.rb +46 -0
- data/lib/pubid/itu/identifiers.rb +16 -0
- data/lib/pubid/itu/model.rb +111 -0
- data/lib/pubid/itu/parser.rb +225 -0
- data/lib/pubid/itu/scheme.rb +174 -0
- data/lib/pubid/itu/urn_generator.rb +105 -0
- data/lib/pubid/itu.rb +22 -0
- data/lib/pubid/jcgm/builder.rb +88 -0
- data/lib/pubid/jcgm/components/publisher.rb +20 -0
- data/lib/pubid/jcgm/components.rb +9 -0
- data/lib/pubid/jcgm/identifier.rb +11 -0
- data/lib/pubid/jcgm/identifiers/amendment.rb +35 -0
- data/lib/pubid/jcgm/identifiers/guide.rb +21 -0
- data/lib/pubid/jcgm/identifiers/gum_guide.rb +51 -0
- data/lib/pubid/jcgm/identifiers.rb +11 -0
- data/lib/pubid/jcgm/parser.rb +84 -0
- data/lib/pubid/jcgm/scheme.rb +60 -0
- data/lib/pubid/jcgm/single_identifier.rb +48 -0
- data/lib/pubid/jcgm/supplement_identifier.rb +16 -0
- data/lib/pubid/jcgm/urn_generator.rb +110 -0
- data/lib/pubid/jcgm.rb +31 -0
- data/lib/pubid/jis/builder.rb +124 -0
- data/lib/pubid/jis/components/code.rb +59 -0
- data/lib/pubid/jis/components.rb +9 -0
- data/lib/pubid/jis/identifier.rb +18 -0
- data/lib/pubid/jis/identifiers/amendment.rb +16 -0
- data/lib/pubid/jis/identifiers/base.rb +72 -0
- data/lib/pubid/jis/identifiers/explanation.rb +22 -0
- data/lib/pubid/jis/identifiers/japanese_industrial_standard.rb +16 -0
- data/lib/pubid/jis/identifiers/standard.rb +27 -0
- data/lib/pubid/jis/identifiers/technical_report.rb +31 -0
- data/lib/pubid/jis/identifiers/technical_specification.rb +31 -0
- data/lib/pubid/jis/identifiers.rb +17 -0
- data/lib/pubid/jis/parser.rb +109 -0
- data/lib/pubid/jis/scheme.rb +49 -0
- data/lib/pubid/jis/single_identifier.rb +37 -0
- data/lib/pubid/jis/supplement_identifier.rb +47 -0
- data/lib/pubid/jis/urn_generator.rb +25 -0
- data/lib/pubid/jis.rb +23 -0
- data/lib/pubid/lutaml/no_store_registration.rb +30 -0
- data/lib/pubid/nist/builder.rb +2100 -0
- data/lib/pubid/nist/components/code.rb +38 -0
- data/lib/pubid/nist/components/edition.rb +118 -0
- data/lib/pubid/nist/components/issue_number.rb +28 -0
- data/lib/pubid/nist/components/part.rb +77 -0
- data/lib/pubid/nist/components/publisher.rb +24 -0
- data/lib/pubid/nist/components/stage.rb +53 -0
- data/lib/pubid/nist/components/supplement.rb +121 -0
- data/lib/pubid/nist/components/translation.rb +42 -0
- data/lib/pubid/nist/components/update.rb +103 -0
- data/lib/pubid/nist/components/version.rb +35 -0
- data/lib/pubid/nist/components/volume.rb +32 -0
- data/lib/pubid/nist/components.rb +19 -0
- data/lib/pubid/nist/configuration.rb +77 -0
- data/lib/pubid/nist/identifiers/base.rb +499 -0
- data/lib/pubid/nist/identifiers/circular.rb +68 -0
- data/lib/pubid/nist/identifiers/circular_supplement.rb +50 -0
- data/lib/pubid/nist/identifiers/commercial_standard.rb +41 -0
- data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +56 -0
- data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +56 -0
- data/lib/pubid/nist/identifiers/crpl_report.rb +135 -0
- data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +94 -0
- data/lib/pubid/nist/identifiers/grant_contractor_report.rb +35 -0
- data/lib/pubid/nist/identifiers/handbook.rb +50 -0
- data/lib/pubid/nist/identifiers/internal_report.rb +56 -0
- data/lib/pubid/nist/identifiers/letter_circular.rb +45 -0
- data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +65 -0
- data/lib/pubid/nist/identifiers/monograph.rb +69 -0
- data/lib/pubid/nist/identifiers/ncstar.rb +41 -0
- data/lib/pubid/nist/identifiers/nsrds.rb +41 -0
- data/lib/pubid/nist/identifiers/owmwp.rb +35 -0
- data/lib/pubid/nist/identifiers/report.rb +68 -0
- data/lib/pubid/nist/identifiers/special_publication.rb +36 -0
- data/lib/pubid/nist/identifiers/technical_note.rb +90 -0
- data/lib/pubid/nist/identifiers.rb +33 -0
- data/lib/pubid/nist/parser.rb +1084 -0
- data/lib/pubid/nist/scheme.rb +199 -0
- data/lib/pubid/nist/supplement_identifier.rb +83 -0
- data/lib/pubid/nist/urn_generator.rb +127 -0
- data/lib/pubid/nist.rb +36 -0
- data/lib/pubid/oiml/builder.rb +189 -0
- data/lib/pubid/oiml/components/code.rb +20 -0
- data/lib/pubid/oiml/components.rb +9 -0
- data/lib/pubid/oiml/identifier.rb +11 -0
- data/lib/pubid/oiml/identifiers/amendment.rb +13 -0
- data/lib/pubid/oiml/identifiers/annex.rb +62 -0
- data/lib/pubid/oiml/identifiers/base.rb +36 -0
- data/lib/pubid/oiml/identifiers/basic_publication.rb +13 -0
- data/lib/pubid/oiml/identifiers/document.rb +13 -0
- data/lib/pubid/oiml/identifiers/expert_report.rb +13 -0
- data/lib/pubid/oiml/identifiers/guide.rb +13 -0
- data/lib/pubid/oiml/identifiers/recommendation.rb +13 -0
- data/lib/pubid/oiml/identifiers/seminar_report.rb +13 -0
- data/lib/pubid/oiml/identifiers/vocabulary.rb +13 -0
- data/lib/pubid/oiml/identifiers.rb +18 -0
- data/lib/pubid/oiml/parser.rb +173 -0
- data/lib/pubid/oiml/scheme.rb +46 -0
- data/lib/pubid/oiml/single_identifier.rb +90 -0
- data/lib/pubid/oiml/supplement_identifier.rb +43 -0
- data/lib/pubid/oiml/urn_generator.rb +64 -0
- data/lib/pubid/oiml.rb +26 -0
- data/lib/pubid/parser/common_parse_methods.rb +13 -0
- data/lib/pubid/parser/common_parse_rules.rb +56 -0
- data/lib/pubid/parser.rb +8 -0
- data/lib/pubid/parsers/base.rb +11 -0
- data/lib/pubid/parsers/mr_string.rb +93 -0
- data/lib/pubid/plateau/builder.rb +50 -0
- data/lib/pubid/plateau/identifiers/annex.rb +16 -0
- data/lib/pubid/plateau/identifiers/base.rb +51 -0
- data/lib/pubid/plateau/identifiers/handbook.rb +34 -0
- data/lib/pubid/plateau/identifiers/technical_report.rb +20 -0
- data/lib/pubid/plateau/identifiers.rb +12 -0
- data/lib/pubid/plateau/parser.rb +63 -0
- data/lib/pubid/plateau/scheme.rb +45 -0
- data/lib/pubid/plateau/supplement_identifier.rb +72 -0
- data/lib/pubid/plateau/urn_generator.rb +29 -0
- data/lib/pubid/plateau.rb +25 -0
- data/lib/pubid/renderers/base.rb +19 -0
- data/lib/pubid/renderers/directives_renderer.rb +62 -0
- data/lib/pubid/renderers/guide_renderer.rb +20 -0
- data/lib/pubid/renderers/human_readable.rb +58 -0
- data/lib/pubid/renderers/iwa_renderer.rb +16 -0
- data/lib/pubid/renderers/mr_string.rb +16 -0
- data/lib/pubid/renderers/supplement_renderer.rb +33 -0
- data/lib/pubid/renderers/urn.rb +11 -0
- data/lib/pubid/renderers.rb +14 -0
- data/lib/pubid/rendering/base.rb +73 -0
- data/lib/pubid/rendering/common.rb +211 -0
- data/lib/pubid/rendering/context.rb +156 -0
- data/lib/pubid/rendering/date.rb +27 -0
- data/lib/pubid/rendering/format.rb +25 -0
- data/lib/pubid/rendering/language.rb +21 -0
- data/lib/pubid/rendering/numbering.rb +24 -0
- data/lib/pubid/rendering/publisher.rb +25 -0
- data/lib/pubid/rendering/stage.rb +38 -0
- data/lib/pubid/rendering/supplement.rb +46 -0
- data/lib/pubid/rendering.rb +16 -0
- data/lib/pubid/sae/builder.rb +32 -0
- data/lib/pubid/sae/components/code.rb +9 -0
- data/lib/pubid/sae/components/date.rb +19 -0
- data/lib/pubid/sae/components/type.rb +19 -0
- data/lib/pubid/sae/components.rb +11 -0
- data/lib/pubid/sae/identifier.rb +14 -0
- data/lib/pubid/sae/identifiers/base.rb +42 -0
- data/lib/pubid/sae/identifiers.rb +9 -0
- data/lib/pubid/sae/parser.rb +55 -0
- data/lib/pubid/sae/scheme.rb +47 -0
- data/lib/pubid/sae/urn_generator.rb +38 -0
- data/lib/pubid/sae.rb +19 -0
- data/lib/pubid/scheme.rb +207 -0
- data/lib/pubid/urn_generator/base.rb +110 -0
- data/lib/pubid/utils/string_normalizer.rb +196 -0
- data/lib/pubid/utils.rb +7 -0
- data/lib/pubid/version.rb +3 -1
- data/lib/pubid.rb +137 -13
- data/lib/tasks/docs.rake +37 -0
- data/lib/tasks/export.rake +38 -0
- data/lib/tasks/website-data.json +7488 -0
- metadata +613 -171
- data/lib/pubid/registry.rb +0 -30
|
@@ -0,0 +1,2100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Nist
|
|
5
|
+
# Builder class for constructing NIST identifier objects from parsed data
|
|
6
|
+
# Single Responsibility: Transform parsed data into identifier objects
|
|
7
|
+
#
|
|
8
|
+
# CRITICAL ARCHITECTURE PRINCIPLE:
|
|
9
|
+
# Builder NEVER makes business logic decisions.
|
|
10
|
+
# Builder ONLY casts parsed data to domain objects.
|
|
11
|
+
class Builder < Pubid::Builder::Base
|
|
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
|
+
def initialize(scheme)
|
|
25
|
+
@scheme = scheme
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Build an identifier object from parsed data
|
|
29
|
+
# @param parsed [Hash, Array] the parsed identifier data
|
|
30
|
+
# @return [Identifiers::Base] the constructed identifier object
|
|
31
|
+
def build(parsed)
|
|
32
|
+
# Parslet can return array of hashes - merge them
|
|
33
|
+
parsed_hash = parsed.is_a?(Array) ? flatten_array(parsed) : parsed
|
|
34
|
+
|
|
35
|
+
# NEW: Fix for update year captured as edition_e
|
|
36
|
+
# Pattern: "800-53r4/Upd3-2015" where parser captures "-2015" as :edition_e
|
|
37
|
+
# but it should be :update_year as part of the update component
|
|
38
|
+
if parsed_hash[:update_prefix] && parsed_hash[:update] && parsed_hash[:edition_e]
|
|
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
|
|
45
|
+
|
|
46
|
+
# NEW: Fix for letter suffix in number with edition_dash_year
|
|
47
|
+
# Pattern: "304a-2017" where parser returns first_number="304a" and edition_dash_year="2017"
|
|
48
|
+
# Expected: number="304", part="A", edition with type="e" and id="2017"
|
|
49
|
+
# We'll handle this by extracting the info and NOT adding :part to parsed_hash
|
|
50
|
+
# to avoid it being processed by cast(:part, ...) which would set type="pt"
|
|
51
|
+
letter_suffix_part = nil
|
|
52
|
+
edition_from_dash_year = nil
|
|
53
|
+
if parsed_hash[:first_number]&.to_s&.match?(/^[0-9]+[a-zA-Z]$/) && parsed_hash[:edition_dash_year]
|
|
54
|
+
number_str = parsed_hash[:first_number].to_s
|
|
55
|
+
# Extract letter suffix from number
|
|
56
|
+
if match_data = number_str.match(/^([0-9]+)([a-zA-Z])$/)
|
|
57
|
+
base_number = match_data[1]
|
|
58
|
+
letter_suffix = match_data[2].upcase
|
|
59
|
+
|
|
60
|
+
# Update first_number to exclude letter suffix
|
|
61
|
+
parsed_hash[:first_number] =
|
|
62
|
+
Components::Code.new(number: base_number)
|
|
63
|
+
|
|
64
|
+
# Store Part component for later (after identifier is initialized)
|
|
65
|
+
letter_suffix_part = Components::Part.new(type: "",
|
|
66
|
+
value: letter_suffix)
|
|
67
|
+
|
|
68
|
+
# Convert edition_dash_year to Edition component with type="e"
|
|
69
|
+
dash_year = parsed_hash[:edition_dash_year][:dash_year]
|
|
70
|
+
edition_from_dash_year = Components::Edition.new(type: "e",
|
|
71
|
+
id: dash_year)
|
|
72
|
+
parsed_hash.delete(:edition_dash_year) # Remove the old key
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
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
|
+
# NEW: Fix for edition embedded in second_number
|
|
297
|
+
# Pattern: "53e5" where second_number="53e5" with edition "e5" embedded
|
|
298
|
+
# Expected: second_number="53", edition with type="e" and id="5"
|
|
299
|
+
if parsed_hash[:second_number]&.to_s&.match?(/^\d+[a-zA-Z]\d+$/)
|
|
300
|
+
second_str = parsed_hash[:second_number].to_s
|
|
301
|
+
# Extract edition from second_number (e.g., "53e5" → "53" + edition "e5")
|
|
302
|
+
if match_data = second_str.match(/^(\d+)([a-zA-Z])(\d+)$/)
|
|
303
|
+
base_number = match_data[1]
|
|
304
|
+
edition_letter = match_data[2]
|
|
305
|
+
edition_id = match_data[3]
|
|
306
|
+
|
|
307
|
+
# Update second_number and create Edition component
|
|
308
|
+
parsed_hash[:second_number] =
|
|
309
|
+
Components::Code.new(number: base_number)
|
|
310
|
+
# Store Edition component for later (after identifier is initialized)
|
|
311
|
+
edition_from_embedded = Components::Edition.new(
|
|
312
|
+
type: edition_letter, id: edition_id,
|
|
313
|
+
)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# NEW: Check for CIRC supplement pattern
|
|
318
|
+
# Note: :base_portion is lost during parser merge, so check for supplement indicators
|
|
319
|
+
if parsed_hash[:supplement_date_range] || parsed_hash[:supplement_slash_year] ||
|
|
320
|
+
parsed_hash[:supplement_month_year] || parsed_hash[:supplement_year] ||
|
|
321
|
+
parsed_hash[:supplement] || parsed_hash[:base_portion]
|
|
322
|
+
return build_circular_supplement(parsed_hash)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Locate the appropriate identifier class via Scheme
|
|
326
|
+
identifier = @scheme.locate_identifier_klass(parsed_hash).new
|
|
327
|
+
|
|
328
|
+
# NEW: If we extracted a letter suffix Part, assign it now (after identifier initialization)
|
|
329
|
+
if letter_suffix_part
|
|
330
|
+
identifier.part = letter_suffix_part
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# NEW: If we extracted an Edition from edition_dash_year, assign it now
|
|
334
|
+
if edition_from_dash_year
|
|
335
|
+
identifier.edition = edition_from_dash_year
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# NEW: If we extracted an Edition from embedded second_number, assign it now
|
|
339
|
+
if edition_from_embedded
|
|
340
|
+
identifier.edition = edition_from_embedded
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# NEW: If we extracted an Edition from edition_dash_year with embedded edition in first_number, assign it now
|
|
344
|
+
if parsed_hash[:edition_embedded_with_year]
|
|
345
|
+
identifier.edition = parsed_hash[:edition_embedded_with_year]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# NEW: If we extracted an Edition from edition_dash_year as year-only edition, assign it now
|
|
349
|
+
if parsed_hash[:edition_from_year]
|
|
350
|
+
identifier.edition = parsed_hash[:edition_from_year]
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# NEW: If we extracted an Edition from edition_dash_year with embedded edition, assign it now
|
|
354
|
+
if parsed_hash[:edition_with_year]
|
|
355
|
+
identifier.edition = parsed_hash[:edition_with_year]
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# NEW: If we have a direct Edition from parsed_hash, assign it now
|
|
359
|
+
# (Used for IR patterns where large dash_year is treated as edition)
|
|
360
|
+
if parsed_hash[:edition]
|
|
361
|
+
identifier.edition = parsed_hash[:edition]
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Track first_number, second_number, decimal_number, and letter_number for building compound number
|
|
365
|
+
first_num = nil
|
|
366
|
+
second_num = nil
|
|
367
|
+
decimal_num = nil
|
|
368
|
+
letter_num = nil
|
|
369
|
+
part_num = nil
|
|
370
|
+
extracted_revision = nil
|
|
371
|
+
|
|
372
|
+
# Cast and assign all attributes
|
|
373
|
+
parsed_hash.each_pair do |key, value|
|
|
374
|
+
realized_components = cast(key.to_sym, value, parsed_hash) # Pass parsed_hash for context
|
|
375
|
+
next if realized_components.nil?
|
|
376
|
+
|
|
377
|
+
# Track number components
|
|
378
|
+
if key == :first_number && realized_components.is_a?(Components::Code)
|
|
379
|
+
first_num = realized_components
|
|
380
|
+
elsif key == :second_number && realized_components.is_a?(Components::Code)
|
|
381
|
+
second_num = realized_components
|
|
382
|
+
elsif key == :crpl_range && realized_components.is_a?(Components::Code)
|
|
383
|
+
# crpl_range is treated as second_number for compound number construction
|
|
384
|
+
second_num = realized_components
|
|
385
|
+
elsif key == :part_number
|
|
386
|
+
part_num = value.to_s
|
|
387
|
+
# NEW: Track decimal_number for IR identifiers (e.g., 80-2073.3)
|
|
388
|
+
# decimal_number is stored as hash with :decimal_base and :decimal_suffix
|
|
389
|
+
elsif key == :decimal_number && realized_components.is_a?(Hash)
|
|
390
|
+
# Store the raw hash for processing during compound number construction
|
|
391
|
+
decimal_num = realized_components
|
|
392
|
+
# NEW: Track letter_number for NCSTAR identifiers (e.g., 1-1A, 1-3B)
|
|
393
|
+
# letter_number is stored as hash with :letter_base and :letter_suffix
|
|
394
|
+
# For SpecialPublication (e.g., 800-56A), we need to:
|
|
395
|
+
# 1. Store the original hash for compound number construction (letter_base)
|
|
396
|
+
# 2. Create a Part component from letter_suffix
|
|
397
|
+
elsif key == :letter_number
|
|
398
|
+
# Store the original hash for compound number construction
|
|
399
|
+
letter_num = value
|
|
400
|
+
# If cast returned a hash with a part component, it will be assigned below
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# Handle composite hash returns (multiple related values)
|
|
404
|
+
case realized_components
|
|
405
|
+
when Hash
|
|
406
|
+
realized_components.each_pair do |sub_key, sub_value|
|
|
407
|
+
# Track first_number from hash returns
|
|
408
|
+
if sub_key == :first_number && sub_value.is_a?(Components::Code)
|
|
409
|
+
first_num = sub_value
|
|
410
|
+
# Track second_number from hash returns
|
|
411
|
+
elsif sub_key == :second_number && sub_value.is_a?(Components::Code)
|
|
412
|
+
second_num = sub_value
|
|
413
|
+
# NEW: Handle second_number with edition (hash with :number_only and :edition_id)
|
|
414
|
+
# For "126r2013": parser returns {:number_only=>"126", :edition_id=>"2013"}
|
|
415
|
+
# We DON'T convert to Components::Code here; we process it during compound number construction
|
|
416
|
+
elsif sub_key == :second_number && sub_value.is_a?(Hash)
|
|
417
|
+
if sub_value[:number_only] && sub_value[:edition_id]
|
|
418
|
+
# Store the raw hash for processing during compound number construction
|
|
419
|
+
# This prevents the hash from being assigned directly to identifier.number
|
|
420
|
+
second_num = sub_value
|
|
421
|
+
extracted_revision = "r" # Mark as revision format
|
|
422
|
+
end
|
|
423
|
+
# Track revision extraction
|
|
424
|
+
elsif sub_key == :revision
|
|
425
|
+
extracted_revision = sub_value
|
|
426
|
+
end
|
|
427
|
+
# Skip assignment for second_number hashes - they'll be processed during compound number construction
|
|
428
|
+
next if sub_key == :second_number && sub_value.is_a?(Hash) && sub_value[:number_only]
|
|
429
|
+
|
|
430
|
+
attrs = identifier.class.attributes
|
|
431
|
+
setter = "#{sub_key}="
|
|
432
|
+
if attrs.key?(sub_key.to_sym)
|
|
433
|
+
identifier.public_send(setter,
|
|
434
|
+
sub_value)
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
else
|
|
438
|
+
attrs = identifier.class.attributes
|
|
439
|
+
setter = "#{key}="
|
|
440
|
+
if attrs.key?(key.to_sym)
|
|
441
|
+
identifier.public_send(setter,
|
|
442
|
+
realized_components)
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Build compound number from first_number and second_number
|
|
448
|
+
if first_num && !identifier.number
|
|
449
|
+
# Skip if this is a v#n# pattern - now handled as Part component
|
|
450
|
+
if identifier.volume && identifier.issue_number
|
|
451
|
+
# V#n# pattern handled as Part in first_number cast
|
|
452
|
+
# NEW: Handle decimal number pattern (e.g., 80-2073.3 for IR identifiers)
|
|
453
|
+
# decimal_num is {:decimal_base => "2073", :decimal_suffix => "3"}
|
|
454
|
+
elsif decimal_num
|
|
455
|
+
decimal_base = decimal_num[:decimal_base].to_s
|
|
456
|
+
decimal_suffix = decimal_num[:decimal_suffix].to_s
|
|
457
|
+
identifier.number = Components::Code.new(number: "#{first_num.value}-#{decimal_base}.#{decimal_suffix}")
|
|
458
|
+
# NEW: Handle letter number pattern (e.g., 1-1A, 1-3B for NCSTAR identifiers)
|
|
459
|
+
# letter_num is {:letter_base => "1", :letter_suffix => "A"}
|
|
460
|
+
# Also handles IR series "R" suffix: "79-1786R" → "79-1786r1"
|
|
461
|
+
elsif letter_num
|
|
462
|
+
letter_base = letter_num[:letter_base].to_s
|
|
463
|
+
letter_suffix = letter_num[:letter_suffix].to_s
|
|
464
|
+
|
|
465
|
+
# SPECIAL CASE: IR series with "R" suffix means "r1" (revision 1)
|
|
466
|
+
# "79-1786R" → number="79-1786", edition="r1"
|
|
467
|
+
is_ir = parsed_hash[:series]&.to_s == "IR"
|
|
468
|
+
if is_ir && letter_suffix == "R"
|
|
469
|
+
# IR "R" suffix converts to revision format "r1"
|
|
470
|
+
identifier.number = Components::Code.new(number: "#{first_num.value}-#{letter_base}")
|
|
471
|
+
edition_obj = Components::Edition.new(type: "r", id: "1")
|
|
472
|
+
identifier.edition = edition_obj
|
|
473
|
+
identifier.edition_component = edition_obj
|
|
474
|
+
identifier.revision = "r1"
|
|
475
|
+
# If a Part component was already set (from cast handler), the letter_suffix
|
|
476
|
+
# is a separate Part component (e.g., SpecialPublication "800-56A" → number="800-56", part="A")
|
|
477
|
+
# Otherwise, letter_suffix is part of the number (e.g., NCSTAR "1-1A" → number="1-1A")
|
|
478
|
+
elsif identifier.part
|
|
479
|
+
# SpecialPublication pattern: letter_suffix is separate Part component
|
|
480
|
+
identifier.number = Components::Code.new(number: "#{first_num.value}-#{letter_base}")
|
|
481
|
+
else
|
|
482
|
+
# NCSTAR pattern: letter_suffix is part of the number
|
|
483
|
+
identifier.number = Components::Code.new(number: "#{first_num.value}-#{letter_base}#{letter_suffix}")
|
|
484
|
+
end
|
|
485
|
+
elsif second_num
|
|
486
|
+
# Check for special patterns first
|
|
487
|
+
# NEW: Handle second_number with edition (hash from parser pattern)
|
|
488
|
+
# For "126r2013": second_num is {:number_only=>"126", :edition_id=>"2013"}
|
|
489
|
+
if second_num.is_a?(Hash) && second_num[:number_only] && second_num[:edition_id]
|
|
490
|
+
# Extract components from hash
|
|
491
|
+
number_part = second_num[:number_only].to_s
|
|
492
|
+
edition_id = second_num[:edition_id].to_s
|
|
493
|
+
|
|
494
|
+
# Create Edition component
|
|
495
|
+
edition_obj = Components::Edition.new(type: "r", id: edition_id)
|
|
496
|
+
|
|
497
|
+
identifier.number = Components::Code.new(number: "#{first_num.value}-#{number_part}")
|
|
498
|
+
identifier.edition = edition_obj
|
|
499
|
+
identifier.edition_component = edition_obj
|
|
500
|
+
identifier.revision = "r#{edition_id}"
|
|
501
|
+
# CS Emergency pattern: e104-43 → number=104, edition_year=1943
|
|
502
|
+
# Logic: e104-43 means "emergency 104 from 1943" (43 = 1943)
|
|
503
|
+
elsif first_num.value.to_s.match?(/^e(\d{3})$/) &&
|
|
504
|
+
second_num.value.to_s.match?(/^\d{2}$/)
|
|
505
|
+
match_data = first_num.value.to_s.match(/^e(\d{3})$/)
|
|
506
|
+
number_part = match_data[1] # 104
|
|
507
|
+
year_suffix = second_num.value.to_s # 43
|
|
508
|
+
# Edition year: 19 + 43 = 1943 (1900s + year suffix)
|
|
509
|
+
edition_year = "19#{year_suffix}"
|
|
510
|
+
|
|
511
|
+
# Create Edition component
|
|
512
|
+
edition_obj = Components::Edition.new(type: "e", id: edition_year)
|
|
513
|
+
|
|
514
|
+
identifier.number = Components::Code.new(number: number_part)
|
|
515
|
+
identifier.edition = edition_obj
|
|
516
|
+
identifier.edition_component = edition_obj
|
|
517
|
+
elsif first_num.value.to_s.match?(/^(\d+)e(\d+)$/) &&
|
|
518
|
+
second_num.value.to_s.match?(/^\d{2,4}$/)
|
|
519
|
+
# Pattern: "11e2-1915" OR "123e2-50" parsed as first="11e2"|"123e2", second="1915"|"50"
|
|
520
|
+
# Extract number and edition from first_num
|
|
521
|
+
match_data = first_num.value.to_s.match(/^(\d+)e(\d+)$/)
|
|
522
|
+
number_part = match_data[1]
|
|
523
|
+
edition_id = match_data[2]
|
|
524
|
+
year_part = second_num.value.to_s
|
|
525
|
+
|
|
526
|
+
# Expand 2-digit year to 4-digit (50 → 1950)
|
|
527
|
+
year_part = "19#{year_part}" if year_part.length == 2
|
|
528
|
+
|
|
529
|
+
identifier.number = Components::Code.new(number: number_part)
|
|
530
|
+
|
|
531
|
+
# For edition+year patterns, handling depends on identifier type:
|
|
532
|
+
# - CIRC: edition number + year as additional_text, rendered with dot ("11e2-1915" → "11e2.1915")
|
|
533
|
+
# - HB, others: edition number + year as additional_text, rendered with dash ("44e2-1955")
|
|
534
|
+
# Both use the same Edition component structure, only rendering differs
|
|
535
|
+
edition_obj = Components::Edition.new(type: "e",
|
|
536
|
+
id: edition_id, additional_text: year_part)
|
|
537
|
+
identifier.edition = edition_obj
|
|
538
|
+
identifier.edition_component = edition_obj
|
|
539
|
+
elsif first_num.value.to_s.match?(/^(\d+)supp?$/) &&
|
|
540
|
+
second_num.value.to_s.match?(/^\d{4}$/)
|
|
541
|
+
# Pattern: "25supp-1924" parsed as first="25supp", second="1924"
|
|
542
|
+
number_part = first_num.value.to_s.match(/^(\d+)supp?$/)[1]
|
|
543
|
+
year_part = second_num.value.to_s
|
|
544
|
+
|
|
545
|
+
identifier.number = Components::Code.new(number: number_part)
|
|
546
|
+
identifier.supplement = year_part
|
|
547
|
+
elsif identifier.is_a?(Identifiers::TechnicalNote) &&
|
|
548
|
+
second_num.value.to_s.match?(/^(19|20)\d{2}$/)
|
|
549
|
+
# SPECIAL CASE FOR TN: second_num is edition year
|
|
550
|
+
# Following "date IS edition" rule: -1993 becomes Edition(type: "e", id: "1993")
|
|
551
|
+
identifier.number = first_num
|
|
552
|
+
edition_obj = Components::Edition.new(type: "e",
|
|
553
|
+
id: second_num.value.to_s)
|
|
554
|
+
identifier.edition_component = edition_obj
|
|
555
|
+
identifier.edition = edition_obj
|
|
556
|
+
identifier.edition_year = second_num.value.to_s
|
|
557
|
+
elsif part_num && parsed_hash[:series].to_s.include?("IR")
|
|
558
|
+
# Normal compound number
|
|
559
|
+
# For IR identifiers, part_number should be a Part component (type="pt"), not in compound number
|
|
560
|
+
identifier.part = Components::Part.new(type: "pt",
|
|
561
|
+
value: part_num)
|
|
562
|
+
identifier.number = Components::Code.new(number: "#{first_num.value}-#{second_num.value}")
|
|
563
|
+
# For IR, create Part component with type="pt"
|
|
564
|
+
else
|
|
565
|
+
# For GCR and others, include part number in compound number
|
|
566
|
+
compound_value = "#{first_num.value}-#{second_num.value}"
|
|
567
|
+
compound_value += "-#{part_num}" if part_num
|
|
568
|
+
identifier.number = Components::Code.new(number: compound_value)
|
|
569
|
+
end
|
|
570
|
+
else
|
|
571
|
+
# No second_num, use first_num directly
|
|
572
|
+
identifier.number = first_num
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# Apply extracted revision if not already set
|
|
577
|
+
if extracted_revision && !identifier.edition
|
|
578
|
+
# Convert extracted revision to Edition component
|
|
579
|
+
identifier.edition = Components::Edition.new(type: "r",
|
|
580
|
+
id: extracted_revision.to_s)
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
# IR-SPECIFIC: Handle compound numbers that were converted to edition+year format
|
|
584
|
+
# For IR identifiers, "84-2946" should remain as compound number, not become "84e2946"
|
|
585
|
+
# The preprocessing converts "84-2946" to "84e2946", so we need to convert it back for IR
|
|
586
|
+
is_ir = begin
|
|
587
|
+
parsed_hash[:series].to_s.include?("IR")
|
|
588
|
+
rescue StandardError
|
|
589
|
+
false
|
|
590
|
+
end
|
|
591
|
+
if is_ir && identifier.number && identifier.number.value.to_s.match?(/^(\d+)e(\d{4})$/)
|
|
592
|
+
# Extract the compound number parts from the edition+year format
|
|
593
|
+
match_data = identifier.number.value.to_s.match(/^(\d+)e(\d{4})$/)
|
|
594
|
+
number_part = match_data[1] # "84"
|
|
595
|
+
year_part = match_data[2] # "2946"
|
|
596
|
+
|
|
597
|
+
# Convert to compound number format
|
|
598
|
+
identifier.number = Components::Code.new(number: "#{number_part}-#{year_part}")
|
|
599
|
+
|
|
600
|
+
# Clear the edition that was incorrectly set from the year
|
|
601
|
+
identifier.edition = nil
|
|
602
|
+
identifier.edition_component = nil
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# Set publisher_was_parsed flag if publisher was set
|
|
606
|
+
# This includes cases where publisher was explicitly parsed or extracted from series prefix
|
|
607
|
+
identifier.publisher_was_parsed = true if identifier.publisher
|
|
608
|
+
|
|
609
|
+
# NEW: Convert revision with month+year to update component (V1 compatibility)
|
|
610
|
+
# Patterns like "NIST IR 4743rJun1992" should be rendered as "NIST IR 4743/Upd1-199206"
|
|
611
|
+
if parsed_hash[:revision_month] && parsed_hash[:revision_year]
|
|
612
|
+
# rJun1992 pattern: revision_month is "Jun", revision_year is "1992"
|
|
613
|
+
month_str = parsed_hash[:revision_month].to_s
|
|
614
|
+
year_str = parsed_hash[:revision_year].to_s
|
|
615
|
+
|
|
616
|
+
# Convert month name to number (Jun → 06, Nov → 11, etc.)
|
|
617
|
+
month_num = month_name_to_number(month_str)
|
|
618
|
+
|
|
619
|
+
# Create update component with default number=1, converted year and month
|
|
620
|
+
update_obj = Components::Update.new(
|
|
621
|
+
number: "1",
|
|
622
|
+
year: year_str,
|
|
623
|
+
month: sprintf("%02d", month_num),
|
|
624
|
+
prefix: "slash", # V1 uses /Upd format
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# Set both V2 component and legacy attribute for backward compatibility
|
|
628
|
+
identifier.update_component = update_obj
|
|
629
|
+
identifier.update = update_obj
|
|
630
|
+
|
|
631
|
+
# Clear the legacy revision_year/revision_month attributes
|
|
632
|
+
identifier.revision_year = nil
|
|
633
|
+
identifier.revision_month = nil
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
identifier
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# Convert month name to month number
|
|
640
|
+
# @param month_name [String] month abbreviation (Jan, Feb, Mar, etc.)
|
|
641
|
+
# @return [Integer] month number (1-12)
|
|
642
|
+
def month_name_to_number(month_name)
|
|
643
|
+
month_map = {
|
|
644
|
+
"Jan" => 1, "January" => 1,
|
|
645
|
+
"Feb" => 2, "February" => 2,
|
|
646
|
+
"Mar" => 3, "March" => 3,
|
|
647
|
+
"Apr" => 4, "April" => 4,
|
|
648
|
+
"May" => 5,
|
|
649
|
+
"Jun" => 6, "June" => 6,
|
|
650
|
+
"Jul" => 7, "July" => 7,
|
|
651
|
+
"Aug" => 8, "August" => 8,
|
|
652
|
+
"Sep" => 9, "Sept" => 9, "September" => 9,
|
|
653
|
+
"Oct" => 10, "October" => 10,
|
|
654
|
+
"Nov" => 11, "November" => 11,
|
|
655
|
+
"Dec" => 12, "December" => 12
|
|
656
|
+
}
|
|
657
|
+
month_map[month_name] || 1 # Default to January if not found
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
# Build CircularSupplement with base_identifier wrapping
|
|
661
|
+
# @param parsed_hash [Hash] the parsed supplement data
|
|
662
|
+
# @return [Identifiers::CircularSupplement] the supplement identifier
|
|
663
|
+
def build_circular_supplement(parsed_hash)
|
|
664
|
+
supplement = Identifiers::CircularSupplement.new
|
|
665
|
+
|
|
666
|
+
# Extract series from circ_series if present (nested structure from parser)
|
|
667
|
+
series_value = nil
|
|
668
|
+
if parsed_hash[:circ_series].is_a?(Hash)
|
|
669
|
+
series_value = parsed_hash[:circ_series][:series]
|
|
670
|
+
elsif parsed_hash[:series]
|
|
671
|
+
series_value = parsed_hash[:series]
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Handle date range supplement (no base)
|
|
675
|
+
if parsed_hash[:supplement_date_range].is_a?(Hash)
|
|
676
|
+
range = parsed_hash[:supplement_date_range]
|
|
677
|
+
month_start = range[:supp_month_start]&.to_s
|
|
678
|
+
year_start = range[:supp_year_start]&.to_s
|
|
679
|
+
month_end = range[:supp_month_end]&.to_s
|
|
680
|
+
year_end = range[:supp_year_end]&.to_s
|
|
681
|
+
|
|
682
|
+
supplement.supplement_date_range_start = "#{month_start}#{year_start}" if month_start && year_start
|
|
683
|
+
supplement.supplement_date_range_end = "#{month_end}#{year_end}" if month_end && year_end
|
|
684
|
+
|
|
685
|
+
return supplement
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# Build base identifier from base_portion (if present)
|
|
689
|
+
# If not present (because it was merged during parsing), use first_number
|
|
690
|
+
if parsed_hash[:base_portion]
|
|
691
|
+
# Extract the actual number value from base_portion hash
|
|
692
|
+
# base_portion can be: {:simple_number=>"118"}, {:base_number=>"145", :revision_letter=>"r", :revision_number=>"11"}, etc.
|
|
693
|
+
base_portion = parsed_hash[:base_portion]
|
|
694
|
+
base_number = if base_portion.is_a?(Hash)
|
|
695
|
+
# Extract the value from whichever key is present
|
|
696
|
+
base_portion[:simple_number] || base_portion[:base_number]
|
|
697
|
+
else
|
|
698
|
+
base_portion
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Check for letter suffix in base_portion (e.g., "378G")
|
|
702
|
+
letter_suffix = nil
|
|
703
|
+
if base_portion.is_a?(Hash) && base_portion[:letter_suffix]
|
|
704
|
+
letter_suffix = base_portion[:letter_suffix].to_s.upcase
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# Extract publisher from circ_series if present
|
|
708
|
+
publisher_value = nil
|
|
709
|
+
if parsed_hash[:circ_series].is_a?(Hash) && parsed_hash[:circ_series][:series]
|
|
710
|
+
series_str = parsed_hash[:circ_series][:series].to_s
|
|
711
|
+
# Extract publisher from series (e.g., "NBS LCIRC" -> "NBS")
|
|
712
|
+
publisher_value = series_str.split.first if series_str.include?(" ")
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# Check if base_portion has revision (for patterns like "145r11/1925")
|
|
716
|
+
has_revision = base_portion.is_a?(Hash) && base_portion[:revision_letter] && base_portion[:revision_number]
|
|
717
|
+
|
|
718
|
+
# NEW: Check if base_portion has edition_number (for patterns like "101e2")
|
|
719
|
+
has_edition = base_portion.is_a?(Hash) && base_portion[:edition_number]
|
|
720
|
+
|
|
721
|
+
# Include letter suffix in base_number if present
|
|
722
|
+
# Also include edition_number if present (for "101e2" pattern)
|
|
723
|
+
base_number_with_suffix = base_number.to_s
|
|
724
|
+
if letter_suffix
|
|
725
|
+
base_number_with_suffix += letter_suffix
|
|
726
|
+
end
|
|
727
|
+
if has_edition
|
|
728
|
+
base_number_with_suffix += "e#{base_portion[:edition_number]}"
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
# Reconstruct parse hash for base identifier
|
|
732
|
+
base_hash = {
|
|
733
|
+
series: series_value,
|
|
734
|
+
first_number: base_number_with_suffix,
|
|
735
|
+
parsed_format: parsed_hash[:parsed_format],
|
|
736
|
+
}
|
|
737
|
+
base_hash[:publisher] = publisher_value if publisher_value
|
|
738
|
+
|
|
739
|
+
# NEW: Add edition_number to base_hash for patterns like "101e2"
|
|
740
|
+
# This will be processed by the normal build() logic to create Edition component
|
|
741
|
+
if has_edition
|
|
742
|
+
# Create edition_e hash that will be converted to Edition with type="e"
|
|
743
|
+
base_hash[:edition_e] =
|
|
744
|
+
{ edition_id: base_portion[:edition_number] }
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
# Recursively build base identifier
|
|
748
|
+
# This will go through normal build() process which extracts edition from "101e2"
|
|
749
|
+
supplement.base_identifier = build(base_hash)
|
|
750
|
+
|
|
751
|
+
# NEW: Handle revision + implicit supplement pattern (e.g., "145r11/1925")
|
|
752
|
+
# Create update format: "Upd1-{year}{revision_number}"
|
|
753
|
+
if has_revision && parsed_hash[:implicit_supplement].is_a?(Hash)
|
|
754
|
+
revision_number = base_portion[:revision_number].to_s
|
|
755
|
+
supplement_year = parsed_hash[:implicit_supplement][:implicit_supplement_year].to_s
|
|
756
|
+
|
|
757
|
+
# Create Update component for revision+supplement pattern
|
|
758
|
+
# Format: Upd1-{year}{revision_number} (always use "1" and concatenate year+revision)
|
|
759
|
+
update_value = "Upd1-#{supplement_year}#{revision_number}"
|
|
760
|
+
supplement.update = update_value
|
|
761
|
+
supplement.implicit_supplement = true # Mark as implicit supplement for rendering
|
|
762
|
+
end
|
|
763
|
+
elsif parsed_hash[:first_number]
|
|
764
|
+
# base_portion was lost during merge, use first_number to build base identifier
|
|
765
|
+
base_hash = {
|
|
766
|
+
publisher: parsed_hash[:publisher],
|
|
767
|
+
series: series_value || parsed_hash[:series],
|
|
768
|
+
first_number: parsed_hash[:first_number],
|
|
769
|
+
parsed_format: parsed_hash[:parsed_format],
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
# Recursively build base identifier
|
|
773
|
+
supplement.base_identifier = build(base_hash)
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# Build supplement edition from captured data
|
|
777
|
+
if parsed_hash[:supplement_month_year]
|
|
778
|
+
# Parse month+year format like "Jan1924"
|
|
779
|
+
month_year = parsed_hash[:supplement_month_year].to_s
|
|
780
|
+
supplement.edition = Components::Edition.new(type: "s",
|
|
781
|
+
id: month_year)
|
|
782
|
+
elsif parsed_hash[:supplement_year]
|
|
783
|
+
# Just year: 1924
|
|
784
|
+
supplement.edition = Components::Edition.new(type: "s",
|
|
785
|
+
id: parsed_hash[:supplement_year].to_s)
|
|
786
|
+
elsif parsed_hash[:supplement_slash_year].is_a?(Hash)
|
|
787
|
+
# NEW: Handle supplement_slash_year pattern (e.g., "sup12/1926", "sup1/1927")
|
|
788
|
+
# V1 format: "Upd1-192612" where "1" is fixed and "192612" is year+number concatenated
|
|
789
|
+
# Single digit numbers are zero-padded: "sup1/1927" → "Upd1-192701"
|
|
790
|
+
supp_hash = parsed_hash[:supplement_slash_year]
|
|
791
|
+
supp_number = supp_hash[:supp_number]&.to_s
|
|
792
|
+
supp_year = supp_hash[:supp_year]&.to_s
|
|
793
|
+
|
|
794
|
+
# Pad supplement number to 2 digits for single-digit numbers
|
|
795
|
+
supp_number_padded = supp_number.rjust(2, "0")
|
|
796
|
+
|
|
797
|
+
# Create Update component for supplement (V1 compatibility uses Update for supplements)
|
|
798
|
+
# Format: Upd1-{year}{padded_number} (always use "1" and concatenate year+padded_number)
|
|
799
|
+
update_value = "Upd1-#{supp_year}#{supp_number_padded}"
|
|
800
|
+
supplement.update = update_value
|
|
801
|
+
elsif parsed_hash[:supplement_empty]
|
|
802
|
+
# Empty supplement - no edition
|
|
803
|
+
# supplement.edition remains nil
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
supplement
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
private
|
|
810
|
+
|
|
811
|
+
# Cast parsed value to appropriate component type
|
|
812
|
+
# ALL conversions happen in this single method
|
|
813
|
+
# @param type [Symbol] the parameter type
|
|
814
|
+
# @param value [Object] the parsed value
|
|
815
|
+
# @param parsed_hash [Hash] the full parsed hash for context
|
|
816
|
+
# @return [Object, Hash, nil] the cast component(s)
|
|
817
|
+
def cast(type, value, parsed_hash = {})
|
|
818
|
+
case type
|
|
819
|
+
when :publisher
|
|
820
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
821
|
+
|
|
822
|
+
Components::Publisher.new(publisher: value.to_s)
|
|
823
|
+
|
|
824
|
+
when :series
|
|
825
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
826
|
+
|
|
827
|
+
str_value = value.to_s
|
|
828
|
+
publisher_extracted = nil
|
|
829
|
+
|
|
830
|
+
# For compound series like "NBS CIRC", extract publisher and series separately
|
|
831
|
+
if str_value.start_with?("NBS ")
|
|
832
|
+
publisher_extracted = "NBS"
|
|
833
|
+
str_value = str_value.sub("NBS ", "")
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
# Return composite hash with both publisher and series if extracted
|
|
837
|
+
if publisher_extracted
|
|
838
|
+
{
|
|
839
|
+
publisher: Components::Publisher.new(publisher: publisher_extracted),
|
|
840
|
+
series: Components::Code.new(number: str_value),
|
|
841
|
+
}
|
|
842
|
+
else
|
|
843
|
+
Components::Code.new(number: str_value)
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
when :volume_number
|
|
847
|
+
# Volume from v#n# pattern - return Volume component
|
|
848
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
849
|
+
|
|
850
|
+
{ volume: Components::Volume.new(value: value.to_s) }
|
|
851
|
+
|
|
852
|
+
when :issue_number
|
|
853
|
+
# Issue number from v#n# pattern - return Part component
|
|
854
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
855
|
+
|
|
856
|
+
{ part: Components::Part.new(type: "n", value: value.to_s) }
|
|
857
|
+
|
|
858
|
+
when :part_number
|
|
859
|
+
# Part number from GCR pattern (e.g., 85-3273-37)
|
|
860
|
+
# Return raw value for inclusion in compound number
|
|
861
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
862
|
+
|
|
863
|
+
value # Return raw value to be tracked in builder
|
|
864
|
+
|
|
865
|
+
when :letter_number
|
|
866
|
+
# Letter number pattern (e.g., 800-56A, 1-1A for NCSTAR, 73-197Ur for IR)
|
|
867
|
+
# Parser returns: {:letter_base=>"56", :letter_suffix=>"A"} or
|
|
868
|
+
# {:letter_base=>"197", :letter_suffix=>"U", :letter_suffix_extra=>"r"}
|
|
869
|
+
# For SpecialPublication, create Part component with letter suffix as value
|
|
870
|
+
# For MONO and NCSTAR, preserve letter suffix as part of the number (return raw value)
|
|
871
|
+
return nil if value.nil? || !value.is_a?(Hash)
|
|
872
|
+
|
|
873
|
+
letter_suffix = value[:letter_suffix]&.to_s&.strip
|
|
874
|
+
letter_suffix_extra = value[:letter_suffix_extra]&.to_s&.strip
|
|
875
|
+
|
|
876
|
+
# Combine letter_suffix and letter_suffix_extra (e.g., "U" + "r" = "Ur")
|
|
877
|
+
full_suffix = if letter_suffix_extra && !letter_suffix_extra.empty?
|
|
878
|
+
letter_suffix + letter_suffix_extra
|
|
879
|
+
else
|
|
880
|
+
letter_suffix
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
return nil if full_suffix.nil? || full_suffix.empty?
|
|
884
|
+
|
|
885
|
+
# Check if this is a MONO or NCSTAR series
|
|
886
|
+
# For these series, the letter suffix should be part of the number, not a separate Part component
|
|
887
|
+
# For IR with "R" or "Ur" suffix, also return raw value so builder can convert to edition "r1"
|
|
888
|
+
is_mono = begin
|
|
889
|
+
parsed_hash[:series].to_s.include?("MONO")
|
|
890
|
+
rescue StandardError
|
|
891
|
+
false
|
|
892
|
+
end
|
|
893
|
+
is_ncstar = begin
|
|
894
|
+
parsed_hash[:series].to_s.include?("NCSTAR")
|
|
895
|
+
rescue StandardError
|
|
896
|
+
false
|
|
897
|
+
end
|
|
898
|
+
# IR with "R" suffix needs special handling (convert to edition "r1")
|
|
899
|
+
# Also handle "Ur" which combines uppercase U with lowercase r
|
|
900
|
+
is_ir_with_r = begin
|
|
901
|
+
parsed_hash[:series].to_s.include?("IR") && (letter_suffix == "R" || full_suffix == "Ur")
|
|
902
|
+
rescue StandardError
|
|
903
|
+
false
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
if is_mono || is_ncstar || is_ir_with_r
|
|
907
|
+
# For MONO and NCSTAR, preserve letter suffix as part of the number
|
|
908
|
+
# For IR with "R" or "Ur", return raw value so builder can convert "79-1786R" to "79-1786r1"
|
|
909
|
+
# Return raw value so builder can construct proper format
|
|
910
|
+
value[:letter_suffix] = full_suffix
|
|
911
|
+
value
|
|
912
|
+
else
|
|
913
|
+
# For SpecialPublication and others, create Part component
|
|
914
|
+
{ part: Components::Part.new(type: "", value: full_suffix.upcase) }
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
when :fips_part
|
|
918
|
+
# Part number from FIPS date pattern (e.g., 11-1-Sep30/1977)
|
|
919
|
+
# Return Part component with pt type
|
|
920
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
921
|
+
|
|
922
|
+
{ part: Components::Part.new(type: "pt", value: value.to_s) }
|
|
923
|
+
|
|
924
|
+
when :owmwp_date_number
|
|
925
|
+
# OWMWP date-based number format (MM-DD-YYYY)
|
|
926
|
+
# Parser returns: {:owmwp_month=>"06", :owmwp_day=>"13", :owmwp_year=>"2018"}
|
|
927
|
+
# Convert to number + edition: "06-13" + edition "e2018"
|
|
928
|
+
return nil if value.nil?
|
|
929
|
+
|
|
930
|
+
number_part = "#{value[:owmwp_month]}-#{value[:owmwp_day]}"
|
|
931
|
+
edition_part = Components::Edition.new(type: "e",
|
|
932
|
+
id: value[:owmwp_year])
|
|
933
|
+
{ first_number: Components::Code.new(number: number_part), edition: edition_part }
|
|
934
|
+
|
|
935
|
+
when :first_number, :second_number
|
|
936
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
937
|
+
|
|
938
|
+
# NEW: Handle OWMWP date-based number (nested hash structure)
|
|
939
|
+
# Parser returns: {:owmwp_date_number=>{:owmwp_month=>"06", :owmwp_day=>"13", :owmwp_year=>"2018"}}
|
|
940
|
+
# Convert to number + edition: "06-13" + edition "e2018"
|
|
941
|
+
if value.is_a?(Hash) && value[:owmwp_date_number]
|
|
942
|
+
owmwp_hash = value[:owmwp_date_number]
|
|
943
|
+
number_part = "#{owmwp_hash[:owmwp_month]}-#{owmwp_hash[:owmwp_day]}"
|
|
944
|
+
edition_part = Components::Edition.new(type: "e",
|
|
945
|
+
id: owmwp_hash[:owmwp_year])
|
|
946
|
+
return { type => Components::Code.new(number: number_part), edition: edition_part }
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# NEW: Handle second_number with edition (hash with :number_only and :edition_id)
|
|
950
|
+
# This handles "126r2013" pattern where parser returns {:number_only=>"126", :edition_id=>"2013"}
|
|
951
|
+
# CRITICAL: Wrap in a structure that builder loop can recognize
|
|
952
|
+
# The builder loop expects keys like :second_number to be present in the hash
|
|
953
|
+
if type == :second_number && value.is_a?(Hash) && value[:number_only] && value[:edition_id]
|
|
954
|
+
# Return wrapped hash so builder loop finds :second_number key
|
|
955
|
+
return { second_number: value }
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
# NEW: Handle second_number with revision_letter (hash with :revision_letter containing :number_only and :letter)
|
|
959
|
+
# This handles "27ra" pattern where parser returns {revision_letter: {number_only: "27", letter: "a"}}
|
|
960
|
+
# Should be combined to "27rA" format
|
|
961
|
+
if type == :second_number && value.is_a?(Hash) && value[:revision_letter]
|
|
962
|
+
revision_data = value[:revision_letter]
|
|
963
|
+
number_only = revision_data[:number_only].to_s
|
|
964
|
+
letter = revision_data[:letter].to_s.upcase
|
|
965
|
+
# Return as second_number with combined format "27rA"
|
|
966
|
+
return { second_number: Components::Code.new(number: "#{number_only}r#{letter}") }
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
# Handle v#n# pattern (CSM series) - comes as hash from parser
|
|
970
|
+
# Return Volume and Part components separately
|
|
971
|
+
if value.is_a?(Hash) && value[:volume_number] && value[:issue_number]
|
|
972
|
+
volume_num = value[:volume_number].to_s
|
|
973
|
+
issue_num = value[:issue_number].to_s
|
|
974
|
+
return {
|
|
975
|
+
volume: Components::Volume.new(value: volume_num),
|
|
976
|
+
part: Components::Part.new(type: "n", value: issue_num),
|
|
977
|
+
}
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
str_value = value.to_s
|
|
981
|
+
|
|
982
|
+
# Handle special patterns embedded in first_number
|
|
983
|
+
if type == :first_number
|
|
984
|
+
|
|
985
|
+
# NEW: Handle first_number hash with number_with_rev_year (e.g., "1013rv1953")
|
|
986
|
+
# Parser returns: {:number_with_rev_year=>{:number=>"1013", :revision_year=>"1953"}}
|
|
987
|
+
if value.is_a?(Hash) && value[:number_with_rev_year]
|
|
988
|
+
number_part = value[:number_with_rev_year][:number].to_s
|
|
989
|
+
revision_year = value[:number_with_rev_year][:revision_year].to_s
|
|
990
|
+
return {
|
|
991
|
+
first_number: Components::Code.new(number: number_part),
|
|
992
|
+
edition: Components::Edition.new(type: "rv", id: revision_year),
|
|
993
|
+
}
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
# NEW: Handle first_number hash with language_code (e.g., "1262es")
|
|
997
|
+
# Parser returns: {:number=>"1262", :language_code=>"es"}
|
|
998
|
+
if value.is_a?(Hash) && value[:number] && value[:language_code]
|
|
999
|
+
number_part = value[:number].to_s
|
|
1000
|
+
language_code = value[:language_code].to_s.strip.downcase
|
|
1001
|
+
# Apply normalization map (es → spa, pt → por, etc.)
|
|
1002
|
+
normalized_code = TRANSLATION_MAP[language_code] || language_code
|
|
1003
|
+
return {
|
|
1004
|
+
first_number: Components::Code.new(number: number_part),
|
|
1005
|
+
translation_component: Components::Translation.new(code: normalized_code),
|
|
1006
|
+
}
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# NEW: Handle first_number hash with number, part_number, and edition_year (MR format)
|
|
1010
|
+
# Parser returns: {:number=>"28", :part_number=>"1", :edition_year=>"1969"}
|
|
1011
|
+
# For "NBS.HB.28pt1e1969" MR format input
|
|
1012
|
+
if value.is_a?(Hash) && value[:number] && value[:part_number] && value[:edition_year]
|
|
1013
|
+
number_part = value[:number].to_s
|
|
1014
|
+
part_number = value[:part_number].to_s
|
|
1015
|
+
edition_year = value[:edition_year].to_s
|
|
1016
|
+
return {
|
|
1017
|
+
first_number: Components::Code.new(number: number_part),
|
|
1018
|
+
part: Components::Part.new(type: "pt", value: part_number),
|
|
1019
|
+
edition: Components::Edition.new(type: "e", id: edition_year),
|
|
1020
|
+
}
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# NEW: Check for edition_year_separate in parsed_hash context
|
|
1024
|
+
# This handles "11e2-1915" where first_number="11e2" and edition_year_separate="1915"
|
|
1025
|
+
if parsed_hash[:edition_year_separate] && str_value =~ /^(\d+)e(\d+)$/
|
|
1026
|
+
number_part = $1
|
|
1027
|
+
edition_id = $2
|
|
1028
|
+
year_part = parsed_hash[:edition_year_separate].to_s
|
|
1029
|
+
return {
|
|
1030
|
+
first_number: Components::Code.new(number: number_part),
|
|
1031
|
+
edition: Components::Edition.new(type: "e", id: edition_id,
|
|
1032
|
+
additional_text: year_part),
|
|
1033
|
+
}
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
# NEW: Check for number_with_volume in value hash (for first_number)
|
|
1037
|
+
# This handles "539v10" where parser captures :number and :volume_suffix separately
|
|
1038
|
+
# Parse tree: value = {:number_with_volume => {:number => "539", :volume_suffix => "10"}}
|
|
1039
|
+
if value.is_a?(Hash) && value[:number_with_volume] && value[:number_with_volume][:volume_suffix]
|
|
1040
|
+
number_part = value[:number_with_volume][:number].to_s
|
|
1041
|
+
volume_value = value[:number_with_volume][:volume_suffix].to_s
|
|
1042
|
+
return {
|
|
1043
|
+
first_number: Components::Code.new(number: number_part),
|
|
1044
|
+
volume: Components::Volume.new(value: volume_value),
|
|
1045
|
+
}
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# NEW: Check for historical_month and historical_year in parsed_hash context
|
|
1049
|
+
# This handles "-April1909" where it's captured as separate month/year
|
|
1050
|
+
if parsed_hash[:historical_month] && parsed_hash[:historical_year]
|
|
1051
|
+
month_part = parsed_hash[:historical_month].to_s
|
|
1052
|
+
year_part = parsed_hash[:historical_year].to_s
|
|
1053
|
+
# Check if str_value is just a number (the part before dash)
|
|
1054
|
+
if /^\d+$/.match?(str_value)
|
|
1055
|
+
return {
|
|
1056
|
+
first_number: Components::Code.new(number: str_value),
|
|
1057
|
+
edition: Components::Edition.new(type: "-",
|
|
1058
|
+
additional_text: "#{month_part}#{year_part}"),
|
|
1059
|
+
}
|
|
1060
|
+
else
|
|
1061
|
+
# No number, just historical edition
|
|
1062
|
+
return {
|
|
1063
|
+
edition: Components::Edition.new(type: "-",
|
|
1064
|
+
additional_text: "#{month_part}#{year_part}"),
|
|
1065
|
+
}
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
# NEW: Pattern "9350sup" - number with "sup" suffix (no year)
|
|
1070
|
+
# This handles RPT supplements like "NBS RPT 9350sup"
|
|
1071
|
+
if str_value =~ /^(\d+)sup$/
|
|
1072
|
+
return {
|
|
1073
|
+
first_number: Components::Code.new(number: $1),
|
|
1074
|
+
supplement: "",
|
|
1075
|
+
}
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
# NEW: Check for supplement_year in parsed_hash context
|
|
1079
|
+
# This handles "25supp-1924" where first_number="25supp" and supplement_year="1924"
|
|
1080
|
+
if parsed_hash[:supplement_year] && str_value =~ /^(\d+)supp?$/
|
|
1081
|
+
number_part = $1
|
|
1082
|
+
year_part = parsed_hash[:supplement_year].to_s
|
|
1083
|
+
return {
|
|
1084
|
+
first_number: Components::Code.new(number: number_part),
|
|
1085
|
+
supplement: year_part,
|
|
1086
|
+
}
|
|
1087
|
+
end
|
|
1088
|
+
|
|
1089
|
+
# Pattern: "154supprev" - supplement with revision
|
|
1090
|
+
if str_value =~ /^(\d+)supprev$/
|
|
1091
|
+
return {
|
|
1092
|
+
first_number: Components::Code.new(number: $1),
|
|
1093
|
+
supplement: "",
|
|
1094
|
+
supplement_has_revision: true,
|
|
1095
|
+
}
|
|
1096
|
+
# NEW: Pattern "11e2-1915" - edition with separate year (inline match)
|
|
1097
|
+
# Creates: number="11", Edition(type: "e", id: "2", additional_text: "1915")
|
|
1098
|
+
# Renders: "NBS CIRC 11e2.1915"
|
|
1099
|
+
elsif str_value =~ /^(\d+)e(\d+)-(\d{4})$/
|
|
1100
|
+
number_part = $1
|
|
1101
|
+
edition_id = $2
|
|
1102
|
+
year_part = $3
|
|
1103
|
+
return {
|
|
1104
|
+
first_number: Components::Code.new(number: number_part),
|
|
1105
|
+
edition: Components::Edition.new(type: "e", id: edition_id,
|
|
1106
|
+
additional_text: year_part),
|
|
1107
|
+
}
|
|
1108
|
+
# NEW: Pattern "-April1909" - historical edition with month+year (inline match)
|
|
1109
|
+
# Creates: Edition(type: "-", additional_text: "April1909")
|
|
1110
|
+
# Renders: "NBS CIRC -April1909"
|
|
1111
|
+
elsif str_value =~ /^-([A-Za-z]{3,9})(\d{4})$/
|
|
1112
|
+
month_part = $1
|
|
1113
|
+
year_part = $2
|
|
1114
|
+
return {
|
|
1115
|
+
edition: Components::Edition.new(type: "-",
|
|
1116
|
+
additional_text: "#{month_part}#{year_part}"),
|
|
1117
|
+
}
|
|
1118
|
+
# NEW: CS Emergency pattern "e104" or "e104-43" → extract number
|
|
1119
|
+
# This must come BEFORE bare edition check to avoid conflict
|
|
1120
|
+
# CS emergency always has 3+ digit number (e104, not e2)
|
|
1121
|
+
# NOTE: If second_number exists (e104-43 pattern), defer to compound number logic
|
|
1122
|
+
elsif /^e(\d{3,})$/.match?(str_value) && !parsed_hash[:second_number]
|
|
1123
|
+
# Extract emergency number: e104 → 104 (only when no second_number)
|
|
1124
|
+
emergency_num = str_value.sub(/^e/, "")
|
|
1125
|
+
return {
|
|
1126
|
+
first_number: Components::Code.new(number: emergency_num),
|
|
1127
|
+
}
|
|
1128
|
+
# If e104-43 pattern (with second_number), keep e prefix for compound number logic
|
|
1129
|
+
elsif /^e(\d{3,})$/.match?(str_value) && parsed_hash[:second_number]
|
|
1130
|
+
# Keep e104 as-is, let compound number logic handle it
|
|
1131
|
+
return {
|
|
1132
|
+
first_number: Components::Code.new(number: str_value),
|
|
1133
|
+
}
|
|
1134
|
+
# NEW: Bare edition pattern like "100e1" (CS series without year)
|
|
1135
|
+
# ONLY when NO second_number present (to avoid conflict with "123e2-50")
|
|
1136
|
+
# Creates: number="100", Edition(type: "e", id: "1")
|
|
1137
|
+
# Renders: "NBS CS 100e1"
|
|
1138
|
+
# CRITICAL: Skip if edition_dash_year is present - let that handler create Edition with additional_text
|
|
1139
|
+
elsif str_value =~ /^(\d+)e(\d+)$/ && !parsed_hash[:second_number] && !parsed_hash[:edition_dash_year]
|
|
1140
|
+
number_part = $1
|
|
1141
|
+
edition_id = $2
|
|
1142
|
+
|
|
1143
|
+
return {
|
|
1144
|
+
first_number: Components::Code.new(number: number_part),
|
|
1145
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1146
|
+
}
|
|
1147
|
+
# NEW: Bare edition pattern "e2" - just edition without number prefix
|
|
1148
|
+
# Creates: Edition(type: "e", id: "2")
|
|
1149
|
+
# Renders: "NBS CIRC e2"
|
|
1150
|
+
# Only matches single or double digit (e1, e2, not e104 which is emergency)
|
|
1151
|
+
elsif str_value =~ /^e(\d{1,2})$/
|
|
1152
|
+
edition_id = $1
|
|
1153
|
+
return {
|
|
1154
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1155
|
+
}
|
|
1156
|
+
# Pattern: "13e2rev1908" - edition with revision year-only (NO month)
|
|
1157
|
+
# Creates: Edition(type: "e", id: "2", additional_text: "1908")
|
|
1158
|
+
# Renders: "e2.1908" (DOT separator)
|
|
1159
|
+
elsif str_value =~ /^(\d+)e(\d+)rev(\d{4})$/
|
|
1160
|
+
# CRITICAL: Capture BEFORE any regex method calls!
|
|
1161
|
+
number_part = $1
|
|
1162
|
+
edition_id_part = $2
|
|
1163
|
+
year_part = $3
|
|
1164
|
+
return {
|
|
1165
|
+
first_number: Components::Code.new(number: number_part),
|
|
1166
|
+
edition: Components::Edition.new(type: "e",
|
|
1167
|
+
id: edition_id_part, additional_text: year_part),
|
|
1168
|
+
}
|
|
1169
|
+
# Pattern: "13e2revJune1908" - edition with revision month+year
|
|
1170
|
+
# Creates: Edition(type: "e", id: "2", additional_text: "June1908")
|
|
1171
|
+
# Renders: "e2.June1908" (DOT separator)
|
|
1172
|
+
elsif str_value =~ /^(\d+)e(\d+)(rev.+)$/
|
|
1173
|
+
# CRITICAL: Capture $1, $2, $3 BEFORE calling .sub() which resets them!
|
|
1174
|
+
number_part = $1
|
|
1175
|
+
edition_id_part = $2
|
|
1176
|
+
rev_part = $3
|
|
1177
|
+
# Strip "rev" prefix from additional_text - store only "June1908" or "1908"
|
|
1178
|
+
additional_text = rev_part.sub(/^rev/, "")
|
|
1179
|
+
return {
|
|
1180
|
+
first_number: Components::Code.new(number: number_part),
|
|
1181
|
+
edition: Components::Edition.new(type: "e",
|
|
1182
|
+
id: edition_id_part, additional_text: additional_text),
|
|
1183
|
+
}
|
|
1184
|
+
# NEW: Pattern "24suppJan1924" - supplement with month and year in first_number
|
|
1185
|
+
# Creates: number="24", supplement="Jan1924"
|
|
1186
|
+
elsif str_value =~ /^(\d+)supp([A-Za-z]{3,9})(\d{4})$/
|
|
1187
|
+
number_part = $1
|
|
1188
|
+
month_part = $2
|
|
1189
|
+
year_part = $3
|
|
1190
|
+
return {
|
|
1191
|
+
first_number: Components::Code.new(number: number_part),
|
|
1192
|
+
supplement: "#{month_part}#{year_part}",
|
|
1193
|
+
}
|
|
1194
|
+
# NEW: Pattern "25supp1924" - supplement with year (no dash, no month)
|
|
1195
|
+
# Creates: number="25", supplement="1924"
|
|
1196
|
+
# Renders: "NBS SP 25supp1924"
|
|
1197
|
+
elsif str_value =~ /^(\d+)supp(\d{4})$/
|
|
1198
|
+
number_part = $1
|
|
1199
|
+
year_part = $2
|
|
1200
|
+
return {
|
|
1201
|
+
first_number: Components::Code.new(number: number_part),
|
|
1202
|
+
supplement: year_part,
|
|
1203
|
+
}
|
|
1204
|
+
# NEW: Pattern "25supp-1924" - supplement with dash-year (inline match)
|
|
1205
|
+
# Creates: number="25", supplement="1924"
|
|
1206
|
+
# Renders: "NBS CIRC 25supp-1924"
|
|
1207
|
+
elsif str_value =~ /^(\d+)supp-(\d{4})$/
|
|
1208
|
+
number_part = $1
|
|
1209
|
+
year_part = $2
|
|
1210
|
+
return {
|
|
1211
|
+
first_number: Components::Code.new(number: number_part),
|
|
1212
|
+
supplement: year_part,
|
|
1213
|
+
}
|
|
1214
|
+
# NEW: Pattern "101e2supp" - edition + supplement
|
|
1215
|
+
# Creates: number="101", Edition(type: "e", id: "2"), supplement=""
|
|
1216
|
+
# Renders: "NBS CIRC 101e2supp"
|
|
1217
|
+
elsif str_value =~ /^(\d+)e(\d+)supp$/
|
|
1218
|
+
number_part = $1
|
|
1219
|
+
edition_id = $2
|
|
1220
|
+
return {
|
|
1221
|
+
first_number: Components::Code.new(number: number_part),
|
|
1222
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1223
|
+
supplement: "",
|
|
1224
|
+
}
|
|
1225
|
+
end
|
|
1226
|
+
elsif type == :second_number && value.is_a?(Hash) && value[:first_number]
|
|
1227
|
+
# Handle second_number as a hash with first_number context
|
|
1228
|
+
# e.g., for pattern 800-57pt1r4
|
|
1229
|
+
number_part = value[:first_number].to_s
|
|
1230
|
+
part_value = value[:part_value]&.to_s
|
|
1231
|
+
revision_value = value[:revision_value]&.to_s
|
|
1232
|
+
return {
|
|
1233
|
+
first_number: Components::Code.new(number: number_part),
|
|
1234
|
+
part: Components::Part.new(value: part_value),
|
|
1235
|
+
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
1236
|
+
}
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
# Extract revision suffix from number (e.g., "53r5" → "53" + Edition(r, 5))
|
|
1240
|
+
# ENHANCED: Also extract revision with slash-year (e.g., "53r5/1917" → "53" + Edition)
|
|
1241
|
+
# ENHANCED: Also extract revision with 4-digit year (e.g., "1019r1963" → "1019" + Edition)
|
|
1242
|
+
# ENHANCED: Also extract revision with month+year (e.g., "4743rJun1992" → "4743" + Edition)
|
|
1243
|
+
|
|
1244
|
+
# NEW: Extract part suffix from number (e.g., "800-57pt1" → "800-57" + Part(1))
|
|
1245
|
+
# This handles SP series part notation
|
|
1246
|
+
# IMPORTANT: Handle combined part+revision first (e.g., "800-57pt1r4")
|
|
1247
|
+
if str_value =~ /^(.+?)pt(\d+)r(\d+[a-z]?)$/
|
|
1248
|
+
number_part = $1
|
|
1249
|
+
part_value = $2
|
|
1250
|
+
revision_value = $3
|
|
1251
|
+
return {
|
|
1252
|
+
type => Components::Code.new(number: number_part),
|
|
1253
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
1254
|
+
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
1255
|
+
}
|
|
1256
|
+
elsif str_value =~ /^(.+?)pt(\d+)$/
|
|
1257
|
+
number_part = $1
|
|
1258
|
+
part_value = $2
|
|
1259
|
+
return {
|
|
1260
|
+
type => Components::Code.new(number: number_part),
|
|
1261
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
1262
|
+
}
|
|
1263
|
+
end
|
|
1264
|
+
|
|
1265
|
+
# NEW: Extract volume suffix from number (e.g., "539v10" → "539" + volume="10")
|
|
1266
|
+
# This handles CIRC volume notation
|
|
1267
|
+
if str_value =~ /^(\d+)v(\d+)$/
|
|
1268
|
+
number_part = $1
|
|
1269
|
+
volume_part = $2
|
|
1270
|
+
return {
|
|
1271
|
+
type => Components::Code.new(number: number_part),
|
|
1272
|
+
volume: volume_part,
|
|
1273
|
+
}
|
|
1274
|
+
end
|
|
1275
|
+
|
|
1276
|
+
# REVISION PATTERNS - These must come BEFORE letter suffix to avoid conflicts
|
|
1277
|
+
case str_value
|
|
1278
|
+
when /^(.+?)(r\d+\/\d{4})$/i
|
|
1279
|
+
# Pattern: r6/1925 (revision with slash-year)
|
|
1280
|
+
number_part = $1
|
|
1281
|
+
revision_with_year = $2 # e.g., "r6/1925"
|
|
1282
|
+
# Extract revision and year
|
|
1283
|
+
if revision_with_year =~ /^r(\d+)\/(\d{4})$/
|
|
1284
|
+
revision_id = $1
|
|
1285
|
+
year_part = $2
|
|
1286
|
+
return {
|
|
1287
|
+
type => Components::Code.new(number: number_part),
|
|
1288
|
+
edition: Components::Edition.new(type: "r", id: revision_id,
|
|
1289
|
+
additional_text: year_part),
|
|
1290
|
+
}
|
|
1291
|
+
end
|
|
1292
|
+
when /^(.+?)(r\d{4})$/i
|
|
1293
|
+
# Pattern: r1963 (revision as 4-digit year)
|
|
1294
|
+
number_part = $1
|
|
1295
|
+
year_value = $2.sub(/^r/, "") # Strip 'r' prefix
|
|
1296
|
+
return {
|
|
1297
|
+
type => Components::Code.new(number: number_part),
|
|
1298
|
+
edition: Components::Edition.new(type: "r", id: year_value),
|
|
1299
|
+
}
|
|
1300
|
+
when /^(.+?)(r[A-Za-z]{3,9}\d{4})$/i
|
|
1301
|
+
# Pattern: rJun1992 (revision with month and year)
|
|
1302
|
+
number_part = $1
|
|
1303
|
+
revision_with_date = $2 # e.g., "rJun1992"
|
|
1304
|
+
# Extract month and year
|
|
1305
|
+
if revision_with_date =~ /^r([A-Za-z]{3,9})(\d{4})$/
|
|
1306
|
+
month_part = $1
|
|
1307
|
+
year_part = $2
|
|
1308
|
+
return {
|
|
1309
|
+
type => Components::Code.new(number: number_part),
|
|
1310
|
+
edition: Components::Edition.new(type: "r",
|
|
1311
|
+
id: "#{month_part}#{year_part}"),
|
|
1312
|
+
}
|
|
1313
|
+
end
|
|
1314
|
+
when /^(.+?)(r\d+[a-z]?)$/i
|
|
1315
|
+
# Pattern: r5, r1a (simple revision)
|
|
1316
|
+
number_part = $1
|
|
1317
|
+
revision_value = $2.sub(/^r/, "") # Strip 'r' prefix
|
|
1318
|
+
return {
|
|
1319
|
+
type => Components::Code.new(number: number_part),
|
|
1320
|
+
edition: Components::Edition.new(type: "r", id: revision_value),
|
|
1321
|
+
}
|
|
1322
|
+
when /^(.+?)(?<![a-zA-Z])(r)$/i
|
|
1323
|
+
# Pattern: bare r with no digits (e.g., "800-90r")
|
|
1324
|
+
# Negative lookbehind ensures r is NOT preceded by a letter (avoids matching Ur, Ua, etc.)
|
|
1325
|
+
number_part = $1
|
|
1326
|
+
return {
|
|
1327
|
+
type => Components::Code.new(number: number_part),
|
|
1328
|
+
edition: Components::Edition.new(type: "r", id: "1"),
|
|
1329
|
+
}
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
# NEW: Extract UPPERCASE letter suffix as Part component (e.g., "800-56A" → "800-56" + Part)
|
|
1333
|
+
# IMPORTANT: These patterns come AFTER revision patterns to avoid conflicts
|
|
1334
|
+
# Letter suffixes are UPPERCASE letters A-Z only (no lowercase to avoid revision markers)
|
|
1335
|
+
|
|
1336
|
+
# Pattern: UPPERCASE letter + revision (e.g., "800-56Ar2" → number + Part("", "A") + Edition(r, 2))
|
|
1337
|
+
# NO /i flag - only match uppercase letters!
|
|
1338
|
+
if str_value =~ /^(.+?)([A-Z])(r\d+[a-z]?)$/
|
|
1339
|
+
number_part = $1
|
|
1340
|
+
letter_part = $2
|
|
1341
|
+
revision_part = $3.sub(/^r/, "")
|
|
1342
|
+
return {
|
|
1343
|
+
type => Components::Code.new(number: number_part),
|
|
1344
|
+
part: Components::Part.new(type: "", value: letter_part),
|
|
1345
|
+
edition: Components::Edition.new(type: "r", id: revision_part),
|
|
1346
|
+
}
|
|
1347
|
+
# Pattern: bare UPPERCASE letter suffix (e.g., "800-56A" → number + Part("", "A"))
|
|
1348
|
+
# Only matches uppercase letters - won't match revision markers
|
|
1349
|
+
# IMPORTANT: For MR format preservation, keep letter suffix as part of number
|
|
1350
|
+
# IMPORTANT: For Report, FIPS, IR, and LC series, preserve letter suffix as part of number
|
|
1351
|
+
elsif str_value =~ /^(.+?)([A-Z])$/
|
|
1352
|
+
number_part = $1
|
|
1353
|
+
letter_part = $2
|
|
1354
|
+
# Check if we should preserve letter suffix in number
|
|
1355
|
+
# Check for specific series that need letter suffix preserved
|
|
1356
|
+
is_report = begin
|
|
1357
|
+
parsed_hash[:series].to_s.include?("RPT")
|
|
1358
|
+
rescue StandardError
|
|
1359
|
+
false
|
|
1360
|
+
end
|
|
1361
|
+
is_fips = begin
|
|
1362
|
+
parsed_hash[:series].to_s.include?("FIPS")
|
|
1363
|
+
rescue StandardError
|
|
1364
|
+
false
|
|
1365
|
+
end
|
|
1366
|
+
is_ir = begin
|
|
1367
|
+
parsed_hash[:series].to_s.include?("IR")
|
|
1368
|
+
rescue StandardError
|
|
1369
|
+
false
|
|
1370
|
+
end
|
|
1371
|
+
is_crpl = begin
|
|
1372
|
+
parsed_hash[:series].to_s.include?("CRPL")
|
|
1373
|
+
rescue StandardError
|
|
1374
|
+
false
|
|
1375
|
+
end
|
|
1376
|
+
is_mono = begin
|
|
1377
|
+
parsed_hash[:series].to_s.include?("MONO")
|
|
1378
|
+
rescue StandardError
|
|
1379
|
+
false
|
|
1380
|
+
end
|
|
1381
|
+
is_mp = begin
|
|
1382
|
+
parsed_hash[:series].to_s.include?("MP")
|
|
1383
|
+
rescue StandardError
|
|
1384
|
+
false
|
|
1385
|
+
end
|
|
1386
|
+
# Check for LC but exclude LCIRC (Letter Circular uses LC, not LCIRC)
|
|
1387
|
+
is_lc = begin
|
|
1388
|
+
parsed_hash[:series].to_s.include?("LC") && !parsed_hash[:series].to_s.include?("LCIRC")
|
|
1389
|
+
rescue StandardError
|
|
1390
|
+
false
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1393
|
+
if parsed_hash[:parsed_format] == :mr || is_report || is_fips || is_ir || is_crpl || is_lc || is_mono || is_mp
|
|
1394
|
+
# For MR format, Report, FIPS, IR, CRPL, LC, MONO, and MP, preserve letter suffix as part of number
|
|
1395
|
+
return { type => Components::Code.new(number: str_value) }
|
|
1396
|
+
else
|
|
1397
|
+
# For other formats, extract letter suffix as separate Part component
|
|
1398
|
+
return {
|
|
1399
|
+
type => Components::Code.new(number: number_part),
|
|
1400
|
+
part: Components::Part.new(type: "", value: letter_part),
|
|
1401
|
+
}
|
|
1402
|
+
end
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
Components::Code.new(number: str_value)
|
|
1406
|
+
|
|
1407
|
+
when :crpl_range
|
|
1408
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1409
|
+
|
|
1410
|
+
# For CRPL range patterns like "2_3-1" or "2_3-1A" (with supplement)
|
|
1411
|
+
# Format: X_Y-Z where X,Y,Z are digits, optional trailing letter is supplement
|
|
1412
|
+
# This should split into:
|
|
1413
|
+
# - X → second_number (to combine with first_number as "1-X")
|
|
1414
|
+
# - Y-Z → Part component (with type "pt" for CRPL)
|
|
1415
|
+
# - trailing letter (if present) → Supplement
|
|
1416
|
+
str_value = value.to_s
|
|
1417
|
+
|
|
1418
|
+
# Check for supplement letter suffix (e.g., "2_3-1A" → supplement="A")
|
|
1419
|
+
if str_value =~ /^(\d+)_(\d+-\d+)([A-Z])$/
|
|
1420
|
+
second_num_part = $1 # "2"
|
|
1421
|
+
part_value = $2 # "3-1"
|
|
1422
|
+
supplement_letter = $3 # "A"
|
|
1423
|
+
|
|
1424
|
+
# Return second_number, Part, and Supplement
|
|
1425
|
+
{
|
|
1426
|
+
second_number: Components::Code.new(number: second_num_part),
|
|
1427
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
1428
|
+
supplement: supplement_letter,
|
|
1429
|
+
}
|
|
1430
|
+
elsif str_value =~ /^(\d+)_(\d+-\d+)$/
|
|
1431
|
+
# No supplement letter
|
|
1432
|
+
second_num_part = $1 # "2"
|
|
1433
|
+
part_value = $2 # "3-1"
|
|
1434
|
+
|
|
1435
|
+
# Return second_number and Part
|
|
1436
|
+
{
|
|
1437
|
+
second_number: Components::Code.new(number: second_num_part),
|
|
1438
|
+
part: Components::Part.new(type: "pt", value: part_value),
|
|
1439
|
+
}
|
|
1440
|
+
else
|
|
1441
|
+
# Fallback: treat entire value as second_number (shouldn't happen with valid CRPL patterns)
|
|
1442
|
+
Components::Code.new(number: str_value)
|
|
1443
|
+
end
|
|
1444
|
+
|
|
1445
|
+
# ========== V2 COMPONENT CASTING ==========
|
|
1446
|
+
|
|
1447
|
+
when :stage
|
|
1448
|
+
# Stage from nested hash with id and type
|
|
1449
|
+
return nil unless value.is_a?(Hash)
|
|
1450
|
+
|
|
1451
|
+
stage_id = value[:stage_id]&.to_s&.downcase
|
|
1452
|
+
stage_type = value[:stage_type]&.to_s&.downcase
|
|
1453
|
+
return nil if stage_id.nil? || stage_type.nil? || stage_id.empty? || stage_type.empty?
|
|
1454
|
+
|
|
1455
|
+
# Return as hash to set the stage attribute
|
|
1456
|
+
{ stage: Components::Stage.new(id: stage_id, type: stage_type) }
|
|
1457
|
+
|
|
1458
|
+
when :stage_id, :stage_type
|
|
1459
|
+
# These are captured by :stage, so skip individual processing
|
|
1460
|
+
nil
|
|
1461
|
+
|
|
1462
|
+
when :parsed_format
|
|
1463
|
+
# Format detection result from parser
|
|
1464
|
+
value&.to_s
|
|
1465
|
+
|
|
1466
|
+
when :translation
|
|
1467
|
+
# V1 TRANSLATION NORMALIZATION
|
|
1468
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1469
|
+
|
|
1470
|
+
code = value.to_s.strip.downcase
|
|
1471
|
+
# Apply normalization map (es → spa, pt → por, etc.)
|
|
1472
|
+
normalized_code = TRANSLATION_MAP[code] || code
|
|
1473
|
+
|
|
1474
|
+
# Return as hash to set translation_component attribute
|
|
1475
|
+
{ translation_component: Components::Translation.new(code: normalized_code) }
|
|
1476
|
+
|
|
1477
|
+
when :version
|
|
1478
|
+
# Version component with dotted notation
|
|
1479
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1480
|
+
|
|
1481
|
+
# Return as hash to set version_component attribute
|
|
1482
|
+
{ version_component: Components::Version.new(value: value.to_s) }
|
|
1483
|
+
|
|
1484
|
+
when :update
|
|
1485
|
+
# Update component with number, year, and optional month
|
|
1486
|
+
if value.is_a?(Hash)
|
|
1487
|
+
# Convert Parslet slice to regular Hash for reliable key access
|
|
1488
|
+
value_hash = value.to_h
|
|
1489
|
+
|
|
1490
|
+
number = value_hash[:update_number]&.to_s # Don't default to "1"
|
|
1491
|
+
year = value_hash[:update_year]&.to_s # String not integer
|
|
1492
|
+
month = value_hash[:update_month]&.to_s # String not integer
|
|
1493
|
+
|
|
1494
|
+
# Determine prefix from update_prefix key (captured by parser)
|
|
1495
|
+
# If not present, default to "slash" (/Upd format)
|
|
1496
|
+
prefix_str = value_hash[:update_prefix]&.to_s
|
|
1497
|
+
prefix_value = if prefix_str&.include?("-") || prefix_str == "-upd"
|
|
1498
|
+
"dash"
|
|
1499
|
+
else
|
|
1500
|
+
"slash"
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
# Create update with at least number
|
|
1504
|
+
update_obj = Components::Update.new(number: number, year: year,
|
|
1505
|
+
month: month, prefix: prefix_value)
|
|
1506
|
+
{
|
|
1507
|
+
update: update_obj, # Main attribute for tests
|
|
1508
|
+
update_component: update_obj, # V2 component
|
|
1509
|
+
}
|
|
1510
|
+
elsif value.to_s.strip.empty?
|
|
1511
|
+
# Empty update string means "-upd" or "/upd" with no details
|
|
1512
|
+
# Create Update with default number="1" (no year/month)
|
|
1513
|
+
# Check update_prefix key to determine correct prefix format
|
|
1514
|
+
prefix_str = parsed_hash[:update_prefix]&.to_s
|
|
1515
|
+
prefix_value = if prefix_str&.include?("-") || prefix_str == "-upd"
|
|
1516
|
+
"dash"
|
|
1517
|
+
else
|
|
1518
|
+
"slash"
|
|
1519
|
+
end
|
|
1520
|
+
update_obj = Components::Update.new(number: "1", year: nil,
|
|
1521
|
+
month: nil, prefix: prefix_value)
|
|
1522
|
+
{
|
|
1523
|
+
update: update_obj,
|
|
1524
|
+
update_component: update_obj,
|
|
1525
|
+
}
|
|
1526
|
+
else
|
|
1527
|
+
# Simple string value - shouldn't reach here
|
|
1528
|
+
{ update: value.to_s.strip } unless value.to_s.strip.empty?
|
|
1529
|
+
end
|
|
1530
|
+
|
|
1531
|
+
when :update_prefix, :update_number, :update_year, :update_month
|
|
1532
|
+
# Captured as part of :update processing
|
|
1533
|
+
nil
|
|
1534
|
+
|
|
1535
|
+
# ========== END V2 COMPONENTS ==========
|
|
1536
|
+
|
|
1537
|
+
when :volume, :section, :appendix, :translation,
|
|
1538
|
+
:errata, :index, :insert, :version
|
|
1539
|
+
return nil if value.nil?
|
|
1540
|
+
return nil if value.is_a?(Array) && value.empty?
|
|
1541
|
+
|
|
1542
|
+
str_value = value.to_s.strip
|
|
1543
|
+
return nil if str_value.empty?
|
|
1544
|
+
|
|
1545
|
+
# For volume, create Volume component from string value
|
|
1546
|
+
# This handles patterns like "v1" that come from parser as simple strings
|
|
1547
|
+
if type == :volume
|
|
1548
|
+
{ volume: Components::Volume.new(value: str_value) }
|
|
1549
|
+
else
|
|
1550
|
+
str_value
|
|
1551
|
+
end
|
|
1552
|
+
|
|
1553
|
+
when :revision
|
|
1554
|
+
# Revision MUST be Edition component with type "r"
|
|
1555
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1556
|
+
|
|
1557
|
+
# Handle new structure with :revision_prefix and :revision_id (format preservation)
|
|
1558
|
+
if value.is_a?(Hash) && value[:revision_prefix] && value[:revision_id]
|
|
1559
|
+
prefix = value[:revision_prefix].to_s
|
|
1560
|
+
id = value[:revision_id].to_s.strip
|
|
1561
|
+
|
|
1562
|
+
# Normalize bare "r" → "r1"
|
|
1563
|
+
revision_id = if id.empty? || id == "r" || id == "R"
|
|
1564
|
+
"1"
|
|
1565
|
+
# Handle "r4", "R5", "4" etc. (but prefix already has the r/rev/etc.)
|
|
1566
|
+
elsif id =~ /^(\d+[a-z]?)$/
|
|
1567
|
+
$1
|
|
1568
|
+
else
|
|
1569
|
+
id
|
|
1570
|
+
end
|
|
1571
|
+
|
|
1572
|
+
# Return Edition component with original_prefix for format preservation
|
|
1573
|
+
{
|
|
1574
|
+
edition: Components::Edition.new(type: "r", id: revision_id,
|
|
1575
|
+
original_prefix: prefix),
|
|
1576
|
+
}
|
|
1577
|
+
else
|
|
1578
|
+
# Legacy handling: revision as simple string value
|
|
1579
|
+
str_value = value.to_s.strip
|
|
1580
|
+
|
|
1581
|
+
# Handle bare "r" → normalize to "r1"
|
|
1582
|
+
revision_id = if str_value.empty? || str_value == "r" || str_value == "R"
|
|
1583
|
+
"1"
|
|
1584
|
+
# Handle "r4", "R5", "4" etc.
|
|
1585
|
+
elsif str_value =~ /^[rR]?(\d+[a-z]?)$/
|
|
1586
|
+
$1
|
|
1587
|
+
else
|
|
1588
|
+
str_value
|
|
1589
|
+
end
|
|
1590
|
+
|
|
1591
|
+
# Return Edition component (no original_prefix available)
|
|
1592
|
+
{
|
|
1593
|
+
edition: Components::Edition.new(type: "r", id: revision_id),
|
|
1594
|
+
}
|
|
1595
|
+
end
|
|
1596
|
+
|
|
1597
|
+
when :revision_year, :revision_month
|
|
1598
|
+
# When revision_year comes from parser as separate element (e.g., "1019 r1963")
|
|
1599
|
+
# Create Edition component
|
|
1600
|
+
if type == :revision_year
|
|
1601
|
+
year_value = value.to_s.strip
|
|
1602
|
+
# Check if this should be an Edition component or legacy revision_year
|
|
1603
|
+
# If revision_month is also present, use legacy attributes for "revJune1908" pattern
|
|
1604
|
+
if parsed_hash[:revision_month]
|
|
1605
|
+
# Legacy: revision with month - keep as revision_year/revision_month
|
|
1606
|
+
year_value
|
|
1607
|
+
else
|
|
1608
|
+
# V2: revision with year only - create Edition component
|
|
1609
|
+
{
|
|
1610
|
+
edition: Components::Edition.new(type: "r", id: year_value),
|
|
1611
|
+
}
|
|
1612
|
+
end
|
|
1613
|
+
else
|
|
1614
|
+
# revision_month - preserve as string for legacy rendering
|
|
1615
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1616
|
+
|
|
1617
|
+
value.to_s.strip
|
|
1618
|
+
end
|
|
1619
|
+
|
|
1620
|
+
when :edition_year_separate
|
|
1621
|
+
# NEW: Edition year from "e2-1915" pattern (captured separately by parser)
|
|
1622
|
+
# This comes with first_number like "11e2" and separate year "1915"
|
|
1623
|
+
# Already handled in first_number regex matching above, but if it reaches here
|
|
1624
|
+
# as a separate capture, we need to process it
|
|
1625
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1626
|
+
|
|
1627
|
+
value.to_s # Return as string for potential use
|
|
1628
|
+
|
|
1629
|
+
when :historical_month
|
|
1630
|
+
# NEW: Historical month from "-April1909" pattern
|
|
1631
|
+
# Handled in first_number pattern matching, but return as string if separate
|
|
1632
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1633
|
+
|
|
1634
|
+
value.to_s
|
|
1635
|
+
|
|
1636
|
+
when :historical_year
|
|
1637
|
+
# NEW: Historical year from "-April1909" pattern
|
|
1638
|
+
# Handled in first_number pattern matching, but return as string if separate
|
|
1639
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1640
|
+
|
|
1641
|
+
value.to_s
|
|
1642
|
+
|
|
1643
|
+
when :supplement_year
|
|
1644
|
+
# NEW: Supplement year from "supp-1924" pattern (captured separately by parser)
|
|
1645
|
+
# This comes with first_number like "25supp" and separate year "1924"
|
|
1646
|
+
# Already handled in first_number regex matching above, but if it reaches here
|
|
1647
|
+
# as a separate capture, return as supplement value
|
|
1648
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1649
|
+
|
|
1650
|
+
{ supplement: value.to_s } # Return as supplement attribute
|
|
1651
|
+
|
|
1652
|
+
when :supplement
|
|
1653
|
+
handle_supplement_cast(value)
|
|
1654
|
+
|
|
1655
|
+
when :supplement_date_range
|
|
1656
|
+
return nil unless value.is_a?(Hash)
|
|
1657
|
+
|
|
1658
|
+
month_start = value[:supp_month_start]&.to_s
|
|
1659
|
+
year_start = value[:supp_year_start]&.to_s
|
|
1660
|
+
month_end = value[:supp_month_end]&.to_s
|
|
1661
|
+
year_end = value[:supp_year_end]&.to_s
|
|
1662
|
+
|
|
1663
|
+
{
|
|
1664
|
+
supplement_date_range_start: (month_start && year_start ? "#{month_start}#{year_start}" : nil),
|
|
1665
|
+
supplement_date_range_end: (month_end && year_end ? "#{month_end}#{year_end}" : nil),
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
when :supplement_date
|
|
1669
|
+
return nil unless value.is_a?(Hash)
|
|
1670
|
+
|
|
1671
|
+
month = value[:supp_month]&.to_s
|
|
1672
|
+
year = value[:supp_year]&.to_s
|
|
1673
|
+
|
|
1674
|
+
month && year ? "#{month}#{year}" : nil
|
|
1675
|
+
|
|
1676
|
+
when :supplement_slash_year
|
|
1677
|
+
return nil unless value.is_a?(Hash)
|
|
1678
|
+
|
|
1679
|
+
number = value[:supp_number]&.to_s
|
|
1680
|
+
year = value[:supp_year]&.to_s
|
|
1681
|
+
|
|
1682
|
+
number && year ? "#{number}/#{year}" : nil
|
|
1683
|
+
|
|
1684
|
+
when :supplement_with_rev
|
|
1685
|
+
{ supplement: "", supplement_has_revision: true }
|
|
1686
|
+
|
|
1687
|
+
when :supp_year
|
|
1688
|
+
# Parser extracts supplement year from patterns like "187supp1924"
|
|
1689
|
+
# This should set the supplement attribute with the year value
|
|
1690
|
+
{ supplement: value.to_s }
|
|
1691
|
+
|
|
1692
|
+
# ========== V2 EDITION COMPONENT ==========
|
|
1693
|
+
|
|
1694
|
+
when :edition_e_date
|
|
1695
|
+
# Edition with "e" prefix + 6-digit date (YYYYMM): e199206, e202103
|
|
1696
|
+
# Used for IR revision+month patterns after preprocessing: "4743rJun1992" → "4743e199206"
|
|
1697
|
+
return nil unless value.is_a?(Hash) && value[:edition_date]
|
|
1698
|
+
|
|
1699
|
+
edition_date = value[:edition_date].to_s
|
|
1700
|
+
# Parse 6-digit date as YYYYMM
|
|
1701
|
+
# Store as id directly - renders as "e199206"
|
|
1702
|
+
{
|
|
1703
|
+
edition: Components::Edition.new(type: "e", id: edition_date),
|
|
1704
|
+
edition_component: Components::Edition.new(type: "e",
|
|
1705
|
+
id: edition_date),
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
when :edition_e
|
|
1709
|
+
# Edition with "e" prefix: e2, e2021
|
|
1710
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1711
|
+
|
|
1712
|
+
edition_id = value[:edition_id].to_s
|
|
1713
|
+
|
|
1714
|
+
{
|
|
1715
|
+
edition: Components::Edition.new(type: "e", id: edition_id),
|
|
1716
|
+
edition_component: Components::Edition.new(type: "e",
|
|
1717
|
+
id: edition_id),
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
when :edition_r
|
|
1721
|
+
# Revision with "r" prefix: r5, r2021
|
|
1722
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1723
|
+
|
|
1724
|
+
edition_id = value[:edition_id].to_s
|
|
1725
|
+
|
|
1726
|
+
{
|
|
1727
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1728
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1729
|
+
id: edition_id),
|
|
1730
|
+
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
when :edition_r_no_space
|
|
1734
|
+
# Revision with "r" prefix (no space pattern): r2, r5
|
|
1735
|
+
# Used for patterns like "800-56Ar2" where edition is "r2"
|
|
1736
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1737
|
+
|
|
1738
|
+
edition_id = value[:edition_id].to_s
|
|
1739
|
+
|
|
1740
|
+
{
|
|
1741
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1742
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1743
|
+
id: edition_id),
|
|
1744
|
+
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
when :edition_rev
|
|
1748
|
+
# Revision with "rev" prefix (verbose): rev2013, rev 2013
|
|
1749
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1750
|
+
|
|
1751
|
+
edition_id = value[:edition_id].to_s
|
|
1752
|
+
|
|
1753
|
+
{
|
|
1754
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1755
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1756
|
+
id: edition_id),
|
|
1757
|
+
revision: "r#{edition_id}", # Also set revision string attribute for compatibility
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
when :edition_r_letter
|
|
1761
|
+
# Revision with "r" prefix and letter suffix: r1a, r2b (for SP patterns like 800-22r1a)
|
|
1762
|
+
return nil unless value.is_a?(Hash) && value[:edition_id] && value[:edition_letter]
|
|
1763
|
+
|
|
1764
|
+
edition_id = value[:edition_id].to_s
|
|
1765
|
+
edition_letter = value[:edition_letter].to_s.downcase
|
|
1766
|
+
|
|
1767
|
+
{
|
|
1768
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1769
|
+
additional_text: edition_letter),
|
|
1770
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1771
|
+
id: edition_id,
|
|
1772
|
+
additional_text: edition_letter),
|
|
1773
|
+
revision: "r#{edition_id}#{edition_letter}", # Also set revision string attribute for compatibility
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
when :edition_r_letter_only
|
|
1777
|
+
# Revision with "r" prefix and only letter (no digit): ra, rb (for SP patterns like 800-27ra)
|
|
1778
|
+
return nil unless value.is_a?(Hash) && value[:edition_letter]
|
|
1779
|
+
|
|
1780
|
+
edition_letter = value[:edition_letter].to_s.downcase
|
|
1781
|
+
|
|
1782
|
+
{
|
|
1783
|
+
edition: Components::Edition.new(type: "r", id: edition_letter),
|
|
1784
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1785
|
+
id: edition_letter),
|
|
1786
|
+
revision: "r#{edition_letter}", # Also set revision string attribute for compatibility
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
when :edition_historical
|
|
1790
|
+
# Historical with "-" prefix: -3, -4
|
|
1791
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1792
|
+
|
|
1793
|
+
edition_id = value[:edition_id].to_s
|
|
1794
|
+
|
|
1795
|
+
{
|
|
1796
|
+
edition: Components::Edition.new(type: "-", id: edition_id),
|
|
1797
|
+
edition_component: Components::Edition.new(type: "-",
|
|
1798
|
+
id: edition_id),
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
when :edition_r_with_space_letter
|
|
1802
|
+
# Revision with "r" prefix, space, and letter: r 5A (format preservation)
|
|
1803
|
+
# Used for patterns like "NIST SP 800-53 r5A"
|
|
1804
|
+
# NOTE: If there's an update component, the space was added by preprocessing
|
|
1805
|
+
return nil unless value.is_a?(Hash) && value[:edition_id] && value[:edition_letter]
|
|
1806
|
+
|
|
1807
|
+
edition_id = value[:edition_id].to_s
|
|
1808
|
+
edition_letter = value[:edition_letter].to_s.upcase
|
|
1809
|
+
|
|
1810
|
+
# Check if this is an embedded edition with update (space added by preprocessing)
|
|
1811
|
+
has_update = parsed_hash[:update_prefix] || parsed_hash[:update]
|
|
1812
|
+
|
|
1813
|
+
if has_update
|
|
1814
|
+
# No original_prefix - space was added by preprocessing
|
|
1815
|
+
{
|
|
1816
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1817
|
+
additional_text: edition_letter),
|
|
1818
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1819
|
+
id: edition_id,
|
|
1820
|
+
additional_text: edition_letter),
|
|
1821
|
+
revision: "r#{edition_id}#{edition_letter}",
|
|
1822
|
+
}
|
|
1823
|
+
else
|
|
1824
|
+
# Space was in original input - preserve format
|
|
1825
|
+
{
|
|
1826
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1827
|
+
additional_text: edition_letter,
|
|
1828
|
+
original_prefix: " r"),
|
|
1829
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1830
|
+
id: edition_id,
|
|
1831
|
+
additional_text: edition_letter,
|
|
1832
|
+
original_prefix: " r"),
|
|
1833
|
+
revision: "r#{edition_id}#{edition_letter}",
|
|
1834
|
+
}
|
|
1835
|
+
end
|
|
1836
|
+
|
|
1837
|
+
when :edition_r_with_space
|
|
1838
|
+
# Revision with "r" prefix and space: r 5 (format preservation)
|
|
1839
|
+
# Used for patterns like "NIST SP 800-53 r5"
|
|
1840
|
+
# NOTE: If there's an update component, the space was added by preprocessing
|
|
1841
|
+
# for patterns like "8115r1/upd" → "8115 r1/upd", so don't set original_prefix
|
|
1842
|
+
return nil unless value.is_a?(Hash) && value[:edition_id]
|
|
1843
|
+
|
|
1844
|
+
edition_id = value[:edition_id].to_s
|
|
1845
|
+
|
|
1846
|
+
# Check if this is an embedded edition with update (space added by preprocessing)
|
|
1847
|
+
# Patterns like "8115r1/upd" become "8115 r1/upd" after preprocessing
|
|
1848
|
+
has_update = parsed_hash[:update_prefix] || parsed_hash[:update]
|
|
1849
|
+
|
|
1850
|
+
if has_update
|
|
1851
|
+
# No original_prefix - space was added by preprocessing
|
|
1852
|
+
{
|
|
1853
|
+
edition: Components::Edition.new(type: "r", id: edition_id),
|
|
1854
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1855
|
+
id: edition_id),
|
|
1856
|
+
revision: "r#{edition_id}",
|
|
1857
|
+
}
|
|
1858
|
+
else
|
|
1859
|
+
# Space was in original input - preserve format
|
|
1860
|
+
{
|
|
1861
|
+
edition: Components::Edition.new(type: "r", id: edition_id,
|
|
1862
|
+
original_prefix: " r"),
|
|
1863
|
+
edition_component: Components::Edition.new(type: "r",
|
|
1864
|
+
id: edition_id,
|
|
1865
|
+
original_prefix: " r"),
|
|
1866
|
+
revision: "r#{edition_id}",
|
|
1867
|
+
}
|
|
1868
|
+
end
|
|
1869
|
+
|
|
1870
|
+
when :edition_id
|
|
1871
|
+
# Captured by edition_e, edition_r, edition_rev, edition_historical
|
|
1872
|
+
nil
|
|
1873
|
+
|
|
1874
|
+
when :edition_date
|
|
1875
|
+
# Captured by edition_e_date
|
|
1876
|
+
nil
|
|
1877
|
+
|
|
1878
|
+
# ========== LEGACY EDITION (for migration) ==========
|
|
1879
|
+
|
|
1880
|
+
when :legacy_edition
|
|
1881
|
+
# Legacy edition patterns - will be phased out
|
|
1882
|
+
# For now, map to old edition_year/edition_month attributes
|
|
1883
|
+
nil # Handled by existing edition_year logic below
|
|
1884
|
+
|
|
1885
|
+
when :edition_month, :edition_year, :edition_day, :edition_has_rev
|
|
1886
|
+
# These work together: edition_month + edition_year → single edition ID
|
|
1887
|
+
# Skip processing if this is edition_month alone (will be processed with edition_year)
|
|
1888
|
+
return nil if type == :edition_month
|
|
1889
|
+
|
|
1890
|
+
# Process edition_year, combining with edition_month if present
|
|
1891
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1892
|
+
|
|
1893
|
+
# Build the edition ID from year and optional month
|
|
1894
|
+
edition_id = value.to_s # Start with year (e.g., "1985")
|
|
1895
|
+
|
|
1896
|
+
# Add month if present (e.g., "Mar" → "03", so "1985" + "03" = "198503")
|
|
1897
|
+
# For FIPS with day: "Sep30/1977" → "19770930" (year + month + day)
|
|
1898
|
+
if parsed_hash[:edition_month]
|
|
1899
|
+
month_str = parsed_hash[:edition_month].to_s
|
|
1900
|
+
month_num = Date::ABBR_MONTHNAMES.index(month_str) ||
|
|
1901
|
+
Date::MONTHNAMES.index(month_str) ||
|
|
1902
|
+
month_str.to_i
|
|
1903
|
+
if month_num&.positive?
|
|
1904
|
+
# Check if this is FIPS series - FIPS uses number format (e198503), not month abbreviations
|
|
1905
|
+
# For historical NBS documents, preserve month name: "April1909" not "190904"
|
|
1906
|
+
is_fips = parsed_hash[:series]&.to_s == "FIPS"
|
|
1907
|
+
if !is_fips && month_str.match?(/^[A-Z][a-z]+/) && edition_id.to_s.match?(/^\d{4}$/)
|
|
1908
|
+
# Historical NBS month+year format: preserve month name, use "-" type for special rendering
|
|
1909
|
+
edition_obj = Components::Edition.new(
|
|
1910
|
+
type: "-",
|
|
1911
|
+
id: "",
|
|
1912
|
+
additional_text: "#{month_str}#{edition_id}",
|
|
1913
|
+
)
|
|
1914
|
+
return {
|
|
1915
|
+
edition: edition_obj,
|
|
1916
|
+
edition_component: edition_obj,
|
|
1917
|
+
edition_year: edition_id.to_s,
|
|
1918
|
+
}
|
|
1919
|
+
else
|
|
1920
|
+
# Modern format (and FIPS): combine year and month as single number: 1985 + 03 = 198503
|
|
1921
|
+
edition_id = "#{edition_id}#{format('%02d', month_num)}"
|
|
1922
|
+
|
|
1923
|
+
# For FIPS with day, append day as well: "Sep30/1977" → "19770930"
|
|
1924
|
+
if is_fips && parsed_hash[:edition_day]
|
|
1925
|
+
day_num = parsed_hash[:edition_day].to_s.to_i
|
|
1926
|
+
if day_num.positive? && day_num <= 31
|
|
1927
|
+
edition_id = "#{edition_id}#{format('%02d', day_num)}"
|
|
1928
|
+
end
|
|
1929
|
+
end
|
|
1930
|
+
end
|
|
1931
|
+
end
|
|
1932
|
+
end
|
|
1933
|
+
|
|
1934
|
+
# Create Edition component with type="e" (edition) and combined ID
|
|
1935
|
+
edition_obj = Components::Edition.new(type: "e", id: edition_id)
|
|
1936
|
+
|
|
1937
|
+
# Return as hash to set edition and edition_year
|
|
1938
|
+
{
|
|
1939
|
+
edition: edition_obj, # Main attribute for tests
|
|
1940
|
+
edition_component: edition_obj, # V2 component
|
|
1941
|
+
edition_year: value.to_s, # Keep string for render logic
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
when :part
|
|
1945
|
+
# Part component - handle part number with optional addendum
|
|
1946
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1947
|
+
|
|
1948
|
+
str_value = value.to_s.strip
|
|
1949
|
+
|
|
1950
|
+
# Pattern: "1adde1" → Part(value: "1"), addendum=true
|
|
1951
|
+
# Note: eN after add is discarded (not included in output per fixture)
|
|
1952
|
+
if str_value =~ /^(\d+)add(e\d+)$/
|
|
1953
|
+
{
|
|
1954
|
+
part: Components::Part.new(type: "pt", value: $1),
|
|
1955
|
+
addendum: "true",
|
|
1956
|
+
}
|
|
1957
|
+
elsif str_value =~ /^(\d+)add/
|
|
1958
|
+
{
|
|
1959
|
+
part: Components::Part.new(type: "pt", value: $1),
|
|
1960
|
+
addendum: "true",
|
|
1961
|
+
}
|
|
1962
|
+
else
|
|
1963
|
+
# Just a part number - return Part component with pt type
|
|
1964
|
+
{ part: Components::Part.new(type: "pt", value: str_value) }
|
|
1965
|
+
end
|
|
1966
|
+
|
|
1967
|
+
when :part_extracted
|
|
1968
|
+
# Legacy - this is now handled by :part
|
|
1969
|
+
nil
|
|
1970
|
+
|
|
1971
|
+
when :edition_letter
|
|
1972
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
1973
|
+
|
|
1974
|
+
value.to_s
|
|
1975
|
+
|
|
1976
|
+
when :public_draft
|
|
1977
|
+
return nil if value.nil?
|
|
1978
|
+
|
|
1979
|
+
value.to_s
|
|
1980
|
+
|
|
1981
|
+
when :draft
|
|
1982
|
+
# Extract draft number from "-draft N" pattern for pd rendering
|
|
1983
|
+
return nil if value.nil?
|
|
1984
|
+
|
|
1985
|
+
str_value = value.to_s.strip
|
|
1986
|
+
return nil if str_value.empty?
|
|
1987
|
+
|
|
1988
|
+
# Pattern: " -draft 2" or "-draft 2" → extract "2" for pd rendering
|
|
1989
|
+
if str_value =~ /^\s*-draft\s+(\d+)$/
|
|
1990
|
+
{ draft_number: $1 }
|
|
1991
|
+
# Pattern: " 2pd" → already in pd format
|
|
1992
|
+
elsif str_value =~ /^\s*(\d+)pd$/
|
|
1993
|
+
{ public_draft: $1 }
|
|
1994
|
+
# Other patterns (parenthetical, simple -draft)
|
|
1995
|
+
else
|
|
1996
|
+
str_value
|
|
1997
|
+
end
|
|
1998
|
+
|
|
1999
|
+
when :update
|
|
2000
|
+
handle_update_cast(value)
|
|
2001
|
+
|
|
2002
|
+
when :update_number, :update_year
|
|
2003
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
2004
|
+
|
|
2005
|
+
value.to_s
|
|
2006
|
+
|
|
2007
|
+
when :addendum
|
|
2008
|
+
handle_addendum_cast(value)
|
|
2009
|
+
|
|
2010
|
+
when :addendum_number
|
|
2011
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
2012
|
+
|
|
2013
|
+
value.to_s
|
|
2014
|
+
|
|
2015
|
+
when :supplement_suffix
|
|
2016
|
+
# Return as hash to set supplement attribute (not supplement_suffix)
|
|
2017
|
+
{ supplement: value.to_s }
|
|
2018
|
+
|
|
2019
|
+
when :date
|
|
2020
|
+
# Date component per NIST spec
|
|
2021
|
+
return nil unless value.is_a?(Hash)
|
|
2022
|
+
|
|
2023
|
+
# NEW: Check if this is historical edition pattern ("-April1909")
|
|
2024
|
+
# Parser captures as date with month + year, but semantically it's an edition
|
|
2025
|
+
if value[:date_month] && value[:date_year] && !value[:date_day]
|
|
2026
|
+
month_str = value[:date_month].to_s
|
|
2027
|
+
year_str = value[:date_year].to_s
|
|
2028
|
+
# If month is a word like "April", this is historical edition format
|
|
2029
|
+
if month_str.match?(/^[A-Za-z]+$/)
|
|
2030
|
+
return {
|
|
2031
|
+
edition: Components::Edition.new(type: "-",
|
|
2032
|
+
additional_text: "#{month_str}#{year_str}"),
|
|
2033
|
+
}
|
|
2034
|
+
end
|
|
2035
|
+
end
|
|
2036
|
+
|
|
2037
|
+
# Regular date processing
|
|
2038
|
+
value[:date_year]&.to_s
|
|
2039
|
+
value[:date_month]&.to_s
|
|
2040
|
+
value[:date_day]&.to_s
|
|
2041
|
+
|
|
2042
|
+
else
|
|
2043
|
+
# Unknown types: return the original value for default processing
|
|
2044
|
+
# This allows hashes with arbitrary structures to be processed
|
|
2045
|
+
# e.g., second_number hash with number_only and edition_id
|
|
2046
|
+
value.is_a?(Hash) ? value : nil
|
|
2047
|
+
end
|
|
2048
|
+
end
|
|
2049
|
+
|
|
2050
|
+
# Handle supplement casting with all its variants
|
|
2051
|
+
def handle_supplement_cast(value)
|
|
2052
|
+
return nil unless value
|
|
2053
|
+
|
|
2054
|
+
if value.is_a?(Array) && value.empty?
|
|
2055
|
+
# Empty array means "supp" was present but no suffix
|
|
2056
|
+
""
|
|
2057
|
+
else
|
|
2058
|
+
str_value = value.to_s.strip
|
|
2059
|
+
str_value.empty? ? nil : str_value
|
|
2060
|
+
end
|
|
2061
|
+
end
|
|
2062
|
+
|
|
2063
|
+
# Handle update casting (number and year)
|
|
2064
|
+
def handle_update_cast(value)
|
|
2065
|
+
if value.is_a?(Hash)
|
|
2066
|
+
{
|
|
2067
|
+
update_number: value[:update_number]&.to_s,
|
|
2068
|
+
update_year: value[:update_year]&.to_s,
|
|
2069
|
+
}.compact
|
|
2070
|
+
elsif value.to_s.strip.empty?
|
|
2071
|
+
# Empty update string (just "-upd" with no details)
|
|
2072
|
+
# Don't create update component - not enough data
|
|
2073
|
+
nil
|
|
2074
|
+
else
|
|
2075
|
+
str_value = value.to_s.strip
|
|
2076
|
+
str_value.empty? ? nil : str_value
|
|
2077
|
+
end
|
|
2078
|
+
end
|
|
2079
|
+
|
|
2080
|
+
# Handle addendum casting (number)
|
|
2081
|
+
def handle_addendum_cast(value)
|
|
2082
|
+
if value.is_a?(Hash)
|
|
2083
|
+
addendum_num = value[:addendum_number]&.to_s&.strip
|
|
2084
|
+
if addendum_num && !addendum_num.empty?
|
|
2085
|
+
{ addendum_number: addendum_num }
|
|
2086
|
+
else
|
|
2087
|
+
{ addendum: "true" }
|
|
2088
|
+
end
|
|
2089
|
+
else
|
|
2090
|
+
str_value = value.to_s.strip
|
|
2091
|
+
if str_value.empty?
|
|
2092
|
+
{ addendum: "true" }
|
|
2093
|
+
else
|
|
2094
|
+
{ addendum_number: str_value }
|
|
2095
|
+
end
|
|
2096
|
+
end
|
|
2097
|
+
end
|
|
2098
|
+
end
|
|
2099
|
+
end
|
|
2100
|
+
end
|