orcid_client 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.example +10 -0
- data/.gitignore +42 -0
- data/.travis.yml +17 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +115 -0
- data/LICENSE.md +21 -0
- data/README.md +45 -0
- data/Rakefile +5 -0
- data/lib/orcid_client.rb +3 -0
- data/lib/orcid_client/api.rb +75 -0
- data/lib/orcid_client/author.rb +56 -0
- data/lib/orcid_client/base.rb +21 -0
- data/lib/orcid_client/date.rb +61 -0
- data/lib/orcid_client/metadata.rb +128 -0
- data/lib/orcid_client/notification.rb +125 -0
- data/lib/orcid_client/version.rb +3 -0
- data/lib/orcid_client/work.rb +200 -0
- data/lib/orcid_client/work_type.rb +68 -0
- data/orcid_client.gemspec +37 -0
- data/resources/common_2.0_rc3/common-2.0_rc3.xsd +1624 -0
- data/resources/common_2.0_rc3/samples/common-2.0_rc3.xml +8 -0
- data/resources/notification_2.0_rc3/notification-custom-2.0_rc3.xsd +32 -0
- data/resources/notification_2.0_rc3/notification-permission-2.0_rc3.xsd +128 -0
- data/resources/notification_2.0_rc3/samples/notification-custom-2.0_rc3.xml +45 -0
- data/resources/notification_2.0_rc3/samples/notification-permission-2.0_rc3.xml +58 -0
- data/resources/record_2.0_rc3/activities-2.0_rc3.xsd +187 -0
- data/resources/record_2.0_rc3/address-2.0_rc3.xsd +73 -0
- data/resources/record_2.0_rc3/bulk-2.0_rc3.xsd +57 -0
- data/resources/record_2.0_rc3/deprecated-2.0_rc3.xsd +80 -0
- data/resources/record_2.0_rc3/education-2.0_rc3.xsd +96 -0
- data/resources/record_2.0_rc3/email-2.0_rc3.xsd +74 -0
- data/resources/record_2.0_rc3/employment-2.0_rc3.xsd +96 -0
- data/resources/record_2.0_rc3/error-2.0_rc3.xsd +84 -0
- data/resources/record_2.0_rc3/funding-2.0_rc3.xsd +257 -0
- data/resources/record_2.0_rc3/history-2.0_rc3.xsd +203 -0
- data/resources/record_2.0_rc3/internal-2.0_rc3.xsd +199 -0
- data/resources/record_2.0_rc3/keyword-2.0_rc3.xsd +72 -0
- data/resources/record_2.0_rc3/other-name-2.0_rc3.xsd +88 -0
- data/resources/record_2.0_rc3/peer-review-2.0_rc3.xsd +246 -0
- data/resources/record_2.0_rc3/person-2.0_rc3.xsd +98 -0
- data/resources/record_2.0_rc3/person-external-identifier-2.0_rc3.xsd +63 -0
- data/resources/record_2.0_rc3/personal-details-2.0_rc3.xsd +186 -0
- data/resources/record_2.0_rc3/preferences-2.0_rc3.xsd +50 -0
- data/resources/record_2.0_rc3/record-2.0_rc3.xsd +105 -0
- data/resources/record_2.0_rc3/researcher-url-2.0_rc3.xsd +90 -0
- data/resources/record_2.0_rc3/samples/activities-2.0_rc3.xml +236 -0
- data/resources/record_2.0_rc3/samples/address-2.0_rc3.xml +16 -0
- data/resources/record_2.0_rc3/samples/addresses-2.0_rc3.xml +32 -0
- data/resources/record_2.0_rc3/samples/biography-2.0_rc3.xml +10 -0
- data/resources/record_2.0_rc3/samples/bulk-work-2.0_rc3.json +56 -0
- data/resources/record_2.0_rc3/samples/bulk-work-2.0_rc3.xml +62 -0
- data/resources/record_2.0_rc3/samples/credit-name-2.0_rc3.xml +5 -0
- data/resources/record_2.0_rc3/samples/deprecated-2.0_rc3.xml +13 -0
- data/resources/record_2.0_rc3/samples/education-2.0_rc3.xml +30 -0
- data/resources/record_2.0_rc3/samples/education-full-2.0_rc3.xml +40 -0
- data/resources/record_2.0_rc3/samples/email-2.0_rc3.xml +16 -0
- data/resources/record_2.0_rc3/samples/emails-2.0_rc3.xml +31 -0
- data/resources/record_2.0_rc3/samples/employment-2.0_rc3.xml +30 -0
- data/resources/record_2.0_rc3/samples/employment-full-2.0_rc3.xml +40 -0
- data/resources/record_2.0_rc3/samples/error-2.0_rc3.xml +8 -0
- data/resources/record_2.0_rc3/samples/external-identifier-2.0_rc3.xml +20 -0
- data/resources/record_2.0_rc3/samples/external-identifiers-2.0_rc3.xml +36 -0
- data/resources/record_2.0_rc3/samples/funding-2.0_rc3.xml +65 -0
- data/resources/record_2.0_rc3/samples/funding-full-2.0_rc3.xml +75 -0
- data/resources/record_2.0_rc3/samples/history-2.0_rc3.xml +22 -0
- data/resources/record_2.0_rc3/samples/keyword-2.0_rc3.xml +18 -0
- data/resources/record_2.0_rc3/samples/keywords-2.0_rc3.xml +34 -0
- data/resources/record_2.0_rc3/samples/name-2.0_rc3.xml +12 -0
- data/resources/record_2.0_rc3/samples/other-name-2.0_rc3.xml +16 -0
- data/resources/record_2.0_rc3/samples/other-names-2.0_rc3.xml +31 -0
- data/resources/record_2.0_rc3/samples/peer-review-2.0_rc3.xml +49 -0
- data/resources/record_2.0_rc3/samples/peer-review-full-2.0_rc3.xml +59 -0
- data/resources/record_2.0_rc3/samples/person-2.0_rc3.xml +122 -0
- data/resources/record_2.0_rc3/samples/personal-details-2.0_rc3.xml +47 -0
- data/resources/record_2.0_rc3/samples/preferences-2.0_rc3.xml +4 -0
- data/resources/record_2.0_rc3/samples/record-2.0_rc3.xml +420 -0
- data/resources/record_2.0_rc3/samples/researcher-url-2.0_rc3.xml +19 -0
- data/resources/record_2.0_rc3/samples/researcher-urls-2.0_rc3.xml +21 -0
- data/resources/record_2.0_rc3/samples/search-2.0_rc3.xml +368 -0
- data/resources/record_2.0_rc3/samples/work-2.0_rc3.xml +49 -0
- data/resources/record_2.0_rc3/samples/work-full-2.0_rc3.xml +59 -0
- data/resources/record_2.0_rc3/search-2.0_rc3.xsd +82 -0
- data/resources/record_2.0_rc3/work-2.0_rc3.xsd +445 -0
- data/spec/api_spec.rb +59 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient/get/should_get_works.yml +646 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient/notifications/post/should_create_notification.yml +104 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient/works/delete/should_delete_work.yml +38 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient/works/get/should_get_works.yml +646 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient/works/post/should_create_work.yml +116 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient/works/put/should_update_work.yml +155 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Notification/data.yml +44 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Notification/schema/validates_data.yml +44 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Notification/schema/validates_item_type_work.yml +41 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/citation.yml +50 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/contributors/literal.yml +47 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/contributors/multiple_titles.yml +47 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/contributors/valid.yml +50 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/contributors/with_ORCID_IDs.yml +157 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/data.yml +50 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/publication_date.yml +50 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/schema/validates_ORCID_IDs_for_contributors.yml +157 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/schema/validates_data.yml +50 -0
- data/spec/fixtures/vcr_cassettes/OrcidClient_Work/schema/validates_work_type_data-set.yml +47 -0
- data/spec/fixtures/work.xml +33 -0
- data/spec/notification_spec.rb +42 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/work_spec.rb +100 -0
- metadata +392 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module OrcidClient
|
2
|
+
module Date
|
3
|
+
def get_date_parts(iso8601_time)
|
4
|
+
return { "date_parts" => [[]] } if iso8601_time.nil?
|
5
|
+
|
6
|
+
year = iso8601_time[0..3].to_i
|
7
|
+
month = iso8601_time[5..6].to_i
|
8
|
+
day = iso8601_time[8..9].to_i
|
9
|
+
{ 'date-parts' => [[year, month, day].reject { |part| part == 0 }] }
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_year_month(iso8601_time)
|
13
|
+
return [nil, nil] if iso8601_time.nil?
|
14
|
+
|
15
|
+
year = iso8601_time[0..3].to_i
|
16
|
+
month = iso8601_time[5..6].to_i
|
17
|
+
|
18
|
+
[year, month]
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_date_parts_from_parts(year = nil, month = nil, day = nil)
|
22
|
+
{ 'date-parts' => [[year.to_i, month.to_i, day.to_i].reject { |part| part == 0 }] }
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_parts_from_date_parts(date_parts)
|
26
|
+
parts = date_parts.fetch('date-parts', []).first
|
27
|
+
return { "date_parts" => [[]] } unless parts.present?
|
28
|
+
|
29
|
+
{ 'year' => parts[0],
|
30
|
+
'month' => parts[1],
|
31
|
+
'day' => parts[2] }.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_year_month_day(iso8601_time)
|
35
|
+
return [] if iso8601_time.nil?
|
36
|
+
|
37
|
+
year = iso8601_time[0..3]
|
38
|
+
month = iso8601_time[5..6]
|
39
|
+
day = iso8601_time[8..9]
|
40
|
+
|
41
|
+
{ 'year' => year.to_i,
|
42
|
+
'month' => month.to_i,
|
43
|
+
'day' => day.to_i }.delete_if { |key, value| value == 0 }
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_iso8601_from_time(time)
|
47
|
+
return nil if time.blank?
|
48
|
+
|
49
|
+
Time.zone.parse(time.to_s).utc.iso8601
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_iso8601_from_epoch(epoch)
|
53
|
+
return nil if epoch.blank?
|
54
|
+
|
55
|
+
# handle milliseconds
|
56
|
+
epoch = epoch.to_i
|
57
|
+
epoch = epoch / 1000 if epoch > 9999999999
|
58
|
+
Time.at(epoch).utc.iso8601
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module OrcidClient
|
2
|
+
module Metadata
|
3
|
+
def get_metadata(id, service, options = {})
|
4
|
+
case service
|
5
|
+
when "crossref" then get_crossref_metadata(id, options = {})
|
6
|
+
when "datacite" then get_datacite_metadata(id, options = {})
|
7
|
+
when "orcid" then get_orcid_metadata(id, options = {})
|
8
|
+
else
|
9
|
+
{ "errors" => [{ "title" => 'Not found.', "status" => 404 }] }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_crossref_metadata(doi, options = {})
|
14
|
+
return {} if doi.blank?
|
15
|
+
|
16
|
+
url = "http://api.crossref.org/works/" + PostRank::URI.escape(doi)
|
17
|
+
response = Maremma.get(url, options.merge(host: true))
|
18
|
+
return response if response["errors"]
|
19
|
+
|
20
|
+
metadata = response.fetch("data", {}).fetch("message", {})
|
21
|
+
return { "errors" => [{ "title" => "Not found.", "status" => 404 }] } if metadata.blank?
|
22
|
+
|
23
|
+
date_parts = metadata.fetch("issued", {}).fetch("date-parts", []).first
|
24
|
+
year, month, day = date_parts[0], date_parts[1], date_parts[2]
|
25
|
+
|
26
|
+
# use date indexed if date issued is in the future
|
27
|
+
if year.nil? || Date.new(*date_parts) > Time.zone.now.to_date
|
28
|
+
date_parts = metadata.fetch("indexed", {}).fetch("date-parts", []).first
|
29
|
+
year, month, day = date_parts[0], date_parts[1], date_parts[2]
|
30
|
+
end
|
31
|
+
metadata["issued"] = { "date-parts" => [date_parts] }
|
32
|
+
|
33
|
+
metadata["title"] = case metadata["title"].length
|
34
|
+
when 0 then nil
|
35
|
+
when 1 then metadata["title"][0]
|
36
|
+
else metadata["title"][0].presence || metadata["title"][1]
|
37
|
+
end
|
38
|
+
|
39
|
+
if metadata["title"].blank? && !TYPES_WITH_TITLE.include?(metadata["type"])
|
40
|
+
metadata["title"] = metadata["container-title"][0].presence || "No title"
|
41
|
+
end
|
42
|
+
|
43
|
+
metadata["container-title"] = metadata.fetch("container-title", [])[0]
|
44
|
+
metadata["type"] = CROSSREF_TYPE_TRANSLATIONS[metadata["type"]] if metadata["type"]
|
45
|
+
metadata["author"] = metadata["author"].map { |author| author.except("affiliation") }
|
46
|
+
|
47
|
+
metadata
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_datacite_metadata(doi, options = {})
|
51
|
+
return {} if doi.blank?
|
52
|
+
|
53
|
+
params = { q: "doi:" + doi,
|
54
|
+
rows: 1,
|
55
|
+
fl: "doi,creator,title,publisher,publicationYear,resourceTypeGeneral,description,datacentre,datacentre_symbol,prefix,relatedIdentifier,xml,minted,updated",
|
56
|
+
wt: "json" }
|
57
|
+
url = "http://search.datacite.org/api?" + URI.encode_www_form(params)
|
58
|
+
response = Maremma.get(url, options)
|
59
|
+
return response if response["errors"]
|
60
|
+
|
61
|
+
metadata = response.fetch("data", {}).fetch("response", {}).fetch("docs", []).first
|
62
|
+
return { "errors" => [{ "title" => "Not found.", "status" => 404 }] } if metadata.blank?
|
63
|
+
|
64
|
+
doi = metadata.fetch("doi", nil)
|
65
|
+
doi = doi.upcase if doi.present?
|
66
|
+
title = metadata.fetch("title", []).first
|
67
|
+
title = title.chomp(".") if title.present?
|
68
|
+
|
69
|
+
xml = Base64.decode64(metadata.fetch('xml', "PGhzaD48L2hzaD4=\n"))
|
70
|
+
xml = Hash.from_xml(xml).fetch("resource", {})
|
71
|
+
authors = xml.fetch("creators", {}).fetch("creator", [])
|
72
|
+
authors = [authors] if authors.is_a?(Hash)
|
73
|
+
|
74
|
+
{ "author" => get_hashed_authors(authors),
|
75
|
+
"title" => title,
|
76
|
+
"container-title" => metadata.fetch("publisher", nil),
|
77
|
+
"description" => metadata.fetch("description", nil),
|
78
|
+
"published" => metadata.fetch("publicationYear", nil),
|
79
|
+
"issued" => metadata.fetch("minted", nil),
|
80
|
+
"DOI" => doi,
|
81
|
+
"type" => metadata.fetch("resourceTypeGeneral", nil),
|
82
|
+
"subtype" => metadata.fetch("resourceType", nil),
|
83
|
+
"publisher_id" => metadata.fetch("datacentre_symbol", nil) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_orcid_metadata(orcid, options = {})
|
87
|
+
return {} if orcid.blank?
|
88
|
+
|
89
|
+
url = "http://pub.orcid.org/v1.2/#{orcid}/orcid-bio"
|
90
|
+
response = Maremma.get(url, options)
|
91
|
+
return response if response["errors"]
|
92
|
+
|
93
|
+
metadata = response.fetch("data", {}).fetch("orcid_message", {}).fetch("orcid_profile", nil)
|
94
|
+
metadata.extend Hashie::Extensions::DeepFetch
|
95
|
+
|
96
|
+
personal_details = metadata.deep_fetch("orcid_bio", "personal_details") { {} }
|
97
|
+
personal_details.extend Hashie::Extensions::DeepFetch
|
98
|
+
|
99
|
+
author = { "family" => personal_details.fetch("family_name", nil),
|
100
|
+
"given" => personal_details.fetch("given_names", nil) }
|
101
|
+
url = metadata.deep_fetch("orcid_identifier", "uri") { nil }
|
102
|
+
timestamp = Time.zone.now.utc.iso8601
|
103
|
+
|
104
|
+
{ "author" => [author],
|
105
|
+
"title" => "ORCID record for #{author.fetch('given', '')} #{author.fetch('family', '')}",
|
106
|
+
"container-title" => "ORCID Registry",
|
107
|
+
"issued" => get_date_parts(timestamp),
|
108
|
+
"timestamp" => timestamp,
|
109
|
+
"URL" => url,
|
110
|
+
"type" => 'entry' }
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_doi_ra(doi, options = {})
|
114
|
+
return {} if doi.blank?
|
115
|
+
|
116
|
+
url = "http://doi.crossref.org/doiRA/" + CGI.unescape(doi)
|
117
|
+
response = Maremma.get(url, options.merge(host: true))
|
118
|
+
return response if response["errors"]
|
119
|
+
|
120
|
+
ra = response.fetch("data", {}).first.fetch("RA", nil)
|
121
|
+
if ra.present?
|
122
|
+
ra.delete(' ').downcase
|
123
|
+
else
|
124
|
+
{ "errors" => [{ "title" => "An error occured", "status" => 400 }] }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
require_relative 'api'
|
5
|
+
require_relative 'author'
|
6
|
+
require_relative 'base'
|
7
|
+
require_relative 'date'
|
8
|
+
require_relative 'metadata'
|
9
|
+
|
10
|
+
module OrcidClient
|
11
|
+
class Notification
|
12
|
+
include OrcidClient::Base
|
13
|
+
include OrcidClient::Metadata
|
14
|
+
include OrcidClient::Author
|
15
|
+
include OrcidClient::Date
|
16
|
+
include OrcidClient::Api
|
17
|
+
|
18
|
+
attr_reader :doi, :orcid, :schema, :notification_access_token, :put_code, :subject, :intro, :notification_host, :validation_errors
|
19
|
+
|
20
|
+
def initialize(doi:, orcid:, notification_access_token:, **options)
|
21
|
+
@doi = doi
|
22
|
+
@orcid = orcid
|
23
|
+
@notification_access_token = notification_access_token
|
24
|
+
@put_code = options.fetch(:put_code, nil)
|
25
|
+
@subject = options.fetch(:subject, nil)
|
26
|
+
@intro = options.fetch(:intro, nil)
|
27
|
+
@notification_host = options[:sandbox] ? 'sandbox.orcid.org' : 'orcid.org'
|
28
|
+
end
|
29
|
+
|
30
|
+
SCHEMA = File.expand_path("../../../resources/notification_#{API_VERSION}/notification-permission-#{API_VERSION}.xsd", __FILE__)
|
31
|
+
|
32
|
+
def metadata
|
33
|
+
@metadata ||= get_metadata(doi, 'datacite')
|
34
|
+
end
|
35
|
+
|
36
|
+
def item_name
|
37
|
+
metadata.fetch('title', nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
def item_type
|
41
|
+
"work"
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_required_elements?
|
45
|
+
doi && item_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def data
|
49
|
+
return nil unless has_required_elements?
|
50
|
+
|
51
|
+
Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
52
|
+
xml.send(:'notification:notification', root_attributes) do
|
53
|
+
insert_notification(xml)
|
54
|
+
end
|
55
|
+
end.to_xml
|
56
|
+
end
|
57
|
+
|
58
|
+
def insert_notification(xml)
|
59
|
+
insert_notification_type(xml)
|
60
|
+
insert_authorization_url(xml)
|
61
|
+
insert_notification_subject(xml)
|
62
|
+
insert_notification_intro(xml)
|
63
|
+
insert_items(xml)
|
64
|
+
end
|
65
|
+
|
66
|
+
def insert_notification_type(xml)
|
67
|
+
xml.send(:'notification:notification-type', "permission")
|
68
|
+
end
|
69
|
+
|
70
|
+
def insert_authorization_url(xml)
|
71
|
+
xml.send(:'notification:authorization-url') do
|
72
|
+
xml.send(:'notification:path', work_notification_path)
|
73
|
+
xml.send(:'notification:host', notification_host)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def work_notification_path
|
78
|
+
"/oauth/authorize?client_id=#{ENV['ORCID_CLIENT_ID']}&response_type=code&scope=/read-limited%20/activities/update%20/person/update&redirect_uri=#{ENV['REDIRECT_URI']}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def insert_notification_subject(xml)
|
82
|
+
xml.send(:'notification:notification-subject', subject)
|
83
|
+
end
|
84
|
+
|
85
|
+
def insert_notification_intro(xml)
|
86
|
+
xml.send(:'notification:notification-intro', intro)
|
87
|
+
end
|
88
|
+
|
89
|
+
def insert_items(xml)
|
90
|
+
return nil unless has_required_elements?
|
91
|
+
|
92
|
+
xml.send(:'notification:items') do
|
93
|
+
xml.send(:'notification:item') do
|
94
|
+
xml.send(:'notification:item-type', item_type)
|
95
|
+
xml.send(:'notification:item-name', item_name)
|
96
|
+
|
97
|
+
insert_id(xml, "doi", doi, "self")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def insert_id(xml, id_type, value, relationship)
|
103
|
+
xml.send(:'common:external-id') do
|
104
|
+
xml.send(:'common:external-id-type', id_type)
|
105
|
+
xml.send(:'common:external-id-value', value)
|
106
|
+
xml.send(:'common:external-id-relationship', relationship)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def root_attributes
|
111
|
+
{ :'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
112
|
+
:'xsi:schemaLocation' => 'http://www.orcid.org/ns/notification ../notification-permission-2.0_rc3.xsd',
|
113
|
+
:'xmlns:common' => 'http://www.orcid.org/ns/common',
|
114
|
+
:'xmlns:notification' => 'http://www.orcid.org/ns/notification' }
|
115
|
+
end
|
116
|
+
|
117
|
+
def schema
|
118
|
+
Nokogiri::XML::Schema(open(SCHEMA))
|
119
|
+
end
|
120
|
+
|
121
|
+
def validation_errors
|
122
|
+
@validation_errors ||= schema.validate(Nokogiri::XML(data)).map { |error| error.to_s }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'sanitize'
|
4
|
+
|
5
|
+
require_relative 'api'
|
6
|
+
require_relative 'author'
|
7
|
+
require_relative 'base'
|
8
|
+
require_relative 'date'
|
9
|
+
require_relative 'metadata'
|
10
|
+
require_relative 'work_type'
|
11
|
+
|
12
|
+
module OrcidClient
|
13
|
+
class Work
|
14
|
+
include OrcidClient::Base
|
15
|
+
include OrcidClient::Metadata
|
16
|
+
include OrcidClient::Author
|
17
|
+
include OrcidClient::Date
|
18
|
+
include OrcidClient::WorkType
|
19
|
+
include OrcidClient::Api
|
20
|
+
|
21
|
+
attr_reader :doi, :orcid, :schema, :access_token, :put_code, :validation_errors
|
22
|
+
|
23
|
+
def initialize(doi:, orcid:, access_token:, **options)
|
24
|
+
@doi = doi
|
25
|
+
@orcid = orcid
|
26
|
+
@access_token = access_token
|
27
|
+
@put_code = options.fetch(:put_code, nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
SCHEMA = File.expand_path("../../../resources/record_#{API_VERSION}/work-#{API_VERSION}.xsd", __FILE__)
|
31
|
+
|
32
|
+
def metadata
|
33
|
+
@metadata ||= get_metadata(doi, 'datacite')
|
34
|
+
end
|
35
|
+
|
36
|
+
def contributors
|
37
|
+
Array(metadata.fetch('author', nil)).map do |contributor|
|
38
|
+
{ orcid: contributor.fetch('ORCID', nil),
|
39
|
+
credit_name: get_credit_name(contributor),
|
40
|
+
role: nil }.compact
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def author_string
|
45
|
+
Array(metadata.fetch('author', nil)).map do |contributor|
|
46
|
+
get_full_name(contributor)
|
47
|
+
end.join(" and ")
|
48
|
+
end
|
49
|
+
|
50
|
+
def sanitize(string)
|
51
|
+
Sanitize.fragment(string).squish
|
52
|
+
end
|
53
|
+
|
54
|
+
def title
|
55
|
+
metadata.fetch('title', nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
def container_title
|
59
|
+
metadata.fetch('container-title', nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
def publisher_id
|
63
|
+
metadata.fetch('publisher_id', nil)
|
64
|
+
end
|
65
|
+
|
66
|
+
def publication_date
|
67
|
+
get_year_month_day(metadata.fetch('published', nil))
|
68
|
+
end
|
69
|
+
|
70
|
+
def description
|
71
|
+
sanitize(Array(metadata.fetch('description', nil)).first)
|
72
|
+
end
|
73
|
+
|
74
|
+
def type
|
75
|
+
orcid_work_type(metadata.fetch('type', nil), metadata.fetch('subtype', nil))
|
76
|
+
end
|
77
|
+
|
78
|
+
def has_required_elements?
|
79
|
+
doi && contributors && title && container_title && publication_date
|
80
|
+
end
|
81
|
+
|
82
|
+
def data
|
83
|
+
return nil unless has_required_elements?
|
84
|
+
|
85
|
+
Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
|
86
|
+
xml.send(:'work:work', root_attributes) do
|
87
|
+
insert_work(xml)
|
88
|
+
end
|
89
|
+
end.to_xml
|
90
|
+
end
|
91
|
+
|
92
|
+
def insert_work(xml)
|
93
|
+
insert_titles(xml)
|
94
|
+
insert_description(xml)
|
95
|
+
insert_type(xml)
|
96
|
+
insert_pub_date(xml)
|
97
|
+
insert_ids(xml)
|
98
|
+
insert_contributors(xml)
|
99
|
+
end
|
100
|
+
|
101
|
+
def insert_titles(xml)
|
102
|
+
if title
|
103
|
+
xml.send(:'work:title') do
|
104
|
+
xml.send(:'common:title', title)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
xml.send(:'work:journal-title', container_title) if container_title
|
109
|
+
end
|
110
|
+
|
111
|
+
def insert_description(xml)
|
112
|
+
return nil unless description.present?
|
113
|
+
|
114
|
+
xml.send(:'work:short-description', description.truncate(2500, separator: ' '))
|
115
|
+
end
|
116
|
+
|
117
|
+
def insert_type(xml)
|
118
|
+
xml.send(:'work:type', type)
|
119
|
+
end
|
120
|
+
|
121
|
+
def insert_pub_date(xml)
|
122
|
+
if publication_date['year']
|
123
|
+
xml.send(:'common:publication-date') do
|
124
|
+
xml.year(publication_date.fetch('year'))
|
125
|
+
xml.month(publication_date.fetch('month', nil)) if publication_date['month']
|
126
|
+
xml.day(publication_date.fetch('day', nil)) if publication_date['month'] && publication_date['day']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def insert_ids(xml)
|
132
|
+
xml.send(:'common:external-ids') do
|
133
|
+
insert_id(xml, 'doi', doi, 'self')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def insert_id(xml, id_type, value, relationship)
|
138
|
+
xml.send(:'common:external-id') do
|
139
|
+
xml.send(:'common:external-id-type', id_type)
|
140
|
+
xml.send(:'common:external-id-value', value)
|
141
|
+
xml.send(:'common:external-id-relationship', relationship)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def insert_contributors(xml)
|
146
|
+
return nil unless contributors.present?
|
147
|
+
|
148
|
+
xml.send(:'work:contributors') do
|
149
|
+
contributors.each do |contributor|
|
150
|
+
xml.contributor do
|
151
|
+
insert_contributor(xml, contributor)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def insert_contributor(xml, contributor)
|
158
|
+
if contributor[:orcid].present?
|
159
|
+
xml.send(:'common:contributor-orcid') do
|
160
|
+
xml.send(:'common:uri', contributor[:orcid])
|
161
|
+
xml.send(:'common:path', contributor[:orcid][17..-1])
|
162
|
+
xml.send(:'common:host', 'orcid.org')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
xml.send(:'credit-name', contributor[:credit_name])
|
166
|
+
if contributor[:role]
|
167
|
+
xml.send(:'contributor-attributes') do
|
168
|
+
xml.send(:'contributor-role', contributor[:role])
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def without_control(s)
|
174
|
+
r = ''
|
175
|
+
s.each_codepoint do |c|
|
176
|
+
if c >= 32
|
177
|
+
r << c
|
178
|
+
end
|
179
|
+
end
|
180
|
+
r
|
181
|
+
end
|
182
|
+
|
183
|
+
def root_attributes
|
184
|
+
{ :'put-code' => put_code,
|
185
|
+
:'visibility' => 'public',
|
186
|
+
:'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
|
187
|
+
:'xsi:schemaLocation' => 'http://www.orcid.org/ns/work ../work-2.0_rc3.xsd',
|
188
|
+
:'xmlns:common' => 'http://www.orcid.org/ns/common',
|
189
|
+
:'xmlns:work' => 'http://www.orcid.org/ns/work' }.compact
|
190
|
+
end
|
191
|
+
|
192
|
+
def schema
|
193
|
+
Nokogiri::XML::Schema(open(SCHEMA))
|
194
|
+
end
|
195
|
+
|
196
|
+
def validation_errors
|
197
|
+
@validation_errors ||= schema.validate(Nokogiri::XML(data)).map { |error| error.to_s }
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|