pennmarc 1.0.15 → 1.0.17

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: 06f7c00af4fabc0cb127ce3bfc16e02e2b13611db0aa8184fca48392a5776295
4
- data.tar.gz: c34a742ed5b5bc1f1eb1d76a564738e0e48df3607e59ec69851a7428876225cb
3
+ metadata.gz: 90b9c248dbc3c08377212a70748dcf13728c9feaa037a4a64cc398c02a36c3df
4
+ data.tar.gz: f3c14ffea869113e6f94fc5d94ff5a083f2749cb17ed33854243131b134185a1
5
5
  SHA512:
6
- metadata.gz: f64552e53b3547222f785bf3f24f593448a3e4587baa309d194ca48fee961d9846b40c590b4e2a8709592557c95a0c52b922c5c4fe9be89058cc85aefe2c2e76
7
- data.tar.gz: 999d8a04a067bf9a1a88d4d0adba0947fda547446b89d5f989541ea18c087d0ab804dde297f67704cbfcd6ca5be4047db4296ffb87f4e68b9c81e991b954c316
6
+ metadata.gz: 25f98a210e26abc52edefb5614bf5ebbdb1acc04baaaaa3c0a770f284f8161f301a2fe768a336ee9291ba640f9251e71d68d39cd5d222707254b8ca5763261b6
7
+ data.tar.gz: c0ee733d64114e1b5c317cd49ebedc330c2a179282be2cb43f3f9457d40253dd197da5fe18e6c09a04f945be1d43fa837a9920648ae8af439a3ad0fdcf23ee38
@@ -6,6 +6,6 @@ module PennMARC
6
6
  # These codes are expected to be found in sf2 when the indicator2 value is 7, indicating "source specified". There
7
7
  # are some sources whose headings we don't want to display.
8
8
  ALLOWED_SOURCE_CODES = %w[aat cct fast ftamc gmgpc gsafd homoit jlabsh lcgft lcsh lcstt lctgm
9
- local/osu mesh ndlsh nlksh rbbin rbgenr rbmscv rbpap rbpri rbprov rbpub rbtyp].freeze
9
+ local/osu mesh ndlsh nli nlksh rbbin rbgenr rbmscv rbpap rbpri rbprov rbpub rbtyp].freeze
10
10
  end
11
11
  end
@@ -16,15 +16,16 @@ module PennMARC
16
16
  # @param [MARC::Record] record
17
17
  # @return [Array]
18
18
  def facet(record)
19
- acc = record.filter_map do |field|
19
+ values = record.filter_map do |field|
20
20
  next AT_THE_LIBRARY if physical_holding_tag?(field)
21
21
  next ONLINE if electronic_holding_tag?(field)
22
22
  end
23
23
 
24
- return acc if acc.size == 2 # return early if all values are already present
24
+ return values if values.size == 2 # return early if all values are already present
25
25
 
26
- acc << ONLINE if acc.exclude?(ONLINE) && finding_aid_linkage?(record) # only check if ONLINE isn't already there
27
- acc
26
+ # only check if ONLINE isn't already there
27
+ values << ONLINE if values.exclude?(ONLINE) && finding_aid_linkage?(record)
28
+ values.uniq
28
29
  end
29
30
 
30
31
  private
@@ -27,7 +27,7 @@ module PennMARC
27
27
  WEBSITE_DATABASE = 'Website/Database'
28
28
 
29
29
  # Get any Format values from {https://www.oclc.org/bibformats/en/3xx/300.html 300},
30
- # 254, 255, 310, 342, 352 or {https://www.oclc.org/bibformats/en/3xx/340.html 340} field. based on the source
30
+ # 254, 255, 310, 342, 352, 362 or {https://www.oclc.org/bibformats/en/3xx/340.html 340} field. based on the source
31
31
  # field, different subfields are used.
32
32
  # @note ported from get_format_display
33
33
  # @param [MARC::Record] record
@@ -39,6 +39,9 @@ module PennMARC
39
39
  end
40
40
  results += record.fields('340').map { |f| join_subfields(f, &subfield_not_in?(%w[0 2 6 8])) }
41
41
  results += record.fields('880').map do |f|
42
+ # skip any 880s associated with non format fields
43
+ next unless subfield_value_in?(f, '6', %w[254 255 300 310 340 342 352 362])
44
+
42
45
  subfield_to_ignore = if subfield_value?(f, 6, /^300/)
