pennmarc 1.4.1 → 1.4.2

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: 81ebb88f882d1b92922aa5dc7338056b6831d4857f91ba1ed1710deb2b678f09
4
- data.tar.gz: 9db8caa363ce8ab4515d62612e033c4e73c185fef68c2a5c115a3520928f95b8
3
+ metadata.gz: 0b30436efad760f5e0ba2299abd4b95bc7209d3239e50e28f7bf983c388fa7e6
4
+ data.tar.gz: 4cf860b94dacc4f51a0198f907d5a3cd071204e984f59a46ce439de16505357a
5
5
  SHA512:
6
- metadata.gz: d3a18e0ff157bdecd8a10cad8c72c4aeffece93dc06117740fb122e27ff972f5940f45b332c8bb3d6d57b05cff2c7b610fc57487b9ce7ae38700df96ca077764
7
- data.tar.gz: 0cb04ac15f6f5aa1f7c86005b953bd86e5c952ab2de99479d04688d131bff991ec3a2bbe17a3cf2707bcfa613c71ed8beb2711951c5868c0d5cd4e240d0fca5a
6
+ metadata.gz: 164c8711a0540db31617a1d91a787a216ed24c8d7539656c30b0410693d3284f4ccdb6e838ff5220d0dbf3c7bd359ba83e305938a0f9c32dfc4de5b7213b305b
7
+ data.tar.gz: 693967b2f782cdae859656da8894e28ab329abfbb363ff2e51adbae940e3a6b0ff7b03f398ce4709b946967a89eabec6b5210725e094c2f645e462fb48f0779d
data/Gemfile CHANGED
@@ -11,6 +11,7 @@ gem 'rake', '~> 13.0'
11
11
  gem 'upennlib-rubocop', require: false
12
12
 
13
13
  group :test, :development do
14
+ gem 'debug', platforms: %i[mri mingw x64_mingw]
14
15
  gem 'rspec', '~> 3.12'
15
16
  end
16
17
 
@@ -21,5 +22,5 @@ end
21
22
  group :development do
22
23
  gem 'puma'
23
24
  gem 'rackup'
24
- gem 'yard', '~> 0.9'
25
+ gem 'yard'
25
26
  end
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- activesupport (8.0.3)
4
+ activesupport (8.1.2)
5
5
  base64
6
- benchmark (>= 0.3)
7
6
  bigdecimal
8
7
  concurrent-ruby (~> 1.0, >= 1.3.1)
9
8
  connection_pool (>= 2.2.5)
10
9
  drb
11
10
  i18n (>= 1.6, < 2)
11
+ json
12
12
  logger (>= 1.4.2)
13
13
  minitest (>= 5.1)
14
14
  securerandom (>= 0.3)
@@ -16,63 +16,85 @@ GEM
16
16
  uri (>= 0.13.1)
17
17
  ast (2.4.3)
18
18
  base64 (0.3.0)
19
- benchmark (0.4.1)
20
- bigdecimal (3.3.1)
21
- concurrent-ruby (1.3.5)
22
- connection_pool (2.5.4)
19
+ bigdecimal (4.0.1)
20
+ concurrent-ruby (1.3.6)
21
+ connection_pool (3.0.2)
22
+ date (3.5.1)
23
+ debug (1.11.1)
24
+ irb (~> 1.10)
25
+ reline (>= 0.3.8)
23
26
  diff-lcs (1.6.2)
24
- docile (1.4.0)
27
+ docile (1.4.1)
25
28
  drb (2.2.3)
26
- i18n (1.14.7)
29
+ erb (6.0.1)
30
+ i18n (1.14.8)
27
31
  concurrent-ruby (~> 1.0)
28
- json (2.13.2)
32
+ io-console (0.8.2)
33
+ irb (1.16.0)
34
+ pp (>= 0.6.0)
35
+ rdoc (>= 4.0.0)
36
+ reline (>= 0.4.2)
37
+ json (2.18.1)
29
38
  language_server-protocol (3.17.0.5)
30
39
  lcsort (0.9.1)
