pubid 1.15.21 → 2.0.0.alpha.10

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