pennmarc 1.0.25 → 1.0.26
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/.rubocop_todo.yml +8 -9
- data/lib/pennmarc/helpers/creator.rb +139 -65
- data/lib/pennmarc/helpers/edition.rb +5 -3
- data/lib/pennmarc/helpers/note.rb +24 -19
- data/lib/pennmarc/helpers/production.rb +113 -20
- data/lib/pennmarc/test/marc_helpers.rb +83 -0
- data/lib/pennmarc/util.rb +98 -69
- data/lib/pennmarc/version.rb +1 -1
- data/lib/pennmarc.rb +7 -0
- data/spec/lib/pennmarc/helpers/access_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/citation_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/classification_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/creator_spec.rb +103 -2
- data/spec/lib/pennmarc/helpers/database_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/date_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/edition_spec.rb +4 -2
- data/spec/lib/pennmarc/helpers/format_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/genre_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/identifer_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/inventory_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/language_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/link_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/location_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/note_spec.rb +22 -29
- data/spec/lib/pennmarc/helpers/production_spec.rb +121 -22
- data/spec/lib/pennmarc/helpers/relation_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/series_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/subject_spec.rb +0 -2
- data/spec/lib/pennmarc/helpers/title_spec.rb +0 -2
- data/spec/lib/pennmarc/marc_util_spec.rb +0 -2
- data/spec/lib/pennmarc/parser_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/support/fixture_helpers.rb +10 -0
- metadata +4 -3
- 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.
|
8
|
-
# @param [MARC::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
|
15
|
-
#
|
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.
|
22
|
-
# @param [MARC::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.
|
30
|
-
# {https://www.
|
31
|
-
# @param [MARC::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.
|
68
|
-
# {https://www.
|
69
|
-
# and {https://www.
|
70
|
-
# @param [MARC::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
|
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]
|
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.
|
110
|
-
# on indicator2.
|
111
|
-
#
|
112
|
-
# @param [
|
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]
|
17
|
-
# @param [String]
|
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]
|
25
|
-
# @param [Proc]
|
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
|
-
# @
|
41
|
-
# @param [
|
42
|
-
# @param [
|
43
|
-
# @
|
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]
|
52
|
-
# @param [String|Integer|Symbol]
|
53
|
-
# @param [Regexp]
|
54
|
-
# @return [
|
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
|
-
#
|
61
|
-
# @param [
|
62
|
-
# @param [
|
63
|
-
# @
|
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]
|
71
|
-
# @param [String|Integer|Symbol]
|
72
|
-
# @param [Array]
|
73
|
-
# @return [
|
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]
|
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]
|
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]
|
94
|
-
# @param [String|Symbol|Integer]
|
95
|
-
# @return [
|
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]
|
102
|
-
# @param [String|Symbol|Integer]
|
103
|
-
# @return [
|
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]
|
110
|
-
# @param [String|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]
|
124
|
-
# @param [String|Symbol]
|
125
|
-
# @param [MARC::Record]
|
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
|
-
#
|
134
|
-
# @param [String]
|
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]
|
142
|
-
# @param [String]
|
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]
|
150
|
-
# @param [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]
|
169
|
-
# @param [String|Array]
|
170
|
-
# @param [Proc]
|
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]
|
184
|
-
# @param [String|Array]
|
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]
|
195
|
-
# @param [String]
|
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]
|
205
|
-
# @param [Object]
|
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]
|
213
|
-
# @param [Object]
|
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]
|
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]
|
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]
|
247
|
-
# @param [Hash]
|
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
|
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]
|
258
|
-
# @param [String]
|
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]
|
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]
|
284
|
-
# @param [Array<String>]
|
285
|
-
# @return [
|
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]
|
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]
|
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]
|
315
|
-
# @param [String]
|
316
|
-
# @param [String]
|
317
|
-
# @param [Hash]
|
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
|
data/lib/pennmarc/version.rb
CHANGED
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'
|