relaton 3.0.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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +57 -1
  3. data/bin/console +0 -1
  4. data/lib/relaton/3gpp/bibliography.rb +82 -7
  5. data/lib/relaton/3gpp/data_fetcher.rb +51 -3
  6. data/lib/relaton/3gpp/docidentifier.rb +114 -0
  7. data/lib/relaton/3gpp/item.rb +6 -0
  8. data/lib/relaton/3gpp/parser.rb +1 -1
  9. data/lib/relaton/3gpp/processor.rb +4 -1
  10. data/lib/relaton/3gpp.rb +5 -1
  11. data/lib/relaton/adobe/bibdata.rb +8 -0
  12. data/lib/relaton/adobe/bibitem.rb +8 -0
  13. data/lib/relaton/adobe/bibliography.rb +92 -0
  14. data/lib/relaton/adobe/docidentifier.rb +49 -0
  15. data/lib/relaton/adobe/doctype.rb +14 -0
  16. data/lib/relaton/adobe/ext.rb +32 -0
  17. data/lib/relaton/adobe/item.rb +15 -0
  18. data/lib/relaton/adobe/item_base.rb +18 -0
  19. data/lib/relaton/adobe/item_data.rb +6 -0
  20. data/lib/relaton/adobe/processor.rb +45 -0
  21. data/lib/relaton/adobe/util.rb +8 -0
  22. data/lib/relaton/adobe.rb +37 -0
  23. data/lib/relaton/bib/model/address.rb +2 -2
  24. data/lib/relaton/bib/model/docidentifier.rb +24 -9
  25. data/lib/relaton/bib/model/localized_string.rb +1 -1
  26. data/lib/relaton/bib/model/structured_identifier.rb +10 -9
  27. data/lib/relaton/bib/sanitizer.rb +202 -6
  28. data/lib/relaton/bib.rb +0 -2
  29. data/lib/relaton/bipm/bibliography.rb +159 -10
  30. data/lib/relaton/bipm/data_fetcher.rb +26 -2
  31. data/lib/relaton/bipm/data_outcomes_parser.rb +3 -4
  32. data/lib/relaton/bipm/id_parser.rb +5 -4
  33. data/lib/relaton/bipm/model/structured_identifier.rb +21 -0
  34. data/lib/relaton/bipm/processor.rb +2 -2
  35. data/lib/relaton/bipm/rawdata_bipm_metrologia/fetcher.rb +2 -4
  36. data/lib/relaton/bipm/si_brochure_parser.rb +5 -3
  37. data/lib/relaton/bipm.rb +7 -1
  38. data/lib/relaton/bsi/bibliography.rb +115 -43
  39. data/lib/relaton/bsi/hit.rb +14 -0
  40. data/lib/relaton/bsi/hit_collection.rb +15 -16
  41. data/lib/relaton/bsi/model/docidentifier.rb +99 -1
  42. data/lib/relaton/bsi/processor.rb +1 -0
  43. data/lib/relaton/calconnect/bibliography.rb +12 -14
  44. data/lib/relaton/calconnect/data_fetcher.rb +77 -9
  45. data/lib/relaton/calconnect/docidentifier.rb +80 -0
  46. data/lib/relaton/calconnect/hit_collection.rb +65 -57
  47. data/lib/relaton/calconnect/model/item.rb +7 -0
  48. data/lib/relaton/calconnect/processor.rb +7 -1
  49. data/lib/relaton/calconnect.rb +11 -1
  50. data/lib/relaton/ccsds/data/fetcher.rb +17 -12
  51. data/lib/relaton/ccsds/data/parser.rb +1 -1
  52. data/lib/relaton/ccsds/hit_collection.rb +6 -1
  53. data/lib/relaton/ccsds/model/docidentifier.rb +121 -0
  54. data/lib/relaton/ccsds/model/item.rb +2 -0
  55. data/lib/relaton/cen/bibliography.rb +75 -47
  56. data/lib/relaton/cen/hit.rb +16 -1
  57. data/lib/relaton/cen/hit_collection.rb +59 -11
  58. data/lib/relaton/cen/model/docidentifier.rb +92 -1
  59. data/lib/relaton/cen/processor.rb +13 -8
  60. data/lib/relaton/cen/scraper.rb +13 -5
  61. data/lib/relaton/cen.rb +1 -0
  62. data/lib/relaton/cie/data_fetcher.rb +215 -30
  63. data/lib/relaton/cie/processor.rb +3 -1
  64. data/lib/relaton/cie/scrapper.rb +15 -2
  65. data/lib/relaton/cie.rb +2 -1
  66. data/lib/relaton/core/data_fetcher.rb +150 -3
  67. data/lib/relaton/core/governor.rb +320 -0
  68. data/lib/relaton/core/pacer.rb +134 -0
  69. data/lib/relaton/core/processor.rb +19 -0
  70. data/lib/relaton/core/request_error.rb +14 -0
  71. data/lib/relaton/core.rb +3 -0
  72. data/lib/relaton/db/registry.rb +41 -1
  73. data/lib/relaton/doi/crossref.rb +19 -2
  74. data/lib/relaton/doi/parser.rb +109 -15
  75. data/lib/relaton/easc/bibdata.rb +8 -0
  76. data/lib/relaton/easc/bibitem.rb +8 -0
  77. data/lib/relaton/easc/bibliography.rb +95 -0
  78. data/lib/relaton/easc/docidentifier.rb +100 -0
  79. data/lib/relaton/easc/doctype.rb +14 -0
  80. data/lib/relaton/easc/ext.rb +44 -0
  81. data/lib/relaton/easc/item.rb +13 -0
  82. data/lib/relaton/easc/item_base.rb +18 -0
  83. data/lib/relaton/easc/item_data.rb +6 -0
  84. data/lib/relaton/easc/processor.rb +46 -0
  85. data/lib/relaton/easc/util.rb +8 -0
  86. data/lib/relaton/easc.rb +35 -0
  87. data/lib/relaton/ecma/bibliography.rb +93 -25
  88. data/lib/relaton/ecma/data_fetcher.rb +71 -12
  89. data/lib/relaton/ecma/docidentifier.rb +124 -0
  90. data/lib/relaton/ecma/item.rb +2 -0
  91. data/lib/relaton/ecma/memento_parser.rb +1 -1
  92. data/lib/relaton/ecma/page_fetcher.rb +15 -3
  93. data/lib/relaton/ecma/parser_common.rb +2 -2
  94. data/lib/relaton/ecma/processor.rb +4 -1
  95. data/lib/relaton/ecma/standard_parser.rb +2 -2
  96. data/lib/relaton/ecma.rb +10 -1
  97. data/lib/relaton/etsi/bibliography.rb +67 -2
  98. data/lib/relaton/etsi/data_fetcher.rb +43 -4
  99. data/lib/relaton/etsi/processor.rb +3 -1
  100. data/lib/relaton/etsi.rb +2 -1
  101. data/lib/relaton/gb/bibliography.rb +55 -29
  102. data/lib/relaton/gb/docidentifier.rb +58 -9
  103. data/lib/relaton/gb/processor.rb +3 -0
  104. data/lib/relaton/gb/scraper.rb +27 -10
  105. data/lib/relaton/gost/bibdata.rb +8 -0
  106. data/lib/relaton/gost/bibitem.rb +8 -0
  107. data/lib/relaton/gost/bibliography.rb +107 -0
  108. data/lib/relaton/gost/docidentifier.rb +80 -0
  109. data/lib/relaton/gost/doctype.rb +16 -0
  110. data/lib/relaton/gost/ext.rb +46 -0
  111. data/lib/relaton/gost/item.rb +15 -0
  112. data/lib/relaton/gost/item_base.rb +18 -0
  113. data/lib/relaton/gost/item_data.rb +6 -0
  114. data/lib/relaton/gost/processor.rb +49 -0
  115. data/lib/relaton/gost/util.rb +8 -0
  116. data/lib/relaton/gost.rb +36 -0
  117. data/lib/relaton/iala/bibdata.rb +8 -0
  118. data/lib/relaton/iala/bibitem.rb +8 -0
  119. data/lib/relaton/iala/bibliography.rb +146 -0
  120. data/lib/relaton/iala/docidentifier.rb +89 -0
  121. data/lib/relaton/iala/doctype.rb +18 -0
  122. data/lib/relaton/iala/ext.rb +32 -0
  123. data/lib/relaton/iala/item.rb +21 -0
  124. data/lib/relaton/iala/item_base.rb +18 -0
  125. data/lib/relaton/iala/item_data.rb +6 -0
  126. data/lib/relaton/iala/processor.rb +43 -0
  127. data/lib/relaton/iala/relation.rb +7 -0
  128. data/lib/relaton/iala/util.rb +8 -0
  129. data/lib/relaton/iala.rb +35 -0
  130. data/lib/relaton/iana/bibliography.rb +67 -14
  131. data/lib/relaton/iana/data_fetcher.rb +35 -5
  132. data/lib/relaton/iana/processor.rb +3 -1
  133. data/lib/relaton/iana.rb +12 -1
  134. data/lib/relaton/iec/data_fetcher.rb +7 -1
  135. data/lib/relaton/iec/hit_collection.rb +1 -1
  136. data/lib/relaton/iec/model/docidentifier.rb +9 -5
  137. data/lib/relaton/iec/model/ext.rb +2 -2
  138. data/lib/relaton/iec/processor.rb +1 -0
  139. data/lib/relaton/ieee/bibliography.rb +25 -3
  140. data/lib/relaton/ieee/data_fetcher.rb +158 -17
  141. data/lib/relaton/ieee/idams_parser.rb +18 -11
  142. data/lib/relaton/ieee/processor.rb +4 -1
  143. data/lib/relaton/ieee/rawbib_id_parser.rb +291 -86
  144. data/lib/relaton/ieee.rb +2 -1
  145. data/lib/relaton/ietf/data_fetcher.rb +295 -12
  146. data/lib/relaton/ietf/processor.rb +7 -3
  147. data/lib/relaton/ietf/rfc/entry.rb +39 -3
  148. data/lib/relaton/ietf/scraper.rb +69 -36
  149. data/lib/relaton/ietf.rb +4 -1
  150. data/lib/relaton/iho/bibliography.rb +1 -1
  151. data/lib/relaton/iho/docidentifier.rb +1 -1
  152. data/lib/relaton/index/file_io.rb +11 -11
  153. data/lib/relaton/index/file_storage.rb +6 -1
  154. data/lib/relaton/index/pool.rb +6 -1
  155. data/lib/relaton/index/shard_source.rb +201 -0
  156. data/lib/relaton/index/type.rb +63 -12
  157. data/lib/relaton/index.rb +2 -1
  158. data/lib/relaton/iso/bibliography.rb +20 -15
  159. data/lib/relaton/iso/data_fetcher.rb +3 -3
  160. data/lib/relaton/iso/data_parser.rb +17 -3
  161. data/lib/relaton/iso/hit_collection.rb +27 -15
  162. data/lib/relaton/iso/item_data.rb +22 -0
  163. data/lib/relaton/iso/model/docidentifier.rb +24 -12
  164. data/lib/relaton/iso/processor.rb +1 -0
  165. data/lib/relaton/iso/scraper.rb +19 -3
  166. data/lib/relaton/itu/bibliography.rb +9 -4
  167. data/lib/relaton/itu/data_crawler_r.rb +664 -0
  168. data/lib/relaton/itu/data_fetcher.rb +496 -50
  169. data/lib/relaton/itu/data_merge_r.rb +149 -0
  170. data/lib/relaton/itu/data_parser_r.rb +163 -89
  171. data/lib/relaton/itu/data_parser_t.rb +228 -0
  172. data/lib/relaton/itu/family_cache.rb +177 -0
  173. data/lib/relaton/itu/governor.rb +56 -0
  174. data/lib/relaton/itu/hit.rb +9 -3
  175. data/lib/relaton/itu/hit_collection.rb +258 -86
  176. data/lib/relaton/itu/model/docidentifier.rb +67 -1
  177. data/lib/relaton/itu/model/structured_identifier.rb +19 -0
  178. data/lib/relaton/itu/processor.rb +10 -4
  179. data/lib/relaton/itu/pubid.rb +27 -5
  180. data/lib/relaton/itu/recommendation_fields.rb +334 -0
  181. data/lib/relaton/itu/recommendation_parser.rb +18 -149
  182. data/lib/relaton/itu/scraper.rb +13 -3
  183. data/lib/relaton/itu.rb +2 -1
  184. data/lib/relaton/jcgm/bibdata.rb +8 -0
  185. data/lib/relaton/jcgm/bibitem.rb +8 -0
  186. data/lib/relaton/jcgm/bibliography.rb +97 -0
  187. data/lib/relaton/jcgm/data_fetcher.rb +81 -0
  188. data/lib/relaton/jcgm/docidentifier.rb +102 -0
  189. data/lib/relaton/jcgm/doctype.rb +12 -0
  190. data/lib/relaton/jcgm/ext.rb +23 -0
  191. data/lib/relaton/jcgm/item.rb +20 -0
  192. data/lib/relaton/jcgm/item_base.rb +18 -0
  193. data/lib/relaton/jcgm/item_data.rb +6 -0
  194. data/lib/relaton/jcgm/meetings_parser.rb +175 -0
  195. data/lib/relaton/jcgm/processor.rb +71 -0
  196. data/lib/relaton/jcgm/relation.rb +9 -0
  197. data/lib/relaton/jcgm/structured_identifier.rb +40 -0
  198. data/lib/relaton/jcgm/util.rb +8 -0
  199. data/lib/relaton/jcgm.rb +24 -0
  200. data/lib/relaton/jis/bibliography.rb +8 -10
  201. data/lib/relaton/jis/data_fetcher.rb +21 -19
  202. data/lib/relaton/jis/docidentifier.rb +104 -5
  203. data/lib/relaton/jis/hit.rb +18 -23
  204. data/lib/relaton/jis/hit_collection.rb +19 -18
  205. data/lib/relaton/jis/processor.rb +1 -1
  206. data/lib/relaton/jis.rb +2 -3
  207. data/lib/relaton/logger/channels/gh_issue.rb +78 -13
  208. data/lib/relaton/nist/data_fetcher.rb +63 -13
  209. data/lib/relaton/nist/docidentifier.rb +165 -0
  210. data/lib/relaton/nist/item.rb +2 -0
  211. data/lib/relaton/nist/item_base.rb +16 -0
  212. data/lib/relaton/nist/mods_parser.rb +38 -12
  213. data/lib/relaton/nist/processor.rb +2 -1
  214. data/lib/relaton/nist/relation.rb +3 -0
  215. data/lib/relaton/nist/scraper.rb +6 -3
  216. data/lib/relaton/oasis/bibliography.rb +147 -6
  217. data/lib/relaton/oasis/data_fetcher.rb +41 -5
  218. data/lib/relaton/oasis/data_parser_utils.rb +37 -3
  219. data/lib/relaton/oasis/docidentifier.rb +54 -0
  220. data/lib/relaton/oasis/item.rb +3 -0
  221. data/lib/relaton/oasis/processor.rb +7 -1
  222. data/lib/relaton/oasis.rb +14 -1
  223. data/lib/relaton/ogc/data_fetcher.rb +23 -2
  224. data/lib/relaton/ogc/docidentifier.rb +105 -0
  225. data/lib/relaton/ogc/hit_collection.rb +78 -3
  226. data/lib/relaton/ogc/processor.rb +2 -1
  227. data/lib/relaton/ogc.rb +5 -1
  228. data/lib/relaton/oiml/bibliography.rb +90 -15
  229. data/lib/relaton/oiml/docidentifier.rb +18 -3
  230. data/lib/relaton/omg/docidentifier.rb +67 -0
  231. data/lib/relaton/omg/item.rb +1 -0
  232. data/lib/relaton/omg/processor.rb +1 -0
  233. data/lib/relaton/omg/scraper.rb +61 -16
  234. data/lib/relaton/omg.rb +1 -0
  235. data/lib/relaton/plateau/bibliography.rb +10 -3
  236. data/lib/relaton/plateau/data_fetcher.rb +25 -2
  237. data/lib/relaton/plateau/handbook_parser.rb +8 -1
  238. data/lib/relaton/plateau/hit.rb +10 -2
  239. data/lib/relaton/plateau/hit_collection.rb +31 -11
  240. data/lib/relaton/plateau/processor.rb +3 -1
  241. data/lib/relaton/plateau/technical_report_parser.rb +8 -1
  242. data/lib/relaton/plateau.rb +2 -1
  243. data/lib/relaton/sdo/config.rb +34 -0
  244. data/lib/relaton/sdo/fetcher.rb +52 -0
  245. data/lib/relaton/sdo/logo.rb +95 -0
  246. data/lib/relaton/sdo/name.rb +26 -0
  247. data/lib/relaton/sdo/organization.rb +71 -0
  248. data/lib/relaton/sdo/store.rb +49 -0
  249. data/lib/relaton/sdo.rb +29 -0
  250. data/lib/relaton/version.rb +1 -1
  251. data/lib/relaton/w3c/bibliography.rb +132 -12
  252. data/lib/relaton/w3c/data_fetcher.rb +194 -16
  253. data/lib/relaton/w3c/data_parser.rb +3 -3
  254. data/lib/relaton/w3c/docidentifier.rb +48 -0
  255. data/lib/relaton/w3c/governor.rb +32 -0
  256. data/lib/relaton/w3c/item.rb +3 -0
  257. data/lib/relaton/w3c/pubid.rb +12 -0
  258. data/lib/relaton/w3c/safe_realize.rb +110 -21
  259. data/lib/relaton/w3c.rb +12 -1
  260. data/lib/relaton/xsf/bibliography.rb +61 -1
  261. data/lib/relaton/xsf/data_fetcher.rb +55 -5
  262. data/lib/relaton/xsf/docidentifier.rb +46 -0
  263. data/lib/relaton/xsf/hit_collection.rb +31 -3
  264. data/lib/relaton/xsf/item.rb +6 -0
  265. data/lib/relaton/xsf/processor.rb +1 -0
  266. data/lib/relaton/xsf.rb +5 -1
  267. data/lib/relaton.rb +42 -0
  268. metadata +135 -24
  269. data/lib/relaton/ieee/pub_id.rb +0 -161
  270. data/lib/relaton/index/id_number.rb +0 -30
