pubid 2.0.0.pre.alpha.7 → 2.0.0.pre.alpha.9

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 (485) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +9 -50
  3. data/data/bipm/update_codes.yaml +9 -0
  4. data/data/iec/update_codes.yaml +3 -0
  5. data/data/ieee/update_codes.yaml +107 -1
  6. data/data/nist/series.yaml +155 -0
  7. data/data/nist/update_codes.yaml +8 -0
  8. data/lib/pubid/adobe/builder.rb +52 -0
  9. data/lib/pubid/adobe/identifier.rb +49 -0
  10. data/lib/pubid/adobe/identifiers/publication.rb +31 -0
  11. data/lib/pubid/adobe/identifiers/tech_note.rb +32 -0
  12. data/lib/pubid/adobe/identifiers.rb +10 -0
  13. data/lib/pubid/adobe/parser.rb +128 -0
  14. data/lib/pubid/adobe/renderer.rb +39 -0
  15. data/lib/pubid/adobe/urn_generator.rb +42 -0
  16. data/lib/pubid/adobe/urn_parser.rb +69 -0
  17. data/lib/pubid/adobe.rb +60 -0
  18. data/lib/pubid/amca/builder.rb +2 -2
  19. data/lib/pubid/amca/identifier.rb +1 -3
  20. data/lib/pubid/amca/identifiers/base.rb +1 -6
  21. data/lib/pubid/amca/identifiers/interpretation.rb +1 -1
  22. data/lib/pubid/amca/identifiers/publication.rb +1 -1
  23. data/lib/pubid/amca/identifiers/standard.rb +1 -1
  24. data/lib/pubid/amca/identifiers.rb +0 -1
  25. data/lib/pubid/amca/single_identifier.rb +1 -1
  26. data/lib/pubid/amca.rb +3 -3
  27. data/lib/pubid/ashrae/builder.rb +22 -22
  28. data/lib/pubid/ashrae/identifier.rb +3 -4
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +1 -1
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +1 -1
  31. data/lib/pubid/ashrae/identifiers/base.rb +14 -8
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +1 -1
  33. data/lib/pubid/ashrae/identifiers.rb +0 -1
  34. data/lib/pubid/ashrae/parser.rb +14 -9
  35. data/lib/pubid/ashrae/renderer.rb +19 -19
  36. data/lib/pubid/ashrae/single_identifier.rb +1 -1
  37. data/lib/pubid/ashrae/supplement_identifier.rb +6 -6
  38. data/lib/pubid/ashrae.rb +3 -3
  39. data/lib/pubid/bipm/builder.rb +107 -0
  40. data/lib/pubid/bipm/identifier.rb +160 -0
  41. data/lib/pubid/bipm/identifiers/committee_document.rb +24 -0
  42. data/lib/pubid/bipm/identifiers/guide.rb +24 -0
  43. data/lib/pubid/bipm/identifiers/meeting.rb +41 -0
  44. data/lib/pubid/bipm/identifiers/mep.rb +21 -0
  45. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  46. data/lib/pubid/bipm/identifiers/si_brochure.rb +20 -0
  47. data/lib/pubid/bipm/identifiers.rb +15 -0
  48. data/lib/pubid/bipm/parser.rb +184 -0
  49. data/lib/pubid/bipm/renderer.rb +110 -0
  50. data/lib/pubid/bipm/urn_generator.rb +58 -0
  51. data/lib/pubid/bipm/urn_parser.rb +52 -0
  52. data/lib/pubid/bipm.rb +86 -0
  53. data/lib/pubid/bsi/builder.rb +14 -14
  54. data/lib/pubid/bsi/identifier.rb +0 -1
  55. data/lib/pubid/bsi/identifiers/addendum_document.rb +2 -2
  56. data/lib/pubid/bsi/identifiers/amendment.rb +4 -4
  57. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +6 -0
  58. data/lib/pubid/bsi/identifiers/corrigendum.rb +4 -4
  59. data/lib/pubid/bsi/identifiers/expert_commentary.rb +5 -5
  60. data/lib/pubid/bsi/identifiers/supplement_document.rb +2 -2
  61. data/lib/pubid/bsi/identifiers/value_added_publication.rb +6 -6
  62. data/lib/pubid/bsi/renderer.rb +8 -8
  63. data/lib/pubid/bsi/single_identifier.rb +48 -0
  64. data/lib/pubid/builder/base.rb +5 -3
  65. data/lib/pubid/bundled_identifier.rb +3 -3
  66. data/lib/pubid/calconnect/builder.rb +38 -0
  67. data/lib/pubid/calconnect/identifier.rb +117 -0
  68. data/lib/pubid/calconnect/identifiers/standard.rb +29 -0
  69. data/lib/pubid/calconnect/identifiers.rb +9 -0
  70. data/lib/pubid/calconnect/parser.rb +54 -0
  71. data/lib/pubid/calconnect/renderer.rb +36 -0
  72. data/lib/pubid/calconnect/urn_generator.rb +33 -0
  73. data/lib/pubid/calconnect/urn_parser.rb +34 -0
  74. data/lib/pubid/calconnect.rb +77 -0
  75. data/lib/pubid/ccsds/builder.rb +1 -1
  76. data/lib/pubid/ccsds/identifier.rb +1 -1
  77. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  78. data/lib/pubid/ccsds/supplement_identifier.rb +6 -6
  79. data/lib/pubid/ccsds/urn_generator.rb +3 -3
  80. data/lib/pubid/cen_cenelec/builder.rb +46 -12
  81. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +2 -2
  82. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +6 -0
  83. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +2 -2
  84. data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +60 -3
  85. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +2 -2
  86. data/lib/pubid/cen_cenelec/renderer.rb +5 -5
  87. data/lib/pubid/cen_cenelec/supplement_identifier.rb +5 -5
  88. data/lib/pubid/cen_cenelec/urn_generator.rb +1 -1
  89. data/lib/pubid/cen_cenelec.rb +76 -72
  90. data/lib/pubid/cie/builder.rb +139 -24
  91. data/lib/pubid/cie/identifier.rb +7 -3
  92. data/lib/pubid/cie/identifiers/bundle.rb +32 -4
  93. data/lib/pubid/cie/identifiers/code_attributes.rb +52 -0
  94. data/lib/pubid/cie/identifiers/conference.rb +9 -4
  95. data/lib/pubid/cie/identifiers/corrigendum.rb +17 -23
  96. data/lib/pubid/cie/identifiers/dual_published.rb +4 -4
  97. data/lib/pubid/cie/identifiers/identical.rb +7 -7
  98. data/lib/pubid/cie/identifiers/joint_published.rb +8 -8
  99. data/lib/pubid/cie/identifiers/proceedings.rb +39 -0
  100. data/lib/pubid/cie/identifiers/standard.rb +33 -7
  101. data/lib/pubid/cie/identifiers/supplement.rb +24 -22
  102. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +6 -2
  103. data/lib/pubid/cie/identifiers.rb +2 -0
  104. data/lib/pubid/cie/parser.rb +54 -5
  105. data/lib/pubid/cie/single_identifier.rb +40 -1
  106. data/lib/pubid/cie/supplement_identifier.rb +29 -16
  107. data/lib/pubid/cie/urn_generator.rb +11 -8
  108. data/lib/pubid/cie.rb +0 -1
  109. data/lib/pubid/components/date.rb +21 -5
  110. data/lib/pubid/csa/builder.rb +1 -1
  111. data/lib/pubid/csa/composite_identifier.rb +17 -1
  112. data/lib/pubid/csa/identifier.rb +48 -3
  113. data/lib/pubid/csa/identifiers/bundled.rb +6 -0
  114. data/lib/pubid/csa/identifiers/combined.rb +6 -0
  115. data/lib/pubid/csa/identifiers/package.rb +2 -2
  116. data/lib/pubid/csa/parser.rb +6 -6
  117. data/lib/pubid/csa/wrapper_identifier.rb +17 -0
  118. data/lib/pubid/doi/builder.rb +18 -0
  119. data/lib/pubid/doi/identifier.rb +46 -0
  120. data/lib/pubid/doi/identifiers/resource.rb +30 -0
  121. data/lib/pubid/doi/identifiers.rb +9 -0
  122. data/lib/pubid/doi/parser.rb +52 -0
  123. data/lib/pubid/doi/renderer.rb +13 -0
  124. data/lib/pubid/doi.rb +55 -0
  125. data/lib/pubid/easc/builder.rb +55 -0
  126. data/lib/pubid/easc/identifier.rb +61 -0
  127. data/lib/pubid/easc/identifiers/pmg.rb +20 -0
  128. data/lib/pubid/easc/identifiers/rmg.rb +19 -0
  129. data/lib/pubid/easc/identifiers.rb +10 -0
  130. data/lib/pubid/easc/parser.rb +80 -0
  131. data/lib/pubid/easc/renderer.rb +48 -0
  132. data/lib/pubid/easc/urn_generator.rb +46 -0
  133. data/lib/pubid/easc/urn_parser.rb +57 -0
  134. data/lib/pubid/easc.rb +52 -0
  135. data/lib/pubid/ecma/builder.rb +31 -0
  136. data/lib/pubid/ecma/identifier.rb +76 -0
  137. data/lib/pubid/ecma/identifiers/memento.rb +30 -0
  138. data/lib/pubid/ecma/identifiers/standard.rb +31 -0
  139. data/lib/pubid/ecma/identifiers/technical_report.rb +31 -0
  140. data/lib/pubid/ecma/identifiers.rb +11 -0
  141. data/lib/pubid/ecma/parser.rb +41 -0
  142. data/lib/pubid/ecma/renderer.rb +46 -0
  143. data/lib/pubid/ecma/urn_generator.rb +25 -0
  144. data/lib/pubid/ecma/urn_parser.rb +35 -0
  145. data/lib/pubid/ecma.rb +67 -0
  146. data/lib/pubid/etsi/builder.rb +7 -3
  147. data/lib/pubid/etsi/identifier.rb +1 -3
  148. data/lib/pubid/etsi/identifiers/base.rb +28 -10
  149. data/lib/pubid/etsi/identifiers/etsi_standard.rb +128 -4
  150. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +35 -3
  151. data/lib/pubid/etsi/identifiers.rb +0 -1
  152. data/lib/pubid/etsi/parser.rb +8 -2
  153. data/lib/pubid/etsi/renderer.rb +8 -2
  154. data/lib/pubid/etsi.rb +5 -5
  155. data/lib/pubid/export/exporter.rb +2 -1
  156. data/lib/pubid/export/flavor_exporter.rb +18 -3
  157. data/lib/pubid/gb/builder.rb +45 -0
  158. data/lib/pubid/gb/identifier.rb +64 -0
  159. data/lib/pubid/gb/identifiers/standard.rb +34 -0
  160. data/lib/pubid/gb/identifiers.rb +9 -0
  161. data/lib/pubid/gb/parser.rb +63 -0
  162. data/lib/pubid/gb/renderer.rb +35 -0
  163. data/lib/pubid/gb.rb +87 -0
  164. data/lib/pubid/gost/builder.rb +148 -0
  165. data/lib/pubid/gost/identifier.rb +49 -0
  166. data/lib/pubid/gost/identifiers/foreign_reference.rb +25 -0
  167. data/lib/pubid/gost/identifiers/harmonized.rb +39 -0
  168. data/lib/pubid/gost/identifiers/identical_adoption.rb +39 -0
  169. data/lib/pubid/gost/identifiers/interstate_standard.rb +20 -0
  170. data/lib/pubid/gost/identifiers/national_standard.rb +19 -0
  171. data/lib/pubid/gost/identifiers.rb +13 -0
  172. data/lib/pubid/gost/parser.rb +97 -0
  173. data/lib/pubid/gost/renderer.rb +48 -0
  174. data/lib/pubid/gost/urn_generator.rb +46 -0
  175. data/lib/pubid/gost/urn_parser.rb +52 -0
  176. data/lib/pubid/gost.rb +49 -0
  177. data/lib/pubid/iala/builder.rb +22 -27
  178. data/lib/pubid/iala/identifier.rb +27 -2
  179. data/lib/pubid/iala/identifiers/advice.rb +1 -1
  180. data/lib/pubid/iala/identifiers/annex.rb +14 -12
  181. data/lib/pubid/iala/identifiers/general_assembly.rb +5 -2
  182. data/lib/pubid/iala/identifiers/guideline.rb +3 -1
  183. data/lib/pubid/iala/identifiers/letter.rb +1 -1
  184. data/lib/pubid/iala/identifiers/manual.rb +3 -1
  185. data/lib/pubid/iala/identifiers/model_course.rb +3 -1
  186. data/lib/pubid/iala/identifiers/recommendation.rb +3 -1
  187. data/lib/pubid/iala/identifiers/report.rb +1 -1
  188. data/lib/pubid/iala/identifiers/resolution.rb +1 -1
  189. data/lib/pubid/iala/identifiers/standard.rb +3 -1
  190. data/lib/pubid/iala/identifiers.rb +0 -1
  191. data/lib/pubid/iala/renderer.rb +1 -1
  192. data/lib/pubid/iala/urn_generator.rb +1 -1
  193. data/lib/pubid/iala/urn_parser.rb +19 -2
  194. data/lib/pubid/iala.rb +4 -4
  195. data/lib/pubid/iana/builder.rb +20 -0
  196. data/lib/pubid/iana/identifier.rb +72 -0
  197. data/lib/pubid/iana/identifiers/registry.rb +33 -0
  198. data/lib/pubid/iana/identifiers.rb +9 -0
  199. data/lib/pubid/iana/parser.rb +38 -0
  200. data/lib/pubid/iana/renderer.rb +29 -0
  201. data/lib/pubid/iana/urn_generator.rb +15 -0
  202. data/lib/pubid/iana/urn_parser.rb +24 -0
  203. data/lib/pubid/iana.rb +69 -0
  204. data/lib/pubid/identifier.rb +210 -23
  205. data/lib/pubid/idf/builder.rb +1 -1
  206. data/lib/pubid/idf/parser.rb +2 -2
  207. data/lib/pubid/idf/renderer.rb +1 -1
  208. data/lib/pubid/idf/supplement_identifier.rb +3 -3
  209. data/lib/pubid/iec/builder.rb +35 -25
  210. data/lib/pubid/iec/identifier.rb +47 -2
  211. data/lib/pubid/iec/identifiers/base.rb +2 -2
  212. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +6 -0
  213. data/lib/pubid/iec/identifiers/fragment_identifier.rb +11 -11
  214. data/lib/pubid/iec/identifiers/international_standard.rb +8 -5
  215. data/lib/pubid/iec/identifiers/sheet_identifier.rb +12 -12
  216. data/lib/pubid/iec/identifiers/technical_group.rb +30 -0
  217. data/lib/pubid/iec/identifiers/technical_report.rb +1 -1
  218. data/lib/pubid/iec/identifiers/technical_specification.rb +1 -1
  219. data/lib/pubid/iec/identifiers/vap_identifier.rb +12 -12
  220. data/lib/pubid/iec/identifiers.rb +1 -0
  221. data/lib/pubid/iec/parser.rb +51 -10
  222. data/lib/pubid/iec/renderer.rb +18 -8
  223. data/lib/pubid/iec/single_identifier.rb +1 -1
  224. data/lib/pubid/iec/supplement_identifier.rb +32 -19
  225. data/lib/pubid/iec.rb +1 -6
  226. data/lib/pubid/ieee/aiee/builder.rb +17 -3
  227. data/lib/pubid/ieee/aiee/identifier.rb +45 -61
  228. data/lib/pubid/ieee/builder.rb +341 -68
  229. data/lib/pubid/ieee/compaction.rb +121 -0
  230. data/lib/pubid/ieee/components/draft.rb +47 -7
  231. data/lib/pubid/ieee/identifier.rb +1 -3
  232. data/lib/pubid/ieee/identifiers/adopted_standard.rb +30 -5
  233. data/lib/pubid/ieee/identifiers/amendment.rb +40 -0
  234. data/lib/pubid/ieee/identifiers/base.rb +106 -13
  235. data/lib/pubid/ieee/identifiers/code_number.rb +82 -0
  236. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +6 -2
  237. data/lib/pubid/ieee/identifiers/corrigendum.rb +17 -2
  238. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +3 -3
  239. data/lib/pubid/ieee/identifiers/dual_identifier.rb +3 -3
  240. data/lib/pubid/ieee/identifiers/dual_published.rb +3 -3
  241. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +102 -5
  242. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +2 -1
  243. data/lib/pubid/ieee/identifiers/joint_development.rb +56 -11
  244. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +1 -1
  245. data/lib/pubid/ieee/identifiers/nesc/base.rb +90 -19
  246. data/lib/pubid/ieee/identifiers/nesc/draft.rb +20 -5
  247. data/lib/pubid/ieee/identifiers/nesc/edition.rb +32 -0
  248. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +16 -5
  249. data/lib/pubid/ieee/identifiers/nesc/redline.rb +11 -2
  250. data/lib/pubid/ieee/identifiers/nesc/standard.rb +18 -3
  251. data/lib/pubid/ieee/identifiers/nesc.rb +1 -0
  252. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +3 -3
  253. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +14 -6
  254. data/lib/pubid/ieee/identifiers/redlined_standard.rb +4 -4
  255. data/lib/pubid/ieee/identifiers/si_standard.rb +4 -1
  256. data/lib/pubid/ieee/identifiers/standard.rb +4 -1
  257. data/lib/pubid/ieee/identifiers/supplement_identifier.rb +26 -7
  258. data/lib/pubid/ieee/identifiers.rb +2 -1
  259. data/lib/pubid/ieee/ire/builder.rb +7 -6
  260. data/lib/pubid/ieee/ire/identifier.rb +48 -33
  261. data/lib/pubid/ieee/nesc/builder.rb +33 -20
  262. data/lib/pubid/ieee/nesc/parser.rb +15 -14
  263. data/lib/pubid/ieee/parser.rb +461 -43
  264. data/lib/pubid/ieee/pre_parser.rb +1 -1
  265. data/lib/pubid/ieee/renderer.rb +154 -57
  266. data/lib/pubid/ieee/typed_stages.rb +12 -1
  267. data/lib/pubid/ieee/urn_generator.rb +3 -5
  268. data/lib/pubid/ieee.rb +56 -3
  269. data/lib/pubid/ietf/builder.rb +62 -0
  270. data/lib/pubid/ietf/identifier.rb +5 -0
  271. data/lib/pubid/ietf/identifiers/base.rb +75 -0
  272. data/lib/pubid/ietf/identifiers/bcp.rb +25 -0
  273. data/lib/pubid/ietf/identifiers/fyi.rb +25 -0
  274. data/lib/pubid/ietf/identifiers/internet_draft.rb +29 -0
  275. data/lib/pubid/ietf/identifiers/rfc.rb +25 -0
  276. data/lib/pubid/ietf/identifiers/std.rb +25 -0
  277. data/lib/pubid/ietf/identifiers.rb +13 -0
  278. data/lib/pubid/ietf/parser.rb +43 -0
  279. data/lib/pubid/ietf/renderer.rb +26 -0
  280. data/lib/pubid/ietf/urn_generator.rb +23 -0
  281. data/lib/pubid/ietf/urn_parser.rb +35 -0
  282. data/lib/pubid/ietf.rb +76 -0
  283. data/lib/pubid/iho/identifier.rb +1 -3
  284. data/lib/pubid/iho/identifiers/base.rb +1 -7
  285. data/lib/pubid/iho/identifiers/bibliographic.rb +1 -1
  286. data/lib/pubid/iho/identifiers/circular_letter.rb +1 -1
  287. data/lib/pubid/iho/identifiers/miscellaneous.rb +1 -1
  288. data/lib/pubid/iho/identifiers/publication.rb +1 -1
  289. data/lib/pubid/iho/identifiers/standard.rb +1 -1
  290. data/lib/pubid/iho/identifiers.rb +0 -1
  291. data/lib/pubid/iho.rb +7 -7
  292. data/lib/pubid/isbn/builder.rb +44 -0
  293. data/lib/pubid/isbn/check_digit.rb +53 -0
  294. data/lib/pubid/isbn/identifier.rb +63 -0
  295. data/lib/pubid/isbn/identifiers/book.rb +30 -0
  296. data/lib/pubid/isbn/identifiers.rb +9 -0
  297. data/lib/pubid/isbn/parser.rb +45 -0
  298. data/lib/pubid/isbn/renderer.rb +16 -0
  299. data/lib/pubid/isbn.rb +52 -0
  300. data/lib/pubid/iso/builder.rb +9 -4
  301. data/lib/pubid/iso/combined_identifier.rb +2 -2
  302. data/lib/pubid/iso/identifier.rb +17 -1
  303. data/lib/pubid/iso/identifiers/directives_supplement.rb +8 -8
  304. data/lib/pubid/iso/normalizer.rb +2 -2
  305. data/lib/pubid/iso/parser.rb +16 -12
  306. data/lib/pubid/iso/supplement_identifier.rb +19 -6
  307. data/lib/pubid/iso/urn_generator.rb +4 -1
  308. data/lib/pubid/iso/urn_parser.rb +1 -1
  309. data/lib/pubid/itu/builder.rb +230 -29
  310. data/lib/pubid/itu/components/code.rb +48 -5
  311. data/lib/pubid/itu/components/designation.rb +35 -0
  312. data/lib/pubid/itu/components.rb +1 -0
  313. data/lib/pubid/itu/identifier.rb +1 -3
  314. data/lib/pubid/itu/identifiers/addendum.rb +15 -0
  315. data/lib/pubid/itu/identifiers/amendment.rb +6 -28
  316. data/lib/pubid/itu/identifiers/annex.rb +13 -2
  317. data/lib/pubid/itu/identifiers/annex_of_recommendation.rb +87 -0
  318. data/lib/pubid/itu/identifiers/appendix_of_recommendation.rb +92 -0
  319. data/lib/pubid/itu/identifiers/base.rb +341 -23
  320. data/lib/pubid/itu/identifiers/combined_identifier.rb +99 -23
  321. data/lib/pubid/itu/identifiers/corrigendum.rb +16 -23
  322. data/lib/pubid/itu/identifiers/errata.rb +15 -0
  323. data/lib/pubid/itu/identifiers/handbook.rb +38 -0
  324. data/lib/pubid/itu/identifiers/question.rb +63 -0
  325. data/lib/pubid/itu/identifiers/recommendation.rb +3 -2
  326. data/lib/pubid/itu/identifiers/report.rb +39 -0
  327. data/lib/pubid/itu/identifiers/special_publication.rb +3 -1
  328. data/lib/pubid/itu/identifiers/standard_serialization.rb +58 -0
  329. data/lib/pubid/itu/identifiers/supplement.rb +119 -14
  330. data/lib/pubid/itu/identifiers.rb +10 -1
  331. data/lib/pubid/itu/model.rb +1 -1
  332. data/lib/pubid/itu/parser.rb +480 -26
  333. data/lib/pubid/itu/urn_generator.rb +36 -6
  334. data/lib/pubid/itu/urn_parser.rb +8 -0
  335. data/lib/pubid/itu.rb +11 -3
  336. data/lib/pubid/jcgm/builder.rb +23 -13
  337. data/lib/pubid/jcgm/identifiers/amendment.rb +0 -2
  338. data/lib/pubid/jcgm/identifiers/corrigendum.rb +34 -0
  339. data/lib/pubid/jcgm/identifiers/gum_guide.rb +3 -3
  340. data/lib/pubid/jcgm/identifiers/meeting.rb +47 -0
  341. data/lib/pubid/jcgm/identifiers.rb +2 -0
  342. data/lib/pubid/jcgm/parser.rb +66 -6
  343. data/lib/pubid/jcgm/renderer.rb +26 -12
  344. data/lib/pubid/jcgm/single_identifier.rb +74 -5
  345. data/lib/pubid/jcgm/supplement_identifier.rb +39 -4
  346. data/lib/pubid/jcgm/urn_generator.rb +21 -13
  347. data/lib/pubid/jcgm/urn_parser.rb +15 -3
  348. data/lib/pubid/jis/identifier.rb +52 -0
  349. data/lib/pubid/nist/builder.rb +29 -9
  350. data/lib/pubid/nist/caster.rb +7 -45
  351. data/lib/pubid/nist/circular_supplement_builder.rb +7 -5
  352. data/lib/pubid/nist/components/supplement.rb +5 -2
  353. data/lib/pubid/nist/configuration.rb +7 -1
  354. data/lib/pubid/nist/identifier.rb +1 -3
  355. data/lib/pubid/nist/identifiers/base.rb +121 -37
  356. data/lib/pubid/nist/identifiers/circular.rb +1 -1
  357. data/lib/pubid/nist/identifiers/commercial_standard.rb +1 -1
  358. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
  359. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +1 -1
  360. data/lib/pubid/nist/identifiers/crpl_report.rb +1 -1
  361. data/lib/pubid/nist/identifiers/dated_document.rb +1 -1
  362. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +1 -1
  363. data/lib/pubid/nist/identifiers/grant_contractor_report.rb +1 -1
  364. data/lib/pubid/nist/identifiers/handbook.rb +1 -1
  365. data/lib/pubid/nist/identifiers/internal_report.rb +1 -1
  366. data/lib/pubid/nist/identifiers/letter_circular.rb +1 -1
  367. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +1 -1
  368. data/lib/pubid/nist/identifiers/monograph.rb +1 -1
  369. data/lib/pubid/nist/identifiers/ncstar.rb +2 -2
  370. data/lib/pubid/nist/identifiers/nsrds.rb +1 -1
  371. data/lib/pubid/nist/identifiers/owmwp.rb +1 -1
  372. data/lib/pubid/nist/identifiers/report.rb +1 -1
  373. data/lib/pubid/nist/identifiers/special_publication.rb +10 -1
  374. data/lib/pubid/nist/identifiers/technical_note.rb +1 -1
  375. data/lib/pubid/nist/identifiers.rb +0 -1
  376. data/lib/pubid/nist/parser.rb +29 -4
  377. data/lib/pubid/nist/preprocessor.rb +15 -0
  378. data/lib/pubid/nist/router.rb +3 -2
  379. data/lib/pubid/nist/series/ir.rb +0 -3
  380. data/lib/pubid/nist/supplement_identifier.rb +9 -9
  381. data/lib/pubid/nist.rb +13 -12
  382. data/lib/pubid/oasis/builder.rb +77 -0
  383. data/lib/pubid/oasis/identifier.rb +82 -0
  384. data/lib/pubid/oasis/identifiers/standard.rb +29 -0
  385. data/lib/pubid/oasis/identifiers.rb +9 -0
  386. data/lib/pubid/oasis/parser.rb +34 -0
  387. data/lib/pubid/oasis/renderer.rb +29 -0
  388. data/lib/pubid/oasis/urn_generator.rb +25 -0
  389. data/lib/pubid/oasis/urn_parser.rb +22 -0
  390. data/lib/pubid/oasis.rb +74 -0
  391. data/lib/pubid/ogc/builder.rb +33 -0
  392. data/lib/pubid/ogc/identifier.rb +77 -0
  393. data/lib/pubid/ogc/identifiers/document.rb +26 -0
  394. data/lib/pubid/ogc/identifiers.rb +10 -0
  395. data/lib/pubid/ogc/parser.rb +40 -0
  396. data/lib/pubid/ogc/renderer.rb +33 -0
  397. data/lib/pubid/ogc/urn_generator.rb +14 -0
  398. data/lib/pubid/ogc/urn_parser.rb +22 -0
  399. data/lib/pubid/ogc.rb +71 -0
  400. data/lib/pubid/oiml/builder.rb +31 -7
  401. data/lib/pubid/oiml/identifier.rb +9 -1
  402. data/lib/pubid/oiml/parser.rb +11 -12
  403. data/lib/pubid/oiml/renderer.rb +13 -13
  404. data/lib/pubid/oiml/single_identifier.rb +20 -0
  405. data/lib/pubid/oiml/supplement_identifier.rb +9 -9
  406. data/lib/pubid/oiml/urn_parser.rb +23 -6
  407. data/lib/pubid/oiml.rb +4 -0
  408. data/lib/pubid/omg/builder.rb +19 -0
  409. data/lib/pubid/omg/identifier.rb +44 -0
  410. data/lib/pubid/omg/identifiers/specification.rb +32 -0
  411. data/lib/pubid/omg/identifiers.rb +9 -0
  412. data/lib/pubid/omg/parser.rb +40 -0
  413. data/lib/pubid/omg/renderer.rb +19 -0
  414. data/lib/pubid/omg.rb +51 -0
  415. data/lib/pubid/parsers/mr_string.rb +101 -7
  416. data/lib/pubid/plateau/builder.rb +8 -4
  417. data/lib/pubid/plateau/identifier.rb +1 -3
  418. data/lib/pubid/plateau/identifiers/base.rb +1 -6
  419. data/lib/pubid/plateau/identifiers/handbook.rb +1 -1
  420. data/lib/pubid/plateau/identifiers/technical_report.rb +1 -1
  421. data/lib/pubid/plateau/identifiers.rb +0 -1
  422. data/lib/pubid/plateau/parser.rb +15 -5
  423. data/lib/pubid/plateau/renderer.rb +1 -1
  424. data/lib/pubid/plateau/supplement_identifier.rb +11 -11
  425. data/lib/pubid/plateau.rb +4 -4
  426. data/lib/pubid/renderers/human_readable.rb +3 -1
  427. data/lib/pubid/renderers/mr_string.rb +46 -4
  428. data/lib/pubid/renderers/supplement_renderer.rb +1 -1
  429. data/lib/pubid/rendering/supplement.rb +4 -4
  430. data/lib/pubid/sae/builder.rb +1 -1
  431. data/lib/pubid/sae/identifier.rb +1 -3
  432. data/lib/pubid/sae/identifiers/base.rb +10 -7
  433. data/lib/pubid/sae/identifiers.rb +0 -1
  434. data/lib/pubid/sae.rb +3 -3
  435. data/lib/pubid/tgpp/builder.rb +41 -0
  436. data/lib/pubid/tgpp/identifier.rb +91 -0
  437. data/lib/pubid/tgpp/identifiers/technical_report.rb +32 -0
  438. data/lib/pubid/tgpp/identifiers/technical_specification.rb +33 -0
  439. data/lib/pubid/tgpp/identifiers.rb +11 -0
  440. data/lib/pubid/tgpp/parser.rb +62 -0
  441. data/lib/pubid/tgpp/renderer.rb +36 -0
  442. data/lib/pubid/tgpp/urn_generator.rb +20 -0
  443. data/lib/pubid/tgpp/urn_parser.rb +28 -0
  444. data/lib/pubid/tgpp.rb +78 -0
  445. data/lib/pubid/type_resolver.rb +59 -0
  446. data/lib/pubid/un/builder.rb +42 -0
  447. data/lib/pubid/un/identifier.rb +37 -0
  448. data/lib/pubid/un/identifiers/document.rb +24 -0
  449. data/lib/pubid/un/identifiers.rb +9 -0
  450. data/lib/pubid/un/parser.rb +25 -0
  451. data/lib/pubid/un/renderer.rb +11 -0
  452. data/lib/pubid/un.rb +46 -0
  453. data/lib/pubid/version.rb +1 -1
  454. data/lib/pubid/w3c/builder.rb +55 -0
  455. data/lib/pubid/w3c/identifier.rb +101 -0
  456. data/lib/pubid/w3c/identifiers/candidate_recommendation.rb +31 -0
  457. data/lib/pubid/w3c/identifiers/candidate_recommendation_draft.rb +31 -0
  458. data/lib/pubid/w3c/identifiers/draft_note.rb +31 -0
  459. data/lib/pubid/w3c/identifiers/note.rb +30 -0
  460. data/lib/pubid/w3c/identifiers/obsolete_recommendation.rb +31 -0
  461. data/lib/pubid/w3c/identifiers/proposed_edited_recommendation.rb +31 -0
  462. data/lib/pubid/w3c/identifiers/proposed_recommendation.rb +31 -0
  463. data/lib/pubid/w3c/identifiers/recommendation.rb +31 -0
  464. data/lib/pubid/w3c/identifiers/standard.rb +12 -0
  465. data/lib/pubid/w3c/identifiers/superseded_recommendation.rb +31 -0
  466. data/lib/pubid/w3c/identifiers/working_draft.rb +30 -0
  467. data/lib/pubid/w3c/identifiers.rb +25 -0
  468. data/lib/pubid/w3c/parser.rb +43 -0
  469. data/lib/pubid/w3c/renderer.rb +35 -0
  470. data/lib/pubid/w3c/urn_generator.rb +18 -0
  471. data/lib/pubid/w3c/urn_parser.rb +36 -0
  472. data/lib/pubid/w3c.rb +67 -0
  473. data/lib/pubid/xsf/builder.rb +17 -0
  474. data/lib/pubid/xsf/identifier.rb +62 -0
  475. data/lib/pubid/xsf/identifiers/xep.rb +31 -0
  476. data/lib/pubid/xsf/identifiers.rb +9 -0
  477. data/lib/pubid/xsf/parser.rb +25 -0
  478. data/lib/pubid/xsf/renderer.rb +23 -0
  479. data/lib/pubid/xsf/urn_generator.rb +13 -0
  480. data/lib/pubid/xsf/urn_parser.rb +18 -0
  481. data/lib/pubid/xsf.rb +76 -0
  482. data/lib/pubid.rb +25 -1
  483. metadata +205 -4
  484. data/lib/pubid/cie/components/code.rb +0 -80
  485. data/lib/pubid/iala/identifiers/base.rb +0 -14
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Itu
5
+ module Identifiers
6
+ # A labelled appendix of a Recommendation — "ITU-T G.101 App. I
7
+ # (05/2000)", "ITU-T G.722 (1988) App. IV (11/2006)".
8
+ #
9
+ # Structurally the twin of AnnexOfRecommendation: an appendix is
10
+ # published and dated separately from the Recommendation it appends, and
11
+ # may itself carry an amendment/errata ("ITU-T G.729 App. I (2002)
12
+ # Amd. 1"). The label is a Roman numeral rather than a series letter.
13
+ class AppendixOfRecommendation < Identifier
14
+ attribute :base, Identifier, polymorphic: true
15
+ # The appendix label ("I", "II", "IV"). Named `number` for symmetry
16
+ # with Supplement's ordinal and AnnexOfRecommendation's label; it
17
+ # shadows Identifier#number (code&.number), which is nil here anyway —
18
+ # the document number is reached via #root.
19
+ attribute :number, :string
20
+ # The companion artefact published under the appendix's designation:
21
+ # "test vectors" or "Software". Its own catalogue record, so it must
22
+ # stay distinct from the appendix itself.
23
+ attribute :material, :string
24
+
25
+ # Compact serialization: the nested (itself-flat) base plus this
26
+ # document's own label, date and language. sector/series/code belong to
27
+ # the base and are deliberately not re-emitted.
28
+ key_value do
29
+ map "_type", to: :_type
30
+ map "number", to: :number
31
+ map "material", to: :material
32
+ map "year", with: { to: :year_to_kv, from: :year_from_kv }
33
+ map "month", with: { to: :month_to_kv, from: :month_from_kv }
34
+ map "base", with: { to: :base_to_kv, from: :base_from_kv }
35
+ map "language", to: :language
36
+ map "common_text_twin",
37
+ with: { to: :common_text_twin_to_kv,
38
+ from: :common_text_twin_from_kv }
39
+ end
40
+
41
+ # "<base urn>:appendix:<label>[:<date>]" — the appendix's own date must
42
+ # be there, or two editions of one appendix share a URN (the base's
43
+ # date rides inside base_urn, so omitting this one would be asymmetric).
44
+ def to_urn
45
+ segments = [base&.to_urn || "urn:itu", "appendix", number&.downcase]
46
+ # Without this the test-vector record and the appendix itself would
47
+ # share a URN.
48
+ segments << material.downcase.gsub(/[^a-z0-9]/, "") if material
49
+ segments << urn_date_segment
50
+ segments.compact.join(":")
51
+ end
52
+
53
+ def render_base(**opts)
54
+ result = base ? base.render_base(**opts) : "#{publisher}-#{sector}"
55
+
56
+ result += " App. #{number}"
57
+ result += " #{material}" if material
58
+ result + render_date_suffix
59
+ end
60
+
61
+ # MR: recurse into the appendixed document and append
62
+ # "app.<label>[.year]" (the documented supplement-suffix hook), so the
63
+ # slug stays distinct per appendix. Without it the wrapper's own nil
64
+ # sector/series/code would collapse every appendix onto "itu.<year>".
65
+ def mr_supplement_suffix
66
+ label = "#{number}#{material}".downcase.gsub(/[^a-z0-9]/, "")
67
+
68
+ ["app", label.empty? ? nil : label, date&.year].compact.join(".")
69
+ end
70
+
71
+ def ==(other)
72
+ return false unless other.instance_of?(self.class)
73
+
74
+ base == other.base &&
75
+ number == other.number &&
76
+ material == other.material &&
77
+ date == other.date &&
78
+ language == other.language &&
79
+ common_text_twin == other.common_text_twin
80
+ end
81
+
82
+ private
83
+
84
+ def urn_date_segment
85
+ return nil unless date
86
+
87
+ date.month ? "#{date.month}/#{date.year}" : date.year.to_s
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -2,17 +2,30 @@
2
2
 