31
40
  library_stdnums (1.6.0)
32
41
  lint_roller (1.1.0)
33
42
  logger (1.7.0)
34
- marc (1.3.0)
43
+ marc (1.4.0)
35
44
  nokogiri (~> 1.0)
36
45
  rexml
37
- minitest (5.26.0)
46
+ minitest (6.0.1)
47
+ prism (~> 1.5)
38
48
  nio4r (2.7.4)
39
- nokogiri (1.18.9-arm64-darwin)
49
+ nokogiri (1.19.0-arm64-darwin)
40
50
  racc (~> 1.4)
41
- nokogiri (1.18.9-x64-mingw-ucrt)
51
+ nokogiri (1.19.0-x64-mingw-ucrt)
42
52
  racc (~> 1.4)
43
- nokogiri (1.18.9-x86_64-darwin)
53
+ nokogiri (1.19.0-x86_64-darwin)
44
54
  racc (~> 1.4)
45
- nokogiri (1.18.9-x86_64-linux-gnu)
55
+ nokogiri (1.19.0-x86_64-linux-gnu)
46
56
  racc (~> 1.4)
47
57
  parallel (1.27.0)
48
- parser (3.3.9.0)
58
+ parser (3.3.10.1)
49
59
  ast (~> 2.4.1)
50
60
  racc
51
- prism (1.4.0)
61
+ pp (0.6.3)
62
+ prettyprint
63
+ prettyprint (0.2.0)
64
+ prism (1.9.0)
65
+ psych (5.3.1)
66
+ date
67
+ stringio
52
68
  puma (7.1.0)
53
69
  nio4r (~> 2.0)
54
70
  racc (1.8.1)
55
- rack (3.2.3)
71
+ rack (3.2.4)
56
72
  rackup (2.2.1)
57
73
  rack (>= 3)
58
74
  rainbow (3.1.1)
59
75
  rake (13.3.0)
60
- regexp_parser (2.11.2)
61
- rexml (3.4.1)
62
- rspec (3.13.1)
76
+ rdoc (7.1.0)
77
+ erb
78
+ psych (>= 4.0.0)
79
+ tsort
80
+ regexp_parser (2.11.3)
81
+ reline (0.6.3)
82
+ io-console (~> 0.5)
83
+ rexml (3.4.4)
84
+ rspec (3.13.2)
63
85
  rspec-core (~> 3.13.0)
64
86
  rspec-expectations (~> 3.13.0)
65
87
  rspec-mocks (~> 3.13.0)
66
- rspec-core (3.13.5)
88
+ rspec-core (3.13.6)
67
89
  rspec-support (~> 3.13.0)
68
90
  rspec-expectations (3.13.5)
69
91
  diff-lcs (>= 1.2.0, < 2.0)
70
92
  rspec-support (~> 3.13.0)
71
- rspec-mocks (3.13.5)
93
+ rspec-mocks (3.13.7)
72
94
  diff-lcs (>= 1.2.0, < 2.0)
73
95
  rspec-support (~> 3.13.0)
74
- rspec-support (3.13.4)
75
- rubocop (1.79.2)
96
+ rspec-support (3.13.7)
97
+ rubocop (1.84.1)
76
98
  json (~> 2.3)
77
99
  language_server-protocol (~> 3.17.0.2)
78
100
  lint_roller (~> 1.1.0)
@@ -80,20 +102,20 @@ GEM
80
102
  parser (>= 3.3.0.2)
81
103
  rainbow (>= 2.2.2, < 4.0)
82
104
  regexp_parser (>= 2.9.3, < 3.0)
83
- rubocop-ast (>= 1.46.0, < 2.0)
105
+ rubocop-ast (>= 1.49.0, < 2.0)
84
106
  ruby-progressbar (~> 1.7)
85
107
  unicode-display_width (>= 2.4.0, < 4.0)
86
- rubocop-ast (1.46.0)
108
+ rubocop-ast (1.49.0)
87
109
  parser (>= 3.3.7.2)
88
- prism (~> 1.4)
110
+ prism (~> 1.7)
89
111
  rubocop-capybara (2.22.1)
