pennmarc 1.0.33 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f26c7e473bed7b22bd7aa63257b9647dd808f79a02aa1aab21e965f264d545a
4
- data.tar.gz: f793a9945291b9b66ee6c4155446c9270fc6e2f994c0754fa6dc0b4ba887cebc
3
+ metadata.gz: cb0bf4c30ad40bcee865ed6596fbfc6d6d16a5d3924d62947d270a3d5fa3aac5
4
+ data.tar.gz: 33ca607af919156ac45a73067605b6ac298fef9c17ef983060e6a871c54ca26f
5
5
  SHA512:
6
- metadata.gz: b3b5296a1f285562ed9c2fd88e5f06f3c3127fd988ad7066298fcbc3cbb3b143678ac1da9c2e63e1fcf5df3075ea763197928712db0221042164525a6191a635
7
- data.tar.gz: 3201a8f615d58871620081112eb663d7526911a68f1eae4a561e3216d77ada399cc10164be0532fdf7d2992a9cc964a829abfeb9272d64fabaa263ddb8f2f31c
6
+ metadata.gz: 5e5d316a7e1340aaf7438297e17198339d02ab797842b47ec2cece1daf75f96a387565b4fdc6ca273ef1a64630e1f486bc713ea75793ed3d783998234a83c517
7
+ data.tar.gz: 6f6d6824e1aa5fecbcd2dc13bbc94ea575531e96ea5f822b992719f3b28189cce4c26febc2f31848f24008a57f6a839f787383752cfd2c915695b1f1e66fe5ca
@@ -42,6 +42,24 @@ module PennMARC
42
42
  }.uniq
43
43
  end
44
44
 
45
+ # Parse call number values from inventory fields, including both hld and itm fields from publishing enrichment.
46
+ # Return only unique values.
47
+ # @param record [MARC::Record]
48
+ # @return [Array<String>] array of call numbers from inventory fields
49
+ def call_number_search(record)
50
+ call_nums = record.fields(TAGS).filter_map do |field|
51
+ subfield_values(field, call_number_sf(field))
52
+ end
53
+
54
+ # Ensure we get call numbers for records with no `itm` tags by also checking `hld` and de-duping
55
+ call_nums += record.fields([Enriched::Pub::PHYS_INVENTORY_TAG]).filter_map do |field|
56
+ first = subfield_values(field, Enriched::Pub::HOLDING_CLASSIFICATION_PART)&.first
57
+ last = subfield_values(field, Enriched::Pub::HOLDING_ITEM_PART)&.first
58
+ "#{first} #{last}".squish.presence
59
+ end
60
+ call_nums.flatten.uniq
61
+ end
62
+
45
63
  private
46
64
 
47
65
  # Retrieve subfield code that stores the call number on enriched marc field
@@ -3,7 +3,6 @@
3
3
  module PennMARC
4
4
  # Methods that return Library and Location values from Alma enhanced MARC fields
5
5
  class Location < Helper
6
- ONLINE_LIBRARY = 'Online library'
7
6
  WEB_LOCATION_CODE = 'web'
8
7
 
9
8
  class << self
@@ -45,7 +44,7 @@ module PennMARC
45
44
  # get enriched marc location tag and relevant subfields
46
45
  enriched_location_tag_and_subfields(record) => {tag:, location_code_sf:, call_num_sf:}
47
46
 
