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
@@ -0,0 +1,175 @@
1
+ require "fileutils"
2
+ require "yaml"
3
+ require "date"
4
+
5
+ module Relaton::Jcgm
6
+ # Harvests JCGM meeting proceedings from the `jcgm/meetings-{en,fr}` dirs of a
7
+ # local checkout of metanorma/bipm-data-outcomes and writes one Relaton YAML
8
+ # per meeting into `<output>/jcgm/meeting/<num>.yaml`, indexing each by its
9
+ # `Pubid::Jcgm` meeting identifier.
10
+ #
11
+ # This is the JCGM-only slice of `Relaton::Bipm::DataOutcomesParser`: JCGM
12
+ # meetings carry no sub-resolutions and no parts, so the resolution/part
13
+ # machinery is dropped. The meeting docnumber's ordinal ("11st"/"12nd"/"13rd"
14
+ # — a naive last-digit rule with no teens exception) is delegated to
15
+ # `Pubid::Jcgm::Identifiers::Meeting.ordinal`, the single source of truth, so
16
+ # the printed form round-trips through pubid.
17
+ class MeetingsParser
18
+ SOURCE_ROOT = "bipm-data-outcomes".freeze
19
+ BODY = "JCGM".freeze
20
+ GH_SRC = "https://raw.githubusercontent.com/metanorma/bipm-data-outcomes/".freeze
21
+
22
+ def self.parse(data_fetcher)
23
+ new(data_fetcher).parse
24
+ end
25
+
26
+ def initialize(data_fetcher)
27
+ @data_fetcher = data_fetcher
28
+ end
29
+
30
+ def parse
31
+ Dir[File.join(SOURCE_ROOT, "jcgm", "meetings-en", "*.{yml,yaml}")].sort.each do |en_file|
32
+ fetch_meeting en_file
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def fetch_meeting(en_file)
39
+ en, fr, fr_file = read_files en_file
40
+ md_en = en["metadata"]
41
+ md_fr = fr && fr["metadata"]
42
+ num = md_en["identifier"].to_s
43
+ date = md_en["date"].to_s
44
+ docnum = meeting_docnum num, date
45
+ id = "JCGMMeeting#{num}#{::Date.parse(date).year}"
46
+
47
+ outdir = File.join @data_fetcher.output, "jcgm", "meeting"
48
+ FileUtils.mkdir_p outdir
49
+ path = File.join outdir, "#{num}.#{@data_fetcher.ext}"
50
+
51
+ item = ItemData.new(**meeting_hash(md_en, md_fr, num, id, docnum,
52
+ meeting_links(en_file, fr_file), en["pdf"]))
53
+ @data_fetcher.write_file path, item
54
+ @data_fetcher.add_to_index docnum, path
55
+ end
56
+
57
+ # Read English + French metadata (the French file mirrors the path with
58
+ # `en` -> `fr`). `Date`/`Time` are permitted because the source may write
59
+ # an unquoted `date:` scalar; the date is coerced to a string downstream.
60
+ def read_files(en_file)
61
+ fr_file = en_file.sub "en", "fr"
62
+ en = load_yaml en_file
63
+ fr = File.exist?(fr_file) ? load_yaml(fr_file) : nil
64
+ [en, fr, (fr && fr_file)]
65
+ end
66
+
67
+ def load_yaml(file)
68
+ YAML.safe_load File.read(file, encoding: "UTF-8"), permitted_classes: [Date, Time]
69
+ end
70
+
71
+ # "JCGM 17th Meeting (2012)" — ordinal via pubid's Meeting.ordinal.
72
+ def meeting_docnum(num, date)
73
+ year = ::Date.parse(date).year
74
+ "#{BODY} #{::Pubid::Jcgm::Identifiers::Meeting.ordinal(num)} Meeting (#{year})"
75
+ end
76
+
77
+ def meeting_hash(md_en, md_fr, num, id, docnum, src, pdf) # rubocop:disable Metrics/MethodLength
78
+ {
79
+ id: id,
80
+ type: "proceedings",
81
+ title: titles(md_en, md_fr),
82
+ place: [Relaton::Bib::Place.new(city: "Paris")],
83
+ date: date(md_en["date"]),
84
+ docidentifier: docids(docnum),
85
+ docnumber: docnum,
86
+ source: links(md_en, md_fr, src, pdf),
87
+ language: %w[en fr],
88
+ script: ["Latn"],
89
+ contributor: [bipm_contrib],
90
+ ext: ext(num),
91
+ }
92
+ end
93
+
94
+ def titles(md_en, md_fr)
95
+ result = []
96
+ result << title(md_en["title"], "en") if md_en && md_en["title"]
97
+ result << title(md_fr["title"], "fr") if md_fr && md_fr["title"]
98
+ result
99
+ end
100
+
101
+ def title(content, language)
102
+ content = content.dup
103
+ content.sub!(/(\d+)(e)/, '\1<sup>\2</sup>') if language == "fr"
104
+ Relaton::Bib::Title.new(content: content, language: language, script: "Latn")
105
+ end
106
+
107
+ def date(at)
108
+ at = at.to_s
109
+ return [] if at.empty?
110
+
111
+ [Relaton::Bib::Date.new(type: "published", at: at)]
112
+ end
113
+
114
+ # en: "JCGM 17th Meeting (2012)"; fr: "JCGM 17<sup>e</sup> réunion (2012)";
115
+ # plus the combined "en / fr" form (mirrors BIPM's create_meeting_docids).
116
+ def docids(en_id)
117
+ fr_id = en_id.sub(/(\d+)(?:st|nd|rd|th)/, '\1e').sub("Meeting", "réunion")
118
+ fr_id_sup = fr_id.sub(/(\d+)(e)/, '\1<sup>\2</sup>')
119
+ [
120
+ docid(en_id, language: "en"),
121
+ docid(fr_id_sup, language: "fr"),
122
+ docid("#{en_id} / #{fr_id_sup}"),
123
+ ]
124
+ end
125
+
126
+ def docid(content, language: nil)
127
+ attrs = { content: content, type: "BIPM", primary: true }
128
+ attrs.merge!(language: language, script: "Latn") if language
129
+ Docidentifier.new(**attrs)
130
+ end
131
+
132
+ def links(md_en, md_fr, src, pdf)
133
+ links = []
134
+ links << uri("citation", md_en["url"], "en") if md_en && md_en["url"]
135
+ links << uri("citation", md_fr["url"], "fr") if md_fr && md_fr["url"]
136
+ Array(pdf).each { |p| links << Relaton::Bib::Uri.new(type: "pdf", content: p) }
137
+ links + Array(src)
138
+ end
139
+
140
+ def uri(type, content, language)
141
+ Relaton::Bib::Uri.new(type: type, content: content, language: language, script: "Latn")
142
+ end
143
+
144
+ # en/fr `src` links back to the source files in bipm-data-outcomes.
145
+ def meeting_links(en_file, fr_file)
146
+ { "en" => en_file, "fr" => fr_file }.filter_map do |lang, file|
147
+ next unless file
148
+
149
+ src = GH_SRC + file.split("/")[-3..].unshift("main").join("/")
150
+ Relaton::Bib::Uri.new(type: "src", content: src, language: lang, script: "Latn")
151
+ end
152
+ end
153
+
154
+ def ext(num)
155
+ Ext.new(doctype: Doctype.new(content: "meeting-report"),
156
+ structuredidentifier: StructuredIdentifier.new(docnumber: num))
157
+ end
158
+
159
+ def bipm_contrib
160
+ names = [
161
+ Relaton::Bib::TypedLocalizedString.new(content: "International Bureau of Weights and Measures", language: "en", script: "Latn"),
162
+ Relaton::Bib::TypedLocalizedString.new(content: "Bureau international des poids et mesures", language: "fr", script: "Latn"),
163
+ ]
164
+ org = Relaton::Bib::Organization.new(
165
+ name: names,
166
+ abbreviation: Relaton::Bib::LocalizedString.new(content: "BIPM", language: "fr", script: "Latn"),
167
+ uri: [Relaton::Bib::Uri.new(content: "www.bipm.org")],
168
+ )
169
+ Relaton::Bib::Contributor.new(
170
+ organization: org,
171
+ role: [Relaton::Bib::Contributor::Role.new(type: "publisher")],
172
+ )
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,71 @@
1
+ require "relaton/core/processor"
2
+
3
+ module Relaton
4
+ module Jcgm
5
+ class Processor < Core::Processor
6
+ attr_reader :idtype
7
+
8
+ def initialize
9
+ @short = :relaton_jcgm
10
+ @prefix = "JCGM"
11
+ @defaultprefix = %r{^JCGM\s}
12
+ @idtype = "JCGM"
13
+ @datasets = %w[bipm-data-outcomes]
14
+ # Prefixes come from pubid (Pubid::Jcgm.prefixes => ["JCGM"]); see
15
+ # Core::Processor#prefixes.
16
+ @pubid_flavor = :Jcgm
17
+ end
18
+
19
+ # @param code [String]
20
+ # @param date [String, nil] year
21
+ # @param opts [Hash]
22
+ # @return [Relaton::Jcgm::ItemData, nil]
23
+ def get(code, date, opts)
24
+ require_relative "../jcgm"
25
+ Bibliography.get(code, date, opts)
26
+ end
27
+
28
+ #
29
+ # Harvest the JCGM meeting proceedings from metanorma/bipm-data-outcomes.
30
+ # Used to (re)generate the relaton-data-jcgm repo; the curated static guides
31
+ # are indexed by that repo's crawler via DataFetcher#add_to_index.
32
+ #
33
+ # @param [String] source source name ("bipm-data-outcomes")
34
+ # @param [Hash] opts
35
+ #
36
+ def fetch_data(source, opts)
37
+ require_relative "data_fetcher"
38
+ DataFetcher.fetch(source, **opts)
39
+ end
40
+
41
+ # @param xml [String]
42
+ # @return [Relaton::Jcgm::ItemData]
43
+ def from_xml(xml)
44
+ require_relative "../jcgm"
45
+ Item.from_xml xml
46
+ end
47
+
48
+ # @param yaml [String]
49
+ # @return [Relaton::Jcgm::ItemData]
50
+ def from_yaml(yaml)
51
+ require_relative "../jcgm"
52
+ Item.from_yaml yaml
53
+ end
54
+
55
+ # Returns hash of XML grammar
56
+ # @return [String]
57
+ def grammar_hash
58
+ require_relative "../jcgm"
59
+ @grammar_hash ||= ::Relaton::Jcgm.grammar_hash
60
+ end
61
+
62
+ #
63
+ # Remove index file
64
+ #
65
+ def remove_index_file
66
+ require_relative "../jcgm"
67
+ Relaton::Index.find_or_create(:jcgm, url: true, file: "#{INDEXFILE}.yaml").remove_file
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "item_base"
2
+
3
+ module Relaton
4
+ module Jcgm
5
+ class Relation < Bib::Relation
6
+ attribute :bibitem, ItemBase
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ module Relaton
2
+ module Jcgm
3
+ # A single structured identifier (e.g. `{ docnumber: "17" }`). JCGM records
4
+ # carry this as a single mapping, not a sequence, so it overrides the base
5
+ # `Bib::Ext#structuredidentifier` collection (mirrors BIPM's model).
6
+ class StructuredIdentifier < Lutaml::Model::Serializable
7
+ attribute :docnumber, :string
8
+ attribute :part, :string
9
+ attribute :appendix, :string
10
+
11
+ xml do
12
+ map_element "docnumber", to: :docnumber
13
+ map_element "part", to: :part
14
+ map_element "appendix", to: :appendix
15
+ end
16
+
17
+ # `Bib::ItemData#ext_to_all_parts!` / `#ext_remove_date` broadcast to
18
+ # `ext.structuredidentifier` just as they do to `docidentifier`, so these
19
+ # three are as required here as on the docidentifier. This class descends
20
+ # from `Lutaml::Model::Serializable` rather than `Bib::StructuredIdentifier`
21
+ # (JCGM stores a single mapping, not a sequence), so the methods were not
22
+ # merely unimplemented — they were absent, and the broadcast raised
23
+ # `NoMethodError` on every JCGM item.
24
+ #
25
+ # JCGM structured identifiers carry no date field, so `remove_date!` has
26
+ # nothing to strip and is a deliberate no-op; the year lives on the
27
+ # docidentifier's pubid, which `Docidentifier#remove_date!` handles.
28
+
29
+ def remove_part!
30
+ self.part = nil
31
+ end
32
+
33
+ def remove_date!; end
34
+
35
+ def to_all_parts!
36
+ remove_part!
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,8 @@
1
+ module Relaton
2
+ module Jcgm
3
+ module Util
4
+ extend Relaton::Bib::Util
5
+ PROGNAME = "relaton-jcgm".freeze
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ require "relaton/bib"
2
+ require "relaton/index"
3
+ require "pubid"
4
+ require_relative "version"
5
+ require_relative "jcgm/util"
6
+ require_relative "jcgm/docidentifier"
7
+ require_relative "jcgm/item"
8
+ require_relative "jcgm/bibitem"
9
+ require_relative "jcgm/bibdata"
10
+ require_relative "jcgm/bibliography"
11
+
12
+ module Relaton
13
+ module Jcgm
14
+ INDEXFILE = "index-v1".freeze
15
+
16
+ class Error < StandardError; end
17
+
18
+ # Returns hash of XML grammar
19
+ # @return [String]
20
+ def self.grammar_hash
21
+ Digest::MD5.hexdigest Relaton::VERSION
22
+ end
23
+ end
24
+ end
@@ -16,16 +16,15 @@ module Relaton
16
16
  # @param [String] code JIS document code
