pubid 2.0.0.pre.alpha.9 → 2.0.0.pre.alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +33 -11
  3. data/conformance/pending.yaml +4 -0
  4. data/lib/pubid/adobe/identifier.rb +1 -3
  5. data/lib/pubid/adobe/parser.rb +10 -2
  6. data/lib/pubid/adobe.rb +1 -0
  7. data/lib/pubid/amca/builder.rb +2 -2
  8. data/lib/pubid/amca/identifiers/base.rb +49 -27
  9. data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
  10. data/lib/pubid/amca/identifiers/publication.rb +13 -9
  11. data/lib/pubid/amca/identifiers/standard.rb +2 -0
  12. data/lib/pubid/amca/parser.rb +1 -1
  13. data/lib/pubid/amca/renderer.rb +4 -4
  14. data/lib/pubid/amca/urn_generator.rb +2 -2
  15. data/lib/pubid/amca.rb +2 -1
  16. data/lib/pubid/ansi/builder.rb +2 -2
  17. data/lib/pubid/ansi/identifier.rb +18 -0
  18. data/lib/pubid/ansi/parser.rb +1 -1
  19. data/lib/pubid/ansi/renderer.rb +2 -2
  20. data/lib/pubid/ansi.rb +5 -5
  21. data/lib/pubid/api/builder.rb +16 -2
  22. data/lib/pubid/api/identifier.rb +25 -4
  23. data/lib/pubid/api/identifiers/mpms.rb +10 -8
  24. data/lib/pubid/api/parser.rb +1 -1
  25. data/lib/pubid/api/renderer.rb +16 -7
  26. data/lib/pubid/api/single_identifier.rb +5 -12
  27. data/lib/pubid/api.rb +1 -0
  28. data/lib/pubid/ashrae/builder.rb +31 -10
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
  31. data/lib/pubid/ashrae/identifiers/base.rb +81 -5
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
  33. data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
  34. data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
  35. data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
  36. data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
  37. data/lib/pubid/ashrae/parser.rb +23 -8
  38. data/lib/pubid/ashrae/renderer.rb +6 -6
  39. data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
  40. data/lib/pubid/ashrae/urn_generator.rb +8 -2
  41. data/lib/pubid/ashrae.rb +7 -1
  42. data/lib/pubid/asme/builder.rb +11 -2
  43. data/lib/pubid/asme/identifier.rb +9 -0
  44. data/lib/pubid/asme/identifiers/standard.rb +86 -2
  45. data/lib/pubid/asme/parser.rb +1 -1
  46. data/lib/pubid/asme/renderer.rb +3 -3
  47. data/lib/pubid/asme/single_identifier.rb +8 -1
  48. data/lib/pubid/asme/urn_generator.rb +2 -2
  49. data/lib/pubid/asme.rb +1 -0
  50. data/lib/pubid/astm/builder.rb +10 -3
  51. data/lib/pubid/astm/identifier.rb +9 -0
  52. data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
  53. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  54. data/lib/pubid/astm/identifiers/data_series.rb +2 -0
  55. data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
  56. data/lib/pubid/astm/identifiers/manual.rb +10 -0
  57. data/lib/pubid/astm/identifiers/monograph.rb +2 -0
  58. data/lib/pubid/astm/identifiers/research_report.rb +10 -0
  59. data/lib/pubid/astm/identifiers/standard.rb +10 -0
  60. data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
  61. data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
  62. data/lib/pubid/astm/identifiers.rb +1 -0
  63. data/lib/pubid/astm/parser.rb +1 -1
  64. data/lib/pubid/astm/renderer.rb +1 -1
  65. data/lib/pubid/astm/single_identifier.rb +31 -1
  66. data/lib/pubid/astm/urn_generator.rb +7 -5
  67. data/lib/pubid/astm.rb +1 -0
  68. data/lib/pubid/bipm/builder.rb +66 -10
  69. data/lib/pubid/bipm/identifier.rb +134 -9
  70. data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
  71. data/lib/pubid/bipm/identifiers/guide.rb +11 -0
  72. data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
  73. data/lib/pubid/bipm/identifiers/mep.rb +12 -0
  74. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  75. data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
  76. data/lib/pubid/bipm/parser.rb +65 -12
  77. data/lib/pubid/bipm/renderer.rb +8 -0
  78. data/lib/pubid/bipm/urn_parser.rb +11 -3
  79. data/lib/pubid/bipm.rb +7 -1
  80. data/lib/pubid/bsi/builder.rb +48 -38
  81. data/lib/pubid/bsi/components/date.rb +10 -4
  82. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  83. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  84. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +1 -2
  85. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  86. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  87. data/lib/pubid/bsi/identifiers/handbook.rb +1 -3
  88. data/lib/pubid/bsi/identifiers/practice_guide.rb +1 -2
  89. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  90. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  91. data/lib/pubid/bsi/parser.rb +1 -1
  92. data/lib/pubid/bsi/renderer.rb +63 -51
  93. data/lib/pubid/bsi/single_identifier.rb +27 -9
  94. data/lib/pubid/bsi/urn_generator.rb +11 -2
  95. data/lib/pubid/bsi.rb +7 -1
  96. data/lib/pubid/builder/base.rb +18 -8
  97. data/lib/pubid/bundled_identifier.rb +16 -6
  98. data/lib/pubid/calconnect/identifier.rb +9 -5
  99. data/lib/pubid/calconnect/parser.rb +1 -1
  100. data/lib/pubid/calconnect.rb +7 -1
  101. data/lib/pubid/ccsds/identifier.rb +13 -2
  102. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  103. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  104. data/lib/pubid/ccsds/parser.rb +1 -1
  105. data/lib/pubid/ccsds/single_identifier.rb +16 -12
  106. data/lib/pubid/ccsds.rb +1 -0
  107. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  108. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  109. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  110. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  111. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  112. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  115. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  116. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  117. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  118. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  119. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  120. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  123. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  124. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  125. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  126. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  127. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  128. data/lib/pubid/cen_cenelec.rb +28 -10
  129. data/lib/pubid/cie/builder.rb +10 -0
  130. data/lib/pubid/cie/identifier.rb +6 -1
  131. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  132. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  133. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  134. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  135. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  136. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  137. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  138. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  139. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  140. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  141. data/lib/pubid/cie/parser.rb +1 -1
  142. data/lib/pubid/cie.rb +7 -1
  143. data/lib/pubid/conformance/checks.rb +72 -0
  144. data/lib/pubid/conformance/corpus/case.rb +55 -0
  145. data/lib/pubid/conformance/corpus.rb +36 -0
  146. data/lib/pubid/conformance/generator.rb +212 -0
  147. data/lib/pubid/conformance/pending.rb +63 -0
  148. data/lib/pubid/conformance/runner.rb +153 -0
  149. data/lib/pubid/conformance.rb +55 -0
  150. data/lib/pubid/core.rb +2 -0
  151. data/lib/pubid/csa/builder.rb +47 -47
  152. data/lib/pubid/csa/composite_identifier.rb +14 -19
  153. data/lib/pubid/csa/identifier.rb +143 -81
  154. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  155. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  156. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  157. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  158. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  159. data/lib/pubid/csa/identifiers/package.rb +2 -2
  160. data/lib/pubid/csa/parser.rb +6 -2
  161. data/lib/pubid/csa/renderer.rb +13 -5
  162. data/lib/pubid/csa/single_identifier.rb +93 -4
  163. data/lib/pubid/csa/urn_generator.rb +9 -2
  164. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  165. data/lib/pubid/csa.rb +7 -1
  166. data/lib/pubid/doi/identifier.rb +6 -3
  167. data/lib/pubid/doi/parser.rb +1 -1
  168. data/lib/pubid/doi.rb +1 -0
  169. data/lib/pubid/easc/identifier.rb +0 -2
  170. data/lib/pubid/easc/parser.rb +10 -2
  171. data/lib/pubid/easc.rb +1 -0
  172. data/lib/pubid/ecma/builder.rb +23 -9
  173. data/lib/pubid/ecma/identifier.rb +81 -10
  174. data/lib/pubid/ecma/parser.rb +39 -4
  175. data/lib/pubid/ecma/renderer.rb +29 -8
  176. data/lib/pubid/ecma/urn_generator.rb +34 -13
  177. data/lib/pubid/ecma/urn_parser.rb +30 -14
  178. data/lib/pubid/ecma.rb +1 -0
  179. data/lib/pubid/errors.rb +80 -0
  180. data/lib/pubid/etsi/builder.rb +6 -1
  181. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  182. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  183. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  184. data/lib/pubid/etsi/parser.rb +1 -1
  185. data/lib/pubid/etsi.rb +1 -0
  186. data/lib/pubid/evs/builder.rb +26 -0
  187. data/lib/pubid/evs/identifier.rb +37 -0
  188. data/lib/pubid/evs/identifiers/national_adoption.rb +22 -0
  189. data/lib/pubid/evs/identifiers.rb +9 -0
  190. data/lib/pubid/evs/parser.rb +27 -0
  191. data/lib/pubid/evs/renderer.rb +30 -0
  192. data/lib/pubid/evs/urn_generator.rb +30 -0
  193. data/lib/pubid/evs/urn_parser.rb +80 -0
  194. data/lib/pubid/evs.rb +90 -0
  195. data/lib/pubid/export.rb +1 -0
  196. data/lib/pubid/format_detector.rb +1 -0
  197. data/lib/pubid/gb/builder.rb +14 -9
  198. data/lib/pubid/gb/identifier.rb +29 -7
  199. data/lib/pubid/gb/parser.rb +5 -1
  200. data/lib/pubid/gb/renderer.rb +4 -3
  201. data/lib/pubid/gb.rb +2 -0
  202. data/lib/pubid/gost/builder.rb +46 -9
  203. data/lib/pubid/gost/identifier.rb +0 -2
  204. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  205. data/lib/pubid/gost/parser.rb +10 -2
  206. data/lib/pubid/gost.rb +1 -0
  207. data/lib/pubid/iala/identifier.rb +9 -6
  208. data/lib/pubid/iala/parser.rb +10 -2
  209. data/lib/pubid/iala/urn_parser.rb +6 -2
  210. data/lib/pubid/iala.rb +1 -0
  211. data/lib/pubid/iana/builder.rb +3 -1
  212. data/lib/pubid/iana/identifier.rb +74 -6
  213. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  214. data/lib/pubid/iana/parser.rb +1 -1
  215. data/lib/pubid/iana/renderer.rb +3 -0
  216. data/lib/pubid/iana/urn_generator.rb +5 -1
  217. data/lib/pubid/iana.rb +1 -0
  218. data/lib/pubid/identifier.rb +605 -13
  219. data/lib/pubid/identifier_metadata.rb +1 -0
  220. data/lib/pubid/idf/builder.rb +3 -3
  221. data/lib/pubid/idf/identifier.rb +25 -9
  222. data/lib/pubid/idf/identifiers/amendment.rb +1 -1
  223. data/lib/pubid/idf/identifiers/corrigendum.rb +1 -1
  224. data/lib/pubid/idf/identifiers/international_standard.rb +1 -1
  225. data/lib/pubid/idf/identifiers/reviewed_method.rb +1 -1
  226. data/lib/pubid/idf/parser.rb +1 -1
  227. data/lib/pubid/idf/renderer.rb +4 -4
  228. data/lib/pubid/idf/single_identifier.rb +1 -1
  229. data/lib/pubid/idf/urn_generator.rb +1 -1
  230. data/lib/pubid/idf.rb +7 -1
  231. data/lib/pubid/iec/builder.rb +14 -7
  232. data/lib/pubid/iec/components.rb +0 -1
  233. data/lib/pubid/iec/identifier.rb +73 -83
  234. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  235. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  236. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  237. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  238. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  239. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  240. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  241. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  242. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  243. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  244. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  245. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  246. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  247. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  248. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  249. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  250. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  251. data/lib/pubid/iec/parser.rb +2 -18
  252. data/lib/pubid/iec/renderer.rb +13 -10
  253. data/lib/pubid/iec/single_identifier.rb +33 -10
  254. data/lib/pubid/iec/urn_generator.rb +227 -61
  255. data/lib/pubid/iec/urn_parser.rb +42 -4
  256. data/lib/pubid/iec.rb +1 -0
  257. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  258. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  259. data/lib/pubid/ieee/builder.rb +87 -3
  260. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  261. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  262. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  263. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  264. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  265. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  266. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  267. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  268. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  269. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  270. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  271. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  272. data/lib/pubid/ieee/identifiers/si_standard.rb +5 -2
  273. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  274. data/lib/pubid/ieee/ire/parser.rb +20 -2
  275. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  276. data/lib/pubid/ieee/parser.rb +208 -33
  277. data/lib/pubid/ieee/project_renderer.rb +46 -0
  278. data/lib/pubid/ieee/renderer.rb +11 -8
  279. data/lib/pubid/ieee/urn_generator.rb +32 -3
  280. data/lib/pubid/ieee.rb +9 -1
  281. data/lib/pubid/ieee_debug.rb +1 -0
  282. data/lib/pubid/ietf/builder.rb +24 -8
  283. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  284. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  285. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  286. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  287. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  288. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  289. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  290. data/lib/pubid/ietf/identifiers.rb +1 -0
  291. data/lib/pubid/ietf/parser.rb +27 -9
  292. data/lib/pubid/ietf/renderer.rb +44 -6
  293. data/lib/pubid/ietf/urn_generator.rb +20 -6
  294. data/lib/pubid/ietf/urn_parser.rb +10 -2
  295. data/lib/pubid/ietf.rb +12 -2
  296. data/lib/pubid/iho/identifiers/base.rb +10 -3
  297. data/lib/pubid/iho/parser.rb +1 -1
  298. data/lib/pubid/iho.rb +1 -0
  299. data/lib/pubid/isbn/identifier.rb +25 -7
  300. data/lib/pubid/isbn/parser.rb +1 -1
  301. data/lib/pubid/isbn.rb +1 -0
  302. data/lib/pubid/iso/builder.rb +10 -11
  303. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  304. data/lib/pubid/iso/combined_identifier.rb +5 -3
  305. data/lib/pubid/iso/components.rb +0 -1
  306. data/lib/pubid/iso/identifier.rb +23 -11
  307. data/lib/pubid/iso/identifiers/directives.rb +7 -4
  308. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  309. data/lib/pubid/iso/identifiers/tc_document.rb +31 -46
  310. data/lib/pubid/iso/normalizer.rb +1 -1
  311. data/lib/pubid/iso/parser.rb +1 -1
  312. data/lib/pubid/iso/urn_generator.rb +10 -10
  313. data/lib/pubid/iso.rb +12 -5
  314. data/lib/pubid/itu/builder.rb +10 -0
  315. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  316. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  317. data/lib/pubid/itu/identifiers/base.rb +29 -5
  318. data/lib/pubid/itu/identifiers/contribution.rb +31 -0
  319. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  320. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  321. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  322. data/lib/pubid/itu/identifiers.rb +1 -0
  323. data/lib/pubid/itu/parser.rb +20 -3
  324. data/lib/pubid/itu.rb +1 -0
  325. data/lib/pubid/jcgm/builder.rb +1 -1
  326. data/lib/pubid/jcgm/identifier.rb +10 -0
  327. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  328. data/lib/pubid/jcgm/parser.rb +10 -3
  329. data/lib/pubid/jcgm/renderer.rb +10 -3
  330. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  331. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  332. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  333. data/lib/pubid/jcgm.rb +10 -0
  334. data/lib/pubid/jis/identifier.rb +10 -3
  335. data/lib/pubid/jis/parser.rb +1 -1
  336. data/lib/pubid/jis.rb +1 -0
  337. data/lib/pubid/nist/builder.rb +12 -12
  338. data/lib/pubid/nist/components/stage.rb +2 -2
  339. data/lib/pubid/nist/configuration.rb +6 -8
  340. data/lib/pubid/nist/identifiers/base.rb +22 -5
  341. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  342. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  343. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
  344. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  345. data/lib/pubid/nist/identifiers/crpl_report.rb +12 -7
  346. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +2 -2
  347. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  348. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  349. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +16 -8
  350. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  351. data/lib/pubid/nist/identifiers/report.rb +14 -6
  352. data/lib/pubid/nist/parser.rb +1 -1
  353. data/lib/pubid/nist/series/ir.rb +3 -7
  354. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  355. data/lib/pubid/nist.rb +7 -1
  356. data/lib/pubid/oasis/builder.rb +12 -10
  357. data/lib/pubid/oasis/identifier.rb +89 -11
  358. data/lib/pubid/oasis/parser.rb +1 -1
  359. data/lib/pubid/oasis.rb +7 -1
  360. data/lib/pubid/ogc/identifier.rb +9 -5
  361. data/lib/pubid/ogc/parser.rb +1 -1
  362. data/lib/pubid/ogc.rb +1 -0
  363. data/lib/pubid/oiml/builder.rb +7 -3
  364. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  365. data/lib/pubid/oiml/identifiers/bulletin.rb +40 -10
  366. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  367. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  368. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  369. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  370. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  371. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  372. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  373. data/lib/pubid/oiml/identifiers.rb +1 -0
  374. data/lib/pubid/oiml/parser.rb +8 -3
  375. data/lib/pubid/oiml/renderer.rb +3 -3
  376. data/lib/pubid/oiml/single_identifier.rb +26 -42
  377. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  378. data/lib/pubid/oiml/urn_generator.rb +4 -4
  379. data/lib/pubid/oiml.rb +7 -1
  380. data/lib/pubid/omg/builder.rb +1 -0
  381. data/lib/pubid/omg/identifier.rb +20 -3
  382. data/lib/pubid/omg/parser.rb +64 -11
  383. data/lib/pubid/omg/renderer.rb +13 -1
  384. data/lib/pubid/omg.rb +2 -1
  385. data/lib/pubid/parser/grammar.rb +74 -0
  386. data/lib/pubid/parser.rb +2 -0
  387. data/lib/pubid/parsers/mr_string.rb +19 -0
  388. data/lib/pubid/plateau/parser.rb +1 -1
  389. data/lib/pubid/plateau.rb +10 -0
  390. data/lib/pubid/prefixes_support.rb +1 -0
  391. data/lib/pubid/renderers/annotator.rb +233 -0
  392. data/lib/pubid/renderers/base.rb +13 -0
  393. data/lib/pubid/renderers/directives_renderer.rb +6 -4
  394. data/lib/pubid/renderers/human_readable.rb +3 -3
  395. data/lib/pubid/renderers/mr_string.rb +15 -9
  396. data/lib/pubid/renderers.rb +1 -0
  397. data/lib/pubid/rendering/numbering.rb +25 -7
  398. data/lib/pubid/rendering.rb +1 -0
  399. data/lib/pubid/sae/builder.rb +1 -1
  400. data/lib/pubid/sae/identifiers/base.rb +13 -3
  401. data/lib/pubid/sae/parser.rb +1 -1
  402. data/lib/pubid/sae/urn_generator.rb +1 -1
  403. data/lib/pubid/sae.rb +1 -0
  404. data/lib/pubid/schema/declaration.rb +33 -0
  405. data/lib/pubid/schema/error.rb +14 -0
  406. data/lib/pubid/schema/identifier_type.rb +23 -0
  407. data/lib/pubid/schema/loader.rb +117 -0
  408. data/lib/pubid/schema/typed_stage.rb +24 -0
  409. data/lib/pubid/schema.rb +25 -0
  410. data/lib/pubid/tgpp/builder.rb +1 -1
  411. data/lib/pubid/tgpp/identifier.rb +21 -5
  412. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  413. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  414. data/lib/pubid/tgpp/parser.rb +22 -6
  415. data/lib/pubid/tgpp/renderer.rb +21 -5
  416. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  417. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  418. data/lib/pubid/tgpp.rb +7 -1
  419. data/lib/pubid/type_resolver.rb +16 -1
  420. data/lib/pubid/un/identifier.rb +6 -3
  421. data/lib/pubid/un/parser.rb +1 -1
  422. data/lib/pubid/un.rb +1 -0
  423. data/lib/pubid/urn_parser/errors.rb +8 -1
  424. data/lib/pubid/urn_parser.rb +1 -0
  425. data/lib/pubid/utils.rb +1 -0
  426. data/lib/pubid/version.rb +1 -1
  427. data/lib/pubid/w3c/builder.rb +5 -5
  428. data/lib/pubid/w3c/identifier.rb +38 -9
  429. data/lib/pubid/w3c/parser.rb +1 -1
  430. data/lib/pubid/w3c/renderer.rb +1 -1
  431. data/lib/pubid/w3c/urn_generator.rb +2 -2
  432. data/lib/pubid/w3c.rb +1 -0
  433. data/lib/pubid/xsf/identifier.rb +9 -5
  434. data/lib/pubid/xsf/parser.rb +17 -4
  435. data/lib/pubid/xsf.rb +7 -1
  436. data/lib/pubid.rb +399 -21
  437. data/lib/tasks/conformance.rake +34 -0
  438. data/lib/tasks/docs.rake +16 -14
  439. data/lib/tasks/schema.rake +88 -0
  440. data/schema/core/joint_prefixes.yaml +24 -0
  441. data/schema/iec.yaml +1017 -0
  442. data/schema/iso.yaml +1586 -0
  443. data/schema/schema.schema.yaml +96 -0
  444. metadata +40 -19
  445. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  446. data/archived-gems/pubid-iec/stages.yaml +0 -129
  447. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  448. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  449. data/archived-gems/pubid-iso/stages.yaml +0 -106
  450. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  451. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  452. data/archived-gems/pubid-itu/series.yaml +0 -42
  453. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  454. data/archived-gems/pubid-nist/series.yaml +0 -121
  455. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  456. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  457. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +0 -70
  458. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  459. data/lib/pubid/iec/components/code.rb +0 -36
  460. data/lib/pubid/iso/components/code.rb +0 -24
  461. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -82,18 +82,6 @@ module Pubid
