pubid 2.0.0.pre.alpha.7 → 2.0.0.pre.alpha.9

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 (485) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +9 -50
  3. data/data/bipm/update_codes.yaml +9 -0
  4. data/data/iec/update_codes.yaml +3 -0
  5. data/data/ieee/update_codes.yaml +107 -1
  6. data/data/nist/series.yaml +155 -0
  7. data/data/nist/update_codes.yaml +8 -0
  8. data/lib/pubid/adobe/builder.rb +52 -0
  9. data/lib/pubid/adobe/identifier.rb +49 -0
  10. data/lib/pubid/adobe/identifiers/publication.rb +31 -0
  11. data/lib/pubid/adobe/identifiers/tech_note.rb +32 -0
  12. data/lib/pubid/adobe/identifiers.rb +10 -0
  13. data/lib/pubid/adobe/parser.rb +128 -0
  14. data/lib/pubid/adobe/renderer.rb +39 -0
  15. data/lib/pubid/adobe/urn_generator.rb +42 -0
  16. data/lib/pubid/adobe/urn_parser.rb +69 -0
  17. data/lib/pubid/adobe.rb +60 -0
  18. data/lib/pubid/amca/builder.rb +2 -2
  19. data/lib/pubid/amca/identifier.rb +1 -3
  20. data/lib/pubid/amca/identifiers/base.rb +1 -6
  21. data/lib/pubid/amca/identifiers/interpretation.rb +1 -1
  22. data/lib/pubid/amca/identifiers/publication.rb +1 -1
  23. data/lib/pubid/amca/identifiers/standard.rb +1 -1
  24. data/lib/pubid/amca/identifiers.rb +0 -1
  25. data/lib/pubid/amca/single_identifier.rb +1 -1
  26. data/lib/pubid/amca.rb +3 -3
  27. data/lib/pubid/ashrae/builder.rb +22 -22
  28. data/lib/pubid/ashrae/identifier.rb +3 -4
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +1 -1
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +1 -1
  31. data/lib/pubid/ashrae/identifiers/base.rb +14 -8
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +1 -1
  33. data/lib/pubid/ashrae/identifiers.rb +0 -1
  34. data/lib/pubid/ashrae/parser.rb +14 -9
  35. data/lib/pubid/ashrae/renderer.rb +19 -19
  36. data/lib/pubid/ashrae/single_identifier.rb +1 -1
  37. data/lib/pubid/ashrae/supplement_identifier.rb +6 -6
  38. data/lib/pubid/ashrae.rb +3 -3
  39. data/lib/pubid/bipm/builder.rb +107 -0
  40. data/lib/pubid/bipm/identifier.rb +160 -0
  41. data/lib/pubid/bipm/identifiers/committee_document.rb +24 -0
  42. data/lib/pubid/bipm/identifiers/guide.rb +24 -0
  43. data/lib/pubid/bipm/identifiers/meeting.rb +41 -0
  44. data/lib/pubid/bipm/identifiers/mep.rb +21 -0
  45. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  46. data/lib/pubid/bipm/identifiers/si_brochure.rb +20 -0
  47. data/lib/pubid/bipm/identifiers.rb +15 -0
  48. data/lib/pubid/bipm/parser.rb +184 -0
  49. data/lib/pubid/bipm/renderer.rb +110 -0
  50. data/lib/pubid/bipm/urn_generator.rb +58 -0
  51. data/lib/pubid/bipm/urn_parser.rb +52 -0
  52. data/lib/pubid/bipm.rb +86 -0
  53. data/lib/pubid/bsi/builder.rb +14 -14
  54. data/lib/pubid/bsi/identifier.rb +0 -1
  55. data/lib/pubid/bsi/identifiers/addendum_document.rb +2 -2
  56. data/lib/pubid/bsi/identifiers/amendment.rb +4 -4
  57. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +6 -0
  58. data/lib/pubid/bsi/identifiers/corrigendum.rb +4 -4
  59. data/lib/pubid/bsi/identifiers/expert_commentary.rb +5 -5
  60. data/lib/pubid/bsi/identifiers/supplement_document.rb +2 -2
  61. data/lib/pubid/bsi/identifiers/value_added_publication.rb +6 -6
  62. data/lib/pubid/bsi/renderer.rb +8 -8
  63. data/lib/pubid/bsi/single_identifier.rb +48 -0
  64. data/lib/pubid/builder/base.rb +5 -3
  65. data/lib/pubid/bundled_identifier.rb +3 -3
  66. data/lib/pubid/calconnect/builder.rb +38 -0
  67. data/lib/pubid/calconnect/identifier.rb +117 -0
  68. data/lib/pubid/calconnect/identifiers/standard.rb +29 -0
  69. data/lib/pubid/calconnect/identifiers.rb +9 -0
  70. data/lib/pubid/calconnect/parser.rb +54 -0
  71. data/lib/pubid/calconnect/renderer.rb +36 -0
  72. data/lib/pubid/calconnect/urn_generator.rb +33 -0
  73. data/lib/pubid/calconnect/urn_parser.rb +34 -0
  74. data/lib/pubid/calconnect.rb +77 -0
  75. data/lib/pubid/ccsds/builder.rb +1 -1
  76. data/lib/pubid/ccsds/identifier.rb +1 -1
  77. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  78. data/lib/pubid/ccsds/supplement_identifier.rb +6 -6
  79. data/lib/pubid/ccsds/urn_generator.rb +3 -3
  80. data/lib/pubid/cen_cenelec/builder.rb +46 -12
  81. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +2 -2
  82. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +6 -0
  83. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +2 -2
  84. data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +60 -3
  85. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +2 -2
  86. data/lib/pubid/cen_cenelec/renderer.rb +5 -5
  87. data/lib/pubid/cen_cenelec/supplement_identifier.rb +5 -5
  88. data/lib/pubid/cen_cenelec/urn_generator.rb +1 -1
  89. data/lib/pubid/cen_cenelec.rb +76 -72
  90. data/lib/pubid/cie/builder.rb +139 -24
  91. data/lib/pubid/cie/identifier.rb +7 -3
  92. data/lib/pubid/cie/identifiers/bundle.rb +32 -4
  93. data/lib/pubid/cie/identifiers/code_attributes.rb +52 -0
  94. data/lib/pubid/cie/identifiers/conference.rb +9 -4
  95. data/lib/pubid/cie/identifiers/corrigendum.rb +17 -23
  96. data/lib/pubid/cie/identifiers/dual_published.rb +4 -4
  97. data/lib/pubid/cie/identifiers/identical.rb +7 -7
  98. data/lib/pubid/cie/identifiers/joint_published.rb +8 -8
  99. data/lib/pubid/cie/identifiers/proceedings.rb +39 -0
  100. data/lib/pubid/cie/identifiers/standard.rb +33 -7
  101. data/lib/pubid/cie/identifiers/supplement.rb +24 -22
  102. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +6 -2
  103. data/lib/pubid/cie/identifiers.rb +2 -0
  104. data/lib/pubid/cie/parser.rb +54 -5
  105. data/lib/pubid/cie/single_identifier.rb +40 -1
  106. data/lib/pubid/cie/supplement_identifier.rb +29 -16
  107. data/lib/pubid/cie/urn_generator.rb +11 -8
  108. data/lib/pubid/cie.rb +0 -1
  109. data/lib/pubid/components/date.rb +21 -5
  110. data/lib/pubid/csa/builder.rb +1 -1
  111. data/lib/pubid/csa/composite_identifier.rb +17 -1
  112. data/lib/pubid/csa/identifier.rb +48 -3
  113. data/lib/pubid/csa/identifiers/bundled.rb +6 -0
  114. data/lib/pubid/csa/identifiers/combined.rb +6 -0
  115. data/lib/pubid/csa/identifiers/package.rb +2 -2
  116. data/lib/pubid/csa/parser.rb +6 -6
  117. data/lib/pubid/csa/wrapper_identifier.rb +17 -0
  118. data/lib/pubid/doi/builder.rb +18 -0
  119. data/lib/pubid/doi/identifier.rb +46 -0
  120. data/lib/pubid/doi/identifiers/resource.rb +30 -0
  121. data/lib/pubid/doi/identifiers.rb +9 -0
  122. data/lib/pubid/doi/parser.rb +52 -0
  123. data/lib/pubid/doi/renderer.rb +13 -0
  124. data/lib/pubid/doi.rb +55 -0
  125. data/lib/pubid/easc/builder.rb +55 -0
  126. data/lib/pubid/easc/identifier.rb +61 -0
  127. data/lib/pubid/easc/identifiers/pmg.rb +20 -0
  128. data/lib/pubid/easc/identifiers/rmg.rb +19 -0
  129. data/lib/pubid/easc/identifiers.rb +10 -0
  130. data/lib/pubid/easc/parser.rb +80 -0
  131. data/lib/pubid/easc/renderer.rb +48 -0
  132. data/lib/pubid/easc/urn_generator.rb +46 -0
  133. data/lib/pubid/easc/urn_parser.rb +57 -0
  134. data/lib/pubid/easc.rb +52 -0
  135. data/lib/pubid/ecma/builder.rb +31 -0
  136. data/lib/pubid/ecma/identifier.rb +76 -0
  137. data/lib/pubid/ecma/identifiers/memento.rb +30 -0
  138. data/lib/pubid/ecma/identifiers/standard.rb +31 -0
  139. data/lib/pubid/ecma/identifiers/technical_report.rb +31 -0
  140. data/lib/pubid/ecma/identifiers.rb +11 -0
  141. data/lib/pubid/ecma/parser.rb +41 -0
  142. data/lib/pubid/ecma/renderer.rb +46 -0
  143. data/lib/pubid/ecma/urn_generator.rb +25 -0
  144. data/lib/pubid/ecma/urn_parser.rb +35 -0
  145. data/lib/pubid/ecma.rb +67 -0
  146. data/lib/pubid/etsi/builder.rb +7 -3
  147. data/lib/pubid/etsi/identifier.rb +1 -3
  148. data/lib/pubid/etsi/identifiers/base.rb +28 -10
  149. data/lib/pubid/etsi/identifiers/etsi_standard.rb +128 -4
  150. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +35 -3
  151. data/lib/pubid/etsi/identifiers.rb +0 -1
  152. data/lib/pubid/etsi/parser.rb +8 -2
  153. data/lib/pubid/etsi/renderer.rb +8 -2
  154. data/lib/pubid/etsi.rb +5 -5
  155. data/lib/pubid/export/exporter.rb +2 -1
  156. data/lib/pubid/export/flavor_exporter.rb +18 -3
  157. data/lib/pubid/gb/builder.rb +45 -0
  158. data/lib/pubid/gb/identifier.rb +64 -0
  159. data/lib/pubid/gb/identifiers/standard.rb +34 -0
  160. data/lib/pubid/gb/identifiers.rb +9 -0
  161. data/lib/pubid/gb/parser.rb +63 -0
  162. data/lib/pubid/gb/renderer.rb +35 -0
  163. data/lib/pubid/gb.rb +87 -0
  164. data/lib/pubid/gost/builder.rb +148 -0
  165. data/lib/pubid/gost/identifier.rb +49 -0
  166. data/lib/pubid/gost/identifiers/foreign_reference.rb +25 -0
  167. data/lib/pubid/gost/identifiers/harmonized.rb +39 -0
  168. data/lib/pubid/gost/identifiers/identical_adoption.rb +39 -0
  169. data/lib/pubid/gost/identifiers/interstate_standard.rb +20 -0
  170. data/lib/pubid/gost/identifiers/national_standard.rb +19 -0
  171. data/lib/pubid/gost/identifiers.rb +13 -0
  172. data/lib/pubid/gost/parser.rb +97 -0
  173. data/lib/pubid/gost/renderer.rb +48 -0
  174. data/lib/pubid/gost/urn_generator.rb +46 -0
  175. data/lib/pubid/gost/urn_parser.rb +52 -0
  176. data/lib/pubid/gost.rb +49 -0
  177. data/lib/pubid/iala/builder.rb +22 -27
  178. data/lib/pubid/iala/identifier.rb +27 -2
  179. data/lib/pubid/iala/identifiers/advice.rb +1 -1
  180. data/lib/pubid/iala/identifiers/annex.rb +14 -12
  181. data/lib/pubid/iala/identifiers/general_assembly.rb +5 -2
  182. data/lib/pubid/iala/identifiers/guideline.rb +3 -1
  183. data/lib/pubid/iala/identifiers/letter.rb +1 -1
  184. data/lib/pubid/iala/identifiers/manual.rb +3 -1
  185. data/lib/pubid/iala/identifiers/model_course.rb +3 -1
  186. data/lib/pubid/iala/identifiers/recommendation.rb +3 -1
  187. data/lib/pubid/iala/identifiers/report.rb +1 -1
  188. data/lib/pubid/iala/identifiers/resolution.rb +1 -1
  189. data/lib/pubid/iala/identifiers/standard.rb +3 -1
  190. data/lib/pubid/iala/identifiers.rb +0 -1
  191. data/lib/pubid/iala/renderer.rb +1 -1
  192. data/lib/pubid/iala/urn_generator.rb +1 -1
  193. data/lib/pubid/iala/urn_parser.rb +19 -2
  194. data/lib/pubid/iala.rb +4 -4
  195. data/lib/pubid/iana/builder.rb +20 -0
  196. data/lib/pubid/iana/identifier.rb +72 -0
  197. data/lib/pubid/iana/identifiers/registry.rb +33 -0
  198. data/lib/pubid/iana/identifiers.rb +9 -0
  199. data/lib/pubid/iana/parser.rb +38 -0
  200. data/lib/pubid/iana/renderer.rb +29 -0
  201. data/lib/pubid/iana/urn_generator.rb +15 -0
  202. data/lib/pubid/iana/urn_parser.rb +24 -0
  203. data/lib/pubid/iana.rb +69 -0
  204. data/lib/pubid/identifier.rb +210 -23
  205. data/lib/pubid/idf/builder.rb +1 -1
  206. data/lib/pubid/idf/parser.rb +2 -2
  207. data/lib/pubid/idf/renderer.rb +1 -1
  208. data/lib/pubid/idf/supplement_identifier.rb +3 -3
  209. data/lib/pubid/iec/builder.rb +35 -25
  210. data/lib/pubid/iec/identifier.rb +47 -2
  211. data/lib/pubid/iec/identifiers/base.rb +2 -2
  212. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +6 -0
  213. data/lib/pubid/iec/identifiers/fragment_identifier.rb +11 -11
  214. data/lib/pubid/iec/identifiers/international_standard.rb +8 -5
  215. data/lib/pubid/iec/identifiers/sheet_identifier.rb +12 -12
  216. data/lib/pubid/iec/identifiers/technical_group.rb +30 -0
  217. data/lib/pubid/iec/identifiers/technical_report.rb +1 -1
  218. data/lib/pubid/iec/identifiers/technical_specification.rb +1 -1
  219. data/lib/pubid/iec/identifiers/vap_identifier.rb +12 -12
  220. data/lib/pubid/iec/identifiers.rb +1 -0
  221. data/lib/pubid/iec/parser.rb +51 -10
  222. data/lib/pubid/iec/renderer.rb +18 -8
  223. data/lib/pubid/iec/single_identifier.rb +1 -1
  224. data/lib/pubid/iec/supplement_identifier.rb +32 -19
  225. data/lib/pubid/iec.rb +1 -6
  226. data/lib/pubid/ieee/aiee/builder.rb +17 -3
  227. data/lib/pubid/ieee/aiee/identifier.rb +45 -61
  228. data/lib/pubid/ieee/builder.rb +341 -68
  229. data/lib/pubid/ieee/compaction.rb +121 -0
  230. data/lib/pubid/ieee/components/draft.rb +47 -7
  231. data/lib/pubid/ieee/identifier.rb +1 -3
  232. data/lib/pubid/ieee/identifiers/adopted_standard.rb +30 -5
  233. data/lib/pubid/ieee/identifiers/amendment.rb +40 -0
  234. data/lib/pubid/ieee/identifiers/base.rb +106 -13
  235. data/lib/pubid/ieee/identifiers/code_number.rb +82 -0
  236. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +6 -2
  237. data/lib/pubid/ieee/identifiers/corrigendum.rb +17 -2
  238. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +3 -3
  239. data/lib/pubid/ieee/identifiers/dual_identifier.rb +3 -3
  240. data/lib/pubid/ieee/identifiers/dual_published.rb +3 -3
  241. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +102 -5
  242. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +2 -1
  243. data/lib/pubid/ieee/identifiers/joint_development.rb +56 -11
  244. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +1 -1
  245. data/lib/pubid/ieee/identifiers/nesc/base.rb +90 -19
  246. data/lib/pubid/ieee/identifiers/nesc/draft.rb +20 -5
  247. data/lib/pubid/ieee/identifiers/nesc/edition.rb +32 -0
  248. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +16 -5
  249. data/lib/pubid/ieee/identifiers/nesc/redline.rb +11 -2
  250. data/lib/pubid/ieee/identifiers/nesc/standard.rb +18 -3
  251. data/lib/pubid/ieee/identifiers/nesc.rb +1 -0
  252. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +3 -3
  253. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +14 -6
  254. data/lib/pubid/ieee/identifiers/redlined_standard.rb +4 -4
  255. data/lib/pubid/ieee/identifiers/si_standard.rb +4 -1
  256. data/lib/pubid/ieee/identifiers/standard.rb +4 -1
  257. data/lib/pubid/ieee/identifiers/supplement_identifier.rb +26 -7
  258. data/lib/pubid/ieee/identifiers.rb +2 -1
  259. data/lib/pubid/ieee/ire/builder.rb +7 -6
  260. data/lib/pubid/ieee/ire/identifier.rb +48 -33
  261. data/lib/pubid/ieee/nesc/builder.rb +33 -20
  262. data/lib/pubid/ieee/nesc/parser.rb +15 -14
  263. data/lib/pubid/ieee/parser.rb +461 -43
  264. data/lib/pubid/ieee/pre_parser.rb +1 -1
  265. data/lib/pubid/ieee/renderer.rb +154 -57
  266. data/lib/pubid/ieee/typed_stages.rb +12 -1
  267. data/lib/pubid/ieee/urn_generator.rb +3 -5
  268. data/lib/pubid/ieee.rb +56 -3
  269. data/lib/pubid/ietf/builder.rb +62 -0
  270. data/lib/pubid/ietf/identifier.rb +5 -0
  271. data/lib/pubid/ietf/identifiers/base.rb +75 -0
  272. data/lib/pubid/ietf/identifiers/bcp.rb +25 -0
  273. data/lib/pubid/ietf/identifiers/fyi.rb +25 -0
  274. data/lib/pubid/ietf/identifiers/internet_draft.rb +29 -0
  275. data/lib/pubid/ietf/identifiers/rfc.rb +25 -0
  276. data/lib/pubid/ietf/identifiers/std.rb +25 -0
  277. data/lib/pubid/ietf/identifiers.rb +13 -0
  278. data/lib/pubid/ietf/parser.rb +43 -0
  279. data/lib/pubid/ietf/renderer.rb +26 -0
  280. data/lib/pubid/ietf/urn_generator.rb +23 -0
  281. data/lib/pubid/ietf/urn_parser.rb +35 -0
  282. data/lib/pubid/ietf.rb +76 -0
  283. data/lib/pubid/iho/identifier.rb +1 -3
  284. data/lib/pubid/iho/identifiers/base.rb +1 -7
  285. data/lib/pubid/iho/identifiers/bibliographic.rb +1 -1
  286. data/lib/pubid/iho/identifiers/circular_letter.rb +1 -1
  287. data/lib/pubid/iho/identifiers/miscellaneous.rb +1 -1
  288. data/lib/pubid/iho/identifiers/publication.rb +1 -1
  289. data/lib/pubid/iho/identifiers/standard.rb +1 -1
  290. data/lib/pubid/iho/identifiers.rb +0 -1
  291. data/lib/pubid/iho.rb +7 -7
  292. data/lib/pubid/isbn/builder.rb +44 -0
  293. data/lib/pubid/isbn/check_digit.rb +53 -0
  294. data/lib/pubid/isbn/identifier.rb +63 -0
  295. data/lib/pubid/isbn/identifiers/book.rb +30 -0
  296. data/lib/pubid/isbn/identifiers.rb +9 -0
  297. data/lib/pubid/isbn/parser.rb +45 -0
  298. data/lib/pubid/isbn/renderer.rb +16 -0
  299. data/lib/pubid/isbn.rb +52 -0
  300. data/lib/pubid/iso/builder.rb +9 -4
  301. data/lib/pubid/iso/combined_identifier.rb +2 -2
  302. data/lib/pubid/iso/identifier.rb +17 -1
  303. data/lib/pubid/iso/identifiers/directives_supplement.rb +8 -8
  304. data/lib/pubid/iso/normalizer.rb +2 -2
  305. data/lib/pubid/iso/parser.rb +16 -12
  306. data/lib/pubid/iso/supplement_identifier.rb +19 -6
  307. data/lib/pubid/iso/urn_generator.rb +4 -1
  308. data/lib/pubid/iso/urn_parser.rb +1 -1
  309. data/lib/pubid/itu/builder.rb +230 -29
  310. data/lib/pubid/itu/components/code.rb +48 -5
  311. data/lib/pubid/itu/components/designation.rb +35 -0
  312. data/lib/pubid/itu/components.rb +1 -0
  313. data/lib/pubid/itu/identifier.rb +1 -3
  314. data/lib/pubid/itu/identifiers/addendum.rb +15 -0
  315. data/lib/pubid/itu/identifiers/amendment.rb +6 -28
  316. data/lib/pubid/itu/identifiers/annex.rb +13 -2
  317. data/lib/pubid/itu/identifiers/annex_of_recommendation.rb +87 -0
  318. data/lib/pubid/itu/identifiers/appendix_of_recommendation.rb +92 -0
  319. data/lib/pubid/itu/identifiers/base.rb +341 -23
  320. data/lib/pubid/itu/identifiers/combined_identifier.rb +99 -23
  321. data/lib/pubid/itu/identifiers/corrigendum.rb +16 -23
  322. data/lib/pubid/itu/identifiers/errata.rb +15 -0
  323. data/lib/pubid/itu/identifiers/handbook.rb +38 -0
  324. data/lib/pubid/itu/identifiers/question.rb +63 -0
  325. data/lib/pubid/itu/identifiers/recommendation.rb +3 -2
  326. data/lib/pubid/itu/identifiers/report.rb +39 -0
  327. data/lib/pubid/itu/identifiers/special_publication.rb +3 -1
  328. data/lib/pubid/itu/identifiers/standard_serialization.rb +58 -0
  329. data/lib/pubid/itu/identifiers/supplement.rb +119 -14
  330. data/lib/pubid/itu/identifiers.rb +10 -1
  331. data/lib/pubid/itu/model.rb +1 -1
  332. data/lib/pubid/itu/parser.rb +480 -26
  333. data/lib/pubid/itu/urn_generator.rb +36 -6
  334. data/lib/pubid/itu/urn_parser.rb +8 -0
  335. data/lib/pubid/itu.rb +11 -3
  336. data/lib/pubid/jcgm/builder.rb +23 -13
  337. data/lib/pubid/jcgm/identifiers/amendment.rb +0 -2
  338. data/lib/pubid/jcgm/identifiers/corrigendum.rb +34 -0
  339. data/lib/pubid/jcgm/identifiers/gum_guide.rb +3 -3
  340. data/lib/pubid/jcgm/identifiers/meeting.rb +47 -0
  341. data/lib/pubid/jcgm/identifiers.rb +2 -0
  342. data/lib/pubid/jcgm/parser.rb +66 -6
  343. data/lib/pubid/jcgm/renderer.rb +26 -12
  344. data/lib/pubid/jcgm/single_identifier.rb +74 -5
  345. data/lib/pubid/jcgm/supplement_identifier.rb +39 -4
  346. data/lib/pubid/jcgm/urn_generator.rb +21 -13
  347. data/lib/pubid/jcgm/urn_parser.rb +15 -3
  348. data/lib/pubid/jis/identifier.rb +52 -0
  349. data/lib/pubid/nist/builder.rb +29 -9
  350. data/lib/pubid/nist/caster.rb +7 -45
  351. data/lib/pubid/nist/circular_supplement_builder.rb +7 -5
  352. data/lib/pubid/nist/components/supplement.rb +5 -2
  353. data/lib/pubid/nist/configuration.rb +7 -1
  354. data/lib/pubid/nist/identifier.rb +1 -3
  355. data/lib/pubid/nist/identifiers/base.rb +121 -37
  356. data/lib/pubid/nist/identifiers/circular.rb +1 -1
  357. data/lib/pubid/nist/identifiers/commercial_standard.rb +1 -1
  358. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
  359. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +1 -1
  360. data/lib/pubid/nist/identifiers/crpl_report.rb +1 -1
  361. data/lib/pubid/nist/identifiers/dated_document.rb +1 -1
  362. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +1 -1
  363. data/lib/pubid/nist/identifiers/grant_contractor_report.rb +1 -1
  364. data/lib/pubid/nist/identifiers/handbook.rb +1 -1
  365. data/lib/pubid/nist/identifiers/internal_report.rb +1 -1
  366. data/lib/pubid/nist/identifiers/letter_circular.rb +1 -1
  367. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +1 -1
  368. data/lib/pubid/nist/identifiers/monograph.rb +1 -1
  369. data/lib/pubid/nist/identifiers/ncstar.rb +2 -2
  370. data/lib/pubid/nist/identifiers/nsrds.rb +1 -1
  371. data/lib/pubid/nist/identifiers/owmwp.rb +1 -1
  372. data/lib/pubid/nist/identifiers/report.rb +1 -1
  373. data/lib/pubid/nist/identifiers/special_publication.rb +10 -1
  374. data/lib/pubid/nist/identifiers/technical_note.rb +1 -1
  375. data/lib/pubid/nist/identifiers.rb +0 -1
  376. data/lib/pubid/nist/parser.rb +29 -4
  377. data/lib/pubid/nist/preprocessor.rb +15 -0
  378. data/lib/pubid/nist/router.rb +3 -2
  379. data/lib/pubid/nist/series/ir.rb +0 -3
  380. data/lib/pubid/nist/supplement_identifier.rb +9 -9
  381. data/lib/pubid/nist.rb +13 -12
  382. data/lib/pubid/oasis/builder.rb +77 -0
  383. data/lib/pubid/oasis/identifier.rb +82 -0
  384. data/lib/pubid/oasis/identifiers/standard.rb +29 -0
  385. data/lib/pubid/oasis/identifiers.rb +9 -0
  386. data/lib/pubid/oasis/parser.rb +34 -0
  387. data/lib/pubid/oasis/renderer.rb +29 -0
  388. data/lib/pubid/oasis/urn_generator.rb +25 -0
  389. data/lib/pubid/oasis/urn_parser.rb +22 -0
  390. data/lib/pubid/oasis.rb +74 -0
  391. data/lib/pubid/ogc/builder.rb +33 -0
  392. data/lib/pubid/ogc/identifier.rb +77 -0
  393. data/lib/pubid/ogc/identifiers/document.rb +26 -0
  394. data/lib/pubid/ogc/identifiers.rb +10 -0
  395. data/lib/pubid/ogc/parser.rb +40 -0
  396. data/lib/pubid/ogc/renderer.rb +33 -0
  397. data/lib/pubid/ogc/urn_generator.rb +14 -0
  398. data/lib/pubid/ogc/urn_parser.rb +22 -0
  399. data/lib/pubid/ogc.rb +71 -0
  400. data/lib/pubid/oiml/builder.rb +31 -7
  401. data/lib/pubid/oiml/identifier.rb +9 -1
  402. data/lib/pubid/oiml/parser.rb +11 -12
  403. data/lib/pubid/oiml/renderer.rb +13 -13
  404. data/lib/pubid/oiml/single_identifier.rb +20 -0
  405. data/lib/pubid/oiml/supplement_identifier.rb +9 -9
  406. data/lib/pubid/oiml/urn_parser.rb +23 -6
  407. data/lib/pubid/oiml.rb +4 -0
  408. data/lib/pubid/omg/builder.rb +19 -0
  409. data/lib/pubid/omg/identifier.rb +44 -0
  410. data/lib/pubid/omg/identifiers/specification.rb +32 -0
  411. data/lib/pubid/omg/identifiers.rb +9 -0
  412. data/lib/pubid/omg/parser.rb +40 -0
  413. data/lib/pubid/omg/renderer.rb +19 -0
  414. data/lib/pubid/omg.rb +51 -0
  415. data/lib/pubid/parsers/mr_string.rb +101 -7
  416. data/lib/pubid/plateau/builder.rb +8 -4
  417. data/lib/pubid/plateau/identifier.rb +1 -3
  418. data/lib/pubid/plateau/identifiers/base.rb +1 -6
  419. data/lib/pubid/plateau/identifiers/handbook.rb +1 -1
  420. data/lib/pubid/plateau/identifiers/technical_report.rb +1 -1
  421. data/lib/pubid/plateau/identifiers.rb +0 -1
  422. data/lib/pubid/plateau/parser.rb +15 -5
  423. data/lib/pubid/plateau/renderer.rb +1 -1
  424. data/lib/pubid/plateau/supplement_identifier.rb +11 -11
  425. data/lib/pubid/plateau.rb +4 -4
  426. data/lib/pubid/renderers/human_readable.rb +3 -1
  427. data/lib/pubid/renderers/mr_string.rb +46 -4
  428. data/lib/pubid/renderers/supplement_renderer.rb +1 -1
  429. data/lib/pubid/rendering/supplement.rb +4 -4
  430. data/lib/pubid/sae/builder.rb +1 -1
  431. data/lib/pubid/sae/identifier.rb +1 -3
  432. data/lib/pubid/sae/identifiers/base.rb +10 -7
  433. data/lib/pubid/sae/identifiers.rb +0 -1
  434. data/lib/pubid/sae.rb +3 -3
  435. data/lib/pubid/tgpp/builder.rb +41 -0
  436. data/lib/pubid/tgpp/identifier.rb +91 -0
  437. data/lib/pubid/tgpp/identifiers/technical_report.rb +32 -0
  438. data/lib/pubid/tgpp/identifiers/technical_specification.rb +33 -0
  439. data/lib/pubid/tgpp/identifiers.rb +11 -0
  440. data/lib/pubid/tgpp/parser.rb +62 -0
  441. data/lib/pubid/tgpp/renderer.rb +36 -0
  442. data/lib/pubid/tgpp/urn_generator.rb +20 -0
  443. data/lib/pubid/tgpp/urn_parser.rb +28 -0
  444. data/lib/pubid/tgpp.rb +78 -0
  445. data/lib/pubid/type_resolver.rb +59 -0
  446. data/lib/pubid/un/builder.rb +42 -0
  447. data/lib/pubid/un/identifier.rb +37 -0
  448. data/lib/pubid/un/identifiers/document.rb +24 -0
  449. data/lib/pubid/un/identifiers.rb +9 -0
  450. data/lib/pubid/un/parser.rb +25 -0
  451. data/lib/pubid/un/renderer.rb +11 -0
  452. data/lib/pubid/un.rb +46 -0
  453. data/lib/pubid/version.rb +1 -1
  454. data/lib/pubid/w3c/builder.rb +55 -0
  455. data/lib/pubid/w3c/identifier.rb +101 -0
  456. data/lib/pubid/w3c/identifiers/candidate_recommendation.rb +31 -0
  457. data/lib/pubid/w3c/identifiers/candidate_recommendation_draft.rb +31 -0
  458. data/lib/pubid/w3c/identifiers/draft_note.rb +31 -0
  459. data/lib/pubid/w3c/identifiers/note.rb +30 -0
  460. data/lib/pubid/w3c/identifiers/obsolete_recommendation.rb +31 -0
  461. data/lib/pubid/w3c/identifiers/proposed_edited_recommendation.rb +31 -0
  462. data/lib/pubid/w3c/identifiers/proposed_recommendation.rb +31 -0
  463. data/lib/pubid/w3c/identifiers/recommendation.rb +31 -0
  464. data/lib/pubid/w3c/identifiers/standard.rb +12 -0
  465. data/lib/pubid/w3c/identifiers/superseded_recommendation.rb +31 -0
  466. data/lib/pubid/w3c/identifiers/working_draft.rb +30 -0
  467. data/lib/pubid/w3c/identifiers.rb +25 -0
  468. data/lib/pubid/w3c/parser.rb +43 -0
  469. data/lib/pubid/w3c/renderer.rb +35 -0
  470. data/lib/pubid/w3c/urn_generator.rb +18 -0
  471. data/lib/pubid/w3c/urn_parser.rb +36 -0
  472. data/lib/pubid/w3c.rb +67 -0
  473. data/lib/pubid/xsf/builder.rb +17 -0
  474. data/lib/pubid/xsf/identifier.rb +62 -0
  475. data/lib/pubid/xsf/identifiers/xep.rb +31 -0
  476. data/lib/pubid/xsf/identifiers.rb +9 -0
  477. data/lib/pubid/xsf/parser.rb +25 -0
  478. data/lib/pubid/xsf/renderer.rb +23 -0
  479. data/lib/pubid/xsf/urn_generator.rb +13 -0
  480. data/lib/pubid/xsf/urn_parser.rb +18 -0
  481. data/lib/pubid/xsf.rb +76 -0
  482. data/lib/pubid.rb +25 -1
  483. metadata +205 -4
  484. data/lib/pubid/cie/components/code.rb +0 -80
  485. data/lib/pubid/iala/identifiers/base.rb +0 -14
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Pubid
6
+ module Ogc
7
+ # Parslet grammar for OGC identifiers.
8
+ #
9
+ # Recognizes `<yy>-<nnn>` optionally followed by a revision suffix that
10
+ # begins with a letter (r/c/a/R…) and may carry a trailing letter
11
+ # (e.g. "r3a"). An optional leading "OGC" publisher token is accepted for
12
+ # leniency, though the canonical printed form omits it.
13
+ #
14
+ # Examples: "25-023", "24-032r1", "01-009a", "04-095c1", "OGC 24-032r1".
15
+ class Parser < Parslet::Parser
16
+ rule(:space) { match["\\s"].repeat(1) }
17
+ rule(:digits) { match["0-9"].repeat(1) }
18
+
19
+ # Optional leading publisher token, e.g. "OGC " — consumed, not captured.
20
+ rule(:publisher) { (str("OGC") >> space).maybe }
21
+
22
+ # Revision suffix: a run of letters/digits that starts after the numeric
23
+ # part (which greedily consumes all digits, so the suffix begins with a
24
+ # separator letter). Kept as a single token for the builder to normalize.
25
+ rule(:revision) { match["A-Za-z0-9"].repeat(1).as(:revision) }
26
+
27
+ rule(:identifier) do
28
+ publisher >>
29
+ digits.as(:year) >> str("-") >> digits.as(:number) >>
30
+ revision.maybe >> space.maybe
31
+ end
32
+
33
+ root(:identifier)
34
+
35
+ def self.parse(input)
36
+ new.parse(input)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ogc
5
+ # Human-readable renderer for OGC identifiers.
6
+ #
7
+ # Produces the canonical printed form, e.g.:
8
+ # "24-032r1"
9
+ # "01-009a"
10
+ # "OGC 24-032r1" (only when with_publisher: true)
11
+ #
12
+ # Registered as the `:human` format in the OGC format registry and invoked
13
+ # via `render(format: :human)`.
14
+ class Renderer < ::Pubid::Renderers::Base
15
+ PUBLISHER = "OGC"
16
+
17
+ def render(**_opts)
18
+ id = @id
19
+ result = +""
20
+ result << "#{PUBLISHER} " if with_publisher?(id)
21
+ result << "#{id.year}-#{id.number}"
22
+ result << id.revision.to_s if id.revision
23
+ result
24
+ end
25
+
26
+ private
27
+
28
+ def with_publisher?(id)
29
+ id.respond_to?(:with_publisher) && id.with_publisher == true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ogc
5
+ # Emits `urn:ogc:<year>:<number>[:<revision>]`, e.g. `urn:ogc:24:032:r1`.
6
+ class UrnGenerator < Pubid::UrnGenerator::Base
7
+ def generate
8
+ parts = ["urn", "ogc", identifier.year.to_s, identifier.number.to_s]
9
+ parts << identifier.revision.to_s if identifier.revision
10
+ parts.join(":")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ogc
5
+ # Parses OGC URNs back into identifiers.
6
+ #
7
+ # UrnGenerator emits: `urn:ogc:<year>:<number>[:<revision>]`.
8
+ #
9
+ # Examples:
10
+ # - urn:ogc:25:023 → 25-023
11
+ # - urn:ogc:24:032:r1 → 24-032r1
12
+ class UrnParser < Pubid::UrnParser::Base
13
+ def parse_urn(urn)
14
+ body = strip_namespace(urn)
15
+ year, number, revision = split_parts(body)
16
+ text = "#{year}-#{number}"
17
+ text += revision.to_s if revision
18
+ flavor_parse(text)
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/pubid/ogc.rb ADDED
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ogc
5
+ extend Pubid::PrefixesSupport
6
+
7
+ # OGC identifiers are digit-leading (`25-023`) and carry no publisher token
8
+ # in the printed form. Per the PREFIXES inclusion policy, bare numeric
9
+ # patterns are NOT listed (they would make cross-flavor routing ambiguous).
10
+ # Instead the sole entry is the publisher token "OGC", which relaton uses to
11
+ # route a reference to this flavor even though it is not literally present
12
+ # in the id string.
13
+ PREFIXES = ["OGC"].freeze
14
+
15
+ autoload :Builder, "#{__dir__}/ogc/builder"
16
+ autoload :Identifier, "#{__dir__}/ogc/identifier"
17
+ autoload :Identifiers, "#{__dir__}/ogc/identifiers"
18
+ autoload :Parser, "#{__dir__}/ogc/parser"
19
+ autoload :Renderer, "#{__dir__}/ogc/renderer"
20
+ autoload :UrnGenerator, "#{__dir__}/ogc/urn_generator"
21
+ autoload :UrnParser, "#{__dir__}/ogc/urn_parser"
22
+
23
+ # Parse an OGC identifier string
24
+ def self.parse(identifier)
25
+ Identifier.parse(identifier)
26
+ end
27
+
28
+ # Per-flavor format registry: inherits global formats, overrides :human
29
+ Identifier.format_registry = FormatRegistry.new(parent: Identifier.format_registry)
30
+ Identifier.format_registry.register(:human, renderer: Ogc::Renderer)
31
+
32
+ # Auto-discover all identifier types from the Identifiers namespace
33
+ # @return [Array<Class>] identifier classes that define a self.type Hash
34
+ def self.identifier_types
35
+ @identifier_types ||= Identifiers.constants
36
+ .filter_map { |c| begin; Identifiers.const_get(c); rescue NameError; nil; end }
37
+ .select { |c| c.is_a?(Class) && c.singleton_methods(false).include?(:type) }
38
+ .select { |c| c.type.is_a?(Hash) }
39
+ end
40
+
41
+ # Build typed stage index from identifier types
42
+ # @return [Array<Pubid::Components::TypedStage>] all typed stages
43
+ def self.all_typed_stages
44
+ @all_typed_stages ||= identifier_types.flat_map do |klass|
45
+ if klass.const_defined?(:TYPED_STAGES)
46
+ klass.const_get(:TYPED_STAGES)
47
+ else
48
+ []
49
+ end
50
+ end
51
+ end
52
+
53
+ # Lookup: type code -> identifier class
54
+ # @param code [String, Symbol] the type key to find
55
+ # @return [Class, nil] the matching identifier class
56
+ def self.locate_type(code)
57
+ identifier_types.find { |t| t.type[:key].to_s == code.to_s }
58
+ end
59
+
60
+ # Lookup: abbreviation -> typed stage
61
+ # @param abbr [String, Symbol] the abbreviation to find
62
+ # @return [Pubid::Components::TypedStage, nil] the matching typed stage
63
+ def self.locate_stage(abbr)
64
+ abbr_str = abbr.to_s.upcase
65
+ all_typed_stages.find { |s| s.abbr.any? { |a| a.to_s.upcase == abbr_str } }
66
+ end
67
+ end
68
+ end
69
+
70
+ # Register OGC flavor with the registry
71
+ Pubid::Registry.register(:ogc, Pubid::Ogc)
@@ -21,8 +21,8 @@ module Pubid
21
21
  return build_short_amendment(parsed_hash)
