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
data/lib/pubid/easc.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Easc
5
6
  extend Pubid::PrefixesSupport
@@ -10,21 +10,35 @@ module Pubid
10
10
  end
11
11
 
12
12
  def build(data)
13
- if data[:tr_number]
14
- Identifiers::TechnicalReport.new(number: data[:tr_number].to_s)
15
- elsif data[:mem_number]
16
- Identifiers::Memento.new(number: data[:mem_number].to_s)
17
- else
18
- build_standard(data)
19
- end
13
+ klass, attrs =
14
+ if data[:tr_number]
15
+ [Identifiers::TechnicalReport, { number: data[:tr_number].to_s }]
16
+ elsif data[:mem_number]
17
+ [Identifiers::Memento, { number: data[:mem_number].to_s }]
18
+ else
19
+ [Identifiers::Standard, standard_attrs(data)]
20
+ end
21
+
22
+ klass.new(**attrs, **suffix_attrs(data))
20
23
  end
21
24
 
22
25
  private
23
26
 
24
- def build_standard(data)
27
+ def standard_attrs(data)
25
28
  attrs = { number: data[:number].to_s }
26
29
  attrs[:part] = data[:part].to_s if data[:part]
27
- Identifiers::Standard.new(**attrs)
30
+ attrs
31
+ end
32
+
33
+ # ` ed<N>` / ` vol<N>` attach after the type alternation, so a technical
34
+ # report and a memento carry them exactly as a standard does. An absent
35
+ # suffix produces no parse key at all, so the attribute stays unset and
36
+ # drops out of the canonical hash.
37
+ def suffix_attrs(data)
38
+ attrs = {}
39
+ attrs[:edition] = data[:edition].to_s if data[:edition]
40
+ attrs[:volume] = data[:volume].to_s if data[:volume]
41
+ attrs
28
42
  end
29
43
  end
30
44
  end
@@ -6,17 +6,32 @@ module Pubid
6
6
  # mirrors Pubid::Jis::Identifier. Concrete identifiers under
7
7
  # Pubid::Ecma::Identifiers descend from this class, so a parsed ECMA id is
8
8
  # an instance of Pubid::Ecma::Identifier. ECMA has no supplement layer.
9
+ # KEEP THIS CLASS BODY IN ONE FILE, AND NEVER REOPEN IT. `number`, `part`
10
+ # and `edition` below redeclare attributes the parent ::Pubid::Identifier
11
+ # types as components, which resolves nondeterministically under
12
+ # multi-flavor load if a subclass can snapshot a half-built attribute
13
+ # table. lutaml deep-dups the parent table into each subclass at
14
+ # class-definition time, and Ruby resolves this superclass constant to
15
+ # completion before opening any leaf body — so a single, never-reopened
16
+ # body is what makes the redeclaration safe. (The W3C precedent; IEEE's
17
+ # split base is the counter-shape.)
9
18
  class Identifier < ::Pubid::Identifier
10
19
  # The document number as a string (preserves any leading zeros). `part`
11
20
  # is only present for standards that split into parts (e.g. ECMA-418-1).
12
21
  attribute :number, :string
13
22
  attribute :part, :string
14
- # Edition is separate metadata (the index stores {:id, :ed} and the YAML
15
- # has edition.content); it is NOT part of the printed identifier string.
16
- # Modelled as an optional string (decimal editions like "5.1" occur) that
17
- # serializes in `to_hash` — so relaton preserves `:ed:` — but is omitted
18
- # from `to_s`. No default, so it drops from the hash when unset.
23
+ # Edition is relaton's `:ed:` (the index stores {:id, :ed, :vol} and the
24
+ # YAML has edition.content). It is part of the printed identifier: the
25
+ # relaton index keys on a bare `to_s`, so without it all 22 editions of
26
+ # ECMA-74 collapse onto one key. An optional string, because decimal
27
+ # editions like "5.1" occur. No default, so it drops from the hash when
28
+ # unset.
19
29
  attribute :edition, :string