82
82
  web: :publicly_available_specification, title: "Publicly Available Specification", short: "PAS" }
83
83
  end
84
84
 
85
- # Override publisher_portion to add PAS or DPAS stage
86
- def publisher_portion
87
- result = publisher.to_s
88
-
89
- if typed_stage
90
- abbr = typed_stage.abbreviation
91
- # PAS uses space for all stages
92
- result += " #{abbr}" unless abbr.empty?
93
- end
94
-
95
- result
96
- end
97
85
  end
98
86
  end
99
87
  end
@@ -24,16 +24,6 @@ module Pubid
24
24
  short: "Trend Report" }
25
25
  end
26
26
 
27
- # Override publisher_portion to add Trend Report
28
- def publisher_portion
29
- result = publisher.to_s
30
-
31
- if typed_stage && typed_stage.abbreviation == "Trend Report"
32
- result += " Trend Report"
33
- end
34
-
35
- result
36
- end
37
27
  end
38
28
  end
39
29
  end
@@ -23,16 +23,6 @@ module Pubid
23
23
  web: :systems_reference_document, title: "Systems Reference Document", short: "SRD" }
24
24
  end
25
25
 
26
- # Override publisher_portion to add SRD
27
- def publisher_portion
28
- result = publisher.to_s
29
-
30
- if typed_stage && typed_stage.abbreviation == "SRD"
31
- result += " SRD"
32
- end
33
-
34
- result
35
- end
36
26
  end
