pennmarc 1.0.25 → 1.0.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +8 -9
  3. data/lib/pennmarc/helpers/creator.rb +139 -65
  4. data/lib/pennmarc/helpers/edition.rb +5 -3
  5. data/lib/pennmarc/helpers/note.rb +24 -19
  6. data/lib/pennmarc/helpers/production.rb +113 -20
  7. data/lib/pennmarc/test/marc_helpers.rb +83 -0
  8. data/lib/pennmarc/util.rb +98 -69
  9. data/lib/pennmarc/version.rb +1 -1
  10. data/lib/pennmarc.rb +7 -0
  11. data/spec/lib/pennmarc/helpers/access_spec.rb +0 -2
  12. data/spec/lib/pennmarc/helpers/citation_spec.rb +0 -2
  13. data/spec/lib/pennmarc/helpers/classification_spec.rb +0 -2
  14. data/spec/lib/pennmarc/helpers/creator_spec.rb +103 -2
  15. data/spec/lib/pennmarc/helpers/database_spec.rb +0 -2
  16. data/spec/lib/pennmarc/helpers/date_spec.rb +0 -2
  17. data/spec/lib/pennmarc/helpers/edition_spec.rb +4 -2
  18. data/spec/lib/pennmarc/helpers/format_spec.rb +0 -2
  19. data/spec/lib/pennmarc/helpers/genre_spec.rb +0 -2
  20. data/spec/lib/pennmarc/helpers/identifer_spec.rb +0 -2
  21. data/spec/lib/pennmarc/helpers/inventory_spec.rb +0 -2
  22. data/spec/lib/pennmarc/helpers/language_spec.rb +0 -2
  23. data/spec/lib/pennmarc/helpers/link_spec.rb +0 -2
  24. data/spec/lib/pennmarc/helpers/location_spec.rb +0 -2
  25. data/spec/lib/pennmarc/helpers/note_spec.rb +22 -29
  26. data/spec/lib/pennmarc/helpers/production_spec.rb +121 -22
  27. data/spec/lib/pennmarc/helpers/relation_spec.rb +0 -2
  28. data/spec/lib/pennmarc/helpers/series_spec.rb +0 -2
  29. data/spec/lib/pennmarc/helpers/subject_spec.rb +0 -2
  30. data/spec/lib/pennmarc/helpers/title_spec.rb +0 -2
  31. data/spec/lib/pennmarc/marc_util_spec.rb +0 -2
  32. data/spec/lib/pennmarc/parser_spec.rb +1 -1
  33. data/spec/spec_helper.rb +7 -0
  34. data/spec/support/fixture_helpers.rb +10 -0
  35. metadata +4 -3
  36. data/spec/support/marc_spec_helpers.rb +0 -85
@@ -4,31 +4,47 @@ module PennMARC
4
4
  # Extracts data related to a resource's production, distribution, manufacture, and publication.
5
5
  class Production < Helper
6
6
  class << self
7
- # Retrieve production values for display from {https://www.oclc.org/bibformats/en/2xx/264.html 264 field}.
8
- # @param [MARC::Record] record
7
+ # Retrieve production values for display from {https://www.loc.gov/marc/bibliographic/bd264.html 264 field}.
8
+ # @param record [MARC::Record]
9
9
  # @return [Array<String>]
10
10
  def show(record)
11
11
  get_264_or_880_fields(record, '0').uniq
12
12
  end
13
13
 
14
- # Retrieve distribution values for display from {https://www.oclc.org/bibformats/en/2xx/264.html 264 field}.
15
- # @param [MARC::Record] record
14
+ # Retrieve production values for searching. Includes only
15
+ # {https://www.loc.gov/marc/bibliographic/bd260.html 260} and
16
+ # {https://www.loc.gov/marc/bibliographic/bd264.html 264}.
17
+ # @param record [MARC::Record]
18
+ # @return [Array<String>]
19
+ def search(record)
20
+ values = record.fields('260').filter_map do |field|
21
+ join_subfields(field, &subfield_in?(['b']))
22
+ end
23
+ values + record.fields('264').filter_map { |field|
24
+ next unless field.indicator2 == '1'
25
+
26
+ join_subfields(field, &subfield_in?(['b']))
27
+ }.uniq
28
+ end
29
+
30
+ # Retrieve distribution values for display from {https://www.loc.gov/marc/bibliographic/bd264.html 264 field}.
31
+ # @param record [MARC::Record]
16
32
  # @return [Array<String>]
