pubid 2.0.0.pre.alpha.2 → 2.0.0.pre.alpha.4

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 (473) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +5 -1
  3. data/data/nist/update_codes.yaml +25 -0
  4. data/lib/pubid/amca/builder.rb +2 -2
  5. data/lib/pubid/amca/identifier.rb +4 -55
  6. data/lib/pubid/amca/identifiers/base.rb +58 -47
  7. data/lib/pubid/amca/identifiers/interpretation.rb +0 -21
  8. data/lib/pubid/amca/identifiers/publication.rb +0 -17
  9. data/lib/pubid/amca/identifiers/standard.rb +0 -4
  10. data/lib/pubid/amca/renderer.rb +82 -0
  11. data/lib/pubid/amca/single_identifier.rb +0 -23
  12. data/lib/pubid/amca/urn_generator.rb +2 -1
  13. data/lib/pubid/amca/urn_parser.rb +28 -0
  14. data/lib/pubid/amca.rb +42 -1
  15. data/lib/pubid/ansi/builder.rb +5 -3
  16. data/lib/pubid/ansi/identifier.rb +1 -43
  17. data/lib/pubid/ansi/identifiers/american_national_standard.rb +2 -1
  18. data/lib/pubid/ansi/identifiers/standard.rb +2 -3
  19. data/lib/pubid/ansi/renderer.rb +53 -0
  20. data/lib/pubid/ansi/single_identifier.rb +2 -31
  21. data/lib/pubid/ansi/urn_generator.rb +3 -38
  22. data/lib/pubid/ansi/urn_parser.rb +23 -0
  23. data/lib/pubid/ansi.rb +38 -3
  24. data/lib/pubid/api/builder.rb +33 -74
  25. data/lib/pubid/api/identifier.rb +5 -52
  26. data/lib/pubid/api/identifiers/base.rb +2 -14
  27. data/lib/pubid/api/identifiers/bulletin.rb +0 -2
  28. data/lib/pubid/api/identifiers/continuous_operations_standard.rb +0 -2
  29. data/lib/pubid/api/identifiers/mpms.rb +0 -20
  30. data/lib/pubid/api/identifiers/publication.rb +0 -2
  31. data/lib/pubid/api/identifiers/recommended_practice.rb +0 -2
  32. data/lib/pubid/api/identifiers/specification.rb +0 -2
  33. data/lib/pubid/api/identifiers/standard.rb +0 -2
  34. data/lib/pubid/api/identifiers/technical_report.rb +0 -2
  35. data/lib/pubid/api/identifiers/typeless_standard.rb +0 -16
  36. data/lib/pubid/api/identifiers.rb +18 -0
  37. data/lib/pubid/api/renderer.rb +89 -0
  38. data/lib/pubid/api/single_identifier.rb +5 -20
  39. data/lib/pubid/api/urn_generator.rb +0 -18
  40. data/lib/pubid/api/urn_parser.rb +35 -0
  41. data/lib/pubid/api.rb +51 -5
  42. data/lib/pubid/ashrae/builder.rb +3 -3
  43. data/lib/pubid/ashrae/identifier.rb +7 -55
  44. data/lib/pubid/ashrae/identifiers/addenda_package.rb +0 -10
  45. data/lib/pubid/ashrae/identifiers/addendum.rb +0 -19
  46. data/lib/pubid/ashrae/identifiers/base.rb +29 -15
  47. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +0 -15
  48. data/lib/pubid/ashrae/identifiers/errata.rb +0 -10
  49. data/lib/pubid/ashrae/identifiers/interpretation.rb +0 -10
  50. data/lib/pubid/ashrae/parser.rb +4 -0
  51. data/lib/pubid/ashrae/renderer.rb +117 -0
  52. data/lib/pubid/ashrae/single_identifier.rb +0 -13
  53. data/lib/pubid/ashrae/urn_generator.rb +0 -8
  54. data/lib/pubid/ashrae/urn_parser.rb +27 -0
  55. data/lib/pubid/ashrae.rb +46 -1
  56. data/lib/pubid/asme/components/code.rb +10 -2
  57. data/lib/pubid/asme/identifier.rb +0 -46
  58. data/lib/pubid/asme/identifiers/base.rb +0 -60
  59. data/lib/pubid/asme/renderer.rb +66 -0
  60. data/lib/pubid/asme/single_identifier.rb +3 -1
  61. data/lib/pubid/asme/urn_parser.rb +31 -0
  62. data/lib/pubid/asme.rb +42 -1
  63. data/lib/pubid/astm/components/code.rb +9 -0
  64. data/lib/pubid/{jis → astm}/components.rb +1 -1
  65. data/lib/pubid/astm/identifier.rb +0 -77
  66. data/lib/pubid/astm/identifiers/adjunct.rb +0 -8
  67. data/lib/pubid/astm/identifiers/data_series.rb +0 -14
  68. data/lib/pubid/astm/identifiers/iso_dual_published.rb +9 -34
  69. data/lib/pubid/astm/identifiers/manual.rb +0 -27
  70. data/lib/pubid/astm/identifiers/monograph.rb +0 -14
  71. data/lib/pubid/astm/identifiers/research_report.rb +0 -7
  72. data/lib/pubid/astm/identifiers/standard.rb +0 -39
  73. data/lib/pubid/astm/identifiers/technical_report.rb +0 -13
  74. data/lib/pubid/astm/identifiers/work_in_progress.rb +0 -11
  75. data/lib/pubid/astm/identifiers.rb +18 -0
  76. data/lib/pubid/astm/renderer.rb +172 -0
  77. data/lib/pubid/astm/single_identifier.rb +3 -11
  78. data/lib/pubid/astm/urn_parser.rb +30 -0
  79. data/lib/pubid/astm.rb +39 -27
  80. data/lib/pubid/bsi/builder.rb +21 -12
  81. data/lib/pubid/bsi/identifier.rb +5 -85
  82. data/lib/pubid/bsi/identifiers/addendum_document.rb +2 -36
  83. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +8 -48
  84. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +8 -39
  85. data/lib/pubid/bsi/identifiers/aerospace_standard.rb +2 -55
  86. data/lib/pubid/bsi/identifiers/amendment.rb +2 -22
  87. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +1 -6
  88. data/lib/pubid/bsi/identifiers/british_standard.rb +2 -1
  89. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +3 -87
  90. data/lib/pubid/bsi/identifiers/committee_document.rb +0 -16
  91. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +3 -87
  92. data/lib/pubid/bsi/identifiers/corrigendum.rb +2 -10
  93. data/lib/pubid/bsi/identifiers/detailed_specification.rb +0 -36
  94. data/lib/pubid/bsi/identifiers/disc.rb +0 -29
  95. data/lib/pubid/bsi/identifiers/draft_document.rb +2 -46
  96. data/lib/pubid/bsi/identifiers/electronic_book.rb +2 -38
  97. data/lib/pubid/bsi/identifiers/expert_commentary.rb +2 -18
  98. data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +0 -47
  99. data/lib/pubid/bsi/identifiers/flex.rb +0 -35
  100. data/lib/pubid/bsi/identifiers/handbook.rb +1 -15
  101. data/lib/pubid/bsi/identifiers/index.rb +0 -32
  102. data/lib/pubid/bsi/identifiers/method.rb +0 -41
  103. data/lib/pubid/bsi/identifiers/national_annex.rb +4 -29
  104. data/lib/pubid/bsi/identifiers/practice_guide.rb +1 -6
  105. data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +2 -54
  106. data/lib/pubid/bsi/identifiers/published_document.rb +2 -54
  107. data/lib/pubid/bsi/identifiers/section.rb +0 -30
  108. data/lib/pubid/bsi/identifiers/set.rb +3 -20
  109. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +0 -9
  110. data/lib/pubid/bsi/identifiers/supplement_document.rb +2 -24
  111. data/lib/pubid/bsi/identifiers/supplementary_index.rb +0 -46
  112. data/lib/pubid/bsi/identifiers/technical_specification.rb +2 -47
  113. data/lib/pubid/bsi/identifiers/test_method.rb +0 -32
  114. data/lib/pubid/bsi/identifiers/value_added_publication.rb +2 -17
  115. data/lib/pubid/bsi/identifiers.rb +0 -1
  116. data/lib/pubid/bsi/renderer.rb +1050 -0
  117. data/lib/pubid/bsi/single_identifier.rb +33 -78
  118. data/lib/pubid/bsi/urn_generator.rb +2 -3
  119. data/lib/pubid/bsi/urn_parser.rb +52 -0
  120. data/lib/pubid/bsi.rb +232 -2
  121. data/lib/pubid/builder/base.rb +57 -10
  122. data/lib/pubid/bundled_identifier.rb +0 -1
  123. data/lib/pubid/ccsds/builder.rb +4 -3
  124. data/lib/pubid/ccsds/identifier.rb +53 -68
  125. data/lib/pubid/ccsds/identifiers/base.rb +11 -61
  126. data/lib/pubid/ccsds/identifiers/corrigendum.rb +7 -6
  127. data/lib/pubid/ccsds/parser.rb +4 -2
  128. data/lib/pubid/ccsds/supplement_identifier.rb +15 -11
  129. data/lib/pubid/ccsds/urn_generator.rb +3 -3
  130. data/lib/pubid/ccsds/urn_parser.rb +20 -0
  131. data/lib/pubid/ccsds.rb +39 -1
  132. data/lib/pubid/cen_cenelec/builder.rb +12 -14
  133. data/lib/pubid/cen_cenelec/identifier.rb +11 -40
  134. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +9 -4
  135. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +0 -10
  136. data/lib/pubid/cen_cenelec/identifiers/base.rb +2 -45
  137. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +2 -1
  138. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +2 -1
  139. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +0 -27
  140. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +0 -15
  141. data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +2 -1
  142. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +2 -8
  143. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +2 -1
  144. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +0 -4
  145. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +2 -1
  146. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +2 -1
  147. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +2 -1
  148. data/lib/pubid/cen_cenelec/renderer.rb +261 -0
  149. data/lib/pubid/cen_cenelec/single_identifier.rb +9 -90
  150. data/lib/pubid/cen_cenelec/urn_generator.rb +6 -6
  151. data/lib/pubid/cen_cenelec/urn_parser.rb +28 -0
  152. data/lib/pubid/cen_cenelec.rb +172 -1
  153. data/lib/pubid/cie/components/code.rb +8 -0
  154. data/lib/pubid/cie/identifier.rb +10 -57
  155. data/lib/pubid/cie/single_identifier.rb +4 -4
  156. data/lib/pubid/cie/urn_parser.rb +28 -0
  157. data/lib/pubid/cie.rb +47 -1
  158. data/lib/pubid/components/adoption.rb +104 -0
  159. data/lib/pubid/components/code.rb +22 -8
  160. data/lib/pubid/components/date.rb +23 -16
  161. data/lib/pubid/components/edition.rb +9 -6
  162. data/lib/pubid/components/iteration.rb +32 -0
  163. data/lib/pubid/components/language.rb +6 -4
  164. data/lib/pubid/components/locality.rb +10 -1
  165. data/lib/pubid/components/publisher.rb +9 -6
  166. data/lib/pubid/components/relationship.rb +151 -0
  167. data/lib/pubid/components/stage.rb +5 -14
  168. data/lib/pubid/components/supplement.rb +184 -0
  169. data/lib/pubid/components/type.rb +5 -15
  170. data/lib/pubid/components/typed_stage.rb +10 -11
  171. data/lib/pubid/components.rb +4 -1
  172. data/lib/pubid/core/update_codes.rb +28 -7
  173. data/lib/pubid/csa/identifier.rb +9 -59
  174. data/lib/pubid/csa/identifiers/base.rb +0 -123
  175. data/lib/pubid/csa/identifiers/cec.rb +0 -103
  176. data/lib/pubid/csa/identifiers/series.rb +0 -104
  177. data/lib/pubid/csa/renderer.rb +292 -0
  178. data/lib/pubid/csa/single_identifier.rb +1 -1
  179. data/lib/pubid/csa/urn_generator.rb +1 -1
  180. data/lib/pubid/csa/urn_parser.rb +33 -0
  181. data/lib/pubid/csa.rb +46 -1
  182. data/lib/pubid/etsi/components/code.rb +10 -9
  183. data/lib/pubid/etsi/components/version.rb +0 -5
  184. data/lib/pubid/etsi/identifier.rb +4 -55
  185. data/lib/pubid/etsi/identifiers/base.rb +31 -27
  186. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +1 -11
  187. data/lib/pubid/etsi/renderer.rb +42 -0
  188. data/lib/pubid/etsi/urn_parser.rb +34 -0
  189. data/lib/pubid/etsi.rb +44 -1
  190. data/lib/pubid/export/exporter.rb +4 -46
  191. data/lib/pubid/export/flavor_exporter.rb +111 -278
  192. data/lib/pubid/export.rb +0 -6
  193. data/lib/pubid/identifier.rb +92 -18
  194. data/lib/pubid/identifier_metadata.rb +1 -1
  195. data/lib/pubid/idf/builder.rb +3 -3
  196. data/lib/pubid/idf/identifier.rb +7 -66
  197. data/lib/pubid/idf/identifiers/amendment.rb +2 -1
  198. data/lib/pubid/idf/identifiers/corrigendum.rb +2 -1
  199. data/lib/pubid/idf/identifiers/international_standard.rb +2 -1
  200. data/lib/pubid/idf/identifiers/reviewed_method.rb +2 -1
  201. data/lib/pubid/idf/parser.rb +3 -2
  202. data/lib/pubid/idf/renderer.rb +84 -0
  203. data/lib/pubid/idf/supplement_identifier.rb +2 -10
  204. data/lib/pubid/idf/urn_generator.rb +4 -39
  205. data/lib/pubid/idf/urn_parser.rb +25 -0
  206. data/lib/pubid/idf.rb +55 -1
  207. data/lib/pubid/iec/builder.rb +46 -64
  208. data/lib/pubid/iec/components/code.rb +8 -32
  209. data/lib/pubid/iec/components/publisher.rb +0 -1
  210. data/lib/pubid/iec/components.rb +14 -0
  211. data/lib/pubid/iec/identifier.rb +251 -213
  212. data/lib/pubid/iec/identifiers/amendment.rb +2 -3
  213. data/lib/pubid/iec/identifiers/base.rb +5 -33
  214. data/lib/pubid/iec/identifiers/component_specification.rb +3 -3
  215. data/lib/pubid/iec/identifiers/conformity_assessment.rb +1 -2
  216. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +23 -26
  217. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -3
  218. data/lib/pubid/iec/identifiers/fragment_identifier.rb +35 -24
  219. data/lib/pubid/iec/identifiers/guide.rb +0 -2
  220. data/lib/pubid/iec/identifiers/international_standard.rb +2 -3
  221. data/lib/pubid/iec/identifiers/interpretation_sheet.rb +2 -3
  222. data/lib/pubid/iec/identifiers/operational_document.rb +3 -3
  223. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +2 -3
  224. data/lib/pubid/iec/identifiers/sheet_identifier.rb +17 -11
  225. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +3 -3
  226. data/lib/pubid/iec/identifiers/systems_reference_document.rb +2 -3
  227. data/lib/pubid/iec/identifiers/technical_report.rb +2 -3
  228. data/lib/pubid/iec/identifiers/technical_specification.rb +2 -3
  229. data/lib/pubid/iec/identifiers/technology_report.rb +1 -2
  230. data/lib/pubid/iec/identifiers/test_report_form.rb +3 -35
  231. data/lib/pubid/iec/identifiers/vap_identifier.rb +24 -21
  232. data/lib/pubid/iec/identifiers/white_paper.rb +3 -3
  233. data/lib/pubid/iec/identifiers/working_document.rb +2 -50
  234. data/lib/pubid/iec/identifiers.rb +30 -0
  235. data/lib/pubid/iec/parser.rb +13 -12
  236. data/lib/pubid/iec/renderer.rb +254 -0
  237. data/lib/pubid/iec/single_identifier.rb +6 -12
  238. data/lib/pubid/iec/supplement_identifier.rb +58 -54
  239. data/lib/pubid/iec/urn_generator.rb +3 -3
  240. data/lib/pubid/iec/urn_parser.rb +3 -3
  241. data/lib/pubid/iec.rb +43 -67
  242. data/lib/pubid/ieee/builder.rb +12 -12
  243. data/lib/pubid/ieee/components/code.rb +8 -0
  244. data/lib/pubid/ieee/components/draft.rb +14 -0
  245. data/lib/pubid/ieee/components/relationship.rb +5 -149
  246. data/lib/pubid/ieee/identifier.rb +5 -52
  247. data/lib/pubid/ieee/identifiers/adopted_standard.rb +0 -9
  248. data/lib/pubid/ieee/identifiers/base.rb +221 -556
  249. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +0 -10
  250. data/lib/pubid/ieee/identifiers/corrigendum.rb +0 -12
  251. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +0 -10
  252. data/lib/pubid/ieee/identifiers/dual_identifier.rb +0 -4
  253. data/lib/pubid/ieee/identifiers/dual_published.rb +0 -4
  254. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +0 -9
  255. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +0 -10
  256. data/lib/pubid/ieee/identifiers/joint_development.rb +2 -0
  257. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +0 -18
  258. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +0 -6
  259. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +15 -0
  260. data/lib/pubid/ieee/identifiers/redlined_standard.rb +0 -7
  261. data/lib/pubid/ieee/identifiers/si_standard.rb +0 -38
  262. data/lib/pubid/ieee/identifiers/standard.rb +1 -1
  263. data/lib/pubid/ieee/pre_parser.rb +301 -0
  264. data/lib/pubid/ieee/renderer.rb +307 -0
  265. data/lib/pubid/ieee/urn_parser.rb +34 -0
  266. data/lib/pubid/ieee.rb +66 -1
  267. data/lib/pubid/ieee_debug.rb +0 -1
  268. data/lib/pubid/iho/builder.rb +2 -2
  269. data/lib/pubid/iho/identifier.rb +7 -59
  270. data/lib/pubid/iho/identifiers/base.rb +64 -31
  271. data/lib/pubid/iho/parser.rb +3 -3
  272. data/lib/pubid/iho/renderer.rb +30 -0
  273. data/lib/pubid/iho/urn_generator.rb +2 -2
  274. data/lib/pubid/iho/urn_parser.rb +58 -0
  275. data/lib/pubid/iho.rb +52 -1
  276. data/lib/pubid/iso/builder.rb +55 -53
  277. data/lib/pubid/iso/bundled_identifier.rb +51 -0
  278. data/lib/pubid/iso/components/code.rb +7 -19
  279. data/lib/pubid/iso/components/publisher.rb +10 -8
  280. data/lib/pubid/iso/components.rb +2 -4
  281. data/lib/pubid/iso/identifier.rb +219 -252
  282. data/lib/pubid/iso/identifiers/addendum.rb +9 -6
  283. data/lib/pubid/iso/identifiers/amendment.rb +8 -4
  284. data/lib/pubid/iso/identifiers/corrigendum.rb +4 -4
  285. data/lib/pubid/iso/identifiers/data.rb +0 -1
  286. data/lib/pubid/iso/identifiers/directives.rb +8 -2
  287. data/lib/pubid/iso/identifiers/directives_supplement.rb +43 -14
  288. data/lib/pubid/iso/identifiers/extract.rb +2 -2
  289. data/lib/pubid/iso/identifiers/guide.rb +0 -1
  290. data/lib/pubid/iso/identifiers/international_standard.rb +4 -4
  291. data/lib/pubid/iso/identifiers/international_standardized_profile.rb +4 -4
  292. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +10 -4
  293. data/lib/pubid/iso/identifiers/pas.rb +2 -2
  294. data/lib/pubid/iso/identifiers/recommendation.rb +2 -2
  295. data/lib/pubid/iso/identifiers/supplement.rb +11 -3
  296. data/lib/pubid/iso/identifiers/tc_document.rb +44 -15
  297. data/lib/pubid/iso/identifiers/technical_report.rb +4 -4
  298. data/lib/pubid/iso/identifiers/technical_specification.rb +2 -2
  299. data/lib/pubid/iso/identifiers/technology_trends_assessments.rb +2 -2
  300. data/lib/pubid/iso/identifiers.rb +0 -1
  301. data/lib/pubid/iso/normalizer.rb +89 -0
  302. data/lib/pubid/iso/parser.rb +22 -4
  303. data/lib/pubid/iso/single_identifier.rb +0 -4
  304. data/lib/pubid/iso/supplement_identifier.rb +15 -2
  305. data/lib/pubid/iso/urn_generator.rb +66 -182
  306. data/lib/pubid/iso/urn_parser.rb +12 -7
  307. data/lib/pubid/iso.rb +177 -2
  308. data/lib/pubid/itu/builder.rb +0 -12
  309. data/lib/pubid/itu/components/code.rb +9 -7
  310. data/lib/pubid/itu/components/sector.rb +15 -4
  311. data/lib/pubid/itu/components/series.rb +0 -4
  312. data/lib/pubid/itu/components.rb +11 -0
  313. data/lib/pubid/itu/identifier.rb +4 -116
  314. data/lib/pubid/itu/identifiers/amendment.rb +0 -2
  315. data/lib/pubid/itu/identifiers/annex.rb +0 -2
  316. data/lib/pubid/itu/identifiers/base.rb +124 -128
  317. data/lib/pubid/itu/identifiers/combined_identifier.rb +0 -2
  318. data/lib/pubid/itu/identifiers/corrigendum.rb +0 -2
  319. data/lib/pubid/itu/identifiers/recommendation.rb +0 -2
  320. data/lib/pubid/itu/identifiers/special_publication.rb +0 -2
  321. data/lib/pubid/itu/identifiers/supplement.rb +0 -2
  322. data/lib/pubid/itu/urn_parser.rb +23 -0
  323. data/lib/pubid/itu.rb +44 -1
  324. data/lib/pubid/jcgm/builder.rb +16 -8
  325. data/lib/pubid/jcgm/identifier.rb +0 -43
  326. data/lib/pubid/jcgm/identifiers/amendment.rb +0 -9
  327. data/lib/pubid/jcgm/identifiers/gum_guide.rb +0 -12
  328. data/lib/pubid/jcgm/renderer.rb +68 -0
  329. data/lib/pubid/jcgm/single_identifier.rb +0 -8
  330. data/lib/pubid/jcgm/urn_generator.rb +4 -6
  331. data/lib/pubid/jcgm/urn_parser.rb +23 -0
  332. data/lib/pubid/jcgm.rb +43 -2
  333. data/lib/pubid/jis/builder.rb +44 -52
  334. data/lib/pubid/jis/identifier.rb +120 -46
  335. data/lib/pubid/jis/identifiers/amendment.rb +1 -1
  336. data/lib/pubid/jis/identifiers/corrigendum.rb +16 -0
  337. data/lib/pubid/jis/identifiers/standard.rb +2 -1
  338. data/lib/pubid/jis/identifiers/technical_report.rb +2 -1
  339. data/lib/pubid/jis/identifiers/technical_specification.rb +2 -1
  340. data/lib/pubid/jis/identifiers.rb +1 -1
  341. data/lib/pubid/jis/parser.rb +31 -5
  342. data/lib/pubid/jis/renderer.rb +69 -0
  343. data/lib/pubid/jis/single_identifier.rb +6 -12
  344. data/lib/pubid/jis/supplement_identifier.rb +17 -14
  345. data/lib/pubid/jis/urn_parser.rb +23 -0
  346. data/lib/pubid/jis.rb +42 -2
  347. data/lib/pubid/nist/builder.rb +63 -1871
  348. data/lib/pubid/nist/caster.rb +1272 -0
  349. data/lib/pubid/nist/circular_supplement_builder.rb +291 -0
  350. data/lib/pubid/nist/components/code.rb +9 -20
  351. data/lib/pubid/nist/components/supplement.rb +2 -2
  352. data/lib/pubid/nist/components.rb +0 -1
  353. data/lib/pubid/nist/identifier.rb +8 -60
  354. data/lib/pubid/nist/identifiers/base.rb +564 -492
  355. data/lib/pubid/nist/identifiers/circular.rb +7 -2
  356. data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -1
  357. data/lib/pubid/nist/identifiers/commercial_standard.rb +2 -1
  358. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +6 -4
  359. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +10 -3
  360. data/lib/pubid/nist/identifiers/crpl_report.rb +8 -8
  361. data/lib/pubid/nist/identifiers/dated_document.rb +49 -0
  362. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +15 -24
  363. data/lib/pubid/nist/identifiers/grant_contractor_report.rb +2 -1
  364. data/lib/pubid/nist/identifiers/handbook.rb +2 -1
  365. data/lib/pubid/nist/identifiers/internal_report.rb +2 -1
  366. data/lib/pubid/nist/identifiers/letter_circular.rb +2 -1
  367. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +5 -4
  368. data/lib/pubid/nist/identifiers/monograph.rb +7 -3
  369. data/lib/pubid/nist/identifiers/report.rb +4 -2
  370. data/lib/pubid/nist/identifiers/special_publication.rb +2 -1
  371. data/lib/pubid/nist/identifiers/technical_note.rb +3 -2
  372. data/lib/pubid/nist/identifiers.rb +1 -0
  373. data/lib/pubid/nist/parser.rb +62 -452
  374. data/lib/pubid/nist/parser_output_normalizer.rb +233 -0
  375. data/lib/pubid/nist/preprocessor.rb +428 -0
  376. data/lib/pubid/nist/renderer.rb +43 -0
  377. data/lib/pubid/nist/router.rb +148 -0
  378. data/lib/pubid/nist/series/base.rb +58 -0
  379. data/lib/pubid/nist/series/crpl.rb +13 -0
  380. data/lib/pubid/nist/series/fips.rb +14 -0
  381. data/lib/pubid/nist/series/ir.rb +60 -0
  382. data/lib/pubid/nist/series/letter_preserving.rb +15 -0
  383. data/lib/pubid/nist/series/mono.rb +19 -0
  384. data/lib/pubid/nist/series/ncstar.rb +20 -0
  385. data/lib/pubid/nist/series.rb +49 -0
  386. data/lib/pubid/nist/supplement_identifier.rb +3 -1
  387. data/lib/pubid/nist/urn_parser.rb +67 -0
  388. data/lib/pubid/nist.rb +86 -4
  389. data/lib/pubid/oiml/builder.rb +21 -3
  390. data/lib/pubid/oiml/components/code.rb +17 -0
  391. data/lib/pubid/oiml/identifier.rb +40 -46
  392. data/lib/pubid/oiml/identifiers/annex.rb +13 -46
  393. data/lib/pubid/oiml/identifiers/errata.rb +15 -0
  394. data/lib/pubid/oiml/identifiers.rb +1 -1
  395. data/lib/pubid/oiml/parser.rb +59 -12
  396. data/lib/pubid/oiml/renderer.rb +171 -0
  397. data/lib/pubid/oiml/single_identifier.rb +61 -42
  398. data/lib/pubid/oiml/supplement_identifier.rb +31 -17
  399. data/lib/pubid/oiml/urn_generator.rb +0 -8
  400. data/lib/pubid/oiml/urn_parser.rb +22 -0
  401. data/lib/pubid/oiml.rb +42 -1
  402. data/lib/pubid/plateau/identifier.rb +4 -55
  403. data/lib/pubid/plateau/identifiers/base.rb +45 -36
  404. data/lib/pubid/plateau/identifiers/handbook.rb +0 -6
  405. data/lib/pubid/plateau/identifiers/technical_report.rb +0 -4
  406. data/lib/pubid/plateau/renderer.rb +51 -0
  407. data/lib/pubid/plateau/supplement_identifier.rb +0 -4
  408. data/lib/pubid/plateau/urn_parser.rb +43 -0
  409. data/lib/pubid/plateau.rb +43 -1
  410. data/lib/pubid/renderers/directives_renderer.rb +22 -8
  411. data/lib/pubid/renderers/guide_renderer.rb +4 -2
  412. data/lib/pubid/renderers/human_readable.rb +18 -7
  413. data/lib/pubid/rendering/context.rb +28 -19
  414. data/lib/pubid/rendering.rb +0 -3
  415. data/lib/pubid/sae/components/date.rb +8 -0
  416. data/lib/pubid/sae/components/type.rb +5 -1
  417. data/lib/pubid/sae/identifier.rb +4 -35
  418. data/lib/pubid/sae/identifiers/base.rb +26 -31
  419. data/lib/pubid/sae/renderer.rb +36 -0
  420. data/lib/pubid/sae/urn_generator.rb +2 -10
  421. data/lib/pubid/sae/urn_parser.rb +36 -0
  422. data/lib/pubid/sae.rb +42 -1
  423. data/lib/pubid/urn_generator/base.rb +12 -12
  424. data/lib/pubid/urn_parser/base.rb +81 -0
  425. data/lib/pubid/urn_parser/errors.rb +9 -0
  426. data/lib/pubid/urn_parser.rb +14 -0
  427. data/lib/pubid/version.rb +1 -1
  428. data/lib/pubid.rb +45 -7
  429. data/lib/tasks/export.rake +6 -5
  430. data/lib/tasks/website-data.json +1940 -1882
  431. metadata +75 -45
  432. data/lib/pubid/amca/scheme.rb +0 -16
  433. data/lib/pubid/ansi/scheme.rb +0 -15
  434. data/lib/pubid/api/scheme.rb +0 -66
  435. data/lib/pubid/ashrae/scheme.rb +0 -53
  436. data/lib/pubid/asme/scheme.rb +0 -37
  437. data/lib/pubid/astm/scheme.rb +0 -55
  438. data/lib/pubid/bsi/identifiers/base.rb +0 -11
  439. data/lib/pubid/bsi/scheme.rb +0 -243
  440. data/lib/pubid/ccsds/scheme.rb +0 -57
  441. data/lib/pubid/cen_cenelec/scheme.rb +0 -164
  442. data/lib/pubid/cie/scheme.rb +0 -64
  443. data/lib/pubid/components/factory.rb +0 -50
  444. data/lib/pubid/csa/scheme.rb +0 -44
  445. data/lib/pubid/etsi/scheme.rb +0 -42
  446. data/lib/pubid/export/data_class_exporter.rb +0 -59
  447. data/lib/pubid/export/ieee_exporter.rb +0 -78
  448. data/lib/pubid/export/itu_exporter.rb +0 -66
  449. data/lib/pubid/export/nist_exporter.rb +0 -64
  450. data/lib/pubid/export/registry_exporter.rb +0 -90
  451. data/lib/pubid/export/scheme_exporter.rb +0 -70
  452. data/lib/pubid/identifier_registry.rb +0 -198
  453. data/lib/pubid/idf/scheme.rb +0 -61
  454. data/lib/pubid/iec/scheme.rb +0 -71
  455. data/lib/pubid/ieee/scheme.rb +0 -90
  456. data/lib/pubid/iho/scheme.rb +0 -29
  457. data/lib/pubid/iso/identifiers/base.rb +0 -115
  458. data/lib/pubid/iso/scheme.rb +0 -193
  459. data/lib/pubid/itu/scheme.rb +0 -174
  460. data/lib/pubid/jcgm/scheme.rb +0 -60
  461. data/lib/pubid/jis/components/code.rb +0 -59
  462. data/lib/pubid/jis/identifiers/base.rb +0 -72
  463. data/lib/pubid/jis/scheme.rb +0 -49
  464. data/lib/pubid/nist/components/publisher.rb +0 -24
  465. data/lib/pubid/nist/scheme.rb +0 -199
  466. data/lib/pubid/oiml/identifiers/base.rb +0 -36
  467. data/lib/pubid/oiml/scheme.rb +0 -46
  468. data/lib/pubid/plateau/scheme.rb +0 -45
  469. data/lib/pubid/rendering/base.rb +0 -73
  470. data/lib/pubid/rendering/common.rb +0 -211
  471. data/lib/pubid/rendering/format.rb +0 -25
  472. data/lib/pubid/sae/scheme.rb +0 -47
  473. data/lib/pubid/scheme.rb +0 -219