@@ -1,161 +1,30 @@
1
+ require_relative "recommendation_fields"
2
+
1
3
  module Relaton
2
4
  module Itu
5
+ # Live-lookup ITU-T Recommendation parser. All field extraction lives in the
6
+ # shared RecommendationFields module (reused by the offline DataParserT
7
+ # harvester); this class only binds the module's `agent`/`idrec`/`imp` hooks.
3
8
  class RecommendationParser
4
- include Relaton::Core::ArrayWrapper
5
-
6
- RECHDR = "https://www.itu.int/mws/api/recommendations/getRecHdrDetail?idrec=%{idrec}&lang=en"
7
- RECEDITIONS = "https://www.itu.int/mws/api/recommendations/getRecEditions?idrec=%{idrec}&lang=en"
8
- RECSUPPLEMENTS = "https://www.itu.int/mws/api/recommendations/getRecSupplements?idrec=%{idrec}&lang=en"
9
- IMPLGUIDES = "https://www.itu.int/mws/api/recommendations/getImplGuides?idrec=%{idrec}&lang=en"
10
-
11
- def initialize(hit, idrec, imp)
12
- @hit = hit
9
+ include RecommendationFields
10
+
11
+ # @param agent [Mechanize] the HTTP agent (was a Hit before decoupling)
12
+ # @param idrec [String, Integer] the recommendation record id
13
+ # @param imp [Boolean] Implementers' Guide flag
14
+ # @param cache [#fetch, #warm] cross-row cache for the family-invariant
15
+ # endpoints. Defaults to NullCache, i.e. off — only the offline
16
+ # harvester passes a real one, so the live lookup path keeps exactly the
17
+ # behaviour and the request count it had before the cache existed.
18
+ def initialize(agent, idrec, imp, cache: NullCache.instance)
19
+ @agent = agent
13
20
  @idrec = idrec
