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
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Ieee
5
6
  autoload :Aiee, "#{__dir__}/aiee"
@@ -28,28 +28,44 @@ module Pubid
28
28
  private
29
29
 
30
30
  def build_rfc(data)
31
- Identifiers::Rfc.new(number: data[:number].to_s)
31
+ Identifiers::Rfc.new(number: unpad(data[:number]))
32
32
  end
33
33
 
34
+ # The series token only selects the class: it is derived back from the
35
+ # class (Identifiers::Std::SERIES) rather than stored, since `_type`
36
+ # already carries it.
34
37
  def build_subseries(data)
35
- series = data[:series].to_s
36
- klass = Identifiers.const_get(SUBSERIES_CLASSES.fetch(series))
37
- klass.new(series: series, number: data[:number].to_s)
38
+ klass = Identifiers.const_get(
39
+ SUBSERIES_CLASSES.fetch(data[:series].to_s),
40
+ )
41
+ klass.new(number: unpad(data[:number]))
42
+ end
43
+
44
+ # Strip the zero-padding the RFC editor's rfc-index.xml writes ("STD0066",
45
+ # "RFC0001") so the canonical printed form is the unpadded one. The
46
+ # look-ahead keeps the last digit, so an all-zero number stays "0" rather
47
+ # than becoming empty.
48
+ def unpad(number)
49
+ number.to_s.sub(/\A0+(?=\d)/, "")
38
50
  end
39
51
 
40
52
  def build_draft(data)
41
53
  full = "draft-#{data[:draft_rest]}"
42
- name, version = split_draft_version(full)
43
- Identifiers::InternetDraft.new(name: name, version: version)
54
+ slug, version = split_draft_version(full)
55
+ Identifiers::InternetDraft.new(number: slug, version: version)
44
56
  end
45
57
 
46
58
  # An Internet-Draft version is a trailing "-NN" of *exactly* two digits at
47
59
  # the very end of the slug. A three-digit topic tail (e.g. "...-256") is
48
60
  # NOT a version: there the char before the last two digits is itself a
49
- # digit, not "-", so the guard below leaves it in the name. This also
61
+ # digit, not "-", so the guard below leaves it in the slug. This also
50
62
  # means a (non-existent in practice) revision >= 100 would be read as part
51
- # of the name — IETF drafts are always two-digit-versioned, so that case
63
+ # of the slug — IETF drafts are always two-digit-versioned, so that case
52
64
  # never arises. Either way the printed string round-trips exactly.
65
+ #
66
+ # Dotted slugs need no special handling here: only the final three
67
+ # characters are inspected, so "draft-ietf-pilc-2.5g3g-12" splits into
68
+ # ("draft-ietf-pilc-2.5g3g", "12").
53
69
  def split_draft_version(full)
54
70
  if full.length > 3 && full[-3] == "-" && full[-2..].match?(/\A\d\d\z/)
55
71
  [full[0...-3], full[-2..]]
@@ -7,22 +7,13 @@ module Pubid
7
7
  # Pubid::Ietf::Identifiers descend from this class, so a parsed IETF id is
8
8
  # an instance of Pubid::Ietf::Identifier.
9
9
  #
10
- # All families are flat, supplement-free, and use plain string attributes:
10
+ # All families are flat, supplement-free, and use plain string attributes.
11
+ # Those attributes are declared on the LEAVES by Identifiers::Serialization
12
+ # (see the determinism note there — never re-add `attribute :number` here):
11
13
  # * Rfc -> number ("2119")
12
- # * Bcp/Std/Fyi -> series ("BCP"/"STD"/"FYI") + number ("3")
13
- # * InternetDraft -> name ("draft-giuliano-treedn") + optional version
14
+ # * Bcp/Std/Fyi -> number ("3") + a derived `series` reader
15
+ # * InternetDraft -> number ("draft-giuliano-treedn") + optional version
14
16
  class Identifier < ::Pubid::Identifier