@@ -2,577 +2,649 @@
2
2
 
3
3
  module Pubid
4
4
  module Nist
5
- module Identifiers
6
- # Base NIST/NBS identifier class
7
- # Each series type inherits from this and overrides series_code
8
- class Base < Pubid::Identifier
9
- # Default: no typed stages. Subclasses override as needed.
10
- def self.typed_stages
11
- []
12
- end
5
+ # NIST/NBS flavor base class. Canonical name Pubid::Nist::Identifier; every
6
+ # concrete NIST identifier (Identifiers::*) descends from it, and
7
+ # Identifiers::Base aliased at the foot of this file points back to it
8
+ # for backward compatibility. Being a real Pubid::Identifier subclass gives
9
+ # native `is_a?` identity and the shared polymorphic `from_hash`.
10
+ class Identifier < ::Pubid::Identifier
11
+ # Delegate to the flavor module so callers can use
12
+ # `Pubid::Nist::Identifier.parse` consistently with other flavors.
13
+ def self.parse(identifier)
14
+ Pubid::Nist.parse(identifier)
15
+ end
13
16
 
14
- # Generate URN for this identifier
15
- #
16
- # @return [String] URN representation
17
-
18
- attribute :publisher, Components::Publisher
19
- attribute :series, Components::Code # Set by Builder from parsed data
20
- attribute :number, Components::Code
21
-
22
- # V2 COMPONENTS (Lutaml::Model objects) - PROPER SEPARATION
23
- attribute :edition, Components::Edition # Edition (type + id): e2, e2021, r5, -3
24
- attribute :edition_component, Components::Edition # V2 edition component (alias)
25
- attribute :volume, Components::Volume # Volume component (v6)
26
- attribute :part, Components::Part # Part component (n1 or pt1)
27
- attribute :stage, Components::Stage
28
- attribute :version_component, Components::Version
29
- attribute :update_component, Components::Update
30
- attribute :translation_component, Components::Translation
31
- attribute :issue_number, Components::IssueNumber
32
- attribute :parsed_format, :string # :mr, :short, :long, :abbrev
33
- attribute :publisher_was_parsed, :boolean, default: -> { false }
34
-
35
- # LEGACY attributes (keep for backward compatibility during migration)
36
- attribute :parts, Components::Code, collection: true
37
- attribute :revision, :string
38
- attribute :revision_year, :string # Year for revision (e.g., r6/1925, r1963, rJun1992)
39
- attribute :revision_month, :string # Month for revision (e.g., rJun1992)
40
- attribute :edition_year, :string # Legacy edition year for backward compatibility
41
- attribute :version, :string
42
- attribute :update, Components::Update
43
- attribute :year, :integer
44
- attribute :month, :integer
45
-
46
- # Additional attributes for complex patterns
47
- attribute :first_number, Components::Code
48
- attribute :second_number, Components::Code
49
- attribute :update_number, :string
50
- attribute :update_year, :string
51
- attribute :addendum, :string
52
- attribute :addendum_number, :string
53
- # Single source of truth for the supplement: a structured component with
54
- # isolated parts (number / year / month / date-range / revision), so a
55
- # supplement's year is queryable independently of its number. Presence
56
- # (non-nil) means "is a supplement"; an all-empty component is a bare
57
- # "sup" marker. Replaces the former flat :supplement string plus the
58
- # separate date-range/has_revision fields.
59
- attribute :supplement, Components::Supplement
60
- attribute :errata, :string
61
- attribute :index, :string
62
- attribute :insert, :string
63
- attribute :section, :string
64
- attribute :appendix, :string
65
- attribute :translation, :string
66
- attribute :draft, :string
67
- attribute :draft_number, :string # For -draft N → N pd rendering
68
-
69
- # Default series_code — subclasses override to provide a normalized series name.
70
- def series_code
71
- nil
72
- end
17
+ # Default: no typed stages. Subclasses override as needed.
18
+ def self.typed_stages
19
+ []
20
+ end
73
21
 