14
21
  @imp = imp
15
- end
16
-
17
- def doc
18
- @doc ||= begin
19
- url = (imp ? IMPLGUIDES : RECHDR) % { idrec: idrec }
20
- resp = get_data url
21
- imp ? resp.first : resp
22
- end
23
- end
24
-
25
- # @return [String, nil]
26
- def fetch_edition
27
- self_edition&.dig("Version")
28
- end
29
-
30
- # @return [Array<Relaton::Bib::Title>]
31
- def fetch_titles
32
- title = imp ? doc["imp_title_e"] : doc["rec_title"]
33
- return [] if title.nil? || title.empty?
34
-
35
- Relaton::Bib::Title.from_string title, "en", "Latn"
36
- end
37
-
38
- # @return [Relaton::Bib::Status, nil]
39
- def fetch_status
40
- inforce = imp ? imp_status : doc["status"]
41
- return if inforce.nil? || inforce.empty?
42
-
43
- status = inforce == "In force" ? "Published" : "Withdrawal"
44
- Relaton::Bib::Status.new(stage: Relaton::Bib::Status::Stage.new(content: status))
45
- end
46
-
47
- # @return [Array<Relaton::Bib::Date>]
48
- def fetch_dates
49
- array(doc_date).map { |on| Relaton::Bib::Date.new(type: "published", at: on) }
50
- end
51
-
52
- # @return [Array<Relaton::Bib::LocalizedMarkedUpString>]
53
- def fetch_abstract
54
- array(doc["summary"]).map do |content|
55
- Relaton::Bib::Abstract.new(content: content, language: "en", script: "Latn")
56
- end
57
- end
58
-
59
- # @return [Array<Relaton::Bib::Uri>]
60
- def fetch_source
61
- link = imp ? doc["imp_dms_link"] : doc["handle_id"]
62
- links = [Relaton::Bib::Uri.new(type: "src", content: link)]
63
- links << Relaton::Bib::Uri.new(type: "pdf", content: doc["handle_id_pdf_link"]) if doc["handle_id_pdf_link"]
64
- imp_word_link { |wlink| links << Relaton::Bib::Uri.new(type: "word", content: wlink) }
65
- links
66
- end
67
-
68
- def doc_date
69
- return @doc_date if defined? @doc_date
70
-
71
- date = imp ? doc["imp_approval_date"] : doc["approval_date"]
72
- @doc_date = Date.parse(date).to_s rescue date # rubocop:disable Style/RescueModifier
73
- end
74
-
75
- # @return [Array<Relaton::Bib::Relation>]
76
- def fetch_relations
77
- relations = []
78
- editions.each do |ed|
79
- next if ed["idrec"] == idrec
80
-
81
- relations << create_relation("hasEdition", ed["title"], ed["rec_name"])
82
- end
83
-
84
- supplements.each { |supp| relations << create_relation("complementOf", supp["title_text"], supp["rec_name"]) }
85
- relations
86
- end
87
-
88
- # Fetch the study group name from the recommendation HTML page.
89
- # @return [String, nil]
90
- def fetch_workgroup
91
- url = "https://www.itu.int/ITU-T/recommendations/rec.aspx?rec=#{idrec}&lang=en"
92
- page = hit.hit_collection.agent.get(url)
93
- wg = page.at('//span[contains(@id, "uc_rec_main_info1_rpt_main_ctl00_Label8")]/a')
94
- wg&.text
95
- rescue StandardError
96
- nil
22
+ @cache = cache
97
23
  end