90
112
  lint_roller (~> 1.1)
91
113
  rubocop (~> 1.72, >= 1.72.1)
92
- rubocop-performance (1.25.0)
114
+ rubocop-performance (1.26.1)
93
115
  lint_roller (~> 1.1)
94
116
  rubocop (>= 1.75.0, < 2.0)
95
- rubocop-ast (>= 1.38.0, < 2.0)
96
- rubocop-rails (2.33.3)
117
+ rubocop-ast (>= 1.47.1, < 2.0)
118
+ rubocop-rails (2.34.3)
97
119
  activesupport (>= 4.2.0)
98
120
  lint_roller (~> 1.1)
99
121
  rack (>= 1.1)
@@ -102,22 +124,24 @@ GEM
102
124
  rubocop-rake (0.7.1)
103
125
  lint_roller (~> 1.1)
104
126
  rubocop (>= 1.72.1)
105
- rubocop-rspec (3.6.0)
127
+ rubocop-rspec (3.9.0)
106
128
  lint_roller (~> 1.1)
107
- rubocop (~> 1.72, >= 1.72.1)
129
+ rubocop (~> 1.81)
108
130
  ruby-progressbar (1.13.0)
109
131
  securerandom (0.4.1)
110
132
  simplecov (0.22.0)
111
133
  docile (~> 1.1)
112
134
  simplecov-html (~> 0.11)
113
135
  simplecov_json_formatter (~> 0.1)
114
- simplecov-html (0.12.3)
136
+ simplecov-html (0.13.2)
115
137
  simplecov_json_formatter (0.1.4)
138
+ stringio (3.2.0)
139
+ tsort (0.2.0)
116
140
  tzinfo (2.0.6)
117
141
  concurrent-ruby (~> 1.0)
118
- unicode-display_width (3.1.5)
119
- unicode-emoji (~> 4.0, >= 4.0.4)
120
- unicode-emoji (4.0.4)
142
+ unicode-display_width (3.2.0)
143
+ unicode-emoji (~> 4.1)
144
+ unicode-emoji (4.2.0)
121
145
  upennlib-rubocop (1.3.0)
122
146
  rubocop (~> 1.72)
123
147
  rubocop-capybara
@@ -125,18 +149,20 @@ GEM
125
149
  rubocop-rails
126
150
  rubocop-rake
127
151
  rubocop-rspec
128
- uri (1.0.4)
129
- yard (0.9.37)
152
+ uri (1.1.1)
153
+ yard (0.9.38)
130
154
 
131
155
  PLATFORMS
132
156
  arm64-darwin-21
133
157
  arm64-darwin-22
158
+ arm64-darwin-24
134
159
  x64-mingw-ucrt
135
160
  x86_64-darwin-21
136
161
  x86_64-linux
137
162
 
138
163
  DEPENDENCIES
139
164
  activesupport (>= 7)
165
+ debug
140
166
  lcsort
141
167
  library_stdnums (~> 1.6)
142
168
  marc (~> 1.2)
@@ -147,7 +173,7 @@ DEPENDENCIES
147
173
  rspec (~> 3.12)
148
174
  simplecov (~> 0.22)
149
175
  upennlib-rubocop
150
- yard (~> 0.9)
176
+ yard
151
177
 
152
178
  BUNDLED WITH
153
179
  2.4.10
@@ -357,25 +357,33 @@ module PennMARC
357
357
  # @param relator_map [Hash]
358
358
  # @return [Array<String>] name values from given tags
359
359
  def name_search_values(record:, tags:, relator_map:)
360
- acc = record.fields(tags).filter_map do |field|
361
- name_from_main_entry field, relator_map, should_convert_name_order: false, for_display: false
360
+ # Standard tags
361
+ standard_names = record.fields(tags).flat_map do |field|
362
+ [false, true].map do |should_convert|
363
+ name_from_main_entry(field, relator_map, should_convert_name_order: should_convert,
364
+ for_display: false)
365
+ end
362
366
  end
363
367
 
