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
@@ -5,16 +5,23 @@ require "lutaml/model"
5
5
  module Pubid
6
6
  module Csa
7
7
  module Identifiers
8
- # CombinedIdentifier represents CSA identifiers combined with "/" separator
8
+ # Combined represents CSA identifiers joined with "/" or ", "
9
9
  # Examples:
10
10
  # CSA A23.1:24/CSA A23.2:24
11
11
  # CSA N285.0:23/CSA N285.6 SERIES:23
12
12
  # CSA A123.1-05/A123.5-05 (R2015)
13
13
  # CSA B44:19/B44.1:19/B44.2:19 (triple combined)
14
- class Combined < Lutaml::Model::Serializable
15
- attribute :first, Standard
16
- attribute :second, Standard
17
- attribute :third, Standard
14
+ class Combined < Identifier
15
+ # The co-equal designations, in printed order.
16
+ #
17
+ # One polymorphic collection, the IEC ConsolidatedIdentifier shape —
18
+ # not the historical first/second/third triple, which hard-capped the
19
+ # form at three and froze a non-standard hash shape. The first member
20
+ # is the primary designation, which is what #root walks so the index
21
+ # key is non-empty. Typed cross-flavor for the same reason as
22
+ # WrapperIdentifier#base.
23
+ attribute :identifiers, ::Pubid::Identifier, polymorphic: true,
24
+ collection: true
18
25
  attribute :reaffirmation, :string
