pubid 2.0.0.pre.alpha.9 → 2.0.0.pre.alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +33 -11
  3. data/conformance/pending.yaml +4 -0
  4. data/lib/pubid/adobe/identifier.rb +1 -3
  5. data/lib/pubid/adobe/parser.rb +10 -2
  6. data/lib/pubid/adobe.rb +1 -0
  7. data/lib/pubid/amca/builder.rb +2 -2
  8. data/lib/pubid/amca/identifiers/base.rb +49 -27
  9. data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
  10. data/lib/pubid/amca/identifiers/publication.rb +13 -9
  11. data/lib/pubid/amca/identifiers/standard.rb +2 -0
  12. data/lib/pubid/amca/parser.rb +1 -1
  13. data/lib/pubid/amca/renderer.rb +4 -4
  14. data/lib/pubid/amca/urn_generator.rb +2 -2
  15. data/lib/pubid/amca.rb +2 -1
  16. data/lib/pubid/ansi/builder.rb +2 -2
  17. data/lib/pubid/ansi/identifier.rb +18 -0
  18. data/lib/pubid/ansi/parser.rb +1 -1
  19. data/lib/pubid/ansi/renderer.rb +2 -2
  20. data/lib/pubid/ansi.rb +5 -5
  21. data/lib/pubid/api/builder.rb +16 -2
  22. data/lib/pubid/api/identifier.rb +25 -4
  23. data/lib/pubid/api/identifiers/mpms.rb +10 -8
  24. data/lib/pubid/api/parser.rb +1 -1
  25. data/lib/pubid/api/renderer.rb +16 -7
  26. data/lib/pubid/api/single_identifier.rb +5 -12
  27. data/lib/pubid/api.rb +1 -0
  28. data/lib/pubid/ashrae/builder.rb +31 -10
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
  31. data/lib/pubid/ashrae/identifiers/base.rb +81 -5
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
  33. data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
  34. data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
  35. data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
  36. data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
  37. data/lib/pubid/ashrae/parser.rb +23 -8
  38. data/lib/pubid/ashrae/renderer.rb +6 -6
  39. data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
  40. data/lib/pubid/ashrae/urn_generator.rb +8 -2
  41. data/lib/pubid/ashrae.rb +7 -1
  42. data/lib/pubid/asme/builder.rb +11 -2
  43. data/lib/pubid/asme/identifier.rb +9 -0
  44. data/lib/pubid/asme/identifiers/standard.rb +86 -2
  45. data/lib/pubid/asme/parser.rb +1 -1
  46. data/lib/pubid/asme/renderer.rb +3 -3
  47. data/lib/pubid/asme/single_identifier.rb +8 -1
  48. data/lib/pubid/asme/urn_generator.rb +2 -2
  49. data/lib/pubid/asme.rb +1 -0
  50. data/lib/pubid/astm/builder.rb +10 -3
  51. data/lib/pubid/astm/identifier.rb +9 -0
  52. data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
  53. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  54. data/lib/pubid/astm/identifiers/data_series.rb +2 -0
  55. data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
  56. data/lib/pubid/astm/identifiers/manual.rb +10 -0
  57. data/lib/pubid/astm/identifiers/monograph.rb +2 -0
  58. data/lib/pubid/astm/identifiers/research_report.rb +10 -0
  59. data/lib/pubid/astm/identifiers/standard.rb +10 -0
  60. data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
  61. data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
  62. data/lib/pubid/astm/identifiers.rb +1 -0
  63. data/lib/pubid/astm/parser.rb +1 -1
  64. data/lib/pubid/astm/renderer.rb +1 -1
  65. data/lib/pubid/astm/single_identifier.rb +31 -1
  66. data/lib/pubid/astm/urn_generator.rb +7 -5
  67. data/lib/pubid/astm.rb +1 -0
  68. data/lib/pubid/bipm/builder.rb +66 -10
  69. data/lib/pubid/bipm/identifier.rb +134 -9
  70. data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
  71. data/lib/pubid/bipm/identifiers/guide.rb +11 -0
  72. data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
  73. data/lib/pubid/bipm/identifiers/mep.rb +12 -0
  74. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  75. data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
  76. data/lib/pubid/bipm/parser.rb +65 -12
  77. data/lib/pubid/bipm/renderer.rb +8 -0
  78. data/lib/pubid/bipm/urn_parser.rb +11 -3
  79. data/lib/pubid/bipm.rb +7 -1
  80. data/lib/pubid/bsi/builder.rb +48 -38
  81. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  82. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  83. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +0 -1
  84. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  85. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  86. data/lib/pubid/bsi/identifiers/handbook.rb +0 -2
  87. data/lib/pubid/bsi/identifiers/practice_guide.rb +0 -1
  88. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  89. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  90. data/lib/pubid/bsi/parser.rb +1 -1
  91. data/lib/pubid/bsi/renderer.rb +63 -51
  92. data/lib/pubid/bsi/single_identifier.rb +16 -7
  93. data/lib/pubid/bsi/urn_generator.rb +11 -2
  94. data/lib/pubid/bsi.rb +7 -1
  95. data/lib/pubid/builder/base.rb +18 -8
  96. data/lib/pubid/bundled_identifier.rb +16 -6
  97. data/lib/pubid/calconnect/identifier.rb +9 -5
  98. data/lib/pubid/calconnect/parser.rb +1 -1
  99. data/lib/pubid/calconnect.rb +7 -1
  100. data/lib/pubid/ccsds/identifier.rb +9 -2
  101. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  102. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  103. data/lib/pubid/ccsds/parser.rb +1 -1
  104. data/lib/pubid/ccsds/single_identifier.rb +2 -2
  105. data/lib/pubid/ccsds.rb +1 -0
  106. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  107. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  108. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  109. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  110. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  111. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  112. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  115. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  116. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  117. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  118. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  119. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  120. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  123. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  124. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  125. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  126. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  127. data/lib/pubid/cen_cenelec.rb +28 -10
  128. data/lib/pubid/cie/builder.rb +10 -0
  129. data/lib/pubid/cie/identifier.rb +6 -1
  130. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  131. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  132. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  133. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  134. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  135. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  136. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  137. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  138. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  139. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  140. data/lib/pubid/cie/parser.rb +1 -1
  141. data/lib/pubid/cie.rb +7 -1
  142. data/lib/pubid/conformance/checks.rb +72 -0
  143. data/lib/pubid/conformance/corpus/case.rb +55 -0
  144. data/lib/pubid/conformance/corpus.rb +36 -0
  145. data/lib/pubid/conformance/generator.rb +212 -0
  146. data/lib/pubid/conformance/pending.rb +63 -0
  147. data/lib/pubid/conformance/runner.rb +153 -0
  148. data/lib/pubid/conformance.rb +55 -0
  149. data/lib/pubid/core.rb +2 -0
  150. data/lib/pubid/csa/builder.rb +47 -47
  151. data/lib/pubid/csa/composite_identifier.rb +14 -19
  152. data/lib/pubid/csa/identifier.rb +143 -81
  153. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  154. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  155. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  156. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  157. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  158. data/lib/pubid/csa/identifiers/package.rb +2 -2
  159. data/lib/pubid/csa/parser.rb +6 -2
  160. data/lib/pubid/csa/renderer.rb +13 -5
  161. data/lib/pubid/csa/single_identifier.rb +93 -4
  162. data/lib/pubid/csa/urn_generator.rb +9 -2
  163. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  164. data/lib/pubid/csa.rb +7 -1
  165. data/lib/pubid/doi/identifier.rb +6 -3
  166. data/lib/pubid/doi/parser.rb +1 -1
  167. data/lib/pubid/doi.rb +1 -0
  168. data/lib/pubid/easc/identifier.rb +0 -2
  169. data/lib/pubid/easc/parser.rb +10 -2
  170. data/lib/pubid/easc.rb +1 -0
  171. data/lib/pubid/ecma/builder.rb +23 -9
  172. data/lib/pubid/ecma/identifier.rb +81 -10
  173. data/lib/pubid/ecma/parser.rb +39 -4
  174. data/lib/pubid/ecma/renderer.rb +29 -8
  175. data/lib/pubid/ecma/urn_generator.rb +34 -13
  176. data/lib/pubid/ecma/urn_parser.rb +30 -14
  177. data/lib/pubid/ecma.rb +1 -0
  178. data/lib/pubid/errors.rb +80 -0
  179. data/lib/pubid/etsi/builder.rb +6 -1
  180. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  181. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  182. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  183. data/lib/pubid/etsi/parser.rb +1 -1
  184. data/lib/pubid/etsi.rb +1 -0
  185. data/lib/pubid/evs/builder.rb +26 -0
  186. data/lib/pubid/evs/identifier.rb +37 -0
  187. data/lib/pubid/evs/identifiers/national_adoption.rb +17 -0
  188. data/lib/pubid/evs/identifiers.rb +9 -0
  189. data/lib/pubid/evs/parser.rb +27 -0
  190. data/lib/pubid/evs/renderer.rb +30 -0
  191. data/lib/pubid/evs/urn_generator.rb +30 -0
  192. data/lib/pubid/evs/urn_parser.rb +80 -0
  193. data/lib/pubid/evs.rb +90 -0
  194. data/lib/pubid/export.rb +1 -0
  195. data/lib/pubid/format_detector.rb +1 -0
  196. data/lib/pubid/gb/identifier.rb +6 -3
  197. data/lib/pubid/gb/parser.rb +1 -1
  198. data/lib/pubid/gb.rb +1 -0
  199. data/lib/pubid/gost/builder.rb +46 -9
  200. data/lib/pubid/gost/identifier.rb +0 -2
  201. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  202. data/lib/pubid/gost/parser.rb +10 -2
  203. data/lib/pubid/gost.rb +1 -0
  204. data/lib/pubid/iala/identifier.rb +9 -6
  205. data/lib/pubid/iala/parser.rb +10 -2
  206. data/lib/pubid/iala/urn_parser.rb +6 -2
  207. data/lib/pubid/iala.rb +1 -0
  208. data/lib/pubid/iana/builder.rb +3 -1
  209. data/lib/pubid/iana/identifier.rb +74 -6
  210. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  211. data/lib/pubid/iana/parser.rb +1 -1
  212. data/lib/pubid/iana/renderer.rb +3 -0
  213. data/lib/pubid/iana/urn_generator.rb +5 -1
  214. data/lib/pubid/iana.rb +1 -0
  215. data/lib/pubid/identifier.rb +594 -10
  216. data/lib/pubid/identifier_metadata.rb +1 -0
  217. data/lib/pubid/idf/builder.rb +3 -3
  218. data/lib/pubid/idf/identifier.rb +18 -9
  219. data/lib/pubid/idf/parser.rb +1 -1
  220. data/lib/pubid/idf/renderer.rb +4 -4
  221. data/lib/pubid/idf/urn_generator.rb +1 -1
  222. data/lib/pubid/idf.rb +7 -1
  223. data/lib/pubid/iec/builder.rb +14 -7
  224. data/lib/pubid/iec/components.rb +0 -1
  225. data/lib/pubid/iec/identifier.rb +73 -83
  226. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  227. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  228. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  229. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  230. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  231. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  232. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  233. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  234. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  235. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  236. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  237. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  238. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  239. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  240. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  241. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  242. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  243. data/lib/pubid/iec/parser.rb +2 -18
  244. data/lib/pubid/iec/renderer.rb +13 -10
  245. data/lib/pubid/iec/single_identifier.rb +33 -10
  246. data/lib/pubid/iec/urn_generator.rb +227 -61
  247. data/lib/pubid/iec/urn_parser.rb +42 -4
  248. data/lib/pubid/iec.rb +1 -0
  249. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  250. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  251. data/lib/pubid/ieee/builder.rb +45 -1
  252. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  253. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  254. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  255. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  256. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  257. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  258. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  259. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  260. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  261. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  262. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  263. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  264. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  265. data/lib/pubid/ieee/ire/parser.rb +1 -1
  266. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  267. data/lib/pubid/ieee/parser.rb +1 -1
  268. data/lib/pubid/ieee/renderer.rb +5 -1
  269. data/lib/pubid/ieee/urn_generator.rb +32 -3
  270. data/lib/pubid/ieee.rb +7 -1
  271. data/lib/pubid/ieee_debug.rb +1 -0
  272. data/lib/pubid/ietf/builder.rb +24 -8
  273. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  274. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  275. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  276. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  277. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  278. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  279. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  280. data/lib/pubid/ietf/identifiers.rb +1 -0
  281. data/lib/pubid/ietf/parser.rb +27 -9
  282. data/lib/pubid/ietf/renderer.rb +44 -6
  283. data/lib/pubid/ietf/urn_generator.rb +20 -6
  284. data/lib/pubid/ietf/urn_parser.rb +10 -2
  285. data/lib/pubid/ietf.rb +12 -2
  286. data/lib/pubid/iho/identifiers/base.rb +10 -3
  287. data/lib/pubid/iho/parser.rb +1 -1
  288. data/lib/pubid/iho.rb +1 -0
  289. data/lib/pubid/isbn/identifier.rb +25 -7
  290. data/lib/pubid/isbn/parser.rb +1 -1
  291. data/lib/pubid/isbn.rb +1 -0
  292. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  293. data/lib/pubid/iso/combined_identifier.rb +5 -3
  294. data/lib/pubid/iso/identifier.rb +12 -7
  295. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  296. data/lib/pubid/iso/parser.rb +1 -1
  297. data/lib/pubid/iso.rb +7 -1
  298. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  299. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  300. data/lib/pubid/itu/identifiers/base.rb +19 -5
  301. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  302. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  303. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  304. data/lib/pubid/itu/parser.rb +1 -1
  305. data/lib/pubid/itu.rb +1 -0
  306. data/lib/pubid/jcgm/builder.rb +1 -1
  307. data/lib/pubid/jcgm/identifier.rb +10 -0
  308. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  309. data/lib/pubid/jcgm/parser.rb +10 -3
  310. data/lib/pubid/jcgm/renderer.rb +10 -3
  311. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  312. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  313. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  314. data/lib/pubid/jcgm.rb +10 -0
  315. data/lib/pubid/jis/identifier.rb +10 -3
  316. data/lib/pubid/jis/parser.rb +1 -1
  317. data/lib/pubid/jis.rb +1 -0
  318. data/lib/pubid/nist/components/stage.rb +2 -2
  319. data/lib/pubid/nist/configuration.rb +6 -8
  320. data/lib/pubid/nist/identifiers/base.rb +12 -2
  321. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  322. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  323. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  324. data/lib/pubid/nist/identifiers/crpl_report.rb +7 -1
  325. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  326. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  327. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +14 -6
  328. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  329. data/lib/pubid/nist/identifiers/report.rb +14 -6
  330. data/lib/pubid/nist/parser.rb +1 -1
  331. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  332. data/lib/pubid/nist.rb +7 -1
  333. data/lib/pubid/oasis/builder.rb +12 -10
  334. data/lib/pubid/oasis/identifier.rb +89 -11
  335. data/lib/pubid/oasis/parser.rb +1 -1
  336. data/lib/pubid/oasis.rb +7 -1
  337. data/lib/pubid/ogc/identifier.rb +9 -5
  338. data/lib/pubid/ogc/parser.rb +1 -1
  339. data/lib/pubid/ogc.rb +1 -0
  340. data/lib/pubid/oiml/builder.rb +5 -1
  341. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  342. data/lib/pubid/oiml/identifiers/bulletin.rb +36 -0
  343. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  344. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  345. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  346. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  347. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  348. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  349. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  350. data/lib/pubid/oiml/identifiers.rb +1 -0
  351. data/lib/pubid/oiml/parser.rb +1 -1
  352. data/lib/pubid/oiml/single_identifier.rb +26 -42
  353. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  354. data/lib/pubid/oiml.rb +7 -1
  355. data/lib/pubid/omg/builder.rb +1 -0
  356. data/lib/pubid/omg/identifier.rb +20 -3
  357. data/lib/pubid/omg/parser.rb +64 -11
  358. data/lib/pubid/omg/renderer.rb +13 -1
  359. data/lib/pubid/omg.rb +2 -1
  360. data/lib/pubid/parser/grammar.rb +74 -0
  361. data/lib/pubid/parser.rb +2 -0
  362. data/lib/pubid/parsers/mr_string.rb +19 -0
  363. data/lib/pubid/plateau/parser.rb +1 -1
  364. data/lib/pubid/plateau.rb +10 -0
  365. data/lib/pubid/prefixes_support.rb +1 -0
  366. data/lib/pubid/renderers/annotator.rb +233 -0
  367. data/lib/pubid/renderers/base.rb +13 -0
  368. data/lib/pubid/renderers/directives_renderer.rb +3 -3
  369. data/lib/pubid/renderers/human_readable.rb +3 -3
  370. data/lib/pubid/renderers/mr_string.rb +15 -9
  371. data/lib/pubid/renderers.rb +1 -0
  372. data/lib/pubid/rendering/numbering.rb +25 -7
  373. data/lib/pubid/rendering.rb +1 -0
  374. data/lib/pubid/sae/identifiers/base.rb +9 -2
  375. data/lib/pubid/sae/parser.rb +1 -1
  376. data/lib/pubid/sae.rb +1 -0
  377. data/lib/pubid/schema/declaration.rb +33 -0
  378. data/lib/pubid/schema/error.rb +14 -0
  379. data/lib/pubid/schema/identifier_type.rb +23 -0
  380. data/lib/pubid/schema/loader.rb +117 -0
  381. data/lib/pubid/schema/typed_stage.rb +24 -0
  382. data/lib/pubid/schema.rb +25 -0
  383. data/lib/pubid/tgpp/builder.rb +1 -1
  384. data/lib/pubid/tgpp/identifier.rb +21 -5
  385. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  386. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  387. data/lib/pubid/tgpp/parser.rb +22 -6
  388. data/lib/pubid/tgpp/renderer.rb +21 -5
  389. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  390. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  391. data/lib/pubid/tgpp.rb +7 -1
  392. data/lib/pubid/type_resolver.rb +16 -1
  393. data/lib/pubid/un/identifier.rb +6 -3
  394. data/lib/pubid/un/parser.rb +1 -1
  395. data/lib/pubid/un.rb +1 -0
  396. data/lib/pubid/urn_parser/errors.rb +8 -1
  397. data/lib/pubid/urn_parser.rb +1 -0
  398. data/lib/pubid/utils.rb +1 -0
  399. data/lib/pubid/version.rb +1 -1
  400. data/lib/pubid/w3c/builder.rb +5 -5
  401. data/lib/pubid/w3c/identifier.rb +38 -9
  402. data/lib/pubid/w3c/parser.rb +1 -1
  403. data/lib/pubid/w3c/renderer.rb +1 -1
  404. data/lib/pubid/w3c/urn_generator.rb +2 -2
  405. data/lib/pubid/w3c.rb +1 -0
  406. data/lib/pubid/xsf/identifier.rb +9 -5
  407. data/lib/pubid/xsf/parser.rb +17 -4
  408. data/lib/pubid/xsf.rb +7 -1
  409. data/lib/pubid.rb +399 -21
  410. data/lib/tasks/conformance.rake +34 -0
  411. data/lib/tasks/docs.rake +3 -3
  412. data/lib/tasks/schema.rake +88 -0
  413. data/schema/core/joint_prefixes.yaml +24 -0
  414. data/schema/iec.yaml +1017 -0
  415. data/schema/iso.yaml +1586 -0
  416. data/schema/schema.schema.yaml +96 -0
  417. metadata +38 -17
  418. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  419. data/archived-gems/pubid-iec/stages.yaml +0 -129
  420. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  421. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  422. data/archived-gems/pubid-iso/stages.yaml +0 -106
  423. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  424. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  425. data/archived-gems/pubid-itu/series.yaml +0 -42
  426. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  427. data/archived-gems/pubid-nist/series.yaml +0 -121
  428. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  429. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  430. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  431. data/lib/pubid/iec/components/code.rb +0 -36
  432. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -39,7 +39,7 @@ module Pubid