22
22
  end
23
23
 
24
- # Check for supplements first (have base_identifier)
25
- if parsed_hash[:base_identifier]
24
+ # Check for supplements first (have base)
25
+ if parsed_hash[:base]
26
26
  return build_supplement(parsed_hash)
27
27
  end
28
28
 
@@ -53,11 +53,11 @@ module Pubid
53
53
  end
54
54
 
55
55
  # Build the base document
56
- base_identifier = build_base_document(base_hash)
56
+ base = build_base_document(base_hash)
57
57
 
58
58
  # Create amendment
59
59
  amendment = Identifiers::Amendment.new
60
- amendment.base_identifier = base_identifier
60
+ amendment.base = base
61
61
 
62
62
  # Extract year from edition_format if present, otherwise from year directly
63
63
  year_value = if parsed_hash[:edition_format].is_a?(Hash)
@@ -97,8 +97,8 @@ module Pubid
97
97
  supplement.joined = true if plus_marker
98
98
 
99
99
  # Recursively parse base identifier
100
- if parsed_hash[:base_identifier]
101
- supplement.base_identifier = build(parsed_hash[:base_identifier])
100
+ if parsed_hash[:base]
101
+ supplement.base = build(parsed_hash[:base])
102
102
  end
103
103
 
104
104
  # Extract year from edition_format if present, otherwise from year directly