48
- locations = record.fields(tag).flat_map { |field|
47
+ record.fields(tag).flat_map { |field|
49
48
  field.filter_map { |subfield|
50
49
  # skip unless subfield matches enriched marc tag subfield code
51
50
  next unless subfield.code == location_code_sf
@@ -60,17 +59,12 @@ module PennMARC
60
59
  override || location_map[location_code.to_sym][display_value.to_sym]
61
60
  }.flatten.compact_blank
62
61
  }.uniq
63
- if record.tags.intersect?([Enriched::Pub::ELEC_INVENTORY_TAG, Enriched::Api::ELEC_INVENTORY_TAG])
64
- locations << ONLINE_LIBRARY
65
- end
66
-
67
- locations
68
62
  end
69
63
 
70
64
  private
71
65
 
72
66
  # Determine enriched marc location tag, location code subfield, and call number subfield,
73
- # giving priority to using 'itm', 'AVA', or 'AVE' fields.
67
+ # giving priority to using 'itm', 'AVA', or 'hld' fields.
74
68
  # @param record [MARC::Record]
75
69
  # @return [Hash<String, String>] containing location tag and subfield code
76
70
  # - `:tag` (String): The enriched marc location tag
@@ -90,7 +90,7 @@ module PennMARC
90
90
  # @return [Array<String>]
91
91
  def access_restriction_show(record)
92
92
  record.fields('506').filter_map { |field|
93
- join_subfields(field, &subfield_not_in?(%w[5 6]))
93
+ join_subfields(field, &subfield_not_in?(%w[2 5 6]))
94
94
  }.uniq
95
95
  end
96
96
 
@@ -93,22 +93,46 @@ module PennMARC
93
93
  # @param record [MARC::Record]
94
94
  # @return [String] single title for display
95
95
  def show(record)
96
+ field = record.fields('245')&.first
97
+ values = title_values(field)
98
+ [format_title(values[:title_or_form]), values[:punctuation], values[:other_info]].compact_blank.join(' ')
99
+ end
100
+
101
+ # Same as show, but with inclusive dates appended. For use on show page.
102
+ # @param record [MARC::Record]
103
+ # @return [String] detailed title for display
104
+ def detailed_show(record)
105
+ field = record.fields('245')&.first
106
+ values = title_values(field)
107
+ title = [format_title(values[:title_or_form]), values[:punctuation],
108
+ trim_trailing(:period, values[:other_info])].compact_blank.join(' ')
109
+ values[:inclusive_dates].present? ? [title, values[:inclusive_dates]].compact_blank.join(', ') : title
110
+ end
111
+
112
+ # Same structure as show, but linked alternate title.
113
+ # @param record [MARC::Record]
114
+ # @return [String, nil] alternate title for display
115
+ def alternate_show(record)
116
+ field = record.fields('880').filter_map { |alternate_field|
117
+ next unless subfield_value?(alternate_field, '6', /^245/)
118
+
119
+ alternate_field
120
+ }.first
121
+ return unless field
122
+
123
+ values = title_values(field)
124
+ [format_title(values[:title_or_form]), values[:punctuation], values[:other_info]].compact_blank.join(' ')
125
+ end
126
+
127
+ # Title statement of responsibility (field 245, subfield c) and linked alternate for display.
128
+ # See https://www.oclc.org/bibformats/en/2xx/245.html#subfieldc for examples
129
+ # @param [MARC::Record] record
130
+ # @return [Array<String>] statement of responsibility and linked alternate
131
+ def statement_of_responsibility_show(record)
96
132
  field = record.fields('245').first
97
- title_or_form = field.find_all(&subfield_in?(%w[a k]))
98
- .map { |sf| trim_trailing(:comma, trim_trailing(:slash, sf.value).rstrip) }
99
- .first || ''
100
- other_info = field.find_all(&subfield_in?(%w[b n p]))
101
- .map { |sf| trim_trailing(:slash, sf.value) }
102
- .join(' ')
103
- hpunct = field.find_all { |sf| sf.code == 'h' }.map { |sf| sf.value.last }.first
104
- punctuation = if [title_or_form.last, hpunct].include?('=')
105
- '='
106
- else
107
- [title_or_form.last, hpunct].include?(':') ? ':' : nil
108
- end
109
- [trim_trailing(:colon, trim_trailing(:equal, title_or_form)).strip,
110
- punctuation,
111
- other_info].compact_blank.join(' ')
133
+ statement = field&.find { |sf| sf.code == 'c' }&.value
134
+ alternate_statement = linked_alternate(record, '245', &subfield_in?(%w[c]))&.first
135
+ [statement, alternate_statement].compact_blank
112
136
  end
113
137
 
114
138
  # Canonical title with non-filing characters relocated to the end.
@@ -212,12 +236,13 @@ module PennMARC
212
236
  def former_show(record)
213
237
  record.fields
214
238
  .filter_map { |field|
215
- next unless field.tag == '247' || (field.tag == '880' && subfield_value?(field, '6', /^247/))
239
+ next unless field.tag == '247' || (field.tag == '880' && subfield_value?(field, '6', /^247/))
216
240
 
217
- former_title = join_subfields field, &subfield_not_in?(%w[6 8 e w]) # 6 and 8 are not meaningful for display
218
- former_title_append = join_subfields field, &subfield_in?(%w[e w])
219
- "#{former_title} #{former_title_append}".strip
220
- }.uniq
241
+ # 6 and 8 are not meaningful for display
242
+ former_title = join_subfields field, &subfield_not_in?(%w[6 8 e w])
243
+ former_title_append = join_subfields field, &subfield_in?(%w[e w])
244
+ "#{former_title} #{former_title_append}".strip
245
+ }.uniq
221
246
  end