39
39
  Builder.new.build(parsed)
40
40
  end
41
41
 
42
- def to_s(date_format: nil, trademark: false)
42
+ def to_s(date_format: nil, trademark: false, **opts)
43
43
  result = [publisher]
44
44
  result << type if type
45
45
  result << code.to_s if code
@@ -81,7 +81,7 @@ module Pubid
81
81
  # document number/filename downstream). It stays reachable on
82
82
  # `relationships`.
83
83
 
84
- base
84
+ annotate_plain_render(base, **opts)
85
85
  end
86
86
  end
87
87
  end
@@ -7,7 +7,7 @@ module Pubid
7
7
  module Aiee
8
8
  # Parser for AIEE (American Institute of Electrical Engineers) identifiers
9
9
  # Historical period: 1884-1963 (merged into IEEE in 1963)
10
- class Parser < Parslet::Parser
10
+ class Parser < ::Pubid::Parser::Grammar
11
11
  # Basic building blocks
12
12
  rule(:space) { str(" ") }
13
13
  rule(:space?) { space.maybe }
@@ -290,6 +290,16 @@ module Pubid
290
290
  return build_interpretation_supplement(parsed_hash)
291
291
  end
292
292
 
293
+ # The same document with a TRAILING DATE parses FLAT — no :base subtree,
294
+ # just an :interpretation marker beside the number — so the branch above
295
+ # skipped it and determine_identifier_class built a base-less
296
+ # InterpretationIdentifier that dropped the number, the part and the
297
+ # publisher: "IEEE Std 1003.2-1992/INT, Dec. 1994 Ed." rendered as
298
+ # " 1003.2-1992/INT". Mirrors build_flat_corrigendum / build_flat_amendment.
299
+ if parsed_hash[:interpretation] && !parsed_hash[:base]
300
+ return build_flat_interpretation(parsed_hash)
301
+ end
302
+
293
303
  # Handle conformance supplements (check for base + conf_number)
