pubid 2.0.0.pre.alpha.9 → 2.0.0.pre.alpha.12

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 (461) 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/components/date.rb +10 -4
  82. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  83. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  84. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +1 -2
  85. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  86. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  87. data/lib/pubid/bsi/identifiers/handbook.rb +1 -3
  88. data/lib/pubid/bsi/identifiers/practice_guide.rb +1 -2
  89. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  90. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  91. data/lib/pubid/bsi/parser.rb +1 -1
  92. data/lib/pubid/bsi/renderer.rb +63 -51
  93. data/lib/pubid/bsi/single_identifier.rb +27 -9
  94. data/lib/pubid/bsi/urn_generator.rb +11 -2
  95. data/lib/pubid/bsi.rb +7 -1
  96. data/lib/pubid/builder/base.rb +18 -8
  97. data/lib/pubid/bundled_identifier.rb +16 -6
  98. data/lib/pubid/calconnect/identifier.rb +9 -5
  99. data/lib/pubid/calconnect/parser.rb +1 -1
  100. data/lib/pubid/calconnect.rb +7 -1
  101. data/lib/pubid/ccsds/identifier.rb +13 -2
  102. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  103. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  104. data/lib/pubid/ccsds/parser.rb +1 -1
  105. data/lib/pubid/ccsds/single_identifier.rb +16 -12
  106. data/lib/pubid/ccsds.rb +1 -0
  107. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  108. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  109. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  110. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  111. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  112. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  115. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  116. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  117. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  118. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  119. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  120. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  123. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  124. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  125. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  126. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  127. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  128. data/lib/pubid/cen_cenelec.rb +28 -10
  129. data/lib/pubid/cie/builder.rb +10 -0
  130. data/lib/pubid/cie/identifier.rb +6 -1
  131. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  132. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  133. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  134. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  135. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  136. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  137. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  138. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  139. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  140. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  141. data/lib/pubid/cie/parser.rb +1 -1
  142. data/lib/pubid/cie.rb +7 -1
  143. data/lib/pubid/conformance/checks.rb +72 -0
  144. data/lib/pubid/conformance/corpus/case.rb +55 -0
  145. data/lib/pubid/conformance/corpus.rb +36 -0
  146. data/lib/pubid/conformance/generator.rb +212 -0
  147. data/lib/pubid/conformance/pending.rb +63 -0
  148. data/lib/pubid/conformance/runner.rb +153 -0
  149. data/lib/pubid/conformance.rb +55 -0
  150. data/lib/pubid/core.rb +2 -0
  151. data/lib/pubid/csa/builder.rb +47 -47
  152. data/lib/pubid/csa/composite_identifier.rb +14 -19
  153. data/lib/pubid/csa/identifier.rb +143 -81
  154. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  155. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  156. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  157. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  158. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  159. data/lib/pubid/csa/identifiers/package.rb +2 -2
  160. data/lib/pubid/csa/parser.rb +6 -2
  161. data/lib/pubid/csa/renderer.rb +13 -5
  162. data/lib/pubid/csa/single_identifier.rb +93 -4
  163. data/lib/pubid/csa/urn_generator.rb +9 -2
  164. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  165. data/lib/pubid/csa.rb +7 -1
  166. data/lib/pubid/doi/identifier.rb +6 -3
  167. data/lib/pubid/doi/parser.rb +1 -1
  168. data/lib/pubid/doi.rb +1 -0
  169. data/lib/pubid/easc/identifier.rb +0 -2
  170. data/lib/pubid/easc/parser.rb +10 -2
  171. data/lib/pubid/easc.rb +1 -0
  172. data/lib/pubid/ecma/builder.rb +23 -9
  173. data/lib/pubid/ecma/identifier.rb +81 -10
  174. data/lib/pubid/ecma/parser.rb +39 -4
  175. data/lib/pubid/ecma/renderer.rb +29 -8
  176. data/lib/pubid/ecma/urn_generator.rb +34 -13
  177. data/lib/pubid/ecma/urn_parser.rb +30 -14
  178. data/lib/pubid/ecma.rb +1 -0
  179. data/lib/pubid/errors.rb +80 -0
  180. data/lib/pubid/etsi/builder.rb +6 -1
  181. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  182. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  183. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  184. data/lib/pubid/etsi/parser.rb +1 -1
  185. data/lib/pubid/etsi.rb +1 -0
  186. data/lib/pubid/evs/builder.rb +26 -0
  187. data/lib/pubid/evs/identifier.rb +37 -0
  188. data/lib/pubid/evs/identifiers/national_adoption.rb +22 -0
  189. data/lib/pubid/evs/identifiers.rb +9 -0
  190. data/lib/pubid/evs/parser.rb +27 -0
  191. data/lib/pubid/evs/renderer.rb +30 -0
  192. data/lib/pubid/evs/urn_generator.rb +30 -0
  193. data/lib/pubid/evs/urn_parser.rb +80 -0
  194. data/lib/pubid/evs.rb +90 -0
  195. data/lib/pubid/export.rb +1 -0
  196. data/lib/pubid/format_detector.rb +1 -0
  197. data/lib/pubid/gb/builder.rb +14 -9
  198. data/lib/pubid/gb/identifier.rb +29 -7
  199. data/lib/pubid/gb/parser.rb +5 -1
  200. data/lib/pubid/gb/renderer.rb +4 -3
  201. data/lib/pubid/gb.rb +2 -0
  202. data/lib/pubid/gost/builder.rb +46 -9
  203. data/lib/pubid/gost/identifier.rb +0 -2
  204. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  205. data/lib/pubid/gost/parser.rb +10 -2
  206. data/lib/pubid/gost.rb +1 -0
  207. data/lib/pubid/iala/identifier.rb +9 -6
  208. data/lib/pubid/iala/parser.rb +10 -2
  209. data/lib/pubid/iala/urn_parser.rb +6 -2
  210. data/lib/pubid/iala.rb +1 -0
  211. data/lib/pubid/iana/builder.rb +3 -1
  212. data/lib/pubid/iana/identifier.rb +74 -6
  213. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  214. data/lib/pubid/iana/parser.rb +1 -1
  215. data/lib/pubid/iana/renderer.rb +3 -0
  216. data/lib/pubid/iana/urn_generator.rb +5 -1
  217. data/lib/pubid/iana.rb +1 -0
  218. data/lib/pubid/identifier.rb +605 -13
  219. data/lib/pubid/identifier_metadata.rb +1 -0
  220. data/lib/pubid/idf/builder.rb +3 -3
  221. data/lib/pubid/idf/identifier.rb +25 -9
  222. data/lib/pubid/idf/identifiers/amendment.rb +1 -1
  223. data/lib/pubid/idf/identifiers/corrigendum.rb +1 -1
  224. data/lib/pubid/idf/identifiers/international_standard.rb +1 -1
  225. data/lib/pubid/idf/identifiers/reviewed_method.rb +1 -1
  226. data/lib/pubid/idf/parser.rb +1 -1
  227. data/lib/pubid/idf/renderer.rb +4 -4
  228. data/lib/pubid/idf/single_identifier.rb +1 -1
  229. data/lib/pubid/idf/urn_generator.rb +1 -1
  230. data/lib/pubid/idf.rb +7 -1
  231. data/lib/pubid/iec/builder.rb +14 -7
  232. data/lib/pubid/iec/components.rb +0 -1
  233. data/lib/pubid/iec/identifier.rb +73 -83
  234. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  235. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  236. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  237. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  238. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  239. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  240. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  241. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  242. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  243. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  244. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  245. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  246. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  247. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  248. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  249. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  250. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  251. data/lib/pubid/iec/parser.rb +2 -18
  252. data/lib/pubid/iec/renderer.rb +13 -10
  253. data/lib/pubid/iec/single_identifier.rb +33 -10
  254. data/lib/pubid/iec/urn_generator.rb +227 -61
  255. data/lib/pubid/iec/urn_parser.rb +42 -4
  256. data/lib/pubid/iec.rb +1 -0
  257. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  258. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  259. data/lib/pubid/ieee/builder.rb +87 -3
  260. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  261. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  262. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  263. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  264. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  265. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  266. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  267. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  268. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  269. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  270. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  271. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  272. data/lib/pubid/ieee/identifiers/si_standard.rb +5 -2
  273. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  274. data/lib/pubid/ieee/ire/parser.rb +20 -2
  275. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  276. data/lib/pubid/ieee/parser.rb +208 -33
  277. data/lib/pubid/ieee/project_renderer.rb +46 -0
  278. data/lib/pubid/ieee/renderer.rb +11 -8
  279. data/lib/pubid/ieee/urn_generator.rb +32 -3
  280. data/lib/pubid/ieee.rb +9 -1
  281. data/lib/pubid/ieee_debug.rb +1 -0
  282. data/lib/pubid/ietf/builder.rb +24 -8
  283. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  284. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  285. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  286. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  287. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  288. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  289. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  290. data/lib/pubid/ietf/identifiers.rb +1 -0
  291. data/lib/pubid/ietf/parser.rb +27 -9
  292. data/lib/pubid/ietf/renderer.rb +44 -6
  293. data/lib/pubid/ietf/urn_generator.rb +20 -6
  294. data/lib/pubid/ietf/urn_parser.rb +10 -2
  295. data/lib/pubid/ietf.rb +12 -2
  296. data/lib/pubid/iho/identifiers/base.rb +10 -3
  297. data/lib/pubid/iho/parser.rb +1 -1
  298. data/lib/pubid/iho.rb +1 -0
  299. data/lib/pubid/isbn/identifier.rb +25 -7
  300. data/lib/pubid/isbn/parser.rb +1 -1
  301. data/lib/pubid/isbn.rb +1 -0
  302. data/lib/pubid/iso/builder.rb +10 -11
  303. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  304. data/lib/pubid/iso/combined_identifier.rb +5 -3
  305. data/lib/pubid/iso/components.rb +0 -1
  306. data/lib/pubid/iso/identifier.rb +23 -11
  307. data/lib/pubid/iso/identifiers/directives.rb +7 -4
  308. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  309. data/lib/pubid/iso/identifiers/tc_document.rb +31 -46
  310. data/lib/pubid/iso/normalizer.rb +1 -1
  311. data/lib/pubid/iso/parser.rb +1 -1
  312. data/lib/pubid/iso/urn_generator.rb +10 -10
  313. data/lib/pubid/iso.rb +12 -5
  314. data/lib/pubid/itu/builder.rb +10 -0
  315. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  316. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  317. data/lib/pubid/itu/identifiers/base.rb +29 -5
  318. data/lib/pubid/itu/identifiers/contribution.rb +31 -0
  319. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  320. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  321. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  322. data/lib/pubid/itu/identifiers.rb +1 -0
  323. data/lib/pubid/itu/parser.rb +20 -3
  324. data/lib/pubid/itu.rb +1 -0
  325. data/lib/pubid/jcgm/builder.rb +1 -1
  326. data/lib/pubid/jcgm/identifier.rb +10 -0
  327. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  328. data/lib/pubid/jcgm/parser.rb +10 -3
  329. data/lib/pubid/jcgm/renderer.rb +10 -3
  330. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  331. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  332. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  333. data/lib/pubid/jcgm.rb +10 -0
  334. data/lib/pubid/jis/identifier.rb +10 -3
  335. data/lib/pubid/jis/parser.rb +1 -1
  336. data/lib/pubid/jis.rb +1 -0
  337. data/lib/pubid/nist/builder.rb +12 -12
  338. data/lib/pubid/nist/components/stage.rb +2 -2
  339. data/lib/pubid/nist/configuration.rb +6 -8
  340. data/lib/pubid/nist/identifiers/base.rb +22 -5
  341. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  342. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  343. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
  344. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  345. data/lib/pubid/nist/identifiers/crpl_report.rb +12 -7
  346. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +2 -2
  347. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  348. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  349. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +16 -8
  350. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  351. data/lib/pubid/nist/identifiers/report.rb +14 -6
  352. data/lib/pubid/nist/parser.rb +1 -1
  353. data/lib/pubid/nist/series/ir.rb +3 -7
  354. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  355. data/lib/pubid/nist.rb +7 -1
  356. data/lib/pubid/oasis/builder.rb +12 -10
  357. data/lib/pubid/oasis/identifier.rb +89 -11
  358. data/lib/pubid/oasis/parser.rb +1 -1
  359. data/lib/pubid/oasis.rb +7 -1
  360. data/lib/pubid/ogc/identifier.rb +9 -5
  361. data/lib/pubid/ogc/parser.rb +1 -1
  362. data/lib/pubid/ogc.rb +1 -0
  363. data/lib/pubid/oiml/builder.rb +7 -3
  364. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  365. data/lib/pubid/oiml/identifiers/bulletin.rb +40 -10
  366. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  367. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  368. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  369. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  370. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  371. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  372. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  373. data/lib/pubid/oiml/identifiers.rb +1 -0
  374. data/lib/pubid/oiml/parser.rb +8 -3
  375. data/lib/pubid/oiml/renderer.rb +3 -3
  376. data/lib/pubid/oiml/single_identifier.rb +26 -42
  377. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  378. data/lib/pubid/oiml/urn_generator.rb +4 -4
  379. data/lib/pubid/oiml.rb +7 -1
  380. data/lib/pubid/omg/builder.rb +1 -0
  381. data/lib/pubid/omg/identifier.rb +20 -3
  382. data/lib/pubid/omg/parser.rb +64 -11
  383. data/lib/pubid/omg/renderer.rb +13 -1
  384. data/lib/pubid/omg.rb +2 -1
  385. data/lib/pubid/parser/grammar.rb +74 -0
  386. data/lib/pubid/parser.rb +2 -0
  387. data/lib/pubid/parsers/mr_string.rb +19 -0
  388. data/lib/pubid/plateau/parser.rb +1 -1
  389. data/lib/pubid/plateau.rb +10 -0
  390. data/lib/pubid/prefixes_support.rb +1 -0
  391. data/lib/pubid/renderers/annotator.rb +233 -0
  392. data/lib/pubid/renderers/base.rb +13 -0
  393. data/lib/pubid/renderers/directives_renderer.rb +6 -4
  394. data/lib/pubid/renderers/human_readable.rb +3 -3
  395. data/lib/pubid/renderers/mr_string.rb +15 -9
  396. data/lib/pubid/renderers.rb +1 -0
  397. data/lib/pubid/rendering/numbering.rb +25 -7
  398. data/lib/pubid/rendering.rb +1 -0
  399. data/lib/pubid/sae/builder.rb +1 -1
  400. data/lib/pubid/sae/identifiers/base.rb +13 -3
  401. data/lib/pubid/sae/parser.rb +1 -1
  402. data/lib/pubid/sae/urn_generator.rb +1 -1
  403. data/lib/pubid/sae.rb +1 -0
  404. data/lib/pubid/schema/declaration.rb +33 -0
  405. data/lib/pubid/schema/error.rb +14 -0
  406. data/lib/pubid/schema/identifier_type.rb +23 -0
  407. data/lib/pubid/schema/loader.rb +117 -0
  408. data/lib/pubid/schema/typed_stage.rb +24 -0
  409. data/lib/pubid/schema.rb +25 -0
  410. data/lib/pubid/tgpp/builder.rb +1 -1
  411. data/lib/pubid/tgpp/identifier.rb +21 -5
  412. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  413. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  414. data/lib/pubid/tgpp/parser.rb +22 -6
  415. data/lib/pubid/tgpp/renderer.rb +21 -5
  416. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  417. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  418. data/lib/pubid/tgpp.rb +7 -1
  419. data/lib/pubid/type_resolver.rb +16 -1
  420. data/lib/pubid/un/identifier.rb +6 -3
  421. data/lib/pubid/un/parser.rb +1 -1
  422. data/lib/pubid/un.rb +1 -0
  423. data/lib/pubid/urn_parser/errors.rb +8 -1
  424. data/lib/pubid/urn_parser.rb +1 -0
  425. data/lib/pubid/utils.rb +1 -0
  426. data/lib/pubid/version.rb +1 -1
  427. data/lib/pubid/w3c/builder.rb +5 -5
  428. data/lib/pubid/w3c/identifier.rb +38 -9
  429. data/lib/pubid/w3c/parser.rb +1 -1
  430. data/lib/pubid/w3c/renderer.rb +1 -1
  431. data/lib/pubid/w3c/urn_generator.rb +2 -2
  432. data/lib/pubid/w3c.rb +1 -0
  433. data/lib/pubid/xsf/identifier.rb +9 -5
  434. data/lib/pubid/xsf/parser.rb +17 -4
  435. data/lib/pubid/xsf.rb +7 -1
  436. data/lib/pubid.rb +399 -21
  437. data/lib/tasks/conformance.rake +34 -0
  438. data/lib/tasks/docs.rake +16 -14
  439. data/lib/tasks/schema.rake +88 -0
  440. data/schema/core/joint_prefixes.yaml +24 -0
  441. data/schema/iec.yaml +1017 -0
  442. data/schema/iso.yaml +1586 -0
  443. data/schema/schema.schema.yaml +96 -0
  444. metadata +40 -19
  445. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  446. data/archived-gems/pubid-iec/stages.yaml +0 -129
  447. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  448. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  449. data/archived-gems/pubid-iso/stages.yaml +0 -106
  450. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  451. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  452. data/archived-gems/pubid-itu/series.yaml +0 -42
  453. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  454. data/archived-gems/pubid-nist/series.yaml +0 -121
  455. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  456. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  457. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +0 -70
  458. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  459. data/lib/pubid/iec/components/code.rb +0 -36
  460. data/lib/pubid/iso/components/code.rb +0 -24
  461. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -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
