pubid 1.15.21 → 2.0.0.alpha.10

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 (863) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.adoc +2044 -53
  4. data/archived-gems/pubid-ccsds/update_codes.yaml +1 -0
  5. data/archived-gems/pubid-iec/stages.yaml +129 -0
  6. data/archived-gems/pubid-iec/update_codes.yaml +67 -0
  7. data/archived-gems/pubid-ieee/update_codes.yaml +104 -0
  8. data/archived-gems/pubid-iso/stages.yaml +106 -0
  9. data/archived-gems/pubid-iso/update_codes.yaml +4 -0
  10. data/archived-gems/pubid-itu/i18n.yaml +13 -0
  11. data/archived-gems/pubid-itu/series.yaml +42 -0
  12. data/archived-gems/pubid-nist/publishers.yaml +6 -0
  13. data/archived-gems/pubid-nist/series.yaml +121 -0
  14. data/archived-gems/pubid-nist/stages.yaml +16 -0
  15. data/archived-gems/pubid-nist/update_codes.yaml +93 -0
  16. data/archived-gems/pubid-plateau/update_codes.yaml +6 -0
  17. data/data/bipm/update_codes.yaml +9 -0
  18. data/data/ccsds/update_codes.yaml +1 -0
  19. data/data/iec/update_codes.yaml +70 -0
  20. data/data/ieee/update_codes.yaml +210 -0
  21. data/data/iso/update_codes.yaml +21 -0
  22. data/data/nist/series.yaml +155 -0
  23. data/data/nist/update_codes.yaml +122 -0
  24. data/data/plateau/update_codes.yaml +6 -0
  25. data/lib/pubid/adobe/builder.rb +52 -0
  26. data/lib/pubid/adobe/identifier.rb +47 -0
  27. data/lib/pubid/adobe/identifiers/publication.rb +31 -0
  28. data/lib/pubid/adobe/identifiers/tech_note.rb +32 -0
  29. data/lib/pubid/adobe/identifiers.rb +10 -0
  30. data/lib/pubid/adobe/parser.rb +136 -0
  31. data/lib/pubid/adobe/renderer.rb +39 -0
  32. data/lib/pubid/adobe/urn_generator.rb +42 -0
  33. data/lib/pubid/adobe/urn_parser.rb +69 -0
  34. data/lib/pubid/adobe.rb +60 -0
  35. data/lib/pubid/amca/builder.rb +176 -0
  36. data/lib/pubid/amca/identifier.rb +4 -0
  37. data/lib/pubid/amca/identifiers/base.rb +92 -0
  38. data/lib/pubid/amca/identifiers/interpretation.rb +38 -0
  39. data/lib/pubid/amca/identifiers/publication.rb +34 -0
  40. data/lib/pubid/amca/identifiers/standard.rb +20 -0
  41. data/lib/pubid/amca/identifiers.rb +11 -0
  42. data/lib/pubid/amca/parser.rb +153 -0
  43. data/lib/pubid/amca/renderer.rb +82 -0
  44. data/lib/pubid/amca/single_identifier.rb +10 -0
  45. data/lib/pubid/amca/urn_generator.rb +51 -0
  46. data/lib/pubid/amca/urn_parser.rb +28 -0
  47. data/lib/pubid/amca.rb +73 -0
  48. data/lib/pubid/ansi/builder.rb +54 -0
  49. data/lib/pubid/ansi/identifier.rb +31 -0
  50. data/lib/pubid/ansi/identifiers/american_national_standard.rb +13 -0
  51. data/lib/pubid/ansi/identifiers/standard.rb +15 -0
  52. data/lib/pubid/ansi/identifiers.rb +11 -0
  53. data/lib/pubid/ansi/parser.rb +91 -0
  54. data/lib/pubid/ansi/renderer.rb +53 -0
  55. data/lib/pubid/ansi/single_identifier.rb +16 -0
  56. data/lib/pubid/ansi/urn_generator.rb +41 -0
  57. data/lib/pubid/ansi/urn_parser.rb +23 -0
  58. data/lib/pubid/ansi.rb +68 -0
  59. data/lib/pubid/api/builder.rb +58 -0
  60. data/lib/pubid/api/components/code.rb +9 -0
  61. data/lib/pubid/api/identifier.rb +42 -0
  62. data/lib/pubid/api/identifiers/base.rb +12 -0
  63. data/lib/pubid/api/identifiers/bulletin.rb +13 -0
  64. data/lib/pubid/api/identifiers/continuous_operations_standard.rb +13 -0
  65. data/lib/pubid/api/identifiers/mpms.rb +26 -0
  66. data/lib/pubid/api/identifiers/publication.rb +13 -0
  67. data/lib/pubid/api/identifiers/recommended_practice.rb +13 -0
  68. data/lib/pubid/api/identifiers/specification.rb +13 -0
  69. data/lib/pubid/api/identifiers/standard.rb +13 -0
  70. data/lib/pubid/api/identifiers/technical_report.rb +13 -0
  71. data/lib/pubid/api/identifiers/typeless_standard.rb +11 -0
  72. data/lib/pubid/api/identifiers.rb +18 -0
  73. data/lib/pubid/api/parser.rb +140 -0
  74. data/lib/pubid/api/renderer.rb +98 -0
  75. data/lib/pubid/api/single_identifier.rb +24 -0
  76. data/lib/pubid/api/urn_generator.rb +23 -0
  77. data/lib/pubid/api/urn_parser.rb +35 -0
  78. data/lib/pubid/api.rb +68 -0
  79. data/lib/pubid/ashrae/builder.rb +519 -0
  80. data/lib/pubid/ashrae/identifier.rb +8 -0
  81. data/lib/pubid/ashrae/identifiers/addenda_package.rb +42 -0
  82. data/lib/pubid/ashrae/identifiers/addendum.rb +44 -0
  83. data/lib/pubid/ashrae/identifiers/base.rb +119 -0
  84. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +43 -0
  85. data/lib/pubid/ashrae/identifiers/errata.rb +39 -0
  86. data/lib/pubid/ashrae/identifiers/guideline.rb +41 -0
  87. data/lib/pubid/ashrae/identifiers/interpretation.rb +45 -0
  88. data/lib/pubid/ashrae/identifiers/standard.rb +41 -0
  89. data/lib/pubid/ashrae/identifiers.rb +15 -0
  90. data/lib/pubid/ashrae/parser.rb +748 -0
  91. data/lib/pubid/ashrae/renderer.rb +117 -0
  92. data/lib/pubid/ashrae/single_identifier.rb +10 -0
  93. data/lib/pubid/ashrae/supplement_identifier.rb +38 -0
  94. data/lib/pubid/ashrae/urn_generator.rb +57 -0
  95. data/lib/pubid/ashrae/urn_parser.rb +27 -0
  96. data/lib/pubid/ashrae.rb +77 -0
  97. data/lib/pubid/asme/builder.rb +162 -0
  98. data/lib/pubid/asme/components/code.rb +26 -0
  99. data/lib/pubid/asme/identifier.rb +24 -0
  100. data/lib/pubid/asme/identifiers/base.rb +10 -0
  101. data/lib/pubid/asme/identifiers/standard.rb +96 -0
  102. data/lib/pubid/asme/identifiers.rb +10 -0
  103. data/lib/pubid/asme/parser.rb +308 -0
  104. data/lib/pubid/asme/renderer.rb +66 -0
  105. data/lib/pubid/asme/single_identifier.rb +38 -0
  106. data/lib/pubid/asme/urn_generator.rb +133 -0
  107. data/lib/pubid/asme/urn_parser.rb +31 -0
  108. data/lib/pubid/asme.rb +67 -0
  109. data/lib/pubid/astm/builder.rb +166 -0
  110. data/lib/pubid/astm/components/code.rb +42 -0
  111. data/lib/pubid/astm/components.rb +9 -0
  112. data/lib/pubid/astm/identifier.rb +24 -0
  113. data/lib/pubid/astm/identifiers/adjunct.rb +28 -0
  114. data/lib/pubid/astm/identifiers/base.rb +13 -0
  115. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  116. data/lib/pubid/astm/identifiers/data_series.rb +13 -0
  117. data/lib/pubid/astm/identifiers/iso_dual_published.rb +65 -0
  118. data/lib/pubid/astm/identifiers/manual.rb +23 -0
  119. data/lib/pubid/astm/identifiers/monograph.rb +13 -0
  120. data/lib/pubid/astm/identifiers/research_report.rb +21 -0
  121. data/lib/pubid/astm/identifiers/standard.rb +23 -0
  122. data/lib/pubid/astm/identifiers/technical_report.rb +12 -0
  123. data/lib/pubid/astm/identifiers/work_in_progress.rb +12 -0
  124. data/lib/pubid/astm/identifiers.rb +19 -0
  125. data/lib/pubid/astm/parser.rb +244 -0
  126. data/lib/pubid/astm/renderer.rb +172 -0
  127. data/lib/pubid/astm/single_identifier.rb +47 -0
  128. data/lib/pubid/astm/urn_generator.rb +101 -0
  129. data/lib/pubid/astm/urn_parser.rb +30 -0
  130. data/lib/pubid/astm.rb +55 -0
  131. data/lib/pubid/bipm/builder.rb +163 -0
  132. data/lib/pubid/bipm/identifier.rb +285 -0
  133. data/lib/pubid/bipm/identifiers/committee_document.rb +36 -0
  134. data/lib/pubid/bipm/identifiers/guide.rb +35 -0
  135. data/lib/pubid/bipm/identifiers/meeting.rb +53 -0
  136. data/lib/pubid/bipm/identifiers/mep.rb +33 -0
  137. data/lib/pubid/bipm/identifiers/metrologia_article.rb +48 -0
  138. data/lib/pubid/bipm/identifiers/si_brochure.rb +33 -0
  139. data/lib/pubid/bipm/identifiers.rb +15 -0
  140. data/lib/pubid/bipm/parser.rb +237 -0
  141. data/lib/pubid/bipm/renderer.rb +118 -0
  142. data/lib/pubid/bipm/urn_generator.rb +58 -0
  143. data/lib/pubid/bipm/urn_parser.rb +60 -0
  144. data/lib/pubid/bipm.rb +91 -0
  145. data/lib/pubid/bsi/builder.rb +1495 -0
  146. data/lib/pubid/bsi/components/code.rb +11 -0
  147. data/lib/pubid/bsi/components/date.rb +11 -0
  148. data/lib/pubid/bsi/components/publisher.rb +11 -0
  149. data/lib/pubid/bsi/components/type.rb +11 -0
  150. data/lib/pubid/bsi/identifier.rb +6 -0
  151. data/lib/pubid/bsi/identifiers/addendum_document.rb +30 -0
  152. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +67 -0
  153. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +51 -0
  154. data/lib/pubid/bsi/identifiers/aerospace_standard.rb +65 -0
  155. data/lib/pubid/bsi/identifiers/amendment.rb +48 -0
  156. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +21 -0
  157. data/lib/pubid/bsi/identifiers/british_standard.rb +34 -0
  158. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +40 -0
  159. data/lib/pubid/bsi/identifiers/committee_document.rb +44 -0
  160. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +84 -0
  161. data/lib/pubid/bsi/identifiers/corrigendum.rb +44 -0
  162. data/lib/pubid/bsi/identifiers/detailed_specification.rb +33 -0
  163. data/lib/pubid/bsi/identifiers/disc.rb +27 -0
  164. data/lib/pubid/bsi/identifiers/draft_document.rb +27 -0
  165. data/lib/pubid/bsi/identifiers/electronic_book.rb +16 -0
  166. data/lib/pubid/bsi/identifiers/expert_commentary.rb +36 -0
  167. data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +35 -0
  168. data/lib/pubid/bsi/identifiers/flex.rb +30 -0
  169. data/lib/pubid/bsi/identifiers/handbook.rb +23 -0
  170. data/lib/pubid/bsi/identifiers/index.rb +30 -0
  171. data/lib/pubid/bsi/identifiers/method.rb +35 -0
  172. data/lib/pubid/bsi/identifiers/national_annex.rb +48 -0
  173. data/lib/pubid/bsi/identifiers/practice_guide.rb +21 -0
  174. data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +27 -0
  175. data/lib/pubid/bsi/identifiers/published_document.rb +27 -0
  176. data/lib/pubid/bsi/identifiers/section.rb +32 -0
  177. data/lib/pubid/bsi/identifiers/set.rb +40 -0
  178. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +40 -0
  179. data/lib/pubid/bsi/identifiers/supplement_document.rb +29 -0
  180. data/lib/pubid/bsi/identifiers/supplementary_index.rb +35 -0
  181. data/lib/pubid/bsi/identifiers/technical_specification.rb +34 -0
  182. data/lib/pubid/bsi/identifiers/test_method.rb +35 -0
  183. data/lib/pubid/bsi/identifiers/value_added_publication.rb +37 -0
  184. data/lib/pubid/bsi/identifiers.rb +51 -0
  185. data/lib/pubid/bsi/model.rb +196 -0
  186. data/lib/pubid/bsi/parser.rb +673 -0
  187. data/lib/pubid/bsi/renderer.rb +1064 -0
  188. data/lib/pubid/bsi/single_identifier.rb +141 -0
  189. data/lib/pubid/bsi/urn_generator.rb +92 -0
  190. data/lib/pubid/bsi/urn_parser.rb +52 -0
  191. data/lib/pubid/bsi.rb +282 -0
  192. data/lib/pubid/builder/base.rb +187 -0
  193. data/lib/pubid/bundled_identifier.rb +134 -0
  194. data/lib/pubid/calconnect/builder.rb +38 -0
  195. data/lib/pubid/calconnect/identifier.rb +121 -0
  196. data/lib/pubid/calconnect/identifiers/standard.rb +29 -0
  197. data/lib/pubid/calconnect/identifiers.rb +9 -0
  198. data/lib/pubid/calconnect/parser.rb +54 -0
  199. data/lib/pubid/calconnect/renderer.rb +36 -0
  200. data/lib/pubid/calconnect/urn_generator.rb +33 -0
  201. data/lib/pubid/calconnect/urn_parser.rb +34 -0
  202. data/lib/pubid/calconnect.rb +82 -0
  203. data/lib/pubid/ccsds/builder.rb +57 -0
  204. data/lib/pubid/ccsds/identifier.rb +76 -0
  205. data/lib/pubid/ccsds/identifiers/base.rb +41 -0
  206. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +70 -0
  207. data/lib/pubid/ccsds/identifiers/corrigendum.rb +40 -0
  208. data/lib/pubid/ccsds/identifiers.rb +10 -0
  209. data/lib/pubid/ccsds/parser.rb +73 -0
  210. data/lib/pubid/ccsds/single_identifier.rb +77 -0
  211. data/lib/pubid/ccsds/supplement_identifier.rb +37 -0
  212. data/lib/pubid/ccsds/urn_generator.rb +115 -0
  213. data/lib/pubid/ccsds/urn_parser.rb +20 -0
  214. data/lib/pubid/ccsds.rb +64 -0
  215. data/lib/pubid/cen_cenelec/builder.rb +362 -0
  216. data/lib/pubid/cen_cenelec/identifier.rb +38 -0
  217. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +45 -0
  218. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +19 -0
  219. data/lib/pubid/cen_cenelec/identifiers/base.rb +34 -0
  220. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +29 -0
  221. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +28 -0
  222. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +28 -0
  223. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +40 -0
  224. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +20 -0
  225. data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +99 -0
  226. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +50 -0
  227. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +29 -0
  228. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +18 -0
  229. data/lib/pubid/cen_cenelec/identifiers/guide.rb +27 -0
  230. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +28 -0
  231. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +28 -0
  232. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +36 -0
  233. data/lib/pubid/cen_cenelec/identifiers.rb +32 -0
  234. data/lib/pubid/cen_cenelec/parser.rb +144 -0
  235. data/lib/pubid/cen_cenelec/renderer.rb +261 -0
  236. data/lib/pubid/cen_cenelec/single_identifier.rb +49 -0
  237. data/lib/pubid/cen_cenelec/supplement_identifier.rb +47 -0
  238. data/lib/pubid/cen_cenelec/urn_generator.rb +129 -0
  239. data/lib/pubid/cen_cenelec/urn_parser.rb +28 -0
  240. data/lib/pubid/cen_cenelec.rb +208 -0
  241. data/lib/pubid/cie/builder.rb +524 -0
  242. data/lib/pubid/cie/components/language.rb +58 -0
  243. data/lib/pubid/cie/identifier.rb +33 -0
  244. data/lib/pubid/cie/identifiers/bundle.rb +50 -0
  245. data/lib/pubid/cie/identifiers/code_attributes.rb +52 -0
  246. data/lib/pubid/cie/identifiers/conference.rb +37 -0
  247. data/lib/pubid/cie/identifiers/corrigendum.rb +34 -0
  248. data/lib/pubid/cie/identifiers/dual_published.rb +41 -0
  249. data/lib/pubid/cie/identifiers/identical.rb +64 -0
  250. data/lib/pubid/cie/identifiers/joint_published.rb +52 -0
  251. data/lib/pubid/cie/identifiers/proceedings.rb +41 -0
  252. data/lib/pubid/cie/identifiers/standard.rb +93 -0
  253. data/lib/pubid/cie/identifiers/supplement.rb +47 -0
  254. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +24 -0
  255. data/lib/pubid/cie/identifiers.rb +19 -0
  256. data/lib/pubid/cie/parser.rb +396 -0
  257. data/lib/pubid/cie/single_identifier.rb +69 -0
  258. data/lib/pubid/cie/supplement_identifier.rb +39 -0
  259. data/lib/pubid/cie/urn_generator.rb +126 -0
  260. data/lib/pubid/cie/urn_parser.rb +28 -0
  261. data/lib/pubid/cie.rb +83 -0
  262. data/lib/pubid/components/adoption.rb +104 -0
  263. data/lib/pubid/components/code.rb +47 -0
  264. data/lib/pubid/components/date.rb +72 -0
  265. data/lib/pubid/components/edition.rb +35 -0
  266. data/lib/pubid/components/iteration.rb +32 -0
  267. data/lib/pubid/components/language.rb +39 -0
  268. data/lib/pubid/components/locality.rb +19 -0
  269. data/lib/pubid/components/publisher.rb +39 -0
  270. data/lib/pubid/components/relationship.rb +151 -0
  271. data/lib/pubid/components/stage.rb +45 -0
  272. data/lib/pubid/components/supplement.rb +184 -0
  273. data/lib/pubid/components/type.rb +48 -0
  274. data/lib/pubid/components/typed_stage.rb +58 -0
  275. data/lib/pubid/components.rb +19 -0
  276. data/lib/pubid/core/pattern_doc_generator.rb +272 -0
  277. data/lib/pubid/core/update_codes.rb +98 -0
  278. data/lib/pubid/core.rb +8 -0
  279. data/lib/pubid/csa/builder.rb +671 -0
  280. data/lib/pubid/csa/components/code.rb +9 -0
  281. data/lib/pubid/csa/components.rb +9 -0
  282. data/lib/pubid/csa/composite_identifier.rb +38 -0
  283. data/lib/pubid/csa/identifier.rb +570 -0
  284. data/lib/pubid/csa/identifiers/base.rb +10 -0
  285. data/lib/pubid/csa/identifiers/bundled.rb +141 -0
  286. data/lib/pubid/csa/identifiers/canadian_adopted.rb +82 -0
  287. data/lib/pubid/csa/identifiers/cec.rb +66 -0
  288. data/lib/pubid/csa/identifiers/combined.rb +158 -0
  289. data/lib/pubid/csa/identifiers/csa_adopted.rb +78 -0
  290. data/lib/pubid/csa/identifiers/package.rb +65 -0
  291. data/lib/pubid/csa/identifiers/series.rb +23 -0
  292. data/lib/pubid/csa/identifiers/standard.rb +10 -0
  293. data/lib/pubid/csa/identifiers.rb +17 -0
  294. data/lib/pubid/csa/parser.rb +449 -0
  295. data/lib/pubid/csa/renderer.rb +300 -0
  296. data/lib/pubid/csa/single_identifier.rb +119 -0
  297. data/lib/pubid/csa/urn_generator.rb +87 -0
  298. data/lib/pubid/csa/urn_parser.rb +33 -0
  299. data/lib/pubid/csa/wrapper_identifier.rb +64 -0
  300. data/lib/pubid/csa.rb +80 -0
  301. data/lib/pubid/doi/builder.rb +18 -0
  302. data/lib/pubid/doi/identifier.rb +49 -0
  303. data/lib/pubid/doi/identifiers/resource.rb +30 -0
  304. data/lib/pubid/doi/identifiers.rb +9 -0
  305. data/lib/pubid/doi/parser.rb +52 -0
  306. data/lib/pubid/doi/renderer.rb +13 -0
  307. data/lib/pubid/doi.rb +55 -0
  308. data/lib/pubid/easc/builder.rb +55 -0
  309. data/lib/pubid/easc/identifier.rb +59 -0
  310. data/lib/pubid/easc/identifiers/pmg.rb +20 -0
  311. data/lib/pubid/easc/identifiers/rmg.rb +19 -0
  312. data/lib/pubid/easc/identifiers.rb +10 -0
  313. data/lib/pubid/easc/parser.rb +88 -0
  314. data/lib/pubid/easc/renderer.rb +48 -0
  315. data/lib/pubid/easc/urn_generator.rb +46 -0
  316. data/lib/pubid/easc/urn_parser.rb +57 -0
  317. data/lib/pubid/easc.rb +52 -0
  318. data/lib/pubid/ecma/builder.rb +45 -0
  319. data/lib/pubid/ecma/identifier.rb +147 -0
  320. data/lib/pubid/ecma/identifiers/memento.rb +30 -0
  321. data/lib/pubid/ecma/identifiers/standard.rb +31 -0
  322. data/lib/pubid/ecma/identifiers/technical_report.rb +31 -0
  323. data/lib/pubid/ecma/identifiers.rb +11 -0
  324. data/lib/pubid/ecma/parser.rb +76 -0
  325. data/lib/pubid/ecma/renderer.rb +67 -0
  326. data/lib/pubid/ecma/urn_generator.rb +46 -0
  327. data/lib/pubid/ecma/urn_parser.rb +51 -0
  328. data/lib/pubid/ecma.rb +67 -0
  329. data/lib/pubid/errors.rb +79 -0
  330. data/lib/pubid/etsi/builder.rb +142 -0
  331. data/lib/pubid/etsi/components/code.rb +43 -0
  332. data/lib/pubid/etsi/components/version.rb +32 -0
  333. data/lib/pubid/etsi/components.rb +10 -0
  334. data/lib/pubid/etsi/identifier.rb +4 -0
  335. data/lib/pubid/etsi/identifiers/amendment.rb +15 -0
  336. data/lib/pubid/etsi/identifiers/base.rb +74 -0
  337. data/lib/pubid/etsi/identifiers/corrigendum.rb +15 -0
  338. data/lib/pubid/etsi/identifiers/etsi_standard.rb +222 -0
  339. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +123 -0
  340. data/lib/pubid/etsi/identifiers.rb +13 -0
  341. data/lib/pubid/etsi/parser.rb +139 -0
  342. data/lib/pubid/etsi/renderer.rb +48 -0
  343. data/lib/pubid/etsi/urn_generator.rb +76 -0
  344. data/lib/pubid/etsi/urn_parser.rb +34 -0
  345. data/lib/pubid/etsi.rb +71 -0
  346. data/lib/pubid/export/auditor.rb +89 -0
  347. data/lib/pubid/export/exporter.rb +33 -0
  348. data/lib/pubid/export/flavor_exporter.rb +250 -0
  349. data/lib/pubid/export/result.rb +97 -0
  350. data/lib/pubid/export.rb +12 -0
  351. data/lib/pubid/format_detector.rb +16 -0
  352. data/lib/pubid/format_registry.rb +42 -0
  353. data/lib/pubid/gb/builder.rb +45 -0
  354. data/lib/pubid/gb/identifier.rb +67 -0
  355. data/lib/pubid/gb/identifiers/standard.rb +34 -0
  356. data/lib/pubid/gb/identifiers.rb +9 -0
  357. data/lib/pubid/gb/parser.rb +63 -0
  358. data/lib/pubid/gb/renderer.rb +35 -0
  359. data/lib/pubid/gb.rb +87 -0
  360. data/lib/pubid/gost/builder.rb +148 -0
  361. data/lib/pubid/gost/identifier.rb +47 -0
  362. data/lib/pubid/gost/identifiers/foreign_reference.rb +25 -0
  363. data/lib/pubid/gost/identifiers/harmonized.rb +39 -0
  364. data/lib/pubid/gost/identifiers/identical_adoption.rb +39 -0
  365. data/lib/pubid/gost/identifiers/interstate_standard.rb +20 -0
  366. data/lib/pubid/gost/identifiers/national_standard.rb +19 -0
  367. data/lib/pubid/gost/identifiers.rb +13 -0
  368. data/lib/pubid/gost/parser.rb +105 -0
  369. data/lib/pubid/gost/renderer.rb +48 -0
  370. data/lib/pubid/gost/urn_generator.rb +46 -0
  371. data/lib/pubid/gost/urn_parser.rb +52 -0
  372. data/lib/pubid/gost.rb +49 -0
  373. data/lib/pubid/iala/builder.rb +102 -0
  374. data/lib/pubid/iala/identifier.rb +90 -0
  375. data/lib/pubid/iala/identifiers/advice.rb +15 -0
  376. data/lib/pubid/iala/identifiers/annex.rb +57 -0
  377. data/lib/pubid/iala/identifiers/general_assembly.rb +19 -0
  378. data/lib/pubid/iala/identifiers/guideline.rb +17 -0
  379. data/lib/pubid/iala/identifiers/letter.rb +16 -0
  380. data/lib/pubid/iala/identifiers/manual.rb +18 -0
  381. data/lib/pubid/iala/identifiers/model_course.rb +17 -0
  382. data/lib/pubid/iala/identifiers/recommendation.rb +17 -0
  383. data/lib/pubid/iala/identifiers/report.rb +16 -0
  384. data/lib/pubid/iala/identifiers/resolution.rb +16 -0
  385. data/lib/pubid/iala/identifiers/standard.rb +17 -0
  386. data/lib/pubid/iala/identifiers.rb +19 -0
  387. data/lib/pubid/iala/parser.rb +138 -0
  388. data/lib/pubid/iala/renderer.rb +36 -0
  389. data/lib/pubid/iala/urn_generator.rb +49 -0
  390. data/lib/pubid/iala/urn_parser.rb +67 -0
  391. data/lib/pubid/iala.rb +71 -0
  392. data/lib/pubid/iana/builder.rb +22 -0
  393. data/lib/pubid/iana/identifier.rb +140 -0
  394. data/lib/pubid/iana/identifiers/registry.rb +69 -0
  395. data/lib/pubid/iana/identifiers.rb +9 -0
  396. data/lib/pubid/iana/parser.rb +38 -0
  397. data/lib/pubid/iana/renderer.rb +32 -0
  398. data/lib/pubid/iana/urn_generator.rb +19 -0
  399. data/lib/pubid/iana/urn_parser.rb +24 -0
  400. data/lib/pubid/iana.rb +69 -0
  401. data/lib/pubid/identifier.rb +1118 -0
  402. data/lib/pubid/identifier_metadata.rb +148 -0
  403. data/lib/pubid/idf/builder.rb +82 -0
  404. data/lib/pubid/idf/identifier.rb +79 -0
  405. data/lib/pubid/idf/identifiers/amendment.rb +28 -0
  406. data/lib/pubid/idf/identifiers/corrigendum.rb +28 -0
  407. data/lib/pubid/idf/identifiers/international_standard.rb +124 -0
  408. data/lib/pubid/idf/identifiers/reviewed_method.rb +101 -0
  409. data/lib/pubid/idf/identifiers.rb +13 -0
  410. data/lib/pubid/idf/parser.rb +144 -0
  411. data/lib/pubid/idf/renderer.rb +84 -0
  412. data/lib/pubid/idf/single_identifier.rb +19 -0
  413. data/lib/pubid/idf/supplement_identifier.rb +35 -0
  414. data/lib/pubid/idf/urn_generator.rb +49 -0
  415. data/lib/pubid/idf/urn_parser.rb +25 -0
  416. data/lib/pubid/idf.rb +89 -0
  417. data/lib/pubid/iec/builder.rb +458 -0
  418. data/lib/pubid/iec/components/code.rb +36 -0
  419. data/lib/pubid/iec/components/consolidated_amendment.rb +59 -0
  420. data/lib/pubid/iec/components/publisher.rb +35 -0
  421. data/lib/pubid/iec/components/sheet.rb +32 -0
  422. data/lib/pubid/iec/components/trf_info.rb +38 -0
  423. data/lib/pubid/iec/components/vap_suffix.rb +41 -0
  424. data/lib/pubid/iec/components.rb +14 -0
  425. data/lib/pubid/iec/identifier.rb +341 -0
  426. data/lib/pubid/iec/identifiers/amendment.rb +97 -0
  427. data/lib/pubid/iec/identifiers/base.rb +54 -0
  428. data/lib/pubid/iec/identifiers/component_specification.rb +29 -0
  429. data/lib/pubid/iec/identifiers/conformity_assessment.rb +28 -0
  430. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +85 -0
  431. data/lib/pubid/iec/identifiers/corrigendum.rb +93 -0
  432. data/lib/pubid/iec/identifiers/fragment_identifier.rb +148 -0
  433. data/lib/pubid/iec/identifiers/guide.rb +86 -0
  434. data/lib/pubid/iec/identifiers/international_standard.rb +162 -0
  435. data/lib/pubid/iec/identifiers/interpretation_sheet.rb +103 -0
  436. data/lib/pubid/iec/identifiers/operational_document.rb +29 -0
  437. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +88 -0
  438. data/lib/pubid/iec/identifiers/sheet_identifier.rb +68 -0
  439. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +30 -0
  440. data/lib/pubid/iec/identifiers/systems_reference_document.rb +29 -0
  441. data/lib/pubid/iec/identifiers/technical_group.rb +30 -0
  442. data/lib/pubid/iec/identifiers/technical_report.rb +114 -0
  443. data/lib/pubid/iec/identifiers/technical_specification.rb +114 -0
  444. data/lib/pubid/iec/identifiers/technology_report.rb +28 -0
  445. data/lib/pubid/iec/identifiers/test_report_form.rb +35 -0
  446. data/lib/pubid/iec/identifiers/vap_identifier.rb +76 -0
  447. data/lib/pubid/iec/identifiers/white_paper.rb +29 -0
  448. data/lib/pubid/iec/identifiers/working_document.rb +62 -0
  449. data/lib/pubid/iec/identifiers.rb +31 -0
  450. data/lib/pubid/iec/parser.rb +443 -0
  451. data/lib/pubid/iec/renderer.rb +267 -0
  452. data/lib/pubid/iec/rendering_style.rb +113 -0
  453. data/lib/pubid/iec/single_identifier.rb +72 -0
  454. data/lib/pubid/iec/supplement_identifier.rb +178 -0
  455. data/lib/pubid/iec/urn_generator.rb +245 -0
  456. data/lib/pubid/iec/urn_parser.rb +128 -0
  457. data/lib/pubid/iec.rb +64 -0
  458. data/lib/pubid/ieee/aiee/builder.rb +85 -0
  459. data/lib/pubid/ieee/aiee/identifier.rb +89 -0
  460. data/lib/pubid/ieee/aiee/parser.rb +130 -0
  461. data/lib/pubid/ieee/aiee.rb +11 -0
  462. data/lib/pubid/ieee/builder.rb +1554 -0
  463. data/lib/pubid/ieee/compaction.rb +121 -0
  464. data/lib/pubid/ieee/components/code.rb +110 -0
  465. data/lib/pubid/ieee/components/draft.rb +147 -0
  466. data/lib/pubid/ieee/components/relationship.rb +13 -0
  467. data/lib/pubid/ieee/components/typed_stage.rb +100 -0
  468. data/lib/pubid/ieee/identifier.rb +5 -0
  469. data/lib/pubid/ieee/identifiers/adopted_standard.rb +81 -0
  470. data/lib/pubid/ieee/identifiers/amendment.rb +40 -0
  471. data/lib/pubid/ieee/identifiers/base.rb +384 -0
  472. data/lib/pubid/ieee/identifiers/code_number.rb +82 -0
  473. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +29 -0
  474. data/lib/pubid/ieee/identifiers/corrigendum.rb +40 -0
  475. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +62 -0
  476. data/lib/pubid/ieee/identifiers/dual_identifier.rb +14 -0
  477. data/lib/pubid/ieee/identifiers/dual_published.rb +79 -0
  478. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +115 -0
  479. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +48 -0
  480. data/lib/pubid/ieee/identifiers/joint_development.rb +223 -0
  481. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +85 -0
  482. data/lib/pubid/ieee/identifiers/nesc/base.rb +127 -0
  483. data/lib/pubid/ieee/identifiers/nesc/draft.rb +43 -0
  484. data/lib/pubid/ieee/identifiers/nesc/edition.rb +32 -0
  485. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +43 -0
  486. data/lib/pubid/ieee/identifiers/nesc/redline.rb +35 -0
  487. data/lib/pubid/ieee/identifiers/nesc/standard.rb +41 -0
  488. data/lib/pubid/ieee/identifiers/nesc.rb +16 -0
  489. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +14 -0
  490. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +49 -0
  491. data/lib/pubid/ieee/identifiers/redlined_standard.rb +26 -0
  492. data/lib/pubid/ieee/identifiers/si_standard.rb +38 -0
  493. data/lib/pubid/ieee/identifiers/standard.rb +44 -0
  494. data/lib/pubid/ieee/identifiers/supplement_identifier.rb +42 -0
  495. data/lib/pubid/ieee/identifiers.rb +34 -0
  496. data/lib/pubid/ieee/ire/builder.rb +62 -0
  497. data/lib/pubid/ieee/ire/identifier.rb +73 -0
  498. data/lib/pubid/ieee/ire/parser.rb +91 -0
  499. data/lib/pubid/ieee/ire.rb +11 -0
  500. data/lib/pubid/ieee/nesc/builder.rb +114 -0
  501. data/lib/pubid/ieee/nesc/parser.rb +155 -0
  502. data/lib/pubid/ieee/nesc.rb +10 -0
  503. data/lib/pubid/ieee/parser.rb +1644 -0
  504. data/lib/pubid/ieee/pre_parser.rb +301 -0
  505. data/lib/pubid/ieee/renderer.rb +408 -0
  506. data/lib/pubid/ieee/typed_stages.rb +183 -0
  507. data/lib/pubid/ieee/urn_generator.rb +215 -0
  508. data/lib/pubid/ieee/urn_parser.rb +34 -0
  509. data/lib/pubid/ieee.rb +163 -0
  510. data/lib/pubid/ieee_debug.rb +30 -0
  511. data/lib/pubid/ietf/builder.rb +78 -0
  512. data/lib/pubid/ietf/identifier.rb +5 -0
  513. data/lib/pubid/ietf/identifiers/base.rb +72 -0
  514. data/lib/pubid/ietf/identifiers/bcp.rb +36 -0
  515. data/lib/pubid/ietf/identifiers/fyi.rb +36 -0
  516. data/lib/pubid/ietf/identifiers/internet_draft.rb +45 -0
  517. data/lib/pubid/ietf/identifiers/rfc.rb +27 -0
  518. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  519. data/lib/pubid/ietf/identifiers/std.rb +36 -0
  520. data/lib/pubid/ietf/identifiers.rb +14 -0
  521. data/lib/pubid/ietf/parser.rb +61 -0
  522. data/lib/pubid/ietf/renderer.rb +64 -0
  523. data/lib/pubid/ietf/urn_generator.rb +37 -0
  524. data/lib/pubid/ietf/urn_parser.rb +43 -0
  525. data/lib/pubid/ietf.rb +85 -0
  526. data/lib/pubid/iho/builder.rb +37 -0
  527. data/lib/pubid/iho/identifier.rb +7 -0
  528. data/lib/pubid/iho/identifiers/base.rb +75 -0
  529. data/lib/pubid/iho/identifiers/bibliographic.rb +16 -0
  530. data/lib/pubid/iho/identifiers/circular_letter.rb +15 -0
  531. data/lib/pubid/iho/identifiers/miscellaneous.rb +16 -0
  532. data/lib/pubid/iho/identifiers/publication.rb +15 -0
  533. data/lib/pubid/iho/identifiers/standard.rb +15 -0
  534. data/lib/pubid/iho/identifiers.rb +13 -0
  535. data/lib/pubid/iho/parser.rb +68 -0
  536. data/lib/pubid/iho/renderer.rb +30 -0
  537. data/lib/pubid/iho/urn_generator.rb +29 -0
  538. data/lib/pubid/iho/urn_parser.rb +58 -0
  539. data/lib/pubid/iho.rb +77 -0
  540. data/lib/pubid/isbn/builder.rb +44 -0
  541. data/lib/pubid/isbn/check_digit.rb +53 -0
  542. data/lib/pubid/isbn/identifier.rb +81 -0
  543. data/lib/pubid/isbn/identifiers/book.rb +30 -0
  544. data/lib/pubid/isbn/identifiers.rb +9 -0
  545. data/lib/pubid/isbn/parser.rb +45 -0
  546. data/lib/pubid/isbn/renderer.rb +16 -0
  547. data/lib/pubid/isbn.rb +52 -0
  548. data/lib/pubid/iso/builder.rb +316 -0
  549. data/lib/pubid/iso/bundled_identifier.rb +138 -0
  550. data/lib/pubid/iso/combined_identifier.rb +24 -0
  551. data/lib/pubid/iso/components/code.rb +24 -0
  552. data/lib/pubid/iso/components/publisher.rb +62 -0
  553. data/lib/pubid/iso/components.rb +10 -0
  554. data/lib/pubid/iso/format_resolver.rb +45 -0
  555. data/lib/pubid/iso/identifier.rb +318 -0
  556. data/lib/pubid/iso/identifiers/addendum.rb +107 -0
  557. data/lib/pubid/iso/identifiers/amendment.rb +132 -0
  558. data/lib/pubid/iso/identifiers/corrigendum.rb +108 -0
  559. data/lib/pubid/iso/identifiers/data.rb +75 -0
  560. data/lib/pubid/iso/identifiers/directives.rb +65 -0
  561. data/lib/pubid/iso/identifiers/directives_supplement.rb +148 -0
  562. data/lib/pubid/iso/identifiers/extract.rb +30 -0
  563. data/lib/pubid/iso/identifiers/guide.rb +99 -0
  564. data/lib/pubid/iso/identifiers/international_standard.rb +168 -0
  565. data/lib/pubid/iso/identifiers/international_standardized_profile.rb +94 -0
  566. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +95 -0
  567. data/lib/pubid/iso/identifiers/pas.rb +93 -0
  568. data/lib/pubid/iso/identifiers/recommendation.rb +45 -0
  569. data/lib/pubid/iso/identifiers/supplement.rb +95 -0
  570. data/lib/pubid/iso/identifiers/tc_document.rb +137 -0
  571. data/lib/pubid/iso/identifiers/technical_report.rb +103 -0
  572. data/lib/pubid/iso/identifiers/technical_specification.rb +102 -0
  573. data/lib/pubid/iso/identifiers/technology_trends_assessments.rb +95 -0
  574. data/lib/pubid/iso/identifiers.rb +32 -0
  575. data/lib/pubid/iso/normalizer.rb +89 -0
  576. data/lib/pubid/iso/parser.rb +534 -0
  577. data/lib/pubid/iso/rendering_style.rb +120 -0
  578. data/lib/pubid/iso/single_identifier.rb +60 -0
  579. data/lib/pubid/iso/supplement_identifier.rb +53 -0
  580. data/lib/pubid/iso/urn_generator.rb +313 -0
  581. data/lib/pubid/iso/urn_parser.rb +442 -0
  582. data/lib/pubid/iso/utilities.rb +86 -0
  583. data/lib/pubid/iso.rb +236 -0
  584. data/lib/pubid/itu/builder.rb +360 -0
  585. data/lib/pubid/itu/components/code.rb +84 -0
  586. data/lib/pubid/itu/components/designation.rb +35 -0
  587. data/lib/pubid/itu/components/sector.rb +46 -0
  588. data/lib/pubid/itu/components/series.rb +25 -0
  589. data/lib/pubid/itu/components.rb +12 -0
  590. data/lib/pubid/itu/i18n.rb +9 -0
  591. data/lib/pubid/itu/i18n.yaml +30 -0
  592. data/lib/pubid/itu/identifier.rb +4 -0
  593. data/lib/pubid/itu/identifiers/addendum.rb +15 -0
  594. data/lib/pubid/itu/identifiers/amendment.rb +19 -0
  595. data/lib/pubid/itu/identifiers/annex.rb +83 -0
  596. data/lib/pubid/itu/identifiers/annex_of_recommendation.rb +87 -0
  597. data/lib/pubid/itu/identifiers/appendix_of_recommendation.rb +92 -0
  598. data/lib/pubid/itu/identifiers/base.rb +482 -0
  599. data/lib/pubid/itu/identifiers/combined_identifier.rb +121 -0
  600. data/lib/pubid/itu/identifiers/corrigendum.rb +35 -0
  601. data/lib/pubid/itu/identifiers/errata.rb +15 -0
  602. data/lib/pubid/itu/identifiers/handbook.rb +38 -0
  603. data/lib/pubid/itu/identifiers/question.rb +63 -0
  604. data/lib/pubid/itu/identifiers/recommendation.rb +15 -0
  605. data/lib/pubid/itu/identifiers/report.rb +39 -0
  606. data/lib/pubid/itu/identifiers/special_publication.rb +31 -0
  607. data/lib/pubid/itu/identifiers/standard_serialization.rb +58 -0
  608. data/lib/pubid/itu/identifiers/supplement.rb +149 -0
  609. data/lib/pubid/itu/identifiers.rb +25 -0
  610. data/lib/pubid/itu/model.rb +111 -0
  611. data/lib/pubid/itu/parser.rb +679 -0
  612. data/lib/pubid/itu/urn_generator.rb +135 -0
  613. data/lib/pubid/itu/urn_parser.rb +31 -0
  614. data/lib/pubid/itu.rb +78 -0
  615. data/lib/pubid/jcgm/builder.rb +106 -0
  616. data/lib/pubid/jcgm/components/publisher.rb +20 -0
  617. data/lib/pubid/jcgm/components.rb +9 -0
  618. data/lib/pubid/jcgm/identifier.rb +21 -0
  619. data/lib/pubid/jcgm/identifiers/amendment.rb +24 -0
  620. data/lib/pubid/jcgm/identifiers/corrigendum.rb +34 -0
  621. data/lib/pubid/jcgm/identifiers/guide.rb +21 -0
  622. data/lib/pubid/jcgm/identifiers/gum_guide.rb +39 -0
  623. data/lib/pubid/jcgm/identifiers/meeting.rb +47 -0
  624. data/lib/pubid/jcgm/identifiers.rb +13 -0
  625. data/lib/pubid/jcgm/parser.rb +151 -0
  626. data/lib/pubid/jcgm/renderer.rb +89 -0
  627. data/lib/pubid/jcgm/single_identifier.rb +118 -0
  628. data/lib/pubid/jcgm/supplement_identifier.rb +51 -0
  629. data/lib/pubid/jcgm/urn_generator.rb +116 -0
  630. data/lib/pubid/jcgm/urn_parser.rb +177 -0
  631. data/lib/pubid/jcgm.rb +86 -0
  632. data/lib/pubid/jis/builder.rb +116 -0
  633. data/lib/pubid/jis/identifier.rb +194 -0
  634. data/lib/pubid/jis/identifiers/amendment.rb +16 -0
  635. data/lib/pubid/jis/identifiers/corrigendum.rb +16 -0
  636. data/lib/pubid/jis/identifiers/explanation.rb +22 -0
  637. data/lib/pubid/jis/identifiers/japanese_industrial_standard.rb +16 -0
  638. data/lib/pubid/jis/identifiers/standard.rb +28 -0
  639. data/lib/pubid/jis/identifiers/technical_report.rb +32 -0
  640. data/lib/pubid/jis/identifiers/technical_specification.rb +32 -0
  641. data/lib/pubid/jis/identifiers.rb +17 -0
  642. data/lib/pubid/jis/parser.rb +135 -0
  643. data/lib/pubid/jis/renderer.rb +69 -0
  644. data/lib/pubid/jis/single_identifier.rb +31 -0
  645. data/lib/pubid/jis/supplement_identifier.rb +50 -0
  646. data/lib/pubid/jis/urn_generator.rb +25 -0
  647. data/lib/pubid/jis/urn_parser.rb +23 -0
  648. data/lib/pubid/jis.rb +69 -0
  649. data/lib/pubid/lutaml/no_store_registration.rb +30 -0
  650. data/lib/pubid/nist/builder.rb +481 -0
  651. data/lib/pubid/nist/caster.rb +1234 -0
  652. data/lib/pubid/nist/circular_supplement_builder.rb +293 -0
  653. data/lib/pubid/nist/components/code.rb +27 -0
  654. data/lib/pubid/nist/components/edition.rb +134 -0
  655. data/lib/pubid/nist/components/issue_number.rb +28 -0
  656. data/lib/pubid/nist/components/part.rb +77 -0
  657. data/lib/pubid/nist/components/stage.rb +53 -0
  658. data/lib/pubid/nist/components/supplement.rb +191 -0
  659. data/lib/pubid/nist/components/translation.rb +42 -0
  660. data/lib/pubid/nist/components/update.rb +103 -0
  661. data/lib/pubid/nist/components/version.rb +35 -0
  662. data/lib/pubid/nist/components/volume.rb +32 -0
  663. data/lib/pubid/nist/components.rb +18 -0
  664. data/lib/pubid/nist/configuration.rb +87 -0
  665. data/lib/pubid/nist/identifier.rb +8 -0
  666. data/lib/pubid/nist/identifiers/base.rb +744 -0
  667. data/lib/pubid/nist/identifiers/circular.rb +80 -0
  668. data/lib/pubid/nist/identifiers/circular_supplement.rb +60 -0
  669. data/lib/pubid/nist/identifiers/commercial_standard.rb +42 -0
  670. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +58 -0
  671. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +69 -0
  672. data/lib/pubid/nist/identifiers/crpl_report.rb +138 -0
  673. data/lib/pubid/nist/identifiers/dated_document.rb +49 -0
  674. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +95 -0
  675. data/lib/pubid/nist/identifiers/grant_contractor_report.rb +36 -0
  676. data/lib/pubid/nist/identifiers/handbook.rb +58 -0
  677. data/lib/pubid/nist/identifiers/internal_report.rb +64 -0
  678. data/lib/pubid/nist/identifiers/letter_circular.rb +46 -0
  679. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +74 -0
  680. data/lib/pubid/nist/identifiers/monograph.rb +80 -0
  681. data/lib/pubid/nist/identifiers/ncstar.rb +41 -0
  682. data/lib/pubid/nist/identifiers/nsrds.rb +41 -0
  683. data/lib/pubid/nist/identifiers/owmwp.rb +35 -0
  684. data/lib/pubid/nist/identifiers/report.rb +77 -0
  685. data/lib/pubid/nist/identifiers/special_publication.rb +46 -0
  686. data/lib/pubid/nist/identifiers/technical_note.rb +91 -0
  687. data/lib/pubid/nist/identifiers.rb +33 -0
  688. data/lib/pubid/nist/parser.rb +752 -0
  689. data/lib/pubid/nist/parser_output_normalizer.rb +233 -0
  690. data/lib/pubid/nist/preprocessor.rb +443 -0
  691. data/lib/pubid/nist/renderer.rb +43 -0
  692. data/lib/pubid/nist/router.rb +149 -0
  693. data/lib/pubid/nist/series/base.rb +58 -0
  694. data/lib/pubid/nist/series/crpl.rb +13 -0
  695. data/lib/pubid/nist/series/fips.rb +14 -0
  696. data/lib/pubid/nist/series/ir.rb +57 -0
  697. data/lib/pubid/nist/series/letter_preserving.rb +15 -0
  698. data/lib/pubid/nist/series/mono.rb +19 -0
  699. data/lib/pubid/nist/series/ncstar.rb +20 -0
  700. data/lib/pubid/nist/series.rb +49 -0
  701. data/lib/pubid/nist/supplement_identifier.rb +84 -0
  702. data/lib/pubid/nist/urn_generator.rb +133 -0
  703. data/lib/pubid/nist/urn_parser.rb +67 -0
  704. data/lib/pubid/nist.rb +138 -0
  705. data/lib/pubid/oasis/builder.rb +79 -0
  706. data/lib/pubid/oasis/identifier.rb +160 -0
  707. data/lib/pubid/oasis/identifiers/standard.rb +29 -0
  708. data/lib/pubid/oasis/identifiers.rb +9 -0
  709. data/lib/pubid/oasis/parser.rb +34 -0
  710. data/lib/pubid/oasis/renderer.rb +29 -0
  711. data/lib/pubid/oasis/urn_generator.rb +25 -0
  712. data/lib/pubid/oasis/urn_parser.rb +22 -0
  713. data/lib/pubid/oasis.rb +79 -0
  714. data/lib/pubid/ogc/builder.rb +33 -0
  715. data/lib/pubid/ogc/identifier.rb +81 -0
  716. data/lib/pubid/ogc/identifiers/document.rb +26 -0
  717. data/lib/pubid/ogc/identifiers.rb +10 -0
  718. data/lib/pubid/ogc/parser.rb +40 -0
  719. data/lib/pubid/ogc/renderer.rb +33 -0
  720. data/lib/pubid/ogc/urn_generator.rb +14 -0
  721. data/lib/pubid/ogc/urn_parser.rb +22 -0
  722. data/lib/pubid/ogc.rb +71 -0
  723. data/lib/pubid/oiml/builder.rb +270 -0
  724. data/lib/pubid/oiml/components/code.rb +37 -0
  725. data/lib/pubid/oiml/components.rb +9 -0
  726. data/lib/pubid/oiml/identifier.rb +64 -0
  727. data/lib/pubid/oiml/identifiers/amendment.rb +13 -0
  728. data/lib/pubid/oiml/identifiers/annex.rb +29 -0
  729. data/lib/pubid/oiml/identifiers/basic_publication.rb +15 -0
  730. data/lib/pubid/oiml/identifiers/bulletin.rb +137 -0
  731. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  732. data/lib/pubid/oiml/identifiers/document.rb +15 -0
  733. data/lib/pubid/oiml/identifiers/errata.rb +15 -0
  734. data/lib/pubid/oiml/identifiers/expert_report.rb +15 -0
  735. data/lib/pubid/oiml/identifiers/guide.rb +15 -0
  736. data/lib/pubid/oiml/identifiers/recommendation.rb +15 -0
  737. data/lib/pubid/oiml/identifiers/seminar_report.rb +15 -0
  738. data/lib/pubid/oiml/identifiers/vocabulary.rb +15 -0
  739. data/lib/pubid/oiml/identifiers.rb +20 -0
  740. data/lib/pubid/oiml/parser.rb +274 -0
  741. data/lib/pubid/oiml/renderer.rb +232 -0
  742. data/lib/pubid/oiml/single_identifier.rb +113 -0
  743. data/lib/pubid/oiml/supplement_identifier.rb +88 -0
  744. data/lib/pubid/oiml/urn_generator.rb +78 -0
  745. data/lib/pubid/oiml/urn_parser.rb +39 -0
  746. data/lib/pubid/oiml.rb +81 -0
  747. data/lib/pubid/omg/builder.rb +19 -0
  748. data/lib/pubid/omg/identifier.rb +47 -0
  749. data/lib/pubid/omg/identifiers/specification.rb +32 -0
  750. data/lib/pubid/omg/identifiers.rb +9 -0
  751. data/lib/pubid/omg/parser.rb +40 -0
  752. data/lib/pubid/omg/renderer.rb +19 -0
  753. data/lib/pubid/omg.rb +51 -0
  754. data/lib/pubid/parser/common_parse_methods.rb +13 -0
  755. data/lib/pubid/parser/common_parse_rules.rb +56 -0
  756. data/lib/pubid/parser/grammar.rb +74 -0
  757. data/lib/pubid/parser.rb +9 -0
  758. data/lib/pubid/parsers/base.rb +11 -0
  759. data/lib/pubid/parsers/mr_string.rb +206 -0
  760. data/lib/pubid/plateau/builder.rb +54 -0
  761. data/lib/pubid/plateau/identifier.rb +4 -0
  762. data/lib/pubid/plateau/identifiers/annex.rb +16 -0
  763. data/lib/pubid/plateau/identifiers/base.rb +55 -0
  764. data/lib/pubid/plateau/identifiers/handbook.rb +28 -0
  765. data/lib/pubid/plateau/identifiers/technical_report.rb +16 -0
  766. data/lib/pubid/plateau/identifiers.rb +11 -0
  767. data/lib/pubid/plateau/parser.rb +73 -0
  768. data/lib/pubid/plateau/renderer.rb +51 -0
  769. data/lib/pubid/plateau/supplement_identifier.rb +68 -0
  770. data/lib/pubid/plateau/urn_generator.rb +29 -0
  771. data/lib/pubid/plateau/urn_parser.rb +43 -0
  772. data/lib/pubid/plateau.rb +82 -0
  773. data/lib/pubid/prefixes_support.rb +51 -0
  774. data/lib/pubid/renderers/annotator.rb +233 -0
  775. data/lib/pubid/renderers/base.rb +66 -0
  776. data/lib/pubid/renderers/directives_renderer.rb +75 -0
  777. data/lib/pubid/renderers/guide_renderer.rb +26 -0
  778. data/lib/pubid/renderers/human_readable.rb +83 -0
  779. data/lib/pubid/renderers/iwa_renderer.rb +20 -0
  780. data/lib/pubid/renderers/mr_string.rb +64 -0
  781. data/lib/pubid/renderers/supplement_renderer.rb +36 -0
  782. data/lib/pubid/renderers/urn.rb +11 -0
  783. data/lib/pubid/renderers.rb +15 -0
  784. data/lib/pubid/rendering/context.rb +168 -0
  785. data/lib/pubid/rendering/date.rb +27 -0
  786. data/lib/pubid/rendering/language.rb +21 -0
  787. data/lib/pubid/rendering/numbering.rb +42 -0
  788. data/lib/pubid/rendering/publisher.rb +25 -0
  789. data/lib/pubid/rendering/stage.rb +38 -0
  790. data/lib/pubid/rendering/supplement.rb +46 -0
  791. data/lib/pubid/rendering.rb +13 -0
  792. data/lib/pubid/sae/builder.rb +32 -0
  793. data/lib/pubid/sae/components/code.rb +9 -0
  794. data/lib/pubid/sae/components/date.rb +27 -0
  795. data/lib/pubid/sae/components/type.rb +23 -0
  796. data/lib/pubid/sae/components.rb +11 -0
  797. data/lib/pubid/sae/identifier.rb +4 -0
  798. data/lib/pubid/sae/identifiers/base.rb +47 -0
  799. data/lib/pubid/sae/identifiers.rb +8 -0
  800. data/lib/pubid/sae/parser.rb +55 -0
  801. data/lib/pubid/sae/renderer.rb +36 -0
  802. data/lib/pubid/sae/urn_generator.rb +30 -0
  803. data/lib/pubid/sae/urn_parser.rb +36 -0
  804. data/lib/pubid/sae.rb +66 -0
  805. data/lib/pubid/tgpp/builder.rb +41 -0
  806. data/lib/pubid/tgpp/identifier.rb +107 -0
  807. data/lib/pubid/tgpp/identifiers/technical_report.rb +32 -0
  808. data/lib/pubid/tgpp/identifiers/technical_specification.rb +33 -0
  809. data/lib/pubid/tgpp/identifiers.rb +11 -0
  810. data/lib/pubid/tgpp/parser.rb +78 -0
  811. data/lib/pubid/tgpp/renderer.rb +52 -0
  812. data/lib/pubid/tgpp/urn_generator.rb +39 -0
  813. data/lib/pubid/tgpp/urn_parser.rb +30 -0
  814. data/lib/pubid/tgpp.rb +83 -0
  815. data/lib/pubid/type_resolver.rb +59 -0
  816. data/lib/pubid/un/builder.rb +42 -0
  817. data/lib/pubid/un/identifier.rb +40 -0
  818. data/lib/pubid/un/identifiers/document.rb +24 -0
  819. data/lib/pubid/un/identifiers.rb +9 -0
  820. data/lib/pubid/un/parser.rb +25 -0
  821. data/lib/pubid/un/renderer.rb +11 -0
  822. data/lib/pubid/un.rb +46 -0
  823. data/lib/pubid/urn_generator/base.rb +110 -0
  824. data/lib/pubid/urn_parser/base.rb +81 -0
  825. data/lib/pubid/urn_parser/errors.rb +16 -0
  826. data/lib/pubid/urn_parser.rb +14 -0
  827. data/lib/pubid/utils/string_normalizer.rb +196 -0
  828. data/lib/pubid/utils.rb +7 -0
  829. data/lib/pubid/version.rb +3 -1
  830. data/lib/pubid/w3c/builder.rb +55 -0
  831. data/lib/pubid/w3c/identifier.rb +130 -0
  832. data/lib/pubid/w3c/identifiers/candidate_recommendation.rb +31 -0
  833. data/lib/pubid/w3c/identifiers/candidate_recommendation_draft.rb +31 -0
  834. data/lib/pubid/w3c/identifiers/draft_note.rb +31 -0
  835. data/lib/pubid/w3c/identifiers/note.rb +30 -0
  836. data/lib/pubid/w3c/identifiers/obsolete_recommendation.rb +31 -0
  837. data/lib/pubid/w3c/identifiers/proposed_edited_recommendation.rb +31 -0
  838. data/lib/pubid/w3c/identifiers/proposed_recommendation.rb +31 -0
  839. data/lib/pubid/w3c/identifiers/recommendation.rb +31 -0
  840. data/lib/pubid/w3c/identifiers/standard.rb +12 -0
  841. data/lib/pubid/w3c/identifiers/superseded_recommendation.rb +31 -0
  842. data/lib/pubid/w3c/identifiers/working_draft.rb +30 -0
  843. data/lib/pubid/w3c/identifiers.rb +25 -0
  844. data/lib/pubid/w3c/parser.rb +43 -0
  845. data/lib/pubid/w3c/renderer.rb +35 -0
  846. data/lib/pubid/w3c/urn_generator.rb +18 -0
  847. data/lib/pubid/w3c/urn_parser.rb +36 -0
  848. data/lib/pubid/w3c.rb +67 -0
  849. data/lib/pubid/xsf/builder.rb +17 -0
  850. data/lib/pubid/xsf/identifier.rb +66 -0
  851. data/lib/pubid/xsf/identifiers/xep.rb +31 -0
  852. data/lib/pubid/xsf/identifiers.rb +9 -0
  853. data/lib/pubid/xsf/parser.rb +25 -0
  854. data/lib/pubid/xsf/renderer.rb +23 -0
  855. data/lib/pubid/xsf/urn_generator.rb +13 -0
  856. data/lib/pubid/xsf/urn_parser.rb +18 -0
  857. data/lib/pubid/xsf.rb +81 -0
  858. data/lib/pubid.rb +551 -13
  859. data/lib/tasks/docs.rake +37 -0
  860. data/lib/tasks/export.rake +39 -0
  861. data/lib/tasks/website-data.json +7546 -0
  862. metadata +875 -171
  863. data/lib/pubid/registry.rb +0 -30
