relaton-nist 2.0.0.pre.alpha.3 → 2.1.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +1 -0
- data/CLAUDE.md +15 -0
- data/Gemfile +1 -0
- data/README.adoc +19 -0
- data/Rakefile +42 -0
- data/grammars/biblio.rng +4 -8
- data/lib/relaton/nist/bibdata.rb +1 -0
- data/lib/relaton/nist/bibitem.rb +1 -0
- data/lib/relaton/nist/ext.rb +3 -7
- data/lib/relaton/nist/hit_collection.rb +29 -7
- data/lib/relaton/nist/version.rb +1 -1
- data/{relaton_nist.gemspec → relaton-nist.gemspec} +2 -2
- metadata +11 -9
- data/lib/relaton/nist/tech_pubs_parser.rb +0 -321
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44c06fc2b378b7ac0ad228f82dbfe6d962d5cd22fb27fa8c6f19cfcde20589f7
|
|
4
|
+
data.tar.gz: 96e9c89fdf092af8933e65dc02b98f4d19d70593de2b12a18a18740d85b47d27
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca9c2ecb226ceec065fd82db6ce7d4584a0c11176364235102a97c5e3673fb156b60f68631afb5655c91bd313a1d370fe2377ec8c5c6498f06b46e57a478f459
|
|
7
|
+
data.tar.gz: 76bcdb75f303654bbb08bb9cc70c1859795d4ea41d73ee1db0b6f854a595b4871d2038ce169146c8112c08f6fad4631d33b80445b024ae56ff71dd1d7100107c
|
data/.github/workflows/rake.yml
CHANGED
data/CLAUDE.md
CHANGED
|
@@ -82,6 +82,21 @@ errors = schema.validate(file)
|
|
|
82
82
|
|
|
83
83
|
Tests use VCR with WebMock. Cassettes are stored in `spec/vcr_cassettes/` and re-record every 7 days.
|
|
84
84
|
|
|
85
|
+
### Test Data Stubbing
|
|
86
|
+
|
|
87
|
+
Tests pre-load both the NIST index and CSRC pubs-export data from local fixtures in `before(:suite)` (see `spec/support/webmock.rb`), avoiding all HTTP requests for these data sources. VCR is configured to ignore both `index-v1.zip` and `pubs-export` requests (`spec/support/vcr.rb`).
|
|
88
|
+
|
|
89
|
+
- **Index**: A `Relaton::Index::Type` instance is created with `@index` set directly from `spec/fixtures/index-v1.zip`, then injected into `Relaton::Index.pool`. Run `rake spec:update_index` to refresh.
|
|
90
|
+
- **PubsExport**: The `PubsExport` singleton's `@data` is set directly from `spec/fixtures/pubs-export.zip`. Run `rake spec:update_pubs_export` to refresh.
|
|
91
|
+
|
|
92
|
+
To apply the index stubbing pattern to other relaton gems:
|
|
93
|
+
|
|
94
|
+
1. Add `spec:update_index` rake task (downloads `index-v1.zip` from the gem's GitHub data repo)
|
|
95
|
+
2. Run `rake spec:update_index` to create `spec/fixtures/index-v1.zip`
|
|
96
|
+
3. In `spec/support/webmock.rb`: parse the zip once, create a `Type` instance with `@index` set directly, override `actual?` to return `true`, inject into `Relaton::Index.pool`
|
|
97
|
+
4. In `spec/support/vcr.rb`: add `ignore_request` for `index-v1.zip`
|
|
98
|
+
5. Remove any `allow_any_instance_of(Relaton::Index::Type)` workarounds from specs
|
|
99
|
+
|
|
85
100
|
## Key Dependencies
|
|
86
101
|
|
|
87
102
|
- `relaton-bib` — base bibliographic models and shared mixins
|
data/Gemfile
CHANGED
data/README.adoc
CHANGED
|
@@ -405,6 +405,25 @@ refer to the https://github.com/relaton/relaton-logger#usage[relaton-logger]
|
|
|
405
405
|
documentation.
|
|
406
406
|
|
|
407
407
|
|
|
408
|
+
== Development
|
|
409
|
+
|
|
410
|
+
=== Updating test fixtures
|
|
411
|
+
|
|
412
|
+
The test suite uses local copies of the NIST index and CSRC pubs-export data
|
|
413
|
+
to avoid network requests. To update the fixtures:
|
|
414
|
+
|
|
415
|
+
[source,sh]
|
|
416
|
+
----
|
|
417
|
+
$ rake spec:update_index
|
|
418
|
+
$ rake spec:update_pubs_export
|
|
419
|
+
----
|
|
420
|
+
|
|
421
|
+
`spec:update_index` downloads the latest `index-v1.zip` from the
|
|
422
|
+
https://github.com/relaton/relaton-data-nist[relaton-data-nist] repository.
|
|
423
|
+
|
|
424
|
+
`spec:update_pubs_export` downloads the latest `pubs-export.zip` from
|
|
425
|
+
the NIST Cybersecurity Resource Center (CSRC).
|
|
426
|
+
|
|
408
427
|
== Contributing
|
|
409
428
|
|
|
410
429
|
Bug reports and pull requests are welcome.
|
data/Rakefile
CHANGED
|
@@ -4,3 +4,45 @@ require "rspec/core/rake_task"
|
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
|
5
5
|
|
|
6
6
|
task :default => :spec
|
|
7
|
+
|
|
8
|
+
namespace :spec do
|
|
9
|
+
desc "Download latest NIST index fixture from relaton-data-nist"
|
|
10
|
+
task :update_index do
|
|
11
|
+
require "net/http"
|
|
12
|
+
require "uri"
|
|
13
|
+
|
|
14
|
+
url = "https://raw.githubusercontent.com/relaton/relaton-data-nist/data-v2/index-v1.zip"
|
|
15
|
+
dest = File.join(__dir__, "spec", "fixtures", "index-v1.zip")
|
|
16
|
+
|
|
17
|
+
puts "Downloading #{url} ..."
|
|
18
|
+
uri = URI.parse(url)
|
|
19
|
+
response = Net::HTTP.get_response(uri)
|
|
20
|
+
|
|
21
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
22
|
+
File.binwrite(dest, response.body)
|
|
23
|
+
puts "Updated #{dest} (#{response.body.bytesize} bytes)"
|
|
24
|
+
else
|
|
25
|
+
abort "Failed to download: HTTP #{response.code}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc "Download latest NIST pubs-export fixture from CSRC"
|
|
30
|
+
task :update_pubs_export do
|
|
31
|
+
require "net/http"
|
|
32
|
+
require "uri"
|
|
33
|
+
|
|
34
|
+
url = "https://csrc.nist.gov/CSRC/media/feeds/metanorma/pubs-export.zip"
|
|
35
|
+
dest = File.join(__dir__, "spec", "fixtures", "pubs-export.zip")
|
|
36
|
+
|
|
37
|
+
puts "Downloading #{url} ..."
|
|
38
|
+
uri = URI.parse(url)
|
|
39
|
+
response = Net::HTTP.get_response(uri)
|
|
40
|
+
|
|
41
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
42
|
+
File.binwrite(dest, response.body)
|
|
43
|
+
puts "Updated #{dest} (#{response.body.bytesize} bytes)"
|
|
44
|
+
else
|
|
45
|
+
abort "Failed to download: HTTP #{response.code}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/grammars/biblio.rng
CHANGED
|
@@ -2015,15 +2015,11 @@ provided that it is not the entire bibliographic item that is so related</a:docu
|
|
|
2015
2015
|
<a:documentation>A version of the bibliographic item (within an edition). Can be used for drafts</a:documentation>
|
|
2016
2016
|
<element name="version">
|
|
2017
2017
|
<optional>
|
|
2018
|
-
<
|
|
2019
|
-
<a:documentation>
|
|
2020
|
-
</
|
|
2021
|
-
</optional>
|
|
2022
|
-
<optional>
|
|
2023
|
-
<ref name="draft">
|
|
2024
|
-
<a:documentation>The identifier for the current draft of the bibliographic item</a:documentation>
|
|
2025
|
-
</ref>
|
|
2018
|
+
<attribute name="type">
|
|
2019
|
+
<a:documentation>Versioning scheme, in case of multiple versioning schemes</a:documentation>
|
|
2020
|
+
</attribute>
|
|
2026
2021
|
</optional>
|
|
2022
|
+
<text/>
|
|
2027
2023
|
</element>
|
|
2028
2024
|
</define>
|
|
2029
2025
|
<define name="vedition">
|
data/lib/relaton/nist/bibdata.rb
CHANGED
data/lib/relaton/nist/bibitem.rb
CHANGED
data/lib/relaton/nist/ext.rb
CHANGED
|
@@ -4,17 +4,13 @@ require_relative "comment_period"
|
|
|
4
4
|
module Relaton
|
|
5
5
|
module Nist
|
|
6
6
|
class Ext < Bib::Ext
|
|
7
|
-
attribute :schema_version, method: :get_schema_version
|
|
8
7
|
attribute :doctype, Doctype
|
|
9
8
|
attribute :commentperiod, CommentPeriod
|
|
10
9
|
|
|
11
|
-
xml
|
|
12
|
-
|
|
13
|
-
end
|
|
10
|
+
xml { map_element "commentperiod", to: :commentperiod }
|
|
11
|
+
key_value { map_element "commentperiod", to: :commentperiod }
|
|
14
12
|
|
|
15
|
-
def get_schema_version
|
|
16
|
-
Relaton.schema_versions["relaton-model-nist"]
|
|
17
|
-
end
|
|
13
|
+
def get_schema_version = Relaton.schema_versions["relaton-model-nist"]
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
end
|
|
@@ -169,14 +169,32 @@ module Relaton
|
|
|
169
169
|
#
|
|
170
170
|
def sort_hits!
|
|
171
171
|
@array.sort! do |a, b|
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
base_a, upd_a = base_and_update(a.hit[:code])
|
|
173
|
+
base_b, upd_b = base_and_update(b.hit[:code])
|
|
174
|
+
|
|
175
|
+
cmp = base_a <=> base_b
|
|
176
|
+
next cmp unless cmp.zero?
|
|
177
|
+
|
|
178
|
+
# Same base code: prefer higher /UpdN (latest update wins).
|
|
179
|
+
cmp = upd_b <=> upd_a
|
|
180
|
+
next cmp unless cmp.zero?
|
|
174
181
|
|
|
175
182
|
b.hit[:release_date] <=> a.hit[:release_date]
|
|
176
183
|
end
|
|
177
184
|
self
|
|
178
185
|
end
|
|
179
186
|
|
|
187
|
+
# Split a code like "NIST FIPS 140-2/Upd2" into ["NIST FIPS 140-2", 2].
|
|
188
|
+
# Codes without an update suffix return update 0.
|
|
189
|
+
def base_and_update(code)
|
|
190
|
+
code = code.to_s
|
|
191
|
+
if (m = code.match(%r{\A(.*?)/Upd(\d+)\z}))
|
|
192
|
+
[m[1], m[2].to_i]
|
|
193
|
+
else
|
|
194
|
+
[code, 0]
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
180
198
|
def pubid(id = ref)
|
|
181
199
|
Pubid::Nist::Identifier.parse(id).to_s
|
|
182
200
|
rescue StandardError
|
|
@@ -230,12 +248,16 @@ module Relaton
|
|
|
230
248
|
id.sub!(/(?:-draft\d*|\.\wpd)$/, "")
|
|
231
249
|
id = id.gsub(".", " ").sub(/-Add(\d*)$/, ' Add\1') if id.match?(/-Add\d*$/)
|
|
232
250
|
pid = ::Pubid::Nist::Identifier.parse(id)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
|
|
252
|
+
# Stage: URI is authoritative, fall back to iteration. "final" => no stage.
|
|
253
|
+
uri_stage = json["uri"] && json["uri"][%r{/(final|ipd|fpd|\dpd)(?:-\(\d+\))?(?:/|$)}, 1]
|
|
254
|
+
stage_src = uri_stage || json["iteration"]
|
|
255
|
+
case stage_src
|
|
256
|
+
when nil, "final"
|
|
257
|
+
# no stage — "final" means published
|
|
236
258
|
when "fpd"
|
|
237
259
|
pid.stage = ::Pubid::Nist::Stage.new id: "f", type: "pd"
|
|
238
|
-
when
|
|
260
|
+
when /\A(\w)pd\z/
|
|
239
261
|
pid.stage = ::Pubid::Nist::Stage.new id: Regexp.last_match(1), type: "pd"
|
|
240
262
|
end
|
|
241
263
|
|
|
@@ -243,7 +265,7 @@ module Relaton
|
|
|
243
265
|
pid.update = Pubid::Nist::Update.new number: upd if upd
|
|
244
266
|
pid.to_s
|
|
245
267
|
rescue StandardError
|
|
246
|
-
id += " #{json["iteration"]
|
|
268
|
+
id += " #{json["iteration"]}" if json["iteration"] && json["iteration"] != "final"
|
|
247
269
|
id
|
|
248
270
|
end
|
|
249
271
|
|
data/lib/relaton/nist/version.rb
CHANGED
|
@@ -25,9 +25,9 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
|
|
26
26
|
spec.add_dependency "base64"
|
|
27
27
|
spec.add_dependency "mechanize", "~> 2.0"
|
|
28
|
-
spec.add_dependency "loc_mods", "~> 0.
|
|
28
|
+
spec.add_dependency "loc_mods", "~> 0.3.0"
|
|
29
29
|
spec.add_dependency "pubid", "~> 1.15.6"
|
|
30
|
-
spec.add_dependency "relaton-bib", "~> 2.
|
|
30
|
+
spec.add_dependency "relaton-bib", "~> 2.1.0"
|
|
31
31
|
spec.add_dependency "relaton-core", "~> 0.0.13"
|
|
32
32
|
spec.add_dependency "relaton-index", "~> 0.2.0"
|
|
33
33
|
spec.add_dependency "rubyzip"
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: relaton-nist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-05-04 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: base64
|
|
@@ -43,14 +44,14 @@ dependencies:
|
|
|
43
44
|
requirements:
|
|
44
45
|
- - "~>"
|
|
45
46
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.
|
|
47
|
+
version: 0.3.0
|
|
47
48
|
type: :runtime
|
|
48
49
|
prerelease: false
|
|
49
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
51
|
requirements:
|
|
51
52
|
- - "~>"
|
|
52
53
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 0.
|
|
54
|
+
version: 0.3.0
|
|
54
55
|
- !ruby/object:Gem::Dependency
|
|
55
56
|
name: pubid
|
|
56
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -71,14 +72,14 @@ dependencies:
|
|
|
71
72
|
requirements:
|
|
72
73
|
- - "~>"
|
|
73
74
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: 2.
|
|
75
|
+
version: 2.1.0
|
|
75
76
|
type: :runtime
|
|
76
77
|
prerelease: false
|
|
77
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
79
|
requirements:
|
|
79
80
|
- - "~>"
|
|
80
81
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: 2.
|
|
82
|
+
version: 2.1.0
|
|
82
83
|
- !ruby/object:Gem::Dependency
|
|
83
84
|
name: relaton-core
|
|
84
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -165,14 +166,14 @@ files:
|
|
|
165
166
|
- lib/relaton/nist/relation.rb
|
|
166
167
|
- lib/relaton/nist/scraper.rb
|
|
167
168
|
- lib/relaton/nist/series.yaml
|
|
168
|
-
- lib/relaton/nist/tech_pubs_parser.rb
|
|
169
169
|
- lib/relaton/nist/util.rb
|
|
170
170
|
- lib/relaton/nist/version.rb
|
|
171
|
-
-
|
|
171
|
+
- relaton-nist.gemspec
|
|
172
172
|
homepage: https://github.com/metanorma/relaton-nist
|
|
173
173
|
licenses:
|
|
174
174
|
- MIT
|
|
175
175
|
metadata: {}
|
|
176
|
+
post_install_message:
|
|
176
177
|
rdoc_options: []
|
|
177
178
|
require_paths:
|
|
178
179
|
- lib
|
|
@@ -187,7 +188,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
187
188
|
- !ruby/object:Gem::Version
|
|
188
189
|
version: '0'
|
|
189
190
|
requirements: []
|
|
190
|
-
rubygems_version: 3.
|
|
191
|
+
rubygems_version: 3.5.22
|
|
192
|
+
signing_key:
|
|
191
193
|
specification_version: 4
|
|
192
194
|
summary: 'Relaton::Nist: retrive NIST standards.'
|
|
193
195
|
test_files: []
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
module Relaton
|
|
2
|
-
module Nist
|
|
3
|
-
class TechPubsParser
|
|
4
|
-
RELATION_TYPES = {
|
|
5
|
-
"replaces" => "obsoletes",
|
|
6
|
-
"isVersionOf" => "editionOf",
|
|
7
|
-
"hasTranslation" => "hasTranslation",
|
|
8
|
-
"isTranslationOf" => "translatedFrom",
|
|
9
|
-
"hasPreprint" => "hasReprint",
|
|
10
|
-
"isPreprintOf" => "hasDraft",
|
|
11
|
-
"isSupplementTo" => "complements",
|
|
12
|
-
"isPartOf" => "partOf",
|
|
13
|
-
"hasPart" => "hasPart",
|
|
14
|
-
}.freeze
|
|
15
|
-
|
|
16
|
-
ATTRS = %i[docidentifier title source abstract date edition contributor
|
|
17
|
-
relation status place series].freeze
|
|
18
|
-
NS = "http://www.crossref.org/relations.xsd".freeze
|
|
19
|
-
|
|
20
|
-
def initialize(doc, series)
|
|
21
|
-
@doc = doc
|
|
22
|
-
@series = series
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
#
|
|
26
|
-
# Parse XML document
|
|
27
|
-
#
|
|
28
|
-
# @param doc [Nokogiri::XML::Element] XML document
|
|
29
|
-
# @param series [Hash] series hash map
|
|
30
|
-
#
|
|
31
|
-
# @return [Relaton::Nist::ItemData] bibliographic item
|
|
32
|
-
#
|
|
33
|
-
def self.parse(doc, series)
|
|
34
|
-
new(doc, series).parse
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
# Create document instance
|
|
39
|
-
#
|
|
40
|
-
# @return [Relaton::Nist::ItemData] bibliographic item
|
|
41
|
-
#
|
|
42
|
-
def parse
|
|
43
|
-
ItemData.new(
|
|
44
|
-
type: "standard", language: [@doc["language"]], script: ["Latn"],
|
|
45
|
-
ext: Ext.new(doctype: parse_doctype), **args
|
|
46
|
-
)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def args
|
|
50
|
-
ATTRS.to_h { |a| [a, send("parse_#{a}")] }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# @return [Array<Bib::Docidentifier>]
|
|
54
|
-
def parse_docidentifier
|
|
55
|
-
[
|
|
56
|
-
{ type: "NIST", content: pub_id, primary: true },
|
|
57
|
-
{ type: "DOI", content: doi },
|
|
58
|
-
].map { |id| Bib::Docidentifier.new(**id) }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
# Parse document's ID from XML
|
|
63
|
-
#
|
|
64
|
-
# @return [String] document's ID
|
|
65
|
-
#
|
|
66
|
-
def pub_id
|
|
67
|
-
if doi
|
|
68
|
-
doi.split("/")[1..].join("/").gsub(".", " ").sub(/^[\D]+/, &:upcase)
|
|
69
|
-
else
|
|
70
|
-
@doc.at("publisher_item/item_number").text
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def doi # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
75
|
-
return @doi if defined? @doi
|
|
76
|
-
|
|
77
|
-
@doi = begin
|
|
78
|
-
id = @doc.at("doi_data/doi")&.text
|
|
79
|
-
case id
|
|
80
|
-
when "10.6028/NBS.CIRC.e2e" then "10.6028/NBS.CIRC.2e2"
|
|
81
|
-
when "10.6028/NBS.CIRC.sup" then "10.6028/NBS.CIRC.24e7sup"
|
|
82
|
-
when "10.6028/NBS.CIRC.supJun1925-Jun1926" then "10.6028/NBS.CIRC.24e7sup2"
|
|
83
|
-
when "10.6028/NBS.CIRC.supJun1925-Jun1927" then "10.6028/NBS.CIRC.24e7sup3"
|
|
84
|
-
when "10.6028/NBS.CIRC.24supJuly1922" then "10.6028/NBS.CIRC.24e6sup"
|
|
85
|
-
when "10.6028/NBS.CIRC.24supJan1924" then "10.6028/NBS.CIRC.24e6sup2"
|
|
86
|
-
else id
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# @return [Array<Bib::Title>]
|
|
92
|
-
def parse_title
|
|
93
|
-
t = @doc.xpath("titles/title|titles/subtitle")
|
|
94
|
-
return [] unless t.any?
|
|
95
|
-
|
|
96
|
-
[Bib::Title.new(content: t.map(&:text).join("\n"), language: "en", script: "Latn")]
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# @return [Array<Bib::Uri>]
|
|
100
|
-
def parse_source
|
|
101
|
-
pdf_url = @doc.at("doi_data/resource").text
|
|
102
|
-
doi_url = "https://doi.org/#{doi}"
|
|
103
|
-
[
|
|
104
|
-
Bib::Uri.new(type: "doi", content: doi_url),
|
|
105
|
-
Bib::Uri.new(type: "pdf", content: pdf_url),
|
|
106
|
-
]
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# @return [Array<Bib::LocalizedMarkedUpString>]
|
|
110
|
-
def parse_abstract
|
|
111
|
-
@doc.xpath(
|
|
112
|
-
"jats:abstract/jats:p", "jats" => "http://www.ncbi.nlm.nih.gov/JATS1"
|
|
113
|
-
).each_with_object([]) do |a, m|
|
|
114
|
-
next if a.text.empty?
|
|
115
|
-
|
|
116
|
-
m << Bib::Abstract.new(
|
|
117
|
-
content: a.text, language: @doc["language"], script: "Latn",
|
|
118
|
-
)
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# @return [Array<Bib::Date>]
|
|
123
|
-
def parse_date
|
|
124
|
-
@doc.xpath("publication_date|approval_date").map do |dt|
|
|
125
|
-
on = dt.at("year").text
|
|
126
|
-
if (m = dt.at "month")
|
|
127
|
-
on += "-#{m.text}"
|
|
128
|
-
d = dt.at "day"
|
|
129
|
-
on += "-#{d.text}" if d
|
|
130
|
-
end
|
|
131
|
-
type = dt.name == "publication_date" ? "published" : "confirmed"
|
|
132
|
-
Bib::Date.new(type: type, at: on)
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def parse_doctype
|
|
137
|
-
Doctype.new(content: "standard")
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# @return [String]
|
|
141
|
-
def parse_edition
|
|
142
|
-
@doc.at("edition_number")&.text
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# @return [Array<Bib::Contributor>]
|
|
146
|
-
def parse_contributor # rubocop:disable Metrics/AbcSize
|
|
147
|
-
contribs = @doc.xpath("contributors/person_name").map do |p|
|
|
148
|
-
person = Bib::Person.new(
|
|
149
|
-
name: fullname(p), affiliation: affiliation, identifier: identifier(p),
|
|
150
|
-
)
|
|
151
|
-
Bib::Contributor.new(
|
|
152
|
-
person: person,
|
|
153
|
-
role: [Bib::Contributor::Role.new(type: p["contributor_role"])],
|
|
154
|
-
)
|
|
155
|
-
end
|
|
156
|
-
contribs + @doc.xpath("publisher").map do |p|
|
|
157
|
-
Bib::Contributor.new(
|
|
158
|
-
organization: create_org(p),
|
|
159
|
-
role: [Bib::Contributor::Role.new(type: "publisher")],
|
|
160
|
-
)
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def identifier(person)
|
|
165
|
-
person.xpath("ORCID").map do |id|
|
|
166
|
-
Bib::Person::Identifier.new(type: "orcid", content: id.text)
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
#
|
|
171
|
-
# Create full name object from person name element.
|
|
172
|
-
#
|
|
173
|
-
# @param [Nokogiri::XML::Element] person name element
|
|
174
|
-
#
|
|
175
|
-
# @return [Bib::FullName] full name object
|
|
176
|
-
#
|
|
177
|
-
def fullname(person)
|
|
178
|
-
fname, initials = forename_initial person
|
|
179
|
-
surname = localized_string person.at("surname")&.text
|
|
180
|
-
completename = localized_string person.text unless surname
|
|
181
|
-
Bib::FullName.new(
|
|
182
|
-
surname: surname, forename: fname, formatted_initials: initials,
|
|
183
|
-
completename: completename,
|
|
184
|
-
)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
#
|
|
188
|
-
# Create affiliation organization
|
|
189
|
-
#
|
|
190
|
-
# @return [Array<Bib::Affiliation>] affiliation
|
|
191
|
-
#
|
|
192
|
-
def affiliation
|
|
193
|
-
@doc.xpath("./institution/institution_department").map do |id|
|
|
194
|
-
org = Bib::Organization.new(
|
|
195
|
-
name: [Bib::TypedLocalizedString.new(content: id.text)],
|
|
196
|
-
)
|
|
197
|
-
Bib::Affiliation.new(organization: org)
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
#
|
|
202
|
-
# Create forename and initials objects from person name element.
|
|
203
|
-
#
|
|
204
|
-
# @param [Nokogiri::XML::Element] person person name element
|
|
205
|
-
#
|
|
206
|
-
# @return [Array<Array<Bib::FullNameType::Forename>, Bib::LocalizedString>]
|
|
207
|
-
#
|
|
208
|
-
def forename_initial(person) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
209
|
-
fnames = []
|
|
210
|
-
fname = person.at("given_name")&.text
|
|
211
|
-
if fname
|
|
212
|
-
if /^(?:(?<name>\w+)\s)?(?<inits>(?:\w(?:\.|\b)\s?)+)/ =~ fname
|
|
213
|
-
ints = inits.split(/[.\s]*/)
|
|
214
|
-
fnames << forename(name, ints.shift)
|
|
215
|
-
ints.each { |i| fnames << forename(nil, i) }
|
|
216
|
-
else
|
|
217
|
-
fn = forename(fname)
|
|
218
|
-
fnames << fn if fn
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
initials = localized_string inits unless inits.nil? || inits.empty?
|
|
222
|
-
[fnames, initials]
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
#
|
|
226
|
-
# Create forename object
|
|
227
|
-
#
|
|
228
|
-
# @param [String, nil] cnt forename content
|
|
229
|
-
# @param [String, nil] init initial content
|
|
230
|
-
#
|
|
231
|
-
# @return [Bib::FullNameType::Forename] forename object
|
|
232
|
-
#
|
|
233
|
-
def forename(cnt, init = nil)
|
|
234
|
-
return if (cnt.nil? || cnt.empty?) && (init.nil? || init.empty?)
|
|
235
|
-
|
|
236
|
-
Bib::FullNameType::Forename.new(
|
|
237
|
-
content: cnt, language: @doc["language"], script: "Latn", initial: init,
|
|
238
|
-
)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
#
|
|
242
|
-
# Create publisher organization
|
|
243
|
-
#
|
|
244
|
-
# @param [Nokogiri::XML::Element] pub publisher element
|
|
245
|
-
#
|
|
246
|
-
# @return [Bib::Organization] publisher organization
|
|
247
|
-
#
|
|
248
|
-
def create_org(pub) # rubocop:disable Metrics/AbcSize
|
|
249
|
-
name = pub.at("publisher_name").text
|
|
250
|
-
abbr = pub.at("../institution[institution_name[.='#{name}']]/institution_acronym")&.text
|
|
251
|
-
place = pub.at("./publisher_place") ||
|
|
252
|
-
pub.at("../institution[institution_name[.='#{name}']]/institution_place")
|
|
253
|
-
cont = []
|
|
254
|
-
if place
|
|
255
|
-
city, state = place.text.split(", ")
|
|
256
|
-
cont << Bib::Address.new(street: [], city: city, state: state, country: "US")
|
|
257
|
-
end
|
|
258
|
-
Bib::Organization.new(
|
|
259
|
-
name: [Bib::TypedLocalizedString.new(content: name)],
|
|
260
|
-
abbreviation: abbr ? Bib::LocalizedString.new(content: abbr) : nil,
|
|
261
|
-
address: cont,
|
|
262
|
-
)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# @return [Array<Nist::Relation>]
|
|
266
|
-
def parse_relation # rubocop:disable Metrics/AbcSize
|
|
267
|
-
@doc.xpath("./ns:program/ns:related_item", ns: NS).map do |rel|
|
|
268
|
-
rdoi = rel.at_xpath("ns:intra_work_relation|ns:inter_work_relation", ns: NS)
|
|
269
|
-
id = rdoi.text.split("/")[1..].join("/").gsub(".", " ")
|
|
270
|
-
fref = Bib::Formattedref.new(content: id)
|
|
271
|
-
docid = Bib::Docidentifier.new(type: "NIST", content: id, primary: true)
|
|
272
|
-
bibitem = ItemData.new(formattedref: fref, docidentifier: [docid])
|
|
273
|
-
type = RELATION_TYPES[rdoi["relationship-type"]]
|
|
274
|
-
warn "Relation type #{rdoi['relationship-type']} not found" unless type
|
|
275
|
-
Relation.new(type: type, bibitem: bibitem)
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
def parse_status
|
|
280
|
-
s = @doc.at("./ns:program/ns:related_item/ns:*[@relationship-type='isPreprintOf']", ns: NS)
|
|
281
|
-
return unless s
|
|
282
|
-
|
|
283
|
-
Bib::Status.new(stage: Bib::Status::Stage.new(content: "preprint"))
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
# @return [Array<Bib::Place>]
|
|
287
|
-
def parse_place
|
|
288
|
-
@doc.xpath("institution/institution_place").map do |p|
|
|
289
|
-
city, state = p.text.split(", ")
|
|
290
|
-
Bib::Place.new(city: city, region: [Bib::Place::RegionType.new(iso: state)])
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
#
|
|
295
|
-
# Fetches series
|
|
296
|
-
#
|
|
297
|
-
# @return [Array<Bib::Series>] series
|
|
298
|
-
#
|
|
299
|
-
def parse_series
|
|
300
|
-
prf, srs, num = pub_id.split
|
|
301
|
-
sname = @series[srs] || srs
|
|
302
|
-
title = Bib::Title.new(content: "#{prf} #{sname}")
|
|
303
|
-
abbr = Bib::LocalizedString.new(content: srs)
|
|
304
|
-
[Bib::Series.new(title: [title], abbreviation: abbr, number: num)]
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
#
|
|
308
|
-
# Create localized string
|
|
309
|
-
#
|
|
310
|
-
# @param [String] content content of string
|
|
311
|
-
#
|
|
312
|
-
# @return [Bib::LocalizedString] localized string
|
|
313
|
-
#
|
|
314
|
-
def localized_string(content)
|
|
315
|
-
return unless content
|
|
316
|
-
|
|
317
|
-
Bib::LocalizedString.new(content: content, language: @doc["language"], script: "Latn")
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
end
|