3
3
  module Pubid
4
4
  module Itu
5
- # Base class for all ITU identifiers. Canonical name Pubid::Itu::Identifier
6
- # (Identifiers::Base is a back-compat alias).
5
+ # Base class for all ITU identifiers. Canonical name Pubid::Itu::Identifier.
7
6
  class Identifier < ::Pubid::Identifier
8
7
  # Parse an ITU identifier string into an identifier object.
9
8
  def self.parse(identifier)
10
- parsed = Parser.parse(identifier)
9
+ parsed = Parser.parse(normalize_whitespace(identifier))
11
10
  Builder.build(parsed)
12
11
  rescue Parslet::ParseFailed => e
13
12
  raise "Failed to parse ITU identifier '#{identifier}': #{e.message}"
14
13
  end
15
14
 
15
+ # ITU's own listings occasionally carry a doubled space
16
+ # ("ITU-T D.271 (10/2016)"). Whitespace is never significant in an ITU
17
+ # identifier, so collapse runs of it rather than teaching every rule to
18
+ # tolerate them. Any string over the shared input-length guard is left
19
+ # untouched — `Pubid.parse` rejects it before this point, and the guard
20
+ # exists precisely to keep long inputs away from regexes.
21
+ def self.normalize_whitespace(identifier)
22
+ return identifier unless identifier.is_a?(String)
23
+ return identifier if identifier.length > ::Pubid::MAX_INPUT_LENGTH
24
+
25
+ identifier.gsub(/[[:space:]]+/, " ").strip
26
+ end
27
+ private_class_method :normalize_whitespace
28
+
16
29
  # Long-form ↔ ITU single-letter language code map. The parser produces