294
304
  if parsed_hash[:base] && parsed_hash[:conf_number]
295
305
  return build_conformance_supplement(parsed_hash)
@@ -432,6 +442,35 @@ module Pubid
432
442
  )
433
443
  end
434
444
 
445
+ # Build an interpretation from a FLAT parse tree — the trailing-date form,
446
+ # where the :interpretation marker sits beside the base's own keys instead
447
+ # of over a :base subtree.
448
+ #
449
+ # The trailing date is the INTERPRETATION's date (an interpretation of the
450
+ # 1992 edition of 1003.2, issued in Dec 1994), so it goes on the wrapper;
451
+ # the base keeps its own `year`. That matches the nested form, whose
452
+ # `int_year` also lands on the wrapper.
453
+ # @param parsed_hash [Hash] flat parsed data carrying :interpretation
454
+ # @return [Identifiers::InterpretationIdentifier]
455
+ def build_flat_interpretation(parsed_hash)
456
+ int_year = extract_value(parsed_hash[:trailing_year]) if parsed_hash[:trailing_year]
457
+
458
+ base_hash = parsed_hash.reject do |key, _|
459
+ %i[interpretation trailing_month trailing_year].include?(key)
460
+ end
461
+ base = build_single_identifier(base_hash)
462
+
463
+ # The trailing MONTH is deliberately dropped, matching the nested form
464
+ # (whose `int_year` carries no month either). It was not preserved
465
+ # before this change, so nothing is lost; and setting it fed the URN's
466
+ # month_day_component, which put "Dec." — a word with a dot — into a
467
+ # URN segment.
468
+ Identifiers::InterpretationIdentifier.new(
469
+ base: base,
470
+ year: int_year,
471
+ )
472
+ end
473
+
435
474
  # Build amendment supplement with recursive base parsing. Mirrors
