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
@@ -37,7 +37,9 @@ module Pubid
37
37
  abbr = annotate(abbr, typed_stage_css(@id.typed_stage),
38
38
  annotated: ann)
39
39
  end
40
- subgroup_str = @id.subgroup.render(context:) if @id.subgroup
40
+ if @id.subgroup
41
+ subgroup_str = render_component(@id.subgroup, context)
42
+ end
41
43
 
42
44
  [
43
45
  pub_str,
@@ -50,15 +52,15 @@ module Pubid
50
52
  ann = context.annotated
51
53
  parts = []
52
54
  if @id.number
53
- parts << annotate(@id.number.render(context:), "docnumber",
55
+ parts << annotate(render_component(@id.number, context), "docnumber",
54
56
  annotated: ann)
55
57
  end
56
58
  if @id.part
57
- parts << " #{annotate(@id.part.render(context:), 'part',
59
+ parts << " #{annotate(render_component(@id.part, context), 'part',
58
60
  annotated: ann)}"
59
61
  end
60
62
  if @id.subpart
61
- parts << "-#{annotate(@id.subpart.render(context:), 'part',
63
+ parts << "-#{annotate(render_component(@id.subpart, context), 'part',
62
64
  annotated: ann)}"
63
65
  end
64
66
  if @id.stage_iteration
@@ -41,15 +41,15 @@ module Pubid
41
41
  ann = context.annotated
42
42
  parts = []
43
43
  if @id.number
44
- parts << annotate(@id.number.render(context:), "docnumber",
44
+ parts << annotate(render_component(@id.number, context), "docnumber",
45
45
  annotated: ann)
46
46
  end
47
47
  if @id.part
48
- parts << "-#{annotate(@id.part.render(context:), 'part',
48
+ parts << "-#{annotate(render_component(@id.part, context), 'part',
49
49
  annotated: ann)}"
50
50
  end
51
51
  if @id.subpart
52
- parts << "-#{annotate(@id.subpart.render(context:), 'part',
52
+ parts << "-#{annotate(render_component(@id.subpart, context), 'part',
53
53
  annotated: ann)}"
54
54
  end
55
55
  if @id.stage_iteration
@@ -28,6 +28,20 @@ module Pubid
28
28
  class MrString < Base
29
29
  SEPARATOR_SUPPLEMENT = "_"
30
30
 
31
+ # The flat MR segments, in order, as the hook each one reads. A hook
32
+ # returning nil contributes no segment, so a flavor fills only the slots
33
+ # its identity actually uses — `mr_volume` (ECMA) is nil everywhere else.
34
+ SEGMENTS = %i[
35
+ mr_publisher
36
+ mr_type
37
+ mr_number_with_part
38
+ mr_year
39
+ mr_edition
40
+ mr_volume
41
+ mr_languages
42
+ mr_all_parts
43
+ ].freeze
44
+
31
45
  def render(context: nil, **)
32
46
  suffix = @id.mr_supplement_suffix
33
47
 
@@ -43,15 +57,7 @@ module Pubid
43
57
  private
44
58
 
45
59
  def render_flat(id)
46
- parts = []
47
- parts << id.mr_publisher
48
- parts << id.mr_type
49
- parts << id.mr_number_with_part
50
- parts << id.mr_year
51
- parts << id.mr_edition
52
- parts << id.mr_languages
53
- parts << id.mr_all_parts
54
- parts.compact.join(".")
60
+ SEGMENTS.filter_map { |hook| id.public_send(hook) }.join(".")
55
61
  end
56
62
  end
57
63
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Pubid
4
4
  module Renderers
5
+ autoload :Annotator, "pubid/renderers/annotator"
5
6
  autoload :Base, "pubid/renderers/base"
6
7
  autoload :DirectivesRenderer, "pubid/renderers/directives_renderer"
7
8
  autoload :GuideRenderer, "pubid/renderers/guide_renderer"
@@ -4,21 +4,39 @@ module Pubid
4
4
  module Rendering
5
5
  module Numbering
6
6
  # Render number with optional parts and subparts