37
27
  end
38
28
  end
@@ -108,23 +108,6 @@ module Pubid
108
108
  web: :technical_report, title: "Technical Report", short: "TR" }
109
109
  end
110
110
 
111
- # Override publisher_portion to handle TR formatting
112
- # If copublishers exist, use parent implementation
113
- def publisher_portion
114
- # If copublishers, delegate to parent (SingleIdentifier) which handles them
115
- return super if copublishers&.any?
116
-
117
- # No copublishers: simple TR formatting
118
- result = publisher.to_s
119
-
120
- if typed_stage
121
- abbr = typed_stage.abbreviation
122
- # For TR: always use space (IEC convention for ALL publishers)
123
- result += " #{abbr}" unless abbr.empty?
124
- end
125
-
126
- result
127
- end
128
111
  end
129
112
  end
130
113
  end
@@ -108,23 +108,6 @@ module Pubid
108
108
  web: :technical_specification, title: "Technical Specification", short: "TS" }
109
109
  end
110
110
 
111
- # Override publisher_portion to handle TS formatting
112
- # If copublishers exist, use parent implementation
113
- def publisher_portion
114
- # If copublishers, delegate to parent (SingleIdentifier) which handles them
115
- return super if copublishers&.any?
116
-
117
- # No copublishers: simple TS formatting
118
- result = publisher.to_s
119
-
120
- if typed_stage
121
- abbr = typed_stage.abbreviation
122
- # TS uses space for all stages
123
- result += " #{abbr}" unless abbr.empty?
124
- end
125
-
126
- result
127
- end
128
111
  end
