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
@@ -30,6 +30,8 @@ module Pubid
30
30
  build_si_brochure(data[:si_brochure])
31
31
  elsif data[:si_brochure_variant]
32
32
  build_si_brochure_variant(data[:si_brochure_variant])
33
+ elsif data.key?(:si_brochure_section)
34
+ build_si_brochure_section(data[:si_brochure_section])
33
35
  elsif data[:mep]
34
36
  build_mep(data[:mep])
35
37
  elsif data[:guide]
@@ -41,61 +43,115 @@ module Pubid
41
43
 
42
44
  private
43
45
 
46
+ # `group` and `language` are normalized here, so a historic committee
47
+ # name (CCDS) and a two-letter language code (EN/FR) never reach an
48
+ # attribute — the index stores only the current name and the one-letter
49
+ # code BIPM prints.
44
50
  def build_committee(node, form:, language: nil)
45
51
  Identifiers::CommitteeDocument.new(
46
- group: node[:group].to_s,
52
+ group: Identifier.normalize_group(node[:group]),
47
53
  type_code: Identifier::TYPE_WORD_TO_CODE[node[:type_word].to_s],
48
54
  number: node[:number]&.to_s,
49
55
  year: node[:year]&.to_s&.to_i,
50
- language: language || node[:language]&.to_s,
56
+ language: Identifier.normalize_language(language || node[:language]),
51
57
  form: form,
52
58
  )
53
59
  end
54
60
 
55
61
  def build_meeting(node, language: nil)
56
62
  Identifiers::Meeting.new(
57
- group: node[:group].to_s,
63
+ group: Identifier.normalize_group(node[:group]),
58
64
  number: node[:number].to_s,
59
65
  year: node[:year]&.to_s&.to_i,
60
- language: language || node[:language]&.to_s,
66
+ language: Identifier.normalize_language(language || node[:language]),
61
67
  )
62
68
  end
63
69
 
70
+ # `number` is the volume, so every article of a volume clusters into one
71
+ # relaton-index bucket (the IETF draft-slug / IANA registry-slug
72
+ # convention). It is nil only for the journal-level record, which carries
73
+ # no volume at all.
74
+ #
75
+ # It is deliberately redundant with `volume`, which stays because the
76
+ # renderer and the URN generator read it. Two limits of that redundancy,
77
+ # both accepted rather than engineered around (see the note above
78
+ # NUMBER_SOURCE_ATTRIBUTES in identifier.rb for the half that IS
79
+ # handled):
80
+ #
81
+ # * THE BUILDER IS THE CONSTRUCTOR OF RECORD. A caller that bypasses it
82
+ # — `MetrologiaArticle.new(volume: 51, …)` — gets a nil `number` and
83
+ # so an empty index key. Deriving it in `initialize` instead would
84
+ # cover that, but `#exclude` rebuilds through `self.class.new`, so it
85
+ # would also silently re-fill a `number` the caller just excluded.
86
+ # * NO SELF-HEALING ACROSS from_hash. lutaml assigns attributes AFTER
87
+ # `initialize`, so a legacy pre-`number` index row (one carrying only
88
+ # `volume`) deserializes with `number` nil and no error at all —
89
+ # lutaml ignores unknown keys and relaton's `id_supported?` skips its
90
+ # round-trip check for concrete subclasses, which every BIPM id is.
91
+ # relaton-data-bipm must regenerate index-v2 to gain the key. This is
92
+ # weaker than the IEEE `CodeNumber` mixin, which rebuilds `code_obj`
93
+ # from its split columns after `from_hash`.
64
94
  def build_metrologia(node)
65
95
  node = {} unless node.is_a?(Hash)
66
96
  Identifiers::MetrologiaArticle.new(
67
- volume: node[:volume]&.to_s&.to_i,
97
+ number: node[:volume]&.to_s,
68
98
  issue: node[:issue]&.to_s,
69
99
  article: node[:article]&.to_s,
70
100
  )
71
101
  end
72
102
 