15
- # Numeric part of an RFC or sub-series id, kept as a string (no zero-pad
16
- # in the printed form, so "2119" not "02119").
17
- attribute :number, :string
18
- # Sub-series token for BCP/STD/FYI ("BCP"/"STD"/"FYI"); nil otherwise.
19
- attribute :series, :string
20
- # Full Internet-Draft slug including the leading "draft-"; nil otherwise.
21
- attribute :name, :string
22
- # Internet-Draft version, a two-digit string preserving zero-pad ("02");
23
- # nil for the unversioned "latest" sibling.
24
- attribute :version, :string
25
-
26
17
  # Polymorphic type map for lutaml::Model key_value (de)serialization: maps
27
18
  # each subclass's polymorphic_name to its class name so a stored hash
28
19
  # rebuilds the correct identifier type via the shared from_hash dispatch.
@@ -35,16 +26,10 @@ module Pubid
35
26
  "Pubid::Ietf::Identifiers::InternetDraft",
36
27
  }.freeze
37
28
 
38
- # Only these keys serialize; the inherited Pubid::Identifier attributes
39
- # are intentionally dropped. nil-valued attributes are stripped by the
40
- # base canonicalizer, so each family serializes just the keys it uses.
41
- key_value do
42
- map "_type", to: :_type, polymorphic_map: IETF_TYPE_MAP
43
- map "number", to: :number
44
- map "series", to: :series
45
- map "name", to: :name
46
- map "version", to: :version
47
- end
29
+ # NOTE: no `key_value` block here on purpose. The mapping lives on the
30
+ # leaves (Identifiers::Serialization); a block on this shared parent would
31
+ # be inherited-and-merged by every leaf and re-emit keys the leaf did not
32
+ # declare.
48
33
 
49
34
  # Publisher is always "IETF". A plain constant (not a `publisher` method)
50
35
  # so it doesn't shadow the inherited lutaml `publisher` attribute, which
@@ -62,14 +47,26 @@ module Pubid
62
47
  # Parse an IETF identifier string into the appropriate identifier object.
63
48
  # @param identifier [String] the IETF identifier string to parse
64
49
  # @return [Identifier] the concrete identifier object
65
- # @raise [RuntimeError] if parsing fails
50
+ # @raise [Pubid::Errors::InvalidInputError] if the input exceeds
51
+ # Pubid::MAX_INPUT_LENGTH
52
+ # @raise [Pubid::Errors::ParseError] if parsing fails
66
53
  def self.parse(identifier)
54
+ # Inline length guard (CodeQL rb/polynomial-redos barrier) — must be
55
+ # the first statement. This class-level funnel is what relaton reaches
56
+ # directly through `pubid_class: ::Pubid::Ietf::Identifier`, so it
57
+ # cannot rely on the guard in Pubid::Ietf.parse.
58
+ unless identifier.is_a?(String)
59
+ raise Pubid::Errors::InvalidInputError,
60
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
61
+ end
62
+
63
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
64
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
65
+ end
66
+
67
67
  parsed = Parser.parse(identifier)
68
68
  Builder.build(parsed)
69
- rescue Parslet::ParseFailed => e
70
- raise "Failed to parse IETF identifier '#{identifier}': #{e.message}"
71
69
  end
72
70
  end
73
-
74
71
  end
75
72
  end
@@ -5,6 +5,17 @@ module Pubid
5
5
  module Identifiers
6
6
  # A BCP (Best Current Practice) sub-series id, e.g. "BCP 3".
7
7
  class Bcp < Identifier
