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
@@ -7,9 +7,11 @@ module Pubid
7
7
  # descend from this class, so a parsed GB id is always an instance of
8
8
  # Pubid::Gb::Identifier.
9
9
  class Identifier < ::Pubid::Identifier
10
- # The issuing body code as printed, e.g. "GB", "JB", "T/GZAEPI".
11
- # Always non-empty for a valid identifier.
12
- attribute :publisher_code, :string
10
+ # The issuing body code as printed — "GB", "JB", "GBn", "T/GZAEPI" —
11
+ # lives in the `publisher` attribute inherited from ::Pubid::Identifier.
12
+ # The flat-scalar hooks below serialize it as a bare string, and the
13
+ # shared URN generator renders it lowercase, so the series is part of
14
+ # the URN identity (GB 20223 and GBn 20223 are two documents).
13
15
 
14
16
  # Mandate category: "T" (recommended), "Z" (guideline), or nil
15
17
  # (mandatory). Carried after the "/" in the printed form.
@@ -31,17 +33,34 @@ module Pubid
31
33
  "pubid:gb:standard" => "Pubid::Gb::Identifiers::Standard",
32
34
  }.freeze
33
35
 
36
+ # This block REPLACES the maps of ::Pubid::Identifier, so every
37
+ # attribute the flavor uses must appear here — "date" included, or the
38
+ # publication year is lost in to_hash. The shared flat-scalar rules
39
+ # (Identifier#to_hash / .from_hash) write it as a bare "year" scalar.
34
40
  key_value do
35
41
  map "_type", to: :_type, polymorphic_map: GB_TYPE_MAP
36
- map "publisher_code", to: :publisher_code
42
+ map "publisher", to: :publisher
37
43
  map "mandate", to: :mandate
38
44
  map "number", to: :number
39
45
  map "part", to: :part
46
+ map "date", to: :date
40
47
  map "all_parts", to: :all_parts, render_default: false
41
48
  end
42
49
 
43
50
  PUBLISHER = "CN"
44
51
 
52
+ # Serialize `publisher` as a bare string instead of a nested
53
+ # {"body" => "GB"}, the CEN/CENELEC precedent. The entry is added for
54
+ # GB's own classes only: a shared entry would change the wire format of
55
+ # every flavor that publishes an index.
56
+ def self.flat_scalar_components
57
+ super.merge(publisher: "publisher")
58
+ end
59
+
60
+ def self.flat_scalar_fields
61
+ super.merge(publisher: :body)
62
+ end
63
+
45
64
  def to_s(**opts)
46
65
  render(format: :human, **opts)
47
66
  end
@@ -50,14 +69,17 @@ module Pubid
50
69
  # @param identifier [String]
51
70
  # @return [Pubid::Gb::Identifier]
52
71
  def self.parse(identifier)
72
+ unless identifier.is_a?(String)
73
+ raise Pubid::Errors::InvalidInputError,
74
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
75
+ end
76
+
53
77
  if identifier.length > Pubid::MAX_INPUT_LENGTH
54
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
78
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
55
79
  end
56
80
 
57
81
  parsed = Parser.parse(identifier)
58
82
  Builder.build(parsed)
59
- rescue Parslet::ParseFailed => e
60
- raise "Failed to parse Chinese Standard identifier '#{identifier}': #{e.message}"
61
83
  end
62
84
  end
63
85
  end
@@ -14,7 +14,7 @@ module Pubid
14
14
  # T/GZAEPI, ...). TYPE is T (recommended), Z (guideline), or omitted.
15
15
  # YEAR is 4 digits, separated by either ASCII dash or em-dash (Chinese
16
16
  # typography uses the em-dash form).
17
- class Parser < Parslet::Parser
17
+ class Parser < ::Pubid::Parser::Grammar
18
18
  rule(:space) { str(" ") }
19
19
  rule(:dot) { str(".") }
20
20
  rule(:dash) { str("-") | str("—") }
@@ -25,6 +25,10 @@ module Pubid
25
25
  rule(:publisher_code) do
26
26
  # Social-group form: "T/" then 2+ uppercase letters/digits.
27
27
  (str("T/") >> match("[A-Z0-9]").repeat(1)).as(:publisher_code) |
