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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +33 -11
  3. data/conformance/pending.yaml +4 -0
  4. data/lib/pubid/adobe/identifier.rb +1 -3
  5. data/lib/pubid/adobe/parser.rb +10 -2
  6. data/lib/pubid/adobe.rb +1 -0
  7. data/lib/pubid/amca/builder.rb +2 -2
  8. data/lib/pubid/amca/identifiers/base.rb +49 -27
  9. data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
  10. data/lib/pubid/amca/identifiers/publication.rb +13 -9
  11. data/lib/pubid/amca/identifiers/standard.rb +2 -0
  12. data/lib/pubid/amca/parser.rb +1 -1
  13. data/lib/pubid/amca/renderer.rb +4 -4
  14. data/lib/pubid/amca/urn_generator.rb +2 -2
  15. data/lib/pubid/amca.rb +2 -1
  16. data/lib/pubid/ansi/builder.rb +2 -2
  17. data/lib/pubid/ansi/identifier.rb +18 -0
  18. data/lib/pubid/ansi/parser.rb +1 -1
  19. data/lib/pubid/ansi/renderer.rb +2 -2
  20. data/lib/pubid/ansi.rb +5 -5
  21. data/lib/pubid/api/builder.rb +16 -2
  22. data/lib/pubid/api/identifier.rb +25 -4
  23. data/lib/pubid/api/identifiers/mpms.rb +10 -8
  24. data/lib/pubid/api/parser.rb +1 -1
  25. data/lib/pubid/api/renderer.rb +16 -7
  26. data/lib/pubid/api/single_identifier.rb +5 -12
  27. data/lib/pubid/api.rb +1 -0
  28. data/lib/pubid/ashrae/builder.rb +31 -10
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
  31. data/lib/pubid/ashrae/identifiers/base.rb +81 -5
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
  33. data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
  34. data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
  35. data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
  36. data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
  37. data/lib/pubid/ashrae/parser.rb +23 -8
  38. data/lib/pubid/ashrae/renderer.rb +6 -6
  39. data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
  40. data/lib/pubid/ashrae/urn_generator.rb +8 -2
  41. data/lib/pubid/ashrae.rb +7 -1
  42. data/lib/pubid/asme/builder.rb +11 -2
  43. data/lib/pubid/asme/identifier.rb +9 -0
  44. data/lib/pubid/asme/identifiers/standard.rb +86 -2
  45. data/lib/pubid/asme/parser.rb +1 -1
  46. data/lib/pubid/asme/renderer.rb +3 -3
  47. data/lib/pubid/asme/single_identifier.rb +8 -1
  48. data/lib/pubid/asme/urn_generator.rb +2 -2
  49. data/lib/pubid/asme.rb +1 -0
  50. data/lib/pubid/astm/builder.rb +10 -3
  51. data/lib/pubid/astm/identifier.rb +9 -0
  52. data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
  53. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  54. data/lib/pubid/astm/identifiers/data_series.rb +2 -0
  55. data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
  56. data/lib/pubid/astm/identifiers/manual.rb +10 -0
  57. data/lib/pubid/astm/identifiers/monograph.rb +2 -0
  58. data/lib/pubid/astm/identifiers/research_report.rb +10 -0
  59. data/lib/pubid/astm/identifiers/standard.rb +10 -0
  60. data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
  61. data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
  62. data/lib/pubid/astm/identifiers.rb +1 -0
  63. data/lib/pubid/astm/parser.rb +1 -1
  64. data/lib/pubid/astm/renderer.rb +1 -1
  65. data/lib/pubid/astm/single_identifier.rb +31 -1
  66. data/lib/pubid/astm/urn_generator.rb +7 -5
  67. data/lib/pubid/astm.rb +1 -0
  68. data/lib/pubid/bipm/builder.rb +66 -10
  69. data/lib/pubid/bipm/identifier.rb +134 -9
  70. data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
  71. data/lib/pubid/bipm/identifiers/guide.rb +11 -0
  72. data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
  73. data/lib/pubid/bipm/identifiers/mep.rb +12 -0
  74. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  75. data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
  76. data/lib/pubid/bipm/parser.rb +65 -12
  77. data/lib/pubid/bipm/renderer.rb +8 -0
  78. data/lib/pubid/bipm/urn_parser.rb +11 -3
  79. data/lib/pubid/bipm.rb +7 -1
  80. data/lib/pubid/bsi/builder.rb +48 -38
  81. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  82. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  83. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +0 -1
  84. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  85. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  86. data/lib/pubid/bsi/identifiers/handbook.rb +0 -2
  87. data/lib/pubid/bsi/identifiers/practice_guide.rb +0 -1
  88. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  89. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  90. data/lib/pubid/bsi/parser.rb +1 -1
  91. data/lib/pubid/bsi/renderer.rb +63 -51
  92. data/lib/pubid/bsi/single_identifier.rb +16 -7
  93. data/lib/pubid/bsi/urn_generator.rb +11 -2
  94. data/lib/pubid/bsi.rb +7 -1
  95. data/lib/pubid/builder/base.rb +18 -8
  96. data/lib/pubid/bundled_identifier.rb +16 -6
  97. data/lib/pubid/calconnect/identifier.rb +9 -5
  98. data/lib/pubid/calconnect/parser.rb +1 -1
  99. data/lib/pubid/calconnect.rb +7 -1
  100. data/lib/pubid/ccsds/identifier.rb +9 -2
  101. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  102. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  103. data/lib/pubid/ccsds/parser.rb +1 -1
  104. data/lib/pubid/ccsds/single_identifier.rb +2 -2
  105. data/lib/pubid/ccsds.rb +1 -0
  106. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  107. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  108. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  109. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  110. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  111. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  112. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  115. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  116. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  117. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  118. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  119. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  120. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  123. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  124. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  125. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  126. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  127. data/lib/pubid/cen_cenelec.rb +28 -10
  128. data/lib/pubid/cie/builder.rb +10 -0
  129. data/lib/pubid/cie/identifier.rb +6 -1
  130. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  131. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  132. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  133. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  134. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  135. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  136. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  137. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  138. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  139. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  140. data/lib/pubid/cie/parser.rb +1 -1
  141. data/lib/pubid/cie.rb +7 -1
  142. data/lib/pubid/conformance/checks.rb +72 -0
  143. data/lib/pubid/conformance/corpus/case.rb +55 -0
  144. data/lib/pubid/conformance/corpus.rb +36 -0
  145. data/lib/pubid/conformance/generator.rb +212 -0
  146. data/lib/pubid/conformance/pending.rb +63 -0
  147. data/lib/pubid/conformance/runner.rb +153 -0
  148. data/lib/pubid/conformance.rb +55 -0
  149. data/lib/pubid/core.rb +2 -0
  150. data/lib/pubid/csa/builder.rb +47 -47
  151. data/lib/pubid/csa/composite_identifier.rb +14 -19
  152. data/lib/pubid/csa/identifier.rb +143 -81
  153. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  154. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  155. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  156. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  157. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  158. data/lib/pubid/csa/identifiers/package.rb +2 -2
  159. data/lib/pubid/csa/parser.rb +6 -2
  160. data/lib/pubid/csa/renderer.rb +13 -5
  161. data/lib/pubid/csa/single_identifier.rb +93 -4
  162. data/lib/pubid/csa/urn_generator.rb +9 -2
  163. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  164. data/lib/pubid/csa.rb +7 -1
  165. data/lib/pubid/doi/identifier.rb +6 -3
  166. data/lib/pubid/doi/parser.rb +1 -1
  167. data/lib/pubid/doi.rb +1 -0
  168. data/lib/pubid/easc/identifier.rb +0 -2
  169. data/lib/pubid/easc/parser.rb +10 -2
  170. data/lib/pubid/easc.rb +1 -0
  171. data/lib/pubid/ecma/builder.rb +23 -9
  172. data/lib/pubid/ecma/identifier.rb +81 -10
  173. data/lib/pubid/ecma/parser.rb +39 -4
  174. data/lib/pubid/ecma/renderer.rb +29 -8
  175. data/lib/pubid/ecma/urn_generator.rb +34 -13
  176. data/lib/pubid/ecma/urn_parser.rb +30 -14
  177. data/lib/pubid/ecma.rb +1 -0
  178. data/lib/pubid/errors.rb +80 -0
  179. data/lib/pubid/etsi/builder.rb +6 -1
  180. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  181. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  182. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  183. data/lib/pubid/etsi/parser.rb +1 -1
  184. data/lib/pubid/etsi.rb +1 -0
  185. data/lib/pubid/evs/builder.rb +26 -0
  186. data/lib/pubid/evs/identifier.rb +37 -0
  187. data/lib/pubid/evs/identifiers/national_adoption.rb +17 -0
  188. data/lib/pubid/evs/identifiers.rb +9 -0
  189. data/lib/pubid/evs/parser.rb +27 -0
  190. data/lib/pubid/evs/renderer.rb +30 -0
  191. data/lib/pubid/evs/urn_generator.rb +30 -0
  192. data/lib/pubid/evs/urn_parser.rb +80 -0
  193. data/lib/pubid/evs.rb +90 -0
  194. data/lib/pubid/export.rb +1 -0
  195. data/lib/pubid/format_detector.rb +1 -0
  196. data/lib/pubid/gb/identifier.rb +6 -3
  197. data/lib/pubid/gb/parser.rb +1 -1
  198. data/lib/pubid/gb.rb +1 -0
  199. data/lib/pubid/gost/builder.rb +46 -9
  200. data/lib/pubid/gost/identifier.rb +0 -2
  201. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  202. data/lib/pubid/gost/parser.rb +10 -2
  203. data/lib/pubid/gost.rb +1 -0
  204. data/lib/pubid/iala/identifier.rb +9 -6
  205. data/lib/pubid/iala/parser.rb +10 -2
  206. data/lib/pubid/iala/urn_parser.rb +6 -2
  207. data/lib/pubid/iala.rb +1 -0
  208. data/lib/pubid/iana/builder.rb +3 -1
  209. data/lib/pubid/iana/identifier.rb +74 -6
  210. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  211. data/lib/pubid/iana/parser.rb +1 -1
  212. data/lib/pubid/iana/renderer.rb +3 -0
  213. data/lib/pubid/iana/urn_generator.rb +5 -1
  214. data/lib/pubid/iana.rb +1 -0
  215. data/lib/pubid/identifier.rb +594 -10
  216. data/lib/pubid/identifier_metadata.rb +1 -0
  217. data/lib/pubid/idf/builder.rb +3 -3
  218. data/lib/pubid/idf/identifier.rb +18 -9
  219. data/lib/pubid/idf/parser.rb +1 -1
  220. data/lib/pubid/idf/renderer.rb +4 -4
  221. data/lib/pubid/idf/urn_generator.rb +1 -1
  222. data/lib/pubid/idf.rb +7 -1
  223. data/lib/pubid/iec/builder.rb +14 -7
  224. data/lib/pubid/iec/components.rb +0 -1
  225. data/lib/pubid/iec/identifier.rb +73 -83
  226. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  227. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  228. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  229. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  230. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  231. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  232. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  233. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  234. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  235. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  236. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  237. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  238. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  239. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  240. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  241. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  242. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  243. data/lib/pubid/iec/parser.rb +2 -18
  244. data/lib/pubid/iec/renderer.rb +13 -10
  245. data/lib/pubid/iec/single_identifier.rb +33 -10
  246. data/lib/pubid/iec/urn_generator.rb +227 -61
  247. data/lib/pubid/iec/urn_parser.rb +42 -4
  248. data/lib/pubid/iec.rb +1 -0
  249. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  250. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  251. data/lib/pubid/ieee/builder.rb +45 -1
  252. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  253. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  254. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  255. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  256. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  257. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  258. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  259. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  260. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  261. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  262. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  263. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  264. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  265. data/lib/pubid/ieee/ire/parser.rb +1 -1
  266. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  267. data/lib/pubid/ieee/parser.rb +1 -1
  268. data/lib/pubid/ieee/renderer.rb +5 -1
  269. data/lib/pubid/ieee/urn_generator.rb +32 -3
  270. data/lib/pubid/ieee.rb +7 -1
  271. data/lib/pubid/ieee_debug.rb +1 -0
  272. data/lib/pubid/ietf/builder.rb +24 -8
  273. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  274. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  275. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  276. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  277. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  278. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  279. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  280. data/lib/pubid/ietf/identifiers.rb +1 -0
  281. data/lib/pubid/ietf/parser.rb +27 -9
  282. data/lib/pubid/ietf/renderer.rb +44 -6
  283. data/lib/pubid/ietf/urn_generator.rb +20 -6
  284. data/lib/pubid/ietf/urn_parser.rb +10 -2
  285. data/lib/pubid/ietf.rb +12 -2
  286. data/lib/pubid/iho/identifiers/base.rb +10 -3
  287. data/lib/pubid/iho/parser.rb +1 -1
  288. data/lib/pubid/iho.rb +1 -0
  289. data/lib/pubid/isbn/identifier.rb +25 -7
  290. data/lib/pubid/isbn/parser.rb +1 -1
  291. data/lib/pubid/isbn.rb +1 -0
  292. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  293. data/lib/pubid/iso/combined_identifier.rb +5 -3
  294. data/lib/pubid/iso/identifier.rb +12 -7
  295. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  296. data/lib/pubid/iso/parser.rb +1 -1
  297. data/lib/pubid/iso.rb +7 -1
  298. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  299. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  300. data/lib/pubid/itu/identifiers/base.rb +19 -5
  301. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  302. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  303. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  304. data/lib/pubid/itu/parser.rb +1 -1
  305. data/lib/pubid/itu.rb +1 -0
  306. data/lib/pubid/jcgm/builder.rb +1 -1
  307. data/lib/pubid/jcgm/identifier.rb +10 -0
  308. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  309. data/lib/pubid/jcgm/parser.rb +10 -3
  310. data/lib/pubid/jcgm/renderer.rb +10 -3
  311. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  312. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  313. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  314. data/lib/pubid/jcgm.rb +10 -0
  315. data/lib/pubid/jis/identifier.rb +10 -3
  316. data/lib/pubid/jis/parser.rb +1 -1
  317. data/lib/pubid/jis.rb +1 -0
  318. data/lib/pubid/nist/components/stage.rb +2 -2
  319. data/lib/pubid/nist/configuration.rb +6 -8
  320. data/lib/pubid/nist/identifiers/base.rb +12 -2
  321. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  322. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  323. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  324. data/lib/pubid/nist/identifiers/crpl_report.rb +7 -1
  325. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  326. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  327. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +14 -6
  328. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  329. data/lib/pubid/nist/identifiers/report.rb +14 -6
  330. data/lib/pubid/nist/parser.rb +1 -1
  331. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  332. data/lib/pubid/nist.rb +7 -1
  333. data/lib/pubid/oasis/builder.rb +12 -10
  334. data/lib/pubid/oasis/identifier.rb +89 -11
  335. data/lib/pubid/oasis/parser.rb +1 -1
  336. data/lib/pubid/oasis.rb +7 -1
  337. data/lib/pubid/ogc/identifier.rb +9 -5
  338. data/lib/pubid/ogc/parser.rb +1 -1
  339. data/lib/pubid/ogc.rb +1 -0
  340. data/lib/pubid/oiml/builder.rb +5 -1
  341. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  342. data/lib/pubid/oiml/identifiers/bulletin.rb +36 -0
  343. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  344. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  345. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  346. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  347. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  348. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  349. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  350. data/lib/pubid/oiml/identifiers.rb +1 -0
  351. data/lib/pubid/oiml/parser.rb +1 -1
  352. data/lib/pubid/oiml/single_identifier.rb +26 -42
  353. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  354. data/lib/pubid/oiml.rb +7 -1
  355. data/lib/pubid/omg/builder.rb +1 -0
  356. data/lib/pubid/omg/identifier.rb +20 -3
  357. data/lib/pubid/omg/parser.rb +64 -11
  358. data/lib/pubid/omg/renderer.rb +13 -1
  359. data/lib/pubid/omg.rb +2 -1
  360. data/lib/pubid/parser/grammar.rb +74 -0
  361. data/lib/pubid/parser.rb +2 -0
  362. data/lib/pubid/parsers/mr_string.rb +19 -0
  363. data/lib/pubid/plateau/parser.rb +1 -1
  364. data/lib/pubid/plateau.rb +10 -0
  365. data/lib/pubid/prefixes_support.rb +1 -0
  366. data/lib/pubid/renderers/annotator.rb +233 -0
  367. data/lib/pubid/renderers/base.rb +13 -0
  368. data/lib/pubid/renderers/directives_renderer.rb +3 -3
  369. data/lib/pubid/renderers/human_readable.rb +3 -3
  370. data/lib/pubid/renderers/mr_string.rb +15 -9
  371. data/lib/pubid/renderers.rb +1 -0
  372. data/lib/pubid/rendering/numbering.rb +25 -7
  373. data/lib/pubid/rendering.rb +1 -0
  374. data/lib/pubid/sae/identifiers/base.rb +9 -2
  375. data/lib/pubid/sae/parser.rb +1 -1
  376. data/lib/pubid/sae.rb +1 -0
  377. data/lib/pubid/schema/declaration.rb +33 -0
  378. data/lib/pubid/schema/error.rb +14 -0
  379. data/lib/pubid/schema/identifier_type.rb +23 -0
  380. data/lib/pubid/schema/loader.rb +117 -0
  381. data/lib/pubid/schema/typed_stage.rb +24 -0
  382. data/lib/pubid/schema.rb +25 -0
  383. data/lib/pubid/tgpp/builder.rb +1 -1
  384. data/lib/pubid/tgpp/identifier.rb +21 -5
  385. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  386. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  387. data/lib/pubid/tgpp/parser.rb +22 -6
  388. data/lib/pubid/tgpp/renderer.rb +21 -5
  389. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  390. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  391. data/lib/pubid/tgpp.rb +7 -1
  392. data/lib/pubid/type_resolver.rb +16 -1
  393. data/lib/pubid/un/identifier.rb +6 -3
  394. data/lib/pubid/un/parser.rb +1 -1
  395. data/lib/pubid/un.rb +1 -0
  396. data/lib/pubid/urn_parser/errors.rb +8 -1
  397. data/lib/pubid/urn_parser.rb +1 -0
  398. data/lib/pubid/utils.rb +1 -0
  399. data/lib/pubid/version.rb +1 -1
  400. data/lib/pubid/w3c/builder.rb +5 -5
  401. data/lib/pubid/w3c/identifier.rb +38 -9
  402. data/lib/pubid/w3c/parser.rb +1 -1
  403. data/lib/pubid/w3c/renderer.rb +1 -1
  404. data/lib/pubid/w3c/urn_generator.rb +2 -2
  405. data/lib/pubid/w3c.rb +1 -0
  406. data/lib/pubid/xsf/identifier.rb +9 -5
  407. data/lib/pubid/xsf/parser.rb +17 -4
  408. data/lib/pubid/xsf.rb +7 -1
  409. data/lib/pubid.rb +399 -21
  410. data/lib/tasks/conformance.rake +34 -0
  411. data/lib/tasks/docs.rake +3 -3
  412. data/lib/tasks/schema.rake +88 -0
  413. data/schema/core/joint_prefixes.yaml +24 -0
  414. data/schema/iec.yaml +1017 -0
  415. data/schema/iso.yaml +1586 -0
  416. data/schema/schema.schema.yaml +96 -0
  417. metadata +38 -17
  418. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  419. data/archived-gems/pubid-iec/stages.yaml +0 -129
  420. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  421. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  422. data/archived-gems/pubid-iso/stages.yaml +0 -106
  423. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  424. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  425. data/archived-gems/pubid-itu/series.yaml +0 -42
  426. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  427. data/archived-gems/pubid-nist/series.yaml +0 -121
  428. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  429. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  430. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  431. data/lib/pubid/iec/components/code.rb +0 -36
  432. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -17,7 +17,8 @@ module Pubid