43
46
  %w[3 6 8]
44
47
  elsif subfield_value?(f, 6, /^340/)
@@ -4,6 +4,7 @@ module PennMARC
4
4
  # Methods that return Library and Location values from Alma enhanced MARC fields
5
5
  class Location < Helper
6
6
  ONLINE_LIBRARY = 'Online library'
7
+ WEB_LOCATION_CODE = 'web'
7
8
 
8
9
  class << self
9
10
  # Retrieves library location from enriched marc 'itm' or 'hld' fields, giving priority to the item location over
@@ -41,43 +42,45 @@ module PennMARC
41
42
  # @param [Hash] location_map hash with location_code as key and location hash as value
42
43
  # @return [Array<String>]
43
44
  def location(record:, display_value:, location_map:)
44
- # get enriched marc location tag and subfield code
45
- location_tag_and_subfield_code(record) => {tag:, subfield_code:}
45
+ # get enriched marc location tag and relevant subfields
46
+ enriched_location_tag_and_subfields(record) => {tag:, location_code_sf:, call_num_sf:}
46
47
 
47
48
  locations = record.fields(tag).flat_map { |field|
48
49
  field.filter_map { |subfield|
49
50
  # skip unless subfield code does not match enriched marc tag subfield code
50
- next unless subfield.code == subfield_code
51
+ next unless subfield.code == location_code_sf
51
52
 
52
- # skip if subfield value is 'web'
53
- # we don't facet for 'web' which is the 'Penn Library Web' location used in Voyager.
54
- # this location should eventually go away completely with data cleanup in Alma.
55
- next if subfield.value == 'web'
53
+ location_code = subfield.value
56
54
 
57
- # skip unless subfield value is a key in location_map
58
- # sometimes "happening locations" are mistakenly used in holdings records.
59
- # that's a data problem that should be fixed.
60
- # here, if we encounter a code we can't map, we ignore it, for faceting purposes
61
- next unless location_map.key?(subfield.value.to_sym)
55
+ next if location_code_to_ignore?(location_map, location_code)
62
56
 
63
- location_map[subfield.value.to_sym][display_value.to_sym]
57
+ override = specific_location_override(display_value: display_value, location_code: location_code,
58
+ field: field, call_num_sf: call_num_sf)
59
+
60
+ if override.present?
61
+ override
62
+ else
63
+ location_map[location_code.to_sym][display_value.to_sym]
64
+ end
64
65
  }.flatten.compact_blank
65
66
  }.uniq
66
67
  if record.tags.intersect?([Enriched::Pub::ELEC_INVENTORY_TAG, Enriched::Api::ELEC_INVENTORY_TAG])
67
68
  locations << ONLINE_LIBRARY
68
69
  end
70
+
69
71
  locations
70
72
  end
71
73
 
72
74
  private
73
75
 
74
- # Determine enriched marc location tag ('itm' or 'hld') and subfield code, giving priority to using 'itm' tag and
75
- # subfield.
76
+ # Determine enriched marc location tag, location code subfield, and call number subfield,
77
+ # giving priority to using 'itm', 'AVA', or 'AVE' fields.
76
78
  # @param [MARC::Record]
77
79
  # @return [Hash<String, String>] containing location tag and subfield code
78
80
  # - `:tag` (String): The enriched marc location tag
79
- # - `:subfield_code` (String): The relevant subfield code
80
- def location_tag_and_subfield_code(record)
81
+ # - `:location_code_sf` (String): The subfield code where location code is stored
82
+ # - `:call_num_sf` (String): The subfield code where call number is stored
83
+ def enriched_location_tag_and_subfields(record)
81
84
  # in holdings records, the shelving location is always the permanent location.
82
85
  # in item records, the current location takes into account
83
86
  # temporary locations and permanent locations. if you update the item's perm location,
@@ -89,18 +92,57 @@ module PennMARC
89
92
  # if the record has an enriched item field present, use it
90
93
  if field_defined?(record, Enriched::Pub::ITEM_TAG)
91
94
  tag = Enriched::Pub::ITEM_TAG
92
- subfield_code = Enriched::Pub::ITEM_CURRENT_LOCATION
93
- # if the record has API inventory tags, use them
95
+ location_code_sf = Enriched::Pub::ITEM_CURRENT_LOCATION
96
+ call_num_sf = Enriched::Pub::ITEM_CALL_NUMBER
97
+ # if the record has API inventory tags, use them
94
98
  elsif field_defined?(record, Enriched::Api::PHYS_INVENTORY_TAG)
