cocina-models 0.110.0 → 0.110.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b9fe9682c10921ac206fea73e6526883ba31fe7fb08f720f17ff7601e54e0d4
4
- data.tar.gz: c854536af0b5c26c355129317c6331ae0ff9c772edc666a03a1f68cde97c0c8e
3
+ metadata.gz: 9ba030653ba2790a20c4d071881472d3889bb4739c3c4637bb68d11d4c320207
4
+ data.tar.gz: 1f5d68d708a345aaf833908dca817af1c5c8499723d7f5194e2b0097817cb3a5
5
5
  SHA512:
6
- metadata.gz: cfe6d191ba1e17e102788fca888c4bd5e099eca554bd38e3642b155b453c26d4cba11f1dbc2b1e02547fe8d682a5382224eb0650d7c6be8dc3abb1636e968f25
7
- data.tar.gz: adb832be7b97215ff35647f94fa20bc86eab92d7045a52e7e81d3274b935fc8156b11c250b0fd9079ac3d0197b266e628693a138aec84ba962791df720a6f79d
6
+ metadata.gz: 796ab6c265801899f9b31ff131313d413133fbaf2829e2899dd0535a616345af723201d76aca3cecd4e9efaab5c2afa3151a5281a32cfb3adb46ece694378a13
7
+ data.tar.gz: 6ef1fc9300e553734fe55bc59b2cee35d80020e80b6539990ceb81d9c6f809e48449995fe6d0173ac9cf1d2a46d1aa264bfd86afa0c587999b7ae927590e84f9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cocina-models (0.110.0)
4
+ cocina-models (0.110.1)
5
5
  activesupport
6
6
  deprecation
7
7
  dry-struct (~> 1.0)
@@ -211,7 +211,7 @@ CHECKSUMS
211
211
  attr_extras (7.1.0) sha256=d96fc9a9dd5d85ba2d37762440a816f840093959ae26bb90da994c2d9f1fc827
212
212
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
213
213
  bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
214
- cocina-models (0.110.0)
214
+ cocina-models (0.110.1)
215
215
  concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
216
216
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
217
217
  date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'marc_relators'
4
-
5
3
  module Cocina
6
4
  module Models
7
5
  module Mapping
@@ -17,7 +17,7 @@ module Cocina
17
17
  form: Form,
18
18
  identifier: Identifier,
19
19
  adminMetadata: AdminMetadata,
20
- # relatedResource: RelatedResource,
20
+ relatedResource: RelatedResource,
21
21
  geographic: Geographic,
22
22
  access: Access
23
23
  }.freeze
@@ -19,19 +19,15 @@ module Cocina
19
19
 
20
20
  # @return [Array<Hash>] an array of event hashes
21
21
  def build
22
- linked_264_field = Util.linked_field(marc, marc['264']) if marc['264']
23
22
  [
24
23
  publication_from008,
25
24
  publication(marc['260']),
26
25
  manufacture(marc['260']),
27
-
28
26
  production(marc['264']),
29
- production(linked_264_field),
30
-
31
27
  edition_statement(marc['250']),
32
28
  frequency_statement(marc['310']),
33
29
  mode_of_issuance(marc['334'])
34
- ].compact
30
+ ].flatten.compact
35
31
  end
36
32
 