17
17
  cc = code_component
18
18
  parts << cc if cc
19
19
 
20
- parts << identifier.year if identifier.year
20
+ yc = year_component
21
+ parts << yc if yc
21
22
 
22
23
  dc = draft_component
23
24
  parts << dc if dc
@@ -126,8 +127,7 @@ module Pubid
126
127
  end
127
128
 
128
129
  def publisher_component
129
- pub = identifier.publisher || "IEEE"
130
- pub = pub.to_s.downcase
130
+ pub = normalized_publisher
131
131
 
132
132
  if identifier.copublisher&.any?
133
133
  copubs = identifier.copublisher.map(&:to_s).map(&:downcase)
@@ -137,6 +137,18 @@ module Pubid
137
137
  pub
138
138
  end
139
139
 
140
+ # A dual-published document has TWO publishers, and
141
+ # DualPublished#publisher returns them as an Array — which `to_s`
142
+ # rendered into the URN as a literal Ruby array literal, brackets and
143
+ # quotes included: `urn:ieee:["ieee", "asme"]`. Join them the same way a
144
+ # copublisher list is joined above.
145
+ def normalized_publisher
146
+ names = Array(identifier.publisher)
147
+ .map { |p| p.to_s.downcase }.reject(&:empty?)
148
+
149
+ names.empty? ? "ieee" : names.join("-")
150
+ end
151
+
140
152
  def type_component