17
33
  def distribution_show(record)
18
34
  get_264_or_880_fields(record, '2').uniq
19
35
  end
20
36
 
21
- # Retrieve manufacture values for display from {https://www.oclc.org/bibformats/en/2xx/264.html 264 field}.
22
- # @param [MARC::Record] record
37
+ # Retrieve manufacture values for display from {https://www.loc.gov/marc/bibliographic/bd264.html 264 field}.
38
+ # @param record [MARC::Record]
23
39
  # @return [Array<String>]
24
40
  def manufacture_show(record)
25
41
  get_264_or_880_fields(record, '3').uniq
26
42
  end
27
43
 
28
44
  # Retrieve publication values. Return publication values from
29
- # {https://www.oclc.org/bibformats/en/2xx/264.html 264 field} only if none found
30
- # {https://www.oclc.org/bibformats/en/2xx/260.html 260}-262 fields.
31
- # @param [MARC::Record] record
45
+ # {https://www.loc.gov/marc/bibliographic/bd264.html 264 field} only if none found
46
+ # {https://www.loc.gov/marc/bibliographic/bd260.html 260}-262 fields.
47
+ # @param record [MARC::Record]
32
48
  # @return [Array<String>]
33
49
  def publication_values(record)
34
50
  # first get inclusive dates
@@ -64,10 +80,10 @@ module PennMARC
64
80
  end
65
81
 
66
82
  # Retrieve publication values for display from fields
67
- # {https://www.oclc.org/bibformats/en/2xx/245.html 245},
68
- # {https://www.oclc.org/bibformats/en/2xx/260.html 260}-262 and their linked alternates,
69
- # and {https://www.oclc.org/bibformats/en/2xx/264.html 264} and its linked alternate.
70
- # @param [MARC::Record] record
83
+ # {https://www.loc.gov/marc/bibliographic/bd245.html 245},
84
+ # {https://www.loc.gov/marc/bibliographic/bd260.html 260}-262 and their linked alternates,
85
+ # and {https://www.loc.gov/marc/bibliographic/bd264.html 264} and its linked alternate.
86
+ # @param record [MARC::Record]
71
87
  # @return [Array<String>]
72
88
  def publication_show(record)
73
89
  values = record.fields('245').first(1).flat_map { |field| subfield_values(field, 'f') }
@@ -89,10 +105,49 @@ module PennMARC
89
105
  values.compact_blank.uniq
90
106
  end
91
107
 
92
- # Retrieve place of publication for display from {https://www.oclc.org/bibformats/en/7xx/752.html 752 field} and
108
+ # Retrieve publication values for citation
109
+ # {https://www.loc.gov/marc/bibliographic/bd245.html 245},
110
+ # {https://www.loc.gov/marc/bibliographic/bd260.html 260}-262 and their linked alternates,
111
+ # and {https://www.loc.gov/marc/bibliographic/bd264.html 264} and its linked alternate.
112
+ # @param [MARC::Record] record
113
+ # @param [Boolean] with_year: return results with publication year if true
114
+ # @return [Array<String>]
115
+ def publication_citation_show(record, with_year: true)
116
+ values = record.fields('245').first(1).flat_map { |field| subfield_values(field, 'f') }
117
+
118
+ subfields = with_year ? %w[6 8] : %w[6 8 c]
119
+ values += record.fields(%w[260 261 262]).first(1).map do |field|
120
+ join_subfields(field, &subfield_not_in?(subfields))
121
+ end
122
+
123
+ subfields = with_year ? %w[a b c] : %w[a b]
124
+ values += record.fields('264').filter_map do |field|
125
+ next unless field.indicator2 == '1'
126
+
127
+ join_subfields(field, &subfield_in?(subfields))
128
+ end
129
+
130
+ values.compact_blank.uniq
131
+ end
132
+
133
+ # Returns the place of publication for RIS
134
+ # @param [MARC::Record] record
135
+ # @return [Array<String>]
136
+ def publication_ris_place_of_pub(record)
137
+ get_publication_ris_values(record, 'a')
138
+ end
139
+
140
+ # Returns the publisher for RIS
141
+ # @param [MARC::Record] record
142
+ # @return [Array<String>]
143
+ def publication_ris_publisher(record)
144
+ get_publication_ris_values(record, 'b')
145
+ end
146
+
147
+ # Retrieve place of publication for display from {https://www.loc.gov/marc/bibliographic/bd752.html 752 field} and
93
148
  # its linked alternate.