436
475
  # {#build_corrigendum_supplement} — reconstructs the base identifier
437
476
  # string, recursively parses it, and wraps it in an Amendment.
@@ -770,9 +809,14 @@ module Pubid
770
809
  # Pattern: slash >> "C" >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits
771
810
  # The parsed crossref is a string - extract the number part
772
811
  # Format: C62.22.1-1996 where C is part of the crossref notation
812
+ # The captured slice keeps the leading slash, which is a SEPARATOR in
813
+ # the printed reference, not part of the number. Storing it made the
814
+ # secondary's code "/C62.22.1-1996", which then failed the renderer's
815
+ # own cross-reference test (`/^C\d+\./`) and fell through to the
816
+ # " and " join. Strip it.
773
817
  secondary_id = Identifiers::Standard.new(
774
818
  publisher: "IEEE",
775
- number: extract_value(crossref),
819
+ number: extract_value(crossref).to_s.sub(%r{\A/}, ""),
776
820
  )
777
821
  elsif parsed[:secondary_joint]
778
822
  # Joint standard format: ", Std 1177-1989"
@@ -30,6 +30,38 @@ module Pubid
30
30
  ieee_identifier&.publisher || "IEEE"
31
31
  end
32
32
 
33
+ # An adoption names one document by two designations and carries no
34
+ # number of its own, so `root.number` — the key relaton-index sorts and
35
+ # bsearches on — was "". Walk to the IEEE designation, which is the one
36
+ # IEEE files the document under, and which is also the source of truth
37
+ # for `publisher` above.
38
+ def root
39
+ ieee_identifier ? ieee_identifier.root : self
40
+ end
41
+
42
+ # The document number must reach the URN and the MR slug too, not only
43
+ # `#root` — the project rule that an identity-bearing marker reaches
44
+ # every identity surface. `UrnGenerator#code_component` and the base
45
+ # `mr_number_with_part` both read `code_obj`, which a wrapper does not
46
+ # have, so all 66 adoptions collapsed onto 5 URNs and 5 slugs — and
47
+ # `to_slug` is an output FILENAME, so that is 66 documents overwriting
48
+ # each other. Delegate to the IEEE designation, as `#root` does.
49
+ #
50
+ # `code_obj` is a plain attr_accessor on the base, not a lutaml
51
+ # attribute, so overriding it as a method is safe here.
52
+ def code_obj
53
+ ieee_identifier&.code_obj
54
+ end
55
+
56
+ # Likewise the year: without it "AIEE No 15-1944" and "AIEE No 15-1959"
57
+ # — two different documents — produced the same slug "aiee.std.15".
58
+ # This is the `mr_year` HOOK, not a `#year` method: `year` is a lutaml
59
+ # attribute on the base, and a method of that name would shadow the
60
+ # generated accessor (the CLAUDE.md landmine).
61
+ def mr_year
62
+ (year || ieee_identifier&.year)&.to_s
63
+ end
64
+
33
65
  # `publisher` is *derived* from `ieee_identifier` (the serialized source
