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
@@ -15,13 +15,42 @@ module Pubid
15
15
  # Type is stored in @type attribute from Identifier
16
16
  # All rendering handled by Identifier class
17
17
 
18
- # Compact serialization (mirrors ISO/JCGM/OIML): the Code component
19
- # collapses to a bare `number` scalar (plus a `parts` array and, rarely,
20
- # a `minor` scalar); Version flattens to a scalar `version` string with
21
- # an `is_edition` boolean flag (omitted when false); Date flattens to
22
- # top-level year/month/day. The constant `publisher` ("ETSI") is
23
- # intentionally NOT mapped — it is reconstructed on load from its
24
- # attribute default, keeping the hash compact.
18
+ # --- Index columns -------------------------------------------------
19
+ #
20
+ # relaton-index sorts and bsearches every row on `id.root.number.to_s`,
21
+ # so the document number must be reachable as `number`. ETSI used to
22
+ # keep it inside an Etsi::Components::Code under a `code` attribute,
23
+ # leaving the inherited `number` nil and every index row keyed "".
24
+ #
25
+ # The three Code fields are now plain sibling attributes, which is the
26
+ # shape the serialized hash ALREADY had — the converters below merely
27
+ # read them directly instead of reaching through `code`, so the wire
28
+ # format is byte-identical and relaton-data-etsi's published index-v2
29
+ # needs no regeneration.
30
+ #
31
+ # `number` holds the BARE number ("300 175"), never the part, so all
32
+ # parts of a document share one index bucket and a part-less reference
33
+ # finds them all — the IEEE/ISO semantics documented in CLAUDE.md.
34
+ #
35
+ # LANDMINE: these redeclare attributes inherited from ::Pubid::Identifier
36
+ # (`number` is a Components::Code there, and Etsi::Components::Code is
37
+ # NOT a subclass of it). That resolves nondeterministically under
38
+ # multi-flavor load when declared on a class others inherit from, so it
39
+ # is confined to this LEAF — EtsiStandard has no subclasses, while the
40
+ # shared Pubid::Etsi::Identifier is also SupplementIdentifier's parent
41
+ # and must stay untouched. spec/pubid/etsi/root_number_spec.rb carries
42
+ # the tripwire and is only meaningful under the full `rake` suite.
43
+ attribute :number, :string
44
+ attribute :minor, :string
45
+ attribute :parts, :string, collection: true, default: -> { [] }
46
+
47
+ # Compact serialization (mirrors ISO/JCGM/OIML): the code fields are
48
+ # bare scalars (`number`, a `parts` array and, rarely, a `minor`);
49
+ # Version flattens to a scalar `version` string with an `is_edition`
50
+ # boolean flag (omitted when false); Date flattens to top-level
51
+ # year/month/day. The constant `publisher` ("ETSI") is intentionally
52
+ # NOT mapped — it is reconstructed on load from its attribute default,
53
+ # keeping the hash compact.
25
54
  #
26
55
  # This block lives on EtsiStandard (a leaf) rather than the shared Identifier:
27
56
  # SupplementIdentifier is a sibling that DELEGATES type/code/version and
@@ -30,9 +59,9 @@ module Pubid
30
59
  key_value do
31
60
  map "_type", to: :_type
32
61
  map "type", to: :type
33
- map "number", with: { to: :number_to_kv, from: :number_from_kv }
34
- map "parts", with: { to: :parts_to_kv, from: :parts_from_kv }
35
- map "minor", with: { to: :minor_to_kv, from: :minor_from_kv }
62
+ map "number", to: :number
63
+ map "parts", to: :parts
64
+ map "minor", to: :minor
36
65
  map "version", with: { to: :version_to_kv, from: :version_from_kv }
37
66
  map "is_edition", with: { to: :edition_to_kv, from: :edition_from_kv }
38
67
  map "year", with: { to: :year_to_kv, from: :year_from_kv }
@@ -40,36 +69,90 @@ module Pubid
40
69
  map "day", with: { to: :day_to_kv, from: :day_from_kv }
41
70
  end
42
71
 