@@ -116,7 +116,7 @@ module Pubid
116
116
  # Annex with no year of its own but a dated base ("R 60:2017 Annexes"):
117
117
  # the year belongs to the base and must render glued to it.
118
118
  if supplement.is_a?(Identifiers::Annex) && !year_value &&
119
- supplement.base_identifier&.date
119
+ supplement.base&.date
120
120
  supplement.year_on_base = true
121
121
  end
122
122
 
@@ -214,6 +214,11 @@ module Pubid
214
214
  # Structured: :issue and :sequence captured directly as zero-padded
215
215
  # strings. Citation: :volume_roman, :issue_arabic, :article_id —
216
216
  # decode the 8-digit article_id to recover year+issue+sequence.
217
+ #
218
+ # For the citation form, the parsed roman volume is cross-checked
219
+ # against the year decoded from the article_id; a mismatch warns but
220
+ # does not raise (the article_id is the source of truth — the roman
221
+ # volume is redundant display).
217
222
  def apply_bulletin_locator(identifier, parsed_hash)
218
223
  if parsed_hash[:article_id]
219
224
  article_id = parsed_hash[:article_id].to_s
@@ -221,6 +226,7 @@ module Pubid
221
226
  identifier.date.year = article_id[0, 4]
222
227
  identifier.issue = article_id[4, 2]
