pennmarc 1.0.18 → 1.0.20

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.
@@ -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