103
+ # The SI Brochure has no document number of its own, so the edition is
104
+ # the index key — it clusters the English and French records of one
105
+ # edition. The grammar makes `edition` mandatory here, so it is never nil.
73
106
  def build_si_brochure(node)
74
107
  Identifiers::SiBrochure.new(
108
+ number: node[:edition].to_s,
75
109
  edition: node[:edition].to_s,
76
110
  version: node[:version].to_s,
77
111
  years: node[:years].to_s,
78
- language: node[:language]&.to_s,
112
+ language: Identifier.normalize_language(node[:language]),
79
113
  )
80
114
  end
81
115
 
116
+ # The derived products (Appendix N, Concise, FAQ) carry no edition, so
117
+ # the variant name is their key. The grammar makes it mandatory here.
82
118
  def build_si_brochure_variant(node)
83
- Identifiers::SiBrochure.new(variant: node[:variant].to_s)
119
+ variant = node[:variant].to_s
120
+ Identifiers::SiBrochure.new(number: variant, variant: variant)
84
121
  end
85
122
 
123
+ # A bare or sectioned reference names no edition, so it gets no key: it is
124
+ # a partial reference that wildcards every edition. The bare form captures
125
+ # nothing, so the parser hands back a slice rather than a hash.
126
+ def build_si_brochure_section(node)
127
+ node = {} unless node.is_a?(Hash)
128
+ Identifiers::SiBrochure.new(part: node[:part]&.to_s)
129
+ end
130
+
131
+ # The Appendix/Annex/Part tail is the full-content spelling of the SAME
132
+ # document, so it stays out of the key.
86
133
  def build_mep(node)
87
134
  Identifiers::Mep.new(
88
- mep_code: node[:mep_code]&.to_s,
89
- report_code: node[:report_code]&.to_s,
135
+ **mep_codes(node),
90
136
  appendix: node[:appendix]&.to_s,
91
137
  annex: node[:annex]&.to_s,
92
138
  part: node[:part]&.to_s,
93
139
  )
94
140
  end
95
141
 
142
+ # A MEP is identified by its unit code, or by the report code for the
143
+ # `Rapport BIPM-YYYY/NN` variant; exactly one of the two is set, and
144
+ # whichever it is becomes the relaton index key.
145
+ def mep_codes(node)
146
+ mep_code = node[:mep_code]&.to_s
147
+ report_code = node[:report_code]&.to_s
148
+ { number: mep_code || report_code, mep_code: mep_code,
149
+ report_code: report_code }
150
+ end
151
+
96
152
  def build_guide(node)
97
153
  Identifiers::Guide.new(
98
- group: node[:group].to_s,
154
+ group: Identifier.normalize_group(node[:group]),
99
155
  guide_kind: node[:guide_kind].to_s,
100
156
  number: node[:number].to_s,
101
157
  appendix: node[:appendix]&.to_s,
@@ -19,6 +19,19 @@ module Pubid
19
19
  CIPM CGPM JCRB CCTF CCQM CCT CCL CCAUV CCU CCM CCEM CCPR CCRI
20
20
  ].freeze
21
21
 
22
+ # Historic committee names consumer references still use. BIPM renamed the
23
+ # CCDS (Comité Consultatif pour la Définition de la Seconde) to CCTF in
24
+ # 1997 and the index keys the current name, so an alias is resolved at
25
+ # parse time and never stored. Extend the map, not the parser, to add one.
26
+ GROUP_ALIASES = { "CCDS" => "CCTF" }.freeze
27
+
28
+ # Every group token the grammar accepts, current names and aliases alike.
29
+ PARSEABLE_GROUPS = (GROUPS + GROUP_ALIASES.keys).freeze
30
+
31
+ # Two-letter codes consumer references use for the one-letter language
32
+ # codes BIPM prints. Resolved at parse time, so only "E"/"F" is stored.
33
+ LANGUAGE_ALIASES = { "EN" => "E", "FR" => "F" }.freeze
34
+
22
35
  # Committee document type codes and their printed long-form names. Note
23
36
  # DECL prints as "Statement" in BOTH languages in the source data.
24
37
  TYPE_CODES = %w[REC RES DECN ACT DECL].freeze