7
- # @param number [Components::Code] primary number
8
- # @param part [Components::Code, nil] optional part
9
- # @param subpart [Components::Code, nil] optional subpart
7
+ # @param number [Components::Code, String, nil] primary number
8
+ # @param part [Components::Code, String, nil] optional part
9
+ # @param subpart [Components::Code, String, nil] optional subpart
10
10
  # @param options [Hash] rendering options
11
11
  # @return [String] formatted numbering string
12
12
  def render_numbering(number, part = nil, subpart = nil, **options)
13
- return "" unless number&.value
13
+ number_str = numbering_value(number)
14
+ return "" unless number_str
14
15
 
15
- result = " #{number.value}"
16
+ result = " #{number_str}"
16
17
  separator = options[:part_separator] || "-"
17
- result += "#{separator}#{part.value}" if part&.value
18
- result += "#{separator}#{subpart.value}" if subpart&.value
18
+ [part, subpart].each do |component|
19
+ value = numbering_value(component)
20
+ result += "#{separator}#{value}" if value
21
+ end
19
22
 
20
23
  result
21
24
  end
25
+
26
+ private
27
+
28
+ # `number`/`part`/`subpart` are a Components::Code on ::Pubid::Identifier
29
+ # but a plain :string in a growing number of flavors, so read the value
30
+ # through whichever shape the caller holds. Returns nil for an absent or
31
+ # empty component, which is what suppresses the segment.
32
+ def numbering_value(component)
33
+ return nil if component.nil?
34
+
35
+ value = component.respond_to?(:value) ? component.value : component
36
+ return nil if value.nil? || value.to_s.empty?
37
+
38
+ value.to_s
39
+ end
22
40
  end
23
41
  end
24
42
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Rendering
5
6
  autoload :Date, "pubid/rendering/date"
@@ -20,7 +20,7 @@ module Pubid
20
20
  when :type
21
21
  Components::Type.new(abbr: value.to_s)
22
22
  when :number
23
- Components::Code.new(value: value.to_s)
23
+ value.to_s
24
24
  when :year
25
25
  { date: Components::Date.new(year: value.to_i) }
26
26
  else
@@ -7,15 +7,25 @@ module Pubid
7
7
  # Handles all SAE document types (AMS, AIR, ARP, AS, MA).
8
8
  class Identifier < ::Pubid::Identifier
9
9
  def self.parse(input)
10
+ unless input.is_a?(String)
11
+ raise Pubid::Errors::InvalidInputError,
12
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
13
+ end
14
+
15
+ if input.length > Pubid::MAX_INPUT_LENGTH
16
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
17
+ end
18
+
10
19
  parsed = Parser.parse(input)
11
20
  Builder.build(parsed)
12
- rescue Parslet::ParseFailed => e
13
- raise "Failed to parse SAE identifier: #{input}\n#{e.message}"
14
21
  end
15
22
 
16
23
  attribute :publisher, :string, default: -> { "SAE" }
17
24
  attribute :type, Sae::Components::Type
18
- attribute :number, Sae::Components::Code
25
+ # A plain :string, like every converted flavor. Sae::Components::Code was
26
+ # a bare alias of the shared component and never carried more than
27
+ # `value`, so this is a representation change only.
28
+ attribute :number, :string
19
29
  attribute :date, Sae::Components::Date
20
30
 
21
31
  def self.type
@@ -4,7 +4,7 @@ require "parslet"
4
4
 
5
5
  module Pubid
6
6
  module Sae
7
- class Parser < Parslet::Parser
7
+ class Parser < ::Pubid::Parser::Grammar
8
8
  rule(:space) { str(" ") }
9
9
  rule(:digit) { match["0-9"] }
10
10
  rule(:digits) { digit.repeat(1) }
@@ -12,7 +12,7 @@ module Pubid
12
12
  def urn_number
13
13
  return nil unless identifier.number
14
14
 
15
- identifier.number.render(context: URN_CONTEXT)
15
+ identifier.number.to_s
16
16
  end
17
17
 
18
18
  def generate
