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
@@ -24,9 +24,9 @@ module Pubid
24
24
  # The corrigendum sequence number is stored in the inherited `number`
25
25
  # attribute (mirroring ISO), so it serializes via Base's key_value as
26
26
  # { _type, number: '1', base: {...} } with no corrigendum-specific map.
27
- def to_s
27
+ def to_s(**opts)
28
28
  base_str = base.to_s
29
- "#{base_str} Cor. #{number}"
29
+ annotate_plain_render("#{base_str} Cor. #{number}", **opts)
30
30
  end
31
31
 
32
32
  def ==(other)
@@ -4,7 +4,7 @@ require "parslet"
4
4
 
5
5
  module Pubid
6
6
  module Ccsds
7
- class Parser < Parslet::Parser
7
+ class Parser < ::Pubid::Parser::Grammar
8
8
  rule(:digit) { match["0-9"] }
9
9
  rule(:digits) { digit.repeat(1) }
10
10
  rule(:alnum) { match["A-Za-z0-9"] }
@@ -9,17 +9,21 @@ module Pubid
9
9
  attribute :publisher, Components::Publisher, default: -> {
10
10
  Components::Publisher.new(body: "CCSDS")
11
11
  }
12
- attribute :series, Components::Code
13
- attribute :number, Components::Code
14
- attribute :part, Components::Code
15
- attribute :book_color, Components::Code
12
+ # Plain :string, like the live Pubid::Ccsds::Identifier hierarchy, which
13
+ # already declared these as strings. This class is otherwise
14
+ # unreferenced — nothing inherits it and no builder instantiates it — but
15
+ # it is public API, so it is retyped rather than deleted.
16
+ attribute :series, :string
17
+ attribute :number, :string
18
+ attribute :part, :string
19
+ attribute :book_color, :string
16
20
  attribute :edition, Components::Edition
17
21
  attribute :retired, :boolean, default: -> { false }
18
22
  attribute :language, Components::Language
19
23
  attribute :type, Components::Type
20
24
  attribute :typed_stage, Components::TypedStage
21
25
 
22
- def to_s(lang: :en, lang_single: false)
26
+ def to_s(lang: :en, lang_single: false, **opts)
23
27
  result = ""
24
28
 
25
29
  # Publisher
@@ -29,14 +33,14 @@ module Pubid
29
33
  result += " "
30
34
 
31
35
  # Series (optional single letter before number)
32
- result += series.value if series
36
+ result += series.to_s if series
33
37
 
34
38
  # Number with part (using dot notation)
35
- result += number.value
36
- result += ".#{part.value}" if part
39
+ result += number.to_s
40
+ result += ".#{part}" if part
37
41
 
38
42
  # Book color (required) - no space before dash
39
- result += "-#{book_color.value}" if book_color
43
+ result += "-#{book_color}" if book_color
40
44
 
41
45
  # Edition (optional)
42
46
  result += "-#{edition.number}" if edition
@@ -47,18 +51,18 @@ module Pubid
47
51
  # Language (optional)
48
52
  result += " - #{language.code} Translated" if language
49
53
 
50
- result
54
+ annotate_plain_render(result, **opts)
51
55
  end
52
56
 
53
57
  def <=>(other)
54
58
  return nil unless other.is_a?(SingleIdentifier)
55
59
 
56
60
  # Compare by number first
57
- num_cmp = number.value.to_i <=> other.number.value.to_i
61
+ num_cmp = number.to_i <=> other.number.to_i
58
62
  return num_cmp unless num_cmp.zero?
59
63
 
60
64
  # Then by part
61
- part_cmp = (part ? part.value.to_i : 0) <=> (other.part ? other.part.value.to_i : 0)
65
+ part_cmp = part.to_i <=> other.part.to_i
62
66
  return part_cmp unless part_cmp.zero?
63
67
 
64
68
  # Then by edition
data/lib/pubid/ccsds.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Ccsds
5
6
  extend Pubid::PrefixesSupport