34
66
  # of truth), so it must not be serialized. Emitting it breaks the
35
67
  # canonical round-trip: the derived value is default-omitted on the parse
@@ -15,7 +15,9 @@ module Pubid
15
15
  # @return [String] URN representation
16
16
 
17
17
  attribute :publisher, :string, default: -> { "IEEE" }
18
- attribute :copublisher, :string, collection: true # IEC, ISO, ANSI, etc.
18
+ # IEC, ISO, ANSI, etc. `initialize_empty` makes from_hash give `[]`, the
19
+ # value a parsed identifier holds, so the two paths stay `==`.
20
+ attribute :copublisher, :string, collection: true, initialize_empty: true
19
21
  # NB: there is deliberately NO `attribute :code`. IEEE's document code is
20
22
  # serialized as the split index columns (number/prefix/parts/separator) on
21
23
  # concrete leaf types via the CodeNumber mixin; `code`/`code_obj` remain a
@@ -72,8 +74,19 @@ module Pubid
72
74
  # what the base `Pubid::Identifier#exclude`/matching machinery uses when it
73
75
  # rebuilds via `self.class.new(attrs)`; without it, `exclude`/`matches?`
74
76
  # raised ArgumentError for every IEEE identifier.
77
+ # Constructor parameters that are not lutaml attributes: `code` is
78
+ # runtime-only (kept as `code_obj`), and the draft forms are turned into
79
+ # component objects below.
80
+ def self.extra_init_keys
81
+ %i[code draft draft_obj]
82
+ end
83
+
75
84
  def initialize(args = {}, **kwargs)
76
85
  args = args.merge(kwargs) unless kwargs.empty?
86
+ # This override calls `super()` with no attributes and assigns through
87
+ # setters instead, so it bypasses the base constructor's coercion and
88
+ # unknown-key check. Apply them here to keep the cross-flavor contract.
89
+ args = self.class.normalize_init_attributes(args)
77
90
  super()
78
91
 
79
92
  # Handle typed_stage if provided
@@ -104,13 +117,26 @@ module Pubid
104
117
  self.draft = args[:draft].to_s
105
118
  end
106
119
 
107
- # Set other attributes
120
+ # Set other attributes.
121
+ #
122
+ # A key the class accepts but does not declare as a lutaml attribute
123
+ # (see `extra_init_keys`) is assigned through its plain accessor.
124
+ # Without that, `CsaDualPublished#csa_identifier` — an `attr_accessor`
125
+ # by design, "stored as-is (not a Lutaml model type)" — was never
126
+ # assigned at all, and `renderer.rb` read nil from it:
127
+ # `IEEE Std 844.1-2017/CSA C22.2 No. 293.1-17` rendered back as
128
+ # `IEEE Std 844.1-2017/CSA`, losing the CSA designation entirely.
108
129
  attrs = self.class.attributes
130
+ extra = self.class.extra_init_keys
109
131
  args.each do |key, value|
110
132
  next if %i[code draft draft_obj typed_stage].include?(key)
111
133
 
112
134
  setter = :"#{key}="