222
247
 
223
248
  # Determine if the record is a "Host" bibliographic record for other bib records ("bound-withs")
@@ -232,6 +257,44 @@ module PennMARC
232
257
 
233
258
  private
234
259
 
260
+ # Extract title values from provided 245 subfields. Main title components are the following:
261
+ # - title_or_form: subfields a and k
262
+ # - inclusive_dates: subfield c
263
+ # - other_info: subfields b, n, and p
264
+ # https://www.oclc.org/bibformats/en/2xx/245.html
265
+ #
266
+ # @param field [MARC::Field]
267
+ # @return [Hash] title values
268
+ def title_values(field)
269
+ title_or_form = field.find_all(&subfield_in?(%w[a k]))
270
+ .map { |sf| trim_trailing(:comma, trim_trailing(:slash, sf.value).rstrip) }
271
+ .first || ''
272
+ inclusive_dates = field.find { |sf| sf.code == 'f' }&.value
273
+ other_info = field.find_all(&subfield_in?(%w[b n p]))
274
+ .map { |sf| trim_trailing(:slash, sf.value) }
275
+ .join(' ')
276
+ title_punctuation = title_or_form.last
277
+ medium_punctuation = field.find_all { |sf| sf.code == 'h' }
278
+ .map { |sf| sf.value.last }
279
+ .first
280
+ punctuation = if [title_punctuation, medium_punctuation].include?('=')
281
+ '='
282
+ else
283
+ [title_punctuation, medium_punctuation].include?(':') ? ':' : nil
284
+ end
285
+ { title_or_form: title_or_form,
286
+ inclusive_dates: inclusive_dates,
287
+ other_info: other_info,
288
+ punctuation: punctuation }
289
+ end
290
+
291
+ # Remove trailing equal from title, then remove trailing colon.
292
+ # @param title [String]
293
+ # @return [String]
294
+ def format_title(title)
295
+ trim_trailing(:colon, trim_trailing(:equal, title)).strip
296
+ end
297
+
235
298
  # Create prefix/filing hash for representing a title value with filing characters removed, with special
236
299
  # consideration for bracketed titles
237
300
  # @todo Is this still useful?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PennMARC
4
- VERSION = '1.0.33'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -2,22 +2,26 @@
2
2
 
3
3
  describe 'PennMARC::Classification' do
4
4
  let(:helper) { PennMARC::Classification }
