relaton-doi 1.14.2 → 1.14.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +1 -0
- data/.github/workflows/release.yml +3 -2
- data/.gitignore +1 -0
- data/.rubocop.yml +1 -1
- data/Gemfile +7 -2
- data/lib/relaton_doi/crossref.rb +11 -424
- data/lib/relaton_doi/parser.rb +773 -0
- data/lib/relaton_doi/version.rb +1 -1
- data/lib/relaton_doi.rb +1 -2
- data/relaton-doi.gemspec +6 -13
- metadata +12 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4184d182bd51c7b61a50ae43d52bd95ddd73024defb5ca8ee96641ca7ae4b13b
|
4
|
+
data.tar.gz: 519e7864a1ddea5cc3a74569848e08ba0951d61877b0e9c9262c76b69afb46c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b544c06393cd74c073cc1c815e12646ef8d06b3f6fcbee464547d21416617d707ea33ffd370bb69c0aba94d6feda816bc78fa0cb36bc7ac5960ed90e5bc4304f
|
7
|
+
data.tar.gz: bd11ec0a75b525c6d34a0b922a1a2b64784a2944f3fd8e49f5756493160b25a7da7078c70ac2b853f7bf8a19ec3e941eb3597327293ccdca6a74429140bb2f55
|
data/.github/workflows/rake.yml
CHANGED
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
@@ -5,8 +5,13 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in relaton-doi.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
gem "debug"
|
9
|
+
gem "equivalent-xml", "~> 0.6"
|
8
10
|
gem "rake", "~> 13.0"
|
9
|
-
|
10
11
|
gem "rspec", "~> 3.0"
|
11
|
-
|
12
12
|
gem "rubocop", "~> 1.7"
|
13
|
+
gem "rubocop-performance"
|
14
|
+
gem "rubocop-rails"
|
15
|
+
gem "simplecov"
|
16
|
+
gem "vcr"
|
17
|
+
gem "webmock"
|
data/lib/relaton_doi/crossref.rb
CHANGED
@@ -1,53 +1,6 @@
|
|
1
1
|
module RelatonDoi
|
2
|
-
|
3
|
-
|
4
|
-
"book-chapter" => "inbook",
|
5
|
-
"book-part" => "inbook",
|
6
|
-
"book-section" => "inbook",
|
7
|
-
"book-series" => "book",
|
8
|
-
"book-set" => "book",
|
9
|
-
"book-track" => "inbook",
|
10
|
-
"component" => "misc",
|
11
|
-
"database" => "dataset",
|
12
|
-
"dissertation" => "thesis",
|
13
|
-
"edited-book" => "book",
|
14
|
-
"grant" => "misc",
|
15
|
-
"journal-article" => "article",
|
16
|
-
"journal-issue" => "journal",
|
17
|
-
"journal-volume" => "journal",
|
18
|
-
"monograph" => "book",
|
19
|
-
"other" => "misc",
|
20
|
-
"peer-review" => "article",
|
21
|
-
"posted-content" => "social_media",
|
22
|
-
"proceedings-article" => "inproceedings",
|
23
|
-
"proceedings-series" => "proceedings",
|
24
|
-
"reference-book" => "book",
|
25
|
-
"reference-entry" => "inbook",
|
26
|
-
"report-component" => "techreport",
|
27
|
-
"report-series" => "techreport",
|
28
|
-
"report" => "techreport",
|
29
|
-
}.freeze
|
30
|
-
|
31
|
-
REALATION_TYPES = {
|
32
|
-
"is-preprint-of" => "reprintOf",
|
33
|
-
"is-review-of" => "reviewOf",
|
34
|
-
"has-review" => "hasReview",
|
35
|
-
"is-identical-to" => "identicalTo", # ?
|
36
|
-
"is-supplement-to" => "complements",
|
37
|
-
}.freeze
|
38
|
-
|
39
|
-
#
|
40
|
-
# Get a document by DOI from the CrossRef API.
|
41
|
-
#
|
42
|
-
# @param [String] doi The DOI.
|
43
|
-
#
|
44
|
-
# @return [RelatonBib::BibliographicItem, RelatonIetf::IetfBibliographicItem,
|
45
|
-
# RelatonBipm::BipmBibliographicItem, RelatonIeee::IeeeBibliographicItem,
|
46
|
-
# RelatonNist::NistBibliographicItem] The bibitem.
|
47
|
-
#
|
48
|
-
def self.get(doi)
|
49
|
-
new.get doi
|
50
|
-
end
|
2
|
+
module Crossref
|
3
|
+
extend self
|
51
4
|
|
52
5
|
#
|
53
6
|
# Get a document by DOI from the CrossRef API.
|
@@ -61,387 +14,21 @@ module RelatonDoi
|
|
61
14
|
def get(doi)
|
62
15
|
warn "[relaton-doi] [\"#{doi}\"] fetching..."
|
63
16
|
id = doi.sub(%r{^doi:}, "")
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
create_bibitem @message["DOI"], bibitem_hash
|
68
|
-
end
|
69
|
-
|
70
|
-
#
|
71
|
-
# Create a bibitem from the bibitem hash.
|
72
|
-
#
|
73
|
-
# @param [String] doi The DOI.
|
74
|
-
# @param [Hash] bibitem The bibitem hash.
|
75
|
-
#
|
76
|
-
# @return [RelatonBib::BibliographicItem, RelatonIetf::IetfBibliographicItem,
|
77
|
-
# RelatonBipm::BipmBibliographicItem, RelatonIeee::IeeeBibliographicItem,
|
78
|
-
# RelatonNist::NistBibliographicItem] The bibitem.
|
79
|
-
#
|
80
|
-
# @raise [RelatonDoi::Error] if the document type is not supported.
|
81
|
-
#
|
82
|
-
def create_bibitem(doi, bibitem) # rubocop:disable Metrics/CyclomaticComplexity
|
83
|
-
# case @message["institution"]&.first&.fetch("acronym")&.first
|
84
|
-
case doi
|
85
|
-
when /\/nist/ then RelatonNist::NistBibliographicItem.new(**bibitem)
|
86
|
-
when /\/rfc\d+/ then RelatonIetf::IetfBibliographicItem.new(**bibitem)
|
87
|
-
when /\/0026-1394\// then RelatonBipm::BipmBibliographicItem.new(**bibitem)
|
88
|
-
# when "ISO" then RelatonIso::IsoBibliographicItem.new(**bibitem)
|
89
|
-
# when "W3C" then RelatonW3c::W3cBibliographicItem.new(**bibitem)
|
90
|
-
when /\/ieee/ then RelatonIeee::IeeeBibliographicItem.new(**bibitem)
|
91
|
-
else RelatonBib::BibliographicItem.new(**bibitem)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
#
|
96
|
-
# Create a bibitem hash from the message hash.
|
97
|
-
#
|
98
|
-
# @return [Hash] The bibitem hash.
|
99
|
-
#
|
100
|
-
def bibitem_hash # rubocop:disable Metrics/MethodLength
|
101
|
-
{
|
102
|
-
type: parse_type,
|
103
|
-
fetched: Date.today.to_s,
|
104
|
-
title: create_title,
|
105
|
-
docid: create_docid,
|
106
|
-
date: create_date,
|
107
|
-
link: create_link,
|
108
|
-
abstract: create_abstract,
|
109
|
-
contributor: create_contributors,
|
110
|
-
doctype: @message["type"],
|
111
|
-
place: create_place,
|
112
|
-
relation: create_relation,
|
113
|
-
extent: create_extent,
|
114
|
-
series: create_series,
|
115
|
-
}
|
116
|
-
end
|
117
|
-
|
118
|
-
#
|
119
|
-
# Parse the document type.
|
120
|
-
#
|
121
|
-
# @return [String] The document type.
|
122
|
-
#
|
123
|
-
def parse_type
|
124
|
-
TYPES[@message["type"]] || @message["type"]
|
125
|
-
end
|
126
|
-
|
127
|
-
#
|
128
|
-
# Create a title and a subtitle from the message hash.
|
129
|
-
#
|
130
|
-
# @return [Array<RelatonBib::TypedTitleString>] The title and subtitle.
|
131
|
-
#
|
132
|
-
def create_title
|
133
|
-
@message["title"].map do |t|
|
134
|
-
RelatonBib::TypedTitleString.new(
|
135
|
-
type: "main", content: t, language: "en", script: "Latn",
|
136
|
-
)
|
137
|
-
end + @message["subtitle"].map do |t|
|
138
|
-
RelatonBib::TypedTitleString.new(
|
139
|
-
type: "subtitle", content: t, language: "en", script: "Latn",
|
140
|
-
)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
#
|
145
|
-
# Create a docid from the message hash.
|
146
|
-
#
|
147
|
-
# @return [Array<RelatonBib::DocumentIdentifier>] The docid.
|
148
|
-
#
|
149
|
-
def create_docid
|
150
|
-
%w[DOI ISBN].each_with_object([]) do |type, obj|
|
151
|
-
id = @message[type].is_a?(Array) ? @message[type].first : @message[type]
|
152
|
-
next unless id
|
153
|
-
|
154
|
-
primary = type == "DOI"
|
155
|
-
obj << RelatonBib::DocumentIdentifier.new(type: type, id: id, primary: primary)
|
156
|
-
end
|
17
|
+
message = get_by_id id
|
18
|
+
warn "[relaton-doi] [\"#{doi}\"] found #{message['DOI']}"
|
19
|
+
Parser.parse message
|
157
20
|
end
|
158
21
|
|
159
22
|
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
# @return [Array<RelatonBib::BibliographicDate>] The dates.
|
163
|
-
#
|
164
|
-
def create_date
|
165
|
-
%w[created issued published approved].each_with_object([]) do |type, obj|
|
166
|
-
next unless @message[type]
|
167
|
-
|
168
|
-
on = @message[type]["date-parts"][0].map { |d| d.to_s.rjust(2, "0") }.join "-"
|
169
|
-
obj << RelatonBib::BibliographicDate.new(type: type, on: on)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
#
|
174
|
-
# Create a link from the message hash.
|
175
|
-
#
|
176
|
-
# @return [Array<RelatonBib::TypedUri>] The link.
|
177
|
-
#
|
178
|
-
def create_link
|
179
|
-
links = []
|
180
|
-
if @message["URL"]
|
181
|
-
links << RelatonBib::TypedUri.new(type: "DOI", content: @message["URL"])
|
182
|
-
end
|
183
|
-
return links unless @message["link"]&.any?
|
184
|
-
|
185
|
-
link = @message["link"].first
|
186
|
-
if link["URL"].match?(/\.pdf$/)
|
187
|
-
links << RelatonBib::TypedUri.new(type: "pdf", content: link["URL"])
|
188
|
-
end
|
189
|
-
links
|
190
|
-
end
|
191
|
-
|
192
|
-
#
|
193
|
-
# Create an abstract from the message hash.
|
194
|
-
#
|
195
|
-
# @return [Array<RelatonBib::FormattedString>] The abstract.
|
196
|
-
#
|
197
|
-
def create_abstract
|
198
|
-
return [] unless @message["abstract"]
|
199
|
-
|
200
|
-
content = @message["abstract"]
|
201
|
-
abstract = RelatonBib::FormattedString.new(
|
202
|
-
content: content, language: "en", script: "Latn", format: "text/html",
|
203
|
-
)
|
204
|
-
[abstract]
|
205
|
-
end
|
206
|
-
|
207
|
-
#
|
208
|
-
# Create contributors from the message hash.
|
209
|
-
#
|
210
|
-
# @return [Array<RelatonBib::ContributionInfo>] The contributors.
|
211
|
-
#
|
212
|
-
def create_contributors # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
213
|
-
contribs = %w[author editor translator].each_with_object([]) do |type, obj|
|
214
|
-
@message[type]&.each do |contrib|
|
215
|
-
obj << contributor(person(contrib), type)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
contribs << contributor(org_publisher, "publisher")
|
219
|
-
end
|
220
|
-
|
221
|
-
#
|
222
|
-
# Cerate an organization publisher from the message hash.
|
223
|
-
#
|
224
|
-
# @return [RelatonBib::Organization] The organization.
|
225
|
-
#
|
226
|
-
def org_publisher
|
227
|
-
pbr = @message["institution"]&.detect do |i|
|
228
|
-
@message["publisher"].include?(i["name"]) ||
|
229
|
-
i["name"].include?(@message["publisher"])
|
230
|
-
end
|
231
|
-
a = pbr["acronym"]&.first if pbr
|
232
|
-
RelatonBib::Organization.new name: @message["publisher"], abbreviation: a
|
233
|
-
end
|
234
|
-
|
235
|
-
#
|
236
|
-
# Create contributor from an entity and a role type.
|
237
|
-
#
|
238
|
-
# @param [RelatonBib::Person, RelatonBib::Organization] entity The entity.
|
239
|
-
# @param [String] type The role type.
|
240
|
-
#
|
241
|
-
# @return [RelatonBib::ContributionInfo] The contributor.
|
242
|
-
#
|
243
|
-
def contributor(entity, type)
|
244
|
-
RelatonBib::ContributionInfo.new(entity: entity, role: [type: type])
|
245
|
-
end
|
246
|
-
|
247
|
-
#
|
248
|
-
# Create a person from a person hash.
|
249
|
-
#
|
250
|
-
# @param [Hash] person The person hash.
|
251
|
-
#
|
252
|
-
# @return [RelatonBib::Person] The person.
|
253
|
-
#
|
254
|
-
def person(person)
|
255
|
-
RelatonBib::Person.new(
|
256
|
-
name: person_name(person), affiliation: affiliation(person),
|
257
|
-
identifier: person_id(person)
|
258
|
-
)
|
259
|
-
end
|
260
|
-
|
261
|
-
#
|
262
|
-
# Create person affiliations from a person hash.
|
263
|
-
#
|
264
|
-
# @param [Hash] person The person hash.
|
265
|
-
#
|
266
|
-
# @return [Array<RelatonBib::Affiliation>] The affiliations.
|
267
|
-
#
|
268
|
-
def affiliation(person)
|
269
|
-
(person["affiliation"] || []).map do |a|
|
270
|
-
org = RelatonBib::Organization.new(name: a["name"])
|
271
|
-
RelatonBib::Affiliation.new organization: org
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
#
|
276
|
-
# Create a person full name from a person hash.
|
277
|
-
#
|
278
|
-
# @param [Hash] person The person hash.
|
279
|
-
#
|
280
|
-
# @return [RelatonBib::FullName] The full name.
|
281
|
-
#
|
282
|
-
def person_name(person)
|
283
|
-
sn = RelatonBib::LocalizedString.new(person["family"], "en", "Latn")
|
284
|
-
RelatonBib::FullName.new(
|
285
|
-
surname: sn, forename: forename(person), addition: nameaddition(person),
|
286
|
-
completename: completename(person), prefix: nameprefix(person)
|
287
|
-
)
|
288
|
-
end
|
289
|
-
|
290
|
-
#
|
291
|
-
# Create a person name prefix from a person hash.
|
292
|
-
#
|
293
|
-
# @param [Hash] person The person hash.
|
294
|
-
#
|
295
|
-
# @return [Array<RelatonBib::LocalizedString>] The name prefix.
|
296
|
-
#
|
297
|
-
def nameprefix(person)
|
298
|
-
return [] unless person["prefix"]
|
299
|
-
|
300
|
-
[RelatonBib::LocalizedString.new(person["prefix"], "en", "Latn")]
|
301
|
-
end
|
302
|
-
|
303
|
-
#
|
304
|
-
# Create a complete name from a person hash.
|
305
|
-
#
|
306
|
-
# @param [Hash] person The person hash.
|
307
|
-
#
|
308
|
-
# @return [RelatonBib::LocalizedString] The complete name.
|
309
|
-
#
|
310
|
-
def completename(person)
|
311
|
-
return unless person["name"]
|
312
|
-
|
313
|
-
RelatonBib::LocalizedString.new(person["name"], "en", "Latn")
|
314
|
-
end
|
315
|
-
|
316
|
-
#
|
317
|
-
# Create a forename from a person hash.
|
318
|
-
#
|
319
|
-
# @param [Hash] person The person hash.
|
320
|
-
#
|
321
|
-
# @return [Array<RelatonBib::LocalizedString>] The forename.
|
322
|
-
#
|
323
|
-
def forename(person)
|
324
|
-
return [] unless person["given"]
|
325
|
-
|
326
|
-
[RelatonBib::Forename.new(content: person["given"], language: "en", script: "Latn")]
|
327
|
-
end
|
328
|
-
|
329
|
-
#
|
330
|
-
# Create an addition from a person hash.
|
331
|
-
#
|
332
|
-
# @param [Hash] person The person hash.
|
333
|
-
#
|
334
|
-
# @return [Array<RelatonBib::LocalizedString>] The addition.
|
335
|
-
#
|
336
|
-
def nameaddition(person)
|
337
|
-
return [] unless person["suffix"]
|
338
|
-
|
339
|
-
[RelatonBib::LocalizedString.new(person["suffix"], "en", "Latn")]
|
340
|
-
end
|
341
|
-
|
342
|
-
#
|
343
|
-
# Create a person identifier from a person hash.
|
344
|
-
#
|
345
|
-
# @param [Hash] person The person hash.
|
346
|
-
#
|
347
|
-
# @return [Array<RelatonBib::PersonIdentifier>] The person identifier.
|
348
|
-
#
|
349
|
-
def person_id(person)
|
350
|
-
return [] unless person["ORCID"]
|
351
|
-
|
352
|
-
[RelatonBib::PersonIdentifier.new("orcid", person["ORCID"])]
|
353
|
-
end
|
354
|
-
|
355
|
-
#
|
356
|
-
# Create a place from the message hash.
|
357
|
-
#
|
358
|
-
# @return [Array<RelatonBib::Place>] The place.
|
359
|
-
#
|
360
|
-
def create_place
|
361
|
-
return [] unless @message["publisher-location"]
|
362
|
-
|
363
|
-
city, rg = @message["publisher-location"].split(", ")
|
364
|
-
region = RelatonBib::Place::RegionType.new(name: rg)
|
365
|
-
[RelatonBib::Place.new(city: city, region: [region])]
|
366
|
-
end
|
367
|
-
|
368
|
-
#
|
369
|
-
# Crerate relations from the message hash.
|
370
|
-
#
|
371
|
-
# @return [Array<RelatonBib::DocumentRelation>] The relations.
|
372
|
-
#
|
373
|
-
def create_relation # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
374
|
-
rels = []
|
375
|
-
@message["container-title"]&.each do |ct|
|
376
|
-
contrib = included_in_editors(ct)
|
377
|
-
bib = RelatonBib::BibliographicItem.new(title: [content: ct], contributor: contrib)
|
378
|
-
rels << RelatonBib::DocumentRelation.new(type: "includedIn", bibitem: bib)
|
379
|
-
end
|
380
|
-
@message["relation"].each_with_object(rels) do |(k, v), a|
|
381
|
-
fref = RelatonBib::FormattedRef.new(content: v["id"])
|
382
|
-
bib = create_bibitem v["id"], formattedref: fref
|
383
|
-
type = REALATION_TYPES[k] || k
|
384
|
-
a << RelatonBib::DocumentRelation.new(type: type, bibitem: bib)
|
385
|
-
end
|
386
|
-
end
|
387
|
-
|
388
|
-
#
|
389
|
-
# Fetch included in editors.
|
390
|
-
#
|
391
|
-
# @param [String] title container-title
|
392
|
-
#
|
393
|
-
# @return [Array<RelatonBib::ContributionInfo>] The editors contribution info.
|
394
|
-
#
|
395
|
-
def included_in_editors(title)
|
396
|
-
item = fetch_included_in title
|
397
|
-
return [] unless item
|
398
|
-
|
399
|
-
item["editor"].map { |e| contributor(person(e), "editor") }
|
400
|
-
end
|
401
|
-
|
402
|
-
#
|
403
|
-
# Fetch included in relation.
|
404
|
-
#
|
405
|
-
# @param [String] title container-title
|
406
|
-
#
|
407
|
-
# @return [Hash] The included in relation item.
|
408
|
-
#
|
409
|
-
def fetch_included_in(title) # rubocop:disable Metrics/AbcSize
|
410
|
-
year = (@message["published"] || @message["approved"])["date-parts"][0][0]
|
411
|
-
query = "#{title}, #{@message['publisher']}, #{@message['publisher-location']}, #{year}"
|
412
|
-
resp = Faraday.get %{http://api.crossref.org/works?query.bibliographic="#{query}"&rows=5&filter=type:book}
|
413
|
-
json = JSON.parse resp.body
|
414
|
-
json["message"]["items"].detect { |i| i["title"].include?(title) && i["editor"] }
|
415
|
-
end
|
416
|
-
|
417
|
-
#
|
418
|
-
# Create an extent from the message hash.
|
419
|
-
#
|
420
|
-
# @return [Array<RelatonBib::Locality>] The extent.
|
421
|
-
#
|
422
|
-
def create_extent # rubocop:disable Metrics/AbcSize
|
423
|
-
extent = []
|
424
|
-
extent << RelatonBib::Locality.new("volume", @message["volume"]) if @message["volume"]
|
425
|
-
extent << RelatonBib::Locality.new("issue", @message["issue"]) if @message["issue"]
|
426
|
-
if @message["page"]
|
427
|
-
from, to = @message["page"].split("-")
|
428
|
-
extent << RelatonBib::Locality.new("page", from, to)
|
429
|
-
end
|
430
|
-
extent.any? ? [RelatonBib::LocalityStack.new(extent)] : []
|
431
|
-
end
|
432
|
-
|
23
|
+
# Get a document by DOI from the CrossRef API.
|
433
24
|
#
|
434
|
-
#
|
25
|
+
# @param [String] id The DOI.
|
435
26
|
#
|
436
|
-
# @return [
|
27
|
+
# @return [Hash] The document.
|
437
28
|
#
|
438
|
-
def
|
439
|
-
|
440
|
-
|
441
|
-
@message["container-title"].map do |ct|
|
442
|
-
title = RelatonBib::TypedTitleString.new content: ct
|
443
|
-
RelatonBib::Series.new title: title
|
444
|
-
end
|
29
|
+
def get_by_id(id)
|
30
|
+
resp = Serrano.works ids: id
|
31
|
+
resp[0]["message"]
|
445
32
|
end
|
446
33
|
end
|
447
34
|
end
|