data/lib/pubid/sae.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Sae
5
6
  extend Pubid::PrefixesSupport
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Schema
5
+ # A loaded flavor declaration (schema/{flavor}.yaml).
6
+ class Declaration
7
+ attr_reader :flavor, :schema_version, :prefixes, :identifier_types
8
+
9
+ def initialize(flavor:, schema_version:, prefixes:, identifier_types:)
10
+ @flavor = flavor
11
+ @schema_version = schema_version
12
+ @prefixes = prefixes.freeze
13
+ @identifier_types = identifier_types.map(&:freeze).freeze
14
+ freeze
15
+ end
16
+
17
+ # Own tokens plus joint/co-publication tokens from
18
+ # schema/core/joint_prefixes.yaml - mirrors the runtime
19
+ # Pubid::{Flavor}.prefixes contract (see PrefixesSupport).
20
+ def merged_prefixes
21
+ (prefixes + Loader.joint_prefixes_for(flavor)).freeze
22
+ end
23
+
24
+ def type_for(key)
25
+ identifier_types.find { |type| type.key == key }
26
+ end
27
+
28
+ def types_count
29
+ identifier_types.size
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Schema
5
+ # Base error for schema loading and lookup failures.
6
+ class Error < StandardError; end
7
+
8
+ # Raised when a flavor declaration file cannot be found.
9
+ class NotFoundError < Error; end
10
+
11
+ # Raised when a declaration file violates the format contract.
12
+ class InvalidError < Error; end
13
+ end
14
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Schema
5
+ # One identifier type declaration (e.g. iso "amendment", iec "vap").
6
+ class IdentifierType
7
+ attr_reader :key, :title, :short, :abbr, :typed_stages
8
+
9
+ def initialize(key:, title:, short: nil, abbr: [], typed_stages: [])
10
+ @key = key
11
+ @title = title
12
+ @short = short
13
+ @abbr = abbr.freeze
14
+ @typed_stages = typed_stages.map(&:freeze).freeze
15
+ freeze
16
+ end
17
+
18
+ def typed_stage_for_abbr(candidate)
19
+ typed_stages.find { |stage| stage.matches_abbr?(candidate) }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Schema
5
+ # Loads and validates flavor declarations from schema/. Declarations
6
+ # are memoized and deeply frozen; loading is lazy per flavor so the
7
+ # parse hot path pays no boot cost.
8
+ class Loader
9
+ SCHEMA_ROOT = File.expand_path("../../../schema", __dir__).freeze
10
+ CORE_DIR = File.join(SCHEMA_ROOT, "core").freeze
11
+
12
+ class << self
13
+ def for(flavor)
14
+ flavor = flavor.to_s
15
+ declarations[flavor] ||= build_declaration(flavor)
16
+ end
17
+
18
+ def joint_prefixes_for(flavor)
19
+ joint_prefixes[flavor.to_s] || [].freeze
20
+ end
21
+
22
+ # Full joint-prefix map (flavor key => tokens) from
23
+ # schema/core/joint_prefixes.yaml.
24
+ def joint_prefixes_map
25
+ joint_prefixes
26
+ end
27
+
28
+ def loaded_flavors
29
+ declarations.keys.sort
30
+ end
31
+
32
+ private
33
+
34
+ def declarations
35
+ @declarations ||= {}
36
+ end
37
+
38
+ def joint_prefixes
39
+ @joint_prefixes ||= load_joint_prefixes
40
+ end
41
+
42
+ def load_joint_prefixes
43
+ path = File.join(CORE_DIR, "joint_prefixes.yaml")
44
+ data = YAML.safe_load_file(path)
45
+ data.fetch("joint_prefixes")
46
+ .transform_values(&:freeze)
47
+ .freeze
48
+ rescue Errno::ENOENT, KeyError => e
49
+ raise InvalidError, "cannot load #{path}: #{e.message}"
50
+ end
51
+
52
+ def build_declaration(flavor)
53
+ path = declaration_path(flavor)
54
+ data = YAML.safe_load_file(path)
55
+ validate!(flavor, path, data)
56
+ Declaration.new(
57
+ flavor: data.fetch("flavor"),
58
+ schema_version: data.fetch("schema_version"),
59
+ prefixes: data.fetch("prefixes"),
60
+ identifier_types: parse_identifier_types(data),
61
+ ).freeze
62
+ end
63
+
64
+ def declaration_path(flavor)
65
+ path = File.join(SCHEMA_ROOT, "#{flavor}.yaml")
66
+ unless File.exist?(path)
67
+ raise NotFoundError, "no declaration for #{flavor} at #{path}"
68
+ end
69
+
70
+ path
71
+ end
72
+
73
+ def parse_identifier_types(data)
74
+ data.fetch("identifier_types")
75
+ .map { |entry| parse_identifier_type(entry) }
76
+ end
77
+
78
+ def parse_identifier_type(entry)
79
+ IdentifierType.new(
80
+ key: entry.fetch("key"),
81
+ title: entry.fetch("title"),
82
+ short: entry["short"],
83
+ abbr: entry["abbr"] || [],
84
+ typed_stages: parse_typed_stages(entry),
85
+ )
86
+ end
87
+
88
+ def parse_typed_stages(entry)
89
+ (entry["typed_stages"] || []).map { |stage| parse_typed_stage(stage) }
90
+ end
91
+
92
+ def parse_typed_stage(stage)
93
+ TypedStage.new(
94
+ stage_code: stage.fetch("stage_code"),
95
+ type_code: stage["type_code"],
96
+ abbr: stage.fetch("abbr"),
97
+ name: stage["name"],
98
+ harmonized_stages: stage["harmonized_stages"] || [],
99
+ )
100
+ end
101
+
102
+ def validate!(flavor, path, data)
103
+ raise InvalidError, "#{path}: not a mapping" unless data.is_a?(Hash)
104
+
105
+ %w[flavor schema_version prefixes identifier_types].each do |key|
106
+ next if data.key?(key)
107
+
108
+ raise InvalidError, "#{path}: missing required key #{key}"
109
+ end
110
+ return unless data["flavor"] != flavor
111
+
112
+ raise InvalidError, "#{path}: flavor/file mismatch"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Schema
5
+ # One typed stage entry of an identifier type declaration.
6
+ class TypedStage
7
+ attr_reader :stage_code, :type_code, :abbr, :name, :harmonized_stages
8
+
9
+ def initialize(stage_code:, type_code: nil, abbr: [], name: nil,
10
+ harmonized_stages: [])
11
+ @stage_code = stage_code
12
+ @type_code = type_code
13
+ @abbr = abbr.freeze
14
+ @name = name
15
+ @harmonized_stages = harmonized_stages.freeze
16
+ freeze
17
+ end
18
+
19
+ def matches_abbr?(candidate)
20
+ abbr.include?(candidate)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Pubid
6
+ # Single source of truth declarations (schema/ directory). TODO 03 of the
7
+ # SSOT restructure: loader and value objects. TODO 04-07 make the runtime
8
+ # a consumer of these declarations (registry bridge, prefixes, metadata).
9
+ module Schema
10
+ autoload :Error, "pubid/schema/error"
11
+ autoload :NotFoundError, "pubid/schema/error"
12
+ autoload :InvalidError, "pubid/schema/error"
13
+ autoload :TypedStage, "pubid/schema/typed_stage"
14
+ autoload :IdentifierType, "pubid/schema/identifier_type"
15
+ autoload :Declaration, "pubid/schema/declaration"
16
+ autoload :Loader, "pubid/schema/loader"
17
+
18
+ class << self
19
+ # Pubid::Schema.for("iso") => frozen Pubid::Schema::Declaration
20
+ def for(flavor)
21
+ Loader.for(flavor)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -13,7 +13,7 @@ module Pubid
13
13
  suffix: data[:suffix]&.to_s,