30
+ # Volume is relaton's `:vol:`. Only ECMA-269 edition 3 uses it: its four
31
+ # volumes share one docidentifier AND one title, so the volume is the only
32
+ # thing that distinguishes those four index rows. A plain string, the
33
+ # `number`/`edition` precedent, so it round-trips verbatim.
34
+ attribute :volume, :string
20
35
 
21
36
  # Polymorphic type map for lutaml::Model key_value (de)serialization:
22
37
  # maps each subclass's polymorphic_name to its class name so a stored
@@ -33,6 +48,7 @@ module Pubid
33
48
  map "number", to: :number
34
49
  map "part", to: :part
35
50
  map "edition", to: :edition
51
+ map "volume", to: :volume
36
52
  end
37
53
 
38
54
  # Publisher is always "ECMA". A plain constant (not a `publisher` method)
@@ -45,6 +61,11 @@ module Pubid
45
61
 
46
62
  # Basic string representation. Delegates to renderer. `with_publisher:
47
63
  # false` drops the ECMA token (e.g. "-411", "TR/84").
64
+ #
65
+ # The edition and volume suffixes are rendered by DEFAULT and are opted
66
+ # out of with `to_s(with_edition: false, with_volume: false)`. Both flags
67
+ # ride in `opts` to the renderer, which owns their defaults — see
68
+ # Pubid::Ecma::Renderer for why the default is "with".
48
69
  def to_s(with_publisher: true, **opts)
49
70
  @with_publisher = with_publisher
50
71
  render(format: :human, **opts)
@@ -56,20 +77,70 @@ module Pubid
56
77
  nil
57
78
  end
58
79
 
80
+ # ECMA keeps its publisher in the PUBLISHER constant rather than the
81
+ # inherited `publisher` attribute, so the base hook — `publisher&.to_s` —
82
+ # is nil and every ECMA slug would lose its "ecma." segment.
83
+ def mr_publisher
84
+ PUBLISHER.downcase
85
+ end
86
+
87
+ # The Builder picks an identifier class; it never sets a `typed_stage`,
88
+ # so the base hook is always nil. Without this, "ECMA-101" and
89
+ # "ECMA TR/101" produce the same slug — and `to_slug` is an output
90
+ # filename, so one document would overwrite the other.
91
+ def mr_type
92
+ type_prefix&.downcase
93
+ end
94
+
95
+ # ECMA's `edition` is a plain :string ("3", "5.1"), not a
96
+ # Components::Edition, so the base hook's `edition&.number` raises
97
+ # NoMethodError on every edition-bearing identifier.
98
+ def mr_edition
99
+ mr_marker("ed", edition)
100
+ end
101
+
102
+ # Volume is identity-bearing (ECMA-269 ed3 vol1..vol4 are four distinct
103
+ # documents), so it must reach the MR string as well as `to_s` and the
104
+ # URN. Fills the shared `mr_volume` slot in Renderers::MrString.
105
+ def mr_volume
106
+ mr_marker("vol", volume)
107
+ end
108
+
59
109
  # Parse an ECMA identifier string into an identifier object.
60
110
  # @param identifier [String] The ECMA identifier string to parse
61
111
  # @return [Identifier] The appropriate identifier object
62
- # @raise [ArgumentError] If the input exceeds MAX_INPUT_LENGTH
63
- # @raise [RuntimeError] If parsing fails
112
+ # @raise [Pubid::Errors::InvalidInputError]
113
+ # If the input exceeds MAX_INPUT_LENGTH
114
+ # @raise [Pubid::Errors::ParseError] If parsing fails
64
115
  def self.parse(identifier)
116
+ unless identifier.is_a?(String)
117
+ raise Pubid::Errors::InvalidInputError,
118
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
119
+ end
120
+
65
121
  if identifier.length > Pubid::MAX_INPUT_LENGTH
66
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
122
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
67
123
  end
68
124
 
69
125
  parsed = Parser.parse(identifier)
70
126
  Builder.build(parsed)
