relaton-doi 1.14.2 → 1.14.3
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/Gemfile +2 -0
- data/lib/relaton_doi/crossref.rb +11 -424
- data/lib/relaton_doi/parser.rb +803 -0
- data/lib/relaton_doi/version.rb +1 -1
- data/lib/relaton_doi.rb +1 -2
- data/relaton-doi.gemspec +1 -1
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c74297f60135d99153e2e5f7f3c4a1acb3945b43fbcc26868bf900b2e9ec3c0
|
4
|
+
data.tar.gz: 6c241a6fc93c87a1751bde8772752c859ee9a000c04729ddc3910386c4827d66
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74ef5db4deb7ff21ba09e5caa2e98c051549e81fa6d0ea1249787767d19d3cb10af911e1c51a11d6cf965fc856e2cdd7d6538b0eea19200bc43782f707550521
|
7
|
+
data.tar.gz: 7b13cd9e6f765f3f0e2fff5c703ae24c9b4c3b98c96a2138b46cb0a969ee64431390a5551696c2a5a2b00b1581b3910988ef3ee40da3a7ad6527174e1660bfc0
|
data/Gemfile
CHANGED
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
|
@@ -0,0 +1,803 @@
|
|
1
|
+
module RelatonDoi
|
2
|
+
class Parser
|
3
|
+
COUNTRIES = %w[USA].freeze
|
4
|
+
|
5
|
+
TYPES = {
|
6
|
+
"book-chapter" => "inbook",
|
7
|
+
"book-part" => "inbook",
|
8
|
+
"book-section" => "inbook",
|
9
|
+
"book-series" => "book",
|
10
|
+
"book-set" => "book",
|
11
|
+
"book-track" => "inbook",
|
12
|
+
"component" => "misc",
|
13
|
+
"database" => "dataset",
|
14
|
+
"dissertation" => "thesis",
|
15
|
+
"edited-book" => "book",
|
16
|
+
"grant" => "misc",
|
17
|
+
"journal-article" => "article",
|
18
|
+
"journal-issue" => "article",
|
19
|
+
"journal-volume" => "journal",
|
20
|
+
"monograph" => "book",
|
21
|
+
"other" => "misc",
|
22
|
+
"peer-review" => "article",
|
23
|
+
"posted-content" => "dataset",
|
24
|
+
"proceedings-article" => "inproceedings",
|
25
|
+
"proceedings-series" => "proceedings",
|
26
|
+
"reference-book" => "book",
|
27
|
+
"reference-entry" => "inbook",
|
28
|
+
"report-component" => "techreport",
|
29
|
+
"report-series" => "techreport",
|
30
|
+
"report" => "techreport",
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
REALATION_TYPES = {
|
34
|
+
"is-cited-by" => "isCitedIn",
|
35
|
+
"belongs-to" => "related",
|
36
|
+
"is-child-of" => "includedIn",
|
37
|
+
"is-expression-of" => "expressionOf",
|
38
|
+
"has-expression" => "hasExpression",
|
39
|
+
"is-manifestation-of" => "manifestationOf",
|
40
|
+
"is-manuscript-of" => "draftOf",
|
41
|
+
"has-manuscript" => "hasDraft",
|
42
|
+
"is-preprint-of" => "draftOf",
|
43
|
+
"has-preprint" => "hasDraft",
|
44
|
+
"is-replaced-by" => "obsoletedBy",
|
45
|
+
"replaces" => "obsoletes",
|
46
|
+
"is-translation-of" => "translatedFrom",
|
47
|
+
"has-translation" => "hasTranslation",
|
48
|
+
"is-version-of" => "editionOf",
|
49
|
+
"has-version" => "hasEdition",
|
50
|
+
"is-based-on" => "updates",
|
51
|
+
"is-basis-for" => "updatedBy",
|
52
|
+
"is-comment-on" => "commentaryOf",
|
53
|
+
"has-comment" => "hasCommentary",
|
54
|
+
"is-continued-by" => "hasSuccessor",
|
55
|
+
"continues" => "successorOf",
|
56
|
+
"is-derived-from" => "derives",
|
57
|
+
"has-derivation" => "derivedFrom",
|
58
|
+
"is-documented-by" => "describedBy",
|
59
|
+
"documents" => "describes",
|
60
|
+
"is-part-of" => "partOf",
|
61
|
+
"has-part" => "hasPart",
|
62
|
+
"is-review-of" => "reviewOf",
|
63
|
+
"has-review" => "hasReview",
|
64
|
+
"references" => "cites",
|
65
|
+
"is-referenced-by" => "isCitedIn",
|
66
|
+
"requires" => "hasComplement",
|
67
|
+
"is-required-by" => "complementOf",
|
68
|
+
"is-supplement-to" => "complementOf",
|
69
|
+
"is-supplemented-by" => "hasComplement",
|
70
|
+
}.freeze
|
71
|
+
|
72
|
+
ATTRS = %i[type fetched title docid date link abstract contributor place
|
73
|
+
doctype relation extent series medium].freeze
|
74
|
+
|
75
|
+
#
|
76
|
+
# Initialize instance.
|
77
|
+
#
|
78
|
+
# @param [Hash] src The source hash.
|
79
|
+
#
|
80
|
+
def initialize(src)
|
81
|
+
@src = src
|
82
|
+
@item = {}
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Initialize instance and parse the source hash.
|
87
|
+
#
|
88
|
+
# @param [Hash] src The source hash.
|
89
|
+
#
|
90
|
+
# @return [RelatonBib::BibliographicItem, RelatonIetf::IetfBibliographicItem,
|
91
|
+
# RelatonBipm::BipmBibliographicItem, RelatonIeee::IeeeBibliographicItem,
|
92
|
+
# RelatonNist::NistBibliographicItem] The bibitem.
|
93
|
+
#
|
94
|
+
def self.parse(src)
|
95
|
+
new(src).parse
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Parse the source hash.
|
100
|
+
#
|
101
|
+
# @return [RelatonBib::BibliographicItem, RelatonIetf::IetfBibliographicItem,
|
102
|
+
# RelatonBipm::BipmBibliographicItem, RelatonIeee::IeeeBibliographicItem,
|
103
|
+
# RelatonNist::NistBibliographicItem] The bibitem.
|
104
|
+
#
|
105
|
+
def parse
|
106
|
+
ATTRS.each { |m| @item[m] = send "parse_#{m}" }
|
107
|
+
create_bibitem @src["DOI"], @item
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Create a bibitem from the bibitem hash.
|
112
|
+
#
|
113
|
+
# @param [String] doi The DOI.
|
114
|
+
# @param [Hash] bibitem The bibitem hash.
|
115
|
+
#
|
116
|
+
# @return [RelatonBib::BibliographicItem, RelatonIetf::IetfBibliographicItem,
|
117
|
+
# RelatonBipm::BipmBibliographicItem, RelatonIeee::IeeeBibliographicItem,
|
118
|
+
# RelatonNist::NistBibliographicItem] The bibitem.
|
119
|
+
#
|
120
|
+
def create_bibitem(doi, bibitem) # rubocop:disable Metrics/CyclomaticComplexity
|
121
|
+
case doi
|
122
|
+
when /\/nist/ then RelatonNist::NistBibliographicItem.new(**bibitem)
|
123
|
+
when /\/rfc\d+/ then RelatonIetf::IetfBibliographicItem.new(**bibitem)
|
124
|
+
when /\/0026-1394\// then RelatonBipm::BipmBibliographicItem.new(**bibitem)
|
125
|
+
when /\/ieee/ then RelatonIeee::IeeeBibliographicItem.new(**bibitem)
|
126
|
+
else RelatonBib::BibliographicItem.new(**bibitem)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Parse the type.
|
132
|
+
#
|
133
|
+
# @return [String] The type.
|
134
|
+
#
|
135
|
+
def parse_type
|
136
|
+
TYPES[@src["type"]] || @src["type"]
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Parse the document type
|
141
|
+
#
|
142
|
+
# @return [String] The document type.
|
143
|
+
#
|
144
|
+
def parse_doctype
|
145
|
+
@src["type"]
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Parse the fetched date.
|
150
|
+
#
|
151
|
+
# @return [String] The fetched date.
|
152
|
+
#
|
153
|
+
def parse_fetched
|
154
|
+
Date.today.to_s
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# Parse titles from the source hash.
|
159
|
+
#
|
160
|
+
# @return [Array<Hash>] The titles.
|
161
|
+
#
|
162
|
+
def parse_title # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
163
|
+
if @src["title"].is_a?(Array) && @src["title"].any?
|
164
|
+
main_sub_titles
|
165
|
+
elsif @src["project"].is_a?(Array) && @src["project"].any?
|
166
|
+
project_titles
|
167
|
+
elsif @src["container-title"].is_a?(Array) && @src["container-title"].size > 1
|
168
|
+
@src["container-title"][0..-2].map { |t| create_title t }
|
169
|
+
else []
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Parse main and subtitle from the source hash.
|
175
|
+
#
|
176
|
+
# @return [Array<Hash>] The titles.
|
177
|
+
#
|
178
|
+
def main_sub_titles
|
179
|
+
title = @src["title"].map { |t| create_title t }
|
180
|
+
RelatonBib.array(@src["subtitle"]).each { |t| title << create_title(t, "subtitle") }
|
181
|
+
RelatonBib.array(@src["short-title"]).each { |t| title << create_title(t, "short") }
|
182
|
+
title
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Fetch titles from the projects.
|
187
|
+
#
|
188
|
+
# @return [Array<Hash>] The titles.
|
189
|
+
#
|
190
|
+
def project_titles
|
191
|
+
RelatonBib.array(@src["project"]).reduce([]) do |memo, proj|
|
192
|
+
memo + RelatonBib.array(proj["project-title"]).map { |t| create_title t["title"] }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# Create a title from the title and type.
|
198
|
+
#
|
199
|
+
# @param [String] title The title content.
|
200
|
+
# @param [String] type The title type. Defaults to "main".
|
201
|
+
#
|
202
|
+
# @return [RelatonBib::TypedTitleString] The title.
|
203
|
+
#
|
204
|
+
def create_title(title, type = "main")
|
205
|
+
cnt = str_cleanup title
|
206
|
+
RelatonBib::TypedTitleString.new type: type, content: cnt, script: "Latn"
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Parse a docid from the source hash.
|
211
|
+
#
|
212
|
+
# @return [Array<RelatonBib::DocumentIdentifier>] The docid.
|
213
|
+
#
|
214
|
+
def parse_docid
|
215
|
+
%w[DOI ISBN ISSN].each_with_object([]) do |type, obj|
|
216
|
+
prm = type == "DOI"
|
217
|
+
RelatonBib.array(@src[type]).each do |id|
|
218
|
+
t = issn_type(type, id)
|
219
|
+
obj << RelatonBib::DocumentIdentifier.new(type: t, id: id, primary: prm)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Create an ISSN type if it's an ISSN ID.
|
226
|
+
#
|
227
|
+
# @param [String] type identifier type
|
228
|
+
# @param [String] id identifier
|
229
|
+
#
|
230
|
+
# @return [String] identifier type
|
231
|
+
#
|
232
|
+
def issn_type(type, id)
|
233
|
+
return type unless type == "ISSN"
|
234
|
+
|
235
|
+
t = @src["issn-type"]&.find { |it| it["value"] == id }&.dig("type")
|
236
|
+
t ? "issn.#{t}" : type.downcase
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Parce dates from the source hash.
|
241
|
+
#
|
242
|
+
# @return [Array<RelatonBib::BibliographicDate>] The dates.
|
243
|
+
#
|
244
|
+
def parse_date # rubocop:disable Metrics/CyclomaticComplexity
|
245
|
+
dates = %w[issued published approved].each_with_object([]) do |type, obj|
|
246
|
+
next unless @src.dig(type, "date-parts")&.first&.compact&.any?
|
247
|
+
|
248
|
+
obj << RelatonBib::BibliographicDate.new(type: type, on: date_type(type))
|
249
|
+
end
|
250
|
+
if dates.none?
|
251
|
+
dates << RelatonBib::BibliographicDate.new(type: "created", on: date_type("created"))
|
252
|
+
end
|
253
|
+
dates
|
254
|
+
end
|
255
|
+
|
256
|
+
#
|
257
|
+
# Join date parts into a string.
|
258
|
+
#
|
259
|
+
# @param [String] type The date type.
|
260
|
+
#
|
261
|
+
# @return [String] The date string.
|
262
|
+
#
|
263
|
+
def date_type(type)
|
264
|
+
@src[type]["date-parts"][0].map { |d| d.to_s.rjust(2, "0") }.join "-"
|
265
|
+
end
|
266
|
+
|
267
|
+
#
|
268
|
+
# Parse links from the source hash.
|
269
|
+
#
|
270
|
+
# @return [Array<RelatonBib::TypedUri>] The links.
|
271
|
+
#
|
272
|
+
def parse_link # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
273
|
+
disprefered_links = %w[similarity-checking text-mining]
|
274
|
+
links = []
|
275
|
+
if @src["URL"]
|
276
|
+
links << RelatonBib::TypedUri.new(type: "DOI", content: @src["URL"])
|
277
|
+
end
|
278
|
+
[@src["link"], @src.dig("resource", "primary")].flatten.compact.each do |l|
|
279
|
+
next if disprefered_links.include? l["intended-application"]
|
280
|
+
|
281
|
+
type = case l["URL"]
|
282
|
+
when /\.pdf$/ then "pdf"
|
283
|
+
# when /\/rfc\d+$|iopscience\.iop\.org|ieeexplore\.ieee\.org/
|
284
|
+
else "src"
|
285
|
+
end
|
286
|
+
links << RelatonBib::TypedUri.new(type: type, content: l["URL"]) # if type
|
287
|
+
end
|
288
|
+
links
|
289
|
+
end
|
290
|
+
|
291
|
+
#
|
292
|
+
# Parse abstract from the source hash.
|
293
|
+
#
|
294
|
+
# @return [Array<RelatonBib::FormattedString>] The abstract.
|
295
|
+
#
|
296
|
+
def parse_abstract
|
297
|
+
return [] unless @src["abstract"]
|
298
|
+
|
299
|
+
content = @src["abstract"]
|
300
|
+
abstract = RelatonBib::FormattedString.new(
|
301
|
+
content: content, language: "en", script: "Latn", format: "text/html",
|
302
|
+
)
|
303
|
+
[abstract]
|
304
|
+
end
|
305
|
+
|
306
|
+
#
|
307
|
+
# Parse contributors from the source hash.
|
308
|
+
#
|
309
|
+
# @return [Array<RelatonBib::ContributionInfo>] The contributors.
|
310
|
+
#
|
311
|
+
def parse_contributor
|
312
|
+
contribs = author_investigators
|
313
|
+
contribs += authors_editors_translators
|
314
|
+
contribs += contribs_from_parent(contribs)
|
315
|
+
contribs << contributor(org_publisher, "publisher")
|
316
|
+
contribs += org_aurhorizer
|
317
|
+
contribs + org_enabler
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# Create authors investigators from the source hash.
|
322
|
+
#
|
323
|
+
# @return [Array<RelatonBib::ContributionInfo>] The authors investigators.
|
324
|
+
#
|
325
|
+
def author_investigators
|
326
|
+
RelatonBib.array(@src["project"]).reduce([]) do |memo, proj|
|
327
|
+
memo + create_investigators(proj, "lead-investigator") +
|
328
|
+
create_investigators(proj, "investigator")
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
#
|
333
|
+
# Create investigators from the project.
|
334
|
+
#
|
335
|
+
# @param [Hash] project The project hash.
|
336
|
+
# @param [String] type The investigator type. "lead-investigator" or "investigator".
|
337
|
+
#
|
338
|
+
# @return [Array<RelatonBib::ContributionInfo>] The investigators.
|
339
|
+
#
|
340
|
+
def create_investigators(project, type)
|
341
|
+
description = type.gsub("-", " ")
|
342
|
+
RelatonBib.array(project[type]).map do |inv|
|
343
|
+
contributor(create_person(inv), "author", description)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
# Create authors editors translators from the source hash.
|
349
|
+
#
|
350
|
+
# @return [Array<RelatonBib::ContributionInfo>] The authors editors translators.
|
351
|
+
#
|
352
|
+
def authors_editors_translators
|
353
|
+
%w[author editor translator].each_with_object([]) do |type, a|
|
354
|
+
@src[type]&.each do |c|
|
355
|
+
contrib = if c["family"]
|
356
|
+
create_person(c)
|
357
|
+
else
|
358
|
+
RelatonBib::Organization.new(name: str_cleanup(c["name"]))
|
359
|
+
end
|
360
|
+
a << contributor(contrib, type)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
#
|
366
|
+
# Fetch authors and editors from parent if they are not present in the book part.
|
367
|
+
#
|
368
|
+
# @param [Array<RelatonBib::ContributionInfo>] contribs present contributors
|
369
|
+
#
|
370
|
+
# @return [Array<RelatonBib::ContributionInfo>] contributors with authors and editors from parent
|
371
|
+
#
|
372
|
+
def contribs_from_parent(contribs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
373
|
+
return [] unless %w[inbook inproceedings dataset].include?(parse_type) && @src["container-title"]
|
374
|
+
|
375
|
+
has_authors = contribs.any? { |c| c.role&.any? { |r| r.type == "author" } }
|
376
|
+
has_editors = contribs.any? { |c| c.role&.any? { |r| r.type == "editor" } }
|
377
|
+
return [] if has_authors && has_editors
|
378
|
+
|
379
|
+
item = fetch_parent
|
380
|
+
authors = create_authors_editors(has_authors, "author", item)
|
381
|
+
editors = create_authors_editors(has_editors, "editor", item)
|
382
|
+
authors + editors
|
383
|
+
end
|
384
|
+
|
385
|
+
#
|
386
|
+
# Fetch parent item from Crossref.
|
387
|
+
#
|
388
|
+
# @return [Hash, nil] parent item
|
389
|
+
#
|
390
|
+
def fetch_parent # rubocop:disable Metrics/AbcSize
|
391
|
+
query = [@src["container-title"][0], fetch_year].compact.join "+"
|
392
|
+
filter = "type:#{%w[book book-set edited-book monograph reference-book].join ',type:'}"
|
393
|
+
resp = Faraday.get "https://api.crossref.org/works?query=#{query}&filter=#{filter}"
|
394
|
+
json = JSON.parse resp.body
|
395
|
+
json["message"]["items"].detect { |i| i["title"].include? @src["container-title"][0] }
|
396
|
+
end
|
397
|
+
|
398
|
+
#
|
399
|
+
# Create authors and editors from parent item.
|
400
|
+
#
|
401
|
+
# @param [Boolean] has true if authors or editors are present in the book part
|
402
|
+
# @param [String] type "author" or "editor"
|
403
|
+
# @param [Hash, nil] item parent item
|
404
|
+
#
|
405
|
+
# @return [Array<RelatonBib::ContributionInfo>] authors or editors
|
406
|
+
#
|
407
|
+
def create_authors_editors(has, type, item)
|
408
|
+
return [] if has || !item
|
409
|
+
|
410
|
+
RelatonBib.array(item[type]).map { |a| contributor(create_person(a), type) }
|
411
|
+
end
|
412
|
+
|
413
|
+
#
|
414
|
+
# Cerate an organization publisher from the source hash.
|
415
|
+
#
|
416
|
+
# @return [RelatonBib::Organization] The organization.
|
417
|
+
#
|
418
|
+
def org_publisher
|
419
|
+
pbr = @src["institution"]&.detect do |i|
|
420
|
+
@src["publisher"].include?(i["name"]) ||
|
421
|
+
i["name"].include?(@src["publisher"])
|
422
|
+
end
|
423
|
+
a = pbr["acronym"]&.first if pbr
|
424
|
+
RelatonBib::Organization.new name: str_cleanup(@src["publisher"]), abbreviation: a
|
425
|
+
end
|
426
|
+
|
427
|
+
#
|
428
|
+
# Clean up trailing punctuation and whitespace from a string.
|
429
|
+
#
|
430
|
+
# @param [String] str The string to clean up.
|
431
|
+
#
|
432
|
+
# @return [String] The cleaned up string.
|
433
|
+
#
|
434
|
+
def str_cleanup(str)
|
435
|
+
str.strip.sub(/[,\/\s]+$/, "").sub(/\s:$/, "")
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Parse authorizer contributor from the source hash.
|
440
|
+
#
|
441
|
+
# @return [Array<RelatonBib::ContributionInfo>] The authorizer contributor.
|
442
|
+
#
|
443
|
+
def org_aurhorizer
|
444
|
+
return [] unless @src["standards-body"]
|
445
|
+
|
446
|
+
name, acronym = @src["standards-body"].values_at("name", "acronym")
|
447
|
+
org = RelatonBib::Organization.new name: name, abbreviation: acronym
|
448
|
+
[contributor(org, "authorizer")]
|
449
|
+
end
|
450
|
+
|
451
|
+
#
|
452
|
+
# Parse enabler contributor from the source hash.
|
453
|
+
#
|
454
|
+
# @return [Array<RelatonBib::ContributionInfo>] The enabler contributor.
|
455
|
+
#
|
456
|
+
def org_enabler
|
457
|
+
RelatonBib.array(@src["project"]).each_with_object([]) do |proj, memo|
|
458
|
+
proj["funding"].each do |f|
|
459
|
+
memo << create_enabler(f.dig("funder", "name"))
|
460
|
+
end
|
461
|
+
end + RelatonBib.array(@src["funder"]).map { |f| create_enabler f["name"] }
|
462
|
+
end
|
463
|
+
|
464
|
+
#
|
465
|
+
# Create enabler contributor with type "enabler".
|
466
|
+
#
|
467
|
+
# @param [String] name <description>
|
468
|
+
#
|
469
|
+
# @return [RelatonBib::ContributionInfo] The enabler contributor.
|
470
|
+
#
|
471
|
+
def create_enabler(name)
|
472
|
+
org = RelatonBib::Organization.new name: name
|
473
|
+
contributor(org, "enabler")
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
# Create contributor from an entity and a role type.
|
478
|
+
#
|
479
|
+
# @param [RelatonBib::Person, RelatonBib::Organization] entity The entity.
|
480
|
+
# @param [String] type The role type.
|
481
|
+
#
|
482
|
+
# @return [RelatonBib::ContributionInfo] The contributor.
|
483
|
+
#
|
484
|
+
def contributor(entity, type, descriprion = nil)
|
485
|
+
role = { type: type }
|
486
|
+
role[:description] = [descriprion] if descriprion
|
487
|
+
RelatonBib::ContributionInfo.new(entity: entity, role: [role])
|
488
|
+
end
|
489
|
+
|
490
|
+
#
|
491
|
+
# Create a person from a person hash.
|
492
|
+
#
|
493
|
+
# @param [Hash] person The person hash.
|
494
|
+
#
|
495
|
+
# @return [RelatonBib::Person] The person.
|
496
|
+
#
|
497
|
+
def create_person(person)
|
498
|
+
RelatonBib::Person.new(
|
499
|
+
name: create_person_name(person),
|
500
|
+
affiliation: create_affiliation(person),
|
501
|
+
identifier: person_id(person),
|
502
|
+
)
|
503
|
+
end
|
504
|
+
|
505
|
+
#
|
506
|
+
# Create person affiliations from a person hash.
|
507
|
+
#
|
508
|
+
# @param [Hash] person The person hash.
|
509
|
+
#
|
510
|
+
# @return [Array<RelatonBib::Affiliation>] The affiliations.
|
511
|
+
#
|
512
|
+
def create_affiliation(person)
|
513
|
+
(person["affiliation"] || []).map do |a|
|
514
|
+
org = RelatonBib::Organization.new(name: a["name"])
|
515
|
+
RelatonBib::Affiliation.new organization: org
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
#
|
520
|
+
# Create a person full name from a person hash.
|
521
|
+
#
|
522
|
+
# @param [Hash] person The person hash.
|
523
|
+
#
|
524
|
+
# @return [RelatonBib::FullName] The full name.
|
525
|
+
#
|
526
|
+
def create_person_name(person)
|
527
|
+
surname = titlecase(person["family"])
|
528
|
+
sn = RelatonBib::LocalizedString.new(surname, "en", "Latn")
|
529
|
+
RelatonBib::FullName.new(
|
530
|
+
surname: sn, forename: forename(person), addition: nameaddition(person),
|
531
|
+
completename: completename(person), prefix: nameprefix(person)
|
532
|
+
)
|
533
|
+
end
|
534
|
+
|
535
|
+
#
|
536
|
+
# Capitalize the first letter of each word in a string except for words that
|
537
|
+
# are 2 letters or less.
|
538
|
+
#
|
539
|
+
# @param [<Type>] str <description>
|
540
|
+
#
|
541
|
+
# @return [<Type>] <description>
|
542
|
+
#
|
543
|
+
def titlecase(str)
|
544
|
+
str.split.map do |s|
|
545
|
+
if s.size > 2 && s.upcase == s && !/\.&/.match?(s)
|
546
|
+
s.capitalize
|
547
|
+
else
|
548
|
+
s
|
549
|
+
end
|
550
|
+
end.join " "
|
551
|
+
end
|
552
|
+
|
553
|
+
#
|
554
|
+
# Create a person name prefix from a person hash.
|
555
|
+
#
|
556
|
+
# @param [Hash] person The person hash.
|
557
|
+
#
|
558
|
+
# @return [Array<RelatonBib::LocalizedString>] The name prefix.
|
559
|
+
#
|
560
|
+
def nameprefix(person)
|
561
|
+
return [] unless person["prefix"]
|
562
|
+
|
563
|
+
[RelatonBib::LocalizedString.new(person["prefix"], "en", "Latn")]
|
564
|
+
end
|
565
|
+
|
566
|
+
#
|
567
|
+
# Create a complete name from a person hash.
|
568
|
+
#
|
569
|
+
# @param [Hash] person The person hash.
|
570
|
+
#
|
571
|
+
# @return [RelatonBib::LocalizedString] The complete name.
|
572
|
+
#
|
573
|
+
def completename(person)
|
574
|
+
return unless person["name"]
|
575
|
+
|
576
|
+
RelatonBib::LocalizedString.new(person["name"], "en", "Latn")
|
577
|
+
end
|
578
|
+
|
579
|
+
#
|
580
|
+
# Create a forename from a person hash.
|
581
|
+
#
|
582
|
+
# @param [Hash] person The person hash.
|
583
|
+
#
|
584
|
+
# @return [Array<RelatonBib::LocalizedString>] The forename.
|
585
|
+
#
|
586
|
+
def forename(person)
|
587
|
+
return [] unless person["given"]
|
588
|
+
|
589
|
+
fname = titlecase(person["given"])
|
590
|
+
[RelatonBib::Forename.new(content: fname, language: "en", script: "Latn")]
|
591
|
+
end
|
592
|
+
|
593
|
+
#
|
594
|
+
# Create an addition from a person hash.
|
595
|
+
#
|
596
|
+
# @param [Hash] person The person hash.
|
597
|
+
#
|
598
|
+
# @return [Array<RelatonBib::LocalizedString>] The addition.
|
599
|
+
#
|
600
|
+
def nameaddition(person)
|
601
|
+
return [] unless person["suffix"]
|
602
|
+
|
603
|
+
[RelatonBib::LocalizedString.new(person["suffix"], "en", "Latn")]
|
604
|
+
end
|
605
|
+
|
606
|
+
#
|
607
|
+
# Create a person identifier from a person hash.
|
608
|
+
#
|
609
|
+
# @param [Hash] person The person hash.
|
610
|
+
#
|
611
|
+
# @return [Array<RelatonBib::PersonIdentifier>] The person identifier.
|
612
|
+
#
|
613
|
+
def person_id(person)
|
614
|
+
return [] unless person["ORCID"]
|
615
|
+
|
616
|
+
[RelatonBib::PersonIdentifier.new("orcid", person["ORCID"])]
|
617
|
+
end
|
618
|
+
|
619
|
+
#
|
620
|
+
# Parse a place from the source hash.
|
621
|
+
#
|
622
|
+
# @return [Array<RelatonBib::Place>] The place.
|
623
|
+
#
|
624
|
+
def parse_place # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
625
|
+
pub_location = @src["publisher-location"] || fetch_location
|
626
|
+
return [] unless pub_location
|
627
|
+
|
628
|
+
pls1, pls2 = pub_location.split(", ")
|
629
|
+
pls1 = str_cleanup pls1
|
630
|
+
pls2 &&= str_cleanup pls2
|
631
|
+
if COUNTRIES.include? pls2
|
632
|
+
country = RelatonBib::Place::RegionType.new(name: pls2)
|
633
|
+
[RelatonBib::Place.new(city: pls1, country: [country])]
|
634
|
+
elsif pls2 && pls2 == pls2&.upcase
|
635
|
+
region = RelatonBib::Place::RegionType.new(name: pls2)
|
636
|
+
[RelatonBib::Place.new(city: pls1, region: [region])]
|
637
|
+
elsif pls1 == pls2 || pls2.nil? || pls2.empty?
|
638
|
+
[RelatonBib::Place.new(city: pls1)]
|
639
|
+
else
|
640
|
+
[RelatonBib::Place.new(city: pls1), RelatonBib::Place.new(city: pls2)]
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
#
|
645
|
+
# Fetch location from container.
|
646
|
+
#
|
647
|
+
# @return [String, nil] The location.
|
648
|
+
#
|
649
|
+
def fetch_location # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
650
|
+
title = @item[:title].first&.title&.content
|
651
|
+
qparts = [title, fetch_year, @src["publisher"]]
|
652
|
+
query = CGI.escape qparts.compact.join("+").gsub(" ", "+")
|
653
|
+
filter = "type:#{%w[book-chapter book-part book-section book-track].join(',type:')}"
|
654
|
+
resp = Faraday.get "https://api.crossref.org/works?query=#{query}&filter=#{filter}"
|
655
|
+
json = JSON.parse resp.body
|
656
|
+
json["message"]["items"].detect do |i|
|
657
|
+
i["publisher-location"] && i["container-title"].include?(title)
|
658
|
+
end&.dig("publisher-location")
|
659
|
+
end
|
660
|
+
|
661
|
+
#
|
662
|
+
# Parse relations from the source hash.
|
663
|
+
#
|
664
|
+
# @return [Array<RelatonBib::DocumentRelation>] The relations.
|
665
|
+
#
|
666
|
+
def parse_relation # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
667
|
+
rels = included_in_relation
|
668
|
+
@src["relation"].each_with_object(rels) do |(k, v), a|
|
669
|
+
type, desc = relation_type k
|
670
|
+
RelatonBib.array(v).each do |r|
|
671
|
+
rel_item = Crossref.get_by_id r["id"]
|
672
|
+
title = rel_item["title"].map { |t| create_title t }
|
673
|
+
docid = RelatonBib::DocumentIdentifier.new(id: r["id"], type: "DOI")
|
674
|
+
bib = create_bibitem r["id"], title: title, docid: [docid]
|
675
|
+
a << RelatonBib::DocumentRelation.new(type: type, description: desc, bibitem: bib)
|
676
|
+
end
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
#
|
681
|
+
# Transform crossref relation type to relaton relation type.
|
682
|
+
#
|
683
|
+
# @param [String] crtype The crossref relation type.
|
684
|
+
#
|
685
|
+
# @return [Array<String>] The relaton relation type and description.
|
686
|
+
#
|
687
|
+
def relation_type(crtype)
|
688
|
+
type = REALATION_TYPES[crtype] || begin
|
689
|
+
desc = RelatonBib::FormattedString.new(content: crtype)
|
690
|
+
"related"
|
691
|
+
end
|
692
|
+
[type, desc]
|
693
|
+
end
|
694
|
+
|
695
|
+
#
|
696
|
+
# Create included in relation.
|
697
|
+
#
|
698
|
+
# @return [Array<RelatonBib::DocumentRelation>] The relations.
|
699
|
+
#
|
700
|
+
def included_in_relation
|
701
|
+
types = %w[
|
702
|
+
book book-chapter book-part book-section book-track dataset journal-issue
|
703
|
+
journal-value proceedings-article reference-entry report-component
|
704
|
+
]
|
705
|
+
return [] unless @src["container-title"] && types.include?(@src["type"])
|
706
|
+
|
707
|
+
@src["container-title"].map do |ct|
|
708
|
+
contrib = included_in_editors(ct)
|
709
|
+
bib = RelatonBib::BibliographicItem.new(title: [content: ct], contributor: contrib)
|
710
|
+
RelatonBib::DocumentRelation.new(type: "includedIn", bibitem: bib)
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
#
|
715
|
+
# Fetch included in editors.
|
716
|
+
#
|
717
|
+
# @param [String] title container-title
|
718
|
+
#
|
719
|
+
# @return [Array<RelatonBib::ContributionInfo>] The editors contribution info.
|
720
|
+
#
|
721
|
+
def included_in_editors(title)
|
722
|
+
item = fetch_included_in title
|
723
|
+
return [] unless item
|
724
|
+
|
725
|
+
item["editor"].map { |e| contributor(create_person(e), "editor") }
|
726
|
+
end
|
727
|
+
|
728
|
+
#
|
729
|
+
# Fetch included in relation.
|
730
|
+
#
|
731
|
+
# @param [String] title container-title
|
732
|
+
#
|
733
|
+
# @return [Hash] The included in relation item.
|
734
|
+
#
|
735
|
+
def fetch_included_in(title)
|
736
|
+
query = CGI.escape [title, @src["publisher"], @src["publisher-location"], fetch_year].join(", ")
|
737
|
+
resp = Faraday.get %{http://api.crossref.org/works?query.bibliographic="#{query}"&rows=10&filter=type:book}
|
738
|
+
json = JSON.parse resp.body
|
739
|
+
json["message"]["items"].detect { |i| i["title"].include?(title) && i["editor"] }
|
740
|
+
end
|
741
|
+
|
742
|
+
#
|
743
|
+
# Fetch year from the source hash.
|
744
|
+
#
|
745
|
+
# @return [String] The year.
|
746
|
+
#
|
747
|
+
def fetch_year
|
748
|
+
d = @src["published"] || @src["approved"] || @src["created"]
|
749
|
+
d["date-parts"][0][0]
|
750
|
+
end
|
751
|
+
|
752
|
+
#
|
753
|
+
# Parse an extent from the source hash.
|
754
|
+
#
|
755
|
+
# @return [Array<RelatonBib::Locality>] The extent.
|
756
|
+
#
|
757
|
+
def parse_extent # rubocop:disable Metrics/AbcSize
|
758
|
+
extent = []
|
759
|
+
extent << RelatonBib::Locality.new("volume", @src["volume"]) if @src["volume"]
|
760
|
+
extent << RelatonBib::Locality.new("issue", @src["issue"]) if @src["issue"]
|
761
|
+
if @src["page"]
|
762
|
+
from, to = @src["page"].split("-")
|
763
|
+
extent << RelatonBib::Locality.new("page", from, to)
|
764
|
+
end
|
765
|
+
extent.any? ? [RelatonBib::LocalityStack.new(extent)] : []
|
766
|
+
end
|
767
|
+
|
768
|
+
#
|
769
|
+
# Parse a series from the source hash.
|
770
|
+
#
|
771
|
+
# @return [Arrey<RelatonBib::Series>] The series.
|
772
|
+
#
|
773
|
+
def parse_series # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
774
|
+
types = %w[inbook incollection inproceedings]
|
775
|
+
return [] if !@src["container-title"] || types.include?(@item[:type]) || @src["type"] == "report-component"
|
776
|
+
|
777
|
+
con_ttl = if main_sub_titles.any? || project_titles.any?
|
778
|
+
@src["container-title"]
|
779
|
+
elsif @src["container-title"].size > 1
|
780
|
+
sct = @src["short-container-title"]&.last
|
781
|
+
abbrev = RelatonBib::LocalizedString.new sct if sct
|
782
|
+
@src["container-title"][-1..-1]
|
783
|
+
else []
|
784
|
+
end
|
785
|
+
con_ttl.map do |ct|
|
786
|
+
title = RelatonBib::TypedTitleString.new content: ct
|
787
|
+
RelatonBib::Series.new title: title, abbreviation: abbrev
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
#
|
792
|
+
# Parse a medium from the source hash.
|
793
|
+
#
|
794
|
+
# @return [RelatonBib::Mediub, nil] The medium.
|
795
|
+
#
|
796
|
+
def parse_medium
|
797
|
+
genre = @src["degree"]&.first
|
798
|
+
return unless genre
|
799
|
+
|
800
|
+
RelatonBib::Medium.new genre: genre
|
801
|
+
end
|
802
|
+
end
|
803
|
+
end
|
data/lib/relaton_doi/version.rb
CHANGED
data/lib/relaton_doi.rb
CHANGED
@@ -8,6 +8,7 @@ require "relaton_ietf"
|
|
8
8
|
require "relaton_ieee"
|
9
9
|
require "relaton_nist"
|
10
10
|
require_relative "relaton_doi/version"
|
11
|
+
require_relative "relaton_doi/parser"
|
11
12
|
require_relative "relaton_doi/crossref"
|
12
13
|
|
13
14
|
Serrano.configuration do |config|
|
@@ -15,6 +16,4 @@ Serrano.configuration do |config|
|
|
15
16
|
end
|
16
17
|
|
17
18
|
module RelatonDoi
|
18
|
-
class Error < StandardError; end
|
19
|
-
# Your code goes here...
|
20
19
|
end
|
data/relaton-doi.gemspec
CHANGED
@@ -41,7 +41,7 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_development_dependency "vcr"
|
42
42
|
spec.add_development_dependency "webmock"
|
43
43
|
|
44
|
-
spec.add_dependency "relaton-bib", "~> 1.14.
|
44
|
+
spec.add_dependency "relaton-bib", "~> 1.14.3"
|
45
45
|
spec.add_dependency "relaton-bipm", "~> 1.14.0"
|
46
46
|
spec.add_dependency "relaton-ieee", "~> 1.14.0"
|
47
47
|
spec.add_dependency "relaton-ietf", "~> 1.14.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: relaton-doi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.14.
|
4
|
+
version: 1.14.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose Inc.
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: equivalent-xml
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 1.14.
|
103
|
+
version: 1.14.3
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 1.14.
|
110
|
+
version: 1.14.3
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: relaton-bipm
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -200,6 +200,7 @@ files:
|
|
200
200
|
- bin/setup
|
201
201
|
- lib/relaton_doi.rb
|
202
202
|
- lib/relaton_doi/crossref.rb
|
203
|
+
- lib/relaton_doi/parser.rb
|
203
204
|
- lib/relaton_doi/processor.rb
|
204
205
|
- lib/relaton_doi/version.rb
|
205
206
|
- relaton-doi.gemspec
|
@@ -208,7 +209,7 @@ licenses:
|
|
208
209
|
- BSD-2-Clause
|
209
210
|
metadata:
|
210
211
|
homepage_uri: https://github.com/relaton/relaton-doi
|
211
|
-
post_install_message:
|
212
|
+
post_install_message:
|
212
213
|
rdoc_options: []
|
213
214
|
require_paths:
|
214
215
|
- lib
|
@@ -223,8 +224,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
223
224
|
- !ruby/object:Gem::Version
|
224
225
|
version: '0'
|
225
226
|
requirements: []
|
226
|
-
rubygems_version: 3.
|
227
|
-
signing_key:
|
227
|
+
rubygems_version: 3.1.6
|
228
|
+
signing_key:
|
228
229
|
specification_version: 4
|
229
230
|
summary: 'RelatonDOI: retrieve Standards for bibliographic using DOI through Crossrefand
|
230
231
|
provide Relaton object.'
|