@@ -30,12 +43,20 @@ module Pubid
30
43
  "REC" => "Recommandation", "RES" => "Résolution",
31
44
  "DECN" => "Décision", "ACT" => "Action", "DECL" => "Statement"
32
45
  }.freeze
33
- # Every printed type word (abbrev + English + French) → canonical code.
46
+ # Printed type words that are not the canonical name of their code. BIPM
47
+ # prints DECL as "Statement" in both languages, but a reference spells the
48
+ # word out; without these the parser matched "Declaration" and then mapped
49
+ # it to NO code, so the renderer emitted a corrupt "CGPM 1 (1971)".
50
+ TYPE_WORD_ALIASES = {
51
+ "Declaration" => "DECL", "Déclaration" => "DECL"
52
+ }.freeze
53
+
54
+ # Every printed type word (abbrev + English + French + alias) → code.
34
55
  TYPE_WORD_TO_CODE = TYPE_CODES.each_with_object({}) do |code, map|
35
56
  map[code] = code
36
57
  map[TYPE_NAME_EN[code]] = code
37
58
  map[TYPE_NAME_FR[code]] = code
38
- end.freeze
59
+ end.merge(TYPE_WORD_ALIASES).freeze
39
60
 
40
61
  # Shared attributes (flat). Booleans/strings carry no default so they stay
41
62
  # nil and drop from the serialized hash unless set.
@@ -48,8 +69,9 @@ module Pubid
48
69
  # "long" (full type-word name). Defaults to "short" so it drops from the
49
70
  # canonical hash for the indexed primary form.
50
71
  attribute :form, :string, default: "short"
51
- # Metrologia journal fields.
52
- attribute :volume, :integer
72
+ # Metrologia journal fields. `volume` is NOT stored — it is exactly the
73
+ # index key, so MetrologiaArticle derives it from `number` (the IANA
74
+ # `registry` / IETF `series` pattern) rather than serializing both.
53
75
  attribute :issue, :string # may be alphanumeric ("1A")
54
76
  attribute :article, :string
55
77
  # SI Brochure fields.
@@ -69,6 +91,17 @@ module Pubid
69
91
  attribute :annex, :string
70
92
  attribute :part, :string
71
93
 
94
+ # Attributes `Builder` derives `number` from for the families that have no
95
+ # document number of their own. Excluding one of these has to clear
96
+ # `number` too, or a wildcarded identifier would keep a stale index key —
97
+ # the "reset the WHOLE cluster, not just the scalar" lesson CLAUDE.md
98
+ # records for CSA's year and IEEE's year/month/day. `:volume` is listed
99
+ # even though it is no longer an attribute: the base loop skips it, but
100
+ # `exclude(:volume)` must still clear the key it derives from.
101
+ NUMBER_SOURCE_ATTRIBUTES = %i[
102
+ volume edition variant mep_code report_code
103
+ ].freeze
104
+
72
105
  # Polymorphic type map for lutaml::Model key_value (de)serialization:
73
106
  # maps each subclass's polymorphic_name to its class name so a stored hash
74
107
  # rebuilds the correct identifier type via from_hash.
@@ -91,7 +124,6 @@ module Pubid
91
124
  map "year", to: :year
92
125
  map "language", to: :language
93
126
  map "form", to: :form
94
- map "volume", to: :volume
95
127
  map "issue", to: :issue
96
128
  map "article", to: :article
97
129
  map "edition", to: :edition
@@ -117,6 +149,20 @@ module Pubid
117
149
  group == "CGPM" ? "de la" : "du"
118
150
  end
119
151
 
152
+ # Resolve a historic committee name to the name the index keys.
153
+ def self.normalize_group(token)
154
+ name = token.to_s
155
+ GROUP_ALIASES.fetch(name, name)
156
+ end
157
+
158
+ # Resolve a two-letter language code to the one-letter code BIPM prints.
159
+ def self.normalize_language(token)
160
+ return nil if token.nil?
161
+
162
+ code = token.to_s
163
+ LANGUAGE_ALIASES.fetch(code, code)
164
+ end
165
+
120
166
  def long?