141
153
  return nil unless identifier.type
142
154
 
@@ -152,6 +164,23 @@ module Pubid
152
164
  identifier.code_obj.to_s
153
165
  end
154
166
 
167
+ # A multi-designation wrapper (AdoptedStandard, DualPublished,
168
+ # MultiNumberedIdentifier) carries no year of its own — the year sits on
169
+ # the designation it wraps — so the URN dropped it and two editions of
170
+ # one document produced the SAME urn: "AIEE No 15-1944" and
171
+ # "AIEE No 15-1959" both gave urn:ieee:aiee:15.
172
+ #
173
+ # Fixed here, in the one place that reads the year, rather than by
174
+ # defining a `#year` method on each wrapper: `year` is a lutaml attribute
175
+ # on the base and a method of that name would shadow its generated
176
+ # accessor. `#root` already walks to the wrapped designation.
177
+ def year_component
178
+ return identifier.year if identifier.year
179
+
180
+ root = identifier.root
181
+ root.equal?(identifier) ? nil : root.year
182
+ end
183
+
155
184
  def draft_component
156
185
  return nil unless identifier.draft_obj
157
186
 
data/lib/pubid/ieee.rb CHANGED
@@ -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
  extend Pubid::PrefixesSupport
@@ -42,8 +43,13 @@ module Pubid
42
43
 
43
44
  class << self
44
45
  def parse(input)
46
+ unless input.is_a?(String)
47
+ raise Pubid::Errors::InvalidInputError,
48
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
49
+ end
50
+
45
51
  if input.length > Pubid::MAX_INPUT_LENGTH
46
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
52
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
47
53
  end
48
54
 
49
55
  Identifier.parse(input)
@@ -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