pennmarc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +23 -0
  6. data/Gemfile.lock +119 -0
  7. data/README.md +82 -0
  8. data/legacy/indexer.rb +568 -0
  9. data/legacy/marc.rb +2964 -0
  10. data/legacy/test_file_output.json +49 -0
  11. data/lib/pennmarc/encoding_level.rb +43 -0
  12. data/lib/pennmarc/enriched_marc.rb +36 -0
  13. data/lib/pennmarc/heading_control.rb +11 -0
  14. data/lib/pennmarc/helpers/citation.rb +31 -0
  15. data/lib/pennmarc/helpers/creator.rb +237 -0
  16. data/lib/pennmarc/helpers/database.rb +89 -0
  17. data/lib/pennmarc/helpers/date.rb +85 -0
  18. data/lib/pennmarc/helpers/edition.rb +90 -0
  19. data/lib/pennmarc/helpers/format.rb +312 -0
  20. data/lib/pennmarc/helpers/genre.rb +71 -0
  21. data/lib/pennmarc/helpers/helper.rb +11 -0
  22. data/lib/pennmarc/helpers/identifier.rb +134 -0
  23. data/lib/pennmarc/helpers/language.rb +37 -0
  24. data/lib/pennmarc/helpers/link.rb +12 -0
  25. data/lib/pennmarc/helpers/location.rb +97 -0
  26. data/lib/pennmarc/helpers/note.rb +132 -0
  27. data/lib/pennmarc/helpers/production.rb +131 -0
  28. data/lib/pennmarc/helpers/relation.rb +135 -0
  29. data/lib/pennmarc/helpers/series.rb +118 -0
  30. data/lib/pennmarc/helpers/subject.rb +304 -0
  31. data/lib/pennmarc/helpers/title.rb +197 -0
  32. data/lib/pennmarc/mappings/language.yml +516 -0
  33. data/lib/pennmarc/mappings/locations.yml +1801 -0
  34. data/lib/pennmarc/mappings/relator.yml +263 -0
  35. data/lib/pennmarc/parser.rb +177 -0
  36. data/lib/pennmarc/util.rb +240 -0
  37. data/lib/pennmarc.rb +6 -0
  38. data/pennmarc.gemspec +22 -0
  39. data/spec/fixtures/marcxml/test.xml +167 -0
  40. data/spec/lib/pennmarc/helpers/citation_spec.rb +27 -0
  41. data/spec/lib/pennmarc/helpers/creator_spec.rb +183 -0
  42. data/spec/lib/pennmarc/helpers/database_spec.rb +60 -0
  43. data/spec/lib/pennmarc/helpers/date_spec.rb +105 -0
  44. data/spec/lib/pennmarc/helpers/edition_spec.rb +38 -0
  45. data/spec/lib/pennmarc/helpers/format_spec.rb +200 -0
  46. data/spec/lib/pennmarc/helpers/genre_spec.rb +89 -0
  47. data/spec/lib/pennmarc/helpers/identifer_spec.rb +105 -0
  48. data/spec/lib/pennmarc/helpers/language_spec.rb +30 -0
  49. data/spec/lib/pennmarc/helpers/location_spec.rb +70 -0
  50. data/spec/lib/pennmarc/helpers/note_spec.rb +233 -0
  51. data/spec/lib/pennmarc/helpers/production_spec.rb +193 -0
  52. data/spec/lib/pennmarc/helpers/relation_spec.rb +120 -0
  53. data/spec/lib/pennmarc/helpers/subject_spec.rb +262 -0
  54. data/spec/lib/pennmarc/helpers/title_spec.rb +169 -0
  55. data/spec/lib/pennmarc/marc_util_spec.rb +206 -0
  56. data/spec/lib/pennmarc/parser_spec.rb +13 -0
  57. data/spec/spec_helper.rb +104 -0
  58. data/spec/support/marc_spec_helpers.rb +84 -0
  59. metadata +171 -0
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PennMARC
4
+ # Methods that return Library and Location values from Alma enhanced MARC fields
5
+ class Location < Helper
6
+ class << self
7
+ # Retrieves library location from enriched marc 'itm' or 'hld' fields, giving priority to the item location over
8
+ # the holdings location. Returns item's location if available. Otherwise, returns holding's location.
9
+ # {PennMARC::EnrichedMarc} maps enriched marc fields and subfields created during Alma publishing.
10
+ # @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/
11
+ # Alma documentation for these added fields
12
+ # @param [MARC::Record] record
13
+ # @param [Hash] location_map hash with location_code as key and location hash as value
14
+ # @return [Array<String>] Array of library locations retrieved from location_map
15
+ def library(record, location_map)
16
+ location(record: record, location_map: location_map, display_value: 'library')
17
+ end
18
+
19
+ # Retrieves the specific location from enriched marc 'itm' or 'hld' fields, giving priority to the item location
20
+ # over the holdings location. Returns item library location if available. Otherwise, returns holdings library
21
+ # location.
22
+ # {PennMARC::EnrichedMarc} maps enriched marc fields and subfields created during Alma publishing.
23
+ # @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/
24
+ # Alma documentation for these added fields
25
+ # @param [MARC::Record] record
26
+ # @param [Hash] location_map hash with location_code as key and location hash as value
27
+ # @return [Array<String>] Array of specific locations retrieved from location_map
28
+ def specific_location(record, location_map)
29
+ location(record: record, location_map: location_map, display_value: 'specific_location')
30
+ end
31
+
32
+ # Base method to retrieve location data from enriched marc 'itm' or 'hld' fields, giving priority to the item
33
+ # location over the holdings location. Returns item location if available. Otherwise, returns holdings location.
34
+ # {PennMARC::EnrichedMarc} maps enriched marc fields and subfields created during Alma publishing.
35
+ # @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/
36
+ # Alma documentation for these added fields
37
+ # @param [MARC::Record] record
38
+ # @param [Hash] location_map hash with location_code as key and location hash as value
39
+ # @param [Symbol | String] display_value field in location hash to retrieve
40
+ # @return [Array<String>]
41
+ def location(record:, location_map:, display_value:)
42
+ # get enriched marc location tag and subfield code
43
+ location_tag_and_subfield_code(record) => {tag:, subfield_code:}
44
+
45
+ locations = record.fields(tag).flat_map do |field|
46
+ field.filter_map do |subfield|
47
+ # skip unless subfield code does not match enriched marc tag subfield code
48
+ next unless subfield.code == subfield_code
49
+
50
+ # skip if subfield value is 'web'
51
+ # we don't facet for 'web' which is the 'Penn Library Web' location used in Voyager.
52
+ # this location should eventually go away completely with data cleanup in Alma.
53
+ next if subfield.value == 'web'
54
+
55
+ # skip unless subfield value is a key in location_map
56
+ # sometimes "happening locations" are mistakenly used in holdings records.
57
+ # that's a data problem that should be fixed.
58
+ # here, if we encounter a code we can't map, we ignore it, for faceting purposes
59
+ next unless location_map.key?(subfield.value.to_sym)
60
+
61
+ location_map[subfield.value.to_sym][display_value.to_sym]
62
+ end.flatten.compact_blank
63
+ end.uniq
64
+ locations << 'Online library' if record.fields(PennMARC::EnrichedMarc::TAG_ELECTRONIC_INVENTORY).any?
65
+ locations
66
+ end
67
+
68
+ private
69
+
70
+ # Determine enriched marc location tag ('itm' or 'hld') and subfield code, giving priority to using 'itm' tag and
71
+ # subfield.
72
+ # @param [MARC::Record]
73
+ # @return [Hash<String, String>] containing location tag and subfield code
74
+ # - `:tag` (String): The enriched marc location tag
75
+ # - `:subfield_code` (String): The relevant subfield code
76
+ def location_tag_and_subfield_code(record)
77
+ # in holdings records, the shelving location is always the permanent location.
78
+ # in item records, the current location takes into account
79
+ # temporary locations and permanent locations. if you update the item's perm location,
80
+ # the holding's shelving location changes.
81
+ #
82
+ # Since item records may reflect locations more accurately, we use them if they exist;
83
+ # if not, we use the holdings.
84
+
85
+ tag = PennMARC::EnrichedMarc::TAG_HOLDING
86
+ subfield_code = PennMARC::EnrichedMarc::SUB_HOLDING_SHELVING_LOCATION
87
+
88
+ if record.fields(PennMARC::EnrichedMarc::TAG_ITEM).any?
89
+ tag = PennMARC::EnrichedMarc::TAG_ITEM
90
+ subfield_code = PennMARC::EnrichedMarc::SUB_ITEM_CURRENT_LOCATION
91
+ end
92
+
93
+ { tag: tag, subfield_code: subfield_code }
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PennMARC
4
+ # Extracts notes from {https://www.oclc.org/bibformats/en/5xx.html 5xx} fields (mostly).
5
+ class Note < Helper
6
+ class << self
7
+ # Retrieve notes for display from fields {https://www.oclc.org/bibformats/en/5xx/500.html 500},
8
+ # {https://www.oclc.org/bibformats/en/5xx/502.html 502}, {https://www.oclc.org/bibformats/en/5xx/504.html 504},
9
+ # {https://www.oclc.org/bibformats/en/5xx/515.html 515}, {https://www.oclc.org/bibformats/en/5xx/518.html 518}
10
+ # {https://www.oclc.org/bibformats/en/5xx/525.html 525}, {https://www.oclc.org/bibformats/en/5xx/533.html 533},
11
+ # {https://www.oclc.org/bibformats/en/5xx/550.html 550}, {https://www.oclc.org/bibformats/en/5xx/580.html 580},
12
+ # {https://www.oclc.org/bibformats/en/5xx/586.html 586}, {https://www.oclc.org/bibformats/en/5xx/588.html 588},
13
+ # and their linked alternates.
14
+ # @todo legacy implementation used conditional to separate join logic for 588 field. However, this doesn't seem
15
+ # necessary because 588 only has subfields 'a', '5', '6', and '8'. Do we need to look into this?
16
+ # @param [MARC::Record] record
17
+ # @return [Array<String>]
18
+ def notes_show(record)
19
+ notes_fields = %w[500 502 504 515 518 525 533 550 580 586 588]
20
+ record.fields(notes_fields + ['880']).filter_map do |field|
21
+ next if field.tag == '880' && subfield_value_not_in?(field, '6', notes_fields)
22
+
23
+ join_subfields(field, &subfield_not_in?(%w[5 6 8]))
24
+ end
25
+ end
26
+
27
+ # Retrieve local notes for display from fields {https://www.oclc.org/bibformats/en/5xx/561.html 561},
28
+ # {https://www.oclc.org/bibformats/en/5xx/562.html 562}, {https://www.oclc.org/bibformats/en/5xx/563.html 563},
29
+ # {https://www.oclc.org/bibformats/en/5xx/585.html 585}, {https://www.oclc.org/bibformats/en/5xx/590.html 590}.
30
+ # Includes linked alternates except for 561.
31
+ # @param [MARC::Record] record
32
+ # @return [Array<String>]
33
+ def local_notes_show(record)
34
+ local_notes = record.fields('561').filter_map do |field|
35
+ next unless subfield_value?(field, 'a', /^Athenaeum copy: /)
36
+
37
+ join_subfields(field, &subfield_in?(%w[a]))
38
+ end
39
+
40
+ additional_fields = %w[562 563 585 590]
41
+
42
+ local_notes + record.fields(additional_fields + ['880']).filter_map do |field|
43
+ next if field.tag == '880' && subfield_value_not_in?(field, '6', additional_fields)
44
+
45
+ join_subfields(field, &subfield_not_in?(%w[5 6 8]))
46
+ end
47
+ end
48
+
49
+ # Retrieve provenance notes for display from fields {https://www.oclc.org/bibformats/en/5xx/561.html 561} and
50
+ # prefixed subject field {https://www.oclc.org/bibformats/en/6xx/650.html 650} and its linked alternate.
51
+ # Ignores 561 fields with subfield 'a' values that begin with 'Athenaeum copy: ' and 650 fields where subfield 'a'
52
+ # does not have the prefix 'PRO'.
53
+ # @param [MARC::Record] record
54
+ # @return [Array<String>]
55
+ def provenance_show(record)
56
+ provenance_notes = record.fields(%w[561 880]).filter_map do |field|
57
+ next unless field.indicator1.in?(['1', '', ' '])
58
+
59
+ next unless field.indicator2.in?([' ', ''])
60
+
61
+ next if field.tag == '880' && subfield_value_not_in?(field, '6', %w[561])
62
+
63
+ next if subfield_value?(field, 'a', /^Athenaeum copy: /)
64
+
65
+ join_subfields(field, &subfield_in?(%w[a]))
66
+ end
67
+ provenance_notes + prefixed_subject_and_alternate(record, 'PRO')
68
+ end
69
+
70
+ # Retrieve contents notes for display from fields {https://www.oclc.org/bibformats/en/5xx/505.html 505} and
71
+ # its linked alternate.
72
+ # @param [MARC::Record] record
73
+ # @return [Array<String>]
74
+ def contents_show(record)
75
+ record.fields(%w[505 880]).filter_map do |field|
76
+ next if field.tag == '880' && subfield_value_not_in?(field, '6', %w[505])
77
+
78
+ join_subfields(field, &subfield_not_in?(%w[6 8])).split('--')
79
+ end.flatten
80
+ end
81
+
82
+ # Retrieve access restricted notes for display from field {https://www.oclc.org/bibformats/en/5xx/506.html 506}.
83
+ # @param [MARC::Record] record
84
+ # @return [Array<String>]
85
+ def access_restriction_show(record)
86
+ record.fields('506').filter_map do |field|
87
+ join_subfields(field, &subfield_not_in?(%w[5 6]))
88
+ end
89
+ end
90
+
91
+ # Retrieve finding aid notes for display from field {https://www.oclc.org/bibformats/en/5xx/555.html 555} and its
92
+ # linked alternate.
93
+ # @param [MARC::Record] record
94
+ # @return [Array<String>]
95
+ def finding_aid_show(record)
96
+ datafield_and_linked_alternate(record, '555')
97
+ end
98
+
99
+ # Retrieve participant notes for display from field {https://www.oclc.org/bibformats/en/5xx/511.html 511} and its
100
+ # linked alternate.
101
+ # @param [MARC::Record] record
102
+ # @return [Array<String>]
103
+ def participant_show(record)
104
+ datafield_and_linked_alternate(record, '511')
105
+ end
106
+
107
+ # Retrieve credits notes for display from field {https://www.oclc.org/bibformats/en/5xx/508.html 508} and its
108
+ # linked alternate.
109
+ # @param [MARC::Record] record
110
+ # @return [Array<String>]
111
+ def credits_show(record)
112
+ datafield_and_linked_alternate(record, '508')
113
+ end
114
+
115
+ # Retrieve biography notes for display from field {https://www.oclc.org/bibformats/en/5xx/545.html 545} and its
116
+ # linked alternate.
117
+ # @param [MARC::Record] record
118
+ # @return [Array<String>]
119
+ def biography_show(record)
120
+ datafield_and_linked_alternate(record, '545')
121
+ end
122
+
123
+ # Retrieve summary notes for display from field {https://www.oclc.org/bibformats/en/5xx/520.html 520} and its
124
+ # linked alternate.
125
+ # @param [MARC::Record] record
126
+ # @return [Array<String>]
127
+ def summary_show(record)
128
+ datafield_and_linked_alternate(record, '520')
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PennMARC
4
+ # Extracts data related to a resource's production, distribution, manufacture, and publication.
5
+ class Production < Helper
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
9
+ # @return [Array<String>]
10
+ def show(record)
11
+ get_264_or_880_fields(record, '0')
12
+ end
13
+
14
+ # Retrieve distribution values for display from {https://www.oclc.org/bibformats/en/2xx/264.html 264 field}.
15
+ # @param [MARC::Record] record
16
+ # @return [Array<String>]
17
+ def distribution_show(record)
18
+ get_264_or_880_fields(record, '2')
19
+ end
20
+
21
+ # Retrieve manufacture values for display from {https://www.oclc.org/bibformats/en/2xx/264.html 264 field}.
22
+ # @param [MARC::Record] record
23
+ # @return [Array<String>]
24
+ def manufacture_show(record)
25
+ get_264_or_880_fields(record, '3')
26
+ end
27
+
28
+ # 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
32
+ # @return [Array<String>]
33
+ def publication_values(record)
34
+ # first get inclusive dates
35
+ values = record.fields('245').first(1).flat_map { |field| subfield_values(field, 'f') }
36
+ added_2xx = record.fields(%w[260 261 262])
37
+ .first(1)
38
+ .map do |field|
39
+ join_subfields(field, &subfield_not_in?(['6'])).squish
40
+ end
41
+
42
+ if added_2xx.present?
43
+ values += added_2xx
44
+ else
45
+ fields264 = record.fields('264')
46
+
47
+ pub_place_name = fields264
48
+ .find(-> { [] }) { |field| field.indicator2 == '1' }
49
+ .filter_map { |sf| sf.value if sf.code.in?(%w[a b]) }
50
+
51
+ pub_date = fields264
52
+ .find(-> { [] }) { |field| field.indicator2 == '1' }
53
+ .filter_map { |sf| sf.value if sf.code.in?(['c']) }
54
+
55
+ copyright_date = fields264
56
+ .find(-> { [] }) { |field| field.indicator2 == '4' }
57
+ .filter_map { |sf| "#{pub_date.present? ? ', ' : ''}#{sf.value}" if sf.code.in?(['c']) }
58
+
59
+ joined264 = Array.wrap((pub_place_name + pub_date + copyright_date).join(' '))
60
+
61
+ values += joined264
62
+ end
63
+ values.filter_map { |value| value&.strip }
64
+ end
65
+
66
+ # 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
71
+ # @return [Object]
72
+ def publication_show(record)
73
+ values = record.fields('245').first(1).flat_map { |field| subfield_values(field, 'f') }
74
+
75
+ values += record.fields(%w[260 261 262]).first(1).map do |field|
76
+ join_subfields(field, &subfield_not_in?(%w[6 8]))
77
+ end
78
+
79
+ values += record.fields('880').filter_map { |field| field if subfield_value?(field, '6', /^(260|261|262)/) }
80
+ .first(1).map { |field| join_subfields(field, &subfield_not_in?(%w[6 8])) }
81
+
82
+ values += record.fields('880').filter_map do |field|
83
+ next unless subfield_value?(field, '6', /^245/)
84
+
85
+ join_subfields(field, &subfield_in?(['f']))
86
+ end
87
+
88
+ values += get_264_or_880_fields(record, '1')
89
+ values.compact_blank
90
+ end
91
+
92
+ # Retrieve place of publication for display from {https://www.oclc.org/bibformats/en/7xx/752.html 752 field} and
93
+ # its linked alternate.
94
+ # @note legacy version returns array of hash objects including data for display link
95
+ # @param [MARC::Record] record
96
+ # @return [Object]
97
+ def place_of_publication_show(record)
98
+ record.fields(%w[752 880]).filter_map do |field|
99
+ next if field.tag == '880' && subfield_values(field, '6').exclude?('752')
100
+
101
+ place = join_subfields(field, &subfield_not_in?(%w[6 8 e w]))
102
+ place_extra = join_subfields(field, &subfield_in?(%w[e w]))
103
+ "#{place} #{place_extra}"
104
+ end
105
+ end
106
+
107
+ private
108
+
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
114
+ # @return [Array<String>]
115
+ def get_264_or_880_fields(record, indicator2)
116
+ values = record.fields('264').filter_map do |field|
117
+ next unless field.indicator2 == indicator2
118
+
119
+ join_subfields(field, &subfield_in?(%w[a b c]))
120
+ end
121
+ values + record.fields('880').filter_map do |field|
122
+ next unless field.indicator2 == indicator2
123
+
124
+ next unless subfield_value?(field, '6', /^264/)
125
+
126
+ join_subfields(field, &subfield_in?(%w[a b c]))
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PennMARC
4
+ # These MARC parsing method are grouped in virtue of their role as describing the relationship of a record to other
5
+ # records.
6
+ class Relation < Helper
7
+ class << self
8
+ CHRONOLOGY_PREFIX = 'CHR'
9
+
10
+ RELATED_WORK_FIELDS = %w[700 710 711 730].freeze
11
+ CONTAINS_FIELDS = %w[700 710 711 730 740].freeze
12
+
13
+ # Get values for "{https://www.oclc.org/bibformats/en/7xx/773.html Host Item}" for this record. Values contained
14
+ # in this field should be sufficient to locate host item record.
15
+ #
16
+ # @param [MARC::Record] record
17
+ # @return [Array] contained in values for display
18
+ def contained_in_show(record)
19
+ record.fields('773').map do |field|
20
+ join_subfields(field, &subfield_not_in?(%w[6 7 8 w]))
21
+ end
22
+ end
23
+
24
+ # Get "chronology" information from specially-prefixed 650 (subject) fields
25
+ # @todo why do we stuff chronology data in a 650 field?
26
+ # @param [MARC::Record] record
27
+ # @return [Array] array of chronology values
28
+ def chronology_show(record)
29
+ prefixed_subject_and_alternate(record, CHRONOLOGY_PREFIX)
30
+ end
31
+
32
+ # Get notes for Related Collections from {https://www.oclc.org/bibformats/en/5xx/544.html MARC 544}.
33
+ # @param [MARC::Record] record
34
+ # @return [Array]
35
+ def related_collections_show(record)
36
+ datafield_and_linked_alternate(record, '544')
37
+ end
38
+
39
+ # Get notes for "Publication About" from {https://www.oclc.org/bibformats/en/5xx/581.html MARC 581}.
40
+ # @param [MARC::Record] record
41
+ # @return [Array]
42
+ def publications_about_show(record)
43
+ datafield_and_linked_alternate(record, '581')
44
+ end
45
+
46
+ # Get related work from {RELATED_WORK_FIELDS} in the 7XX range. Require presence of sf t (title) and absence of
47
+ # an indicator2 value. Prefix returned values with sf i value. Also map relator codes found in sf 4. Ignore sf 0.
48
+ # @param [MARC::Record] record
49
+ # @param [Hash] relator_map
50
+ # @return [Array]
51
+ def related_work_show(record, relator_map)
52
+ values = record.fields(RELATED_WORK_FIELDS).filter_map do |field|
53
+ next unless field.indicator2.blank?
54
+
55
+ next unless subfield_defined?(field, 't')
56
+
57
+ values_with_title_prefix(field, sf_exclude: %w[0 4 6 8 i], relator_map: relator_map)
58
+ end
59
+ values + record.fields('880').filter_map do |field|
60
+ next unless field.indicator2.blank?
61
+
62
+ next unless subfield_value?(field, '6', /^(#{RELATED_WORK_FIELDS.join('|')})/)
63
+
64
+ next unless subfield_defined?(field, 't')
65
+
66
+ values_with_title_prefix(field, sf_exclude: %w[0 4 6 8 i], relator_map: relator_map)
67
+ end
68
+ end
69
+
70
+ # Get "Contains" values from {CONTAINS_FIELDS} in the 7XX range. Must have indicator 2 value of 2 indicating an
71
+ # "Analytical Entry" meaning that the record is contained by the matching field. Map relator codes in sf 4. Ignore
72
+ # values in sf 0, 5, 6, and 8.
73
+ # @param [MARC::Record] record
74
+ # @param [Hash] relator_map
75
+ # @return [Array]
76
+ def contains_show(record, relator_map)
77
+ acc = record.fields(CONTAINS_FIELDS).filter_map do |field|
78
+ next unless field.indicator2 == '2'
79
+
80
+ values_with_title_prefix(field, sf_exclude: %w[0 4 5 6 8 i], relator_map: relator_map)
81
+ end
82
+ acc + record.fields('880').filter_map do |field|
83
+ next unless field.indicator2 == '2'
84
+
85
+ next unless subfield_value?(field, '6', /^(#{CONTAINS_FIELDS.join('|')})/)
86
+
87
+ values_with_title_prefix(field, sf_include: %w[0 5 6 8 i])
88
+ end
89
+ end
90
+
91
+ # Get "Constituent Unit" values from {https://www.oclc.org/bibformats/en/7xx/774.html MARC 774}. Include
92
+ # subfield values in i, a, s and t.
93
+ # @param [MARC::Record] record
94
+ # @return [Array]
95
+ def constituent_unit_show(record)
96
+ acc = record.fields('774').filter_map do |field|
97
+ join_subfields(field, &subfield_in?(%w[i a s t]))
98
+ end
99
+ acc + linked_alternate(record, '774', &subfield_in?(%w[i a s t]))
100
+ end
101
+
102
+ # Get "Has Supplement" values from {https://www.oclc.org/bibformats/en/7xx/770.html MARC 770}. Ignore
103
+ # subfield values in 6 and 8.
104
+ # @param [MARC::Record] record
105
+ # @return [Array]
106
+ def has_supplement_show(record)
107
+ datafield_and_linked_alternate(record, '770')
108
+ end
109
+
110
+ private
111
+
112
+ # Handle common behavior when a relator field references a title in subfield i
113
+ # @param [MARC::DataField] field
114
+ # @param [Array, nil] sf_include subfields to include, optional
115
+ # @param [Array, nil] sf_exclude subfields to exclude, optional
116
+ # @param [Hash, nil] relator_map map relator in sf4 using this map, optional
117
+ # @return [String] extracted and processed values from field
118
+ def values_with_title_prefix(field, sf_include: nil, sf_exclude: nil, relator_map: nil)
119
+ raise ArgumentError('Define only sf_include or sf_exclude.') if sf_include.present? && sf_exclude.present?
120
+
121
+ subi = remove_paren_value_from_subfield_i(field) || ''
122
+ relator = translate_relator(subfield_values(field, '4').first, relator_map) if relator_map.present?
123
+ contains = if sf_include.present?
124
+ join_subfields(field, &subfield_not_in?(sf_include))
125
+ elsif sf_exclude.present?
126
+ join_subfields(field, &subfield_not_in?(sf_exclude))
127
+ end
128
+ [
129
+ subi,
130
+ [contains, relator].compact_blank.join(', ')
131
+ ].compact_blank.join(': ')
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PennMARC
4
+ # Do Series-y stuff
5
+ class Series < Helper
6
+ class << self
7
+ def show(record)
8
+ acc = []
9
+
10
+ tags_present = series_tags.select { |tag| record[tag].present? }
11
+
12
+ if %w[800 810 811 400 410 411].member?(tags_present.first)
13
+ record.fields(tags_present.first).each do |field|
14
+ # added 2017/04/10: filter out 0 (authority record numbers) added by Alma
15
+ series = join_subfields(field, &subfield_not_in(%w[0 5 6 8 e t w v n]))
16
+ pairs = field.map do |sf|
17
+ if %w[e w v n t].member?(sf.code)
18
+ [' ', sf.value]
19
+ elsif sf.code == '4'
20
+ [', ', relator_codes[sf.value]]
21
+ end
22
+ end
23
+ series_append = pairs.flatten.join.strip
24
+ acc << { value: series, value_append: series_append, link_type: 'author_search' }
25
+ end
26
+ elsif %w[830 440 490].member?(tags_present.first)
27
+ record.fields(tags_present.first).each do |field|
28
+ # added 2017/04/10: filter out 0 (authority record numbers) added by Alma
29
+ series = join_subfields(field, &subfield_not_in(%w[0 5 6 8 c e w v n]))
30
+ series_append = join_subfields(field, &subfield_in(%w[c e w v n]))
31
+ acc << { value: series, value_append: series_append, link_type: 'title_search' }
32
+ end
33
+ end
34
+
35
+ record.fields(tags_present.drop(1)).each do |field|
36
+ # added 2017/04/10: filter out 0 (authority record numbers) added by Alma
37
+ series = join_subfields(field, &subfield_not_in(%w[0 5 6 8]))
38
+ acc << { value: series, link: false }
39
+ end
40
+
41
+ record.fields('880')
42
+ .select { |f| has_subfield6_value(f, /^(800|810|811|830|400|410|411|440|490)/) }
43
+ .each do |field|
44
+ series = join_subfields(field, &subfield_not_in(%w[5 6 8]))
45
+ acc << { value: series, link: false }
46
+ end
47
+
48
+ acc
49
+ end
50
+
51
+ def values(record)
52
+ acc = []
53
+ added_8xx = false
54
+ record.fields(%w[800 810 811 830]).take(1).each do |field|
55
+ acc << get_series_8xx_field(field)
56
+ added_8xx = true
57
+ end
58
+ unless added_8xx
59
+ record.fields(%w[400 410 411 440 490]).take(1).map do |field|
60
+ acc << get_series_4xx_field(field)
61
+ end
62
+ end
63
+ acc
64
+ end
65
+
66
+ def search(record)
67
+ acc += record.fields(%w[400 410 411])
68
+ .select { |f| f.indicator2 == '0' }
69
+ .map do |field|
70
+ join_subfields(field, &subfield_not_in(%w[4 6 8]))
71
+ end
72
+ acc += record.fields(%w[400 410 411])
73
+ .select { |f| f.indicator2 == '1' }
74
+ .map do |field|
75
+ join_subfields(field, &subfield_not_in(%w[4 6 8 a]))
76
+ end
77
+ acc += record.fields(%w[440])
78
+ .map do |field|
79
+ join_subfields(field, &subfield_not_in(%w[0 5 6 8 w]))
80
+ end
81
+ acc += record.fields(%w[800 810 811])
82
+ .map do |field|
83
+ join_subfields(field, &subfield_not_in(%w[0 4 5 6 7 8 w]))
84
+ end
85
+ acc += record do |field|
86
+ join_subfields(field, &subfield_not_in(%w[0 5 6 7 8 w]))
87
+ end
88
+ acc + record.fields(%w[533])
89
+ .map do |field|
90
+ field.find_all { |sf| sf.code == 'f' }
91
+ .map(&:value)
92
+ .map { |v| v.gsub(/\(|\)/, '') }
93
+ .join(' ')
94
+ end
95
+ end
96
+
97
+ def get_continues_display(record)
98
+ get_continues(record, '780')
99
+ end
100
+
101
+ def get_continued_by_display(record)
102
+ get_continues(record, '785')
103
+ end
104
+
105
+ private
106
+
107
+ # logic for 'Continues' and 'Continued By' is very similar
108
+ def get_continues(record, tag)
109
+ record.fields
110
+ .select { |f| f.tag == tag || (f.tag == '880' && has_subfield6_value(f, /^#{tag}/)) }
111
+ .select { |f| f.any?(&subfield_in(%w[i a s t n d])) }
112
+ .map do |field|
113
+ join_subfields(field, &subfield_in(%w[i a s t n d]))
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end