121
167
  form == "long"
122
168
  end
@@ -130,6 +176,7 @@ module Pubid
130
176
  def exclude(*args)
131
177
  result = super
132
178
  result.year = nil if args.include?(:year) || args.include?(:date)
179
+ result.number = nil if args.intersect?(NUMBER_SOURCE_ATTRIBUTES)
133
180
  result
134
181
  end
135
182
 
@@ -138,13 +185,93 @@ module Pubid
138
185
  render(format: :human, **opts)
139
186
  end
140
187
 
188
+ # --- MR string / slug -------------------------------------------------
189
+ #
190
+ # `to_slug` is what consumers use as an output FILENAME, so every BIPM
191
+ # identifier needs a non-empty, filename-safe, non-colliding MR. None of
192
+ # the base ::Pubid::Identifier hooks work here unchanged, because BIPM
193
+ # models each of them with its own flat attribute rather than the shared
194
+ # component the base expects. Each override below says which.
195
+ #
196
+ # The number segment and the family discriminator are supplied per leaf
197
+ # (`mr_number_with_part` / `mr_type`), because BIPM's six families share
198
+ # no numbering.
199
+ #
200
+ # Three deliberate collapses — MR is a canonical DOCUMENT slug, so
201
+ # alternate print spellings of one document share it (the URN already
202
+ # collapses the first two): `form` (short vs long committee spelling) and
203
+ # the MEP/Guide `appendix`/`annex`/`part` full-content tail are omitted,
204
+ # while `language` is kept because the E and F records are distinct
205
+ # documents upstream.
206
+
207
+ # BIPM keeps its publisher in the PUBLISHER constant rather than the
208
+ # inherited `publisher` attribute (which would fail type validation), so
209
+ # the base hook — `publisher&.to_s` — is nil. The JIS/IANA shape.
210
+ def mr_publisher
211
+ PUBLISHER.downcase
212
+ end
213
+
214
+ # BIPM stores the publication year in its own :integer attribute rather
215
+ # than a Components::Date, so the base hook reads an unset `date`.
216
+ def mr_year
217
+ year&.to_s
218
+ end
219
+
220
+ # BIPM's `edition` is a plain :string ("9e"), not a Components::Edition,
221
+ # so the base hook's `edition.number` raises NoMethodError. SiBrochure
222
+ # folds the edition into its own number segment instead.
223
+ def mr_edition
224
+ nil
225
+ end
226
+
227
+ # BIPM has a single `language` string, not the base's `languages`
228
+ # collection.
229
+ def mr_languages
230
+ language&.to_s&.downcase
231
+ end
232
+
233
+ # BIPM uses its own `type_code` attribute rather than a typed_stage, so
234
+ # the base hook is always nil; the leaves supply the discriminator.
235
+ def mr_type
236
+ nil
237
+ end
238
+
239
+ # Join the parts of a number segment into one filename-safe token.
240
+ #
241
+ # The sanitisation is by CHARSET, not an enumerated escape list, so a
242
+ # field added later cannot leak an unsafe character: everything outside
243
+ # [a-z0-9] collapses to `-`. BIPM needs at least three of those today —
244
+ # a `/` (the MEP report code "BIPM-2019/05"), a space (the SI Brochure
245
+ # variant "Appendix 3") and a `.` (the brochure version "v3.01"). The
246
+ # dot matters beyond filename safety: Renderers::MrString joins SEGMENTS
247
+ # with ".", so a dot inside one would break the documented segment
248
+ # structure.
249
+ #
250
+ # Caveat: `-` is both the intra-segment join and a substitute, so two
251
+ # different field splits could in principle produce one slug. That
252
+ # cannot happen with today's grammar — none of `group`, `guide_kind`,
253
+ # `mep_code`, `issue` or `article` admits a `-`, and `number`'s own
254
+ # internal `-` ("10-1") is unambiguous because the `group` before it
255
+ # cannot contain one. A future hyphen-bearing free-text field would
256
+ # need a different join.
257
+ def mr_slug(*parts)
258
+ slug = parts.compact.map(&:to_s).reject(&:empty?).join("-").downcase
259
+ slug = slug.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
260
+ slug unless slug.empty?
261
+ end
262
+
141
263
  # Parse a BIPM identifier string into an identifier object.