71
- rescue Parslet::ParseFailed => e
72
- raise "Failed to parse ECMA identifier '#{identifier}': #{e.message}"
127
+ end
128
+
129
+ private
130
+
131
+ # Renderers::MrString joins SEGMENTS with ".", so a dot inside one would
132
+ # break the documented segment structure — hence "ed5-1" for edition
133
+ # "5.1". Sanitising by CHARSET rather than by an enumerated escape list,
134
+ # so a value added later cannot leak an unsafe character (the BIPM
135
+ # `mr_slug` precedent).
136
+ #
137
+ # The MR may be lossy here because nothing parses an ECMA MR back
138
+ # (`Parsers::MrString::FLAVOR_MAP` has no ECMA entry). The URN, which
139
+ # UrnParser inverts, keeps the dot instead.
140
+ def mr_marker(label, value)
141
+ return nil if value.nil? || value.to_s.empty?
142
+
143
+ "#{label}#{value.to_s.downcase.gsub(/[^a-z0-9]+/, '-')}"
73
144
  end
74
145
  end
75
146
  end
@@ -6,16 +6,21 @@ module Pubid
6
6
  module Ecma
7
7
  # Parslet grammar for ECMA identifiers.
8
8
  #
9
- # Four printed forms (see HANDOFFS/ecma.md):
9
+ # Four printed forms:
10
10
  # ECMA-411 standard
11
11
  # ECMA-418-1 standard with part
12
12
  # ECMA TR/101 technical report
13
13
  # ECMA MEM/1970 memento
14
14
  #
15
+ # Any of them may carry an edition and a volume, in that order:
16
+ # ECMA-269 ed3 third edition
17
+ # ECMA-402 ed5.1 decimal edition (real: ECMA-402)
18
+ # ECMA-269 ed3 vol2 second volume of the third edition
19
+ #
15
20
  # Each branch captures its number under a distinct key so the builder can
16
21
  # pick the identifier class without a separate type token. Numbers are kept
17
22
  # as strings to preserve any leading zeros.
18
- class Parser < Parslet::Parser
23
+ class Parser < ::Pubid::Parser::Grammar
19
24
  rule(:digits) { match["0-9"].repeat(1) }
20
25
 
21
26
  rule(:prefix) { str("ECMA") }
@@ -24,12 +29,42 @@ module Pubid
24
29
 
25
30
  rule(:mem) { str(" MEM/") >> digits.as(:mem_number) }
26
31
 
32
+ # Standards print with a hyphen ("ECMA-6"), but references are commonly
33
+ # written with a space ("ECMA 6"). Both parse; the renderer normalises to
34
+ # the hyphen, so the space form round-trips as "ECMA-6" by design and must
35
+ # never enter the byte-exact `pass` fixtures.
36
+ rule(:standard_separator) { str("-") | str(" ") }
37
+
27
38
  rule(:standard) do
28
- str("-") >> digits.as(:number) >>
39
+ standard_separator >> digits.as(:number) >>
29
40
  (str("-") >> digits.as(:part)).maybe
30
41
  end
31
42
 
32
- rule(:identifier) { prefix >> (tr | mem | standard) }
43
+ # Editions are not all integers: ECMA-402 ed5.1 is a real document. The
44
+ # value is stored verbatim so it renders and serializes unchanged.
45
+ rule(:edition_number) { digits >> (str(".") >> digits).repeat(0) }
46
+
47
+ rule(:edition) { str(" ed") >> edition_number.as(:edition) }
48
+
49
+ rule(:volume) { str(" vol") >> digits.as(:volume) }
50
+
51
+ # The suffixes attach AFTER the type alternation, so a technical report
52
+ # and a memento carry them exactly as a standard does.
53
+ #
54
+ # Two PEG facts this arrangement depends on:
55
+ #
56
+ # 1. `standard` cannot shadow " TR/" or " MEM/", because after the space
57
+ # it demands a digit and "T"/"M" are not digits. Keeping tr|mem first
58
+ # is belt-and-braces; the DISJOINT FIRST CHARACTER is the load-bearing
59
+ # invariant.
60
+ # 2. Appending a sequence moves the alternation out of tail position, so
61
+ # Parslet no longer threads consume_all into it and will not backtrack
62
+ # into an alternative that already succeeded. A future branch that
63
+ # could match a proper prefix of another branch's input would now fail
64
+ # the whole parse instead of being rescued by the next alternative.
65
+ rule(:identifier) do
66
+ prefix >> (tr | mem | standard) >> edition.maybe >> volume.maybe
67
+ end
33
68
 
