pennmarc 0.0.1

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.
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