142
264
  # @param identifier [String] the BIPM identifier string
143
265
  # @return [Identifier] the appropriate identifier object
144
- # @raise [RuntimeError] if parsing fails
266
+ # @raise [Pubid::Errors::ParseError] if parsing fails
145
267
  def self.parse(identifier)
268
+ unless identifier.is_a?(String)
269
+ raise Pubid::Errors::InvalidInputError,
270
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
271
+ end
272
+
146
273
  if identifier.length > Pubid::MAX_INPUT_LENGTH
147
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
274
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
148
275
  end
149
276
 
150
277
  # Normalize legacy/docnumber-style spellings before parsing
@@ -152,8 +279,6 @@ module Pubid
152
279
  normalized = Core::UpdateCodes.apply(identifier, :bipm)
153
280
  parsed = Parser.parse(normalized)
154
281
  Builder.build(parsed)
155
- rescue Parslet::ParseFailed => e
156
- raise "Failed to parse BIPM identifier '#{identifier}': #{e.message}"
157
282
  end
158
283
  end
159
284
  end
@@ -18,6 +18,18 @@ module Pubid
18
18
  { key: :committee_document, web: :committee_document,
19
19
  title: "Committee Document", short: "committee-document" }
20
20
  end
21
+
22
+ # MR: `bipm.<type>.<group>-<number>.<year>[.<lang>]`. The type code
23
+ # discriminates REC/RES/DECN/ACT/DECL; the bare CIPM MRA form has none.
24
+ def mr_type
25
+ type_code&.downcase
26
+ end
27
+
28
+ # The group has to be in the number segment or `CCTF REC 2` and
29
+ # `CCEM REC 2` would share a slug.
30
+ def mr_number_with_part
31
+ mr_slug(group, number)
32
+ end
21
33
  end
22
34
  end
23
35
  end
@@ -18,6 +18,17 @@ module Pubid
18
18
  def self.type
19
19
  { key: :guide, web: :guide, title: "Guide", short: "guide" }
20
20
  end
21
+
22
+ # MR: `bipm.guide.<group>-<kind>-<number>`. Both the committee and the
23
+ # kind are needed — the trailing sequence restarts per committee, so
24
+ # `CCL-GD-MeP-1` and `CCEM-GD-RSI-1` both carry number "1".
25
+ def mr_type
26
+ "guide"
27
+ end
28
+
29
+ def mr_number_with_part
30
+ mr_slug(group, guide_kind, number)
31
+ end
21
32
  end
22
33
  end
23
34
  end
@@ -35,6 +35,18 @@ module Pubid
35
35
  def ordinal_en
36
36
  "#{number}#{self.class.ordinal_suffix(number)}"
37
37
  end
38
+
39
+ # MR: `bipm.meeting.<group>-<number>.<year>`. The English and French
40
+ # spellings of one meeting collapse onto the same slug on purpose.
41
+ def mr_type
42
+ "meeting"
43
+ end
44
+
45
+ # The group has to be in the number segment or the 17th CGPM and the
46
+ # 17th CIPM meeting would share a slug.
47
+ def mr_number_with_part
48
+ mr_slug(group, number)
49
+ end
38
50
  end
39
51
  end
40
52
  end
@@ -15,6 +15,18 @@ module Pubid
15
15
  def self.type
16
16
  { key: :mep, web: :mep, title: "Mise en pratique", short: "mep" }
17
17
  end
18
+
19
+ # MR: `bipm.mep.<code>`. Exactly one of `mep_code` / `report_code` is
20
+ # set; the report code carries a `/` that `mr_slug` sanitises away. The
21
+ # full-content "Appendix 2 Part x" tail is the same document, so it
22
+ # stays out of the slug.
23
+ def mr_type
24
+ "mep"
25
+ end
26
+
27
+ def mr_number_with_part
28
+ mr_slug(mep_code || report_code)
29
+ end
18
30
  end