@@ -18,6 +18,28 @@ module Pubid
18
18
  return build_fragment_identifier(data)
19
19
  end
20
20
 
21
+ # Every base document (an adopted norm, an ENV adoption, a plain
22
+ # identifier) goes through the same supplement wrapping. The adopted
23
+ # branches used to return early, so "CEN ISO/TS 21003-7:2008/A1:2010"
24
+ # lost its "/A1:2010".
25
+ base = build_base_document(data)
26
+
27
+ # A slash separator makes a standalone amendment/corrigendum; a plus
28
+ # separator makes a consolidated (bundled) identifier.
29
+ if has_slash_supplements?(data)
30
+ build_standalone_supplement(base, data)
31
+ elsif (supplements_data = extract_supplements(data)).any?
32
+ wrap_with_consolidated(base, supplements_data)
33
+ else
34
+ base
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # The document that the supplements of +data+ apply to, or the whole
41
+ # identifier when there are no supplements.
42
+ def build_base_document(data)
21
43
  # Check if this is an adopted identifier (EN ISO, EN IEC, etc.)
22
44
  if data[:adopted_string]
23
45
  # Special case: ENV can adopt ISO/IEC standards
@@ -28,11 +50,6 @@ module Pubid
28
50
  return build_adopted_identifier(data)
29
51
  end
30
52
 
31
- # Check if this is a standalone amendment/corrigendum (slash separator)
32
- if has_slash_supplements?(data)
33
- return build_standalone_supplement(data)
34
- end
35
-
36
53
  # Implicit adoption: when an EN has no explicit "IEC" prefix but
37
54
  # the number falls in the IEC range (60000-79999), treat it as an
38
55
  # adoption of the corresponding IEC standard. The lower ISO-only
@@ -42,29 +59,32 @@ module Pubid
42
59
  # can still write "EN ISO 12345".
43
60
  # (https://github.com/pubid/pubid/issues/249)
44
61
  # Skip when parts are present — they would be lost when we rebuild
45
- # the identifier from just the base number.
46
- if data[:publisher]&.to_s == "EN" && !data[:adopted_string] &&
47
- data[:parts].to_a.empty? &&
48
- (adopted = build_implicit_adoption(data))
62
+ # the identifier from just the base number — and when supplements
63
+ # are present, which the adoption used to drop the same way.
64
+ if data[:publisher]&.to_s == "EN" && data[:parts].to_a.empty? &&
65
+ !supplements?(data) &&
66
+ (adopted = build_implicit_adoption(data))
49
67
  return adopted
50
68
  end
51
69
 
52
- # Extract supplements before building base identifier (only plus/bundled)
53
- supplements_data = extract_supplements(data)
54
-
55
70
  # Determine identifier class using the module's lookup helpers
56
- identifier = locate_identifier_klass(data).new
57
- assign_attributes(identifier, data)
71
+ base_data = data.except(:supplements)
72
+ identifier = locate_identifier_klass(base_data).new
73
+ assign_attributes(identifier, base_data)
74
+ identifier
75
+ end
58
76
 
59
- # Wrap with consolidated if supplements present (plus/bundled only)
60
- if supplements_data.any?
61
- wrap_with_consolidated(identifier, supplements_data)
62
- else
63
- identifier
64
- end
77
+ def supplements?(data)
78
+ has_slash_supplements?(data) || extract_supplements(data).any?
65
79
  end
66
80
 
67
- private
81
+ # A number or month the grammar captured as an empty string is stored
82
+ # as nil: `to_hash` drops an empty string, so from_hash would give nil
83
+ # and the two identifiers would not be `==`.
84
+ def present_string(value)
85
+ str = value&.to_s
86
+ str unless str.nil? || str.empty?
87
+ end
68
88
 
69
89
  def build_fragment_identifier(data)
70
90
  # Build base identifier first (without amendment/fragment)
@@ -78,13 +98,13 @@ module Pubid
78
98
  # Build Amendment identifier wrapping the base