113
- public_send(setter, value) if attrs.key?(key)
135
+ if attrs.key?(key)
136
+ public_send(setter, value)
137
+ elsif extra.include?(key) && respond_to?(setter)
138
+ public_send(setter, value)
139
+ end
114
140
  end
115
141
  end
116
142
 
@@ -208,8 +234,13 @@ module Pubid
208
234
  # orchestrator that consumes a PreParser::Result and routes to the
209
235
  # correct builder.
210
236
  def self.parse(input)
237
+ unless input.is_a?(String)
238
+ raise Pubid::Errors::InvalidInputError,
239
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
240
+ end
241
+
211
242
  if input.length > Pubid::MAX_INPUT_LENGTH
212
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
243
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
213
244
  end
214
245
 
215
246
  result = PreParser.preprocess(input)
@@ -275,9 +306,15 @@ module Pubid
275
306
  def self.build_aiee_asa_adoption(parts)
276
307
  aiee_id = parse_single(parts[0])
277
308
  asa_id = parse_single(parts[1])
309
+ # `adopted_identifierS` — AdoptedStandard declares a collection. The
310
+ # singular spelling was not an attribute, so lutaml dropped it without
311
+ # a word and the ASA half of these 13 identifiers was never stored:
312
+ # `AIEE No 18-1934 (ASA C55 1934)` rendered back as `AIEE No 18-1934`.
313
+ # The unknown-key contract turned that silent loss into a raise, which
314
+ # is how it was found.
278
315
  Identifiers::AdoptedStandard.new(
279
316
  ieee_identifier: aiee_id,
280
- adopted_identifier: asa_id,
317
+ adopted_identifiers: [asa_id],
281
318
  )
282
319
  end
283
320
  private_class_method :build_aiee_asa_adoption
@@ -11,6 +11,27 @@ module Pubid
11
11
  # CSA identifier is stored as-is (not a Lutaml model type)
12
12
  attr_accessor :csa_identifier
13
13
 
14
+ # `csa_identifier` is a real constructor parameter even though it is
15
+ # not a lutaml attribute, so the unknown-key contract must let it
16
+ # through — and the constructor must actually assign it.
17
+ def self.extra_init_keys
18
+ super + %i[csa_identifier]
19
+ end
20
+
21
+ # Walk to the IEEE designation for the relaton-index key: this wrapper
22
+ # names one document by an IEEE and a CSA number and holds neither
23
+ # itself, so `root.number` was "".
24
+ def root
25
+ ieee_identifier ? ieee_identifier.root : self
26
+ end
27
+
28
+ # Carry the number onto the URN and the MR slug as well as #root — see
29
+ # AdoptedStandard#code_obj. `#code` below delegates already, but the
30
+ # URN generator and the MR renderer both read `code_obj`.
31
+ def code_obj
32
+ ieee_identifier&.code_obj
33
+ end
34
+
14
35
  # Delegate common attributes to ieee_identifier
15
36
  def publisher
16
37
  ieee_identifier.publisher
@@ -18,6 +18,61 @@ module Pubid
18
18
  # Return array of both publishers
19
19
  [first_identifier&.publisher, second_identifier&.publisher].compact
20
20
  end
21
+
22
+ # `publisher` above is DERIVED from the two member identifiers (the
23
+ # serialized source of truth), so it must not be serialized itself.
24
+ # Emitting it breaks the canonical round-trip exactly as it did on
25
+ # Identifiers::AdoptedStandard and Identifiers::JointDevelopment: the
26
+ # derived value is default-omitted on the parse path (the attribute is
27
+ # unset), but re-emitted once from_hash materializes the attribute
28
+ # default, because the override then returns the members' publishers
29
+ # rather than the "IEEE" default. Worse here, it is an ARRAY, so the
30
+ # re-emitted value was the literal string `["IEEE", "IEEE"]`.
31
+ #
32
+ # This made all 25 DualPublished ids in the fixture corpus fail
33
+ # `from_hash(to_hash) == to_hash` — the gate relaton's index build
34
+ # uses, which SKIPS a document that fails it. Dropping the key is
35
+ # lossless: first_identifier and second_identifier rebuild it.
36
+ #
37
+ # A top-level to_hash delete is sufficient because DualPublished is
38
+ # only ever a top-level wrapper, never a nested `base` (where lutaml's
39
+ # own transform would bypass this override).
40
+ def to_hash(*args)
41
+ hash = super
42
+ hash.delete("publisher") if hash.is_a?(::Hash)
43
+ hash
44
+ end
45
+
46
+ # A dual-published document is one document under two designations, and
47
+ # this wrapper carries neither number itself — so `root.number`, the key
48
+ # relaton-index sorts and bsearches on, was "". Walk to the first
49
+ # designation, which is the one the printed reference leads with.
50
+ def root
51
+ first_identifier ? first_identifier.root : self
52
+ end
53
+
54
+ # Carry the number onto the URN and the MR slug as well as #root — see
55
+ # AdoptedStandard#code_obj for why all three surfaces need it.
56
+ def code_obj
57
+ first_identifier&.code_obj
58
+ end
59
+
60
+ # See AdoptedStandard#mr_year — without it the 1989 and the 1993
61
+ # printings of "IEEE Std 960 and IEEE Std 1177" shared one slug.
62
+ def mr_year
63
+ (year || first_identifier&.year)&.to_s
64
+ end
65
+
66
+ # `#publisher` above returns an ARRAY, and the inherited `mr_publisher`
67
+ # is `publisher&.to_s&.downcase` — so the MR slug contained a literal
68
+ # Ruby array (`["ieee", "asme"]`, brackets, quotes, comma and space
69
+ # included) and `to_slug` is an output FILENAME. Join the two instead,
70
+ # keeping both publishers in the slug: they are equally part of this
71
+ # document's identity, so by the project rule both must reach it.
72
+ def mr_publisher
73
+ names = Array(publisher).map { |p| p.to_s.downcase }.reject(&:empty?)
74
+ names.empty? ? nil : names.join("-")
75
+ end
21
76
  end
22
77
  end
23
78
  end
@@ -10,6 +10,29 @@ module Pubid
10
10
  # Uniform `year` (the interpretation year), not `int_year` — inherited