19
31
  end
20
32
  end
@@ -18,6 +18,30 @@ module Pubid
18
18
  { key: :metrologia_article, web: :metrologia_article,
19
19
  title: "Metrologia Article", short: "metrologia-article" }
20
20
  end
21
+
22
+ # MR: `bipm.metrologia[.<volume>[-<issue>[-<article>]]]`.
23
+ # The volume IS the index key, so it is derived from `number` instead
24
+ # of being stored beside it — storing both would duplicate the same
25
+ # value in every one of the 6,204 published article rows. Kept as an
26
+ # Integer because that is the public type consumers already read
27
+ # (`Relaton::Bipm::Bibliography#id_hash` calls `pubid.volume`).
28
+ #
29
+ # Safe as a plain method only because `volume` is no longer a lutaml
30
+ # attribute anywhere in the hierarchy — a method that shadows a
31
+ # generated accessor corrupts attribute resolution (CLAUDE.md).
32
+ def volume
33
+ number&.to_i
34
+ end
35
+
36
+ def mr_type
37
+ "metrologia"
38
+ end
39
+
40
+ # `number` is only the volume (it is the CLUSTERING relaton-index key),
41
+ # so per-article distinctness has to come from here: the full triple.
42
+ def mr_number_with_part
43
+ mr_slug(volume, issue, article)
44
+ end
21
45
  end
22
46
  end
23
47
  end
@@ -14,6 +14,19 @@ module Pubid
14
14
  { key: :si_brochure, web: :si_brochure,
15
15
  title: "SI Brochure", short: "si-brochure" }
16
16
  end
17
+
18
+ # MR: `bipm.si-brochure.<edition>-<version>[.<lang>]` for the brochure
19
+ # itself, `bipm.si-brochure.<variant>` for a derived product. Exactly
20
+ # one of `variant` / `edition` is set (the grammar guarantees it).
21
+ def mr_type
22
+ "si-brochure"
23
+ end
24
+
25
+ # The edition is folded in here rather than emitted through
26
+ # `mr_edition`, whose base implementation expects a Components::Edition.
27
+ def mr_number_with_part
28
+ variant ? mr_slug(variant) : mr_slug(edition, version)
29
+ end
17
30
  end
18
31
  end
19
32
  end
@@ -11,7 +11,7 @@ module Pubid
11
11
  #
12
12
  # Group and type-word alternations are built from the shared vocabularies on
13
13
  # {Identifier} (sorted longest-first so e.g. "CCTF" is tried before "CCT").
14
- class Parser < Parslet::Parser
14
+ class Parser < ::Pubid::Parser::Grammar
15
15
  # --- primitives ---
16
16
  rule(:space) { str(" ") }
17
17
  rule(:digits) { match["0-9"].repeat(1) }
@@ -21,7 +21,11 @@ module Pubid
21
21
  (digits >> (str("-") >> digits).repeat).as(:number)
22
22
  end
23
23
  rule(:year) { match["0-9"].repeat(4, 4).as(:year) }
24
- rule(:lang) { match["EF"].as(:language) }
24
+ # "E"/"F" as BIPM prints them, plus the two-letter codes consumer
25
+ # references use (longest first, so "EN" is never read as a bare "E").
26
+ rule(:lang) do
27
+ (str("EN") | str("FR") | match["EF"]).as(:language)
28
+ end
25
29
  # "(YYYY)" or "(YYYY, E)"
26
30
  rule(:year_paren) do
27
31
  str("(") >> year >> (str(", ") >> lang).maybe >> str(")")
@@ -34,7 +38,7 @@ module Pubid
34
38
  tokens.sort_by { |t| -t.length }.map { |t| str(t) }.reduce(:|)
35
39
  end
36
40
 
37
- rule(:group) { alternation(Identifier::GROUPS).as(:group) }
41
+ rule(:group) { alternation(Identifier::PARSEABLE_GROUPS).as(:group) }
38
42
 
39
43
  rule(:type_abbrev) do
40
44
  alternation(Identifier::TYPE_CODES).as(:type_word)