28
+ # Confidential national series. The lowercase "n" is part of the
29
+ # code, so it comes before the uppercase-only rule below: a PEG
30
+ # takes the first branch that matches and does not backtrack.
31
+ (str("GBn") >> (str("/T") | str("/Z")).maybe).as(:publisher_code) |
28
32
  # Standard form: 1-3 letters, optional slash-T-or-Z.
29
33
  (match("[A-Z]").repeat(1, 3) >>
30
34
  (str("/T") | str("/Z")).maybe).as(:publisher_code)
@@ -8,7 +8,8 @@ module Pubid
8
8
  # "GB/T 20223-2006"
9
9
  # "GB/T 5606.1-2004"
10
10
  # "GB/T 5606 (all parts)"
11
- # "T/GZAEPI 001—2018" (social-group form, em-dash year)
11
+ # "T/GZAEPI 001-2018" (social-group form; an em-dash year
12
+ # separator in the input renders as a hyphen)
12
13
  class Renderer < ::Pubid::Renderers::Base
13
14
  def render(**_opts)
14
15
  parts = [publisher_portion, " #{number_portion}"]
@@ -19,8 +20,8 @@ module Pubid
19
20
  private
20
21
 
21
22
  def publisher_portion
22
- code = @id.publisher_code.to_s
23
- code += "/#{@id.mandate}" if @id.mandate && !@id.publisher_code.include?("/")
23
+ code = @id.publisher.to_s
24
+ code += "/#{@id.mandate}" if @id.mandate && !code.include?("/")
24
25
  code
25
26
  end
26
27
 
data/lib/pubid/gb.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  # Chinese Standard (GB) flavor.
5
6
  #
@@ -16,6 +17,7 @@ module Pubid
16
17
  # "GB" comes first so PrefixesSupport's longest-match wins.