34
69
  rule(:root) { identifier }
35
70
 
@@ -5,28 +5,49 @@ module Pubid
5
5
  # Human-readable renderer for ECMA identifiers.
6
6
  #
7
7
  # Produces strings like:
8
- # "ECMA-411" standard
9
- # "ECMA-418-1" standard with part
10
- # "ECMA TR/101" technical report
11
- # "ECMA MEM/1970" memento
8
+ # "ECMA-411" standard
9
+ # "ECMA-418-1" standard with part
10
+ # "ECMA TR/101" technical report
11
+ # "ECMA MEM/1970" memento
12
+ # "ECMA-269 ed3 vol2" with the edition and volume suffixes
12
13
  #
13
14
  # With `with_publisher: false` the leading ECMA token is dropped, yielding
14
- # "-411" / "TR/101" / "MEM/1970". The edition is never rendered — it is
15
- # separate metadata carried only in `to_hash`.
15
+ # "-411" / "TR/101" / "MEM/1970".
16
16
  class Renderer < ::Pubid::Renderers::Base
17
17
  PUBLISHER = "ECMA"
18
18
 
19
- def render(context: nil, **opts)
19
+ # `with_edition` and `with_volume` default to TRUE — the inverse of
20
+ # pubid's usual opt-in, deliberately. `Relaton::Index::Type#add_or_update`
21
+ # keys on a BARE `id.to_s` and cannot pass options, so the default
22
+ # rendering is what the index keys on, and it has to be unique per row.
23
+ # With the edition omitted, all 22 editions of ECMA-74 collapsed onto one
24
+ # key and 383 of the 804 published rows were dropped silently.
25
+ #
26
+ # Per-flavor divergence in these defaults is the established pattern:
27
+ # Pubid::Tgpp defaults `with_publisher` to false and Pubid::W3c to true,
28
+ # each for its own consumer's reason.
29
+ def render(context: nil, with_edition: true, with_volume: true, **_opts)
20
30
  id = @id
21
31
  # Standard joins the publisher directly ("ECMA-411"); TR/MEM separate it
22
32
  # with a space ("ECMA TR/101"). type_prefix is nil only for standards.
23
33
  sep = id.type_prefix.nil? ? "" : " "
24
34
  core = id.type_prefix.nil? ? standard_core(id) : typed_core(id)
25
- with_publisher?(id) ? "#{PUBLISHER}#{sep}#{core}" : core
35
+ result = with_publisher?(id) ? "#{PUBLISHER}#{sep}#{core}" : core
36
+ result + suffixes(id, with_edition, with_volume)
26
37
  end
27
38
 
28
39
  private
29
40
 
41
+ # " ed<edition>" then " vol<volume>", each only when the identifier
42
+ # carries it and the caller has not opted out. The order is canonical:
43
+ # the grammar accepts the volume only after the edition.
44
+ def suffixes(id, with_edition, with_volume)
45
+ out = +""
46
+ out << " ed#{id.edition}" if with_edition && id.edition
47
+ out << " vol#{id.volume}" if with_volume && id.volume
48
+ out
49
+ end
50
+
30
51
  # Standard: "-<number>[-<part>]".
31
52
  def standard_core(id)
32
53
  core = "-#{id.number}"
@@ -2,23 +2,44 @@
2
2
 
3
3
  module Pubid
4
4
  module Ecma
