relaton 3.0.0.pre.alpha.16 → 3.0.0.pre.alpha.18

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/data/equivalence/adoption-chains.yaml +38 -0
  3. data/lib/relaton/3gpp/CLAUDE.md +187 -0
  4. data/lib/relaton/3gpp/README.adoc +149 -0
  5. data/lib/relaton/adobe/CLAUDE.md +104 -0
  6. data/lib/relaton/bib/CLAUDE.md +130 -0
  7. data/lib/relaton/bib/README.adoc +330 -0
  8. data/lib/relaton/bib/model/series.rb +1 -1
  9. data/lib/relaton/bipm/CLAUDE.md +173 -0
  10. data/lib/relaton/bipm/README.adoc +352 -0
  11. data/lib/relaton/bipm/data_fetcher.rb +3 -2
  12. data/lib/relaton/bsi/CLAUDE.md +35 -0
  13. data/lib/relaton/bsi/README.adoc +112 -0
  14. data/lib/relaton/calconnect/CLAUDE.md +177 -0
  15. data/lib/relaton/calconnect/README.adoc +140 -0
  16. data/lib/relaton/ccsds/CLAUDE.md +74 -0
  17. data/lib/relaton/ccsds/README.adoc +154 -0
  18. data/lib/relaton/cen/CLAUDE.md +219 -0
  19. data/lib/relaton/cen/README.adoc +155 -0
  20. data/lib/relaton/cie/CLAUDE.md +90 -0
  21. data/lib/relaton/cie/README.adoc +121 -0
  22. data/lib/relaton/core/CLAUDE.md +140 -0
  23. data/lib/relaton/core/README.adoc +64 -0
  24. data/lib/relaton/core/data_fetcher.rb +50 -3
  25. data/lib/relaton/db/CLAUDE.md +373 -0
  26. data/lib/relaton/db.rb +19 -1
  27. data/lib/relaton/doi/CLAUDE.md +105 -0
  28. data/lib/relaton/doi/README.adoc +89 -0
  29. data/lib/relaton/easc/CLAUDE.md +85 -0
  30. data/lib/relaton/ecma/CLAUDE.md +217 -0
  31. data/lib/relaton/ecma/README.adoc +178 -0
  32. data/lib/relaton/equivalence.rb +89 -0
  33. data/lib/relaton/etsi/CLAUDE.md +219 -0
  34. data/lib/relaton/etsi/README.adoc +137 -0
  35. data/lib/relaton/evs/CLAUDE.md +120 -0
  36. data/lib/relaton/evs/README.adoc +27 -0
  37. data/lib/relaton/gb/CLAUDE.md +47 -0
  38. data/lib/relaton/gb/README.adoc +169 -0
  39. data/lib/relaton/gost/CLAUDE.md +100 -0
  40. data/lib/relaton/iala/CLAUDE.md +97 -0
  41. data/lib/relaton/iana/CLAUDE.md +105 -0
  42. data/lib/relaton/iana/README.adoc +107 -0
  43. data/lib/relaton/iec/CLAUDE.md +13 -0
  44. data/lib/relaton/iec/README.adoc +280 -0
  45. data/lib/relaton/iec/data_fetcher.rb +24 -8
  46. data/lib/relaton/iec/project_fetcher.rb +48 -0
  47. data/lib/relaton/iec/project_parser.rb +111 -0
  48. data/lib/relaton/iec.rb +5 -0
  49. data/lib/relaton/ieee/CLAUDE.md +176 -0
  50. data/lib/relaton/ieee/README.adoc +131 -0
  51. data/lib/relaton/ietf/CLAUDE.md +357 -0
  52. data/lib/relaton/ietf/README.adoc +167 -0
  53. data/lib/relaton/iho/CLAUDE.md +70 -0
  54. data/lib/relaton/iho/README.adoc +119 -0
  55. data/lib/relaton/index/CLAUDE.md +207 -0
  56. data/lib/relaton/index/README.adoc +221 -0
  57. data/lib/relaton/index/dataset_lock.rb +122 -0
  58. data/lib/relaton/index/family.rb +63 -0
  59. data/lib/relaton/index/type.rb +32 -0
  60. data/lib/relaton/index.rb +3 -0
  61. data/lib/relaton/isbn/CLAUDE.md +47 -0
  62. data/lib/relaton/isbn/README.adoc +91 -0
  63. data/lib/relaton/iso/CLAUDE.md +38 -0
  64. data/lib/relaton/iso/README.adoc +370 -0
  65. data/lib/relaton/iso/data_fetcher.rb +16 -1
  66. data/lib/relaton/itu/CLAUDE.md +685 -0
  67. data/lib/relaton/itu/README.adoc +188 -0
  68. data/lib/relaton/jcgm/CLAUDE.md +107 -0
  69. data/lib/relaton/jis/CLAUDE.md +64 -0
  70. data/lib/relaton/jis/README.adoc +167 -0
  71. data/lib/relaton/logger/CLAUDE.md +37 -0
  72. data/lib/relaton/logger/README.adoc +120 -0
  73. data/lib/relaton/nist/CLAUDE.md +180 -0
  74. data/lib/relaton/nist/README.adoc +389 -0
  75. data/lib/relaton/nist/bibliography.rb +17 -1
  76. data/lib/relaton/nist/data_fetcher.rb +60 -4
  77. data/lib/relaton/nist/hit_collection.rb +121 -59
  78. data/lib/relaton/nist/scraper.rb +21 -7
  79. data/lib/relaton/oasis/CLAUDE.md +251 -0
  80. data/lib/relaton/oasis/README.adoc +132 -0
  81. data/lib/relaton/ogc/CLAUDE.md +242 -0
  82. data/lib/relaton/ogc/README.adoc +134 -0
  83. data/lib/relaton/oiml/CLAUDE.md +47 -0
  84. data/lib/relaton/oiml/README.adoc +70 -0
  85. data/lib/relaton/omg/CLAUDE.md +137 -0
  86. data/lib/relaton/omg/README.adoc +101 -0
  87. data/lib/relaton/plateau/CLAUDE.md +126 -0
  88. data/lib/relaton/plateau/README.adoc +139 -0
  89. data/lib/relaton/sdo/CLAUDE.md +81 -0
  90. data/lib/relaton/un/CLAUDE.md +91 -0
  91. data/lib/relaton/un/README.adoc +125 -0
  92. data/lib/relaton/version.rb +1 -1
  93. data/lib/relaton/w3c/CLAUDE.md +369 -0
  94. data/lib/relaton/w3c/README.adoc +140 -0
  95. data/lib/relaton/w3c/data_fetcher.rb +1 -1
  96. data/lib/relaton/xsf/CLAUDE.md +189 -0
  97. data/lib/relaton/xsf/README.adoc +127 -0
  98. data/lib/relaton.rb +1 -0
  99. metadata +83 -2