17
18
  PREFIXES = %w[
18
19
  GB GB/T GB/Z
20
+ GBn GBn/T GBn/Z
19
21
  DB DB/T DB/Z
20
22
  QB QB/T
21
23
  ZB ZB/T
@@ -120,23 +120,60 @@ module Pubid
120
120
  [m[1], m[2]]
121
121
  end
122
122
 
123
- # Try every registered flavor (other than GOST itself) as a parser
124
- # for a foreign adopted identifier. Returns the first successful
125
- # parse, or nil if none recognize the string.
123
+ # Route a foreign adopted identifier to its owning flavor, then
124
+ # fall back to trying every other flavor as a parser. Returns the
125
+ # first successful parse, or nil if none recognize the string.
126
+ #
127
+ # The prefix route is what makes this deterministic: Registry
128
+ # insertion order is module-load order, which belongs to the host
129
+ # application (a script that references Pubid::Ieee before
130
+ # Pubid::Astm registers IEEE first, and IEEE's grammar accepts
131
+ # "ASTM D129-18" as a publisher token plus an IEEE code), so a
132
+ # first-success loop over insertion order routed the same GOST
133
+ # adoption to different flavors per process. The fallback loop
134
+ # iterates sorted flavor names for the same reason.
126
135
  def parse_foreign(raw)
127
136
  ::Pubid.eager_load_flavors!
128
- ::Pubid::Registry.flavors.each_value do |mod|
137
+
138
+ owner = prefix_owner(raw)
139
+ parsed = owner && parse_quietly(owner, raw)
140
+ return parsed if parsed
141
+
142
+ try_each_flavor(raw)
143
+ end
144
+
145
+ def parse_quietly(mod, raw)
146
+ mod.parse(raw)
147
+ rescue StandardError
148
+ nil
149
+ end
150
+
151
+ def try_each_flavor(raw)
152
+ ::Pubid::Registry.flavor_names.sort.each do |name|
153
+ mod = ::Pubid::Registry.get(name)
129
154
  next if mod == ::Pubid::Gost
130
155
 
131
- begin
132
- return mod.parse(raw)
133
- rescue StandardError
134
- next
135
- end
156
+ parsed = parse_quietly(mod, raw)
157
+ return parsed if parsed
136
158
  end
137
159
  nil
138
160
  end
139
161
 
162
+ # The single flavor owning the longest registered prefix that the
163
+ # string starts with; nil when no prefix matches or the prefix is
164
+ # jointly owned (joint forms go through the fallback loop, where
165
+ # every owner parses them correctly).
166
+ def prefix_owner(raw)
167
+ index = ::Pubid.prefix_flavors
168
+ match = index.keys
169
+ .select { |p| raw.start_with?("#{p} ") || raw == p }
170
+ .max_by(&:length)
171
+ return nil unless match
172
+
173
+ owners = index[match]
174
+ owners.one? ? ::Pubid::Registry.get(owners.first) : nil
175
+ end
176
+
140
177
  def stringify(value)
141
178
  return nil if value.nil?
142
179
 
@@ -15,8 +15,6 @@ module Pubid
15
15
  def self.parse(identifier)
16
16
  parsed = Parser.parse(identifier)
17
17
  Builder.build(parsed)
18
- rescue Parslet::ParseFailed => e
19
- raise "Failed to parse GOST identifier '#{identifier}': #{e.message}"
20
18
  end
21
19
 
22
20
  attribute :publisher, :string, default: "GOST"
@@ -16,7 +16,7 @@ module Pubid
16
16
  { key: :"foreign-reference", title: "Foreign Reference", short: nil }
17
17
  end
18
18
 
19
- def to_s
19
+ def to_s(**_opts)
20
20
  raw.to_s
21
21
  end
22
22
  end
@@ -26,7 +26,7 @@ module Pubid
26
26
  #
27
27
  # The builder interprets prefix_text (splits copublisher vs subtype,
28
28
  # normalizes Cyrillic to Latin) and raw (splits number vs year).
29
- class Parser < Parslet::Parser
29
+ class Parser < ::Pubid::Parser::Grammar
30
30
  include ::Pubid::Parser::CommonParseRules
31
31
 
32
32
  root :identifier
@@ -88,7 +88,15 @@ module Pubid
88
88
  end
89
89
 
90
90
  def self.parse(string)
91
- raise ArgumentError, ::Pubid::INPUT_TOO_LONG_MESSAGE if string.length > ::Pubid::MAX_INPUT_LENGTH
91
+ unless string.is_a?(String)
92
+ raise ::Pubid::Errors::InvalidInputError,
93
+ ::Pubid::INPUT_NOT_A_STRING_MESSAGE
94
+ end
95
+
96
+ if string.length > ::Pubid::MAX_INPUT_LENGTH
97
+ raise ::Pubid::Errors::InvalidInputError,
98
+ ::Pubid::INPUT_TOO_LONG_MESSAGE
99
+ end
92
100
 
93
101
  new.parse(string.strip)
94
102
  end
data/lib/pubid/gost.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Gost
5
6
  extend Pubid::PrefixesSupport
@@ -15,15 +15,18 @@ module Pubid
15
15
  # R1016:ed2.0(F)
16
16
  # C0103-1 Ed 3.0
17
17
  class Identifier < ::Pubid::Identifier
18
- # Parse an IALA identifier string into an identifier object.
18
+ # Parse an IALA identifier — human form ("IALA G1199 Ed 1.0") or
19
+ # MRN URN ("urn:mrn:iala:pub:g1199:ed1.0") — into an identifier.
19
20
  # @param identifier [String]
20
21
  # @return [Pubid::Iala::Identifier]
21
- # @raise [Parslet::ParseFailed] If parsing fails
22
+ # @raise [Pubid::Errors::ParseError] If parsing fails
22
23
  def self.parse(identifier)
23
- parsed = Parser.parse(identifier)
24
- Builder.build(parsed)
25
- rescue Parslet::ParseFailed => e
26
- raise "Failed to parse IALA identifier '#{identifier}': #{e.message}"
24
+ if FormatDetector.detect(identifier) == :urn
25
+ UrnParser.parse(identifier)
26
+ else
27
+ parsed = Parser.parse(identifier)
28
+ Builder.build(parsed)
29
+ end
27
30
  end
28
31
 
29
32
  attribute :publisher, :string, default: "IALA"
@@ -15,7 +15,7 @@ module Pubid
15
15
  # R1016:ed2.0(F)
16
16
  # C0103-1
17
17
  # C0103-1 Ed 3.0
18
- class Parser < Parslet::Parser
18
+ class Parser < ::Pubid::Parser::Grammar
19
19
  include ::Pubid::Parser::CommonParseRules
20
20
 
21
21
  root :identifier
@@ -121,7 +121,15 @@ module Pubid
121
121
  # @param string [String]
122
122
  # @return [Hash]
123
123
  def self.parse(string)
124
- raise ArgumentError, "IALA identifier string exceeds maximum length" if string.length > ::Pubid::MAX_INPUT_LENGTH
124
+ unless string.is_a?(String)
125
+ raise ::Pubid::Errors::InvalidInputError,
126
+ ::Pubid::INPUT_NOT_A_STRING_MESSAGE
127
+ end
128
+
129
+ if string.length > ::Pubid::MAX_INPUT_LENGTH
130
+ raise ::Pubid::Errors::InvalidInputError,
131
+ ::Pubid::INPUT_TOO_LONG_MESSAGE
132
+ end
125
133
 
126
134
  new.parse(string.strip)
127
135
  end
@@ -37,8 +37,12 @@ module Pubid
37
37
  # generator's casing so URN round-trip is exact.
38
38
  annex_letter = Regexp.last_match(2)&.upcase
39
39
  annex_form = annex_letter ? "ANNEX" : "Annex"
40
- when /\Aed/
41
- edition = seg.sub(/\Aed/, "")
40
+ # Edition marker: case-insensitive with an optional separator dot.
41
+ # Both variants occur in published documents (R1026 prints
42
+ # "Ed1.0" and "ed.1.0"); each parses to the canonical edition
43
+ # value and re-renders as lowercase "ed<x.x>".
44
+ when /\Aed\.?/i
45
+ edition = seg.sub(/\Aed\.?/i, "")
42
46
  when /\A[a-z]\z/i
43
47
  language = seg.upcase
44
48
  end
data/lib/pubid/iala.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Iala
5
6
  extend Pubid::PrefixesSupport
@@ -10,8 +10,10 @@ module Pubid
10
10
  end
11
11
 
12
12
  def build(data)
13
+ # The top-level slug is stored as `number` — it is the relaton-index
14
+ # key; `Identifier#registry` reads it back.
13
15
  Identifiers::Registry.new(
14
- registry: data[:registry].to_s,
16
+ number: data[:registry].to_s,
15
17
  sub_registry: data[:sub_registry]&.to_s,
16
18
  )
17
19
  end
@@ -9,9 +9,12 @@ module Pubid
9
9
  # IANA entries are protocol registries, not numbered standards: the value is
10
10
  # a hierarchical registry slug (`registry` before the single "/", optional
11
11
  # `sub_registry` after it). Both are kept verbatim.
12
+ #
13
+ # The top-level slug is stored as `number` on the concrete leaf
14
+ # (Identifiers::Registry) so it can serve as the relaton-index key; see the
15
+ # long comment there for why the attribute lives on the leaf. `registry` is
16
+ # a derived reader over it, so it is NOT a serialized key.
12
17
  class Identifier < ::Pubid::Identifier
13
- # The registry slug (e.g. "_6lowpan-parameters"). Required.
14
- attribute :registry, :string
15
18
  # The sub-registry slug (e.g. "lowpan_nhc"). nil for top registries; a nil
16
19
  # default means it is dropped from the serialized hash when absent.
17
20
  attribute :sub_registry, :string
@@ -23,9 +26,12 @@ module Pubid
23
26
  "pubid:iana:registry" => "Pubid::Iana::Identifiers::Registry",
24
27
  }.freeze