@@ -224,13 +228,13 @@ module Pubid
224
228
  article_id = parsed_hash[:article_id].to_s
225
229
  identifier.date ||= Pubid::Components::Date.new
226
230
  identifier.date.year = article_id[0, 4]
227
- identifier.issue = article_id[4, 2]
231
+ identifier.number = article_id[4, 2]
228
232
  identifier.sequence = article_id[6, 2]
229
233
  warn_on_volume_mismatch(identifier, parsed_hash[:volume_roman])
230
234
  return
231
235
  end
232
236
 
233
- identifier.issue = parsed_hash[:issue].to_s if parsed_hash[:issue]
237
+ identifier.number = parsed_hash[:issue].to_s if parsed_hash[:issue]
234
238
  identifier.sequence = parsed_hash[:sequence].to_s if parsed_hash[:sequence]
235
239
  end
236
240
 
@@ -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
@@ -33,18 +33,48 @@ module Pubid
33
33
  # carried in the structured form and the roman volume in citations.
34
34
  BASE_YEAR_OFFSET = 1959
35
35
 
36
- # Zero-padded issue number within the year ("01".."04", plus "07"/"10"
37
- # for the online-bulletin series). Always 2 digits.
38
- attribute :issue, :string
36
+ # The issue: the zero-padded number within the volume ("01".."04",
37
+ # plus "07"/"10" for the online-bulletin series). Always 2 digits. The
38
+ # year is the volume and the issue is the number, as in the citation
39
+ # form "LXVII(2)".
40
+ #
41
+ # Declared as `number`, so it is the relaton index key
42
+ # (`id.root.number.to_s`) and every index row with an issue shows a
43
+ # `number`. The key puts the same issue number of all volumes in one
44
+ # bucket; the MR slug keeps each article distinct.
45
+ #
46
+ # Declared on this LEAF, which has no subclasses. It redefines the
47
+ # `Components::Code number` of ::Pubid::Identifier as a :string, and a
48
+ # redefinition on a class that other classes inherit is the
49
+ # multi-flavor determinism landmine (see Identifiers::CodeNumber).
50
+ #
51
+ # nil for a volume ("OIML Bulletin 1960") and for the bare periodical
52
+ # reference "OIML Bulletin", which name no issue.
53
+ attribute :number, :string
39
54
  # Zero-padded sequence within the issue ("00" = editorial, "01"+ for