17
30
  # single-letter codes (E/F/S/R/A/C); API callers (e.g. metanorma-itu)
18
31
  # pass long-form (en/fr/es/ru/ar/zh). Storage is normalized to the
@@ -30,6 +43,36 @@ module Pubid
30
43
  attribute :code, Pubid::Itu::Components::Code
31
44
  attribute :date, Pubid::Components::Date
32
45
  attribute :language, :string
46
+ # "(V14)" of "ITU-T H.264 (V14) (08/2021)" — an ITU-T revision marker
47
+ # distinct from the publication date. Declared here (not on the leaves)
48
+ # because `render_base` lives here and the parser feeds it to both plain
49
+ # and combined recommendations, and to the nested base of a supplement.
50
+ # Safe from the number/stage redefinition landmine: ::Pubid::Identifier
51
+ # declares no `version` attribute, so this is a new one, not a retype.
52
+ attribute :version, :string
53
+ # The literal word "series" of "ITU-T E-100 series Suppl. 1" and
54
+ # "ITU-T E.1100 series Suppl. 1" — the document is the series group, not
55
+ # one Recommendation within it. It cannot live inside `series` itself
56
+ # because it also follows a *dotted* code, where folding it into the
57
+ # series token would corrupt the value every index row is keyed on.
58
+ # Named so the common case is the `false` default and stays out of
59
+ # `to_hash`.
60
+ attribute :series_word, :boolean, default: -> { false }
61
+ # A series-code document joins its mnemonic series to its number with a
62
+ # dash rather than a dot ("ITU-T EMC-5", "ITU-T SEC-QKD"). Keeping the
63
+ # two halves split — rather than storing "EMC-5" in `series` — is what
64
+ # keeps `root.number` non-empty for relaton-index. Named for the rare
65
+ # form, so the dotted majority stays out of `to_hash`.
66
+ attribute :series_dash, :boolean, default: -> { false }
67
+ # "ITU-T H.350 attachment (08/2003)" — the machine-readable attachment
68
+ # of a Recommendation, catalogued as its own record.
69
+ attribute :attachment, :boolean, default: -> { false }
70
+ # The upper bound of a Recommendation range published as one document —
71
+ # "ITU-T Q.120-Q.139 (11/1988)", where this holds "Q.139". A plain string
72
+ # rather than a structured designation: five legacy records, and the
73
+ # printed form is the whole of their identity.
74
+ attribute :range_end, :string
75
+ attribute :common_text_twin, ::Pubid::Identifier
33
76
 