129
112
  end
130
113
  end
@@ -22,16 +22,6 @@ module Pubid
22
22
  { key: :tec, title: "Technology Report", short: "Technology Report" }
23
23
  end
24
24
 
25
- # Override publisher_portion to add Technology Report
26
- def publisher_portion
27
- result = publisher.to_s
28
-
29
- if typed_stage && typed_stage.abbreviation == "Technology Report"
30
- result += " Technology Report"
31
- end
32
-
33
- result
34
- end
35
25
  end
36
26
  end
37
27
  end
@@ -28,17 +28,6 @@ module Pubid
28
28
  web: :test_report_form, title: "Test Report Form", short: "TRF" }
29
29
  end
30
30
 
31
- # Override publisher_portion to add TRF with space
32
- def publisher_portion
33
- result = publisher.to_s
34
-
35
- if typed_stage && typed_stage.abbreviation == "TRF"
36
- result += " TRF"
37
- end
38
-
39
- result
40
- end
41
-
42
31
  # TRF uses special rendering via the Renderer
43
32
  end
44
33
  end
@@ -23,16 +23,6 @@ module Pubid
23
23
  web: :white_paper, title: "White Paper", short: "White Paper" }
24
24
  end
25
25
 
26
- # Override publisher_portion to add White Paper
27
- def publisher_portion
28
- result = publisher.to_s
29
-
30
- if typed_stage && typed_stage.abbreviation == "White Paper"
31
- result += " White Paper"
32
- end
33
-
34
- result
35
- end
36
26
  end