364
- acc += record.fields(tags).filter_map do |field|
365
- name_from_main_entry field, relator_map, should_convert_name_order: true, for_display: false
368
+ # Author 700 fields
369
+ added_entry_personal_names = record.fields('700').filter_map do |field|
370
+ next unless describes_author?(field)
371
+
372
+ convert_name_order(field['a']) if field['a']
366
373
  end
367
374
 
368
- acc += record.fields(['880']).filter_map do |field|
375
+ # Linked 880 fields
376
+ linked_names = record.fields('880').filter_map do |field|
369
377
  next unless subfield_value?(field, '6', /^(#{tags.join('|')})/)
370
378
 
371
- suba = field.find_all(&subfield_in?(%w[a])).filter_map { |sf|
372
- convert_name_order(sf.value)
373
- }.first
374
- oth = join_and_squish(field.find_all(&subfield_not_in?(%w[6 8 a t])).map(&:value))
375
- join_and_squish [suba, oth]
379
+ sub_a = field['a'] ? convert_name_order(field['a']) : nil
380
+ others = join_and_squish(field.subfields.reject { |sf| %w[6 8 a t].include?(sf.code) }.map(&:value))
381
+
382
+ join_and_squish([sub_a, others])
376
383
  end
377
384
 
378
- acc.uniq
385
+ # Merge and deduplicate
386
+ (standard_names + added_entry_personal_names + linked_names).compact.uniq
379
387
  end
380
388
 
381
389
  # Extract the information we care about from 1xx fields, map relator codes, and use appropriate punctuation
@@ -52,7 +52,7 @@ module PennMARC
52
52
  added_2xx = record.fields(%w[260 261 262])
53
53
  .first(1)
54
54
  .map do |field|
55
- join_subfields(field, &subfield_not_in?(['6'])).squish
55
+ join_subfields(field, &subfield_not_in?(['6'])).squish
56
56
  end
57
57
 
58
58
  if added_2xx.present?
@@ -59,6 +59,7 @@ module PennMARC
59
59
  end
60
60
 
61
61
  # All Subjects for faceting
62
+ # When a library of congress subject heading has subdivisions, we also facet the decomposed main term ($a)
62
63
  #
63
64
  # @note this is ported mostly form MG's new-style Subject parsing
64
65
  # @param record [MARC::Record]
@@ -69,13 +70,18 @@ module PennMARC
69
70
  term_hash = build_subject_hash(field)
70
71
  next if term_hash.blank? || term_hash[:count]&.zero?
71
72
 
72
- format_term type: :facet, term: term_hash
73
- }.uniq
73
+ heading = format_term type: :facet, term: term_hash
74
+ main_term = trim_trailing(:period, term_hash[:subfield_a]) if term_hash[:lcsh] && term_hash[:subfield_a]
75
+
76
+ [heading, main_term].compact_blank
77
+ }.flatten.uniq
78
+
74
79
  override ? HeadingControl.term_override(values) : values
75
80
  end
76
81
 
77
82
  # All Subjects for display. This includes all {DISPLAY_TAGS} and {LOCAL_TAGS}. For tags that specify a source,
78
83
  # only those with an allowed source code (see ALLOWED_SOURCE_CODES) are included.
84
+ # When a library of congress subject heading has subdivisions, we also display the decomposed main term ($a)
79
85
  #
80
86
  # @param record [MARC::Record]
81
87
  # @param override [Boolean] to remove undesirable terms or not
@@ -85,8 +91,11 @@ module PennMARC
85
91
  term_hash = build_subject_hash(field)
86
92
  next if term_hash.blank? || term_hash[:count]&.zero?
87
93
 
88
- format_term type: :display, term: term_hash
89
- }.uniq
94
+ heading = format_term type: :display, term: term_hash
95
+ main_term = "#{trim_trailing(:period, term_hash[:subfield_a])}." if term_hash[:lcsh] && term_hash[:subfield_a]
96
+
97
+ [heading, main_term].compact_blank
98
+ }.flatten.uniq
90
99
  override ? HeadingControl.term_override(values) : values
91
100
  end