34
77
  def initialize(**kwargs)
35
78
  if kwargs[:language]
@@ -41,10 +84,91 @@ module Pubid
41
84
  validate_ob_no_sector!
42
85
  end
43
86
 
87
+ # The document number lives on the `code` component for ITU; surface it at
88
+ # the identifier root so consumers that key on `#number` — e.g.
89
+ # Relaton::Index's sort/bsearch narrowing on `id.root.number.to_s` — work
90
+ # without special-casing ITU. Returns the `code.number` string (e.g.
91
+ # "530"); serialization is unaffected because the flat key_value block
92
+ # maps "number" via `number_to_kv`/`number_from_kv`, which read/write
93
+ # `code.number` directly and never touch the inherited `number` attribute.
94
+ # Supplement/Amendment/Corrigendum/Errata declare their own `:string`
95
+ # `number` (the ordinal), which overrides this reader; their base document
96
+ # number is still reachable via `root.number`.
97
+ def number
98
+ code&.number
99
+ end
100
+
101
+ # ITU keeps the document number and its edition/part together inside one
102
+ # Code component (like ETSI, unlike ISO's separate `part` attribute), so a
103
+ # top-level exclude(:part) can't reach the part. Handle it during the
104
+ # nested-exclusion pass: when :part/:parts is excluded, return a copy of
105
+ # the Code with its parts cleared while keeping number/series/subseries.
106
+ # This lets a part-less reference (e.g. `ITU-R P.838`) match every edition
107
+ # (`ITU-R P.838-3`) via matches?(other, ignore: [:parts]). Any non-Code
108
+ # value falls through to super, so nested identifiers (common_text_twin,
109
+ # supplement `base`, combined `Designation`s) still recurse.
110
+ def exclude_from_nested(value, args)
111
+ part_keys = args & %i[part parts]
112
+ if value.is_a?(Pubid::Itu::Components::Code) && !part_keys.empty?
113
+ return Pubid::Itu::Components::Code.new(
114
+ imp_marker: value.imp_marker,
115
+ number: value.number,
116
+ series_suffix: value.series_suffix,
117
+ series_suffix_spaced: value.series_suffix_spaced,
118
+ subseries: value.subseries,
119
+ parts: [],
120
+ qualifier: value.qualifier,
121
+ qualifier_glued: value.qualifier_glued,
122
+ )
123
+ end
124
+
125
+ super
126
+ end
127
+
44
128
  # Generate URN for this identifier