14
14
  parts: extract_part_strings(data[:parts]),
15
15
  release: data[:release]&.to_s,
16
- version: data[:version].to_s,
16
+ version: data[:version]&.to_s,
17
17
  )
18
18
  end
19
19
 
@@ -14,7 +14,13 @@ module Pubid
14
14
  attribute :suffix, :string
15
15
  # 1–2 hyphen parts (e.g. "26.171-1", "29.198-04-1"); strings preserve any
16
16
  # zero-padding.
17
- attribute :parts, :string, collection: true
17
+ # `initialize_empty` makes a part-less identifier hold `[]` on BOTH
18
+ # construction paths: Builder always passes an array, but the serialized
19
+ # hash carries no `parts` key, so from_hash used to leave it nil and the
20
+ # two compared unequal — which silently broke `#matches?` (and so every
21
+ # part-less relaton index lookup). It does not change the serialized
22
+ # shape: lutaml omits an empty collection either way. See CLAUDE.md.
23
+ attribute :parts, :string, collection: true, initialize_empty: true
18
24
  # Raw release token, stored verbatim: "REL-4", "Ph1", "UMTS",
19
25
  # "Release 2000".
20
26
  attribute :release, :string
@@ -73,18 +79,28 @@ module Pubid
73
79
  @with_publisher = false