98
24
 
99
25
  private
100
26
 
101
- attr_reader :hit, :idrec, :imp
102
-
103
- def get_data(url)
104
- JSON.parse request_document(url).body
105
- end
106
-
107
- def request_document(url)
108
- hit.hit_collection.agent.get url
109
- rescue Mechanize::ResponseCodeError, SocketError, Timeout::Error, Errno::ECONNRESET,
110
- EOFError, Net::ProtocolError, OpenSSL::SSL::SSLError => e
111
- raise Relaton::RequestError, "Could not access #{url}: #{e.message}"
112
- end
113
-
114
- def editions
115
- @editions ||= begin
116
- url = RECEDITIONS % { idrec: idrec }
117
- get_data(url) || []
118
- end
119
- end
120
-
121
- def self_edition
122
- @self_edition ||= editions.find { |ed| ed["idrec"] == idrec }
123
- end
124
-
125
- def imp_status
126
- self_edition&.dig("status")
127
- end
128
-
129
- def imp_word_link
130
- return unless doc["imp_dms_link"]
131
-
132
- @doc_page ||= request_document(doc["imp_dms_link"])
133
- wrd_elm = @doc_page.at("//font[contains(.,'Word')]/../..")
134
- yield wrd_elm[:href] if block_given? && wrd_elm
135
- end
136
-
137
- def create_relation(type, title_text, id)
138
- titles = []
139
- if title_text && !title_text.empty?
140
- titles << Relaton::Bib::Title.new(content: title_text, language: "en", script: "Latn")
141
- end
142
-
143
- fref = titles.empty? ? id : nil
144
- did = Relaton::Bib::Docidentifier.new(type: "ITU", content: id, primary: true)
145
- bibitem = Relaton::Bib::ItemData.new(title: titles, formattedref: (fref ? Relaton::Bib::Formattedref.new(content: fref) : nil), docidentifier: [did])
146
- Relaton::Bib::Relation.new(type: type, bibitem: bibitem)
147
- end
148
-
149
- def supplements
150
- @supplements ||= begin
151
- if imp
152
- []
153
- else
154
- url = RECSUPPLEMENTS % { idrec: idrec }
155
- get_data(url) || []
156
- end
157
- end
158
- end
27
+ attr_reader :agent, :idrec, :imp, :cache
159
28
  end