223
228
  identifier.sequence = article_id[6, 2]
229
+ warn_on_volume_mismatch(identifier, parsed_hash[:volume_roman])
224
230
  return
225
231
  end
226
232
 
@@ -228,6 +234,24 @@ module Pubid
228
234
  identifier.sequence = parsed_hash[:sequence].to_s if parsed_hash[:sequence]
229
235
  end
230
236
 
237
+ # Citation form carries the volume in two places: spelled out as a
238
+ # roman numeral ("LXVII") and encoded in the 8-digit article_id
239
+ # (year+issue+sequence, where year - 1959 = volume). They should
240
+ # agree; if they don't, the input was malformed. Warn loudly so the
241
+ # user sees the discrepancy, but continue using the article_id
242
+ # (deterministic) rather than the roman (possibly mistyped).
243
+ def warn_on_volume_mismatch(identifier, parsed_volume_roman)
244
+ return unless parsed_volume_roman && identifier.date&.year
245
+
246
+ declared = parsed_volume_roman.to_s
247
+ computed = Identifiers::Bulletin.to_roman(identifier.date.year.to_i -
248
+ Identifiers::Bulletin::BASE_YEAR_OFFSET)
249
+ return if declared == computed
250
+
251
+ warn "OIML Bulletin citation volume mismatch: parsed '#{declared}' " \
252
+ "but article_id year #{identifier.date.year} implies '#{computed}'"
253
+ end
254
+
231
255
  def extract_language(lang_data)