40
55
  # articles). Always 2 digits.
41
56
  attribute :sequence, :string
42
57
 
43
58
  key_value do
44
- map "issue", to: :issue
59
+ map "number", to: :number
45
60
  map "sequence", to: :sequence
46
61
  end
47
62
 
63
+ # The inherited hook reads through `code`, which Bulletin does not
64
+ # have, so it would return nil and collapse every article of a year
65
+ # onto `oiml.bulletin.<year>`; `to_slug` is an output FILENAME, so that
66
+ # is an overwrite. Emit the rest of the locator instead.
67
+ #
68
+ # The year is deliberately NOT included: Renderers::MrString already
69
+ # gives it its own segment (`mr_year`), so repeating it here would
70
+ # render `oiml.bulletin.1960-03-01.1960`.
71
+ def mr_number_with_part
72
+ segments = [number, sequence].compact
73
+ return nil if segments.empty?
74
+
75
+ segments.join("-")
76
+ end
77
+
48
78
  def type_string
49
79
  "Bulletin"
50
80
  end
@@ -60,18 +90,18 @@ module Pubid
60
90
  # Volume as a roman-numeral string ("LXVII"), derived from the year.
61
91
  # nil when the year is absent.
62
92
  def volume_roman
63
- number = volume_arabic&.to_i
64
- return nil unless number&.positive?
93
+ volume = volume_arabic&.to_i
94
+ return nil unless volume&.positive?
65
95
 