45
129
  #
46
130
  # @return [String] URN representation
47
131
 
132
+ # ITU encodes identity across `sector` (T/R/CD), `series` (G/H/J…), and
133
+ # `code` (number with optional part), not in the inherited `number`/
134
+ # `typed_stage`. The generic MrString renderer would otherwise drop all
135
+ # three and emit just `ITU`. Losslessness for issue #142 requires the
136
+ # sector letter, series letter, and document number to appear in MR.
137
+ # Lowercased to match the all-lowercase MR convention.
138
+ def mr_publisher
139
+ publisher&.to_s&.downcase
140
+ end
141
+
142
+ def mr_type
143
+ sector&.sector&.to_s&.downcase
144
+ end
145
+
146
+ def mr_number_with_part
147
+ segments = []
148
+ segments << series&.series&.to_s&.downcase if series&.series
149
+ # The edition word and the qualifier letter are part of the document's
150
+ # identity, so they must reach the slug: without them "D.200" and
151
+ # "D.200 R" — and, worse, "Q.2931 B" and "Q.2931 C" — collapse onto
152
+ # one MR string. They are glued to the number rather than given their
153
+ # own "-" segment so they read as part of it ("x-50bis", "d-200r").
154
+ number = code&.number&.to_s
155
+ if number
156
+ number += code.series_suffix.to_s if code.series_suffix
157
+ number += code.qualifier.to_s.downcase if code.qualifier
158
+ segments << number
159
+ end
160
+ segments << code&.subseries&.to_s if code&.subseries
161
+ segments.concat(code&.parts&.map(&:to_s) || [])
162
+ # Same reasoning as the suffixes above, and as the URN generator: these
163
+ # three distinguish documents that otherwise slug identically.
164
+ segments << "series" if series_word
165
+ segments << "attachment" if attachment
166
+ segments << "to-#{range_end.to_s.downcase.tr('.', '-')}" if range_end
167
+ return nil if segments.empty?
168
+
169
+ segments.join("-")
170
+ end
171
+
48
172
  # Stored as a plain string (always "ITU") so it round-trips through