5
- let(:record) do
6
- marc_record fields: [marc_field(tag: tag,
7
- subfields: { call_number_type_sf => '0', call_number_sf => 'TA683 .B3 1909b' }),
8
- marc_field(tag: tag,
9
- subfields: { call_number_type_sf => '0', call_number_sf => 'QL756 .S643' }),
10
- marc_field(tag: tag,
11
- subfields: { call_number_type_sf => '1', call_number_sf => '691.3 B2141' }),
12
- marc_field(tag: tag,
13
- subfields: { call_number_type_sf => '1', call_number_sf => '378.748 POS1952.29' })]
14
- end
5
+ let(:record) { marc_record fields: fields }
15
6
 
16
7
  describe '.facet' do
17
- context 'with enrichment via the Alma publishing process' do
18
- let(:tag) { PennMARC::Enriched::Pub::ITEM_TAG }
19
- let(:call_number_type_sf) { PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE }
20
- let(:call_number_sf) { PennMARC::Enriched::Pub::ITEM_CALL_NUMBER }
8
+ let(:fields) do
9
+ [marc_field(tag: config[:tag],
10
+ subfields: { config[:call_number_type_sf] => '0', config[:call_number_sf] => 'TA683 .B3 1909b' }),
11
+ marc_field(tag: config[:tag],
12
+ subfields: { config[:call_number_type_sf] => '0', config[:call_number_sf] => 'QL756 .S643' }),
13
+ marc_field(tag: config[:tag],
14
+ subfields: { config[:call_number_type_sf] => '1', config[:call_number_sf] => '691.3 B2141' }),
15
+ marc_field(tag: config[:tag],
16
+ subfields: { config[:call_number_type_sf] => '1', config[:call_number_sf] => '378.748 POS1952.29' })]
17
+ end
18
+
19
+ context 'with enrichment via the Alma publishing process and itm fields' do
20
+ let(:config) do
21
+ { tag: PennMARC::Enriched::Pub::ITEM_TAG,
22
+ call_number_type_sf: PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE,
23
+ call_number_sf: PennMARC::Enriched::Pub::ITEM_CALL_NUMBER }
24
+ end
21
25
 
22
26
  it 'returns expected values' do