94
149
  # @note legacy version returns array of hash objects including data for display link
95
- # @param [MARC::Record] record
150
+ # @param record [MARC::Record]
96
151
  # @return [Array<String>]
97
152
  def place_of_publication_show(record)
98
153
  record.fields(%w[752 880]).filter_map { |field|
@@ -104,13 +159,32 @@ module PennMARC
104
159
  }.uniq
105
160
  end
106
161
 
162
+ # Retrieves place of publication values for searching. Includes
163
+ # {https://www.loc.gov/marc/bibliographic/bd752.html 752} as well as sf a from
164
+ # {https://www.loc.gov/marc/bibliographic/bd260.html 260} and
165
+ # {https://www.loc.gov/marc/bibliographic/bd264.html 264} with an indicator2 of 1.
166
+ # @param record [MARC::Record]
167
+ # @return [Array<String>]
168
+ def place_of_publication_search(record)
169
+ values = record.fields('260').filter_map do |field|
170
+ join_subfields(field, &subfield_in?(['a']))
171
+ end
172
+ values += record.fields('264').filter_map do |field|
173
+ next unless field.indicator2 == '1'
174
+
175
+ join_subfields(field, &subfield_in?(['a']))
176
+ end
177
+ values + record.fields('752').filter_map { |field|
178
+ join_subfields(field, &subfield_in?(%w[a b c d f g h]))
179
+ }.uniq
180
+ end
181
+
107
182
  private
108
183
 
109
- # base method to retrieve production values from {https://www.oclc.org/bibformats/en/2xx/264.html 264 field} based
110
- # on indicator2.
111
- # distribution and manufacture share the same logic except for indicator2
112
- # @param [MARC::Record] record
113
- # @param [String] indicator2
184
+ # base method to retrieve production values from {https://www.loc.gov/marc/bibliographic/bd264.html 264 field}
185
+ # based on indicator2. "Distribution" and "manufacture" share the same logic except for indicator2.
186
+ # @param record [MARC::Record]
187
+ # @param indicator2 [String]
114
188
  # @return [Array<String>]
115
189
  def get_264_or_880_fields(record, indicator2)
116
190
  values = record.fields('264').filter_map do |field|
@@ -126,6 +200,25 @@ module PennMARC
126
200
  join_subfields(field, &subfield_in?(%w[a b c]))
127
201
  end
128
202
  end
203
+
204
+ # Returns the publication value of the given subfield
205
+ # @param [MARC::Record] record
206
+ # @param [String] subfield
207
+ def get_publication_ris_values(record, subfield)
208
+ values = record.fields('245').first(1).flat_map { |field| subfield_values(field, 'f') }
209
+
210
+ values += record.fields(%w[260 261 262]).first(1).map do |field|
211
+ join_subfields(field, &subfield_in?([subfield]))
212
+ end
213
+
214
+ values += record.fields('264').filter_map do |field|
215
+ next unless field.indicator2 == '1'
216
+
217
+ join_subfields(field, &subfield_in?([subfield]))
218
+ end
219
+
220
+ values.compact_blank.uniq
221
+ end
129
222
  end
130
223
  end