5
- # Emits `urn:ecma:<tag>:<number>[:part-<part>]` where the tag is bare for
6
- # standards, "tr" for technical reports and "mem" for mementos:
7
- # ECMA-411 -> urn:ecma:411
8
- # ECMA-418-1 -> urn:ecma:418:part-1
9
- # ECMA TR/101 -> urn:ecma:tr:101
10
- # ECMA MEM/1970 -> urn:ecma:mem:1970
5
+ # Emits
6
+ # `urn:ecma:<tag>:<number>[:part-<part>][:ed-<edition>][:vol-<volume>]`,
7
+ # where the tag is bare for standards, "tr" for technical reports and "mem"
8
+ # for mementos:
9
+ # ECMA-411 -> urn:ecma:411
10
+ # ECMA-418-1 -> urn:ecma:418:part-1
11
+ # ECMA TR/101 -> urn:ecma:tr:101
12
+ # ECMA MEM/1970 -> urn:ecma:mem:1970
13
+ # ECMA-269 ed3 vol2 -> urn:ecma:269:ed-3:vol-2
14
+ # ECMA-402 ed5.1 -> urn:ecma:402:ed-5.1
15
+ #
16
+ # The edition and volume segments are additive: they only ever split a
17
+ # collision, so a URN minted before they existed still reads back (UrnParser
18
+ # probes for the labelled prefix). They are needed because edition and
19
+ # volume are identity-bearing — without them 804 published documents share
20
+ # 421 URNs.
21
+ #
22
+ # The dot of a decimal edition is KEPT: "." is `unreserved` in RFC 8141, and
23
+ # the URN has an inverse, so it must be lossless. (The MR string, which has
24
+ # no inverse, collapses it to "ed5-1" instead.)
11
25
  class UrnGenerator < Pubid::UrnGenerator::Base
12
26
  def generate
13
- parts = ["urn", "ecma"]
14
-
15
- tag = identifier.type_prefix&.downcase
16
- parts << tag if tag
27
+ parts = ["urn", "ecma", identifier.type_prefix&.downcase,
28
+ identifier.number.to_s]
29
+ parts.concat(labelled_segments)
30
+ parts.compact.join(":")
31
+ end
17
32
 
18
- parts << identifier.number.to_s
19
- parts << "part-#{identifier.part}" if identifier.part
33
+ private
20
34
 
21
- parts.join(":")
35
+ # Every optional field carries its own label, so a URN written before a
36
+ # given label existed still reads back (UrnParser probes for the prefix).
37
+ def labelled_segments
38
+ {
39
+ "part" => identifier.part,
40
+ "ed" => identifier.edition,
41
+ "vol" => identifier.volume,
42
+ }.filter_map { |label, value| "#{label}-#{value}" if value }
22
43
  end
23
44
  end
24
45
  end
@@ -5,30 +5,46 @@ module Pubid
5
5
  # Parses ECMA URNs back into identifiers, inverting {UrnGenerator}.
6
6
  #
7
7
  # Examples:
8
- # urn:ecma:411 -> ECMA-411
9
- # urn:ecma:418:part-1 -> ECMA-418-1
10
- # urn:ecma:tr:101 -> ECMA TR/101
11
- # urn:ecma:mem:1970 -> ECMA MEM/1970
8
+ # urn:ecma:411 -> ECMA-411
9
+ # urn:ecma:418:part-1 -> ECMA-418-1
10
+ # urn:ecma:tr:101 -> ECMA TR/101
11
+ # urn:ecma:mem:1970 -> ECMA MEM/1970
12
+ # urn:ecma:269:ed-3:vol-2 -> ECMA-269 ed3 vol2
13
+ #
14
+ # Every trailing segment is consumed only if it carries its own label, so a
15
+ # URN written before the edition and volume segments existed still reads
16
+ # back with both nil.
12
17
  class UrnParser < Pubid::UrnParser::Base
13
18
  def parse_urn(urn)
14
19
  parts = split_parts(strip_namespace(urn))
15
20
  tag = %w[tr mem].include?(parts.first) ? parts.shift : nil
16
21
  number = parts.shift
17
- part = if parts.first&.start_with?("part-")
18
- parts.shift.sub("part-", "")
19
- end
20
- flavor_parse(reconstruct(tag, number, part))
22
+ part = shift_labelled(parts, "part-")
23
+ edition = shift_labelled(parts, "ed-")
24
+ volume = shift_labelled(parts, "vol-")
25
+ flavor_parse(reconstruct(tag, number, part, edition, volume))
21
26
  end
22
27
 
23
28
  private
24
29
 