@@ -49,6 +53,9 @@ module Pubid
49
53
  end
50
54
  rule(:connective) { str("de la") | str("du") }
51
55
  rule(:ordinal) { str("st") | str("nd") | str("rd") | str("th") }
56
+ # The word naming a meeting in French. BIPM prints it lowercase; a
57
+ # consumer reference capitalizes it, as relaton's `Id::TYPES` does.
58
+ rule(:meeting_word_fr) { str("Réunion") | str("réunion") }
52
59
 
53
60
  # --- committee documents ---
54
61
  # The trailing "(YYYY)" date is optional (`.maybe`) so a partial reference
@@ -68,6 +75,17 @@ module Pubid
68
75
  connective >> space >> group >> (space >> year_paren_nolang).maybe)
69
76
  .as(:committee_long_fr)
70
77
  end
78
+ # Loose consumer form: a French type name in the English (group-leading)
79
+ # word order, e.g. "CIPM Décision 101-1 (2012)". BIPM never prints this,
80
+ # so it renders back as the canonical "Décision 101-1 du CIPM (2012)".
81
+ # Tried after `committee_long_en`, which is what keeps the words both
82
+ # languages share ("Action", "Statement") English.
83
+ rule(:committee_group_fr) do
84
+ (group >> space >> type_name_fr >>
85
+ (space >> number).maybe >> (space >> year_paren_nolang).maybe)
86
+ .as(:committee_long_fr)
87
+ end
88
+
71
89
  # Bare MRA-interpretation form: a group directly followed by a document
72
90
  # number with no type word (e.g. "CIPM 2005-06"). Tried last, so typed
73
91
  # committee documents and meetings always win; only fires when a digit
@@ -78,13 +96,26 @@ module Pubid
78
96
  end
79
97
 
80
98
  # --- meetings ---
99
+ # Canonical: "<group> <n><ordinal> Meeting [(YYYY)]". The second branch is
100
+ # the loose consumer word order "<group> Meeting <n>", which relaton's
101
+ # bespoke grammar accepts; both build the same tree, so the loose form
102
+ # renders back in the canonical spelling.
81
103
  rule(:meeting_en) do
82
- (group >> space >> number >> ordinal >> space >>
83
- str("Meeting") >> (space >> year_paren).maybe).as(:meeting_en)
84
- end
104
+ ((group >> space >> number >> ordinal >> space >> str("Meeting") >>
105
+ (space >> year_paren).maybe) |
106
+ (group >> space >> str("Meeting") >> space >> number >>
107
+ (space >> year_paren).maybe)).as(:meeting_en)
108
+ end
109
+ # Canonical: "<group> <n><sup>e</sup> réunion [(YYYY)]". The second and
110
+ # third branches are the loose consumer spellings — the plain "e" ordinal
111
+ # and the "<group> Réunion <n>" word order.
85
112
  rule(:meeting_fr) do
86
- (group >> space >> number >> str("<sup>e</sup>") >> space >>
87
- str("réunion") >> (space >> year_paren).maybe).as(:meeting_fr)
113
+ ((group >> space >> number >> str("<sup>e</sup>") >> space >>
114
+ str("réunion") >> (space >> year_paren).maybe) |
115
+ (group >> space >> number >> str("e") >> space >> meeting_word_fr >>
116
+ (space >> year_paren).maybe) |
117
+ (group >> space >> meeting_word_fr >> space >> number >>
118
+ (space >> year_paren).maybe)).as(:meeting_fr)
88
119
  end
89
120
 
90
121
  # --- Metrologia journal ---
@@ -99,8 +130,12 @@ module Pubid
99
130
  rule(:edition) { (digits >> str("e")).as(:edition) }
100
131
  rule(:version) { (str("v") >> match["0-9."].repeat(1)).as(:version) }
101
132
  rule(:years) { (digits >> (str("/") >> digits).maybe).as(:years) }
133
+ # The "BIPM " prefix is optional, as it is on the variant and section
134
+ # rules: `si-brochure.yaml` stores the docnumber WITHOUT it, and
135
+ # `Relaton::Bipm::Bibliography.search` strips a leading "BIPM " before it
136
+ # ever reaches pubid. Either spelling renders back with the prefix.
102
137
  rule(:si_brochure) do