74
80
  end
75
81
 
76
- # Parse a 3GPP identifier string into an identifier object
82
+ # Parse a 3GPP identifier string into an identifier object.
83
+ #
84
+ # Let Parslet::ParseFailed propagate on a bad reference (matching ISO and
85
+ # ETSI), so relaton-cli's `rescue Parslet::ParseFailed` fetch handler
86
+ # catches it instead of a bare RuntimeError. The length guard keeps its
87
+ # ArgumentError — that is a different failure (input too long, not
88
+ # unparseable) and it is the ReDoS barrier.
89
+ #
77
90
  # @param identifier [String] The 3GPP identifier string to parse
78
91
  # @return [Identifier] The appropriate identifier object
79
92
  def self.parse(identifier)
93
+ unless identifier.is_a?(String)
94
+ raise Pubid::Errors::InvalidInputError,
95
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
96
+ end
97
+
80
98
  if identifier.length > Pubid::MAX_INPUT_LENGTH
81
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
99
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
82
100
  end
83
101
 
84
102
  parsed = Parser.parse(identifier)
85
103
  Builder.build(parsed)
86
- rescue Parslet::ParseFailed => e
87
- raise "Failed to parse 3GPP identifier '#{identifier}': #{e.message}"
88
104
  end
89
105
  end
90
106
  end
@@ -4,8 +4,8 @@ module Pubid
4
4
  module Tgpp
5
5
  module Identifiers
6
6
  # Technical Report identifier type.
7
- # Format: [3GPP ]TR NN.NNN[suffix][-part…]:<release>/<version>
8
- # Example: TR 00.01U:UMTS/3.0.0
7
+ # Format: [3GPP ]TR NN.NNN[suffix][-part…][:<release>][/<version>]
8
+ # Example: TR 00.01U:UMTS/3.0.0, or the partial "TR 00.01U".
9
9
  class TechnicalReport < Pubid::Tgpp::Identifier