92
101
 
@@ -98,10 +107,10 @@ module PennMARC
98
107
  def childrens_show(record, override: true)
99
108
  values = subject_fields(record, type: :display, options: { tags: DISPLAY_TAGS, indicator2: '1' })
100
109
  .filter_map { |field|
101
- term_hash = build_subject_hash(field)
102
- next if term_hash.blank? || term_hash[:count]&.zero?
110
+ term_hash = build_subject_hash(field)
111
+ next if term_hash.blank? || term_hash[:count]&.zero?
103
112
 
104
- format_term type: :display, term: term_hash
113
+ format_term type: :display, term: term_hash
105
114
  }.uniq
106
115
  override ? HeadingControl.term_override(values) : values
107
116
  end
@@ -114,10 +123,10 @@ module PennMARC
114
123
  def medical_show(record, override: true)
115
124
  values = subject_fields(record, type: :display, options: { tags: DISPLAY_TAGS, indicator2: '2' })
116
125
  .filter_map { |field|
117
- term_hash = build_subject_hash(field)
118
- next if term_hash.blank? || term_hash[:count]&.zero?
126
+ term_hash = build_subject_hash(field)
127
+ next if term_hash.blank? || term_hash[:count]&.zero?
119
128
 
120
- format_term type: :display, term: term_hash
129
+ format_term type: :display, term: term_hash
121
130
  }.uniq
122
131
  override ? HeadingControl.term_override(values) : values
123
132
  end
@@ -242,11 +251,13 @@ module PennMARC
242
251
  # heading. - MG
243
252
  # @todo do i need all this?
244
253
  # @param field [MARC::DataField]
245
- # @return [Hash{Symbol => Integer, Array, Boolean}, Nil]
254
+ # @return [Hash{Symbol => Integer, Array, Boolean, String}, Nil]
246
255
  def build_subject_hash(field)
247
256
  term_info = { count: 0, parts: [], append: [], uri: nil,
248
257
  local: field.indicator2 == '4' || field.tag.starts_with?('69'), # local subject heading
249
- vernacular: field.tag == '880' }
258
+ vernacular: field.tag == '880',
259
+ subfield_a: nil,
260
+ lcsh: lcsh?(field) }
250
261
  field.each do |subfield|
251
262
  case subfield.code
252
263
  when '0', '6', '8', '5', '7'
@@ -263,6 +274,7 @@ module PennMARC
263
274
 
264
275
  term_info[:parts] << subfield.value.strip
265
276
  term_info[:count] += 1
277
+ term_info[:subfield_a] = trim_trailing(:comma, subfield.value.strip)
266
278
  when '2'
267
279
  term_info[:source] = subfield.value.strip
268
280
  when 'e', 'w'
@@ -289,6 +301,18 @@ module PennMARC
289
301
  term_info
290
302
  end
291
303
 
304
+ # Determine if field contains a library of congress subject heading
305
+ # @param field [MARC::Field]
306
+ # @return [Boolean]
307
+ def lcsh?(field)
308
+ return false unless field.respond_to?(:indicator2)
309
+
310
+ return true if field.indicator2.in? %w[0]
311
+ return true if field.indicator2 == '7' && subfield_values(field, '2').first == 'lcsh'
312
+
313
+ false
314
+ end
315
+
292
316
  # Determine if a field should be considered for Subject search inclusion. It must be either contained in
293
317
  # {SEARCH_TAGS}, be an 880 field otherwise linked to a valid Search tag, or be in {LOCAL_TAGS}.
294
318
  # @param field [MARC::DataField]
@@ -8,6 +8,7 @@ module PennMARC
8
8
  # We use these fields when retrieving auxiliary titles in the *search_aux methods:
9
9
  # {https://www.loc.gov/marc/bibliographic/bd130.html 130},
10
10
  # {https://www.loc.gov/marc/bibliographic/bd210.html 210},
11
+ # {https://www.loc.gov/marc/bibliographic/bd222.html 222},
11
12
  # {https://www.loc.gov/marc/bibliographic/bd245.html 245},