131
224
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'marc'
5
+
6
+ module PennMARC
7
+ module Test
8
+ # Helper methods for use in constructing MARC objects for testing
9
+ module MarcHelpers
10
+ # Return a MARC::XMLReader that will parse a given file and return MARC::Record objects
11
+ # @param [String] filename of MARCXML fixture
12
+ # @return [MARC::Record, NilClass]
13
+ def record_from(filename)
14
+ MARC::XMLReader.new(marc_xml_path(filename)).first
15
+ end
16
+
17
+ # Create an isolated MARC::Subfield object for use in specs or as part of a MARC::Field
18
+ # @param [String] code
19
+ # @param [String] value
20
+ # @return [MARC::Subfield]
21
+ def marc_subfield(code, value)
22
+ MARC::Subfield.new code.to_s, value
23
+ end
24
+
25
+ # Return a new ControlField (000-009)
26
+ # @param [String] tag
27
+ # @param [String] value
28
+ # @return [MARC::ControlField]
29
+ def marc_control_field(tag:, value:)
30
+ MARC::ControlField.new tag, value
31
+ end
32
+
33
+ # Create an isolated MARC::DataField object for use in specs
34
+ # Can pass in tag, indicators and subfields (using simple hash structure). E.g.,
35
+ # marc_field(tag: '650', indicator2: '7'),
36
+ # subfields: { a: 'Tax planning',
37
+ # m: ['Multiple', 'Subfields']
38
+ # z: 'United States.',
39
+ # '0': http://id.loc.gov/authorities/subjects/sh2008112546 }
40
+ # )
41
+ # @param [String (frozen)] tag MARC tag, e.g., 001, 665
42
+ # @param [String (frozen)] indicator1 MARC indicator, e.g., 0
43
+ # @param [String (frozen)] indicator2
44
+ # @param [Hash] subfields hash of subfield values as code => value or code => [value, value]
45
+ # @return [MARC::DataField]
46
+ def marc_field(tag: 'TST', indicator1: ' ', indicator2: ' ', subfields: {})
47
+ subfield_objects = subfields.each_with_object([]) do |(code, value), array|
48
+ Array.wrap(value).map { |v| array << marc_subfield(code, v) }
49
+ end
50
+ MARC::DataField.new tag, indicator1, indicator2, *subfield_objects
51
+ end
52
+
53
+ # Return a MARC::Record containing passed in DataFields
54
+ # @param [Array<MARC::DataField>] fields
55
+ # @param [String, nil] leader
56
+ # @return [MARC::Record]
57
+ def marc_record(fields: [], leader: nil)
58
+ record = MARC::Record.new
59
+ fields.each { |field| record << field }
60
+ record.leader = leader if leader
61
+ record
62
+ end
63
+
64
+ # Mock map for location lookup using Location helper
65
+ # The location codes :dent and :stor are the two outermost keys
66
+ # :specific_location, :library, :display are the inner keys that store location values
67
+ # @example
68
+ # location_map[:stor][:library] #=> 'LIBRA'
69
+ # @return [Hash]
70
+ def location_map
71
+ { dent: { specific_location: 'Levy Dental Medicine Library - Stacks',
72
+ library: ['Health Sciences Libraries', 'Levy Dental Medicine Library'],
73
+ display: 'Levy Dental Medicine Library - Stacks' },
74
+ stor: { specific_location: 'LIBRA',
75
+ library: 'LIBRA',
76
+ display: 'LIBRA' },
77
+ vanp: { specific_location: 'Van Pelt - Stacks',
78
+ library: 'Van Pelt-Dietrich Library Center',
79
+ display: 'Van Pelt Library' } }
80
+ end
81
+ end
82
+ end
83
+ end
data/lib/pennmarc/util.rb CHANGED
@@ -13,16 +13,16 @@ module PennMARC
13
13
  period: /\.\s*$/ }.freeze # TODO: revise to exclude "etc."
14
14
 
15
15
  # Check if a given record has a field present by tag (e.g., '041')
16
- # @param [MARC::Record] record
17
- # @param [String] marc_field
16
+ # @param record [MARC::Record]
17
+ # @param marc_field [String]
18
18
  # @return [Boolean]
19
19
  def field_defined?(record, marc_field)
20
20
  record.select { |field| field.tag == marc_field }.any?
21
21
  end
22
22
 
23
23
  # Join subfields from a field selected based on a provided proc
24
- # @param [MARC::DataField, nil] field
25
- # @param [Proc] selector
24
+ # @param field [MARC::DataField, nil]
25
+ # @param selector [Proc]
26
26
  # @return [String]
27
27
  def join_subfields(field, &selector)
28
28
  return '' unless field
@@ -37,77 +37,75 @@ module PennMARC
37
37
 
38
38
  # returns true if field has a value that matches
39
39
  # passed-in regex and passed in subfield