10
10
  TYPED_STAGES = [
11
11
  Pubid::Components::TypedStage.new(
@@ -4,8 +4,8 @@ module Pubid
4
4
  module Tgpp
5
5
  module Identifiers
6
6
  # Technical Specification identifier type.
7
- # Format: [3GPP ]TS NN.NNN[suffix][-part…]:<release>/<version>
8
- # Example: TS 23.207:REL-4/2.0.0
7
+ # Format: [3GPP ]TS NN.NNN[suffix][-part…][:<release>][/<version>]
8
+ # Example: TS 23.207:REL-4/2.0.0, or the partial "TS 23.207".
9
9
  class TechnicalSpecification < Pubid::Tgpp::Identifier
10
10
  TYPED_STAGES = [
11
11
  Pubid::Components::TypedStage.new(
@@ -6,12 +6,18 @@ module Pubid
6
6
  module Tgpp
7
7
  # Parslet grammar for 3GPP identifiers.
8
8
  #
9
- # Form: `[3GPP ]<TR|TS> <NN.NNN>[suffix][-part…]:<release>/<version>`
9
+ # Form: `[3GPP ]<TR|TS> <NN.NNN>[suffix][-part…][:<release>][/<version>]`
10
10
  # TS 23.207:REL-4/2.0.0
11
11
  # 3GPP TR 00.01U:UMTS/3.0.0
12
12
  # TS 29.198-04-1:REL-5/5.0.0
13
13
  # TS 02.68:Release 2000/9.0.0
14
- class Parser < Parslet::Parser
14
+ # 3GPP TS 23.207 (bare reference)
15
+ #
16
+ # The release and the version are both optional: a user reference names the
17
+ # document alone ("3GPP TS 23.207"), and relaton parses that bare form to
18
+ # search the index. Only the type and the dotted number core are required,
19
+ # so "TS" and "TS foo" are still rejected.
20
+ class Parser < ::Pubid::Parser::Grammar
15
21
  rule(:digit) { match["0-9"] }
16
22
  rule(:digits) { digit.repeat(1) }
17
23
  rule(:space) { str(" ") }
@@ -37,19 +43,29 @@ module Pubid
37
43
  # Excludes ":" (as well as "/") so the ":"-delimited URN scheme can always
38
44
  # encode it — every real release ("REL-4", "Ph1", "UMTS", "Release 2000")
39
45
  # is colon-free.
40
- rule(:release) { match["^/:"].repeat(1).as(:release) }
46
+ #
47
+ # A trailing segment shaped exactly like a version is a mistyped "/"
48
+ # separator ("TS 23.207:2.0.0"), not a release: no release in the 88,464
49
+ # published index rows contains a dot at all. Reject it rather than
50
+ # silently misfiling the version as the release — which only became
51
+ # reachable once the version segment was made optional.
52
+ rule(:release) do
53
+ (version_core >> any.absent?).absent? >>
54
+ match["^/:"].repeat(1).as(:release)
55
+ end
41
56
 
42
57
  # Three-part version, kept as a string.
43
- rule(:version) do
44
- (digits >> str(".") >> digits >> str(".") >> digits).as(:version)
58
+ rule(:version_core) do
59
+ digits >> str(".") >> digits >> str(".") >> digits
45
60
  end
61
+ rule(:version) { version_core.as(:version) }
46
62
 
47
63
  rule(:identifier) do
48
64
  publisher_prefix.maybe >>
49
65
  type >> space >>
50
66
  number_core >> suffix.maybe >> parts >>
51
67
  (str(":") >> release).maybe >>
52
- str("/") >> version
68
+ (str("/") >> version).maybe
53
69
  end
54
70
 
55
71
  rule(:root) { identifier }
@@ -9,6 +9,7 @@ module Pubid
9
9
  # "3GPP TS 23.207:REL-4/2.0.0" (with_publisher: true)
10
10
  # "TS 29.198-04-1:REL-5/5.0.0" (parts)
11
11
  # "TR 00.01U:UMTS/3.0.0" (suffix)
12
+ # "TS 23.207" (partial reference)
12
13
  class Renderer < ::Pubid::Renderers::Base
13
14
  PUBLISHER = "3GPP"
14
15
 
@@ -17,15 +18,30 @@ module Pubid
17
18
  head = []
18
19
  head << PUBLISHER if with_publisher?(id)
19
20
  head << id.type_prefix
20
- result = "#{head.join(' ')} #{id.code}"
21
- # A handful of legacy records omit the release segment entirely
22
- # (e.g. "TS 29.215/2.0.0").
23
- result += ":#{id.release}" if id.release
24
- "#{result}/#{id.version}"
21
+ "#{head.join(' ')} #{id.code}#{qualifiers(id)}"
25
22
  end
26
23
 
27
24
  private
28
25
 
26
+ # The trailing ":<release>" and "/<version>" qualifiers. Both are
27
+ # optional: a handful of legacy records omit the release entirely
28
+ # (e.g. "TS 29.215/2.0.0"), and a partial user reference omits both
29
+ # (e.g. "TS 23.207"). Render no separator for an absent one.
30
+ #
31
+ # Blank, not just nil: the grammar cannot produce "", but a hand-built
32
+ # hash can, and a dangling "TS 23.207:" would not re-parse. This mirrors
33
+ # the same guard in UrnGenerator#tail_segments.
34
+ def qualifiers(id)
35
+ result = +""
36
+ result << ":#{id.release}" unless blank?(id.release)
37
+ result << "/#{id.version}" unless blank?(id.version)
38
+ result
39
+ end
40
+
41
+ def blank?(value)
42
+ value.nil? || value.empty?
43
+ end
44
+
29
45
  # Default is no publisher token (the relaton index id form); the caller
30
46
  # opts in via `to_s(with_publisher: true)`.
31
47
  def with_publisher?(id)