pubid 1.15.19 → 2.0.0.pre.alpha.2

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.
Files changed (604) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.adoc +2041 -53
  4. data/archived-gems/pubid-ccsds/update_codes.yaml +1 -0
  5. data/archived-gems/pubid-iec/stages.yaml +129 -0
  6. data/archived-gems/pubid-iec/update_codes.yaml +67 -0
  7. data/archived-gems/pubid-ieee/update_codes.yaml +104 -0
  8. data/archived-gems/pubid-iso/stages.yaml +106 -0
  9. data/archived-gems/pubid-iso/update_codes.yaml +4 -0
  10. data/archived-gems/pubid-itu/i18n.yaml +13 -0
  11. data/archived-gems/pubid-itu/series.yaml +42 -0
  12. data/archived-gems/pubid-nist/publishers.yaml +6 -0
  13. data/archived-gems/pubid-nist/series.yaml +121 -0
  14. data/archived-gems/pubid-nist/stages.yaml +16 -0
  15. data/archived-gems/pubid-nist/update_codes.yaml +93 -0
  16. data/archived-gems/pubid-plateau/update_codes.yaml +6 -0
  17. data/data/ccsds/update_codes.yaml +1 -0
  18. data/data/iec/update_codes.yaml +67 -0
  19. data/data/ieee/update_codes.yaml +104 -0
  20. data/data/iso/update_codes.yaml +21 -0
  21. data/data/nist/update_codes.yaml +89 -0
  22. data/data/plateau/update_codes.yaml +6 -0
  23. data/lib/pubid/amca/builder.rb +176 -0
  24. data/lib/pubid/amca/identifier.rb +57 -0
  25. data/lib/pubid/amca/identifiers/base.rb +64 -0
  26. data/lib/pubid/amca/identifiers/interpretation.rb +51 -0
  27. data/lib/pubid/amca/identifiers/publication.rb +47 -0
  28. data/lib/pubid/amca/identifiers/standard.rb +22 -0
  29. data/lib/pubid/amca/identifiers.rb +12 -0
  30. data/lib/pubid/amca/parser.rb +153 -0
  31. data/lib/pubid/amca/scheme.rb +16 -0
  32. data/lib/pubid/amca/single_identifier.rb +33 -0
  33. data/lib/pubid/amca/urn_generator.rb +50 -0
  34. data/lib/pubid/amca.rb +26 -0
  35. data/lib/pubid/ansi/builder.rb +52 -0
  36. data/lib/pubid/ansi/identifier.rb +55 -0
  37. data/lib/pubid/ansi/identifiers/american_national_standard.rb +12 -0
  38. data/lib/pubid/ansi/identifiers/standard.rb +16 -0
  39. data/lib/pubid/ansi/identifiers.rb +11 -0
  40. data/lib/pubid/ansi/parser.rb +91 -0
  41. data/lib/pubid/ansi/scheme.rb +15 -0
  42. data/lib/pubid/ansi/single_identifier.rb +45 -0
  43. data/lib/pubid/ansi/urn_generator.rb +76 -0
  44. data/lib/pubid/ansi.rb +27 -0
  45. data/lib/pubid/api/builder.rb +85 -0
  46. data/lib/pubid/api/components/code.rb +9 -0
  47. data/lib/pubid/api/identifier.rb +68 -0
  48. data/lib/pubid/api/identifiers/base.rb +24 -0
  49. data/lib/pubid/api/identifiers/bulletin.rb +15 -0
  50. data/lib/pubid/api/identifiers/continuous_operations_standard.rb +15 -0
  51. data/lib/pubid/api/identifiers/mpms.rb +44 -0
  52. data/lib/pubid/api/identifiers/publication.rb +15 -0
  53. data/lib/pubid/api/identifiers/recommended_practice.rb +15 -0
  54. data/lib/pubid/api/identifiers/specification.rb +15 -0
  55. data/lib/pubid/api/identifiers/standard.rb +15 -0
  56. data/lib/pubid/api/identifiers/technical_report.rb +15 -0
  57. data/lib/pubid/api/identifiers/typeless_standard.rb +27 -0
  58. data/lib/pubid/api/parser.rb +140 -0
  59. data/lib/pubid/api/scheme.rb +66 -0
  60. data/lib/pubid/api/single_identifier.rb +46 -0
  61. data/lib/pubid/api/urn_generator.rb +41 -0
  62. data/lib/pubid/api.rb +17 -0
  63. data/lib/pubid/ashrae/builder.rb +498 -0
  64. data/lib/pubid/ashrae/identifier.rb +57 -0
  65. data/lib/pubid/ashrae/identifiers/addenda_package.rb +46 -0
  66. data/lib/pubid/ashrae/identifiers/addendum.rb +55 -0
  67. data/lib/pubid/ashrae/identifiers/base.rb +23 -0
  68. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +51 -0
  69. data/lib/pubid/ashrae/identifiers/errata.rb +40 -0
  70. data/lib/pubid/ashrae/identifiers/guideline.rb +38 -0
  71. data/lib/pubid/ashrae/identifiers/interpretation.rb +39 -0
  72. data/lib/pubid/ashrae/identifiers/standard.rb +38 -0
  73. data/lib/pubid/ashrae/identifiers.rb +16 -0
  74. data/lib/pubid/ashrae/parser.rb +724 -0
  75. data/lib/pubid/ashrae/scheme.rb +53 -0
  76. data/lib/pubid/ashrae/single_identifier.rb +23 -0
  77. data/lib/pubid/ashrae/supplement_identifier.rb +23 -0
  78. data/lib/pubid/ashrae/urn_generator.rb +59 -0
  79. data/lib/pubid/ashrae.rb +21 -0
  80. data/lib/pubid/asme/builder.rb +153 -0
  81. data/lib/pubid/asme/components/code.rb +18 -0
  82. data/lib/pubid/asme/identifier.rb +61 -0
  83. data/lib/pubid/asme/identifiers/base.rb +70 -0
  84. data/lib/pubid/asme/identifiers/standard.rb +12 -0
  85. data/lib/pubid/asme/identifiers.rb +10 -0
  86. data/lib/pubid/asme/parser.rb +308 -0
  87. data/lib/pubid/asme/scheme.rb +37 -0
  88. data/lib/pubid/asme/single_identifier.rb +29 -0
  89. data/lib/pubid/asme/urn_generator.rb +133 -0
  90. data/lib/pubid/asme.rb +21 -0
  91. data/lib/pubid/astm/builder.rb +159 -0
  92. data/lib/pubid/astm/components/code.rb +33 -0
  93. data/lib/pubid/astm/identifier.rb +92 -0
  94. data/lib/pubid/astm/identifiers/adjunct.rb +21 -0
  95. data/lib/pubid/astm/identifiers/base.rb +13 -0
  96. data/lib/pubid/astm/identifiers/data_series.rb +25 -0
  97. data/lib/pubid/astm/identifiers/iso_dual_published.rb +74 -0
  98. data/lib/pubid/astm/identifiers/manual.rb +40 -0
  99. data/lib/pubid/astm/identifiers/monograph.rb +25 -0
  100. data/lib/pubid/astm/identifiers/research_report.rb +18 -0
  101. data/lib/pubid/astm/identifiers/standard.rb +52 -0
  102. data/lib/pubid/astm/identifiers/technical_report.rb +23 -0
  103. data/lib/pubid/astm/identifiers/work_in_progress.rb +21 -0
  104. data/lib/pubid/astm/parser.rb +244 -0
  105. data/lib/pubid/astm/scheme.rb +55 -0
  106. data/lib/pubid/astm/single_identifier.rb +25 -0
  107. data/lib/pubid/astm/urn_generator.rb +99 -0
  108. data/lib/pubid/astm.rb +38 -0
  109. data/lib/pubid/bsi/builder.rb +1483 -0
  110. data/lib/pubid/bsi/components/code.rb +11 -0
  111. data/lib/pubid/bsi/components/date.rb +11 -0
  112. data/lib/pubid/bsi/components/publisher.rb +11 -0
  113. data/lib/pubid/bsi/components/type.rb +11 -0
  114. data/lib/pubid/bsi/identifier.rb +87 -0
  115. data/lib/pubid/bsi/identifiers/addendum_document.rb +64 -0
  116. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +95 -0
  117. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +82 -0
  118. data/lib/pubid/bsi/identifiers/aerospace_standard.rb +118 -0
  119. data/lib/pubid/bsi/identifiers/amendment.rb +40 -0
  120. data/lib/pubid/bsi/identifiers/base.rb +11 -0
  121. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +27 -0
  122. data/lib/pubid/bsi/identifiers/british_standard.rb +33 -0
  123. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +114 -0
  124. data/lib/pubid/bsi/identifiers/committee_document.rb +51 -0
  125. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +152 -0
  126. data/lib/pubid/bsi/identifiers/corrigendum.rb +28 -0
  127. data/lib/pubid/bsi/identifiers/detailed_specification.rb +69 -0
  128. data/lib/pubid/bsi/identifiers/disc.rb +56 -0
  129. data/lib/pubid/bsi/identifiers/draft_document.rb +71 -0
  130. data/lib/pubid/bsi/identifiers/electronic_book.rb +52 -0
  131. data/lib/pubid/bsi/identifiers/expert_commentary.rb +47 -0
  132. data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +82 -0
  133. data/lib/pubid/bsi/identifiers/flex.rb +61 -0
  134. data/lib/pubid/bsi/identifiers/handbook.rb +39 -0
  135. data/lib/pubid/bsi/identifiers/index.rb +62 -0
  136. data/lib/pubid/bsi/identifiers/method.rb +76 -0
  137. data/lib/pubid/bsi/identifiers/national_annex.rb +73 -0
  138. data/lib/pubid/bsi/identifiers/practice_guide.rb +27 -0
  139. data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +79 -0
  140. data/lib/pubid/bsi/identifiers/published_document.rb +79 -0
  141. data/lib/pubid/bsi/identifiers/section.rb +62 -0
  142. data/lib/pubid/bsi/identifiers/set.rb +46 -0
  143. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +40 -0
  144. data/lib/pubid/bsi/identifiers/supplement_document.rb +51 -0
  145. data/lib/pubid/bsi/identifiers/supplementary_index.rb +81 -0
  146. data/lib/pubid/bsi/identifiers/technical_specification.rb +79 -0
  147. data/lib/pubid/bsi/identifiers/test_method.rb +67 -0
  148. data/lib/pubid/bsi/identifiers/value_added_publication.rb +52 -0
  149. data/lib/pubid/bsi/identifiers.rb +52 -0
  150. data/lib/pubid/bsi/model.rb +196 -0
  151. data/lib/pubid/bsi/parser.rb +659 -0
  152. data/lib/pubid/bsi/scheme.rb +243 -0
  153. data/lib/pubid/bsi/single_identifier.rb +129 -0
  154. data/lib/pubid/bsi/urn_generator.rb +84 -0
  155. data/lib/pubid/bsi.rb +32 -0
  156. data/lib/pubid/builder/base.rb +138 -0
  157. data/lib/pubid/bundled_identifier.rb +126 -0
  158. data/lib/pubid/ccsds/builder.rb +56 -0
  159. data/lib/pubid/ccsds/identifier.rb +84 -0
  160. data/lib/pubid/ccsds/identifiers/base.rb +89 -0
  161. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +70 -0
  162. data/lib/pubid/ccsds/identifiers/corrigendum.rb +39 -0
  163. data/lib/pubid/ccsds/identifiers.rb +10 -0
  164. data/lib/pubid/ccsds/parser.rb +71 -0
  165. data/lib/pubid/ccsds/scheme.rb +57 -0
  166. data/lib/pubid/ccsds/single_identifier.rb +77 -0
  167. data/lib/pubid/ccsds/supplement_identifier.rb +33 -0
  168. data/lib/pubid/ccsds/urn_generator.rb +115 -0
  169. data/lib/pubid/ccsds.rb +21 -0
  170. data/lib/pubid/cen_cenelec/builder.rb +330 -0
  171. data/lib/pubid/cen_cenelec/identifier.rb +52 -0
  172. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +40 -0
  173. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +29 -0
  174. data/lib/pubid/cen_cenelec/identifiers/base.rb +75 -0
  175. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +28 -0
  176. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +27 -0
  177. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +28 -0
  178. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +61 -0
  179. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +35 -0
  180. data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +41 -0
  181. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +37 -0
  182. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +28 -0
  183. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +22 -0
  184. data/lib/pubid/cen_cenelec/identifiers/guide.rb +27 -0
  185. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +27 -0
  186. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +27 -0
  187. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +35 -0
  188. data/lib/pubid/cen_cenelec/identifiers.rb +32 -0
  189. data/lib/pubid/cen_cenelec/parser.rb +144 -0
  190. data/lib/pubid/cen_cenelec/scheme.rb +164 -0
  191. data/lib/pubid/cen_cenelec/single_identifier.rb +130 -0
  192. data/lib/pubid/cen_cenelec/supplement_identifier.rb +48 -0
  193. data/lib/pubid/cen_cenelec/urn_generator.rb +129 -0
  194. data/lib/pubid/cen_cenelec.rb +21 -0
  195. data/lib/pubid/cie/builder.rb +399 -0
  196. data/lib/pubid/cie/components/code.rb +72 -0
  197. data/lib/pubid/cie/components/language.rb +58 -0
  198. data/lib/pubid/cie/identifier.rb +71 -0
  199. data/lib/pubid/cie/identifiers/bundle.rb +20 -0
  200. data/lib/pubid/cie/identifiers/conference.rb +32 -0
  201. data/lib/pubid/cie/identifiers/corrigendum.rb +40 -0
  202. data/lib/pubid/cie/identifiers/dual_published.rb +41 -0
  203. data/lib/pubid/cie/identifiers/identical.rb +64 -0
  204. data/lib/pubid/cie/identifiers/joint_published.rb +52 -0
  205. data/lib/pubid/cie/identifiers/standard.rb +58 -0
  206. data/lib/pubid/cie/identifiers/supplement.rb +45 -0
  207. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +20 -0
  208. data/lib/pubid/cie/identifiers.rb +17 -0
  209. data/lib/pubid/cie/parser.rb +347 -0
  210. data/lib/pubid/cie/scheme.rb +64 -0
  211. data/lib/pubid/cie/single_identifier.rb +30 -0
  212. data/lib/pubid/cie/supplement_identifier.rb +26 -0
  213. data/lib/pubid/cie/urn_generator.rb +123 -0
  214. data/lib/pubid/cie.rb +28 -0
  215. data/lib/pubid/components/code.rb +33 -0
  216. data/lib/pubid/components/date.rb +49 -0
  217. data/lib/pubid/components/edition.rb +32 -0
  218. data/lib/pubid/components/factory.rb +50 -0
  219. data/lib/pubid/components/language.rb +37 -0
  220. data/lib/pubid/components/locality.rb +10 -0
  221. data/lib/pubid/components/publisher.rb +36 -0
  222. data/lib/pubid/components/stage.rb +54 -0
  223. data/lib/pubid/components/type.rb +58 -0
  224. data/lib/pubid/components/typed_stage.rb +59 -0
  225. data/lib/pubid/components.rb +16 -0
  226. data/lib/pubid/core/pattern_doc_generator.rb +272 -0
  227. data/lib/pubid/core/update_codes.rb +77 -0
  228. data/lib/pubid/core.rb +8 -0
  229. data/lib/pubid/csa/builder.rb +671 -0
  230. data/lib/pubid/csa/components/code.rb +9 -0
  231. data/lib/pubid/csa/components.rb +9 -0
  232. data/lib/pubid/csa/composite_identifier.rb +27 -0
  233. data/lib/pubid/csa/identifier.rb +513 -0
  234. data/lib/pubid/csa/identifiers/base.rb +133 -0
  235. data/lib/pubid/csa/identifiers/bundled.rb +125 -0
  236. data/lib/pubid/csa/identifiers/canadian_adopted.rb +82 -0
  237. data/lib/pubid/csa/identifiers/cec.rb +129 -0
  238. data/lib/pubid/csa/identifiers/combined.rb +130 -0
  239. data/lib/pubid/csa/identifiers/csa_adopted.rb +78 -0
  240. data/lib/pubid/csa/identifiers/package.rb +65 -0
  241. data/lib/pubid/csa/identifiers/series.rb +127 -0
  242. data/lib/pubid/csa/identifiers/standard.rb +10 -0
  243. data/lib/pubid/csa/identifiers.rb +17 -0
  244. data/lib/pubid/csa/parser.rb +445 -0
  245. data/lib/pubid/csa/scheme.rb +44 -0
  246. data/lib/pubid/csa/single_identifier.rb +30 -0
  247. data/lib/pubid/csa/urn_generator.rb +80 -0
  248. data/lib/pubid/csa/wrapper_identifier.rb +31 -0
  249. data/lib/pubid/csa.rb +25 -0
  250. data/lib/pubid/etsi/builder.rb +133 -0
  251. data/lib/pubid/etsi/components/code.rb +42 -0
  252. data/lib/pubid/etsi/components/version.rb +37 -0
  253. data/lib/pubid/etsi/components.rb +10 -0
  254. data/lib/pubid/etsi/identifier.rb +57 -0
  255. data/lib/pubid/etsi/identifiers/amendment.rb +15 -0
  256. data/lib/pubid/etsi/identifiers/base.rb +38 -0
  257. data/lib/pubid/etsi/identifiers/corrigendum.rb +15 -0
  258. data/lib/pubid/etsi/identifiers/etsi_standard.rb +19 -0
  259. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +91 -0
  260. data/lib/pubid/etsi/identifiers.rb +14 -0
  261. data/lib/pubid/etsi/parser.rb +133 -0
  262. data/lib/pubid/etsi/scheme.rb +42 -0
  263. data/lib/pubid/etsi/urn_generator.rb +76 -0
  264. data/lib/pubid/etsi.rb +21 -0
  265. data/lib/pubid/export/auditor.rb +89 -0
  266. data/lib/pubid/export/data_class_exporter.rb +59 -0
  267. data/lib/pubid/export/exporter.rb +74 -0
  268. data/lib/pubid/export/flavor_exporter.rb +402 -0
  269. data/lib/pubid/export/ieee_exporter.rb +78 -0
  270. data/lib/pubid/export/itu_exporter.rb +66 -0
  271. data/lib/pubid/export/nist_exporter.rb +64 -0
  272. data/lib/pubid/export/registry_exporter.rb +90 -0
  273. data/lib/pubid/export/result.rb +97 -0
  274. data/lib/pubid/export/scheme_exporter.rb +70 -0
  275. data/lib/pubid/export.rb +18 -0
  276. data/lib/pubid/format_detector.rb +16 -0
  277. data/lib/pubid/format_registry.rb +42 -0
  278. data/lib/pubid/identifier.rb +242 -0
  279. data/lib/pubid/identifier_metadata.rb +148 -0
  280. data/lib/pubid/identifier_registry.rb +198 -0
  281. data/lib/pubid/idf/builder.rb +82 -0
  282. data/lib/pubid/idf/identifier.rb +129 -0
  283. data/lib/pubid/idf/identifiers/amendment.rb +27 -0
  284. data/lib/pubid/idf/identifiers/corrigendum.rb +27 -0
  285. data/lib/pubid/idf/identifiers/international_standard.rb +123 -0
  286. data/lib/pubid/idf/identifiers/reviewed_method.rb +100 -0
  287. data/lib/pubid/idf/identifiers.rb +13 -0
  288. data/lib/pubid/idf/parser.rb +143 -0
  289. data/lib/pubid/idf/scheme.rb +61 -0
  290. data/lib/pubid/idf/single_identifier.rb +19 -0
  291. data/lib/pubid/idf/supplement_identifier.rb +43 -0
  292. data/lib/pubid/idf/urn_generator.rb +84 -0
  293. data/lib/pubid/idf.rb +25 -0
  294. data/lib/pubid/iec/builder.rb +458 -0
  295. data/lib/pubid/iec/components/code.rb +60 -0
  296. data/lib/pubid/iec/components/consolidated_amendment.rb +59 -0
  297. data/lib/pubid/iec/components/publisher.rb +36 -0
  298. data/lib/pubid/iec/components/sheet.rb +32 -0
  299. data/lib/pubid/iec/components/trf_info.rb +38 -0
  300. data/lib/pubid/iec/components/vap_suffix.rb +41 -0
  301. data/lib/pubid/iec/identifier.rb +256 -0
  302. data/lib/pubid/iec/identifiers/amendment.rb +94 -0
  303. data/lib/pubid/iec/identifiers/base.rb +82 -0
  304. data/lib/pubid/iec/identifiers/component_specification.rb +39 -0
  305. data/lib/pubid/iec/identifiers/conformity_assessment.rb +39 -0
  306. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +82 -0
  307. data/lib/pubid/iec/identifiers/corrigendum.rb +94 -0
  308. data/lib/pubid/iec/identifiers/fragment_identifier.rb +137 -0
  309. data/lib/pubid/iec/identifiers/guide.rb +104 -0
  310. data/lib/pubid/iec/identifiers/international_standard.rb +147 -0
  311. data/lib/pubid/iec/identifiers/interpretation_sheet.rb +104 -0
  312. data/lib/pubid/iec/identifiers/operational_document.rb +39 -0
  313. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +101 -0
  314. data/lib/pubid/iec/identifiers/sheet_identifier.rb +62 -0
  315. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +40 -0
  316. data/lib/pubid/iec/identifiers/systems_reference_document.rb +40 -0
  317. data/lib/pubid/iec/identifiers/technical_report.rb +132 -0
  318. data/lib/pubid/iec/identifiers/technical_specification.rb +132 -0
  319. data/lib/pubid/iec/identifiers/technology_report.rb +39 -0
  320. data/lib/pubid/iec/identifiers/test_report_form.rb +78 -0
  321. data/lib/pubid/iec/identifiers/vap_identifier.rb +73 -0
  322. data/lib/pubid/iec/identifiers/white_paper.rb +39 -0
  323. data/lib/pubid/iec/identifiers/working_document.rb +96 -0
  324. data/lib/pubid/iec/parser.rb +417 -0
  325. data/lib/pubid/iec/rendering_style.rb +113 -0
  326. data/lib/pubid/iec/scheme.rb +71 -0
  327. data/lib/pubid/iec/single_identifier.rb +80 -0
  328. data/lib/pubid/iec/supplement_identifier.rb +161 -0
  329. data/lib/pubid/iec/urn_generator.rb +79 -0
  330. data/lib/pubid/iec/urn_parser.rb +90 -0
  331. data/lib/pubid/iec.rb +85 -0
  332. data/lib/pubid/ieee/aiee/builder.rb +71 -0
  333. data/lib/pubid/ieee/aiee/identifier.rb +105 -0
  334. data/lib/pubid/ieee/aiee/parser.rb +130 -0
  335. data/lib/pubid/ieee/aiee.rb +11 -0
  336. data/lib/pubid/ieee/builder.rb +1237 -0
  337. data/lib/pubid/ieee/components/code.rb +102 -0
  338. data/lib/pubid/ieee/components/draft.rb +93 -0
  339. data/lib/pubid/ieee/components/relationship.rb +157 -0
  340. data/lib/pubid/ieee/components/typed_stage.rb +100 -0
  341. data/lib/pubid/ieee/identifier.rb +54 -0
  342. data/lib/pubid/ieee/identifiers/adopted_standard.rb +33 -0
  343. data/lib/pubid/ieee/identifiers/base.rb +591 -0
  344. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +35 -0
  345. data/lib/pubid/ieee/identifiers/corrigendum.rb +37 -0
  346. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +51 -0
  347. data/lib/pubid/ieee/identifiers/dual_identifier.rb +18 -0
  348. data/lib/pubid/ieee/identifiers/dual_published.rb +28 -0
  349. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +27 -0
  350. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +34 -0
  351. data/lib/pubid/ieee/identifiers/joint_development.rb +172 -0
  352. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +51 -0
  353. data/lib/pubid/ieee/identifiers/nesc/base.rb +56 -0
  354. data/lib/pubid/ieee/identifiers/nesc/draft.rb +28 -0
  355. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +32 -0
  356. data/lib/pubid/ieee/identifiers/nesc/redline.rb +26 -0
  357. data/lib/pubid/ieee/identifiers/nesc/standard.rb +26 -0
  358. data/lib/pubid/ieee/identifiers/nesc.rb +15 -0
  359. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +20 -0
  360. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +26 -0
  361. data/lib/pubid/ieee/identifiers/redlined_standard.rb +33 -0
  362. data/lib/pubid/ieee/identifiers/si_standard.rb +73 -0
  363. data/lib/pubid/ieee/identifiers/standard.rb +41 -0
  364. data/lib/pubid/ieee/identifiers/supplement_identifier.rb +23 -0
  365. data/lib/pubid/ieee/identifiers.rb +33 -0
  366. data/lib/pubid/ieee/ire/builder.rb +61 -0
  367. data/lib/pubid/ieee/ire/identifier.rb +58 -0
  368. data/lib/pubid/ieee/ire/parser.rb +91 -0
  369. data/lib/pubid/ieee/ire.rb +11 -0
  370. data/lib/pubid/ieee/nesc/builder.rb +101 -0
  371. data/lib/pubid/ieee/nesc/parser.rb +154 -0
  372. data/lib/pubid/ieee/nesc.rb +10 -0
  373. data/lib/pubid/ieee/parser.rb +1226 -0
  374. data/lib/pubid/ieee/scheme.rb +90 -0
  375. data/lib/pubid/ieee/typed_stages.rb +172 -0
  376. data/lib/pubid/ieee/urn_generator.rb +188 -0
  377. data/lib/pubid/ieee.rb +32 -0
  378. data/lib/pubid/ieee_debug.rb +31 -0
  379. data/lib/pubid/iho/builder.rb +37 -0
  380. data/lib/pubid/iho/identifier.rb +61 -0
  381. data/lib/pubid/iho/identifiers/base.rb +41 -0
  382. data/lib/pubid/iho/identifiers/bibliographic.rb +16 -0
  383. data/lib/pubid/iho/identifiers/circular_letter.rb +15 -0
  384. data/lib/pubid/iho/identifiers/miscellaneous.rb +16 -0
  385. data/lib/pubid/iho/identifiers/publication.rb +15 -0
  386. data/lib/pubid/iho/identifiers/standard.rb +15 -0
  387. data/lib/pubid/iho/identifiers.rb +14 -0
  388. data/lib/pubid/iho/parser.rb +68 -0
  389. data/lib/pubid/iho/scheme.rb +29 -0
  390. data/lib/pubid/iho/urn_generator.rb +29 -0
  391. data/lib/pubid/iho.rb +21 -0
  392. data/lib/pubid/iso/builder.rb +309 -0
  393. data/lib/pubid/iso/bundled_identifier.rb +85 -0
  394. data/lib/pubid/iso/combined_identifier.rb +22 -0
  395. data/lib/pubid/iso/components/code.rb +36 -0
  396. data/lib/pubid/iso/components/publisher.rb +60 -0
  397. data/lib/pubid/iso/components.rb +12 -0
  398. data/lib/pubid/iso/format_resolver.rb +45 -0
  399. data/lib/pubid/iso/identifier.rb +330 -0
  400. data/lib/pubid/iso/identifiers/addendum.rb +104 -0
  401. data/lib/pubid/iso/identifiers/amendment.rb +128 -0
  402. data/lib/pubid/iso/identifiers/base.rb +115 -0
  403. data/lib/pubid/iso/identifiers/corrigendum.rb +108 -0
  404. data/lib/pubid/iso/identifiers/data.rb +76 -0
  405. data/lib/pubid/iso/identifiers/directives.rb +59 -0
  406. data/lib/pubid/iso/identifiers/directives_supplement.rb +119 -0
  407. data/lib/pubid/iso/identifiers/extract.rb +30 -0
  408. data/lib/pubid/iso/identifiers/guide.rb +100 -0
  409. data/lib/pubid/iso/identifiers/international_standard.rb +168 -0
  410. data/lib/pubid/iso/identifiers/international_standardized_profile.rb +94 -0
  411. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +89 -0
  412. data/lib/pubid/iso/identifiers/pas.rb +93 -0
  413. data/lib/pubid/iso/identifiers/recommendation.rb +45 -0
  414. data/lib/pubid/iso/identifiers/supplement.rb +87 -0
  415. data/lib/pubid/iso/identifiers/tc_document.rb +108 -0
  416. data/lib/pubid/iso/identifiers/technical_report.rb +103 -0
  417. data/lib/pubid/iso/identifiers/technical_specification.rb +102 -0
  418. data/lib/pubid/iso/identifiers/technology_trends_assessments.rb +95 -0
  419. data/lib/pubid/iso/identifiers.rb +33 -0
  420. data/lib/pubid/iso/parser.rb +512 -0
  421. data/lib/pubid/iso/rendering_style.rb +120 -0
  422. data/lib/pubid/iso/scheme.rb +193 -0
  423. data/lib/pubid/iso/single_identifier.rb +64 -0
  424. data/lib/pubid/iso/supplement_identifier.rb +27 -0
  425. data/lib/pubid/iso/urn_generator.rb +426 -0
  426. data/lib/pubid/iso/urn_parser.rb +437 -0
  427. data/lib/pubid/iso/utilities.rb +86 -0
  428. data/lib/pubid/iso.rb +50 -0
  429. data/lib/pubid/itu/builder.rb +171 -0
  430. data/lib/pubid/itu/components/code.rb +39 -0
  431. data/lib/pubid/itu/components/sector.rb +35 -0
  432. data/lib/pubid/itu/components/series.rb +29 -0
  433. data/lib/pubid/itu/i18n.rb +9 -0
  434. data/lib/pubid/itu/i18n.yaml +30 -0
  435. data/lib/pubid/itu/identifier.rb +118 -0
  436. data/lib/pubid/itu/identifiers/amendment.rb +43 -0
  437. data/lib/pubid/itu/identifiers/annex.rb +74 -0
  438. data/lib/pubid/itu/identifiers/base.rb +154 -0
  439. data/lib/pubid/itu/identifiers/combined_identifier.rb +47 -0
  440. data/lib/pubid/itu/identifiers/corrigendum.rb +44 -0
  441. data/lib/pubid/itu/identifiers/recommendation.rb +16 -0
  442. data/lib/pubid/itu/identifiers/special_publication.rb +31 -0
  443. data/lib/pubid/itu/identifiers/supplement.rb +46 -0
  444. data/lib/pubid/itu/identifiers.rb +16 -0
  445. data/lib/pubid/itu/model.rb +111 -0
  446. data/lib/pubid/itu/parser.rb +225 -0
  447. data/lib/pubid/itu/scheme.rb +174 -0
  448. data/lib/pubid/itu/urn_generator.rb +105 -0
  449. data/lib/pubid/itu.rb +22 -0
  450. data/lib/pubid/jcgm/builder.rb +88 -0
  451. data/lib/pubid/jcgm/components/publisher.rb +20 -0
  452. data/lib/pubid/jcgm/components.rb +9 -0
  453. data/lib/pubid/jcgm/identifier.rb +54 -0
  454. data/lib/pubid/jcgm/identifiers/amendment.rb +35 -0
  455. data/lib/pubid/jcgm/identifiers/guide.rb +21 -0
  456. data/lib/pubid/jcgm/identifiers/gum_guide.rb +51 -0
  457. data/lib/pubid/jcgm/identifiers.rb +11 -0
  458. data/lib/pubid/jcgm/parser.rb +84 -0
  459. data/lib/pubid/jcgm/scheme.rb +60 -0
  460. data/lib/pubid/jcgm/single_identifier.rb +48 -0
  461. data/lib/pubid/jcgm/supplement_identifier.rb +16 -0
  462. data/lib/pubid/jcgm/urn_generator.rb +110 -0
  463. data/lib/pubid/jcgm.rb +31 -0
  464. data/lib/pubid/jis/builder.rb +124 -0
  465. data/lib/pubid/jis/components/code.rb +59 -0
  466. data/lib/pubid/jis/components.rb +9 -0
  467. data/lib/pubid/jis/identifier.rb +61 -0
  468. data/lib/pubid/jis/identifiers/amendment.rb +16 -0
  469. data/lib/pubid/jis/identifiers/base.rb +72 -0
  470. data/lib/pubid/jis/identifiers/explanation.rb +22 -0
  471. data/lib/pubid/jis/identifiers/japanese_industrial_standard.rb +16 -0
  472. data/lib/pubid/jis/identifiers/standard.rb +27 -0
  473. data/lib/pubid/jis/identifiers/technical_report.rb +31 -0
  474. data/lib/pubid/jis/identifiers/technical_specification.rb +31 -0
  475. data/lib/pubid/jis/identifiers.rb +17 -0
  476. data/lib/pubid/jis/parser.rb +109 -0
  477. data/lib/pubid/jis/scheme.rb +49 -0
  478. data/lib/pubid/jis/single_identifier.rb +37 -0
  479. data/lib/pubid/jis/supplement_identifier.rb +47 -0
  480. data/lib/pubid/jis/urn_generator.rb +25 -0
  481. data/lib/pubid/jis.rb +23 -0
  482. data/lib/pubid/lutaml/no_store_registration.rb +30 -0
  483. data/lib/pubid/nist/builder.rb +2269 -0
  484. data/lib/pubid/nist/components/code.rb +38 -0
  485. data/lib/pubid/nist/components/edition.rb +134 -0
  486. data/lib/pubid/nist/components/issue_number.rb +28 -0
  487. data/lib/pubid/nist/components/part.rb +77 -0
  488. data/lib/pubid/nist/components/publisher.rb +24 -0
  489. data/lib/pubid/nist/components/stage.rb +53 -0
  490. data/lib/pubid/nist/components/supplement.rb +188 -0
  491. data/lib/pubid/nist/components/translation.rb +42 -0
  492. data/lib/pubid/nist/components/update.rb +103 -0
  493. data/lib/pubid/nist/components/version.rb +35 -0
  494. data/lib/pubid/nist/components/volume.rb +32 -0
  495. data/lib/pubid/nist/components.rb +19 -0
  496. data/lib/pubid/nist/configuration.rb +77 -0
  497. data/lib/pubid/nist/identifier.rb +62 -0
  498. data/lib/pubid/nist/identifiers/base.rb +578 -0
  499. data/lib/pubid/nist/identifiers/circular.rb +68 -0
  500. data/lib/pubid/nist/identifiers/circular_supplement.rb +50 -0
  501. data/lib/pubid/nist/identifiers/commercial_standard.rb +41 -0
  502. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +56 -0
  503. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +56 -0
  504. data/lib/pubid/nist/identifiers/crpl_report.rb +132 -0
  505. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +104 -0
  506. data/lib/pubid/nist/identifiers/grant_contractor_report.rb +35 -0
  507. data/lib/pubid/nist/identifiers/handbook.rb +50 -0
  508. data/lib/pubid/nist/identifiers/internal_report.rb +56 -0
  509. data/lib/pubid/nist/identifiers/letter_circular.rb +45 -0
  510. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +65 -0
  511. data/lib/pubid/nist/identifiers/monograph.rb +69 -0
  512. data/lib/pubid/nist/identifiers/ncstar.rb +41 -0
  513. data/lib/pubid/nist/identifiers/nsrds.rb +41 -0
  514. data/lib/pubid/nist/identifiers/owmwp.rb +35 -0
  515. data/lib/pubid/nist/identifiers/report.rb +67 -0
  516. data/lib/pubid/nist/identifiers/special_publication.rb +36 -0
  517. data/lib/pubid/nist/identifiers/technical_note.rb +90 -0
  518. data/lib/pubid/nist/identifiers.rb +33 -0
  519. data/lib/pubid/nist/parser.rb +1117 -0
  520. data/lib/pubid/nist/scheme.rb +199 -0
  521. data/lib/pubid/nist/supplement_identifier.rb +67 -0
  522. data/lib/pubid/nist/urn_generator.rb +133 -0
  523. data/lib/pubid/nist.rb +37 -0
  524. data/lib/pubid/oiml/builder.rb +189 -0
  525. data/lib/pubid/oiml/components/code.rb +20 -0
  526. data/lib/pubid/oiml/components.rb +9 -0
  527. data/lib/pubid/oiml/identifier.rb +61 -0
  528. data/lib/pubid/oiml/identifiers/amendment.rb +13 -0
  529. data/lib/pubid/oiml/identifiers/annex.rb +62 -0
  530. data/lib/pubid/oiml/identifiers/base.rb +36 -0
  531. data/lib/pubid/oiml/identifiers/basic_publication.rb +13 -0
  532. data/lib/pubid/oiml/identifiers/document.rb +13 -0
  533. data/lib/pubid/oiml/identifiers/expert_report.rb +13 -0
  534. data/lib/pubid/oiml/identifiers/guide.rb +13 -0
  535. data/lib/pubid/oiml/identifiers/recommendation.rb +13 -0
  536. data/lib/pubid/oiml/identifiers/seminar_report.rb +13 -0
  537. data/lib/pubid/oiml/identifiers/vocabulary.rb +13 -0
  538. data/lib/pubid/oiml/identifiers.rb +18 -0
  539. data/lib/pubid/oiml/parser.rb +173 -0
  540. data/lib/pubid/oiml/scheme.rb +46 -0
  541. data/lib/pubid/oiml/single_identifier.rb +90 -0
  542. data/lib/pubid/oiml/supplement_identifier.rb +43 -0
  543. data/lib/pubid/oiml/urn_generator.rb +64 -0
  544. data/lib/pubid/oiml.rb +26 -0
  545. data/lib/pubid/parser/common_parse_methods.rb +13 -0
  546. data/lib/pubid/parser/common_parse_rules.rb +56 -0
  547. data/lib/pubid/parser.rb +8 -0
  548. data/lib/pubid/parsers/base.rb +11 -0
  549. data/lib/pubid/parsers/mr_string.rb +93 -0
  550. data/lib/pubid/plateau/builder.rb +50 -0
  551. data/lib/pubid/plateau/identifier.rb +57 -0
  552. data/lib/pubid/plateau/identifiers/annex.rb +16 -0
  553. data/lib/pubid/plateau/identifiers/base.rb +51 -0
  554. data/lib/pubid/plateau/identifiers/handbook.rb +34 -0
  555. data/lib/pubid/plateau/identifiers/technical_report.rb +20 -0
  556. data/lib/pubid/plateau/identifiers.rb +12 -0
  557. data/lib/pubid/plateau/parser.rb +63 -0
  558. data/lib/pubid/plateau/scheme.rb +45 -0
  559. data/lib/pubid/plateau/supplement_identifier.rb +72 -0
  560. data/lib/pubid/plateau/urn_generator.rb +29 -0
  561. data/lib/pubid/plateau.rb +26 -0
  562. data/lib/pubid/renderers/base.rb +53 -0
  563. data/lib/pubid/renderers/directives_renderer.rb +61 -0
  564. data/lib/pubid/renderers/guide_renderer.rb +24 -0
  565. data/lib/pubid/renderers/human_readable.rb +70 -0
  566. data/lib/pubid/renderers/iwa_renderer.rb +20 -0
  567. data/lib/pubid/renderers/mr_string.rb +16 -0
  568. data/lib/pubid/renderers/supplement_renderer.rb +36 -0
  569. data/lib/pubid/renderers/urn.rb +11 -0
  570. data/lib/pubid/renderers.rb +14 -0
  571. data/lib/pubid/rendering/base.rb +73 -0
  572. data/lib/pubid/rendering/common.rb +211 -0
  573. data/lib/pubid/rendering/context.rb +159 -0
  574. data/lib/pubid/rendering/date.rb +27 -0
  575. data/lib/pubid/rendering/format.rb +25 -0
  576. data/lib/pubid/rendering/language.rb +21 -0
  577. data/lib/pubid/rendering/numbering.rb +24 -0
  578. data/lib/pubid/rendering/publisher.rb +25 -0
  579. data/lib/pubid/rendering/stage.rb +38 -0
  580. data/lib/pubid/rendering/supplement.rb +46 -0
  581. data/lib/pubid/rendering.rb +16 -0
  582. data/lib/pubid/sae/builder.rb +32 -0
  583. data/lib/pubid/sae/components/code.rb +9 -0
  584. data/lib/pubid/sae/components/date.rb +19 -0
  585. data/lib/pubid/sae/components/type.rb +19 -0
  586. data/lib/pubid/sae/components.rb +11 -0
  587. data/lib/pubid/sae/identifier.rb +37 -0
  588. data/lib/pubid/sae/identifiers/base.rb +42 -0
  589. data/lib/pubid/sae/identifiers.rb +9 -0
  590. data/lib/pubid/sae/parser.rb +55 -0
  591. data/lib/pubid/sae/scheme.rb +47 -0
  592. data/lib/pubid/sae/urn_generator.rb +38 -0
  593. data/lib/pubid/sae.rb +19 -0
  594. data/lib/pubid/scheme.rb +219 -0
  595. data/lib/pubid/urn_generator/base.rb +110 -0
  596. data/lib/pubid/utils/string_normalizer.rb +196 -0
  597. data/lib/pubid/utils.rb +7 -0
  598. data/lib/pubid/version.rb +3 -1
  599. data/lib/pubid.rb +137 -13
  600. data/lib/tasks/docs.rake +37 -0
  601. data/lib/tasks/export.rake +38 -0
  602. data/lib/tasks/website-data.json +7488 -0
  603. metadata +616 -171
  604. data/lib/pubid/registry.rb +0 -30
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Pubid
6
+ module Csa
7
+ # CompositeIdentifier is the base class for identifiers that contain
8
+ # collections of other identifiers or package materials.
9
+ #
10
+ # Examples:
11
+ # - PackageIdentifier: Base + package materials
12
+ # - Future: BundleIdentifier refactor to use composite
13
+ #
14
+ # This follows the Composite pattern where an identifier can contain
15
+ # other identifiers or additional metadata as a collection.
16
+ class CompositeIdentifier < Lutaml::Model::Serializable
17
+ # The primary/base identifier
18
+ # Use attr_accessor since it can be any identifier object
19
+ attr_accessor :base_identifier
20
+
21
+ # Subclasses MUST implement to_s to define how they render
22
+ def to_s
23
+ raise NotImplementedError, "Subclasses must implement to_s method"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,513 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Csa
5
+ class Identifier
6
+ # Factory that builds a CSA identifier from a hash of primitives.
7
+ # Default is {Identifiers::Standard}.
8
+ TYPE_KEY_TO_KLASS = {
9
+ standard: "Standard",
10
+ bundled: "Bundled",
11
+ canadian_adopted: "CanadianAdopted",
12
+ cec: "Cec",
13
+ combined: "Combined",
14
+ csa_adopted: "CsaAdopted",
15
+ package: "Package",
16
+ series: "Series",
17
+ }.freeze
18
+
19
+ def self.create(type: nil, **opts)
20
+ klass = resolve_create_class(type)
21
+ klass.new(**coerce_create_attrs(opts, klass: klass))
22
+ end
23
+
24
+ def self.resolve_create_class(type)
25
+ return Identifiers::Standard if type.nil?
26
+
27
+ klass_name = TYPE_KEY_TO_KLASS[type.to_sym]
28
+ raise ArgumentError, "Unknown CSA type: #{type.inspect}" unless klass_name
29
+
30
+ Identifiers.const_get(klass_name)
31
+ end
32
+
33
+ def self.coerce_create_attrs(opts, klass:)
34
+ attrs = {}
35
+
36
+ if (v = opts[:code] || opts[:number])
37
+ attrs[:code] = Pubid::Components::Code.new(value: v.to_s)
38
+ end
39
+
40
+ if opts[:year]
41
+ year_str = opts[:year].to_s
42
+ attrs[:year] = year_str
43
+ # Preserve 4-digit year rendering (e.g. "B51:2024") rather than
44
+ # collapsing to "B51:24".
45
+ attrs[:original_year_4digit] = year_str.length == 4 unless opts.key?(:original_year_4digit)
46
+ end
47
+ attrs[:original_year_4digit] = opts[:original_year_4digit] if opts.key?(:original_year_4digit)
48
+ attrs[:year_format] = opts[:year_format].to_s if opts[:year_format]
49
+ attrs[:year_prefix] = opts[:year_prefix].to_s if opts[:year_prefix]
50
+ attrs[:reaffirmation] = opts[:reaffirmation].to_s if opts[:reaffirmation]
51
+ attrs[:french] = opts[:french] if opts.key?(:french)
52
+ attrs[:has_publisher] = opts.fetch(:has_publisher, true)
53
+ attrs[:publisher_prefix] = opts[:publisher_prefix].to_s if opts[:publisher_prefix]
54
+ attrs[:series_prefix] = opts[:series_prefix].to_s if opts[:series_prefix]
55
+ attrs[:series] = opts[:series] if opts.key?(:series) && [true, false].include?(opts[:series])
56
+ attrs[:package] = opts[:package].to_s if opts[:package]
57
+ attrs[:no_number] = opts[:no_number].to_s if opts[:no_number]
58
+ attrs
59
+ end
60
+ private_class_method :resolve_create_class, :coerce_create_attrs
61
+
62
+ def self.parse(input)
63
+ # Filter out comments
64
+ return nil if input.start_with?("#")
65
+
66
+ # Filter out non-standards
67
+ return nil if input.match?(/^CSA (Communities|Group|Learning|OnDemand|Update)/)
68
+
69
+ # Preprocessing: normalize CEI to IEC (French name)
70
+ input = input.gsub("CEI/IEC", "IEC").gsub(/\bCEI\b/, "IEC")
71
+
72
+ # Detect CAN/ wrapper (Canadian adoption)
73
+ if input.start_with?("CAN/")
74
+ # Check if this is actually a bundled/combined identifier with + or /
75
+ # that should NOT be wrapped in CanadianAdopted
76
+ wrapped_input = input.sub(/^CAN\//, "")
77
+
78
+ # Check for bundled (+) or combined (/ or ,) patterns that should remain as-is
79
+ # These should be parsed normally, with publisher prefix applied to each part
80
+ # Patterns to detect:
81
+ # 1. Bundled: + separator (e.g., "CSA B127.1:99 + B127.2:99")
82
+ # 2. Combined with space: / separator with space before CSA- (e.g., "CSA A23.1:24/CSA A23.2:24")
83
+ # 3. Combined with CAN/CSA-: / separator with CSA- appearing multiple times (e.g., "CSA-B138.1-17/CSA-B138.2-17")
84
+ if wrapped_input.include?("+") ||
85
+ (wrapped_input.include?("/") && wrapped_input.match?(/\s+CSA-/)) ||
86
+ (wrapped_input.include?("/") && wrapped_input.scan("CSA-").length > 1)
87
+ # This is a bundled or combined identifier - parse normally and add prefix
88
+ # Normalize CSA- to CSA (with space) for parsing
89
+ normalized = wrapped_input.sub(/^CSA-/, "CSA ")
90
+ normalized = normalized.gsub("CAN/CSA-", "CSA ").gsub("CAN3-",
91
+ "CSA ")
92
+ normalized = normalized.gsub(/\s+/, " ").strip
93
+
94
+ # Parse normally (will create Bundled or Combined identifier)
95
+ tree = Parser.new.parse(normalized)
96
+ result = Builder.new.build(tree)
97
+
98
+ # Apply CAN/CSA- prefix to the appropriate parts
99
+ if result
100
+ set_publisher_prefix(result, "CAN/CSA-")
101
+
102
+ # Handle reaffirmation if present
103
+ if (wrapped_input =~ /\(R(\d{4})\)/) && result.class.attributes.key?(:reaffirmation)
104
+ result.reaffirmation = $1
105
+ end
106
+ end
107
+
108
+ return result
109
+ end
110
+
111
+ # This is a standard single identifier wrapped in CanadianAdopted
112
+ # Remove CAN/ prefix (already done above)
113
+
114
+ # Extract reaffirmation FIRST (before any other processing)
115
+ reaffirm_year = nil
116
+ reaffirmation_was_4digit = false # Track original format
117
+ if wrapped_input =~ /\(R(\d{2,4})\)/
118
+ reaffirm_year = $1
119
+ reaffirmation_was_4digit = ($1.length == 4) # Track if original was 4-digit
120
+ # Convert 2-digit year to 4-digit if needed
121
+ if reaffirm_year.length == 2
122
+ year_int = reaffirm_year.to_i
123
+ reaffirm_year = year_int < 50 ? "20#{reaffirm_year}" : "19#{reaffirm_year}"
124
+ end
125
+ wrapped_input = wrapped_input.sub(/\s*\(R\d{2,4}\)/, "")
126
+ end
127
+
128
+ # Detect and preserve original format (CSA- vs CSA)
129
+ # Also track that this is a CAN/CSA- identifier (not just CSA-)
130
+ original_prefix = if wrapped_input.start_with?("CSA-")
131
+ "CSA-"
132
+ elsif wrapped_input.start_with?("CSA ")
133
+ "CSA"
134
+ end
135
+ is_can_csa = true # Track that this has the CAN/ wrapper
136
+
137
+ # Normalize CSA- to CSA (with space) for parsing
138
+ wrapped_input = wrapped_input.sub(/^CSA-/, "CSA ")
139
+
140
+ # Parse the wrapped identifier recursively
141
+ wrapped_identifier = parse(wrapped_input)
142
+ return nil unless wrapped_identifier
143
+
144
+ # NOTE: Series identifiers should be wrapped in CanadianAdopted when
145
+ # they have CAN/CSA- prefix. The Series will handle rendering correctly.
146
+ # Do NOT return Series directly - always wrap in CanadianAdopted.
147
+
148
+ # Set publisher prefix on wrapped identifier
149
+ # For Series identifiers with CAN/ wrapper, use full "CAN/CSA-" prefix
150
+ # For other identifiers, use the detected original_prefix ("CSA-" or "CSA")
151
+ if wrapped_identifier.class.attributes.key?(:publisher_prefix)
152
+ if is_can_csa && wrapped_identifier.is_a?(Identifiers::Series)
153
+ # Series gets full "CAN/CSA-" prefix for proper rendering
154
+ wrapped_identifier.publisher_prefix = "CAN/CSA-"
155
+ elsif original_prefix
156
+ # For Combined identifiers, set on first identifier
157
+ if wrapped_identifier.is_a?(Identifiers::Combined) && wrapped_identifier.first
158
+ wrapped_identifier.first.publisher_prefix = original_prefix if wrapped_identifier.first.class.attributes.key?(:publisher_prefix)
159
+ else
160
+ # For non-Combined identifiers, set directly
161
+ wrapped_identifier.publisher_prefix = original_prefix
162
+ end
163
+ end
164
+ end
165
+
166
+ # Set reaffirmation on wrapped_identifier if it has the attribute
167
+ if wrapped_identifier.class.attributes.key?(:reaffirmation) && reaffirm_year
168
+ wrapped_identifier.reaffirmation = reaffirm_year
169
+ if wrapped_identifier.class.attributes.key?(:original_reaffirmation_4digit)
170
+ wrapped_identifier.original_reaffirmation_4digit = reaffirmation_was_4digit
171
+ end
172
+ end
173
+
174
+ # Create CanadianAdoptedIdentifier wrapper
175
+ result = Identifiers::CanadianAdopted.new
176
+ result.wrapped_identifier = wrapped_identifier
177
+ result.reaffirmation = reaffirm_year if reaffirm_year
178
+
179
+ return result
180
+ end
181
+
182
+ # Detect CAN3- wrapper (historical Canadian adoption)
183
+ if input.start_with?("CAN3-")
184
+ # This is a historical Canadian adoption - parse as wrapper
185
+ # Remove CAN3- prefix
186
+ wrapped_input = input.sub(/^CAN3-/, "CSA ")
187
+
188
+ # Detect year format before normalization (CAN3- standards use dash format)
189
+ # Format: CAN3-Z299.0-86 (uses dash, not colon)
190
+ has_dash_year = wrapped_input.match?(/-\d{2}\b/) # Match -86, -05, etc. (2-digit years)
191
+
192
+ # Extract reaffirmation FIRST (before any other processing)
193
+ reaffirm_year = nil
194
+ reaffirmation_was_4digit = false # Track original format
195
+ if wrapped_input =~ /\(R(\d{2,4})\)/
196
+ reaffirm_year = $1
197
+ reaffirmation_was_4digit = ($1.length == 4) # Track if original was 4-digit
198
+ # Convert 2-digit year to 4-digit if needed
199
+ if reaffirm_year.length == 2
200
+ year_int = reaffirm_year.to_i
201
+ reaffirm_year = year_int < 50 ? "20#{reaffirm_year}" : "19#{reaffirm_year}"
202
+ end
203
+ wrapped_input = wrapped_input.sub(/\s*\(R\d{2,4}\)/, "")
204
+ end
205
+
206
+ # Parse the wrapped identifier recursively
207
+ wrapped_identifier = parse(wrapped_input)
208
+ return nil unless wrapped_identifier
209
+
210
+ # Set year_format for dash format identifiers (preserve original 2-digit year)
211
+ if has_dash_year && wrapped_identifier.class.attributes.key?(:year_format)
212
+ wrapped_identifier.year_format = "dash"
213
+ # Mark original year as 2-digit so renderer converts back (1986 → 86)
214
+ if wrapped_identifier.class.attributes.key?(:original_year_4digit)
215
+ wrapped_identifier.original_year_4digit = false
216
+ end
217
+ end
218
+
219
+ # Check if this is a Series identifier - return it directly with CAN3- prefix
220
+ # Series identifiers are complete identifier types and handle the prefix themselves
221
+ # They don't need to be wrapped in CanadianAdopted
222
+ if wrapped_identifier.is_a?(Identifiers::Series)
223
+ wrapped_identifier.publisher_prefix = "CAN3-"
224
+ wrapped_identifier.reaffirmation = reaffirm_year if reaffirm_year
225
+ wrapped_identifier.original_reaffirmation_4digit = reaffirmation_was_4digit
226
+ return wrapped_identifier
227
+ end
228
+
229
+ # Set CAN3- as publisher prefix on wrapped identifier
230
+ if wrapped_identifier.class.attributes.key?(:publisher_prefix)
231
+ wrapped_identifier.publisher_prefix = "CAN3-"
232
+ end
233
+
234
+ # Set reaffirmation on wrapped_identifier if it has the attribute
235
+ if wrapped_identifier.class.attributes.key?(:reaffirmation) && reaffirm_year
236
+ wrapped_identifier.reaffirmation = reaffirm_year
237
+ if wrapped_identifier.class.attributes.key?(:original_reaffirmation_4digit)
238
+ wrapped_identifier.original_reaffirmation_4digit = reaffirmation_was_4digit
239
+ end
240
+ end
241
+
242
+ # Create CanadianAdoptedIdentifier wrapper
243
+ result = Identifiers::CanadianAdopted.new
244
+ result.wrapped_identifier = wrapped_identifier
245
+ result.reaffirmation = reaffirm_year if reaffirm_year
246
+
247
+ return result
248
+ end
249
+
250
+ # Detect CSA adoption of international standards
251
+ # Examples: CSA ISO/IEC TR 12785-3:15, CSA CISPR 16-1-1:18, CSA IEC 60601-1:08
252
+ # CSA CEI/IEC 61000-4-28-01 (bilingual)
253
+ if input.match?(/^CSA (ISO\/IEC|CEI\/IEC|CISPR|IEC|CEI|ISO)\s/)
254
+ # This is CSA adoption of international standard
255
+ # Extract the wrapped standard portion
256
+ wrapped_input = input.sub(/^CSA\s+/, "")
257
+
258
+ # Extract reaffirmation FIRST (before parsing)
259
+ reaffirm_year = nil
260
+ if wrapped_input =~ /\(R(\d{2,4})\)/
261
+ reaffirm_year = $1
262
+ # Convert 2-digit year to 4-digit if needed
263
+ if reaffirm_year.length == 2
264
+ year_int = reaffirm_year.to_i
265
+ reaffirm_year = year_int < 50 ? "20#{reaffirm_year}" : "19#{reaffirm_year}"
266
+ end
267
+ wrapped_input = wrapped_input.sub(/\s*\(R\d{2,4}\)/, "")
268
+ end
269
+
270
+ # Convert 2-digit years to 4-digit for external parser
271
+ # :15 → :2015, :04 → :2004
272
+ if wrapped_input =~ /:(\d{2})\b/
273
+ short_year_str = $1 # Keep as string "04", "15", etc.
274
+ short_year_int = short_year_str.to_i
275
+ # Determine century: 00-49 → 2000s, 50-99 → 1900s
276
+ full_year = short_year_int < 50 ? "20#{short_year_str}" : "19#{short_year_str}"
277
+ wrapped_input = wrapped_input.sub(/:#{short_year_str}\b/,
278
+ ":#{full_year}")
279
+ end
280
+
281
+ # Convert CSA amendment format to ISO/IEC format
282
+ # /A1:22 → /Amd 1:2022, /A1-22 → /Amd 1-2022
283
+ # Also handle 2-digit and 4-digit amendment years (convert to 4-digit)
284
+ # Note: In gsub blocks with string regex, we must use $1, $2, $3
285
+ # because the block receives a String, not MatchData
286
+ wrapped_input = wrapped_input.gsub(%r{/A(\d+)([:/-])(\d{2,4})\b}) do
287
+ amendment_num = $1
288
+ separator = $2
289
+ amend_year_str = $3
290
+ amend_year_int = amend_year_str.to_i
291
+ # Convert 2-digit year to 4-digit if needed
292
+ if amend_year_str.length == 2
293
+ amend_full_year = amend_year_int < 50 ? "20#{amend_year_str}" : "19#{amend_year_str}"
294
+ else
295
+ amend_full_year = amend_year_str
296
+ end
297
+ "/Amd #{amendment_num}#{separator}#{amend_full_year}"
298
+ end
299
+
300
+ # Parse with appropriate flavor parser
301
+ wrapped_identifier = parse_external_standard(wrapped_input)
302
+ return nil unless wrapped_identifier
303
+
304
+ # Create CsaAdoptedIdentifier wrapper
305
+ result = Identifiers::CsaAdopted.new
306
+ result.wrapped_identifier = wrapped_identifier
307
+ result.reaffirmation = reaffirm_year if reaffirm_year
308
+
309
+ return result
310
+ end
311
+
312
+ # Detect package identifiers
313
+ # Examples: CSA Z662:23 PACKAGE INCLUDES: +1 (PDF & ESA)
314
+ # CSA B149.1:25 Code, Handbook & Training Package (materials BEFORE keyword)
315
+ # CSA B149.1:20 PACKAGE (PDF + PRINT) (no materials)
316
+ # CSA B108:23 PACKAGE (no trailing space)
317
+ if input.match?(/\sPACKAGE\b/i)
318
+ # Handle two formats:
319
+ # 1. {base} PACKAGE {materials} - materials after keyword
320
+ # 2. {base} {materials} PACKAGE - materials before keyword
321
+
322
+ # Check if materials come after PACKAGE keyword
323
+ # Match: "CSA Z662:23 PACKAGE INCLUDES: +1" or "CSA B149.1:20 PACKAGE (PDF + PRINT)"
324
+ case input
325
+ when /\sPACKAGE\s+[A-Z]/i
326
+ # Format: {base} PACKAGE {materials}
327
+ # Extract materials after PACKAGE keyword
328
+ base_input, package_materials = input.split(/\s+PACKAGE\s+/i, 2)
329
+ base_input = base_input.strip
330
+ package_materials = package_materials ? package_materials.strip : ""
331
+ materials_after = true
332
+ when /:(\d{2,4})(\s+[^P]+)\s+PACKAGE/i
333
+ # Format: {base} PACKAGE or {base} {materials} PACKAGE
334
+ # Examples: "C22.1-15 PACKAGE" (no materials), "CSA B149.1:25 Code, Handbook & Training Package"
335
+
336
+ # Check if there's a year followed by materials before PACKAGE
337
+ # Must check this BEFORE the generic "{base} PACKAGE" pattern
338
+ $1
339
+ $2
340
+
341
+ # Check if this is a combined identifier (has comma with CSA after it)
342
+ # Pattern: ", CSA" or ", CAN" which indicates combined identifier
343
+ if input.match?(/,\s+CSA/)
344
+ # For combined identifiers, we need to extract everything before "& Training Package"
345
+ # The base is the combined identifier, materials is just "& Training Package"
346
+ combined_match = input.match(/^(.+?)(\s+&[^P]+)\s+PACKAGE$/i)
347
+ if combined_match
348
+ base_input = combined_match[1].strip
349
+ package_materials = "#{combined_match[2].strip} Package" # Keep "& Training Package"
350
+ else
351
+ # Fallback to year-based extraction
352
+ year_match = input.match(/:\d{2,4}/)
353
+ if year_match
354
+ base_input = input[0..(year_match.end(0) - 1)]
355
+ materials_input = input[year_match.end(0)..].strip
356
+ package_materials = materials_input
357
+ end
358
+ end
359
+ else
360
+ # Standard single identifier with materials before PACKAGE
361
+ year_match = input.match(/:\d{2,4}/)
362
+ if year_match
363
+ base_input = input[0..(year_match.end(0) - 1)]
364
+ # Materials is everything between base and PACKAGE - keep the "Package" suffix for correct capitalization
365
+ materials_input = input[year_match.end(0)..].strip
366
+ package_materials = materials_input # Keep full materials including "Package" suffix
367
+ base_input = base_input.strip
368
+ end
369
+ end
370
+ materials_after = false
371
+ # Format: {base} {materials} PACKAGE - "CSA B149.1:25 Code, Handbook & Training Package"
372
+ # OR: Combined identifier with package: "CSA B149.1:25, CSA B149.2:25 & Training Package"
373
+ when /^(.+?)\s+PACKAGE\s*$/i
374
+ # Format: {base} PACKAGE (no materials) - "C22.1-15 PACKAGE"
375
+ # Treat PACKAGE as the package description
376
+ base_input = input.sub(/\s+PACKAGE\s*$/i, "").strip
377
+ package_materials = "" # No additional materials
378
+ materials_after = true
379
+ else
380
+ # Fallback: try parsing incrementally
381
+ tokens = input.split(/\s+/)
382
+ base_input = ""
383
+ tokens.each do |token|
384
+ break if token.match?(/^PACKAGE$/i)
385
+
386
+ test_input = base_input.empty? ? token : "#{base_input} #{token}"
387
+ parsed = parse(test_input)
388
+ if parsed
389
+ base_input = test_input
390
+ else
391
+ break
392
+ end
393
+ end
394
+
395
+ # Extract materials as everything between base and PACKAGE
396
+ if base_input && base_input.length < input.length
397
+ materials_input = input[base_input.length..].strip
398
+ package_materials = materials_input.sub(/\s+PACKAGE\s*$/i,
399
+ "").strip
400
+ materials_after = !package_materials.empty?
401
+ end
402
+ end
403
+
404
+ # Parse base identifier recursively
405
+ base_identifier = base_input ? parse(base_input) : nil
406
+ return nil unless base_identifier
407
+
408
+ # Create PackageIdentifier
409
+ result = Identifiers::Package.new
410
+ result.base_identifier = base_identifier
411
+ # Set materials if present
412
+ if package_materials && !package_materials.empty?
413
+ result.package_materials = package_materials
414
+ end
415
+ result.package_keyword = "PACKAGE"
416
+ # Set materials_after_keyword flag based on which format we detected
417
+ result.materials_after_keyword = materials_after
418
+
419
+ return result
420
+ end
421
+
422
+ # Legacy handling for CAN/CSA- and CAN3- (will be migrated to proper classes later)
423
+ # Detect original publisher prefix before normalization
424
+ publisher_prefix = if input.start_with?("CAN/CSA-")
425
+ "CAN/CSA-"
426
+ elsif input.start_with?("CAN3-")
427
+ "CAN3-"
428
+ elsif input.start_with?("CSA ")
429
+ "CSA"
430
+ end
431
+
432
+ # Detect year format before normalization
433
+ # CAN/CSA- standards use dash format: CAN/CSA-C22.2-05
434
+ # Modern CSA standards use colon format: CSA B149:20
435
+ has_dash_year = input.match?(/-\d{2}\b/)
436
+
437
+ # Normalize CAN/CSA- and CAN3- to CSA (global replacement for combined identifiers)
438
+ normalized = input.gsub("CAN/CSA-", "CSA ")
439
+ # Normalize CAN3- to CSA (historical prefix)
440
+ normalized = normalized.gsub("CAN3-", "CSA ")
441
+
442
+ tree = Parser.new.parse(normalized)
443
+ result = Builder.new.build(tree)
444
+
445
+ # Set publisher prefix if detected
446
+ if result && publisher_prefix
447
+ set_publisher_prefix(result, publisher_prefix)
448
+ end
449
+
450
+ # Set year format if detected as dash and not already set
451
+ if result && has_dash_year && result.year_format.nil?
452
+ result.year_format = "dash"
453
+ end
454
+
455
+ result
456
+ rescue Parslet::ParseFailed => e
457
+ raise e
458
+ end
459
+
460
+ def self.set_publisher_prefix(obj, prefix)
461
+ # Set on main object if it has the attribute
462
+ if obj.class.attributes.key?(:publisher_prefix)
463
+ obj.publisher_prefix = prefix
464
+ end
465
+
466
+ # Set on combined identifier parts
467
+ if obj.is_a?(Identifiers::Combined)
468
+ if obj.first&.class&.attributes&.key?(:publisher_prefix)
469
+ obj.first.publisher_prefix = prefix
470
+ end
471
+ if obj.second && obj.second.class.attributes.key?(:has_publisher) && obj.second.has_publisher && obj.second.class.attributes.key?(:publisher_prefix)
472
+ obj.second.publisher_prefix = prefix
473
+ end
474
+ if obj.third && obj.third.class.attributes.key?(:has_publisher) && obj.third.has_publisher && obj.third.class.attributes.key?(:publisher_prefix)
475
+ obj.third.publisher_prefix = prefix
476
+ end
477
+ end
478
+
479
+ # Set on bundled identifier base
480
+ if obj.is_a?(Identifiers::Bundled) && obj.base
481
+ set_publisher_prefix(obj.base, prefix)
482
+ end
483
+ end
484
+
485
+ def self.parse_external_standard(input)
486
+ # Ensure full Pubid is loaded (handles Scheme and all flavors)
487
+ require_relative "../../pubid" unless defined?(Pubid::Iso) && defined?(Pubid::Iec)
488
+
489
+ # Try ISO/IEC first (most common)
490
+ if input.match?(/^(ISO\/IEC|ISO|IEC|CEI|CEI\/IEC)\s/)
491
+ begin
492
+ # Normalize CEI/IEC to IEC for parsing (CEI is French for IEC)
493
+ normalized_input = input.sub(/^CEI\/IEC/, "IEC")
494
+ return Pubid::Iso.parse(normalized_input)
495
+ rescue StandardError
496
+ return nil
497
+ end
498
+ end
499
+
500
+ # Try CISPR (uses IEC parser)
501
+ if input.match?(/^CISPR\s/)
502
+ begin
503
+ return Pubid::Iec.parse(input)
504
+ rescue StandardError
505
+ return nil
506
+ end
507
+ end
508
+
509
+ nil
510
+ end
511
+ end
512
+ end
513
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Csa
5
+ module Identifiers
6
+ class Base < SingleIdentifier
7
+ def to_s
8
+ prefix = publisher_prefix || "CSA"
9
+
10
+ # Handle code_only identifiers (empty string means no prefix)
11
+ if prefix == ""
12
+ # No prefix for code_only identifiers
13
+ parts = []
14
+ else
15
+ # Determine if we need space after prefix
16
+ # CAN/CSA- and CAN3- end with dash, so no space needed
17
+ needs_space = !prefix.end_with?("-")
18
+
19
+ parts = []
20
+ parts << prefix
21
+ end
22
+
23
+ # Code and year together
24
+ code_part = code.to_s if code
25
+
26
+ # NO. keyword
27
+ if no_number
28
+ code_part += " NO. #{no_number}"
29
+ end
30
+
31
+ # Series prefix and keyword (before year)
32
+ if series_prefix
33
+ code_part += " #{series_prefix} SERIES"
34
+ elsif series
35
+ # SERIES without prefix
36
+ code_part += " SERIES"
37
+ end
38
+
39
+ parts << code_part if code_part
40
+
41
+ # Year with proper format (colon or dash)
42
+ if year
43
+ # Use dash if year_format is dash, otherwise colon
44
+ separator = year_format == "dash" ? "-" : ":"
45
+ year_part = separator
46
+ year_part += year_prefix if year_prefix # Add M or F prefix
47
+ year_part += "F" if french && year_format != "dash" && !year_prefix # Only add F if no prefix
48
+ # Convert 4-digit year back to 2-digit for M/F prefix preservation
49
+ # M1983 → M83, F1983 → F83, 1983 → 83, 20xx → xx
50
+ # However, preserve original 4-digit format if the input was 4-digit (original_year_4digit)
51
+ year_str = year.to_s
52
+ year_part += if original_year_4digit
53
+ # Preserve 4-digit format (e.g., "M1981" stays "M1981")
54
+ year_str
55
+ elsif year_prefix&.match?(/^[MF]$/) && year_str.length == 4 && year_str.start_with?("19")
56
+ # M/F + 4-digit year (1900s) → convert to 2-digit with prefix
57
+ year_str[2..3]
58
+ elsif year_str.length == 4 && year_str.start_with?("20")
59
+ # 2000s → just last 2 digits
60
+ year_str[2..3]
61
+ elsif year_str.length == 4 && year_str.start_with?("19") && !original_year_4digit
62
+ # 1900s with no M/F prefix, but original was 2-digit (e.g., CAN3-Z299.0-86)
63
+ # Convert back to 2-digit
64
+ year_str[2..3]
65
+ else
66
+ # Other formats - keep as-is
67
+ year_str
68
+ end
69
+ parts[-1] += year_part
70
+ end
71
+
72
+ result = if prefix == ""
73
+ # No prefix, just join parts
74
+ parts.join(" ")
75
+ elsif needs_space
76
+ parts.join(" ")
77
+ elsif parts.length == 2
78
+ # No space after dash-ending prefix - join first two parts directly
79
+ # If there are more parts after code, they should still have spaces
80
+ parts.join
81
+ else
82
+ # More than 2 parts shouldn't happen in current design
83
+ parts.join
84
+ end
85
+
86
+ # Reaffirmation - preserve original format and determine spacing
87
+ if reaffirmation
88
+ # Check if year was originally 2-digit (original_year_4digit flag)
89
+ year_was_2digit = !original_year_4digit
90
+
91
+ # Check if reaffirmation was originally 4-digit (original_reaffirmation_4digit flag)
92
+ reaffirmation_was_4digit = original_reaffirmation_4digit
93
+
94
+ # Preserve original reaffirmation format
95
+ # If original was 4-digit, keep as 4-digit
96
+ # If original was 2-digit, convert from 4-digit storage back to 2-digit
97
+ reaffirmation_str = if reaffirmation_was_4digit
98
+ # Original was 4-digit, keep as-is
99
+ reaffirmation.to_s
100
+ elsif reaffirmation.to_s.length == 4 && reaffirmation.to_s.start_with?(
101
+ "19", "20"
102
+ )
103
+ # Original was 2-digit, convert 4-digit storage back to 2-digit
104
+ # (R2004) → (R04), (R1994) → (R94)
105
+ reaffirmation.to_s[2..3]
106
+ else
107
+ # Already 2-digit or other format
108
+ reaffirmation.to_s
109
+ end
110
+
111
+ # Determine spacing based on original formats
112
+ # Space needed if year is 2-digit and reaffirmation is 4-digit (original format)
113
+ # No space if both year and reaffirmation are 2-digit, or both are 4-digit
114
+ if year_was_2digit && reaffirmation_was_4digit
115
+ # Year was 2-digit, reaffirmation was 4-digit → add space
116
+ result += " (R#{reaffirmation_str})"
117
+ else
118
+ # Both 2-digit, both 4-digit, or other cases → no space
119
+ result += "(R#{reaffirmation_str})"
120
+ end
121
+ end
122
+
123
+ # Package (already has leading space from parser)
124
+ if package
125
+ result += package
126
+ end
127
+
128
+ result
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end