40
- # @todo example usage
41
- # @param [MARC::DataField] field
42
- # @param [String|Integer|Symbol] subfield
43
- # @param [Regexp] regex
44
- # @return [TrueClass, FalseClass]
40
+ # @param field [MARC::DataField]
41
+ # @param subfield [String|Integer|Symbol]
42
+ # @param regex [Regexp]
43
+ # @return [Boolean, nil]
45
44
  def subfield_value?(field, subfield, regex)
46
45
  field&.any? { |sf| sf.code == subfield.to_s && sf.value =~ regex }
47
46
  end
48
47
 
49
48
  # returns true if field has no value that matches
50
49
  # 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]
50
+ # @param field [MARC::DataField]
51
+ # @param subfield [String|Integer|Symbol]
52
+ # @param regex [Regexp]
53
+ # @return [Boolean, nil]
55
54
  def no_subfield_value_matches?(field, subfield, regex)
56
55
  field&.none? { |sf| sf.code == subfield.to_s && sf.value =~ regex }
57
56
  end
58
57
 
59
58
  # returns true if a given field has a given subfield value in a given array
60
- # TODO: example usage
61
- # @param [MARC:DataField] field
62
- # @param [String|Integer|Symbol] subfield
63
- # @param [Array] array
64
- # @return [TrueClass, FalseClass]
59
+ # @param field [MARC:DataField]
60
+ # @param subfield [String|Integer|Symbol]
61
+ # @param array [Array]
62
+ # @return [Boolean]
65
63
  def subfield_value_in?(field, subfield, array)
66
64
  field.any? { |sf| sf.code == subfield.to_s && sf.value.in?(array) }
67
65
  end
68
66
 
69
67
  # returns true if a given field does not have a given subfield value in a given array
70
- # @param [MARC:DataField] field
71
- # @param [String|Integer|Symbol] subfield
72
- # @param [Array] array
73
- # @return [TrueClass, FalseClass
68
+ # @param field [MARC:DataField]
69
+ # @param subfield [String|Integer|Symbol]
70
+ # @param array [Array]
71
+ # @return [Boolean]
74
72
  def subfield_value_not_in?(field, subfield, array)
75
73
  field.none? { |sf| sf.code == subfield.to_s && sf.value.in?(array) }
76
74
  end
77
75
 
78
76
  # returns a lambda checking if passed-in subfield's code is a member of array
79
- # @param [Array] array
77
+ # @param array [Array]
80
78
  # @return [Proc]
81
79
  def subfield_in?(array)
82
80
  ->(subfield) { array.member?(subfield.code) }
83
81
  end
84
82
 
85
83
  # returns a lambda checking if passed-in subfield's code is NOT a member of array
86
- # @param [Array] array
84
+ # @param array [Array]
87
85
  # @return [Proc]
88
86
  def subfield_not_in?(array)
89
87
  ->(subfield) { !array.member?(subfield.code) }
90
88
  end
91
89
 
92
90
  # Check if a field has a given subfield defined
93
- # @param [MARC::DataField] field
94
- # @param [String|Symbol|Integer] subfield
95
- # @return [TrueClass, FalseClass]
91
+ # @param field [MARC::DataField]
92
+ # @param subfield [String|Symbol|Integer]
93
+ # @return [Boolean]
96
94
  def subfield_defined?(field, subfield)
97
95
  field.any? { |sf| sf.code == subfield.to_s }
98
96
  end
99
97
 
100
98
  # Check if a field does not have a given subfield defined
101
- # @param [MARC::DataField] field
102
- # @param [String|Symbol|Integer] subfield
103
- # @return [TrueClass, FalseClass]
99
+ # @param field [MARC::DataField]
100
+ # @param subfield [String|Symbol|Integer]
101
+ # @return [Boolean]
104
102
  def subfield_undefined?(field, subfield)
105
103
  field.none? { |sf| sf.code == subfield.to_s }
106
104
  end
107
105
 
108
106
  # Gets all subfield values for a subfield in a given field
109
- # @param [MARC::DataField] field
110
- # @param [String|Symbol] subfield as a string or symbol
107
+ # @param field [MARC::DataField]
108
+ # @param subfield [String|Symbol] as a string or symbol
111
109
  # @return [Array] subfield values for given subfield code