12
13
  # {https://www.loc.gov/marc/bibliographic/bd246.html 246},
13
14
  # {https://www.loc.gov/marc/bibliographic/bd247.html 247},
@@ -25,7 +26,7 @@ module PennMARC
25
26
  # {https://www.loc.gov/marc/bibliographic/bd711.html 711},
26
27
  # {https://www.loc.gov/marc/bibliographic/bd505.html 505}
27
28
  AUX_TITLE_TAGS = {
28
- main: %w[130 210 240 245 246 247 440 490 730 740 830],
29
+ main: %w[130 210 222 240 245 246 247 440 490 730 740 830],
29
30
  related: %w[773 774 780 785],
30
31
  entity: %w[700 710 711],
31
32
  note: %w[505]
@@ -230,9 +231,9 @@ module PennMARC
230
231
  end
231
232
  other_titles += record.fields('740')
232
233
  .filter_map do |field|
233
- next unless field.indicator2.in? ['', ' ', '0', '1', '3']
234
+ next unless field.indicator2.in? ['', ' ', '0', '1', '3']
234
235
 
235
- join_subfields(field, &subfield_not_in?(%w[5 6 8]))
236
+ join_subfields(field, &subfield_not_in?(%w[5 6 8]))
236
237
  end
237
238
  titles = other_titles + record.fields('880').filter_map do |field|
238
239
  next unless subfield_value? field, '6', /^(246|740)/
@@ -995,12 +995,11 @@ pahref:
995
995
  - Pennsylvania Hospital Library
996
996
  display: Pennsylvania Hospital Library - Reference
997
997
  pahres:
998
- specific_location: Pennsylvania Hospital Library - Reserve
998
+ specific_location: Pennsylvania Hospital Library
999
999
  library:
1000
1000
  - Health Sciences Libraries
1001
1001
  - Pennsylvania Hospital Library
1002
- - Reserve
1003
- display: Pennsylvania Hospital Library - Reserve
1002
+ display: Pennsylvania Hospital Library
1004
1003
  pahresrm:
1005
1004
  specific_location: Pennsylvania Hospital Library - Reserve Room
1006
1005
  library:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PennMARC
4
- VERSION = '1.4.1'
4
+ VERSION = '1.4.2'
5
5
  end
data/pennmarc.gemspec CHANGED
@@ -18,11 +18,11 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.required_ruby_version = '>= 3.2'
20
20
 
21
- s.add_dependency 'activesupport', '>= 7'
21
+ s.add_dependency 'activesupport', '>= 8'
22
22
  s.add_dependency 'lcsort', '~> 0.9'
23
23
  s.add_dependency 'library_stdnums', '~> 1.6'
24
- s.add_dependency 'marc', '~> 1.2'
25
- s.add_dependency 'nokogiri', '~> 1.15'
24
+ s.add_dependency 'marc', '~> 1.4'
25
+ s.add_dependency 'nokogiri', '~> 1.19'
26
26
 
27
27
  s.metadata['rubygems_mfa_required'] = 'false'
28
28
  end
@@ -11,14 +11,16 @@ describe 'PennMARC::Creator' do
11
11
  let(:fields) do
12
12
  [marc_field(tag: '100', subfields: { a: 'Surname, Name,', '0': 'http://cool.uri/12345',
13
13
  e: 'author', d: '1900-2000' }),
14
- marc_field(tag: '880', subfields: { a: 'Surname, Alternative,', '6': '100' })]
14
+ marc_field(tag: '880', subfields: { a: 'Surname, Alternative,', '6': '100' }),
15
+ marc_field(tag: '700', subfields: { a: 'Surname, Third', e: 'author.', '6': '100' })]
15
16
  end
16
17
 
17
18
  it 'contains the expected search field values for a single author work' do
18
19
  expect(helper.search(record, relator_map: mapping)).to contain_exactly(
19
20
  'Name Surname http://cool.uri/12345 1900-2000, author.',
20
21
  'Surname, Name http://cool.uri/12345 1900-2000, author.',
21
- 'Alternative Surname'
22
+ 'Alternative Surname',
23
+ 'Third Surname'
22
24
  )