103
- (str("BIPM SI Brochure") >> space >>
138
+ ((str("BIPM SI Brochure") | str("SI Brochure")) >> space >>
104
139
  (str("sur le SI") >> space).maybe >>
105
140
  edition >> space >> version >> space >>
106
141
  str("(") >> years >> str(", ") >> lang >> str(")")).as(:si_brochure)
@@ -120,6 +155,24 @@ module Pubid
120
155
  brochure_variant).as(:si_brochure_variant)
121
156
  end
122
157
 
158
+ # Bare and sectioned SI Brochure references: "SI Brochure",
159
+ # "SI Brochure Part 1". Neither names an edition, so both are partial
160
+ # references whose `number` stays nil and which wildcard every edition —
161
+ # the same reading a date-less "CCTF REC 2" gets. "Part N" points at a
162
+ # section INSIDE the brochure, not a separate record, so it rides in the
163
+ # shared `part` attribute and stays out of the index key, exactly as the
164
+ # MEP/guide "Part N.M" tail does. Listed after the edition and variant
165
+ # rules for readability, not for correctness: Parslet threads
166
+ # `consume_all` down through an alternation, so an alternative that
167
+ # matches only a PREFIX ("SI Brochure" of "SI Brochure Concise") counts
168
+ # as a failure and the next one is tried whatever the order. Order only
169
+ # decides between two alternatives that BOTH consume the whole input.
170
+ rule(:si_brochure_section) do
171
+ ((str("BIPM SI Brochure") | str("SI Brochure")) >>
172
+ (space >> str("Part") >> space >> digits.as(:part)).maybe)
173
+ .as(:si_brochure_section)
174
+ end
175
+
123
176
  # Shared "Appendix N [Annex N] Part N[.M]" tail carried by the full
124
177
  # docidentifier content of MEPs and CC guides (the "Appendix 2 Part 1.1"
125
178
  # or "Appendix 2 Annex 2 Part 1" suffix on "BIPM SI MEP …" etc.).
@@ -166,12 +219,12 @@ module Pubid
166
219
  # a digit → meeting; a type word → committee.
167
220
  rule(:group_leading) do
168
221
  meeting_en | meeting_fr | committee_short | committee_long_en |
169
- committee_bare
222
+ committee_group_fr | committee_bare
170
223
  end
171
224
 
172
225
  rule(:identifier) do
173
- metrologia | si_brochure | si_brochure_variant | mep | guide |
174
- committee_long_fr | group_leading
226
+ metrologia | si_brochure | si_brochure_variant | si_brochure_section |
227
+ mep | guide | committee_long_fr | group_leading
175
228
  end
176
229
 
177
230
  root(:identifier)
@@ -75,6 +75,9 @@ module Pubid
75
75
  # Appendix / derived-product form indexed by relaton, keyed on the full
76
76
  # "BIPM SI Brochure …" primary docidentifier content.
77
77
  return "BIPM SI Brochure #{id.variant}" if id.variant
78
+ # Bare or sectioned reference: no edition means no version and no year
79
+ # to print either — just the brochure, optionally down to one part.
80
+ return "BIPM SI Brochure#{part_segment(id)}" unless id.edition
78
81
 
79
82
  phrase = id.language == "F" ? "sur le SI " : ""
80
83
  lang = id.language ? ", #{id.language}" : ""
@@ -82,6 +85,11 @@ module Pubid
82
85
  "(#{id.years}#{lang})"
83
86
  end
84
87
 
88
+ # Optional " Part <n>" segment of an SI Brochure section reference.
89
+ def part_segment(id)
90
+ id.part ? " Part #{id.part}" : ""
91
+ end
92
+
85
93
  # MEP: short docnumber ("SI MEP <code>" / "Rapport BIPM-YYYY/NN"), or the
86
94
  # full "BIPM … Appendix N [Annex N] Part N.M" content the crawler keys on.
87
95
  def render_mep(id)