49
173
  # to_hash/from_hash. Was a `def publisher` method, which made lutaml
50
174
  # serialize a String against the Components::Publisher attribute.
@@ -61,29 +185,43 @@ module Pubid
61
185
  # identifiers, otherwise the default short form.
62
186
  def to_s(**opts)
63
187
  opts = self.class.normalize_to_s_opts(opts)
64
- render_base(**opts) + render_language_suffix
188
+ result = render_base(**opts) + render_language_suffix
189
+ result += " | #{common_text_twin}" if common_text_twin
190
+ result
65
191
  end
66
192
 
67
193
  def render_base(**_opts)
68
194
  result = "#{publisher}-#{sector}"
69
195
 
70
- # Add series and code
71
- result += if series
72
- " #{series}.#{code}"
196
+ # Add series and code. A series with no code is a series GROUP
197
+ # ("ITU-T G-100 series"); without this branch it rendered a dangling
198
+ # trailing dot.
199
+ result += if series && code
200
+ " #{series}#{series_dash ? '-' : '.'}#{code}"
201
+ elsif series
202
+ " #{series}"
73
203
  else
74
204
  " #{code}"
75
205
  end
76
206
 
77
- # Add date if present
78
- if date
79
- result += if date.month
80
- " (#{date.month}/#{date.year})"
81
- else
82
- " (#{date.year})"
83
- end
84
- end
207
+ result += "-#{range_end}" if range_end
208
+ result += " series" if series_word
209
+ result += " attachment" if attachment
85
210
 