79
99
  amendment = Identifiers::Amendment.new(
80
100
  base: base,
81
- amendment_number: data[:amendment_number].to_s,
101
+ number: data[:amendment_number].to_s,
82
102
  )
83
103
 
84
104
  # Build Fragment wrapping the amendment
85
105
  Identifiers::Fragment.new(
86
106
  base: amendment,
87
- fragment_number: data[:fragment_number].to_s,
107
+ number: data[:fragment_number].to_s,
88
108
  )
89
109
  end
90
110
 
@@ -94,7 +114,7 @@ module Pubid
94
114
 
95
115
  # Check if publisher is actually a type code (CWA, HD, ES, CR, ENV act as publisher)
96
116
  publisher_str = parsed_hash[:publisher].to_s
97
- if %w[CWA HD ES CR ENV].include?(publisher_str)
117
+ if CenCenelec::PUBLISHER_TYPES.include?(publisher_str)
98
118
  typed_stage = CenCenelec.locate_stage(publisher_str)
99
119
  if (klass = CenCenelec.locate_type(typed_stage.type_code))
100
120
  return klass
@@ -132,7 +152,7 @@ module Pubid
132
152
  end
133
153
 
134
154
  when :number
135
- Components::Code.new(value: value.to_s)
155
+ value.to_s
136
156
 
137
157
  when :parts
138
158
  # Extract first part, preserve full multi-level part string
@@ -142,11 +162,11 @@ module Pubid
142
162
  part_str = parts_array.first[:part].to_s
143
163
 
144
164
  # Store the full part value for multi-level parts
145
- { part: Components::Code.new(value: part_str) }
165
+ { part: part_str }
146
166
  end
147
167
 
148
168
  when :part
149
- Components::Code.new(value: value[:part].to_s) if value.is_a?(Hash)
169
+ value[:part].to_s if value.is_a?(Hash)
150
170
 
151
171
  when :year, :date
152
172
  # Check if month is present in the data and include it
@@ -202,30 +222,43 @@ module Pubid
202
222
  nil
203
223
  end
204
224
 
205
- # Build publishers array (EN is default for adoptions)
206
- publishers = []
207
- publishers << data[:publisher].to_s if data[:publisher]
208
-
209
- # Handle copublishers array
210
- if data[:copublishers]
211
- Array(data[:copublishers]).each do |copub|
212
- publishers << copub[:copublisher].to_s
225
+ # The publisher is a Components::Publisher (EN is the default for
226
+ # adoptions); a second publisher ("CEN/CLC") is a copublisher.
227
+ copublishers = if data[:copublishers]
228
+ Array(data[:copublishers]).map do |copub|
229
+ copub[:copublisher].to_s
230
+ end
231
+ elsif data[:copublisher]
232
+ [data[:copublisher].to_s]
233
+ else
234
+ []
235
+ end
236
+
237
+ attrs = { adopted: adopted_id }
238
+ # "prEN ISO 1234:2020": the draft stage is on the CEN adoption. The
239
+ # parser captures it as type_with_stage and not as a publisher, and
240
+ # it used to be dropped here, so the identifier rendered as "EN ISO".
241
+ if data[:type_with_stage]
242
+ attrs.merge!(cast(:type_with_stage, data[:type_with_stage]))
243
+ end
244
+ if data[:publisher]
245
+ attrs[:publisher] =
246
+ Components::Publisher.new(body: data[:publisher].to_s)
247
+ end
248
+ # Only when present: an empty collection would not survive to_hash,
249
+ # and from_hash would then give nil where the parse gave [].
250
+ unless copublishers.empty?
251
+ attrs[:copublishers] = copublishers.map do |body|
252
+ Components::Publisher.new(body: body)
213
253
  end
214
- elsif data[:copublisher]
215
- publishers << data[:copublisher].to_s
216
254
  end
217
255
 
218
- publishers = ["EN"] if publishers.empty?
219
-
220
- Identifiers::AdoptedEuropeanNorm.new(
221
- publisher: publishers,
222
- adopted_identifier: adopted_id,
223
- )
256
+ Identifiers::AdoptedEuropeanNorm.new(**attrs)
224
257
  end