66
- self.class.to_roman(number)
96
+ self.class.to_roman(volume)
67
97
  end
68
98
 
69
99
  # 8-digit oiml.org article id ("20260211"). Composed by concatenating
70
- # year + issue + sequence. nil unless all three are present.
100
+ # year + issue (number) + sequence. nil unless all three are present.
71
101
  def article_id
72
- return nil unless date&.year && issue && sequence
102
+ return nil unless date&.year && number && sequence
73
103
 
74
- "#{date.year}#{issue}#{sequence}"
104
+ "#{date.year}#{number}#{sequence}"
75
105
  end
76
106
 
77
107
  class << self
@@ -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 permanently-nil part/subpart/suffix columns. It declares its own
32
+ # `number` (the issue) 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
 
@@ -122,9 +122,14 @@ module Pubid
122
122
  ).as(:edition_format) # Wrap entire match to capture that Edition was used
123
123
  end
124
124
 
125
- # Date - year after colon OR edition portion (with optional space before year)
125
+ # Date - year after colon, the VIM's paren-year form ("OIML V 2-200
126
+ # (2000)" — space + (YYYY) instead of :YYYY, the shape #342 flags as
127
+ # the one real-world spelling that did not parse), OR edition portion
128
+ # (with optional space before year)
126
129
  rule(:date) do