43
- # --- Code component flattened to bare number / parts / minor ---
44
- def number_to_kv(model, doc)
45
- emit_kv(doc, "number", model.code&.number)
46
- end
47
-
48
- def number_from_kv(model, value)
49
- code_for(model).number = value.to_s
50
- end
51
-
52
- def minor_to_kv(model, doc)
53
- emit_kv(doc, "minor", model.code&.minor)
54
- end
55
-
56
- def minor_from_kv(model, value)
57
- code_for(model).minor = value.to_s
58
- end
59
-
60
- def parts_to_kv(model, doc)
61
- parts = model.code&.parts
62
- return if parts.nil? || parts.empty?
63
-
64
- doc.add_child(
65
- Lutaml::KeyValue::DataModel::Element.new(
66
- "parts", parts.map(&:to_s)
67
- ),
72
+ # The composed document code, rebuilt on demand from the split columns
73
+ # so the renderer, the URN generator and #== keep one place to ask for
74
+ # the printed form ("300 175-1"). Deliberately NOT an attribute and NOT
75
+ # memoised: `number`/`parts` are writable (the base #exclude rebuilds
76
+ # through them), so a cached Code would go stale.
77
+ #
78
+ # nil when there is no number, preserving the old nil-`code` semantics
79
+ # the URN generator branches on.
80
+ def code
81
+ return nil if number.nil?
82
+
83
+ Pubid::Etsi::Components::Code.new(
84
+ number: number,
85
+ minor: minor,
86
+ parts: parts || [],
68
87
  )
69
88
  end
70
89
 
71
- def parts_from_kv(model, value)
72
- code_for(model).parts = Array(value).map(&:to_s)
90
+ # MR slug number segment.
91
+ #
92
+ # The base hook reads the inherited `number` and `part`; ETSI kept
93
+ # neither, so before the index columns landed every ETSI slug was just
94
+ # `etsi.<date>` — 454 distinct slugs for 24,724 documents, and
95
+ # `to_slug` is what consumers use as an output FILENAME.
96
+ #
97
+ # Two things the base hook cannot do for ETSI:
98
+ # * the parts live in the `parts` collection, not the inherited
99
+ # scalar `part`, so they must be appended here or every part of a
100
+ # document collapses onto one slug;
101
+ # * an ETSI number legitimately contains a SPACE ("300 175", "GSM
102
+ # 02.01"), which is outside the [a-z0-9.-] charset Renderers::MrString
103
+ # documents and guarantees. Sanitize BY CHARSET rather than by an
104
+ # enumerated escape list (the BIPM `mr_slug` precedent in
105
+ # CLAUDE.md), so a character added later cannot reach a filename.
106
+ # `minor` is included because it is identity-bearing everywhere else —
107
+ # Code#== compares it, Code#to_s renders it and the URN carries it — so
108
+ # omitting it here would let two distinct documents share one filename.
109
+ # No fixture populates it today; it is in the grammar, so it is here.
110
+ def mr_number_with_part
111
+ return nil if number.nil?
112
+
113
+ mr_sanitize([number, minor, *Array(parts)].compact.join("-"))
114
+ end
115
+
116
+ # ETSI's identity is type + code + version + date (see
117
+ # Pubid::Etsi::Identifier#==), so all four must reach the slug —
118
+ # CLAUDE.md's rule that an identity-bearing marker has to appear on
119
+ # every identity surface, not just in `==`. The base hooks find
120
+ # neither: `mr_type` reads `typed_stage` (ETSI has none) and
121
+ # `mr_edition` reads `edition.number` (ETSI models the edition as
122
+ # `version`). Without these, "ETSI EN 300 729 V6.1.1 (2000-04)",
123
+ # "…V7.1.1 (2000-04)" and "ETSI ETS 300 729 ed.2 (2000-04)" all share
124
+ # one slug — i.e. one output filename.
125
+ def mr_type
126
+ type&.to_s&.downcase
127
+ end
128
+
129
+ def mr_edition
130
+ mr_sanitize(version&.version)
131
+ end
132
+
133
+ # Segment sanitizer. Renderers::MrString joins SEGMENTS with ".", so a
134
+ # dot inside one would break that structure — hence "." is replaced
135
+ # here even though it is inside the renderer's overall charset (the
136
+ # BIPM `bipm.si-brochure.9e-v3-01.e` precedent in CLAUDE.md). Filtering
137
+ # by charset rather than by an enumerated escape list keeps a character
138
+ # introduced later from leaking into a filename.
139
+ #
140
+ # Public purely to keep this file's surface flat, next to the
141
+ # lutaml-invoked converters below — it has no external callers and
142
+ # would work as well private.
143
+ #
144
+ # The emptiness check is on the OUTPUT, not just the input: a value
145
+ # made entirely of characters outside the charset collapses to "-" and
146
+ # then to "", and an empty string is truthy in Ruby, so it would reach
147
+ # Renderers::MrString as a blank segment and produce a double dot in
148
+ # the joined slug. Unreachable from today's grammar, which only feeds
149
+ # digit/alnum captures here — but not structurally prevented.
150
+ def mr_sanitize(value)
151
+ return nil if value.nil? || value.to_s.empty?
152
+
153
+ sanitized = value.to_s.downcase
154
+ .gsub(/[^a-z0-9-]+/, "-").gsub(/\A-+|-+\z/, "")
155
+ sanitized.empty? ? nil : sanitized
73
156
  end
74
157
 
75
158
  # --- Version flattened to scalar version + is_edition flag ---
@@ -126,10 +209,6 @@ module Pubid
126
209
  )