74
- def initialize(**attributes)
75
- super()
22
+ # Generate URN for this identifier
23
+ #
24
+ # @return [String] URN representation
25
+
26
+ # Plain string ("NIST"/"NBS"), not a Components::Publisher wrapper: the
27
+ # value is a single token, so a string serializes flat
28
+ # (`publisher: NBS`, not `publisher: {publisher: NBS}`) and accepts the
29
+ # raw string the circular/supplement builders pass straight through.
30
+ attribute :publisher, :string
31
+ attribute :series, Components::Code # Set by Builder from parsed data
32
+ attribute :number, Components::Code
33
+
34
+ # V2 COMPONENTS (Lutaml::Model objects) - PROPER SEPARATION
35
+ attribute :edition, Components::Edition # Edition (type + id): e2, e2021, r5, -3
36
+ attribute :edition_component, Components::Edition # V2 edition component (alias)
37
+ attribute :volume, Components::Volume # Volume component (v6)
38
+ attribute :part, Components::Part # Part component (n1 or pt1)
39
+ attribute :stage, Components::Stage
40
+ attribute :version_component, Components::Version
41
+ attribute :update_component, Components::Update
42
+ attribute :translation_component, Components::Translation
43
+ attribute :issue_number, Components::IssueNumber
44
+ attribute :parsed_format, :string # :mr, :short, :long, :abbrev
45
+ # Whether the publisher prefix (NIST/NBS) should be rendered. Defaults
46
+ # to true so the common publisher-bearing id need not serialize the
47
+ # flag at all — the Builder only assigns it (false) for prefix-less
48
+ # inputs, so `to_hash` carries `publisher_was_parsed: false` only in
49
+ # that case and omits it otherwise. (lutaml emits a boolean iff it was
50
+ # explicitly assigned; an omitted key loads as this default.)
51
+ attribute :publisher_was_parsed, :boolean, default: -> { true }
52
+
53
+ # LEGACY attributes (keep for backward compatibility during migration)
54
+ attribute :parts, Components::Code, collection: true
55
+ attribute :revision, :string
56
+ attribute :revision_year, :string # Year for revision (e.g., r6/1925, r1963, rJun1992)
57
+ attribute :revision_month, :string # Month for revision (e.g., rJun1992)
58
+ attribute :edition_year, :string # Legacy edition year for backward compatibility
59
+ attribute :version, :string
60
+ attribute :update, Components::Update
61
+ attribute :year, :integer
62
+ attribute :month, :integer
63
+
64
+ # Additional attributes for complex patterns
65
+ attribute :first_number, Components::Code
66
+ attribute :second_number, Components::Code
67
+ attribute :update_number, :string
68
+ attribute :update_year, :string
69
+ attribute :addendum, :string
70
+ attribute :addendum_number, :string
71
+ # Single source of truth for the supplement: a structured component with
72
+ # isolated parts (number / year / month / date-range / revision), so a
73
+ # supplement's year is queryable independently of its number. Presence
74
+ # (non-nil) means "is a supplement"; an all-empty component is a bare
75
+ # "sup" marker. Replaces the former flat :supplement string plus the
76
+ # separate date-range/has_revision fields.
77
+ attribute :supplement, Components::Supplement
78
+ attribute :errata, :string
79
+ attribute :index, :string
80
+ attribute :insert, :string
81
+ attribute :section, :string
82
+ attribute :appendix, :string
83
+ attribute :translation, :string
84
+ attribute :draft, :string
85
+ attribute :draft_number, :string # For -draft N → N pd rendering
86
+
87
+ # Default series_code — subclasses override to provide a normalized series name.
88
+ def series_code
89
+ nil
90
+ end
76
91
 
