pubid 2.0.0.pre.alpha.9 → 2.0.0.pre.alpha.11

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 (432) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +33 -11
  3. data/conformance/pending.yaml +4 -0
  4. data/lib/pubid/adobe/identifier.rb +1 -3
  5. data/lib/pubid/adobe/parser.rb +10 -2
  6. data/lib/pubid/adobe.rb +1 -0
  7. data/lib/pubid/amca/builder.rb +2 -2
  8. data/lib/pubid/amca/identifiers/base.rb +49 -27
  9. data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
  10. data/lib/pubid/amca/identifiers/publication.rb +13 -9
  11. data/lib/pubid/amca/identifiers/standard.rb +2 -0
  12. data/lib/pubid/amca/parser.rb +1 -1
  13. data/lib/pubid/amca/renderer.rb +4 -4
  14. data/lib/pubid/amca/urn_generator.rb +2 -2
  15. data/lib/pubid/amca.rb +2 -1
  16. data/lib/pubid/ansi/builder.rb +2 -2
  17. data/lib/pubid/ansi/identifier.rb +18 -0
  18. data/lib/pubid/ansi/parser.rb +1 -1
  19. data/lib/pubid/ansi/renderer.rb +2 -2
  20. data/lib/pubid/ansi.rb +5 -5
  21. data/lib/pubid/api/builder.rb +16 -2
  22. data/lib/pubid/api/identifier.rb +25 -4
  23. data/lib/pubid/api/identifiers/mpms.rb +10 -8
  24. data/lib/pubid/api/parser.rb +1 -1
  25. data/lib/pubid/api/renderer.rb +16 -7
  26. data/lib/pubid/api/single_identifier.rb +5 -12
  27. data/lib/pubid/api.rb +1 -0
  28. data/lib/pubid/ashrae/builder.rb +31 -10
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
  31. data/lib/pubid/ashrae/identifiers/base.rb +81 -5
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
  33. data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
  34. data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
  35. data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
  36. data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
  37. data/lib/pubid/ashrae/parser.rb +23 -8
  38. data/lib/pubid/ashrae/renderer.rb +6 -6
  39. data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
  40. data/lib/pubid/ashrae/urn_generator.rb +8 -2
  41. data/lib/pubid/ashrae.rb +7 -1
  42. data/lib/pubid/asme/builder.rb +11 -2
  43. data/lib/pubid/asme/identifier.rb +9 -0
  44. data/lib/pubid/asme/identifiers/standard.rb +86 -2
  45. data/lib/pubid/asme/parser.rb +1 -1
  46. data/lib/pubid/asme/renderer.rb +3 -3
  47. data/lib/pubid/asme/single_identifier.rb +8 -1
  48. data/lib/pubid/asme/urn_generator.rb +2 -2
  49. data/lib/pubid/asme.rb +1 -0
  50. data/lib/pubid/astm/builder.rb +10 -3
  51. data/lib/pubid/astm/identifier.rb +9 -0
  52. data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
  53. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  54. data/lib/pubid/astm/identifiers/data_series.rb +2 -0
  55. data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
  56. data/lib/pubid/astm/identifiers/manual.rb +10 -0
  57. data/lib/pubid/astm/identifiers/monograph.rb +2 -0
  58. data/lib/pubid/astm/identifiers/research_report.rb +10 -0
  59. data/lib/pubid/astm/identifiers/standard.rb +10 -0
  60. data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
  61. data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
  62. data/lib/pubid/astm/identifiers.rb +1 -0
  63. data/lib/pubid/astm/parser.rb +1 -1
  64. data/lib/pubid/astm/renderer.rb +1 -1
  65. data/lib/pubid/astm/single_identifier.rb +31 -1
  66. data/lib/pubid/astm/urn_generator.rb +7 -5
  67. data/lib/pubid/astm.rb +1 -0
  68. data/lib/pubid/bipm/builder.rb +66 -10
  69. data/lib/pubid/bipm/identifier.rb +134 -9
  70. data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
  71. data/lib/pubid/bipm/identifiers/guide.rb +11 -0
  72. data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
  73. data/lib/pubid/bipm/identifiers/mep.rb +12 -0
  74. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  75. data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
  76. data/lib/pubid/bipm/parser.rb +65 -12
  77. data/lib/pubid/bipm/renderer.rb +8 -0
  78. data/lib/pubid/bipm/urn_parser.rb +11 -3
  79. data/lib/pubid/bipm.rb +7 -1
  80. data/lib/pubid/bsi/builder.rb +48 -38
  81. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  82. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  83. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +0 -1
  84. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  85. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  86. data/lib/pubid/bsi/identifiers/handbook.rb +0 -2
  87. data/lib/pubid/bsi/identifiers/practice_guide.rb +0 -1
  88. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  89. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  90. data/lib/pubid/bsi/parser.rb +1 -1
  91. data/lib/pubid/bsi/renderer.rb +63 -51
  92. data/lib/pubid/bsi/single_identifier.rb +16 -7
  93. data/lib/pubid/bsi/urn_generator.rb +11 -2
  94. data/lib/pubid/bsi.rb +7 -1
  95. data/lib/pubid/builder/base.rb +18 -8
  96. data/lib/pubid/bundled_identifier.rb +16 -6
  97. data/lib/pubid/calconnect/identifier.rb +9 -5
  98. data/lib/pubid/calconnect/parser.rb +1 -1
  99. data/lib/pubid/calconnect.rb +7 -1
  100. data/lib/pubid/ccsds/identifier.rb +9 -2
  101. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  102. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  103. data/lib/pubid/ccsds/parser.rb +1 -1
  104. data/lib/pubid/ccsds/single_identifier.rb +2 -2
  105. data/lib/pubid/ccsds.rb +1 -0
  106. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  107. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  108. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  109. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  110. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  111. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  112. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  115. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  116. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  117. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  118. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  119. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  120. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  123. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  124. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  125. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  126. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  127. data/lib/pubid/cen_cenelec.rb +28 -10
  128. data/lib/pubid/cie/builder.rb +10 -0
  129. data/lib/pubid/cie/identifier.rb +6 -1
  130. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  131. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  132. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  133. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  134. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  135. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  136. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  137. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  138. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  139. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  140. data/lib/pubid/cie/parser.rb +1 -1
  141. data/lib/pubid/cie.rb +7 -1
  142. data/lib/pubid/conformance/checks.rb +72 -0
  143. data/lib/pubid/conformance/corpus/case.rb +55 -0
  144. data/lib/pubid/conformance/corpus.rb +36 -0
  145. data/lib/pubid/conformance/generator.rb +212 -0
  146. data/lib/pubid/conformance/pending.rb +63 -0
  147. data/lib/pubid/conformance/runner.rb +153 -0
  148. data/lib/pubid/conformance.rb +55 -0
  149. data/lib/pubid/core.rb +2 -0
  150. data/lib/pubid/csa/builder.rb +47 -47
  151. data/lib/pubid/csa/composite_identifier.rb +14 -19
  152. data/lib/pubid/csa/identifier.rb +143 -81
  153. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  154. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  155. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  156. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  157. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  158. data/lib/pubid/csa/identifiers/package.rb +2 -2
  159. data/lib/pubid/csa/parser.rb +6 -2
  160. data/lib/pubid/csa/renderer.rb +13 -5
  161. data/lib/pubid/csa/single_identifier.rb +93 -4
  162. data/lib/pubid/csa/urn_generator.rb +9 -2
  163. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  164. data/lib/pubid/csa.rb +7 -1
  165. data/lib/pubid/doi/identifier.rb +6 -3
  166. data/lib/pubid/doi/parser.rb +1 -1
  167. data/lib/pubid/doi.rb +1 -0
  168. data/lib/pubid/easc/identifier.rb +0 -2
  169. data/lib/pubid/easc/parser.rb +10 -2
  170. data/lib/pubid/easc.rb +1 -0
  171. data/lib/pubid/ecma/builder.rb +23 -9
  172. data/lib/pubid/ecma/identifier.rb +81 -10
  173. data/lib/pubid/ecma/parser.rb +39 -4
  174. data/lib/pubid/ecma/renderer.rb +29 -8
  175. data/lib/pubid/ecma/urn_generator.rb +34 -13
  176. data/lib/pubid/ecma/urn_parser.rb +30 -14
  177. data/lib/pubid/ecma.rb +1 -0
  178. data/lib/pubid/errors.rb +80 -0
  179. data/lib/pubid/etsi/builder.rb +6 -1
  180. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  181. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  182. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  183. data/lib/pubid/etsi/parser.rb +1 -1
  184. data/lib/pubid/etsi.rb +1 -0
  185. data/lib/pubid/evs/builder.rb +26 -0
  186. data/lib/pubid/evs/identifier.rb +37 -0
  187. data/lib/pubid/evs/identifiers/national_adoption.rb +17 -0
  188. data/lib/pubid/evs/identifiers.rb +9 -0
  189. data/lib/pubid/evs/parser.rb +27 -0
  190. data/lib/pubid/evs/renderer.rb +30 -0
  191. data/lib/pubid/evs/urn_generator.rb +30 -0
  192. data/lib/pubid/evs/urn_parser.rb +80 -0
  193. data/lib/pubid/evs.rb +90 -0
  194. data/lib/pubid/export.rb +1 -0
  195. data/lib/pubid/format_detector.rb +1 -0
  196. data/lib/pubid/gb/identifier.rb +6 -3
  197. data/lib/pubid/gb/parser.rb +1 -1
  198. data/lib/pubid/gb.rb +1 -0
  199. data/lib/pubid/gost/builder.rb +46 -9
  200. data/lib/pubid/gost/identifier.rb +0 -2
  201. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  202. data/lib/pubid/gost/parser.rb +10 -2
  203. data/lib/pubid/gost.rb +1 -0
  204. data/lib/pubid/iala/identifier.rb +9 -6
  205. data/lib/pubid/iala/parser.rb +10 -2
  206. data/lib/pubid/iala/urn_parser.rb +6 -2
  207. data/lib/pubid/iala.rb +1 -0
  208. data/lib/pubid/iana/builder.rb +3 -1
  209. data/lib/pubid/iana/identifier.rb +74 -6
  210. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  211. data/lib/pubid/iana/parser.rb +1 -1
  212. data/lib/pubid/iana/renderer.rb +3 -0
  213. data/lib/pubid/iana/urn_generator.rb +5 -1
  214. data/lib/pubid/iana.rb +1 -0
  215. data/lib/pubid/identifier.rb +594 -10
  216. data/lib/pubid/identifier_metadata.rb +1 -0
  217. data/lib/pubid/idf/builder.rb +3 -3
  218. data/lib/pubid/idf/identifier.rb +18 -9
  219. data/lib/pubid/idf/parser.rb +1 -1
  220. data/lib/pubid/idf/renderer.rb +4 -4
  221. data/lib/pubid/idf/urn_generator.rb +1 -1
  222. data/lib/pubid/idf.rb +7 -1
  223. data/lib/pubid/iec/builder.rb +14 -7
  224. data/lib/pubid/iec/components.rb +0 -1
  225. data/lib/pubid/iec/identifier.rb +73 -83
  226. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  227. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  228. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  229. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  230. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  231. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  232. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  233. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  234. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  235. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  236. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  237. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  238. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  239. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  240. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  241. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  242. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  243. data/lib/pubid/iec/parser.rb +2 -18
  244. data/lib/pubid/iec/renderer.rb +13 -10
  245. data/lib/pubid/iec/single_identifier.rb +33 -10
  246. data/lib/pubid/iec/urn_generator.rb +227 -61
  247. data/lib/pubid/iec/urn_parser.rb +42 -4
  248. data/lib/pubid/iec.rb +1 -0
  249. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  250. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  251. data/lib/pubid/ieee/builder.rb +45 -1
  252. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  253. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  254. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  255. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  256. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  257. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  258. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  259. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  260. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  261. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  262. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  263. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  264. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  265. data/lib/pubid/ieee/ire/parser.rb +1 -1
  266. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  267. data/lib/pubid/ieee/parser.rb +1 -1
  268. data/lib/pubid/ieee/renderer.rb +5 -1
  269. data/lib/pubid/ieee/urn_generator.rb +32 -3
  270. data/lib/pubid/ieee.rb +7 -1
  271. data/lib/pubid/ieee_debug.rb +1 -0
  272. data/lib/pubid/ietf/builder.rb +24 -8
  273. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  274. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  275. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  276. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  277. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  278. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  279. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  280. data/lib/pubid/ietf/identifiers.rb +1 -0
  281. data/lib/pubid/ietf/parser.rb +27 -9
  282. data/lib/pubid/ietf/renderer.rb +44 -6
  283. data/lib/pubid/ietf/urn_generator.rb +20 -6
  284. data/lib/pubid/ietf/urn_parser.rb +10 -2
  285. data/lib/pubid/ietf.rb +12 -2
  286. data/lib/pubid/iho/identifiers/base.rb +10 -3
  287. data/lib/pubid/iho/parser.rb +1 -1
  288. data/lib/pubid/iho.rb +1 -0
  289. data/lib/pubid/isbn/identifier.rb +25 -7
  290. data/lib/pubid/isbn/parser.rb +1 -1
  291. data/lib/pubid/isbn.rb +1 -0
  292. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  293. data/lib/pubid/iso/combined_identifier.rb +5 -3
  294. data/lib/pubid/iso/identifier.rb +12 -7
  295. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  296. data/lib/pubid/iso/parser.rb +1 -1
  297. data/lib/pubid/iso.rb +7 -1
  298. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  299. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  300. data/lib/pubid/itu/identifiers/base.rb +19 -5
  301. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  302. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  303. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  304. data/lib/pubid/itu/parser.rb +1 -1
  305. data/lib/pubid/itu.rb +1 -0
  306. data/lib/pubid/jcgm/builder.rb +1 -1
  307. data/lib/pubid/jcgm/identifier.rb +10 -0
  308. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  309. data/lib/pubid/jcgm/parser.rb +10 -3
  310. data/lib/pubid/jcgm/renderer.rb +10 -3
  311. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  312. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  313. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  314. data/lib/pubid/jcgm.rb +10 -0
  315. data/lib/pubid/jis/identifier.rb +10 -3
  316. data/lib/pubid/jis/parser.rb +1 -1
  317. data/lib/pubid/jis.rb +1 -0
  318. data/lib/pubid/nist/components/stage.rb +2 -2
  319. data/lib/pubid/nist/configuration.rb +6 -8
  320. data/lib/pubid/nist/identifiers/base.rb +12 -2
  321. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  322. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  323. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  324. data/lib/pubid/nist/identifiers/crpl_report.rb +7 -1
  325. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  326. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  327. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +14 -6
  328. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  329. data/lib/pubid/nist/identifiers/report.rb +14 -6
  330. data/lib/pubid/nist/parser.rb +1 -1
  331. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  332. data/lib/pubid/nist.rb +7 -1
  333. data/lib/pubid/oasis/builder.rb +12 -10
  334. data/lib/pubid/oasis/identifier.rb +89 -11
  335. data/lib/pubid/oasis/parser.rb +1 -1
  336. data/lib/pubid/oasis.rb +7 -1
  337. data/lib/pubid/ogc/identifier.rb +9 -5
  338. data/lib/pubid/ogc/parser.rb +1 -1
  339. data/lib/pubid/ogc.rb +1 -0
  340. data/lib/pubid/oiml/builder.rb +5 -1
  341. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  342. data/lib/pubid/oiml/identifiers/bulletin.rb +36 -0
  343. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  344. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  345. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  346. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  347. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  348. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  349. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  350. data/lib/pubid/oiml/identifiers.rb +1 -0
  351. data/lib/pubid/oiml/parser.rb +1 -1
  352. data/lib/pubid/oiml/single_identifier.rb +26 -42
  353. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  354. data/lib/pubid/oiml.rb +7 -1
  355. data/lib/pubid/omg/builder.rb +1 -0
  356. data/lib/pubid/omg/identifier.rb +20 -3
  357. data/lib/pubid/omg/parser.rb +64 -11
  358. data/lib/pubid/omg/renderer.rb +13 -1
  359. data/lib/pubid/omg.rb +2 -1
  360. data/lib/pubid/parser/grammar.rb +74 -0
  361. data/lib/pubid/parser.rb +2 -0
  362. data/lib/pubid/parsers/mr_string.rb +19 -0
  363. data/lib/pubid/plateau/parser.rb +1 -1
  364. data/lib/pubid/plateau.rb +10 -0
  365. data/lib/pubid/prefixes_support.rb +1 -0
  366. data/lib/pubid/renderers/annotator.rb +233 -0
  367. data/lib/pubid/renderers/base.rb +13 -0
  368. data/lib/pubid/renderers/directives_renderer.rb +3 -3
  369. data/lib/pubid/renderers/human_readable.rb +3 -3
  370. data/lib/pubid/renderers/mr_string.rb +15 -9
  371. data/lib/pubid/renderers.rb +1 -0
  372. data/lib/pubid/rendering/numbering.rb +25 -7
  373. data/lib/pubid/rendering.rb +1 -0
  374. data/lib/pubid/sae/identifiers/base.rb +9 -2
  375. data/lib/pubid/sae/parser.rb +1 -1
  376. data/lib/pubid/sae.rb +1 -0
  377. data/lib/pubid/schema/declaration.rb +33 -0
  378. data/lib/pubid/schema/error.rb +14 -0
  379. data/lib/pubid/schema/identifier_type.rb +23 -0
  380. data/lib/pubid/schema/loader.rb +117 -0
  381. data/lib/pubid/schema/typed_stage.rb +24 -0
  382. data/lib/pubid/schema.rb +25 -0
  383. data/lib/pubid/tgpp/builder.rb +1 -1
  384. data/lib/pubid/tgpp/identifier.rb +21 -5
  385. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  386. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  387. data/lib/pubid/tgpp/parser.rb +22 -6
  388. data/lib/pubid/tgpp/renderer.rb +21 -5
  389. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  390. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  391. data/lib/pubid/tgpp.rb +7 -1
  392. data/lib/pubid/type_resolver.rb +16 -1
  393. data/lib/pubid/un/identifier.rb +6 -3
  394. data/lib/pubid/un/parser.rb +1 -1
  395. data/lib/pubid/un.rb +1 -0
  396. data/lib/pubid/urn_parser/errors.rb +8 -1
  397. data/lib/pubid/urn_parser.rb +1 -0
  398. data/lib/pubid/utils.rb +1 -0
  399. data/lib/pubid/version.rb +1 -1
  400. data/lib/pubid/w3c/builder.rb +5 -5
  401. data/lib/pubid/w3c/identifier.rb +38 -9
  402. data/lib/pubid/w3c/parser.rb +1 -1
  403. data/lib/pubid/w3c/renderer.rb +1 -1
  404. data/lib/pubid/w3c/urn_generator.rb +2 -2
  405. data/lib/pubid/w3c.rb +1 -0
  406. data/lib/pubid/xsf/identifier.rb +9 -5
  407. data/lib/pubid/xsf/parser.rb +17 -4
  408. data/lib/pubid/xsf.rb +7 -1
  409. data/lib/pubid.rb +399 -21
  410. data/lib/tasks/conformance.rake +34 -0
  411. data/lib/tasks/docs.rake +3 -3
  412. data/lib/tasks/schema.rake +88 -0
  413. data/schema/core/joint_prefixes.yaml +24 -0
  414. data/schema/iec.yaml +1017 -0
  415. data/schema/iso.yaml +1586 -0
  416. data/schema/schema.schema.yaml +96 -0
  417. metadata +38 -17
  418. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  419. data/archived-gems/pubid-iec/stages.yaml +0 -129
  420. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  421. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  422. data/archived-gems/pubid-iso/stages.yaml +0 -106
  423. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  424. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  425. data/archived-gems/pubid-itu/series.yaml +0 -42
  426. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  427. data/archived-gems/pubid-nist/series.yaml +0 -121
  428. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  429. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  430. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  431. data/lib/pubid/iec/components/code.rb +0 -36
  432. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -4,15 +4,17 @@ module Pubid