37
33
  MARC_264_INDICATOR2 = {
@@ -69,6 +65,13 @@ module Cocina
69
65
  def production(field)
70
66
  return unless field
71
67
 
68
+ fields = [build_production(field)]
69
+ alt_script_field = Util.linked_field(marc, field)
70
+ fields << build_production(alt_script_field) if alt_script_field
71
+ fields
72
+ end
73
+
74
+ def build_production(field)
72
75
  indicator = MARC_264_INDICATOR2.fetch(field.indicator2)
73
76
  return copyright(field) if indicator[:type] == 'copyright notice'
74
77
 
@@ -95,6 +98,13 @@ module Cocina
95
98
  def edition_statement(field)
96
99
  return unless field
97
100
 
101
+ fields = [build_edition_statement(field)]
102
+ alt_script_field = Util.linked_field(marc, field)
103
+ fields << build_edition_statement(alt_script_field) if alt_script_field
104
+ fields
105
+ end
106
+
107
+ def build_edition_statement(field)
98
108
  statement = field.subfields.select { %w[a b].include? it.code }.map(&:value).join(' ')
99
109
  { type: 'publication', note: [{ value: statement, type: 'edition' }]}
100
110
  end
@@ -116,13 +126,20 @@ module Cocina
116
126
  def publication(field)
117
127
  return unless field
118
128
 
119
- publication_place = field.subfields.find { it.code == 'a' }&.value
129
+ fields = [build_publication(field)]
130
+ alt_script_field = Util.linked_field(marc, field)
131
+ fields << build_publication(alt_script_field) if alt_script_field
132
+ fields
133
+ end
134
+
135
+ def build_publication(field)
136
+ publication_places = field.subfields.filter_map { Util.strip_punctuation(it.value) if it.code == 'a' }
120
137
  publisher = field.subfields.find { it.code == 'b' }&.value
121
138
  publication_date = field.subfields.find { it.code == 'c' }.value.delete_suffix('.')
122
139
 
123
140
  {
124
141
  type: 'publication',
125
- place: [{ value: Util.strip_punctuation(publication_place) }],
142
+ place: publication_places.map { { value: it } },
126
143
  contributor: [{ name: [{ value: Util.strip_punctuation(publisher) }], role: [{ value: 'publisher' }] }],
127
144
  date: [{ value: publication_date, type: 'publication' }]
128
145
  }
@@ -133,9 +150,9 @@ module Cocina
133
150
 
134
151
  manufacture_place = field.subfields.find { |subfield| subfield.code == 'e' }&.value
135
152
  manufacturer = field.subfields.find { |subfield| subfield.code == 'f' }&.value
136
- manufacture_date = field.subfields.find { |subfield| subfield.code == 'g' }&.value
137
153
  return nil unless manufacture_place || manufacturer
138
154
 
155
+ manufacture_date = field.subfields.find { |subfield| subfield.code == 'g' }&.value
139
156
  {
140
157
  type: 'manufacture',
141
158
  place: [{ value: Util.strip_punctuation(manufacture_place) }],
@@ -37,7 +37,7 @@ module Cocina
37
37
  '530' => { codes: %w[a b c d u 3], type: 'additional physical form' },
38
38
  '532' => %w[a 3],
39
39
  '533' => { codes: %w[a b c d e f m n y 3], type: 'reproduction' },
40
- '534' => %w[3 a b c e f k l m n o p t x z 3],
40
+ '534' => %w[a b c e f k l m n o p t x z 3],
41
41
  '535' => { codes: %w[3 a b c d g], type: 'original location' },
42
42
  '536' => { codes: %w[a b c d e f g h], type: 'funding' },
43
43
  '538' => { codes: ['a'], type: 'system details' },
@@ -50,18 +50,18 @@ module Cocina
50
50
  '547' => ['a'],
51
51
  '550' => ['a'],
52
52
  '552' => %w[a b c d e f g h i j k l m n o p u z],
53
- '555' => %w[3 a b c d u 3],
53
+ '555' => %w[a b c d u 3],
54
54
  '556' => %w[a z],
55
55
  '561' => { codes: %w[a u 3], type: 'ownership' },
56
- '562' => { codes: %w[3 a b c d e 3], type: 'version identification' },
56
+ '562' => { codes: %w[a b c d e 3], type: 'version identification' },
57
57
  '563' => { codes: %w[a u 3], type: 'binding' },
58
58
  '565' => %w[a b c d e 3],
59
59
  '567' => %w[a b],
60
60
  '580' => ['a'],
61
61
  '581' => { codes: %w[a z 3], type: 'publications' },
62
- '583' => { codes: %w[3 a b c d e f h i j k l n o u z 3], type: 'action' },
62
+ '583' => { codes: %w[a b c d e f h i j k l n o u z 3], type: 'action' },
63
63
  '584' => %w[3 a b 3],
64
- '585' => { codes: %w[3 a 3], type: 'exhibitions' },
64
+ '585' => { codes: %w[a 3], type: 'exhibitions' },
65
65
  '586' => %w[a 3],
66
66
  '588' => ['a'],
67
67
  '590' => ['a'],
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Mapping
6
+ module FromMarc
7
+ # Maps relatedResource information from MARC records to Cocina models.
8
+ class RelatedResource # rubocop:disable Metrics/ClassLength
9
+ # @see #initialize
10
+ # @see #build
11
+ def self.build(...)
12
+ new(...).build
13
+ end
14
+
15
+ # @param [MARC::Record] marc MARC record from FOLIO
16
+ def initialize(marc:)
17
+ @marc = marc
18
+ end
19
+
20
+ # @return [Array<Hash>] an array of relatedResource hashes
21
+ def build
22
+ [
23
+ in_series(marc['490']),
24
+ marc.fields.select { it.tag == '700' }.map { has_part(it) }.compact,
25
+ marc.fields.select { it.tag == '700' }.map { related_title(it) }.compact,
26
+ has_part_corporate(marc['710']),
27
+ related_to_meeting(marc['711'])
28
+ ].flatten.compact_blank
29
+ end
30
+
31
+ private
32
+
33
+ def in_series(field)
34
+ return unless field
35
+
36
+ { type: 'in series', title: [field['3'], field['a'], field['v'], field['l'], field['x']].join(' ') }
37
+ end
38
+
39
+ def has_part(field) # rubocop:disable Naming/PredicatePrefix
40
+ return unless field && field.indicator2 == '2' && field['t']
41
+
42
+ contributor = if field.indicator1 == '3'
43
+ family_contributor(field)
44
+ else
45
+ person_contributor(field)
46
+ end
47
+
48
+ body_has_part(field, contributor)
49
+ end
50
+
51
+ def has_part_corporate(field) # rubocop:disable Naming/PredicatePrefix
52
+ return unless field && field.indicator2 == '2' && field['t']
53
+
54
+ contributor = corporate_contributor(field)
55
+ body_has_part(field, contributor)
56
+ end
57
+
58
+ def body_has_part(field, contributor)
59
+ {
60
+ type: 'has part',
61
+ displayLabel: field['i'],
62
+ title: [
63
+ {
64
+ value: [field['t'], field['f'], field['g'], field['k'], field['l'], field['m'], field['n'], field['p'], field['o'], field['r'], field['s']].compact.join(' ')
65
+ }
66
+ ],
67
+ contributor: [contributor]
68
+ }
69
+ end
70
+
71
+ def related_title(field)
72
+ return unless field && field.indicator2 != '2' && field['t']
73
+
74
+ linked = Util.linked_field(marc, field)
75
+ vals = [build_related_title(field)]
76
+ vals << build_related_title(linked) if linked
77
+ vals
78
+ end
79
+
80
+ def build_related_title(field)
81
+ contributor = if field.indicator1 == '3'
82
+ family_contributor(field)
83
+ else
84
+ person_contributor(field)
85
+ end
86
+ body_related_to(field, contributor)
87
+ end
88
+
89
+ def related_to_meeting(field)
90
+ return unless field && field.indicator2 != '2' && field['t']
91
+
92
+ contributor = meeting_contributor(field)
93
+ body_related_to(field, contributor)
94
+ end
95
+
96
+ def body_related_to(field, contributor)
97
+ {
98
+ type: 'related to',
99
+ displayLabel: field['i'],
100
+ title: [
101
+ {
102
+ value: [field['t'], field['f'], field['g'], field['k'], field['l'], field['m'], field['n'], field['p'], field['o'], field['r'], field['s']].compact.join(' ')
103
+ }
104
+ ],
105
+ contributor: [
106
+ contributor
107
+ ]
108
+ }.compact
109
+ end
110
+
111
+ def person_contributor(field)
112
+ name = field.subfields.filter_map { it.value if %w[a b c d j q].include?(it.code) }.join(' ').delete_suffix(',')
113
+ {
114
+ type: 'person',
115
+
116
+ name: [{ value: name }],
117
+ affiliation: [{ value: field['u'] }.compact].compact_blank,
118
+ role: [{ value: field['e']&.delete_suffix('.') }.compact].compact_blank,
119
+ identifier: [{ uri: field['1'] }.compact].compact_blank
120
+ }.compact_blank
121
+ end
122
+
123
+ def family_contributor(field)
124
+ {
125
+ type: 'family',
126
+ name: [
127
+ {
128
+ value: [field['a'], field['b'], field['c'], field['d'], field['j'], field['q']].compact.join(' ').delete_suffix(',')
129
+ }
130
+ ],
131
+ affiliation: [{ value: field['u'] }.compact].compact_blank,
132
+ role: [{ value: field['e']&.delete_suffix('.') }.compact].compact_blank,
133
+ identifier: [{ uri: field['1'] }.compact].compact_blank
134
+ }.compact_blank
135
+ end
136
+
137
+ def corporate_contributor(field)
138
+ {
139
+ type: 'organization',
140
+ name: [
141
+ {
142
+ value: [field['a'], field['b'], field['c'], field['d']].compact.join(' ').delete_suffix(',')
143
+ }
144
+ ],
145
+ role: [{ value: field['e']&.delete_suffix('.') }.compact].compact_blank,
146
+ identifier: [{ uri: field['1'] }.compact].compact_blank
147
+ }.compact_blank
148
+ end
149
+
150
+ def meeting_contributor(field)
151
+ {
152
+ type: 'organization',
153
+ name: [
154
+ {
155
+ value: [field['a'], field['b'], field['c'], field['d'], field['e'], field['q']].compact.join(' ').delete_suffix(',')
156
+ }
157
+ ],
158
+ affiliation: [{ value: field['u'] }.compact].compact_blank,
159
+ role: [{ value: field['j']&.delete_suffix('.') }.compact].compact_blank,
160
+ identifier: [{ uri: field['1'] }.compact].compact_blank
161
+ }.compact_blank
162
+ end
163
+
164
+ attr_reader :marc
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -13,12 +13,16 @@ module Cocina
13
13
  value.gsub(regex, '')
14
14
  end
15
15
 
16
+ # Parse a MARC 880$6
17
+ # See https://www.loc.gov/marc/bibliographic/ecbdcntf.html
16
18
  def self.linked_field(marc, field)
17
19
  pointer = field.subfields.find { |subfield| subfield.code == '6' }
18
20
  return unless pointer
19
21
 
20
- field_id, index = pointer.value.split('-')
21
- marc.fields(field_id)[index.to_i - 1]
22
+ # Subfield $6 is formatted thusly:
23
+ # $6 [linking tag]-[occurrence number]/[script identification code]/[field orientation code]
24
+ linking_tag, occurrence_number = pointer.value.split(%r{-|/})
25
+ marc.fields(linking_tag)[occurrence_number.to_i - 1]
22
26
  end
23
27
  end
24
28
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.110.0'
5
+ VERSION = '0.110.1'
6
6
  end
7
7
  end
data/lib/cocina/models.rb CHANGED
@@ -23,6 +23,7 @@ class CocinaModelsInflector < Zeitwerk::Inflector
23
23
  'dro_structural' => 'DROStructural',
24
24
  'dro_with_metadata' => 'DROWithMetadata',
25
25
  'libraries_doi' => 'LibrariesDOI',
26
+ 'marc_relators' => 'MARC_RELATORS',
26
27
  'preregistered_repository_doi' => 'PreregisteredRepositoryDOI',
27
28
  'repository_doi' => 'RepositoryDOI',
28
29
  'request_dro' => 'RequestDRO',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocina-models
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.110.0
4
+ version: 0.110.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
@@ -431,6 +431,7 @@ files:
431
431
  - lib/cocina/models/mapping/from_marc/language.rb
432
432
  - lib/cocina/models/mapping/from_marc/marc_relators.rb
433
433
  - lib/cocina/models/mapping/from_marc/note.rb
434
+ - lib/cocina/models/mapping/from_marc/related_resource.rb
434
435
  - lib/cocina/models/mapping/from_marc/title.rb
435
436
  - lib/cocina/models/mapping/from_marc/util.rb
436
437
  - lib/cocina/models/mapping/from_mods/access.rb