77
- attrs = self.class.attributes
78
- attributes.each do |key, value|
79
- next if value.nil?
92
+ def initialize(**attributes)
93
+ super()
80
94
 
81
- setter = :"#{key}="
82
- public_send(setter, value) if attrs.key?(key)
83
- end
95
+ attrs = self.class.attributes
96
+ attributes.each do |key, value|
97
+ next if value.nil?
84
98
 
85
- # NOTE: Compound number building is handled by the Builder class
86
- # Do NOT build compound numbers here - let the builder apply special patterns first
87
- # See lib/pubid/nist/builder.rb lines 368-472 for compound number logic
99
+ setter = :"#{key}="
100
+ public_send(setter, value) if attrs.key?(key)
88
101
  end
89
102
 
90
- # Attributes that are build artifacts or rendering aliases, not part
91
- # of an identifier's logical identity. They diverge between equally-
92
- # valid spellings of the same id (e.g. long "Rev. 1" vs short "r1"):
93
- # - edition_component: redundant alias of :edition
94
- # - first_number/second_number: decomposed parts of the canonical
95
- # :number, retained from the parse for building
96
- # - parsed_format: records the input format for round-trip rendering
97
- EQUALITY_IGNORED_ATTRS = %i[
98
- edition_component first_number second_number parsed_format
99
- ].freeze
100
-
101
- # Logical identity comparison: equal when every attribute except the
102
- # build artifacts/aliases above matches. (Edition#== already ignores
103
- # its rendering-only original_prefix.)
104
- def ==(other)
105
- return false unless other.instance_of?(self.class)
106
-
107
- self.class.attributes.each_key.all? do |name|
108
- EQUALITY_IGNORED_ATTRS.include?(name) || send(name) == other.send(name)
109
- end
103
+ # NOTE: Compound number building is handled by the Builder class
104
+ # Do NOT build compound numbers here - let the builder apply special patterns first
105
+ # See lib/pubid/nist/builder.rb lines 368-472 for compound number logic
106
+ end
107
+
108
+ # Attributes that are build artifacts or rendering aliases, not part
109
+ # of an identifier's logical identity. They diverge between equally-
110
+ # valid spellings of the same id (e.g. long "Rev. 1" vs short "r1"):
111
+ # - edition_component: redundant alias of :edition
112
+ # - first_number/second_number: decomposed parts of the canonical
113
+ # :number, retained from the parse for building
114
+ # - parsed_format: records the input format for round-trip rendering
115
+ EQUALITY_IGNORED_ATTRS = %i[
116
+ edition_component first_number second_number parsed_format
117
+ ].freeze
118
+
119
+ # Logical identity comparison: equal when every attribute except the
120
+ # build artifacts/aliases above matches. (Edition#== already ignores
121
+ # its rendering-only original_prefix.)
122
+ def ==(other)
123
+ return false unless other.instance_of?(self.class)
124
+
125
+ self.class.attributes.each_key.all? do |name|
126
+ EQUALITY_IGNORED_ATTRS.include?(name) || public_send(name) == other.public_send(name)
110
127
  end