30
+ # Consume the next segment only if it carries `label`. `delete_prefix`,
31
+ # not `sub`, so a label-shaped substring later in the value is untouched.
32
+ def shift_labelled(parts, label)
33
+ return nil unless parts.first&.start_with?(label)
34
+
35
+ parts.shift.delete_prefix(label)
36
+ end
37
+
25
38
  # Rebuild the printed identifier string from the URN fields.
26
- def reconstruct(tag, number, part)
27
- case tag
28
- when "tr" then "ECMA TR/#{number}"
29
- when "mem" then "ECMA MEM/#{number}"
30
- else part ? "ECMA-#{number}-#{part}" : "ECMA-#{number}"
31
- end
39
+ def reconstruct(tag, number, part, edition, volume)
40
+ base = case tag
41
+ when "tr" then "ECMA TR/#{number}"
42
+ when "mem" then "ECMA MEM/#{number}"
43
+ else part ? "ECMA-#{number}-#{part}" : "ECMA-#{number}"
44
+ end
45
+ base += " ed#{edition}" if edition
46
+ base += " vol#{volume}" if volume
47
+ base
32
48
  end
33
49
  end
34
50
  end
data/lib/pubid/ecma.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Ecma
5
6
  extend Pubid::PrefixesSupport
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pubid"
4
+ require "parslet"
5
+
6
+ module Pubid
7
+ # Every failure pubid raises.
8
+ #
9
+ # The three classes below have three different superclasses, chosen so that
10
+ # code written against the old contract keeps working: relaton-cli rescues
11
+ # `Parslet::ParseFailed`, and the input guards have always raised
12
+ # `ArgumentError`. What unifies them is the marker module
13
+ # {Pubid::Errors::Error}, which every one of them includes, so a caller that
14
+ # wants "pubid could not do this" writes one rescue:
15
+ #
16
+ # begin
17
+ # Pubid::Iso.parse(reference)
18
+ # rescue Pubid::Errors::Error => e
19
+ # warn e.message
20
+ # end
21
+ #
22
+ # A URN failure deliberately does not inherit `Parslet::ParseFailed`: no
23
+ # grammar runs, so there is no parse failure to report.
24
+ module Errors
25
+ # Marker module included by every pubid error. Rescue this to catch all
26
+ # of them, whatever their superclass.
27
+ module Error; end
28
+
29
+ # A printed identifier could not be turned into an identifier object.
30
+ #
31
+ # Raised for a grammar rejection, and also for the few flavors that reject
32
+ # an input before the grammar runs (a CSA comment line, a bad ISBN check
33
+ # digit). Those synthetic failures carry no parslet cause, so
34
+ # {#parse_failure_cause} is nillable — check it before calling
35
+ # `ascii_tree` on it.
36
+ #
37
+ # v1's `Pubid::Core::Errors::ParseError` flattened the cause into the
38
+ # message and threw the object away. This one keeps it.
39
+ class ParseError < ::Parslet::ParseFailed
40
+ include Error
41
+
42
+ # The string that could not be parsed, when it is known.
43
+ # @return [String, nil]
44
+ attr_reader :input
45
+
46
+ # The flavor whose grammar rejected the input, lowercased ("iso").
47
+ #
48
+ # This is the flavor that actually ran, which is not always the one the
49
+ # caller named: `Pubid::Iso.parse("ITU-T G.711")` routes through the
50
+ # MR-string parser into ITU, and reports "itu".
51
+ # @return [String, nil]
52
+ attr_reader :flavor
53
+
54
+ # @param message [String]
55
+ # @param parse_failure_cause [Parslet::Cause, nil] parslet's structured
56
+ # cause, nil for a failure raised without running a grammar
57
+ # @param input [String, nil]
58
+ # @param flavor [String, nil]
59
+ def initialize(message, parse_failure_cause = nil, input: nil,
60
+ flavor: nil)
61
+ super(message, parse_failure_cause)
62
+ @input = input
63
+ @flavor = flavor
64
+ end
65
+ end
66
+
67
+ # A URN could not be turned into an identifier object.
68
+ class UrnParseError < ::StandardError
69
+ include Error
70
+ end
71
+
72
+ # The input was not a String, or was longer than {Pubid::MAX_INPUT_LENGTH}.
73
+ #
74
+ # Inherits `ArgumentError` because that is what the guards have always
75
+ # raised, and because the input is wrong in kind rather than unparseable.
76
+ class InvalidInputError < ::ArgumentError
77
+ include Error
78
+ end
79
+ end
80
+ end
@@ -52,9 +52,14 @@ module Pubid
52
52
  version = build_version(data)