@@ -0,0 +1,389 @@
1
+ = Relaton for NIST: bibliographic retrieval of NIST publications
2
+
3
+ == Purpose
4
+
5
+ Relaton for NIST provides bibliographic information of NIST publications using
6
+ the
7
+ https://github.com/metanorma/metanorma-model-nist#nist-bibliographic-item-model[NistBibliographicItem model].
8
+
9
+ Relaton for NIST has been developed in cooperation with the NIST Cybersecurity
10
+ Resource Center (CSRC) and the Computer Security Division (ITL/CSD).
11
+
12
+ == Data sources
13
+
14
+ === General
15
+
16
+ Relaton for NIST retrieves bibliographic information from two sources,
17
+ in the following priority:
18
+
19
+ . NIST Cybersecurity Resource Center (CSRC)
20
+
21
+ . NIST Library of the NIST Information Services Office (ISO)
22
+
23
+ === NIST Cybersecurity Resource Center (CSRC)
24
+
25
+ The NIST CSRC bibliographic feed is used as the primary source for all CSRC
26
+ publications.
27
+
28
+ Bibliographic information offered through CSRC is provided with enhanced
29
+ metadata unavailable from the NIST Library dataset, including:
30
+
31
+ * versioned drafts (the NIST Library dataset only contains the latest publication: latest draft or final publication)
32
+ * revision information: revision number, iteration
33
+ * document stage information: retired, withdrawn, etc.
34
+ * bibliographic dates, including issued date, updated date, published date,
35
+ obsolete date, commenting period
36
+ * document relationships: supersession and replacements
37
+ * contacts: enhanced name parts and affiliation information
38
+
39
+ The NIST CSRC provides the following documents:
40
+
41
+ * NIST SP 800-*
42
+ * NIST SP 500-*
43
+ * NIST SP 1800-*
44
+ * NIST FIPS {31, 39, 41, 46, 46-$$*$$, 48, 65, 73, 74, 81, 83, 87, 102, 112, 113, 139, 140-$$*$$, 141, 171, 180, 180-$$*$$, 181, 186, 186-$$*$$, 188, 190, 191, 196, 197, 198, 198-1, 199, 200, 201, 201-*, 202}
45
+
46
+ As this feed is provided by CSRC in the native Relaton JSON format, no data
47
+ transform is necessary.
48
+
49
+ === NIST Library
50
+
51
+ The NIST Library offers the full NIST Technical Publications dataset at
52
+ https://github.com/usnistgov/NIST-Tech-Pubs[GitHub].
53
+
54
+ The library data is managed internally using MARC21, and the GitHub repository
55
+ offers this information via MARC21 XML and MODS (in XML).
56
+
57
+ Relaton for NIST uses the MODS dataset obtained from the following location:
58
+
59
+ * https://github.com/usnistgov/NIST-Tech-Pubs/releases[NIST-Tech-Pubs/releases],
60
+ the `allrecords-MODS.xml` file
61
+
62
+ The MODS XML file is parsed using the
63
+ https://github.com/relaton/loc_mods[`loc_mods`] library and its data mapped to
64
+ the Relaton model.
65
+
66
+ The NIST Library dataset provides documents listed in the
67
+ https://github.com/relaton/relaton-data-nist/blob/v2/index-v2.yaml[index].
68
+
69
+ == Usage
70
+
71
+ === Retrieving an entry
72
+
73
+ ==== By NIST PubID
74
+
75
+ Relaton supports using the
76
+ https://www.nist.gov/system/files/documents/2022/04/01/PubID_Syntax_NIST_TechPubs.pdf[NIST PubID]
77
+ as the retrieval key for bibliographic entries.
78
+
79
+ NOTE: Relaton for NIST is the first public implementation of the
80
+ https://www.nist.gov/news-events/news/2021/08/nist-technical-series-publications-proposed-publication-identifier-syntax[NIST PubID].
81
+
82
+ .Retrieving a bibliographic entry using NIST PubID
83
+ [example]
84
+ ====
85
+ [source,ruby]
86
+ ----
87
+ require 'relaton/nist'
88
+ => true
89
+
90
+ hit_collection = Relaton::Nist::Bibliography.search("NISTIR 8200")
91
+ [relaton-nist] INFO: (NIST IR 8200) Fetching from csrc.nist.gov ...
92
+ [relaton-nist] INFO: (NIST IR 8200) Fetching from Relaton repository ...
93
+ => <Relaton::Nist::HitCollection:0x00000000002c10 @ref=NIST IR 8200 @fetched=false>
94
+
95
+ item = hit_collection[0].item
96
+ => #<Relaton::Nist::ItemData:0x000000012c1d8008
97
+ # ...
98
+ ----
99
+ ====
100
+
101
+ The publication year can also be given if the publication has seen multiple
102
+ revisions or editions.
103
+
104
+ .Retrieving a bibliographic entry using NIST PubID with year
105
+ [example]
106
+ ====
107
+ [source,ruby]
108
+ ----
109
+ Relaton::Nist::Bibliography.get("NIST IR 8200", "2018")
110
+ [relaton-nist] INFO: (NIST IR 8200) Fetching from csrc.nist.gov ...
111
+ [relaton-nist] INFO: (NIST IR 8200) Fetching from Relaton repository ...
112
+ [relaton-nist] INFO: (NIST IR 8200) Found: `NIST IR 8200`
113
+ => #<Relaton::Nist::ItemData:0x000000012c5e7590
114
+ # ...
115
+ ----
116
+ ====
117
+
118
+ A NIST PubID can contain these optional parameters `{ptN}{vN}{verN}{rN}{/Add}`:
119
+
120
+ * Part is specified as `ptN` (SP 800-57pt1)
121
+ * Volume is specified as `vN` (SP 800-60v1)
122
+ * Version is specified as `verN` (SP 800-45ver2)
123
+ * Revision is specified as `rN` (SP 800-40r3) or `-N`
124
+ * Addendum is specified as `/Add` (SP 800-38A/Add)
125
+
126
+ .Retrieving a bibliographic entry with a PubID that contains revision
127
+ [example]
128
+ ====
129
+ [source,ruby]
130
+ ----
131
+ item = Relaton::Nist::Bibliography.get 'NIST SP 800-67r1'
132
+ [relaton-nist] INFO: (NIST SP 800-67r1) Fetching from csrc.nist.gov ...
133
+ [relaton-nist] INFO: (NIST SP 800-67r1) Found: `NIST SP 800-67r1 fpd`
134
+ => #<Relaton::Nist::ItemData:0x000000012cf294c8
135
+ ...
136
+
137
+ item.docidentifier.first.content
138
+ => "NIST SP 800-67r1 fpd"
139
+ ----
140
+ ====
141
+
142
+ .Retrieving a bibliographic entry with a PubID that specifies an addendum
143
+ [example]
144
+ ====
145
+ [source,ruby]
146
+ ----
147
+ item = Relaton::Nist::Bibliography.get 'NIST SP 800-38A/Add'
148
+ [relaton-nist] INFO: (NIST SP 800-38A Add) Fetching from csrc.nist.gov ...
149
+ [relaton-nist] INFO: (NIST SP 800-38A Add) Found: `NIST SP 800-38A fpd Add`
150
+ => #<Relaton::Nist::ItemData:0x0000000124864190
151
+ ...
152
+
153
+ item.docidentifier.first.content
154
+ => "NIST SP 800-38A fpd Add"
155
+ ----
156
+ ====
157
+
158
+ ==== By NIST PubID abbreviated form
159
+
160
+ The NIST PubID "abbreviated form" is the form of PubID printed on the inner
161
+ cover of a NIST publication.
162
+
163
+ The NIST PubID abbreviated format has the following syntax:
164
+
165
+ * `NIST {abbrev(series)} {docnumber} {(edition), optional} {(stage), optional}`, or
166
+ * `{abbrev(series)} {docnumber} {(edition), optional} {(stage), optional}`
167
+
168
+ Where,
169
+
170
+ `(stage)`::
171
+ empty if the state is "final" (published).
172
+ In case the state is "draft" it should be:
173
+ `PD`::: for the public draft
174
+ `IPD`::: for the initial public draft
175
+ `{n}PD`::: for subsequent public drafts, such as 2PD, 3PD and so on.
176
+ `FPD`::: for the final public draft
177
+
178
+ `(edition)`:: the date of publication or update
179
+ `docnumber`:: the full NIST document number, e.g., 800-52.
180
+
181
+ .Retrieving an entry using the NIST PubID abbreviated form
182
+ [example]
183
+ ====
184
+ [source,ruby]
185
+ ----
186
+ Relaton::Nist::Bibliography.get("NIST SP 800-205 (February 2019) (IPD)")
187
+ [relaton-nist] INFO: (NIST SP 800-205) Fetching from csrc.nist.gov ...
188
+ [relaton-nist] INFO: (NIST SP 800-205) Found: `NIST SP 800-205 ipd`
189
+ => #<Relaton::Nist::ItemData:0x000000012494fb40
190
+ # ...
191
+ ----
192
+ ====
193
+
194
+ ==== By FIPS PubID
195
+
196
+ The format for FIPS publications is:
197
+
198
+ * `FIPS {docnumber}`, or
199
+ * `NIST FIPS {docnumber}`
200
+
201
+ .Retrieving an entry using the FIPS PubID
202
+ [example]
203
+ ====
204
+ [source,ruby]
205
+ ----
206
+ Relaton::Nist::Bibliography.get("NIST FIPS 140-3")
207
+ [relaton-nist] INFO: (NIST FIPS 140-3) Fetching from csrc.nist.gov ...
208
+ [relaton-nist] INFO: (NIST FIPS 140-3) Found: `NIST FIPS 140-3 2pd`
209
+ => #<Relaton::Nist::ItemData:0x0000000124d8dfe0
210
+ # ...
211
+ ----
212
+ ====
213
+
214
+ === Serializing an entry
215
+
216
+ ==== To Relaton XML
217
+
218
+ .Serializing an entry to Relaton XML
219
+ [example]
220
+ ====
221
+ [source,ruby]
222
+ ----
223
+ item.to_xml
224
+ => "<bibitem id="NISTSP80038AfpdAdd" type="standard" schema-version="v1.4.1">
225
+ <fetched>2026-02-26</fetched>
226
+ <title language="en" script="Latn">Recommendation for Block Cipher Modes of Operation - Three Variants of Ciphertext Stealing for CBC Mode</title>
227
+ <uri type="src">https://csrc.nist.gov/pubs/sp/800/38/a/sup/final</uri>
228
+ <uri type="doi">https://doi.org/10.6028/NIST.SP.800-38A-Add</uri>
229
+ <docidentifier type="NIST" primary="true">NIST SP 800-38A fpd Add</docidentifier>
230
+ <docidentifier type="DOI">NIST.SP.800-38A-Add</docidentifier>
231
+ ...
232
+ </bibitem>"
233
+ ----
234
+ ====
235
+
236
+ When the option `bibdata: true` is given, the method outputs XML wrapped by
237
+ the `bibdata` element and adds a flavor-specific `ext` element.
238
+
239
+ .Serializing an entry to Relaton XML with `bibdata: true`
240
+ [example]
241
+ ====
242
+ [source,ruby]
243
+ ----
244
+ item.to_xml bibdata: true
245
+ => "<bibdata type="standard" schema-version="v1.4.1">
246
+ <fetched>2026-02-26</fetched>
247
+ <title language="en" script="Latn">Recommendation for Block Cipher Modes of Operation - Three Variants of Ciphertext Stealing for CBC Mode</title>
248
+ <uri type="src">https://csrc.nist.gov/pubs/sp/800/38/a/sup/final</uri>
249
+ <uri type="doi">https://doi.org/10.6028/NIST.SP.800-38A-Add</uri>
250
+ <docidentifier type="NIST" primary="true">NIST SP 800-38A fpd Add</docidentifier>
251
+ <docidentifier type="DOI">NIST.SP.800-38A-Add</docidentifier>
252
+ ...
253
+ <ext schema-version="v1.0.0">
254
+ <doctype>standard</doctype>
255
+ <flavor>nist</flavor>
256
+ </ext>
257
+ </bibdata>
258
+ ----
259
+ ====
260
+
261
+ === Accessing attributes
262
+
263
+ ==== Identifier components
264
+
265
+ A NIST PubID can contain these optional parameters `{ptN}{vN}{verN}{rN}{/Add}`.
266
+
267
+ They can be accessed through the following methods:
268
+
269
+ * `part`
270
+ * `volume`
271
+ * `version`
272
+ * `revision`
273
+ * `addendum`
274
+
275
+ ==== Typed links
276
+
277
+ NIST publications may have `src` and `doi` links, obtained using the `#link`
278
+ method.
279
+
280
+ .Accessing source links of a NIST bibliographic item
281
+ [example]
282
+ ====
283
+ [source,ruby]
284
+ ----
285
+ item.source[0].type
286
+ => "src"
287
+
288
+ item.source[0].content
289
+ => "https://csrc.nist.gov/pubs/sp/800/38/a/sup/final"
290
+ ----
291
+ ====
292
+
293
+ === Loading entries
294
+
295
+ // ==== From XML
296
+
297
+ ==== From YAML
298
+
299
+ Offline Relaton YAML files can be loaded directly as bibliographic items.
300
+
301
+ .Loading a Relaton YAML file
302
+ [example]
303
+ ====
304
+ [source,ruby]
305
+ ----
306
+ Relaton::Nist::Item.from_yaml File.read("spec/fixtures/item.yaml")
307
+ => #<Relaton::Nist::ItemData:0x0000000124d4ab50
308
+ ...
309
+ ----
310
+ ====
311
+
312
+ === Fetching data
313
+
314
+ On a search, the CSRC data sources are fetched and stored in the user's cache.
315
+
316
+ This gem uses the https://github.com/usnistgov/NIST-Tech-Pubs/releases/download/May2024/allrecords-MODS.xml dataset as NIST-Tech-Pubs data source.
317
+
318
+ The following method fetches all the documents from the NIST-Tech-Pubs dataset, then saves them to the `./data` folder in YAML format.
319
+
320
+ .Fetching all data with default configuration
321
+ [example]
322
+ ====
323
+ [source,ruby]
324
+ ----
325
+ require "relaton/nist/data_fetcher"
326
+
327
+ Relaton::Nist::DataFetcher.fetch
328
+ Started at: 2021-09-01 18:01:01 +0200
329
+ Stopped at: 2021-09-01 18:01:43 +0200
330
+ Done in: 42 sec.
331
+ => nil
332
+ ----
333
+ ====
334
+
335
+ .Fetching all data to the `data` folder
336
+ [example]
337
+ ====
338
+ [source,ruby]
339
+ ----
340
+ > Relaton::Nist::DataFetcher.fetch(output: "data", format: "yaml")
341
+ ----
342
+ ====
343
+
344
+ Options:
345
+
346
+ `output`:: folder to save documents (default `./data`).
347
+ `format`:: format in which the documents are saved. Possible formats are:
348
+ `yaml`::: (default) save in Relaton YAML format
349
+ `xml`::: save in Relaton XML format
350
+ `bibxml`::: save in the IETF BibXML format
351
+
352
+ === Logging
353
+
354
+ Relaton for NIST uses https://github.com/relaton/relaton-logger[relaton-logger]
355
+ for logging.
356
+
357
+ By default, it logs to `$STDOUT`. To change log levels and add other loggers,
358
+ refer to the https://github.com/relaton/relaton-logger#usage[relaton-logger]
359
+ documentation.
360
+
361
+ == Development
362
+
363
+ === Updating test fixtures
364
+
365
+ The test suite uses local copies of the NIST index and CSRC pubs-export data
366
+ to avoid network requests. To update the fixtures:
367
+
368
+ [source,sh]
369
+ ----
370
+ $ rake spec:update_index
371
+ $ rake spec:update_pubs_export
372
+ ----
373
+
374
+ `spec:update_index` downloads the latest `index-v2.zip` from the
375
+ https://github.com/relaton/relaton-data-nist[relaton-data-nist] repository.
376
+
377
+ `spec:update_pubs_export` downloads the latest `pubs-export.zip` from
378
+ the NIST Cybersecurity Resource Center (CSRC).
379
+
380
+ == Contributing
381
+
382
+ Bug reports and pull requests are welcome.
383
+
384
+ == License
385
+
386
+ Copyright Ribose.
387
+
388
+ The gem is available as open source under the terms of the
389
+ https://opensource.org/licenses/MIT[MIT License].
@@ -94,6 +94,22 @@ module Relaton
94
94
  def nistbib_results_filter(result, year, opts)