232
256
  # Handle both direct string and nested hash from parser
233
257
  case lang_data
@@ -37,7 +37,7 @@ module Pubid
37
37
  # lutaml's polymorphic_map only validates `_type` on deserialization; it
38
38
  # does not re-instantiate the concrete subclass. Route by `_type` so
39
39
  # `Pubid::Oiml::Identifier.from_hash(h)` returns the right class (and its
40
- # nested base_identifier), mirroring Pubid::Iso::Identifier.from_hash.
40
+ # nested base), mirroring Pubid::Iso::Identifier.from_hash.
41
41
  def self.from_hash(data, options = {})
42
42
  type = data["_type"] || data[:_type]
43
43
  klass_name = OIML_TYPE_MAP[type]
@@ -48,6 +48,14 @@ module Pubid
48
48
  super
49
49
  end
50
50
 
51
+ # Uniform class-level parse entry, mirroring every other flavor's
52
+ # `Pubid::<Flavor>::Identifier.parse`. OIML's real logic lives in the
53
+ # module method, so delegate to it (same pattern as jcgm/nist/plateau).
54
+ # The `Pubid::MAX_INPUT_LENGTH` ReDoS guard lives in `Pubid::Oiml.parse`.
55
+ def self.parse(identifier)
56
+ Pubid::Oiml.parse(identifier)
57
+ end
58
+
51
59
  def to_urn