25
28
 
29
+ # Identifiers::Registry merges its own block mapping `number` on top of
30
+ # this one. `number` is intentionally absent here: mapping it on the class
31
+ # the leaf inherits from would re-declare the parent's Components::Code
32
+ # attribute (see Identifiers::Registry).
26
33
  key_value do
27
34
  map "_type", to: :_type, polymorphic_map: IANA_TYPE_MAP
28
- map "registry", to: :registry
29
35
  map "sub_registry", to: :sub_registry
30
36
  end
31
37
 
@@ -38,6 +44,14 @@ module Pubid
38
44
  # dropped so callers can recover the bare index-key slug.
39
45
  attr_reader :with_publisher
40
46
 
47
+ # The top-level registry slug. Derived from `number`, which is where it is
48
+ # stored — a plain method is safe because `registry` is no longer a lutaml
49
+ # attribute, so no generated accessor competes with it (the same shape as
50
+ # Pubid::Ietf::Identifiers::Bcp#series).
51
+ def registry
52
+ number
53
+ end
54
+
41
55
  # The rendered registry code, e.g. "_6lowpan-parameters/lowpan_nhc".
42
56
  def code
43
57
  sub_registry ? "#{registry}/#{sub_registry}" : registry
@@ -51,6 +65,57 @@ module Pubid
51
65
  render(format: :human, **opts)