95
95
  missed_years = []
96
96
  iteration = parse_iteration(opts[:stage])
97
+ # A pubid-stage reference ("NIST FIPS 197 ipd") carries its stage
98
+ # in the identifier, not the parenthetical form parse_iteration
99
+ # reads. The stage-matched hit is already sorted first
100
+ # (HitCollection#sort_hits!), so the per-item iteration filter
101
+ # is not applied to it.
102
+ unless iteration
103
+ st = begin
104
+ ::Pubid::Nist::Identifier.parse(result.reference)&.stage
105
+ rescue StandardError
106
+ nil
107
+ end
108
+ if st
109
+ iteration = "#{st.id}#{st.type}"
110
+ pubid_stage = true
111
+ end
112
+ end
97
113
 
98
114
  # A stageless query means "the published document". Multiple
99
115
  # editions can match once stage is excluded (e.g. a final "r2"
@@ -109,7 +125,7 @@ module Relaton
109
125
  ordered.each do |h|
110
126
  r = h.item
111
127
  next if opts[:date] && !match_date?(r, opts[:date])
112
- next if iteration && r.status&.iteration != iteration
128
+ next if iteration && !pubid_stage && r.status&.iteration != iteration
113
129
  return { ret: r } unless year
114
130
  next unless match_year?(r, year) { |y| missed_years << y }