11
11
  # from the base, so no attribute is declared here.
12
12
 
13
+ # The interpreted standard's code must reach the URN. Before this
14
+ # change the flat-parsed form kept its number in the wrapper's own
15
+ # `code_obj` (the builder never built a base), so the URN carried it by
16
+ # accident; now that the number lives on `base` where it belongs, the
17
+ # URN generator has to be pointed at it or the URN silently loses the
18
+ # document number. `code_obj` is a plain attr_accessor, not a lutaml
19
+ # attribute, so overriding it as a method is safe.
20
+ def code_obj
21
+ super || base&.code_obj
22
+ end
23
+
24
+ # MR supplement suffix: `int.{year}` — mirrors Corrigendum/Amendment.
25
+ # Without it the MrString renderer slugs an interpretation FLAT off
26
+ # attributes a supplement does not have, instead of recursing into
27
+ # `base`, so every interpretation of every standard collapsed onto
28
+ # "ieee.std.{year}" with no document number at all — and `to_slug` is
29
+ # an output FILENAME.
30
+ def mr_supplement_suffix
31
+ segments = ["int"]
32
+ segments << year.to_s if year
33
+ segments.join(".")
34
+ end
35
+
13
36
  # TYPED_STAGES for interpretation
14
37
  # Interpretation uses "INT" abbreviation
15
38
  TYPED_STAGES = [
@@ -93,7 +93,9 @@ module Pubid
93
93
  # @param format [Symbol] :ieee or :iso (defaults to canonical_format)
94
94
  # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
95
95
  # @return [String] formatted identifier
96
- def to_s(format: canonical_format, trademark: false)
96
+ # `**_opts` absorbs render flags this list does not name (`annotated:`),
97
+ # which the closed keyword list used to reject outright.
98
+ def to_s(format: canonical_format, trademark: false, **opts)
97
99
  # The mark goes after the code number, before the draft and the year
98
100
  # ("ISO/IEC/IEEE P26511™/D8-2018"), so it is threaded into the
99
101
  # format builders rather than appended to the finished string.
@@ -105,12 +107,14 @@ module Pubid
105
107
  ""
106
108
  end
107
109
 
108
- case format
109
- when :iso
110
- to_iso_format(mark)
111
- else
112
- to_ieee_format(mark)
113
- end
110
+ result = case format
111
+ when :iso
112
+ to_iso_format(mark)
113
+ else
114
+ to_ieee_format(mark)
115
+ end
116
+
117
+ annotate_plain_render(result, **opts)
114
118
  end
115
119
 
116
120
  private
@@ -9,12 +9,41 @@ module Pubid
9
9
  # - "IEEE Std 1299/C62.22.1-1996" (same document, two numbers)
10
10
  # - "IEEE Std 960-1989, Std 1177-1989" (same document, two numbers)
11
11
  class MultiNumberedIdentifier < Identifier
12
- # Primary identifier (main IEEE standard number)
13
- # Stored as-is (Lutaml model type checking doesn't work with polymorphic identifiers)
14
- attr_accessor :primary_identifier
12
+ # Both designations are real lutaml attributes.
13
+ #
14
+ # They used to be `attr_accessor`s, which lutaml cannot see — so
15
+ # `to_hash` emitted {"_type" => "pubid:ieee:multi-numbered-identifier"}
16
+ # and NOTHING else, `from_hash` could not rebuild either member, and
17
+ # `#exclude` (which iterates self.class.attributes) skipped them. The
18
+ # whole identity was dropped. That is the CSA container defect recorded
19
+ # in CLAUDE.md, and the fix is the same: make the members attributes.
20
+ #
21
+ # The type is the CROSS-FLAVOR ::Pubid::Identifier, not IEEE's own:
22
+ # `polymorphic: true` widens only to subclasses, and the second
23
+ # designation may belong to another body — the same reason
24
+ # AdoptedStandard's members carry that type.
25
+ attribute :primary_identifier, ::Pubid::Identifier, polymorphic: true
26
+ attribute :secondary_identifier, ::Pubid::Identifier, polymorphic: true
15
27
 
16
- # Secondary identifier (additional number for same document)
17
- attr_accessor :secondary_identifier
28
+ # One document under two numbers, neither of them held here, so
29
+ # `root.number` was "". Walk to the primary designation.
30
+ def root
31
+ primary_identifier ? primary_identifier.root : self
32
+ end
33
+
34
+ # Carry the number onto the URN and the MR slug as well as #root — see
35
+ # AdoptedStandard#code_obj.
36
+ def code_obj
37
+ primary_identifier&.code_obj
38
+ end
39
+
40
+ # See AdoptedStandard#mr_year. `year` below already delegates, so this
41
+ # only stringifies — it is kept explicit so the hook survives if that
42
+ # delegation is ever removed in favour of the URN generator's
43
+ # `year_component` root fallback.
44
+ def mr_year
45
+ year&.to_s
46
+ end
18
47
 
19
48
  def publisher
20
49
  primary_identifier&.publisher
@@ -27,6 +56,29 @@ module Pubid
27
56
  def year
28
57
  primary_identifier&.year
29
58
  end
59
+
60
+ # `publisher` and `year` are DERIVED from primary_identifier — and,
61
+ # since that member became a serialized attribute above, the derived
62
+ # values are now re-emitted after from_hash materializes the attribute
63
+ # defaults, while the parse path omits them as unset. That asymmetry
64
+ # breaks `from_hash(to_hash) == to_hash`, the gate relaton's index
65
+ # build uses to decide whether to keep a document.
66
+ #
67
+ # It is latent rather than live for the one corpus id (its primary is
68
+ # IEEE-published with no year, so both derived values match their
69
+ # defaults and are dropped anyway), but the class documents that its
70
+ # members may belong to another body — and a synthetic AIEE primary
71
+ # reproduces it immediately. Dropping the keys is lossless:
72
+ # primary_identifier rebuilds both. Same treatment as
73
+ # AdoptedStandard#to_hash and DualPublished#to_hash.
74
+ def to_hash(*args)
75
+ hash = super
76
+ return hash unless hash.is_a?(::Hash)
77
+
78
+ hash.delete("publisher")
79
+ hash.delete("year")
80
+ hash
81
+ end
30
82
  end
31
83
  end
32
84
  end
@@ -88,10 +88,10 @@ module Pubid
88
88
  #
89
89
  # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
90
90
  # @return [String] String representation
91
- def to_s(trademark: false)
91
+ def to_s(trademark: false, **opts)
92
92
  result = ["IEEE Std", year, name_portion].compact.join(" ")
93
93
  result += trademark_symbol if trademark
94
- result
94
+ annotate_plain_render(result, **opts)
95
95
  end
96
96
 
97
97
  # Trademark symbol for this document's own code. A NESC string starts
@@ -29,12 +29,12 @@ module Pubid
29
29
  #
30
30
  # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
31
31
  # @return [String] Draft format with optional month and year
32
- def to_s(trademark: false)
32
+ def to_s(trademark: false, **opts)
33
33
  parts = ["Draft National Electrical Safety Code"]
34
34
  parts << ", #{month} #{year}" if month && year
35
35
  result = parts.join
36
36
  result += trademark_symbol if trademark
37
- result
37
+ annotate_plain_render(result, **opts)
38
38
  end
39
39
  end
40
40
  end
@@ -27,14 +27,14 @@ module Pubid
27
27
  #
28
28
  # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
29
29
  # @return [String] YYYY NESC Handbook format with optional edition
30
- def to_s(trademark: false)
30
+ def to_s(trademark: false, **opts)
31
31
  abbr = "NESC"
32
32
  abbr += "(R)" if registered
33
33
  parts = [["IEEE Std", year, "#{abbr} Handbook"].compact.join(" ")]
34
34
  parts << ", #{edition}" if edition
35
35
  result = parts.join
36
36
  result += trademark_symbol if trademark
37
- result
37
+ annotate_plain_render(result, **opts)
38
38
  end
39
39
  end
40
40
  end
@@ -23,10 +23,10 @@ module Pubid
23
23
  #
24
24
  # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
25
25
  # @return [String] YYYY NESC Redline format
26
- def to_s(trademark: false)
26
+ def to_s(trademark: false, **opts)
27
27
  result = [year, "NESC Redline"].compact.join(" ")
28
28
  result += trademark_symbol if trademark
29
- result
29
+ annotate_plain_render(result, **opts)
30
30
  end
31
31
  end
32
32
  end
@@ -26,13 +26,13 @@ module Pubid
26
26
  # @param trademark [Boolean] append the IEEE trademark symbol (™/®)
27
27
  # @return [String] C2-YYYY format (bare "C2" when the year is absent,
28
28
  # e.g. a partial reference produced by `#exclude(:year)`)
29
- def to_s(trademark: false)
29
+ def to_s(trademark: false, **opts)
30
30
  # Unlike the year-first NESC editions, this form renders a code, so
31
31
  # the mark goes after it and before the year — as everywhere else.
32
32
  code_part = "C2"
33
33
  code_part += trademark_symbol if trademark
34
34
  code_part += "-#{year}" if year
35
- "#{code_part} National Electrical Safety Code"
35
+ annotate_plain_render("#{code_part} National Electrical Safety Code", **opts)
36
36
  end
37
37
  end
38
38
  end
@@ -44,7 +44,7 @@ module Pubid
44
44
  builder.build(parsed)
45
45
  end
46
46
 
47
- def to_s(trademark: false)
47
+ def to_s(trademark: false, **opts)
48
48
  # Year comes FIRST in IRE format - render as 2-digit short year
49
49
  result = [(short_year if year), publisher, type, code&.to_s]
50
50
  .compact.join(" ")
@@ -56,7 +56,7 @@ module Pubid
56
56
  code_obj&.prefix,
57
57
  publishers: [publisher])