4
4
  module Oasis
5
5
  # Turns the parse tree into an `Identifiers::Standard`: stores the slug
6
6
  # verbatim in `original`, then performs a best-effort, ORDER-INDEPENDENT
7
- # decomposition into spec / version / stage / part / label.
7
+ # decomposition into number / version / stage / part / label.
8
8
  #
9
9
  # Decomposition splits `original` on "-" and classifies each *whole*
10
10
  # fragment (so a stage-like substring inside a spec name — e.g. "CSDL" — is
11
- # never mistaken for a stage). `spec` is the run of leading name fragments
12
- # before the first recognized fragment; `version`/`stage`/`part` are the
13
- # first fragment of each recognized kind; `label` is any name fragment
14
- # appearing after the first recognized one. Anything the classifier cannot
15
- # place stays only in `original`, so the printed form always round-trips.
11
+ # never mistaken for a stage). `number` is the specification name: the run
12
+ # of leading name fragments before the first recognized fragment, and the
13
+ # key relaton-index sorts and binary-searches on. `version`/`stage`/`part`
14
+ # are the first fragment of each recognized kind; `label` is any name
15
+ # fragment appearing after the first recognized one. Anything the
16
+ # classifier cannot place stays only in `original`, so the printed form
17
+ # always round-trips.
16
18
  class Builder