52
60
  UrnGenerator.new(self).generate
53
61
  end
@@ -20,7 +20,7 @@ module Pubid
20
20
  rule(:identifier) do
21
21
  amendment_identifier | amendment_short | annex_letter_identifier |
22
22
  annex_identifier | plus_supplement_identifier |
23
- trailing_supplement_identifier | bulletin_identifier | base_identifier
23
+ trailing_supplement_identifier | bulletin_identifier | base
24
24
  end
25
25
 
26
26
  # Publisher - always "OIML"
@@ -44,10 +44,9 @@ module Pubid
44
44
  rule(:two_digits) { match('\d').repeat(2, 2) }
45
45
 
46
46
  # Roman numeral token composed of I,V,X,L,C,D,M (uppercase, matching
47
- # OIML's print convention). Consumed but not captured — the 8-digit
48
- # article id in the same citation carries year/issue/sequence
49
- # deterministically, so the roman volume is redundant for parsing.
50
- rule(:roman_numeral) { match("[IVXLCDM]").repeat(1) }
47
+ # OIML's print convention). Captured so the builder can cross-check
48
+ # the declared volume against the year implied by the article_id.
49
+ rule(:roman_numeral) { match("[IVXLCDM]").repeat(1).as(:volume_roman) }
51
50
 