225
258
 
226
259
  # Implicit adoption by number range. Returns an AdoptedEuropeanNorm
227
260
  # wrapping the IEC identifier that the EN number implies, or nil if the
228
- # number doesn't fall in the IEC range. Limited to IEC range only —
261
+ # number does not fall in the IEC range. Limited to IEC range only —
229
262
  # see the call site comment for why the ISO range is excluded.
230
263
  def build_implicit_adoption(data)
231
264
  number = data[:number].to_s
@@ -235,9 +268,9 @@ module Pubid
235
268
  num = num_str.to_i
236
269
  return nil unless (60_000..79_999).cover?(num)
237
270
 
271
+ # The publisher is the "EN" default.
238
272
  Identifiers::AdoptedEuropeanNorm.new(
239
- publisher: ["EN"],
240
- adopted_identifier: Pubid::Iec.parse("IEC #{num_str}"),
273
+ adopted: Pubid::Iec.parse("IEC #{num_str}"),
241
274
  )
242
275
  end
243
276
 
@@ -254,27 +287,30 @@ module Pubid
254
287
  Pubid::Iec.parse(adopted_str)
255
288
  end
256
289
 
290
+ # The publisher is ENV, as for a plain "ENV 1613:1995"; the
291
+ # SingleIdentifier default is EN.
257
292
  Identifiers::EuropeanPrestandard.new(
258
- adopted_identifier: adopted_id,
293
+ publisher: Components::Publisher.new(body: "ENV"),
294
+ adopted: adopted_id,
259
295
  )
260
296
  end
261
297
 
298
+ # The members after the first carry no `base`: the consolidated
299
+ # identifier holds the base document once, as its first member. With a
300
+ # `base` in each member, `to_hash` wrote the base again for every "+".
262
301
  def wrap_with_consolidated(base, supplements_data)
263
302
  supplement_ids = supplements_data.map do |supp|
264
303
  if supp[:type] == :amendment
265
304
  Identifiers::Amendment.new(
266
- base: base,
267
- amendment_number: supp[:number],
268
- amendment_year: supp[:year]&.to_i,
305
+ number: present_string(supp[:number]),
306
+ year: present_string(supp[:year]),
269
307
  )
270
308
  else
271
- corr_attrs = {
272
- base: base,
273
- corrigendum_number: supp[:number],
274
- corrigendum_year: supp[:year]&.to_i,
275
- }
276
- corr_attrs[:corrigendum_month] = supp[:month] if supp[:month]
277
- Identifiers::Corrigendum.new(**corr_attrs)
309
+ Identifiers::Corrigendum.new(
310
+ number: present_string(supp[:number]),
311
+ year: present_string(supp[:year]),
312
+ month: present_string(supp[:month]),
313
+ )
278
314
  end
279
315
  end
280
316
 
@@ -324,13 +360,7 @@ module Pubid
324
360
  end
325
361
  end
326
362
 
327
- def build_standalone_supplement(data)
328
- # Build base identifier first (without supplements)
329
- base_data = data.dup
330
- base_data.delete(:supplements)
331
- base = locate_identifier_klass(base_data).new
332
- assign_attributes(base, base_data)
333
-
363
+ def build_standalone_supplement(base, data)
334
364
  # Get the first supplement (slash means only one supplement)
335
365
  supp_array = data[:supplements]
336
366
  supp_data = supp_array.first
@@ -340,21 +370,16 @@ module Pubid
340
370
  if supp_data[:amd_number]
341
371
  Identifiers::Amendment.new(
342
372
  base: base,
343
- amendment_number: supp_data[:amd_number].to_s,
344
- amendment_year: supp_data[:amd_year]&.to_i,
373
+ number: supp_data[:amd_number].to_s,
374
+ year: present_string(supp_data[:amd_year]),
345
375
  )
346
376
  else