128
+ end
111
129
 
112
- alias eql? ==
130
+ alias eql? ==
113
131
 
114
- def hash
115
- vals = self.class.attributes.each_key.reject do |name|
116
- EQUALITY_IGNORED_ATTRS.include?(name)
117
- end.map { |name| send(name) }
118
- [self.class, *vals].hash
119
- end
132
+ def hash
133
+ vals = self.class.attributes.each_key.reject do |name|
134
+ EQUALITY_IGNORED_ATTRS.include?(name)
135
+ end.map { |name| public_send(name) }
136
+ [self.class, *vals].hash
137
+ end
120
138
 
121
- # Wildcard / partial-identifier match. Treats +self+ as a QUERY pattern
122
- # and +candidate+ as a concrete document: every ID part SET on the query
123
- # must equal the candidate's, while parts left unset (nil/empty) are
124
- # wildcards that match any value. So a query carrying no edition and no
125
- # supplement matches that document across ALL editions, years, and
126
- # supplements — the basis for "select docs by ID parts".
127
- #
128
- # Asymmetric (unlike ==): "NBS CIRC 25".matches?("NBS CIRC 25sup1924")
129
- # is true, but not the reverse. The candidate must be the same class or
130
- # a subclass so series-level identity still holds.
131
- def matches?(candidate)
132
- return false unless candidate.is_a?(self.class)
133
-
134
- self.class.attributes.each_key.all? do |name|
135
- next true if EQUALITY_IGNORED_ATTRS.include?(name)
136
-
137
- query_val = public_send(name)
138
- next true if query_val.nil? ||
139
- (query_val.respond_to?(:empty?) && query_val.empty?)
140
-
141
- query_val == candidate.public_send(name)
142
- end
139
+ # Wildcard / partial-identifier match. Treats +self+ as a QUERY pattern
140
+ # and +candidate+ as a concrete document: every ID part SET on the query
141
+ # must equal the candidate's, while parts left unset (nil/empty) are
142
+ # wildcards that match any value. So a query carrying no edition and no
143
+ # supplement matches that document across ALL editions, years, and
144
+ # supplements — the basis for "select docs by ID parts".
145
+ #
146
+ # Asymmetric (unlike ==): "NBS CIRC 25".matches?("NBS CIRC 25sup1924")
147
+ # is true, but not the reverse. The candidate must be the same class or
148
+ # a subclass so series-level identity still holds.
149
+ def matches?(candidate)
150
+ return false unless candidate.is_a?(self.class)
151
+
152
+ self.class.attributes.each_key.all? do |name|
153
+ next true if EQUALITY_IGNORED_ATTRS.include?(name)
154
+
155
+ query_val = public_send(name)
156
+ next true if query_val.nil?
157
+ next true if query_val.is_a?(String) && query_val.empty?
158
+ next true if query_val.is_a?(Array) && query_val.empty?
159
+
160
+ query_val == candidate.public_send(name)
143
161
  end
162
+ end
144
163
 
145
- # Return a copy with the named attributes nil'd. Overrides
146
- # Pubid::Identifier#exclude because NIST's initialize is keyword-only
147
- # (initialize(**attributes)) while the inherited exclude rebuilds via
148
- # the positional self.class.new(attrs) form — passing a positional
149
- # hash to a keyword-only initializer raises ArgumentError. Rebuild
150
- # with the keyword splat instead.
151
- def exclude(*args)
152
- excluded_args = args.dup
153
- excluded_args << :date if excluded_args.delete(:year)
154
-
155
- attrs = self.class.attributes.each_with_object({}) do |(name, _), h|
156
- h[name] = excluded_args.include?(name) ? nil : send(name)
157
- end
158
- self.class.new(**attrs)
164
+ # Return a copy with the named attributes nil'd. Overrides
165
+ # Pubid::Identifier#exclude because NIST's initialize is keyword-only
166
+ # (initialize(**attributes)) while the inherited exclude rebuilds via
167
+ # the positional self.class.new(attrs) form — passing a positional
168
+ # hash to a keyword-only initializer raises ArgumentError. Rebuild
169
+ # with the keyword splat instead.
170
+ def exclude(*args)
171
+ excluded_args = args.dup
172
+ excluded_args << :date if excluded_args.delete(:year)
173
+
174
+ attrs = self.class.attributes.each_with_object({}) do |(name, _), h|
175
+ h[name] = excluded_args.include?(name) ? nil : public_send(name)
159
176
  end
177
+ self.class.new(**attrs)
178
+ end
160
179
 
161
- # Short-form supplement fragment ("sup", "sup1924", "supJan1924",
162
- # "suprev", " supJun1925-Jun1926"), rendered from the structured
163
- # component. A present-but-empty component is the bare "sup" marker; a
164
- # number-less date range gets the leading space the number would have
165
- # supplied. Shared by base and the per-series to_short_style overrides.
166
- def supplement_short
167
- return "" unless supplement
168
-
169
- prefix = (supplement.range? && !number) ? " " : ""
170
- rendered = supplement.to_s(:short)
171
- prefix + (rendered.empty? ? "sup" : rendered)
172
- end
180
+ # Short-form supplement fragment ("sup", "sup1924", "supJan1924",
181
+ # "suprev", " supJun1925-Jun1926"), rendered from the structured
182
+ # component. A present-but-empty component is the bare "sup" marker; a
183
+ # number-less date range gets the leading space the number would have
184
+ # supplied. Shared by base and the per-series to_short_style overrides.
185
+ def supplement_short
186
+ return "" unless supplement
187
+
188
+ prefix = supplement.range? && !number ? " " : ""
189
+ rendered = supplement.to_s(:short)
190
+ prefix + (rendered.empty? ? "sup" : rendered)
191
+ end
173
192
 
174
- # Compute revision from edition component for backward compatibility
175
- # @return [String, nil] revision string (e.g., "r5") or nil
176
- def revision
177
- return @revision if @revision
193
+ # Compute revision from edition component for backward compatibility
194
+ # @return [String, nil] revision string (e.g., "r5") or nil
195
+ def revision
196
+ return @revision if @revision
178
197
 
179
- # Compute from edition component if available
180
- if edition&.type && edition.id
181
- "#{edition.type}#{edition.id}"
182
- end
198
+ # Compute from edition component if available
199
+ if edition&.type && edition.id
200
+ "#{edition.type}#{edition.id}"
183
201
  end
202
+ end
184
203
 
185
- # Backward compatibility: translation method returns translation_component
186
- # This allows tests to use parsed.translation.language instead of parsed.translation_component.language
187
- def translation
188
- translation_component
189
- end
204
+ # Backward compatibility: translation method returns translation_component
205
+ # This allows tests to use parsed.translation.language instead of parsed.translation_component.language
206
+ def translation
207
+ translation_component
208
+ end
190
209
 
191
- # Backward compatibility: language method returns translation_component
192
- # This allows tests to use parsed.language instead of parsed.translation_component
193
- def language
194
- translation_component
195
- end
210
+ # Backward compatibility: language method returns translation_component
211
+ # This allows tests to use parsed.language instead of parsed.translation_component
212
+ def language
213
+ translation_component
214
+ end
196
215
 
