datacite 0.7.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.
@@ -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
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datacite
4
+ module Mapping
5
+ module FromCocina
6
+ # Transform the Cocina::Models::Description subjects attributes to the DataCite subjects attributes
7
+ # see https://support.datacite.org/reference/dois-2#put_dois-id
8
+ class Subject
9
+ # @param [Cocina::Models::Description] description
10
+ # @return [NilClass,Array<Hash>] list of DataCite subjects 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 subjects attributes, conforming to the expectations of
21
+ # HTTP PUT request to DataCite
22
+ def call
23
+ return if description&.subject.blank?
24
+
25
+ results = description.subject.map do |cocina_subject|
26
+ subject(cocina_subject)
27
+ end
28
+ results.compact.presence
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :description
34
+
35
+ def subject(cocina_subject)
36
+ return if cocina_subject.blank?
37
+
38
+ if fast?(cocina_subject)
39
+ fast_subject(cocina_subject)
40
+ else
41
+ non_fast_subject(cocina_subject)
42
+ end
43
+ end
44
+
45
+ def fast_subject(cocina_subject)
46
+ {
47
+ subjectScheme: 'fast',
48
+ schemeUri: 'http://id.worldcat.org/fast/'
49
+
50
+ }.tap do |attribs|
51
+ attribs[:subject] = cocina_subject.value if cocina_subject.value.present?
52
+ attribs[:valueUri] = cocina_subject.uri if cocina_subject.uri.present?
53
+ end
54
+ end
55
+
56
+ def non_fast_subject(cocina_subject)
57
+ return if cocina_subject.value.blank?
58
+
59
+ { subject: cocina_subject.value }
60
+ end
61
+
62
+ def fast?(cocina_subject)
63
+ cocina_subject&.source&.code == 'fast'
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datacite
4
+ module Mapping
5
+ module FromCocina
6
+ # Transform the Cocina::Models::Description title attributes to attributes for one DataCite title
7
+ # see https://support.datacite.org/reference/dois-2#put_dois-id
8
+ class Titles
9
+ # @param [Cocina::Models::Description] description
10
+ # @return [Array<Hash>] list of titles for DataCite, conforming to the expectations of HTTP PUT request
11
+ # to DataCite
12
+ def self.build(...)
13
+ new(...).call
14
+ end
15
+
16
+ def initialize(description:)
17
+ @description = description
18
+ end
19
+
20
+ # @return [Array<Hash>] list of titles for DataCite, conforming to the expectations of HTTP PUT request
21
+ # to DataCite
22
+ def call
23
+ [{ title: description.title.first.value }]
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :description
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datacite
4
+ module Mapping
5
+ module FromCocina
6
+ # Transform the Cocina::Models::Description form attributes to the DataCite types attributes
7
+ # see https://support.datacite.org/reference/dois-2#put_dois-id
8
+ class Types
9
+ # @param [Cocina::Models::Description] description
10
+ # @return [NilClass, Hash] the DataCite types attributes, conforming to the expectations of HTTP PUT request
11
+ # 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, Hash] the DataCite types attributes, conforming to the expectations of HTTP PUT request
21
+ # to DataCite
22
+ def call
23
+ return unless resource_type_general || resource_type
24
+
25
+ {
26
+ resourceTypeGeneral: resource_type_general,
27
+ resourceType: resource_type || ''
28
+ }
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :description
34
+
35
+ # @return String DataCite resourceTypeGeneral value
36
+ def resource_type_general
37
+ @resource_type_general ||= Array(description.form).find do |cocina_form|
38
+ datacite_resource_types_form?(cocina_form)
39
+ end&.value
40
+ end
41
+
42
+ # @return [String] DataCite resourceType value
43
+ def resource_type
44
+ @resource_type ||= begin
45
+ self_deposit_form = Array(description.form).find { |cocina_form| self_deposit_form?(cocina_form) }
46
+
47
+ subtypes = self_deposit_subtypes(self_deposit_form)
48
+ if subtypes.blank?
49
+ self_deposit_type(self_deposit_form)
50
+ else
51
+ subtypes.select { |subtype| subtype if subtype != resource_type_general }.join('; ')
52
+ end
53
+ end
54
+ end
55
+
56
+ # call with cocina form element for Stanford self deposit resource types
57
+ # @return String the value from the structuredValue when the type is 'type' for the cocina form element
58
+ def self_deposit_type(cocina_self_deposit_form)
59
+ cocina_self_deposit_form&.structuredValue&.each do |struct_val|
60
+ return struct_val.value if struct_val.type == 'type'
61
+ end
62
+ end
63
+
64
+ def self_deposit_subtypes(cocina_self_deposit_form)
65
+ cocina_self_deposit_form&.structuredValue&.filter_map do |struct_val|
66
+ struct_val.value if struct_val.type == 'subtype'
67
+ end
68
+ end
69
+
70
+ def self_deposit_form?(cocina_form)
71
+ cocina_form.type == 'resource type' &&
72
+ cocina_form.source&.value == 'Stanford self-deposit resource types'
73
+ end
74
+
75
+ def datacite_resource_types_form?(cocina_form)
76
+ cocina_form.source&.value == 'DataCite resource types'
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end