23
25
  end
24
26
  end
@@ -57,6 +57,18 @@ describe 'PennMARC::Subject' do
57
57
  expect(values.first).to eq 'Unicorns dpc Depicted'
58
58
  end
59
59
  end
60
+
61
+ context 'with subject heading subdivisions' do
62
+ let(:fields) do
63
+ [marc_field(tag: '610', indicator2: '0', subfields: { a: 'University of Pennsylvania',
64
+ v: 'Ivy league', x: 'Higher education',
65
+ y: '19th century', z: 'Philly' })]
66
+ end
67
+
68
+ it 'includes subdivisions' do
69
+ expect(values).to contain_exactly('University of Pennsylvania Ivy league Higher education 19th century Philly')
70
+ end
71
+ end
60
72
  end
61
73
 
62
74
  describe '.facet' do
@@ -152,7 +164,7 @@ describe 'PennMARC::Subject' do
152
164
 
153
165
  it 'drops the final trailing period' do
154
166
  expect(values).to contain_exactly('R.G. (Robert Gordon). Spiritual order and Christian liberty proved ' \
155
- 'to be consistent in the Churches of Christ')
167
+ 'to be consistent in the Churches of Christ', 'R.G')
156
168
  end
157
169
  end
158
170
 
@@ -162,7 +174,29 @@ describe 'PennMARC::Subject' do
162
174
  end
163
175
 
164
176
  it 'treats the first part it comes across as a main subject part' do
165
- expect(values).to contain_exactly('Italian--Architectural theory')
177
+ expect(values).to include('Italian--Architectural theory')
178
+ end
179
+
180
+ it 'returns subfield "a" for library of congress subject headings' do
181
+ expect(values).to contain_exactly('Architectural theory', 'Italian--Architectural theory')
182
+ end
183
+ end
184
+
185
+ context 'with a record with subject heading subdivisions' do
186
+ let(:fields) do
187
+ [marc_field(tag: '650', indicator2: '0', subfields: { a: 'Franklin, Benjamin,', d: '1706-1790',
188
+ x: 'Books and reading' }),
189
+ marc_field(tag: '610', indicator2: '7',
190
+ subfields: { '2': 'lcsh', a: 'Philadelphia (Pa.)', y: '18th century.' }),
191
+ marc_field(tag: '600', indicator2: '7',
192
+ subfields: { '2': 'fast', a: 'Do not decompose', v: 'Penn Libraries' })]
193
+ end
194
+
195
+ it 'returns decomposed subfield "a" values for library of congress subject headings' do
196
+ expect(values).to contain_exactly('Franklin, Benjamin, 1706-1790--Books and reading',
197
+ 'Philadelphia (Pa.)--18th century',
198
+ 'Do not decompose--Penn Libraries',
199
+ 'Franklin, Benjamin', 'Philadelphia (Pa.)')
166
200
  end
167
201
  end
168
202
  end
@@ -225,7 +259,8 @@ describe 'PennMARC::Subject' do
225
259
 
226
260
  it 'properly handles punctuation in subject parts' do
227
261
  expect(values).to contain_exactly 'Franklin, Benjamin, 1706-1790.',
228
- 'Franklin, Benjamin, 1706-1790--As inventor.', 'Franklin stoves.'
262
+ 'Franklin, Benjamin, 1706-1790--As inventor.', 'Franklin stoves.',
263
+ 'Franklin, Benjamin.'
229
264
  end
230
265
  end
231
266
 
@@ -326,6 +361,24 @@ describe 'PennMARC::Subject' do
326
361
  )
327
362
  end
328
363
  end