86
- result
211
+ # Add version marker if present — always between code and date
212
+ result += " (V#{version})" if version
213
+
214
+ result + render_date_suffix
215
+ end
216
+
217
+ # " (MM/YYYY)", or " (YYYY)" when the month is unknown; "" when undated.
218
+ # Shared by every type that prints its date this way (Supplement and its
219
+ # subclasses, CombinedIdentifier, AnnexOfRecommendation). NOT used by
220
+ # SpecialPublication, which zero-pads its month.
221
+ def render_date_suffix
222
+ return "" unless date
223
+
224
+ date.month ? " (#{date.month}/#{date.year})" : " (#{date.year})"
87
225
  end
88
226
 
89
227
  def render_language_suffix
@@ -109,14 +247,199 @@ module Pubid
109
247
  opts
110
248
  end
111
249
 
250
+ # Class-strict, like Supplement#== — the ITU *type* is part of the
251
+ # identity, not just the sector/series/number. Report ITU-R BT.2020-1 and
252
+ # Recommendation ITU-R BT.2020-1 are two different, both-current
253
+ # documents; an `is_a?` guard would make them equal (and `#matches?`
254
+ # resolve one to the other). Using `self.class` rather than a hard-coded
255
+ # class keeps the comparison symmetric in both directions, and closes the
256
+ # same one-way holes that existed against CombinedIdentifier and
257
+ # SpecialPublication.
112
258
  def ==(other)
113
- return false unless other.is_a?(Pubid::Itu::Identifier)
259
+ return false unless other.instance_of?(self.class)
114
260
 
115
261
  sector == other.sector &&
116
262
  series == other.series &&
117
263
  code == other.code &&
118
264
  date == other.date &&
119
- language == other.language
265
+ version == other.version &&
266
+ # "the E.1100 series" is not Recommendation E.1100.
267
+ series_word == other.series_word &&
268
+ series_dash == other.series_dash &&
269
+ attachment == other.attachment &&
270
+ range_end == other.range_end &&
271
+ language == other.language &&
272
+ common_text_twin == other.common_text_twin
273
+ end
274
+
275
+ # --- Compact flat key_value converters (shared) --------------------
276
+ # Collapse the identity components (Sector/Series/Code/Date) to bare
277
+ # top-level scalars so the serialized hash is flat and index-friendly,
278
+ # mirroring ISO/ETSI/JCGM/OIML. Used by the per-leaf key_value blocks
279
+ # (see StandardSerialization) and by the Supplement/Annex/Combined
280
+ # blocks. `publisher` ("ITU") is intentionally never mapped — it is
281
+ # reconstructed from its attribute default.
282
+
283
+ def sector_to_kv(model, doc)
284
+ emit_kv(doc, "sector", model.sector&.sector)
285
+ end
286
+
287
+ def sector_from_kv(model, value)
288
+ model.sector = Components::Sector.new(sector: value.to_s)
289
+ end
290
+
291
+ def series_to_kv(model, doc)
292
+ emit_kv(doc, "series", model.series&.series)
293
+ end
294
+
295
+ def series_from_kv(model, value)
296
+ model.series = Components::Series.new(series: value.to_s)
297
+ end
298
+
299
+ def imp_marker_to_kv(model, doc)
300
+ emit_kv(doc, "imp_marker", model.code&.imp_marker)
301
+ end
302
+
303
+ def imp_marker_from_kv(model, value)
304
+ code_for(model).imp_marker = value.to_s
305
+ end
306
+
307
+ def number_to_kv(model, doc)
308
+ emit_kv(doc, "number", model.code&.number)
309
+ end
310
+
311
+ def number_from_kv(model, value)
312
+ code_for(model).number = value.to_s
313
+ end
314
+
315
+ def series_suffix_to_kv(model, doc)
316
+ emit_kv(doc, "series_suffix", model.code&.series_suffix)
317
+ end
318
+
319
+ def series_suffix_from_kv(model, value)
320
+ code_for(model).series_suffix = value.to_s
321
+ end
322
+
323
+ # The three spelling/qualifier fields of Components::Code. The two
324
+ # booleans emit only when true, so no already-published index row gains
325
+ # a key; `qualifier` emits only when present.
326
+ def series_suffix_spaced_to_kv(model, doc)
327
+ return unless model.code&.series_suffix_spaced
328
+
329
+ doc["series_suffix_spaced"] = true
330
+ end
331
+
332
+ def series_suffix_spaced_from_kv(model, value)
333
+ code_for(model).series_suffix_spaced = value
334
+ end
335
+
336
+ def qualifier_to_kv(model, doc)
337
+ emit_kv(doc, "qualifier", model.code&.qualifier)
338
+ end
339
+
340
+ def qualifier_from_kv(model, value)
341
+ code_for(model).qualifier = value.to_s
342
+ end
343
+
344
+ def qualifier_glued_to_kv(model, doc)
345
+ return unless model.code&.qualifier_glued
346
+
347
+ doc["qualifier_glued"] = true
348
+ end
349
+
350
+ def qualifier_glued_from_kv(model, value)
351
+ code_for(model).qualifier_glued = value
352
+ end
353
+
354
+ def subseries_to_kv(model, doc)
355
+ emit_kv(doc, "subseries", model.code&.subseries)
356
+ end
357
+
358
+ def subseries_from_kv(model, value)
359
+ code_for(model).subseries = value.to_s
360
+ end
361
+
362
+ def parts_to_kv(model, doc)
363
+ parts = model.code&.parts
364
+ return if parts.nil? || parts.empty?
365
+
366
+ doc.add_child(
367
+ Lutaml::KeyValue::DataModel::Element.new("parts", parts.map(&:to_s)),
368
+ )
369
+ end
370
+
371
+ def parts_from_kv(model, value)
372
+ code_for(model).parts = Array(value).map(&:to_s)
373
+ end
374
+
375
+ def year_to_kv(model, doc)
376
+ emit_kv(doc, "year", model.date&.year)
377
+ end
378
+
379
+ def year_from_kv(model, value)
380
+ date_for(model).year = value.to_s
381
+ end
382
+
383
+ def month_to_kv(model, doc)
384
+ emit_kv(doc, "month", model.date&.month)
385
+ end
386
+
387
+ def month_from_kv(model, value)
388
+ date_for(model).month = value.to_s
389
+ end
390
+
391
+ # Serialize a nested `base` identifier via its own to_hash so it collapses
392
+ # to the compact shape, and re-dispatch through the flavor base on load so
393
+ # `_type` resolves to the concrete subclass (a bare polymorphic cast would
394
+ # rebuild a plain Identifier and lose the subclass). Mirrors ETSI/JCGM.
395
+ def base_to_kv(model, doc)
396
+ return unless model.base
397
+
398
+ doc.add_child(
399
+ Lutaml::KeyValue::DataModel::Element.new("base", model.base.to_hash),
400
+ )
401
+ end
402
+
403
+ def base_from_kv(model, value)
404
+ return unless value
405
+
406
+ model.base = Pubid::Itu::Identifier.from_hash(value)
407
+ end
408
+
409
+ # The common-text twin ("| ISO/IEC 13818-1:2022") is another flavor's
410
+ # identifier (ISO/IEC …); serialize it via its own flat to_hash and
411
+ # reconstruct it cross-flavor through the top-level polymorphic router so
412
+ # its concrete class (and custom mappings) are restored by `_type`.
413
+ def common_text_twin_to_kv(model, doc)
414
+ return unless model.common_text_twin
415
+
416
+ doc.add_child(
417
+ Lutaml::KeyValue::DataModel::Element.new(
418
+ "common_text_twin", model.common_text_twin.to_hash
419
+ ),
420
+ )
421
+ end
422
+
423
+ def common_text_twin_from_kv(model, value)
424
+ return unless value
425
+
426
+ model.common_text_twin = ::Pubid::Identifier.from_hash(value)
427
+ end
428
+
429
+ def emit_kv(doc, key, value)
430
+ return if value.nil? || value.to_s.empty?
431
+
432
+ doc.add_child(
433
+ Lutaml::KeyValue::DataModel::Element.new(key, value.to_s),
434
+ )
435
+ end
436
+
437
+ def code_for(model)
438
+ model.code ||= Components::Code.new
439
+ end
440
+
441
+ def date_for(model)
442
+ model.date ||= Pubid::Components::Date.new
120
443
  end