127
- edition_portion | (colon >> space.maybe >> year_digits.as(:year))
130
+ edition_portion |
131
+ (colon >> space.maybe >> year_digits.as(:year)) |
132
+ (space.maybe >> lparen >> year_digits.as(:year) >> rparen)
128
133
  end
129
134
 
130
135
  # Draft stage - WD or CD with optional iteration
@@ -65,7 +65,7 @@ module Pubid
65
65
  result = "#{id.publisher} Bulletin"
66
66
  if id.date&.year
67
67
  result += " #{id.date.year}"
68
- result += "-#{id.issue}" if id.issue
68
+ result += "-#{id.number}" if id.number
69
69
  result += "-#{id.sequence}" if id.sequence
70
70
  end
71
71
  result += " (#{id.language})" if id.language
@@ -77,9 +77,9 @@ module Pubid
77
77
  # structured since OIML's citation format doesn't define volume-only
78
78
  # or issue-only variants.
79
79
  def render_bulletin_citation(id)
80
- return render_bulletin_structured(id) unless id.date&.year && id.issue && id.sequence
80
+ return render_bulletin_structured(id) unless id.date&.year && id.number && id.sequence
81
81
 
82
- "#{id.publisher} Bulletin #{id.volume_roman}(#{id.issue.to_i}) #{id.article_id}"
82
+ "#{id.publisher} Bulletin #{id.volume_roman}(#{id.number.to_i}) #{id.article_id}"
83
83
  end
84
84
 
85
85
  def effective_format(id)
@@ -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, declare its own `number` (the issue) and map it.
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