berkeley_library-tind 0.5.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +15 -3
  3. data/.gitignore +3 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +10 -0
  5. data/.idea/tind.iml +10 -9
  6. data/.rubocop.yml +1 -1
  7. data/CHANGES.md +15 -1
  8. data/README.md +165 -2
  9. data/berkeley_library-tind.gemspec +2 -2
  10. data/bin/alma-multiple-tind +50 -0
  11. data/bin/alma-single-tind +48 -0
  12. data/bin/save_tind_records +80 -0
  13. data/bin/tind-marc +73 -0
  14. data/lib/berkeley_library/tind/export/ods_exporter.rb +4 -6
  15. data/lib/berkeley_library/tind/mapping/additional_datafield_process.rb +128 -0
  16. data/lib/berkeley_library/tind/mapping/alma.rb +42 -0
  17. data/lib/berkeley_library/tind/mapping/alma_base.rb +108 -0
  18. data/lib/berkeley_library/tind/mapping/alma_multiple_tind.rb +31 -0
  19. data/lib/berkeley_library/tind/mapping/alma_single_tind.rb +28 -0
  20. data/lib/berkeley_library/tind/mapping/config.rb +44 -0
  21. data/lib/berkeley_library/tind/mapping/csv_mapper.rb +35 -0
  22. data/lib/berkeley_library/tind/mapping/csv_multiple_mapper.rb +41 -0
  23. data/lib/berkeley_library/tind/mapping/data/one_to_multiple_mapping.csv +4 -0
  24. data/lib/berkeley_library/tind/mapping/data/one_to_one_mapping.csv +39 -0
  25. data/lib/berkeley_library/tind/mapping/external_tind_field.rb +103 -0
  26. data/lib/berkeley_library/tind/mapping/field_catalog.rb +137 -0
  27. data/lib/berkeley_library/tind/mapping/field_catalog_util.rb +105 -0
  28. data/lib/berkeley_library/tind/mapping/match_tind_field.rb +77 -0
  29. data/lib/berkeley_library/tind/mapping/misc.rb +69 -0
  30. data/lib/berkeley_library/tind/mapping/multiple_rule.rb +36 -0
  31. data/lib/berkeley_library/tind/mapping/single_rule.rb +149 -0
  32. data/lib/berkeley_library/tind/mapping/tind_control_subfield.rb +59 -0
  33. data/lib/berkeley_library/tind/mapping/tind_field.rb +49 -0
  34. data/lib/berkeley_library/tind/mapping/tind_field_from_leader.rb +27 -0
  35. data/lib/berkeley_library/tind/mapping/tind_field_from_multiple_map.rb +59 -0
  36. data/lib/berkeley_library/tind/mapping/tind_field_from_single_map.rb +182 -0
  37. data/lib/berkeley_library/tind/mapping/tind_field_util.rb +112 -0
  38. data/lib/berkeley_library/tind/mapping/tind_marc.rb +134 -0
  39. data/lib/berkeley_library/tind/mapping/tind_record_util.rb +135 -0
  40. data/lib/berkeley_library/tind/mapping/tind_subfield_util.rb +154 -0
  41. data/lib/berkeley_library/tind/mapping/util.rb +136 -0
  42. data/lib/berkeley_library/tind/mapping.rb +1 -0
  43. data/lib/berkeley_library/tind/module_info.rb +1 -1
  44. data/spec/berkeley_library/tind/mapping/additional_datafield_process_spec.rb +35 -0
  45. data/spec/berkeley_library/tind/mapping/alma_base_spec.rb +115 -0
  46. data/spec/berkeley_library/tind/mapping/alma_multiple_tind_spec.rb +20 -0
  47. data/spec/berkeley_library/tind/mapping/alma_single_tind_spec.rb +87 -0
  48. data/spec/berkeley_library/tind/mapping/alma_spec.rb +28 -0
  49. data/spec/berkeley_library/tind/mapping/config_spec.rb +19 -0
  50. data/spec/berkeley_library/tind/mapping/csv_mapper_spec.rb +27 -0
  51. data/spec/berkeley_library/tind/mapping/csv_multiple_mapper_spec.rb +27 -0
  52. data/spec/berkeley_library/tind/mapping/external_tind_field_spec.rb +45 -0
  53. data/spec/berkeley_library/tind/mapping/field_catalog_spec.rb +78 -0
  54. data/spec/berkeley_library/tind/mapping/field_catalog_util_spec.rb +105 -0
  55. data/spec/berkeley_library/tind/mapping/match_tind_field_spec.rb +24 -0
  56. data/spec/berkeley_library/tind/mapping/misc_spec.rb +51 -0
  57. data/spec/berkeley_library/tind/mapping/multiple_rule_spec.rb +44 -0
  58. data/spec/berkeley_library/tind/mapping/single_rule_spec.rb +52 -0
  59. data/spec/berkeley_library/tind/mapping/tind_control_subfield_spec.rb +96 -0
  60. data/spec/berkeley_library/tind/mapping/tind_field_from_leader_spec.rb +21 -0
  61. data/spec/berkeley_library/tind/mapping/tind_field_from_multiple_map_spec.rb +31 -0
  62. data/spec/berkeley_library/tind/mapping/tind_field_from_single_map_spec.rb +167 -0
  63. data/spec/berkeley_library/tind/mapping/tind_field_spec.rb +60 -0
  64. data/spec/berkeley_library/tind/mapping/tind_field_util_spec.rb +68 -0
  65. data/spec/berkeley_library/tind/mapping/tind_marc_spec.rb +88 -0
  66. data/spec/berkeley_library/tind/mapping/tind_record_util_spec.rb +30 -0
  67. data/spec/berkeley_library/tind/mapping/tind_subfield_util_spec.rb +48 -0
  68. data/spec/berkeley_library/tind/mapping/util_spec.rb +56 -0
  69. data/spec/berkeley_library/tind/marc/xml_writer_spec.rb +24 -0
  70. data/spec/data/api/pre_assigned_response.json +15 -0
  71. data/spec/data/api/result_file.csv +3 -0
  72. data/spec/data/api/upload_file.json +1 -0
  73. data/spec/data/api/upload_response.json +13 -0
  74. data/spec/data/mapping/991032333019706532-sru.xml +216 -0
  75. data/spec/data/mapping/one_to_multiple_mapping.csv +4 -0
  76. data/spec/data/mapping/one_to_one_mapping.csv +39 -0
  77. data/spec/data/mapping/record.xml +266 -0
  78. data/spec/data/mapping/record_not_qualified.xml +36 -0
  79. metadata +89 -54
  80. data/lib/berkeley_library/util/files.rb +0 -39
