pubid 1.15.17 → 2.0.0.pre.alpha.1

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 (601) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.adoc +2041 -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/ccsds/update_codes.yaml +1 -0
  18. data/data/iec/update_codes.yaml +67 -0
  19. data/data/ieee/update_codes.yaml +104 -0
  20. data/data/iso/update_codes.yaml +21 -0
  21. data/data/nist/update_codes.yaml +87 -0
  22. data/data/plateau/update_codes.yaml +6 -0
  23. data/lib/pubid/amca/builder.rb +176 -0
  24. data/lib/pubid/amca/identifier.rb +18 -0
  25. data/lib/pubid/amca/identifiers/base.rb +64 -0
  26. data/lib/pubid/amca/identifiers/interpretation.rb +51 -0
  27. data/lib/pubid/amca/identifiers/publication.rb +47 -0
  28. data/lib/pubid/amca/identifiers/standard.rb +22 -0
  29. data/lib/pubid/amca/identifiers.rb +12 -0
  30. data/lib/pubid/amca/parser.rb +153 -0
  31. data/lib/pubid/amca/scheme.rb +16 -0
  32. data/lib/pubid/amca/single_identifier.rb +33 -0
  33. data/lib/pubid/amca/urn_generator.rb +50 -0
  34. data/lib/pubid/amca.rb +26 -0
  35. data/lib/pubid/ansi/builder.rb +52 -0
  36. data/lib/pubid/ansi/identifier.rb +13 -0
  37. data/lib/pubid/ansi/identifiers/american_national_standard.rb +12 -0
  38. data/lib/pubid/ansi/identifiers/standard.rb +16 -0
  39. data/lib/pubid/ansi/identifiers.rb +11 -0
  40. data/lib/pubid/ansi/parser.rb +91 -0
  41. data/lib/pubid/ansi/scheme.rb +15 -0
  42. data/lib/pubid/ansi/single_identifier.rb +45 -0
  43. data/lib/pubid/ansi/urn_generator.rb +76 -0
  44. data/lib/pubid/ansi.rb +27 -0
  45. data/lib/pubid/api/builder.rb +85 -0
  46. data/lib/pubid/api/components/code.rb +9 -0
  47. data/lib/pubid/api/identifier.rb +21 -0
  48. data/lib/pubid/api/identifiers/base.rb +24 -0
  49. data/lib/pubid/api/identifiers/bulletin.rb +15 -0
  50. data/lib/pubid/api/identifiers/continuous_operations_standard.rb +15 -0
  51. data/lib/pubid/api/identifiers/mpms.rb +44 -0
  52. data/lib/pubid/api/identifiers/publication.rb +15 -0
  53. data/lib/pubid/api/identifiers/recommended_practice.rb +15 -0
  54. data/lib/pubid/api/identifiers/specification.rb +15 -0
  55. data/lib/pubid/api/identifiers/standard.rb +15 -0
  56. data/lib/pubid/api/identifiers/technical_report.rb +15 -0
  57. data/lib/pubid/api/identifiers/typeless_standard.rb +27 -0
  58. data/lib/pubid/api/parser.rb +140 -0
  59. data/lib/pubid/api/scheme.rb +66 -0
  60. data/lib/pubid/api/single_identifier.rb +46 -0
  61. data/lib/pubid/api/urn_generator.rb +41 -0
  62. data/lib/pubid/api.rb +17 -0
  63. data/lib/pubid/ashrae/builder.rb +498 -0
  64. data/lib/pubid/ashrae/identifier.rb +18 -0
  65. data/lib/pubid/ashrae/identifiers/addenda_package.rb +46 -0
  66. data/lib/pubid/ashrae/identifiers/addendum.rb +55 -0
  67. data/lib/pubid/ashrae/identifiers/base.rb +23 -0
  68. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +51 -0
  69. data/lib/pubid/ashrae/identifiers/errata.rb +40 -0
  70. data/lib/pubid/ashrae/identifiers/guideline.rb +38 -0
  71. data/lib/pubid/ashrae/identifiers/interpretation.rb +39 -0
  72. data/lib/pubid/ashrae/identifiers/standard.rb +38 -0
  73. data/lib/pubid/ashrae/identifiers.rb +16 -0
  74. data/lib/pubid/ashrae/parser.rb +724 -0
  75. data/lib/pubid/ashrae/scheme.rb +53 -0
  76. data/lib/pubid/ashrae/single_identifier.rb +23 -0
  77. data/lib/pubid/ashrae/supplement_identifier.rb +23 -0
  78. data/lib/pubid/ashrae/urn_generator.rb +59 -0
  79. data/lib/pubid/ashrae.rb +21 -0
  80. data/lib/pubid/asme/builder.rb +153 -0
  81. data/lib/pubid/asme/components/code.rb +18 -0
  82. data/lib/pubid/asme/identifier.rb +15 -0
  83. data/lib/pubid/asme/identifiers/base.rb +70 -0
  84. data/lib/pubid/asme/identifiers/standard.rb +12 -0
  85. data/lib/pubid/asme/identifiers.rb +10 -0
  86. data/lib/pubid/asme/parser.rb +308 -0
  87. data/lib/pubid/asme/scheme.rb +37 -0
  88. data/lib/pubid/asme/single_identifier.rb +29 -0
  89. data/lib/pubid/asme/urn_generator.rb +133 -0
  90. data/lib/pubid/asme.rb +21 -0
  91. data/lib/pubid/astm/builder.rb +159 -0
  92. data/lib/pubid/astm/components/code.rb +33 -0
  93. data/lib/pubid/astm/identifier.rb +15 -0
  94. data/lib/pubid/astm/identifiers/adjunct.rb +21 -0
  95. data/lib/pubid/astm/identifiers/base.rb +13 -0
  96. data/lib/pubid/astm/identifiers/data_series.rb +25 -0
  97. data/lib/pubid/astm/identifiers/iso_dual_published.rb +74 -0
  98. data/lib/pubid/astm/identifiers/manual.rb +40 -0
  99. data/lib/pubid/astm/identifiers/monograph.rb +25 -0
  100. data/lib/pubid/astm/identifiers/research_report.rb +18 -0
  101. data/lib/pubid/astm/identifiers/standard.rb +52 -0
  102. data/lib/pubid/astm/identifiers/technical_report.rb +23 -0
  103. data/lib/pubid/astm/identifiers/work_in_progress.rb +21 -0
  104. data/lib/pubid/astm/parser.rb +244 -0
  105. data/lib/pubid/astm/scheme.rb +55 -0
  106. data/lib/pubid/astm/single_identifier.rb +25 -0
  107. data/lib/pubid/astm/urn_generator.rb +99 -0
  108. data/lib/pubid/astm.rb +38 -0
  109. data/lib/pubid/bsi/builder.rb +1483 -0
  110. data/lib/pubid/bsi/components/code.rb +11 -0
  111. data/lib/pubid/bsi/components/date.rb +11 -0
  112. data/lib/pubid/bsi/components/publisher.rb +11 -0
  113. data/lib/pubid/bsi/components/type.rb +11 -0
  114. data/lib/pubid/bsi/identifier.rb +27 -0
  115. data/lib/pubid/bsi/identifiers/addendum_document.rb +64 -0
  116. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +95 -0
  117. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +82 -0
  118. data/lib/pubid/bsi/identifiers/aerospace_standard.rb +118 -0
  119. data/lib/pubid/bsi/identifiers/amendment.rb +40 -0
  120. data/lib/pubid/bsi/identifiers/base.rb +11 -0
  121. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +27 -0
  122. data/lib/pubid/bsi/identifiers/british_standard.rb +33 -0
  123. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +114 -0
  124. data/lib/pubid/bsi/identifiers/committee_document.rb +51 -0
  125. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +152 -0
  126. data/lib/pubid/bsi/identifiers/corrigendum.rb +28 -0
  127. data/lib/pubid/bsi/identifiers/detailed_specification.rb +69 -0
  128. data/lib/pubid/bsi/identifiers/disc.rb +56 -0
  129. data/lib/pubid/bsi/identifiers/draft_document.rb +71 -0
  130. data/lib/pubid/bsi/identifiers/electronic_book.rb +52 -0
  131. data/lib/pubid/bsi/identifiers/expert_commentary.rb +47 -0
  132. data/lib/pubid/bsi/identifiers/explanatory_supplement.rb +82 -0
  133. data/lib/pubid/bsi/identifiers/flex.rb +61 -0
  134. data/lib/pubid/bsi/identifiers/handbook.rb +39 -0
  135. data/lib/pubid/bsi/identifiers/index.rb +62 -0
  136. data/lib/pubid/bsi/identifiers/method.rb +76 -0
  137. data/lib/pubid/bsi/identifiers/national_annex.rb +73 -0
  138. data/lib/pubid/bsi/identifiers/practice_guide.rb +27 -0
  139. data/lib/pubid/bsi/identifiers/publicly_available_specification.rb +79 -0
  140. data/lib/pubid/bsi/identifiers/published_document.rb +79 -0
  141. data/lib/pubid/bsi/identifiers/section.rb +62 -0
  142. data/lib/pubid/bsi/identifiers/set.rb +46 -0
  143. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +40 -0
  144. data/lib/pubid/bsi/identifiers/supplement_document.rb +51 -0
  145. data/lib/pubid/bsi/identifiers/supplementary_index.rb +81 -0
  146. data/lib/pubid/bsi/identifiers/technical_specification.rb +79 -0
  147. data/lib/pubid/bsi/identifiers/test_method.rb +67 -0
  148. data/lib/pubid/bsi/identifiers/value_added_publication.rb +52 -0
  149. data/lib/pubid/bsi/identifiers.rb +52 -0
  150. data/lib/pubid/bsi/model.rb +196 -0
  151. data/lib/pubid/bsi/parser.rb +659 -0
  152. data/lib/pubid/bsi/scheme.rb +243 -0
  153. data/lib/pubid/bsi/single_identifier.rb +129 -0
  154. data/lib/pubid/bsi/urn_generator.rb +84 -0
  155. data/lib/pubid/bsi.rb +32 -0
  156. data/lib/pubid/builder/base.rb +138 -0
  157. data/lib/pubid/bundled_identifier.rb +126 -0
  158. data/lib/pubid/ccsds/builder.rb +56 -0
  159. data/lib/pubid/ccsds/identifier.rb +16 -0
  160. data/lib/pubid/ccsds/identifiers/base.rb +78 -0
  161. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +70 -0
  162. data/lib/pubid/ccsds/identifiers/corrigendum.rb +39 -0
  163. data/lib/pubid/ccsds/identifiers.rb +10 -0
  164. data/lib/pubid/ccsds/parser.rb +71 -0
  165. data/lib/pubid/ccsds/scheme.rb +57 -0
  166. data/lib/pubid/ccsds/single_identifier.rb +74 -0
  167. data/lib/pubid/ccsds/supplement_identifier.rb +33 -0
  168. data/lib/pubid/ccsds/urn_generator.rb +115 -0
  169. data/lib/pubid/ccsds.rb +21 -0
  170. data/lib/pubid/cen_cenelec/builder.rb +330 -0
  171. data/lib/pubid/cen_cenelec/identifier.rb +15 -0
  172. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +40 -0
  173. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +29 -0
  174. data/lib/pubid/cen_cenelec/identifiers/base.rb +75 -0
  175. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +28 -0
  176. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +27 -0
  177. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +28 -0
  178. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +61 -0
  179. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +35 -0
  180. data/lib/pubid/cen_cenelec/identifiers/european_norm.rb +41 -0
  181. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +37 -0
  182. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +28 -0
  183. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +22 -0
  184. data/lib/pubid/cen_cenelec/identifiers/guide.rb +27 -0
  185. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +27 -0
  186. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +27 -0
  187. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +35 -0
  188. data/lib/pubid/cen_cenelec/identifiers.rb +32 -0
  189. data/lib/pubid/cen_cenelec/parser.rb +144 -0
  190. data/lib/pubid/cen_cenelec/scheme.rb +164 -0
  191. data/lib/pubid/cen_cenelec/single_identifier.rb +130 -0
  192. data/lib/pubid/cen_cenelec/supplement_identifier.rb +48 -0
  193. data/lib/pubid/cen_cenelec/urn_generator.rb +129 -0
  194. data/lib/pubid/cen_cenelec.rb +21 -0
  195. data/lib/pubid/cie/builder.rb +399 -0
  196. data/lib/pubid/cie/components/code.rb +72 -0
  197. data/lib/pubid/cie/components/language.rb +58 -0
  198. data/lib/pubid/cie/identifier.rb +18 -0
  199. data/lib/pubid/cie/identifiers/bundle.rb +20 -0
  200. data/lib/pubid/cie/identifiers/conference.rb +32 -0
  201. data/lib/pubid/cie/identifiers/corrigendum.rb +40 -0
  202. data/lib/pubid/cie/identifiers/dual_published.rb +41 -0
  203. data/lib/pubid/cie/identifiers/identical.rb +64 -0
  204. data/lib/pubid/cie/identifiers/joint_published.rb +52 -0
  205. data/lib/pubid/cie/identifiers/standard.rb +58 -0
  206. data/lib/pubid/cie/identifiers/supplement.rb +45 -0
  207. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +20 -0
  208. data/lib/pubid/cie/identifiers.rb +17 -0
  209. data/lib/pubid/cie/parser.rb +347 -0
  210. data/lib/pubid/cie/scheme.rb +64 -0
  211. data/lib/pubid/cie/single_identifier.rb +30 -0
  212. data/lib/pubid/cie/supplement_identifier.rb +26 -0
  213. data/lib/pubid/cie/urn_generator.rb +123 -0
  214. data/lib/pubid/cie.rb +28 -0
  215. data/lib/pubid/components/code.rb +33 -0
  216. data/lib/pubid/components/date.rb +49 -0
  217. data/lib/pubid/components/edition.rb +32 -0
  218. data/lib/pubid/components/language.rb +37 -0
  219. data/lib/pubid/components/locality.rb +10 -0
  220. data/lib/pubid/components/publisher.rb +36 -0
  221. data/lib/pubid/components/stage.rb +54 -0
  222. data/lib/pubid/components/type.rb +58 -0
  223. data/lib/pubid/components/typed_stage.rb +55 -0
  224. data/lib/pubid/components.rb +15 -0
  225. data/lib/pubid/core/pattern_doc_generator.rb +272 -0
  226. data/lib/pubid/core/update_codes.rb +77 -0
  227. data/lib/pubid/core.rb +8 -0
  228. data/lib/pubid/csa/builder.rb +671 -0
  229. data/lib/pubid/csa/components/code.rb +9 -0
  230. data/lib/pubid/csa/components.rb +9 -0
  231. data/lib/pubid/csa/composite_identifier.rb +27 -0
  232. data/lib/pubid/csa/identifier.rb +457 -0
  233. data/lib/pubid/csa/identifiers/base.rb +133 -0
  234. data/lib/pubid/csa/identifiers/bundled.rb +125 -0
  235. data/lib/pubid/csa/identifiers/canadian_adopted.rb +82 -0
  236. data/lib/pubid/csa/identifiers/cec.rb +129 -0
  237. data/lib/pubid/csa/identifiers/combined.rb +130 -0
  238. data/lib/pubid/csa/identifiers/csa_adopted.rb +78 -0
  239. data/lib/pubid/csa/identifiers/package.rb +65 -0
  240. data/lib/pubid/csa/identifiers/series.rb +127 -0
  241. data/lib/pubid/csa/identifiers/standard.rb +10 -0
  242. data/lib/pubid/csa/identifiers.rb +17 -0
  243. data/lib/pubid/csa/parser.rb +445 -0
  244. data/lib/pubid/csa/scheme.rb +44 -0
  245. data/lib/pubid/csa/single_identifier.rb +30 -0
  246. data/lib/pubid/csa/urn_generator.rb +80 -0
  247. data/lib/pubid/csa/wrapper_identifier.rb +31 -0
  248. data/lib/pubid/csa.rb +25 -0
  249. data/lib/pubid/etsi/builder.rb +133 -0
  250. data/lib/pubid/etsi/components/code.rb +42 -0
  251. data/lib/pubid/etsi/components/version.rb +37 -0
  252. data/lib/pubid/etsi/components.rb +10 -0
  253. data/lib/pubid/etsi/identifier.rb +14 -0
  254. data/lib/pubid/etsi/identifiers/amendment.rb +15 -0
  255. data/lib/pubid/etsi/identifiers/base.rb +38 -0
  256. data/lib/pubid/etsi/identifiers/corrigendum.rb +15 -0
  257. data/lib/pubid/etsi/identifiers/etsi_standard.rb +19 -0
  258. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +91 -0
  259. data/lib/pubid/etsi/identifiers.rb +14 -0
  260. data/lib/pubid/etsi/parser.rb +133 -0
  261. data/lib/pubid/etsi/scheme.rb +42 -0
  262. data/lib/pubid/etsi/urn_generator.rb +76 -0
  263. data/lib/pubid/etsi.rb +21 -0
  264. data/lib/pubid/export/auditor.rb +89 -0
  265. data/lib/pubid/export/data_class_exporter.rb +59 -0
  266. data/lib/pubid/export/exporter.rb +74 -0
  267. data/lib/pubid/export/flavor_exporter.rb +402 -0
  268. data/lib/pubid/export/ieee_exporter.rb +78 -0
  269. data/lib/pubid/export/itu_exporter.rb +66 -0
  270. data/lib/pubid/export/nist_exporter.rb +64 -0
  271. data/lib/pubid/export/registry_exporter.rb +90 -0
  272. data/lib/pubid/export/result.rb +97 -0
  273. data/lib/pubid/export/scheme_exporter.rb +70 -0
  274. data/lib/pubid/export.rb +18 -0
  275. data/lib/pubid/format_detector.rb +16 -0
  276. data/lib/pubid/format_registry.rb +42 -0
  277. data/lib/pubid/identifier.rb +235 -0
  278. data/lib/pubid/identifier_metadata.rb +148 -0
  279. data/lib/pubid/identifier_registry.rb +198 -0
  280. data/lib/pubid/idf/builder.rb +82 -0
  281. data/lib/pubid/idf/identifier.rb +69 -0
  282. data/lib/pubid/idf/identifiers/amendment.rb +27 -0
  283. data/lib/pubid/idf/identifiers/corrigendum.rb +27 -0
  284. data/lib/pubid/idf/identifiers/international_standard.rb +123 -0
  285. data/lib/pubid/idf/identifiers/reviewed_method.rb +100 -0
  286. data/lib/pubid/idf/identifiers.rb +13 -0
  287. data/lib/pubid/idf/parser.rb +143 -0
  288. data/lib/pubid/idf/scheme.rb +61 -0
  289. data/lib/pubid/idf/single_identifier.rb +19 -0
  290. data/lib/pubid/idf/supplement_identifier.rb +43 -0
  291. data/lib/pubid/idf/urn_generator.rb +84 -0
  292. data/lib/pubid/idf.rb +25 -0
  293. data/lib/pubid/iec/builder.rb +457 -0
  294. data/lib/pubid/iec/components/code.rb +59 -0
  295. data/lib/pubid/iec/components/consolidated_amendment.rb +59 -0
  296. data/lib/pubid/iec/components/publisher.rb +35 -0
  297. data/lib/pubid/iec/components/sheet.rb +32 -0
  298. data/lib/pubid/iec/components/trf_info.rb +38 -0
  299. data/lib/pubid/iec/components/vap_suffix.rb +41 -0
  300. data/lib/pubid/iec/identifier.rb +21 -0
  301. data/lib/pubid/iec/identifiers/amendment.rb +94 -0
  302. data/lib/pubid/iec/identifiers/base.rb +78 -0
  303. data/lib/pubid/iec/identifiers/component_specification.rb +39 -0
  304. data/lib/pubid/iec/identifiers/conformity_assessment.rb +39 -0
  305. data/lib/pubid/iec/identifiers/consolidated_identifier.rb +86 -0
  306. data/lib/pubid/iec/identifiers/corrigendum.rb +94 -0
  307. data/lib/pubid/iec/identifiers/fragment_identifier.rb +141 -0
  308. data/lib/pubid/iec/identifiers/guide.rb +104 -0
  309. data/lib/pubid/iec/identifiers/international_standard.rb +147 -0
  310. data/lib/pubid/iec/identifiers/interpretation_sheet.rb +104 -0
  311. data/lib/pubid/iec/identifiers/operational_document.rb +39 -0
  312. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +101 -0
  313. data/lib/pubid/iec/identifiers/sheet_identifier.rb +66 -0
  314. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +40 -0
  315. data/lib/pubid/iec/identifiers/systems_reference_document.rb +40 -0
  316. data/lib/pubid/iec/identifiers/technical_report.rb +132 -0
  317. data/lib/pubid/iec/identifiers/technical_specification.rb +132 -0
  318. data/lib/pubid/iec/identifiers/technology_report.rb +39 -0
  319. data/lib/pubid/iec/identifiers/test_report_form.rb +78 -0
  320. data/lib/pubid/iec/identifiers/vap_identifier.rb +77 -0
  321. data/lib/pubid/iec/identifiers/white_paper.rb +39 -0
  322. data/lib/pubid/iec/identifiers/working_document.rb +96 -0
  323. data/lib/pubid/iec/parser.rb +412 -0
  324. data/lib/pubid/iec/rendering_style.rb +113 -0
  325. data/lib/pubid/iec/scheme.rb +71 -0
  326. data/lib/pubid/iec/single_identifier.rb +80 -0
  327. data/lib/pubid/iec/supplement_identifier.rb +161 -0
  328. data/lib/pubid/iec/urn_generator.rb +193 -0
  329. data/lib/pubid/iec/urn_parser.rb +289 -0
  330. data/lib/pubid/iec.rb +85 -0
  331. data/lib/pubid/ieee/aiee/builder.rb +71 -0
  332. data/lib/pubid/ieee/aiee/identifier.rb +105 -0
  333. data/lib/pubid/ieee/aiee/parser.rb +130 -0
  334. data/lib/pubid/ieee/aiee.rb +11 -0
  335. data/lib/pubid/ieee/builder.rb +1237 -0
  336. data/lib/pubid/ieee/components/code.rb +102 -0
  337. data/lib/pubid/ieee/components/draft.rb +93 -0
  338. data/lib/pubid/ieee/components/relationship.rb +157 -0
  339. data/lib/pubid/ieee/components/typed_stage.rb +100 -0
  340. data/lib/pubid/ieee/identifier.rb +13 -0
  341. data/lib/pubid/ieee/identifiers/adopted_standard.rb +33 -0
  342. data/lib/pubid/ieee/identifiers/base.rb +591 -0
  343. data/lib/pubid/ieee/identifiers/conformance_identifier.rb +35 -0
  344. data/lib/pubid/ieee/identifiers/corrigendum.rb +37 -0
  345. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +51 -0
  346. data/lib/pubid/ieee/identifiers/dual_identifier.rb +18 -0
  347. data/lib/pubid/ieee/identifiers/dual_published.rb +28 -0
  348. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +27 -0
  349. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +34 -0
  350. data/lib/pubid/ieee/identifiers/joint_development.rb +172 -0
  351. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +51 -0
  352. data/lib/pubid/ieee/identifiers/nesc/base.rb +56 -0
  353. data/lib/pubid/ieee/identifiers/nesc/draft.rb +28 -0
  354. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +32 -0
  355. data/lib/pubid/ieee/identifiers/nesc/redline.rb +26 -0
  356. data/lib/pubid/ieee/identifiers/nesc/standard.rb +26 -0
  357. data/lib/pubid/ieee/identifiers/nesc.rb +15 -0
  358. data/lib/pubid/ieee/identifiers/parenthetical_identifier.rb +20 -0
  359. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +26 -0
  360. data/lib/pubid/ieee/identifiers/redlined_standard.rb +33 -0
  361. data/lib/pubid/ieee/identifiers/si_standard.rb +73 -0
  362. data/lib/pubid/ieee/identifiers/standard.rb +41 -0
  363. data/lib/pubid/ieee/identifiers/supplement_identifier.rb +23 -0
  364. data/lib/pubid/ieee/identifiers.rb +33 -0
  365. data/lib/pubid/ieee/ire/builder.rb +61 -0
  366. data/lib/pubid/ieee/ire/identifier.rb +58 -0
  367. data/lib/pubid/ieee/ire/parser.rb +91 -0
  368. data/lib/pubid/ieee/ire.rb +11 -0
  369. data/lib/pubid/ieee/nesc/builder.rb +101 -0
  370. data/lib/pubid/ieee/nesc/parser.rb +154 -0
  371. data/lib/pubid/ieee/nesc.rb +10 -0
  372. data/lib/pubid/ieee/parser.rb +1226 -0
  373. data/lib/pubid/ieee/scheme.rb +90 -0
  374. data/lib/pubid/ieee/typed_stages.rb +172 -0
  375. data/lib/pubid/ieee/urn_generator.rb +188 -0
  376. data/lib/pubid/ieee.rb +32 -0
  377. data/lib/pubid/ieee_debug.rb +31 -0
  378. data/lib/pubid/iho/builder.rb +37 -0
  379. data/lib/pubid/iho/identifier.rb +19 -0
  380. data/lib/pubid/iho/identifiers/base.rb +41 -0
  381. data/lib/pubid/iho/identifiers/bibliographic.rb +20 -0
  382. data/lib/pubid/iho/identifiers/circular_letter.rb +19 -0
  383. data/lib/pubid/iho/identifiers/miscellaneous.rb +20 -0
  384. data/lib/pubid/iho/identifiers/publication.rb +19 -0
  385. data/lib/pubid/iho/identifiers/standard.rb +19 -0
  386. data/lib/pubid/iho/identifiers.rb +14 -0
  387. data/lib/pubid/iho/parser.rb +68 -0
  388. data/lib/pubid/iho/scheme.rb +29 -0
  389. data/lib/pubid/iho/urn_generator.rb +29 -0
  390. data/lib/pubid/iho.rb +21 -0
  391. data/lib/pubid/iso/builder.rb +305 -0
  392. data/lib/pubid/iso/bundled_identifier.rb +85 -0
  393. data/lib/pubid/iso/combined_identifier.rb +22 -0
  394. data/lib/pubid/iso/components/code.rb +36 -0
  395. data/lib/pubid/iso/components/publisher.rb +60 -0
  396. data/lib/pubid/iso/components.rb +12 -0
  397. data/lib/pubid/iso/format_resolver.rb +45 -0
  398. data/lib/pubid/iso/identifier.rb +69 -0
  399. data/lib/pubid/iso/identifiers/addendum.rb +104 -0
  400. data/lib/pubid/iso/identifiers/amendment.rb +128 -0
  401. data/lib/pubid/iso/identifiers/base.rb +115 -0
  402. data/lib/pubid/iso/identifiers/corrigendum.rb +108 -0
  403. data/lib/pubid/iso/identifiers/data.rb +76 -0
  404. data/lib/pubid/iso/identifiers/directives.rb +59 -0
  405. data/lib/pubid/iso/identifiers/directives_supplement.rb +119 -0
  406. data/lib/pubid/iso/identifiers/extract.rb +30 -0
  407. data/lib/pubid/iso/identifiers/guide.rb +100 -0
  408. data/lib/pubid/iso/identifiers/international_standard.rb +168 -0
  409. data/lib/pubid/iso/identifiers/international_standardized_profile.rb +94 -0
  410. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +89 -0
  411. data/lib/pubid/iso/identifiers/pas.rb +93 -0
  412. data/lib/pubid/iso/identifiers/recommendation.rb +45 -0
  413. data/lib/pubid/iso/identifiers/supplement.rb +87 -0
  414. data/lib/pubid/iso/identifiers/tc_document.rb +108 -0
  415. data/lib/pubid/iso/identifiers/technical_report.rb +103 -0
  416. data/lib/pubid/iso/identifiers/technical_specification.rb +102 -0
  417. data/lib/pubid/iso/identifiers/technology_trends_assessments.rb +95 -0
  418. data/lib/pubid/iso/identifiers.rb +33 -0
  419. data/lib/pubid/iso/parser.rb +510 -0
  420. data/lib/pubid/iso/rendering_style.rb +120 -0
  421. data/lib/pubid/iso/scheme.rb +187 -0
  422. data/lib/pubid/iso/single_identifier.rb +61 -0
  423. data/lib/pubid/iso/supplement_identifier.rb +27 -0
  424. data/lib/pubid/iso/urn_generator.rb +412 -0
  425. data/lib/pubid/iso/urn_parser.rb +423 -0
  426. data/lib/pubid/iso/utilities.rb +86 -0
  427. data/lib/pubid/iso.rb +50 -0
  428. data/lib/pubid/itu/builder.rb +171 -0
  429. data/lib/pubid/itu/components/code.rb +39 -0
  430. data/lib/pubid/itu/components/sector.rb +35 -0
  431. data/lib/pubid/itu/components/series.rb +29 -0
  432. data/lib/pubid/itu/i18n.rb +9 -0
  433. data/lib/pubid/itu/i18n.yaml +30 -0
  434. data/lib/pubid/itu/identifier.rb +53 -0
  435. data/lib/pubid/itu/identifiers/amendment.rb +43 -0
  436. data/lib/pubid/itu/identifiers/annex.rb +74 -0
  437. data/lib/pubid/itu/identifiers/base.rb +154 -0
  438. data/lib/pubid/itu/identifiers/combined_identifier.rb +47 -0
  439. data/lib/pubid/itu/identifiers/corrigendum.rb +44 -0
  440. data/lib/pubid/itu/identifiers/recommendation.rb +16 -0
  441. data/lib/pubid/itu/identifiers/special_publication.rb +31 -0
  442. data/lib/pubid/itu/identifiers/supplement.rb +46 -0
  443. data/lib/pubid/itu/identifiers.rb +16 -0
  444. data/lib/pubid/itu/model.rb +111 -0
  445. data/lib/pubid/itu/parser.rb +225 -0
  446. data/lib/pubid/itu/scheme.rb +174 -0
  447. data/lib/pubid/itu/urn_generator.rb +105 -0
  448. data/lib/pubid/itu.rb +22 -0
  449. data/lib/pubid/jcgm/builder.rb +88 -0
  450. data/lib/pubid/jcgm/components/publisher.rb +20 -0
  451. data/lib/pubid/jcgm/components.rb +9 -0
  452. data/lib/pubid/jcgm/identifier.rb +11 -0
  453. data/lib/pubid/jcgm/identifiers/amendment.rb +35 -0
  454. data/lib/pubid/jcgm/identifiers/guide.rb +21 -0
  455. data/lib/pubid/jcgm/identifiers/gum_guide.rb +51 -0
  456. data/lib/pubid/jcgm/identifiers.rb +11 -0
  457. data/lib/pubid/jcgm/parser.rb +84 -0
  458. data/lib/pubid/jcgm/scheme.rb +60 -0
  459. data/lib/pubid/jcgm/single_identifier.rb +48 -0
  460. data/lib/pubid/jcgm/supplement_identifier.rb +16 -0
  461. data/lib/pubid/jcgm/urn_generator.rb +110 -0
  462. data/lib/pubid/jcgm.rb +31 -0
  463. data/lib/pubid/jis/builder.rb +124 -0
  464. data/lib/pubid/jis/components/code.rb +59 -0
  465. data/lib/pubid/jis/components.rb +9 -0
  466. data/lib/pubid/jis/identifier.rb +18 -0
  467. data/lib/pubid/jis/identifiers/amendment.rb +16 -0
  468. data/lib/pubid/jis/identifiers/base.rb +72 -0
  469. data/lib/pubid/jis/identifiers/explanation.rb +22 -0
  470. data/lib/pubid/jis/identifiers/japanese_industrial_standard.rb +16 -0
  471. data/lib/pubid/jis/identifiers/standard.rb +27 -0
  472. data/lib/pubid/jis/identifiers/technical_report.rb +31 -0
  473. data/lib/pubid/jis/identifiers/technical_specification.rb +31 -0
  474. data/lib/pubid/jis/identifiers.rb +17 -0
  475. data/lib/pubid/jis/parser.rb +109 -0
  476. data/lib/pubid/jis/scheme.rb +49 -0
  477. data/lib/pubid/jis/single_identifier.rb +37 -0
  478. data/lib/pubid/jis/supplement_identifier.rb +47 -0
  479. data/lib/pubid/jis/urn_generator.rb +25 -0
  480. data/lib/pubid/jis.rb +23 -0
  481. data/lib/pubid/lutaml/no_store_registration.rb +30 -0
  482. data/lib/pubid/nist/builder.rb +2100 -0
  483. data/lib/pubid/nist/components/code.rb +38 -0
  484. data/lib/pubid/nist/components/edition.rb +118 -0
  485. data/lib/pubid/nist/components/issue_number.rb +28 -0
  486. data/lib/pubid/nist/components/part.rb +77 -0
  487. data/lib/pubid/nist/components/publisher.rb +24 -0
  488. data/lib/pubid/nist/components/stage.rb +53 -0
  489. data/lib/pubid/nist/components/supplement.rb +121 -0
  490. data/lib/pubid/nist/components/translation.rb +42 -0
  491. data/lib/pubid/nist/components/update.rb +103 -0
  492. data/lib/pubid/nist/components/version.rb +35 -0
  493. data/lib/pubid/nist/components/volume.rb +32 -0
  494. data/lib/pubid/nist/components.rb +19 -0
  495. data/lib/pubid/nist/configuration.rb +77 -0
  496. data/lib/pubid/nist/identifiers/base.rb +499 -0
  497. data/lib/pubid/nist/identifiers/circular.rb +68 -0
  498. data/lib/pubid/nist/identifiers/circular_supplement.rb +50 -0
  499. data/lib/pubid/nist/identifiers/commercial_standard.rb +41 -0
  500. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +56 -0
  501. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +56 -0
  502. data/lib/pubid/nist/identifiers/crpl_report.rb +135 -0
  503. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +94 -0
  504. data/lib/pubid/nist/identifiers/grant_contractor_report.rb +35 -0
  505. data/lib/pubid/nist/identifiers/handbook.rb +50 -0
  506. data/lib/pubid/nist/identifiers/internal_report.rb +56 -0
  507. data/lib/pubid/nist/identifiers/letter_circular.rb +45 -0
  508. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +65 -0
  509. data/lib/pubid/nist/identifiers/monograph.rb +69 -0
  510. data/lib/pubid/nist/identifiers/ncstar.rb +41 -0
  511. data/lib/pubid/nist/identifiers/nsrds.rb +41 -0
  512. data/lib/pubid/nist/identifiers/owmwp.rb +35 -0
  513. data/lib/pubid/nist/identifiers/report.rb +68 -0
  514. data/lib/pubid/nist/identifiers/special_publication.rb +36 -0
  515. data/lib/pubid/nist/identifiers/technical_note.rb +90 -0
  516. data/lib/pubid/nist/identifiers.rb +33 -0
  517. data/lib/pubid/nist/parser.rb +1084 -0
  518. data/lib/pubid/nist/scheme.rb +199 -0
  519. data/lib/pubid/nist/supplement_identifier.rb +83 -0
  520. data/lib/pubid/nist/urn_generator.rb +127 -0
  521. data/lib/pubid/nist.rb +36 -0
  522. data/lib/pubid/oiml/builder.rb +189 -0
  523. data/lib/pubid/oiml/components/code.rb +20 -0
  524. data/lib/pubid/oiml/components.rb +9 -0
  525. data/lib/pubid/oiml/identifier.rb +11 -0
  526. data/lib/pubid/oiml/identifiers/amendment.rb +13 -0
  527. data/lib/pubid/oiml/identifiers/annex.rb +62 -0
  528. data/lib/pubid/oiml/identifiers/base.rb +36 -0
  529. data/lib/pubid/oiml/identifiers/basic_publication.rb +13 -0
  530. data/lib/pubid/oiml/identifiers/document.rb +13 -0
  531. data/lib/pubid/oiml/identifiers/expert_report.rb +13 -0
  532. data/lib/pubid/oiml/identifiers/guide.rb +13 -0
  533. data/lib/pubid/oiml/identifiers/recommendation.rb +13 -0
  534. data/lib/pubid/oiml/identifiers/seminar_report.rb +13 -0
  535. data/lib/pubid/oiml/identifiers/vocabulary.rb +13 -0
  536. data/lib/pubid/oiml/identifiers.rb +18 -0
  537. data/lib/pubid/oiml/parser.rb +173 -0
  538. data/lib/pubid/oiml/scheme.rb +46 -0
  539. data/lib/pubid/oiml/single_identifier.rb +90 -0
  540. data/lib/pubid/oiml/supplement_identifier.rb +43 -0
  541. data/lib/pubid/oiml/urn_generator.rb +64 -0
  542. data/lib/pubid/oiml.rb +26 -0
  543. data/lib/pubid/parser/common_parse_methods.rb +13 -0
  544. data/lib/pubid/parser/common_parse_rules.rb +56 -0
  545. data/lib/pubid/parser.rb +8 -0
  546. data/lib/pubid/parsers/base.rb +11 -0
  547. data/lib/pubid/parsers/mr_string.rb +93 -0
  548. data/lib/pubid/plateau/builder.rb +50 -0
  549. data/lib/pubid/plateau/identifiers/annex.rb +16 -0
  550. data/lib/pubid/plateau/identifiers/base.rb +51 -0
  551. data/lib/pubid/plateau/identifiers/handbook.rb +34 -0
  552. data/lib/pubid/plateau/identifiers/technical_report.rb +20 -0
  553. data/lib/pubid/plateau/identifiers.rb +12 -0
  554. data/lib/pubid/plateau/parser.rb +63 -0
  555. data/lib/pubid/plateau/scheme.rb +45 -0
  556. data/lib/pubid/plateau/supplement_identifier.rb +72 -0
  557. data/lib/pubid/plateau/urn_generator.rb +29 -0
  558. data/lib/pubid/plateau.rb +25 -0
  559. data/lib/pubid/renderers/base.rb +19 -0
  560. data/lib/pubid/renderers/directives_renderer.rb +62 -0
  561. data/lib/pubid/renderers/guide_renderer.rb +20 -0
  562. data/lib/pubid/renderers/human_readable.rb +58 -0
  563. data/lib/pubid/renderers/iwa_renderer.rb +16 -0
  564. data/lib/pubid/renderers/mr_string.rb +16 -0
  565. data/lib/pubid/renderers/supplement_renderer.rb +33 -0
  566. data/lib/pubid/renderers/urn.rb +11 -0
  567. data/lib/pubid/renderers.rb +14 -0
  568. data/lib/pubid/rendering/base.rb +73 -0
  569. data/lib/pubid/rendering/common.rb +211 -0
  570. data/lib/pubid/rendering/context.rb +156 -0
  571. data/lib/pubid/rendering/date.rb +27 -0
  572. data/lib/pubid/rendering/format.rb +25 -0
  573. data/lib/pubid/rendering/language.rb +21 -0
  574. data/lib/pubid/rendering/numbering.rb +24 -0
  575. data/lib/pubid/rendering/publisher.rb +25 -0
  576. data/lib/pubid/rendering/stage.rb +38 -0
  577. data/lib/pubid/rendering/supplement.rb +46 -0
  578. data/lib/pubid/rendering.rb +16 -0
  579. data/lib/pubid/sae/builder.rb +32 -0
  580. data/lib/pubid/sae/components/code.rb +9 -0
  581. data/lib/pubid/sae/components/date.rb +19 -0
  582. data/lib/pubid/sae/components/type.rb +19 -0
  583. data/lib/pubid/sae/components.rb +11 -0
  584. data/lib/pubid/sae/identifier.rb +14 -0
  585. data/lib/pubid/sae/identifiers/base.rb +42 -0
  586. data/lib/pubid/sae/identifiers.rb +9 -0
  587. data/lib/pubid/sae/parser.rb +55 -0
  588. data/lib/pubid/sae/scheme.rb +47 -0
  589. data/lib/pubid/sae/urn_generator.rb +38 -0
  590. data/lib/pubid/sae.rb +19 -0
  591. data/lib/pubid/scheme.rb +207 -0
  592. data/lib/pubid/urn_generator/base.rb +110 -0
  593. data/lib/pubid/utils/string_normalizer.rb +196 -0
  594. data/lib/pubid/utils.rb +7 -0
  595. data/lib/pubid/version.rb +3 -1
  596. data/lib/pubid.rb +137 -13
  597. data/lib/tasks/docs.rake +37 -0
  598. data/lib/tasks/export.rake +38 -0
  599. data/lib/tasks/website-data.json +7488 -0
  600. metadata +613 -171
  601. data/lib/pubid/registry.rb +0 -30