197
- # Generate identifier string in specified format
198
- # @param format [:full, :long, :abbreviated, :short, :mr] output format
199
- def to_s(format = nil)
200
- # Handle both keyword argument (hash) and positional argument (symbol/string)
201
- format = format[:format] if format.is_a?(Hash)
202
-
203
- # Default to parsed_format if available (preserves input format on round-trip)
204
- # Falls back to :short format for output (normalization)
205
- # Explicit format parameter always overrides parsed_format
206
- effective_format = format || parsed_format&.to_sym || :short
207
- effective_format = effective_format.to_sym if effective_format.is_a?(String)
208
- case effective_format
209
- when :full, :long
210
- to_full_style
211
- when :abbreviated, :abbrev
212
- to_abbreviated_style
213
- when :short
214
- to_short_style
215
- when :mr
216
- to_mr_style
217
- else
218
- to_short_style
219
- end
216
+ # Override parent render to pass NIST-specific format option through
217
+ # to the renderer. Pubid::Identifier#render only forwards
218
+ # :with_edition; NIST needs :nist_format for multi-format support.
219
+ def render(format: :human, **opts)
220
+ registry = self.class.format_registry
221
+ unless registry
222
+ raise ArgumentError, "No format registry configured on #{self.class}"
220
223
  end
221
224
 
222
- # Returns weight based on amount of defined attributes
223
- # Used for ranking identifiers by specificity for conflict resolution
224
- # @return [Integer] weight score (higher = more specific)
225
- def weight
226
- self.class.attributes.keys.inject(0) do |sum, key|
227
- val = public_send(key)
228
- val && !val.to_s.empty? ? sum + 1 : sum
229
- end
225
+ renderer = registry.renderer_for(format)
226
+ unless renderer
227
+ raise ArgumentError, "No renderer registered for format: #{format}"
230
228
  end
231
229
 
232
- # Merge another document into this one
233
- # Used for combining document data, preferring more specific values
234
- # @param document [Base] another NIST document to merge
235
- # @return [Base] self with merged attributes
236
- def merge(document)
237
- return self unless document.is_a?(Base)
238
-
239
- attrs = self.class.attributes
240
- attrs.each_key do |var_name|
241
- next if var_name == :rendering_style
242
- next if var_name == :parsed_format
243
-
244
- current_val = public_send(var_name)
245
- new_val = document.public_send(var_name)
246
- next unless new_val
247
-
248
- should_merge = case var_name
249
- when :publisher, :series, :number
250
- true
251
- when :edition
252
- current_val.nil? || edition_greater?(new_val,
253
- current_val)
254
- when :volume, :part, :version, :revision
255
- current_val.nil? || (new_val.to_s.length > current_val.to_s.length)
256
- when :supplement, :errata, :index, :insert, :section, :appendix, :translation
257
- true
258
- when :year, :month, :update, :draft
259
- true
260
- else
261
- false
262
- end
230
+ renderer.new(self).render(**opts)
231
+ end
263
232
 
264
- public_send(:"#{var_name}=", new_val) if should_merge
265
- end
233
+ # Generate identifier string in specified format
234
+ # @param format [:full, :long, :abbreviated, :short, :mr] output format
235
+ def to_s(format = nil)
236
+ # Handle both keyword argument (hash) and positional argument (symbol/string)
237
+ format = format[:format] if format.is_a?(Hash)
238
+
239
+ # Default to parsed_format if available (preserves input format on round-trip)
240
+ # Falls back to :short format for output (normalization)
241
+ # Explicit format parameter always overrides parsed_format
242
+ render(format: :human,
243
+ nist_format: format || parsed_format&.to_sym || :short)
244
+ end
266
245
 
267
- self
246
+ # Returns weight based on amount of defined attributes
247
+ # Used for ranking identifiers by specificity for conflict resolution
248
+ # @return [Integer] weight score (higher = more specific)
249
+ def weight
250
+ self.class.attributes.keys.inject(0) do |sum, key|
251
+ val = public_send(key)
252
+ val && !val.to_s.empty? ? sum + 1 : sum
268
253
  end
254
+ end
269
255
 
270
- # Helper to compare edition values numerically
271
- # @return [Boolean] true if edition1 is greater than edition2
272
- def edition_greater?(edition1, edition2)
273
- num1 = extract_edition_number(edition1)
274
- num2 = extract_edition_number(edition2)
275
- num1 && num2 && num1 > num2
256
+ # Merge another document into this one
257
+ # Used for combining document data, preferring more specific values
258
+ # @param document [Base] another NIST document to merge
259
+ # @return [Base] self with merged attributes
260
+ def merge(document)
261
+ return self unless document.is_a?(Pubid::Nist::Identifier)
262
+
263
+ attrs = self.class.attributes
264
+ attrs.each_key do |var_name|
265
+ next if var_name == :rendering_style
266
+ next if var_name == :parsed_format
267
+
268
+ current_val = public_send(var_name)
269
+ new_val = document.public_send(var_name)
270
+ next unless new_val
271
+
272
+ should_merge = case var_name
273
+ when :publisher, :series, :number
274
+ true
275
+ when :edition
276
+ current_val.nil? || edition_greater?(new_val,
277
+ current_val)
278
+ when :volume, :part, :version, :revision
279
+ current_val.nil? || (new_val.to_s.length > current_val.to_s.length)
280
+ when :supplement, :errata, :index, :insert, :section, :appendix, :translation
281
+ true
282
+ when :year, :month, :update, :draft
283
+ true
284
+ else
285
+ false
286
+ end
287
+
288
+ public_send(:"#{var_name}=", new_val) if should_merge
276
289
  end
277
290
 
278
- # Extract numeric value from edition (r3 -> 3, r5 -> 5, e2 -> 2)
279
- # @return [Integer, nil] the edition number or nil if not extractable
280
- def extract_edition_number(edition)
281
- # Handle both String and Edition component
282
- edition_str = edition.to_s
283
- # Match patterns like r3, r5, e2, etc.
284
- match = edition_str.match(/^[er]?(\d+)$/)
285
- match ? match[1].to_i : nil
286
- end
291
+ self
292
+ end
293
+
294
+ # Helper to compare edition values numerically
295
+ # @return [Boolean] true if edition1 is greater than edition2
296
+ def edition_greater?(edition1, edition2)
297
+ num1 = extract_edition_number(edition1)
298
+ num2 = extract_edition_number(edition2)
299
+ num1 && num2 && num1 > num2
300
+ end
287
301
 
288
- private
302
+ # Extract numeric value from edition (r3 -> 3, r5 -> 5, e2 -> 2)
303
+ # @return [Integer, nil] the edition number or nil if not extractable
304
+ def extract_edition_number(edition)
305
+ # Handle both String and Edition component
306
+ edition_str = edition.to_s
307
+ # Match patterns like r3, r5, e2, etc.
308
+ match = edition_str.match(/^[er]?(\d+)$/)
309
+ match ? match[1].to_i : nil
310
+ end
289
311
 
290
- def to_full_style
291
- # "National Institute of Standards and Technology Special Publication 800-27, Revision A"
292
- result = publisher_full_name
293
- result += " #{series_full_name}" if series
294
- result += " #{number.value}" if number
295
- result += parts.map { |p| "-#{p}" }.join if parts&.any?
312
+ public
296
313
 
297
- # Render volume and issue number in long form: "Vol. 6, No. 12"
298
- if volume && issue_number
299
- result += " Vol. #{volume}, #{issue_number.to_s(:long)}"
300
- elsif volume
301
- result += " Vol. #{volume}"
302
- end
314
+ def to_full_style
315
+ # "National Institute of Standards and Technology Special Publication 800-27, Revision A"
316
+ result = publisher_full_name
317
+ result += " #{series_full_name}" if series
318
+ result += " #{number.value}" if number
319
+ result += parts.map { |p| "-#{p}" }.join if parts&.any?
303
320
 
304
- # NEW: Use edition component properly
305
- result += " #{edition.to_s(:long)}" if edition
321
+ # Render volume and issue number in long form: "Vol. 6, No. 12"
322
+ if volume && issue_number
323
+ result += " Vol. #{volume}, #{issue_number.to_s(:long)}"
324
+ elsif volume
325
+ result += " Vol. #{volume}"
326
+ end
306
327
 
307
- result += ", Revision #{revision.sub(/^r/, '')}" if revision
328
+ # NEW: Use edition component properly
329
+ result += " #{edition.to_s(:long)}" if edition
308
330
 
309
- # V2: Use version_component
310
- result += " #{version_component.to_s(:long)}" if version_component
331
+ result += ", Revision #{revision.sub(/^r/, '')}" if revision
311
332
 
312
- # V2: Use update_component
313
- result += " #{update_component.to_s(:long)}" if update_component
333
+ # V2: Use version_component
334
+ result += " #{version_component.to_s(:long)}" if version_component
314
335
 
315
- # V2: Use stage
316
- result += " #{stage.to_s(:long)}" if stage
336
+ # V2: Use update_component
337
+ result += " #{update_component.to_s(:long)}" if update_component
317
338
 
318
- # V2: Use translation_component (already includes space)
319
- result += translation_component.to_s(:long) if translation_component
339
+ # V2: Use stage
340
+ result += " #{stage.to_s(:long)}" if stage
320
341
 
321
- result
322
- end
342
+ # V2: Use translation_component (already includes space)
343
+ result += translation_component.to_s(:long) if translation_component
344
+
345
+ result
346
+ end
347
+
348
+ def to_abbreviated_style
349
+ # "Natl. Inst. Stand. Technol. Spec. Publ. 800-57 Part 1, Revision 4"
350
+ result = publisher_abbreviated_name
351
+ result += " #{series_abbreviated_name}" if series
352
+ result += " #{number}" if number
353
+ result += " Part #{parts.first}" if parts&.any?
354
+
355
+ # NEW: Use edition component properly
356
+ result += " #{edition.to_s(:abbrev)}" if edition
323
357
 