58
58
  end
59
- result
59
+ annotate_plain_render(result, **opts)
60
60
  end
61
61
 
62
62
  private
@@ -7,7 +7,7 @@ module Pubid
7
7
  module Ire
8
8
  # Parser for IRE (Institute of Radio Engineers) identifiers
9
9
  # Historical period: 1912-1963 (merged into IEEE in 1963)
10
- class Parser < Parslet::Parser
10
+ class Parser < ::Pubid::Parser::Grammar
11
11
  # Basic building blocks
12
12
  rule(:space) { str(" ") }
13
13
  rule(:space?) { space.maybe }
@@ -20,7 +20,7 @@ module Pubid
20
20
  # @example Parse handbook
21
21
  # parser = Parser.new
22
22
  # result = parser.parse("2017 NESC Handbook, Premier Edition")
23
- class Parser < Parslet::Parser
23
+ class Parser < ::Pubid::Parser::Grammar
24
24
  root(:nesc_identifier)
25
25
 
26
26
  rule(:space) { str(" ") }
@@ -7,7 +7,7 @@ module Pubid
7
7
  # Parser class for IEEE identifiers
8
8
  # Single Responsibility: Parsing IEEE identifier syntax
9
9
  # Note: IEEE is extremely complex with many edge cases
10
- class Parser < Parslet::Parser
10
+ class Parser < ::Pubid::Parser::Grammar
11
11
  # Basic building blocks
12
12
  rule(:space) { str(" ") }
13
13
  rule(:space?) { space.maybe }
@@ -320,7 +320,11 @@ module Pubid
320
320
 
321
321
  secondary_code = id.secondary_identifier.code.to_s
322
322
  if secondary_code.start_with?("C") && secondary_code.match?(/^C\d+\./)
323
- "#{primary}/#{secondary}"
323
+ # A cross-reference prints the bare second designation after the
324
+ # slash — "IEEE Std 1299/C62.22.1-1996" — not a second full
325
+ # reference, so this branch must not reuse `secondary`, which
326
+ # carries its own "IEEE Std " prefix.
327
+ "#{primary}/#{secondary_code}"
324
328
  else
325
329
  "#{primary} and #{secondary}"
326
330
  end