121
444
 
122
445
  private
@@ -141,10 +464,5 @@ module Pubid
141
464
  end
142
465
  end
143
466
 
144
- module Identifiers
145
- # Backward-compatible alias: ITU's base class used to be
146
- # Pubid::Itu::Identifiers::Base. It is now Pubid::Itu::Identifier.
147
- Base = Pubid::Itu::Identifier
148
- end
149
467
  end
150
468
  end
@@ -3,41 +3,117 @@
3
3
  module Pubid
4
4
  module Itu
5
5
  module Identifiers
6
- # Combined identifier for dual-series recommendations
7
- # Format: ITU-T G.780/Y.1351
8
- # Example: ITU-T G.780/Y.1351 (2004)
9
- class CombinedIdentifier < Base
10
- attribute :combined_series, Pubid::Itu::Components::Series
11
- attribute :combined_code, Pubid::Itu::Components::Code
12
-
13
- def to_s
6
+ # Combined (joint) recommendation — a single document published under two
7
+ # or more series/number designations at once.
8
+ # Format: ITU-T G.780/Y.1351 (dual)
9
+ # ITU-T G.780/Y.1351/Z.1362 (triple)
10
+ #
11
+ # The primary designation lives on the base `series`/`code`; every
12
+ # additional designation is a Components::Designation in `combined`
13
+ # (one for a dual form, two+ for triple).
14
+ class CombinedIdentifier < Identifier
15
+ include StandardSerialization
16
+
17
+ attribute :combined, Pubid::Itu::Components::Designation,
18
+ collection: true, default: -> { [] }
19
+
20
+ # Extra map merged on top of StandardSerialization: the additional
21
+ # designations as a flat list of { series, number, subseries?, parts? }.
22
+ key_value do
23
+ map "combined",
24
+ with: { to: :combined_to_kv, from: :combined_from_kv }
25
+ end
26
+
27
+ def combined_to_kv(model, doc)
28
+ designations = model.combined
29
+ return if designations.nil? || designations.empty?
30
+
31
+ rows = designations.map do |d|
32
+ row = { "series" => d.series&.series.to_s,
33
+ "number" => d.code&.number.to_s }
34
+ row["subseries"] = d.code.subseries.to_s if d.code&.subseries
35
+ row["parts"] = d.code.parts.map(&:to_s) if d.code&.parts&.any?
36
+ # Each emitted only when set, so an ordinary joint designation's
37
+ # row keeps its existing two-key shape.
38
+ if d.code&.series_suffix
39
+ row["series_suffix"] = d.code.series_suffix.to_s
40
+ row["series_suffix_spaced"] = true if d.code.series_suffix_spaced
41
+ end
42
+ if d.code&.qualifier
43
+ row["qualifier"] = d.code.qualifier.to_s
44
+ row["qualifier_glued"] = true if d.code.qualifier_glued
45
+ end
46
+ row
47
+ end
48
+
49
+ doc.add_child(
50
+ Lutaml::KeyValue::DataModel::Element.new("combined", rows),
51
+ )
52
+ end
53
+
54
+ def combined_from_kv(model, value)
55
+ model.combined = Array(value).map do |row|
56
+ row = row.transform_keys(&:to_s)
57
+ Components::Designation.new(
58
+ series: Components::Series.new(series: row["series"].to_s),
59
+ code: Components::Code.new(
60
+ number: row["number"].to_s,
61
+ series_suffix: row["series_suffix"]&.to_s,
62
+ series_suffix_spaced: !!row["series_suffix_spaced"],
63
+ subseries: row["subseries"]&.to_s,
64
+ parts: Array(row["parts"]).map(&:to_s),
65
+ qualifier: row["qualifier"]&.to_s,
66
+ qualifier_glued: !!row["qualifier_glued"],
67
+ ),
68
+ )
69
+ end
70
+ end
71
+
72
+ # Rendering hooks into `render_base` (not `to_s`) so that any wrapper
73
+ # composing this identifier — AnnexOfRecommendation, which renders
74
+ # "<base.render_base> Annex <label>" — keeps the co-designations.
75
+ # Overriding `to_s` instead silently dropped the "/Y.1351" half.
76
+ # The inherited `to_s` adds the language suffix and common-text twin.
77
+ def render_base(**_opts)
14
78
  result = "#{publisher}-#{sector}"
15
79
 
16
80
  # Add primary series and code
17
81
  result += if series
18
- " #{series}.#{code}"
82
+ " #{series}#{series_dash ? '-' : '.'}#{code}"
19
83
  else
20
84
  " #{code}"
21
85
  end
22
86
 
23
- # Add combined series and code
24
- if combined_series && combined_code
25
- result += "/#{combined_series}.#{combined_code}"
26
- end
87
+ result += "-#{range_end}" if range_end
27
88
 
28
- # Add date if present
29
- if date
30
- result += if date.month
31
- " (#{date.month}/#{date.year})"
32
- else
33
- " (#{date.year})"
34
- end
89
+ # Add additional designations
90
+ if combined&.any?
91
+ result += "/#{combined.join('/')}"
35
92
  end
36
93
 
37
- # Add language
38
- result += "-#{language}" if language
94
+ result += " series" if series_word
95
+ result += " attachment" if attachment
96
+
97
+ # Add version marker if present — always between code and date
98
+ result += " (V#{version})" if version
99
+
100
+ result + render_date_suffix
101
+ end
102
+
103
+ def ==(other)
104
+ return false unless other.is_a?(CombinedIdentifier)
39
105
 
40
- result
106
+ sector == other.sector &&
107
+ series == other.series &&
108
+ code == other.code &&
109
+ combined == other.combined &&
110
+ date == other.date &&
111
+ version == other.version &&
112
+ series_word == other.series_word &&
113
+ series_dash == other.series_dash &&
114
+ attachment == other.attachment &&
115
+ range_end == other.range_end &&
116
+ language == other.language
41
117
  end
42
118
  end
43
119
  end