115
131
 
@@ -5,6 +5,7 @@ require "mechanize"
5
5
  require "loc_mods"
6
6
  require_relative "../nist"
7
7
  require_relative "mods_parser"
8
+ require_relative "pubs_export"
8
9
 
9
10
  module Relaton
10
11
  module Nist
@@ -13,19 +14,71 @@ module Relaton
13
14
  MODS_ASSET = "allrecords-MODS.xml"
14
15
 
15
16
  def fetch(source = nil)
16
- FileUtils.rm Dir[File.join(@output, "*.#{@ext}")]
17
+ # An incremental run keeps the tree: unchanged records are not
18
+ # rewritten (Core#force_write) and files this crawl no longer produces
19
+ # are pruned after it completes (Core::DataFetcher.fetch(prune: true)).
20
+ FileUtils.rm Dir[File.join(@output, "*.#{@ext}")] unless prune
17
21
  fetch_tech_pubs source
22
+ fetch_csrc
18
23
  # add_static_files
19
24
  index.save
20
25
  report_errors
21
26
  end
22
27
 
23
28
  def fetch_tech_pubs(source = nil)
24
- xml_data = Mechanize.new.get(source_url(source)).body
25
- docs = LocMods::Collection.from_xml xml_data
29
+ page = Mechanize.new.get(source_url(source))
30
+ # The resolved URI names the concrete release the redirect landed on
31
+ # (latest/download moves as NIST-Tech-Pubs publishes) — log it so every
32
+ # crawl proves which release it read.
33
+ Util.info "NIST-Tech-Pubs release: #{page.uri}", key: "nist"
34
+ docs = LocMods::Collection.from_xml page.body
26
35
  docs.mods.each { |doc| write_file ModsParser.new(doc, series, @errors).parse }