19
26
  attribute :original_reaffirmation_4digit, :boolean, default: -> {
20
27
  false
@@ -23,67 +30,74 @@ module Pubid
23
30
  attribute :year_format, :string # Dummy for compatibility
24
31
  attribute :separator, :string, default: -> { "/" } # "/" or ", "
25
32
 
26
- # CSA combined ids are not Pubid::Identifier objects, so they do not
27
- # inherit #root. They are their own origin for matching purposes.
33
+ # A combined id stores its members in `identifiers`, not `base`, so
34
+ # walk the primary designation to the origin document
35
+ # (Iec::Identifiers::ConsolidatedIdentifier does the same).
28
36
  def root
29
- self
37
+ identifiers&.first&.root || self
30
38
  end
31
39
 
32
- def to_s
33
- # For comma separator, render both parts with full prefix
34
- # For slash separator, second/third parts without prefix
35
- if separator == ", "
36
- # Comma: Both with full CSA prefix
37
- parts = [first.to_s, second.to_s]
38
- parts << third.to_s if third
39
- else
40
- # Slash: Second and third without CSA prefix (continuation)
41
- parts = [first.to_s]
42
- parts << render_continuation(second)
43
- parts << render_continuation(third) if third
44
- end
45
-
46
- result = parts.join(separator || "/")
40
+ # Co-equal designations, so both matching primitives reduce to the
41
+ # primary one — the Iec::Identifiers::ConsolidatedIdentifier shape.
42
+ def base_document
43
+ identifiers&.first&.base_document || self
44
+ end
47
45
 
48
- # Reaffirmation - preserve original format and determine spacing
49
- if reaffirmation
50
- # For combined identifiers, check year format from first identifier
51
- # to determine spacing
52
- year_was_2digit = first.class.attributes.key?(:original_year_4digit) && !first.original_year_4digit
53
-
54
- # Check if reaffirmation was originally 4-digit
55
- # Note: We need to track this at the Combined level, but for now
56
- # assume 4-digit if the value is 4 digits and starts with 19/20
57
- reaffirmation_was_4digit = reaffirmation.to_s.length == 4 &&
58
- reaffirmation.to_s.start_with?("19", "20")
59
-
60
- # Determine spacing based on original formats
61
- # Space needed if year is 2-digit and reaffirmation is 4-digit
62
- result += if year_was_2digit && reaffirmation_was_4digit
63
- " (R#{reaffirmation})"
64
- else
65
- "(R#{reaffirmation})"
66
- end
67
- end
46
+ def drop_supplements
47
+ identifiers&.first || self
48
+ end
68
49
 
50
+ def to_s(**opts)
51
+ parts = render_parts
52
+ result = parts.join(separator || "/")
53
+ result += render_reaffirmation if reaffirmation
69
54
  result += package if package
70
- result
55
+ annotate_plain_render(result, **opts)
71
56
  end
72
57
 
73
58
  private
74
59
 
60
+ # With a comma separator every designation carries its full prefix;
61
+ # with a slash the trailing ones are continuations printed bare.
62
+ def render_parts
63
+ return identifiers.map(&:to_s) if separator == ", "
64
+
65
+ identifiers.each_with_index.map do |identifier, index|
66
+ index.zero? ? identifier.to_s : render_continuation(identifier)
67
+ end
68
+ end
69
+
70
+ # Reaffirmation - preserve original format and determine spacing.
71
+ # A space is needed when the year was printed 2-digit and the
72
+ # reaffirmation 4-digit; the year format comes from the primary
73
+ # designation.
74
+ def render_reaffirmation
75
+ primary = identifiers&.first
76
+ year_was_2digit =
77
+ primary &&
78
+ primary.class.attributes.key?(:original_year_4digit) &&
79
+ !primary.original_year_4digit
80
+
81
+ reaffirmation_was_4digit = reaffirmation.to_s.length == 4 &&
82
+ reaffirmation.to_s.start_with?("19", "20")
83
+
84
+ if year_was_2digit && reaffirmation_was_4digit
85
+ " (R#{reaffirmation})"
86
+ else
87
+ "(R#{reaffirmation})"
88
+ end
89
+ end
90
+
75
91
  # Render identifier without CSA prefix (unless has_publisher is true)
76
92
  def render_continuation(identifier)
77
93
  parts = []
78
94
 
79
95
  # Add CSA prefix if present in original
80
96
  if identifier.has_publisher
81
- prefix = identifier.publisher_prefix || "CSA"
82
- !prefix.end_with?("-")
83
- parts << prefix
97
+ parts << (identifier.publisher_prefix || "CSA")
84
98
  end
85
99
 
86
- code_part = identifier.code.to_s if identifier.code
100
+ code_part = identifier.number.to_s if identifier.number
87
101
 
88
102
  # NO. keyword
89
103
  if identifier.no_number
@@ -98,34 +112,42 @@ module Pubid
98
112
  code_part += " SERIES"
99
113
  end
100
114
 
101
- # Year with proper format (colon or dash)
102
- if identifier.year
103
- separator = identifier.year_format == "dash" ? "-" : ":"
104
- year_part = separator
105
- year_part += identifier.year_prefix if identifier.year_prefix # Add M or F prefix
106
- year_part += "F" if identifier.french && identifier.year_format != "dash" && !identifier.year_prefix # Only add F if no prefix
107
- # Convert 4-digit year back to 2-digit
108
- year_str = identifier.year.to_s
109
- year_part += if year_str.length == 4 && year_str.start_with?("20")
110
- year_str[2..3]
111
- else
112
- year_str
113
- end
114
- code_part += year_part
115
- end
115
+ code_part += render_continuation_year(identifier) if identifier.year
116
116
 
117
117
  parts << code_part if code_part
118
118
 
119
- # Join with proper spacing based on prefix
120
- if identifier.has_publisher && parts.length > 1
121
- prefix = parts[0]
122
- needs_space = !prefix.end_with?("-")
123
- if needs_space
124
- parts.join(" ")
125
- else
126
- # No space after dash-ending prefix
127
- parts[0] + parts[1..].join(" ")
128
- end
119
+ join_continuation(identifier, parts)
120
+ end
121
+
122
+ # Year with proper format (colon or dash)
123
+ def render_continuation_year(identifier)
124
+ separator = identifier.year_format == "dash" ? "-" : ":"
125
+ year_part = separator
126
+ # Add M or F prefix
127
+ year_part += identifier.year_prefix if identifier.year_prefix
128
+ # Only add F if no prefix
129
+ if identifier.french && identifier.year_format != "dash" &&
130
+ !identifier.year_prefix
131
+ year_part += "F"
132
+ end
133
+ # Convert 4-digit year back to 2-digit
134
+ year_str = identifier.year.to_s
135
+ year_part + if year_str.length == 4 && year_str.start_with?("20")
136
+ year_str[2..3]
137
+ else
138
+ year_str
139
+ end
140
+ end
141
+
142
+ # Join with proper spacing based on prefix
143
+ def join_continuation(identifier, parts)
144
+ return parts.join(" ") unless identifier.has_publisher &&
145
+ parts.length > 1
146
+
147
+ prefix = parts[0]
148
+ if prefix.end_with?("-")
149
+ # No space after dash-ending prefix
150
+ prefix + parts[1..].join(" ")
129
151
  else
130
152
  parts.join(" ")
131
153
  end
@@ -18,16 +18,16 @@ module Pubid
18
18
  #
19
19
  # This is a wrapper pattern where:
20
20
  # - CSA prefix indicates adoption
21
- # - The wrapped_identifier is the external standard (ISO/IEC/CISPR)
21
+ # - The base is the external standard (ISO/IEC/CISPR)
22
22
  # - Wrapped identifier is recursively parsed using external flavor parsers
23
23
  class CsaAdopted < WrapperIdentifier
24
24
  # Publisher prefix (e.g., "CSA-", "CAN3-") for rendering
25
25
  # When set, uses dash format without space (e.g., "CSA-ISO" not "CSA ISO")
26
26
  attribute :publisher_prefix, :string
27
27
 
28
- def to_s
28
+ def to_s(**opts)
29
29
  # Get string representation from wrapped identifier
30
- base_str = wrapped_identifier.to_s
30
+ base_str = base.to_s
31
31
 
32
32
  # Convert 4-digit years to 2-digit for CSA adoption format
33
33
  # ISO/IEC TR 12785-3:2015 → ISO/IEC TR 12785-3:15
@@ -70,7 +70,7 @@ module Pubid
70
70
  # Append reaffirmation if present
71
71
  result += " (R#{reaffirmation})" if reaffirmation
72
72
 
73
- result
73
+ annotate_plain_render(result, **opts)
74
74
  end
75
75
  end
76
76
  end
@@ -33,7 +33,7 @@ module Pubid
33
33
  # false = materials BEFORE: "CSA B149.1:25 Code, Handbook & Training Package"
34
34
  attribute :materials_after_keyword, :boolean, default: -> { true }
35
35
 
36
- def to_s
36
+ def to_s(**opts)
37
37
  result = base.to_s
38
38
 
39
39
  # Add package materials if present
@@ -57,7 +57,7 @@ module Pubid
57
57
  result += " PACKAGE"
58
58
  end
59
59
 
60
- result
60
+ annotate_plain_render(result, **opts)
61
61
  end
62
62
  end
63
63
  end
@@ -4,7 +4,7 @@ require "parslet"
4
4
 
5
5
  module Pubid
6
6
  module Csa
7
- class Parser < Parslet::Parser
7
+ class Parser < ::Pubid::Parser::Grammar
8
8
  # Basic building blocks
9
9
  rule(:space) { str(" ") }
10
10
  rule(:dash) { str("-") }
@@ -367,7 +367,11 @@ module Pubid
367
367
  # Preprocessing to normalize input
368
368
  def parse(input)
369
369
  # Skip comment lines
370
- raise Parslet::ParseFailed.new("Comment line") if input.strip.start_with?("#")
370
+ if input.strip.start_with?("#")
371
+ raise Pubid::Errors::ParseError.new(
372
+ "Comment line", input: input, flavor: "csa"
373
+ )
374
+ end
371
375
 
372
376
  # Remove CONSOLIDATED notation FIRST (before other processing)
373
377
  normalized = input.gsub(/\s*\(\s*CONSOLIDATED\s*\)\s*/, " ")
@@ -35,7 +35,7 @@ module Pubid
35
35
 
36
36
  # Base rendering: "CSA B149.1:20", "CAN/CSA-C22.2-05"
37
37
  def render_base(id)
38
- prefix = id.publisher_prefix || "CSA"
38
+ prefix = publisher_prefix_for(id)
39
39
 
40
40
  # Handle code_only identifiers (empty string means no prefix)
41
41
  if prefix == ""
@@ -51,7 +51,7 @@ module Pubid
51
51
  end
52
52
 
53
53
  # Code and year together
54
- code_part = id.code.to_s if id.code
54
+ code_part = id.number.to_s if id.number
55
55
 
56
56
  # NO. keyword
57
57
  if id.no_number
@@ -131,7 +131,7 @@ module Pubid
131
131
  parts = []
132
132
 
133
133
  # Publisher prefix (CSA, CAN/CSA-, CAN3-)
134
- prefix = id.publisher_prefix || "CSA"
134
+ prefix = publisher_prefix_for(id)
135
135
 
136
136
  # Determine if we need space after prefix
137
137
  # CAN/CSA- and CAN3- end with dash, so no space needed
@@ -203,7 +203,7 @@ module Pubid
203
203
  result = series_publisher_prefix_portion(id)
204
204
 
205
205
  # Add code FIRST (before SERIES keyword)
206
- result += id.code.to_s
206
+ result += id.number.to_s
207
207
 
208
208
  # Add space before series prefix/SERIES keyword
209
209
  result += " "
@@ -255,8 +255,16 @@ module Pubid
255
255
  end
256
256
  end
257
257
 
258
+ # The publisher prefix to print. A code_only identifier printed none, so
259
+ # it must not gain the default "CSA" — see SingleIdentifier#code_only.
260
+ def publisher_prefix_for(id)
261
+ return "" if id.respond_to?(:code_only) && id.code_only
262
+
263
+ id.publisher_prefix || "CSA"
264
+ end
265
+
258
266
  def series_publisher_prefix_portion(id)
259
- prefix = id.publisher_prefix || "CSA"
267
+ prefix = publisher_prefix_for(id)
260
268
 
261
269
  # Handle code_only identifiers (empty string means no prefix)
262
270
  return "" if prefix == ""
@@ -3,11 +3,18 @@
3
3
  module Pubid
4
4
  module Csa
5
5
  class SingleIdentifier < Identifier
6
- # Generate URN for this identifier
6
+ # The document code — "B149.1", "Z240", "C22.2" — and the relaton index
7
+ # key: Relaton::Index sorts and bsearches every row on
8
+ # `id.root.number.to_s`, so it must never be empty.
7
9
  #
8
- # @return [String] URN representation
9
-
10
- attribute :code, Components::Code
10
+ # It is the `number` INHERITED from ::Pubid::Identifier, not a
11
+ # redeclaration: Pubid::Csa::Components::Code is a bare alias of
12
+ # Pubid::Components::Code, which is exactly the parent's declared type.
13
+ # That matters. CSA's base is split across two files (csa/identifier.rb +
14
+ # csa/single_identifier.rb) — the IEEE shape in which a leaf can snapshot
15
+ # a half-built parent attribute table (CLAUDE.md, the `number`
16
+ # determinism landmine). Declaring nothing here keeps CSA clear of it.
17
+ # The attribute was named `code` before; there is no alias.
11
18
  attribute :no_number, :string
12
19
  attribute :year, :string
13
20
  attribute :year_format, :string # "colon" or "dash"
@@ -25,6 +32,88 @@ module Pubid
25
32
  attribute :series, :boolean # Track if SERIES keyword present
26
33
  attribute :package, :string # Package portion (Code, Handbook, etc.)
27
34
  attribute :publisher_prefix, :string # Original prefix: "CAN/CSA-", "CSA", "CAN3-"
35
+
36
+ # True when the reference printed no publisher at all ("C22.1-15"), so
37
+ # rendering must not supply the default "CSA".
38
+ #
39
+ # This used to be an empty-string `publisher_prefix` sentinel, which the
40
+ # canonical `to_hash` drops (it strips empty and default-valued
41
+ # attributes by design) — so a code-only identifier came back
42
+ # from `from_hash` with a nil prefix and re-rendered as
43
+ # "CSA C22.1-15". Named for the RARE case with a `false` default,
44
+ # per CLAUDE.md, so it is dropped from every ordinary row.
45
+ attribute :code_only, :boolean, default: -> { false }
46
+
47
+ # A flat, index-friendly serialized shape: the `Components::Code`
48
+ # attributes collapse to bare scalars (`number: C22.2-286`, not
49
+ # `number: {value: C22.2-286}`), the ISO/ETSI/ITU `key_value` pattern.
50
+ #
51
+ # This is deliberately a serialization mapping rather than an
52
+ # `attribute :number, :string` redeclaration. CSA's base is split across
53
+ # two files (csa/identifier.rb + csa/single_identifier.rb) — the IEEE
54
+ # shape in which a leaf can snapshot a half-built parent attribute table
55
+ # — so a retype here would reintroduce the `number` determinism landmine
56
+ # recorded in CLAUDE.md. The runtime attribute stays a Components::Code;
57
+ # only the hash is flat.
58
+ #
59
+ # A lutaml key_value block is EXHAUSTIVE — an unmapped attribute is
60
+ # dropped, `_type` included — so every attribute above appears here. It
61
+ # lives on SingleIdentifier, which only the single-document types
62
+ # inherit; the container types descend from Csa::Identifier directly and
63
+ # keep the default shape for their nested `base`/`identifiers`.
64
+ key_value do
65
+ map "_type", to: :_type
66
+ map "number", with: { to: :number_to_kv, from: :number_from_kv }
67
+ map "no_number",
68
+ with: { to: :no_number_to_kv, from: :no_number_from_kv }
69
+ map "year", to: :year
70
+ map "year_format", to: :year_format
71
+ map "year_prefix", to: :year_prefix
72
+ map "original_year_4digit", to: :original_year_4digit
73
+ map "french", to: :french
74
+ map "reaffirmation", to: :reaffirmation
75
+ map "original_reaffirmation_4digit", to: :original_reaffirmation_4digit
76
+ map "has_publisher", to: :has_publisher
77
+ map "series_prefix", to: :series_prefix
78
+ map "series", to: :series
79
+ map "package", to: :package
80
+ map "publisher_prefix", to: :publisher_prefix
81
+ map "code_only", to: :code_only
82
+ end
83
+
84
+ def number_to_kv(model, doc)
85
+ emit_kv(doc, "number", model.number)
86
+ end
87
+
88
+ def number_from_kv(model, value)
89
+ model.number = value.to_s
90
+ end
91
+
92
+ # `no_number` is a plain :string here but a Components::Code on Cec, so
93
+ # the converter reads through whichever it is given.
94
+ def no_number_to_kv(model, doc)
95
+ emit_kv(doc, "no_number", model.no_number)
96
+ end
97
+
98
+ def no_number_from_kv(model, value)
99
+ model.no_number =
100
+ if model.class.attributes[:no_number].type == Components::Code
101
+ Components::Code.new(value: value.to_s)
102
+ else
103
+ value.to_s
104
+ end
105
+ end
106
+
107
+ # Emit a scalar, skipping a nil or empty value so the canonical
108
+ # no-defaults hash never gains an empty key.
109
+ def emit_kv(doc, key, value)
110
+ text = value.respond_to?(:value) ? value.value : value
111
+ return if text.nil? || text.to_s.empty?
112
+
113
+ doc.add_child(
114
+ Lutaml::KeyValue::DataModel::Element.new(key, text.to_s),
115
+ )
116
+ end
28
117
  end
29
118
  end
30
119
  end
@@ -3,16 +3,23 @@
3
3
  module Pubid
4
4
  module Csa
5
5
  class UrnGenerator < Pubid::UrnGenerator::Base
6
+ # A code_only identifier printed no publisher, so it keeps an empty
7
+ # publisher segment rather than falling back to "csa" in #generate.
6
8
  def urn_publisher_prefix
9
+ return "" if code_only?
7
10
  return nil unless identifier.publisher_prefix
8
11
 
9
12
  identifier.publisher_prefix.to_s.downcase
10
13
  end
11
14
 
15
+ def code_only?
16
+ identifier.respond_to?(:code_only) && identifier.code_only
17
+ end
18
+
12
19
  def urn_number
13
- return nil unless identifier.code
20
+ return nil unless identifier.number
14
21
 
15
- identifier.code.render(context: URN_CONTEXT)
22
+ identifier.number.to_s
16
23
  end
17
24
 
18
25
  def urn_no_number
@@ -4,44 +4,60 @@ require "lutaml/model"
4
4
 
5
5
  module Pubid
6
6
  module Csa
7
- # WrapperIdentifier is the base class for all CSA identifiers that wrap other identifiers
7
+ # WrapperIdentifier is the base class for all CSA identifiers that wrap
8
+ # other identifiers.
8
9
  # Examples:
9
- # - CanadianAdoptedIdentifier: CAN/{wrapped_identifier}
10
- # - CsaAdoptedIdentifier: CSA {ISO/IEC/CISPR identifier}
10
+ # - CanadianAdopted: CAN/{base}
11
+ # - CsaAdopted: CSA {ISO/IEC/CISPR identifier}
11
12
  #
12
- # The wrapper pattern allows proper MODEL-DRIVEN architecture where adoptions
13
- # are objects that contain other identifier objects, not string prefixes.
14
- class WrapperIdentifier < Lutaml::Model::Serializable
15
- # The wrapped identifier (recursively parsed)
16
- # Use attr_accessor since it can be any identifier object (CSA, ISO, IEC, etc.)
17
- attr_accessor :wrapped_identifier
13
+ # The wrapper pattern allows proper MODEL-DRIVEN architecture where
14
+ # adoptions are objects that contain other identifier objects, not string
15
+ # prefixes.
16
+ class WrapperIdentifier < Identifier
17
+ # The wrapped identifier (recursively parsed).
18
+ #
19
+ # A real lutaml attribute, not an attr_accessor: the accessor was
20
+ # invisible to `to_hash`, `from_hash` AND `#exclude` (which iterates
21
+ # `self.class.attributes`), so a wrapper serialized to a hash with no
22
+ # identifier in it at all. It is named `base` — the uniform parent
23
+ # accessor every flavor uses — so the inherited `#root` walks it to the
24
+ # origin document and `exclude_from_nested` reaches inside. There is no
25
+ # `wrapped_identifier` alias.
26
+ #
27
+ # Typed as the cross-flavor `::Pubid::Identifier`, not the CSA one:
28
+ # CsaAdopted wraps ISO/IEC/CISPR ids, and lutaml enforces the declared
29
+ # type — `polymorphic: true` widens it only to SUBCLASSES, so a narrower
30
+ # type raises Lutaml::Model::IncorrectModelError on `to_hash`. The
31
+ # Ieee::Identifiers::AdoptedStandard precedent. The nested child carries
32
+ # its own `_type`, so `from_hash` routes it back via TypeResolver.
33
+ attribute :base, ::Pubid::Identifier, polymorphic: true
18
34
 
19
35
  # Reaffirmation year (common across wrappers)
20
36
  attribute :reaffirmation, :string
21
37
 
38
+ # Whether the reaffirmation was printed 4-digit ("(R2004)") rather than
39
+ # 2-digit ("(R04)"). CanadianAdopted#to_s reads it to choose the spacing
40
+ # and to render the year back in its original width.
41
+ attribute :original_reaffirmation_4digit, :boolean, default: -> { false }
42
+
22
43
  # Year format tracking (for compatibility)
23
44
  attribute :year_format, :string
24
45
 
25
46
  # Subclasses MUST implement to_s
26
- def to_s
47
+ def to_s(**_opts)
27
48
  raise NotImplementedError, "Subclasses must implement to_s method"
28
49
  end
29
50
 
30
- # CSA wrappers serialise through `to_s` (their distinct shape is what
31
- # makes them wrappers), so MR mirrors that with ` ` → `.`, `:` → `.`,
32
- # `/` → `-`, then lowercased (issue #142).
33
- def to_mr_string
34
- to_s.tr(" ", ".").tr(":", ".").tr("/", "-").downcase
35
- end
36
-
37
- def to_slug
38
- to_mr_string
51
+ # The matching primitives relaton uses to normalise a reference before
52
+ # comparing it. An adoption wrapper peels to the document it adopts;
53
+ # without these the inherited defaults return the WRAPPER, which is a
54
+ # silently wrong answer rather than a loud one.
55
+ def base_document
56
+ base ? base.base_document : self
39
57
  end
40
58
 
41
- # CSA wrappers are not Pubid::Identifier objects, so they do not inherit
42
- # #root. They are their own origin for matching purposes.
43
- def root
44
- self
59
+ def drop_supplements
60
+ base || self
45
61
  end
46
62
  end
47
63
  end
data/lib/pubid/csa.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Csa
5
6
  extend Pubid::PrefixesSupport
@@ -20,8 +21,13 @@ module Pubid
20
21
  autoload :Components, "#{__dir__}/csa/components"
21
22
 
22
23
  def self.parse(identifier_string)
24
+ unless identifier_string.is_a?(String)
25
+ raise Pubid::Errors::InvalidInputError,
26
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
27
+ end
28
+
23
29
  if identifier_string.length > Pubid::MAX_INPUT_LENGTH
24
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
30
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
25
31
  end
26
32
 
27
33
  Identifier.parse(identifier_string)
@@ -32,14 +32,17 @@ module Pubid
32
32
  end
33
33
 
34
34
  def self.parse(identifier)
35
+ unless identifier.is_a?(String)
36
+ raise Pubid::Errors::InvalidInputError,
37
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
38
+ end
39
+
35
40
  if identifier.length > Pubid::MAX_INPUT_LENGTH
36
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
41
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
37
42
  end
38
43
 
39
44
  parsed = Parser.parse(identifier)
40
45
  Builder.build(parsed)
41
- rescue Parslet::ParseFailed => e
42
- raise "Failed to parse DOI '#{identifier}': #{e.message}"
43
46
  end
44
47
  end
45
48
  end
@@ -13,7 +13,7 @@ module Pubid
13
13
  # SUFFIX is one-or-more chars from [A-Za-z0-9._\-/()] (printable minus
14
14
  # whitespace). The grammar is intentionally permissive on suffix chars
15
15
  # because real DOIs cover the full printable range.
16
- class Parser < Parslet::Parser
16
+ class Parser < ::Pubid::Parser::Grammar
17
17
  rule(:dot) { str(".") }
18
18
  rule(:slash) { str("/") }
19
19
 
data/lib/pubid/doi.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  # DOI (Digital Object Identifier) flavor.
5
6
  #
@@ -30,8 +30,6 @@ module Pubid
30
30
  def self.parse(identifier)
31
31
  parsed = Parser.parse(identifier)
32
32
  Builder.build(parsed)
33
- rescue Parslet::ParseFailed => e
34
- raise "Failed to parse EASC identifier '#{identifier}': #{e.message}"
35
33
  end
36
34
 
37
35
  attribute :publisher, :string, default: "EASC"
@@ -16,7 +16,7 @@ module Pubid
16
16
  # Also accepts Latin transliterations (PMG, RMG, V) for
17
17
  # round-tripping with English-language tooling. The renderer
18
18
  # emits Cyrillic as the canonical form.
19
- class Parser < Parslet::Parser
19
+ class Parser < ::Pubid::Parser::Grammar
20
20
  include ::Pubid::Parser::CommonParseRules
21
21
 
22
22
  root :identifier
@@ -71,7 +71,15 @@ module Pubid
71
71
  end
72
72
 
73
73
  def self.parse(string)
74
- raise ArgumentError, ::Pubid::INPUT_TOO_LONG_MESSAGE if string.length > ::Pubid::MAX_INPUT_LENGTH
74
+ unless string.is_a?(String)
75
+ raise ::Pubid::Errors::InvalidInputError,
76
+ ::Pubid::INPUT_NOT_A_STRING_MESSAGE
77
+ end
78
+
79
+ if string.length > ::Pubid::MAX_INPUT_LENGTH
80
+ raise ::Pubid::Errors::InvalidInputError,
81
+ ::Pubid::INPUT_TOO_LONG_MESSAGE
82
+ end
75
83
 
76
84
  new.parse(string.strip)
77
85
  end