52
51
  # Bulletin locator — citation form. The format OIML prints on the
53
52
  # article page is: "LXVII(2) 20260211" where LXVII is the volume in
@@ -62,7 +61,7 @@ module Pubid
62
61
  # Bulletin identifier — no code; the locator (when present) is the
63
62
  # (year, issue, sequence) tuple drawn from either the structured
64
63
  # YYYY-II-SS form or the citation VOLUME(ISSUE) ARTID form. Both
65
- # decode to the same record. Tried before base_identifier because
64
+ # decode to the same record. Tried before base because
66
65
  # "Bulletin" is a word that the single-letter doc_type rule cannot
67
66
  # match.
68
67
  rule(:bulletin_identifier) do
@@ -188,7 +187,7 @@ module Pubid
188
187
  rule(:amendment_identifier) do
189
188
  str("Amendment") >> space >> lparen >> year_digits.as(:year) >> rparen >>
190
189
  space >> str("to") >> space >>
191
- base_without_language.as(:base_identifier) >>
190
+ base_without_language.as(:base) >>
192
191
  language_portion.maybe.as(:language)
193
192
  end
194
193
 
@@ -210,7 +209,7 @@ module Pubid
210
209
  # amendment_short is tried first; it only matches when a year follows the
211
210
  # word, so the no-year trailing form falls through to here.
212
211
  rule(:trailing_supplement_identifier) do
213
- base_without_language.as(:base_identifier) >>
212
+ base_without_language.as(:base) >>
214
213
  space >> (str("Amendment") | str("Errata")).as(:trailing_marker) >>
215
214
  language_portion.maybe.as(:language)
216
215
  end
@@ -220,7 +219,7 @@ module Pubid
220
219
  # and errata to dated bases (e.g. "OIML B 10:2011+Amendment:2012").
221
220
  # Annexes already encode the year-on-base intent via their own model.
222
221
  rule(:plus_supplement_identifier) do
223
- base_without_language.as(:base_identifier) >>
222
+ base_without_language.as(:base) >>
224
223
  str("+") >>
225
224
  (str("Amendment") | str("Errata")).as(:plus_marker) >>
226
225
  (colon >> year_digits.as(:year)).maybe >>
@@ -230,7 +229,7 @@ module Pubid
230
229
  # Annex identifier - "BASE Annexes Edition YYYY" / "BASE Annexes:YYYY" /
231
230
  # "BASE:YYYY Annexes" (year on the base, no annex year).
232
231
  rule(:annex_identifier) do
233
- base_without_language.as(:base_identifier) >>
232
+ base_without_language.as(:base) >>
234
233
  space >> str("Annexes").as(:annex_marker) >>