324
- def to_abbreviated_style
325
- # "Natl. Inst. Stand. Technol. Spec. Publ. 800-57 Part 1, Revision 4"
326
- result = publisher_abbreviated_name
327
- result += " #{series_abbreviated_name}" if series
328
- result += " #{number}" if number
329
- result += " Part #{parts.first}" if parts&.any?
358
+ result += ", Revision #{revision}" if revision
330
359
 
331
- # NEW: Use edition component properly
332
- result += " #{edition.to_s(:abbrev)}" if edition
360
+ # V2: Use version_component
361
+ result += " #{version_component.to_s(:abbrev)}" if version_component
333
362
 
334
- result += ", Revision #{revision}" if revision
363
+ # V2: Use update_component
364
+ result += " #{update_component.to_s(:abbrev)}" if update_component
335
365
 
336
- # V2: Use version_component
337
- result += " #{version_component.to_s(:abbrev)}" if version_component
366
+ # V2: Use stage
367
+ result += " #{stage.to_s(:abbrev)}" if stage
338
368
 
339
- # V2: Use update_component
340
- result += " #{update_component.to_s(:abbrev)}" if update_component
369
+ # V2: Use translation_component
370
+ result += ", #{translation_component.to_s(:abbrev)}" if translation_component
341
371
 
342
- # V2: Use stage
343
- result += " #{stage.to_s(:abbrev)}" if stage
372
+ result
373
+ end
344
374
 
345
- # V2: Use translation_component
346
- result += ", #{translation_component.to_s(:abbrev)}" if translation_component
375
+ def to_short_style
376
+ # "SP 800-187" or "NIST SP 800-187" - handle compound series properly
377
+ result = ""
378
+
379
+ # Determine effective publisher
380
+ # Only show publisher if it was explicitly parsed (either directly or from series prefix)
381
+ effective_publisher = if publisher && publisher_was_parsed
382
+ # Publisher was in input (either as separate field or extracted from "NBS CS" series)
383
+ publisher.to_s
384
+ else
385
+ # No publisher in input, don't show default
386
+ nil
387
+ end
388
+
389
+ # Determine effective series - PREFER series_code if subclass defines it
390
+ # This allows normalization (e.g., LCIRC → LC in LetterCircular)
391
+ effective_series = if series_code
392
+ series_code
393
+ elsif series
394
+ series.to_s
395
+ end
347
396
 
348
- result
397
+ # Special handling for compound series that include publisher prefix
398
+ # If series starts with "NBS " (like "NBS CIRC"), use it as-is
399
+ if effective_series&.start_with?("NBS ")
400
+ result += effective_series
401
+ elsif effective_publisher && effective_series
402
+ result += "#{effective_publisher} #{effective_series}"
403
+ elsif effective_series && publisher_was_parsed
404
+ # Only add "NIST" prefix if publisher was explicitly in the input
405
+ result += "NIST #{effective_series}"
406
+ elsif effective_series
407
+ # No publisher in input, just show series without prefix
408
+ result += effective_series
349
409
  end
350
410
 
351
- def to_short_style
352
- # "SP 800-187" or "NIST SP 800-187" - handle compound series properly
353
- result = ""
354
-
355
- # Determine effective publisher
356
- # Only show publisher if it was explicitly parsed (either directly or from series prefix)
357
- effective_publisher = if publisher && publisher_was_parsed
358
- # Publisher was in input (either as separate field or extracted from "NBS CS" series)
359
- publisher.to_s
360
- else
361
- # No publisher in input, don't show default
362
- nil
363
- end
364
-
365
- # Determine effective series - PREFER series_code if subclass defines it
366
- # This allows normalization (e.g., LCIRC → LC in LetterCircular)
367
- effective_series = if series_code
368
- series_code
369
- elsif series
370
- series.to_s
371
- end
372
-
373
- # Special handling for compound series that include publisher prefix
374
- # If series starts with "NBS " (like "NBS CIRC"), use it as-is
375
- if effective_series&.start_with?("NBS ")
376
- result += effective_series
377
- elsif effective_publisher && effective_series
378
- result += "#{effective_publisher} #{effective_series}"
379
- elsif effective_series && publisher_was_parsed
380
- # Only add "NIST" prefix if publisher was explicitly in the input
381
- result += "NIST #{effective_series}"
382
- elsif effective_series
383
- # No publisher in input, just show series without prefix
384
- result += effective_series
385
- end
411
+ result += " #{number}" if number
412
+ result += parts.map { |p| "-#{p}" }.join if parts&.any?
386
413
 
387
- result += " #{number}" if number
388
- result += parts.map { |p| "-#{p}" }.join if parts&.any?
389
-
390
- # NEW: Use Volume and Part components (v6n1 notation for CSM, pt1 for SP)
391
- if volume.is_a?(Components::Volume) && part.is_a?(Components::Part)
392
- # CSM series: v#n# notation
393
- result += " #{volume}#{part}"
394
- elsif part.is_a?(Components::Part)
395
- # SP and other series: use Part.type to determine format
396
- result += part.to_s
397
- # Legacy: Render standalone volume (not part of v#n#)
398
- elsif volume && !issue_number && !part
399
- vol_str = volume.is_a?(Components::Volume) ? volume.to_s : "v#{volume}"
400
- result += vol_str
401
- elsif volume && issue_number
402
- # Render volume and issue number in short form: "v6n12"
403
- vol_str = volume.is_a?(Components::Volume) ? volume.to_s : "v#{volume}"
404
- result += "#{vol_str}n#{issue_number.number}"
405
- end
414
+ # Append every optional component the parser may have attached
415
+ # (volume, part, edition, version, supplement, update, stage, ...).
416
+ # Shared with the lossy subclasses so they cannot silently drop a
417
+ # distinguishing component and collide with another document.
418
+ result += append_short_components
406
419
 
407
- # NEW: Use edition component properly (e2, e2021, r5, -3)
408
- # NO space before edition when number present (per NIST spec)
409
- # Only add space for bare edition (no number case) or if original_prefix has specific format
410
- if edition
411
- if edition.original_prefix && !edition.original_prefix.empty?
412
- # original_prefix includes the full prefix (e.g., " Rev. " for verbose format)
413
- result += edition.to_s
414
- elsif number
415
- # Number present, NO space: "800-53r5"
416
- result += edition.to_s
417
- else
418
- # Bare edition, add space: " r5"
419
- result += " #{edition}"
420
- end
421
- end
420
+ result
421
+ end
422
422
 
423
- # V2: Use version_component if available, else use version string.
424
- # Attach directly (no leading space) to match edition rendering
425
- # (e.g. "800-53r5"), so version reads "800-45ver2" not
426
- # "800-45 ver2".
427
- if version_component
428
- result += version_component.to_s(:short)
429
- elsif version
430
- result += "ver#{version}"
431
- end
423
+ # Render the optional component "tail" that follows
424
+ # publisher/series/number in short (human) form. Extracted from
425
+ # #to_short_style so subclasses that build a series-specific prefix can
426
+ # reuse it instead of hand-listing components (and forgetting some).
427
+ #
428
+ # skip_part: true lets a series that renders its part differently
429
+ # (e.g. FIPS uses dash "-1", not "pt1") emit the part itself and skip
430
+ # the generic part rendering here, while still getting volume/edition/
431
+ # supplement/etc.
432
+ def append_short_components(skip_part: false)
433
+ result = ""
434
+ effective_part = skip_part ? nil : part
435
+
436
+ # Volume and Part components (v6n1 notation for CSM, pt1 for SP)
437
+ if volume.is_a?(Components::Volume) && effective_part.is_a?(Components::Part)
438
+ # CSM series: v#n# notation
439
+ result += " #{volume}#{effective_part}"
440
+ elsif effective_part.is_a?(Components::Part)
441
+ # SP and other series: use Part.type to determine format
442
+ result += effective_part.to_s
443
+ # Legacy: Render standalone volume (not part of v#n#)
444
+ elsif volume && !issue_number && !effective_part
445
+ vol_str = volume.is_a?(Components::Volume) ? volume.to_s : "v#{volume}"
446
+ result += vol_str
447
+ elsif volume && issue_number
448
+ # Render volume and issue number in short form: "v6n12"
449
+ vol_str = volume.is_a?(Components::Volume) ? volume.to_s : "v#{volume}"
450
+ result += "#{vol_str}n#{issue_number.number}"
451
+ end
432
452
 
433
- # Add supplement. NIST/NBS canonical short form is single-p "sup"
434
- # with the suffix attached directly, no dash (relaton-data-nist
435
- # uses "sup2", "sup1940", "supA"); date-range keeps its inner dash.
436
- # Rendered from the structured component; a present-but-empty
437
- # component is the bare "sup" marker.
438
- result += supplement_short
439
-
440
- # Add other attributes
441
- result += errata.to_s if errata
442
- result += "index" if index
443
- result += "insert" if insert
444
- result += "sec#{section}" if section
445
- result += "app" if appendix
446
-
447
- # Add addendum - render as " Add." suffix
448
- if addendum || addendum_number
449
- result += " Add."
453
+ # Use edition component properly (e2, e2021, r5, -3)
454
+ # NO space before edition when number present (per NIST spec)
455
+ # Only add space for bare edition (no number case) or if original_prefix has specific format
456
+ if edition
457
+ if edition.original_prefix && !edition.original_prefix.empty?
458
+ # original_prefix includes the full prefix (e.g., " Rev. " for verbose format)
459
+ result += edition.to_s
460
+ elsif number
461
+ # Number present, NO space: "800-53r5"
462
+ result += edition.to_s
463
+ else
464
+ # Bare edition, add space: " r5"
465
+ result += " #{edition}"
450
466
  end
467
+ end
451
468
 