127
210
  end
128
211
 
129
- def code_for(model)
130
- model.code ||= Pubid::Etsi::Components::Code.new
131
- end
132
-
133
212
  def version_for(model)
134
213
  model.version ||= Pubid::Etsi::Components::Version.new
135
214
  end
@@ -88,6 +88,16 @@ module Pubid
88
88
  raise NotImplementedError
89
89
  end
90
90
 
91
+ # Make Renderers::MrString recurse into `base` instead of slugging the
92
+ # supplement flat. Without this the flat path reads the supplement's own
93
+ # `number` — its ordinal, 1 — and the base document is dropped entirely,
94
+ # so every "/C1" corrigendum published in one month shared the slug
95
+ # `etsi.1.<date>`. `to_slug` is an output FILENAME, so that is an
96
+ # overwrite. Same fix as ITU's AnnexOfRecommendation (see CLAUDE.md).
97
+ def mr_supplement_suffix
98
+ [supplement_notation.to_s.downcase, number].compact.join(".")
99
+ end
100
+
91
101
  def ==(other)
92
102
  return false unless other.is_a?(SupplementIdentifier)
93
103
  return false unless other.class == self.class
@@ -4,7 +4,7 @@ require "parslet"
4
4
 
5
5
  module Pubid
6
6
  module Etsi
7
- class Parser < Parslet::Parser
7
+ class Parser < ::Pubid::Parser::Grammar
8
8
  # Basic elements
9
9
  rule(:digit) { match["0-9"] }
10
10
  rule(:digits) { digit.repeat(1) }
data/lib/pubid/etsi.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  require "lutaml/model"
4
5
 