23
27
  expect(helper.facet(record)).to contain_exactly('T - Technology', '600 - Technology',
@@ -26,9 +30,11 @@ describe 'PennMARC::Classification' do
26
30
  end
27
31
 
28
32
  context 'with enrichment with availability info via Alma Api' do
29
- let(:tag) { PennMARC::Enriched::Api::PHYS_INVENTORY_TAG }
30
- let(:call_number_type_sf) { PennMARC::Enriched::Api::PHYS_CALL_NUMBER_TYPE }
31
- let(:call_number_sf) { PennMARC::Enriched::Api::PHYS_CALL_NUMBER }
33
+ let(:config) do
34
+ { tag: PennMARC::Enriched::Api::PHYS_INVENTORY_TAG,
35
+ call_number_type_sf: PennMARC::Enriched::Api::PHYS_CALL_NUMBER_TYPE,
36
+ call_number_sf: PennMARC::Enriched::Api::PHYS_CALL_NUMBER }
37
+ end
32
38
 
33
39
  it 'returns expected values' do
34
40
  expect(helper.facet(record)).to contain_exactly('T - Technology', '600 - Technology',
@@ -36,4 +42,63 @@ describe 'PennMARC::Classification' do
36
42
  end
37
43
  end
38
44
  end
45
+
46
+ describe '.call_number_search' do
47
+ let(:fields) do
48
+ [marc_field(tag: config[:tag],
49
+ subfields: { config[:call_number_type_sf] => '0', config[:call_number_sf] => 'QL756 .S643' }),
50
+ marc_field(tag: config[:tag],
51
+ subfields: { config[:call_number_type_sf] => '1', config[:call_number_sf] => '691.3 B2141' })]
52
+ end
53
+
54
+ context 'with enrichment via the Alma publishing process' do
55
+ let(:config) do
56
+ { tag: PennMARC::Enriched::Pub::ITEM_TAG,
57
+ call_number_type_sf: PennMARC::Enriched::Pub::ITEM_CALL_NUMBER_TYPE,
58
+ call_number_sf: PennMARC::Enriched::Pub::ITEM_CALL_NUMBER }
59
+ end
60
+
61
+ it 'returns expected values' do
62
+ expect(helper.call_number_search(record)).to contain_exactly '691.3 B2141', 'QL756 .S643'
63
+ end
64
+ end
65
+
66
+ context 'with enrichment via the Alma publishing process and no itm fields' do
67
+ let(:fields) do
68
+ [marc_field(tag: PennMARC::Enriched::Pub::PHYS_INVENTORY_TAG,
69
+ subfields: { PennMARC::Enriched::Pub::HOLDING_CLASSIFICATION_PART => 'KF6450',
70
+ PennMARC::Enriched::Pub::HOLDING_ITEM_PART => '.C59 1989' })]
71
+ end
72
+
73
+ it 'returns expected values from the hld tag' do
74
+ expect(helper.call_number_search(record)).to contain_exactly('KF6450 .C59 1989')
75
+ end
76
+ end
77
+
78
+ context 'with enrichment via the Alma publishing process and values from both hld and itm fields' do
79
+ let(:fields) do
80
+ [marc_field(tag: PennMARC::Enriched::Pub::PHYS_INVENTORY_TAG,
81
+ subfields: { PennMARC::Enriched::Pub::HOLDING_CLASSIFICATION_PART => 'KF6450',
82
+ PennMARC::Enriched::Pub::HOLDING_ITEM_PART => '.C59 1989' }),
83
+ marc_field(tag: PennMARC::Enriched::Pub::ITEM_TAG,
84
+ subfields: { PennMARC::Enriched::Pub::ITEM_CALL_NUMBER => 'KF6450 .C59 1989' })]
85
+ end
86
+
87
+ it 'returns a single call number' do
88
+ expect(helper.call_number_search(record)).to contain_exactly('KF6450 .C59 1989')
89
+ end
90
+ end
91
+
92
+ context 'with enrichment with availability info via Alma Api' do
93
+ let(:config) do
94
+ { tag: PennMARC::Enriched::Api::PHYS_INVENTORY_TAG,
95
+ call_number_type_sf: PennMARC::Enriched::Api::PHYS_CALL_NUMBER_TYPE,
96
+ call_number_sf: PennMARC::Enriched::Api::PHYS_CALL_NUMBER }
97
+ end
98
+
99
+ it 'returns expected values' do
100
+ expect(helper.call_number_search(record)).to contain_exactly '691.3 B2141', 'QL756 .S643'
101
+ end
102
+ end
103
+ end
39
104
  end
@@ -66,19 +66,6 @@ describe 'PennMARC::Location' do
66
66
  end
67
67
  end
68
68
 
69
- context 'with electronic inventory tag' do
70
- let(:record) do
71
- marc_record(fields: [marc_field(tag: enriched_marc::Pub::ITEM_TAG,
72
- subfields: { enriched_marc::Pub::ITEM_CURRENT_LOCATION => 'stor' }),
73
- marc_field(tag: enriched_marc::Pub::ELEC_INVENTORY_TAG)])
74
- end
75
-
76
- it 'returns expected value' do
77
- expect(helper.location(record: record, location_map: mapping,
78
- display_value: :library)).to contain_exactly('LIBRA', helper::ONLINE_LIBRARY)
79
- end
80
- end
81
-
82
69
  context 'with AVA fields' do
83
70
  let(:record) do
84
71
  marc_record(fields: [marc_field(tag: enriched_marc::Api::PHYS_INVENTORY_TAG,
@@ -96,19 +83,6 @@ describe 'PennMARC::Location' do
96
83
  end
97
84
  end
98
85
 
99
- context 'with AVE fields' do
100
- let(:record) do
101
- marc_record(fields: [marc_field(tag: enriched_marc::Api::ELEC_INVENTORY_TAG,
102
- subfields: { enriched_marc::Api::ELEC_COLLECTION_NAME => 'Nature' })])
103
- end
104
-
105
- it 'returns expected values' do
106
- expect(helper.location(record: record, location_map: mapping, display_value: :library)).to(
107
- contain_exactly(helper::ONLINE_LIBRARY)
108
- )
109
- end
110
- end
111
-
112
86
  context 'with a specific location override' do
113
87
  let(:record) do
114
88
  marc_record(fields: [marc_field(tag: enriched_marc::Pub::ITEM_TAG,
@@ -136,7 +136,7 @@ describe 'PennMARC::Note' do
136
136
  it 'returns expected values from 506' do
137
137
  expect(values).to contain_exactly(
138
138
  'Open to users with valid PennKey Donor Appointment Only estate executors Some Policy No online access
139
- 20300101 Van Pelt URI star'.squish
139
+ 20300101 Van Pelt URI'.squish
140
140
  )
141
141
  end
142
142
  end
@@ -138,6 +138,66 @@ describe 'PennMARC::Title' do
138
138
  end
139
139
  end
140
140
 
141
+ describe '.detailed_show' do
142
+ let(:record) do
143
+ marc_record fields: [
144
+ marc_field(tag: '245', subfields: { k: 'Letters', f: '1972-1982', b: 'to Lewis Mumford.' })
145
+ ]
146
+ end
147
+
148
+ context 'with subfields ǂk, ǂf and ǂc' do
149
+ it 'returns detailed title values' do
150
+ expect(helper.detailed_show(record)).to eq 'Letters to Lewis Mumford, 1972-1982'
151
+ end
152
+ end
153
+ end
154
+
155
+ describe '.alternate_show' do
156
+ let(:record) do
157
+ marc_record fields: [
158
+ marc_field(tag: '245', subfields: { k: 'Letters', b: 'to Lewis Mumford. ' }),
159
+ marc_field(tag: '880', subfields: { '6': '245', k: 'Lettres', b: 'à Lewis Mumford.' })
160
+ ]
161
+ end
162
+
163
+ context 'with subfields ǂk and ǂb' do
164
+ it 'returns alternate title values' do
165
+ expect(helper.alternate_show(record)).to eq 'Lettres à Lewis Mumford.'
166
+ end
167
+ end
168
+
169
+ context 'when 880 field is not present' do
170
+ let(:record) do
171
+ marc_record fields: [
172
+ marc_field(tag: '245', subfields: { k: 'Letters', b: 'to Lewis Mumford. ' })
173
+ ]
174
+ end
175
+
176
+ it 'returns nil' do
177
+ expect(helper.alternate_show(record)).to be_nil
178
+ end
179
+ end
180
+ end
181
+
182
+ describe '.statement_of_responsibility_show' do
183
+ let(:record) do
184
+ marc_record fields: [marc_field(tag: '245', subfields: { c: 'statement of responsibility' }),
185
+ marc_field(tag: '880', subfields: { '6': '245', c: 'déclaration de responsabilité' })]
186
+ end
187
+
188
+ context 'with ǂc defined' do
189
+ it 'returns statement of responsibility' do
190
+ expect(helper.statement_of_responsibility_show(record)).to include 'statement of responsibility'
191
+ end
192
+ end
193
+
194
+ context 'with linked alternate of 245 ǂc defined' do
195
+ it 'returns alternate statement of responsibility' do
196
+ expect(helper.statement_of_responsibility_show(record)).to include 'déclaration de responsabilité'
197
+ end
198
+ end
199
+ end
200
+
141
201
  describe '.sort' do
142
202
  context 'with a record with a valid indicator2 value' do
143
203
  let(:record) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pennmarc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.33
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Kanning
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2024-08-12 00:00:00.000000000 Z
15
+ date: 2024-08-20 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activesupport