347
- corr_attrs = {
377
+ Identifiers::Corrigendum.new(
348
378
  base: base,
349
- corrigendum_number: supp_data[:cor_number]&.to_s,
350
- corrigendum_year: (supp_data[:cor_year] || supp_data[:year])&.to_i,
351
- }
352
- month_val = supp_data[:month]&.to_s
353
- if month_val && !month_val.empty?
354
- corr_attrs[:corrigendum_month] =
355
- month_val
356
- end
357
- Identifiers::Corrigendum.new(**corr_attrs)
379
+ number: present_string(supp_data[:cor_number]),
380
+ year: present_string(supp_data[:year]),
381
+ month: present_string(supp_data[:month]),
382
+ )
358
383
  end
359
384
  end
360
385
  end
@@ -8,15 +8,127 @@ module Pubid
8
8
  # CEN/CENELEC identifier `is_a?(Pubid::CenCenelec::Identifier)` natively (and
9
9
  # giving them the shared polymorphic `from_hash`). No facade needed.
10
10
  class Identifier < ::Pubid::Identifier
11
+ # `number`/`part`/`subpart` are declared here rather than inherited as a
12
+ # Components::Code: CEN/CENELEC stores a bare string in every one of them.
13
+ # This class is the shared parent of BOTH branches, so one declaration
14
+ # here replaces the three that used to disagree (Identifiers::Base said
15
+ # :string, SingleIdentifier inherited Code, SupplementIdentifier declared
16
+ # Code again). Declaring here is safe because this body lives in one file
17
+ # and is never reopened, so every subclass body opens after it has run
18
+ # (lutaml deep-dups the parent attribute table at class-definition time).
19
+ attribute :number, :string
20
+ attribute :part, :string
21
+ attribute :subpart, :string
22
+
23
+ # The publisher serializes as a bare string ("publisher" => "CEN"), and
24
+ # the copublishers as a list of strings. The shared table does not do
25
+ # this for every flavor, because IEEE has a published index whose rows
26
+ # hold the nested {"body" => …} form.
27
+ def self.flat_scalar_components
28
+ super.merge(publisher: "publisher", copublishers: "copublishers")
29
+ end
30
+
31
+ def self.flat_scalar_fields
32
+ super.merge(publisher: :body, copublishers: :body)
33
+ end
34
+
35
+ # A draft stage serializes as its typed-stage code ("stage" => "pren"),
36
+ # as ISO ("dis") and IEC ("cd") do. The `type`, `stage` and
37
+ # `typed_stage` components all repeat one entry of the stage registry,
38
+ # so the code is enough, and #inflate_scalar_components rebuilds the
39
+ # three from it. The short form is written only when the three are
40
+ # exactly what the registry entry gives; any other combination keeps
41
+ # the nested components, so the round trip stays exact.
42
+ def self.compact_hash(model, hash)
43
+ stage = model.typed_stage
44
+ return unless stage && hash.key?("typed_stage")
45
+ return unless model.type == stage.to_type &&
46
+ model.stage == stage.to_stage
47
+
48
+ hash.delete("type")
49
+ hash.delete("typed_stage")
50
+ hash["stage"] = stage.code.to_s
51
+ end
52
+
53
+ # Reads the short stage form back; see #compact_hash. The nested form
54
+ # still reads as before.
55
+ def self.inflate_scalar_components(data)
56
+ data = super
57
+ return data unless data.is_a?(::Hash) && component_attribute?(:stage)
58
+
59
+ key = data.key?("stage") ? "stage" : :stage
60
+ value = data[key]
61
+ return data unless value.is_a?(::String) || value.is_a?(::Symbol)
62
+
63
+ stage = CenCenelec.locate_stage_by_code(value)
64
+ return data unless stage
65
+
66
+ data = data.dup
67
+ data.delete(key)
68
+ data["type"] = stage.to_type.to_hash
69
+ data["stage"] = stage.to_stage.to_hash
70
+ data["typed_stage"] = stage.to_hash
71
+ data
72
+ end
73
+
74
+ # The slug names a draft stage by its code ("en.pren.1234.2020"), so a
75
+ # prEN, an FprEN and an EN with one number and year get three slugs.
76
+ # The shared hook gave the type code "en" for all of them.
77
+ def mr_type
78
+ stage = typed_stage
79
+ return super unless stage && stage.stage_code.to_s != "published"
80
+
81
+ stage.code.to_s.downcase
82
+ end
83
+
84
+ # The attributes that hold a supplement's own date: `year`, and `month`
85
+ # on a corrigendum. Empty for a document. See #exclude.
86
+ def self.supplement_date_attributes
87
+ []
88
+ end
89
+
90
+ # The CEN year rule, as in BSI: `:year` removes only the base
91
+ # document's year ("EN 13250:2000/A1:2005" -> "EN 13250/A1:2005"), and
92
+ # the CEN key `:supplement_year` removes the supplement's own date
93
+ # ("EN 13250:2000/A1"). A supplement declares its date as a `year`
94
+ # attribute, which the base #exclude resets for `:year`, so the value is
95
+ # put back here. The month goes with the year, or a "/AC:2016-11" would
96
+ # keep a month with no year. The base #exclude passes every key on to
97
+ # the nested identifiers, so the members of a consolidated identifier
98
+ # follow the same rule.
99
+ def exclude(*args)
100
+ result = super
101
+ attrs = self.class.supplement_date_attributes
102
+ # `exclude(:amendment)` returns the base document, not a copy of self.
103
+ return result if attrs.empty? || !result.instance_of?(self.class)
104
+
105
+ drop = args.include?(:supplement_year)
106
+ attrs.each do |attr|
107
+ result.public_send(:"#{attr}=", drop ? nil : public_send(attr))
108
+ end
109
+ result
110
+ end
111
+
11
112
  def self.parse(identifier)