37
27
  end
38
28
  end
@@ -28,19 +28,33 @@ module Pubid
28
28
  web: :working_document, title: "Working Document", short: "WD" }
29
29
  end
30
30
 
31
- # Return stage object for PWI/PNW stage
31
+ # A working document prints its committee, number, language and stage —
32
+ # `1/2457(F)/FDIS` — and none of those is the `number` the shared token
33
+ # table looks for, so it had no annotatable token. `wd_stage` is the
34
+ # printed stage token ("FDIS"); the derived `#stage` reader covers only
35
+ # the PWI/PNW work-programme form and is nil here.
36
+ def annotation_tokens
37
+ super + [
38
+ [:technical_committee, "docnumber"],
39
+ [:wd_number, "docnumber"],
40
+ [:wd_language, "language"],
41
+ [:wd_stage, "stage"],
42
+ [:wp_stage, "stage"],
43
+ [:wp_type, "doctype"],
44
+ ]
45
+ end
46
+
47
+ # Return stage object for the PWI/PNW work-programme stage.
48
+ #
49
+ # This used to build a bare Stage from a hand-written abbreviation map,
50
+ # so the stage carried no harmonized code and "pnw" existed as a stage
51
+ # code nowhere else in the registry. It now resolves through the shared
52
+ # registry, so PWI carries 00.* and PNW carries 10.20 — which is what
53
+ # the URN generator reads.
32
54
  def stage
33
55
  return nil unless wp_stage
34
56
 
35
- # Map PWI/PNW to stage codes
36
- stage_map = {
37
- "PWI" => "pwi",
38
- "PNW" => "pnw",
39
- }
40
- stage_code = stage_map[wp_stage]
41
- return nil unless stage_code
42
-
43
- @stage ||= ::Pubid::Components::Stage.new(stage_code: stage_code)
57
+ @stage ||= Pubid::Iec.locate_stage(wp_stage)&.to_stage
44
58
  end
45
59
  end
46
60
  end
@@ -3,7 +3,7 @@ require "parslet"
3
3
 
4
4
  module Pubid
5
5
  module Iec
6
- class Parser < Parslet::Parser
6
+ class Parser < ::Pubid::Parser::Grammar
7
7
  include ::Pubid::Parser::CommonParseRules
8
8
  include ::Pubid::Parser::CommonParseMethods
9
9
 
@@ -17,8 +17,7 @@ module Pubid
17
17
  root :identifier
18
18
 
19
19
  rule(:identifier) do
20
- working_programme_with_publisher |
21
- working_programme |
20
+ working_programme |
22
21
  technical_group |
23
22
  working_document |
24
23
  sheet_supplement_identifier |
@@ -81,21 +80,6 @@ module Pubid
81
80
  (space >> edition).maybe
82
81
  end
83
82
 
84
- # Working Programme with publisher prefix: IEC/PWI [TR] number edition
85
- # Examples: "IEC/PWI 60038", "ISO/IEC/PWI TR 100-36 ED1"
86
- rule(:working_programme_with_publisher) do
87
- prefix_with_copublishers >> str("/") >>
88
- (str("PWI") | str("PNW")).as(:wp_stage) >>
89
- space >>
90
- (
91
- (str("TR") >> space) |
92
- (str("TS") >> space) |
93
- (str("SRD") >> space)
94
- ).maybe.as(:wp_type) >>
95
- wp_number >>
96
- (space >> edition).maybe
97
- end
98
-
99
83
  # Working programme numbers can be digits or TC names
100
84
  rule(:wp_number) do
101
85
  # TC-style: SYCSMARTENERGY-1, SyCLVDC-125
@@ -89,13 +89,18 @@ module Pubid
89
89
  end
90
90
 
91
91
  def render_supplement_standalone(id, context)
92
+ # This is a second, independent publisher/stage join that
93
+ # SingleIdentifier#publisher_portion never reaches. It follows the same
94
+ # house style: the slash separates copublishers, and a SPACE separates
95
+ # the publisher from the stage abbreviation. See issue #360 item 2.
92
96
  parts = []
93
97
 
94
98
  if id.publisher
95
- parts << id.publisher.render(context:)
99
+ publishers = [id.publisher.render(context:)]
96
100
  if id.copublishers&.any?
97
- parts << "/#{id.copublishers.map { |c| c.render(context:) }.join('/')}"
101
+ publishers += id.copublishers.map { |c| c.render(context:) }
98
102
  end
103
+ parts << publishers.join("/")
99
104
  end
100
105
 
101
106
  abbr = id.typed_stage.abbr.first