@@ -0,0 +1,1237 @@
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 Scheme objects
7
+ class Builder
8
+ attr_accessor :original_input
9
+ attr_reader :identifier_class
10
+
11
+ def initialize(identifier_class = Identifiers::Base)
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 [Scheme] the constructed scheme object
18
+ def build(parsed)
19
+ # Handle CSA dual published patterns
20
+ if parsed[:ieee_portion] && parsed[:csa_portion]
21
+ return build_csa_dual_published(parsed)
22
+ end
23
+
24
+ # Handle combined AIEE identifiers (from "Nos X and Y" preprocessing)
25
+ if parsed[:first_aiee] && parsed[:second_aiee]
26
+ return build_combined_aiee(parsed)
27
+ end
28
+
29
+ # Handle dual published patterns
30
+ if parsed[:first] && parsed[:second]
31
+ return build_dual_published(parsed)
32
+ end
33
+
34
+ # Handle IEC/IEEE copublished patterns
35
+ if parsed[:content]
36
+ return build_iec_ieee_copublished(parsed)
37
+ end
38
+
39
+ # Handle NESC identifiers (National Electrical Safety Code)
40
+ if parsed[:nesc]
41
+ nesc_builder = Nesc::Builder.new
42
+ return nesc_builder.build(parsed[:nesc])
43
+ end
44
+
45
+ # Handle AIEE identifiers (American Institute of Electrical Engineers)
46
+ if parsed[:aiee]
47
+ aiee_builder = Aiee::Builder.new
48
+ return aiee_builder.build(parsed[:aiee])
49
+ end
50
+
51
+ # Handle IRE identifiers (Institute of Radio Engineers)
52
+ if parsed[:ire]
53
+ ire_builder = Ire::Builder.new
54
+ return ire_builder.build(parsed[:ire])
55
+ end
56
+
57
+ # Handle IEEE/ASTM SI/PSI identifiers (Système International)
58
+ if parsed[:si_type]
59
+ return build_si_psi_identifier(parsed)
60
+ end
61
+
62
+ # Handle single identifier
63
+ build_single_identifier(parsed)
64
+ end
65
+
66
+ private
67
+
68
+ # Build IEC/IEEE copublished identifier
69
+ def build_iec_ieee_copublished(parsed)
70
+ content = extract_value(parsed[:content])
71
+
72
+ # Parse the content to extract components
73
+ copublished_number = nil
74
+ draft_info = nil
75
+ iec_year = nil
76
+ date_info = nil
77
+
78
+ if content
79
+ # Extract copublished number (everything before IEC: or comma or parenthesis)
80
+ copublished_number = if content.include?("IEC:")
81
+ content.split(" IEC:").first.strip
82
+ elsif content.include?(", ")
83
+ content.split(", ").first.strip
84
+ elsif content.include?(" (")
85
+ content.split(" (").first.strip
86
+ else
87
+ content.strip
88
+ end
89
+
90
+ # Extract draft info if present
91
+ if copublished_number.include?("/D")
92
+ parts = copublished_number.split("/D")
93
+ copublished_number = parts[0]
94
+ draft_info = "/D#{parts[1] || ''}"
95
+ end
96
+
97
+ # Extract IEC year if present
98
+ if content.include?("IEC:")
99
+ iec_part = content.split("IEC:")[1]
100
+ if iec_part
101
+ iec_year = iec_part.split.first
102
+ end
103
+ end
104
+
105
+ # Extract date info if present
106
+ if content.include?(" (")
107
+ date_part = content.split(" (")[1]
108
+ if date_part&.include?(")")
109
+ date_info = date_part.split(")")[0]
110
+ end
111
+ end
112
+ end
113
+
114
+ Identifiers::IecIeeeCopublished.new(
115
+ copublished_number: copublished_number,
116
+ draft_info: draft_info,
117
+ iec_year: iec_year,
118
+ date_info: date_info,
119
+ )
120
+ end
121
+
122
+ # Build dual published identifier
123
+ def build_dual_published(parsed)
124
+ first_id = build_single_identifier(parsed[:first])
125
+ second_id = build_single_identifier(parsed[:second])
126
+
127
+ Identifiers::DualPublished.new(
128
+ first_identifier: first_id,
129
+ second_identifier: second_id,
130
+ )
131
+ end
132
+
133
+ # Build CSA dual published identifier
134
+ # @param parsed [Hash] parsed CSA dual published data
135
+ # @return [Identifiers::CsaDualPublished] CSA dual published identifier
136
+ def build_csa_dual_published(parsed)
137
+ # Build IEEE portion
138
+ ieee_id = build_single_identifier(parsed[:ieee_portion])
139
+
140
+ # Extract and parse CSA portion using CSA parser
141
+ csa_string = extract_value(parsed[:csa_portion])
142
+
143
+ # Prepend "CSA " prefix if not present (CSA parser expects this format)
144
+ csa_string = "CSA #{csa_string}" unless csa_string.start_with?("CSA",
145
+ "CAN/")
146
+
147
+ # Use CSA parser to parse the CSA portion
148
+ csa_id = Pubid::Csa.parse(csa_string)
149
+
150
+ Identifiers::CsaDualPublished.new(
151
+ ieee_identifier: ieee_id,
152
+ csa_identifier: csa_id,
153
+ )
154
+ end
155
+
156
+ # Build single identifier
157
+ def build_single_identifier(parsed, building_secondary: false)
158
+ # Parslet can return array of hashes - merge them
159
+ parsed_hash = parsed.is_a?(Array) ? merge_parsed_array(parsed) : parsed
160
+
161
+ # Handle multi-numbered identifiers (cross-reference and joint standards)
162
+ # CRITICAL: Don't recurse when building secondary identifier to prevent infinite loop
163
+ if !building_secondary && parsed_hash[:primary_identifier] && (parsed_hash[:secondary_crossref] || parsed_hash[:secondary_joint])
164
+ return build_multi_numbered_identifier(parsed_hash)
165
+ end
166
+
167
+ # Handle corrigendum supplements (check for base_identifier + cor_number)
168
+ # This enables recursive base identifier parsing like ISO/IEC Amendment
169
+ if parsed_hash[:base_identifier] && parsed_hash[:cor_number]
170
+ return build_corrigendum_supplement(parsed_hash)
171
+ end
172
+
173
+ # Handle interpretation supplements (check for base_identifier + int_year)
174
+ if parsed_hash[:base_identifier] && (parsed_hash[:int_year] || parsed_hash[:interpretation])
175
+ return build_interpretation_supplement(parsed_hash)
176
+ end
177
+
178
+ # Handle conformance supplements (check for base_identifier + conf_number)
179
+ if parsed_hash[:base_identifier] && parsed_hash[:conf_number]
180
+ return build_conformance_supplement(parsed_hash)
181
+ end
182
+
183
+ # Handle joint development patterns from parser
184
+ if parsed_hash[:joint_publishers] || parsed_hash[:iso_stage]
185
+ return build_joint_development(parsed_hash)
186
+ end
187
+
188
+ attributes = extract_attributes(parsed_hash)
189
+
190
+ # Handle relationships if present (Pattern 4)
191
+ if parsed_hash[:relationship_type] || parsed_hash[:relationship_clause]
192
+ attributes[:relationships] = build_relationships(parsed_hash)
193
+ end
194
+
195
+ # Route to appropriate identifier class based on content
196
+ identifier_class = determine_identifier_class(attributes)
197
+ identifier_class.new(**attributes)
198
+ end
199
+
200
+ # Build corrigendum supplement with recursive base parsing
201
+ # @param parsed_hash [Hash] parsed data with base and supplement info
202
+ # @return [Identifiers::Corrigendum] corrigendum identifier
203
+ def build_corrigendum_supplement(parsed_hash)
204
+ # Reconstruct base identifier string from parsed components
205
+ base_parts = []
206
+ base_data = parsed_hash[:base_identifier]
207
+
208
+ # Extract publisher
209
+ if base_data[:publishers]
210
+ pub_data = base_data[:publishers]
211
+ publisher_str = extract_value(pub_data[:publisher])
212
+
213
+ if pub_data[:copublishers] && !pub_data[:copublishers].empty?
214
+ copubs = pub_data[:copublishers]
215
+ copubs = [copubs] unless copubs.is_a?(Array)
216
+ copub_strs = copubs.filter_map do |cp|
217
+ extract_value(cp[:copublisher])
218
+ end
219
+ publisher_str += "/#{copub_strs.join('/')}" if !copub_strs.empty?
220
+ end
221
+
222
+ base_parts << publisher_str
223
+ end
224
+
225
+ # Extract type
226
+ if base_data[:type]
227
+ base_parts << extract_value(base_data[:type])
228
+ end
229
+
230
+ # Extract number with parts and year
231
+ # Build the complete code string: "802.1AC-2016" or "535-2013" or "C37.41-2016"
232
+ number_str = extract_value(base_data[:number])
233
+
234
+ # Add part if present (e.g., ".1AC" or ".41")
235
+ if base_data[:part]
236
+ part_val = extract_value(base_data[:part])
237
+ # Determine separator: dot for most cases, dash for some
238
+ separator = number_str.match?(/^[A-Z]/) ? "." : "." # Letter prefix uses dot
239
+ number_str += separator + part_val
240
+ end
241
+
242
+ # Add subpart if present
243
+ if base_data[:subpart]
244
+ subparts = base_data[:subpart]
245
+ subparts = [subparts] unless subparts.is_a?(Array)
246
+ subparts.each do |sp|
247
+ subpart_val = extract_value(sp)
248
+ number_str += ".#{subpart_val}" if subpart_val
249
+ end
250
+ end
251
+
252
+ # Add year with dash (e.g., "-2016")
253
+ if base_data[:year]
254
+ year_val = extract_value(base_data[:year])
255
+ number_str += "-#{year_val}"
256
+ end
257
+
258
+ base_parts << number_str
259
+
260
+ # Build base string and recursively parse it
261
+ base_string = base_parts.join(" ")
262
+
263
+ # Recursively parse base identifier using Base.parse
264
+ base_identifier = Identifiers::Base.parse(base_string)
265
+
266
+ # Extract corrigendum attributes
267
+ cor_number = extract_value(parsed_hash[:cor_number])
268
+ cor_year = extract_value(parsed_hash[:cor_year])
269
+
270
+ # Create Corrigendum with parsed base
271
+ Identifiers::Corrigendum.new(
272
+ base_identifier: base_identifier,
273
+ cor_number: cor_number,
274
+ cor_year: cor_year,
275
+ )
276
+ end
277
+
278
+ # Build interpretation supplement with recursive base parsing
279
+ # @param parsed_hash [Hash] parsed data with base and supplement info
280
+ # @return [Identifiers::InterpretationIdentifier] interpretation identifier
281
+ def build_interpretation_supplement(parsed_hash)
282
+ # Reconstruct base identifier string from parsed components (same logic as corrigendum)
283
+ base_parts = []
284
+ base_data = parsed_hash[:base_identifier]
285
+
286
+ # Extract publisher
287
+ if base_data[:publishers]
288
+ pub_data = base_data[:publishers]
289
+ publisher_str = extract_value(pub_data[:publisher])
290
+
291
+ if pub_data[:copublishers] && !pub_data[:copublishers].empty?
292
+ copubs = pub_data[:copublishers]
293
+ copubs = [copubs] unless copubs.is_a?(Array)
294
+ copub_strs = copubs.filter_map do |cp|
295
+ extract_value(cp[:copublisher])
296
+ end
297
+ publisher_str += "/#{copub_strs.join('/')}" if !copub_strs.empty?
298
+ end
299
+
300
+ base_parts << publisher_str
301
+ end
302
+
303
+ # Extract type
304
+ if base_data[:type]
305
+ base_parts << extract_value(base_data[:type])
306
+ end
307
+
308
+ # Extract number with parts and year
309
+ number_str = extract_value(base_data[:number])
310
+
311
+ # Add part if present
312
+ if base_data[:part]
313
+ part_val = extract_value(base_data[:part])
314
+ separator = number_str.match?(/^[A-Z]/) ? "." : "."
315
+ number_str += separator + part_val
316
+ end
317
+
318
+ # Add subpart if present
319
+ if base_data[:subpart]
320
+ subparts = base_data[:subpart]
321
+ subparts = [subparts] unless subparts.is_a?(Array)
322
+ subparts.each do |sp|
323
+ subpart_val = extract_value(sp)
324
+ number_str += ".#{subpart_val}" if subpart_val
325
+ end
326
+ end
327
+
328
+ # Add year with dash
329
+ if base_data[:year]
330
+ year_val = extract_value(base_data[:year])
331
+ number_str += "-#{year_val}"
332
+ end
333
+
334
+ base_parts << number_str
335
+
336
+ # Build base string and recursively parse it
337
+ base_string = base_parts.join(" ")
338
+
339
+ # Recursively parse base identifier using Base.parse
340
+ base_identifier = Identifiers::Base.parse(base_string)
341
+
342
+ # Extract interpretation attributes
343
+ int_year = extract_value(parsed_hash[:int_year])
344
+
345
+ # Create InterpretationIdentifier with parsed base
346
+ Identifiers::InterpretationIdentifier.new(
347
+ base_identifier: base_identifier,
348
+ int_year: int_year,
349
+ )
350
+ end
351
+
352
+ # Build conformance supplement with recursive base parsing
353
+ # @param parsed_hash [Hash] parsed data with base and supplement info
354
+ # @return [Identifiers::ConformanceIdentifier] conformance identifier
355
+ def build_conformance_supplement(parsed_hash)
356
+ # Reconstruct base identifier string from parsed components (same logic as corrigendum)
357
+ base_parts = []
358
+ base_data = parsed_hash[:base_identifier]
359
+
360
+ # Extract publisher
361
+ if base_data[:publishers]
362
+ pub_data = base_data[:publishers]
363
+ publisher_str = extract_value(pub_data[:publisher])
364
+
365
+ if pub_data[:copublishers] && !pub_data[:copublishers].empty?
366
+ copubs = pub_data[:copublishers]
367
+ copubs = [copubs] unless copubs.is_a?(Array)
368
+ copub_strs = copubs.filter_map do |cp|
369
+ extract_value(cp[:copublisher])
370
+ end
371
+ publisher_str += "/#{copub_strs.join('/')}" if !copub_strs.empty?
372
+ end
373
+
374
+ base_parts << publisher_str
375
+ end
376
+
377
+ # Extract type
378
+ if base_data[:type]
379
+ base_parts << extract_value(base_data[:type])
380
+ end
381
+
382
+ # Extract number with parts and year
383
+ number_str = extract_value(base_data[:number])
384
+
385
+ # Add part if present
386
+ if base_data[:part]
387
+ part_val = extract_value(base_data[:part])
388
+ separator = number_str.match?(/^[A-Z]/) ? "." : "."
389
+ number_str += separator + part_val
390
+ end
391
+
392
+ # Add subpart if present
393
+ if base_data[:subpart]
394
+ subparts = base_data[:subpart]
395
+ subparts = [subparts] unless subparts.is_a?(Array)
396
+ subparts.each do |sp|
397
+ subpart_val = extract_value(sp)
398
+ number_str += ".#{subpart_val}" if subpart_val
399
+ end
400
+ end
401
+
402
+ # Add year with dash
403
+ if base_data[:year]
404
+ year_val = extract_value(base_data[:year])
405
+ number_str += "-#{year_val}"
406
+ end
407
+
408
+ base_parts << number_str
409
+
410
+ # Build base string and recursively parse it
411
+ base_string = base_parts.join(" ")
412
+
413
+ # Recursively parse base identifier using Base.parse
414
+ base_identifier = Identifiers::Base.parse(base_string)
415
+
416
+ # Extract conformance attributes
417
+ conf_number = extract_value(parsed_hash[:conf_number])
418
+ conf_year = extract_value(parsed_hash[:conf_year])
419
+
420
+ # Create ConformanceIdentifier with parsed base
421
+ Identifiers::ConformanceIdentifier.new(
422
+ base_identifier: base_identifier,
423
+ conf_number: conf_number,
424
+ conf_year: conf_year,
425
+ )
426
+ end
427
+
428
+ # Build joint development identifier from parsed data
429
+ def build_joint_development(parsed)
430
+ attributes = {}
431
+
432
+ # Extract publishers from joint_publishers
433
+ if parsed[:joint_publishers]
434
+ joint_pub_str = extract_value(parsed[:joint_publishers])
435
+ attributes[:publishers] = joint_pub_str.split("/")
436
+ end
437
+
438
+ # Build code with parts if present
439
+ code_parts = []
440
+ code_parts << extract_value(parsed[:part]) if parsed[:part]
441
+
442
+ code_str = extract_value(parsed[:number])
443
+ if code_str && !code_parts.empty?
444
+ code_str += ".#{code_parts.join('.')}"
445
+ end
446
+ attributes[:code] = code_str
447
+
448
+ # Extract year
449
+ attributes[:year] = extract_value(parsed[:year]) if parsed[:year]
450
+
451
+ # Detect lead party based on pattern
452
+ if parsed[:iso_stage]
453
+ # ISO format - lead party is ISO
454
+ attributes[:lead_party] = "ISO"
455
+ attributes[:iso_stage] = extract_value(parsed[:iso_stage])
456
+
457
+ # Create typed_stage for ISO stage
458
+ stage_abbr = attributes[:iso_stage]
459
+ if stage_abbr
460
+ attributes[:typed_stage] =
461
+ Ieee::Scheme.locate_typed_stage_by_abbr(stage_abbr)
462
+ end
463
+ else
464
+ # IEEE format - lead party is IEEE
465
+ attributes[:lead_party] = "IEEE"
466
+
467
+ # Extract draft version if present (e.g., D8 from /D8)
468
+ if parsed[:draft_version]
469
+ draft_ver = extract_value(parsed[:draft_version])
470
+ # Remove leading 'D' if present since draft_version already has it
471
+ draft_ver = draft_ver.sub(/^D/, "") if draft_ver
472
+ attributes[:ieee_draft] = "D#{draft_ver}" if draft_ver
473
+ end
474
+
475
+ # Mark as project (P prefix)
476
+ attributes[:type] = "P"
477
+
478
+ # Create typed_stage for IEEE project
479
+ attributes[:typed_stage] =
480
+ Ieee::Scheme.locate_typed_stage_by_abbr("P")
481
+ end
482
+
483
+ Identifiers::JointDevelopment.new(**attributes)
484
+ end
485
+
486
+ # Build SI/PSI identifier from parsed data
487
+ # @param parsed [Hash] parsed SI/PSI data
488
+ # @return [Identifiers::SiStandard] SI or PSI identifier (both use same class)
489
+ def build_si_psi_identifier(parsed)
490
+ attributes = {}
491
+
492
+ # Extract SI type (SI or PSI)
493
+ si_type = extract_value(parsed[:si_type])
494
+
495
+ # Extract publishers (should be "IEEE/ASTM")
496
+ if parsed[:publishers]
497
+ publishers_str = extract_value(parsed[:publishers])
498
+ # Split by slash to get individual publishers
499
+ pubs = publishers_str.split("/")
500
+ attributes[:publisher] = pubs.first
501
+ attributes[:copublisher] = pubs.drop(1) if pubs.length > 1
502
+ end
503
+
504
+ # Extract number
505
+ attributes[:code] = extract_value(parsed[:number])
506
+
507
+ # For PSI (draft), create proper Draft component object
508
+ if si_type == "PSI"
509
+ if parsed[:draft_version]
510
+ draft_version = extract_value(parsed[:draft_version])
511
+ # Create Draft component (Lutaml::Model object)
512
+ attributes[:draft_obj] =
513
+ Components::Draft.new(version: draft_version)
514
+ end
515
+ # Set PSI typed_stage (draft stage)
516
+ attributes[:typed_stage] = Identifiers::SiStandard::TYPED_STAGES.find do |ts|
517
+ ts.abbr.include?("PSI")
518
+ end
519
+ else
520
+ # Set SI typed_stage (published stage)
521
+ attributes[:typed_stage] = Identifiers::SiStandard::TYPED_STAGES.find do |ts|
522
+ ts.abbr.include?("SI")
523
+ end
524
+ end
525
+
526
+ # Extract year and month
527
+ attributes[:year] = extract_value(parsed[:year]) if parsed[:year]
528
+ attributes[:month] = extract_value(parsed[:month]) if parsed[:month]
529
+
530
+ # Handle relationships if present
531
+ if parsed[:relationship_type] || parsed[:relationship_clause]
532
+ attributes[:relationships] = build_relationships(parsed)
533
+ end
534
+
535
+ # Handle parenthetical content
536
+ handle_parameters(parsed, attributes)
537
+
538
+ # BOTH SI and PSI use SiStandard class - just different typed_stages
539
+ Identifiers::SiStandard.new(**attributes)
540
+ end
541
+
542
+ # Build multi-numbered identifier from parsed data
543
+ # @param parsed [Hash] parsed multi-numbered data
544
+ # @return [Identifiers::MultiNumberedIdentifier] multi-numbered identifier
545
+ def build_multi_numbered_identifier(parsed)
546
+ # Build primary identifier
547
+ primary_id = build_single_identifier(parsed[:primary_identifier],
548
+ building_secondary: false)
549
+
550
+ # Build secondary identifier based on format
551
+ if parsed[:secondary_crossref]
552
+ # Cross-reference format: /C62.22.1-1996
553
+ # Build directly to avoid re-parsing which could cause infinite recursion
554
+ crossref = parsed[:secondary_crossref]
555
+ # Pattern: slash >> "C" >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits
556
+ # The parsed crossref is a string - extract the number part
557
+ # Format: C62.22.1-1996 where C is part of the crossref notation
558
+ secondary_id = Identifiers::Standard.new(
559
+ publisher: "IEEE",
560
+ number: extract_value(crossref),
561
+ )
562
+ elsif parsed[:secondary_joint]
563
+ # Joint standard format: ", Std 1177-1989"
564
+ # Build from parsed components
565
+ secondary_id = build_single_identifier(parsed[:secondary_joint],
566
+ building_secondary: true)
567
+ else
568
+ secondary_id = nil
569
+ end
570
+
571
+ Identifiers::MultiNumberedIdentifier.new(
572
+ primary_identifier: primary_id,
573
+ secondary_identifier: secondary_id,
574
+ )
575
+ end
576
+
577
+ # Determine which identifier class to use based on attributes
578
+ # CRITICAL: Use Scheme for type-based decisions, NOT hardcoded logic here
579
+ # Builder's job is ONLY to cast types, not make business decisions
580
+ def determine_identifier_class(attributes)
581
+ # Check for AIEE publisher FIRST (AIEE has its own identifier class)
582
+ # This must be checked before type-based routing because AIEE uses "No" as type
583
+ if attributes[:publisher] == "AIEE"
584
+ return Ieee::Aiee::Identifier
585
+ end
586
+
587
+ # Get type from attributes and use Scheme to locate class
588
+ type_code = attributes[:type]
589
+
590
+ # Use Scheme registry for type-to-class mapping
591
+ if type_code
592
+ klass = Ieee::Scheme.locate_identifier_klass_by_type_code(type_code)
593
+ return klass if klass
594
+ end
595
+
596
+ # Non-type-based routing (structural patterns, not business logic)
597
+ # These are structural checks, not type decisions
598
+
599
+ # Check for SI standards (handles both SI and PSI via typed_stage)
600
+ if ["SI", "PSI"].include?(type_code)
601
+ return Identifiers::SiStandard
602
+ end
603
+
604
+ # Check for interpretation supplements (structural: has interpretation flag or int_year)
605
+ if attributes[:interpretation] || attributes[:int_year]
606
+ return Identifiers::InterpretationIdentifier
607
+ end
608
+
609
+ # Check for conformance supplements (structural: has conf_number)
610
+ if attributes[:conf_number]
611
+ return Identifiers::ConformanceIdentifier
612
+ end
613
+
614
+ # Check for corrigendum supplements (structural: has cor_number)
615
+ if attributes[:cor_number]
616
+ return Identifiers::Corrigendum
617
+ end
618
+
619
+ # Check for multi-numbered standards (structural: has crossref or joint patterns)
620
+ # These are handled at the build() method level, not here
621
+ # (cross-reference patterns like /C62.22.1-1996 and joint standards)
622
+
623
+ # Check for adopted standards (parenthetical adoptions)
624
+ if attributes[:adoption] || (attributes[:parameters] && attributes[:parameters][:adoption])
625
+ return Identifiers::AdoptedStandard
626
+ end
627
+
628
+ # Check for redline standards (structural: has redline flag)
629
+ if attributes[:redline]
630
+ return Identifiers::RedlinedStandard
631
+ end
632
+
633
+ # Default to base identifier
634
+ Identifiers::Base
635
+ end
636
+
637
+ # Detect lead party for joint development identifiers
638
+ # @param attributes [Hash] the identifier attributes
639
+ # @return [String] the lead party ("IEEE", "ISO", or "IEC")
640
+ def detect_lead_party(attributes)
641
+ # Rule 1: Check for P prefix in code (IEEE-led)
642
+ if attributes[:code]&.start_with?("P") || attributes[:type] == "P"
643
+ return "IEEE"
644
+ end
645
+
646
+ # Rule 2: Check for IEEE draft notation in original input (IEEE-led)
647
+ if original_input&.match?(/\/D\d+/)
648
+ return "IEEE"
649
+ end
650
+
651
+ # Rule 3: Check for ISO stage codes (ISO-led)
652
+ iso_stages = %w[FDIS DIS CD WD PWI NP]
653
+ if attributes[:typed_stage]
654
+ stage_abbr = attributes[:typed_stage].abbr
655
+ stage_abbr = [stage_abbr] unless stage_abbr.is_a?(Array)
656
+ if stage_abbr.any? { |abbr| iso_stages.include?(abbr) }
657
+ return "ISO"
658
+ end
659
+ end
660
+
661
+ # Rule 4: Check for colon before year in original input (ISO-led)
662
+ if original_input&.match?(/:\d{4}/)
663
+ return "ISO"
664
+ end
665
+
666
+ # Rule 5: Default to first publisher
667
+ attributes[:publisher] || "IEEE"
668
+ end
669
+
670
+ # Merge an array of parsed hashes into a single hash
671
+ # @param parsed_array [Array<Hash>] array of parsed hashes
672
+ # @return [Hash] merged hash
673
+ def merge_parsed_array(parsed_array)
674
+ parsed_array.inject({}) do |result, hash|
675
+ result.merge(hash)
676
+ end
677
+ end
678
+
679
+ # Extract and normalize attributes from parsed data
680
+ # @param parsed [Hash] the parsed data
681
+ # @return [Hash] normalized attributes for Scheme
682
+ def extract_attributes(parsed)
683
+ attributes = {}
684
+
685
+ # Handle publishers
686
+ if parsed[:publishers]
687
+ pub_data = parsed[:publishers]
688
+ attributes[:publisher] = extract_value(pub_data[:publisher])
689
+
690
+ if pub_data[:copublishers]
691
+ copubs = pub_data[:copublishers]
692
+ copubs = [copubs] unless copubs.is_a?(Array)
693
+ attributes[:copublisher] = copubs.filter_map do |cp|
694
+ extract_value(cp[:copublisher])
695
+ end
696
+ end
697
+ elsif parsed[:publisher]
698
+ attributes[:publisher] = extract_value(parsed[:publisher])
699
+ end
700
+
701
+ # Build code component with parts and subparts
702
+ code_parts = []
703
+ code_parts << extract_value(parsed[:part]) if parsed[:part]
704
+ if parsed[:subpart]
705
+ subparts = parsed[:subpart]
706
+ subparts = [subparts] unless subparts.is_a?(Array)
707
+ code_parts.concat(subparts.filter_map { |sp| extract_value(sp) })
708
+ end
709
+
710
+ # Create code string with parts
711
+ code_str = extract_value(parsed[:number])
712
+
713
+ # Extract type and draft_status for typed_stage lookup
714
+ type_value = extract_value(parsed[:type])
715
+ draft_status_value = extract_value(parsed[:draft_status])
716
+
717
+ # Handle case where parser captured number without "P" prefix
718
+ # Check original input to see if there was a P before the number
719
+ # The ieee_p_identifier rule consumes P without capturing it
720
+ if !type_value && original_input&.match?(/IEEE\s+P/)
721
+ type_value = "P"
722
+ end
723
+
724
+ # Handle ANSI P prefix patterns (e.g., "ANSI PN42.34-2015")
725
+ if !type_value && original_input&.match?(/ANSI\s+P/)
726
+ # Extract P as type since it was in the original but parser stripped it
727
+ type_value = "P"
728
+ end
729
+
730
+ # NOTE: "P" is a project/draft stage indicator, NOT a code prefix
731
+ # It should be reflected in typed_stage, not in the Code component
732
+ # Do NOT prepend "P" to code_str - code.prefix should remain nil for project drafts
733
+
734
+ # Special case: For IEEE/CSA dual published patterns, strip P prefix from code
735
+ # Pattern: "IEEE/CSA P844.1-2017" -> code should be "844.1" not "P844.1"
736
+ if code_str&.start_with?("P") && original_input&.include?("/CSA")
737
+ # Check if the copublisher attribute indicates CSA
738
+ pub_data = parsed[:publishers]
739
+ has_csa_copub = if pub_data && pub_data[:copublishers]
740
+ copubs = pub_data[:copublishers]
741
+ copubs = [copubs] unless copubs.is_a?(Array)
742
+ copubs.any? do |cp|
743
+ extract_value(cp[:copublisher])&.include?("CSA")
744
+ end
745
+ else
746
+ original_input&.include?("/CSA")
747
+ end
748
+
749
+ if has_csa_copub
750
+ code_str = code_str.sub(/^P/, "")
751
+ end
752
+ end
753
+
754
+ if code_str && !code_parts.empty?
755
+ code_str += ".#{code_parts.join('.')}"
756
+ end
757
+
758
+ # Year detection: If code_str ends with year-like pattern (dash + 4 digits, 1884-2099)
759
+ # and there's no year attribute yet, treat it as a year
760
+ # Examples: "535-2013", "802.1AC-2016", "C37.41-2016"
761
+ # AIEE established in 1884, so years range from 1884 to 2099
762
+ # ONLY apply this when there are NO code_parts (meaning parser didn't capture parts separately)
763
+ # This prevents breaking legitimate dual-published identifiers
764
+ # Match ending with dash followed by 4 digits only (not dot, to preserve 802.1AC patterns)
765
+ if code_str && !parsed[:year] && code_parts.empty? && (match = code_str.match(/^(.+)-(\d{4})$/))
766
+ potential_year = match[2].to_i
767
+ if potential_year.between?(1884, 2099)
768
+ # This is a year - extract it
769
+ code_str = match[1] # Remove year from code
770
+ parsed[:year] = match[2] # Add to parsed for extraction below
771
+ end
772
+ end
773
+
774
+ attributes[:code] = code_str
775
+
776
+ # Extract year - check for edition_month parsed separately
777
+ if parsed[:year]
778
+ year_str = extract_value(parsed[:year])
779
+ attributes[:year] = year_str
780
+ end
781
+
782
+ # Extract edition_month if parsed separately
783
+ if parsed[:edition_month]
784
+ attributes[:edition_month] = extract_value(parsed[:edition_month])
785
+ end
786
+
787
+ # Set type attribute for backward compatibility (after P extraction)
788
+ # NOTE: "P" IS a type code that maps to ProjectDraftIdentifier via Scheme
789
+ # Type can be: "Std", "No", "P" (project draft), etc.
790
+ attributes[:type] = type_value if type_value
791
+
792
+ # Lookup typed_stage from registry
793
+ typed_stage_abbr = determine_stage_abbr(type_value, draft_status_value,
794
+ parsed)
795
+ if typed_stage_abbr
796
+ attributes[:typed_stage] =
797
+ Ieee::Scheme.locate_typed_stage_by_abbr(typed_stage_abbr)
798
+ end
799
+
800
+ attributes[:draft_status] = draft_status_value
801
+
802
+ # Optional attributes (excluding part/subpart since they're in code)
803
+ extract_optional(parsed, attributes, :edition)
804
+ extract_optional(parsed, attributes, :revision)
805
+
806
+ # Month/day - always extract if present (but not if already extracted from year)
807
+ unless attributes[:edition_month]
808
+ extract_optional(parsed, attributes,
809
+ :month)
810
+ end
811
+ extract_optional(parsed, attributes, :day)
812
+
813
+ # Handle draft (can be complex)
814
+ handle_draft(parsed, attributes)
815
+
816
+ # Handle corrigendum
817
+ handle_corrigendum(parsed, attributes)
818
+
819
+ # Handle amendment
820
+ handle_amendment(parsed, attributes)
821
+
822
+ # Handle interpretation (already extracted as boolean)
823
+ # attributes[:interpretation] = true if parsed[:interpretation]
824
+
825
+ # Handle conformance
826
+ handle_conformance(parsed, attributes)
827
+
828
+ # Handle ASHRAE copub
829
+ handle_ashrae_copub(parsed, attributes)
830
+
831
+ # Handle IEEE crossref
832
+ handle_ieee_crossref(parsed, attributes)
833
+
834
+ # Handle reaffirmed
835
+ handle_reaffirmed(parsed, attributes)
836
+
837
+ # Handle additional parameters
838
+ handle_parameters(parsed, attributes)
839
+
840
+ # Book nickname
841
+ if parsed[:nickname]
842
+ attributes[:nickname] = extract_value(parsed[:nickname])
843
+ end
844
+
845
+ # Interpretation
846
+ attributes[:interpretation] = true if parsed[:interpretation]
847
+
848
+ # Redline
849
+ attributes[:redline] = true if parsed[:redline]
850
+
851
+ attributes
852
+ end
853
+
854
+ # Determine stage abbreviation for TYPED_STAGE lookup
855
+ # @param type_value [String] the type value (e.g., "Std", "P")
856
+ # @param draft_status_value [String] the draft status (e.g., "Unapproved")
857
+ # @param parsed [Hash] the full parsed data
858
+ # @return [String, nil] the abbreviation to use for stage lookup
859
+ def determine_stage_abbr(type_value, _draft_status_value, parsed)
860
+ # Check for specific draft notation (D1, D2, etc.)
861
+ if parsed[:draft]
862
+ draft_data = parsed[:draft]
863
+ if draft_data.is_a?(Array)
864
+ draft_data = draft_data.inject({}) do |r, e|
865
+ r.merge(e)
866
+ end
867
+ end
868
+
869
+ if draft_data.is_a?(Hash) && draft_data[:draft_version]
870
+ dv = draft_data[:draft_version]
871
+ version = if dv.is_a?(Array)
872
+ dv.map do |v|
873
+ extract_value(v)
874
+ end.join
875
+ else
876
+ extract_value(dv)
877
+ end
878
+
879
+ # Construct draft notation like "D1", "D2", etc.
880
+ if version
881
+ draft_abbr = "D#{version}"
882
+ # Check if this specific draft stage is in registry
883
+ stage = Ieee::Scheme.locate_typed_stage_by_abbr(draft_abbr)
884
+ return draft_abbr if stage&.abbr&.include?(draft_abbr)
885
+ end
886
+ end
887
+ end
888
+
889
+ # Check type value for known abbreviations
890
+ if type_value
891
+ # Remove leading "P" and check if it exists in registry
892
+ if type_value.start_with?("P")
893
+ # Could be just "P" prefix or "P" as type indicator
894
+ # If the type is literally "P" followed by numbers, it's a project identifier
895
+ # Return "P" to get the generic project typed_stage
896
+ return "P"
897
+ elsif type_value == "Std"
898
+ return "Std"
899
+ elsif type_value.match?(/^No\.?$/)
900
+ return type_value
901
+ end
902
+ end
903
+
904
+ # Default to "Std" for published standards
905
+ "Std"
906
+ end
907
+
908
+ # Extract a simple value from parsed data
909
+ # @param value [Object] the value to extract
910
+ # @return [String, nil] the extracted string value
911
+ def extract_value(value)
912
+ return nil if value.nil?
913
+ return nil if value.is_a?(Array) && value.empty?
914
+
915
+ if value.is_a?(Array)
916
+ joined = value.join
917
+ return joined.length.positive? ? joined : nil
918
+ end
919
+
920
+ str_value = value.to_s.strip
921
+ str_value.length.positive? ? str_value : nil
922
+ end
923
+
924
+ # Extract optional attribute if present
925
+ def extract_optional(parsed, attributes, key)
926
+ value = extract_value(parsed[key])
927
+ attributes[key] = value if value
928
+ end
929
+
930
+ # Handle draft information
931
+ def handle_draft(parsed, attributes)
932
+ draft_data = parsed[:draft] || parsed[:digit_draft]
933
+ return unless draft_data
934
+
935
+ # Draft can be an array of hash elements or a single hash
936
+ if draft_data.is_a?(Array)
937
+ # Merge all elements in the array
938
+ merged = draft_data.inject({}) { |result, elem| result.merge(elem) }
939
+ draft_data = merged
940
+ end
941
+
942
+ # Extract draft version and revision
943
+ version = nil
944
+ revision = nil
945
+ month = nil
946
+ year = nil
947
+ day = nil
948
+ comma_before_month = false
949
+
950
+ if draft_data.is_a?(Hash)
951
+ if draft_data[:draft_version]
952
+ dv = draft_data[:draft_version]
953
+ version = if dv.is_a?(Array)
954
+ dv.map { |v| extract_value(v) }.join
955
+ else
956
+ extract_value(dv)
957
+ end
958
+ end
959
+
960
+ revision = extract_value(draft_data[:revision]) if draft_data[:revision]
961
+
962
+ # Extract date information from draft data
963
+ month_slice = draft_data[:month]
964
+ month = extract_value(month_slice) if month_slice
965
+ year = extract_value(draft_data[:year]) if draft_data[:year]
966
+ day = extract_value(draft_data[:day]) if draft_data[:day]
967
+
968
+ # Detect comma before month and space before draft by checking the original input
969
+ if original_input
970
+ if month
971
+ # Look for ", Month" pattern in the original input
972
+ comma_before_month = original_input.match?(/,\s+#{Regexp.escape(month)}/i)
973
+ end
974
+
975
+ # Check if there's a space before /D in the original input
976
+ if version && original_input.match?(/\s+\/D#{Regexp.escape(version)}/i)
977
+ attributes[:space_before_draft] = true
978
+ end
979
+ end
980
+ else
981
+ version = extract_value(draft_data)
982
+ end
983
+
984
+ # Create Draft component object if we have version info
985
+ if version
986
+ draft_obj = Components::Draft.new(
987
+ version: version,
988
+ revision: revision,
989
+ month: month,
990
+ year: year,
991
+ day: day,
992
+ )
993
+ draft_obj.comma_before_month = comma_before_month
994
+ attributes[:draft_obj] = draft_obj
995
+ attributes[:draft] = draft_obj.to_s
996
+ end
997
+ end
998
+
999
+ # Handle corrigendum information
1000
+ def handle_corrigendum(parsed, attributes)
1001
+ if parsed[:corrigendum].is_a?(Hash)
1002
+ cor_data = parsed[:corrigendum]
1003
+ attributes[:cor_number] = extract_value(cor_data[:cor_number])
1004
+ if cor_data[:cor_year]
1005
+ attributes[:cor_year] =
1006
+ extract_value(cor_data[:cor_year])
1007
+ end
1008
+ elsif parsed[:corrigendum]
1009
+ attributes[:corrigendum] = extract_value(parsed[:corrigendum])
1010
+ end
1011
+ end
1012
+
1013
+ # Handle amendment information
1014
+ def handle_amendment(parsed, attributes)
1015
+ if parsed[:amendment].is_a?(Hash)
1016
+ amd_data = parsed[:amendment]
1017
+ attributes[:amd_number] = extract_value(amd_data[:amd_number])
1018
+ if amd_data[:amd_year]
1019
+ attributes[:amd_year] =
1020
+ extract_value(amd_data[:amd_year])
1021
+ end
1022
+ elsif parsed[:amendment]
1023
+ attributes[:amendment] = extract_value(parsed[:amendment])
1024
+ end
1025
+ end
1026
+
1027
+ # Handle reaffirmed information
1028
+ def handle_reaffirmed(parsed, attributes)
1029
+ if parsed[:reaffirmed].is_a?(Hash)
1030
+ reaf_data = parsed[:reaffirmed]
1031
+ attributes[:reaffirmed] = extract_value(reaf_data[:year])
1032
+ elsif parsed[:reaffirmed]
1033
+ attributes[:reaffirmed] = extract_value(parsed[:reaffirmed])
1034
+ end
1035
+ end
1036
+
1037
+ # Handle conformance document information
1038
+ def handle_conformance(parsed, attributes)
1039
+ if parsed[:conformance].is_a?(Hash)
1040
+ conf_data = parsed[:conformance]
1041
+ attributes[:conf_number] = extract_value(conf_data[:conf_number])
1042
+ if conf_data[:conf_year]
1043
+ attributes[:conf_year] = extract_value(conf_data[:conf_year])
1044
+ end
1045
+ elsif parsed[:conformance]
1046
+ attributes[:conformance] = extract_value(parsed[:conformance])
1047
+ end
1048
+ end
1049
+
1050
+ # Handle ASHRAE joint publication information
1051
+ def handle_ashrae_copub(parsed, attributes)
1052
+ if parsed[:ashrae_copub].is_a?(Hash)
1053
+ ashrae_data = parsed[:ashrae_copub]
1054
+ attributes[:ashrae_number] =
1055
+ extract_value(ashrae_data[:ashrae_number])
1056
+ if ashrae_data[:ashrae_year]
1057
+ attributes[:ashrae_year] = extract_value(ashrae_data[:ashrae_year])
1058
+ end
1059
+ elsif parsed[:ashrae_copub]
1060
+ attributes[:ashrae_copub] = extract_value(parsed[:ashrae_copub])
1061
+ end
1062
+ end
1063
+
1064
+ # Handle IEEE cross-reference information
1065
+ def handle_ieee_crossref(parsed, attributes)
1066
+ if parsed[:ieee_crossref].is_a?(Hash)
1067
+ parsed[:ieee_crossref]
1068
+ # Extract the cross-reference string (e.g., "C62.22.1-1996")
1069
+ # Store it for rendering
1070
+ attributes[:crossref] =
1071
+ "/C#{extract_value(crossref).to_s.sub(/^\//, '')}"
1072
+ elsif parsed[:ieee_crossref]
1073
+ attributes[:crossref] = extract_value(parsed[:ieee_crossref])
1074
+ end
1075
+ end
1076
+
1077
+ # Handle additional parameters
1078
+ def handle_parameters(parsed, attributes)
1079
+ if parsed[:parameters].is_a?(Hash)
1080
+ param_data = parsed[:parameters]
1081
+
1082
+ # Handle parenthetical content (for multi-part adoptions like "ANSI Y32.21-1976, NCTA 006-0975")
1083
+ if param_data[:parenthetical_content]
1084
+ attributes[:parenthetical_content] =
1085
+ extract_value(param_data[:parenthetical_content])
1086
+ end
1087
+
1088
+ if param_data[:revision_of]
1089
+ attributes[:revision_of] =
1090
+ extract_value(param_data[:revision_of])
1091
+ end
1092
+ if param_data[:amendment_to]
1093
+ attributes[:amendment_to] =
1094
+ extract_value(param_data[:amendment_to])
1095
+ end
1096
+ if param_data[:adoption]
1097
+ attributes[:adoption] =
1098
+ extract_value(param_data[:adoption])
1099
+ end
1100
+ if param_data[:note]
1101
+ attributes[:note] =
1102
+ extract_value(param_data[:note])
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ # Build relationships from parsed relationship clause (Pattern 4)
1108
+ # @param parsed_hash [Hash] the parsed data containing relationship information
1109
+ # @return [Array<Components::Relationship>] array of Relationship objects
1110
+ def build_relationships(parsed_hash)
1111
+ return [] unless parsed_hash[:relationship_type] || parsed_hash[:relationship_clause]
1112
+
1113
+ relationships = []
1114
+
1115
+ # Main relationship
1116
+ if parsed_hash[:relationship_type]
1117
+ rel_type = extract_relationship_type(parsed_hash[:relationship_type])
1118
+ related = parse_identifier_list(parsed_hash[:related_ids])
1119
+
1120
+ # Handle "as amended by" clause
1121
+ amendments = nil
1122
+ approved_flag = false
1123
+
1124
+ if parsed_hash[:amendments]
1125
+ # Check if it's the "and its approved amendments" flag
1126
+ if parsed_hash[:amendments].is_a?(Hash) && parsed_hash[:amendments][:approved_amendments]
1127
+ approved_flag = true
1128
+ else
1129
+ amendments = parse_identifier_list(parsed_hash[:amendments])
1130
+ end
1131
+ end
1132
+
1133
+ relationships << Components::Relationship.new(
1134
+ relationship_type: rel_type,
1135
+ related_identifiers: related,
1136
+ intermediate_amendments: amendments,
1137
+ approved_amendments_flag: approved_flag,
1138
+ )
1139
+ end
1140
+
1141
+ # Additional relationships (separated by " / ")
1142
+ if parsed_hash[:additional_rels]
1143
+ additional = parsed_hash[:additional_rels]
1144
+ additional = [additional] unless additional.is_a?(Array)
1145
+
1146
+ additional.each do |rel_data|
1147
+ rel_type = extract_relationship_type(rel_data[:relationship_type])
1148
+ related = parse_identifier_list(rel_data[:related_ids])
1149
+
1150
+ # Handle "as amended by" clause for additional relationships
1151
+ amendments = nil
1152
+ approved_flag = false
1153
+
1154
+ if rel_data[:amendments]
1155
+ if rel_data[:amendments].is_a?(Hash) && rel_data[:amendments][:approved_amendments]
1156
+ approved_flag = true
1157
+ else
1158
+ amendments = parse_identifier_list(rel_data[:amendments])
1159
+ end
1160
+ end
1161
+
1162
+ relationships << Components::Relationship.new(
1163
+ relationship_type: rel_type,
1164
+ related_identifiers: related,
1165
+ intermediate_amendments: amendments,
1166
+ approved_amendments_flag: approved_flag,
1167
+ )
1168
+ end
1169
+ end
1170
+
1171
+ relationships
1172
+ end
1173
+
1174
+ # Extract relationship type from parsed hash
1175
+ # @param type_hash [Hash] hash with relationship type key
1176
+ # @return [String] the relationship type constant name
1177
+ def extract_relationship_type(type_hash)
1178
+ return nil unless type_hash.is_a?(Hash)
1179
+
1180
+ # type_hash has key like :revision_of, :amendment_to, etc.
1181
+ type_hash.keys.first.to_s
1182
+ end
1183
+
1184
+ # Parse a list of identifier strings into identifier objects
1185
+ # @param list_data [Hash, Array, nil] the parsed identifier list data
1186
+ # @return [Array<Identifiers::Base>] array of parsed identifier objects
1187
+ def parse_identifier_list(list_data)
1188
+ return [] unless list_data
1189
+
1190
+ # Extract identifier strings from parsed data
1191
+ id_strings = extract_identifier_strings(list_data)
1192
+
1193
+ # Recursively parse each identifier string
1194
+ id_strings.map do |id_str|
1195
+ # Use Identifiers::Base.parse for recursive parsing
1196
+ Identifiers::Base.parse(id_str)
1197
+ rescue Parslet::ParseFailed
1198
+ # If parsing fails, create minimal identifier with just the string
1199
+ # This maintains graceful degradation
1200
+ Identifiers::Base.new(parenthetical_content: id_str)
1201
+ end
1202
+ end
1203
+
1204
+ # Extract identifier strings from parsed list data
1205
+ # @param list_data [Hash, Array] the parsed list data
1206
+ # @return [Array<String>] array of identifier strings
1207
+ def extract_identifier_strings(list_data)
1208
+ if list_data.is_a?(Array)
1209
+ # Array of id hashes: [{id: "..."}, {id: "..."}]
1210
+ list_data.map { |item| item[:id].to_s.strip }
1211
+ elsif list_data.is_a?(Hash) && list_data[:id]
1212
+ # Single id hash: {id: "..."}
1213
+ [list_data[:id].to_s.strip]
1214
+ else
1215
+ []
1216
+ end
1217
+ end
1218
+
1219
+ # Build combined AIEE identifier from "and"-separated identifiers
1220
+ # @param parsed [Hash] parsed combined AIEE data
1221
+ # @return [Identifiers::CombinedAiee] combined AIEE identifier
1222
+ def build_combined_aiee(parsed)
1223
+ # Build each AIEE identifier using AIEE builder
1224
+ aiee_builder = Aiee::Builder.new
1225
+ first_id = aiee_builder.build(parsed[:first_aiee])
1226
+ second_id = aiee_builder.build(parsed[:second_aiee])
1227
+
1228
+ # Create a simple combined identifier (reuse DualPublished pattern)
1229
+ Identifiers::DualPublished.new(
1230
+ first_identifier: first_id,
1231
+ second_identifier: second_id,
1232
+ separator: " and ",
1233
+ )
1234
+ end
1235
+ end
1236
+ end
1237
+ end