113
+ unless identifier.is_a?(String)
114
+ raise Pubid::Errors::InvalidInputError,
115
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
116
+ end
117
+
12
118
  if identifier.length > Pubid::MAX_INPUT_LENGTH
13
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
119
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
14
120
  end
15
121
 
16
122
  parsed = Parser.parse(identifier)
17
123
  Builder.new.build(parsed)
18
- rescue Parslet::ParseFailed => e
19
- raise "Failed to parse CEN identifier '#{identifier}': #{e.message}"
124
+ end
125
+
126
+ private
127
+
128
+ # "amd.1.2005" from ("amd", "1", 2005). A nil or blank segment is left
129
+ # out, so an unnumbered "/AC:2003" gives "cor.2003", not "cor..2003".
130
+ def mr_join_segments(*segments)
131
+ segments.map(&:to_s).reject(&:empty?).join(".").downcase
20
132
  end
21
133
  end
22
134
  end
@@ -5,8 +5,15 @@ module Pubid
5
5
  module Identifiers
6
6
  # AdoptedEuropeanNorm wraps ISO/IEC identifiers
7
7
  # Example: "EN ISO 8601:2019" where ISO 8601:2019 is an ISO identifier object
8
+ #
9
+ # `publisher` is the inherited Components::Publisher ("EN", "CEN",
10
+ # "CR", "HD"); a second publisher goes in `copublishers`. It used to be
11
+ # an Array of Strings in that Components::Publisher attribute, so
12
+ # `to_hash` raised on every adopted norm.
8
13
  class AdoptedEuropeanNorm < EuropeanNorm
9
- attribute :adopted_identifier, Base, polymorphic: true # ISO/IEC/IEEE object
14
+ # Typed to the root class: the adopted document is an ISO or IEC
15
+ # identifier, never a CEN one.
16
+ attribute :adopted, ::Pubid::Identifier, polymorphic: true
10
17
 
11
18
  # Override self.type to return nil so that AdoptedEuropeanNorm is NOT
12
19
  # registered as a type in CenCenelec.identifier_types. The class is a
@@ -19,25 +26,29 @@ module Pubid
19
26
  nil