8
+ include Serialization
9
+
10
+ # The series token is derived from the class, not stored:
11
+ # `_type` already encodes it, so serializing `series` would
12
+ # duplicate the type in every index row.
13
+ SERIES = "BCP"
14
+
15
+ def series
16
+ SERIES
17
+ end
18
+
8
19
  TYPED_STAGES = [
9
20
  Pubid::Components::TypedStage.new(
10
21
  code: :bcp,
@@ -5,6 +5,17 @@ module Pubid
5
5
  module Identifiers
6
6
  # An FYI (For Your Information) sub-series id, e.g. "FYI 1".
7
7
  class Fyi < Identifier
8
+ include Serialization
9
+
10
+ # The series token is derived from the class, not stored:
11
+ # `_type` already encodes it, so serializing `series` would
12
+ # duplicate the type in every index row.
13
+ SERIES = "FYI"
14
+
15
+ def series
16
+ SERIES
17
+ end
18
+
8
19
  TYPED_STAGES = [
9
20
  Pubid::Components::TypedStage.new(
10
21
  code: :fyi,
@@ -5,9 +5,25 @@ module Pubid
5
5
  module Identifiers
6
6
  # An Internet-Draft, e.g. "draft-giuliano-treedn-02" (versioned) or
7
7
  # "draft-giuliano-treedn" (the unversioned "latest" sibling). The full
8
- # slug (including the leading "draft-") is stored in `name`; the optional
9
- # trailing two-digit version in `version`.
8
+ # slug (including the leading "draft-") is stored in `number` — it is the
9
+ # relaton-index narrowing key, shared by a draft's unversioned aggregator
10
+ # row and all of its versions; the optional trailing two-digit version is
11
+ # in `version`.
10
12
  class InternetDraft < Identifier
13
+ include Serialization
14
+
15
+ # Two-digit version string, zero-pad preserved ("02"); nil for the
16
+ # unversioned "latest" sibling, in which case the canonical to_hash
17
+ # drops the key entirely.
18
+ attribute :version, :string
19
+
20
+ # Merged with the block Serialization installs (lutaml combines a
21
+ # mixin-installed key_value with a second in-class one), so the
22
+ # serialized shape is {_type, number} plus `version` when set.
23
+ key_value do
24
+ map "version", to: :version
25
+ end
26
+
11
27
  TYPED_STAGES = [
12
28
  Pubid::Components::TypedStage.new(
13
29
  code: :internet_draft,
@@ -5,6 +5,8 @@ module Pubid
5
5
  module Identifiers
6
6
  # An RFC (Request for Comments), e.g. "RFC 2119".
7
7
  class Rfc < Identifier
8
+ include Serialization
9
+
8
10
  TYPED_STAGES = [
9
11
  Pubid::Components::TypedStage.new(
10
12
  code: :rfc,
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ietf
5
+ module Identifiers
6
+ # Installs the flat, index-friendly attribute set and the shared
7
+ # `key_value` block on the five concrete IETF leaves (Rfc, Bcp, Std, Fyi,
8
+ # InternetDraft).
9
+ #
10
+ # Why the leaves and not the shared Pubid::Ietf::Identifier base:
11
+ # `number` redefines the parent ::Pubid::Identifier's
12
+ # `attribute :number, Components::Code` as a plain :string. Declaring that
13
+ # on a class the concrete types INHERIT from resolves against the parent
14
+ # definition nondeterministically under multi-flavor load — it passes an
15
+ # IETF-only run and can flip `root.number` to "" under the full suite (the
16
+ # IEEE lesson; see lib/pubid/ieee/identifiers/code_number.rb and
17
+ # CLAUDE.md). Declaring `number` directly on each leaf removes the
18
+ # ambiguity at the point of use.
19
+ #
20
+ # This matters because relaton-index bsearches on `id.root.number.to_s`,
21
+ # and every IETF identifier is its own root (the flavor has no wrapper or
22
+ # supplement types), so an empty key silently defeats the search for every
23
+ # row. spec/pubid/ietf/root_number_spec.rb guards it and must run under
24
+ # the FULL suite to be meaningful.
25
+ #
26
+ # `series` is deliberately NOT an attribute here: `_type` already encodes
27
+ # it, so storing it would duplicate the type in every sub-series row. The
28
+ # three sub-series leaves expose it as a derived reader instead.
29
+ module Serialization
30
+ def self.included(base)
31
+ # Document number of an RFC or sub-series id ("2119", "66"), kept as
32
+ # a string so the printed form carries no zero-pad; for an
33
+ # Internet-Draft, the version-less slug INCLUDING the leading
34
+ # "draft-" ("draft-ietf-quic-transport"). The slug is the right index
35
+ # key for the same reason the number is for an RFC: a draft's
36
+ # unversioned aggregator row and every one of its versions share it,
37
+ # so a document and its variants cluster into one bucket.
38
+ base.attribute :number, :string
39
+
40
+ # Only these keys serialize; the inherited Pubid::Identifier
41
+ # attributes are intentionally dropped. InternetDraft merges its own
42
+ # `version` mapping on top of this block.
43
+ base.key_value do
44
+ map "_type", to: :_type, polymorphic_map: Identifier::IETF_TYPE_MAP
45
+ map "number", to: :number
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -5,6 +5,17 @@ module Pubid
5
5
  module Identifiers
6
6
  # An STD (Internet Standard) sub-series id, e.g. "STD 66".
7
7
  class Std < Identifier
8
+ include Serialization
9
+
10
+ # The series token is derived from the class, not stored:
11
+ # `_type` already encodes it, so serializing `series` would
12
+ # duplicate the type in every index row.
13
+ SERIES = "STD"
14
+
15
+ def series
16
+ SERIES
17
+ end
18
+
8
19
  TYPED_STAGES = [
9
20
  Pubid::Components::TypedStage.new(
10
21
  code: :std,
@@ -3,6 +3,7 @@
3
3
  module Pubid
4
4
  module Ietf
5
5
  module Identifiers
6
+ autoload :Serialization, "#{__dir__}/identifiers/serialization"
6
7
  autoload :Rfc, "#{__dir__}/identifiers/rfc"
7
8
  autoload :Bcp, "#{__dir__}/identifiers/bcp"
8
9
  autoload :Std, "#{__dir__}/identifiers/std"
@@ -12,24 +12,42 @@ module Pubid
12
12
  # * draft -> { draft_rest: } (everything after "draft-"; the version
13
13
  # split is done in the Builder to keep the digit-tail
14
14
  # heuristic in one place)
15
- class Parser < Parslet::Parser
15
+ class Parser < ::Pubid::Parser::Grammar
16
16
  rule(:space) { str(" ") }
17
17
  rule(:digit) { match["0-9"] }
18
18
  rule(:digits) { digit.repeat(1) }
19
19
 
20
- # "RFC 2119"
21
- rule(:rfc) { str("RFC") >> space >> digits.as(:number) }
20
+ # "RFC 2119", and the zero-padded, space-less spelling the RFC editor's
21
+ # rfc-index.xml emits ("RFC0001"). See #subseries for why.
22
+ rule(:rfc) { str("RFC") >> space.maybe >> digits.as(:number) }
22
23
 
23
- # "BCP 3" / "STD 66" / "FYI 1"
24
+ # "BCP 3" / "STD 66" / "FYI 1".
25
+ #
26
+ # The space is optional and leading zeros are allowed because
27
+ # rfc-index.xml writes the sub-series membership of an RFC in the padded,
28
+ # space-less form -- <is-also><doc-id>STD0066</doc-id></is-also> -- and
29
+ # relaton builds the STD/BCP/FYI <-> RFC cross-references from exactly
30
+ # those elements without normalizing them itself. The Builder strips the
31
+ # pad, so the printed form stays the canonical "STD 66".
24
32
  rule(:subseries) do
25
33
  (str("BCP") | str("STD") | str("FYI")).as(:series) >>
26
- space >> digits.as(:number)
34
+ space.maybe >> digits.as(:number)
27
35
  end
28
36
 
29
- # Internet-Draft slug characters (lowercase, digits, and the "-", "+", "_"
30
- # separators seen in the corpus). The whole remainder after "draft-" is
31
- # captured; the Builder anchors the optional trailing "-NN" version.
32
- rule(:draft_rest) { match['a-z0-9+_\-'].repeat(1).as(:draft_rest) }
37
+ # Internet-Draft slug characters: lowercase, digits and the "-", "+", "_"
38
+ # separators, plus two historical shapes the published corpus carries --
39
+ # a dot inside a topic token ("draft-ietf-pilc-2.5g3g-12") and uppercase
40
+ # letters in protocol/organisation names ("draft-chapin-clnp-ISO8473-00").
41
+ # 50 of the 166,740 relaton-data-ids identifiers need those two
42
+ # characters; nothing else outside [a-z0-9-] occurs in the corpus.
43
+ #
44
+ # Widening the tail is unambiguous: the top-level alternation is keyed on
45
+ # the leading token (RFC / BCP|STD|FYI / draft-), so a wider draft tail
46
+ # cannot make another branch match.
47
+ #
48
+ # The whole remainder after "draft-" is captured; the Builder anchors the
49
+ # optional trailing "-NN" version.
50
+ rule(:draft_rest) { match['a-zA-Z0-9+_.\-'].repeat(1).as(:draft_rest) }
33
51
  rule(:draft) { str("draft-") >> draft_rest }
34
52
 
35
53
  rule(:identifier) { rfc | subseries | draft }
@@ -10,17 +10,55 @@ module Pubid
10
10
  # "draft-giuliano-treedn-02" / "draft-giuliano-treedn"
11
11
  class Renderer < ::Pubid::Renderers::Base
12
12
  def render(context: nil, **_opts)
13
- id = @id
13
+ require_number!(@id)
14
+ render_identifier(@id)
15
+ end
16
+
17
+ private
14
18
 
19
+ # For an Internet-Draft, `number` holds the version-less slug including
20
+ # the leading "draft-"; for the sub-series, `series` is derived from the
21
+ # class rather than stored. The sub-series arm names its three classes
22
+ # explicitly instead of being a catch-all `else`, because a bare
23
+ # Pubid::Ietf::Identifier has no `series` reader at all.
24
+ def render_identifier(id)
15
25
  case id
16
- when Identifiers::Rfc
17
- "RFC #{id.number}"
18
- when Identifiers::InternetDraft
19
- id.version ? "#{id.name}-#{id.version}" : id.name
20
- else # Bcp/Std/Fyi sub-series
26
+ when Identifiers::Rfc then "RFC #{id.number}"
27
+ when Identifiers::InternetDraft then render_draft(id)
28
+ when Identifiers::Bcp, Identifiers::Std, Identifiers::Fyi
21
29
  "#{id.series} #{id.number}"
30
+ else unsupported!(id)
22
31
  end
23
32
  end
33
+
34
+ def render_draft(id)
35
+ id.version ? "#{id.number}-#{id.version}" : id.number
36
+ end
37
+
38
+ # Fail loudly rather than render a nil/partial string. `number` carries
39
+ # the whole document identity for every IETF type, so a nil one means the
40
+ # object was built from something that is not a current IETF hash — most
41
+ # likely a pre-`number` index row, which stored an Internet-Draft slug
42
+ # under `name` (a key nothing reads any more; lutaml ignores it
43
+ # silently). Such a row would otherwise deserialize into an identifier
44
+ # that renders "" or "-02" and keys the index under "", with no error
45
+ # anywhere — exactly the silent-wrong-lookup failure this flavor's
46
+ # round-trip specs exist to prevent. Regenerate the
47
+ # relaton-data-{rfcs,rfcsubseries,ids} indexes instead.
48
+ def require_number!(id)
49
+ return unless id.respond_to?(:number)
50
+ return unless id.number.nil? || id.number.empty?
51
+
52
+ raise ArgumentError,
53
+ "Cannot render #{id.class} with an empty number " \
54
+ "(a pre-`number` index row stored the draft slug under `name`; " \
55
+ "regenerate the index)"
56
+ end
57
+
58
+ def unsupported!(id)
59
+ raise ArgumentError,
60
+ "Cannot render #{id.class}: not a concrete IETF identifier"
61
+ end
24
62
  end
25
63
  end
26
64
  end
@@ -7,17 +7,31 @@ module Pubid
7
7
  # BCP/STD/FYI -> urn:ietf:bcp:3 / urn:ietf:std:66 / urn:ietf:fyi:1
8
8
  # Internet-Draft -> urn:ietf:id:draft-giuliano-treedn[:02]
9
9
  class UrnGenerator < Pubid::UrnGenerator::Base
10
+ # The sub-series arm names its three classes explicitly rather than being
11
+ # a catch-all `else`, because a bare Pubid::Ietf::Identifier has no
12
+ # `series` reader at all — `series` is derived from the concrete class.
10
13
  def generate
11
14
  case identifier
12
- when Identifiers::Rfc
13
- "urn:ietf:rfc:#{identifier.number}"
14
- when Identifiers::InternetDraft
15
- base = "urn:ietf:id:#{identifier.name}"
16
- identifier.version ? "#{base}:#{identifier.version}" : base
17
- else # Bcp/Std/Fyi sub-series
15
+ when Identifiers::Rfc then "urn:ietf:rfc:#{identifier.number}"
16
+ when Identifiers::InternetDraft then draft_urn
17
+ when Identifiers::Bcp, Identifiers::Std, Identifiers::Fyi
18
18
  "urn:ietf:#{identifier.series.downcase}:#{identifier.number}"
19
+ else unsupported!
19
20
  end
20
21
  end
22
+
23
+ private
24
+
25
+ def draft_urn
26
+ base = "urn:ietf:id:#{identifier.number}"
27
+ identifier.version ? "#{base}:#{identifier.version}" : base
28
+ end
29
+
30
+ def unsupported!
31
+ raise ArgumentError,
32
+ "Cannot build a URN for #{identifier.class}: " \
33
+ "not a concrete IETF identifier"
34
+ end
21
35
  end
22
36
  end
23
37
  end
@@ -21,11 +21,19 @@ module Pubid
21
21
  when "rfc" then "RFC #{parts[0]}"
22
22
  when "bcp", "std", "fyi" then "#{type.upcase} #{parts[0]}"
23
23
  when "id" then draft_text(parts)
24
- else
25
- raise Errors::ParseError, "Invalid IETF URN type: #{type.inspect}"
24
+ else invalid_type!(type)
26
25
  end
27
26
  end
28
27
 
28
+ # `Errors` is defined under Pubid::UrnParser (the module), which is
29
+ # neither in this class's lexical scope nor an ancestor of Base, so the
30
+ # constant must be fully qualified — a bare `Errors::ParseError` raises
31
+ # NameError instead of the intended error.
32
+ def invalid_type!(type)
33
+ raise Pubid::UrnParser::Errors::ParseError,
34
+ "Invalid IETF URN type: #{type.inspect}"
35
+ end
36
+
29
37
  def draft_text(parts)
30
38
  name, version = parts
31
39
  version ? "#{name}-#{version}" : name
data/lib/pubid/ietf.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  # IETF flavor: one module covering the three IETF document families that share
5
6
  # the IETF publisher and the `urn:ietf:` namespace —
@@ -24,8 +25,17 @@ module Pubid
24
25
  # Parse an IETF identifier string
25
26
  def self.parse(identifier)
26
27
  # Inline length guard (CodeQL rb/polynomial-redos barrier) before the
27
- # normalization/parse path — must be the first statement.
28
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE if identifier.length > Pubid::MAX_INPUT_LENGTH
28
+ # normalization/parse path. The type check goes above it because it is
29
+ # what makes `.length` safe to call; the length check itself must still
30
+ # be the first thing the input string meets.
31
+ unless identifier.is_a?(String)
32
+ raise Pubid::Errors::InvalidInputError,
33
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
34
+ end
35
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
36
+ raise Pubid::Errors::InvalidInputError,
37
+ Pubid::INPUT_TOO_LONG_MESSAGE
38
+ end
29
39
 
30
40
  Identifier.parse(identifier)
31
41
  end
@@ -14,12 +14,19 @@ module Pubid
14
14
  # Parse an IHO identifier string into an identifier object
15
15
  # @param identifier [String] The IHO identifier string to parse
16
16
  # @return [Pubid::Iho::Identifier] The appropriate identifier object
17
- # @raise [Parslet::ParseFailed] If parsing fails
17
+ # @raise [Pubid::Errors::ParseError] If parsing fails
18
18
  def self.parse(identifier)
19
+ unless identifier.is_a?(String)
20
+ raise Pubid::Errors::InvalidInputError,
21
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
22
+ end
23
+
24
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
25
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
26
+ end
27
+
19
28
  parsed = Parser.parse(identifier)
20
29
  Builder.build(parsed)
21
- rescue Parslet::ParseFailed => e
22
- raise "Failed to parse IHO identifier '#{identifier}': #{e.message}"
23
30
  end
24
31
 
25
32
  # `number` overrides the generic Components::Code attribute with a plain
@@ -5,7 +5,7 @@ require "parslet"
5
5
  module Pubid
6
6
  module Iho
7
7
  # Parser class for IHO identifiers
8
- class Parser < Parslet::Parser
8
+ class Parser < ::Pubid::Parser::Grammar
9
9
  rule(:space) { str(" ") }
10
10
  rule(:dash) { str("-") }
11
11
  rule(:dot) { str(".") }
data/lib/pubid/iho.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Iho
5
6
  extend Pubid::PrefixesSupport
@@ -44,20 +44,38 @@ module Pubid
44
44
  end
45
45
 
46
46
  def self.parse(identifier)
47
+ unless identifier.is_a?(String)
48
+ raise Pubid::Errors::InvalidInputError,
49
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
50
+ end
51
+
47
52
  if identifier.length > Pubid::MAX_INPUT_LENGTH
48
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
53
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
49
54
  end
50
55
 
56
+ # The two guards above must stay OUTSIDE build_identifier: this method
57
+ # rescues ArgumentError to convert the Builder's check-digit and
58
+ # length validation into a parse failure, and that rescue used to
59
+ # swallow the guards' own ArgumentError and re-raise it as a
60
+ # RuntimeError - so ISBN was the one guarded flavor where an over-long
61
+ # input did not surface as ArgumentError.
62
+ build_identifier(identifier)
63
+ end
64
+
65
+ # @raise [Pubid::Errors::ParseError] if the string is not a valid ISBN
66
+ def self.build_identifier(identifier)
51
67
  parsed = Parser.parse(identifier)
52
68
  Builder.build(parsed)
53
- rescue Parslet::ParseFailed => e
54
- raise "Failed to parse ISBN '#{identifier}': #{e.message}"
55
69
  rescue ArgumentError => e
56
- # Builder validates length/check-digit; surface as a parse error so
57
- # the error contract matches other flavors (always raises a string
58
- # beginning with "Failed to parse").
59
- raise "Failed to parse ISBN '#{identifier}': #{e.message}"
70
+ # The Builder validates length and check digit. Surface that as a parse
71
+ # failure, so every ISBN rejection reaches the caller as the same class
72
+ # a grammar rejection does.
73
+ raise Pubid::Errors::ParseError.new(
74
+ "invalid ISBN '#{identifier}': #{e.message}",
75
+ input: identifier, flavor: "isbn",
76
+ )
60
77
  end
78
+ private_class_method :build_identifier
61
79
  end
62
80
  end
63
81
  end
@@ -12,7 +12,7 @@ module Pubid
12
12
  # ISBN-10 final digit may be "X" (check digit value 10). Hyphens are
13
13
  # optional and may appear in any group position; they are preserved
14
14
  # for round-trip rendering.
15
- class Parser < Parslet::Parser
15
+ class Parser < ::Pubid::Parser::Grammar
16
16
  rule(:space) { str(" ") }
17
17
  rule(:colon) { str(":") }
18
18
  rule(:hyphen) { str("-") }
data/lib/pubid/isbn.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  # ISBN flavor — bibliographic book identifier per ISO 2108.
5
6
  #