112
110
  def subfield_values(field, subfield)
113
111
  field.filter_map do |sf|
@@ -120,9 +118,9 @@ module PennMARC
120
118
  end
121
119
 
122
120
  # Get all subfield values for a provided subfield from any occurrence of a provided tag/tags
123
- # @param [String|Array] tag tags to consider
124
- # @param [String|Symbol] subfield to take the values from
125
- # @param [MARC::Record] record source
121
+ # @param tag [String|Array] tags to consider
122
+ # @param subfield [String|Symbol] to take the values from
123
+ # @param record [MARC::Record]
126
124
  # @return [Array] array of subfield values
127
125
  def subfield_values_for(tag:, subfield:, record:)
128
126
  record.fields(tag).flat_map do |field|
@@ -130,24 +128,43 @@ module PennMARC
130
128
  end
131
129
  end
132
130
 
133
- # @param [Symbol|String] trailer to target for removal
134
- # @param [String] string to modify
131
+ # Trim punctuation method extracted from Traject macro, to ensure consistent output
132
+ # @param string [String]
133
+ # @return [String] string with relevant punctuation removed
134
+ def trim_punctuation(string)
135
+ return string unless string
136
+
137
+ string = string.sub(%r{ *[ ,/;:] *\Z}, '')
138
+
139
+ # trailing period if it is preceded by at least three letters (possibly preceded and followed by whitespace)
140
+ string = string.sub(/( *[[:word:]]{3,})\. *\Z/, '\1')
141
+
142
+ # single square bracket characters if they are the start and/or end chars and there are no internal square
143
+ # brackets.
144
+ string = string.sub(/\A\[?([^\[\]]+)\]?\Z/, '\1')
145
+
146
+ # trim any leading or trailing whitespace
147
+ string.strip
148
+ end
149
+
150
+ # @param trailer [Symbol|String] to target for removal
151
+ # @param string [String] to modify
135
152
  # @return [String]
136
153
  def trim_trailing(trailer, string)
137
154
  string.sub TRAILING_PUNCTUATIONS_PATTERNS[trailer.to_sym], ''
138
155
  end
139
156
 
140
157
  # trim trailing punctuation, manipulating string in place
141
- # @param [Symbol|String] trailer to target for removal
142
- # @param [String] string to modify
158
+ # @param trailer [Symbol|String] to target for removal
159
+ # @param string [String] to modify
143
160
  # @return [String, Nil] string to modify
144
161
  def trim_trailing!(trailer, string)
145
162
  string.sub! TRAILING_PUNCTUATIONS_PATTERNS[trailer.to_sym], ''
146
163
  end
147
164
 
148
165
  # Intelligently append given punctuation to the end of a string
149
- # @param [Symbol] trailer
150
- # @param [String] string
166
+ # @param trailer [Symbol]
167
+ # @param string [String]
151
168
  # @return [String]
152
169
  def append_trailing(trailer, string)
153
170
  return string if string.end_with?('.', '-')
@@ -165,9 +182,9 @@ module PennMARC
165
182
  # translations of title values. A common need is to extract subfields as selected by
166
183
  # passed-in block from 880 datafield that has a particular subfield 6 value.
167
184
  # See: https://www.loc.gov/marc/bibliographic/bd880.html
168
- # @param [MARC::Record] record
169
- # @param [String|Array] subfield6_value either a string to look for in sub6 or an array of them
170
- # @param [Proc] selector takes a subfield as argument, returns a boolean
185
+ # @param record [MARC::Record]
186
+ # @param subfield6_value [String|Array] either a string to look for in sub6 or an array of them
187
+ # @param selector [Proc] takes a subfield as argument, returns a boolean
171
188
  # @return [Array] array of linked alternates
172
189
  def linked_alternate(record, subfield6_value, &selector)
173
190
  record.fields('880').filter_map do |field|
@@ -180,8 +197,8 @@ module PennMARC
180
197
  # Common case of wanting to extract all the subfields besides 6 or 8,
181
198
  # from 880 datafield that has a particular subfield 6 value. We exclude 6 because
182
199
  # that value is the linkage ID itself and 8 because... IDK