160
29
  end
161
30
  end
@@ -78,19 +78,29 @@ module Relaton
78
78
  def parser
79
79
  @parser ||= begin
80
80
  if idrec
81
- RecommendationParser.new hit, idrec, imp
81
+ RecommendationParser.new hit.hit_collection.agent, idrec, imp
82
82
  else
83
83
  RadioRegulationsParser.new hit
84
84
  end
85
85
  end
86
86
  end
87
87
 
88
- # Fetch docid.
88
+ # The primary docid comes from the hit's code; a recommendation additionally
89
+ # carries the equivalent ISO/IEC(/IEEE) identifier, which used to be part of
90
+ # the search result's media name and now comes from the recommendation
91
+ # header's `iso_number` via the shared `RecommendationFields#iso_docid`.
92
+ # Both are guarded on `idrec` — `RadioRegulationsParser` (RR/OB) parses an
93
+ # HTML page and has neither `rec_name` nor `iso_docid`.
94
+ #
89
95
  # @return [Array<Relaton::Bib::Docidentifier>]
90
96
  def docid
91
97
  @docid ||= begin
92
98
  docids = hit.hit[:code].to_s.split(" | ").map { |c| createdocid(c) }
93
- docids << createdocid(doc["rec_name"]) if docids.empty?
99
+ if idrec
100
+ docids << createdocid("ITU-T #{parser.doc['rec_name']}") if docids.empty?
101
+ iso = parser.iso_docid
102
+ docids << iso if iso
103
+ end
94
104
  docids
95
105
  end
96
106
  end