95
99
  tag = Enriched::Api::PHYS_INVENTORY_TAG
96
- subfield_code = Enriched::Api::PHYS_LOCATION_CODE
97
- # otherwise use Pub holding tags
100
+ location_code_sf = Enriched::Api::PHYS_LOCATION_CODE
101
+ call_num_sf = Enriched::Api::PHYS_CALL_NUMBER
102
+ # otherwise use Pub holding tags
98
103
  else
99
104
  tag = Enriched::Pub::PHYS_INVENTORY_TAG
100
- subfield_code = Enriched::Pub::PHYS_LOCATION_CODE
105
+ location_code_sf = Enriched::Pub::PHYS_LOCATION_CODE
106
+ call_num_sf = Enriched::Pub::HOLDING_CLASSIFICATION_PART
107
+ end
108
+
109
+ { tag: tag, location_code_sf: location_code_sf, call_num_sf: call_num_sf }
110
+ end
111
+
112
+ # Determines whether to ignore a location code.
113
+ # We ignore location codes that are not keys in the location map. Sometimes "happening locations" are
114
+ # mistakenly used in holdings records. That's a data problem that should be fixed. If we encounter a code we can't
115
+ # map, we ignore it, for faceting purposes. We also ignore the location code 'web'. We don't facet for 'web'
116
+ # which is the 'Penn Library Web' location used in Voyager. This location should eventually go away completely
117
+ # with data cleanup in Alma.
118
+ # @param [location_map] location_map hash with location_code as key and location hash as value
119
+ # @param [location_code] location_code retrieved from record
120
+ # @return [Boolean]
121
+ def location_code_to_ignore?(location_map, location_code)
122
+ location_map.key?(location_code.to_sym) == false || location_code == WEB_LOCATION_CODE
123
+ end
124
+
125
+ # Retrieves a specific location override based on location code and call number. Specific location overrides are
126
+ # located in `location_overrides.yml`.
127
+ # @param [String] display_value
128
+ # @param [String] location_code
129
+ # @param [MARC::Field] field
130
+ # @param [String] call_num_sf
131
+ # @return [String, Nil]
132
+ def specific_location_override(display_value:, location_code:, field:, call_num_sf:)
133
+ return unless display_value == :specific_location
134
+
135
+ locations_overrides = Mappers.location_overrides
136
+
137
+ call_numbers = subfield_values(field, call_num_sf)
138
+
139
+ override = locations_overrides.select do |_key, value|
140
+ value[:location_code] == location_code && call_numbers.any? { |num| num.match?(value[:call_num_pattern]) }
101
141
  end
102
142
 
103
- { tag: tag, subfield_code: subfield_code }
143
+ return if override.blank?
144
+
145
+ override[override.keys.first][:specific_location]
104
146
  end
105
147
  end
106
148
  end
@@ -166,12 +166,19 @@ module PennMARC
166
166
  system_details_notes.uniq
167
167
  end
168
168
 
169
+ # Retrieve "With" notes for display from field {https://www.loc.gov/marc/bibliographic/bd501.html 501}
170
+ # @param [Marc::Record] record
171
+ # @return [Array<String>]
172
+ def bound_with_show(record)
173
+ record.fields('501').filter_map { |field| join_subfields(field, &subfield_in?(['a'])).presence }.uniq
174
+ end
175
+
169
176
  private
170
177
 
171
178
  # For system details: extract subfield ǂ3 plus other subfields as specified by passed-in block. Pays special
172
179
  # attention to punctuation, joining subfield ǂ3 values with a colon-space (': ').
173
180
  # @param [MARC::DataField] field
174
- # @param [Proc] selector
181
+ # @param [Proc] &
175
182
  # @return [String]
176
183
  def sub3_and_other_subs(field, &)
177
184
  sub3 = field.filter_map { |sf| trim_trailing('period', sf.value) if sf.code == '3' }.join(': ')
@@ -160,7 +160,6 @@ module PennMARC
160
160
 
161
161
  # Format a term hash as a string for display
162
162
  #
163
- # @todo confirm punctuation handling
164
163
  # @todo support search field formatting?
165
164
  # @param [Symbol] type
166
165
  # @param [Hash] term components and information as a hash
