pennmarc 1.0.18 → 1.0.20

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,7 +36,7 @@ module PennMARC
36
36
  # @return [Array<String>] array of title values for search
37
37
  def search(record)
38
38
  record.fields(%w[245 880]).filter_map { |field|
39
- next if field.tag == '880' && subfield_value_not_in?(field, '6', %w[245])
39
+ next if field.tag == '880' && no_subfield_value_matches?(field, '6', /^245/)
40
40
 
41
41
  join_subfields(field, &subfield_not_in?(%w[c 6 8 h]))
42
42
  }.uniq
@@ -63,7 +63,7 @@ module PennMARC
63
63
  return [] if not_a_serial?(record)
64
64
 
65
65
  record.fields(%w[245 880]).filter_map { |field|
66
- next if field.tag == '880' && subfield_value_not_in?(field, '6', %w[245])
66
+ next if field.tag == '880' && no_subfield_value_matches?(field, '6', /^245/)
67
67
 
68
68
  join_subfields(field, &subfield_not_in?(%w[c 6 8 h]))
69
69
  }.uniq
@@ -166,7 +166,7 @@ module PennMARC
166
166
  end
167
167
  titles = standardized_titles + record.fields('880').filter_map do |field|
168
168
  next unless subfield_undefined?(field, 'i') ||
169
- subfield_value_in?(field, '6', %w[130 240 730])
169
+ subfield_value?(field, '6', /^(130|240|730)/)
170
170
 
171
171
  join_subfields field, &subfield_not_in?(%w[5 6 8 e w])
172
172
  end
@@ -191,7 +191,7 @@ module PennMARC
191
191
  join_subfields(field, &subfield_not_in?(%w[5 6 8]))
192
192
  end
193
193
  titles = other_titles + record.fields('880').filter_map do |field|
194
- next unless subfield_value_in? field, '6', %w[246 740]
194
+ next unless subfield_value? field, '6', /^(246|740)/
195
195
 
196
196
  join_subfields(field, &subfield_not_in?(%w[5 6 8]))
197
197
  end
@@ -263,7 +263,7 @@ module PennMARC
263
263
  record.fields(tags).filter_map do |field|
264
264
  next if field.tag == '505' && indicators_are_not_value?(field, '0')
265
265
 