52
66
  end
53
67
 
68
+ # --- Machine-readable slug -------------------------------------------
69
+ # The shared MrString renderer composes publisher/type/number/... from the
70
+ # inherited attributes, none of which IANA populates: `publisher` is a
71
+ # constant rather than an attribute (see PUBLISHER above) and the builder
72
+ # sets no typed_stage. Without these two overrides every IANA identifier
73
+ # rendered the empty MR string, so all of them collided on `to_mr_string`
74
+ # and `to_slug` (issue #142) — and `to_slug` is used as a filename.
75
+ #
76
+ # Verified over the 3405 published relaton-data-iana rows: the resulting
77
+ # slugs are all distinct and stay inside the documented [a-z0-9._-] MR
78
+ # charset. Note that IANA slugs legitimately contain "_", which is MR's
79
+ # supplement separator, so an IANA MR is not re-parseable by
80
+ # Pubid::Parsers::MrString; uniqueness and filename-safety are what these
81
+ # buy, not MR round-tripping.
82
+ def mr_publisher
83
+ PUBLISHER.downcase
84
+ end
85
+
86
+ def mr_number_with_part
87
+ [number, sub_registry].compact.join(".").downcase
88
+ end
89
+
90
+ # The guard below has to cover this surface too: without it a nil number
91
+ # renders the bare publisher token "iana" for every affected row, i.e. one
92
+ # filename shared by all of them.
93
+ def to_mr_string
94
+ require_number!
95
+ super
96
+ end
97
+
98
+ # The top-level slug moved from a `registry` key into `number` with no
99
+ # alias, so a row serialized before that change deserializes with a nil
100
+ # number and NO error: lutaml ignores unknown keys, and relaton's
101
+ # Index::FileIO#id_supported? skips its round-trip check for concrete
102
+ # subclasses (every IANA id is an Identifiers::Registry). Failing loudly
103
+ # at render time is the only thing that surfaces a stale index.
104
+ #
105
+ # It is called from all three identity surfaces — to_s (via Renderer),
106
+ # to_urn (via UrnGenerator) and to_mr_string — because guarding only one
107
+ # still lets every stale row collide on the other two ("urn:iana:",
108
+ # "iana"), which is the silent overwrite this whole change exists to
109
+ # prevent.
110
+ def require_number!
111
+ return unless number.nil? || number.to_s.empty?
112
+
113
+ raise ArgumentError,
114
+ "Cannot render #{self.class} with an empty number " \
115
+ "(a pre-`number` index row stored the registry slug under " \
116
+ "`registry`; regenerate the index)"
117
+ end
118
+
54
119
  # from_hash is the shared polymorphic dispatch on Pubid::Identifier;
55
120
  # IANA_TYPE_MAP remains as the key_value polymorphic_map.
56
121
 
@@ -58,14 +123,17 @@ module Pubid
58
123
  # @param identifier [String] The IANA identifier string to parse
59
124
  # @return [Identifier] The appropriate identifier object
60
125
  def self.parse(identifier)
126
+ unless identifier.is_a?(String)
127
+ raise Pubid::Errors::InvalidInputError,
128
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
129
+ end
130
+
61
131
  if identifier.length > Pubid::MAX_INPUT_LENGTH
62
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
132
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
63
133
  end
64
134
 
65
135
  parsed = Parser.parse(identifier)
66
136
  Builder.build(parsed)
67
- rescue Parslet::ParseFailed => e
68
- raise "Failed to parse IANA identifier '#{identifier}': #{e.message}"
69
137
  end
70
138
  end
71
139
  end
@@ -11,6 +11,42 @@ module Pubid
11
11
  # IANA _6lowpan-parameters
12
12
  # IANA _6lowpan-parameters/lowpan_nhc