@@ -104,7 +109,7 @@ module Pubid
104
109
  number_str += "-#{id.subpart}" if id.subpart
105
110
  parts << "#{abbr} #{number_str}"
106
111
 
107
- result = parts.join("/")
112
+ result = parts.join(" ")
108
113
  result += ":#{id.date.render(context:)}" if id.date
109
114
  result += " #{id.edition.render(context:)}" if id.edition&.number
110
115
  result
@@ -197,14 +202,12 @@ module Pubid
197
202
 
198
203
  def render_working_document(id, context)
199
204
  if id.wp_stage
205
+ # A work-programme registration renders bare, as it is written:
206
+ # "PNW 65-915 ED1", not "IEC/PNW 65-915 ED1". Its number is
207
+ # TC-scoped, not an IEC document number, and prefixing "IEC " would
208
+ # break idempotence — "IEC PNW 65-915 ED1" re-parses as an
209
+ # International Standard, so rendering would not be a fixed point.
200
210
  parts = []
201
- if id.publisher
202
- parts << id.publisher.render(context:)
203
- if id.copublishers&.any?
204
- parts << "/#{id.copublishers.map { |c| c.render(context:) }.join('/')}"
205
- end
206
- parts << "/"
207
- end
208
211
  parts << id.wp_stage
209
212
  if id.wp_type && !id.wp_type.strip.empty?
210
213
  parts << " #{id.wp_type.strip}"
@@ -8,6 +8,31 @@ module Pubid
8
8
  attribute :typed_stage, ::Pubid::Components::TypedStage,
9
9
  default: -> { self.class.published_typed_stage }
10
10
 
11
+ # number/part/subpart are mapped HERE, not on Iec::Identifier, and the
12
+ # placement is the whole design.
13
+ #
14
+ # The four wrapper identifiers — FragmentIdentifier,
15
+ # ConsolidatedIdentifier, VapIdentifier and SheetIdentifier — inherit
16
+ # from Iec::Identifier DIRECTLY and override `number`/`part`/`subpart` as
17
+ # readers that delegate to what they wrap. A map on the shared parent
18
+ # would therefore serialize
19
+ # the delegated value twice: once at the wrapper's top level and again
20
+ # inside "base"/"identifiers". SingleIdentifier is the seam that excludes
21
+ # exactly those four while covering both classes that own a number of
22
+ # their own (Identifiers::Base and SupplementIdentifier).
23
+ #
24
+ # That is a structural exclusion, not a denylist of no-op converters that
25
+ # a later key can be forgotten from — which is how `month`/`day` came to
26
+ # leak (see DelegatedFieldSuppression).
27
+ #
28
+ # The values are plain strings, so no converter is needed: lutaml writes
29
+ # the scalar, and ::Pubid::Identifier#to_hash drops a nil.
30
+ key_value do
31
+ map "number", to: :number
32
+ map "part", to: :part
33
+ map "subpart", to: :subpart
34
+ end
35
+
11
36
  # Generate URN for this identifier
12
37
  #
13
38
  # @return [String] URN representation
@@ -28,16 +53,14 @@ module Pubid
28
53
  publisher.body
29
54
  end
30
55
 
31
- # Add type abbreviation if present
32
- if typed_stage && !typed_stage.abbreviation.empty?
33
- abbr = typed_stage.abbreviation
34
- # For copublishers or empty abbr, use space; otherwise use slash
35
- pub_string += if copublishers&.any? || abbr == ""
36
- (abbr == "" ? "" : " #{abbr}")
37
- else
38
- "/#{abbr}"
39
- end
40
- end
56
+ # IEC house style separates the publisher from the typed-stage
57
+ # abbreviation with a SPACE, for every type and whether or not there
58
+ # are copublishers: "IEC CD 60038", "IEC PNW 1000-1:2023". The slash
59
+ # stays the copublisher separator only. The slash input spelling
60
+ # ("IEC/CD 60038") is still accepted by the parser; only rendering
61
+ # normalises. See issue #360 item 2.
62
+ abbr = typed_stage&.abbreviation.to_s
63
+ pub_string += " #{abbr}" unless abbr.empty?
41
64
 
42
65
  pub_string
43
66
  end
@@ -2,77 +2,243 @@
2
2
 
3
3
  module Pubid
4
4
  module Iec
5
- # Generates IEC URNs in the legacy positional format used as ground truth
6
- # by relaton-data-iec:
5
+ # Generates IEC URNs in the positional format used as ground truth by
6
+ # relaton-data-iec:
7
7
  #
8
- # urn:iec:std:{publisher}:{number}[-{part}]:{date}:{type}:{deliverable}:{language}[:{adjuncts}]
8
+ # urn:iec:std:{authority}:{number}[-{part}[-{subpart}]]
9
+ # :{date}:{stage-or-type}:{deliverable}:{language}{adjuncts}
9
10
  #
10
- # e.g. +urn:iec:std:iec:60050-102:2007:::+ (type after date; trailing empty
11
- # type/deliverable/language slots). This is a port of relaton-iec's
12
- # +Relaton::Iec.code_to_urn+ operating on +identifier.to_s+, which
13
- # round-trips faithfully. The all-parts series URN is the one exception: it
14
- # omits the language slot (8 fields), so it has a dedicated branch.
11
+ # e.g. +urn:iec:std:iec:60050-102:2007:::+. Absent trailing slots are
12
+ # emitted as empty fields. The all-parts series URN is the one exception:
13
+ # a bare series omits the language slot (8 fields).
14
+ #
15
+ # This used to be a port of relaton-iec's +code_to_urn+, which regex-scraped
16
+ # +identifier.to_s+. Three defects followed from reading the printed form:
17
+ # the stage was folded into the authority slot (+urn:iec:std:iec-np:...+),
18
+ # the edition was never emitted, and the regex was unanchored, so any type
19
+ # token outside its hard-coded list consumed the publisher instead
20
+ # (+IEC/DTS 62271-1+ became +urn:iec:std:dts:62271-1::::+, and every
21
+ # +IECEE TRF ...+ keyed +urn:iec:std:trf:...+). The generator now reads the
22
+ # identifier's own components. The slot ORDER is unchanged, because the
23
+ # published corpus uses it. See issue #360 item 4.
15
24
  class UrnGenerator < Pubid::UrnGenerator::Base
