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,1644 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "parslet"
4
+
5
+ module Pubid
6
+ module Ieee
7
+ # Parser class for IEEE identifiers
8
+ # Single Responsibility: Parsing IEEE identifier syntax
9
+ # Note: IEEE is extremely complex with many edge cases
10
+ class Parser < ::Pubid::Parser::Grammar
11
+ # Basic building blocks
12
+ rule(:space) { str(" ") }
13
+ rule(:space?) { space.maybe }
14
+ rule(:dash) { str("-") }
15
+ rule(:dash?) { dash.maybe }
16
+ rule(:dot) { str(".") }
17
+ rule(:slash) { str("/") }
18
+ rule(:comma) { str(", ") }
19
+ rule(:digit) { match("[0-9]") }
20
+ rule(:digits) { digit.repeat(1) }
21
+ rule(:letter) { match("[A-Za-z]") }
22
+ rule(:upper) { match("[A-Z]") }
23
+ rule(:lower) { match("[a-z]") }
24
+
25
+ # Year pattern (4 digits starting with 19 or 20), optionally followed by letter(s)
26
+ # e.g. 2012, 201x, 2010a
27
+ rule(:year_digits) do
28
+ (str("19") | str("20")) >> digit.repeat(2,
29
+ 2) >> lower.repeat(0,
30
+ 2) >> digits.absent?
31
+ end
32
+
33
+ # Month patterns - numeric format (01-12)
34
+ rule(:month_numeric) do
35
+ (str("0") >> match("[1-9]")) | # 01-09
36
+ (str("1") >> match("[0-2]")) # 10-12
37
+ end
38
+
39
+ # Comprehensive date parsing
40
+ # Format 1: "September 2018" or "Sept 2018" (text month + year)
41
+ rule(:date_with_month_text) do
42
+ month_name.as(:month) >> space >> year_digits.as(:year)
43
+ end
44
+
45
+ # Format 2: "2018-09" (year-numeric month)
46
+ rule(:date_with_month_numeric) do
47
+ year_digits.as(:year) >> dash >> month_numeric.as(:month)
48
+ end
49
+
50
+ # Format 3: Just year "2018"
51
+ rule(:date_year_only) do
52
+ year_digits.as(:year)
53
+ end
54
+
55
+ # Combined date rule - longest match first
56
+ rule(:date_standalone) do
57
+ date_with_month_text | date_with_month_numeric | date_year_only
58
+ end
59
+
60
+ # Trailing print/reaffirm date "<sep>Month YYYY" — captured under the
61
+ # distinct :trailing_month/:trailing_year keys so it never collides with a
62
+ # base -YYYY identity year (the builder promotes it only when no base year
63
+ # exists). Accepts BOTH a month name ("May 2014") and a month-first numeric
64
+ # month ("05 2014"). The two variants are unambiguous: month_name never
65
+ # starts with a digit, and month_numeric (01-12) never matches a bare
66
+ # 19xx/20xx year — so a bare trailing year still falls through to callers'
67
+ # own bare-year clause.
68
+ rule(:trailing_month_year) do
69
+ # The numeric branch accepts `-` as well as space before the year:
70
+ # preprocessing (the `(\d)\s+(\d{4})` gsub) rewrites a trailing
71
+ # "05 2014" → "05-2014", so a numeric month reaches the grammar
72
+ # dash-joined; the month-name branch escapes that gsub (the char before
73
+ # the space is a letter) and stays space-joined.
74
+ ((comma | space) >> month_name.as(:trailing_month) >> space >> year_digits.as(:trailing_year)) |
75
+ ((comma | space) >> month_numeric.as(:trailing_month) >> (space | dash) >> year_digits.as(:trailing_year))
76
+ end
77
+
78
+ # Month patterns
79
+ rule(:month_name) do
80
+ # Period-suffixed abbreviations (longest first)
81
+ str("Sept.") | str("Oct.") | str("Nov.") | str("Dec.") |
82
+ str("Jan.") | str("Feb.") | str("Mar.") | str("Apr.") |
83
+ str("Jun.") | str("Jul.") | str("Aug.") |
84
+ # Full month names
85
+ str("January") | str("February") | str("March") | str("April") |
86
+ str("May") | str("June") | str("July") | str("August") |
87
+ str("September") | str("October") | str("November") | str("December") |
88
+ # Non-period abbreviations
89
+ str("Jan") | str("Feb") | str("Mar") | str("Apr") | str("Jun") |
90
+ str("Jul") | str("Aug") | str("Sep") | str("Sept") | str("Oct") | str("Nov") | str("Dec")
91
+ end
92
+
93
+ # Organizations
94
+ rule(:organization) do
95
+ str("IEEE") | str("AIEE") | str("ANSI") | str("ASA") |
96
+ # ANS (American Nuclear Society) — a third co-publisher on nuclear
97
+ # standards ("ANSI/IEEE/ANS 7.4-3-2-1982"). Listed AFTER ANSI so it
98
+ # never shadows the longer token.
99
+ str("ANS") |
100
+ str("IEC") | str("ISO") | str("ASTM") | str("CSA") | str("ASME") |
101
+ str("NACE") | str("NSF") | str("ASHRAE") | str("NCTA") | str("AESC") |
102
+ str("EIA") | # NEW Session 224: Add EIA support
103
+ # Historical / foreign co-publishers seen in relaton-data-ieee
104
+ # (bucket 4): AMPP, USAS (both standalone), and the IEEE sub-board /
105
+ # partner co-publishers USEMCSC, EAB, MPAI.
106
+ str("USEMCSC") | str("AMPP") | str("USAS") | str("EAB") | str("MPAI")
107
+ end
108
+
109
+ # Complex organization prefixes (Category 5: ANSI Complex)
110
+ rule(:complex_org_prefix) do
111
+ str("ANSI/IEEE-ANS") | str("ANSI/IEEE") | str("ANSI")
112
+ end
113
+
114
+ # Characteristic IEEE number patterns (without prefix)
115
+ # These patterns are distinctly IEEE even without "IEEE Std" prefix
116
+ rule(:characteristic_ieee_number) do
117
+ # C37.xxx series (power systems) - C followed by 2 digits, dot, more digits
118
+ (str("C") >> digit.repeat(2,
119
+ 2) >> dot >> digits >> match("[a-z]").repeat.maybe) |
120
+ # 802.xxx series (networking) - 802 followed by dot, digits, optional letter suffix
121
+ (str("802") >> dot >> digits >> match("[a-z]").repeat.maybe) |
122
+ # P followed by digits (draft projects)
123
+ (str("P") >> digits.repeat(1))
124
+ end
125
+
126
+ rule(:publisher) do
127
+ complex_org_prefix.as(:publisher) | organization.as(:publisher)
128
+ end
129
+
130
+ rule(:copublisher) do
131
+ # Three-way copublisher strings (treat as single unit, longest first)
132
+ str("/ISO/IEC").as(:copublisher) |
133
+ str("/IEC/ISO").as(:copublisher) |
134
+ # Two-way copublishers (original pattern)
135
+ (slash >> space? >> organization.as(:copublisher))
136
+ end
137
+
138
+ # Conformance document patterns (/Conformance01-2003, /Conformance02-2014)
139
+ # Allow optional space before slash for malformed inputs
140
+ rule(:conformance) do
141
+ (space? >> slash >> str("Conformance") >> match("[0-9]").repeat(1).as(:conf_number) >> dash >> year_digits.as(:conf_year)).as(:conformance)
142
+ end
143
+
144
+ # ASHRAE joint publication patterns (/ASHRAE Guideline 21-2012)
145
+ # Also handles /ASHRAE 21 without "Guideline"
146
+ rule(:ashrae_copub) do
147
+ (slash >> str("ASHRAE") >> space >>
148
+ (str("Guideline") >> space).maybe >>
149
+ digits.as(:ashrae_number) >>
150
+ (dash >> year_digits.as(:ashrae_year)).maybe).as(:ashrae_copub)
151
+ end
152
+
153
+ # IEEE cross-reference patterns (/C62.22.1-1996)
154
+ # References another IEEE standard from a specific series (e.g., C62, C37, C57)
155
+ rule(:ieee_crossref) do
156
+ (slash >> str("C") >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits).as(:ieee_crossref)
157
+ end
158
+
159
+ # IPCEA co-designation suffix (/IPCEA P-46-426-1962). Captured verbatim
160
+ # (leading slash included) so it round-trips through the `crossref`
161
+ # attribute. Used only by the S-designation rule below.
162
+ rule(:ipcea_copub) do
163
+ (slash >> str("IPCEA") >> space >>
164
+ match('[A-Za-z0-9.\-]').repeat(1)).as(:ipcea_copub)
165
+ end
166
+
167
+ # Historical IEEE/IPCEA co-published cable designation (e.g. S-135).
168
+ # The "S-<digits>" number has a dash between the letter series and the
169
+ # digits, which the shared `number` rule deliberately rejects (that
170
+ # tightening is what keeps a bare "IEEE S" from parsing). So this one-off
171
+ # family gets its own rule rather than loosening `number` and risking the
172
+ # 12k-row corpus. Requiring `str("S") >> dash >> digits` keeps "IEEE S"
173
+ # (no dash+digit) rejected. Handles: "IEEE Std S-135", "IEEE S-135",
174
+ # bare "S-135", the "/IPCEA …" slash co-designation, and the
175
+ # "(IPCEA …)" parenthetical variant.
176
+ rule(:s_designation) do
177
+ (publisher >> space).maybe >>
178
+ (type_word.as(:type) >> space?).maybe >>
179
+ (str("S") >> dash >> digits).as(:s_number) >>
180
+ ipcea_copub.maybe >>
181
+ parenthetical.maybe
182
+ end
183
+
184
+ # Document number - support letters and digits, with optional prefix P
185
+ # Complex multi-part numbers like P11073-10404-10419 should be fully captured
186
+ # But simple cases like "623-1976" should not consume the dash before year
187
+ rule(:number) do
188
+ (str("P").maybe >>
189
+ # The numeric core must contain at least one digit: an optional letter
190
+ # prefix (C, S, …) then a required `digits` run, then any digit/letter
191
+ # tail. The old `(digits | upper).repeat(1)` wrongly accepted an
192
+ # all-letter token like a bare "S" (a prefix with no number — e.g.
193
+ # "IEEE S"), which is not a valid IEEE identifier.
194
+ upper.repeat(0) >> digits >> (digits | upper).repeat(0) >>
195
+ # Only consume dash+digits if followed by another dash+digits (multi-part pattern)
196
+ # OR if the digits don't look like a year (not 4 digits starting with 19/20)
197
+ # This prevents consuming "623-1976" as a number but allows "P11073-10404-10419"
198
+ (dash >> digits >> year_digits.absent? >> (dash >> digits).repeat).maybe >>
199
+ lower.maybe).as(:number)
200
+ end
201
+
202
+ # Type - handle "No." and "No" (case-insensitive), longest first
203
+ rule(:type_word) do
204
+ str("Draft Std") | str("STD") | str("Standard") |
205
+ str("Std No.") | str("Std") | # Add "Std No." before "Std"
206
+ str("PTC") | # ASME Performance Test Code
207
+ (match("[Nn]") >> str("o.")) | (match("[Nn]") >> str("o")) |
208
+ str("No")
209
+ end
210
+
211
+ # Part and subpart - handle both dot and dash separators
212
+ rule(:part) do
213
+ (dot | dash) >> match("[0-9A-Za-z]").repeat(1).as(:part)
214
+ end
215
+
216
+ rule(:subpart) do
217
+ (dot | dash | str("_")) >>
218
+ ((str("REV") | str("Rev")).maybe >> match("[0-9a-z]").repeat(1) >>
219
+ (dot >> digits).maybe).as(:subpart)
220
+ end
221
+
222
+ # Year component - updated to use comprehensive date parsing
223
+ rule(:year) do
224
+ (dot | dash) >> date_standalone >> str("(E)").maybe
225
+ end
226
+
227
+ # Draft patterns
228
+ rule(:draft_status) do
229
+ # "Active" (bucket 3) joins the generic draft-status path so
230
+ # "IEEE Active Std P… /D…" parses like the "Unapproved" forms, without
231
+ # touching ieee_approved_draft_identifier (which would break the
232
+ # issue-#209 unapproved-drops-Std rendering). "Active Approved" completes
233
+ # the two-word matrix alongside "Active Unapproved" (draft-grammar
234
+ # coverage: the "<status> Draft P…/D…" prefix forms). Longest token first.
235
+ (str("Active Unapproved") | str("Active Approved") | str("Unapproved") |
236
+ str("Approved") | str("Active")) >> space
237
+ end
238
+
239
+ rule(:draft_prefix) do
240
+ space? >> (str("/") | str("_") | dash | space)
241
+ end
242
+
243
+ rule(:draft_version) do
244
+ # Enhanced to handle multiple draft notation patterns
245
+ # D is optional to handle /08 style drafts (e.g., IEEE P1052/08)
246
+ # A draft never begins with "R-" — that is the revision suffix
247
+ # (revision_suffix rule); guard so the D-less path doesn't swallow a
248
+ # bare "/R-<id>" (e.g. the no-draft "P1722/R-1") as a draft.
249
+ (str("R") >> dash).absent? >>
250
+ (str("D") >> str("IS").absent?).maybe >> # Avoid matching "DIS" (ISO stage)
251
+ (
252
+ # Pattern: D3.1 (decimal with 1-2 digits on each side) - MOST COMMON, put first
253
+ # Also handles trailing letter: D7.3A, D2.0E
254
+ (match("[0-9]").repeat(1,
255
+ 2) >> dot >> match("[0-9]").repeat(1,
256
+ 2) >> lower.maybe) |
257
+ # Pattern: D.XX (decimal starting with dot) - e.g., D.19
258
+ (dot >> digits) |
259
+ # Pattern: DX+X (plus sign) - e.g., D1+1
260
+ (digits >> str("+") >> digits) |
261
+ # Pattern: DXXXXeYY or DXXXX.eYY (complex) - e.g., D2012.e27
262
+ (digits >> dot.maybe >> str("e") >> digits) |
263
+ # Pattern: D-X or DX or DX-d or DX_letter (original patterns)
264
+ # Handles: D12, D3.0, D043Rev18, suffixes like D15Sept
265
+ (str("-").maybe >> match("[0-9A-Za-z]").repeat(1) >> (str("-d") | (str("_") >> match("[0-9A-Za-z]").repeat(0))).maybe)
266
+ ).as(:draft_version)
267
+ end
268
+
269
+ rule(:draft_date) do
270
+ # Enhanced to handle: ", Sept 2008" or " Sept 2008" or ", Month Year"
271
+ ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year)) |
272
+ # Numeric-month form ", 05 2007" / " 05 2007" — no text month name. The
273
+ # year separator may be a dash: preprocessing (parser.rb ~1278) rewrites
274
+ # a trailing " <digits> <year>" to "<digits>-<year>", so "05 2007"
275
+ # reaches the grammar as "05-2007".
276
+ ((comma | space) >> month_numeric.as(:month) >> (space | dash) >> year_digits.as(:year)) |
277
+ (((space? >> comma >> space?) | space) >> month_name.as(:month) >>
278
+ (
279
+ # "Month DD, Year" (day then comma) and "Month DD Year" (day, no
280
+ # comma) — the day/year separator may be a dash for the same
281
+ # preprocessing reason ("July 15 2012" → "July 15-2012").
282
+ ((space >> digits.as(:day)) >> ((comma | space | dash) >> year_digits.as(:year))) |
283
+ ((space >> digits.as(:day)).maybe >> comma >> year_digits.as(:year)) |
284
+ (comma >> space? >> year_digits.as(:year)) |
285
+ (space >> year_digits.as(:year))
286
+ ))
287
+ end
288
+
289
+ # FDIS and similar ISO stage codes without D prefix (Pattern 3)
290
+ # These appear after / in IEEE P identifiers but don't have the D prefix
291
+ # Examples: IEEE P15939/FDIS, IEEE P1234/CDV
292
+ rule(:fdraft) do
293
+ (slash >>
294
+ (str("FDIS") | str("CDV") | str("CD") | str("WD") | str("PWI") | str("NP")) >>
295
+ (
296
+ ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year)) | # Month Year
297
+ ((comma | space) >> year_digits.as(:year)) # Year only (e.g., /FDIS, 2016)
298
+ ).maybe >>
299
+ parenthetical.maybe).as(:fdraft)
300
+ end
301
+
302
+ rule(:draft) do
303
+ (draft_prefix >> draft_version.repeat(1, 2) >>
304
+ (dot >> digits.as(:revision)).maybe >>
305
+ draft_date.maybe).as(:draft)
306
+ end
307
+
308
+ # Trailing revision suffix "/R-<id>". Normalization funnels every revision
309
+ # spelling here — IEEE's native inline "Rev<n>" (repositioned) and
310
+ # relaton's synthetic "/R-<x>" (kept in place) — so a single trailing rule
311
+ # captures them all. The id is alphanumeric ("2", "18", "i").
312
+ rule(:revision_suffix) do
313
+ slash >> str("R") >> dash >> match('[0-9A-Za-z]').repeat(1).as(:revision)
314
+ end
315
+
316
+ # Edition - enhanced to support IEC formats like "Edition 1.0 2015-03"
317
+ #
318
+ # The edition's own year is captured as :edition_year (NOT :year). The
319
+ # generic Std branch carries a base "-YYYY" slot AND a separate trailing
320
+ # `edition.maybe`; a bare :year here would collide with the base :year
321
+ # when both fire, so Parslet warns "Duplicate subtrees … keys: [:year]"
322
+ # and drops the base identity year (the same failure #299 fixed for the
323
+ # trailing month/year clause). The builder promotes :edition_year to the
324
+ # identity year only when there is no base year.
325
+ rule(:edition) do
326
+ (comma >> year_digits.as(:edition_year) >> str(" Edition")) |
327
+ ((space | dash) >> str("Edition ") >>
328
+ (digits >> dot >> digits).as(:edition) >>
329
+ # Year separator: a space, " - ", or a bare dash (preprocessing
330
+ # rewrites "Edition 3.0 2016" -> "Edition 3.0-2016", the shape the
331
+ # normalized "/E-<n>-YYYY" suffix produces — nil-residue item 1).
332
+ (str(" - ") | space | dash) >>
333
+ year_digits.as(:edition_year) >>
334
+ (dash >> digit.repeat(2, 2).as(:edition_month)).maybe) # Capture -MM as edition_month
335
+ end
336
+
337
+ # Part/subpart/year combinations
338
+ rule(:part_subpart_year) do
339
+ (part >> subpart.repeat(1, 2) >> year) |
340
+ (part >> subpart >> year) |
341
+ (part >> year) |
342
+ (part >> subpart) |
343
+ year |
344
+ part
345
+ end
346
+
347
+ # Corrigendum
348
+ rule(:corrigendum) do
349
+ # Enhanced: Accept space as separator, make separators more flexible
350
+ # Also accept "Corrigendum" as alternative to "Cor"
351
+ ((str("_") | slash | dash | space) >>
352
+ (str("Corrigendum") | str("Cor")) >>
353
+ (dash | dot | space).maybe >> # More flexible separator after "Cor"
354
+ space? >> # Add space handling after separator
355
+ digits.as(:cor_number).maybe >>
356
+ ((dash | str(":") | space) >> year_digits.as(:cor_year)).maybe).as(:corrigendum)
357
+ end
358
+
359
+ # Amendment — mirrors the corrigendum rule's separator flexibility so
360
+ # IEEE-format "/Amd 2-2004" and "/Amd2-2004" parse the same way as
361
+ # their Cor counterparts (issue #210).
362
+ rule(:amendment) do
363
+ ((str("_") | slash | dash | space) >>
364
+ (str("Amendment") | str("Amd")) >>
365
+ (dash | dot | space).maybe >>
366
+ space? >>
367
+ digits.as(:amd_number).maybe >>
368
+ ((dash | str(":") | space) >> year_digits.as(:amd_year)).maybe).as(:amendment)
369
+ end
370
+
371
+ # Interpretation notation (/INT)
372
+ # Enhanced to support optional year suffix: /INT-1991, /INT 1991
373
+ rule(:interpretation) do
374
+ (slash >> str("INT") >> ((dash | str(":") | space) >> year_digits.as(:int_year)).maybe).as(:interpretation)
375
+ end
376
+
377
+ # Reaffirmed - enhanced to support (R1992) format without space
378
+ rule(:reaffirmed) do
379
+ (
380
+ # Format: "Reaffirmed 1992"
381
+ (str("Reaffirmed ") >> year_digits.as(:year)) |
382
+ # Format: "(R1992)" - parentheses with R prefix (with or without space before)
383
+ (space.maybe >> str("(R") >> year_digits.as(:year) >> str(")"))
384
+ ).as(:reaffirmed)
385
+ end
386
+
387
+ # Redline suffix at the very end. Accepts relaton's canonical " Redline"
388
+ # (space, no dash) and pubid's older " - Redline" (space-dash-space),
389
+ # case-insensitive. Captured (presence only) so the builder sets a
390
+ # `redline: true` flag the renderer restores — a redline is a distinct
391
+ # document and must not collapse to its base standard.
392
+ rule(:redline) do
393
+ (space >> (dash >> space).maybe >>
394
+ (str("Redline") | str("REDLINE") | str("redline"))).as(:redline)
395
+ end
396
+
397
+ # Book nickname (e.g., "[The Orange Book]", "[IEEE Gold Book]")
398
+ rule(:book_nickname) do
399
+ space >> str("[") >> match("[^\\]]").repeat(1).as(:nickname) >> str("]")
400
+ end
401
+
402
+ # Relationship type keywords for Pattern 4 identifiers
403
+ rule(:relationship_revision_of) do
404
+ str("Revision of ") | str("Revison of ")
405
+ end
406
+ rule(:relationship_amendment_to) { str("Amendment to ") }
407
+ rule(:relationship_corrigendum_to) do
408
+ str("Corrigendum to ") | str("Corrigenda to ")
409
+ end
410
+ rule(:relationship_incorporates) do
411
+ str("incorporates ") | str("Incorporating ") | str("Incorporates ")
412
+ end
413
+ rule(:relationship_adoption_of) { str("Adoption of ") }
414
+ rule(:relationship_supplement_to) { str("Supplement to ") }
415
+ rule(:relationship_draft_amendment) do
416
+ str("Draft Amendment to ") | str("DRAFT Amendment to ")
417
+ end
418
+ rule(:relationship_draft_revision) { str("Draft Revision of ") }
419
+ rule(:relationship_reaffirmation) { str("Reaffirmation of ") }
420
+ rule(:relationship_redesignation) do
421
+ str("Redesignation of ") | str("redesignated as ")
422
+ end
423
+ rule(:relationship_supersedes) { str("Supersedes ") | str("Supercedes ") }
424
+ rule(:relationship_previously_designated) do
425
+ str("Previously designated as ")
426
+ end
427
+ rule(:relationship_includes) { str("Includes ") } # NEW Session 171
428
+
429
+ # Combined relationship type (longest match first)
430
+ rule(:relationship_type) do
431
+ relationship_draft_amendment.as(:draft_amendment_to) |
432
+ relationship_draft_revision.as(:draft_revision_of) |
433
+ relationship_previously_designated.as(:previously_designated_as) |
434
+ relationship_reaffirmation.as(:reaffirmation_of) |
435
+ relationship_redesignation.as(:redesignation_of) |
436
+ relationship_supersedes.as(:supersedes) |
437
+ relationship_includes.as(:includes) | # NEW Session 171
438
+ relationship_revision_of.as(:revision_of) |
439
+ relationship_amendment_to.as(:amendment_to) |
440
+ relationship_corrigendum_to.as(:corrigendum_to) |
441
+ relationship_incorporates.as(:incorporates) |
442
+ relationship_adoption_of.as(:adoption_of) |
443
+ relationship_supplement_to.as(:supplement_to)
444
+ end
445
+
446
+ # Identifier string (for parsing list of related identifiers)
447
+ # Captures text until delimiter: comma, closing paren, "and", " / ", "; ", "as amended by"
448
+ # Uses absent? to ensure we stop at these delimiters
449
+ rule(:identifier_string) do
450
+ (
451
+ str(", and ").absent? >>
452
+ str(" and ").absent? >>
453
+ str(", ").absent? >>
454
+ str(" as amended by ").absent? >>
455
+ # Stop at any "/", ";", or "-" that introduces another relationship
456
+ # (look-ahead: separator + relationship_type keyword). This lets the
457
+ # slash inside "IEEE Std 525-2007/Cor 1-2015" stay part of the
458
+ # identifier, while the slash before "Incorporates ..." splits.
459
+ relationship_break.absent? >>
460
+ str(")").absent? >>
461
+ match(".")
462
+ ).repeat(1)
463
+ end
464
+
465
+ # Identifier list (comma and "and" separated)
466
+ rule(:identifier_list) do
467
+ identifier_string.as(:id) >>
468
+ (
469
+ (str(", and ") | str(" and ") | str(", ")) >>
470
+ identifier_string.as(:id)
471
+ ).repeat
472
+ end
473
+
474
+ # "as amended by" clause with identifier list
475
+ rule(:as_amended_by_clause) do
476
+ # Variant 1: "as amended by IEEE's X, Y, Z"
477
+ (str(" as amended by IEEE's ") >> identifier_list.as(:amendments)) |
478
+ # Variant 2: "as amended by X, Y, Z" (standard)
479
+ (str(" as amended by ") >> identifier_list.as(:amendments)) |
480
+ # Variant 3: "and its approved amendments" (no specific list)
481
+ str(" and its approved amendments").as(:approved_amendments)
482
+ end
483
+
484
+ # A character sequence that may separate two relationships inside the
485
+ # parenthetical: a "/", ";", or "-" with optional surrounding spaces.
486
+ # Standalone it is permissive — the actual decision to split is gated by
487
+ # `relationship_break`, which requires another relationship_type to
488
+ # follow. This way the slash in "IEEE Std 525-2007/Cor 1-2015" stays
489
+ # part of the related identifier.
490
+ rule(:relationship_separator) do
491
+ (space.maybe >> str("/") >> space.maybe) |
492
+ (space.maybe >> str(";") >> space.maybe) |
493
+ (space.maybe >> str("-") >> space.maybe)
494
+ end
495
+
496
+ # Look-ahead: a separator followed by another relationship_type keyword.
497
+ # Used as an absent? guard in identifier_string so the parser stops
498
+ # consuming characters right before a new relationship begins.
499
+ rule(:relationship_break) do
500
+ relationship_separator >> space.maybe >> relationship_type
501
+ end
502
+
503
+ # Relationship clause (handles all relationship types)
504
+ rule(:relationship_clause) do
505
+ space.maybe >> str("(") >>
506
+ relationship_type.as(:relationship_type) >>
507
+ identifier_list.as(:related_ids) >>
508
+ as_amended_by_clause.maybe >>
509
+ # Additional relationships separated by "/", ";", or "-" (optionally
510
+ # surrounded by spaces). The separator is only honored when followed
511
+ # by another relationship_type — identifier-internal slashes like
512
+ # "/Cor 1-2015" are not mistaken for relationship breaks because
513
+ # identifier_string stops at relationship_break ahead.
514
+ (
515
+ relationship_separator >>
516
+ relationship_type.as(:relationship_type) >>
517
+ identifier_list.as(:related_ids) >>
518
+ as_amended_by_clause.maybe
519
+ ).repeat.as(:additional_rels) >>
520
+ str(")")
521
+ end
522
+
523
+ # Title portion separated by colon (Category 8)
524
+ rule(:title_portion) do
525
+ str(":") >> space >> match('[^\n]').repeat(1).as(:title)
526
+ end
527
+
528
+ # Approved Draft suffix (Category 7)
529
+ rule(:approved_draft_suffix) do
530
+ (space >> str("- (Approved Draft)")) | (space >> str("(Approved Draft)"))
531
+ end
532
+
533
+ # Additional parameters (inside parentheses)
534
+ rule(:additional_parameters) do
535
+ (space.maybe >> str("(") >> # Make space before '(' optional
536
+ (reaffirmed |
537
+ # Handle "Revision of IEEE Std ..." with optional space after Std
538
+ (str("Revision of IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
539
+ # Handle typo "Revison of IEEE Std ..." with optional space after Std
540
+ (str("Revison of IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
541
+ # Handle "Revision to IEEE Std ..." with optional space after Std
542
+ (str("Revision to IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
543
+ # Handle "Revison to IEEE Std ..." with optional space after Std
544
+ (str("Revison to IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
545
+ # Amendment patterns (case-insensitive DRAFT)
546
+ ((str("DRAFT") | str("Draft") | str("draft")) >> str(" Amendment to ") >> match("[^)]").repeat(1).as(:draft_amendment_to)) |
547
+ (str("Amendment to IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:amendment_to)) |
548
+ # Adoption patterns
549
+ (str("Adoption of ") >> match("[^)]").repeat(1).as(:adoption)) |
550
+ # Other specific patterns
551
+ (str("Notebooks") >> space? >> match("[^,\\)]").repeat(1).as(:notebooks)) |
552
+ (str("Standard Newspaper(s)") >> space? >> match("[^,\\)]").repeat(1).as(:standard_newspapers)) |
553
+ # Catch-all for any other parenthetical content (MUST BE LAST)
554
+ match("[^)]").repeat(1).as(:parenthetical_content)
555
+ ) >>
556
+ str(")").maybe).as(:parameters)
557
+ end
558
+
559
+ # Parenthetical - try relationship_clause first, then fall back to additional_parameters
560
+ rule(:parenthetical) do
561
+ relationship_clause | additional_parameters
562
+ end
563
+
564
+ # IEC/IEEE copublished pattern - handle all variations comprehensively
565
+ # BUT exclude P prefix patterns (those are joint development)
566
+ rule(:iec_ieee_copublished) do
567
+ str("IEC/IEEE") >>
568
+ space >>
569
+ str("P").absent? >> # NOT a P prefix (would be joint development)
570
+ # The copublished number must contain at least one digit. This rejects
571
+ # an all-letter placeholder like "IEC/IEEE TR" (no real document
572
+ # number) — the same "require a digit" tightening the Standard number
573
+ # rule got, applied to the copublished number grammar.
574
+ (match("[^0-9\n]").repeat >> digit).present? >>
575
+ match("[^\n]").repeat(1).as(:content)
576
+ end
577
+
578
+ # Joint development patterns (ISO/IEC/IEEE in either IEEE or ISO format)
579
+ rule(:joint_development_ieee_format) do
580
+ # ISO/IEC/IEEE P26511/D8-2018 or ISO/IEEE P1003.1-2008 or IEC/IEEE P62582-1-2011
581
+ # ALSO handle: IEC/IEEE P60780-323, CDV1 2014 (comma before stage code)
582
+ # ALSO handle: IEEE/CSA P844.1/293.1/D2 (CSA dual numbering)
583
+ (str("ISO/IEC/IEEE") | str("ISO/IEEE") | str("IEC/IEEE") | str("IEEE/CSA")).as(:joint_publishers) >>
584
+ space >>
585
+ str("P") >> # P indicates IEEE-led
586
+ digits.as(:number) >>
587
+ ((dot | dash) >> digits.as(:part)).maybe >> # Optional part like .1 or -1
588
+ # CSA dual numbering: /293.1 (second number)
589
+ (slash >> digits >> (dot >> digits).maybe >> (dash >> digits.as(:draft_version)).maybe).maybe >>
590
+ (
591
+ # Variant 1: /D8 notation (original)
592
+ (slash >> str("D") >> digits.as(:draft_version)) |
593
+ # Variant 2: , CDV1 notation (comma before stage code)
594
+ (comma >> (str("CDV") | str("FDIS") | str("CD") | str("DIS")).as(:iec_stage) >> digits.maybe.as(:stage_iteration))
595
+ ).maybe >>
596
+ # Optional edition, from relaton's "/E-<n>" suffix normalized to
597
+ # "Edition <n>.0[ YYYY]" (nil-residue hand-off item 1). The edition
598
+ # rule carries its own year, so the year clause below simply doesn't
599
+ # fire when an edition is present.
600
+ edition.maybe >>
601
+ ((dash >> year_digits.as(:year)) | # Either -YEAR
602
+ (comma.maybe >> space >> month_name.as(:month) >> space.maybe >> year_digits.as(:year))).maybe >> # Or Month YEAR (with optional comma)
603
+ revision_suffix.maybe
604
+ end
605
+
606
+ rule(:joint_development_iso_format) do
607
+ # ISO-led stage designations. Two spellings:
608
+ # colon form : "ISO/IEC/IEEE FDIS 26511:2018" (already used)
609
+ # corpus form : "ISO/IEC/IEEE FDIS P26515-2018-05" (historical)
610
+ # The corpus form adds a leading "P" on the number, a trailing
611
+ # "-YYYY[-MM]" date (instead of ":YYYY"), multi-digit committee-draft
612
+ # stage codes (CD1..CD4) plus CDV, and a wider set of joint publishers.
613
+ # (roadmap items 2/3, phase 1). longest publisher token first.
614
+ (str("ISO/IEC/IEEE") | str("IEEE/ISO/IEC") | str("IEEE/IEC/ISO") |
615
+ str("ISO/IEEE") | str("IEC/IEEE") | str("IEEE/IEC") | str("ISO/IEC") |
616
+ str("IEEE")).as(:joint_publishers) >>
617
+ space >>
618
+ # ISO stage codes: FDIS, FCD, CDV; DIS/CD with an optional round digit
619
+ # (DIS2, CD1..CD4); WD/PWI/NP. (FCD before FDIS is fine — distinct.)
620
+ (str("FDIS") | str("FCD") | str("CDV") |
621
+ (str("DIS") >> digit.maybe) |
622
+ (str("CD") >> digit.maybe) |
623
+ str("WD") | str("PWI") | str("NP")).as(:iso_stage) >>
624
+ # optional " Std" noise word after the stage (e.g. "FDIS Std P15288")
625
+ (space >> str("Std")).maybe >>
626
+ space >>
627
+ str("P").maybe >> # optional project marker on the number
628
+ digits.as(:number) >>
629
+ # part must not swallow the trailing year (year_digits.absent?)
630
+ ((dot | dash) >> year_digits.absent? >> digits.as(:part)).maybe >>
631
+ (
632
+ (str(":") >> year_digits.as(:year)) |
633
+ (dash >> year_digits.as(:year) >>
634
+ (dash >> month_numeric.as(:month)).maybe) |
635
+ # Trailing text date ", April 2015" / " April 2015" (build_joint_development
636
+ # already reads :month/:year). No :year collision — the iso rule has no
637
+ # other :year capture (edition uses :edition_year).
638
+ ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year))
639
+ ).maybe >>
640
+ # Optional /D<draft> tail. normalize_relaton_suffixes repositions the
641
+ # historical "…/D-3-2017" onto the number as "…-2017/D3", so by the
642
+ # time this rule runs the draft usually trails the date (bucket 5);
643
+ # a date-less "/D-4" keeps its hyphen (bucket 7), hence dash.maybe.
644
+ (slash >> str("D") >> dash.maybe >>
645
+ match('[0-9.]').repeat(1).as(:draft_version)).maybe >>
646
+ # Optional edition, from relaton's "/E-<n>" suffix normalized to
647
+ # "Edition <n>.0[ YYYY]" (nil-residue hand-off item 1).
648
+ edition.maybe >>
649
+ revision_suffix.maybe
650
+ end
651
+
652
+ # Embedded (stage-LAST) ISO-led designations: the corpus writes the ISO
653
+ # stage AFTER the (dotted or dashed) part —
654
+ # "ISO/IEC/IEEE 29119.4.FDIS, April 2015" (dot part .4, stage .FDIS)
655
+ # "ISO/IEC/IEEE 24748-5.CD3, February 2015" (dash part -5, stage .CD3)
656
+ # "IEEE P24748.5.CD3, July 2015" (bare IEEE + P)
657
+ # — rather than before the number (joint_development_iso_format). Same
658
+ # meaning as the stage-first form: `29119.4`/`29119-4` = "29119 part 4" and
659
+ # the trailing `.FDIS` is the ISO stage, NOT a second part. Routes through
660
+ # the same build_joint_development (via :joint_publishers/:iso_stage), so the
661
+ # stage is modeled correctly and the numeric part stays separate.
662
+ rule(:joint_development_embedded_stage) do
663
+ # publisher set mirrors joint_development_iso_format (incl. bare IEEE);
664
+ # longest token first.
665
+ (str("ISO/IEC/IEEE") | str("IEEE/ISO/IEC") | str("IEEE/IEC/ISO") |
666
+ str("ISO/IEEE") | str("IEC/IEEE") | str("IEEE/IEC") | str("ISO/IEC") |
667
+ str("IEEE")).as(:joint_publishers) >>
668
+ space >>
669
+ str("P").maybe >> # optional project marker on the number
670
+ digits.as(:number) >>
671
+ # optional numeric part (dot or dash); must not swallow a year
672
+ ((dot | dash) >> year_digits.absent? >> digits.as(:part)).maybe >>
673
+ # the embedded stage, dot-separated, closed vocab (same as iso_stage).
674
+ # The (digit|letter) look-ahead keeps it at a token boundary so "CD"
675
+ # can't match inside a longer token and a glued no-space date
676
+ # (".DISMay2013") is left to fall through.
677
+ dot >>
678
+ (str("FDIS") | str("FCD") | str("CDV") |
679
+ (str("DIS") >> digit.maybe) |
680
+ (str("CD") >> digit.maybe) |
681
+ str("WD") | str("PWI") | str("NP")).as(:iso_stage) >>
682
+ (digit | match("[A-Za-z]")).absent? >>
683
+ # optional trailing date: :YYYY, -YYYY[-MM], or text "Month YYYY"
684
+ (
685
+ (str(":") >> year_digits.as(:year)) |
686
+ (dash >> year_digits.as(:year) >>
687
+ (dash >> month_numeric.as(:month)).maybe) |
688
+ ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year))
689
+ ).maybe
690
+ end
691
+
692
+ # Number-first pattern: "1873-2015 IEEE Standard..."
693
+ rule(:number_first_identifier) do
694
+ number >>
695
+ (dash >> year_digits.as(:year)).maybe >>
696
+ space >>
697
+ (publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >>
698
+ space >>
699
+ (type_word.as(:type) >> space?).maybe >>
700
+ match("[^\n]").repeat(0).as(:title)
701
+ end
702
+
703
+ # IEEE P pattern (without Std): "IEEE P1003.1..." OR just "P1003.1..." (prefix optional)
704
+ rule(:ieee_p_identifier) do
705
+ (str("IEEE").as(:publisher) >> space).maybe >> # Make IEEE prefix optional
706
+ str("P") >> space.maybe >> # Make space after P optional
707
+ number >>
708
+ (part_subpart_year | edition).maybe >>
709
+ # Pattern for /08 style drafts (digits without D prefix) - MUST come before corrigendum
710
+ (slash >> digits.as(:draft_version)).as(:digit_draft).maybe >>
711
+ # FDIS and other ISO stage codes without D prefix (Pattern 3)
712
+ fdraft.maybe >>
713
+ # Trailing "Month YYYY" print/reaffirm date. Captured under distinct
714
+ # keys so it never collides with the base -YYYY identity year (a
715
+ # collision made Parslet drop the base year and warn "Duplicate
716
+ # subtrees … keys: [:year]"). The builder promotes it to the identity
717
+ # only when there is no base year.
718
+ trailing_month_year.maybe >>
719
+ corrigendum.maybe >>
720
+ draft.maybe >>
721
+ # Revision trails the draft (before any date), matching normalization's
722
+ # ".../D<n>/R-<x>" repositioning of "P802.16Rev2/D3 Feb 2008".
723
+ revision_suffix.maybe >>
724
+ # ALSO accept month/year after draft (some patterns like /DX, Month YEAR)
725
+ trailing_month_year.maybe >>
726
+ # Trailing corrigendum AFTER the draft+date ("…/D1, Jan 2007/Cor. 1").
727
+ # The draft's own draft_date has already consumed the date, leaving
728
+ # "/Cor. 1" here; the resulting flat corrigendum+draft tree routes to
729
+ # build_flat_corrigendum (disjoint from the pre-draft corrigendum slot,
730
+ # which only fires when no draft precedes it).
731
+ corrigendum.maybe >>
732
+ parenthetical.maybe
733
+ end
734
+
735
+ # ANSI P pattern: "ANSI PN42.34-D9a, 2015" OR "ANSI P1234/D5"
736
+ rule(:ansi_p_identifier) do
737
+ str("ANSI").as(:publisher) >> space >>
738
+ str("P") >> space.maybe >> # Make space after P optional
739
+ number >>
740
+ (part_subpart_year | edition).maybe >>
741
+ # Trailing "Month YYYY"/bare-year date under distinct keys so they
742
+ # never collide with the base -YYYY identity year (see ieee_p_identifier).
743
+ trailing_month_year.maybe >>
744
+ corrigendum.maybe >>
745
+ draft.maybe >>
746
+ revision_suffix.maybe >>
747
+ # ALSO accept month/year after draft
748
+ trailing_month_year.maybe >>
749
+ # Accept bare year after draft: ", 2015"
750
+ ((comma | space) >> year_digits.as(:trailing_year)).maybe >>
751
+ # Trailing corrigendum after the draft+date (parity with ieee_p_identifier).
752
+ corrigendum.maybe >>
753
+ parenthetical.maybe
754
+ end
755
+
756
+ # IEEE Draft P pattern: "IEEE Draft P802.11..." OR "Draft P802.11..." (IEEE prefix optional)
757
+ # An optional status phrase may precede "Draft" — "IEEE Unapproved Draft P…",
758
+ # "IEEE Active Approved Draft P…" — the largest index-v2 draft bucket. The
759
+ # status is captured (draft_status ends in a space, so "Approved Draft"
760
+ # splits cleanly) and round-trips as the draft_status attribute; the literal
761
+ # "Draft" stays a bare marker (dropped on render, like the plain form).
762
+ rule(:ieee_draft_p_identifier) do
763
+ (str("IEEE").as(:publisher) >> space).maybe >> # Make IEEE prefix optional
764
+ draft_status.as(:draft_status).maybe >>
765
+ str("Draft") >> space >>
766
+ # `P` is optional — a status-word draft may carry a bare number
767
+ # ("IEEE Unapproved Draft 802.1ah/D4.2"), mirroring
768
+ # ieee_approved_draft_identifier's str("P").maybe. The `number` rule
769
+ # already accepts the bare forms (802.1ah, C57.15, 11073-10471).
770
+ str("P").maybe >>
771
+ number >>
772
+ (part_subpart_year | edition).maybe >>
773
+ # Trailing "Month YYYY" date under distinct keys so it never collides
774
+ # with the base -YYYY identity year (see ieee_p_identifier).
775
+ ((space >> month_name.as(:trailing_month) >> space >> year_digits.as(:trailing_year)) |
776
+ (space >> month_numeric.as(:trailing_month) >> (space | dash) >> year_digits.as(:trailing_year))).maybe >>
777
+ draft.maybe >>
778
+ revision_suffix.maybe >>
779
+ # Trailing corrigendum after the draft ("…/D2.0/Cor. 1", or
780
+ # "…/D1.0, Dec 2007/Cor. 1" where the draft's own draft_date consumes
781
+ # the date, leaving "/Cor. N"). The flat corrigendum+draft tree (no
782
+ # :base) routes to build_flat_corrigendum, which rebuilds the base
783
+ # standard (carrying the draft + draft_status) and wraps it.
784
+ corrigendum.maybe >>
785
+ parenthetical.maybe
786
+ end
787
+
788
+ # IEEE Approved Draft pattern: "IEEE Approved Draft Std P..."
789
+ rule(:ieee_approved_draft_identifier) do
790
+ str("IEEE").as(:publisher) >>
791
+ space >>
792
+ str("Approved") >> space >>
793
+ (str("Draft Std") | str("Std")).as(:type) >> space >>
794
+ str("P").maybe >>
795
+ number >>
796
+ (part_subpart_year | edition).maybe >>
797
+ draft.maybe >>
798
+ revision_suffix.maybe >>
799
+ parenthetical.maybe
800
+ end
801
+
802
+ # Combined AIEE identifier pattern: "AIEE No 72-1932 and AIEE No 73-1932"
803
+ # Handles "and"-separated AIEE identifiers (from "Nos X and Y" preprocessing)
804
+ rule(:combined_aiee_identifier) do
805
+ # First AIEE identifier
806
+ Aiee::Parser.new.aiee_identifier.as(:first_aiee) >>
807
+ # "and" separator
808
+ space >> str("and") >> space >>
809
+ # Second AIEE identifier
810
+ Aiee::Parser.new.aiee_identifier.as(:second_aiee)
811
+ end
812
+
813
+ # AIEE (American Institute of Electrical Engineers) patterns
814
+ # Detect AIEE patterns and delegate to AIEE parser
815
+ rule(:aiee_identifier) do
816
+ # Lookahead for AIEE patterns - do not consume input
817
+ (
818
+ # IEEE-AIEE transitional pattern
819
+ (str("IEEE-AIEE") >> space >> (str("No.") | str("Nos") | str("No") | str("Standard") | str("Trans."))) |
820
+ # A.I.E.E. pattern (with dots, no spaces)
821
+ (str("A.I.E.E.") >> space >> (str("No.") | str("Nos") | str("No"))) |
822
+ # A. I. E. E. pattern (with dots and spaces)
823
+ (str("A. I. E. E.") >> space >> (str("No.") | str("Nos") | str("No") | str("Standard"))) |
824
+ # AIEE pattern - extended to include more type words
825
+ (str("AIEE") >> space >> (str("No.") | str("Nos") | str("No") | str("Standard") | str("Trans.") | str("Std")))
826
+ ).present? >>
827
+ # Delegate to AIEE parser if pattern detected
828
+ Aiee::Parser.new.aiee_identifier.as(:aiee)
829
+ end
830
+
831
+ # IRE (Institute of Radio Engineers) patterns
832
+ # Detect IRE patterns and delegate to IRE parser
833
+ rule(:ire_identifier) do
834
+ # Lookahead for IRE patterns - do not consume input
835
+ (
836
+ # Year-first pattern: "52 IRE 7.S2" or "60 IRE 28 PS7"
837
+ ((match("[1-6]") >> digit >> space >> str("IRE")) | # 2-digit year format
838
+ (str("19") >> digit.repeat(2, 2) >> space >> str("IRE"))) |
839
+ # IEEE-IRE transitional pattern
840
+ (str("IEEE-IRE") >> space)
841
+ ).present? >>
842
+ # Delegate to IRE parser if pattern detected
843
+ Ire::Parser.new.ire_identifier.as(:ire)
844
+ end
845
+
846
+ # NESC (National Electrical Safety Code) patterns
847
+ # Detect NESC patterns and delegate to NESC parser
848
+ rule(:nesc_identifier) do
849
+ # Lookahead for NESC patterns - do not consume input
850
+ (
851
+ # C2-YYYY pattern
852
+ (str("C2-") >> year_digits) |
853
+ # YYYY NESC pattern
854
+ (year_digits >> space >> (str("NESC") | str("National Electrical Safety Code"))) |
855
+ # Draft NESC pattern
856
+ (str("Draft") >> space >> (str("NESC") | str("National Electrical Safety Code"))) |
857
+ # Name-first pattern (NEW)
858
+ (str("National Electrical Safety Code") >> str(",") >> space >> str("C2-"))
859
+ ).present? >>
860
+ # Delegate to NESC parser if pattern detected
861
+ Nesc::Parser.new.nesc_identifier.as(:nesc)
862
+ end
863
+
864
+ # IEEE/ASTM SI/PSI (Système International) patterns
865
+ # SI = Published metric system standard
866
+ # PSI = Proposed SI (draft)
867
+ rule(:ieee_astm_si_psi) do
868
+ str("IEEE/ASTM").as(:publishers) >>
869
+ space >>
870
+ (str("PSI") | str("SI")).as(:si_type) >>
871
+ space >>
872
+ digits.as(:number) >>
873
+ # Draft notation for PSI (e.g., /D2, /D3)
874
+ (slash >> str("D") >> digits.as(:draft_version)).maybe >>
875
+ # Year with optional month
876
+ (
877
+ # Format: ", Month Year"
878
+ (comma >> month_name.as(:month) >> space >> year_digits.as(:year)) |
879
+ # Format: "-YEAR"
880
+ (dash >> year_digits.as(:year))
881
+ ).maybe >>
882
+ # Optional parenthetical (revision relationships)
883
+ parenthetical.maybe
884
+ end
885
+
886
+ # No-prefix IEEE identifier (characteristic patterns without "IEEE Std")
887
+ # These are patterns that are distinctly IEEE even without explicit publisher
888
+ rule(:no_prefix_ieee) do
889
+ characteristic_ieee_number.as(:number) >>
890
+ # Optional suffix (like -a, -b)
891
+ (dash >> match("[A-Za-z]")).maybe.as(:suffix) >>
892
+ # Optional year
893
+ (dash >> year_digits).maybe.as(:year) >>
894
+ # Optional draft notation
895
+ draft.maybe >>
896
+ # Optional language portion
897
+ (str("(E)") | str("(F)")).maybe >>
898
+ # Optional parenthetical content
899
+ parenthetical.maybe
900
+ end
901
+
902
+ # Corrigendum identifier with recursive base parsing
903
+ # Captures base identifier for recursive parsing, then corrigendum supplement
904
+ # Example: IEEE Std 535-2013/Cor. 1-2017
905
+ rule(:corrigendum_identifier) do
906
+ # Match a complete base identifier (reuse existing patterns)
907
+ # Try standard patterns that would match "IEEE Std 535-2013"
908
+ (
909
+ ((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >>
910
+ (type_word.as(:type) >> space?).maybe >>
911
+ number >>
912
+ part_subpart_year.maybe # This captures the full identifier before /Cor
913
+ ).as(:base) >>
914
+ # Now match the corrigendum portion
915
+ (slash | dash | space) >>
916
+ str("Cor") >>
917
+ (dash | dot | space).maybe >> # More flexible separator after "Cor"
918
+ space? >>
919
+ digits.as(:cor_number) >>
920
+ ((dash | str(":") | space) >> year_digits.as(:cor_year)).maybe >> # Optional cor year suffix
921
+ parenthetical.maybe
922
+ end
923
+
924
+ # Interpretation identifier with recursive base parsing
925
+ # Captures base identifier for recursive parsing, then interpretation supplement
926
+ # Example: IEEE Std 1076/INT-1991, IEEE Std 1003.1-1988/INT
927
+ rule(:interpretation_identifier) do
928
+ # Match a complete base identifier
929
+ (
930
+ ((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >>
931
+ (type_word.as(:type) >> space?).maybe >>
932
+ number >>
933
+ part_subpart_year.maybe
934
+ ).as(:base) >>
935
+ # Now match the interpretation portion
936
+ (slash | dash | space) >>
937
+ str("INT") >>
938
+ ((dash | str(":") | space) >> year_digits.as(:int_year)).maybe >> # Optional year suffix
939
+ parenthetical.maybe
940
+ end
941
+
942
+ # Conformance identifier with recursive base parsing
943
+ # Captures base identifier for recursive parsing, then conformance supplement
944
+ # Example: IEEE Std 802.16/Conformance01-2003
945
+ rule(:conformance_identifier) do
946
+ # Match a complete base identifier
947
+ (
948
+ ((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >>
949
+ (type_word.as(:type) >> space?).maybe >>
950
+ number >>
951
+ part_subpart_year.maybe
952
+ ).as(:base) >>
953
+ # Now match the conformance portion
954
+ (slash | dash | space) >>
955
+ str("Conformance") >>
956
+ match("[0-9]").repeat(1).as(:conf_number) >>
957
+ dash >>
958
+ year_digits.as(:conf_year) >>
959
+ parenthetical.maybe
960
+ end
961
+
962
+ # Multi-numbered identifier: same document with multiple numbers
963
+ # Examples: IEEE Std 1299/C62.22.1-1996, IEEE Std 960-1989, Std 1177-1989
964
+ rule(:multi_numbered_identifier) do
965
+ # Primary identifier (full IEEE identifier)
966
+ ((
967
+ (publisher >> space).maybe >>
968
+ (type_word.as(:type) >> space?).maybe >>
969
+ number >>
970
+ (part_subpart_year | edition).maybe
971
+ ).as(:primary_identifier) >>
972
+ # Separator: slash for cross-ref format, comma for joint standard
973
+ (slash >> str("C") >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits).as(:secondary_crossref)) |
974
+ (comma >> space >> (type_word.as(:type) >> space?).maybe >> number >> dash >> year_digits).as(:secondary_joint)
975
+ end
976
+
977
+ # CSA dual published pattern: IEEE Std 844.1-2017/CSA C22.2 No. 293.1-17
978
+ rule(:csa_dual_published) do
979
+ # IEEE portion (full identifier)
980
+ (
981
+ publisher >> space >>
982
+ (type_word.as(:type) >> space?).maybe >>
983
+ number >>
984
+ (part_subpart_year | edition).maybe
985
+ ).as(:ieee_portion) >>
986
+ # CSA portion with slash separator
987
+ slash >>
988
+ str("CSA") >> space >>
989
+ # CSA number formats (various patterns observed)
990
+ (
991
+ # Format 1: C22.2 No. 293.1-17 (with NO.)
992
+ (str("C") >> digit.repeat(2) >> dot >> digit >> space >> str("No") >> dot >> space >>
993
+ match("[0-9.]").repeat(1) >> (dash | str(":")) >> digit.repeat(2)) |
994
+ # Format 2: C293.2-17 (without NO., dash year)
995
+ (str("C") >> match("[0-9.]").repeat(1) >> dash >> digit.repeat(2)) |
996
+ # Format 3: C22.2 No. 293.3:19 (with NO., colon year)
997
+ (str("C") >> digit.repeat(2) >> dot >> digit >> space >> str("No") >> dot >> space >>
998
+ match("[0-9.]").repeat(1) >> str(":") >> digit.repeat(2)) |
999
+ # Format 4: C293.4:19 (without NO., colon year)
1000
+ (str("C") >> match("[0-9.]").repeat(1) >> str(":") >> digit.repeat(2))
1001
+ ).as(:csa_portion)
1002
+ end
1003
+
1004
+ # Basic IEEE identifier (no dual PubIDs or complex revisions yet)
1005
+ rule(:identifier) do
1006
+ combined_aiee_identifier |
1007
+ aiee_identifier |
1008
+ combined_aiee_identifier |
1009
+ ire_identifier |
1010
+ nesc_identifier |
1011
+ ieee_astm_si_psi | # NEW Session 171: Add IEEE/ASTM SI/PSI support
1012
+ multi_numbered_identifier | # NEW: Try multi-numbered identifiers before generic patterns
1013
+ csa_dual_published | # NEW: Try CSA dual published before generic patterns
1014
+ s_designation | # Historical IEEE/IPCEA cable designation (S-135)
1015
+ corrigendum_identifier | # NEW: Try corrigendum before generic patterns
1016
+ interpretation_identifier | # NEW: Try interpretation identifier before generic patterns
1017
+ conformance_identifier | # NEW: Try conformance identifier before generic patterns
1018
+ joint_development_ieee_format |
1019
+ joint_development_iso_format |
1020
+ joint_development_embedded_stage | # stage-LAST embedded form (before generic)
1021
+ iec_ieee_copublished |
1022
+ number_first_identifier |
1023
+ ieee_approved_draft_identifier |
1024
+ ieee_draft_p_identifier |
1025
+ ieee_p_identifier |
1026
+ ansi_p_identifier | # NEW: ANSI P prefix support
1027
+ (((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >> # Make publisher optional
1028
+ draft_status.as(:draft_status).maybe >>
1029
+ (str("Draft Std").as(:type) >> space?).maybe >>
1030
+ (type_word.as(:type) >> (space >> str("No") >> space).maybe >> space?).maybe >>
1031
+ number >>
1032
+ (part_subpart_year | edition).maybe >>
1033
+ corrigendum.maybe >>
1034
+ amendment.maybe >>
1035
+ interpretation.maybe >> # NEW: Add /INT support
1036
+ conformance.maybe >> # NEW: Add /Conformance support
1037
+ ashrae_copub.maybe >> # NEW: Add /ASHRAE Guideline support
1038
+ ieee_crossref.maybe >> # NEW: Add /C62.22.1-1996 cross-reference support
1039
+ draft.maybe >>
1040
+ revision_suffix.maybe >>
1041
+ # Trailing "Month YYYY" print/reaffirm date under distinct keys so it
1042
+ # never collides with the base -YYYY identity year (see
1043
+ # ieee_p_identifier). The builder promotes it only when no base year.
1044
+ trailing_month_year.maybe >>
1045
+ # Trailing corrigendum after the draft+date ("IEEE Approved P1015/D1,
1046
+ # Jan 2007/Cor. 1"): the generic bucket is the only path a status-word
1047
+ # form reaches. Routes via build_flat_corrigendum (disjoint from the
1048
+ # pre-draft corrigendum slot at line 954).
1049
+ corrigendum.maybe >>
1050
+ edition.maybe >>
1051
+ parenthetical.maybe >> # REVERT: Back to single parenthetical
1052
+ book_nickname.maybe >> # NEW: Add book nickname support
1053
+ redline.maybe >>
1054
+ title_portion.maybe >>
1055
+ approved_draft_suffix.maybe) |
1056
+ no_prefix_ieee # NEW: Try no-prefix patterns last (lowest priority)
1057
+ end
1058
+
1059
+ root(:identifier)
1060
+
1061
+ # Rewrite relaton's historical IEEE serialization into canonical pubid
1062
+ # spellings. relaton's own formatter (Relaton::Ieee::PubId::Id#to_s) emits
1063
+ # suffix tokens that differ from pubid's grammar:
1064
+ #
1065
+ # /D-N-YYYY[-MM] draft + trailing numeric date (the dominant form)
1066
+ # /E-N[-YYYY[-MM]] edition
1067
+ # /R-N[-YYYY] revision (pubid has no revision suffix)
1068
+ # " Redline" redline suffix without the " - " pubid expects
1069
+ #
1070
+ # The draft/edition trailing date is repositioned onto the document number
1071
+ # as a base year/month (a form pubid already parses), which also keeps the
1072
+ # draft component clean so it round-trips through to_hash/from_hash.
1073
+ def self.normalize_relaton_suffixes(cleaned)
1074
+ # NOTE: the trailing " Redline"/" - Redline" suffix is NO LONGER stripped
1075
+ # here — the grammar's `redline` rule captures it into a redline flag so
1076
+ # a redline id stays distinct from its base standard.
1077
+
1078
+ # Combined draft + corrigendum: relaton emits "…/D-N/CorM-YYYY" (draft
1079
+ # then corrigendum), but pubid's grammar accepts the corrigendum first.
1080
+ # Swap them so the corrigendum keeps its own year and the draft trails.
1081
+ # The hyphen after "D" is mandatory here: relaton's formatter always
1082
+ # emits "/D-<draft>", whereas pubid's own canonical joint-development
1083
+ # form is "/D<draft>-<year>" (no hyphen, year kept on the draft) — which
1084
+ # already parses and must not be repositioned. A trailing corrigendum
1085
+ # month (the "-MM" in "/CorM-YYYY-MM") is intentionally dropped: pubid's
1086
+ # corrigendum model carries only a year.
1087
+ cleaned = cleaned.sub(
1088
+ %r{\A(.*)/D-([0-9A-Za-z][0-9A-Za-z.+]*?)/Cor\.?[ ]?(\d+)(?:-((?:19|20)\d\d))?(?:-\d\d)?\z},
1089
+ ) do
1090
+ base, draft, cor, year = Regexp.last_match.captures
1091
+ "#{base}/Cor #{cor}#{year ? "-#{year}" : ''}/D#{draft}"
1092
+ end
1093
+
1094
+ # Combined draft + revision, and the empty-draft revision-only form:
1095
+ # "…/D-<d>/R-<x>-YYYY[-MM]" and "…/D-/R-<x>-YYYY" (nil-residue #2).
1096
+ # Reposition the base publication date onto the number (pubid's
1097
+ # "-YYYY[-MM]" shape), keep the draft as "/D<d>" (dropped when the draft
1098
+ # is empty), and leave a trailing "/R-<x>" the grammar captures as the
1099
+ # revision. Runs before the plain "/D-…" reposition, which the embedded
1100
+ # "/R-" would otherwise defeat.
1101
+ cleaned = cleaned.sub(
1102
+ %r{\A(.*?)/D-([0-9A-Za-z.+]*)/R-([0-9A-Za-z]+)(?:-((?:19|20)\d\d)(?:-(0[1-9]|1[0-2]))?)?\z},
1103
+ ) do
1104
+ base, draft, rev, year, month = Regexp.last_match.captures
1105
+ date = year ? "-#{year}#{month ? "-#{month}" : ''}" : ""
1106
+ draft_part = draft.to_s.empty? ? "" : "/D#{draft}"
1107
+ "#{base}#{date}#{draft_part}/R-#{rev}"
1108
+ end
1109
+
1110
+ # /D-N drafts with a trailing numeric date, when the draft is the last
1111
+ # suffix: reposition the -YYYY[-MM] date onto the number. A following
1112
+ # /Cor, /Amd, /R or /E suffix carries its own year, so the `\z` anchor
1113
+ # keeps this from firing on those combined forms.
1114
+ cleaned = cleaned.sub(
1115
+ %r{\A(.*)/D-([0-9A-Za-z][0-9A-Za-z.+]*?)-((?:19|20)\d\d)(?:-(0[1-9]|1[0-2]))?\z},
1116
+ ) do
1117
+ base, draft, year, month = Regexp.last_match.captures
1118
+ "#{base}-#{year}#{month ? "-#{month}" : ''}/D#{draft}"
1119
+ end
1120
+
1121
+ # /E-N editions: relaton's "/E-2-2023-02" → pubid's "Edition 2.0 2023-02".
1122
+ cleaned = cleaned.sub(
1123
+ %r{\A(.*?)/E-(\d+)(?:-((?:19|20)\d\d)(?:-(0[1-9]|1[0-2]))?)?\z},
1124
+ ) do
1125
+ base, edition, year, month = Regexp.last_match.captures
1126
+ date = year ? " #{year}#{month ? "-#{month}" : ''}" : ""
1127
+ "#{base} Edition #{edition}.0#{date}"
1128
+ end
1129
+
1130
+ # /R-N revisions: PRESERVE them (the grammar's revision_suffix rule now
1131
+ # captures a trailing "/R-<x>" into the `revision` attribute). Just
1132
+ # reposition any trailing publication year onto the number, keeping the
1133
+ # "/R-<x>" in place for the grammar.
1134
+ cleaned.sub(
1135
+ %r{\A(.*?)/R-([0-9A-Za-z]+)(?:-((?:19|20)\d\d))?\z},
1136
+ ) do
1137
+ base, rev, year = Regexp.last_match.captures
1138
+ "#{year ? "#{base}-#{year}" : base}/R-#{rev}"
1139
+ end
1140
+ end
1141
+
1142
+ # Strip the IEEE rawbib revision-notation dialects. `REV`/`Rev`
1143
+ # (case-insensitive) + a trailing revision id `[A-Za-z0-9]+`, glued to the
1144
+ # number or separated by `-`, `/`, `_`, `.`, or a space, and preceding the
1145
+ # draft. pubid's canonical "<num>/D<n>/R-<x>" form already drops the
1146
+ # revision on render (normalize_relaton_suffixes strips a trailing /R-x),
1147
+ # so the revision-less result is *the same identifier* — and stripping
1148
+ # (rather than reordering) leaves any trailing date/parenthetical intact,
1149
+ # which is why forms that already parse (`Draft P…-REVmb/D3.0, Mar 2010`)
1150
+ # are NOT disturbed. Examples:
1151
+ # "P802.16.2-REVa/D8" -> "P802.16.2/D8"
1152
+ # "P802.16/REVd/D5" -> "P802.16/D5"
1153
+ # "P802.15.1REVa/D5" -> "P802.15.1/D5"
1154
+ # "P802.11REVmb" -> "P802.11" (no draft)
1155
+ def self.normalize_revision_notation(cleaned)
1156
+ # NUMBERED revisions ("Rev<digits>") are PRESERVED — repositioned to a
1157
+ # trailing "/R-<n>" suffix the grammar captures as the `revision`
1158
+ # attribute (IEEE's native inline spelling; numbered-revision hand-off).
1159
+ # A "\d+" right after "Rev" both selects the numbered subset and keeps
1160
+ # these off the English word "Revision". Three source positions:
1161
+ # after a draft : "PC37.30.2/D043 Rev 18" -> ".../D043/R-18"
1162
+ cleaned = cleaned.sub(
1163
+ %r{(/D[0-9A-Za-z.]*)\s+[Rr][Ee][Vv]\s*(\d+)}, '\1/R-\2'
1164
+ )
1165
+ # before a draft: "P802.16Rev2/D3" -> "P802.16/D3/R-2"
1166
+ cleaned = cleaned.sub(
1167
+ %r{[-/_.]?\s?[Rr][Ee][Vv][-\s]?(\d+)(/D[0-9A-Za-z.]*)}, '\2/R-\1'
1168
+ )
1169
+ # no draft, trailing: "P1722-rev1" -> "P1722/R-1"
1170
+ cleaned = cleaned.sub(
1171
+ %r{(\d)[-._]?\s?[Rr][Ee][Vv]\s*(\d+)\s*\z}, '\1/R-\2'
1172
+ )
1173
+
1174
+ # LETTERED inline revisions ("REVa", "REVmb") have no pubid model and are
1175
+ # still STRIPPED (unchanged behaviour). The numbered forms above already
1176
+ # became "/R-<n>", so these regexes only see the lettered residue.
1177
+ # Revision token that PRECEDES a draft: drop it (keep the /D…).
1178
+ cleaned = cleaned.sub(
1179
+ %r{[-/_.]?\s?[Rr][Ee][Vv][-\s]?[A-Za-z0-9]+(?=/D[0-9])},
1180
+ "",
1181
+ )
1182
+ # Trailing revision glued to the number with no draft ("P802.11REVmb");
1183
+ # a digit must immediately precede REV so a trailing English word like
1184
+ # "…Revision" can't match.
1185
+ cleaned.sub(%r{(\d)[Rr][Ee][Vv][A-Za-z0-9]+\s*\z}, '\1')
1186
+ end
1187
+
1188
+ def self.parse(string)
1189
+ # Strip .pdf extension if present (Pattern 3: File Extensions)
1190
+ cleaned = string.sub(/\.pdf$/i, "")
1191
+
1192
+ # Note: IEC and ANSI identifiers are NOT filtered here because they can have
1193
+ # IEEE co-publication or adoption. The Base.parse method handles determining
1194
+ # which standards are actually IEEE-related.
1195
+ # ISO-only standards are still filtered as they have separate handling.
1196
+
1197
+ # Pattern 3: Replace underscore before ISO stage codes with slash
1198
+ # These are joint development drafts that use underscore instead of slash
1199
+ cleaned = cleaned.gsub(/_(FDIS|CDV|CD|DIS|WD|PWI|NP)/, '/\1')
1200
+
1201
+ # NEW: Normalize multiple spaces to single space
1202
+ # No valid IEEE identifier pattern needs more than 1 space
1203
+ cleaned = cleaned.gsub(/\s+/, " ")
1204
+
1205
+ # A joint ISO-led publisher list is sometimes crawled with a stray slash
1206
+ # (or slash+space) before the ISO stage code — "ISO/IEC/IEEE/ FDIS …" or
1207
+ # "ISO/IEC/IEEE/FDIS …". Restore the space separator so the stage parses
1208
+ # (bucket 7).
1209
+ cleaned = cleaned.gsub(
1210
+ %r{\b(ISO/IEC/IEEE|IEEE/ISO/IEC|IEEE/IEC/ISO|ISO/IEEE|IEC/IEEE|IEEE/IEC|ISO/IEC)/ ?(FDIS|FCD|CDV|DIS\d?|CD\d?|WD|PWI|NP)\b},
1211
+ '\1 \2',
1212
+ )
1213
+
1214
+ # Rewrite the rawbib revision-notation dialects (REVa/REVd/glued) into
1215
+ # the canonical /R-<x> form before the suffix normalization below.
1216
+ cleaned = normalize_revision_notation(cleaned)
1217
+
1218
+ # Normalize relaton's bespoke historical serialization (the spellings
1219
+ # emitted by Relaton::Ieee::PubId::Id#to_s) into canonical pubid forms
1220
+ # so `relaton-data-ieee` parses. See #normalize_relaton_suffixes.
1221
+ cleaned = normalize_relaton_suffixes(cleaned)
1222
+
1223
+ # NEW Session 171: CONSERVATIVE data quality fixes for TODO.IEEE-MUST-DO.txt
1224
+ # Only fix clear typos: space before dash + 4-digit year, OR dash + space + 4-digit year
1225
+ # Do NOT touch " - " (space-dash-space) which is valid formatting
1226
+ cleaned = cleaned.gsub(/(\d)\s+-(\d{4})\b/, '\1-\2') # "C37.101 -2006" → "C37.101-2006"
1227
+ cleaned = cleaned.gsub(/(\d)-\s+(\d{4})\b/, '\1-\2') # "C62.35- 2010" → "C62.35-2010"
1228
+
1229
+ # NEW Session 171: HTML entity for en dash (&#x2013;)
1230
+ # ONLY convert if not already followed by a dash (avoid creating --)
1231
+ cleaned = cleaned.gsub(/&#x2013;(?!-)/, "-") # En dash → regular hyphen (if not followed by dash)
1232
+ cleaned = cleaned.gsub("&#x2013;-", "-") # En-dash-dash → single dash
1233
+
1234
+ # NEW Session 171: Remove wrong ! prefix
1235
+ cleaned = cleaned.gsub(/^!IEEE /, "IEEE ")
1236
+
1237
+ # NEW Session 171: Fix "IEEE/ ASTM" spacing (extra space after slash)
1238
+ cleaned = cleaned.gsub("IEEE/ ASTM", "IEEE/ASTM")
1239
+
1240
+ # NEW Phase 1: Handle HTML entities comprehensively
1241
+ cleaned = cleaned.gsub("&#x2122;", "™") # Trademark symbol
1242
+ cleaned = cleaned.gsub("&#x2019;", "'") # Smart apostrophe
1243
+ cleaned = cleaned.gsub("&amp;amp;", "&") # Double-encoded ampersand
1244
+ cleaned = cleaned.gsub("&amp;", "&") # Single-encoded ampersand
1245
+
1246
+ # NEW: Wrap P&V notation in parentheses (Paper & Video, etc.)
1247
+ # Pattern: "IEEE Std 500-1984 P&V" → "IEEE Std 500-1984 (P&V)"
1248
+ cleaned = cleaned.gsub(/\s+(P&V)\s*$/, ' (\1)')
1249
+
1250
+ # NEW Phase 1: Fix number spacing issues (e.g., "C57.1 2.25" → "C57.12.25")
1251
+ # This handles cases where a space appears in the middle of a number
1252
+ cleaned = cleaned.gsub(/(\d+\.\d+)\s+(\d+\.)/, '\1\2')
1253
+
1254
+ # NEW Phase 1: Fix year spacing issues (e.g., "1 996" → "1996")
1255
+ # Remove spaces within 4-digit years
1256
+ cleaned = cleaned.gsub(/\b(1|2)\s+(\d{3})\b/, '\1\2')
1257
+
1258
+ # NEW: Fix month+year spacing (e.g., "March2016" → "March 2016")
1259
+ # Add space between month name and 4-digit year when they're concatenated
1260
+ cleaned = cleaned.gsub(
1261
+ /\b(January|February|March|April|May|June|July|August|September|October|November|December)(\d{4})\b/, '\1 \2'
1262
+ )
1263
+ # Also handle abbreviated months
1264
+ cleaned = cleaned.gsub(
1265
+ /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)(\d{4})\b/, '\1 \2'
1266
+ )
1267
+
1268
+ # NEW: Convert IEC/IEEE space-separated to semicolon format
1269
+ # Pattern: "IEC 61523-3 First edition 2004-09; IEEE 1497" → already semicolon
1270
+ # Pattern: "IEC 62539 First Edition 2007-07 IEEE 930" → needs semicolon
1271
+ # Pattern: "IEC 60076-21:2011 Edition 1.0 2011-12 IEEE Std C57.15" → needs semicolon (issue #202)
1272
+ # Match: IEC identifier (with optional colon-year, optional "First"/numeric
1273
+ # Edition + YYYY-MM) + space + IEEE identifier.
1274
+ cleaned = cleaned.gsub(
1275
+ /(IEC\s+\d+(?:-\d+)?(?::\d{4})?(?:\s+(?:First\s+)?[Ee]dition\s+\d+(?:\.\d+)?\s+\d{4}-\d{2})?)\s+(IEEE\s+Std\s+\S+|IEEE\s+\S+)/,
1276
+ '\1; \2'
1277
+ )
1278
+
1279
+ # Strip ":YYYY" from IEC numbers when an Edition clause follows — the
1280
+ # IEEE parser's number rule doesn't accept the colon-year form, but
1281
+ # the year is preserved in the "Edition N.M YYYY-MM" suffix.
1282
+ # (issue #202)
1283
+ cleaned = cleaned.gsub(
1284
+ /^(IEC\s+\d+(?:-\d+)?):\d{4}(\s+(?:First\s+)?[Ee]dition\s+\d+(?:\.\d+)?\s+\d{4}-\d{2})/,
1285
+ '\1\2'
1286
+ )
1287
+
1288
+ # NEW Phase 1 (Session 141): Remove literal trademark symbol
1289
+ # "C57.110™-2018" → "C57.110-2018"
1290
+ cleaned = cleaned.gsub(/™/, "")
1291
+
1292
+ # NEW Phase 1 (Session 141): Fix specific year typo
1293
+ # "19969" → "1969" (very specific pattern, won't affect other text)
1294
+ cleaned = cleaned.gsub(/\b19969\b/, "1969")
1295
+
1296
+ # NEW Session 169: Fix comma typo in 802.3 series numbers
1297
+ # "802.3ch-2020,802.3ca-2020" → "802.3ch-2020, 802.3ca-2020"
1298
+ # Very specific: 4 digits, comma, 3 digits (likely 802.3xx typo)
1299
+ cleaned = cleaned.gsub(/(\d{4}),(\d{3})/, '\1, \2')
1300
+
1301
+ # NEW Session 169: Fix /lNT typo (lowercase L as 1)
1302
+ # "1003.1/2003.l/lNT" → "1003.1/2003.1/INT"
1303
+ cleaned = cleaned.gsub(/\/lNT\b/, "/INT")
1304
+ cleaned = cleaned.gsub(".l/", ".1/") # Also fix .l/ -> .1/
1305
+
1306
+ # NEW Session 169: Fix I99O typo (letter I and O instead of digits)
1307
+ # "IEEE 1076-CONC-I99O" → "IEEE 1076-CONC-1990"
1308
+ cleaned = cleaned.gsub(/\bI99O\b/, "1990")
1309
+
1310
+ # NEW: Fix common typos (Category 9)
1311
+ cleaned = cleaned.gsub(/^EEE /, "IEEE ")
1312
+
1313
+ # NEW Session 170: Additional safe typo fixes
1314
+ # Fix "I EEE" (space between I and EEE)
1315
+ cleaned = cleaned.gsub(/^I EEE /, "IEEE ")
1316
+
1317
+ # Fix "lEEE" (lowercase L instead of I)
1318
+ cleaned = cleaned.gsub(/^lEEE /, "IEEE ")
1319
+
1320
+ # Fix missing closing parenthesis at end only (very conservative)
1321
+ # Only if there's exactly one more opening than closing paren
1322
+ open_count = cleaned.count("(")
1323
+ close_count = cleaned.count(")")
1324
+ if open_count == close_count + 1 && !cleaned.end_with?(")")
1325
+ cleaned = "#{cleaned})"
1326
+ end
1327
+
1328
+ # NEW Phase 1: Remove trailing commas/colons and text
1329
+ cleaned = cleaned.gsub(/,\s*Standard\s*$/, "") # ", Standard" at end
1330
+ cleaned = cleaned.gsub(/[,:]\s*$/, "") # Trailing comma/colon
1331
+ cleaned = cleaned.gsub(/,\s+and\s+IEEE\s+Std\s/, " and ") # Handle "IEEE Std and Std" case
1332
+
1333
+ # Enhanced: Fix unbalanced parentheses comprehensively
1334
+ # Handle three cases: missing closing, extra opening, nested unbalanced
1335
+ open_count = cleaned.count("(")
1336
+ close_count = cleaned.count(")")
1337
+
1338
+ if open_count > close_count
1339
+ # More opening than closing - add closing parens at end
1340
+ # This handles both simple missing and nested unbalanced cases
1341
+ missing = open_count - close_count
1342
+ cleaned = cleaned + (")" * missing)
1343
+ elsif close_count > open_count
1344
+ # More closing than opening - remove extra closing from end
1345
+ # Very conservative: only remove trailing excess closing parens
1346
+ extra = close_count - open_count
1347
+ cleaned = cleaned.sub(/\){#{extra}}$/, "")
1348
+ end
1349
+
1350
+ # === SESSION 173: TODO.IEEE-MUST-DO.txt Preprocessing Enhancements ===
1351
+
1352
+ # Part A: Simple Normalizations (Lines 13, 16, 32-35, 36, 39-41 from TODO)
1353
+
1354
+ # 1. Missing dash before year: "802.16g 2007" → "802.16g-2007"
1355
+ # But be careful not to affect month names (already have space)
1356
+ # Only apply if: digit + space + 4-digit year (and not after a month name)
1357
+ cleaned = cleaned.gsub(/(\d)\s+(\d{4})(?=\s*\(|\s*$)/, '\1-\2')
1358
+
1359
+ # 2. Space-dash-space before year: "802.1ag - 2007" → "802.1ag-2007"
1360
+ # This is distinct from " - " in titles, targets space-dash-space-year pattern
1361
+ cleaned = cleaned.gsub(/\s+-\s+(\d{4})\b/, '-\1')
1362
+
1363
+ # 3. Add missing "Std" after IEEE: "IEEE 1070-1995" → "IEEE Std 1070-1995"
1364
+ # Only at start of string, IEEE + space + digit
1365
+ cleaned = cleaned.gsub(/^IEEE\s+(?!Std\b)(\d)/, 'IEEE Std \1')
1366
+
1367
+ # 3.5. Convert "IEEE No." to "IEEE Std": "IEEE No. 264-1968" → "IEEE Std 264-1968"
1368
+ # NOTE: Do NOT convert AIEE No - AIEE uses "No" as standard format
1369
+ cleaned = cleaned.gsub(/^IEEE\s+No\.\s*/, "IEEE Std ")
1370
+ cleaned = cleaned.gsub(/^IEEE\s+No\s/, "IEEE Std ")
1371
+ # Skip AIEE No conversion - AIEE preserves "No" format
1372
+
1373
+ # 4. Space before slash in dual published: "262-1973 /ANSI" → "262-1973/ANSI"
1374
+ cleaned = cleaned.gsub(/\s+\//, "/")
1375
+
1376
+ # 5. Comma before Edition: ", 1998 Edition" → "-1998"
1377
+ # Normalize to standard year format for parser
1378
+ cleaned = cleaned.gsub(/,\s+(\d{4})\s+Edition/, '-\1')
1379
+
1380
+ # 6. ISO/IEC spacing: "ISO/IEC15802" → "ISO/IEC 15802"
1381
+ # Add space between publisher prefix and number
1382
+ cleaned = cleaned.gsub(/(ISO\/IEC)(\d)/, '\1 \2')
1383
+
1384
+ # Part B: Publisher Order (Line 38 from TODO)
1385
+
1386
+ # Fix wrong publisher order: "IEEE Std ANSI/IEEE" → "ANSI/IEEE Std"
1387
+ # This handles cases where IEEE Std appears before ANSI/IEEE publisher
1388
+ cleaned = cleaned.gsub(/^IEEE\s+Std\s+(ANSI\/IEEE)/, '\1 Std')
1389
+
1390
+ # Part C: Dual Published Formats (Lines 8, 19 from TODO)
1391
+
1392
+ # 1. Semicolon to parenthetical for dual published (MultiLabeledIdentifier)
1393
+ # "IEEE Std 120-1955; ASME PTC 19.6-1955" → "IEEE Std 120-1955 (ASME PTC 19.6-1955)"
1394
+ # Only if semicolon + space + organization abbreviation (capital letters)
1395
+ if cleaned.match?(/;\s+[A-Z]{2,}/)
1396
+ cleaned = cleaned.sub(/;\s+([A-Z][^;]+)$/, ' (\1)')
1397
+ end
1398
+
1399
+ # === SESSION 174: Additional TODO.IEEE-MUST-DO.txt Preprocessing ===
1400
+
1401
+ # Part A: Edition Abbreviation Normalization (Lines 10-11)
1402
+ # Pattern: ", 1999 Edn. (Reaff 2003)" → "-1999 (R2003)"
1403
+ # Normalize both the Edition abbreviation and the Reaffirmed format
1404
+ cleaned = cleaned.gsub(/,\s+(\d{4})\s+Edn\.\s+\(Reaff\s+(\d{4})\)/,
1405
+ '-\1 (R\2)')
1406
+ # Also handle without initial comma (might occur in relationships)
1407
+ cleaned = cleaned.gsub(/(\d{4})\s+Edn\.\s+\(Reaff\s+(\d{4})\)/,
1408
+ '\1 (R\2)')
1409
+
1410
+ # Part B: IRE Parenthetical Split (Line 9)
1411
+ # Pattern: "(Reaffirmed 1980, 56 IRE 28.S2)" → "(R1980) (56 IRE 28.S2)"
1412
+ # Split nested reaffirmation + IRE reference into two parentheticals
1413
+ cleaned = cleaned.gsub(/\(Reaffirmed\s+(\d{4}),\s+(\d+\s+IRE[^)]+)\)/,
1414
+ '(R\1) (\2)')
1415
+
1416
+ # Part C: Slash to Parenthetical (Line 37)
1417
+ # Pattern: "number-year/ANSI identifier" → "number-year (ANSI identifier)"
1418
+ # Only convert if slash is followed by ANSI and NOT a relationship keyword
1419
+ # Look ahead to ensure we're at end of main identifier (before paren or end of string)
1420
+ cleaned = cleaned.gsub(%r{(\d{4})/ANSI\s+([^(]+)(?=\s*\(|$)},
1421
+ '\1 (ANSI \2)')
1422
+
1423
+ # Part D: ISO/IEC TR Spacing (Line 40)
1424
+ # Pattern: "ISO/IEC TR11802" → "ISO/IEC TR 11802"
1425
+ # Add space after TR when directly followed by digit
1426
+ cleaned = cleaned.gsub(/(ISO\/IEC\s+TR)(\d)/, '\1 \2')
1427
+ # === SESSION 178: AIEE Dual Numbers Expansion (Line 45) ===
1428
+
1429
+ # Part E: AIEE "Nos X and Y" Expansion
1430
+ # Pattern: "AIEE Nos 72 and 73 - 1932" → "AIEE No 72-1932 and AIEE No 73-1932"
1431
+ # Expands dual AIEE numbers to separate identifiers with shared year
1432
+ if cleaned.match?(/AIEE\s+Nos\s+(\d+)\s+and\s+(\d+)\s+-\s+(\d{4})/)
1433
+ cleaned = cleaned.sub(/AIEE\s+Nos\s+(\d+)\s+and\s+(\d+)\s+-\s+(\d{4})/) do
1434
+ first_num = $1
1435
+ second_num = $2
1436
+ year = $3
1437
+ "AIEE No #{first_num}-#{year} and AIEE No #{second_num}-#{year}"
1438
+ end
1439
+ end
1440
+
1441
+ # === SESSION 222: TODO.IEEE-MUST-FIX-IDs.txt Comprehensive Fixes ===
1442
+
1443
+ # Part A: Typo Fixes
1444
+ # 1. "Stad" -> "Std" (typo)
1445
+ cleaned = cleaned.gsub(/\bStad\b/, "Std")
1446
+
1447
+ # 2. Lowercase "std" -> "Std" when after IEEE/ANSI publishers
1448
+ cleaned = cleaned.gsub(/\b(IEEE|ANSI|AIEE)\s+std\b/, '\1 Std')
1449
+
1450
+ # Part B: Symbol Normalization
1451
+ # 3. Additional (TM) patterns - strip them out
1452
+ cleaned = cleaned.gsub("(TM)", "")
1453
+
1454
+ # Part C: Year-first format normalization
1455
+ # 4. Pattern "62704-4/D4, 2020" -> "IEEE P62704-4/D4, 2020"
1456
+ # Only if starts with digits-dash-digits/D pattern
1457
+ if cleaned.match?(/^(\d+[-.]\d+)\/D\d+/)
1458
+ cleaned = "IEEE P#{cleaned}"
1459
+ end
1460
+
1461
+ # Part D: Suffix Normalization
1462
+ # 5. "/Preprint" -> remove (data quality - not standard suffix)
1463
+ cleaned = cleaned.gsub(/\/Preprint\b/, "")
1464
+
1465
+ # Part E: Relationship Text Normalization
1466
+ # 6. "Proposed Revision of" -> "Revision of"
1467
+ cleaned = cleaned.gsub("Proposed Revision of", "Revision of")
1468
+
1469
+ # 7. "ammended" typo -> "amended"
1470
+ cleaned = cleaned.gsub(/\bammended\b/i, "amended")
1471
+
1472
+ # Part F: Trailing Characters After Special Patterns
1473
+ # 8. Remove trailing periods after /INT, /Cor, etc.
1474
+ cleaned = cleaned.gsub(/(\/INT|\/Cor\s+\d+-\d{4})\./, '\1')
1475
+
1476
+ # Part G: Conformance Pattern Spacing
1477
+ # 9. Fix spacing in "/Conformance" patterns WITHOUT year (malformed only)
1478
+ # "1904.1(TM)/Conformance02" -> "1904.1 /Conformance02" (space before slash)
1479
+ # BUT: DO NOT touch valid patterns like "802.16/Conformance01-2003" (with year)
1480
+ # Use positive check for year suffix to exclude valid patterns
1481
+ # Actually, this preprocessing is breaking valid patterns - just remove it entirely
1482
+ # The parser can handle both "6/Conformance01-2003" and "6 /Conformance02" formats
1483
+
1484
+ # Part H: Edition Text After /INT
1485
+ # 10. Handle ", Month YYYY Edition" after /INT by converting to month-year format
1486
+ # "1003.1/INT, March 1994 Edition" -> "1003.1/INT, March 1994"
1487
+ cleaned = cleaned.gsub(/(\/INT),\s+([A-Z][a-z]+)\s+(\d{4})\s+Edition/,
1488
+ '\1, \2 \3')
1489
+
1490
+ # Part I: Handle "Ed." abbreviation
1491
+ # 11. "Dec. 1994 Ed." -> "Dec. 1994"
1492
+ cleaned = cleaned.gsub(/\s+Ed\.\s*$/, "")
1493
+
1494
+ # === PHASE 2: High-impact preprocessing for fixture failures ===
1495
+
1496
+ # Quick wins from SESSION 224 (must come before more complex fixes)
1497
+
1498
+ # Remove period after "Std": "IEEE Std." -> "IEEE Std"
1499
+ cleaned = cleaned.gsub(/\bStd\.\s+/, "Std ")
1500
+
1501
+
1502
+ # Title portion removal after year: "YYYY - IEEE Standard for..."
1503
+ cleaned = cleaned.gsub(
1504
+ /(\d{4})(\s+\([^)]+\))?\s+-\s+IEEE\s+Standard\s+for.*$/, '\1\2'
1505
+ )
1506
+
1507
+ # Fix 2A: "IEEE PC" prefix -> "IEEE Std PC" or "IEEE P" treatment
1508
+ # "IEEE PC37.20.9/D7.3A" -> needs to parse as IEEE project draft
1509
+ # Strategy: Add "Std" after "IEEE" when followed by "PC" to route to standard pattern
1510
+ # Actually, the issue is the number rule consumes "PC37" as P + C37.
1511
+ # Better: normalize "IEEE PC" to "IEEE Std PC" so it hits the standard identifier path
1512
+ cleaned = cleaned.gsub(/^IEEE\s+PC(\d)/, 'IEEE Std PC\1')
1513
+ cleaned = cleaned.gsub(/^IEEE\s+Unapproved\s+Draft\s+Std\s+PC(\d)/,
1514
+ 'IEEE Unapproved Draft Std PC\1')
1515
+
1516
+ # Fix 2B: "IEEE P" without "Std"/"Draft" prefix
1517
+ # ieee_p_identifier rule handles these directly - no preprocessing needed
1518
+ # Only handle "IEEE P" followed by "and ASHRAE" (copub case)
1519
+ cleaned = cleaned.gsub(/^IEEE\s+P(\d+)\s+and\s+ASHRAE/,
1520
+ 'IEEE Std P\1 and ASHRAE')
1521
+
1522
+ # Fix 2C: "ISO/IEC XXXX-YYYY: Title" -> strip title after colon for ISO/IEC published standards
1523
+ # These are ISO-format identifiers with IEEE adoption, strip the title
1524
+ cleaned = cleaned.gsub(/^(ISO\/IEC \d+[-.]\d+-\d{4}):.*$/, '\1')
1525
+ cleaned = cleaned.gsub(/^(ISO\/IEC \d+-\d{4}):.*$/, '\1')
1526
+
1527
+ # Fix 2D: "ISO/IEC XXXX : YYYY" -> normalize spacing around colon
1528
+ cleaned = cleaned.gsub(/^(ISO\/IEC \d+[-.]\d*)\s*:\s*(\d{4})/, '\1:\2')
1529
+ cleaned = cleaned.gsub(/^(ISO\/IEC \d+)\s*:\s*(\d{4})/, '\1:\2')
1530
+
1531
+ # Fix 2G: "IEC/IEEE PXXX_D5" -> underscore to slash
1532
+ cleaned = cleaned.gsub(/^(IEC\/IEEE P[\w.-]+)_D/, '\1/D')
1533
+
1534
+ # Fix 2H: "IEC XXXX First edition YYYY-MM; IEEE NNNN" -> normalize semicolon
1535
+ # Already handled by earlier semicolon normalization
1536
+
1537
+ # Fix 2I: "IEEE/ISO/IEC PXXX/DIS" -> normalize to "ISO/IEC/IEEE PXXX/DIS"
1538
+ cleaned = cleaned.gsub(/^IEEE\/ISO\/IEC\s+(P[\w.-]+)/,
1539
+ 'ISO/IEC/IEEE \1')
1540
+ cleaned = cleaned.gsub(/^IEEE\/IEC\/ISO\s+(P[\w.-]+)/,
1541
+ 'IEC/ISO/IEEE \1')
1542
+
1543
+ # Fix 2J: "IEEE/IEC PXXX D5" -> normalize space to slash before D
1544
+ cleaned = cleaned.gsub(/^(IEEE\/IEC P[\w.-]+)\s+D(\d)/, '\1/D\2')
1545
+ cleaned = cleaned.gsub(
1546
+ /^(IEEE\/IEC P[\w.-]+)\s+(CDV|FDIS|CD|DIS|ED\d)/, '\1/\2'
1547
+ )
1548
+
1549
+ # Fix 2K: "ISO /IEC/IEEE" -> fix space before slash
1550
+ cleaned = cleaned.gsub(/^ISO\s+\/IEC\/IEEE/, "ISO/IEC/IEEE")
1551
+ cleaned = cleaned.gsub(/^ISO\s+\/IEC/, "ISO/IEC")
1552
+
1553
+ # Fix 2L: "IS0" typo (letter O instead of digit 0)
1554
+ cleaned = cleaned.gsub(/^IS0\//, "ISO/")
1555
+
1556
+ # Fix 2M: "IEEE-P15026-3-DIS-January 2015" -> dash-separated format
1557
+ # Normalize to "ISO/IEC/IEEE P15026-3/DIS, January 2015"
1558
+ cleaned = cleaned.gsub(/^IEEE-P(\d+)-(\d+)-DIS-(.*)/,
1559
+ 'ISO/IEC/IEEE P\1-\2/DIS, \3')
1560
+
1561
+ # Fix 2N: "IEEE/CSA P844.1/293.1/D2" -> normalize CSA dual numbering
1562
+ cleaned = cleaned.gsub(/^IEEE\/CSA\s+(P[\d.]+)\/([\d.]+)\/D(\d+)/,
1563
+ 'IEEE/CSA \1/D\3')
1564
+
1565
+ # Fix 2O: "IEEE Approved Draft Std P" -> normalize spacing
1566
+ cleaned = cleaned.gsub(/^IEEE\s+Approved\s+Draft\s+Std\s+(P\d)/,
1567
+ 'IEEE Approved Draft Std \1')
1568
+ # Fix: "IEEE Approved Draft Std P1234 / D12" -> remove space before slash
1569
+ cleaned = cleaned.gsub(/^(IEEE Approved Draft Std P[\w.-]+)\s+\/\s*D/,
1570
+ '\1/D')
1571
+
1572
+ # Fix 2P: "IEEE/EIA" -> normalize (parser handles IEEE/EIA via copublisher)
1573
+ # Already works - no fix needed
1574
+
1575
+ # Fix 2Q: AIEE format variations
1576
+ # "AIEE No.1C-1954" -> "AIEE No. 1C-1954" (add space after No.)
1577
+ cleaned = cleaned.gsub(/^AIEE\s+No\.\s*(\d)/, 'AIEE No. \1')
1578
+ # "AIEE no 700-1945" -> "AIEE No 700-1945" (capitalize)
1579
+ cleaned = cleaned.gsub(/^AIEE\s+no\s/, "AIEE No ")
1580
+ # "AIEE Std No. 800" -> "AIEE Standard No 800" (normalize type word)
1581
+ cleaned = cleaned.gsub(/^AIEE\s+Std\s+No\.\s*/, "AIEE Standard No ")
1582
+ # "AIEE No 750.1-1960" -> handled by AIEE parser if decimal support added
1583
+
1584
+ # Fix 2R: "IEEE PSI 10/D2" -> normalize to "IEEE/ASTM PSI 10/D2"
1585
+ cleaned = cleaned.gsub(/^IEEE\s+PSI\s+(\d)/, 'IEEE/ASTM PSI \1')
1586
+
1587
+ # Fix 2S: "IEEE/IEC P62271-111/PC37.60_D5" -> normalize
1588
+ cleaned = cleaned.gsub(/^(IEEE\/IEC P[\d.-]+\/PC[\d.]+)_D/, '\1/D')
1589
+
1590
+ # Fix 2T: "IEC P62271-111/IEEE PC37.60_D5" -> normalize to IEC/IEEE format
1591
+ cleaned = cleaned.gsub(/^IEC\s+(P[\d.-]+)\/IEEE\s+(PC[\d.]+)_D/,
1592
+ 'IEC/IEEE \2/D')
1593
+
1594
+ # Fix 2U: "IEC/IEC P" -> "IEC/IEEE P" (typo)
1595
+ cleaned = cleaned.gsub(/^IEC\/IEC\s+(P\d)/, 'IEC/IEEE \1')
1596
+
1597
+ # Fix 2V: "NACE SPXXXX-YYYY/IEEE Std NNNN-YYYY" -> normalize slash to parenthetical
1598
+ cleaned = cleaned.gsub(/^(NACE\s+SP\d+-\d+)\/(IEEE\s+Std\s+\d+-\d+)$/,
1599
+ '\1 (\2)')
1600
+
1601
+ # Fix 2W: "IEEE Std 802.11g-2003 (Amendment to IEEE Std 802.11, 1999 Edn. (Reaff 2003) as amended by"
1602
+ # This is a complex relationship - strip the parenthetical if too complex
1603
+ # Let the parser handle it but fix "Edn." to "Edition"
1604
+ cleaned = cleaned.gsub("Edn.", "Edition")
1605
+
1606
+ # Fix 2X: "IEEE-P15026-3-DIS" format -> normalize
1607
+ # Already handled by Fix 2M
1608
+
1609
+ # Fix 2Y: "P1635/D10/ASHARE 21/D10" -> fix ASHARE typo to ASHRAE
1610
+ cleaned = cleaned.gsub("ASHARE", "ASHRAE")
1611
+
1612
+ # Fix 2Z: "PC37.30.2/D043 Rev 18" -> normalize draft version with Rev
1613
+ # "PC57-15 D2.0" -> normalize to "P57-15/D2.0"
1614
+ cleaned = cleaned.gsub(/^PC(\d)/, 'P\1')
1615
+
1616
+ # Fix 2AA: "IEEE/ISO/IEC 8802-1Q-2020/Amd31-2021" -> normalize
1617
+ cleaned = cleaned.gsub(/^IEEE\/ISO\/IEC\s+(8802[\w.-]+)/,
1618
+ 'ISO/IEC/IEEE \1')
1619
+
1620
+ # Fix 2AB: "IEEE C57.139/D14June 2010" -> add missing space
1621
+ cleaned = cleaned.gsub(
1622
+ /^(IEEE\s+C?\d[\d.]*\/D\d+)([A-Z][a-z]+\s+\d{4})/, '\1, \2'
1623
+ )
1624
+
1625
+ # Fix 2AC: "IEEE Std: Title" -> strip colon and title (ANSI/IEEE Std: )
1626
+ cleaned = cleaned.gsub(/^(ANSI\/IEEE Std):\s+.*$/, '\1')
1627
+
1628
+ # Fix 2AD: "IEEE 1076 IEC 61691-1-1 First edition 2004-10" -> semicolon format
1629
+ cleaned = cleaned.gsub(
1630
+ /^(IEEE\s+[\d.]+)\s+(IEC\s+\d+[-\d]*\s+.*edition\s+\d{4}-\d{2})$/i, '\1; \2'
1631
+ )
1632
+
1633
+ # Fix 2AE: "IEEE No 29-1941 / ASA C77.1-1943" -> normalize to IEEE Std format
1634
+ cleaned = cleaned.gsub(/^IEEE\s+No\s+(\d+-\d+)\s+\/\s+ASA\s+(.*)/,
1635
+ 'IEEE Std \1 (ASA \2)')
1636
+
1637
+ # Fix 2AF: "IEEE Std 1003.1/2003.l/lNT" -> fix typos
1638
+ # .l -> .1 and lNT -> INT handled by existing fixes
1639
+
1640
+ new.parse(cleaned)
1641
+ end
1642
+ end
1643
+ end
1644
+ end