364
+
365
+ context 'with a record with subject heading subdivisions' do
366
+ let(:fields) do
367
+ [marc_field(tag: '650', indicator2: '0', subfields: { a: 'Franklin, Benjamin,', d: '1706-1790',
368
+ x: 'Books and reading' }),
369
+ marc_field(tag: '610', indicator2: '7',
370
+ subfields: { '2': 'lcsh', a: 'Philadelphia (Pa.)', y: '18th century.' }),
371
+ marc_field(tag: '600', indicator2: '7',
372
+ subfields: { '2': 'fast', a: 'Do not decompose', v: 'Penn Libraries' })]
373
+ end
374
+
375
+ it 'returns decomposed subfield "a" values for library of congress subject headings' do
376
+ expect(values).to contain_exactly('Franklin, Benjamin, 1706-1790--Books and reading.',
377
+ 'Philadelphia (Pa.)--18th century.',
378
+ 'Do not decompose--Penn Libraries.',
379
+ 'Franklin, Benjamin.', 'Philadelphia (Pa.).')
380
+ end
381
+ end
329
382
  end
330
383
 
331
384
  describe '.childrens_show' do
@@ -107,6 +107,7 @@ describe 'PennMARC::Title' do
107
107
  let(:leader) { 'ZZZZZnasZa22ZZZZZzZZ4500' }
108
108
  let(:fields) do
109
109
  [marc_field(tag: '130', subfields: { a: 'Uniform Title', c: '130 not included' }),
110
+ marc_field(tag: '222', subfields: { a: 'Key Title', b: '(Qualifying Info)' }),
110
111
  marc_field(tag: '880', subfields: { '6': '130', a: 'Alternative Uniform Title' }),
111
112
  marc_field(tag: '773', subfields: { a: 'Host Item - Main entry heading', s: 'Host Item - Uniform title',
112
113
  t: 'Host Item - Title' }),
@@ -118,6 +119,7 @@ describe 'PennMARC::Title' do
118
119
 
119
120
  it 'returns auxiliary journal search titles' do
120
121
  expect(helper.journal_search_aux(record)).to contain_exactly('Uniform Title', 'Alternative Uniform Title',
122
+ 'Key Title (Qualifying Info)',
121
123
  'Host Item - Uniform title Host Item - Title',
122
124
  'Personal Entry Title',
123
125
  'Formatted Contents Note Title')
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,7 @@ Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
8
8
  # Require test helpers from gem lib
9
9
  Dir[File.join(__dir__, '../lib/pennmarc/test', '*.rb')].each { |f| require f }
10
10
 
11
+ require 'debug'
11
12
  require 'pennmarc'
12
13
 
13
14
  # This file was generated by the `rspec --init` command. Conventionally, all
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.4.1
4
+ version: 1.4.2
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: 2025-11-13 00:00:00.000000000 Z
15
+ date: 2026-03-05 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: activesupport
@@ -20,14 +20,14 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: '7'
23
+ version: '8'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
28
  - - ">="
29
29
  - !ruby/object:Gem::Version
30
- version: '7'
30
+ version: '8'
31
31
  - !ruby/object:Gem::Dependency
32
32
  name: lcsort
33
33
  requirement: !ruby/object:Gem::Requirement
@@ -62,28 +62,28 @@ dependencies:
62
62
  requirements:
63
63
  - - "~>"
64
64
  - !ruby/object:Gem::Version
65
- version: '1.2'
65
+ version: '1.4'
66
66
  type: :runtime
67
67
  prerelease: false
68
68
  version_requirements: !ruby/object:Gem::Requirement
69
69
  requirements:
70
70
  - - "~>"
71
71
  - !ruby/object:Gem::Version
72
- version: '1.2'
72
+ version: '1.4'
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: nokogiri
75
75
  requirement: !ruby/object:Gem::Requirement
76
76
  requirements:
77
77
  - - "~>"
78
78
  - !ruby/object:Gem::Version
79
- version: '1.15'
79
+ version: '1.19'
80
80
  type: :runtime
81
81
  prerelease: false
82
82
  version_requirements: !ruby/object:Gem::Requirement
83
83
  requirements:
84
84
  - - "~>"
85
85
  - !ruby/object:Gem::Version
86
- version: '1.15'
86
+ version: '1.19'
87
87
  description: |-
88
88
  This gem provides methods for parsing a Penn Libraries MARCXML record into string, array and date
89
89
  objects for use in discovery or preservation applications.