16
- # Deliverable markers occupying the positional deliverable slot.
17
- DELIVERABLES = /cmv|csv|exv|prv|rlv|ser/
25
+ URN_CONTEXT = Rendering::RenderingContext.urn.freeze
26
+ private_constant :URN_CONTEXT
27
+
28
+ # Type codes that occupy the type slot. `is` and `trf` render empty
29
+ # there, as they always have. `tec` and `wp` never appear in the corpus
30
+ # but the legacy grammar accepted them, so they stay recognised.
31
+ TYPE_SLOT_CODES = %w[tr ts pas srd guide tec wp].freeze
32
+
33
+ # Deliverable markers occupying the deliverable slot. "ser" reaches this
34
+ # list as a spelled-out VAP code ("IEC 60034:2026 SER"), which is not the
35
+ # same thing as the `all_parts` series form handled below.
36
+ DELIVERABLES = %w[cmv csv exv prv rlv ser].freeze
37
+
38
+ # Supplement classes that contribute an adjunct, and the token each one
39
+ # writes into it.
40
+ ADJUNCT_TOKENS = {
41
+ "Amendment" => "amd",
42
+ "Corrigendum" => "cor",
43
+ "InterpretationSheet" => "ish",
44
+ }.freeze
18
45
 
19
46
  def generate
20
- return series_urn if identifier.class.attributes.key?(:all_parts) && identifier.all_parts
47
+ doc = document
48
+ return nil if doc.nil?
21
49
 
22
- code_to_urn(identifier.to_s, urn_language)
50
+ num = number_segment(doc)
51
+ return nil if num.nil? || num.empty?
52
+
53
+ slots = ["urn", "iec", "std", authority(doc), num,
54
+ date_slot(doc), stage_or_type_slot(doc), deliverable_slot]
55
+ adjuncts = adjunct_slots(identifier)
56
+ language = language_slot
57
+ slots << language unless bare_series?(slots, adjuncts, language)
58
+
59
+ (slots + adjuncts).join(":").downcase
23
60
  end
24
61
 
25
62
  private
26
63
 
