datacite 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +31 -9
- data/Gemfile +11 -10
- data/Gemfile.lock +136 -35
- data/README.md +20 -0
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/datacite.gemspec +19 -17
- data/lib/datacite/autogenerate_doi_request_body.rb +1 -1
- data/lib/datacite/client.rb +6 -6
- data/lib/datacite/mapping/from_cocina/alternate_identifiers.rb +30 -0
- data/lib/datacite/mapping/from_cocina/attributes.rb +74 -0
- data/lib/datacite/mapping/from_cocina/contributor_attributes.rb +209 -0
- data/lib/datacite/mapping/from_cocina/date.rb +198 -0
- data/lib/datacite/mapping/from_cocina/descriptions.rb +41 -0
- data/lib/datacite/mapping/from_cocina/identifiers.rb +28 -0
- data/lib/datacite/mapping/from_cocina/related_resource.rb +168 -0
- data/lib/datacite/mapping/from_cocina/rights_list.rb +36 -0
- data/lib/datacite/mapping/from_cocina/subject.rb +68 -0
- data/lib/datacite/mapping/from_cocina/titles.rb +32 -0
- data/lib/datacite/mapping/from_cocina/types.rb +81 -0
- data/lib/datacite/register_doi_request_body.rb +1 -1
- data/lib/datacite/response.rb +1 -1
- data/lib/datacite/schema/datacite-v4.6.json +643 -0
- data/lib/datacite/validators/attributes_validator.rb +43 -0
- data/lib/datacite/validators/cocina_validator.rb +26 -0
- data/lib/datacite/version.rb +1 -1
- data/lib/datacite.rb +1 -1
- metadata +46 -7
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datacite
|
|
4
|
+
module Mapping
|
|
5
|
+
module FromCocina
|
|
6
|
+
# Transform the Cocina::Models::Description to contributor attributes
|
|
7
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
8
|
+
class ContributorAttributes # rubocop:disable Metrics/ClassLength
|
|
9
|
+
DATACITE_PERSON_CONTRIBUTOR_TYPES = {
|
|
10
|
+
'copyright holder' => 'RightsHolder',
|
|
11
|
+
'compiler' => 'DataCollector',
|
|
12
|
+
'editor' => 'Editor',
|
|
13
|
+
'organizer' => 'Supervisor',
|
|
14
|
+
'research team head' => 'ProjectLeader',
|
|
15
|
+
'researcher' => 'Researcher'
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
DATACITE_ORGANIZATION_CONTRIBUTOR_TYPES = {
|
|
19
|
+
'copyright holder' => 'RightsHolder',
|
|
20
|
+
'compiler' => 'DataCollector',
|
|
21
|
+
'distributor' => 'Distributor',
|
|
22
|
+
'host institution' => 'HostingInstitution',
|
|
23
|
+
'issuing body' => 'Distributor',
|
|
24
|
+
'publisher' => 'Distributor',
|
|
25
|
+
'researcher' => 'ResearchGroup',
|
|
26
|
+
'sponsor' => 'Sponsor'
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# @param [Cocina::Models::Description] description
|
|
30
|
+
# @return [Hash] Hash of DataCite attributes containing creators, contributors, and fundingReferences keys
|
|
31
|
+
def self.build(...)
|
|
32
|
+
new(...).call
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(description:)
|
|
36
|
+
@description = description
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Hash] Hash of DataCite attributes containing creators, contributors, and fundingReferences keys
|
|
40
|
+
def call
|
|
41
|
+
{
|
|
42
|
+
creators: datacite_creators,
|
|
43
|
+
contributors: datacite_contributors,
|
|
44
|
+
fundingReferences: datacite_funders
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :description
|
|
51
|
+
|
|
52
|
+
def cocina_creators
|
|
53
|
+
@cocina_creators ||= Array(description.contributor).select do |cocina_contributor|
|
|
54
|
+
datacite_creator?(cocina_contributor)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cocina_contributors
|
|
59
|
+
@cocina_contributors ||= Array(description.contributor).select do |cocina_contributor|
|
|
60
|
+
datacite_contributor?(cocina_contributor)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def cocina_funders
|
|
65
|
+
@cocina_funders ||= Array(description.contributor).select do |cocina_contributor|
|
|
66
|
+
datacite_funder?(cocina_contributor)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def datacite_creator?(cocina_contributor)
|
|
71
|
+
!datacite_funder?(cocina_contributor) && !datacite_contributor?(cocina_contributor)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def datacite_funder?(cocina_contributor)
|
|
75
|
+
marc_relator(cocina_contributor) == 'funder'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def datacite_contributor?(cocina_contributor)
|
|
79
|
+
marc_relator(cocina_contributor) == 'publisher' || degree_committee_member?(cocina_contributor)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def datacite_creators
|
|
83
|
+
@datacite_creators ||= cocina_creators.map { |cocina_creator| datacite_creator(cocina_creator) }.uniq
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def datacite_contributors
|
|
87
|
+
@datacite_contributors ||= cocina_contributors.map do |cocina_contributor|
|
|
88
|
+
datacite_contributor(cocina_contributor)
|
|
89
|
+
end.uniq
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def datacite_funders
|
|
93
|
+
@datacite_funders ||= cocina_funders.map { |cocina_funder| { funderName: cocina_funder.name.first.value } }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def datacite_creator(cocina_contributor)
|
|
97
|
+
return personal_name(cocina_contributor) if person?(cocina_contributor)
|
|
98
|
+
|
|
99
|
+
organizational_name(cocina_contributor)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def person?(cocina_contributor)
|
|
103
|
+
cocina_contributor.type == 'person'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def datacite_contributor(cocina_contributor)
|
|
107
|
+
datacite_creator(cocina_contributor).merge({ contributorType: contributor_type(cocina_contributor) })
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def personal_name(cocina_contributor) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
111
|
+
{
|
|
112
|
+
nameType: 'Personal',
|
|
113
|
+
nameIdentifiers: name_identifiers(cocina_contributor).presence,
|
|
114
|
+
affiliation: affiliations(cocina_contributor).presence
|
|
115
|
+
}.tap do |name_hash|
|
|
116
|
+
# NOTE: This is needed for ETDs, for which we do not receive structured
|
|
117
|
+
# contributor names from Axess for ETD readers
|
|
118
|
+
if cocina_contributor.name.first.structuredValue.empty?
|
|
119
|
+
name_hash[:name] = cocina_contributor.name.first.value
|
|
120
|
+
elsif (name = cocina_contributor.name.first.structuredValue.find { |part| part.type == 'name' }).present?
|
|
121
|
+
name_hash[:name] = name.value
|
|
122
|
+
else
|
|
123
|
+
forename = cocina_contributor.name.first.structuredValue.find { |part| part.type == 'forename' }
|
|
124
|
+
surname = cocina_contributor.name.first.structuredValue.find { |part| part.type == 'surname' }
|
|
125
|
+
|
|
126
|
+
name_hash[:name] = "#{surname.value}, #{forename.value}"
|
|
127
|
+
name_hash[:givenName] = forename.value
|
|
128
|
+
name_hash[:familyName] = surname.value
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
.compact
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def organizational_name(cocina_contributor)
|
|
135
|
+
name = cocina_contributor.name.first.structuredValue.first || cocina_contributor.name.first
|
|
136
|
+
{
|
|
137
|
+
name: name.value,
|
|
138
|
+
nameType: 'Organizational',
|
|
139
|
+
nameIdentifiers: name_identifiers(name).presence
|
|
140
|
+
}.compact
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def name_identifiers(cocina_contributor)
|
|
144
|
+
Array(cocina_contributor.identifier).map do |identifier|
|
|
145
|
+
{
|
|
146
|
+
nameIdentifier: identifier.value || identifier.uri,
|
|
147
|
+
nameIdentifierScheme: identifier.type,
|
|
148
|
+
schemeURI: identifier.source.uri
|
|
149
|
+
}.compact
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def affiliations(cocina_contributor) # rubocop:disable Metrics/MethodLength
|
|
154
|
+
Array(cocina_contributor.affiliation).map do |affiliation|
|
|
155
|
+
institution = affiliation.structuredValue.find { |descriptive_value| descriptive_value.identifier.present? }
|
|
156
|
+
institution ||= affiliation # if no structured value with identifier, use the affiliation itself
|
|
157
|
+
identifier = institution.identifier.find { |id| id.type == 'ROR' }
|
|
158
|
+
next unless identifier&.uri
|
|
159
|
+
|
|
160
|
+
{
|
|
161
|
+
affiliationIdentifier: identifier.uri,
|
|
162
|
+
affiliationIdentifierScheme: 'ROR',
|
|
163
|
+
name: institution.value,
|
|
164
|
+
schemeUri: 'https://ror.org/'
|
|
165
|
+
}.compact
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def contributor_type(cocina_contributor)
|
|
170
|
+
if person?(cocina_contributor)
|
|
171
|
+
return DATACITE_PERSON_CONTRIBUTOR_TYPES.fetch(marc_relator(cocina_contributor),
|
|
172
|
+
'Other')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
DATACITE_ORGANIZATION_CONTRIBUTOR_TYPES.fetch(marc_relator(cocina_contributor), 'Other')
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# NOTE: This is how ETDs map to Cocina by way of MARC21 to MODS, where the
|
|
179
|
+
# 700$e and 700$4 subfields cannot be interpreted as pertaining to
|
|
180
|
+
# the same role, so the Cocina winds up expressing this as two
|
|
181
|
+
# roles, e.g.:
|
|
182
|
+
#
|
|
183
|
+
# role: [
|
|
184
|
+
# {
|
|
185
|
+
# value: "degree committee member",
|
|
186
|
+
# },
|
|
187
|
+
# {
|
|
188
|
+
# code: "ths",
|
|
189
|
+
# source: {
|
|
190
|
+
# code: "marcrelator",
|
|
191
|
+
# },
|
|
192
|
+
# }
|
|
193
|
+
# ]
|
|
194
|
+
def degree_committee_member?(cocina_contributor)
|
|
195
|
+
cocina_contributor.role.any? { |contrib_role| contrib_role.value == 'degree committee member' } &&
|
|
196
|
+
cocina_contributor.role.any? do |contrib_role|
|
|
197
|
+
contrib_role.code == 'ths' && contrib_role.source.code == 'marcrelator'
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def marc_relator(cocina_contributor)
|
|
202
|
+
Array(cocina_contributor.role).find do |role|
|
|
203
|
+
role&.source&.code == 'marcrelator'
|
|
204
|
+
end&.value
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datacite
|
|
4
|
+
module Mapping
|
|
5
|
+
module FromCocina
|
|
6
|
+
# Transform the Cocina::Models::DRO.description.event attributes to appropriate DataCite attributes
|
|
7
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
8
|
+
class Date # rubocop:disable Metrics/ClassLength
|
|
9
|
+
# @param [Cocina::Models::DRO] cocina_object
|
|
10
|
+
def self.build(...)
|
|
11
|
+
new(...).call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(cocina_object:)
|
|
15
|
+
@cocina_object = cocina_object
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# DataCite publicationYear is the year (YYYY) the object is published to purl, and is either:
|
|
19
|
+
## The embargo end date, if present (cocina event type release, date type publication)
|
|
20
|
+
## The deposit date (cocina event type deposit, date type publication)
|
|
21
|
+
#
|
|
22
|
+
# @return [String] publication year, conforming to the expectations of HTTP PUT request to DataCite
|
|
23
|
+
def pub_year # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
24
|
+
if embargo?
|
|
25
|
+
embargo_release_date = cocina_dro_access&.embargo&.releaseDate
|
|
26
|
+
embargo_release_date&.year&.to_s
|
|
27
|
+
elsif deposit_event_publication_date_value
|
|
28
|
+
DateTime.parse(deposit_event_publication_date_value).year&.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# H2 publisher role > same cocina event as publication date > see DataCite contributor mappings
|
|
33
|
+
# Add Stanford Digital Repository as publisher to cocina release event if present, otherwise deposit event
|
|
34
|
+
#
|
|
35
|
+
# sdr is the publisher for the event where the content becomes public via purl -- deposit if no embargo,
|
|
36
|
+
# release if embargo present.
|
|
37
|
+
# if it's not public via purl, sdr should not be the publisher;
|
|
38
|
+
# the user may enter someone with the publisher role in h2, referring to publication in another venue,
|
|
39
|
+
# regardless of the purl status.
|
|
40
|
+
def publisher
|
|
41
|
+
# TODO: implement this
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# DataCite date (YYYY-MM-DD) is repeatable and each DataCite data has an associated type attribute
|
|
45
|
+
# @return [Array<Hash>] DataCite date hashs, conforming to the expectations of HTTP PUT request to DataCite
|
|
46
|
+
def call # rubocop:disable Metrics/AbcSize
|
|
47
|
+
[].tap do |dates|
|
|
48
|
+
dates << submitted_date if submitted_date.present?
|
|
49
|
+
dates << available_date if available_date.present?
|
|
50
|
+
dates << issued_date if issued_date.present?
|
|
51
|
+
dates << created_date if created_date.present?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
attr_reader :cocina_object
|
|
58
|
+
|
|
59
|
+
def embargo?
|
|
60
|
+
cocina_dro_access&.embargo&.releaseDate.presence
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# If embargo,
|
|
64
|
+
# Cocina event type deposit, date type deposit maps to DataCite date type Submitted
|
|
65
|
+
# If no embargo
|
|
66
|
+
# Cocina event type deposit, date type publication maps to DataCite date type Submitted
|
|
67
|
+
# If no embargo and no deposit event with date type publication,
|
|
68
|
+
# Cocina event type publication, date type publication maps to DataCite date type Submitted
|
|
69
|
+
def submitted_date # rubocop:disable Metrics/AbcSize
|
|
70
|
+
@submitted_date ||= {}.tap do |submitted_date|
|
|
71
|
+
if embargo? && deposit_event_deposit_date_value.present?
|
|
72
|
+
submitted_date[:date] = deposit_event_deposit_date_value
|
|
73
|
+
elsif deposit_event_publication_date_value.present? # no embargo
|
|
74
|
+
submitted_date[:date] = deposit_event_publication_date_value
|
|
75
|
+
elsif publication_event_publication_date_value.present? # no embargo
|
|
76
|
+
submitted_date[:date] = publication_event_publication_date_value
|
|
77
|
+
end
|
|
78
|
+
submitted_date[:dateType] = 'Submitted' if submitted_date.present?
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# from Arcadia:
|
|
83
|
+
# Cocina event type release, date type publication maps to DataCite date type Available
|
|
84
|
+
# In actuality:
|
|
85
|
+
# embargo release date is in DROAccess, not in an event
|
|
86
|
+
def available_date
|
|
87
|
+
return unless embargo?
|
|
88
|
+
|
|
89
|
+
@available_date ||=
|
|
90
|
+
{
|
|
91
|
+
date: cocina_dro_access&.embargo&.releaseDate.to_s,
|
|
92
|
+
dateType: 'Available'
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Cocina event type publication, date type publication maps to DataCite date type Issued
|
|
97
|
+
def issued_date
|
|
98
|
+
return if publication_event_publication_date_value.blank?
|
|
99
|
+
|
|
100
|
+
@issued_date ||=
|
|
101
|
+
{
|
|
102
|
+
date: publication_event_publication_date_value,
|
|
103
|
+
dateType: 'Issued'
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Cocina event type creation, date type creation maps to DataCite date type Created
|
|
108
|
+
def created_date # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
109
|
+
return if creation_event_creation_date.blank?
|
|
110
|
+
|
|
111
|
+
@created_date ||= begin
|
|
112
|
+
created_date = {
|
|
113
|
+
dateType: 'Created'
|
|
114
|
+
}
|
|
115
|
+
if creation_event_creation_date.value
|
|
116
|
+
created_date[:date] = creation_event_creation_date.value
|
|
117
|
+
if creation_event_creation_date.qualifier.present?
|
|
118
|
+
created_date[:dateInformation] =
|
|
119
|
+
creation_event_creation_date.qualifier
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
created_date.merge!(structured_date_result(creation_event_creation_date))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
created_date
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def deposit_event_deposit_date_value
|
|
130
|
+
@deposit_event_deposit_date_value ||= deposit_event&.date&.find { |date| date&.type == 'deposit' }&.value
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def deposit_event_publication_date_value
|
|
134
|
+
@deposit_event_publication_date_value ||= deposit_event&.date&.find do |date|
|
|
135
|
+
date&.type == 'publication'
|
|
136
|
+
end&.value
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def deposit_event
|
|
140
|
+
@deposit_event ||= cocina_events&.find { |event| event&.type == 'deposit' }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def publication_event_publication_date_value
|
|
144
|
+
@publication_event_publication_date_value ||= publication_event&.date&.find do |date|
|
|
145
|
+
date&.type == 'publication'
|
|
146
|
+
end&.value
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def publication_event
|
|
150
|
+
@publication_event ||= cocina_events&.find { |event| event&.type == 'publication' }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def creation_event_creation_date
|
|
154
|
+
@creation_event_creation_date ||= creation_event&.date&.find { |date| date&.type == 'creation' }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def creation_event
|
|
158
|
+
@creation_event ||= cocina_events&.find { |event| event&.type == 'creation' }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def structured_date_result(date) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
162
|
+
return unless date.structuredValue
|
|
163
|
+
|
|
164
|
+
start_date, end_date, = ''
|
|
165
|
+
info = date.qualifier if date.qualifier.present?
|
|
166
|
+
date.structuredValue.each do |structured_val|
|
|
167
|
+
start_date = structured_val.value if structured_val.type == 'start'
|
|
168
|
+
end_date = structured_val.value if structured_val.type == 'end'
|
|
169
|
+
info = structured_val.qualifier if structured_val.qualifier
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
result_date = if start_date.present? && end_date.present?
|
|
173
|
+
"#{start_date}/#{end_date}"
|
|
174
|
+
elsif start_date.present?
|
|
175
|
+
start_date
|
|
176
|
+
elsif end_date.present?
|
|
177
|
+
end_date
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
{
|
|
181
|
+
date: result_date
|
|
182
|
+
}.tap do |attributes|
|
|
183
|
+
attributes[:dateInformation] = info if info.present? && result_date.present?
|
|
184
|
+
end.compact
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def cocina_events
|
|
188
|
+
@cocina_events ||= cocina_object.description.event
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# embargo is in Cocina::Models::DROAccess -- the top level access, not description.access
|
|
192
|
+
def cocina_dro_access
|
|
193
|
+
@cocina_dro_access ||= cocina_object.access
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datacite
|
|
4
|
+
module Mapping
|
|
5
|
+
module FromCocina
|
|
6
|
+
# Transform the Cocina::Models::Description note attributes to the DataCite descriptions attributes
|
|
7
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
8
|
+
class Descriptions
|
|
9
|
+
# @param [Cocina::Models::Description] cocina_desc
|
|
10
|
+
# @return [NilClass, Array<Hash>] list of DataCite descriptions attributes, conforming to the expectations of
|
|
11
|
+
# HTTP PUT request to DataCite
|
|
12
|
+
def self.build(...)
|
|
13
|
+
new(...).call
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(description:)
|
|
17
|
+
@description = description
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [NilClass, Array<Hash>] list of DataCite descriptions attributes, conforming to the expectations of
|
|
21
|
+
# HTTP PUT request to DataCite
|
|
22
|
+
def call
|
|
23
|
+
return unless abstract
|
|
24
|
+
|
|
25
|
+
[{
|
|
26
|
+
description: abstract,
|
|
27
|
+
descriptionType: 'Abstract'
|
|
28
|
+
}]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
attr_reader :description
|
|
34
|
+
|
|
35
|
+
def abstract
|
|
36
|
+
@abstract ||= Array(description.note).find { |cocina_note| cocina_note.type == 'abstract' }&.value
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datacite
|
|
4
|
+
module Mapping
|
|
5
|
+
module FromCocina
|
|
6
|
+
# Transform the Cocina::Models::Identification to the DataCite identifier attributes
|
|
7
|
+
class Identifiers
|
|
8
|
+
# @param [Cocina::Models::Identification] identification
|
|
9
|
+
def self.build(...)
|
|
10
|
+
new(...).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(identification:)
|
|
14
|
+
@identification = identification
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :identification
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
[{
|
|
21
|
+
identifier: identification.doi,
|
|
22
|
+
identifierType: 'DOI'
|
|
23
|
+
}]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datacite
|
|
4
|
+
module Mapping
|
|
5
|
+
module FromCocina
|
|
6
|
+
# Transform the Cocina::Models::Description relatedResource attributes to the DataCite relatedItem attributes
|
|
7
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
8
|
+
class RelatedResource # rubocop:disable Metrics/ClassLength
|
|
9
|
+
RELATION_TYPE_MAP = {
|
|
10
|
+
'supplement to' => 'IsSupplementTo',
|
|
11
|
+
'supplemented by' => 'IsSupplementedBy',
|
|
12
|
+
'referenced by' => 'IsReferencedBy',
|
|
13
|
+
'references' => 'References',
|
|
14
|
+
'derived from' => 'IsDerivedFrom',
|
|
15
|
+
'source of' => 'IsSourceOf',
|
|
16
|
+
'version of record' => 'IsVersionOf',
|
|
17
|
+
'identical to' => 'IsIdenticalTo',
|
|
18
|
+
'has version' => 'HasVersion',
|
|
19
|
+
'preceded by' => 'Continues',
|
|
20
|
+
'succeeded by' => 'IsContinuedBy',
|
|
21
|
+
'part of' => 'IsPartOf',
|
|
22
|
+
'has part' => 'HasPart'
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
# @param [Cocina::Models::RelatedResource] related_resource
|
|
26
|
+
# @return [Hash] Hash of DataCite relatedItem attributes, conforming to the expectations of HTTP PUT
|
|
27
|
+
# request to DataCite or nil if blank
|
|
28
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
29
|
+
def self.related_item_attributes(...)
|
|
30
|
+
new(...).related_item_attributes
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param [Cocina::Models::RelatedResource] related_resource
|
|
34
|
+
# @return [Hash] Hash of DataCite relatedIdentifier attributes, conforming to the expectations of HTTP PUT
|
|
35
|
+
# request to DataCite or nil if blank
|
|
36
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
37
|
+
def self.related_identifier_attributes(...)
|
|
38
|
+
new(...).related_identifier_attributes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(related_resource:)
|
|
42
|
+
@related_resource = related_resource
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Hash,nil] Array of DataCite relatedItem attributes, conforming to the expectations of HTTP PUT
|
|
46
|
+
# request to DataCite or nil if blank
|
|
47
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
48
|
+
def related_item_attributes # rubocop:disable Metrics/MethodLength
|
|
49
|
+
return if related_resource_blank?
|
|
50
|
+
|
|
51
|
+
titles = related_item_title ? [title: related_item_title] : []
|
|
52
|
+
id, type = unpack_related_uri_and_type
|
|
53
|
+
|
|
54
|
+
if id && type
|
|
55
|
+
{
|
|
56
|
+
relatedItemType: 'Other',
|
|
57
|
+
titles: titles,
|
|
58
|
+
relationType: relation_type,
|
|
59
|
+
relatedItemIdentifier: {
|
|
60
|
+
relatedItemIdentifier: id,
|
|
61
|
+
relatedItemIdentifierType: type
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
{
|
|
66
|
+
relatedItemType: 'Other',
|
|
67
|
+
titles: titles,
|
|
68
|
+
relationType: relation_type
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Hash,nil] Array of DataCite relatedIdentifier attributes, conforming to the expectations of HTTP PUT
|
|
74
|
+
# request to DataCite or nil if blank or the identifier lacks a URI or Type
|
|
75
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
76
|
+
def related_identifier_attributes
|
|
77
|
+
return if related_identifier_blank?
|
|
78
|
+
|
|
79
|
+
id, type = unpack_related_uri_and_type
|
|
80
|
+
return unless id && type
|
|
81
|
+
|
|
82
|
+
{
|
|
83
|
+
resourceTypeGeneral: 'Other',
|
|
84
|
+
relationType: relation_type,
|
|
85
|
+
relatedIdentifier: id,
|
|
86
|
+
relatedIdentifierType: type
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
attr_reader :related_resource
|
|
93
|
+
|
|
94
|
+
def relation_type
|
|
95
|
+
related_resource.dataCiteRelationType || RELATION_TYPE_MAP.fetch(related_resource.type, 'References')
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def related_resource_blank?
|
|
99
|
+
return true if related_resource.blank?
|
|
100
|
+
|
|
101
|
+
related_resource_hash = related_resource.to_h.slice(:note, :title, :access, :identifier)
|
|
102
|
+
related_resource_hash.blank? || related_resource_hash.each_value.all?(&:blank?)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def related_identifier_blank?
|
|
106
|
+
return true if related_resource.blank?
|
|
107
|
+
|
|
108
|
+
related_resource_hash = related_resource.to_h.slice(:access, :identifier)
|
|
109
|
+
related_resource_hash.blank? || related_resource_hash.each_value.all?(&:blank?)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def related_item_title
|
|
113
|
+
@related_item_title ||= preferred_citation || other_title \
|
|
114
|
+
|| related_item_doi || related_item_arxiv || related_item_pmid \
|
|
115
|
+
|| related_item_identifier_url
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def preferred_citation
|
|
119
|
+
Array(related_resource.note).find do |note|
|
|
120
|
+
note.type == 'preferred citation' && note.value.present?
|
|
121
|
+
end&.value
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def other_title
|
|
125
|
+
Array(related_resource.title).find do |title|
|
|
126
|
+
title.value.present?
|
|
127
|
+
end&.value
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def related_item_doi
|
|
131
|
+
Array(related_resource.identifier).find do |identifier|
|
|
132
|
+
identifier.type == 'doi' && identifier.uri.present?
|
|
133
|
+
end&.uri
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def related_item_arxiv
|
|
137
|
+
Array(related_resource.identifier).find do |identifier|
|
|
138
|
+
identifier.type == 'arxiv' && identifier.uri.present?
|
|
139
|
+
end&.uri
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def related_item_pmid
|
|
143
|
+
Array(related_resource.identifier).find do |identifier|
|
|
144
|
+
identifier.type == 'pmid' && identifier.uri.present?
|
|
145
|
+
end&.uri
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def related_item_identifier_url
|
|
149
|
+
@related_item_identifier_url ||= Array(related_resource.access&.url).find do |url|
|
|
150
|
+
url.value.present?
|
|
151
|
+
end&.value
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def unpack_related_uri_and_type
|
|
155
|
+
if related_item_doi
|
|
156
|
+
[related_item_doi, 'DOI']
|
|
157
|
+
elsif related_item_arxiv
|
|
158
|
+
[related_item_arxiv, 'arXiv']
|
|
159
|
+
elsif related_item_pmid
|
|
160
|
+
[related_item_pmid, 'PMID']
|
|
161
|
+
elsif related_item_identifier_url
|
|
162
|
+
[related_item_identifier_url, 'URL']
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datacite
|
|
4
|
+
module Mapping
|
|
5
|
+
module FromCocina
|
|
6
|
+
# Transform the Cocina::Models::DROAccess attributes to the DataCite rightsList attributes
|
|
7
|
+
# see https://support.datacite.org/reference/dois-2#put_dois-id
|
|
8
|
+
class RightsList
|
|
9
|
+
# @param [Cocina::Models::DROAccess] cocina_item_access
|
|
10
|
+
# @return [NilClass,Array<Hash>] list of DataCite rightsList attributes, conforming to the expectations of
|
|
11
|
+
# HTTP PUT request to DataCite
|
|
12
|
+
def self.build(...)
|
|
13
|
+
new(...).call
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(access:)
|
|
17
|
+
@access = access
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [NilClass,Array<Hash>] list of DataCite rightsList attributes, conforming to the expectations of
|
|
21
|
+
# HTTP PUT request to DataCite
|
|
22
|
+
def call
|
|
23
|
+
return if access&.license.blank?
|
|
24
|
+
|
|
25
|
+
[{
|
|
26
|
+
rightsUri: access&.license
|
|
27
|
+
}]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :access
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|