17
19
  # A fragment is a version when it is `v?N(.N)+` (e.g. "3.0", "v1.2.1",
18
20
  # "V1.0"). Bare integers are deliberately NOT versions (too ambiguous
@@ -36,16 +38,16 @@ module Pubid
36
38
 
37
39
  private
38
40
 
39
- # @return [Hash] best-effort { spec:, version:, stage:, part:, label: }.
40
- # Each fragment is classified once into a [fragment, kind] pair. `spec` is
41
- # the leading run of name fragments; `label` is any name fragment after
41
+ # @return [Hash] best-effort { number:, version:, stage:, part:, label: }.
42
+ # Each fragment is classified once into a [fragment, kind] pair. `number`
43
+ # is the leading run of name fragments; `label` is any name fragment after
42
44
  # the first recognized one; version/stage/part are the first of each kind.
43
45
  def decompose(original)
44
46
  pairs = original.split("-").map { |f| [f, classify(f)] }
45
47
  lead = pairs.take_while { |_, kind| kind.nil? }
46
48
  rest = pairs.drop(lead.length)
47
49
  {
48
- spec: names(lead),
50
+ number: names(lead),
49
51
  version: first_of(rest, :version),
50
52
  stage: first_of(rest, :stage),
51
53
  part: first_of(rest, :part),
@@ -16,15 +16,27 @@ module Pubid
16
16
  # The exact slug printed after "OASIS " — always set. Drives `to_s`, so
17
17
  # the printed form round-trips verbatim regardless of decomposition.
18
18
  attribute :original, :string
19
- # Best-effort decomposition (any may be nil). `spec`/`label` are plain
19
+ # Best-effort decomposition (any may be nil). `number`/`label` are plain
20
20
  # slug fragments; `version`/`stage`/`part` carry the recognized fragment
21
21
  # verbatim (e.g. "3.0", "v1.2.1", "PS01", "Errata01", "Pt8", "Part1").
22
- attribute :spec, :string
22
+ #
23
+ # `number` holds the specification name ("OSLC-CM", "STIX", "amqp-core").
24
+ # It is the key relaton-index sorts and binary-searches on
25
+ # (`id.root.number.to_s`), and it deliberately CLUSTERS: every version,
26
+ # stage and part of one specification shares it, the same shape as an
27
+ # IETF draft slug or an IANA registry slug.
28
+ #
29
+ # `number`/`stage`/`part` intentionally override the inherited
30
+ # Components::Code / Components::Stage attributes with plain strings (the
31
+ # mechanism JIS uses for `number` and W3C for `date`): OASIS names,
32
+ # stages and parts are opaque tokens that must round-trip exactly and are
33
+ # never re-parsed as structured parts. Redefining an inherited attribute
34
+ # is only deterministic under multi-flavor load because this class body
35
+ # lives in ONE file and is never reopened, so lutaml has finished
36
+ # building the table before `Identifiers::Standard` snapshots it — DO NOT
37
+ # split this class across two files.
38
+ attribute :number, :string
23
39
  attribute :version, :string
24
- # `stage`/`part` intentionally override the inherited Components::Stage /
25
- # Components::Code attributes with plain strings (same mechanism JIS uses
26
- # for `number`, W3C for `date`): OASIS stages/parts are opaque tokens that
27
- # must round-trip exactly and are never re-parsed as structured parts.
28
40
  attribute :stage, :string
29
41
  attribute :part, :string
30
42
  attribute :label, :string
@@ -39,7 +51,7 @@ module Pubid
39
51
  key_value do
40
52
  map "_type", to: :_type, polymorphic_map: OASIS_TYPE_MAP
41
53
  map "original", to: :original
42
- map "spec", to: :spec
54
+ map "number", to: :number
43
55
  map "version", to: :version
44
56
  map "stage", to: :stage
45
57
  map "part", to: :part
@@ -58,24 +70,90 @@ module Pubid
58
70
  render(format: :human, **opts)
59
71
  end
60
72
 
73
+ # The decomposition fields. Excluding any of them must also clear
74
+ # `original`, which still spells the excluded value out verbatim.
75
+ DECOMPOSITION_KEYS = %i[number version stage part label].freeze
76
+
77
+ # Widen a partial reference. `#matches?` is
78
+ # `exclude(*ignore) == other.exclude(*ignore)`, and `original` carries
79
+ # every component verbatim, so without this a bare "OASIS WSDM" could
80
+ # never match "OASIS WSDM-v1.1" however much the caller ignored.
81
+ #
82
+ # `original` is cleared ONLY when the exclusion touches the
83
+ # decomposition — the "reset the whole cluster" rule CSA applies to its
84
+ # year-format siblings. A plain `==` still compares `original`, so two
85
+ # slugs that decompose alike but print differently stay distinct: the
86
+ # decomposition loses fragment order and drops repeated fragments, so it
87
+ # is not by itself an identity.
88
+ def exclude(*args)
89
+ result = super
90
+ result.original = nil if args.intersect?(DECOMPOSITION_KEYS)
91
+ result
92
+ end
93
+
94
+ # MR string hooks. `to_slug` delegates to `to_mr_string` and consumers
95
+ # use it as an output FILENAME, so the slug must be unique per document.
96
+ #
97
+ # The publisher lives in the PUBLISHER constant rather than the inherited
98
+ # lutaml attribute, so the base `mr_publisher` returns nil (the BIPM and
99
+ # IANA case) and is supplied here.
100
+ def mr_publisher
101
+ PUBLISHER.downcase
102
+ end
103
+
104
+ # The whole verbatim slug, not the decomposed fields: decomposition loses
105
+ # fragment order and drops repeated fragments, so "x-1.0-os-Pt1" and
106
+ # "x-1.0-Pt1-os" would assemble to one slug. `original` cannot collide.
107
+ def mr_number_with_part
108
+ mr_sanitize(original)
109
+ end
110
+
61
111
  # from_hash is the shared polymorphic dispatch on Pubid::Identifier;
62
112
  # OASIS_TYPE_MAP remains as the key_value polymorphic_map.
63
113
 
64
114
  # Parse an OASIS identifier string into an identifier object
65
115
  # @param identifier [String] The OASIS identifier string to parse
66
116
  # @return [Identifier] The Standard identifier object
67
- # @raise [RuntimeError] If parsing fails
117
+ # @raise [Pubid::Errors::ParseError] If parsing fails
68
118
  def self.parse(identifier)
69
119
  # Reject pathological inputs before they reach the parser
70
120
  # (CodeQL rb/polynomial-redos barrier — inline .length by design).
121
+ unless identifier.is_a?(String)
122
+ raise Pubid::Errors::InvalidInputError,
123
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
124
+ end
125
+
71
126
  if identifier.length > Pubid::MAX_INPUT_LENGTH
72
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
127
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
73
128
  end
74
129
 
75
130
  parsed = Parser.parse(identifier)
76
131
  Builder.build(parsed)
77
- rescue Parslet::ParseFailed => e
78
- raise "Failed to parse OASIS identifier '#{identifier}': #{e.message}"
132
+ end
133
+
134
+ private
135
+
136
+ # Filter by CHARSET, not by an enumerated escape list, so a field added
137
+ # later cannot leak an unsafe character (the BIPM `mr_slug` precedent).
138
+ # Three characters need it today: the "." in every version —
139
+ # Renderers::MrString joins SEGMENTS with ".", so a dot inside one breaks
140
+ # the documented segment structure — plus the space and "]" of a few
141
+ # malformed records.
142
+ #
143
+ # A RUN of non-alphanumerics collapses to one "-", which is why "-" is
144
+ # outside the kept set (BIPM's form, not ETSI's): "v3.0]-PS01" would
145
+ # otherwise print a bare "--".
146
+ #
147
+ # Caveat, the same one BIPM records: "-" is both the intra-slug join and
148
+ # the substitute, so two slugs differing ONLY in which non-slug character
149
+ # they use collapse onto one MR string. The corpus has exactly one such
150
+ # pair, the malformed "OpenC2-MQTT-v1.0] -CS01" spelled once with a
151
+ # normal space and once with a non-breaking one — two spellings of one
152
+ # document. Every genuinely distinct identifier keeps its own slug.
153
+ def mr_sanitize(value)
154
+ slug = value.to_s.downcase.gsub(/[^a-z0-9]+/, "-")
155
+ .gsub(/\A-+|-+\z/, "")
156
+ slug.empty? ? nil : slug
79
157
  end
80
158
  end
81
159
  end
@@ -16,7 +16,7 @@ module Pubid
16
16
  # Builder with order-independent fragment classification. Keeping
17
17
  # decomposition out of the PEG avoids brittle backtracking and guarantees a
18
18
  # lossless round-trip.
19
- class Parser < Parslet::Parser
19
+ class Parser < ::Pubid::Parser::Grammar
20
20
  rule(:prefix) { str("OASIS") >> str(" ") }
21
21
 
22
22
  # The whole slug, captured verbatim (any character, incl. spaces / "]").
data/lib/pubid/oasis.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Oasis
5
6
  extend Pubid::PrefixesSupport
@@ -21,8 +22,13 @@ module Pubid
21
22
  def self.parse(identifier)
22
23
  # Reject pathological inputs before they reach the parser
23
24
  # (CodeQL rb/polynomial-redos barrier — inline .length by design).
25
+ unless identifier.is_a?(String)
26
+ raise Pubid::Errors::InvalidInputError,
27
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
28
+ end
29
+
24
30
  if identifier.length > Pubid::MAX_INPUT_LENGTH
25
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
31
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
26
32
  end
27
33
 
28
34
  Identifier.parse(identifier)
@@ -61,16 +61,20 @@ module Pubid
61
61
  # Parse an OGC identifier string into an identifier object.
62
62
  # @param identifier [String] The OGC identifier string to parse
63
63
  # @return [Identifier] The document identifier object
64
- # @raise [ArgumentError] If the input exceeds the maximum length
65
- # @raise [RuntimeError] If parsing fails
64
+ # @raise [Pubid::Errors::InvalidInputError]
65
+ # If the input exceeds the maximum length
66
+ # @raise [Pubid::Errors::ParseError] If parsing fails
66
67
  def self.parse(identifier)
68
+ unless identifier.is_a?(String)
69
+ raise Pubid::Errors::InvalidInputError,
70
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
71
+ end
72
+
67
73
  if identifier.length > Pubid::MAX_INPUT_LENGTH
68
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
74
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
69
75
  end
70
76
 
71
77
  Builder.build(Parser.parse(identifier))
72
- rescue Parslet::ParseFailed => e
73
- raise "Failed to parse OGC identifier '#{identifier}': #{e.message}"
74
78
  end
75
79
  end
76
80
  end
@@ -12,7 +12,7 @@ module Pubid
12
12
  # leniency, though the canonical printed form omits it.
13
13
  #
14
14
  # Examples: "25-023", "24-032r1", "01-009a", "04-095c1", "OGC 24-032r1".
15
- class Parser < Parslet::Parser
15
+ class Parser < ::Pubid::Parser::Grammar
16
16
  rule(:space) { match["\\s"].repeat(1) }
17
17
  rule(:digits) { match["0-9"].repeat(1) }
18
18
 
data/lib/pubid/ogc.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Ogc
5
6
  extend Pubid::PrefixesSupport
@@ -152,7 +152,11 @@ module Pubid
152
152
  code_attrs[:suffix] = parsed_hash[:code_suffix].to_s
153
153
  code_attrs[:space_suffix] = true if parsed_hash.key?(:space_suffix)
154
154
  end
155
- identifier.code = Components::Code.new(**code_attrs)
155
+ # The code fields are flat index columns on the leaf (see
156
+ # Identifiers::CodeNumber) — `number` is what relaton-index keys on —
157
+ # so they are assigned individually rather than as a Code object.
158
+ # Bulletin has no code columns and never reaches this branch.
159
+ code_attrs.each { |name, value| identifier.public_send(:"#{name}=", value) }
156
160
  end
157
161
 
158
162
  # Handle other attributes
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class BasicPublication < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "B"
9
11
  end
@@ -45,6 +45,42 @@ module Pubid
45
45
  map "sequence", to: :sequence
46
46
  end
47
47
 
48
+ # relaton-index keys on `id.root.number.to_s`. Bulletin is the one OIML
49
+ # leaf with no code (it does not include Identifiers::CodeNumber), so
50
+ # it derives the key from the year it already stores: every issue and
51
+ # article of a volume clusters into one bucket, with per-article
52
+ # distinctness coming from the MR slug instead. That is the BIPM
53
+ # Metrologia-volume precedent recorded in CLAUDE.md.
54
+ #
55
+ # A DERIVED READER, deliberately not an attribute: storing it would
56
+ # duplicate `year` in every row, and the key_value block above maps no
57
+ # "number", so nothing is added to the wire format. Safe as a plain
58
+ # method because Bulletin declares no `number` attribute of its own
59
+ # (the ITU itu/identifiers/base.rb precedent).
60
+ #
61
+ # nil for the bare periodical reference "OIML Bulletin", which names no
62
+ # year — a deliberate gap, pinned in spec/pubid/oiml/root_number_spec.rb.
63
+ def number
64
+ date&.year&.to_s
65
+ end
66
+
67
+ # The index key above is deliberately coarse — a whole volume in one
68
+ # bucket — which is only defensible because the MR slug stays
69
+ # per-article. The inherited hook reads through `code`, which Bulletin
70
+ # does not have, so it would return nil and collapse every article of a
71
+ # year onto `oiml.bulletin.<year>`; `to_slug` is an output FILENAME, so
72
+ # that is an overwrite. Emit the rest of the locator instead.
73
+ #
74
+ # The year is deliberately NOT included: Renderers::MrString already
75
+ # gives it its own segment (`mr_year`), so repeating it here would
76
+ # render `oiml.bulletin.1960-03-01.1960`.
77
+ def mr_number_with_part
78
+ segments = [issue, sequence].compact
79
+ return nil if segments.empty?
80
+
81
+ segments.join("-")
82
+ end
83
+
48
84
  def type_string
49
85
  "Bulletin"
50
86
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Oiml
5
+ module Identifiers
6
+ # Installs the flat, index-friendly code columns and their serialization
7
+ # on the seven concrete OIML document leaves (Recommendation, Document,
8
+ # Vocabulary, Guide, BasicPublication, ExpertReport, SeminarReport).
9
+ #
10
+ # Why the leaves and not Pubid::Oiml::SingleIdentifier, which they all
11
+ # inherit from: `number` redefines the parent ::Pubid::Identifier's
12
+ # `attribute :number, Components::Code` as a plain :string (and `part` /
13
+ # `subpart` likewise). Declaring that on a class the concrete types
14
+ # INHERIT from resolves against the parent definition nondeterministically
15
+ # under multi-flavor load — it passes an OIML-only run and can flip
16
+ # `root.number` to "" under the full suite (the IEEE lesson; see
17
+ # lib/pubid/ieee/identifiers/code_number.rb and CLAUDE.md). Declaring on
18
+ # each leaf removes the ambiguity at the point of use.
19
+ #
20
+ # This matters because relaton-index sorts and bsearches every row on
21
+ # `id.root.number.to_s`. OIML kept the code in an Oiml::Components::Code
22
+ # under a `code` attribute and never set `number`, so all 12,226 lines of
23
+ # the published relaton-data-oiml index-v2 keyed "".
24
+ #
25
+ # The serialized hash ALREADY had these as bare top-level scalars — the
26
+ # old converters merely read them through `code` — so the wire format is
27
+ # byte-identical and that index needs no regeneration.
28
+ #
29
+ # Bulletin deliberately does NOT include this module. It carries no code
30
+ # at all (its locator is the year/issue/sequence tuple), so it would only
31
+ # gain a permanently-nil `number` and a `number` key that never appears.
32
+ # It derives its index key from the year instead; see bulletin.rb.
33
+ module CodeNumber
34
+ def self.included(base)
35
+ install_attributes(base)
36
+ install_mappings(base)
37
+ end
38
+
39
+ def self.install_attributes(base)
40
+ # Bare document number ("144"), the relaton index key. The part is a
41
+ # sibling column, never folded in, so every part of a document shares
42
+ # one bucket and a part-less reference finds them all.
43
+ base.attribute :number, :string
44
+ base.attribute :part, :string
45
+ base.attribute :subpart, :string
46
+ # Free-form trailing suffix glued to the code, e.g. "sup", "A",
47
+ # "erratum", "GUM 1", "ISO3930". Preserved verbatim for round-trip.
48
+ base.attribute :suffix, :string
49
+ # When true the suffix is space-separated ("D 1 Brochure") rather
50
+ # than the default dash ("R 60-sup"). Named for the RARE case with a
51
+ # false default, so the canonical to_hash drops it from every
52
+ # ordinary row.
53
+ base.attribute :space_suffix, :boolean, default: -> { false }
54
+ end
55
+
56
+ # Merged by lutaml with the block SingleIdentifier declares, which
57
+ # carries publisher/year/edition/stage/iteration.
58
+ def self.install_mappings(base)
59
+ base.key_value do
60
+ map "number", to: :number
61
+ map "part", to: :part
62
+ map "subpart", to: :subpart
63
+ map "suffix", to: :suffix
64
+ map "space_suffix", to: :space_suffix
65
+ end
66
+ end
67
+
68
+ # The composed document code, rebuilt on demand from the split columns
69
+ # so the renderer and the URN generator keep one place to ask for the
70
+ # printed form ("144-1", "60-sup"). Deliberately NOT an attribute and
71
+ # NOT memoised: the columns are writable (the base #exclude rebuilds
72
+ # through them), so a cached Code would go stale.
73
+ #
74
+ # nil when there is no number, preserving the old nil-`code` semantics
75
+ # the URN generator branches on.
76
+ def code
77
+ return nil if number.nil?
78
+
79
+ Components::Code.new(
80
+ number: number,
81
+ part: part,
82
+ subpart: subpart,
83
+ suffix: suffix,
84
+ space_suffix: space_suffix,
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class Document < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "D"
9
11
  end
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class ExpertReport < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "E"
9
11
  end
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class Guide < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "G"
9
11
  end
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class Recommendation < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "R"
9
11
  end
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class SeminarReport < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "S"
9
11
  end
@@ -4,6 +4,8 @@ module Pubid
4
4
  module Oiml
5
5
  module Identifiers
6
6
  class Vocabulary < SingleIdentifier
7
+ include CodeNumber
8
+
7
9
  def type_string
8
10
  "V"
9
11
  end
@@ -7,6 +7,7 @@ module Pubid
7
7
  autoload :Annex, "#{__dir__}/identifiers/annex"
8
8
  autoload :BasicPublication, "#{__dir__}/identifiers/basic_publication"
9
9
  autoload :Bulletin, "#{__dir__}/identifiers/bulletin"
10
+ autoload :CodeNumber, "#{__dir__}/identifiers/code_number"
10
11
  autoload :Document, "#{__dir__}/identifiers/document"
11
12
  autoload :Errata, "#{__dir__}/identifiers/errata"
12
13
  autoload :ExpertReport, "#{__dir__}/identifiers/expert_report"
@@ -4,7 +4,7 @@ require "parslet"
4
4
 
5
5
  module Pubid
6
6
  module Oiml
7
- class Parser < Parslet::Parser
7
+ class Parser < ::Pubid::Parser::Grammar
8
8
  include ::Pubid::Parser::CommonParseRules
9
9
  include ::Pubid::Parser::CommonParseMethods
10
10
 
@@ -5,7 +5,6 @@ module Pubid
5
5
  class SingleIdentifier < Identifier
6
6
  # Base class for OIML single identifiers (non-supplements)
7
7
  attribute :publisher, :string
8
- attribute :code, Oiml::Components::Code
9
8
  attribute :date, Pubid::Components::Date
10
9
  attribute :edition, :string
11
10
  attribute :stage, :string
@@ -16,46 +15,24 @@ module Pubid
16
15
  } # Track parsed format
17
16
 
18
17
  # Serialization delta on top of Oiml::Identifier's shared block. The
19
- # `code` (number/part/subpart/suffix) and `date` (year) components are
20
- # flattened to top-level keys rather than nested hashes, mirroring ISO
21
- # (lib/pubid/iso/identifier.rb). `type` is intentionally omitted
22
- # (recomputed from the class on load).
18
+ # `date` (year) component is flattened to a top-level key rather than a
19
+ # nested hash, mirroring ISO (lib/pubid/iso/identifier.rb). `type` is
20
+ # intentionally omitted (recomputed from the class on load).
21
+ #
22
+ # The code columns (number/part/subpart/suffix/space_suffix) are NOT
23
+ # here: they are declared, and mapped, by Identifiers::CodeNumber on each
24
+ # concrete leaf, because redefining the inherited `number` on this class
25
+ # — which every leaf inherits — is the multi-flavor determinism landmine.
26
+ # Keeping the mapping with the attribute is also what lets Bulletin, the
27
+ # one leaf with no code, omit both.
23
28
  key_value do
24
29
  map "publisher", to: :publisher
25
- map "number", with: { to: :number_to_kv, from: :number_from_kv }
26
- map "part", with: { to: :part_to_kv, from: :part_from_kv }
27
- map "subpart", with: { to: :subpart_to_kv, from: :subpart_from_kv }
28
- map "suffix", with: { to: :suffix_to_kv, from: :suffix_from_kv }
29
- map "space_suffix",
30
- with: { to: :space_suffix_to_kv, from: :space_suffix_from_kv }
31
30
  map "year", with: { to: :year_to_kv, from: :year_from_kv }
32
31
  map "edition", to: :edition
33
32
  map "stage", to: :stage
34
33
  map "iteration", to: :iteration
35
34
  end
36
35
 
37
- # --- code components flattened to top-level keys ---
38
- def number_to_kv(model, doc) = emit_kv(doc, "number", model.code&.number)
39
- def number_from_kv(model, value) = code_for(model).number = value.to_s
40
- def part_to_kv(model, doc) = emit_kv(doc, "part", model.code&.part)
41
- def part_from_kv(model, value) = code_for(model).part = value.to_s
42
- def subpart_to_kv(model, doc) = emit_kv(doc, "subpart", model.code&.subpart)
43
- def subpart_from_kv(model, value) = code_for(model).subpart = value.to_s
44
- def suffix_to_kv(model, doc) = emit_kv(doc, "suffix", model.code&.suffix)
45
- def suffix_from_kv(model, value) = code_for(model).suffix = value.to_s
46
-
47
- def space_suffix_to_kv(model, doc)
48
- return unless model.code&.space_suffix
49
-
50
- doc.add_child(
51
- Lutaml::KeyValue::DataModel::Element.new("space_suffix", true),
52
- )
53
- end
54
-
55
- def space_suffix_from_kv(model, value)
56
- code_for(model).space_suffix = value
57
- end
58
-
59
36
  # --- date flattened to a top-level year ---
60
37
  def year_to_kv(model, doc) = emit_kv(doc, "year", model.date&.year)
61
38
 
@@ -69,10 +46,6 @@ module Pubid
69
46
  doc.add_child(Lutaml::KeyValue::DataModel::Element.new(key, value.to_s))
70
47
  end
71
48
 
72
- def code_for(model)
73
- model.code ||= Components::Code.new
74
- end
75
-
76
49
  attr_reader :requested_format
77
50
 
78
51
  # Type is determined by the subclass
@@ -103,11 +76,15 @@ module Pubid
103
76
  raise NotImplementedError, "Subclasses must implement type_string"
104
77
  end
105
78
 
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.
79
+ # OIML keeps identity in the code columns and `type_string` (e.g. "R",
80
+ # "V", "D"), not in the inherited `typed_stage` — the generic MrString
81
+ # renderer would otherwise drop both and produce `OIML.<year>`.
82
+ # Losslessness for issue #142 requires the type letter and document
83
+ # number to appear in MR.
84
+ #
85
+ # Reads through `code`, which every code-bearing leaf composes from its
86
+ # split columns (Identifiers::CodeNumber); Bulletin has no code and
87
+ # returns nil here, keeping its (year, issue, sequence) slug.
111
88
  def mr_number_with_part
112
89
  segments = []
113
90
  segments << code&.number&.to_s if code&.number
@@ -119,6 +96,13 @@ module Pubid
119
96
  segments.join("-")
120
97
  end
121
98
 
99
+ # Bulletin overrides `code`; every other leaf gets it from
100
+ # Identifiers::CodeNumber. Returning nil here keeps #== , the renderer
101
+ # and the URN generator total for the abstract class itself.
102
+ def code
103
+ nil
104
+ end
105
+
122
106
  def mr_type
123
107
  type_string&.downcase
124
108
  end
@@ -8,6 +8,33 @@ module Pubid
8
8
  attribute :base, Oiml::Identifier, polymorphic: true
9
9
  attribute :year, :string
10
10
  attribute :language, :string
11
+
12
+ # Delegate the document code to the wrapped standard, mirroring
13
+ # Pubid::Etsi::Identifiers::SupplementIdentifier#code.
14
+ #
15
+ # PRE-EXISTING BUG this closes: `UrnGenerator#urn_number` calls
16
+ # `identifier.code` for every non-Bulletin type, but a supplement
17
+ # descends from Oiml::Identifier directly — it is a SIBLING of
18
+ # SingleIdentifier, which is where `code` used to live — so it inherited
19
+ # no `code` at all and `to_urn` raised NoMethodError for every Amendment,
20
+ # Errata and Annex. Verified against a baseline captured on main: all
21
+ # five supplement fixtures already raised there, so this is a fix, not a
22
+ # regression introduced by the move to split index columns.
23
+ #
24
+ # It stayed hidden because spec/pubid/oiml/urn_spec.rb only exercises a
25
+ # bare Recommendation and spec/pubid/oiml/fixtures_spec.rb globs a bad
26
+ # path and runs 0 examples (hand-off: ten-dead-fixture-specs).
27
+ # `iteration` is the same story: declared on SingleIdentifier, read
28
+ # unconditionally by UrnGenerator#urn_iteration, absent from the
29
+ # supplement branch of the hierarchy. (`stage` escapes because
30
+ # ::Pubid::Identifier declares one, so it merely returns nil.)
31
+ def code
32
+ base&.code
33
+ end
34
+
35
+ def iteration
36
+ base&.iteration
37
+ end
11
38
  # True for the trailing-word shorthand ("OIML R 138:2009 Amendment"),
12
39
  # where the supplement word is appended after a dated base instead of the
13
40
  # "Amendment (YYYY) to BASE" prose form. The word itself comes from the