@@ -0,0 +1,27 @@
1
+ require 'marc'
2
+ require 'berkeley_library/tind/mapping/tind_control_subfield'
3
+
4
+ module BerkeleyLibrary
5
+ module TIND
6
+ require 'marc'
7
+ module Mapping
8
+
9
+ class TindFieldFromLeader
10
+ include CsvMultipleMapper
11
+ include Util
12
+ include TindControlSubfield
13
+
14
+ def initialize(record)
15
+ @leader_value = record.leader
16
+ end
17
+
18
+ def to_datafields
19
+ leader_rules = rules[Util.tag_symbol('LDR')]
20
+ return [] unless @leader_value && leader_rules
21
+
22
+ extracted_fields_from_leader(leader_rules, @leader_value)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,59 @@
1
+ require 'marc'
2
+ require 'berkeley_library/tind/mapping/tind_control_subfield'
3
+
4
+ module BerkeleyLibrary
5
+ module TIND
6
+ module Mapping
7
+
8
+ class TindFieldFromMultipleMap
9
+ include CsvMultipleMapper
10
+ include Util
11
+ include TindControlSubfield
12
+
13
+ def initialize(controlfield, current_datafields)
14
+ @from_controlfield = controlfield
15
+ @current_tags = current_datafields.map(&:tag)
16
+ end
17
+
18
+ def to_datafields
19
+ datafields = []
20
+ control_rules = rules_on_controldatafield
21
+
22
+ if control_rules
23
+ control_rules.each do |rule|
24
+ df = to_datafield(rule)
25
+ datafields << df if df
26
+ end
27
+ end
28
+
29
+ datafields
30
+ end
31
+
32
+ private
33
+
34
+ # one control field may have multiple rules
35
+ def rules_on_controldatafield
36
+ tag = @from_controlfield.tag
37
+ sym = Util.tag_symbol(tag)
38
+ rules[sym]
39
+ end
40
+
41
+ # Check mapped current datafields has the pre-existed tag defined in the row (rule) of csv file
42
+ def pre_exsited_tag?(rule)
43
+ @current_tags.include? rule.pre_existed_tag.to_s
44
+ end
45
+
46
+ # get a datafield on a rule (row in csv file)
47
+ def to_datafield(rule)
48
+ return nil if pre_exsited_tag?(rule)
49
+
50
+ to_value = extract_value(rule, @from_controlfield.value)
51
+ return nil unless to_value
52
+
53
+ extracted_field(rule, to_value)
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,182 @@
1
+ require 'marc'
2
+ require 'berkeley_library/tind/mapping/util'
3
+ require 'berkeley_library/tind/mapping/tind_subfield_util'
4
+
5
+ # 1. datafield could be a regular alma field
6
+ # 1) data_fields_normal - using @single_rule_hash from SingleRule
7
+ # 2) data_fields_with_pre_existed_field - using @single_rule_hash from SingleRule
8
+ # 3) data_fields_with_pre_existed_subfield - using @single_rule_subfield_excluded_hash from SingleRule
9
+
10
+ # 2. data field could be an 880 alma field , below types are definded based on the tag from subfield6
11
+ # 1) data_fields_normal - using @single_rule_hash from SingleRule
12
+ # 2) data_fields_with_pre_existed_field - using @single_rule_hash from SingleRule
13
+ # 3) data_fields_with_pre_existed_subfield - using @single_rule_subfield_excluded_hash from SingleRule
14
+
15
+ # 3. map_to_tag, indicator are from mapping rule for output tindfield
16
+ # 4. subfileds are re-mapped, or combined, used as subfields for output tindfield
17
+
18
+ module BerkeleyLibrary
19
+ module TIND
20
+ module Mapping
21
+
22
+ class TindFieldFromSingleMap
23
+ include CsvMapper
24
+ include Util
25
+ include TindSubfieldUtil
26
+ include Misc
27
+
28
+ # excluding_subfield = false: mapping by rule.single_rule_hash
29
+ # excluding_subfield = true: mapping by rule.single_rule_subfield_excluded_hash
30
+ def initialize(datafield, excluding_subfield)
31
+ @from_datafield = datafield
32
+ @excluding_subfield = excluding_subfield
33
+
34
+ @is_880_field = is_880_field?(datafield)
35
+
36
+ @mapping_rule = rule
37
+ @map_to_tag = nil
38
+ @indicator = nil
39
+ @single_mapping = nil
40
+ @ready_to_mapping = ready_to_mapping?
41
+
42
+ @codes = subfield_codes(@from_datafield)
43
+ @to_subfields = all_subfields
44
+ end
45
+
46
+ def to_datafield
47
+ return nil unless mapped?
48
+
49
+ tindfield = Util.datafield(@map_to_tag, @indicator, @to_subfields)
50
+ @is_880_field ? reversed_880_field(tindfield) : tindfield
51
+ end
52
+
53
+ private
54
+
55
+ # A referred tag from 880 subfield6 may not have a rule
56
+ # For example: 880 subfild6 pass in a value in wrong format
57
+ # In above case, rule is nil
58
+ # Get mapping parameters from rule when having a rule
59
+ def ready_to_mapping?
60
+ return false unless @mapping_rule
61
+
62
+ @map_to_tag = @mapping_rule.tag_destination
63
+ @indicator = @mapping_rule.indicator
64
+ @single_mapping = @excluding_subfield ? @mapping_rule.single_rule_subfield_excluded_hash : @mapping_rule.single_rule_hash
65
+
66
+ return false unless @map_to_tag && @indicator && !@single_mapping.empty?
67
+
68
+ true
69
+ end
70
+
71
+ def mapped?
72
+ !@to_subfields.empty?
73
+ end
74
+
75
+ # tag - regular alma field
76
+ # referred tag - got tag from subfield6 value of a 880 field
77
+ # nil rule caused by nil referred tag - eg. 880 subfild6 pass in a value in wrong format
78
+ def rule
79
+ tag = origin_mapping_tag(@from_datafield)
80
+ return nil unless tag
81
+
82
+ rules[Util.tag_symbol(tag)]
83
+ end
84
+
85
+ def all_subfields
86
+ return [] unless @ready_to_mapping
87
+
88
+ subfields = subfields_from_single_map + subfields_from_combined_map
89
+ codes = @mapping_rule.subfields_order || @codes
90
+
91
+ return subfields unless subfields.length > 1
92
+
93
+ Util.order_subfields(subfields, codes)
94
+ end
95
+
96
+ def subfield_codes(f)
97
+ f.subfields.map(&:code).uniq
98
+ end
99
+
100
+ # 1.subfields mapped with single rule, mapping one subfield to another subfield
101
+ # 2. one subfield is mapped to one subfield
102
+ # 3. When mutiple subfields with the same name found in an orignal field,
103
+ # they will be mapped one by one
104
+ def subfields_from_single_map
105
+ return [] if @single_mapping.empty?
106
+
107
+ mapped_subfields = []
108
+ @single_mapping.each do |from, to|
109
+ subfields = subfields_from_to(@from_datafield, from, to)
110
+ mapped_subfields.concat(subfields)
111
+ end
112
+ mapped_subfields
113
+ end
114
+
115
+ # return all subfields mapped with diferent combined rules - different destination subfield names
116
+ # mapped with all combined rules, exmaple: [[["a,b,c,d", "b", "--"],["o,p,q", "b", ""]],[["x,y,z", "a", "--"]]]
117
+ # mapping using above example rules will return two subfield: $b, $a
118
+ def subfields_from_combined_map
119
+ all_rules = @mapping_rule.combined_rules
120
+ return [] if all_rules.empty?
121
+
122
+ mapped_subfields = []
123
+ all_rules.each do |rules|
124
+ subfield = subfield_on_same_tosubfieldname(rules)
125
+ mapped_subfields.push(subfield) if subfield
126
+ end
127
+ mapped_subfields
128
+ end
129
+
130
+ # create one subfield with a desintaion subfield name
131
+ # input array of rules example: [["a,b,c,d", "b", "--"],["o,p,q", "b", ""]] -- all rules with the same destination subfield name "b"
132
+ # get a subfield$b with a concatenated value
133
+ def subfield_on_same_tosubfieldname(rules)
134
+ return nil if rules.empty?
135
+
136
+ val = subfield_value_on_rules(rules)
137
+ return nil if val.strip.empty?
138
+
139
+ subfield_name_to = rules[0][1]
140
+ Util.subfield(subfield_name_to, Util.remove_extra_symbol(rules, val))
141
+ end
142
+
143
+ # input an array of rules, example: [["a,b,c,d", "b", "--"],["o,p,q", "b", ""]]
144
+ # Theese rules have the same destination subfield name, for example "b" in above example
145
+ # get a value concatenated with values mapped using different rules
146
+ def subfield_value_on_rules(rules)
147
+ val = ''
148
+ rules.each { |rule| val << subfield_value_on_rule(rule) }
149
+ val
150
+ end
151
+
152
+ # input a rule (for example ["a,b,c,d", "b", "--"]),
153
+ # get a combined value of subfield a,b,c,d concatenated by " -- " as above example
154
+ # One subfield names may occurs mutiple times in a an orignal field
155
+ def subfield_value_on_rule(rule)
156
+ subfield_names_from = rule[0].strip.split(',')
157
+ symbol = Util.concatenation_symbol(rule[2])
158
+ val = ''
159
+ subfield_names_from.each do |subfield_name|
160
+ sub_val = combined_subfield_value(@from_datafield, subfield_name, symbol)
161
+ val << sub_val
162
+ end
163
+ val
164
+ end
165
+
166
+ # 880 datafield: reverse tag from 'to_tag' defined mapping rule to '880'
167
+ def reversed_880_field(f)
168
+ update_datafield6(f)
169
+ f.tag = '880'
170
+ f
171
+ end
172
+
173
+ # update subfield6 tag with destination tag from the rule
174
+ # since an origin tag may have been mapped a different tag - destination tag
175
+ def update_datafield6(f) # need test
176
+ f['6'].sub!(@mapping_rule.tag_origin, @mapping_rule.tag_destination)
177
+ end
178
+
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,112 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ module TindFieldUtil
7
+
8
+ # tag - regular alma field
9
+ # referred tag - got tag from subfield6 value of a 880 field
10
+ # nil rule caused by nil referred tag - eg. 880 subfild6 has a value in wrong format
11
+ def rule(field)
12
+ tag = origin_mapping_tag(field)
13
+ return nil unless tag
14
+
15
+ rules[Util.tag_symbol(tag)]
16
+ end
17
+
18
+ def tindfield_existed?(field, fields)
19
+ return false unless field_has_rule?(field)
20
+
21
+ field_rule = rule(field)
22
+ mapping_to_tag = field_rule.pre_existed_tag
23
+ return false unless mapping_to_tag
24
+
25
+ map_to_tag_existed_in_fields?(field, fields, mapping_to_tag)
26
+ end
27
+
28
+ # To check TIND datafield and the specific subfield from rule existed
29
+ def tindfield_subfield_existed?(field, fields)
30
+ return false unless field_has_rule?(field)
31
+
32
+ field_rule = rule(field)
33
+ return false unless pre_existed_tag_subfield_in_rule?(field_rule)
34
+
35
+ tag_subfield = field_rule.pre_existed_tag_subfield
36
+ mapping_to_tag = tag_subfield[0]
37
+ return false unless map_to_tag_existed_in_fields?(field, fields, mapping_to_tag)
38
+
39
+ existed_datafield = field_pre_existed(mapping_to_tag, field, fields)
40
+ return false unless existed_datafield
41
+
42
+ subfield_name = tag_subfield[1]
43
+ existed_datafield[subfield_name] ? true : false
44
+ end
45
+
46
+ def field_880_on_subfield6_tag(tag, fields)
47
+ datafield_on_tag(tag, fields) { |f| referred_tag(f) == tag }
48
+ end
49
+
50
+ def field_on_tag(tag, fields)
51
+ datafield_on_tag(tag, fields) { |f| f.tag == tag }
52
+ end
53
+
54
+ private
55
+
56
+ def field_has_rule?(field)
57
+ field_rule = rule(field)
58
+ return false unless field_rule
59
+
60
+ true
61
+ end
62
+
63
+ def pre_existed_tag_subfield_in_rule?(rule)
64
+ tag_subfield = rule.pre_existed_tag_subfield
65
+ return false unless tag_subfield
66
+
67
+ return false unless tag_subfield.length == 2
68
+
69
+ true
70
+ end
71
+
72
+ def map_to_tag_existed_in_fields?(field, fields, mapping_to_tag)
73
+ existed_tags = if is_880_field?(field)
74
+ tags_from_fields(fields) { |f| tag_from_880_subfield6(f) }
75
+ else
76
+ tags_from_fields(fields, &:tag)
77
+ end
78
+
79
+ existed_tags.include? mapping_to_tag
80
+ end
81
+
82
+ # field, fields be both regular fields
83
+ # or field, fields be both 880 fields
84
+ # since a field may mapped to another one in TIND, mapping_to_tag is not always the same as field.tag
85
+ def field_pre_existed(mapping_to_tag, field, fields)
86
+ if is_880_field?(field)
87
+ field_880_on_subfield6_tag(mapping_to_tag,
88
+ fields)
89
+ else
90
+ field_on_tag(mapping_to_tag, fields)
91
+ end
92
+ end
93
+
94
+ def datafield_on_tag(tag, fields)
95
+ fields.find do |f|
96
+ yield(f, tag)
97
+ end
98
+ end
99
+
100
+ def tags_from_fields(fields, &block)
101
+ fields.map(&block)
102
+ end
103
+
104
+ def tag_from_880_subfield6(field)
105
+ field['6'].split('-')[0]
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,134 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ class TindMarc
7
+ include CsvMapper
8
+ include Util
9
+ include TindSubfieldUtil
10
+ include Misc
11
+ include TindFieldUtil
12
+ include AdditionalDatafieldProcess
13
+ include BerkeleyLibrary::Logging
14
+ include MatchTindField
15
+
16
+ attr_accessor :source_marc_record
17
+ attr_writer :tind_external_datafields
18
+ attr_reader :field_catalog
19
+ attr_reader :mms_id
20
+
21
+ # input an alma record
22
+ def initialize(record)
23
+ @source_marc_record = record
24
+ @field_catalog = DataFieldsCatalog.new(@source_marc_record)
25
+ @tind_external_datafields = []
26
+ end
27
+
28
+ # return mapped tind datafields
29
+ # keep the order of different mapping
30
+ def tindfields
31
+ fields = []
32
+ fields.concat tindfields_group
33
+ fields.concat tindfields_group_880
34
+ fields
35
+ end
36
+
37
+ # return a TIND Marc record
38
+ # add external datafields
39
+ # flag to do additional TIND datafield process before generating a TIND Marc record
40
+ def tind_record
41
+ fields = tindfields
42
+ fields.concat @tind_external_datafields
43
+ more_process(fields)
44
+ record = ::MARC::Record.new
45
+ fields.each { |f| record.append(f) }
46
+ record
47
+ end
48
+
49
+ private
50
+
51
+ # return mapped tind datafields
52
+ # keep the order of different mapping
53
+ def tindfields_group
54
+ fields_from_normal = tindfields_from_normal(@field_catalog.data_fields_group[:normal])
55
+ fields_from_leader = TindFieldFromLeader.new(@source_marc_record).to_datafields
56
+
57
+ temp_fields = fields_from_normal.concat fields_from_leader
58
+ temp_fields.concat tindfields_from_control(temp_fields)
59
+ temp_fields.concat tindfields_with_pre_existed_field(@field_catalog.data_fields_group[:pre_tag], temp_fields)
60
+ temp_fields.concat tindfields_with_pre_existed_subfield(@field_catalog.data_fields_group[:pre_tag_subfield], temp_fields)
61
+
62
+ temp_fields
63
+ end
64
+
65
+ # return mapped tind 880 datafields
66
+ # keep the order of different mapping
67
+ def tindfields_group_880
68
+ fields = []
69
+ fields.concat tindfields_from_normal(@field_catalog.data_fields_880_group[:normal])
70
+ fields.concat tindfields_with_pre_existed_field(@field_catalog.data_fields_880_group[:pre_tag], fields)
71
+ fields.concat tindfields_with_pre_existed_subfield(@field_catalog.data_fields_880_group[:pre_tag_subfield], fields)
72
+ fields.concat @field_catalog.data_fields_880_00
73
+ fields
74
+ end
75
+
76
+ # # Return TIND datafields mapped in a normal way
77
+ # # Normal way mapping: one regular datafield is mapped one TIND datafield
78
+ def tindfields_from_normal(alma_fields)
79
+ new_fls = []
80
+ alma_fields.each do |f|
81
+ add_tindfield(new_fls, f, excluding_subfield: false)
82
+ end
83
+ new_fls
84
+ end
85
+
86
+ # Return TIND datafield mapped from a control datafield
87
+ # One control datafield could be mapped to multiple TIND datafields
88
+ def tindfields_from_control(currentfields)
89
+ new_fls = []
90
+ @field_catalog.control_fields.each do |f|
91
+ add_tindcontrolfield(new_fls, f, currentfields)
92
+ end
93
+ new_fls
94
+ end
95
+
96
+ # Return TIND datafields if no pre_existed field
97
+ def tindfields_with_pre_existed_field(alma_fields, currentfields)
98
+ new_fls = []
99
+ alma_fields.each do |f|
100
+ add_tindfield(new_fls, f, excluding_subfield: false) unless tindfield_existed?(f, currentfields)
101
+ end
102
+ new_fls
103
+ end
104
+
105
+ # Return TIND datafields considering excluding pre_existing subfields
106
+ def tindfields_with_pre_existed_subfield(alma_fields, currentfields)
107
+ new_fls = []
108
+ alma_fields.each do |f|
109
+ excluding_subfield = tindfield_subfield_existed?(f, currentfields)
110
+ add_tindfield(new_fls, f, excluding_subfield: excluding_subfield)
111
+ end
112
+ new_fls
113
+ end
114
+
115
+ def add_tindfield(fls, f, excluding_subfield: false)
116
+ tindfield = TindFieldFromSingleMap.new(f, excluding_subfield).to_datafield
117
+ fls << tindfield if tindfield
118
+ end
119
+
120
+ def add_tindcontrolfield(fls, f, currentfields)
121
+ fls.concat TindFieldFromMultipleMap.new(f, currentfields).to_datafields
122
+ end
123
+
124
+ # Additional processes - run in a sequence
125
+ def more_process(fields)
126
+ remove_repeats(fields)
127
+ clean_subfields(fields)
128
+ un_matched_fields_880(fields, @field_catalog.mms_id)
129
+ end
130
+
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,135 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ module TindRecordUtil
7
+
8
+ class << self
9
+ include TindRecordUtil
10
+ end
11
+
12
+ # 1) tag_subfield_hash: a hash to add a new, or update an existing subfield to a field in a TIND Marc record
13
+ # If a subfield existed, it will replace the subfield, otherwise, add a new subfield
14
+ # when 'a' => nil, it will skip add/update to subfield,
15
+ # It can be useed in a case: only need to add/update subfields of specific records in a collection
16
+ # This is an example: tag_subfield_hash = { '245' => { 'b' => 'subtitle', 'a' => 'title' }, '336' => { 'a' => nil } }
17
+ # 2) fields_removal_list: an array including fields' informat: tag, indicators - "[tag, inicator1, indicator2]".
18
+ # This list is to remove fields in a record.
19
+ # An example fields_removal_list = [%w[856 4 1], %w[041 _ _]].
20
+ # '_' means an empty indicator ''
21
+ # 3) How to use it:
22
+ # a. add/update subfields of existed fields in record: TindRecordUtil.update_record(record, tag_subfield_hash)
23
+ # b. remove a list of fields in the record: TindRecordUtil.update_record(record, nil, fields_removal_list)
24
+ # c. both a. and b. : TindRecordUtil.update_record(record, tag_subfield_hash, fields_removal_list)
25
+ def update_record(record, tag_subfield_hash, fields_removal_list = nil)
26
+ return record unless valid_hash?(tag_subfield_hash) || valid_hash?(fields_removal_list)
27
+
28
+ fields = record.fields
29
+ final_fields = tag_subfield_hash ? subfields_to_existed_fields(fields, tag_subfield_hash) : fields
30
+ remove_fields(final_fields, fields_removal_list) if fields_removal_list
31
+ new_record(final_fields)
32
+ end
33
+
34
+ private
35
+
36
+ def subfields_to_existed_fields(fields, tag_subfield_hash)
37
+ updated_fields = []
38
+ tags = tag_subfield_hash.keys
39
+ fields.each do |field|
40
+ tag = field.tag
41
+ need_change_subfield = tags.include? tag
42
+ updated_fields << (need_change_subfield ? field_changed_subfields(field, tag_subfield_hash[tag]) : field)
43
+ end
44
+ fields
45
+ end
46
+
47
+ # example: fields_removal_list = [%w[856 4 1]]
48
+ def remove_fields(fields, fields_removal_list)
49
+ fields.reject! { |f| field_in_removal_list?(f, fields_removal_list) }
50
+ end
51
+
52
+ def new_record(fields)
53
+ record = ::MARC::Record.new
54
+ fields.each { |f| record.append(f) }
55
+ record
56
+ end
57
+
58
+ # subfield_hash example { 'b' => 'subtitle', 'a' => 'title' }
59
+ def field_changed_subfields(field, subfield_hash)
60
+ tag = field.tag
61
+ indicators = [field.indicator1, field.indicator2]
62
+ subfields = changed_subfields(field, subfield_hash)
63
+ new_datafield(tag, indicators, subfields)
64
+ end
65
+
66
+ # example subfield_hash = { 'p' => 'subtitle'}
67
+ def changed_subfields(field, subfield_hash)
68
+ subfields = field.subfields
69
+ codes = subfields.map(&:code)
70
+ warning_duplicated_subfield_code(codes)
71
+
72
+ keys = subfield_hash.keys
73
+
74
+ keys_no_related_codes = keys - codes
75
+ keys_with_related_codes = keys - keys_no_related_codes
76
+
77
+ updated_subfields(subfields, keys_with_related_codes, subfield_hash)
78
+
79
+ subfields.concat new_subfields(field, keys_no_related_codes, subfield_hash)
80
+ subfields
81
+ end
82
+
83
+ # example subfield_hash = { 'p' => 'subtitle'}
84
+ def updated_subfields(subfields, existed_codes, subfield_hash)
85
+ subfields.each do |subfield|
86
+ code = subfield.code
87
+ next unless existed_codes.include? code
88
+
89
+ val = subfield_hash[code]
90
+ next unless val
91
+
92
+ subfield.value = val
93
+ end
94
+ end
95
+
96
+ def new_subfields(_field, new_codes, subfield_hash)
97
+ subfields = []
98
+ subfield_hash.each do |key, val|
99
+ next unless val
100
+
101
+ subfields << ::MARC::Subfield.new(key, val) if new_codes.include? key
102
+ end
103
+ subfields
104
+ end
105
+
106
+ def field_in_removal_list?(f, fields_removal_list)
107
+ ls = [f.tag, clr(f.indicator1), clr(f.indicator2)]
108
+ fields_removal_list.include? ls
109
+ end
110
+
111
+ def clr(str)
112
+ str.strip.empty? ? '_' : str.strip
113
+ end
114
+
115
+ def new_datafield(tag, indicator, subfields)
116
+ datafield = ::MARC::DataField.new(tag, indicator[0], indicator[1])
117
+ subfields.each { |sf| datafield.append(sf) }
118
+ datafield
119
+ end
120
+
121
+ # suppose there are no duplicated subfield codes in a field
122
+ # giving warning for investigation if finding any dublicated subfields in a field
123
+ def warning_duplicated_subfield_code(codes)
124
+ duplicated_codes = codes.select { |code| codes.count(code) > 1 }.uniq
125
+ puts "Warning: duplicated subfields #{duplicated_codes.join(' ; ')}" unless duplicated_codes.empty?
126
+ end
127
+
128
+ def valid_hash?(hash)
129
+ hash && !hash.empty?
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end