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,1118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ class Identifier < Lutaml::Model::Serializable
5
+ # Components that serialize as a bare scalar when they carry only their
6
+ # single significant field: attribute name => the key the scalar is emitted
7
+ # under. `date` is RENAMED to `year`, matching the flat shape ISO and IEC
8
+ # already emit through their own converters.
9
+ FLAT_SCALAR_COMPONENTS = {
10
+ edition: "edition",
11
+ date: "year",
12
+ }.freeze
13
+
14
+ # The one field each of those components degenerates to.
15
+ FLAT_SCALAR_FIELDS = {
16
+ edition: :number,
17
+ date: :year,
18
+ }.freeze
19
+
20
+ # The one field each degenerate component collapses to, keyed by the
21
+ # component CLASS rather than the attribute name — so a collection
22
+ # attribute (`languages`, `copublishers`) and a flavor subclass
23
+ # (Pubid::Iec::Components::Publisher) are both covered by one entry.
24
+ #
25
+ # `Components::Code` is deliberately absent. A String left in `number`
26
+ # renders and serializes correctly today, and coercing it would turn
27
+ # `to_hash` from {"number" => "1000"} into {"number" => {"value" => "1000"}}
28
+ # on the six flavors that still declare a Code (iso, iec, nist, csa, sae,
29
+ # ccsds) — an index wire-format change, and one the `number` retype tranches
30
+ # sequence to land last. The two entries here are the components that RAISE
31
+ # when a String reaches them, so coercing them can only repair.
32
+ DEGENERATE_COMPONENT_FIELDS = {
33
+ Components::Language => :code,
34
+ Components::Publisher => :body,
35
+ }.freeze
36
+
37
+ # Keys lutaml reads out of the attribute hash itself
38
+ # (Serializable#extract_register_id), so they are reserved rather than
39
+ # unknown and must survive the unknown-key check.
40
+ RESERVED_INIT_KEYS = %i[lutaml_register].freeze
41
+
42
+ # Scalar aliases accepted by the constructor for a component-valued
43
+ # attribute: alias => [target attribute, the component field it fills].
44
+ # `year` is the one the issue names — it is a READER over `date`, never an
45
+ # attribute, on every date-based flavor, so `new(number: "1000", year: 2023)`
46
+ # used to build an identifier with no year and say nothing.
47
+ SCALAR_ATTRIBUTE_ALIASES = {
48
+ year: [:date, :year],
49
+ month: [:date, :month],
50
+ day: [:date, :day],
51
+ }.freeze
52
+
53
+
54
+ class << self
55
+ def format_registry
56
+ @format_registry || superclass&.format_registry
57
+ end
58
+
59
+ attr_writer :format_registry
60
+
61
+ # Polymorphic deserialization shared by every flavor base. lutaml's
62
+ # `key_value` polymorphic_map reads `_type` only to VALIDATE; it does not
63
+ # re-instantiate the concrete subclass. So a base-class `from_hash` would
64
+ # return a bare base object and drop subtype-specific attributes (e.g. a
65
+ # supplement's base). Route by `_type` to the concrete class
66
+ # named in `polymorphic_type_map`, then let its inherited from_hash (this
67
+ # method again, where klass == self) fall through to lutaml's real work.
68
+ #
69
+ # This generalizes the per-flavor `*_TYPE_MAP` dispatch that ISO, JIS,
70
+ # IEC, CCSDS and IHO each hand-rolled — one implementation, inherited by
71
+ # every flavor base. Cross-flavor dispatch (a nested adopted identifier
72
+ # whose _type belongs to a different flavor) is delegated to
73
+ # {TypeResolver}, which knows about every registered flavor.
74
+ def from_hash(data, options = {})
75
+ klass = concrete_class_for(data)
76
+ return klass.from_hash(data, options) if klass && klass != self
77
+
78
+ super(inflate_scalar_components(data), options)
79
+ end
80
+
81
+ # Accept the flat scalar form that {#to_hash} now emits, and the nested
82
+ # form every stored row was written with. lutaml casts a Hash into a
83
+ # component but passes a String straight through (Attribute#cast_element),
84
+ # so the flat form has to be re-inflated here or it would be assigned raw.
85
+ #
86
+ # Reading BOTH shapes is what keeps published relaton-data indexes
87
+ # deserializing across this change.
88
+ def inflate_scalar_components(data)
89
+ return data unless data.is_a?(::Hash)
90
+
91
+ FLAT_SCALAR_COMPONENTS.each_with_object(data.dup) do |(attr, key), acc|
92
+ inflate_scalar_component(acc, attr, key)
93
+ end
94
+ end
95
+
96
+ # @api private
97
+ def inflate_scalar_component(data, attr_name, flat_key)
98
+ return unless component_attribute?(attr_name)
99
+ # A flavor with its OWN converter for the flat key (IEC's
100
+ # year_from_kv) already knows how to read it, and inflating would fight
101
+ # it. A plain `map "edition", to: :edition` is not such a converter —
102
+ # it hands the value to lutaml, which cannot cast a String into a
103
+ # component — so that one still needs inflating.
104
+ return if converted_hash_keys.include?(flat_key)
105
+ # Only when the flat key RENAMES the attribute (`date` -> `year`) can
106
+ # it collide with a different attribute. ~20 flavors model the edition
107
+ # as a real `year` attribute of their own (ASHRAE, ASME, CIE, CSA, JIS,
108
+ # OGC, IEEE …), and folding that into a `date` component silently drops
109
+ # the year on every round trip — the same collision
110
+ # `fold_scalar_aliases` guards against on the constructor path.
111
+ # `edition` keeps its own name, so it is never a collision.
112
+ return if flat_key != attr_name.to_s && declared_attribute?(flat_key)
113
+
114
+ key = data.key?(flat_key) ? flat_key : flat_key.to_sym
115
+ value = data[key]
116
+ return if value.nil? || value.is_a?(::Hash) || value.is_a?(::Array)
117
+
118
+ data.delete(key)
119
+ data[attr_name.to_s] =
120
+ { FLAT_SCALAR_FIELDS.fetch(attr_name).to_s => value.to_s }
121
+ end
122
+
123
+ # True when +name+ is a declared attribute on this class, under either a
124
+ # Symbol or a String key.
125
+ def declared_attribute?(name)
126
+ attributes.key?(name.to_sym) || attributes.key?(name.to_s)
127
+ end
128
+
129
+ # True when +name+ is declared on this class as a component (a nested
130
+ # Serializable), rather than a plain scalar. The coercions are
131
+ # type-aware: ~16 classes declare `edition` as a plain :string and must
132
+ # keep the scalar they were given.
133
+ def component_attribute?(name)
134
+ attr = attributes[name] || attributes[name.to_s]
135
+ return false unless attr
136
+
137
+ type = attr.type
138
+ type.is_a?(Class) && type <= Lutaml::Model::Serializable
139
+ rescue StandardError
140
+ false
141
+ end
142
+
143
+ # Serialization keys this class reads through a converter of its own
144
+ # (a `key_value` rule declared with `with: {to:, from:}`). Those keys are
145
+ # already handled and must not be rewritten underneath the flavor.
146
+ #
147
+ # Empty for a flavor with no `key_value` block, which serializes
148
+ # attribute names directly.
149
+ def converted_hash_keys
150
+ @converted_hash_keys ||=
151
+ begin
152
+ rules = mappings[:hash]&.mappings || []
153
+ rules.reject { |rule| rule.custom_methods.nil? || rule.custom_methods.empty? }
154
+ .map { |rule| rule.name.to_s }
155
+ rescue StandardError
156
+ []
157
+ end
158
+ end
159
+
160
+ # The blessed constructor path (pubid#360 item 3).
161
+ #
162
+ # Two jobs, both of which lutaml declines to do:
163
+ #
164
+ # * coerce a scalar into the component the attribute declares, so
165
+ # `edition: 2` and `year: 2023` work as written;
166
+ # * refuse a key this class does not know, instead of dropping it.
167
+ #
168
+ # The refusal is the important half. lutaml reads only declared attribute
169
+ # names out of the hash (Serialize#initialize_attributes) and reports
170
+ # nothing about the rest, so `new(number: "1000", year: 2023)` returned an
171
+ # identifier with no year at all and no diagnostic — which is exactly how
172
+ # the issue was filed. A dropped key is a silent wrong answer; a raise is
173
+ # a loud one.
174
+ #
175
+ # Safe to apply here because neither internal rebuild path routes
176
+ # attribute keys through the constructor: `from_hash` calls `new` with no
177
+ # attributes and assigns through setters afterwards, and `#exclude`
178
+ # rebuilds from `self.class.attributes` — only declared names, by
179
+ # construction.
180
+ #
181
+ # @param attrs [Hash] caller-supplied attributes
182
+ # @return [Hash] the same attributes, coerced
183
+ # @raise [ArgumentError] naming every key this class does not declare
184
+ def normalize_init_attributes(attrs)
185
+ return attrs.dup unless attrs.is_a?(::Hash)
186
+
187
+ normalized = attrs.dup
188
+ fold_scalar_aliases(normalized)
189
+ coerce_component_scalars(normalized)
190
+ reject_unknown_keys(normalized)
191
+ normalized
192
+ end
193
+
194
+ # Fold `year:` / `month:` / `day:` into the `date` component. Only when
195
+ # the alias is not itself a declared attribute — ~20 flavors model the
196
+ # edition as a plain `year` attribute, and folding there would destroy it.
197
+ def fold_scalar_aliases(attrs)
198
+ SCALAR_ATTRIBUTE_ALIASES.each do |alias_name, (target, field)|
199
+ key = attrs.key?(alias_name) ? alias_name : alias_name.to_s
200
+ next unless attrs.key?(key)
201
+ next if attributes.key?(alias_name) || attributes.key?(alias_name.to_s)
202
+ next unless component_attribute?(target)
203
+
204
+ value = attrs.delete(key)
205
+ next if value.nil?
206
+
207
+ existing = attrs[target] || attrs[target.to_s] || {}
208
+ existing = component_to_hash(existing)
209
+ attrs.delete(target.to_s)
210
+ attrs[target] = existing.merge(field => value.to_s)
211
+ end
212
+ end
213
+
214
+ # Wrap a scalar in the component its attribute declares. A flavor that
215
+ # declares the attribute as a plain :string keeps the scalar untouched.
216
+ def coerce_component_scalars(attrs)
217
+ FLAT_SCALAR_FIELDS.each do |attr_name, field|
218
+ key = attrs.key?(attr_name) ? attr_name : attr_name.to_s
219
+ next unless attrs.key?(key)
220
+ next unless component_attribute?(attr_name)
221
+
222
+ value = attrs[key]
223
+ next if value.nil? || value.is_a?(::Hash) ||
224
+ value.is_a?(Lutaml::Model::Serialize)
225
+
226
+ attrs[key] = { field => value.to_s }
227
+ end
228
+
229
+ coerce_degenerate_components(attrs)
230
+ end
231
+
232
+ # Wrap a scalar in a degenerate component wherever the attribute declares
233
+ # one, INCLUDING a collection — the residue of pubid#360 item 3.
234
+ #
235
+ # lutaml casts a Hash element into the component but passes a String
236
+ # through untouched, so `languages: ["en"]` stored raw Strings and then
237
+ # raised in the renderer (`undefined method 'code'`) and in the URN
238
+ # generator. The Hash form `languages: [{code: "en"}]` already worked, so
239
+ # this is a coercion gap, not a lutaml limit.
240
+ def coerce_degenerate_components(attrs)
241
+ attrs.keys.each do |key|
242
+ next unless key.is_a?(::Symbol) || key.is_a?(::String)
243
+
244
+ field = degenerate_field_for(key)
245
+ next unless field
246
+
247
+ attrs[key] = wrap_degenerate(attrs[key], field)
248
+ end
249
+ end
250
+
251
+ # The degenerate field of the component +key+ declares, or nil when it
252
+ # declares none. A collection attribute reports its ELEMENT class, so one
253
+ # lookup covers both shapes; the `<=` test is what admits a flavor
254
+ # subclass such as Pubid::Iec::Components::Publisher.
255
+ def degenerate_field_for(key)
256
+ type = declared_component_type(key)
257
+ return nil unless type
258
+
259
+ DEGENERATE_COMPONENT_FIELDS.find { |klass, _| type <= klass }&.last
260
+ end
261
+
262
+ # The component class +key+ declares, or nil when the attribute is
263
+ # absent or declared as a plain scalar.
264
+ def declared_component_type(key)
265
+ type = (attributes[key.to_sym] || attributes[key.to_s])&.type
266
+ type.is_a?(::Class) ? type : nil
267
+ rescue StandardError
268
+ nil
269
+ end
270
+
271
+ # A scalar becomes `{field => scalar}` and an Array is mapped
272
+ # element-wise. nil, a Hash and an already-built component pass through
273
+ # untouched, so every shape that worked before still works.
274
+ def wrap_degenerate(value, field)
275
+ if value.is_a?(::Array)
276
+ return value.map { |v| wrap_degenerate(v, field) }
277
+ end
278
+ return value if value.nil? || value.is_a?(::Hash) ||
279
+ value.is_a?(Lutaml::Model::Serialize)
280
+
281
+ { field => value.to_s }
282
+ end
283
+
284
+ def reject_unknown_keys(attrs)
285
+ allowed = RESERVED_INIT_KEYS + extra_init_keys
286
+ unknown = attrs.keys.reject do |key|
287
+ # A key that is neither a Symbol nor a String cannot name an
288
+ # attribute, so it is unknown by definition — and `to_sym` on it
289
+ # would raise NoMethodError instead of reporting that.
290
+ next false unless key.is_a?(::Symbol) || key.is_a?(::String)
291
+
292
+ allowed.include?(key.to_sym) || declared_attribute?(key)
293
+ end
294
+ return if unknown.empty?
295
+
296
+ raise ArgumentError,
297
+ "unknown attribute#{'s' if unknown.size > 1} for " \
298
+ "#{name || self}: #{unknown.map(&:to_s).sort.join(', ')}"
299
+ end
300
+
301
+ # Constructor parameters a flavor accepts that are not lutaml attributes.
302
+ # IEEE takes `code:` and `draft:` this way — runtime-only values its
303
+ # `initialize` turns into component objects. Override to widen.
304
+ #
305
+ # @return [Array<Symbol>]
306
+ def extra_init_keys
307
+ []
308
+ end
309
+
310
+ # @api private
311
+ def component_to_hash(value)
312
+ case value
313
+ when ::Hash then value.transform_keys(&:to_sym)
314
+ when Lutaml::Model::Serialize then value.to_hash.transform_keys(&:to_sym)
315
+ else {}
316
+ end
317
+ end
318
+
319
+ # lutaml's nested polymorphic cast (Attribute#cast → apply_mappings)
320
+ # bypasses {from_hash}, so cross-flavor dispatch wouldn't fire for a
321
+ # nested attribute that carries another flavor's _type. Intercept
322
+ # apply_mappings and route to {from_hash} so the concrete class's own
323
+ # mappings (with their flavor-specific custom cast methods, e.g. ISO's
324
+ # number_from_kv) drive deserialization.
325
+ def apply_mappings(doc, format, options = {})
326
+ return super unless hash_with_type?(doc, format)
327
+
328
+ klass = concrete_class_for(doc)
329
+ return super unless klass && klass != self
330
+
331
+ klass.from_hash(doc, options)
332
+ end
333
+
334
+ # Resolve a polymorphic _type to the concrete class that owns it:
335
+ # this flavor's own map first, then any registered flavor's map via
336
+ # {TypeResolver}. Returns nil for blank or unknown types.
337
+ def concrete_class_for(data)
338
+ type = data && (data["_type"] || data[:_type])
339
+ return nil unless type
340
+
341
+ polymorphic_type_map[type] || ::Pubid::TypeResolver.resolve(type)
342
+ end
343
+
344
+ # Map of polymorphic_name ("pubid:iso:corrigendum") => concrete class for
345
+ # the flavor this base belongs to. Built once by scanning the flavor's
346
+ # `Identifiers` namespace for Pubid::Identifier descendants and unioning
347
+ # the flavor's `identifier_types` registry (which may register classes
348
+ # living outside that namespace, e.g. ISO's BundledIdentifier). Memoized
349
+ # per class. Public so {TypeResolver} can read another flavor's map for
350
+ # cross-flavor polymorphic dispatch.
351
+ def polymorphic_type_map
352
+ @polymorphic_type_map ||=
353
+ identifier_registry_classes.each_with_object({}) do |klass, map|
354
+ poly = klass.polymorphic_name
355
+ map[poly] ||= klass if poly
356
+ end
357
+ end
358
+
359
+ private
360
+
361
+ def hash_with_type?(doc, format)
362
+ format == :hash && doc.is_a?(Hash) && (doc["_type"] || doc[:_type])
363
+ end
364
+
365
+ private
366
+
367
+ # The flavor module namespacing this base, e.g. Pubid::Iso for
368
+ # Pubid::Iso::Identifier (and for the inherited Pubid::Iso::Identifiers::
369
+ # Corrigendum). Always the first two name components, so it is stable
370
+ # whether `self` is the base or a concrete subclass.
371
+ def flavor_module
372
+ @flavor_module ||= Object.const_get(name.split("::")[0, 2].join("::"))
373
+ end
374
+
375
+ def identifiers_namespace
376
+ @identifiers_namespace ||= flavor_module.const_get(:Identifiers)
377
+ end
378
+
379
+ def identifier_registry_classes
380
+ (scanned_identifier_classes +
381
+ registered_identifier_classes +
382
+ additional_identifier_classes +
383
+ own_base_class).uniq
384
+ end
385
+
386
+ # The flavor's own Identifier base, when a builder instantiates it
387
+ # directly (BSI does, for the members of a bundled identifier). Such an
388
+ # object serializes with `_type: "pubid:<flavor>:identifier"`, and
389
+ # without an entry here that type resolves to nothing — so a nested
390
+ # polymorphic attribute typed ::Pubid::Identifier silently deserialized
391
+ # the member as the abstract root instead of the flavor class. Added
392
+ # LAST, so a concrete leaf that somehow claimed the same name still wins.
393
+ def own_base_class
394
+ return [] if self == ::Pubid::Identifier
395
+ return [] unless name && polymorphic_name
396
+
397
+ [self]
398
+ end
399
+
400
+ # Concrete identifier classes found directly in the flavor's `Identifiers`
401
+ # namespace. Empty for the abstract root (no flavor) or a flavor without
402
+ # that namespace. Only this namespace is scanned (never the flavor top
403
+ # level), to avoid force-autoloading unrelated flavor constants.
404
+ def scanned_identifier_classes
405
+ return [] unless flavor_identifiers_namespace?
406
+
407
+ identifiers_namespace.constants.filter_map do |const|
408
+ klass = identifiers_namespace.const_get(const)
409
+ klass if klass.is_a?(Class) && klass < ::Pubid::Identifier
410
+ rescue NameError
411
+ nil
412
+ end
413
+ end
414
+
415
+ # Classes the flavor explicitly registers via `identifier_types`, if any.
416
+ def registered_identifier_classes
417
+ return [] unless flavor_module.respond_to?(:identifier_types)
418
+
419
+ Array(flavor_module.identifier_types)
420
+ end
421
+
422
+ # Hook for identifier classes that carry a polymorphic `_type` but live
423
+ # OUTSIDE the flavor's `Identifiers` namespace (e.g. Pubid::Iso::
424
+ # BundledIdentifier, Pubid::Iec::SingleIdentifier). Flavors with such
425
+ # classes override this; the default is none. Replaces the hand-listing
426
+ # of these in the old static `*_TYPE_MAP`s.
427
+ def additional_identifier_classes
428
+ []
429
+ end
430
+
431
+ def flavor_identifiers_namespace?
432
+ flavor_module.const_defined?(:Identifiers)
433
+ rescue NameError
434
+ false
435
+ end
436
+ end
437
+
438
+ attribute :_type, :string, polymorphic_class: true
439
+ attribute :number, Components::Code
440
+ attribute :part, Components::Code
441
+ attribute :subpart, Components::Code
442
+ attribute :stage_iteration, Components::Iteration
443
+ attribute :date, Components::Date
444
+ attribute :edition, Components::Edition
445
+ attribute :languages, Components::Language, collection: true
446
+ attribute :publisher, Components::Publisher
447
+ attribute :copublishers, Components::Publisher, collection: true
448
+ attribute :type, Components::Type
449
+ attribute :stage, Components::Stage
450
+ attribute :locality, Components::Locality
451
+ attribute :typed_stage, Components::TypedStage
452
+ attribute :all_parts, Lutaml::Model::Type::Boolean, default: false
453
+ # base is declared by supplement subclasses with proper type
454
+ def base
455
+ nil
456
+ end
457
+
458
+ # The underlying standard, with amendment / corrigendum / Expert-commentary
459
+ # / Flex-version wrappers peeled recursively. Wrapper subclasses override
460
+ # this; a plain identifier is its own base document.
461
+ def base_document
462
+ self
463
+ end
464
+
465
+ # @return [String, nil] publication year from the date component
466
+ def year
467
+ date&.year&.to_s
468
+ end
469
+
470
+ # Canonicalize the serialized hash so it never carries a defaulted attribute
471
+ # still at its default (or empty) value. This makes to_hash a pure function
472
+ # of the identifier's values, independent of how the object was built.
473
+ #
474
+ # Motivation: lutaml materializes each attribute's default as an explicit
475
+ # assignment during deserialization (flipping using_default? to false), so a
476
+ # naive from_hash(x).to_hash re-emits defaults that parse(x).to_hash omits —
477
+ # breaking the exact-equality round-trip relaton-index relies on
478
+ # (from_hash(raw).to_hash == raw). pubid never consults lutaml's unset
479
+ # tracking and defines no render_default: true, so a defaulted attribute at
480
+ # its default/empty value carries no meaning and does not belong in the
481
+ # canonical hash. Dropping it here (rather than repairing from_hash) fixes
482
+ # the round-trip through every construction path — parse, from_hash, manual.
483
+ def to_hash(*args)
484
+ hash = super
485
+ canonicalize_hash(self, hash) if hash.is_a?(::Hash)
486
+ hash
487
+ end
488
+
489
+ # Recursively drop attributes holding only their default (or empty) value
490
+ # from +hash+, the serialization of +model+. Recurses into nested component
491
+ # / identifier values because lutaml serializes those via its own transform
492
+ # — bypassing their public to_hash — so a nested defaulted attribute (e.g.
493
+ # Components::Supplement#has_revision) would otherwise leak into the parent
494
+ # hash and break the idempotent round-trip.
495
+ def canonicalize_hash(model, hash)
496
+ register = model.lutaml_register
497
+ model.class.attributes(register).each do |name, attr|
498
+ canonicalize_attr(model, hash, name, attr) unless name == :_type
499
+ end
500
+ flatten_scalar_components(model, hash)
501
+ end
502
+
503
+ # A component that carries only ONE meaningful value serializes as that
504
+ # value: `edition: "2"`, not `edition: {number: "2"}`; `year: "2024"`, not
505
+ # `date: {year: "2024"}`. That is the shape an index row wants to be read
506
+ # in, and the shape a caller can write without knowing pubid's component
507
+ # classes — the complaint in pubid#360 item 3.
508
+ #
509
+ # The test is per INSTANCE, not per flavor, because one flavor's corpus
510
+ # holds both shapes. Of 9,730 dated IEC identifiers, 9,728 carry a year
511
+ # alone but 10 carry a month (`IEC CA 01:2025-10`) and 2 are undated
512
+ # (`IEC 60050:--`); a flavor-wide switch would have discarded those.
513
+ #
514
+ # Flavors that already emit the flat form are untouched for free: IEC and
515
+ # ISO serialize the date as top-level year/month/day, so no "date" key ever
516
+ # reaches this method.
517
+ def flatten_scalar_components(model, hash)
518
+ FLAT_SCALAR_COMPONENTS.each do |attr_name, flat_key|
519
+ flatten_scalar_component(model, hash, attr_name, flat_key)
520
+ end
521
+ end
522
+
523
+ # Replace +hash+'s serialization of +attr_name+ with a bare scalar, when
524
+ # the component holds nothing but its single significant field.
525
+ def flatten_scalar_component(model, hash, attr_name, flat_key)
526
+ key = hash.key?(attr_name.to_s) ? attr_name.to_s : attr_name
527
+ return unless hash.key?(key) && hash[key].is_a?(::Hash)
528
+ return unless model.respond_to?(attr_name)
529
+
530
+ component = model.public_send(attr_name)
531
+ return unless component.is_a?(Lutaml::Model::Serialize)
532
+
533
+ scalar = degenerate_scalar(component, FLAT_SCALAR_FIELDS.fetch(attr_name))
534
+ return if scalar.nil?
535
+ # Never overwrite a key the flavor already emits under the flat name,
536
+ # and never claim a name the flavor declares as a real attribute of its
537
+ # own — ~20 flavors have a `year` attribute, and renaming `date` onto it
538
+ # would make the two indistinguishable on the way back in.
539
+ if flat_key != key.to_s
540
+ return if hash.key?(flat_key) || model.class.declared_attribute?(flat_key)
541
+ end
542
+
543
+ replace_key_in_place(hash, key, flat_key, scalar)
544
+ end
545
+
546
+ # Replace +old_key+ with +new_key+ WITHOUT moving it to the end.
547
+ #
548
+ # A plain delete-then-assign appends, which would reorder every key after
549
+ # the one being flattened — cosmetic for `==`, but it rewrites the byte
550
+ # order of every generated relaton-data YAML row for no reason.
551
+ def replace_key_in_place(hash, old_key, new_key, value)
552
+ if old_key.to_s == new_key
553
+ hash[old_key] = value
554
+ return
555
+ end
556
+
557
+ rebuilt = hash.each_with_object({}) do |(k, v), acc|
558
+ if k == old_key
559
+ acc[new_key] = value
560
+ else
561
+ acc[k] = v
562
+ end
563
+ end
564
+
565
+ hash.replace(rebuilt)
566
+ end
567
+
568
+ # The scalar +component+ degenerates to, or nil when it carries anything
569
+ # besides +field+.
570
+ #
571
+ # A +field+ holding another component (ISO's edition number is a
572
+ # Components::Code) is deliberately NOT flattened: the flat form would come
573
+ # back as a plain String, so `from_hash(to_hash) == parse` would break —
574
+ # the silent inequality that CLAUDE.md records as the costliest failure
575
+ # mode in this codebase, because `#matches?` is built on `==`.
576
+ def degenerate_scalar(component, field)
577
+ register = component.lutaml_register
578
+ found = nil
579
+
580
+ component.class.attributes(register).each do |name, attr|
581
+ value = component.public_send(name)
582
+ # `Utils.empty?(nil)` is false, so nil needs its own test — an
583
+ # unpopulated attribute is exactly what makes a component degenerate.
584
+ next if value.nil? || Lutaml::Model::Utils.empty?(value)
585
+ next if attr.default_set?(register, component) &&
586
+ value == attr.default(register, component)
587
+ return nil unless name == field
588
+ return nil if value.is_a?(Lutaml::Model::Serialize)
589
+
590
+ found = value
591
+ end
592
+
593
+ found&.to_s
594
+ end
595
+
596
+ # Canonicalize a single attribute against its serialized value in +hash+:
597
+ # drop it when it holds only its default/empty value, else recurse into it.
598
+ def canonicalize_attr(model, hash, name, attr)
599
+ key = hash.key?(name.to_s) ? name.to_s : name
600
+ return unless hash.key?(key)
601
+
602
+ value = model.public_send(name)
603
+ if default_valued?(value, attr, model)
604
+ hash.delete(key)
605
+ else
606
+ canonicalize_nested(value, hash[key])
607
+ end
608
+ end
609
+
610
+ # True when +value+ is +attr+'s default (or empty), so it can be dropped.
611
+ def default_valued?(value, attr, model)
612
+ register = model.lutaml_register
613
+ attr.default_set?(register, model) &&
614
+ (Lutaml::Model::Utils.empty?(value) ||
615
+ value == attr.default(register, model))
616
+ end
617
+
618
+ # Recurse into a nested component / identifier (or a collection of them) so
619
+ # its own defaulted attributes are canonicalized against its sub-hash.
620
+ def canonicalize_nested(value, sub)
621
+ if value.is_a?(Lutaml::Model::Serialize)
622
+ canonicalize_hash(value, sub) if sub.is_a?(::Hash)
623
+ elsif value.is_a?(::Array)
624
+ canonicalize_collection(value, sub)
625
+ end
626
+ end
627
+
628
+ # Canonicalize each element of a collection against its serialized sub-hash.
629
+ # Only zip when object and serialized sizes match, so a (never-observed)
630
+ # length mismatch can't pair an element with the wrong sub-hash — worst case
631
+ # it leaves that collection untouched.
632
+ def canonicalize_collection(models, subs)
633
+ return unless subs.is_a?(::Array) && models.size == subs.size
634
+
635
+ models.each_with_index do |model, i|
636
+ canonicalize_nested(model, subs[i])
637
+ end
638
+ end
639
+ private :canonicalize_hash, :canonicalize_attr, :default_valued?,
640
+ :canonicalize_nested, :canonicalize_collection,
641
+ :flatten_scalar_components, :flatten_scalar_component,
642
+ :degenerate_scalar, :replace_key_in_place
643
+
644
+ def initialize(attrs = {}, options = {})
645
+ attrs = self.class.normalize_init_attributes(attrs)
646
+ attrs[:_type] ||= self.class.polymorphic_name
647
+ super
648
+ end
649
+
650
+ def self.polymorphic_name
651
+ return nil unless name
652
+
653
+ parts = name.split("::")
654
+ flavor = parts[1]&.downcase
655
+ type_kebab = parts.last
656
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1-\2')
657
+ .gsub(/([a-z\d])([A-Z])/, '\1-\2')
658
+ .downcase
659
+ "pubid:#{flavor}:#{type_kebab}"
660
+ end
661
+
662
+ def root
663
+ return base.root if base
664
+
665
+ self
666
+ end
667
+
668
+ # Unified render — delegates to format registry
669
+ def render(format: :human, **opts)
670
+ registry = self.class.format_registry
671
+ unless registry
672
+ raise ArgumentError, "No format registry configured on #{self.class}"
673
+ end
674
+
675
+ renderer = registry.renderer_for(format)
676
+ unless renderer
677
+ raise ArgumentError, "No renderer registered for format: #{format}"
678
+ end
679
+
680
+ # `:trademark` (IEEE) and `:with_volume` (ECMA) are render-time flags
681
+ # consumed by the renderer, not the rendering context — and some flavors
682
+ # override build_rendering_context with a strict signature, so strip them
683
+ # here rather than widening that signature.
684
+ ctx_opts = opts.except(:trademark, :with_volume)
685
+ context = build_rendering_context(renderer, format:, **ctx_opts)
686
+ render_opts = opts.slice(:with_edition, :trademark, :with_volume)
687
+ result = renderer.new(self).render(context:, **render_opts)
688
+
689
+ annotate_rendered(result, format, ctx_opts[:annotated], context)
690
+ end
691
+
692
+ # Apply semantic spans when the renderer did not.
693
+ #
694
+ # `context.annotated` reaches all 39 flavor renderers and all 39 ignore it,
695
+ # so annotation was a v1 -> v2 regression for every flavor but ISO. This is
696
+ # the single choke point every renderer already passes through, which is
697
+ # why the fallback lives here rather than in each renderer.
698
+ #
699
+ # The `<span` test is what keeps ISO's exact, render-time placement: a
700
+ # renderer that annotated itself is left alone.
701
+ def annotate_rendered(result, format, annotated, context)
702
+ return result unless annotated && format == :human
703
+ return result unless result.is_a?(String)
704
+ return result if result.include?("<span")
705
+
706
+ Renderers::Annotator.new(self, context).annotate(result)
707
+ end
708
+ private :annotate_rendered
709
+
710
+ # The same fallback, for a `to_s` that composes its string by hand instead
711
+ # of going through {#render} — ITU, CCSDS, CIE and CSA wrappers all do.
712
+ # Those never reach the choke point above, so they apply it on the way out.
713
+ #
714
+ # Every such `to_s` must also ACCEPT `annotated:`. Before they did, 13 type
715
+ # families raised on the flag (`wrong number of arguments (given 1,
716
+ # expected 0)` for the ones declaring no parameters at all), which is worse
717
+ # than a missing span: a wrapper is exactly the shape a consumer renders.
718
+ #
719
+ # This is deliberately a per-class opt-in rather than a module prepended to
720
+ # every identifier class. That was tried: prepending to ~500 ancestor
721
+ # chains made a GOST identical adoption render its adopted ISO Technical
722
+ # Report as `ISO TR …` instead of `ISO/TR …`, but only under full-suite
723
+ # load — the multi-flavor nondeterminism this file warns about elsewhere.
724
+ # An explicit call in the handful of classes that need it changes no
725
+ # ancestor chain and cannot have that effect.
726
+ #
727
+ # @param rendered [String] the plain rendering this method produced
728
+ # @param opts [Hash] the render options it was called with
729
+ # @return [String] annotated when `annotated:` was asked for, else as-is
730
+ def annotate_plain_render(rendered, **opts)
731
+ annotate_rendered(rendered, :human, opts[:annotated], nil)
732
+ end
733
+ protected :annotate_plain_render
734
+
735
+ # The tokens {Renderers::Annotator} looks for in this identifier's rendered
736
+ # string, as [attribute_name, css_class] pairs.
737
+ #
738
+ # The shared table names the components a printed identifier usually
739
+ # carries — publisher, type, number, part, year. A flavor that keeps its
740
+ # identity somewhere else answers with its own names appended: IEC's
741
+ # working documents are `technical_committee`/`wd_number`/`wd_stage`, and
742
+ # CSA's electrical code prints `cec_part`/`no_number` rather than the
743
+ # `number` it stores. Without this they carried no annotatable token at all.
744
+ #
745
+ # Appending rather than replacing keeps whatever the shared table does
746
+ # find; a token whose value is not in the string is skipped anyway.
747
+ def annotation_tokens
748
+ Renderers::Annotator::TOKENS
749
+ end
750
+
751
+ def to_s(**opts)
752
+ render(format: :human, **opts)
753
+ end
754
+
755
+ def to_mr_string
756
+ render(format: :mr_string)
757
+ end
758
+
759
+ # Filesystem-/URL-safe slug derived from the MR string. Defaults to
760
+ # `to_mr_string` because every flavor except NIST already emits an
761
+ # all-lowercase, filename-safe MR (only `[a-z0-9.-]` characters, plus
762
+ # `_` as the supplement separator and `-` as the copublisher separator).
763
+ # Flavors whose MR is not slug-safe (notably NIST, whose MR format is
764
+ # fixed by the pubid standard) override this to project the MR into a
765
+ # slug-safe form.
766
+ def to_slug
767
+ to_mr_string
768
+ end
769
+
770
+ # MR string template methods — flavors override as needed.
771
+ #
772
+ # The MR format is a lossless, dot-separated, all-lowercase, filename-safe
773
+ # slug mirroring `to_s`'s structure:
774
+ #
775
+ # {publisher}[-{copublisher}…][.{type}].{number}[-{part}[-{subpart}…]]
776
+ # [.{year}[-{month}[-{day}]]|’--’}][.{edition}][.{language}-{language}…]
777
+ # [.all-parts][_supplements…]
778
+ #
779
+ # Supplements append `_{type}.{number}.{year}` recursively, so a chained
780
+ # supplement like `…/Amd 3:2016/Cor 1:2017` round-trips as
781
+ # `…_amd.3.2016_cor.1.2017`. Copublishers join with `-`
782
+ # (`iso-iec.17031-1.2020`) so the slug never contains a path separator.
783
+ # Distinct identifiers never collide on `to_mr_string` (issue #142).
784
+ def mr_publisher
785
+ publisher&.to_s&.downcase&.tr("/", "-")
786
+ end
787
+
788
+ def mr_type
789
+ return nil unless typed_stage
790
+
791
+ code = typed_stage.type_code
792
+ return nil if code.nil? || code.empty? || code.to_s == "is"
793
+
794
+ code.to_s.downcase
795
+ end
796
+
797
+ def mr_number_with_part
798
+ num = mr_number
799
+ return nil unless num
800
+
801
+ segments = [num]
802
+ segments << mr_part if part
803
+ segments << mr_subpart if subpart
804
+ segments.compact.join("-").downcase
805
+ end
806
+
807
+ # Join MR segments and return nil rather than "" when nothing survives.
808
+ #
809
+ # Renderers::MrString does `parts.compact.join(".")`, and `compact` removes
810
+ # nil but NOT an empty string — so a hook that returns "" contributes a
811
+ # blank segment and the slug gains a double dot. The base
812
+ # #mr_number_with_part guards this with its own `return nil unless num`;
813
+ # flavor overrides that append their own marker to `super` need the same
814
+ # guard, and this is it.
815
+ def mr_join(*segments)
816
+ joined = segments.compact.reject { |s| s.to_s.empty? }.join("-")
817
+ joined.empty? ? nil : joined
818
+ end
819
+
820
+ def mr_number
821
+ number&.to_s&.downcase
822
+ end
823
+
824
+ def mr_part
825
+ part&.to_s&.downcase
826
+ end
827
+
828
+ def mr_subpart
829
+ subpart&.to_s&.downcase
830
+ end
831
+
832
+ def mr_year
833
+ return nil unless date
834
+ return "--" if date.respond_to?(:undated?) && date.undated?
835
+ return nil unless date.year
836
+
837
+ result = date.year.to_s
838
+ result += "-#{date.month}" if date.respond_to?(:month) && date.month
839
+ result += "-#{date.day}" if date.respond_to?(:day) && date.day
840
+ result
841
+ end
842
+
843
+ def mr_edition
844
+ return nil unless edition&.number
845
+
846
+ "ed#{edition.number}"
847
+ end
848
+
849
+ # Hook for flavors whose identity includes a volume discriminator — ECMA-269
850
+ # ed3 vol1..vol4 are four distinct documents sharing one docidentifier.
851
+ # Literally nil here, NOT `volume&.to_s`: Pubid::Nist::Identifiers::Base
852
+ # declares its own `volume` attribute with different MR semantics, so a
853
+ # generic reader would change NIST's slug. A flavor that needs the segment
854
+ # overrides this (see Pubid::Ecma::Identifier#mr_volume).
855
+ def mr_volume
856
+ nil
857
+ end
858
+
859
+ def mr_languages
860
+ return nil unless languages&.any?
861
+
862
+ # Hyphen-joined, no parens — parens are shell-/URL-unsafe.
863
+ languages.map { |l| l.code.to_s.downcase }.join("-")
864
+ end
865
+
866
+ def mr_all_parts
867
+ return nil unless all_parts
868
+
869
+ "all-parts"
870
+ end
871
+
872
+ # Hook: supplement / wrapper subclasses return the `{type}.{number}.{year}`
873
+ # suffix that distinguishes them from their base. `nil` for non-supplements.
874
+ # The base MrString renderer recurses into `base` whenever this returns
875
+ # a non-nil value, so chained supplements (Cor → Amd → IS) render fully.
876
+ # The suffix is appended with `_` (not `/`) so the MR stays filename-safe.
877
+ def mr_supplement_suffix
878
+ nil
879
+ end
880
+
881
+ # URN template methods — flavors override as needed
882
+ def urn_type_code
883
+ nil
884
+ end
885
+
886
+ def urn_supplement_type
887
+ nil
888
+ end
889
+
890
+ # Supplement rendering hook — flavors override for supplement-specific rendering
891
+ def to_supplement_s(**opts)
892
+ to_s(**opts)
893
+ end
894
+
895
+ # Default URN generation — resolves flavor's UrnGenerator class
896
+ def to_urn
897
+ resolve_urn_generator.new(self).generate
898
+ end
899
+
900
+ def resolve_urn_generator
901
+ flavor = self.class.name.split("::")[1]
902
+ Object.const_get("Pubid::#{flavor}::UrnGenerator")
903
+ rescue NameError
904
+ Pubid::UrnGenerator::Base
905
+ end
906
+
907
+ # Excluded attributes are nilled; every other value is passed through
908
+ # #exclude_from_nested so the exclusion also propagates into nested
909
+ # identifiers — wrapper types (adopted standards, consolidated amendments,
910
+ # expert-commentary wrappers) delegate their date to an inner identifier
911
+ # rather than storing it in their own attribute.
912
+ def exclude(*args)
913
+ # :amendment / :supplement are structural, not attributes — they reduce
914
+ # a supplemented identifier to the standard it wraps (#drop_supplements).
915
+ supplement_keys = args & %i[amendment supplement]
916
+ unless supplement_keys.empty?
917
+ return drop_supplements.exclude(*(args - supplement_keys))
918
+ end
919
+
920
+ excluded_args = args.dup
921
+ # Map :year to :date (year-in-date flavors), but ALSO keep :year so a
922
+ # flavor that models the edition as a plain `year` attribute (e.g. GOST)
923
+ # has it excluded too. The loop below nils whichever name the flavor has.
924
+ excluded_args << :date if excluded_args.include?(:year)
925
+
926
+ attrs = self.class.attributes.each_with_object({}) do |(name, _), h|
927
+ value = excluded_args.include?(name) ? nil : public_send(name)
928
+ h[name] = exclude_from_nested(value, args)
929
+ end
930
+ # Splat the rebuilt attributes as keywords. Flavors whose identifier
931
+ # #initialize is keyword-only (ITU, IEEE, …) would raise ArgumentError on
932
+ # a positional hash under Ruby 3 kwarg separation; the base
933
+ # initialize(attrs = {}, options = {}) still accepts **attrs because Ruby
934
+ # passes keywords to a no-keyword method as a trailing positional Hash.
935
+ self.class.new(**attrs)
936
+ end
937
+
938
+ # The standard this identifier supplements, dropping its own supplement
939
+ # layer (one level). Overridden by ConsolidatedIdentifier / Amendment /
940
+ # Corrigendum; a non-supplement identifier supplements nothing, so it is
941
+ # returned unchanged.
942
+ def drop_supplements
943
+ self
944
+ end
945
+
946
+ # Fuzz-level equality: two identifiers match when they are equal after
947
+ # excluding the given aspects. `ignore` accepts the same symbols as #exclude
948
+ # (e.g. :date, :edition, :amendment). This is the primitive relaton uses to
949
+ # match a reference against catalogue hits at varying strictness.
950
+ def matches?(other, ignore: [])
951
+ return false unless other.is_a?(::Pubid::Identifier)
952
+
953
+ exclude(*ignore) == other.exclude(*ignore)
954
+ end
955
+
956
+ def new_edition_of?(other)
957
+ unless publisher == other.publisher
958
+ raise ArgumentError,
959
+ "Cannot compare edition: different publisher"
960
+ end
961
+ unless number == other.number
962
+ raise ArgumentError,
963
+ "Cannot compare edition: different number"
964
+ end
965
+ unless part == other.part
966
+ raise ArgumentError,
967
+ "Cannot compare edition: different part"
968
+ end
969
+
970
+ unless date && other.date
971
+ raise ArgumentError,
972
+ "Cannot compare identifier without date/year"
973
+ end
974
+
975
+ return date.year > other.date.year if date.year != other.date.year
976
+
977
+ if edition && other.edition
978
+ return edition.number > other.edition.number
979
+ end
980
+
981
+ false
982
+ end
983
+
984
+ # --- Relational algebra (pubid/pubid#247) ---
985
+ # Each predicate returns true / false. Predicates that don't apply to
986
+ # this identifier type (e.g. supplement checks on a non-wrapper) return
987
+ # false rather than raising. This makes them safe for chain-of-checks
988
+ # patterns like `id.related_to?(other)`.
989
+
990
+ # Self is a supplement (amendment / corrigendum / errata / supplement)
991
+ # of +other+. Self must be a wrapper (has a +base+ attribute) whose
992
+ # inner base equals +other+.
993
+ def supplement_of?(other)
994
+ return false unless other.is_a?(::Pubid::Identifier)
995
+ return false unless self.class.attributes.key?(:base)
996
+
997
+ base == other
998
+ end
999
+
1000
+ # Inverse of #supplement_of? — self is the base document and +other+
1001
+ # is a supplement attached to it.
1002
+ def has_supplement?(other)
1003
+ other.is_a?(::Pubid::Identifier) && other.supplement_of?(self)
1004
+ end
1005
+
1006
+ # Self and +other+ refer to the same document but differ only in the
1007
+ # trailing date / year — the "dated vs undated" reference pattern.
1008
+ def dated_version_of?(other)
1009
+ return false unless other.is_a?(::Pubid::Identifier)
1010
+
1011
+ matches?(other, ignore: [:date, :year]) && self != other
1012
+ end
1013
+
1014
+ # Self and +other+ have the same publisher + number but differ in
1015
+ # part, subpart, or edition. This is the "sibling documents" pattern:
1016
+ # same base standard, different sub-component.
1017
+ def sibling_of?(other)
1018
+ return false unless other.is_a?(::Pubid::Identifier)
1019
+
1020
+ publisher == other.publisher &&
1021
+ number == other.number &&
1022
+ self != other
1023
+ end
1024
+
1025
+ # Self is an all-parts collection that covers +other+.
1026
+ def includes?(other)
1027
+ return false unless other.is_a?(::Pubid::Identifier)
1028
+ return false unless all_parts
1029
+
1030
+ root == other.root
1031
+ end
1032
+
1033
+ # Self is a draft version of +other+ (the published identifier).
1034
+ # IEEE uses draft_status + draft; ISO/IEC use typed_stage.
1035
+ def draft_of?(other)
1036
+ return false unless other.is_a?(::Pubid::Identifier)
1037
+
1038
+ root == other.root &&
1039
+ self != other &&
1040
+ matches?(other, ignore: [:date, :year, :stage, :typed_stage])
1041
+ end
1042
+
1043
+ # Self and +other+ have the same publisher + number + part but differ
1044
+ # in edition / revision marker.
1045
+ def edition_of?(other)
1046
+ return false unless other.is_a?(::Pubid::Identifier)
1047
+
1048
+ publisher == other.publisher &&
1049
+ number == other.number &&
1050
+ part == other.part &&
1051
+ self != other
1052
+ end
1053
+
1054
+ # Any relational predicate holds.
1055
+ def related_to?(other)
1056
+ %i[supplement_of? has_supplement? dated_version_of? edition_of?
1057
+ sibling_of? includes? draft_of?].any? do |m|
1058
+ public_send(m, other)
1059
+ end
1060
+ end
1061
+
1062
+ def hash
1063
+ @hash ||= compute_hash
1064
+ end
1065
+
1066
+ def eql?(other)
1067
+ return false unless other.is_a?(self.class)
1068
+
1069
+ hash == other.hash && self == other
1070
+ end
1071
+
1072
+ private
1073
+
1074
+ # Propagate an #exclude into a nested attribute value: recurse when it is
1075
+ # (or contains) another identifier, otherwise return it unchanged.
1076
+ # Components and scalars are copied as-is. Passes the original args so
1077
+ # nested identifiers re-apply the same :year->:date mapping themselves.
1078
+ def exclude_from_nested(value, args)
1079
+ case value
1080
+ when ::Pubid::Identifier
1081
+ value.exclude(*args)
1082
+ when Array
1083
+ value.map { |item| exclude_from_nested(item, args) }
1084
+ else
1085
+ value
1086
+ end
1087
+ end
1088
+
1089
+ def build_rendering_context(_renderer, format:, with_edition: false,
1090
+ lang: :en, lang_single: false,
1091
+ stage_format_long: nil, with_date: nil,
1092
+ annotated: false)
1093
+ if format == :mr_string
1094
+ nil
1095
+ else
1096
+ Rendering::RenderingContext.new(
1097
+ with_language_code: lang_single ? :single : :none,
1098
+ stage_format_long: stage_format_long || false,
1099
+ with_date: with_date.nil? || with_date,
1100
+ annotated: annotated,
1101
+ )
1102
+ end
1103
+ end
1104
+
1105
+ def compute_hash
1106
+ attrs = [
1107
+ publisher,
1108
+ number,
1109
+ part,
1110
+ subpart,
1111
+ date,
1112
+ type,
1113
+ stage,
1114
+ ]
1115
+ attrs.compact.map(&:hash).hash
1116
+ end
1117
+ end
1118
+ end