27
36
  end
28
37
 
38
+ # Supplements the MODS collection with the CSRC pubs-export feed
39
+ # (csrc.nist.gov feeds/metanorma/pubs-export.zip, the same zip
40
+ # PubsExport caches for the runtime): the MODS carries no draft-stage
41
+ # records at all, while the CSRC feed lists them (e.g. FIPS 197 as the
42
+ # 2001 final, upd1, and upd1.ipd). Only draft rows are supplemented —
43
+ # the finals already come from the MODS pass, and a draft row never
44
+ # shadows its base document (relaton#313, the root of relaton#146).
45
+ #
46
+ # A row is built exactly as the runtime builds a CSRC hit
47
+ # (HitCollection#hit_data → Scraper.parse_page), so the crawled record
48
+ # and the live one share one id: the stage, revision and update all come
49
+ # from the row's uri and DOI ("NIST FIPS 197/Upd1 ipd",
50
+ # "NIST SP 800-108r1 ipd", "NIST SP 1800-12 2pd"). A row whose id
51
+ # carries no stage is a final, and is skipped.
52
+ #
53
+ # CSRC lists a republished draft twice, at …/ipd and …/ipd-(1), with one
54
+ # DOI and so one id: the plain uri goes first, and each id is written once.
55
+ def fetch_csrc(rows = PubsExport.instance.data)
56
+ hits = HitCollection.new ""
57
+ codes = Set.new
58
+ plain, republished = rows.partition { |doc| doc["uri"].to_s !~ /-\(\d+\)\z/ }
59
+ (plain + republished).each do |doc|
60
+ bib = csrc_bib hits, doc
61
+ write_file bib if bib && codes.add?(bib.docidentifier.first.content)
62
+ end
63
+ end
64
+
65
+ # Builds the record of a draft-stage CSRC row, or nil for a final row or
66
+ # a row that fails to build (recorded in #failures). Write errors are not
67
+ # rescued here: they stop the crawl, as they do in the MODS pass.
68
+ def csrc_bib(hits, doc)
69
+ data = hits.hit_data doc
70
+ return unless pubid(data[:code])&.stage
71
+
72
+ bib = Scraper.parse_page data
73
+ # The runtime stamps the fetch date; a crawled record must not, or it
74
+ # changes every day and Core#force_write rewrites it on each crawl.
75
+ bib.fetched = nil
76
+ bib
77
+ rescue StandardError => e
78
+ failures << "CSRC record skipped: #{e.message} (#{doc['docidentifier']}, #{doc['uri']})"
79
+ nil
80
+ end
81
+
29
82
  # Build the MODS download URL for a NIST-Tech-Pubs release. With no tag
30
83
  # (nil, blank, or "latest") use GitHub's `latest/download` redirect so new
31
84
  # releases are picked up automatically by the crawler; a concrete tag
@@ -53,6 +106,9 @@ module Relaton
53
106
  elsif file != output_file(docid)
54
107
  Util.warn "File #{output_file docid} exists. Docid: #{id.content}. Writing #{file} instead."
55
108
  end
109
+ # Serialize before indexing: a record that fails to serialize must not
110
+ # leave an index row that names a file never written.
111
+ content = serialize(bib)
56
112
  @files << file
57
113
  pid = pubid id.content
58
114
  if pid
@@ -60,7 +116,7 @@ module Relaton
60
116
  else
61
117
  failures << "Unparseable id `#{id.content}` was not indexed (#{file})"
62
118
  end
63
- File.write file, serialize(bib), encoding: "UTF-8"
119
+ force_write file, content
64
120
  end
65
121
 
66
122
  # A human-readable label for a bib with no usable docidentifier, so the