@@ -171,11 +170,12 @@ module PennMARC
171
170
  # attempt to handle poorly coded record
172
171
  normalize_single_subfield(term[:parts].first) if term[:count] == 1 && term[:parts].first.present?
173
172
 
174
- case type.to_sym
173
+ case type
175
174
  when :facet
176
- term[:parts].join('--').strip
175
+ trim_trailing(:period, term[:parts].join('--').strip)
177
176
  when :display
178
- "#{term[:parts].join('--')} #{term[:append].join(' ')}".strip
177
+ display_value = "#{term[:parts].join('--')} #{term[:append].join(' ')}".strip
178
+ display_value.ends_with?('.') ? display_value : "#{display_value}."
179
179
  end
180
180
  end
181
181
 
@@ -225,9 +225,8 @@ module PennMARC
225
225
  # because we're using (where? - MK) "subdivision count" as a heuristic for the quality level of the
226
226
  # heading. - MG
227
227
  # @todo do i need all this?
228
- # @todo do i need to handle punctuation? see append_new_part
229
228
  # @param [MARC::DataField] field
230
- # @return [Hash{Symbol => Integer, Array}, Nil]
229
+ # @return [Hash{Symbol => Integer, Array, Boolean}, Nil]
231
230
  def build_subject_hash(field)
232
231
  term_info = { count: 0, parts: [], append: [], uri: nil,
233
232
  local: field.indicator2 == '4' || field.tag.starts_with?('69'), # local subject heading
@@ -243,6 +242,9 @@ module PennMARC
243
242
  # filter out PRO/CHR entirely (but only need to check on local heading types)
244
243
  return nil if term_info[:local] && subfield.value =~ /^%?(PRO|CHR)([ $]|$)/
245
244
 
245
+ # remove trailing punctuation of previous subject part
246
+ trim_trailing_commas_or_periods!(term_info[:parts].last)
247
+
246
248
  term_info[:parts] << subfield.value.strip
247
249
  term_info[:count] += 1
248
250
  when '2'
@@ -252,11 +254,16 @@ module PennMARC
252
254
  term_info[:append] << subfield.value.strip # TODO: map relator code?
253
255
  when 'b', 'c', 'd', 'p', 'q', 't'
254
256
  # these are appended to the last component (part) if possible (i.e., when joined, should have no delimiter)
255
- term_info[:parts].last << ", #{subfield.value.strip}"
257
+ # assume that there is an 'a' preceding value
258
+ term_info[:parts].last << " #{subfield.value.strip}"
256
259
  term_info[:count] += 1
257
260
  else
258
261
  # the usual case; add a new component to `parts`
259
262
  # this typically includes g, v, x, y, z, 4
263
+
264
+ # remove trailing punctuation of previous subject part
265
+ trim_trailing_commas_or_periods!(term_info[:parts].last)
266
+
260
267
  term_info[:parts] << subfield.value.strip
261
268
  term_info[:count] += 1
262
269
  end
@@ -299,6 +306,15 @@ module PennMARC
299
306
  first_part.gsub!(/([[[:alpha:]])])\s+-\s+([[:upper:]]|[[:digit:]]{2,})/, '\1--\2')
300
307
  first_part.gsub!(/([[[:alnum:]])])\s+-\s+([[:upper:]])/, '\1--\2')
301
308
  end
309
+
310
+ # removes trailing comma or period, manipulating the string in place
311
+ # @param [String, NilClass] subject_part
312
+ # @return [String, NilClass]
313
+ def trim_trailing_commas_or_periods!(subject_part)
314
+ return if subject_part.blank?
315
+
316
+ trim_trailing!(:comma, subject_part) || trim_trailing!(:period, subject_part)
317
+ end
302
318
  end
303
319
  end
304
320
  end
@@ -18,6 +18,11 @@ module PennMARC
18
18
  @location ||= load_map('locations.yml')
19
19
  end
20
20
 
21
+ # @return [Hash]
22
+ def location_overrides
23
+ @location_overrides ||= load_map('location_overrides.yml')
24
+ end
25
+
21
26
  # @return [Hash]
22
27
  def relator
23
28
  @relator ||= load_map('relator.yml')
@@ -0,0 +1,5 @@
1
+ albrecht:
2
+ location_code: 'vanp'
3
+ call_num_pattern: '^M.*'
4
+ specific_location: 'Van Pelt - Albrecht Music Library'
5
+