266
- next if field.tag == '880' && subfield_value_not_in?(field, '6', tags)
266
+ next if field.tag == '880' && no_subfield_value_matches?(field, '6', /^(#{tags.join('|')})/)
267
267
 
268
268
  join_subfields(field, &join_selector)
269
269
  end
@@ -1579,6 +1579,10 @@ vpfeatdvd:
1579
1579
  specific_location: Van Pelt - Featured DVD Display - First Floor
1580
1580
  library: Van Pelt-Dietrich Library Center
1581
1581
  display: Van Pelt - Featured DVD Display - First Floor
1582
+ vpfolio:
1583
+ specific_location: Van Pelt - Folios
1584
+ library: Van Pelt-Dietrich Library Center
1585
+ display: Van Pelt Library
1582
1586
  vpjuv:
1583
1587
  specific_location: Van Pelt - Notable Juvenile Books
1584
1588
  library: Van Pelt-Dietrich Library Center
data/lib/pennmarc/util.rb CHANGED
@@ -46,6 +46,16 @@ module PennMARC
46
46
  field&.any? { |sf| sf.code == subfield.to_s && sf.value =~ regex }
47
47
  end
48
48
 
49
+ # returns true if field has no value that matches
50
+ # passed-in regex and passed in subfield
51
+ # @param [MARC::DataField] field
52
+ # @param [String|Integer|Symbol] subfield
53
+ # @param [Regexp] regex
54
+ # @return [TrueClass, FalseClass, nil]
55
+ def no_subfield_value_matches?(field, subfield, regex)
56
+ field&.none? { |sf| sf.code == subfield.to_s && sf.value =~ regex }
57
+ end
58
+
49
59
  # returns true if a given field has a given subfield value in a given array
50
60
  # TODO: example usage
51
61
  # @param [MARC:DataField] field
@@ -161,7 +171,7 @@ module PennMARC
161
171
  # @return [Array] array of linked alternates
162
172
  def linked_alternate(record, subfield6_value, &selector)
163
173
  record.fields('880').filter_map do |field|
164
- next unless subfield_value?(field, '6', /^#{Array.wrap(subfield6_value).join('|')}/)
174
+ next unless subfield_value?(field, '6', /^(#{Array.wrap(subfield6_value).join('|')})/)
165
175
 
166
176
  field.select(&selector).map(&:value).join(' ')
167
177
  end
@@ -251,7 +261,7 @@ module PennMARC
251
261
  record.fields(%w[650 880]).filter_map { |field|
252
262
  next unless field.indicator2 == '4'
253
263
 
254
- next if field.tag == '880' && subfield_values(field, '6').exclude?('650')
264
+ next if field.tag == '880' && no_subfield_value_matches?(field, '6', /^650/)
255
265
 
256
266
  next unless field.any? { |sf| sf.code == 'a' && sf.value =~ /^(#{prefix}|%#{prefix})/ }
257
267
 
@@ -268,5 +278,56 @@ module PennMARC
268
278
  def valid_subject_genre_source_code?(field)
269
279
  subfield_value_in?(field, '2', PennMARC::HeadingControl::ALLOWED_SOURCE_CODES)
270
280
  end
281
+
282
+ # Does a field or its linked alternate match any of the specified tags?
283
+ # @param [MARC::Field] field
284
+ # @param [Array<String>] tags
285
+ # @return [TrueClass, FalseClass]
286
+ def field_or_its_linked_alternate?(field, tags)
287
+ return true if field.tag.in? tags
288
+ return true if field.tag == '880' && subfield_value?(field, '6', /^(#{tags.join('|')})/)
289
+
290
+ false
291
+ end
292
+
293
+ # Match any open dates ending a given string to determine join separator for relator term in 1xx/7xx fields.
294
+ # @param [String] str
295
+ # @return [String (frozen)]
296
+ def relator_join_separator(str)
297
+ /\b\d+-\z/.match?(str) ? ' ' : ', '
298
+ end
299
+
300
+ # For a given field, determine in which subfield to find relator term
301
+ # The following fields and their linked alternates use $j for relator terms:
302
+ # {https://www.loc.gov/marc/bibliographic/bd111.html 111}, {https://www.loc.gov/marc/bibliographic/bd411.html 411},
303
+ # {https://www.loc.gov/marc/bibliographic/bd611.html 611}, {https://www.loc.gov/marc/bibliographic/bd711.html 711},
304
+ # {https://www.loc.gov/marc/bibliographic/bd811.html 811}
305
+ # @param [MARC:Field] field
306
+ # @return [String (frozen)]
307
+ def relator_term_subfield(field)
308
+ field_or_its_linked_alternate?(field, %w[111 411 611 711 811]) ? 'j' : 'e'
309
+ end
310
+
311
+ # Appends a relator value to the given string. It prioritizes relator codes found in subfield $4
312
+ # and falls back to the specified relator term subfield (defaulting to 'e') if no valid codes are found in $4.
313
+ # Use with 1xx/7xx fields.
314
+ # @param [MARC::Field] field where relator values are stored
315
+ # @param [String] joined_subfields the string to which the relator is appended
316
+ # @param [String] relator_term_sf MARC subfield that stores relator term
317
+ # @param [Hash] relator_map
318
+ # @return [String]
319
+ def append_relator(field:, joined_subfields:, relator_term_sf:, relator_map: Mappers.relator)
320
+ joined_subfields = trim_trailing(:comma, joined_subfields)
321
+
322
+ join_separator = relator_join_separator(joined_subfields)
323
+
324
+ relator = subfield_values(field, '4').filter_map { |code| translate_relator(code, relator_map) }
325
+
326
+ relator = subfield_values(field, relator_term_sf) if relator.blank?
327
+
328
+ relator = append_trailing(:period, relator.join(', ')) if relator.present?
329
+
330
+ [joined_subfields, relator].compact_blank.join(join_separator).squish
331
+ end
271
332
  end
272
333
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PennMARC
4
- VERSION = '1.0.18'
4
+ VERSION = '1.0.20'
5
5
  end
@@ -18,8 +18,8 @@ describe 'PennMARC::Creator' do
18
18
 
19
19
  it 'contains the expected search field values for a single author work' do
20
20
  expect(helper.search(record, relator_map: mapping)).to contain_exactly(
21
- 'Name Surname http://cool.uri/12345 author 1900-2000.',
22
- 'Surname, Name http://cool.uri/12345 author 1900-2000.',
21
+ 'Name Surname http://cool.uri/12345 1900-2000, author.',
22
+ 'Surname, Name http://cool.uri/12345 1900-2000, author.',
23
23
  'Alternative Surname'
24
24
  )
25
25
  end
@@ -63,62 +63,103 @@ describe 'PennMARC::Creator' do
63
63
  end
64
64
  end
65
65
 
66
- describe '.values' do
66
+ describe '.show' do
67
67
  let(:record) { marc_record fields: fields }
68
68
 
69
69
  context 'with a single author record' do
70
70
  let(:fields) do
71
- [marc_field(tag: '100', subfields: { a: 'Author', c: 'Fancy', d: 'active 24th century AD', '4': 'aut' }),
72
- marc_field(tag: '880', subfields: { '6': '100', a: 'Alt Author', c: 'Alt Fanciness' })]
71
+ [marc_field(tag: '100', subfields: { a: 'Surname, Name', '0': 'http://cool.uri/12345', d: '1900-2000',
72
+ e: 'author.', '4': 'http://cool.uri/vocabulary/relators/aut' }),
73
+ marc_field(tag: '880', subfields: { a: 'Surname, Alternative', '6': '100' })]
73
74
  end
74
75
 
75
- it 'returns values for the author, including mapped relator code from ǂ4' do
76
- values = helper.values(record, relator_map: mapping)
77
- expect(values).to contain_exactly 'Author Fancy active 24th century AD, Author.'
78
- expect(values.join.downcase).not_to include 'alt'
76
+ it 'returns single author values with no URIs anywhere' do
77
+ values = helper.show(record)
78
+ expect(values).to contain_exactly 'Surname, Name 1900-2000, author.', 'Surname, Alternative'
79
+ expect(values.join.downcase).not_to include 'http'
79
80
  end
80
81
  end
81
82
 
82
83
  context 'with a corporate author record' do
83
84
  let(:fields) do
84
- [marc_field(tag: '110', subfields: { a: 'Annual Report', b: 'Leader', e: 'author', '4': 'aut' })]
85
+ [marc_field(tag: '110', subfields: { a: 'Group of People', b: 'Annual Meeting', '4': 'aut' }),
86
+ marc_field(tag: '880', subfields: { '6': '110', a: 'Alt. Group Name', b: 'Alt. Annual Meeting' })]
85
87
  end
86
88
 
87
- it 'returns values for the corporate author, including mapped relator code from ǂ4' do
88
- expect(helper.values(record, relator_map: mapping)).to contain_exactly(
89
- 'Annual Report Leader author, Author.'
90
- )
89
+ it 'returns corporate author values with no URIs anywhere' do
90
+ values = helper.show(record, relator_map: mapping)
91
+ expect(values).to contain_exactly 'Alt. Group Name Alt. Annual Meeting',
92
+ 'Group of People Annual Meeting, Author.'
93
+ expect(values.join.downcase).not_to include 'http'
91
94
  end
92
95
  end
93
96
  end
94
97
 
95
- describe '.show' do
98
+ describe '.show_aux' do
96
99
  let(:record) { marc_record fields: fields }
97
100
 
98
101
  context 'with a single author record' do
99
102
  let(:fields) do
100
- [marc_field(tag: '100', subfields: { a: 'Surname, Name', '0': 'http://cool.uri/12345', d: '1900-2000',
101
- e: 'author', '4': 'http://cool.uri/vocabulary/relators/aut' }),
102
- marc_field(tag: '880', subfields: { a: 'Surname, Alternative', '6': '100' })]
103
+ [marc_field(tag: '100', subfields: { a: 'Person', c: 'Loquacious', d: 'active 24th century AD', '4': 'aut' }),
104
+ marc_field(tag: '880', subfields: { '6': '100', a: 'Alt Author', c: 'Alt Fanciness' })]
103
105
  end
104
106
 
105
- it 'returns single author values with no URIs anywhere' do
106
- values = helper.show(record)
107
- expect(values).to contain_exactly 'Surname, Name 1900-2000', 'Surname, Alternative'
108
- expect(values.join.downcase).not_to include 'http'
107
+ it 'returns mapped relator code from ǂ4 at the end with a terminal period' do
108
+ expect(helper.show_aux(record, relator_map: mapping).first).to end_with ', Author.'
109
+ end
110
+
111
+ it 'does not include linked 880 field' do
112
+ expect(helper.show_aux(record, relator_map: mapping).join(' ')).not_to include 'Alt'
109
113
  end
110
114
  end
111
115
 
112
116
  context 'with a corporate author record' do
113
117
  let(:fields) do
114
- [marc_field(tag: '110', subfields: { a: 'Group of People', b: 'Annual Meeting', '4': 'aut' }),
115
- marc_field(tag: '880', subfields: { '6': '110', a: 'Alt. Group Name', b: 'Alt. Annual Meeting' })]
118
+ [marc_field(tag: '110', subfields: { a: 'Annual Report', b: 'Leader', e: 'author', '4': 'aut' })]
116
119
  end
117
120
 
118
- it 'returns corporate author values with no URIs anywhere' do
119
- values = helper.show(record)
120
- expect(values).to contain_exactly 'Alt. Group Name Alt. Annual Meeting', 'Group of People Annual Meeting'
121
- expect(values.join.downcase).not_to include 'http'
121
+ it 'returns values for the corporate author, including mapped relator code from ǂ4' do
122
+ expect(helper.show_aux(record, relator_map: mapping)).to contain_exactly(
123
+ 'Annual Report Leader, Author.'
124
+ )
125
+ end
126
+ end
127
+
128
+ context 'with relator term and translatable relator code' do
129
+ let(:fields) do
130
+ [marc_field(tag: '100', subfields: { a: 'Person', c: 'Loquacious', d: 'active 24th century AD', e: 'Ignore',
131
+ '4': 'aut' })]
132
+ end
133
+
134
+ it 'only appends translatable relator' do
135
+ expect(helper.show_aux(record, relator_map: mapping)).to contain_exactly(
136
+ 'Person Loquacious active 24th century AD, Author.'
137
+ )
138
+ end
139
+ end
140
+
141
+ context 'with multiple translatable relator codes' do
142
+ let(:mapping) { { aut: 'Author', stl: 'Storyteller' } }
143
+ let(:fields) do
144
+ [marc_field(tag: '100', subfields: { a: 'Person', c: 'Loquacious', d: 'active 24th century AD',
145
+ '4': %w[aut stl] })]
146
+ end
147
+
148
+ it 'appends all translatable relators' do
149
+ expect(helper.show_aux(record, relator_map: mapping)).to contain_exactly(
150
+ 'Person Loquacious active 24th century AD, Author, Storyteller.'
151
+ )
152
+ end
153
+ end
154
+
155
+ context 'without translatable relator code' do
156
+ let(:fields) do
157
+ [marc_field(tag: '100', subfields: { a: 'Person', c: 'Loquacious', d: 'active 24th century AD',
158
+ e: 'author' })]
159
+ end
160
+
161
+ it 'appends all translatable relators' do
162
+ expect(helper.show_aux(record)).to contain_exactly('Person Loquacious active 24th century AD, author.')
122
163
  end
123
164
  end
124
165
  end
@@ -197,18 +238,22 @@ describe 'PennMARC::Creator' do
197
238
  describe '.conference_detail_show' do
198
239
  let(:record) do
199
240
  marc_record fields: [
200
- marc_field(tag: '111', subfields: { a: 'MARC History Symposium', c: 'Moscow' }),
241
+ marc_field(tag: '111', subfields: { a: 'MARC History Symposium', e: 'Advisory Committee', c: 'Moscow',
242
+ j: 'author', '4': 'aut' }),
201
243
  marc_field(tag: '711', subfields: { a: 'Russian Library Conference', j: 'author' }),
202
244
  marc_field(tag: '711', indicator2: '1', subfields: { a: 'Ignored Entry', j: 'author' }),
203
245
  marc_field(tag: '880', subfields: { a: 'Proceedings', '6': '111' }),
246
+ marc_field(tag: '880', subfields: { a: 'Opening Remarks', j: 'author', '4': 'aut', '6': '711' }),
204
247
  marc_field(tag: '880', subfields: { a: 'Not Included', i: 'something', '6': '111' })
205
248
  ]
206
249
  end
207
250
 
208
251
  it 'returns detailed conference name information for display, including linked 880 fields without ǂi, and ignoring
209
252
  any 111 or 711 with a defined indicator 2 value' do
210
- expect(helper.conference_detail_show(record)).to eq ['MARC History Symposium Moscow',
211
- 'Russian Library Conference author', 'Proceedings']
253
+ expect(helper.conference_detail_show(record, relator_map: mapping)).to contain_exactly(
254
+ 'MARC History Symposium Moscow Advisory Committee, Author.',
255
+ 'Russian Library Conference, author.', 'Proceedings', 'Opening Remarks, Author.'
256
+ )
212
257
  end
213
258
  end
214
259
 
@@ -225,33 +270,72 @@ describe 'PennMARC::Creator' do
225
270
  end
226
271
 
227
272
  describe '.contributor_show' do
228
- let(:record) do
229
- marc_record fields: [
230
- marc_field(tag: '700', subfields: { a: 'Name', b: 'I', c: 'laureate', d: '1968', e: 'author',
231
- j: 'pseud', q: 'Fuller Name', u: 'affiliation', '3': 'materials',
232
- '4': 'aut' }),
233
- marc_field(tag: '700', subfields: { a: 'Ignore' }, indicator2: '1'),
234
- marc_field(tag: '700', subfields: { i: 'Ignore' }),
235
- marc_field(tag: '710', subfields: { a: 'Corporation', b: 'A division', c: 'Office', d: '1968', e: 'author',
236
- u: 'affiliation', '3': 'materials',
237
- '4': 'aut' }),
238
- marc_field(tag: '880', subfields: { '6': '700', a: 'Alt Name', b: 'Alt num', c: 'Alt title',
239
- d: 'Alt date', e: 'Alt relator', j: 'Alt qualifier', q: 'Alt Fuller Name',
240
- u: 'Alt affiliation', '3': 'Alt materials' }),
241
- marc_field(tag: '880', subfields: { '6': '710', a: 'Alt Corp Name', b: 'Alt unit', c: 'Alt location',
242
- d: 'Alt date', e: 'Alt relator', u: 'Alt Affiliation',
243
- '3': 'Alt materials' }),
244
- marc_field(tag: '880', subfields: { i: 'Ignore', '6': '700' })
245
- ]
273
+ let(:record) { marc_record fields: fields }
274
+
275
+ context 'when idicator2 is "1"' do
276
+ let(:fields) do
277
+ [marc_field(tag: '700', subfields: { a: 'Ignore' }, indicator2: '1')]
278
+ end
279
+
280
+ it 'ignores the field' do
281
+ values = helper.contributor_show(record, relator_map: mapping)
282
+ expect(values).to be_empty
283
+ end
246
284
  end
247
285
 
248
- it 'returns expected contributor values' do
249
- expect(helper.contributor_show(record, relator_map: mapping)).to contain_exactly(
250
- 'Name I laureate 1968 pseud Fuller Name author affiliation materials, Author',
251
- 'Corporation A division Office 1968 author affiliation materials, Author',
252
- 'Alt Name Alt num Alt title Alt date Alt qualifier Alt Fuller Name Alt relator Alt affiliation Alt materials',
253
- 'Alt Corp Name Alt unit Alt location Alt date Alt relator Alt Affiliation Alt materials'
254
- )
286
+ context 'with subfield "i"' do
287
+ let(:fields) do
288
+ [
289
+ marc_field(tag: '700', subfields: { i: 'Ignore' }),
290
+ marc_field(tag: '880', subfields: { i: 'Ignore', '6': '700' })
291
+ ]
292
+ end
293
+
294
+ it 'ignores the field' do
295
+ values = helper.contributor_show(record, relator_map: mapping)
296
+ expect(values).to be_empty
297
+ end
298
+ end
299
+
300
+ context 'with a single contributor and linked alternate' do
301
+ let(:fields) do
302
+ [
303
+ marc_field(tag: '700', subfields: { a: 'Name', b: 'I', c: 'laureate', d: '1968', e: 'author',
304
+ j: 'pseud', q: 'Fuller Name', u: 'affiliation', '3': 'materials',
305
+ '4': 'aut' }),
306
+ marc_field(tag: '880', subfields: { '6': '700', a: 'Alt Name', b: 'Alt num', c: 'Alt title',
307
+ d: 'Alt date', e: 'Alt relator', j: 'Alt qualifier',
308
+ q: 'Alt Fuller Name', u: 'Alt affiliation', '3': 'Alt material' })
309
+ ]
310
+ end
311
+
312
+ it 'returns expected contributor values' do
313
+ values = helper.contributor_show(record, relator_map: mapping)
314
+ expect(values).to contain_exactly(
315
+ 'Name I laureate 1968 pseud Fuller Name affiliation materials, Author.',
316
+ 'Alt Name Alt num Alt title Alt date Alt qualifier Alt Fuller Name Alt affiliation Alt material, Alt relator.'
317
+ )
318
+ end
319
+ end
320
+
321
+ context 'with a corporate contributor and linked alternate' do
322
+ let(:fields) do
323
+ [
324
+ marc_field(tag: '710', subfields: { a: 'Corporation', b: 'A division', c: 'Office', d: '1968', e: 'author',
325
+ u: 'affiliation', '3': 'materials', '4': 'aut' }),
326
+ marc_field(tag: '880', subfields: { '6': '710', a: 'Alt Corp Name', b: 'Alt unit', c: 'Alt location',
327
+ d: 'Alt date', e: ['Alt relator', 'another'], u: 'Alt Affiliation',
328
+ '3': 'Alt materials' })
329
+ ]
330
+ end
331
+
332
+ it 'returns expected contributor values' do
333
+ values = helper.contributor_show(record)
334
+ expect(values).to contain_exactly(
335
+ 'Corporation A division Office 1968 affiliation materials, Author.',
336
+ 'Alt Corp Name Alt unit Alt location Alt date Alt Affiliation Alt materials, Alt relator, another.'
337
+ )
338
+ end
255
339
  end
256
340
  end
257
341
  end