relaton 2.2.0.pre.alpha.1 → 3.0.0.pre.alpha.2

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 (702) hide show
  1. checksums.yaml +4 -4
  2. data/{docs/README.adoc → README.adoc} +165 -38
  3. data/bin/console +0 -1
  4. data/lib/relaton/3gpp/bibdata.rb +9 -0
  5. data/lib/relaton/3gpp/bibitem.rb +9 -0
  6. data/lib/relaton/3gpp/bibliography.rb +123 -0
  7. data/lib/relaton/3gpp/data_fetcher.rb +303 -0
  8. data/lib/relaton/3gpp/docidentifier.rb +114 -0
  9. data/lib/relaton/3gpp/doctype.rb +9 -0
  10. data/lib/relaton/3gpp/ext.rb +31 -0
  11. data/lib/relaton/3gpp/item.rb +18 -0
  12. data/lib/relaton/3gpp/item_data.rb +15 -0
  13. data/lib/relaton/3gpp/parser.rb +400 -0
  14. data/lib/relaton/3gpp/processor.rb +71 -0
  15. data/lib/relaton/3gpp/release.rb +34 -0
  16. data/lib/relaton/3gpp/util.rb +8 -0
  17. data/lib/relaton/3gpp.rb +29 -0
  18. data/lib/relaton/adobe/bibdata.rb +8 -0
  19. data/lib/relaton/adobe/bibitem.rb +8 -0
  20. data/lib/relaton/adobe/bibliography.rb +92 -0
  21. data/lib/relaton/adobe/docidentifier.rb +49 -0
  22. data/lib/relaton/adobe/doctype.rb +14 -0
  23. data/lib/relaton/adobe/ext.rb +32 -0
  24. data/lib/relaton/adobe/item.rb +15 -0
  25. data/lib/relaton/adobe/item_base.rb +18 -0
  26. data/lib/relaton/adobe/item_data.rb +6 -0
  27. data/lib/relaton/adobe/processor.rb +45 -0
  28. data/lib/relaton/adobe/util.rb +8 -0
  29. data/lib/relaton/adobe.rb +37 -0
  30. data/lib/relaton/bib/converter/asciibib/to_asciibib.rb +663 -0
  31. data/lib/relaton/bib/converter/asciibib.rb +13 -0
  32. data/lib/relaton/bib/converter/bibtex/from_bibtex.rb +245 -0
  33. data/lib/relaton/bib/converter/bibtex/to_bibtex.rb +341 -0
  34. data/lib/relaton/bib/converter/bibtex.rb +23 -0
  35. data/lib/relaton/bib/converter/bibxml/from_rfcxml.rb +386 -0
  36. data/lib/relaton/bib/converter/bibxml/from_rfcxml_referencegroup.rb +71 -0
  37. data/lib/relaton/bib/converter/bibxml/to_rfcxml.rb +308 -0
  38. data/lib/relaton/bib/converter/bibxml/to_rfcxml_referencegroup.rb +52 -0
  39. data/lib/relaton/bib/converter/bibxml.rb +51 -0
  40. data/lib/relaton/bib/hash_parser_v1.rb +767 -0
  41. data/lib/relaton/bib/item_data.rb +229 -0
  42. data/lib/relaton/bib/model/abstract.rb +16 -0
  43. data/lib/relaton/bib/model/address.rb +22 -0
  44. data/lib/relaton/bib/model/affiliation.rb +16 -0
  45. data/lib/relaton/bib/model/bibdata.rb +11 -0
  46. data/lib/relaton/bib/model/bibdata_shared.rb +12 -0
  47. data/lib/relaton/bib/model/bibitem.rb +11 -0
  48. data/lib/relaton/bib/model/bibitem_shared.rb +12 -0
  49. data/lib/relaton/bib/model/contact.rb +18 -0
  50. data/lib/relaton/bib/model/contribution_info.rb +15 -0
  51. data/lib/relaton/bib/model/contributor.rb +29 -0
  52. data/lib/relaton/bib/model/copyright.rb +27 -0
  53. data/lib/relaton/bib/model/date.rb +31 -0
  54. data/lib/relaton/bib/model/depiction.rb +16 -0
  55. data/lib/relaton/bib/model/docidentifier.rb +49 -0
  56. data/lib/relaton/bib/model/doctype.rb +14 -0
  57. data/lib/relaton/bib/model/edition.rb +14 -0
  58. data/lib/relaton/bib/model/ext.rb +39 -0
  59. data/lib/relaton/bib/model/extent.rb +16 -0
  60. data/lib/relaton/bib/model/formattedref.rb +43 -0
  61. data/lib/relaton/bib/model/full_name_type.rb +64 -0
  62. data/lib/relaton/bib/model/fullname.rb +11 -0
  63. data/lib/relaton/bib/model/ics.rb +49 -0
  64. data/lib/relaton/bib/model/image.rb +28 -0
  65. data/lib/relaton/bib/model/item.rb +96 -0
  66. data/lib/relaton/bib/model/item_base.rb +20 -0
  67. data/lib/relaton/bib/model/item_shared.rb +88 -0
  68. data/lib/relaton/bib/model/keyword.rb +30 -0
  69. data/lib/relaton/bib/model/locality.rb +18 -0
  70. data/lib/relaton/bib/model/locality_stack.rb +14 -0
  71. data/lib/relaton/bib/model/localized_string.rb +48 -0
  72. data/lib/relaton/bib/model/localized_string_attrs.rb +24 -0
  73. data/lib/relaton/bib/model/logo.rb +14 -0
  74. data/lib/relaton/bib/model/medium.rb +22 -0
  75. data/lib/relaton/bib/model/note.rb +16 -0
  76. data/lib/relaton/bib/model/organization.rb +13 -0
  77. data/lib/relaton/bib/model/organization_type.rb +42 -0
  78. data/lib/relaton/bib/model/person.rb +36 -0
  79. data/lib/relaton/bib/model/phone.rb +14 -0
  80. data/lib/relaton/bib/model/place.rb +33 -0
  81. data/lib/relaton/bib/model/price.rb +14 -0
  82. data/lib/relaton/bib/model/relation.rb +43 -0
  83. data/lib/relaton/bib/model/series.rb +34 -0
  84. data/lib/relaton/bib/model/size.rb +23 -0
  85. data/lib/relaton/bib/model/source_locality_stack.rb +14 -0
  86. data/lib/relaton/bib/model/status.rb +27 -0
  87. data/lib/relaton/bib/model/structured_identifier.rb +49 -0
  88. data/lib/relaton/bib/model/subdivision.rb +16 -0
  89. data/lib/relaton/bib/model/title.rb +56 -0
  90. data/lib/relaton/bib/model/type/plain_date.rb +16 -0
  91. data/lib/relaton/bib/model/type/string_date.rb +48 -0
  92. data/lib/relaton/bib/model/uri.rb +18 -0
  93. data/lib/relaton/bib/model/validity.rb +16 -0
  94. data/lib/relaton/bib/model/version.rb +43 -0
  95. data/lib/relaton/bib/namespace_helper.rb +21 -0
  96. data/lib/relaton/bib/sanitizer.rb +264 -0
  97. data/lib/relaton/bib/util.rb +18 -0
  98. data/lib/relaton/bib/versions.json +35 -0
  99. data/lib/relaton/bib.rb +46 -0
  100. data/lib/relaton/bipm/bibliography.rb +231 -0
  101. data/lib/relaton/bipm/converter/asciibib.rb +64 -0
  102. data/lib/relaton/bipm/data_fetcher.rb +81 -0
  103. data/lib/relaton/bipm/data_outcomes_parser.rb +656 -0
  104. data/lib/relaton/bipm/id_parser.rb +278 -0
  105. data/lib/relaton/bipm/item_data.rb +47 -0
  106. data/lib/relaton/bipm/model/bibdata.rb +9 -0
  107. data/lib/relaton/bipm/model/bibitem.rb +9 -0
  108. data/lib/relaton/bipm/model/comment_period.rb +13 -0
  109. data/lib/relaton/bipm/model/doctype.rb +12 -0
  110. data/lib/relaton/bipm/model/ext.rb +38 -0
  111. data/lib/relaton/bipm/model/item.rb +11 -0
  112. data/lib/relaton/bipm/model/structured_identifier.rb +36 -0
  113. data/lib/relaton/bipm/processor.rb +69 -0
  114. data/lib/relaton/bipm/rawdata_bipm_metrologia/affiliations.rb +111 -0
  115. data/lib/relaton/bipm/rawdata_bipm_metrologia/fetcher.rb +172 -0
  116. data/lib/relaton/bipm/rawdata_bipm_metrologia/niso_jats_parser.rb +353 -0
  117. data/lib/relaton/bipm/si_brochure_parser.rb +188 -0
  118. data/lib/relaton/bipm/util.rb +8 -0
  119. data/lib/relaton/bipm.rb +38 -0
  120. data/lib/relaton/bsi/bibliography.rb +196 -0
  121. data/lib/relaton/bsi/hit.rb +28 -0
  122. data/lib/relaton/bsi/hit_collection.rb +113 -0
  123. data/lib/relaton/bsi/item_data.rb +13 -0
  124. data/lib/relaton/bsi/model/bibdata.rb +8 -0
  125. data/lib/relaton/bsi/model/bibitem.rb +8 -0
  126. data/lib/relaton/bsi/model/docidentifier.rb +107 -0
  127. data/lib/relaton/bsi/model/doctype.rb +14 -0
  128. data/lib/relaton/bsi/model/ext.rb +17 -0
  129. data/lib/relaton/bsi/model/item.rb +17 -0
  130. data/lib/relaton/bsi/model/item_base.rb +22 -0
  131. data/lib/relaton/bsi/model/relation.rb +9 -0
  132. data/lib/relaton/bsi/processor.rb +43 -0
  133. data/lib/relaton/bsi/schema.json +24882 -0
  134. data/lib/relaton/bsi/scraper.rb +288 -0
  135. data/lib/relaton/bsi/util.rb +8 -0
  136. data/lib/relaton/bsi.rb +25 -0
  137. data/lib/relaton/calconnect/bibliography.rb +93 -0
  138. data/lib/relaton/calconnect/data_fetcher.rb +174 -0
  139. data/lib/relaton/calconnect/docidentifier.rb +80 -0
  140. data/lib/relaton/calconnect/hit.rb +14 -0
  141. data/lib/relaton/calconnect/hit_collection.rb +92 -0
  142. data/lib/relaton/calconnect/item_data.rb +12 -0
  143. data/lib/relaton/calconnect/model/bibdata.rb +8 -0
  144. data/lib/relaton/calconnect/model/bibitem.rb +8 -0
  145. data/lib/relaton/calconnect/model/doctype.rb +11 -0
  146. data/lib/relaton/calconnect/model/ext.rb +11 -0
  147. data/lib/relaton/calconnect/model/item.rb +20 -0
  148. data/lib/relaton/calconnect/processor.rb +72 -0
  149. data/lib/relaton/calconnect/scraper.rb +87 -0
  150. data/lib/relaton/calconnect/util.rb +8 -0
  151. data/lib/relaton/calconnect.rb +36 -0
  152. data/lib/relaton/ccsds/bibliography.rb +63 -0
  153. data/lib/relaton/ccsds/data/fetcher.rb +252 -0
  154. data/lib/relaton/ccsds/data/iso_references.rb +30 -0
  155. data/lib/relaton/ccsds/data/parser.rb +194 -0
  156. data/lib/relaton/ccsds/hit.rb +24 -0
  157. data/lib/relaton/ccsds/hit_collection.rb +47 -0
  158. data/lib/relaton/ccsds/item_data.rb +9 -0
  159. data/lib/relaton/ccsds/model/bibdata.rb +8 -0
  160. data/lib/relaton/ccsds/model/bibitem.rb +8 -0
  161. data/lib/relaton/ccsds/model/docidentifier.rb +121 -0
  162. data/lib/relaton/ccsds/model/doctype.rb +9 -0
  163. data/lib/relaton/ccsds/model/ext.rb +19 -0
  164. data/lib/relaton/ccsds/model/item.rb +15 -0
  165. data/lib/relaton/ccsds/processor.rb +68 -0
  166. data/lib/relaton/ccsds/util.rb +10 -0
  167. data/lib/relaton/ccsds.rb +33 -0
  168. data/lib/relaton/cen/bibliography.rb +149 -0
  169. data/lib/relaton/cen/committees.yaml +66 -0
  170. data/lib/relaton/cen/hit.rb +31 -0
  171. data/lib/relaton/cen/hit_collection.rb +116 -0
  172. data/lib/relaton/cen/item_data.rb +7 -0
  173. data/lib/relaton/cen/model/bibdata.rb +8 -0
  174. data/lib/relaton/cen/model/bibitem.rb +8 -0
  175. data/lib/relaton/cen/model/docidentifier.rb +100 -0
  176. data/lib/relaton/cen/model/ext.rb +11 -0
  177. data/lib/relaton/cen/model/item.rb +14 -0
  178. data/lib/relaton/cen/model/structured_identifier.rb +9 -0
  179. data/lib/relaton/cen/processor.rb +45 -0
  180. data/lib/relaton/cen/scraper.rb +218 -0
  181. data/lib/relaton/cen/util.rb +8 -0
  182. data/lib/relaton/cen.rb +30 -0
  183. data/lib/relaton/cie/bibdata.rb +8 -0
  184. data/lib/relaton/cie/bibitem.rb +8 -0
  185. data/lib/relaton/cie/bibliography.rb +31 -0
  186. data/lib/relaton/cie/data_fetcher.rb +540 -0
  187. data/lib/relaton/cie/ext.rb +7 -0
  188. data/lib/relaton/cie/item.rb +11 -0
  189. data/lib/relaton/cie/item_data.rb +6 -0
  190. data/lib/relaton/cie/processor.rb +68 -0
  191. data/lib/relaton/cie/scrapper.rb +52 -0
  192. data/lib/relaton/cie/util.rb +8 -0
  193. data/lib/relaton/cie.rb +29 -0
  194. data/lib/relaton/core/array_wrapper.rb +20 -0
  195. data/lib/relaton/core/data_fetcher.rb +244 -0
  196. data/lib/relaton/core/date_parser.rb +42 -0
  197. data/lib/relaton/core/governor.rb +320 -0
  198. data/lib/relaton/core/hash_keys_sybolizer.rb +19 -0
  199. data/lib/relaton/core/hit.rb +49 -0
  200. data/lib/relaton/core/hit_collection.rb +118 -0
  201. data/lib/relaton/core/pacer.rb +134 -0
  202. data/lib/relaton/core/processor.rb +67 -0
  203. data/lib/relaton/core/request_error.rb +14 -0
  204. data/lib/relaton/core/workers_pool.rb +45 -0
  205. data/lib/relaton/core.rb +12 -0
  206. data/lib/relaton/db/registry.rb +44 -4
  207. data/lib/relaton/db.rb +0 -1
  208. data/lib/relaton/doi/crossref.rb +89 -0
  209. data/lib/relaton/doi/parser.rb +921 -0
  210. data/lib/relaton/doi/processor.rb +65 -0
  211. data/lib/relaton/doi/util.rb +8 -0
  212. data/lib/relaton/doi.rb +20 -0
  213. data/lib/relaton/easc/bibdata.rb +8 -0
  214. data/lib/relaton/easc/bibitem.rb +8 -0
  215. data/lib/relaton/easc/bibliography.rb +95 -0
  216. data/lib/relaton/easc/docidentifier.rb +100 -0
  217. data/lib/relaton/easc/doctype.rb +14 -0
  218. data/lib/relaton/easc/ext.rb +44 -0
  219. data/lib/relaton/easc/item.rb +13 -0
  220. data/lib/relaton/easc/item_base.rb +18 -0
  221. data/lib/relaton/easc/item_data.rb +6 -0
  222. data/lib/relaton/easc/processor.rb +46 -0
  223. data/lib/relaton/easc/util.rb +8 -0
  224. data/lib/relaton/easc.rb +35 -0
  225. data/lib/relaton/ecma/bibdata.rb +8 -0
  226. data/lib/relaton/ecma/bibitem.rb +8 -0
  227. data/lib/relaton/ecma/bibliography.rb +149 -0
  228. data/lib/relaton/ecma/data_fetcher.rb +162 -0
  229. data/lib/relaton/ecma/data_parser.rb +49 -0
  230. data/lib/relaton/ecma/docidentifier.rb +124 -0
  231. data/lib/relaton/ecma/edition_parser.rb +80 -0
  232. data/lib/relaton/ecma/ext.rb +7 -0
  233. data/lib/relaton/ecma/item.rb +13 -0
  234. data/lib/relaton/ecma/item_data.rb +6 -0
  235. data/lib/relaton/ecma/memento_parser.rb +60 -0
  236. data/lib/relaton/ecma/page_fetcher.rb +39 -0
  237. data/lib/relaton/ecma/parser_common.rb +33 -0
  238. data/lib/relaton/ecma/processor.rb +69 -0
  239. data/lib/relaton/ecma/standard_parser.rb +134 -0
  240. data/lib/relaton/ecma/util.rb +8 -0
  241. data/lib/relaton/ecma.rb +33 -0
  242. data/lib/relaton/etsi/bibdata.rb +10 -0
  243. data/lib/relaton/etsi/bibitem.rb +10 -0
  244. data/lib/relaton/etsi/bibliography.rb +111 -0
  245. data/lib/relaton/etsi/data_fetcher.rb +167 -0
  246. data/lib/relaton/etsi/data_parser.rb +208 -0
  247. data/lib/relaton/etsi/doctype.rb +30 -0
  248. data/lib/relaton/etsi/ext.rb +31 -0
  249. data/lib/relaton/etsi/item.rb +15 -0
  250. data/lib/relaton/etsi/item_data.rb +6 -0
  251. data/lib/relaton/etsi/processor.rb +71 -0
  252. data/lib/relaton/etsi/pubid.rb +37 -0
  253. data/lib/relaton/etsi/status.rb +13 -0
  254. data/lib/relaton/etsi/util.rb +8 -0
  255. data/lib/relaton/etsi.rb +26 -0
  256. data/lib/relaton/gb/bibdata.rb +8 -0
  257. data/lib/relaton/gb/bibitem.rb +8 -0
  258. data/lib/relaton/gb/bibliography.rb +171 -0
  259. data/lib/relaton/gb/ccs.rb +14 -0
  260. data/lib/relaton/gb/committee.rb +13 -0
  261. data/lib/relaton/gb/docidentifier.rb +72 -0
  262. data/lib/relaton/gb/doctype.rb +9 -0
  263. data/lib/relaton/gb/ext.rb +36 -0
  264. data/lib/relaton/gb/gb_scraper.rb +61 -0
  265. data/lib/relaton/gb/gb_type.rb +20 -0
  266. data/lib/relaton/gb/hit.rb +48 -0
  267. data/lib/relaton/gb/hit_collection.rb +19 -0
  268. data/lib/relaton/gb/item.rb +13 -0
  269. data/lib/relaton/gb/item_data.rb +6 -0
  270. data/lib/relaton/gb/processor.rb +49 -0
  271. data/lib/relaton/gb/project_number.rb +38 -0
  272. data/lib/relaton/gb/scraper.rb +221 -0
  273. data/lib/relaton/gb/sec_scraper.rb +92 -0
  274. data/lib/relaton/gb/stage_name.rb +13 -0
  275. data/lib/relaton/gb/structured_identifier.rb +26 -0
  276. data/lib/relaton/gb/t_scraper.rb +126 -0
  277. data/lib/relaton/gb/util.rb +8 -0
  278. data/lib/relaton/gb/yaml/prefixes.yaml +200 -0
  279. data/lib/relaton/gb.rb +33 -0
  280. data/lib/relaton/gost/bibdata.rb +8 -0
  281. data/lib/relaton/gost/bibitem.rb +8 -0
  282. data/lib/relaton/gost/bibliography.rb +107 -0
  283. data/lib/relaton/gost/docidentifier.rb +80 -0
  284. data/lib/relaton/gost/doctype.rb +16 -0
  285. data/lib/relaton/gost/ext.rb +46 -0
  286. data/lib/relaton/gost/item.rb +15 -0
  287. data/lib/relaton/gost/item_base.rb +18 -0
  288. data/lib/relaton/gost/item_data.rb +6 -0
  289. data/lib/relaton/gost/processor.rb +49 -0
  290. data/lib/relaton/gost/util.rb +8 -0
  291. data/lib/relaton/gost.rb +36 -0
  292. data/lib/relaton/iala/bibdata.rb +8 -0
  293. data/lib/relaton/iala/bibitem.rb +8 -0
  294. data/lib/relaton/iala/bibliography.rb +146 -0
  295. data/lib/relaton/iala/docidentifier.rb +89 -0
  296. data/lib/relaton/iala/doctype.rb +18 -0
  297. data/lib/relaton/iala/ext.rb +32 -0
  298. data/lib/relaton/iala/item.rb +21 -0
  299. data/lib/relaton/iala/item_base.rb +18 -0
  300. data/lib/relaton/iala/item_data.rb +6 -0
  301. data/lib/relaton/iala/processor.rb +43 -0
  302. data/lib/relaton/iala/relation.rb +7 -0
  303. data/lib/relaton/iala/util.rb +8 -0
  304. data/lib/relaton/iala.rb +35 -0
  305. data/lib/relaton/iana/bibdata.rb +8 -0
  306. data/lib/relaton/iana/bibitem.rb +8 -0
  307. data/lib/relaton/iana/bibliography.rb +100 -0
  308. data/lib/relaton/iana/data_fetcher.rb +101 -0
  309. data/lib/relaton/iana/item.rb +7 -0
  310. data/lib/relaton/iana/item_data.rb +6 -0
  311. data/lib/relaton/iana/parser.rb +146 -0
  312. data/lib/relaton/iana/processor.rb +70 -0
  313. data/lib/relaton/iana/util.rb +8 -0
  314. data/lib/relaton/iana.rb +40 -0
  315. data/lib/relaton/iec/bibliography.rb +283 -0
  316. data/lib/relaton/iec/data_fetcher.rb +222 -0
  317. data/lib/relaton/iec/data_parser.rb +391 -0
  318. data/lib/relaton/iec/hit.rb +26 -0
  319. data/lib/relaton/iec/hit_collection.rb +138 -0
  320. data/lib/relaton/iec/item_data.rb +7 -0
  321. data/lib/relaton/iec/model/bibdata.rb +8 -0
  322. data/lib/relaton/iec/model/bibitem.rb +8 -0
  323. data/lib/relaton/iec/model/docidentifier.rb +135 -0
  324. data/lib/relaton/iec/model/doctype.rb +12 -0
  325. data/lib/relaton/iec/model/ext.rb +53 -0
  326. data/lib/relaton/iec/model/item.rb +20 -0
  327. data/lib/relaton/iec/model/item_base.rb +12 -0
  328. data/lib/relaton/iec/model/relation.rb +7 -0
  329. data/lib/relaton/iec/model/stage_name.rb +13 -0
  330. data/lib/relaton/iec/processor.rb +74 -0
  331. data/lib/relaton/iec/statuses.yml +199 -0
  332. data/lib/relaton/iec/util.rb +8 -0
  333. data/lib/relaton/iec.rb +98 -0
  334. data/lib/relaton/ieee/balloting_group.rb +13 -0
  335. data/lib/relaton/ieee/bibdata.rb +8 -0
  336. data/lib/relaton/ieee/bibitem.rb +8 -0
  337. data/lib/relaton/ieee/bibliography.rb +73 -0
  338. data/lib/relaton/ieee/converter/bibxml/from_rfcxml.rb +10 -0
  339. data/lib/relaton/ieee/converter/bibxml/from_rfcxml_referencegroup.rb +10 -0
  340. data/lib/relaton/ieee/converter/bibxml.rb +20 -0
  341. data/lib/relaton/ieee/data_fetcher.rb +771 -0
  342. data/lib/relaton/ieee/doctype.rb +9 -0
  343. data/lib/relaton/ieee/editorial_group.rb +19 -0
  344. data/lib/relaton/ieee/ext.rb +36 -0
  345. data/lib/relaton/ieee/idams_parser.rb +330 -0
  346. data/lib/relaton/ieee/item.rb +11 -0
  347. data/lib/relaton/ieee/item_data.rb +7 -0
  348. data/lib/relaton/ieee/processor.rb +71 -0
  349. data/lib/relaton/ieee/rawbib_id_parser.rb +753 -0
  350. data/lib/relaton/ieee/util.rb +8 -0
  351. data/lib/relaton/ieee.rb +30 -0
  352. data/lib/relaton/ietf/bibdata.rb +8 -0
  353. data/lib/relaton/ietf/bibitem.rb +8 -0
  354. data/lib/relaton/ietf/bibliography.rb +35 -0
  355. data/lib/relaton/ietf/bibxml_parser.rb +232 -0
  356. data/lib/relaton/ietf/data_fetcher.rb +561 -0
  357. data/lib/relaton/ietf/doctype.rb +9 -0
  358. data/lib/relaton/ietf/ext.rb +63 -0
  359. data/lib/relaton/ietf/item.rb +16 -0
  360. data/lib/relaton/ietf/item_base.rb +18 -0
  361. data/lib/relaton/ietf/item_data.rb +6 -0
  362. data/lib/relaton/ietf/processing_instructions.rb +79 -0
  363. data/lib/relaton/ietf/processor.rb +72 -0
  364. data/lib/relaton/ietf/relation.rb +9 -0
  365. data/lib/relaton/ietf/rfc/abstract.rb +19 -0
  366. data/lib/relaton/ietf/rfc/author.rb +21 -0
  367. data/lib/relaton/ietf/rfc/entry.rb +446 -0
  368. data/lib/relaton/ietf/rfc/entry_date.rb +21 -0
  369. data/lib/relaton/ietf/rfc/format.rb +19 -0
  370. data/lib/relaton/ietf/rfc/index.rb +46 -0
  371. data/lib/relaton/ietf/rfc/is_also.rb +21 -0
  372. data/lib/relaton/ietf/rfc/keywords.rb +19 -0
  373. data/lib/relaton/ietf/rfc/rfc_index_namespace.rb +11 -0
  374. data/lib/relaton/ietf/scraper.rb +101 -0
  375. data/lib/relaton/ietf/util.rb +8 -0
  376. data/lib/relaton/ietf/wg_name_resolver.rb +42 -0
  377. data/lib/relaton/ietf.rb +30 -0
  378. data/lib/relaton/iho/bibdata.rb +8 -0
  379. data/lib/relaton/iho/bibitem.rb +8 -0
  380. data/lib/relaton/iho/bibliography.rb +114 -0
  381. data/lib/relaton/iho/comment_period.rb +13 -0
  382. data/lib/relaton/iho/docidentifier.rb +31 -0
  383. data/lib/relaton/iho/doctype.rb +10 -0
  384. data/lib/relaton/iho/ext.rb +20 -0
  385. data/lib/relaton/iho/hash_parser_v1.rb +144 -0
  386. data/lib/relaton/iho/item.rb +20 -0
  387. data/lib/relaton/iho/item_base.rb +18 -0
  388. data/lib/relaton/iho/item_data.rb +6 -0
  389. data/lib/relaton/iho/processor.rb +54 -0
  390. data/lib/relaton/iho/relation.rb +9 -0
  391. data/lib/relaton/iho/structured_identifier.rb +20 -0
  392. data/lib/relaton/iho/util.rb +8 -0
  393. data/lib/relaton/iho.rb +27 -0
  394. data/lib/relaton/index/config.rb +52 -0
  395. data/lib/relaton/index/file_io.rb +305 -0
  396. data/lib/relaton/index/file_storage.rb +66 -0
  397. data/lib/relaton/index/pool.rb +46 -0
  398. data/lib/relaton/index/shard_source.rb +201 -0
  399. data/lib/relaton/index/type.rb +210 -0
  400. data/lib/relaton/index/util.rb +18 -0
  401. data/lib/relaton/index.rb +61 -0
  402. data/lib/relaton/isbn/isbn.rb +61 -0
  403. data/lib/relaton/isbn/open_library.rb +43 -0
  404. data/lib/relaton/isbn/parser.rb +88 -0
  405. data/lib/relaton/isbn/processor.rb +48 -0
  406. data/lib/relaton/isbn/util.rb +8 -0
  407. data/lib/relaton/isbn.rb +19 -0
  408. data/lib/relaton/iso/bibliography.rb +412 -0
  409. data/lib/relaton/iso/data_fetcher.rb +338 -0
  410. data/lib/relaton/iso/data_parser.rb +458 -0
  411. data/lib/relaton/iso/hash_parser_v1.rb +177 -0
  412. data/lib/relaton/iso/hit.rb +57 -0
  413. data/lib/relaton/iso/hit_collection.rb +196 -0
  414. data/lib/relaton/iso/item_data.rb +50 -0
  415. data/lib/relaton/iso/model/bibdata.rb +10 -0
  416. data/lib/relaton/iso/model/bibitem.rb +8 -0
  417. data/lib/relaton/iso/model/contributor.rb +6 -0
  418. data/lib/relaton/iso/model/contributor_info.rb +9 -0
  419. data/lib/relaton/iso/model/docidentifier.rb +146 -0
  420. data/lib/relaton/iso/model/doctype.rb +13 -0
  421. data/lib/relaton/iso/model/ext.rb +35 -0
  422. data/lib/relaton/iso/model/item.rb +17 -0
  423. data/lib/relaton/iso/model/item_base.rb +22 -0
  424. data/lib/relaton/iso/model/organization.rb +9 -0
  425. data/lib/relaton/iso/model/project_number.rb +22 -0
  426. data/lib/relaton/iso/model/relation.rb +9 -0
  427. data/lib/relaton/iso/model/stagename.rb +14 -0
  428. data/lib/relaton/iso/model/structured_identifier.rb +31 -0
  429. data/lib/relaton/iso/processor.rb +85 -0
  430. data/lib/relaton/iso/scraper.rb +635 -0
  431. data/lib/relaton/iso/type/pubid.rb +50 -0
  432. data/lib/relaton/iso/util.rb +8 -0
  433. data/lib/relaton/iso.rb +30 -0
  434. data/lib/relaton/itu/bibliography.rb +95 -0
  435. data/lib/relaton/itu/data_crawler_r.rb +664 -0
  436. data/lib/relaton/itu/data_fetcher.rb +550 -0
  437. data/lib/relaton/itu/data_merge_r.rb +149 -0
  438. data/lib/relaton/itu/data_parser_r.rb +214 -0
  439. data/lib/relaton/itu/data_parser_t.rb +228 -0
  440. data/lib/relaton/itu/family_cache.rb +177 -0
  441. data/lib/relaton/itu/governor.rb +56 -0
  442. data/lib/relaton/itu/hit.rb +28 -0
  443. data/lib/relaton/itu/hit_collection.rb +299 -0
  444. data/lib/relaton/itu/item_data.rb +6 -0
  445. data/lib/relaton/itu/model/approval_stage.rb +13 -0
  446. data/lib/relaton/itu/model/bibdata.rb +8 -0
  447. data/lib/relaton/itu/model/bibitem.rb +8 -0
  448. data/lib/relaton/itu/model/docidentifier.rb +75 -0
  449. data/lib/relaton/itu/model/doctype.rb +13 -0
  450. data/lib/relaton/itu/model/ext.rb +47 -0
  451. data/lib/relaton/itu/model/item.rb +13 -0
  452. data/lib/relaton/itu/model/meeting.rb +13 -0
  453. data/lib/relaton/itu/model/meeting_date.rb +15 -0
  454. data/lib/relaton/itu/model/question.rb +13 -0
  455. data/lib/relaton/itu/model/recommendation_status.rb +11 -0
  456. data/lib/relaton/itu/model/structured_identifier.rb +38 -0
  457. data/lib/relaton/itu/processor.rb +72 -0
  458. data/lib/relaton/itu/pubid.rb +199 -0
  459. data/lib/relaton/itu/radio_regulations_parser.rb +70 -0
  460. data/lib/relaton/itu/recommendation_fields.rb +334 -0
  461. data/lib/relaton/itu/recommendation_parser.rb +30 -0
  462. data/lib/relaton/itu/scraper.rb +203 -0
  463. data/lib/relaton/itu/util.rb +8 -0
  464. data/lib/relaton/itu.rb +34 -0
  465. data/lib/relaton/jcgm/bibdata.rb +8 -0
  466. data/lib/relaton/jcgm/bibitem.rb +8 -0
  467. data/lib/relaton/jcgm/bibliography.rb +97 -0
  468. data/lib/relaton/jcgm/data_fetcher.rb +81 -0
  469. data/lib/relaton/jcgm/docidentifier.rb +102 -0
  470. data/lib/relaton/jcgm/doctype.rb +12 -0
  471. data/lib/relaton/jcgm/ext.rb +23 -0
  472. data/lib/relaton/jcgm/item.rb +20 -0
  473. data/lib/relaton/jcgm/item_base.rb +18 -0
  474. data/lib/relaton/jcgm/item_data.rb +6 -0
  475. data/lib/relaton/jcgm/meetings_parser.rb +175 -0
  476. data/lib/relaton/jcgm/processor.rb +71 -0
  477. data/lib/relaton/jcgm/relation.rb +9 -0
  478. data/lib/relaton/jcgm/structured_identifier.rb +40 -0
  479. data/lib/relaton/jcgm/util.rb +8 -0
  480. data/lib/relaton/jcgm.rb +24 -0
  481. data/lib/relaton/jis/bibdata.rb +8 -0
  482. data/lib/relaton/jis/bibitem.rb +8 -0
  483. data/lib/relaton/jis/bibliography.rb +73 -0
  484. data/lib/relaton/jis/data_fetcher.rb +183 -0
  485. data/lib/relaton/jis/docidentifier.rb +120 -0
  486. data/lib/relaton/jis/doctype.rb +9 -0
  487. data/lib/relaton/jis/ext.rb +13 -0
  488. data/lib/relaton/jis/hit.rb +88 -0
  489. data/lib/relaton/jis/hit_collection.rb +128 -0
  490. data/lib/relaton/jis/item.rb +22 -0
  491. data/lib/relaton/jis/item_base.rb +26 -0
  492. data/lib/relaton/jis/item_data.rb +8 -0
  493. data/lib/relaton/jis/processor.rb +55 -0
  494. data/lib/relaton/jis/relation.rb +11 -0
  495. data/lib/relaton/jis/scraper.rb +232 -0
  496. data/lib/relaton/jis/structured_identifier.rb +13 -0
  497. data/lib/relaton/jis/util.rb +8 -0
  498. data/lib/relaton/jis.rb +32 -0
  499. data/lib/relaton/logger/channels/gh_issue.rb +143 -0
  500. data/lib/relaton/logger/config.rb +34 -0
  501. data/lib/relaton/logger/formatter_json.rb +8 -0
  502. data/lib/relaton/logger/formatter_string.rb +12 -0
  503. data/lib/relaton/logger/log.rb +64 -0
  504. data/lib/relaton/logger/log_device.rb +27 -0
  505. data/lib/relaton/logger/pool.rb +24 -0
  506. data/lib/relaton/logger.rb +18 -0
  507. data/lib/relaton/nist/bibdata.rb +8 -0
  508. data/lib/relaton/nist/bibitem.rb +8 -0
  509. data/lib/relaton/nist/bibliography.rb +191 -0
  510. data/lib/relaton/nist/comment_period.rb +15 -0
  511. data/lib/relaton/nist/data_fetcher.rb +134 -0
  512. data/lib/relaton/nist/date.rb +7 -0
  513. data/lib/relaton/nist/docidentifier.rb +165 -0
  514. data/lib/relaton/nist/doctype.rb +7 -0
  515. data/lib/relaton/nist/ext.rb +16 -0
  516. data/lib/relaton/nist/hit.rb +18 -0
  517. data/lib/relaton/nist/hit_collection.rb +362 -0
  518. data/lib/relaton/nist/item.rb +19 -0
  519. data/lib/relaton/nist/item_base.rb +16 -0
  520. data/lib/relaton/nist/item_data.rb +6 -0
  521. data/lib/relaton/nist/mods_parser.rb +302 -0
  522. data/lib/relaton/nist/processor.rb +69 -0
  523. data/lib/relaton/nist/pubs_export.rb +69 -0
  524. data/lib/relaton/nist/relation.rb +10 -0
  525. data/lib/relaton/nist/scraper.rb +293 -0
  526. data/lib/relaton/nist/series.yaml +49 -0
  527. data/lib/relaton/nist/util.rb +8 -0
  528. data/lib/relaton/nist.rb +34 -0
  529. data/lib/relaton/oasis/bibdata.rb +8 -0
  530. data/lib/relaton/oasis/bibitem.rb +8 -0
  531. data/lib/relaton/oasis/bibliography.rb +201 -0
  532. data/lib/relaton/oasis/browser_agent.rb +71 -0
  533. data/lib/relaton/oasis/data_fetcher.rb +130 -0
  534. data/lib/relaton/oasis/data_parser.rb +305 -0
  535. data/lib/relaton/oasis/data_parser_utils.rb +330 -0
  536. data/lib/relaton/oasis/data_part_parser.rb +239 -0
  537. data/lib/relaton/oasis/docidentifier.rb +54 -0
  538. data/lib/relaton/oasis/doctype.rb +7 -0
  539. data/lib/relaton/oasis/ext.rb +19 -0
  540. data/lib/relaton/oasis/item.rb +14 -0
  541. data/lib/relaton/oasis/item_data.rb +6 -0
  542. data/lib/relaton/oasis/processor.rb +74 -0
  543. data/lib/relaton/oasis/util.rb +8 -0
  544. data/lib/relaton/oasis.rb +41 -0
  545. data/lib/relaton/ogc/bibdata.rb +8 -0
  546. data/lib/relaton/ogc/bibitem.rb +8 -0
  547. data/lib/relaton/ogc/bibliography.rb +64 -0
  548. data/lib/relaton/ogc/data_fetcher.rb +131 -0
  549. data/lib/relaton/ogc/docidentifier.rb +111 -0
  550. data/lib/relaton/ogc/doctype.rb +12 -0
  551. data/lib/relaton/ogc/ext.rb +15 -0
  552. data/lib/relaton/ogc/hit.rb +18 -0
  553. data/lib/relaton/ogc/hit_collection.rb +111 -0
  554. data/lib/relaton/ogc/item.rb +13 -0
  555. data/lib/relaton/ogc/item_data.rb +6 -0
  556. data/lib/relaton/ogc/processor.rb +59 -0
  557. data/lib/relaton/ogc/scraper.rb +225 -0
  558. data/lib/relaton/ogc/util.rb +8 -0
  559. data/lib/relaton/ogc.rb +30 -0
  560. data/lib/relaton/oiml/bibdata.rb +8 -0
  561. data/lib/relaton/oiml/bibitem.rb +8 -0
  562. data/lib/relaton/oiml/bibliography.rb +169 -0
  563. data/lib/relaton/oiml/docidentifier.rb +48 -0
  564. data/lib/relaton/oiml/doctype.rb +13 -0
  565. data/lib/relaton/oiml/ext.rb +42 -0
  566. data/lib/relaton/oiml/item.rb +20 -0
  567. data/lib/relaton/oiml/item_base.rb +18 -0
  568. data/lib/relaton/oiml/item_data.rb +6 -0
  569. data/lib/relaton/oiml/processor.rb +54 -0
  570. data/lib/relaton/oiml/relation.rb +9 -0
  571. data/lib/relaton/oiml/util.rb +8 -0
  572. data/lib/relaton/oiml.rb +24 -0
  573. data/lib/relaton/omg/bibdata.rb +8 -0
  574. data/lib/relaton/omg/bibitem.rb +8 -0
  575. data/lib/relaton/omg/bibliography.rb +31 -0
  576. data/lib/relaton/omg/docidentifier.rb +67 -0
  577. data/lib/relaton/omg/ext.rb +7 -0
  578. data/lib/relaton/omg/item.rb +9 -0
  579. data/lib/relaton/omg/item_data.rb +6 -0
  580. data/lib/relaton/omg/processor.rb +35 -0
  581. data/lib/relaton/omg/scraper.rb +186 -0
  582. data/lib/relaton/omg/util.rb +8 -0
  583. data/lib/relaton/omg.rb +25 -0
  584. data/lib/relaton/plateau/bibdata.rb +8 -0
  585. data/lib/relaton/plateau/bibitem.rb +8 -0
  586. data/lib/relaton/plateau/bibliography.rb +31 -0
  587. data/lib/relaton/plateau/data_fetcher.rb +176 -0
  588. data/lib/relaton/plateau/doctype.rb +7 -0
  589. data/lib/relaton/plateau/ext.rb +23 -0
  590. data/lib/relaton/plateau/handbook_parser.rb +102 -0
  591. data/lib/relaton/plateau/hit.rb +21 -0
  592. data/lib/relaton/plateau/hit_collection.rb +79 -0
  593. data/lib/relaton/plateau/item.rb +12 -0
  594. data/lib/relaton/plateau/item_data.rb +6 -0
  595. data/lib/relaton/plateau/parser.rb +101 -0
  596. data/lib/relaton/plateau/processor.rb +51 -0
  597. data/lib/relaton/plateau/technical_report_parser.rb +98 -0
  598. data/lib/relaton/plateau/util.rb +8 -0
  599. data/lib/relaton/plateau.rb +29 -0
  600. data/lib/relaton/sdo/config.rb +34 -0
  601. data/lib/relaton/sdo/fetcher.rb +52 -0
  602. data/lib/relaton/sdo/logo.rb +95 -0
  603. data/lib/relaton/sdo/name.rb +26 -0
  604. data/lib/relaton/sdo/organization.rb +71 -0
  605. data/lib/relaton/sdo/store.rb +49 -0
  606. data/lib/relaton/sdo.rb +29 -0
  607. data/lib/relaton/un/bibdata.rb +8 -0
  608. data/lib/relaton/un/bibitem.rb +8 -0
  609. data/lib/relaton/un/bibliography.rb +48 -0
  610. data/lib/relaton/un/doctype.rb +10 -0
  611. data/lib/relaton/un/ext.rb +30 -0
  612. data/lib/relaton/un/hit.rb +56 -0
  613. data/lib/relaton/un/hit_collection.rb +61 -0
  614. data/lib/relaton/un/item.rb +11 -0
  615. data/lib/relaton/un/item_data.rb +6 -0
  616. data/lib/relaton/un/parser.rb +108 -0
  617. data/lib/relaton/un/processor.rb +38 -0
  618. data/lib/relaton/un/session.rb +25 -0
  619. data/lib/relaton/un/token_generator.rb +105 -0
  620. data/lib/relaton/un/util.rb +8 -0
  621. data/lib/relaton/un/wasm/decoder.rb +434 -0
  622. data/lib/relaton/un/wasm/instance.rb +101 -0
  623. data/lib/relaton/un/wasm/interpreter.rb +613 -0
  624. data/lib/relaton/un/wasm/memory.rb +112 -0
  625. data/lib/relaton/un/wasm/module.rb +47 -0
  626. data/lib/relaton/un/wasm.rb +17 -0
  627. data/lib/relaton/un/wasm_v_bg.wasm +0 -0
  628. data/lib/relaton/un.rb +28 -0
  629. data/lib/relaton/version.rb +1 -1
  630. data/lib/relaton/w3c/bibdata.rb +8 -0
  631. data/lib/relaton/w3c/bibitem.rb +8 -0
  632. data/lib/relaton/w3c/bibliography.rb +174 -0
  633. data/lib/relaton/w3c/data_fetcher.rb +487 -0
  634. data/lib/relaton/w3c/data_parser.rb +379 -0
  635. data/lib/relaton/w3c/docidentifier.rb +48 -0
  636. data/lib/relaton/w3c/doctype.rb +7 -0
  637. data/lib/relaton/w3c/ext.rb +11 -0
  638. data/lib/relaton/w3c/governor.rb +32 -0
  639. data/lib/relaton/w3c/item.rb +15 -0
  640. data/lib/relaton/w3c/item_data.rb +6 -0
  641. data/lib/relaton/w3c/processor.rb +49 -0
  642. data/lib/relaton/w3c/pubid.rb +85 -0
  643. data/lib/relaton/w3c/safe_realize.rb +148 -0
  644. data/lib/relaton/w3c/util.rb +8 -0
  645. data/lib/relaton/w3c.rb +36 -0
  646. data/lib/relaton/xsf/bibdata.rb +8 -0
  647. data/lib/relaton/xsf/bibitem.rb +8 -0
  648. data/lib/relaton/xsf/bibliography.rb +84 -0
  649. data/lib/relaton/xsf/data_fetcher.rb +106 -0
  650. data/lib/relaton/xsf/docidentifier.rb +46 -0
  651. data/lib/relaton/xsf/hit.rb +17 -0
  652. data/lib/relaton/xsf/hit_collection.rb +52 -0
  653. data/lib/relaton/xsf/item.rb +13 -0
  654. data/lib/relaton/xsf/item_data.rb +6 -0
  655. data/lib/relaton/xsf/processor.rb +51 -0
  656. data/lib/relaton/xsf/util.rb +9 -0
  657. data/lib/relaton/xsf.rb +34 -0
  658. data/lib/relaton-core.rb +1 -0
  659. data/lib/relaton.rb +85 -0
  660. metadata +775 -161
  661. data/.github/workflows/rake.yml +0 -14
  662. data/.github/workflows/release.yml +0 -24
  663. data/.github/workflows/rubocop.yml +0 -48
  664. data/.gitignore +0 -12
  665. data/.rspec +0 -3
  666. data/CLAUDE.md +0 -78
  667. data/Gemfile +0 -29
  668. data/Rakefile +0 -6
  669. data/docs/CHANGELOG.adoc +0 -16
  670. data/docs/VERSIONING_POLICY.adoc +0 -38
  671. data/docs/navigation.adoc +0 -6
  672. data/lib/relaton/db/version.rb +0 -5
  673. data/relaton.gemspec +0 -72
  674. data/spec/relaton/config_spec.rb +0 -10
  675. data/spec/relaton/db_cache_spec.rb +0 -51
  676. data/spec/relaton/db_spec.rb +0 -567
  677. data/spec/relaton/registry_spec.rb +0 -178
  678. data/spec/relaton/util_spec.rb +0 -3
  679. data/spec/relaton_meta_spec.rb +0 -25
  680. data/spec/relaton_spec.rb +0 -800
  681. data/spec/spec_helper.rb +0 -45
  682. data/spec/support/gb_t_20223_2006.xml +0 -33
  683. data/spec/support/iso_111111119115_1.xml +0 -0
  684. data/spec/support/iso_19115_1.xml +0 -115
  685. data/spec/support/iso_19115_2.xml +0 -95
  686. data/spec/support/rfc_8341.xml +0 -46
  687. data/spec/vcr_cassetes/api_relaton_org.yml +0 -121
  688. data/spec/vcr_cassetes/api_relaton_org_unavailable.yml +0 -140
  689. data/spec/vcr_cassetes/cc_dir_10005_2019.yml +0 -202
  690. data/spec/vcr_cassetes/cipm_meeting_43.yml +0 -1505
  691. data/spec/vcr_cassetes/gb_t_20223_2006.yml +0 -729
  692. data/spec/vcr_cassetes/iso_111111119115_1.yml +0 -12951
  693. data/spec/vcr_cassetes/iso_19115_1.yml +0 -17334
  694. data/spec/vcr_cassetes/iso_19115_1_2.yml +0 -322
  695. data/spec/vcr_cassetes/iso_19115_1_std.yml +0 -17334
  696. data/spec/vcr_cassetes/iso_19115_all_parts.yml +0 -185
  697. data/spec/vcr_cassetes/iso_19133_2005.yml +0 -144
  698. data/spec/vcr_cassetes/iso_combined_applied.yml +0 -318
  699. data/spec/vcr_cassetes/iso_combined_included.yml +0 -318
  700. data/spec/vcr_cassetes/ogc_19_025r1.yml +0 -374
  701. data/spec/vcr_cassetes/omg_ami4ccm_1_0.yml +0 -317
  702. data/spec/vcr_cassetes/rfc_8341.yml +0 -1278