13
13
  class Registry < Identifier
14
+ # The top-level registry slug ("_6lowpan-parameters"), and the index
15
+ # key: relaton-index bsearches on `id.root.number.to_s`, and every IANA
16
+ # identifier is its own root (the flavor has no wrapper or supplement
17
+ # types), so an empty key silently defeats the search for every row.
18
+ #
19
+ # An IANA identifier carries no numeric document number — digits only
20
+ # ever appear glued inside a slug token (`sip-parameters-13`,
21
+ # `idna-tables-11.0.0`), never as a separable component — so the slug
22
+ # itself is the key, exactly as an Internet-Draft's slug is
23
+ # (lib/pubid/ietf/identifiers/serialization.rb). Keying on the TOP-LEVEL
24
+ # slug rather than the full `registry/sub_registry` code means a
25
+ # registry and all of its sub-registries cluster into one bucket, the
26
+ # analogue of a draft clustering with its versions.
27
+ #
28
+ # WHY THIS IS DECLARED HERE AND NOT ON Pubid::Iana::Identifier:
29
+ # it redefines the parent ::Pubid::Identifier's
30
+ # `attribute :number, Components::Code` as a plain :string. Declaring
31
+ # that on a class the concrete type INHERITS from resolves against the
32
+ # parent definition nondeterministically under multi-flavor load — it
33
+ # passes an IANA-only run and can flip `root.number` to "" under the
34
+ # full suite (the IEEE/IETF lesson; see
35
+ # lib/pubid/ieee/identifiers/code_number.rb and CLAUDE.md). Declaring it
36
+ # directly on the leaf removes the ambiguity at the point of use.
37
+ # For the same reason, never define a `#number` *method* — it would
38
+ # collide with lutaml's generated accessor.
39
+ # spec/pubid/iana/root_number_spec.rb guards this and must run under the
40
+ # FULL suite to be meaningful.
41
+ attribute :number, :string
42
+
43
+ # Merged by lutaml with the base's key_value block, which maps `_type`
44
+ # and `sub_registry`. `registry` is deliberately NOT stored — it is the
45
+ # same string as `number`, and the base exposes it as a derived reader.
46
+ key_value do
47
+ map "number", to: :number
48
+ end
49
+
14
50
  # IANA registries carry no revision/stage lifecycle; a single published
15
51
  # typed stage keeps parity with the other flavors' type registries.
16
52
  TYPED_STAGES = [
@@ -12,7 +12,7 @@ module Pubid
12
12
  #
13
13
  # Slugs use only [a-zA-Z0-9._-] and contain at most one "/", so a plain
14
14
  # character-class repeat parses them; there is no ReDoS surface.
15
- class Parser < Parslet::Parser
15
+ class Parser < ::Pubid::Parser::Grammar
16
16
  # A single registry slug: letters, digits, dot, underscore, hyphen.
17
17
  rule(:slug) { match['a-zA-Z0-9._\-'].repeat(1) }
18
18
 
@@ -15,6 +15,9 @@ module Pubid
15
15
 
16
16
  def render(**_opts)
17
17
  id = @id
18
+ # A stale (pre-`number`) row must not render as a bare "IANA " or
19
+ # "IANA /lowpan_nhc"; see Identifier#require_number!.
20
+ id.require_number!
18
21
  code = id.code
19
22
  with_publisher?(id) ? "#{PUBLISHER} #{code}" : code
20
23
  end
@@ -6,7 +6,11 @@ module Pubid
6
6
  # (charset is [a-zA-Z0-9._-]), so the two levels split unambiguously.
7
7
  class UrnGenerator < Pubid::UrnGenerator::Base
8
8
  def generate
9
- parts = ["urn", "iana", identifier.registry]
9
+ # A stale (pre-`number`) row would otherwise emit the bare "urn:iana:"
10
+ # for every affected id; see Identifier#require_number!.
11
+ identifier.require_number!
12
+ # `number` holds the top-level registry slug (Identifiers::Registry).
13
+ parts = ["urn", "iana", identifier.number]
10
14
  parts << identifier.sub_registry if identifier.sub_registry
11
15
  parts.join(":")
12
16
  end
data/lib/pubid/iana.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Iana
5
6
  extend Pubid::PrefixesSupport