20
27
  end
21
28
 
22
- # Delegate common methods to adopted identifier
23
- def number
24
- adopted_identifier&.number
29
+ # Walk to the adopted document for the relaton-index key.
30
+ #
31
+ # This replaces reader methods named number, year, date, parts and
32
+ # part that delegated to the adopted document. `number`, `date` and
33
+ # `part` are lutaml attributes, so the serializer read the ISO values
34
+ # through them and wrote them again at the top level of the hash.
35
+ # `#root` is the documented wrapper shape (EuropeanPrestandard,
36
+ # ConsolidatedIdentifier).
37
+ def root
38
+ adopted ? adopted.root : self
25
39
  end
26
40
 
27
- def year
28
- adopted_identifier&.year if adopted_identifier&.methods&.include?(:year)
41
+ # MR "cen.iso.ts.21003-7.2019": the CEN publisher ("cen" or
42
+ # "cen-clc"), then the adopted document's own MR string in place of a
43
+ # number. These are hooks and not a `to_mr_string` override, so the
44
+ # MR of an amendment on this norm renders its base the same way.
45
+ def mr_publisher
46
+ mr_join(*([publisher] + Array(copublishers)).compact
47
+ .map { |p| p.to_s.downcase })
29
48
  end
30
49
 
31
- def date
32
- adopted_identifier&.date if adopted_identifier&.methods&.include?(:date)
33
- end
34
-
35
- def parts
36
- adopted_identifier&.parts if adopted_identifier&.methods&.include?(:parts)
37
- end
38
-
39
- def part
40
- adopted_identifier&.part if adopted_identifier&.methods&.include?(:part)
50
+ def mr_number_with_part
51
+ adopted&.to_mr_string
41
52
  end
42
53
  end
43
54
  end
@@ -5,13 +5,53 @@ module Pubid
5
5
  module Identifiers
6
6
  # Amendment Identifier
7
7
  # Contains a base identifier plus amendment parameters
8
- class Amendment < Base
9
- attribute :base, Base, polymorphic: true
10
- attribute :amendment_number, :string
11
- attribute :amendment_year, :integer
8
+ #
9
+ # It descends from the shared Pubid::CenCenelec::Identifier, not from
10
+ # the legacy Identifiers::Base. That class declares publisher, year and
11
+ # parts attributes that an amendment does not have, and its `==` compared
12
+ # only those, so any two amendments were equal.
13
+ #
14
+ # The amendment number is the inherited `number`, and its year is a
15
+ # `year` string, so the hash uses the same keys as a document:
16
+ # {"base" => {…}, "number" => "1", "year" => "2005"}. The member of a
17
+ # consolidated identifier has no `base`; the consolidated identifier
18
+ # holds the base document once.
19
+ class Amendment < Pubid::CenCenelec::Identifier
20
+ # Typed to the shared parent: the base of an amendment is a
21
+ # SingleIdentifier ("EN 13250:2000"), an adopted norm
22
+ # ("CEN ISO/TS 21003-7:2008") or another supplement.
23
+ attribute :base, Pubid::CenCenelec::Identifier, polymorphic: true
24
+ attribute :year, :string
12
25
 
13
- def publisher
14
- base&.publisher
26
+ def self.supplement_date_attributes
27
+ %i[year]
28
+ end
29
+
30
+ def base_document
31
+ base&.base_document || self
32
+ end
33
+
34
+ # Dropping the supplement layer yields the base standard.
35
+ def drop_supplements
36
+ base || self
37
+ end
38
+
39
+ # Uniform supplement interface (shared with Corrigendum and with BSI)
40
+ # so callers need not special-case the class.
41
+ def supplement_type
42
+ :amendment
43
+ end
44
+
45
+ def supplement_number
46
+ number
47
+ end
48
+
49
+ def supplement_year
50
+ year
51
+ end
52
+
53
+ def mr_supplement_suffix
54
+ mr_join_segments("amd", number, year)
15
55
  end
16
56
  end
17
57
  end