235
234
  (
236
235
  (space >> edition_text >> space >> year_digits.as(:year)).as(:edition_format) |
@@ -246,7 +245,7 @@ module Pubid
246
245
 
247
246
  # Annex with letter - "BASE Annex A Edition YYYY" / "BASE:YYYY Annex B-C"
248
247
  rule(:annex_letter_identifier) do
249
- base_without_language.as(:base_identifier) >>
248
+ base_without_language.as(:base) >>
250
249
  space >> str("Annex") >> space >> annex_letter_value >>
251
250
  ((space >> edition_text >> space >> year_digits.as(:year)) | (colon >> year_digits.as(:year))).maybe >>
252
251
  language_portion.maybe.as(:language)
@@ -262,7 +261,7 @@ module Pubid
262
261
  end
263
262
 
264
263
  # Base identifier for recursion and standalone parsing
265
- rule(:base_identifier) do
264
+ rule(:base) do
266
265
  publisher >>
267
266
  doc_type >>
268
267
  full_number >>
@@ -138,7 +138,7 @@ module Pubid
138
138
  # Plus-joined: "BASE+Amendment:YEAR" / "BASE+Errata:YEAR" with both
139
139
  # the base and the supplement carrying their own year.
140
140
  if id.joined
141
- base_str = strip_language(id.base_identifier.to_s)
141
+ base_str = strip_language(id.base.to_s)
142
142
  result = "#{base_str}+#{id.supplement_type}"
143
143
  result += ":#{id.year}" if id.year
144
144
  result += " (#{id.language})" if id.language
@@ -149,7 +149,7 @@ module Pubid
149
149
  # publication year kept on the base identifier. The word comes from the
150
150
  # concrete supplement class.
151
151
  if id.trailing
152
- base_str = strip_language(id.base_identifier.to_s)
152
+ base_str = strip_language(id.base.to_s)
153
153
  result = "#{base_str} #{id.supplement_type}"
154
154
  result += " (#{id.language})" if id.language
155
155
  return result
@@ -157,16 +157,16 @@ module Pubid
157
157
 
158
158
  base_format = if format && format != :short
159
159
  format
160
- elsif id.base_identifier.class.attributes.key?(:parsed_format) && id.base_identifier.parsed_format == "long"
160
+ elsif id.base.class.attributes.key?(:parsed_format) && id.base.parsed_format == "long"
161
161
  :long
162
162
  else
163
163
  :short
164
164
  end
165
165
 
166
- base_str = if id.base_identifier.is_a?(SingleIdentifier)
167
- id.base_identifier.to_s(format: base_format)
166
+ base_str = if id.base.is_a?(SingleIdentifier)
167
+ id.base.to_s(format: base_format)
168
168
  else
169
- id.base_identifier.to_s
169
+ id.base.to_s
170
170
  end
171
171
  base_str = strip_language(base_str)
172
172
 
@@ -182,14 +182,14 @@ module Pubid
182
182
  # "BASE:YYYY Annex(es)" — the year is glued to the base, the marker
183
183
  # carries none. Keep the base date instead of stripping it.
184
184
  if id.year_on_base
185
- base_str = strip_language(id.base_identifier.to_s)
185
+ base_str = strip_language(id.base.to_s)
186
186
  marker = id.letter ? "Annex #{id.letter}" : "Annexes"
187
187
  result = "#{base_str} #{marker}"
188
188
  result += " (#{id.language})" if id.language
189
189
  return result
190
190
  end
191
191
 
192
- base_format = if id.base_identifier.class.attributes.key?(:parsed_format) && id.base_identifier.parsed_format == "long"
192
+ base_format = if id.base.class.attributes.key?(:parsed_format) && id.base.parsed_format == "long"
193
193
  :long
194
194
  else
195
195
  :short
@@ -203,10 +203,10 @@ module Pubid
203
203
  :short
204
204
  end
205
205
 
206
- base_str = if id.base_identifier.is_a?(SingleIdentifier)
207
- id.base_identifier.to_s(format: base_format)
206
+ base_str = if id.base.is_a?(SingleIdentifier)
207
+ id.base.to_s(format: base_format)
208
208
  else
209
- id.base_identifier.to_s
209
+ id.base.to_s
210
210
  end
211
211
  result = base_str.sub(/:.*/, "").sub(/\s+Edition\s+\d{4}/, "").sub(
212
212
  /\(.*\)/, ""
@@ -219,8 +219,8 @@ module Pubid
219
219
  result += " Annexes"
220
220
  if id.year
221
221
  result += annex_format == :long ? " Edition #{id.year}" : ":#{id.year}"
222
- elsif id.base_identifier.date
223
- result += annex_format == :long ? " Edition #{id.base_identifier.date.render(context: @context)}" : ":#{id.base_identifier.date.render(context: @context)}"
222
+ elsif id.base.date
223
+ result += annex_format == :long ? " Edition #{id.base.date.render(context: @context)}" : ":#{id.base.date.render(context: @context)}"
224
224
  end
225
225
  end
226
226
 
@@ -103,6 +103,26 @@ module Pubid
103
103
  raise NotImplementedError, "Subclasses must implement type_string"
104
104
  end
105
105
 
106
+ # OIML keeps identity in `code` (Components::Code) and `type_string`
107
+ # (e.g. "R", "V", "D"), not in the inherited `number`/`typed_stage` —
108
+ # the generic MrString renderer would otherwise drop both and produce
109
+ # `OIML.<year>`. Losslessness for issue #142 requires the type letter
110
+ # and document number to appear in MR.
111
+ def mr_number_with_part
112
+ segments = []
113
+ segments << code&.number&.to_s if code&.number
114
+ segments << code&.part&.to_s if code&.part
115
+ segments << code&.subpart&.to_s if code&.subpart
116
+ segments << code&.suffix&.to_s if code&.suffix
117
+ return nil if segments.empty?
118
+
119
+ segments.join("-")
120
+ end
121
+
122
+ def mr_type
123
+ type_string&.downcase
124
+ end
125
+
106
126
  # Subclasses override this
107
127
  end
108
128
  end
@@ -5,7 +5,7 @@ module Pubid
5
5
  class SupplementIdentifier < Identifier
6
6
  # Base class for OIML supplements (amendments, annexes)
7
7
  # These wrap a base identifier like ISO amendments
8
- attribute :base_identifier, Oiml::Identifier, polymorphic: true
8
+ attribute :base, Oiml::Identifier, polymorphic: true
9
9
  attribute :year, :string
10
10
  attribute :language, :string
11
11
  # True for the trailing-word shorthand ("OIML R 138:2009 Amendment"),
@@ -21,28 +21,28 @@ module Pubid
21
21
  } # Track supplement's parsed format
22
22
 
23
23
  # Serialization delta on top of Oiml::Identifier's shared block. The
24
- # nested base_identifier is (de)serialized recursively through the
24
+ # nested base is (de)serialized recursively through the
25
25
  # polymorphic router so its own `_type` selects the right subclass.
26
26
  key_value do
27
- map "base_identifier",
28
- with: { to: :base_identifier_to_kv, from: :base_identifier_from_kv }
27
+ map "base",
28
+ with: { to: :base_to_kv, from: :base_from_kv }
29
29
  map "year", to: :year
30
30
  map "trailing", to: :trailing
31
31
  map "joined", to: :joined
32
32
  end
33
33
 
34
- def base_identifier_to_kv(model, doc)
35
- base = model.base_identifier
34
+ def base_to_kv(model, doc)
35
+ base = model.base
36
36
  return unless base
37
37
 
38
38
  doc.add_child(
39
- Lutaml::KeyValue::DataModel::Element.new("base_identifier",
39
+ Lutaml::KeyValue::DataModel::Element.new("base",
40
40
  base.to_hash),
41
41
  )
42
42
  end
43
43
 
44
- def base_identifier_from_kv(model, value)
45
- model.base_identifier = ::Pubid::Oiml::Identifier.from_hash(value) if value
44
+ def base_from_kv(model, value)
45
+ model.base = ::Pubid::Oiml::Identifier.from_hash(value) if value
46
46
  end
47
47
 
48
48
  attr_reader :requested_format
@@ -4,18 +4,35 @@ module Pubid
4
4
  module Oiml
5
5
  # Parses OIML URNs back into identifiers.
6
6
  #
7
- # UrnGenerator emits: `urn:oiml:{type}:{number}[-{part}]` where type
8
- # is the lowercase document class (r for Recommendation, d for Document).
7
+ # UrnGenerator emits: `urn:oiml:{type}:{locator}` where type is the
8
+ # lowercase document class — single letter for typed documents
9
+ # (r, d, b, g, …) or the word "bulletin" for Bulletin issues. The
10
+ # locator is the number-part-subpart for typed documents and the
11
+ # YYYY-II-SS tuple for Bulletins.
9
12
  #
10
13
  # Examples:
11
- # - urn:oiml:r:111-1 → OIML R 111-1
12
- # - urn:oiml:d:1 → OIML D 1
14
+ # - urn:oiml:r:111-1 → OIML R 111-1
15
+ # - urn:oiml:d:1 → OIML D 1
16
+ # - urn:oiml:bulletin:2026-02-11 → OIML Bulletin 2026-02-11
13
17
  class UrnParser < Pubid::UrnParser::Base
14
18
  def parse_urn(urn)
15
19
  body = strip_namespace(urn)
16
20
  parts = split_parts(body)
17
- type_token, number = parts
18
- flavor_parse("OIML #{type_token.upcase} #{number}")
21
+ type_token = parts.fetch(0)
22
+ number = parts[1]
23
+
24
+ text = "OIML #{display_type(type_token)}"
25
+ text += " #{number}" if number
26
+ flavor_parse(text)
27
+ end
28
+
29
+ private
30
+
31
+ # URN type tokens are lowercase. Typed documents use a single letter
32
+ # that maps cleanly via upcase; Bulletin is the multi-letter word
33
+ # that the human parser expects capitalized.
34
+ def display_type(token)
35
+ token.downcase == "bulletin" ? "Bulletin" : token.upcase
19
36
  end
20
37
  end
21
38
  end
data/lib/pubid/oiml.rb CHANGED
@@ -19,6 +19,10 @@ module Pubid
19
19
  autoload :UrnParser, "#{__dir__}/oiml/urn_parser"
20
20
 
21
21
  def self.parse(identifier)
22
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
23
+ raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
24
+ end
25
+
22
26
  parser = Parser.new
23
27
  builder = Builder.new
24
28
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Omg
5
+ # Builds a Pubid::Omg::Identifier from a parse tree.
6
+ class Builder
7
+ def self.build(parsed_data)
8
+ new.build(parsed_data)
9
+ end
10
+
11
+ def build(data)
12
+ Identifiers::Specification.new(
13
+ acronym: data[:acronym].to_s,
14
+ version: data[:version]&.to_s,
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Omg
5
+ # Base class for every OMG identifier AND the flavor's parse/create
6
+ # entry point.
7
+ class Identifier < ::Pubid::Identifier
8
+ # The spec family acronym, e.g. "AMI4CCM", "UML", "CORBA".
9
+ attribute :acronym, :string
10
+
11
+ # Free-form version string. Stored verbatim because OMG versioning is
12
+ # loose ("1.0", "2.5.1", "5 beta 3", "1.1"). Use Version#to_s for
13
+ # rendering.
14
+ attribute :version, :string
15
+
16
+ OMG_TYPE_MAP = {
17
+ "pubid:omg:specification" => "Pubid::Omg::Identifiers::Specification",
18
+ }.freeze
19
+
20
+ key_value do
21
+ map "_type", to: :_type, polymorphic_map: OMG_TYPE_MAP
22
+ map "acronym", to: :acronym
23
+ map "version", to: :version
24
+ end
25
+
26
+ PUBLISHER = "OMG"
27
+
28
+ def to_s(**opts)
29
+ render(format: :human, **opts)
30
+ end
31
+
32
+ def self.parse(identifier)
33
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
34
+ raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
35
+ end
36
+
37
+ parsed = Parser.parse(identifier)
38
+ Builder.build(parsed)
39
+ rescue Parslet::ParseFailed => e
40
+ raise "Failed to parse OMG identifier '#{identifier}': #{e.message}"
41
+ end
42
+ end
43
+ end
44
+ end