17
17
  # @param [String, nil] year JIS document year
18
18
  #
19
- # @return [Relaton::Jis::HitCollection, nil] search result, or nil when
20
- # the reference cannot be parsed
19
+ # @return [Relaton::Jis::HitCollection] search result
20
+ #
21
+ # @raise [Pubid::Errors::ParseError] when the reference is not a JIS
22
+ # identifier, so a malformed reference is not reported as "not found"
21
23
  #
22
24
  def search(code, year = nil)
23
25
  pubid = ::Pubid::Jis::Identifier.parse code
24
26
  pubid.year ||= year.to_i if year
25
27
  HitCollection.new pubid
26
- rescue StandardError => e
27
- Util.warn "Unable to parse `#{code}` with pubid: #{e.message}"
28
- nil
29
28
  end
30
29
 
31
30
  #
@@ -38,15 +37,14 @@ module Relaton
38
37
  #
39
38
  # @return [Relaton::Jis::Item, nil] JIS document
40
39
  #
41
- def get(ref, year = nil, opts = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
40
+ # @raise [Pubid::Errors::ParseError] when the reference is not a JIS
41
+ # identifier
42
+ #
43
+ def get(ref, year = nil, opts = {}) # rubocop:disable Metrics/AbcSize
42
44
  code = ref.sub(/\s\((all parts|規格群)\)/, "")
43
45
  opts[:all_parts] ||= !$1.nil?
44
46
  Util.info "Fetching from webdesk.jsa.or.jp ...", key: ref
45
47
  hits = search(code, year)
46
- unless hits
47
- hint [], ref, year
48
- return
49
- end
50
48
  result = opts[:all_parts] ? hits.find_all_parts : hits.find
51
49
  if result.is_a? Bib::ItemData
52
50
  Util.info "Found: `#{result.docidentifier[0].content}`", key: ref
@@ -19,23 +19,17 @@ module Relaton
19
19
  Util.error msg
20
20
  end
21
21
 
22
+ # The pubid index-v2, the only index a crawl writes. relaton-data-jis
23
+ # rebuilds the legacy string index-v1 from data/ in its own crawler.
22
24
  def index
23
- @index ||= Relaton::Index.find_or_create :jis, file: "#{INDEXFILE}.yaml"
24
- end
25
-
26
- # Pubid-based index built in parallel with the legacy string index. The
27
- # pool keys by type, so requesting a second :jis index with a different
28
- # file evicts the v1 Type from the pool, but we keep our own reference in
29
- # @index, so both stay live for the duration of the crawl.
30
- def index_v2
31
- @index_v2 ||= Relaton::Index.find_or_create(
32
- :jis, file: "#{INDEXFILE_V2}.yaml", pubid_class: ::Pubid::Jis::Identifier
25
+ @index ||= Relaton::Index.find_or_create(
26
+ :jis, file: "#{INDEXFILE}.yaml", pubid_class: ::Pubid::Jis::Identifier
33
27
  )
34
28
  end
35
29
 
36
30
  # Parse a primary docidentifier string into a pubid identifier; nil (with
37
31
  # a warning) if pubid can't parse it, so a single bad id never aborts the
38
- # crawl or corrupts index-v2.
32
+ # crawl or corrupts the index.
39
33
  def pubid(id)
40
34
  ::Pubid::Jis::Identifier.parse id
41
35
  rescue StandardError => e
@@ -89,7 +83,6 @@ module Relaton
89
83
  resp = agent.get "#{URL}W11M0070/index"
90
84
  parse_page resp
91
85
  index.save
92
- index_v2.save
93
86
  report_errors
94
87
  end
95
88
 
@@ -163,17 +156,26 @@ module Relaton
163
156
  return unless bib
164
157
 
165
158
  id = bib.docidentifier.find(&:primary).content
166
- file = output_file id
167
159
  @mutex.synchronize do
160
+ # Two distinct ids can sanitize to one filename, in which case the
161
+ # second document used to be dropped. Give it a path of its own; a
162
+ # genuine duplicate (same id) still resolves to one path and is still
163
+ # skipped. Inside the lock: unique_output_file reserves state.
164
+ file = unique_output_file id
165
+ # A reserved path only ever belongs to one id, so a hit here is the
166
+ # same document again. Checked FIRST: a disambiguated path stays !=
167
+ # output_file forever, so gating this on that comparison would make a
168
+ # repeat of a disambiguated id skip the skip and overwrite itself.
168
169
  if @files.include?(file)
169
170
  Util.warn "File #{file} already exists. Duplication URL: #{url}"
170
- else
171
- @files << file
172
- File.write file, serialize(bib), encoding: "UTF-8"
173
- index.add_or_update id, file
174
- pid = pubid id
175
- index_v2.add_or_update pid, file if pid
171
+ next
176
172
  end
173
+
174
+ Util.warn "File #{output_file id} already exists; writing #{file} instead" if file != output_file(id)
175
+ @files << file
176
+ File.write file, serialize(bib), encoding: "UTF-8"
177
+ pid = pubid id
178
+ index.add_or_update pid, file if pid
177
179
  end
178
180
  end
179
181
  end
@@ -2,19 +2,118 @@
2
2
 
3
3
  module Relaton
4
4
  module Jis
5
+ # A JIS document identifier, backed by `Pubid::Jis`.
6
+ #
7
+ # `content` stays the source string for serialization. `#pubid` parses it
8
+ # into a `Pubid::Jis::Identifier`, and the three mutators change that pubid
9
+ # and render it back into `content`. There is no string surgery.
10
+ #
11
+ # The parse is lazy and soft, as in `Relaton::Itu::Docidentifier`: it runs
12
+ # on first use, and content that does not parse gives a nil `#pubid`, with
13
+ # no log. The all-parts form "JIS C 0364 (all parts)" does not parse, and
14
+ # a cached all-parts item is deserialized again, so a log would be noise.
15
+ # With a nil `#pubid` the mutators do nothing.
16
+ #
17
+ # ## Rendering rules
18
+ #
19
+ # - The `JIS ` publisher is rendered only when the source has it. pubid
20
+ # always adds it, but the source spells a technical report
21
+ # `TR Z 0010:2008`.
22
+ # - Everything else is the pubid canonical spelling. pubid does not keep the
23
+ # source spelling `AMENDMENT 1` / `EXPLANATION 1`, so a changed supplement
24
+ # renders as `AMD 1` / `EXPL 1`, the form that the index uses.
25
+ # - The all-parts marker is ` (all parts)`, as in
26
+ # `Relaton::Jis::StructuredIdentifier`, not the pubid `(規格群)`.
27
+ # `Bibliography.get` accepts both as a reference.
5
28
  class Docidentifier < Bib::Docidentifier
29
+ # Capture the inherited content setter before the override, so that a
30
+ # mutation can write the rendered string back and keep the changed pubid.
31
+ # `content=` would clear it, and a new parse of "… (all parts)" fails.
32
+ alias_method :store_content, :content=
33
+
34
+ def content=(value)
35
+ super
36
+ @pubid = nil
37
+ @pubid_parsed = false
38
+ end
39
+
40
+ # @return [Pubid::Jis::Identifier, nil] nil if the content does not parse
41
+ def pubid
42
+ return @pubid if @pubid_parsed
43
+
44
+ @pubid_parsed = true
45
+ @pubid = parse
46
+ end
47
+
48
+ # Removes every part level: `JIS C 0364-2-21:1999` -> `JIS C 0364:1999`.
49
+ # For a supplement, the part is on its base.
6
50
  def remove_part!
7
- content&.sub!(/-\d+/, "")
51
+ return unless pubid
52
+
53
+ change_document { |id| id.exclude(:parts) }
54
+ refresh_content!
8
55
  end
9
56
 
57
+ # Removes the year and the reaffirmation marker:
58
+ # `JIS C 9901:2019R` -> `JIS C 9901`.
59
+ #
60
+ # For a supplement, only the base loses its date, and the supplement keeps
61
+ # its own year: `JIS B 3700-101:1996/CORRIGENDUM 1:2002` ->
62
+ # `JIS B 3700-101/CORRIGENDUM 1:2002`. pubid renders a supplement without
63
+ # a year as `…/CORRIGENDUM 1:`, with a trailing colon.
10
64
  def remove_date!
11
- content&.sub!(/:\d{4}/, "")
65
+ return unless pubid
66
+
67
+ change_document { |id| id.exclude(:year, :reaffirmed) }
68
+ refresh_content!
12
69
  end
13
70
 
71
+ # `JIS C 0364-2-21:1999` -> `JIS C 0364 (all parts)`, with `all_parts`
72
+ # set on `#pubid`.
14
73
  def to_all_parts!
15
- remove_part!
16
- remove_date!
17
- self.content = "#{content} (all parts)" if content
74
+ return if !pubid || pubid.all_parts?
75
+
76
+ # `#exclude` (no args) rebuilds a full independent copy;
77
+ # `change_document` itself never mutates in place (it only reassigns
78
+ # `@pubid`), but this stays correct even if that changes.
79
+ original = @pubid.exclude
80
+ change_document { |id| id.exclude(:parts, :year, :reaffirmed) }
81
+ document = render(@pubid)
82
+ @pubid = original.to_all_parts
83
+ store_content "#{document} (all parts)"
84
+ end
85
+
86
+ private
87
+
88
+ def parse
89
+ return unless content
90
+
91
+ require "pubid"
92
+ ::Pubid::Jis::Identifier.parse content.to_s
93
+ rescue LoadError, StandardError
94
+ nil
95
+ end
96
+
97
+ # Applies the change to the document identifier: the pubid itself, or the
98
+ # base of a supplement. Copy on write, as `Pubid::Identifier#exclude` is:
99
+ # an argument-less `exclude` copies the supplement, so a pubid that a
100
+ # caller got before the mutation does not change.
101
+ def change_document(&)
102
+ @pubid = document_changed(@pubid, &)
103
+ end
104
+
105
+ def document_changed(id, &)
106
+ return yield(id) unless id.is_a?(::Pubid::Jis::SupplementIdentifier)
107
+
108
+ id.exclude.tap { |copy| copy.base &&= document_changed(copy.base, &) }
109
+ end
110
+
111
+ def refresh_content!
112
+ store_content render(@pubid)
113
+ end
114
+
115
+ def render(id)
116
+ id.to_s(with_publisher: content.to_s.start_with?("JIS"))
18
117
  end
19
118
  end
20
119
  end
@@ -15,24 +15,34 @@ module Relaton
15
15
  new hit, collection
16
16
  end
17
17
 
18
+ # The edition aspects. {HitCollection} picks the edition, so a match
19
+ # ignores them. The reaffirmation mark `R` goes with the year:
20
+ # `exclude(:year)` keeps it, and `JIS L 4107` must match
21
+ # `JIS L 4107:2000R`.
22
+ EDITION = %i[year reaffirmed].freeze
23
+
18
24
  #
19
25
  # Check if the hit matches the collection's reference.
20
26
  #
21
- # The candidate must be the same document type (so a plain standard query
22
- # never matches its amendments) and share series and number. Part is
23
- # compared only when the reference names a specific part and `all_parts`
24
- # is off. Year is filtered separately by {HitCollection}.
27
+ # The candidate must be the same document type, so a plain standard
28
+ # query never matches its amendments. The class check is explicit
29
+ # because pubid's JIS `==` does not compare the class
30
+ # (`JIS TR X 0014:1999 == JIS X 0014:1999`). The other aspects are
31
+ # compared, except the edition, and the parts when `all_parts` is on.
32
+ # This includes the base document of a supplement. The `SYMBOL` is
33
+ # compared on a standard, but not on a supplement: pubid's supplement
34
+ # `==` ignores it. {HitCollection#find_by_year} puts the exact printed id
35
+ # first for that reason.
25
36
  #
26
37
  # @param [Boolean] all_parts match any part of the document
27
38
  #
28
39
  # @return [Boolean] true if the hit matches
29
40
  #
30
41
  def matches?(all_parts: false)
31
- cand = pubid
32
- return false unless cand && same_document?(cand)
33
- return true if all_parts || reference_partless?
42
+ return false unless pubid.instance_of?(reference.class)
34
43
 
35
- Array(cand.parts).map(&:to_s) == reference_parts
44
+ ignore = all_parts ? EDITION + [:parts] : EDITION
45
+ reference.matches? pubid, ignore: ignore
36
46
  end
37
47
 
38
48
  #
@@ -73,21 +83,6 @@ module Relaton
73
83
  def reference
74
84
  hit_collection.pubid
75
85
  end
76
-
77
- # Same document type, series and number as the reference.
78
- def same_document?(cand)
79
- cand.instance_of?(reference.class) &&
80
- cand.series == reference.series &&
81
- cand.number.to_s == reference.number.to_s
82
- end
83
-
84
- def reference_partless?
85
- reference.parts.nil? || reference.parts.empty?
86
- end
87
-
88
- def reference_parts
89
- Array(reference.parts).map(&:to_s)
90
- end
91
86
  end
92
87
  end
93
88
  end