27
- # Hyphen-joined language codes (e.g. "en-fr"), or nil when unset.
28
- def urn_language
29
- return nil unless identifier.class.attributes.key?(:languages)
30
-
31
- langs = identifier.languages
32
- return nil unless langs&.any?
33
-
34
- langs.map(&:code).join("-")
35
- end
36
-
37
- # The all-parts series URN drops the language slot and carries no part or
38
- # date: urn:iec:std:iec:80000:::ser.
39
- def series_urn
40
- code = identifier.to_s.sub(/\s*\(all parts\)\s*\z/, "")
41
- m = code.downcase.match(/(?<head>\S+)\s+(?<pnum>[\d-]+)/)
42
- head = m[:head].split("/").join("-")
43
- ["urn", "iec", "std", head, m[:pnum], "", "", "ser"].join(":")
44
- end
45
-
46
- # Port of Relaton::Iec.code_to_urn.
47
- def code_to_urn(code, lang = nil)
48
- rest = code.downcase.sub(%r{
49
- (?<head>[^\s]+)\s
50
- (?<type>is|ts|tr|pas|srd|guide|tec|wp)?(?(<type>)\s)
51
- (?<pnum>[\d-]+)\s?
52
- (?<_dd>:)?(?(<_dd>)(?<date>[\d-]+)\s?)
53
- }x, "")
54
- m = $~
55
- return unless m && m[:head] && m[:pnum]
56
-
57
- deliv = DELIVERABLES.match(code.downcase).to_s
58
- urn = ["urn", "iec", "std", m[:head].split("/").join("-"), m[:pnum],
59
- m[:date], m[:type], deliv, lang]
60
- (urn + adjunct_to_urn(rest)).join(":")
61
- end
62
-
63
- # Port of Relaton::Iec.ajunct_to_urn — recursively emits amd/cor/ish
64
- # adjuncts, prefixing "plus" for the "+" (consolidated) relation.
65
- def adjunct_to_urn(rest)
66
- r = rest.sub(%r{
67
- (?<pl>\+|/)(?(<pl>)(?<adjunct>(?:amd|cor|ish))(?<adjnum>\d+)\s?)
68
- (?<_d2>:)?(?(<_d2>)(?<adjdt>[\d-]+)\s?)
69
- }x, "")
70
- m = $~ || {}
71
- return [] unless m[:adjunct]
72
-
73
- plus = "plus" if m[:pl] == "+"
74
- urn = [plus, m[:adjunct], m[:adjnum], m[:adjdt]]
75
- urn + adjunct_to_urn(r)
64
+ # The document the URN names: the origin standard, with every amendment,
65
+ # corrigendum, consolidation, VAP and fragment layer peeled off. Those
66
+ # layers are what the adjunct and deliverable slots record.
67
+ #
68
+ # A CISPR test report form carries no number of its own — the document it
69
+ # reports on sits in `cispr_identifier`, and that is what the URN has
70
+ # always named.
71
+ def document
72
+ doc = identifier.respond_to?(:root) ? identifier.root : identifier
73
+ return doc if doc.nil? || number_segment(doc)
74
+
75
+ maybe_of(doc, :cispr_identifier) || doc
76
+ end
77
+
78
+ # A publisher body may carry a space ("IEC CA", "IECQ CS", "IECQ OD").
79
+ # The published corpus records only the last token for those
80
+ # (+urn:iec:std:ca:01:2017:::+), which is what the old whitespace-
81
+ # splitting regex produced. Keeping that spelling is what keeps those
82
+ # rows byte-identical; correcting it to "iec-ca" would need a data
83
+ # migration first.
84
+ def authority(doc)
85
+ publishers = [doc.publisher] + Array(doc.copublishers)
86
+ publishers.compact.map { |p| p.body.to_s.split(/\s+/).last.to_s }
87
+ .reject(&:empty?).join("-")
88
+ end
89
+
90
+ def number_segment(doc)
91
+ return nil unless doc.respond_to?(:number) && doc.number
92
+
93
+ segment = [doc.number, maybe_of(doc, :part), maybe_of(doc, :subpart)]
94
+ .compact.map(&:to_s).reject(&:empty?).join("-")
95
+ return nil if segment.empty?
96
+
97
+ # A few document numbers are multi-word ("DIR 1 IEC SUP"). A URN
98
+ # carries no spaces, so they become hyphens.
99
+ segment.gsub(/\s+/, "-")
100
+ end
101
+
102
+ # `Components::Date#render` under a URN context deliberately drops the
103
+ # month, which is the ISO convention. IEC keeps the precision it was
104
+ # given ("IEC CA 01:2025-10" keys ...:2025-10:...), so this reads the
105
+ # printed form.
106
+ def date_slot(doc)
107
+ maybe_of(doc, :date).to_s
108
+ end
109
+
110
+ # The stage slot and the type slot are the same slot, and a draft of a
111
+ # typed document has both. Both are kept, type first
112
+ # ("ts-stage-50.00"), because the harmonized codes are shared across
113
+ # types: dropping the type would make a draft TS, a draft TR and a draft
114
+ # International Standard collide, and `parse_urn` would resolve every one
115
+ # of them to an International Standard.
116
+ def stage_or_type_slot(doc)
117
+ type = type_token(doc)
118
+ stage = stage_token(doc)
119
+
120
+ return type if stage.nil?
121
+
122
+ [type, stage].reject { |t| t.nil? || t.empty? }.join("-")
123
+ end
124
+
125
+ def type_token(doc)
126
+ ts = maybe_of(doc, :typed_stage)
127
+ code = ts&.type_code.to_s.downcase
128
+ TYPE_SLOT_CODES.include?(code) ? code : ""
129
+ end
130
+
131
+ # nil when the document is published (or carries no stage at all), so the
132
+ # slot keeps holding the bare type token for every published row.
133
+ def stage_token(doc)
134
+ stage = maybe_of(doc, :typed_stage)&.to_stage || maybe_of(doc, :stage)
135
+ harmonized = stage&.harmonized_stages&.first.to_s
136
+ return nil if harmonized.empty?
137
+ return nil if published_stage?(stage)
138
+
139
+ "stage-#{harmonized}"
140
+ end
141
+
142
+ def published_stage?(stage)
143
+ stage.stage_code.to_s == "published"
144
+ end
145
+
146
+ # A deliverable code owns the slot; the edition takes it only when there
147
+ # is none.
148
+ def deliverable_slot
149
+ return "ser" if maybe_of(identifier, :all_parts)
150
+
151
+ vap = vap_codes
152
+ return vap unless vap.empty?
153
+
154
+ edition = maybe_of(identifier, :edition)
155
+ return "" unless edition&.number
156
+
157
+ edition.render(context: URN_CONTEXT).to_s
158
+ end
159
+
160
+ # A deliverable sits on the VAP layer, which is not always the outermost
161
+ # one: "IEC 60079-10-1:2020 CMV/COR1:2021" is a corrigendum wrapping the
162
+ # CMV, so the codes have to be looked for down the chain.
163
+ def vap_codes
164
+ codes = find_down(:vap)
165
+ return "" unless codes
166
+
167
+ Array(codes).map { |c| c.to_s.downcase }
168
+ .select { |c| DELIVERABLES.include?(c) }.join("-")
169
+ end
170
+
171
+ # First non-empty value of `attribute` from the outermost identifier
172
+ # inwards.
173
+ def find_down(attribute)
174
+ id = identifier
175
+ while id
176
+ value = maybe_of(id, attribute)
177
+ return value unless value.nil? ||
178
+ (value.respond_to?(:empty?) && value.empty?)
179
+
180
+ id = maybe_of(id, :base) ||
181
+ Array(maybe_of(id, :identifiers)).first
182
+ end
183
+ nil
184
+ end
185
+
186
+ def language_slot
187
+ langs = maybe_of(identifier, :languages)
188
+ return "" unless langs&.any?
189
+
190
+ langs.map { |l| l.code.to_s }.join("-")
191
+ end
192
+
193
+ # A bare, undated, stage- and language-less all-parts series drops the
194
+ # trailing empty language slot, matching the canonical relaton-data-iec
195
+ # form urn:iec:std:iec:80000:::ser. A dated series, or one carrying a
196
+ # stage, keeps the slot.
197
+ def bare_series?(slots, adjuncts, language)
198
+ slots.last == "ser" && slots[5, 2].all? { |s| s.to_s.empty? } &&
199
+ adjuncts.empty? && language.empty?
200
+ end
201
+
202
+ # Walks the identifier tree for the amendment / corrigendum /
203
+ # interpretation-sheet layers, innermost first. A member of a
204
+ # consolidated identifier carries the "plus" relation marker, which the
205
+ # printed form spells "+".
206
+ def adjunct_slots(id)
207
+ return [] if id.nil?
208
+
209
+ case id.class.name.split("::").last
210
+ when "ConsolidatedIdentifier"
211
+ members = Array(id.identifiers)
212
+ adjunct_slots(members.first) +
213
+ members.drop(1).flat_map { |m| adjunct_fields(m, plus: true) }
214
+ when *ADJUNCT_TOKENS.keys
215
+ adjunct_slots(id.base) + adjunct_fields(id, plus: false)
216
+ else
217
+ adjunct_slots(id.respond_to?(:base) ? id.base : nil)
218
+ end
219
+ end
220
+
221
+ def adjunct_fields(id, plus:)
222
+ token = ADJUNCT_TOKENS[id.class.name.split("::").last]
223
+ return adjunct_slots(id.respond_to?(:base) ? id.base : nil) unless token
224
+
225
+ [plus ? "plus" : "", token, id.number.to_s,
226
+ maybe_of(id, :date)&.render(context: URN_CONTEXT).to_s]
227
+ end
228
+
229
+ # Not every IEC class declares every attribute, and a wrapper may not
230
+ # declare one its base does. The `respond_to?` guard covers the missing
231
+ # attribute; the rescue covers an attribute that exists but nil-chases
232
+ # while resolving — `SupplementIdentifier#number` runs `ensure_base` and
233
+ # reads through a base that may not be built yet. It is deliberately
234
+ # narrow: a broader rescue would swallow a real bug into a silently
235
+ # empty URN slot instead of failing loudly.
236
+ def maybe_of(id, attribute)
237
+ return nil unless id.respond_to?(attribute)
238
+
239
+ id.public_send(attribute)
240
+ rescue NoMethodError
241
+ nil
76
242
  end
77
243
  end
78
244
  end