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 +4 -4
- data/lib/pennmarc/heading_control.rb +1 -1
- data/lib/pennmarc/helpers/access.rb +5 -4
- data/lib/pennmarc/helpers/format.rb +4 -1
- data/lib/pennmarc/helpers/location.rb +65 -23
- data/lib/pennmarc/helpers/note.rb +8 -1
- data/lib/pennmarc/helpers/subject.rb +23 -7
- data/lib/pennmarc/mappers.rb +5 -0
- data/lib/pennmarc/mappings/location_overrides.yml +5 -0
- data/lib/pennmarc/mappings/locations.yml +101 -1
- data/lib/pennmarc/util.rb +17 -7
- data/lib/pennmarc/version.rb +1 -1
- data/spec/lib/pennmarc/helpers/format_spec.rb +14 -0
- data/spec/lib/pennmarc/helpers/location_spec.rb +16 -0
- data/spec/lib/pennmarc/helpers/note_spec.rb +18 -0
- data/spec/lib/pennmarc/helpers/subject_spec.rb +54 -22
- data/spec/support/marc_spec_helpers.rb +5 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90b9c248dbc3c08377212a70748dcf13728c9feaa037a4a64cc398c02a36c3df
|
4
|
+
data.tar.gz: f3c14ffea869113e6f94fc5d94ff5a083f2749cb17ed33854243131b134185a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
24
|
+
return values if values.size == 2 # return early if all values are already present
|
25
25
|
|
26
|
-
|
27
|
-
|
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
|
45
|
-
|
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 ==
|
51
|
+
next unless subfield.code == location_code_sf
|
51
52
|
|
52
|
-
|
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
|
-
|
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
|
-
|
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
|
75
|
-
#
|
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
|
-
# - `:
|
80
|
-
|
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
|
-
|
93
|
-
|
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
|
-
|
97
|
-
|
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
|
-
|
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
|
-
|
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]
|
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
|
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
|
-
|
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
|
data/lib/pennmarc/mappers.rb
CHANGED
@@ -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')
|