5
6
  module Pubid
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ # Builds an EVS national adoption from the composed parse tree: the
6
+ # structured :adopted subtree (produced by the embedded CEN grammar) goes
7
+ # straight to the CEN/CENELEC builder as data — no string
8
+ # re-serialization anywhere.
9
+ class Builder < Pubid::Builder::Base
10
+ def self.build(parsed_data)
11
+ new.build(parsed_data)
12
+ end
13
+
14
+ def build(data)
15
+ data = flatten_array(data) if data.is_a?(Array)
16
+
17
+ adopted = Pubid::CenCenelec::Builder.build(data[:adopted])
18
+
19
+ Identifiers::NationalAdoption.new(
20
+ adopted_identifier: adopted,
21
+ separator: data[:evs_separator] || "-",
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ # An EVS national adoption wraps a CEN identifier the same way BSI's
6
+ # AdoptedEuropeanNorm does — polymorphic `adopted_identifier` so the
7
+ # wrapped object stays a real Pubid::CenCenelec identifier.
8
+ #
9
+ # Examples:
10
+ # "EVS-EN 18216:2026" (adopted_identifier: EN 18216:2026)
11
+ # "EVS-EN ISO 14001:2026" (adopted_identifier: EN ISO 14001:2026)
12
+ # "EVS-EN ISO/IEC 27017:2026" (adopted_identifier: EN ISO/IEC 27017:2026)
13
+ # "EVS-EN ISO 9001:2015/A1:2024" (adopted_identifier: EN ISO 9001:2015/A1:2024)
14
+ class Identifier < ::Pubid::Identifier
15
+ attribute :adopted_identifier, ::Pubid::Identifier, polymorphic: true
16
+ # "-" or " " — preserves the printed separator ("EVS-EN" vs "EVS EN")
17
+ attribute :separator, :string, default: -> { "-" }
18
+
19
+ def to_s(**opts)
20
+ render(format: :human, **opts)
21
+ end
22
+
23
+ def self.parse(string)
24
+ unless string.is_a?(String)
25
+ raise Pubid::Errors::InvalidInputError,
26
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
27
+ end
28
+
29
+ if string.length > Pubid::MAX_INPUT_LENGTH
30
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
31
+ end
32
+
33
+ Pubid::Evs.parse(string)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ module Identifiers
6
+ # The EVS national adoption of a European Standard.
7
+ # Example: "EVS-EN 18216:2026"
8
+ class NationalAdoption < ::Pubid::Evs::Identifier
9
+ attribute :type, Components::Type, default: -> { self.class.type[:key] }
10
+
11
+ def self.type
12
+ { key: :evs_en, title: "EVS National Adoption of a European Standard" }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ module Identifiers
6
+ autoload :NationalAdoption, "#{__dir__}/identifiers/national_adoption"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Pubid
6
+ module Evs
7
+ # Grammar for the printed EVS national-adoption reference, composed with
8
+ # the CEN/CENELEC parser as an embedded atom (the same pattern ISO uses
9
+ # for IDF joint identifiers, and IEEE for AIEE/IRE/NESC):
10
+ #
11
+ # "EVS" ("-" | " ") <CEN identifier>
12
+ #
13
+ # The adopted portion is parsed by the real CEN grammar — publisher,
14
+ # adopted org, number, parts, year, supplements, edition — and captured
15
+ # as a structured :adopted subtree for the CEN builder.
16
+ #
17
+ # "EVS-EN 18216:2026" → {evs_separator, adopted: {publisher, number, year}}
18
+ # "EVS-EN ISO 9001:2015/A1:2024" → {evs_separator, adopted: {publisher, adopted_string, supplements}}
19
+ class Parser < ::Pubid::Parser::Grammar
20
+ rule(:root) do
21
+ str("EVS") >>
22
+ (str("-") | str(" ")).as(:evs_separator) >>
23
+ ::Pubid::CenCenelec::Parser.new.identifier.as(:adopted)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ # Human-readable renderer for EVS identifiers.
6
+ #
7
+ # Produces strings like:
8
+ # "EVS-EN 18216:2026"
9
+ # "EVS-EN ISO 14001:2026"
10
+ # "EVS-EN ISO/IEC 27017:2026"
11
+ # "EVS-EN ISO 9001:2015/A1:2024"
12
+ class Renderer < ::Pubid::Renderers::Base
13
+ def render(context: nil, **_opts)
14
+ # The adopted identifier renders with its own "EN …" prefix, so the
15
+ # national prefix composes as "EVS" + separator + adopted.
16
+ "EVS#{separator}#{adopted}"
17
+ end
18
+
19
+ private
20
+
21
+ def separator
22
+ @id.separator || "-"
23
+ end
24
+
25
+ def adopted
26
+ @id.adopted_identifier.to_s
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ # URNs mirror the CEN adoption namespace with the national-body prefix:
6
+ #
7
+ # "EVS-EN 18216:2026" → urn:evs:en:18216:2026
8
+ # "EVS-EN ISO 14001:2026" → urn:evs:en:iso:14001:2026
9
+ # "EVS-EN ISO/IEC 27017:2026" → urn:evs:en:iso-iec:27017:2026
10
+ # "EVS-EN ISO 9001:2015/A1:2024" → urn:evs:en:iso:9001:2015:amd:1:2024
11
+ #
12
+ # Generation delegates to the adopted CEN identifier's URN and swaps the
13
+ # `urn:cen:` namespace for `urn:evs:`, so the two stay consistent by
14
+ # construction.
15
+ class UrnGenerator < Pubid::UrnGenerator::Base
16
+ CEN_NAMESPACE = "urn:cen:"
17
+ EVS_NAMESPACE = "urn:evs:"
18
+
19
+ def generate
20
+ adopted_urn = identifier.adopted_identifier.to_urn
21
+ unless adopted_urn.start_with?(CEN_NAMESPACE)
22
+ raise Errors::ParseError,
23
+ "expected adopted CEN URN, got #{adopted_urn.inspect}"
24
+ end
25
+
26
+ adopted_urn.sub(/\A#{Regexp.escape(CEN_NAMESPACE)}/o, EVS_NAMESPACE)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Evs
5
+ # Parses `urn:evs:…` URNs back into identifiers, following the same
6
+ # pattern as Pubid::CenCenelec::UrnParser: reassemble the adopted
7
+ # identifier's human text and delegate to the real grammar so every
8
+ # structural check happens in parsers, not in this class.
9
+ #
10
+ # urn:evs:en:18216:2026 → EVS-EN 18216:2026
11
+ # urn:evs:en:iso:14001:2026 → EVS-EN ISO 14001:2026
12
+ # urn:evs:en:iso-iec:27017:2026 → EVS-EN ISO/IEC 27017:2026
13
+ # urn:evs:en:iso:9001:2015:amd:1:2024 → EVS-EN ISO 9001:2015/A1:2024
14
+ class UrnParser < Pubid::UrnParser::Base
15
+ ORGS = {
16
+ "iso" => "ISO",
17
+ "iso-iec" => "ISO/IEC",
18
+ "iec" => "IEC",
19
+ "cispr" => "CISPR",
20
+ }.freeze
21
+
22
+ # UrnGenerator emits "urn:evs:" (national-body namespace).
23
+ def flavor_name
24
+ "evs"
25
+ end
26
+
27
+ def parse_urn(urn)
28
+ parts = split_parts(strip_namespace(urn))
29
+
30
+ type = parts.shift
31
+ unless /\Aen\z/i.match?(type)
32
+ raise Errors::ParseError, "unsupported type in #{urn.inspect}"
33
+ end
34
+
35
+ text = +"EN"
36
+ org_parts = []
37
+ org_parts << parts.shift while parts.first&.match?(/\A[a-z][a-z-]*\z/)
38
+ if org_parts.any?
39
+ org = ORGS.fetch(org_parts.join("-")) do
40
+ raise Errors::ParseError, "unknown adopted org in #{urn.inspect}"
41
+ end
42
+ text << " #{org}"
43
+ end
44
+
45
+ number = parts.shift
46
+ year = parts.shift
47
+ unless number
48
+ raise Errors::ParseError,
49
+ "missing number in #{urn.inspect}"
50
+ end
51
+
52
+ text << " #{number}"
53
+ text << ":#{year}" if year
54
+
55
+ append_supplement(text, parts, urn) unless parts.empty?
56
+
57
+ adopted = Pubid::CenCenelec.parse(text)
58
+ ::Pubid::Evs::Identifiers::NationalAdoption.new(
59
+ adopted_identifier: adopted,
60
+ )
61
+ end
62
+
63
+ private
64
+
65
+ # Appends the trailing `amd|cor : iteration [: year]` in human form.
66
+ def append_supplement(text, parts, urn)
67
+ year = parts.last&.match?(/\A\d{4}\z/) ? parts.pop : nil
68
+ kind = parts.shift
69
+ iteration = parts.shift
70
+ unless %w[amd cor].include?(kind) && iteration && parts.empty?
71
+ raise Errors::ParseError, "malformed supplement in #{urn.inspect}"
72
+ end
73
+
74
+ text << (kind == "amd" ? "/A#{iteration}" : "/AC#{iteration}")
75
+ text << ":#{year}" if year
76
+ text
77
+ end
78
+ end
79
+ end
80
+ end
data/lib/pubid/evs.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pubid"
4
+ module Pubid
5
+ module Evs
6
+ extend Pubid::PrefixesSupport
7
+
8
+ # Leading tokens a printed EVS reference can start with, drawn from the
9
+ # reference PDFs in metanorma/mn-samples-evs-private. Longest first so
10
+ # prefix routing picks the most specific claim. Deliberately EXCLUDES the
11
+ # bare adopted forms ("EN …" alone routes to Pubid::CenCenelec).
12
+ PREFIXES = [
13
+ "EVS-EN ISO/IEC", "EVS-EN ISO", "EVS-EN",
14
+ "EVS EN ISO/IEC", "EVS EN ISO", "EVS EN",
15
+ "EVS"
16
+ ].freeze
17
+
18
+ autoload :Builder, "#{__dir__}/evs/builder"
19
+ autoload :Identifier, "#{__dir__}/evs/identifier"
20
+ autoload :Identifiers, "#{__dir__}/evs/identifiers"
21
+ autoload :Parser, "#{__dir__}/evs/parser"
22
+ autoload :Renderer, "#{__dir__}/evs/renderer"
23
+ autoload :UrnGenerator, "#{__dir__}/evs/urn_generator"
24
+ autoload :UrnParser, "#{__dir__}/evs/urn_parser"
25
+
26
+ def self.parse(identifier)
27
+ unless identifier.is_a?(String)
28
+ raise Pubid::Errors::InvalidInputError,
29
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
30
+ end
31
+
32
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
33
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
34
+ end
35
+
36
+ parsed = Parser.new.parse(identifier)
37
+ Builder.new.build(parsed)
38
+ end
39
+
40
+ # Build an EVS identifier from a parse-tree hash.
41
+ # @param hash [Hash] parse tree
42
+ # @return [Pubid::Evs::Identifier]
43
+ def self.build_from_parse(hash)
44
+ Builder.new.build(hash)
45
+ end
46
+
47
+ # Per-flavor format registry: inherits global formats, overrides :human.
48
+ Identifier.format_registry = FormatRegistry.new(parent: Identifier.format_registry)
49
+ Identifier.format_registry.register(:human, renderer: Evs::Renderer)
50
+
51
+ # Auto-discover all identifier types from the Identifiers namespace.
52
+ # @return [Array<Class>]
53
+ def self.identifier_types
54
+ @identifier_types ||= Identifiers.constants.filter_map do |c|
55
+ begin
56
+ Identifiers.const_get(c)
57
+ rescue NameError
58
+ nil
59
+ end
60
+ end
61
+ .select { |c| c.is_a?(Class) && c.singleton_methods(false).include?(:type) }
62
+ .select { |c| c.type.is_a?(Hash) }
63
+ end
64
+
65
+ # EVS identifiers have no typed stages.
66
+ # @return [Array<Pubid::Components::TypedStage>]
67
+ def self.all_typed_stages
68
+ @all_typed_stages ||= identifier_types.flat_map do |klass|
69
+ klass.const_defined?(:TYPED_STAGES) ? klass.const_get(:TYPED_STAGES) : []
70
+ end
71
+ end
72
+
73
+ # Lookup: type code -> identifier class
74
+ # @param code [String, Symbol]
75
+ # @return [Class, nil]
76
+ def self.locate_type(code)
77
+ identifier_types.find { |t| t.type[:key].to_s == code.to_s }
78
+ end
79
+
80
+ # Lookup: abbreviation -> typed stage
81
+ # @param _abbr [String, Symbol]
82
+ # @return [nil] EVS identifiers have no typed stages
83
+ def self.locate_stage(_abbr)
84
+ nil
85
+ end
86
+ end
87
+ end
88
+
89
+ # Register EVS flavor with the registry
90
+ Pubid::Registry.register(:evs, Pubid::Evs)
data/lib/pubid/export.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Export
5
6
  autoload :IdentifierTypeResult, "#{__dir__}/export/result"
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module FormatDetector
5
6
  URN_PATTERN = /\Aurn:/i
@@ -50,14 +50,17 @@ module Pubid
50
50
  # @param identifier [String]
51
51
  # @return [Pubid::Gb::Identifier]
52
52
  def self.parse(identifier)
53
+ unless identifier.is_a?(String)
54
+ raise Pubid::Errors::InvalidInputError,
55
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
56
+ end
57
+
53
58
  if identifier.length > Pubid::MAX_INPUT_LENGTH
54
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
59
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
55
60
  end
56
61
 
57
62
  parsed = Parser.parse(identifier)
58
63
  Builder.build(parsed)
59
- rescue Parslet::ParseFailed => e
60
- raise "Failed to parse Chinese Standard identifier '#{identifier}': #{e.message}"
61
64
  end
62
65
  end
63
66
  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("—") }
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
  #