183
- # @param [MARC::Record] record
184
- # @param [String|Array] subfield6_value either a string to look for in sub6 or an array of them
200
+ # @param record [MARC::Record]
201
+ # @param subfield6_value [String|Array] either a string to look for in sub6 or an array of them
185
202
  # @return [Array] array of linked alternates without 8 or 6 values
186
203
  def linked_alternate_not_6_or_8(record, subfield6_value)
187
204
  excluded_subfields = %w[6 8]
@@ -191,8 +208,8 @@ module PennMARC
191
208
  end
192
209
 
193
210
  # Returns the non-6,8 subfields from a datafield and its 880 link.
194
- # @param [MARC::Record] record
195
- # @param [String] tag
211
+ # @param record [MARC::Record]
212
+ # @param tag [String]
196
213
  # @return [Array<String>] values
197
214
  def datafield_and_linked_alternate(record, tag)
198
215
  record.fields(tag).filter_map { |field|
@@ -201,23 +218,23 @@ module PennMARC
201
218
  end
202
219
 
203
220
  # Get the substring of a string up to a given target character
204
- # @param [Object] string to split
205
- # @param [Object] target character to split upon
221
+ # @param string [Object] to split
222
+ # @param target [Object] character to split upon
206
223
  # @return [String (frozen)]
207
224
  def substring_before(string, target)
208
225
  string.scan(target).present? ? string.split(target, 2).first : ''
209
226
  end
210
227
 
211
228
  # Get the substring of a string after the first occurrence of a target character
212
- # @param [Object] string to split
213
- # @param [Object] target character to split upon
229
+ # @param string [Object] to split
230
+ # @param target [Object] character to split upon
214
231
  # @return [String (frozen)]
215
232
  def substring_after(string, target)
216
233
  string.scan(target).present? ? string.split(target, 2).second : ''
217
234
  end
218
235
 
219
236
  # Join array and normalizing extraneous spaces
220
- # @param [Array] array
237
+ # @param array [Array]
221
238
  # @return [String]
222
239
  def join_and_squish(array)
223
240
  array.join(' ').squish
@@ -225,7 +242,7 @@ module PennMARC
225
242
 
226
243
  # If there's a subfield i, extract its value, and if there's something
227
244
  # in parentheses in that value, extract that.
228
- # @param [MARC::Field] field
245
+ # @param field [MARC::Field]
229
246
  # @return [String] subfield i without parentheses value
230
247
  def remove_paren_value_from_subfield_i(field)
231
248
  val = field.filter_map { |sf|
@@ -243,19 +260,19 @@ module PennMARC
243
260
 
244
261
  # Translate a relator code using mapping
245
262
  # @todo handle case of receiving a URI? E.g., http://loc.gov/relator/aut
246
- # @param [String, NilClass] relator_code
247
- # @param [Hash] mapping
263
+ # @param relator_code [String, NilClass]
264
+ # @param mapping [Hash]
248
265
  # @return [String, NilClass] full relator string
249
266
  def translate_relator(relator_code, mapping)
250
267
  return if relator_code.blank?
251
268
 
252
- mapping[relator_code.to_sym]
269
+ mapping[relator_code&.to_sym]
253
270
  end
254
271
 
255
272
  # Get 650 & 880 for Provenance and Chronology: prefix should be 'PRO' or 'CHR' and may be preceded by a '%'
256
273
  # @note 11/2018: do not display $5 in PRO or CHR subjs
257
- # @param [MARC::Record] record
258
- # @param [String] prefix to select from subject field
274
+ # @param record [MARC::Record]
275
+ # @param prefix [String] to select from subject field
259
276
  # @return [Array] array of values
260
277
  def prefixed_subject_and_alternate(record, prefix)
261
278
  record.fields(%w[650 880]).filter_map { |field|
@@ -273,16 +290,16 @@ module PennMARC
273
290
 
274
291
  # Does the given field specify an allowed source code?
275
292
  #
276
- # @param [MARC::DataField] field
293
+ # @param field [MARC::DataField]
277
294
  # @return [Boolean]
278
295
  def valid_subject_genre_source_code?(field)
279
296
  subfield_value_in?(field, '2', PennMARC::HeadingControl::ALLOWED_SOURCE_CODES)
280
297
  end
281
298
 
282
299
  # 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]
300
+ # @param field [MARC::Field]
301
+ # @param tags [Array<String>]
302
+ # @return [Boolean]
286
303
  def field_or_its_linked_alternate?(field, tags)
287
304
  return true if field.tag.in? tags
288
305
  return true if field.tag == '880' && subfield_value?(field, '6', /^(#{tags.join('|')})/)
@@ -291,7 +308,7 @@ module PennMARC
291
308
  end
292
309
 
293
310
  # Match any open dates ending a given string to determine join separator for relator term in 1xx/7xx fields.
294
- # @param [String] str
311
+ # @param str [String]
295
312
  # @return [String (frozen)]
296
313
  def relator_join_separator(str)
297
314
  /\b\d+-\z/.match?(str) ? ' ' : ', '
@@ -302,7 +319,7 @@ module PennMARC
302
319
  # {https://www.loc.gov/marc/bibliographic/bd111.html 111}, {https://www.loc.gov/marc/bibliographic/bd411.html 411},
303
320
  # {https://www.loc.gov/marc/bibliographic/bd611.html 611}, {https://www.loc.gov/marc/bibliographic/bd711.html 711},
304
321
  # {https://www.loc.gov/marc/bibliographic/bd811.html 811}
305
- # @param [MARC:Field] field
322
+ # @param field [MARC:Field]
306
323
  # @return [String (frozen)]
307
324
  def relator_term_subfield(field)
308
325
  field_or_its_linked_alternate?(field, %w[111 411 611 711 811]) ? 'j' : 'e'
@@ -311,10 +328,10 @@ module PennMARC
311
328
  # Appends a relator value to the given string. It prioritizes relator codes found in subfield $4
312
329
  # and falls back to the specified relator term subfield (defaulting to 'e') if no valid codes are found in $4.
313
330
  # 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
331
+ # @param field [MARC::Field] where relator values are stored
332
+ # @param joined_subfields [String] the string to which the relator is appended
333
+ # @param relator_term_sf [String] MARC subfield that stores relator term
334
+ # @param relator_map [Hash]
318
335
  # @return [String]
319
336
  def append_relator(field:, joined_subfields:, relator_term_sf:, relator_map: Mappers.relator)
320
337
  joined_subfields = trim_trailing(:comma, joined_subfields)
@@ -329,5 +346,17 @@ module PennMARC
329
346
 
330
347
  [joined_subfields, relator].compact_blank.join(join_separator).squish
331
348
  end
349
+
350
+ # Returns a relator value of the given field. Like append_relator, it prioritizes relator codes found in subfileld
351
+ # $4 and falls back to the specified relator term subfield relator_term_sf if no valid codes are found in $4
352
+ # @param [MARC::Field] field where relator values are stored
353
+ # @param [String] relator_term_sf MARC subfield that stores relator term
354
+ # @param [Hash] relator_map
355
+ # @return [String]
356
+ def relator(field:, relator_term_sf:, relator_map: Mappers.relator)
357
+ relator = subfield_values(field, '4').filter_map { |code| translate_relator(code, relator_map) }
358
+ relator = subfield_values(field, relator_term_sf) if relator.blank?
359
+ relator.join
360
+ end
332
361
  end
333
362
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PennMARC
4
- VERSION = '1.0.25'
4
+ VERSION = '1.0.26'
5
5
  end
data/lib/pennmarc.rb CHANGED
@@ -2,5 +2,12 @@
2
2
 
3
3
  $LOAD_PATH.unshift(__dir__) unless $LOAD_PATH.include?(__dir__)
4
4
 
5
+ module PennMARC
6
+ # Autoload MARC helpers
7
+ module Test
8
+ autoload :MarcHelpers, 'pennmarc/test/marc_helpers'
9
+ end
10
+ end
11
+
5
12
  require_relative 'pennmarc/parser'
6
13
  require 'library_stdnums'
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe 'PennMARC::Access' do
4
- include MarcSpecHelpers
5
-
6
4
  let(:helper) { PennMARC::Access }
7
5
 
8
6
  describe '.facet' do
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  describe 'PennMARC::Citation' do
4
- include MarcSpecHelpers
5
-
6
4
  let(:helper) { PennMARC::Citation }
7
5
 
8
6
  describe '.cited_in_show' do