@@ -0,0 +1,771 @@
1
+ require "etc"
2
+ require "zip"
3
+ require_relative "../ieee"
4
+ require_relative "converter/bibxml"
5
+ require_relative "idams_parser"
6
+ require_relative "rawbib_id_parser"
7
+
8
+ module Relaton
9
+ module Ieee
10
+ class DataFetcher < Core::DataFetcher
11
+ RELATION_TYPES = {
12
+ "S" => { type: "obsoletedBy" },
13
+ "V" => { type: "updates", description: "revises" },
14
+ "T" => { type: "updates", description: "amends" },
15
+ "C" => { type: "updates", description: "corrects" },
16
+ "O" => { type: "adoptedFrom" },
17
+ "P" => { type: "complementOf", description: "supplement" },
18
+ "N" => false, "G" => false,
19
+ "F" => false, "I" => false,
20
+ "E" => false, "B" => false, "W" => false
21
+ }.freeze
22
+
23
+ # Publication subtypes that IEEE does not give a designation to. Such a
24
+ # record carries a category in `stdnumber` ("White Paper", "Smart Grid
25
+ # Research: …") and a title in `normtitle`, and IEEE itself cites it by
26
+ # title. See {#no_standard_number?}.
27
+ NON_STANDARD_SUBTYPES = ["Whitepapers", "Research Documents"].freeze
28
+
29
+ class << self
30
+ #
31
+ # Build the pubid-structured `index-v2` from a directory of already
32
+ # generated per-document BibYAML files (the output of {.fetch}). This is
33
+ # a **separate step** from doc fetching: `relaton-data-ieee`'s crawler
34
+ # runs it after obtaining `data/` (a fresh crawl, or a clone of the
35
+ # published data repo). It parses each document's primary IEEE
36
+ # docidentifier into a `Pubid::Ieee::Identifier` and writes the index
37
+ # keyed by `pubid_class: ::Pubid::Ieee::Identifier`, so rows serialize to
38
+ # the `_type: pubid:ieee:*` structured form.
39
+ #
40
+ # Ids that pubid can't parse/round-trip are skipped (they'd otherwise
41
+ # break the all-pubid index that FileIO deserializes and number-sorts);
42
+ # the per-file skip and the final coverage summary are logged so the loss
43
+ # is never silent.
44
+ #
45
+ # @param dir [String] directory holding the per-document `*.yaml` files
46
+ # @param index_file [String] output index filename
47
+ # @return [Hash] `{ total:, indexed:, skipped: }` counts
48
+ #
49
+ def build_index(dir: "data", index_file: "#{INDEXFILE}.yaml")
50
+ index = Relaton::Index.find_or_create(
51
+ :ieee, file: index_file, pubid_class: ::Pubid::Ieee::Identifier
52
+ )
53
+ total = indexed = 0
54
+ Dir["#{dir}/*.yaml"].sort.each do |file|
55
+ total += 1
56
+ docid = primary_docid(file)
57
+ pid = docid && pubid(docid)
58
+ if pid
59
+ index.add_or_update pid, file
60
+ indexed += 1
61
+ else
62
+ Util.warn "Skipped (unparseable id): `#{docid || '<none>'}` (#{file})"
63
+ end
64
+ end
65
+ index.save
66
+ Util.info "IEEE #{index_file}: #{indexed}/#{total} indexed, " \
67
+ "#{total - indexed} skipped " \
68
+ "(#{total.zero? ? 0 : (100.0 * indexed / total).round(1)}% coverage)"
69
+ { total: total, indexed: indexed, skipped: total - indexed }
70
+ end
71
+
72
+ private
73
+
74
+ # Extract the primary, non-trademark IEEE docidentifier string from a
75
+ # BibYAML file, or nil when absent/unreadable.
76
+ #
77
+ # @param file [String]
78
+ # @return [String, nil]
79
+ def primary_docid(file)
80
+ yaml = YAML.safe_load(File.read(file, encoding: "UTF-8"),
81
+ permitted_classes: [Symbol, Date, Time])
82
+ id = yaml && yaml["docidentifier"]&.find do |i|
83
+ i["type"] == "IEEE" && i["primary"] == true && i["trademark"].nil?
84
+ end
85
+ id && id["content"]
86
+ rescue StandardError
87
+ nil
88
+ end
89
+
90
+ # Parse a docid string into a Pubid::Ieee::Identifier, or nil if pubid
91
+ # can't parse it or the structured id won't round-trip through the index
92
+ # (matching FileIO's `id_supported?` acceptance test).
93
+ #
94
+ # @param id [String]
95
+ # @return [::Pubid::Ieee::Identifier, nil]
96
+ def pubid(id)
97
+ pid = ::Pubid::Ieee::Identifier.parse id
98
+ hash = pid.to_hash
99
+ return nil unless ::Pubid::Ieee::Identifier.from_hash(hash).to_hash == hash
100
+
101
+ pid
102
+ rescue StandardError
103
+ nil
104
+ end
105
+ end
106
+
107
+ #
108
+ # Convert documents from `ieee-rawbib` dir (IEEE dataset) to BibYAML/BibXML
109
+ #
110
+ def log_error(msg)
111
+ Util.error msg
112
+ end
113
+
114
+ def fetch(_source = nil)
115
+ files = Dir["ieee-rawbib/**/*.{xml,zip}"].reject { |f| f["Deleted_"] }
116
+ files = prefilter_winners(files) unless ENV["IEEE_FETCH_PREFILTER"] == "0"
117
+ process_files(files)
118
+ update_relations
119
+ report_errors
120
+ end
121
+
122
+ # @return [Hash] list of AMSID => PubID
123
+ def backrefs
124
+ @backrefs ||= {}
125
+ end
126
+
127
+ # @return [Hash] list of docnumber => parsed bib (cache for update_relations)
128
+ def docs
129
+ @docs ||= {}
130
+ end
131
+
132
+ # @return [Hash] docnumber => max global glob-index whose write was
133
+ # accepted by commit_doc. Populated only when running with parallel
134
+ # workers (writes are staged to per-glob-index suffixed paths and
135
+ # reconciled into the final filename after the parsing phase).
136
+ def saved_writes
137
+ @saved_writes ||= {}
138
+ end
139
+
140
+ # Mutex guarding worker-thread mutations of shared state during parse.
141
+ def mutex
142
+ @mutex ||= Mutex.new
143
+ end
144
+
145
+ #
146
+ # Save unresolved relation reference. Called from worker threads via
147
+ # IdamsParser#parse_relation, so mutates crossrefs under a mutex.
148
+ #
149
+ # @param [String] docnumber of main document
150
+ # @param [Nokogiri::XML::Element] amsid relation data
151
+ #
152
+ def add_crossref(docnumber, amsid)
153
+ return if RELATION_TYPES[amsid.type] == false
154
+
155
+ ref = { amsid: amsid.date_string, type: amsid.type }
156
+ mutex.synchronize { crossrefs[docnumber] << ref }
157
+ end
158
+
159
+ #
160
+ # Create relation instance
161
+ #
162
+ # @param [String] type IEEE relation type
163
+ # @param [String] fref reference
164
+ #
165
+ # @return [RelatonBib::DocumentRelation]
166
+ #
167
+ def create_relation(type, fref)
168
+ unless RELATION_TYPES.key? type
169
+ Util.warn "Unknown relation type: '#{type}' for reference '#{fref}'", key: fref
170
+ return
171
+ end
172
+ return if RELATION_TYPES[type] == false
173
+
174
+ docid = Bib::Docidentifier.new(type: "IEEE", content: fref, primary: true)
175
+ bib = ItemData.new formattedref: Bib::Formattedref.new(content: fref), docidentifier: [docid]
176
+ description = create_relation_description type
177
+ Bib::Relation.new(type: RELATION_TYPES[type][:type], description: description, bibitem: bib)
178
+ end
179
+
180
+ private
181
+
182
+ def create_relation_description(type)
183
+ desc = RELATION_TYPES[type][:description] if RELATION_TYPES[type]
184
+ return unless desc
185
+
186
+ desc && Bib::LocalizedMarkedUpString.new(content: desc, language: "en", script: "Latn")
187
+ end
188
+
189
+ # @return [Hash] list of PubID => list of unresolved relations
190
+ def crossrefs
191
+ @crossrefs ||= Hash.new { |hash, key| hash[key] = [] }
192
+ end
193
+
194
+ #
195
+ # Extract XML file from zip archive
196
+ #
197
+ # @param [String] file path to achive
198
+ #
199
+ # @return [String] file content
200
+ #
201
+ def read_zip(file)
202
+ Zip::File.open(file) do |zf|
203
+ entry = zf.glob("**/*.xml").first
204
+ entry.get_input_stream.read
205
+ end
206
+ end
207
+
208
+ #
209
+ # Pre-filter the input file list down to the subset that actually
210
+ # has to be fully parsed.
211
+ #
212
+ # The IEEE rawbib dataset has ~50× duplication: every docnumber
213
+ # appears in `cache/` plus most `updates.YYYYMMDD/` folders. The
214
+ # original semantic is "latest update wins on disk", so for any
215
+ # docnumber that has at least one updates-folder file, the cache
216
+ # file's parse result is just thrown away. Pre-filter avoids
217
+ # parsing those throwaway files entirely.
218
+ #
219
+ # The cheap path here only has to extract three small XML elements
220
+ # (normtitle, stdnumber, standard_id) per file — done with
221
+ # regex on the raw XML so we skip lutaml-model's heavy DOM-to-
222
+ # object construction (which is what dominates fetch time).
223
+ #
224
+ # Selection rules:
225
+ # - For each docnumber with any updates-folder entry: keep only
226
+ # the highest-glob-idx updates-folder file.
227
+ # - For docnumbers with cache-folder entries only: keep all
228
+ # of them (commit_doc's matches-stdnumber dedup handles them).
229
+ # - Files where the cheap parse couldn't compute a docnumber
230
+ # are kept as-is — the full parse will surface any real error.
231
+ #
232
+ # Disable with IEEE_FETCH_PREFILTER=0.
233
+ #
234
+ def prefilter_winners(files)
235
+ threshold = Integer(ENV["IEEE_FETCH_PREFILTER_MIN"] || 200)
236
+ return files if files.size < threshold
237
+
238
+ procs = Integer(ENV["IEEE_FETCH_PROCESSES"] || Etc.nprocessors)
239
+ index = procs <= 1 ? prefilter_serial(files) : prefilter_parallel(files, procs)
240
+ select_prefilter_winners(index, files.size)
241
+ end
242
+
243
+ def prefilter_serial(files)
244
+ files.each_with_index.map { |f, i| extract_index_entry(i, f) }.compact
245
+ end
246
+
247
+ def prefilter_parallel(files, procs) # rubocop:disable Metrics/MethodLength
248
+ batch_size = Integer(ENV["IEEE_PREFILTER_BATCH"] || 5000)
249
+ batches = files.each_slice(batch_size).each_with_index.to_a
250
+
251
+ next_batch = 0
252
+ inflight = {}
253
+ collected = []
254
+
255
+ procs.times do
256
+ break if next_batch >= batches.size
257
+
258
+ inflight.merge!(spawn_prefilter_batch(*batches[next_batch], batch_size))
259
+ next_batch += 1
260
+ end
261
+
262
+ until inflight.empty?
263
+ pid = Process.wait
264
+ collected << inflight.delete(pid)
265
+
266
+ if next_batch < batches.size
267
+ inflight.merge!(spawn_prefilter_batch(*batches[next_batch], batch_size))
268
+ next_batch += 1
269
+ end
270
+ end
271
+
272
+ index = []
273
+ collected.each do |path|
274
+ next unless path && File.exist?(path) && File.size(path).positive?
275
+
276
+ index.concat(Marshal.load(File.binread(path)))
277
+ File.unlink(path)
278
+ end
279
+ index
280
+ end
281
+
282
+ def spawn_prefilter_batch(batch_files, batch_idx, batch_size)
283
+ require "tmpdir"
284
+ require "securerandom"
285
+ state_path = File.join(
286
+ Dir.tmpdir,
287
+ "ieee_prefilter_#{Process.pid}_#{batch_idx}_#{SecureRandom.hex(4)}.bin",
288
+ )
289
+ base_idx = batch_idx * batch_size
290
+
291
+ pid = Process.fork do
292
+ entries = batch_files.each_with_index.map do |file, i|
293
+ extract_index_entry(base_idx + i, file)
294
+ end.compact
295
+ File.binwrite(state_path, Marshal.dump(entries))
296
+ exit!(0)
297
+ end
298
+ { pid => state_path }
299
+ end
300
+
301
+ #
302
+ # Cheap-parse one file: read XML, regex-extract three fields,
303
+ # compute docnumber via the existing RawbibIdParser. Returns
304
+ # `[glob_idx, file, docnumber_or_nil, in_updates_folder?]`.
305
+ #
306
+ def extract_index_entry(idx, file)
307
+ xml = case File.extname(file)
308
+ when ".zip" then read_zip(file)
309
+ when ".xml" then File.read(file, encoding: "UTF-8")
310
+ end
311
+ return nil unless xml
312
+ return nil if cheap_extract_field(xml, "standard_id") == "0"
313
+
314
+ normtitle = cheap_extract_field(xml, "normtitle")
315
+ stdnumber = cheap_extract_field(xml, "stdnumber")
316
+ # Drop it here as well as in #parse_entry: the winner selection groups
317
+ # files by docnumber, so a white paper that reads as `IEEE Std 802`
318
+ # would join the group of the real standard and push its file out.
319
+ subtype = cheap_extract_field(xml, "publicationsubtype")
320
+ return nil if no_standard_number?(subtype, stdnumber)
321
+
322
+ docnumber = nil
323
+ if normtitle && stdnumber
324
+ pubid = RawbibIdParser.parse(normtitle, stdnumber)
325
+ # The article title (what `IdamsParser#pubid` compares against) is
326
+ # the *last* `<title>` in the file: the top-level one (first) is the
327
+ # designation, present even for a real standard.
328
+ title = cheap_extract_field(xml, "title", last: true)
329
+ isbn = xml.include?("<isbn")
330
+ fabricated = RawbibIdParser.fabricated_title_id?(pubid, title, isbn)
331
+ docnumber = pubid&.to_s unless fabricated
332
+ end
333
+ [idx, file, docnumber, file.include?("/updates.")]
334
+ rescue StandardError
335
+ # Cheap parse couldn't handle this file — keep it; full parse will
336
+ # either succeed or surface the real error.
337
+ [idx, file, nil, file.include?("/updates.")]
338
+ end
339
+
340
+ #
341
+ # True when the record has no standard number. IEEE gives a white paper
342
+ # or a research document no designation, so `stdnumber` holds a category
343
+ # and `normtitle` holds a title. An id built from either is a title
344
+ # fragment, and records that share a fragment overwrite each other's
345
+ # output file. Both conditions are required: a record of such a subtype
346
+ # that does carry a number keeps it.
347
+ #
348
+ # @param [String, nil] subtype document element "publicationsubtype"
349
+ # @param [String, nil] stdnumber document element "stdnumber"
350
+ #
351
+ # @return [Boolean]
352
+ #
353
+ def no_standard_number?(subtype, stdnumber)
354
+ NON_STANDARD_SUBTYPES.include?(subtype) && !stdnumber.to_s.match?(/\d/)
355
+ end
356
+
357
+ def cheap_extract_field(xml, tag, last: false)
358
+ re = %r{<#{tag}[^>]*?>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</#{tag}>}m
359
+ matches = xml.scan(re)
360
+ m = last ? matches.last : matches.first
361
+ m && m[0].strip
362
+ end
363
+
364
+ def select_prefilter_winners(index, total)
365
+ unknown = index.select { |e| e[2].nil? }
366
+ by_doc = index.reject { |e| e[2].nil? }.group_by { |e| e[2] }
367
+
368
+ selected = []
369
+ by_doc.each_value do |entries|
370
+ updates = entries.select { |e| e[3] }
371
+ if updates.any?
372
+ selected << updates.max_by { |e| e[0] }
373
+ else
374
+ selected.concat(entries)
375
+ end
376
+ end
377
+
378
+ kept = (selected + unknown).sort_by { |e| e[0] }.map { |e| e[1] }
379
+ Util.warn "Prefilter: #{total} input files -> #{kept.size} winners " \
380
+ "(#{(100.0 * kept.size / total).round(1)}%)"
381
+ kept
382
+ end
383
+
384
+ #
385
+ # Parse files across a pool of short-lived forked workers. Each
386
+ # worker processes one bounded batch (IEEE_FETCH_BATCH files,
387
+ # default 5000), writes its output YAMLs to disk, marshals its
388
+ # local backrefs / crossrefs / errors to a tmp file, and exits.
389
+ # The parent keeps `procs` workers in flight; as each one exits
390
+ # it merges that worker's state and spawns the next batch.
391
+ #
392
+ # Why short-lived workers, not one long-running shard per core:
393
+ # Ruby's heap grows monotonically and the VM doesn't return
394
+ # freed memory to the OS, so a child that parses 50k files ends
395
+ # up at 1+ GB RSS even with the docs cache disabled. With ten
396
+ # such children the box swaps and slows to a crawl. Exiting a
397
+ # child after a batch of a few thousand files lets the OS
398
+ # reclaim its heap; the next fork starts fresh from the parent's
399
+ # baseline. Fork is cheap (copy-on-write), so the overhead is
400
+ # negligible compared to the memory savings.
401
+ #
402
+ # Caveats from sharding (same as the previous design):
403
+ # - Cross-batch duplicates: when the same docnumber appears in
404
+ # multiple batches, the last-finishing batch's write wins.
405
+ # Merged backrefs/crossrefs are still complete, so
406
+ # update_relations resolves cross-refs correctly.
407
+ # - "Document exists" warnings are per-batch, so cross-batch
408
+ # duplicates may not log a warning. Logging only.
409
+ #
410
+ # @param [Array<String>] files paths to rawbib XML/zip files
411
+ #
412
+ def process_files(files) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
413
+ procs = Integer(ENV["IEEE_FETCH_PROCESSES"] || Etc.nprocessors)
414
+ procs = 1 if files.empty? || procs < 2 || files.size < procs * 2
415
+
416
+ return run_shard(files, 0) if procs <= 1
417
+
418
+ batch_size = Integer(ENV["IEEE_FETCH_BATCH"] || 1000)
419
+ batches = files.each_slice(batch_size).each_with_index.to_a
420
+
421
+ state_paths = run_worker_pool(batches, procs)
422
+ merge_state_files(state_paths)
423
+ reconcile_staged_outputs
424
+ end
425
+
426
+ #
427
+ # Promote the highest-glob-index staged write per docnumber to its
428
+ # final on-disk filename, then delete any leftover staged files.
429
+ # Restores exact "latest update wins" semantics across batches:
430
+ # without this pass, a slow batch finishing late could overwrite a
431
+ # newer update that an earlier-completing batch had already saved.
432
+ #
433
+ def reconcile_staged_outputs
434
+ return if saved_writes.empty?
435
+
436
+ saved_writes.each do |docnumber, max_idx|
437
+ final = output_file(docnumber)
438
+ winner = "#{final}.#{max_idx}"
439
+ File.rename(winner, final) if File.exist?(winner)
440
+ end
441
+
442
+ # Stragglers: any remaining staged files (losing duplicates,
443
+ # or bib filenames that didn't end up in saved_writes due to a
444
+ # crash) get cleaned up so they don't pollute `data/`. Log the
445
+ # count so an unexpectedly large sweep (e.g. a worker that lost
446
+ # its state) is visible instead of silent.
447
+ stragglers = Dir.glob(File.join(@output, "*.#{@ext}.*"))
448
+ stragglers.each do |f|
449
+ File.unlink(f)
450
+ rescue StandardError
451
+ # ignore — best-effort cleanup
452
+ end
453
+ Util.info "Reconcile: cleaned #{stragglers.size} staged straggler(s)" unless stragglers.empty?
454
+ end
455
+
456
+ #
457
+ # Merge all batch state files into the parent's hashes. Runs once,
458
+ # after the worker pool has drained, so the parent's heap only
459
+ # has to hold the cumulative merged state (small) plus one batch's
460
+ # transient marshaled payload at a time.
461
+ #
462
+ def merge_state_files(state_paths)
463
+ state_paths.each_with_index do |path, i|
464
+ merge_batch_state(path)
465
+ # Periodic GC.start keeps the transient marshal allocations
466
+ # from piling up over hundreds of merges.
467
+ GC.start if (i % 50).zero?
468
+ end
469
+ end
470
+
471
+ #
472
+ # Maintain `procs` concurrent short-lived workers. Each Process.wait
473
+ # call blocks until any worker exits; we collect its state-file
474
+ # path and spawn the next batch (if any).
475
+ #
476
+ # Critically, we do NOT merge state into the parent's hashes here.
477
+ # Loading and merging dozens of MB of marshaled hashes per batch
478
+ # bloated the parent's heap into the multi-GB range, and every
479
+ # subsequent fork inherited that bloat via copy-on-write — driving
480
+ # the box into swap. By deferring all merging to after the parsing
481
+ # phase, the parent stays at ~baseline RSS while children are alive,
482
+ # so each fork's COW baseline is small.
483
+ #
484
+ # @return [Array<String>] state-file paths in completion order
485
+ #
486
+ def run_worker_pool(batches, procs) # rubocop:disable Metrics/MethodLength
487
+ next_batch = 0
488
+ inflight = {} # pid => state_path
489
+ collected = []
490
+
491
+ procs.times do
492
+ break if next_batch >= batches.size
493
+
494
+ inflight.merge!(spawn_batch(*batches[next_batch]))
495
+ next_batch += 1
496
+ end
497
+
498
+ until inflight.empty?
499
+ pid = Process.wait
500
+ collected << inflight.delete(pid)
501
+
502
+ if next_batch < batches.size
503
+ inflight.merge!(spawn_batch(*batches[next_batch]))
504
+ next_batch += 1
505
+ end
506
+ end
507
+
508
+ collected
509
+ end
510
+
511
+ #
512
+ # Fork one short-lived worker for a single batch. Returns a
513
+ # `{pid => state_path}` Hash. The worker writes its marshaled
514
+ # local state to `state_path` then exits; the tmp file is read
515
+ # and unlinked by the parent in `merge_batch_state`.
516
+ #
517
+ def spawn_batch(batch_files, batch_idx) # rubocop:disable Metrics/MethodLength
518
+ require "tmpdir"
519
+ require "securerandom"
520
+ state_path = File.join(
521
+ Dir.tmpdir,
522
+ "ieee_fetch_#{Process.pid}_#{batch_idx}_#{SecureRandom.hex(4)}.bin",
523
+ )
524
+ base_idx = batch_idx * Integer(ENV["IEEE_FETCH_BATCH"] || 1000)
525
+
526
+ pid = Process.fork do
527
+ batch_files.each_with_index do |file, i|
528
+ glob_idx = base_idx + i
529
+ commit_entry(glob_idx, file, glob_idx)
530
+ end
531
+ File.binwrite(state_path, Marshal.dump(
532
+ backrefs: backrefs,
533
+ crossrefs: {}.merge(crossrefs),
534
+ errors: {}.merge(@errors),
535
+ saved_writes: saved_writes,
536
+ ))
537
+ exit!(0)
538
+ end
539
+
540
+ { pid => state_path }
541
+ end
542
+
543
+ #
544
+ # Read one batch's marshaled state, merge into parent state,
545
+ # remove the tmp file. Tolerates a missing/empty file (worker
546
+ # crash) by treating it as an empty merge.
547
+ #
548
+ def merge_batch_state(state_path)
549
+ if state_path && File.exist?(state_path) && File.size(state_path).positive?
550
+ payload = Marshal.load(File.binread(state_path))
551
+ merge_shard_state(payload)
552
+ end
553
+ ensure
554
+ File.unlink(state_path) if state_path && File.exist?(state_path)
555
+ end
556
+
557
+ #
558
+ # Merge one child's per-shard state into the parent's. backrefs uses
559
+ # ||= so the lowest-shard-id value wins for any amsid/docnumber pair
560
+ # that happens to appear in multiple shards (in practice they agree).
561
+ # `saved_writes` tracks the highest glob-index at which any worker
562
+ # saved a doc, so the parent can later rename the winning staged
563
+ # file to its final name.
564
+ #
565
+ def merge_shard_state(state)
566
+ state[:backrefs].each { |amsid, content| backrefs[amsid] ||= content }
567
+ state[:crossrefs].each { |dnum, refs| crossrefs[dnum].concat(refs) }
568
+ state[:errors].each { |k, v| @errors[k] &&= v }
569
+ (state[:saved_writes] || {}).each do |dnum, idx|
570
+ prev = saved_writes[dnum]
571
+ saved_writes[dnum] = idx if prev.nil? || idx > prev
572
+ end
573
+ end
574
+
575
+ #
576
+ # Process one shard sequentially. Either runs in a forked child or,
577
+ # when procs == 1, in the parent.
578
+ #
579
+ # `shard` is an array of [original_idx, file] tuples (or, when
580
+ # called from the procs==1 fallback, just the array of file paths
581
+ # — we normalize below).
582
+ #
583
+ def run_shard(shard, _shard_idx)
584
+ shard.each_with_index do |entry, i|
585
+ idx, file = entry.is_a?(Array) ? entry : [i, entry]
586
+ commit_entry(idx, file)
587
+ end
588
+ end
589
+
590
+ #
591
+ # Parse one file and commit it, guarding the commit so a single bad
592
+ # document (e.g. a pathological docnumber that would raise
593
+ # Errno::ENAMETOOLONG) logs and is skipped rather than aborting the
594
+ # serial crawl or killing a parallel worker before it persists its
595
+ # state. Shared by the serial (`run_shard`) and parallel
596
+ # (`spawn_batch`) paths.
597
+ #
598
+ # @param [Integer] idx original glob index (preserves dedup order)
599
+ # @param [String] file path to rawbib file
600
+ # @param [Integer, nil] glob_idx staging index for parallel mode
601
+ #
602
+ def commit_entry(idx, file, glob_idx = nil)
603
+ result = parse_entry(idx, file)
604
+ return unless result
605
+
606
+ _, _, doc, bib, local_errors = result
607
+ merge_errors(local_errors)
608
+ begin
609
+ commit_doc(doc, bib, file, glob_idx)
610
+ rescue StandardError => e
611
+ Util.error "commit failed for `#{file}`: #{e.class}: #{e.message}"
612
+ end
613
+ end
614
+
615
+ #
616
+ # Worker-thread entry point: read file, parse XML, build bib.
617
+ # Returns nil for files we should skip; otherwise a tuple consumed
618
+ # in submission order by the main-thread commit loop.
619
+ #
620
+ # @param [Integer] idx original glob index (preserves dedup order)
621
+ # @param [String] file path to rawbib file
622
+ #
623
+ # @return [Array, nil] [idx, file, doc, bib, local_errors] or nil
624
+ #
625
+ def parse_entry(idx, file)
626
+ xml = case File.extname(file)
627
+ when ".zip" then read_zip file
628
+ when ".xml" then File.read file, encoding: "UTF-8"
629
+ end
630
+ doc = begin
631
+ ::Ieee::Idams::Publication.from_xml(xml)
632
+ rescue StandardError
633
+ Util.warn "Empty file: `#{file}`"
634
+ return nil
635
+ end
636
+ return nil if doc.publicationinfo&.standard_id == "0"
637
+
638
+ info = doc.publicationinfo
639
+ if no_standard_number?(info&.publicationsubtype, info&.stdnumber)
640
+ Util.warn "No standard number. Normtitle: `#{doc.normtitle}`, " \
641
+ "file: `#{file}`"
642
+ return nil
643
+ end
644
+
645
+ local_errors = Hash.new(true)
646
+ bib = IdamsParser.new(doc, self, local_errors).parse
647
+ if bib.docnumber.nil?
648
+ Util.warn "PubID parse error. Normtitle: `#{doc.normtitle}`, file: `#{file}`"
649
+ return nil
650
+ end
651
+ [idx, file, doc, bib, local_errors]
652
+ rescue StandardError => e
653
+ Util.error "File: #{file}\n#{e.message}\n#{e.backtrace}"
654
+ nil
655
+ end
656
+
657
+ #
658
+ # Merge a worker's local errors hash into the shared @errors hash,
659
+ # preserving the existing AND semantics (`@errors[k] &&= v`).
660
+ #
661
+ def merge_errors(local_errors)
662
+ local_errors.each { |k, v| @errors[k] &&= v }
663
+ end
664
+
665
+ #
666
+ # Dedup against backrefs and save. This runs once per parsed file —
667
+ # in the parent for the procs==1 fallback, or in each forked child
668
+ # for its shard. Same logic the old fetch_doc tail had, plus
669
+ # optional staged-output bookkeeping when `glob_idx` is provided.
670
+ #
671
+ # When `glob_idx` is given (parallel mode), save_doc writes to a
672
+ # per-glob-index suffixed path; the parent reconciles after the
673
+ # parsing phase and renames the highest-glob-index winner per
674
+ # docnumber to the final filename. This preserves the original
675
+ # "latest update wins on disk" semantic across batch boundaries
676
+ # — without it, a slow batch finishing late could overwrite a
677
+ # newer update written by an earlier-completing batch.
678
+ #
679
+ def commit_doc(doc, bib, filename, glob_idx = nil)
680
+ amsid = doc.publicationinfo.amsid
681
+ if backrefs.value?(bib.docidentifier[0].content) && /updates\.\d+/ !~ filename
682
+ oamsid = backrefs.key bib.docidentifier[0].content
683
+ Util.warn "Document exists ID: `#{bib.docidentifier[0].content}` AMSID: " \
684
+ "`#{amsid}` source: `#{filename}`. Other AMSID: `#{oamsid}`"
685
+ if bib.docidentifier.find(&:primary).content.include?(doc.publicationinfo.stdnumber)
686
+ save_doc(bib, glob_idx) # rewrite file if the PubID matches to the stdnumber
687
+ backrefs[amsid] = bib.docidentifier[0].content
688
+ track_save(bib.docnumber, glob_idx)
689
+ end
690
+ else
691
+ save_doc(bib, glob_idx)
692
+ backrefs[amsid] = bib.docidentifier[0].content
693
+ track_save(bib.docnumber, glob_idx)
694
+ end
695
+ end
696
+
697
+ #
698
+ # Record that we wrote a staged copy of `docnumber` at this
699
+ # `glob_idx`. The parent later picks the highest tracked idx
700
+ # per docnumber as the surviving on-disk version.
701
+ #
702
+ def track_save(docnumber, glob_idx)
703
+ return unless glob_idx
704
+
705
+ prev = saved_writes[docnumber]
706
+ saved_writes[docnumber] = glob_idx if prev.nil? || glob_idx > prev
707
+ end
708
+
709
+ #
710
+ # Save document to file. When `glob_idx` is provided (parallel
711
+ # mode), writes to a per-glob-index suffixed staging path so
712
+ # concurrent workers can't clobber each other's files; the parent
713
+ # reconciles after parsing. With no glob_idx, writes the final
714
+ # filename directly (sequential mode and update_relations).
715
+ #
716
+ # @param [RelatonIeee::IeeeBibliographicItem] bib
717
+ # @param [Integer, nil] glob_idx position in the original file glob
718
+ #
719
+ def save_doc(bib, glob_idx = nil)
720
+ path = output_file(bib.docnumber)
721
+ path = "#{path}.#{glob_idx}" if glob_idx
722
+ File.write path, serialize(bib), encoding: "UTF-8"
723
+ end
724
+
725
+ def to_yaml(bib) = bib.to_yaml
726
+ def to_xml(bib) = bib.to_xml(bibdata: true)
727
+ def to_bibxml(bib) = bib.to_rfcxml
728
+
729
+ #
730
+ # Resolve cross-references collected during parse. Uses the in-memory
731
+ # `docs` cache so we don't re-read+re-deserialize files from disk, and
732
+ # writes each mutated bib once instead of once per relation.
733
+ #
734
+ def update_relations # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
735
+ crossrefs.each do |dnum, rfs|
736
+ bib = nil
737
+ mutated = false
738
+ rfs.each do |rf|
739
+ if backrefs[rf[:amsid]]
740
+ rel = create_relation(rf[:type], backrefs[rf[:amsid]])
741
+ if rel
742
+ bib ||= docs[dnum] || read_bib(dnum)
743
+ bib.relation << rel
744
+ mutated = true
745
+ end
746
+ else
747
+ Util.warn "Unresolved relation: '#{rf[:amsid]}' type: '#{rf[:type]}' for '#{dnum}'"
748
+ end
749
+ end
750
+ save_doc(bib) if mutated
751
+ end
752
+ end
753
+
754
+ #
755
+ # Read document form BibXML/BibYAML file
756
+ #
757
+ # @param [String] docnumber
758
+ #
759
+ # @return [RelatonIeee::IeeeBibliographicItem]
760
+ #
761
+ def read_bib(docnumber)
762
+ c = File.read output_file(docnumber), encoding: "UTF-8"
763
+ case @format
764
+ when "xml" then Item.from_xml c
765
+ when "bibxml" then Converter::BibXml.to_item c
766
+ else Item.from_yaml c
767
+ end
768
+ end
769
+ end
770
+ end
771
+ end