53
53
  date = build_date(data)
54
54
 
55
+ # The code fields are stored as flat index columns on the leaf (see
56
+ # EtsiStandard) — `number` is what relaton-index keys on — so the Code
57
+ # object built above is unpacked rather than assigned.
55
58
  Identifiers::EtsiStandard.new(
56
59
  type: data[:type].to_s,
57
- code: code,
60
+ number: code.number,
61
+ minor: code.minor,
62
+ parts: code.parts || [],
58
63
  version: version,
59
64
  date: date,
60
65
  )
@@ -9,12 +9,20 @@ module Pubid
9
9
  # relaton-cli's `rescue Parslet::ParseFailed` fetch handler catches it
10
10
  # instead of a bare RuntimeError.
11
11
  def self.parse(identifier)
12
+ unless identifier.is_a?(String)
13
+ raise Pubid::Errors::InvalidInputError,
14
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
15
+ end
16
+
17
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
18
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
19
+ end
20
+
12
21
  parsed = Parser.parse(identifier)
13
22
  Builder.build(parsed)
14
23
  end
15
24
 
16
25
  attribute :type, :string
17
- attribute :code, Pubid::Etsi::Components::Code
18
26
  attribute :version, Pubid::Etsi::Components::Version
19
27
  attribute :date, Pubid::Components::Date
20
28
  # Stored as a plain string (always "ETSI") so it round-trips through
@@ -32,27 +40,33 @@ module Pubid
32
40
  date == other.date
33
41
  end
34
42
 
35
- private
36
-
37
- # ETSI keeps the number and part together inside one Code component
38
- # (unlike ISO's separate `part` attribute), so a top-level exclude(:part)
39
- # can't reach the part. Handle it during the nested-exclusion pass: when
40
- # :part/:parts is excluded, return a copy of the Code with its parts
41
- # cleared while keeping the number/minor. This propagates into supplement
42
- # wrappers too, because the base #exclude recurses through here with the
43
- # original args. It lets a part-less reference match all parts via
44
- # matches?(ignore: [..., :part]).
45
- def exclude_from_nested(value, args)
43
+ # The document code is composed from the split index columns declared on
44
+ # EtsiStandard (`number`/`minor`/`parts`). Returning nil here keeps `==`
45
+ # and the URN generator total for the abstract base and for a supplement
46
+ # whose own base is missing; EtsiStandard composes the real value and
47
+ # SupplementIdentifier delegates to its base.
48
+ def code
49
+ nil
50
+ end
51
+
52
+ # `parts` is a plain collection attribute on the leaf, so the base
53
+ # #exclude reaches it directly and nils it. It must come back as an EMPTY
54
+ # ARRAY instead: a part-less reference parses with `parts` defaulting to
55
+ # [], so a nil here would make `==` — and therefore #matches? — fail
56
+ # against exactly the reference this exclusion exists to match. Same
57
+ # "reset the whole cluster" rule recorded for CSA's year and IEEE's
58
+ # year/month/day in CLAUDE.md.
59
+ #
60
+ # This also covers supplements: the base #exclude recurses into the
61
+ # nested `base` identifier through exclude_from_nested, which re-enters
62
+ # this method on the inner EtsiStandard.
63
+ def exclude(*args)
64
+ result = super
46
65
  part_keys = args & %i[part parts]
47
- if value.is_a?(Pubid::Etsi::Components::Code) && !part_keys.empty?
48
- return Pubid::Etsi::Components::Code.new(
49
- number: value.number,
50
- minor: value.minor,
51
- parts: [],
52
- )
66
+ if !part_keys.empty? && result.respond_to?(:parts=)
67
+ result.parts = []
53
68
  end
54
-
55
- super
69
+ result
56
70
  end
57
71
  end
58
72