data/lib/relaton/itu.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "mechanize"
2
2
  require "parslet"
3
+ require "pubid"
3
4
  require "digest/md5"
4
5
  require "relaton/index"
5
6
  require "relaton/bib"
@@ -17,7 +18,7 @@ require_relative "itu/bibliography"
17
18
 
18
19
  module Relaton
19
20
  module Itu
20
- INDEXFILE = "index-v1"
21
+ INDEXFILE = "index-v2"
21
22
 
22
23
  class Error < StandardError; end
23
24
 
@@ -0,0 +1,8 @@
1
+ module Relaton
2
+ module Jcgm
3
+ class Bibdata < Item
4
+ model ItemData
5
+ include Bib::BibdataShared
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Relaton
2
+ module Jcgm
3
+ class Bibitem < Item
4
+ model ItemData
5
+ include Bib::BibitemShared
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,97 @@
1
+ require "net/http"
2
+
3
+ module Relaton
4
+ module Jcgm
5
+ # Retrieval for JCGM (Joint Committee for Guides in Metrology) publications.
6
+ # Index-backed (no scraping): the reference is parsed with `Pubid::Jcgm`,
7
+ # matched against the pre-built index (whose rows are `Pubid::Jcgm::Identifier`
8
+ # hashes, deserialized via `pubid_class`), and the matching per-document YAML
9
+ # is fetched from the `relaton/relaton-data-jcgm` GitHub repo.
10
+ module Bibliography
11
+ ENDPOINT = "https://raw.githubusercontent.com/relaton/relaton-data-jcgm/main/".freeze
12
+
13
+ class << self
14
+ #
15
+ # Search for a JCGM publication by its identifier.
16
+ #
17
+ # @param text [String, Pubid::Jcgm::Identifier] e.g. "JCGM 200:2012" or
18
+ # "JCGM 17th Meeting (2012)"
19
+ # @param year [String, nil] edition year (optional; may be embedded)
20
+ # @param _opts [Hash] options (unused)
21
+ #
22
+ # @return [Relaton::Jcgm::Item, nil]
23
+ #
24
+ def search(text, year = nil, _opts = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
25
+ pubid = parse_ref(text)
26
+ Util.info "Fetching from Relaton repository ...", key: pubid.to_s
27
+ row = index.search(pubid) { |r| pubid_match?(r[:id], pubid, year) }
28
+ .max_by { |r| r[:id].year.to_i }
29
+ unless row
30
+ Util.info "Not found.", key: pubid.to_s
31
+ return
32
+ end
33
+
34
+ uri = URI("#{ENDPOINT}#{row[:file]}")
35
+ resp = Net::HTTP.get_response uri
36
+ unless resp.code == "200"
37
+ raise Relaton::RequestError, "Could not access #{uri}: HTTP #{resp.code}"
38
+ end
39
+
40
+ item = Relaton::Jcgm::Item.from_yaml resp.body
41
+ Util.info "Found: `#{item.docidentifier.first&.content}`", key: pubid.to_s
42
+ item.tap { |i| i.fetched = Date.today.to_s }
43
+ rescue SocketError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
44
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
45
+ Net::ProtocolError, Net::ReadTimeout, OpenSSL::SSL::SSLError,
46
+ Errno::ETIMEDOUT => e
47
+ raise Relaton::RequestError, "Could not access #{uri}: #{e.message}"
48
+ end
49
+
50
+ # @see #search
51
+ def get(ref, year = nil, opts = {})
52
+ search(ref, year, opts)
53
+ end
54
+
55
+ private
56
+
57
+ # Parse a reference into a Pubid::Jcgm identifier. An unrecognized
58
+ # reference raises; like ISO and 3GPP we let it propagate --
59
+ # relaton-cli rescues Pubid::Errors::Error and renders "... is not a
60
+ # recognized standards identifier". The `search` rescue lists transport
61
+ # errors only, so it does not swallow the parse error.
62
+ def parse_ref(text)
63
+ return text unless text.is_a?(String)
64
+
65
+ ::Pubid::Jcgm.parse(text)
66
+ end
67
+
68
+ def index
69
+ Relaton::Index.find_or_create(
70
+ :jcgm,
71
+ url: "#{ENDPOINT}#{INDEXFILE}.zip",
72
+ file: "#{INDEXFILE}.yaml",
73
+ pubid_class: ::Pubid::Jcgm::Identifier,
74
+ )
75
+ end
76
+
77
+ # `row_id` and `query` are both `Pubid::Jcgm::Identifier` instances.
78
+ # Matching is pubid's asymmetric subset match (`Pubid::SubsetMatch`):
79
+ # the reference on the left, the row on the right, and a component the
80
+ # reference omits matches any value. Guides distinguish editions by year
81
+ # (`JCGM 200:2008` vs `:2012`), so a reference without a year reaches
82
+ # both and the latest edition is picked by `max_by` in #search. Meetings
83
+ # distinguish by number (`17th` vs `18th`), which the reference states,
84
+ # so they never collapse together. The type is the identifier's class,
85
+ # and `===` requires the same class, so cross-type collisions cannot
86
+ # occur. The `year` argument lets a caller pin an edition the reference
87
+ # string omitted; a year the reference states is compared by `===`
88
+ # itself.
89
+ def pubid_match?(row_id, query, year)
90
+ wanted_year = (query.year || year)&.to_s
91
+ query === row_id &&
92
+ (wanted_year.nil? || row_id.year.to_s == wanted_year)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,81 @@
1
+ require_relative "../jcgm"
2
+ require_relative "meetings_parser"
3
+
4
+ module Relaton::Jcgm
5
+ # Harvests the JCGM **meeting proceedings** from the `jcgm/meetings-{en,fr}`
6
+ # dirs of a local checkout of metanorma/bipm-data-outcomes, writing one Relaton
7
+ # YAML per meeting and indexing each by its `Pubid::Jcgm` identifier.
8
+ #
9
+ # The curated guide/GUM/VIM records are **not** harvested here: they are
10
+ # hand-maintained YAMLs that live in the data repo's own `static/` dir, so the
11
+ # data repo's `crawler.rb` indexes them (like relaton-data-bipm's crawler does)
12
+ # by looping `static/**` and calling the public, guarded {#add_to_index} —
13
+ # keeping the "which static files" policy in the data repo while the guarded,
14
+ # sorted, `_type: pubid:jcgm:*` indexing mechanism stays here.
15
+ class DataFetcher < Relaton::Core::DataFetcher
16
+ attr_reader :output, :format, :ext, :files, :errors
17
+
18
+ def index
19
+ @index ||= Relaton::Index.find_or_create(
20
+ :jcgm, file: "#{INDEXFILE}.yaml", pubid_class: ::Pubid::Jcgm::Identifier
21
+ )
22
+ end
23
+
24
+ def log_error(msg) = Util.error msg
25
+
26
+ #
27
+ # @param [String] source dataset name ("bipm-data-outcomes")
28
+ #
29
+ def fetch(source)
30
+ MeetingsParser.parse(self) if source == "bipm-data-outcomes"
31
+ index.save
32
+ report_errors
33
+ end
34
+
35
+ #
36
+ # Save document to file
37
+ #
38
+ # @param [String] path Path to file
39
+ # @param [Relaton::Jcgm::ItemData] item document to save
40
+ # @param [Boolean, nil] warn_duplicate Warn if document already exists
41
+ #
42
+ def write_file(path, item, warn_duplicate: true)
43
+ content = serialize item
44
+ if @files.include?(path)
45
+ Util.warn "File #{path} already exists" if warn_duplicate
46
+ else
47
+ @files << path
48
+ end
49
+ File.write path, content, encoding: "UTF-8"
50
+ end
51
+
52
+ #
53
+ # Index a document by its pubid. Guarded defensively: a docnumber the
54
+ # `Pubid::Jcgm` grammar can't parse is skipped with a warning rather than
55
+ # allowed to corrupt the whole index (`Relaton::Index` rejects an index if a
56
+ # single row fails to deserialize). All current JCGM records parse on pubid
57
+ # main; the guard protects against a future malformed record.
58
+ #
59
+ # Public so the data repo's crawler can index its curated `static/` guides
60
+ # through the same guarded, sorted, pubid-backed path the meetings use.
61
+ #
62
+ # @param [String] docnumber the record's docnumber (e.g. "JCGM 17th Meeting (2012)")
63
+ # @param [String] path repo-relative path to the YAML file
64
+ #
65
+ def add_to_index(docnumber, path)
66
+ # Store the Pubid::Jcgm identifier object (not its hash): with pubid_class
67
+ # set, Relaton::Index then sorts the index by id number on save and
68
+ # serializes each id to its `_type: pubid:jcgm:*` hash, so the published
69
+ # index is already sorted (no "not sorted by id number" warning on fetch).
70
+ index.add_or_update ::Pubid::Jcgm.parse(docnumber), path
71
+ rescue StandardError => e
72
+ Util.warn "Skipping index entry for `#{docnumber}` (#{path}): #{e.message}"
73
+ end
74
+
75
+ def to_xml(item) = item.to_xml bibdata: true
76
+
77
+ def to_yaml(item) = item.to_yaml
78
+
79
+ def to_bibxml(item) = item.to_rfcxml
80
+ end
81
+ end
@@ -0,0 +1,102 @@
1
+ module Relaton
2
+ module Jcgm
3
+ # A JCGM document identifier, backed by `Pubid::Jcgm` (mirrors OIML's
4
+ # pubid-backed Docidentifier). Parsing is best-effort: forms pubid doesn't
5
+ # recognise (e.g. the combined bilingual docidentifiers and the French
6
+ # `réunion` form) leave `pubid` nil via the `content=` rescue rather than
7
+ # raising during deserialization.
8
+ class Docidentifier < Bib::Docidentifier
9
+ attr_reader :pubid
10
+
11
+ # Capture the inherited content setter before overriding #content=, so
12
+ # #refresh_content! can write the re-rendered string back WITHOUT
13
+ # re-parsing (a re-parse would rebuild @pubid from the string, which may
14
+ # not even parse back — e.g. an all-parts wrapper's own "(all parts)"
15
+ # rendering — and would discard whatever mutation was just made).
16
+ alias_method :store_content, :content=
17
+
18
+ def initialize(attrs = {}, options = {})
19
+ pubid = attrs.is_a?(Hash) ? attrs.delete(:pubid) : nil
20
+ attrs[:content] ||= pubid.to_s if pubid
21
+ super
22
+ @pubid = pubid if pubid
23
+ end
24
+
25
+ def content=(value)
26
+ super
27
+ @pubid = ::Pubid::Jcgm.parse(value) if value
28
+ rescue StandardError
29
+ @pubid = nil
30
+ end
31
+
32
+ # The class carried `#pubid` but none of `Bib::Docidentifier`'s abstract
33
+ # `remove_part!` / `remove_date!` / `to_all_parts!`, so it looked migrated
34
+ # while still raising `NotImplementedError` on every JCGM item through
35
+ # `Bib::ItemData#to_all_parts` / `#to_most_recent_reference` — and
36
+ # `NotImplementedError` descends from `ScriptError`, so a caller's
37
+ # `rescue => e` did not catch it.
38
+ #
39
+ # `Pubid::Jcgm` keeps the publication date in `date` (a
40
+ # `Components::Date`, unlike EASC's and GOST's plain `year`), so
41
+ # `remove_date!` clears that: `JCGM 100:2008` -> `JCGM 100`. `part` and
42
+ # `subpart` come from the pubid base class and are unused by JCGM today,
43
+ # so `remove_part!` is a no-op for the rendered string — implemented so it
44
+ # never raises.
45
+ #
46
+ # The mutation is applied to the identifier **and to its `#root`**. A
47
+ # supplement/corrigendum wraps the document it amends in `base`, and it
48
+ # exposes its own `date=` while carrying nil there — the year lives on the
49
+ # base. Clearing only the wrapper is therefore a silent no-op:
50
+ # `JCGM 200:2008 Corrigendum` keeps its 2008. Clearing the root as well
51
+ # gives `JCGM 200 Corrigendum`. `#root` is the same accessor
52
+ # `Relaton::Index::Type#candidates_by_number` narrows on, so the two stay
53
+ # consistent.
54
+ #
55
+ # Every write is guarded by `respond_to?`, and all three no-op when
56
+ # `@pubid` is nil — the common case the `content=` rescue exists for (the
57
+ # combined bilingual docidentifiers and the French `réunion` form).
58
+
59
+ def remove_part!
60
+ clear_attr!(:part)
61
+ end
62
+
63
+ def remove_date!
64
+ clear_attr!(:date)
65
+ end
66
+
67
+ def to_all_parts!
68
+ return if !@pubid || @pubid.all_parts?
69
+
70
+ # `#exclude` (no args) rebuilds a full independent copy, including
71
+ # the `#root` of a supplement/corrigendum — `remove_part!`/
72
+ # `remove_date!` mutate BOTH `@pubid` and its root in place (see
73
+ # `clear_attr!`), so even a shallow `.dup` would still lose the
74
+ # original date to that mutation.
75
+ original = @pubid.exclude
76
+ remove_part!
77
+ remove_date!
78
+ @pubid = original.to_all_parts
79
+ end
80
+
81
+ private
82
+
83
+ def clear_attr!(attr)
84
+ return unless @pubid
85
+
86
+ targets = [@pubid]
87
+ targets << @pubid.root if @pubid.respond_to?(:root) && !@pubid.root.equal?(@pubid)
88
+ targets.each do |id|
89
+ next unless id.respond_to?("#{attr}=")
90
+
91
+ id.public_send("#{attr}=", nil)
92
+ id.subpart = nil if attr == :part && id.respond_to?(:subpart=)
93
+ end
94
+ refresh_content!
95
+ end
96
+
97
+ def refresh_content!
98
+ store_content(@pubid.to_s) if @pubid
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,12 @@
1
+ module Relaton
2
+ module Jcgm
3
+ # JCGM document types. Meetings are `meeting-report`; the guides/GUM/VIM
4
+ # records carry `international-standard` (the shape they were filed with
5
+ # under BIPM). The list is intentionally not enforced as an enum so any
6
+ # unforeseen dataset value still round-trips (mirrors OIML's Doctype).
7
+ class Doctype < Bib::Doctype
8
+ TYPES = %w[meeting-report guide international-standard vocabulary
9
+ monographie rapport].freeze
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "doctype"
2
+ require_relative "structured_identifier"
3
+
4
+ module Relaton
5
+ module Jcgm
6
+ # JCGM bibliographic item extension. JCGM records are BIPM-shaped, so this
7
+ # mirrors `Relaton::Bipm::Ext`: a JCGM `Doctype` and a single
8
+ # `structuredidentifier` (overriding the base collection). `flavor`,
9
+ # `schema_version`, `subdoctype`, `ics` come from the shared `Bib::Ext`.
10
+ class Ext < Bib::Ext
11
+ attribute :doctype, Doctype
12
+ attribute :structuredidentifier, StructuredIdentifier
13
+
14
+ xml do
15
+ map_element "structuredidentifier", to: :structuredidentifier
16
+ end
17
+
18
+ key_value do
19
+ map_element "structuredidentifier", to: :structuredidentifier
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "item_data"
2
+ require_relative "ext"
3
+ require_relative "docidentifier"
4
+
5
+ module Relaton
6
+ module Jcgm
7
+ class Item < Bib::Item
8
+ model ItemData
9
+
10
+ attribute :ext, Ext
11
+ attribute :docidentifier, Docidentifier, collection: true,
12
+ initialize_empty: true
13
+ end
14
+ end
15
+ end
16
+
17
+ require_relative "relation"
18
+
19
+ Relaton::Jcgm::Item.attribute :relation, Relaton::Jcgm::Relation,
20
+ collection: true, initialize_empty: true
@@ -0,0 +1,18 @@
1
+ module Relaton
2
+ module Jcgm
3
+ class ItemBase < Lutaml::Model::Serializable
4
+ include Bib::NamespaceHelper
5
+
6
+ attr_accessor :type
7
+
8
+ model ItemData
9
+
10
+ instance_exec(&Bib::ItemShared::ATTRIBUTES)
11
+
12
+ xml do
13
+ map_attribute "type", to: :type
14
+ instance_exec(&Bib::ItemShared::XML_BODY)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ module Relaton
2
+ module Jcgm
3
+ class ItemData < Bib::ItemData
4
+ end
5
+ end
6
+ end