@@ -0,0 +1,1554 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Ieee
5
+ # Builder class for constructing IEEE identifier scheme from parsed data
6
+ # Single Responsibility: Transform parsed data into identifier objects
7
+ class Builder
8
+ attr_accessor :original_input
9
+ attr_reader :identifier_class
10
+
11
+ def initialize(identifier_class = Identifier)
12
+ @identifier_class = identifier_class
13
+ end
14
+
15
+ # Build a scheme object from parsed data
16
+ # @param parsed [Hash, Array] the parsed identifier data
17
+ # @return [identifier] the constructed identifier object
18
+ def build(parsed)
19
+ # Parslet returns a top-level *array* of hashes when a repeated capture
20
+ # (e.g. the two subparts in "7.4-3-2") stops the sibling captures from
21
+ # hash-merging. The dispatch checks below need hash access, so read them
22
+ # off a merged view (`d`); build_single_identifier re-handles the array
23
+ # itself (preserving every subpart), so it still gets the original.
24
+ d = parsed.is_a?(Array) ? merge_parsed_array(parsed) : parsed
25
+
26
+ # Handle CSA dual published patterns
27
+ if d[:ieee_portion] && d[:csa_portion]
28
+ return build_csa_dual_published(parsed)
29
+ end
30
+
31
+ # Handle the historical IEEE/IPCEA cable designation (S-135)
32
+ if d[:s_number]
33
+ return build_s_designation(parsed)
34
+ end
35
+
36
+ # Handle combined AIEE identifiers (from "Nos X and Y" preprocessing)
37
+ if d[:first_aiee] && d[:second_aiee]
38
+ return build_combined_aiee(parsed)
39
+ end
40
+
41
+ # Handle dual published patterns
42
+ if d[:first] && d[:second]
43
+ return build_dual_published(parsed)
44
+ end
45
+
46
+ # Handle IEC/IEEE copublished patterns
47
+ if d[:content]
48
+ return build_iec_ieee_copublished(parsed)
49
+ end
50
+
51
+ # Handle NESC identifiers (National Electrical Safety Code)
52
+ if d[:nesc]
53
+ nesc_builder = Nesc::Builder.new
54
+ return nesc_builder.build(d[:nesc])
55
+ end
56
+
57
+ # Handle AIEE identifiers (American Institute of Electrical Engineers)
58
+ if d[:aiee]
59
+ aiee_builder = Aiee::Builder.new
60
+ return aiee_builder.build(d[:aiee])
61
+ end
62
+
63
+ # Handle IRE identifiers (Institute of Radio Engineers)
64
+ if d[:ire]
65
+ ire_builder = Ire::Builder.new
66
+ return ire_builder.build(d[:ire])
67
+ end
68
+
69
+ # Handle IEEE/ASTM SI/PSI identifiers (Système International)
70
+ if d[:si_type]
71
+ return build_si_psi_identifier(parsed)
72
+ end
73
+
74
+ # Handle single identifier
75
+ build_single_identifier(parsed)
76
+ end
77
+
78
+ private
79
+
80
+ # Build IEC/IEEE copublished identifier
81
+ def build_iec_ieee_copublished(parsed)
82
+ content = extract_value(parsed[:content])
83
+
84
+ # Parse the content to extract components
85
+ copublished_number = nil
86
+ draft_info = nil
87
+ iec_year = nil
88
+ date_info = nil
89
+
90
+ if content
91
+ # Extract copublished number (everything before IEC: or comma or parenthesis)
92
+ copublished_number = if content.include?("IEC:")
93
+ content.split(" IEC:").first.strip
94
+ elsif content.include?(", ")
95
+ content.split(", ").first.strip
96
+ elsif content.include?(" (")
97
+ content.split(" (").first.strip
98
+ else
99
+ content.strip
100
+ end
101
+
102
+ # Extract draft info if present
103
+ if copublished_number.include?("/D")
104
+ parts = copublished_number.split("/D")
105
+ copublished_number = parts[0]
106
+ draft_info = "/D#{parts[1] || ''}"
107
+ end
108
+
109
+ # Extract IEC year if present
110
+ if content.include?("IEC:")
111
+ iec_part = content.split("IEC:")[1]
112
+ if iec_part
113
+ iec_year = iec_part.split.first
114
+ end
115
+ end
116
+
117
+ # Extract date info if present
118
+ if content.include?(" (")
119
+ date_part = content.split(" (")[1]
120
+ if date_part&.include?(")")
121
+ date_info = date_part.split(")")[0]
122
+ end
123
+ end
124
+ end
125
+
126
+ Identifiers::IecIeeeCopublished.new(
127
+ draft_info: draft_info,
128
+ iec_year: iec_year,
129
+ date_info: date_info,
130
+ **copublished_structured(copublished_number),
131
+ )
132
+ end
133
+
134
+ # Decompose an IEC/IEEE `copublished_number` into the split index columns
135
+ # that reconstruct it losslessly (IecIeeeCopublished#copublished_number is
136
+ # the inverse). First peel a trailing *publication year* (19xx/20xx with
137
+ # its `-`/`:` separator) into `year`/`year_sep` — a bare 4-digit strip
138
+ # would wrongly eat a part like "60076.57-1202" (1202 is a part, not a
139
+ # year). Then tokenise the remainder on `.`/`-`, keeping each part's
140
+ # leading separator (a single code mixes them). So `number` narrows to the
141
+ # bucket, `parts` matches within it, and `separators` renders it back.
142
+ # `year_sep` is omitted when the default `-`. Returns empty for no number.
143
+ def copublished_structured(copublished_number)
144
+ return {} if copublished_number.nil? || copublished_number.empty?
145
+
146
+ remainder, year, year_sep = peel_copublished_year(copublished_number)
147
+ tokens = remainder.scan(/\A[^.\-]+|[.\-][^.\-]+/)
148
+ { number: tokens.shift }
149
+ .merge(copublished_parts(tokens))
150
+ .merge(copublished_year(year, year_sep))
151
+ end
152
+
153
+ # The `parts`/`separators` columns from the tokens after the number.
154
+ def copublished_parts(tokens)
155
+ return {} if tokens.empty?
156
+
157
+ { parts: tokens.map { |t| t[1..] }, separators: tokens.map { |t| t[0] } }
158
+ end
159
+
160
+ # The `year`/`year_sep` columns; `year_sep` is omitted when the default `-`.
161
+ def copublished_year(year, year_sep)
162
+ return {} unless year
163
+
164
+ year_sep == "-" ? { year: year } : { year: year, year_sep: year_sep }
165
+ end
166
+
167
+ # Peel a trailing publication year (19xx/20xx with its `-`/`:` separator)
168
+ # off a copublished number, returning [remainder, year, year_sep].
169
+ def peel_copublished_year(copublished_number)
170
+ return [copublished_number, nil, nil] unless
171
+ copublished_number =~ /([-:])((?:19|20)\d\d)\z/
172
+
173
+ year_sep = Regexp.last_match(1)
174
+ year = Regexp.last_match(2)
175
+ trimmed = copublished_number[0...(copublished_number.length -
176
+ year_sep.length - year.length)]
177
+ [trimmed, year, year_sep]
178
+ end
179
+
180
+ # Build dual published identifier
181
+ def build_dual_published(parsed)
182
+ first_id = build_single_identifier(parsed[:first])
183
+ second_id = build_single_identifier(parsed[:second])
184
+
185
+ Identifiers::DualPublished.new(
186
+ first_identifier: first_id,
187
+ second_identifier: second_id,
188
+ )
189
+ end
190
+
191
+ # Build CSA dual published identifier
192
+ # @param parsed [Hash] parsed CSA dual published data
193
+ # @return [Identifiers::CsaDualPublished] CSA dual published identifier
194
+ def build_csa_dual_published(parsed)
195
+ # Build IEEE portion
196
+ ieee_id = build_single_identifier(parsed[:ieee_portion])
197
+
198
+ # Extract and parse CSA portion using CSA parser
199
+ csa_string = extract_value(parsed[:csa_portion])
200
+
201
+ # Prepend "CSA " prefix if not present (CSA parser expects this format)
202
+ csa_string = "CSA #{csa_string}" unless csa_string.start_with?("CSA",
203
+ "CAN/")
204
+
205
+ # Use CSA parser to parse the CSA portion
206
+ csa_id = Pubid::Csa.parse(csa_string)
207
+
208
+ Identifiers::CsaDualPublished.new(
209
+ ieee_identifier: ieee_id,
210
+ csa_identifier: csa_id,
211
+ )
212
+ end
213
+
214
+ # Build the historical IEEE/IPCEA cable designation (e.g. S-135).
215
+ #
216
+ # The "S-135" designation is modelled as a plain Standard whose code holds
217
+ # the whole "S-135" string as `number` (prefix nil). It is passed as the
218
+ # `number:` split column directly — NOT as `code:` — so it bypasses
219
+ # Components::Code.parse, which would peel "S" as a prefix and leave an
220
+ # empty number (breaking the relaton index key and the round-trip). This
221
+ # keeps `root.number` == "S-135" and renders the dash back verbatim.
222
+ #
223
+ # The "/IPCEA …" slash co-designation is stored verbatim in `crossref`
224
+ # (rendered as-is); the "(IPCEA …)" parenthetical variant flows through
225
+ # the normal `parenthetical_content` path.
226
+ def build_s_designation(parsed)
227
+ attributes = {
228
+ number: extract_value(parsed[:s_number]),
229
+ publisher: extract_value(parsed[:publisher]) || "IEEE",
230
+ typed_stage: Pubid::Ieee.locate_stage("Std"),
231
+ }
232
+
233
+ if parsed[:ipcea_copub]
234
+ attributes[:crossref] = extract_value(parsed[:ipcea_copub])
235
+ end
236
+ handle_parameters(parsed, attributes)
237
+
238
+ Identifiers::Standard.new(**attributes)
239
+ end
240
+
241
+ # Build single identifier
242
+ def build_single_identifier(parsed, building_secondary: false)
243
+ # Parslet can return array of hashes - merge them
244
+ parsed_hash = parsed.is_a?(Array) ? merge_parsed_array(parsed) : parsed
245
+
246
+ # Handle multi-numbered identifiers (cross-reference and joint standards)
247
+ # CRITICAL: Don't recurse when building secondary identifier to prevent infinite loop
248
+ if !building_secondary && parsed_hash[:primary_identifier] && (parsed_hash[:secondary_crossref] || parsed_hash[:secondary_joint])
249
+ return build_multi_numbered_identifier(parsed_hash)
250
+ end
251
+
252
+ # Handle corrigendum supplements (check for base + cor_number)
253
+ # This enables recursive base identifier parsing like ISO/IEC Amendment
254
+ if parsed_hash[:base] && parsed_hash[:cor_number]
255
+ return build_corrigendum_supplement(parsed_hash)
256
+ end
257
+
258
+ # Handle the flat draft + corrigendum form (relaton's `…/D-N/CorM-YYYY`,
259
+ # normalized to `…/Cor M-YYYY/D N`): the parser emits a top-level
260
+ # `corrigendum` hash alongside `draft`/number with NO `:base` subtree, so
261
+ # the supplement path above is skipped. Rebuild a real base standard
262
+ # (carrying the draft) and wrap it, so `to_s` renders correctly from a
263
+ # fresh parse and after a from_hash round-trip. The `!parsed_hash[:base]`
264
+ # guard keeps this disjoint from the nested-base branch above; a
265
+ # `:base` + nested-`corrigendum`-hash shape is not produced by the
266
+ # grammar, so it would fall through to `extract_attributes` (as before).
267
+ if parsed_hash[:corrigendum].is_a?(Hash) && !parsed_hash[:base]
268
+ return build_flat_corrigendum(parsed_hash)
269
+ end
270
+
271
+ # Handle amendment supplements — mirrors the corrigendum path above.
272
+ # Every amendment the grammar produces is FLAT (a top-level `amendment`
273
+ # hash, no `:base`): the `amendment` rule only ever appears inline via
274
+ # `amendment.maybe`, so `build_flat_amendment` carries all real traffic
275
+ # (`IEEE Std …/Amd N-YYYY` and `ISO/IEC/IEEE 8802.x/AmdN-YYYY`). The
276
+ # recursive-base branch below is kept for structural parity with
277
+ # corrigendum (which has a dedicated `corrigendum_identifier` rule
278
+ # emitting a `:base` + top-level `:amd_number`-style shape); no amendment
279
+ # grammar rule emits it today, so it would only fire if one were added.
280
+ if parsed_hash[:base] && parsed_hash[:amd_number]
281
+ return build_amendment_supplement(parsed_hash)
282
+ end
283
+
284
+ if parsed_hash[:amendment].is_a?(Hash) && !parsed_hash[:base]
285
+ return build_flat_amendment(parsed_hash)
286
+ end
287
+
288
+ # Handle interpretation supplements (check for base + int_year)
289
+ if parsed_hash[:base] && (parsed_hash[:int_year] || parsed_hash[:interpretation])
290
+ return build_interpretation_supplement(parsed_hash)
291
+ end
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
+
303
+ # Handle conformance supplements (check for base + conf_number)
304
+ if parsed_hash[:base] && parsed_hash[:conf_number]
305
+ return build_conformance_supplement(parsed_hash)
306
+ end
307
+
308
+ # Handle joint development patterns from parser
309
+ if parsed_hash[:joint_publishers] || parsed_hash[:iso_stage]
310
+ return build_joint_development(parsed_hash)
311
+ end
312
+
313
+ attributes = extract_attributes(parsed_hash)
314
+
315
+ # Handle relationships if present (Pattern 4)
316
+ if parsed_hash[:relationship_type] || parsed_hash[:relationship_clause]
317
+ attributes[:relationships] = build_relationships(parsed_hash)
318
+ end
319
+
320
+ # Route to appropriate identifier class based on content
321
+ identifier_class = determine_identifier_class(attributes)
322
+ rename_supplement_keys(attributes)
323
+ identifier_class.new(**attributes)
324
+ end
325
+
326
+ # Supplement wrappers (Corrigendum/Amendment/Conformance/Interpretation)
327
+ # store their ordinal/year under the uniform `number`/`year`, but the parse
328
+ # tree and class-routing above key on the distinct
329
+ # `cor_*`/`amd_*`/`conf_*`/`int_year` names. Translate them **after**
330
+ # routing, just before construction. Guards ensure a plain standard's own
331
+ # `number`/`year` are never overwritten (it carries none of these keys).
332
+ def rename_supplement_keys(attributes)
333
+ ordinal = attributes.delete(:cor_number) ||
334
+ attributes.delete(:amd_number) || attributes.delete(:conf_number)
335
+ attributes[:number] = ordinal if ordinal
336
+ year = attributes.delete(:cor_year) || attributes.delete(:amd_year) ||
337
+ attributes.delete(:conf_year) || attributes.delete(:int_year)
338
+ attributes[:year] = year if year
339
+ end
340
+
341
+ # Reconstruct the base identifier string from a parsed `:base` subtree
342
+ # (publisher/copublishers + type + number/part/subpart/year), so it can be
343
+ # recursively re-parsed by the supplement builders (corrigendum/amendment).
344
+ # @param base_data [Hash] the `:base` subtree
345
+ # @return [String] e.g. "IEEE Std 802.1AC-2016" or "535-2013"
346
+ def reconstruct_base_string(base_data)
347
+ base_parts = []
348
+
349
+ # Extract publisher
350
+ if base_data[:publishers]
351
+ pub_data = base_data[:publishers]
352
+ publisher_str = extract_value(pub_data[:publisher])
353
+
354
+ if pub_data[:copublishers] && !pub_data[:copublishers].empty?
355
+ copubs = pub_data[:copublishers]
356
+ copubs = [copubs] unless copubs.is_a?(Array)
357
+ copub_strs = copubs.filter_map do |cp|
358
+ extract_value(cp[:copublisher])
359
+ end
360
+ publisher_str += "/#{copub_strs.join('/')}" if !copub_strs.empty?
361
+ end
362
+
363
+ base_parts << publisher_str
364
+ end
365
+
366
+ # Extract type
367
+ if base_data[:type]
368
+ base_parts << extract_value(base_data[:type])
369
+ end
370
+
371
+ # Extract number with parts and year
372
+ # Build the complete code string: "802.1AC-2016" or "535-2013" or "C37.41-2016"
373
+ number_str = extract_value(base_data[:number])
374
+
375
+ # Add part if present (e.g., ".1AC" or ".41")
376
+ if base_data[:part]
377
+ part_val = extract_value(base_data[:part])
378
+ # Determine separator: dot for most cases, dash for some
379
+ separator = number_str.match?(/^[A-Z]/) ? "." : "." # Letter prefix uses dot
380
+ number_str += separator + part_val
381
+ end
382
+
383
+ # Add subpart if present
384
+ if base_data[:subpart]
385
+ subparts = base_data[:subpart]
386
+ subparts = [subparts] unless subparts.is_a?(Array)
387
+ subparts.each do |sp|
388
+ subpart_val = extract_value(sp)
389
+ number_str += ".#{subpart_val}" if subpart_val
390
+ end
391
+ end
392
+
393
+ # Add year with dash (e.g., "-2016")
394
+ if base_data[:year]
395
+ year_val = extract_value(base_data[:year])
396
+ number_str += "-#{year_val}"
397
+ end
398
+
399
+ base_parts << number_str
400
+ base_parts.join(" ")
401
+ end
402
+
403
+ # Build corrigendum supplement with recursive base parsing
404
+ # @param parsed_hash [Hash] parsed data with base and supplement info
405
+ # @return [Identifiers::Corrigendum] corrigendum identifier
406
+ def build_corrigendum_supplement(parsed_hash)
407
+ # Recursively parse the base identifier reconstructed from the subtree.
408
+ base = Identifier.parse(reconstruct_base_string(parsed_hash[:base]))
409
+
410
+ # Extract corrigendum attributes (parse-tree keys are cor_*; the
411
+ # identifier stores them as the uniform `number`/`year`).
412
+ cor_number = extract_value(parsed_hash[:cor_number])
413
+ cor_year = extract_value(parsed_hash[:cor_year])
414
+
415
+ # Create Corrigendum with parsed base
416
+ Identifiers::Corrigendum.new(
417
+ base: base,
418
+ number: cor_number,
419
+ year: cor_year,
420
+ )
421
+ end
422
+
423
+ # Build a Corrigendum from the flat parse tree produced by the combined
424
+ # `…/Cor M-YYYY/D N` form, where the corrigendum lives in a top-level
425
+ # `corrigendum` hash and there is no `:base` subtree. Peel the corrigendum
426
+ # off, build the remaining attributes into the base standard (with its
427
+ # draft) via the normal single-identifier path, then wrap.
428
+ # @param parsed_hash [Hash] flat parsed data with a :corrigendum hash
429
+ # @return [Identifiers::Corrigendum]
430
+ def build_flat_corrigendum(parsed_hash)
431
+ cor_data = parsed_hash[:corrigendum]
432
+ cor_number = extract_value(cor_data[:cor_number])
433
+ cor_year = extract_value(cor_data[:cor_year]) if cor_data[:cor_year]
434
+
435
+ base_hash = parsed_hash.reject { |key, _| key == :corrigendum }
436
+ base = build_single_identifier(base_hash)
437
+
438
+ Identifiers::Corrigendum.new(
439
+ base: base,
440
+ number: cor_number,
441
+ year: cor_year,
442
+ )
443
+ end
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
+
474
+ # Build amendment supplement with recursive base parsing. Mirrors
475
+ # {#build_corrigendum_supplement} — reconstructs the base identifier
476
+ # string, recursively parses it, and wraps it in an Amendment.
477
+ # @param parsed_hash [Hash] parsed data with base and amendment info
478
+ # @return [Identifiers::Amendment] amendment identifier
479
+ def build_amendment_supplement(parsed_hash)
480
+ base = Identifier.parse(reconstruct_base_string(parsed_hash[:base]))
481
+
482
+ amd_number = extract_value(parsed_hash[:amd_number])
483
+ amd_year = extract_value(parsed_hash[:amd_year])
484
+
485
+ Identifiers::Amendment.new(
486
+ base: base,
487
+ number: amd_number,
488
+ year: amd_year,
489
+ )
490
+ end
491
+
492
+ # Build an Amendment from the flat parse tree produced by the common
493
+ # `…/Amd N-YYYY` form, where the amendment lives in a top-level
494
+ # `amendment` hash and there is no `:base` subtree. Peel the amendment
495
+ # off, build the remaining attributes into the base standard via the
496
+ # normal single-identifier path, then wrap. Mirrors
497
+ # {#build_flat_corrigendum}.
498
+ # @param parsed_hash [Hash] flat parsed data with an :amendment hash
499
+ # @return [Identifiers::Amendment]
500
+ def build_flat_amendment(parsed_hash)
501
+ amd_data = parsed_hash[:amendment]
502
+ amd_number = extract_value(amd_data[:amd_number])
503
+ amd_year = extract_value(amd_data[:amd_year]) if amd_data[:amd_year]
504
+
505
+ base_hash = parsed_hash.reject { |key, _| key == :amendment }
506
+ base = build_single_identifier(base_hash)
507
+
508
+ Identifiers::Amendment.new(
509
+ base: base,
510
+ number: amd_number,
511
+ year: amd_year,
512
+ )
513
+ end
514
+
515
+ # Build interpretation supplement with recursive base parsing
516
+ # @param parsed_hash [Hash] parsed data with base and supplement info
517
+ # @return [Identifiers::InterpretationIdentifier] interpretation identifier
518
+ def build_interpretation_supplement(parsed_hash)
519
+ # Reconstruct base identifier string from parsed components (same logic as corrigendum)
520
+ base_parts = []
521
+ base_data = parsed_hash[:base]
522
+
523
+ # Extract publisher
524
+ if base_data[:publishers]
525
+ pub_data = base_data[:publishers]
526
+ publisher_str = extract_value(pub_data[:publisher])
527
+
528
+ if pub_data[:copublishers] && !pub_data[:copublishers].empty?
529
+ copubs = pub_data[:copublishers]
530
+ copubs = [copubs] unless copubs.is_a?(Array)
531
+ copub_strs = copubs.filter_map do |cp|
532
+ extract_value(cp[:copublisher])
533
+ end
534
+ publisher_str += "/#{copub_strs.join('/')}" if !copub_strs.empty?
535
+ end
536
+
537
+ base_parts << publisher_str
538
+ end
539
+
540
+ # Extract type
541
+ if base_data[:type]
542
+ base_parts << extract_value(base_data[:type])
543
+ end
544
+
545
+ # Extract number with parts and year
546
+ number_str = extract_value(base_data[:number])
547
+
548
+ # Add part if present
549
+ if base_data[:part]
550
+ part_val = extract_value(base_data[:part])
551
+ separator = number_str.match?(/^[A-Z]/) ? "." : "."
552
+ number_str += separator + part_val
553
+ end
554
+
555
+ # Add subpart if present
556
+ if base_data[:subpart]
557
+ subparts = base_data[:subpart]
558
+ subparts = [subparts] unless subparts.is_a?(Array)
559
+ subparts.each do |sp|
560
+ subpart_val = extract_value(sp)
561
+ number_str += ".#{subpart_val}" if subpart_val
562
+ end
563
+ end
564
+
565
+ # Add year with dash
566
+ if base_data[:year]
567
+ year_val = extract_value(base_data[:year])
568
+ number_str += "-#{year_val}"
569
+ end
570
+
571
+ base_parts << number_str
572
+
573
+ # Build base string and recursively parse it
574
+ base_string = base_parts.join(" ")
575
+
576
+ # Recursively parse base identifier using Base.parse
577
+ base = Identifier.parse(base_string)
578
+
579
+ # Extract interpretation attributes
580
+ int_year = extract_value(parsed_hash[:int_year])
581
+
582
+ # Create InterpretationIdentifier with parsed base (int_year -> year)
583
+ Identifiers::InterpretationIdentifier.new(
584
+ base: base,
585
+ year: int_year,
586
+ )
587
+ end
588
+
589
+ # Build conformance supplement with recursive base parsing
590
+ # @param parsed_hash [Hash] parsed data with base and supplement info
591
+ # @return [Identifiers::ConformanceIdentifier] conformance identifier
592
+ def build_conformance_supplement(parsed_hash)
593
+ # Reconstruct base identifier string from parsed components (same logic as corrigendum)
594
+ base_parts = []
595
+ base_data = parsed_hash[:base]
596
+
597
+ # Extract publisher
598
+ if base_data[:publishers]
599
+ pub_data = base_data[:publishers]
600
+ publisher_str = extract_value(pub_data[:publisher])
601
+
602
+ if pub_data[:copublishers] && !pub_data[:copublishers].empty?
603
+ copubs = pub_data[:copublishers]
604
+ copubs = [copubs] unless copubs.is_a?(Array)
605
+ copub_strs = copubs.filter_map do |cp|
606
+ extract_value(cp[:copublisher])
607
+ end
608
+ publisher_str += "/#{copub_strs.join('/')}" if !copub_strs.empty?
609
+ end
610
+
611
+ base_parts << publisher_str
612
+ end
613
+
614
+ # Extract type
615
+ if base_data[:type]
616
+ base_parts << extract_value(base_data[:type])
617
+ end
618
+
619
+ # Extract number with parts and year
620
+ number_str = extract_value(base_data[:number])
621
+
622
+ # Add part if present
623
+ if base_data[:part]
624
+ part_val = extract_value(base_data[:part])
625
+ separator = number_str.match?(/^[A-Z]/) ? "." : "."
626
+ number_str += separator + part_val
627
+ end
628
+
629
+ # Add subpart if present
630
+ if base_data[:subpart]
631
+ subparts = base_data[:subpart]
632
+ subparts = [subparts] unless subparts.is_a?(Array)
633
+ subparts.each do |sp|
634
+ subpart_val = extract_value(sp)
635
+ number_str += ".#{subpart_val}" if subpart_val
636
+ end
637
+ end
638
+
639
+ # Add year with dash
640
+ if base_data[:year]
641
+ year_val = extract_value(base_data[:year])
642
+ number_str += "-#{year_val}"
643
+ end
644
+
645
+ base_parts << number_str
646
+
647
+ # Build base string and recursively parse it
648
+ base_string = base_parts.join(" ")
649
+
650
+ # Recursively parse base identifier using Base.parse
651
+ base = Identifier.parse(base_string)
652
+
653
+ # Extract conformance attributes
654
+ conf_number = extract_value(parsed_hash[:conf_number])
655
+ conf_year = extract_value(parsed_hash[:conf_year])
656
+
657
+ # Create ConformanceIdentifier with parsed base (conf_* -> number/year)
658
+ Identifiers::ConformanceIdentifier.new(
659
+ base: base,
660
+ number: conf_number,
661
+ year: conf_year,
662
+ )
663
+ end
664
+
665
+ # Build joint development identifier from parsed data
666
+ def build_joint_development(parsed)
667
+ attributes = {}
668
+
669
+ # Extract publishers from joint_publishers
670
+ if parsed[:joint_publishers]
671
+ joint_pub_str = extract_value(parsed[:joint_publishers])
672
+ attributes[:publishers] = joint_pub_str.split("/")
673
+ end
674
+
675
+ # Build code with parts if present
676
+ code_parts = []
677
+ code_parts << extract_value(parsed[:part]) if parsed[:part]
678
+
679
+ code_str = extract_value(parsed[:number])
680
+ if code_str && !code_parts.empty?
681
+ code_str += ".#{code_parts.join('.')}"
682
+ end
683
+ attributes[:code] = code_str
684
+
685
+ # Extract year (and optional numeric month, e.g. the -MM of a historical
686
+ # "…-2018-05" ISO-stage date). The joint ISO/IEC rules also use the
687
+ # `edition` rule, whose year is captured as :edition_year (parser.rb);
688
+ # it is promoted to the identity only when no base :year was captured.
689
+ if parsed[:year]
690
+ attributes[:year] = extract_value(parsed[:year])
691
+ elsif parsed[:edition_year]
692
+ attributes[:year] = extract_value(parsed[:edition_year])
693
+ end
694
+ attributes[:month] = extract_value(parsed[:month]) if parsed[:month]
695
+
696
+ # Extract edition, from relaton's "/E-<n>" suffix (normalized to
697
+ # "Edition <n>.0"). nil-residue hand-off item 1.
698
+ attributes[:edition] = extract_value(parsed[:edition]) if parsed[:edition]
699
+ if parsed[:edition_month]
700
+ attributes[:edition_month] = extract_value(parsed[:edition_month])
701
+ end
702
+
703
+ # Extract draft version if present (e.g., D8 from /D8). This applies to
704
+ # both the ISO-led (bucket 5: "…FDIS P15289/D-3-2017") and IEEE-led
705
+ # forms, so it lives before the lead-party split.
706
+ if parsed[:draft_version]
707
+ draft_ver = extract_value(parsed[:draft_version])
708
+ # Remove leading 'D' if present since draft_version already has it
709
+ draft_ver = draft_ver.sub(/^D/, "") if draft_ver
710
+ attributes[:ieee_draft] = "D#{draft_ver}" if draft_ver
711
+ end
712
+
713
+ # Detect lead party based on pattern
714
+ if parsed[:iso_stage]
715
+ # ISO format - lead party is ISO
716
+ attributes[:lead_party] = "ISO"
717
+ attributes[:iso_stage] = extract_value(parsed[:iso_stage])
718
+
719
+ # Create typed_stage for ISO stage
720
+ stage_abbr = attributes[:iso_stage]
721
+ if stage_abbr
722
+ attributes[:typed_stage] =
723
+ Pubid::Ieee.locate_stage(stage_abbr)
724
+ end
725
+ else
726
+ # IEEE format - lead party is IEEE
727
+ attributes[:lead_party] = "IEEE"
728
+
729
+ # Mark as project (P prefix)
730
+ attributes[:type] = "P"
731
+
732
+ # Create typed_stage for IEEE project
733
+ attributes[:typed_stage] =
734
+ Pubid::Ieee.locate_stage("P")
735
+ end
736
+
737
+ Identifiers::JointDevelopment.new(**attributes)
738
+ end
739
+
740
+ # Build SI/PSI identifier from parsed data
741
+ # @param parsed [Hash] parsed SI/PSI data
742
+ # @return [Identifiers::SiStandard] SI or PSI identifier (both use same class)
743
+ def build_si_psi_identifier(parsed)
744
+ attributes = {}
745
+
746
+ # Extract SI type (SI or PSI)
747
+ si_type = extract_value(parsed[:si_type])
748
+
749
+ # Extract publishers (should be "IEEE/ASTM")
750
+ if parsed[:publishers]
751
+ publishers_str = extract_value(parsed[:publishers])
752
+ # Split by slash to get individual publishers
753
+ pubs = publishers_str.split("/")
754
+ attributes[:publisher] = pubs.first
755
+ attributes[:copublisher] = pubs.drop(1) if pubs.length > 1
756
+ end
757
+
758
+ # Extract number
759
+ attributes[:code] = extract_value(parsed[:number])
760
+
761
+ # For PSI (draft), create proper Draft component object
762
+ if si_type == "PSI"
763
+ if parsed[:draft_version]
764
+ draft_version = extract_value(parsed[:draft_version])
765
+ # Create Draft component (Lutaml::Model object)
766
+ attributes[:draft_obj] =
767
+ Components::Draft.new(version: draft_version)
768
+ end
769
+ # Set PSI typed_stage (draft stage)
770
+ attributes[:typed_stage] = Identifiers::SiStandard::TYPED_STAGES.find do |ts|
771
+ ts.abbr.include?("PSI")
772
+ end
773
+ else
774
+ # Set SI typed_stage (published stage)
775
+ attributes[:typed_stage] = Identifiers::SiStandard::TYPED_STAGES.find do |ts|
776
+ ts.abbr.include?("SI")
777
+ end
778
+ end
779
+
780
+ # Extract year and month
781
+ attributes[:year] = extract_value(parsed[:year]) if parsed[:year]
782
+ attributes[:month] = extract_value(parsed[:month]) if parsed[:month]
783
+
784
+ # Handle relationships if present
785
+ if parsed[:relationship_type] || parsed[:relationship_clause]
786
+ attributes[:relationships] = build_relationships(parsed)
787
+ end
788
+
789
+ # Handle parenthetical content
790
+ handle_parameters(parsed, attributes)
791
+
792
+ # BOTH SI and PSI use SiStandard class - just different typed_stages
793
+ Identifiers::SiStandard.new(**attributes)
794
+ end
795
+
796
+ # Build multi-numbered identifier from parsed data
797
+ # @param parsed [Hash] parsed multi-numbered data
798
+ # @return [Identifiers::MultiNumberedIdentifier] multi-numbered identifier
799
+ def build_multi_numbered_identifier(parsed)
800
+ # Build primary identifier
801
+ primary_id = build_single_identifier(parsed[:primary_identifier],
802
+ building_secondary: false)
803
+
804
+ # Build secondary identifier based on format
805
+ if parsed[:secondary_crossref]
806
+ # Cross-reference format: /C62.22.1-1996
807
+ # Build directly to avoid re-parsing which could cause infinite recursion
808
+ crossref = parsed[:secondary_crossref]
809
+ # Pattern: slash >> "C" >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits
810
+ # The parsed crossref is a string - extract the number part
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.
817
+ secondary_id = Identifiers::Standard.new(
818
+ publisher: "IEEE",
819
+ number: extract_value(crossref).to_s.sub(%r{\A/}, ""),
820
+ )
821
+ elsif parsed[:secondary_joint]
822
+ # Joint standard format: ", Std 1177-1989"
823
+ # Build from parsed components
824
+ secondary_id = build_single_identifier(parsed[:secondary_joint],
825
+ building_secondary: true)
826
+ else
827
+ secondary_id = nil
828
+ end
829
+
830
+ Identifiers::MultiNumberedIdentifier.new(
831
+ primary_identifier: primary_id,
832
+ secondary_identifier: secondary_id,
833
+ )
834
+ end
835
+
836
+ # Determine which identifier class to use based on attributes
837
+ # CRITICAL: Use flavor module for type-based decisions, NOT hardcoded logic here
838
+ # Builder's job is ONLY to cast types, not make business decisions
839
+ def determine_identifier_class(attributes)
840
+ # Check for AIEE publisher FIRST (AIEE has its own identifier class)
841
+ # This must be checked before type-based routing because AIEE uses "No" as type
842
+ if attributes[:publisher] == "AIEE"
843
+ return Ieee::Aiee::Identifier
844
+ end
845
+
846
+ # Get type from attributes and use flavor module to locate class
847
+ type_code = attributes[:type]
848
+
849
+ # Use flavor module for type-to-class mapping
850
+ if type_code
851
+ klass = Pubid::Ieee.locate_type(type_code)
852
+ return klass if klass
853
+ end
854
+
855
+ # Non-type-based routing (structural patterns, not business logic)
856
+ # These are structural checks, not type decisions
857
+
858
+ # Check for SI standards (handles both SI and PSI via typed_stage)
859
+ if ["SI", "PSI"].include?(type_code)
860
+ return Identifiers::SiStandard
861
+ end
862
+
863
+ # Check for interpretation supplements (structural: has interpretation flag or int_year)
864
+ if attributes[:interpretation] || attributes[:int_year]
865
+ return Identifiers::InterpretationIdentifier
866
+ end
867
+
868
+ # Check for conformance supplements (structural: has conf_number)
869
+ if attributes[:conf_number]
870
+ return Identifiers::ConformanceIdentifier
871
+ end
872
+
873
+ # Check for corrigendum supplements (structural: has cor_number)
874
+ if attributes[:cor_number]
875
+ return Identifiers::Corrigendum
876
+ end
877
+
878
+ # Check for amendment supplements (structural: has amd_number)
879
+ if attributes[:amd_number]
880
+ return Identifiers::Amendment
881
+ end
882
+
883
+ # Check for multi-numbered standards (structural: has crossref or joint patterns)
884
+ # These are handled at the build() method level, not here
885
+ # (cross-reference patterns like /C62.22.1-1996 and joint standards)
886
+
887
+ # Check for adopted standards (parenthetical adoptions)
888
+ if attributes[:adoption] || (attributes[:parameters] && attributes[:parameters][:adoption])
889
+ return Identifiers::AdoptedStandard
890
+ end
891
+
892
+ # A redline is a plain Standard carrying `redline: true` (a distinct
893
+ # document from its base) — NOT the RedlinedStandard wrapper, which
894
+ # expects a nested `base` the flat build path never sets. render_base
895
+ # restores the " - Redline" suffix from the flag.
896
+
897
+ # Default to the concrete Standard leaf (a plain published standard).
898
+ # Routing to a leaf — rather than instantiating the shared base
899
+ # Ieee::Identifier — is what lets `number` be a reliable :string index
900
+ # key (see Identifiers::Standard).
901
+ Identifiers::Standard
902
+ end
903
+
904
+ # Detect lead party for joint development identifiers
905
+ # @param attributes [Hash] the identifier attributes
906
+ # @return [String] the lead party ("IEEE", "ISO", or "IEC")
907
+ def detect_lead_party(attributes)
908
+ # Rule 1: Check for P prefix in code (IEEE-led)
909
+ if attributes[:code]&.start_with?("P") || attributes[:type] == "P"
910
+ return "IEEE"
911
+ end
912
+
913
+ # Rule 2: Check for IEEE draft notation in original input (IEEE-led)
914
+ if original_input&.match?(/\/D\d+/)
915
+ return "IEEE"
916
+ end
917
+
918
+ # Rule 3: Check for ISO stage codes (ISO-led)
919
+ iso_stages = %w[FDIS DIS CD WD PWI NP]
920
+ if attributes[:typed_stage]
921
+ stage_abbr = attributes[:typed_stage].abbr
922
+ stage_abbr = [stage_abbr] unless stage_abbr.is_a?(Array)
923
+ if stage_abbr.any? { |abbr| iso_stages.include?(abbr) }
924
+ return "ISO"
925
+ end
926
+ end
927
+
928
+ # Rule 4: Check for colon before year in original input (ISO-led)
929
+ if original_input&.match?(/:\d{4}/)
930
+ return "ISO"
931
+ end
932
+
933
+ # Rule 5: Default to first publisher
934
+ attributes[:publisher] || "IEEE"
935
+ end
936
+
937
+ # Merge an array of parsed hashes into a single hash
938
+ # @param parsed_array [Array<Hash>] array of parsed hashes
939
+ # @return [Hash] merged hash
940
+ def merge_parsed_array(parsed_array)
941
+ parsed_array.inject({}) do |result, hash|
942
+ result.merge(hash)
943
+ end
944
+ end
945
+
946
+ # Extract and normalize attributes from parsed data
947
+ # @param parsed [Hash] the parsed data
948
+ # @return [Hash] normalized attributes for identifier
949
+ def extract_attributes(parsed)
950
+ attributes = {}
951
+
952
+ # Handle publishers
953
+ if parsed[:publishers]
954
+ pub_data = parsed[:publishers]
955
+ attributes[:publisher] = extract_value(pub_data[:publisher])
956
+
957
+ if pub_data[:copublishers]
958
+ copubs = pub_data[:copublishers]
959
+ copubs = [copubs] unless copubs.is_a?(Array)
960
+ attributes[:copublisher] = copubs.filter_map do |cp|
961
+ extract_value(cp[:copublisher])
962
+ end
963
+ end
964
+ elsif parsed[:publisher]
965
+ attributes[:publisher] = extract_value(parsed[:publisher])
966
+ end
967
+
968
+ # Build code component with parts and subparts
969
+ code_parts = []
970
+ code_parts << extract_value(parsed[:part]) if parsed[:part]
971
+ if parsed[:subpart]
972
+ subparts = parsed[:subpart]
973
+ subparts = [subparts] unless subparts.is_a?(Array)
974
+ code_parts.concat(subparts.filter_map { |sp| extract_value(sp) })
975
+ end
976
+
977
+ # Create code string with parts
978
+ code_str = extract_value(parsed[:number])
979
+
980
+ # Extract type and draft_status for typed_stage lookup
981
+ type_value = extract_value(parsed[:type])
982
+ draft_status_value = extract_value(parsed[:draft_status])
983
+
984
+ # Handle case where parser captured number without "P" prefix
985
+ # Check original input to see if there was a P before the number
986
+ # The ieee_p_identifier rule consumes P without capturing it
987
+ if !type_value && original_input&.match?(/IEEE\s+P/)
988
+ type_value = "P"
989
+ end
990
+
991
+ # Handle ANSI P prefix patterns (e.g., "ANSI PN42.34-2015")
992
+ if !type_value && original_input&.match?(/ANSI\s+P/)
993
+ # Extract P as type since it was in the original but parser stripped it
994
+ type_value = "P"
995
+ end
996
+
997
+ # NOTE: "P" is a project/draft stage indicator, NOT a code prefix
998
+ # It should be reflected in typed_stage, not in the Code component
999
+ # Do NOT prepend "P" to code_str - code.prefix should remain nil for project drafts
1000
+
1001
+ # Special case: For IEEE/CSA dual published patterns, strip P prefix from code
1002
+ # Pattern: "IEEE/CSA P844.1-2017" -> code should be "844.1" not "P844.1"
1003
+ if code_str&.start_with?("P") && original_input&.include?("/CSA")
1004
+ # Check if the copublisher attribute indicates CSA
1005
+ pub_data = parsed[:publishers]
1006
+ has_csa_copub = if pub_data && pub_data[:copublishers]
1007
+ copubs = pub_data[:copublishers]
1008
+ copubs = [copubs] unless copubs.is_a?(Array)
1009
+ copubs.any? do |cp|
1010
+ extract_value(cp[:copublisher])&.include?("CSA")
1011
+ end
1012
+ else
1013
+ original_input&.include?("/CSA")
1014
+ end
1015
+
1016
+ if has_csa_copub
1017
+ code_str = code_str.sub(/^P/, "")
1018
+ end
1019
+ end
1020
+
1021
+ if code_str && !code_parts.empty?
1022
+ code_str += ".#{code_parts.join('.')}"
1023
+ end
1024
+
1025
+ # Year detection: If code_str ends with year-like pattern (dash + 4 digits, 1884-2099)
1026
+ # and there's no year attribute yet, treat it as a year
1027
+ # Examples: "535-2013", "802.1AC-2016", "C37.41-2016"
1028
+ # AIEE established in 1884, so years range from 1884 to 2099
1029
+ # ONLY apply this when there are NO code_parts (meaning parser didn't capture parts separately)
1030
+ # This prevents breaking legitimate dual-published identifiers
1031
+ # Match ending with dash followed by 4 digits only (not dot, to preserve 802.1AC patterns)
1032
+ if code_str && !parsed[:year] && code_parts.empty? && (match = code_str.match(/^(.+)-(\d{4})$/))
1033
+ potential_year = match[2].to_i
1034
+ if potential_year.between?(1884, 2099)
1035
+ # This is a year - extract it
1036
+ code_str = match[1] # Remove year from code
1037
+ parsed[:year] = match[2] # Add to parsed for extraction below
1038
+ end
1039
+ end
1040
+
1041
+ attributes[:code] = code_str
1042
+
1043
+ # Extract year - check for edition_month parsed separately
1044
+ if parsed[:year]
1045
+ year_str = extract_value(parsed[:year])
1046
+ attributes[:year] = year_str
1047
+ elsif parsed[:edition_year]
1048
+ # The `edition` rule captures its own year under :edition_year (not
1049
+ # :year) so it never collides with the base -YYYY (see parser.rb).
1050
+ # With no base year the edition's year IS the document date and is
1051
+ # promoted to the identity (e.g. "IEEE Std 802.11 Edition 3.0-2009"
1052
+ # -> year 2009). When a base year exists it wins above and
1053
+ # :edition_year is dropped — the edition ordinal is still kept.
1054
+ attributes[:year] = extract_value(parsed[:edition_year])
1055
+ elsif parsed[:trailing_year]
1056
+ # No base -YYYY identity year: the trailing "Month YYYY" IS the
1057
+ # document date (e.g. "IEEE Std 519, May 1993"), so promote it.
1058
+ # When a base year exists it wins (the IEEE approval/identity year)
1059
+ # and the trailing date is intentionally dropped rather than
1060
+ # fabricating a wrong month/year pairing. The trailing captures use
1061
+ # distinct keys (:trailing_month/:trailing_year) so they never collide
1062
+ # with the base :year (Parslet no longer warns "Duplicate subtrees").
1063
+ attributes[:year] = extract_value(parsed[:trailing_year])
1064
+ if parsed[:trailing_month]
1065
+ attributes[:month] = extract_value(parsed[:trailing_month])
1066
+ end
1067
+ end
1068
+
1069
+ # Extract edition_month if parsed separately
1070
+ if parsed[:edition_month]
1071
+ attributes[:edition_month] = extract_value(parsed[:edition_month])
1072
+ end
1073
+
1074
+ # Set type attribute for backward compatibility (after P extraction)
1075
+ # NOTE: "P" IS a type code that maps to ProjectDraftIdentifier via flavor module
1076
+ # Type can be: "Std", "No", "P" (project draft), etc.
1077
+ attributes[:type] = type_value if type_value
1078
+
1079
+ # Lookup typed_stage from registry
1080
+ typed_stage_abbr = determine_stage_abbr(type_value, draft_status_value,
1081
+ parsed)
1082
+ if typed_stage_abbr
1083
+ attributes[:typed_stage] =
1084
+ Pubid::Ieee.locate_stage(typed_stage_abbr)
1085
+ end
1086
+
1087
+ attributes[:draft_status] = draft_status_value
1088
+
1089
+ # Optional attributes (excluding part/subpart since they're in code)
1090
+ extract_optional(parsed, attributes, :edition)
1091
+ extract_optional(parsed, attributes, :revision)
1092
+
1093
+ # Month/day - always extract if present (but not if already extracted from year)
1094
+ unless attributes[:edition_month]
1095
+ extract_optional(parsed, attributes,
1096
+ :month)
1097
+ end
1098
+ extract_optional(parsed, attributes, :day)
1099
+
1100
+ # Handle draft (can be complex)
1101
+ handle_draft(parsed, attributes)
1102
+
1103
+ # Default draft version 1 when the type indicates a Draft but the
1104
+ # parser found no explicit version (issue #205). Real-world IEEE
1105
+ # drafts always have at least version 1; the missing suffix is a
1106
+ # source-data omission, not an undrafted document.
1107
+ apply_default_draft_version(attributes, type_value)
1108
+
1109
+ # Handle corrigendum
1110
+ handle_corrigendum(parsed, attributes)
1111
+
1112
+ # Handle amendment
1113
+ handle_amendment(parsed, attributes)
1114
+
1115
+ # Handle interpretation (already extracted as boolean)
1116
+ # attributes[:interpretation] = true if parsed[:interpretation]
1117
+
1118
+ # Handle conformance
1119
+ handle_conformance(parsed, attributes)
1120
+
1121
+ # Handle ASHRAE copub
1122
+ handle_ashrae_copub(parsed, attributes)
1123
+
1124
+ # Handle IEEE crossref
1125
+ handle_ieee_crossref(parsed, attributes)
1126
+
1127
+ # Handle reaffirmed
1128
+ handle_reaffirmed(parsed, attributes)
1129
+
1130
+ # Handle additional parameters
1131
+ handle_parameters(parsed, attributes)
1132
+
1133
+ # Book nickname
1134
+ if parsed[:nickname]
1135
+ attributes[:nickname] = extract_value(parsed[:nickname])
1136
+ end
1137
+
1138
+ # Interpretation
1139
+ attributes[:interpretation] = true if parsed[:interpretation]
1140
+
1141
+ # Redline
1142
+ attributes[:redline] = true if parsed[:redline]
1143
+
1144
+ attributes
1145
+ end
1146
+
1147
+ # Determine stage abbreviation for TYPED_STAGE lookup
1148
+ # @param type_value [String] the type value (e.g., "Std", "P")
1149
+ # @param draft_status_value [String] the draft status (e.g., "Unapproved")
1150
+ # @param parsed [Hash] the full parsed data
1151
+ # @return [String, nil] the abbreviation to use for stage lookup
1152
+ def determine_stage_abbr(type_value, _draft_status_value, parsed)
1153
+ # Check for specific draft notation (D1, D2, etc.)
1154
+ if parsed[:draft]
1155
+ draft_data = parsed[:draft]
1156
+ if draft_data.is_a?(Array)
1157
+ draft_data = draft_data.inject({}) do |r, e|
1158
+ r.merge(e)
1159
+ end
1160
+ end
1161
+
1162
+ if draft_data.is_a?(Hash) && draft_data[:draft_version]
1163
+ dv = draft_data[:draft_version]
1164
+ version = if dv.is_a?(Array)
1165
+ dv.map do |v|
1166
+ extract_value(v)
1167
+ end.join
1168
+ else
1169
+ extract_value(dv)
1170
+ end
1171
+
1172
+ # Construct draft notation like "D1", "D2", etc.
1173
+ if version
1174
+ draft_abbr = "D#{version}"
1175
+ # Check if this specific draft stage is in registry
1176
+ stage = Pubid::Ieee.locate_stage(draft_abbr)
1177
+ return draft_abbr if stage&.abbr&.include?(draft_abbr)
1178
+ end
1179
+ end
1180
+ end
1181
+
1182
+ # Check type value for known abbreviations
1183
+ if type_value
1184
+ # Remove leading "P" and check if it exists in registry
1185
+ if type_value.start_with?("P")
1186
+ # Could be just "P" prefix or "P" as type indicator
1187
+ # If the type is literally "P" followed by numbers, it's a project identifier
1188
+ # Return "P" to get the generic project typed_stage
1189
+ return "P"
1190
+ elsif type_value == "Std"
1191
+ return "Std"
1192
+ elsif type_value.match?(/^No\.?$/)
1193
+ return type_value
1194
+ end
1195
+ end
1196
+
1197
+ # Default to "Std" for published standards
1198
+ "Std"
1199
+ end
1200
+
1201
+ # Extract a simple value from parsed data
1202
+ # @param value [Object] the value to extract
1203
+ # @return [String, nil] the extracted string value
1204
+ def extract_value(value)
1205
+ return nil if value.nil?
1206
+ return nil if value.is_a?(Array) && value.empty?
1207
+
1208
+ if value.is_a?(Array)
1209
+ joined = value.join
1210
+ return joined.length.positive? ? joined : nil
1211
+ end
1212
+
1213
+ str_value = value.to_s.strip
1214
+ str_value.length.positive? ? str_value : nil
1215
+ end
1216
+
1217
+ # Extract optional attribute if present
1218
+ def extract_optional(parsed, attributes, key)
1219
+ value = extract_value(parsed[key])
1220
+ attributes[key] = value if value
1221
+ end
1222
+
1223
+ # Default draft version to "1" when the identifier type indicates a
1224
+ # Draft but the parser found no explicit version (issue #205).
1225
+ def apply_default_draft_version(attributes, type_value)
1226
+ return if attributes[:draft] || attributes[:draft_obj]
1227
+ return unless type_value.to_s == "Draft Std"
1228
+
1229
+ attributes[:draft] = "D1"
1230
+ # Carry the identifier's year into the draft object so the renderer
1231
+ # doesn't drop it (the renderer suppresses the `-YYYY` suffix when
1232
+ # a draft is attached, expecting the year to live inside /D...).
1233
+ attributes[:draft_obj] =
1234
+ Components::Draft.new(version: "1", year: attributes[:year])
1235
+ end
1236
+
1237
+ # Handle draft information
1238
+ def handle_draft(parsed, attributes)
1239
+ draft_data = parsed[:draft] || parsed[:digit_draft]
1240
+ return unless draft_data
1241
+
1242
+ # Draft can be an array of hash elements or a single hash. The grammar's
1243
+ # `draft_version.repeat(1,2)` splits a dotted designator like "1.2.6"
1244
+ # into ["1.2", ".6"], so DON'T let a plain merge overwrite it (that
1245
+ # dropped every part but the last — "1.2.6" -> ".6"). Collect ALL the
1246
+ # draft_version parts into an array (the version extraction below joins
1247
+ # them back into one verbatim string) and merge the remaining keys.
1248
+ if draft_data.is_a?(Array)
1249
+ hashes = draft_data.select { |e| e.is_a?(Hash) }
1250
+ versions = hashes.filter_map { |e| e[:draft_version] }
1251
+ merged = hashes.inject({}) { |result, elem| result.merge(elem) }
1252
+ merged[:draft_version] = versions unless versions.empty?
1253
+ draft_data = merged
1254
+ end
1255
+
1256
+ # Extract draft version and revision
1257
+ version = nil
1258
+ revision = nil
1259
+ month = nil
1260
+ year = nil
1261
+ day = nil
1262
+ comma_before_month = false
1263
+
1264
+ if draft_data.is_a?(Hash)
1265
+ if draft_data[:draft_version]
1266
+ dv = draft_data[:draft_version]
1267
+ version = if dv.is_a?(Array)
1268
+ dv.map { |v| extract_value(v) }.join
1269
+ else
1270
+ extract_value(dv)
1271
+ end
1272
+ # Normalize a leading hyphen ("/D-3.0" -> "3.0"); the draft
1273
+ # designator is otherwise held verbatim so no digit is lost.
1274
+ version = version.sub(/\A-/, "") if version
1275
+ end
1276
+
1277
+ revision = extract_value(draft_data[:revision]) if draft_data[:revision]
1278
+
1279
+ # Extract date information from draft data
1280
+ month_slice = draft_data[:month]
1281
+ month = extract_value(month_slice) if month_slice
1282
+ year = extract_value(draft_data[:year]) if draft_data[:year]
1283
+ day = extract_value(draft_data[:day]) if draft_data[:day]
1284
+
1285
+ # Detect comma before month and space before draft by checking the original input
1286
+ if original_input
1287
+ if month
1288
+ # Look for ", Month" pattern in the original input
1289
+ comma_before_month = original_input.match?(/,\s+#{Regexp.escape(month)}/i)
1290
+ end
1291
+
1292
+ # Check if there's a space before /D in the original input
1293
+ if version && original_input.match?(/\s+\/D#{Regexp.escape(version)}/i)
1294
+ attributes[:space_before_draft] = true
1295
+ end
1296
+ end
1297
+ else
1298
+ version = extract_value(draft_data)
1299
+ end
1300
+
1301
+ # Create Draft component object if we have version info
1302
+ if version
1303
+ draft_obj = Components::Draft.new(
1304
+ version: version,
1305
+ revision: revision,
1306
+ month: month,
1307
+ year: year,
1308
+ day: day,
1309
+ )
1310
+ draft_obj.comma_before_month = comma_before_month
1311
+ attributes[:draft_obj] = draft_obj
1312
+ attributes[:draft] = draft_obj.to_s
1313
+ end
1314
+ end
1315
+
1316
+ # Handle corrigendum information
1317
+ def handle_corrigendum(parsed, attributes)
1318
+ if parsed[:corrigendum].is_a?(Hash)
1319
+ cor_data = parsed[:corrigendum]
1320
+ attributes[:cor_number] = extract_value(cor_data[:cor_number])
1321
+ if cor_data[:cor_year]
1322
+ attributes[:cor_year] =
1323
+ extract_value(cor_data[:cor_year])
1324
+ end
1325
+ elsif parsed[:corrigendum]
1326
+ attributes[:corrigendum] = extract_value(parsed[:corrigendum])
1327
+ end
1328
+ end
1329
+
1330
+ # Handle amendment information
1331
+ def handle_amendment(parsed, attributes)
1332
+ if parsed[:amendment].is_a?(Hash)
1333
+ amd_data = parsed[:amendment]
1334
+ attributes[:amd_number] = extract_value(amd_data[:amd_number])
1335
+ if amd_data[:amd_year]
1336
+ attributes[:amd_year] =
1337
+ extract_value(amd_data[:amd_year])
1338
+ end
1339
+ elsif parsed[:amendment]
1340
+ attributes[:amendment] = extract_value(parsed[:amendment])
1341
+ end
1342
+ end
1343
+
1344
+ # Handle reaffirmed information
1345
+ def handle_reaffirmed(parsed, attributes)
1346
+ if parsed[:reaffirmed].is_a?(Hash)
1347
+ reaf_data = parsed[:reaffirmed]
1348
+ attributes[:reaffirmed] = extract_value(reaf_data[:year])
1349
+ elsif parsed[:reaffirmed]
1350
+ attributes[:reaffirmed] = extract_value(parsed[:reaffirmed])
1351
+ end
1352
+ end
1353
+
1354
+ # Handle conformance document information
1355
+ def handle_conformance(parsed, attributes)
1356
+ if parsed[:conformance].is_a?(Hash)
1357
+ conf_data = parsed[:conformance]
1358
+ attributes[:conf_number] = extract_value(conf_data[:conf_number])
1359
+ if conf_data[:conf_year]
1360
+ attributes[:conf_year] = extract_value(conf_data[:conf_year])
1361
+ end
1362
+ elsif parsed[:conformance]
1363
+ attributes[:conformance] = extract_value(parsed[:conformance])
1364
+ end
1365
+ end
1366
+
1367
+ # Handle ASHRAE joint publication information
1368
+ def handle_ashrae_copub(parsed, attributes)
1369
+ if parsed[:ashrae_copub].is_a?(Hash)
1370
+ ashrae_data = parsed[:ashrae_copub]
1371
+ attributes[:ashrae_number] =
1372
+ extract_value(ashrae_data[:ashrae_number])
1373
+ if ashrae_data[:ashrae_year]
1374
+ attributes[:ashrae_year] = extract_value(ashrae_data[:ashrae_year])
1375
+ end
1376
+ elsif parsed[:ashrae_copub]
1377
+ attributes[:ashrae_copub] = extract_value(parsed[:ashrae_copub])
1378
+ end
1379
+ end
1380
+
1381
+ # Handle IEEE cross-reference information
1382
+ def handle_ieee_crossref(parsed, attributes)
1383
+ if parsed[:ieee_crossref].is_a?(Hash)
1384
+ parsed[:ieee_crossref]
1385
+ # Extract the cross-reference string (e.g., "C62.22.1-1996")
1386
+ # Store it for rendering
1387
+ attributes[:crossref] =
1388
+ "/C#{extract_value(crossref).to_s.sub(/^\//, '')}"
1389
+ elsif parsed[:ieee_crossref]
1390
+ attributes[:crossref] = extract_value(parsed[:ieee_crossref])
1391
+ end
1392
+ end
1393
+
1394
+ # Handle additional parameters
1395
+ def handle_parameters(parsed, attributes)
1396
+ if parsed[:parameters].is_a?(Hash)
1397
+ param_data = parsed[:parameters]
1398
+
1399
+ # Handle parenthetical content (for multi-part adoptions like "ANSI Y32.21-1976, NCTA 006-0975")
1400
+ if param_data[:parenthetical_content]
1401
+ attributes[:parenthetical_content] =
1402
+ extract_value(param_data[:parenthetical_content])
1403
+ end
1404
+
1405
+ if param_data[:revision_of]
1406
+ attributes[:revision_of] =
1407
+ extract_value(param_data[:revision_of])
1408
+ end
1409
+ if param_data[:amendment_to]
1410
+ attributes[:amendment_to] =
1411
+ extract_value(param_data[:amendment_to])
1412
+ end
1413
+ if param_data[:adoption]
1414
+ attributes[:adoption] =
1415
+ extract_value(param_data[:adoption])
1416
+ end
1417
+ if param_data[:note]
1418
+ attributes[:note] =
1419
+ extract_value(param_data[:note])
1420
+ end
1421
+ end
1422
+ end
1423
+
1424
+ # Build relationships from parsed relationship clause (Pattern 4)
1425
+ # @param parsed_hash [Hash] the parsed data containing relationship information
1426
+ # @return [Array<Components::Relationship>] array of Relationship objects
1427
+ def build_relationships(parsed_hash)
1428
+ return [] unless parsed_hash[:relationship_type] || parsed_hash[:relationship_clause]
1429
+
1430
+ relationships = []
1431
+
1432
+ # Main relationship
1433
+ if parsed_hash[:relationship_type]
1434
+ rel_type = extract_relationship_type(parsed_hash[:relationship_type])
1435
+ related = parse_identifier_list(parsed_hash[:related_ids])
1436
+
1437
+ # Handle "as amended by" clause
1438
+ amendments = nil
1439
+ approved_flag = false
1440
+
1441
+ if parsed_hash[:amendments]
1442
+ # Check if it's the "and its approved amendments" flag
1443
+ if parsed_hash[:amendments].is_a?(Hash) && parsed_hash[:amendments][:approved_amendments]
1444
+ approved_flag = true
1445
+ else
1446
+ amendments = parse_identifier_list(parsed_hash[:amendments])
1447
+ end
1448
+ end
1449
+
1450
+ relationships << Components::Relationship.new(
1451
+ relationship_type: rel_type,
1452
+ related_identifiers: related,
1453
+ intermediate_amendments: amendments,
1454
+ approved_amendments_flag: approved_flag,
1455
+ )
1456
+ end
1457
+
1458
+ # Additional relationships (separated by " / ")
1459
+ if parsed_hash[:additional_rels]
1460
+ additional = parsed_hash[:additional_rels]
1461
+ additional = [additional] unless additional.is_a?(Array)
1462
+
1463
+ additional.each do |rel_data|
1464
+ rel_type = extract_relationship_type(rel_data[:relationship_type])
1465
+ related = parse_identifier_list(rel_data[:related_ids])
1466
+
1467
+ # Handle "as amended by" clause for additional relationships
1468
+ amendments = nil
1469
+ approved_flag = false
1470
+
1471
+ if rel_data[:amendments]
1472
+ if rel_data[:amendments].is_a?(Hash) && rel_data[:amendments][:approved_amendments]
1473
+ approved_flag = true
1474
+ else
1475
+ amendments = parse_identifier_list(rel_data[:amendments])
1476
+ end
1477
+ end
1478
+
1479
+ relationships << Components::Relationship.new(
1480
+ relationship_type: rel_type,
1481
+ related_identifiers: related,
1482
+ intermediate_amendments: amendments,
1483
+ approved_amendments_flag: approved_flag,
1484
+ )
1485
+ end
1486
+ end
1487
+
1488
+ relationships
1489
+ end
1490
+
1491
+ # Extract relationship type from parsed hash
1492
+ # @param type_hash [Hash] hash with relationship type key
1493
+ # @return [String] the relationship type constant name
1494
+ def extract_relationship_type(type_hash)
1495
+ return nil unless type_hash.is_a?(Hash)
1496
+
1497
+ # type_hash has key like :revision_of, :amendment_to, etc.
1498
+ type_hash.keys.first.to_s
1499
+ end
1500
+
1501
+ # Parse a list of identifier strings into identifier objects
1502
+ # @param list_data [Hash, Array, nil] the parsed identifier list data
1503
+ # @return [Array<Identifier>] array of parsed identifier objects
1504
+ def parse_identifier_list(list_data)
1505
+ return [] unless list_data
1506
+
1507
+ # Extract identifier strings from parsed data
1508
+ id_strings = extract_identifier_strings(list_data)
1509
+
1510
+ # Recursively parse each identifier string
1511
+ id_strings.map do |id_str|
1512
+ # Use Identifier.parse for recursive parsing
1513
+ Identifier.parse(id_str)
1514
+ rescue Parslet::ParseFailed
1515
+ # If parsing fails, create minimal identifier with just the string
1516
+ # This maintains graceful degradation
1517
+ Identifier.new(parenthetical_content: id_str)
1518
+ end
1519
+ end
1520
+
1521
+ # Extract identifier strings from parsed list data
1522
+ # @param list_data [Hash, Array] the parsed list data
1523
+ # @return [Array<String>] array of identifier strings
1524
+ def extract_identifier_strings(list_data)
1525
+ if list_data.is_a?(Array)
1526
+ # Array of id hashes: [{id: "..."}, {id: "..."}]
1527
+ list_data.map { |item| item[:id].to_s.strip }
1528
+ elsif list_data.is_a?(Hash) && list_data[:id]
1529
+ # Single id hash: {id: "..."}
1530
+ [list_data[:id].to_s.strip]
1531
+ else
1532
+ []
1533
+ end
1534
+ end
1535
+
1536
+ # Build combined AIEE identifier from "and"-separated identifiers
1537
+ # @param parsed [Hash] parsed combined AIEE data
1538
+ # @return [Identifiers::CombinedAiee] combined AIEE identifier
1539
+ def build_combined_aiee(parsed)
1540
+ # Build each AIEE identifier using AIEE builder
1541
+ aiee_builder = Aiee::Builder.new
1542
+ first_id = aiee_builder.build(parsed[:first_aiee])
1543
+ second_id = aiee_builder.build(parsed[:second_aiee])
1544
+
1545
+ # Create a simple combined identifier (reuse DualPublished pattern)
1546
+ Identifiers::DualPublished.new(
1547
+ first_identifier: first_id,
1548
+ second_identifier: second_id,
1549
+ separator: " and ",
1550
+ )
1551
+ end
1552
+ end
1553
+ end
1554
+ end