452
- # V2: Use update_component if available, else use update string
453
- if update_component
454
- result += update_component.to_s(:short)
455
- elsif update
456
- result += "-upd#{update}"
457
- end
469
+ # Use version_component if available, else use version string.
470
+ # Attach directly (no leading space) to match edition rendering
471
+ # (e.g. "800-53r5"), so version reads "800-45ver2" not
472
+ # "800-45 ver2".
473
+ if version_component
474
+ result += version_component.to_s(:short)
475
+ elsif version
476
+ result += "ver#{version}"
477
+ end
458
478
 
459
- # Add draft - render as {N}pd if draft_number present
460
- if draft_number
461
- result += " #{draft_number}pd"
462
- elsif draft&.to_s&.include?("draft") && !draft.to_s.include?("Draft)")
463
- result += "-draft"
464
- end
479
+ # Add supplement. NIST/NBS canonical short form is single-p "sup"
480
+ # with the suffix attached directly, no dash (relaton-data-nist
481
+ # uses "sup2", "sup1940", "supA"); date-range keeps its inner dash.
482
+ # Rendered from the structured component; a present-but-empty
483
+ # component is the bare "sup" marker.
484
+ result += supplement_short
485
+
486
+ # Add other attributes
487
+ result += errata.to_s if errata
488
+ result += "index" if index
489
+ result += "insert" if insert
490
+ result += "sec#{section}" if section
491
+ result += "app" if appendix
492
+
493
+ # Add addendum - render as " Add." suffix
494
+ if addendum || addendum_number
495
+ result += " Add."
496
+ end
465
497
 
466
- # V2: Add stage component (at end, before translation)
467
- if stage
468
- result += " #{stage.to_s(:short)}"
469
- end
498
+ # Use update_component if available, else use update string
499
+ if update_component
500
+ result += update_component.to_s(:short)
501
+ elsif update
502
+ result += "-upd#{update}"
503
+ end
470
504
 
471
- # V2: Use translation_component if available, else use translation string
472
- # Note: translation_component.to_s already includes the space prefix
473
- if translation_component
474
- result += translation_component.to_s(:short)
475
- elsif translation
476
- result += " #{translation}"
477
- end
505
+ # Add draft - render as {N}pd if draft_number present
506
+ if draft_number
507
+ result += " #{draft_number}pd"
508
+ elsif draft&.to_s&.include?("draft") && !draft.to_s.include?("Draft)")
509
+ result += "-draft"
510
+ end
478
511
 
479
- result
512
+ # Add stage component (at end, before translation)
513
+ if stage
514
+ result += " #{stage.to_s(:short)}"
480
515
  end
481
516
 
482
- def to_mr_style
483
- # "NIST.SP.800-116r1.ipd" (machine-readable with dots)
484
- result = (publisher || "NIST").to_s
485
-
486
- # Determine effective series - PREFER series_code if subclass defines it
487
- # This allows normalization (e.g., LCIRC → LC in LetterCircular)
488
- effective_series = if series_code
489
- series_code
490
- elsif series
491
- series.to_s
492
- end
493
-
494
- result += ".#{effective_series}" if effective_series
495
- result += ".#{number}" if number
496
- result += parts.map { |p| "-#{p}" }.join if parts&.any?
497
-
498
- # Part component (pt1, v6n1, etc.)
499
- result += part.to_s if part.is_a?(Components::Part)
500
-
501
- # NEW: Use edition component - NO space before edition in MR format (per NIST spec)
502
- if edition
503
- # If edition has original_prefix set (e.g., verbose " Rev. "), use it as-is
504
- # Otherwise, no space needed in MR format: ".800-53r5"
505
- if edition.original_prefix && !edition.original_prefix.empty?
506
- end
507
- result += edition.to_s
508
- end
517
+ # Use translation_component if available, else use translation string
518
+ # Note: translation_component.to_s already includes the space prefix
519
+ if translation_component
520
+ result += translation_component.to_s(:short)
521
+ elsif translation
522
+ result += " #{translation}"
523
+ end
509
524
 
510
- # V2: Use version_component
511
- result += version_component.to_s(:mr) if version_component
525
+ result
526
+ end
512
527
 
513
- # V2: Use update_component
514
- result += update_component.to_s(:mr) if update_component
528
+ def to_mr_style
529
+ # "NIST.SP.800-116r1.ipd" (machine-readable with dots)
530
+ result = (publisher || "NIST").to_s
515
531
 
516
- # V2: Use stage
517
- result += ".#{stage.to_s(:mr)}" if stage
532
+ # Determine effective series - PREFER series_code if subclass defines it
533
+ # This allows normalization (e.g., LCIRC → LC in LetterCircular)
534
+ effective_series = if series_code
535
+ series_code
536
+ elsif series
537
+ series.to_s
538
+ end
518
539
 
519
- # Add addendum - render as ".Add." suffix in MR format
520
- if addendum || addendum_number
521
- result += ".Add."
522
- end
540
+ result += ".#{effective_series}" if effective_series
541
+ result += ".#{number}" if number
542
+ result += parts.map { |p| "-#{p}" }.join if parts&.any?
523
543
 
524
- # V2: Use translation_component
525
- result += translation_component.to_s(:mr) if translation_component
544
+ result += append_mr_components
526
545
 
527
- result
528
- end
546
+ result
547
+ end
529
548
 
530
- def series_full_name
531
- {
532
- "SP" => "Special Publication",
533
- "FIPS" => "Federal Information Processing Standards",
534
- "IR" => "Interagency Report",
535
- "TN" => "Technical Note",
536
- }[series] || series
549
+ # Render the optional component tail for machine-readable (MR) form.
550
+ # Mirrors #append_short_components so the MR output is just as lossless;
551
+ # subclasses that override #to_mr_style reuse this instead of
552
+ # hand-listing components. skip_part: behaves as in the short helper.
553
+ def append_mr_components(skip_part: false)
554
+ result = ""
555
+ effective_part = skip_part ? nil : part
556
+
557
+ # Volume / Part components (pt1, v6n1, etc.)
558
+ if volume.is_a?(Components::Volume) && effective_part.is_a?(Components::Part)
559
+ result += "#{volume}#{effective_part}"
560
+ elsif effective_part.is_a?(Components::Part)
561
+ result += effective_part.to_s
562
+ elsif volume && !issue_number && !effective_part
563
+ result += (volume.is_a?(Components::Volume) ? volume.to_s : "v#{volume}")
564
+ elsif volume && issue_number
565
+ vol_str = volume.is_a?(Components::Volume) ? volume.to_s : "v#{volume}"
566
+ result += "#{vol_str}n#{issue_number.number}"
537
567
  end
538
568
 
539
- def series_abbreviated_name
540
- {
541
- "SP" => "Spec. Publ.",
542
- "FIPS" => "Fed. Inf. Proc. Stand.",
543
- "IR" => "Interag. Rep.",
544
- "TN" => "Tech. Note",
545
- }[series&.to_s || series_code] || (series&.to_s || series_code)
546
- end
569
+ # Use edition component - NO space before edition in MR format (per NIST spec)
570
+ result += edition.to_s if edition
547
571
 
548
- def publisher_full_name
549
- case publisher.to_s
550
- when "NBS"
551
- "National Bureau of Standards"
552
- when "NIST"
553
- "National Institute of Standards and Technology"
554
- else
555
- publisher.to_s
556
- end
572
+ # Use version_component
573
+ result += version_component.to_s(:mr) if version_component
574
+
575
+ # Supplement (e.g. ".9981sup7") - keep distinct documents distinct
576
+ result += supplement_short
577
+
578
+ # Use update_component
579
+ result += update_component.to_s(:mr) if update_component
580
+
581
+ # Use stage
582
+ result += ".#{stage.to_s(:mr)}" if stage
583
+
584
+ # Add addendum - render as ".Add." suffix in MR format
585
+ if addendum || addendum_number
586
+ result += ".Add."
557
587
  end
558
588
 
559
- def publisher_abbreviated_name
560
- case publisher.to_s
561
- when "NBS"
562
- "Natl. Bur. Stand."
563
- when "NIST"
564
- "Natl. Inst. Stand. Technol."
565
- else
566
- publisher.to_s
567
- end
589
+ # Use translation_component
590
+ result += translation_component.to_s(:mr) if translation_component
591
+
592
+ result
593
+ end
594
+
595
+ def series_full_name
596
+ {
597
+ "SP" => "Special Publication",
598
+ "FIPS" => "Federal Information Processing Standards",
599
+ "IR" => "Interagency Report",
600
+ "TN" => "Technical Note",
601
+ }[series] || series
602
+ end
603
+
604
+ def series_abbreviated_name
605
+ {
606
+ "SP" => "Spec. Publ.",
607
+ "FIPS" => "Fed. Inf. Proc. Stand.",
608
+ "IR" => "Interag. Rep.",
609
+ "TN" => "Tech. Note",
610
+ }[series&.to_s || series_code] || (series&.to_s || series_code)
611
+ end
612
+
613
+ def publisher_full_name
614
+ case publisher.to_s
615
+ when "NBS"
616
+ "National Bureau of Standards"
617
+ when "NIST"
618
+ "National Institute of Standards and Technology"
619
+ else
620
+ publisher.to_s
568
621
  end
622
+ end
569
623
 
570
- # Default publisher for series without explicit publisher
571
- # Subclasses can override
572
- def default_publisher
573
- "NIST"
624
+ def publisher_abbreviated_name
625
+ case publisher.to_s
626
+ when "NBS"
627
+ "Natl. Bur. Stand."
628
+ when "NIST"
629
+ "Natl. Inst. Stand. Technol."
630
+ else
631
+ publisher.to_s
574
632
  end
575
633
  end
634
+
635
+ # Default publisher for series without explicit publisher
636
+ # Subclasses can override
637
+ def default_publisher
638
+ "NIST"
639
+ end
640
+ end
641
+
642
+ module Identifiers
643
+ # Backward-compatible alias: NIST's base class used to be
644
+ # Pubid::Nist::Identifiers::Base. It is now Pubid::Nist::Identifier; keep
645
+ # the old constant working for `class Foo < Base` declarations and any
646
+ # external reference.
647
+ Base = Pubid::Nist::Identifier
576
648
  end
577
649
  end
578
650
  end