berkeley_library-tind 0.5.1 → 0.6.0

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 (72) 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 +4 -3
  6. data/CHANGES.md +6 -0
  7. data/README.md +121 -2
  8. data/berkeley_library-tind.gemspec +1 -0
  9. data/bin/alma-multiple-tind +50 -0
  10. data/bin/alma-single-tind +48 -0
  11. data/bin/save_tind_records +80 -0
  12. data/bin/tind-marc +73 -0
  13. data/lib/berkeley_library/tind/mapping/additional_datafield_process.rb +128 -0
  14. data/lib/berkeley_library/tind/mapping/alma.rb +42 -0
  15. data/lib/berkeley_library/tind/mapping/alma_base.rb +101 -0
  16. data/lib/berkeley_library/tind/mapping/alma_multiple_tind.rb +31 -0
  17. data/lib/berkeley_library/tind/mapping/alma_single_tind.rb +28 -0
  18. data/lib/berkeley_library/tind/mapping/config.rb +44 -0
  19. data/lib/berkeley_library/tind/mapping/csv_mapper.rb +35 -0
  20. data/lib/berkeley_library/tind/mapping/csv_multiple_mapper.rb +41 -0
  21. data/lib/berkeley_library/tind/mapping/data/one_to_multiple_mapping.csv +4 -0
  22. data/lib/berkeley_library/tind/mapping/data/one_to_one_mapping.csv +39 -0
  23. data/lib/berkeley_library/tind/mapping/external_tind_field.rb +103 -0
  24. data/lib/berkeley_library/tind/mapping/field_catalog.rb +146 -0
  25. data/lib/berkeley_library/tind/mapping/field_catalog_util.rb +59 -0
  26. data/lib/berkeley_library/tind/mapping/match_tind_field.rb +77 -0
  27. data/lib/berkeley_library/tind/mapping/misc.rb +69 -0
  28. data/lib/berkeley_library/tind/mapping/multiple_rule.rb +36 -0
  29. data/lib/berkeley_library/tind/mapping/single_rule.rb +143 -0
  30. data/lib/berkeley_library/tind/mapping/tind_control_subfield.rb +59 -0
  31. data/lib/berkeley_library/tind/mapping/tind_field.rb +49 -0
  32. data/lib/berkeley_library/tind/mapping/tind_field_from_leader.rb +27 -0
  33. data/lib/berkeley_library/tind/mapping/tind_field_from_multiple_map.rb +59 -0
  34. data/lib/berkeley_library/tind/mapping/tind_field_from_single_map.rb +170 -0
  35. data/lib/berkeley_library/tind/mapping/tind_field_util.rb +112 -0
  36. data/lib/berkeley_library/tind/mapping/tind_marc.rb +134 -0
  37. data/lib/berkeley_library/tind/mapping/tind_subfield_util.rb +154 -0
  38. data/lib/berkeley_library/tind/mapping/util.rb +117 -0
  39. data/lib/berkeley_library/tind/mapping.rb +1 -0
  40. data/lib/berkeley_library/tind/module_info.rb +1 -1
  41. data/lib/berkeley_library/util/files.rb +1 -2
  42. data/spec/berkeley_library/tind/mapping/additional_datafield_process_spec.rb +35 -0
  43. data/spec/berkeley_library/tind/mapping/alma_base_spec.rb +115 -0
  44. data/spec/berkeley_library/tind/mapping/alma_multiple_tind_spec.rb +20 -0
  45. data/spec/berkeley_library/tind/mapping/alma_single_tind_spec.rb +87 -0
  46. data/spec/berkeley_library/tind/mapping/alma_spec.rb +28 -0
  47. data/spec/berkeley_library/tind/mapping/config_spec.rb +19 -0
  48. data/spec/berkeley_library/tind/mapping/csv_mapper_spec.rb +27 -0
  49. data/spec/berkeley_library/tind/mapping/csv_multiple_mapper_spec.rb +27 -0
  50. data/spec/berkeley_library/tind/mapping/external_tind_field_spec.rb +45 -0
  51. data/spec/berkeley_library/tind/mapping/field_catalog_spec.rb +78 -0
  52. data/spec/berkeley_library/tind/mapping/field_catalog_util_spec.rb +57 -0
  53. data/spec/berkeley_library/tind/mapping/match_tind_field_spec.rb +25 -0
  54. data/spec/berkeley_library/tind/mapping/misc_spec.rb +51 -0
  55. data/spec/berkeley_library/tind/mapping/multiple_rule_spec.rb +44 -0
  56. data/spec/berkeley_library/tind/mapping/single_rule_spec.rb +52 -0
  57. data/spec/berkeley_library/tind/mapping/tind_control_subfield_spec.rb +96 -0
  58. data/spec/berkeley_library/tind/mapping/tind_field_from_leader_spec.rb +21 -0
  59. data/spec/berkeley_library/tind/mapping/tind_field_from_multiple_map_spec.rb +31 -0
  60. data/spec/berkeley_library/tind/mapping/tind_field_from_single_map_spec.rb +150 -0
  61. data/spec/berkeley_library/tind/mapping/tind_field_spec.rb +60 -0
  62. data/spec/berkeley_library/tind/mapping/tind_field_util_spec.rb +68 -0
  63. data/spec/berkeley_library/tind/mapping/tind_marc_spec.rb +88 -0
  64. data/spec/berkeley_library/tind/mapping/tind_subfield_util_spec.rb +48 -0
  65. data/spec/berkeley_library/tind/mapping/util_spec.rb +56 -0
  66. data/spec/berkeley_library/tind/marc/xml_writer_spec.rb +24 -0
  67. data/spec/data/mapping/991032333019706532-sru.xml +216 -0
  68. data/spec/data/mapping/one_to_multiple_mapping.csv +4 -0
  69. data/spec/data/mapping/one_to_one_mapping.csv +39 -0
  70. data/spec/data/mapping/record.xml +263 -0
  71. data/spec/data/mapping/record_not_qualified.xml +36 -0
  72. metadata +105 -2
@@ -0,0 +1,77 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ module MatchTindField
7
+
8
+ # return regular fields without matched 880 fields
9
+ # return 880 fields without matched regular fields
10
+ def un_matched_fields_880(fields, mms_id)
11
+ unmached_fields = []
12
+
13
+ str_arr_from_880 = subfield6_values_from_880_fields(fields)
14
+ str_arr_from_regular = subfield6_values_from_regular_fields(fields)
15
+
16
+ fields_tobe_validated = fields_need_880_validation(fields)
17
+
18
+ fields_880_tobe_validated = fields_tobe_validated.select { |f| is_880_field?(f) }
19
+ fields_regular_tobe_validated = fields_tobe_validated.reject { |f| is_880_field?(f) }
20
+
21
+ unmached_fields.concat un_matched_fields(fields_880_tobe_validated, str_arr_from_regular)
22
+ unmached_fields.concat un_matched_fields(fields_regular_tobe_validated, str_arr_from_880)
23
+
24
+ log_warning(unmached_fields, mms_id)
25
+ end
26
+
27
+ def check_abnormal_formated_subfield6(fields)
28
+ fields.each { |f| check_subfield6_format(f) if check_subfield6?(f) }
29
+ end
30
+
31
+ private
32
+
33
+ def subfield6_values_from_880_fields(fields)
34
+ formated_subfield6_value_arr(fields_by(fields) { |f| is_880_field?(f) })
35
+ end
36
+
37
+ def subfield6_values_from_regular_fields(fields)
38
+ formated_subfield6_value_arr(fields_by(fields) { |f| !is_880_field?(f) })
39
+ end
40
+
41
+ def fields_need_880_validation(fields)
42
+ fields_with_subfield6(fields).reject { |f| subfield6_endwith_00?(f) }
43
+ end
44
+
45
+ # return true when field has a matched 880 field,
46
+ # or an 880 field has a matched regular field
47
+ def match?(f, arr)
48
+ str = formated_subfield6_value(f)
49
+ arr.include? str
50
+ end
51
+
52
+ def un_matched_fields(fields, arr)
53
+ fds = []
54
+ fields.each { |f| fds << f unless match?(f, arr) }
55
+ fds
56
+ end
57
+
58
+ def log_warning(fields, mms_id)
59
+ warning_message_for_rspec = []
60
+ fields.each do |f|
61
+ msg = "Please check 880 matching: mms_id: #{mms_id}, tag: #{f.tag}, value: #{f['6']} "
62
+ warning_message_for_rspec << msg
63
+ logger.warn(msg)
64
+ end
65
+ warning_message_for_rspec
66
+ end
67
+
68
+ def check_subfield6?(f)
69
+ return false if ::MARC::ControlField.control_tag?(f.tag)
70
+
71
+ f['6'] ? true : false
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,69 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ module Misc
7
+
8
+ #### referred tag ###
9
+ def origin_mapping_tag(f)
10
+ is_880_field?(f) ? referred_tag(f) : f.tag
11
+ end
12
+
13
+ # get the 880 referred tag.
14
+ # An example $6 value: '650-05/$1', referred tag is 650
15
+ def referred_tag(field)
16
+ return nil unless subfield6?(field)
17
+
18
+ field['6'].strip.split('-')[0]
19
+ end
20
+
21
+ # check a tag in subfield 6 of a 880 datafield
22
+ def field_880_has_referred_tag?(tag, field)
23
+ referred_tag_from_880 = referred_tag(field)
24
+ return false unless referred_tag_from_880
25
+
26
+ referred_tag_from_880 == tag
27
+ end
28
+
29
+ ### referred tag end ###
30
+
31
+ # add subfield6 validation
32
+ def check_subfield6_format(f)
33
+ val = f['6']
34
+ reg1 = %r{^\d{3}-\d{2}/}
35
+ reg2 = /^\d{3}-\d{2}$/
36
+
37
+ logger.warn("Unusual subfield6 format: #{val}; correct format examples: 1) 880-02 ; 2)246-02/$1") unless reg1.match(val) || reg2.match(val)
38
+ end
39
+
40
+ private
41
+
42
+ # manipulate original values
43
+ # Delete characters when occuring at the end of a subfield value
44
+ def rm_punctuation(str)
45
+ return str if str.empty? || str.nil?
46
+
47
+ punctuations = Config.punctuations
48
+ char = str[-1]
49
+ return str unless punctuations.include? char
50
+
51
+ rm_punctuation(str.delete_suffix!(char))
52
+ end
53
+
54
+ def clr_value(value)
55
+ new_value = rm_punctuation(value)
56
+ ['[', ']'].each { |v| value.gsub!(v, ' ') }
57
+ new_value.strip
58
+ end
59
+
60
+ # input example: 1) 880-02 ; 2)246-02/$1
61
+ def seq_no(value)
62
+ # logger if not started with ***-** format
63
+ value.split('/')[0].split('-')[1].to_i # nil.to_i => 0, ''.to_i = >0
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,36 @@
1
+ module BerkeleyLibrary
2
+ module TIND
3
+ module Mapping
4
+
5
+ class MultipleRule
6
+ include Util
7
+
8
+ attr_reader :tag_origin
9
+ attr_reader :tag_destination
10
+ attr_reader :indicator
11
+ attr_reader :pre_existed_tag
12
+ attr_reader :subfield_key
13
+ attr_reader :position_from_to
14
+
15
+ def initialize(row)
16
+ @tag_origin = row[:tag_origin]
17
+ @tag_destination = row[:tag_destination]
18
+ @indicator = Util.indicator(row[:new_indecator])
19
+ @pre_existed_tag = row[:map_if_no_this_tag_existed]
20
+ @subfield_key = row[:subfield_key]
21
+ @position_from_to = extract_position(row[:value_from], row[:value_to])
22
+ end
23
+
24
+ private
25
+
26
+ # return an array with string positons for extracting value
27
+ def extract_position(f, t)
28
+ return nil unless f && t
29
+
30
+ [f.to_i, t.to_i]
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,143 @@
1
+ module BerkeleyLibrary
2
+ module TIND
3
+ module Mapping
4
+ # TODO: ADD CSV VALIDATION, WHEN NEED ADD A NEW MAPPING CSV FILE
5
+ # 1. not empty row
6
+ # 2. a subfield name can appear in either normal mapping or combine mapping, not both
7
+ # 3. single map has the same amount of from names and to names
8
+ # 4. Combine mappping should have tree columns, validate more?
9
+ # 5. Combine from_subfield, to_subfield should have values, no empty
10
+ # 6. Tag from row[:map_if_no_this_tag_subfield_existed]), row[:map_if_no_this_tag_existed] # This tag should be the same as destination tag
11
+ # 7. In single map csv, one row cannot have both "map_if_no_this_tag_existed" (245) and ":map_if_no_this_tag_subfield_existed"
12
+ # (245__b) because tag in these two column are identical
13
+ # 8. csv file validation - a row should have coulumns: tag origin and destintation ? single rule
14
+ # 9. Validating these column names
15
+ # 10. csv file validation - a row should have coulumns: tag origin and destintation ? single rule
16
+ # 11. validating headers
17
+ # 12. Formats for some of the columns
18
+
19
+ class SingleRule
20
+ include Util
21
+ attr_reader :tag_origin
22
+ attr_reader :tag_destination
23
+ attr_reader :indicator
24
+ attr_reader :pre_existed_tag
25
+ attr_reader :pre_existed_tag_subfield
26
+ attr_reader :single_rule_hash
27
+ attr_reader :single_rule_subfield_excluded_hash
28
+ attr_reader :combined_rules
29
+
30
+ def initialize(row)
31
+ @tag_origin = row[:tag_origin]
32
+ @tag_destination = row[:tag_destination]
33
+ @indicator = Util.indicator(row[:new_indecator])
34
+ @pre_existed_tag = row[:map_if_no_this_tag_existed]
35
+ @pre_existed_tag_subfield = existed_tag_subfield(row[:map_if_no_this_tag_subfield_existed]) # This tag should be the same as destination tag
36
+ @single_rule_hash = single_map_dic(row[:subfield_single_from], row[:subfield_single_to])
37
+ @single_rule_subfield_excluded_hash = single_map_subfield_excluded_dic
38
+ @combined_rules = rules_with_same_subfield_name(row)
39
+ end
40
+
41
+ # 1. Return an array of combined rules, an item in the array
42
+ # is an array of rules which have the same 'to subfield name'
43
+ # 2. An example: [[["a,b,c,d", "b", "--"],["o,p,q", "b", ""]],[["x,y,z", "a", "--"]]]
44
+ def rules_with_same_subfield_name(row)
45
+ rules = all_combined_rules(row)
46
+ identical_to_subfield_names = unique_tosubfield_names(rules)
47
+ identical_to_subfield_names.each_with_object([]) do |name, result|
48
+ result << rules_with_sametosubfield(name, rules)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # return an array of tag and subfield name, example '255__a' => ['255','a']
55
+ def existed_tag_subfield(str)
56
+ str.nil? ? nil : str.split('__')
57
+ end
58
+
59
+ # list identical 'to subfield name's from combined mapping rules
60
+ # (an example rule ['a,b,c', 'b', ' -- '])
61
+ def unique_tosubfield_names(rules)
62
+ names = rules.map { |rule| rule[1] }
63
+ names.uniq
64
+ end
65
+
66
+ # numbers of combined rules
67
+ def combined_rule_counts(row)
68
+ headers = row.headers
69
+ headers.select { |h| h.to_s.include? 'subfield_combined_from_' }.count
70
+ end
71
+
72
+ # Three coulumns 'subfield_combined_from_*','subfield_combined_to_*','symbol_*'
73
+ # define a combined mapping rule
74
+ def combined_rule(row, i)
75
+ from_subfield = row["subfield_combined_from_#{i}".to_sym]
76
+ to_subfield = row["subfield_combined_to_#{i}".to_sym]
77
+ s = row["symbol_#{i}".to_sym]
78
+ from_subfield.nil? || to_subfield.nil? ? nil : [from_subfield, to_subfield, s] # add validation rule , such as not empty later
79
+ end
80
+
81
+ def all_combined_rules(row)
82
+ rules = []
83
+ n = combined_rule_counts(row)
84
+ (1..n).each do |i|
85
+ rule = combined_rule(row, i)
86
+ rules << rule if rule
87
+ end
88
+ rules
89
+ end
90
+
91
+ # list all combined rules with the same 'to subfield name'
92
+ def rules_with_sametosubfield(name, rules)
93
+ rules.each_with_object([]) do |rule, result|
94
+ result << rule if rule[1] == name
95
+ end
96
+ end
97
+
98
+ # Define a hash for single map rules, key is 'from subfield name',
99
+ # value is 'to subfield name'
100
+ def single_map_dic(str_from, str_to)
101
+ dic = {}
102
+ if should_single_map?(str_from, str_to)
103
+ arr_from = str_from.strip.split(',')
104
+ arr_to = str_to.strip.split(',')
105
+ arr_from.each_with_index { |from_name, i| dic[from_name.to_s] = arr_to[i].to_s }
106
+ end
107
+ dic
108
+ end
109
+
110
+ # Hash removed the excluding subfield
111
+ def single_map_subfield_excluded_dic
112
+ dic = @single_rule_hash
113
+ return dic unless excluding_subfield?
114
+
115
+ excluding_subfield_name = @pre_existed_tag_subfield[1].to_s
116
+ dic.delete(excluding_subfield_name) if dic.key? excluding_subfield_name
117
+ dic
118
+ end
119
+
120
+ # Check excluding subfield
121
+ def excluding_subfield?
122
+ return false unless @pre_existed_tag_subfield
123
+ return false unless @pre_existed_tag_subfield[1]
124
+
125
+ true
126
+ end
127
+
128
+ # add this to validation
129
+ def should_single_map?(str_from, str_to)
130
+ return false unless str_from && str_to
131
+
132
+ arr_from = str_from.split(',')
133
+ arr_to = str_to.split(',')
134
+ return false unless arr_from.count == arr_to.count
135
+
136
+ true
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,59 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ module TindControlSubfield
7
+
8
+ def extract_value(rule, value)
9
+ pos = rule.position_from_to
10
+ return nil unless pos
11
+
12
+ value[pos[0]..pos[1]]
13
+ end
14
+
15
+ # return a mapped datafield based on rule and extract value
16
+ def extracted_field(rule, sub_value)
17
+ subname = rule.subfield_key
18
+ destiantion_tag = rule.tag_destination
19
+ indicator = rule.indicator
20
+ return nil unless subname && destiantion_tag && indicator
21
+
22
+ new_sub_value = clean_subfield_value(destiantion_tag, sub_value)
23
+ return nil unless new_sub_value
24
+
25
+ new_sub_value = clean_subfield_value(destiantion_tag, sub_value)
26
+ subfields = [Util.subfield(subname, new_sub_value)]
27
+ Util.datafield(destiantion_tag, indicator, subfields)
28
+ end
29
+
30
+ # pass in rules, a string value; return datafields based on rules
31
+ def extracted_fields_from_leader(leader_rules, leader_value)
32
+ new_fls = []
33
+ leader_rules.each do |rule|
34
+ sub_value = extract_value(rule, leader_value)
35
+ next unless sub_value
36
+
37
+ newfield = extracted_field(rule, sub_value)
38
+ new_fls << newfield if newfield
39
+ end
40
+ new_fls
41
+ end
42
+
43
+ private
44
+
45
+ def clean_subfield_value(tag, val)
46
+ return val if tag != '269'
47
+
48
+ new_val = val.downcase.sub(/u$/, '0')
49
+ qualified_269?(new_val) ? new_val : nil
50
+ end
51
+
52
+ def qualified_269?(val)
53
+ val =~ /^\d{4}$/
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ require 'marc'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module Mapping
6
+ module TindField
7
+ class << self
8
+
9
+ def f_035_from_alma_id(alma_id, value_980)
10
+ val = "(#{value_980})#{alma_id}"
11
+ f('035', 'a', val)
12
+ end
13
+
14
+ def f_035(val)
15
+ f('035', 'a', val)
16
+ end
17
+
18
+ def f_245_p(val)
19
+ f('245', 'p', val)
20
+ end
21
+
22
+ def f_fft(url, txt = None)
23
+ return f('FFT', 'a', url) unless txt
24
+
25
+ ::MARC::DataField.new('FFT', ' ', ' ', ['d', txt], ['a', url])
26
+ end
27
+
28
+ def f_902_d
29
+ f('902', 'd', Time.now.strftime('%F'))
30
+ end
31
+
32
+ def f_902_n(name_initial)
33
+ f('902', 'n', name_initial)
34
+ end
35
+
36
+ def f_982_p(val)
37
+ f('982', 'p', val)
38
+ end
39
+
40
+ def f(tag, code, value)
41
+ ::MARC::DataField.new(tag, ' ', ' ', [code, value])
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -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,170 @@
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
+ @to_subfields = all_subfields
43
+ end
44
+
45
+ def to_datafield
46
+ return nil unless mapped?
47
+
48
+ tindfield = Util.datafield(@map_to_tag, @indicator, @to_subfields)
49
+ @is_880_field ? reversed_880_field(tindfield) : tindfield
50
+ end
51
+
52
+ private
53
+
54
+ # A referred tag from 880 subfield6 may not have a rule
55
+ # For example: 880 subfild6 pass in a value in wrong format
56
+ # In above case, rule is nil
57
+ # Get mapping parameters from rule when having a rule
58
+ def ready_to_mapping?
59
+ return false unless @mapping_rule
60
+
61
+ @map_to_tag = @mapping_rule.tag_destination
62
+ @indicator = @mapping_rule.indicator
63
+ @single_mapping = @excluding_subfield ? @mapping_rule.single_rule_subfield_excluded_hash : @mapping_rule.single_rule_hash
64
+
65
+ return false unless @map_to_tag && @indicator && !@single_mapping.empty?
66
+
67
+ true
68
+ end
69
+
70
+ def mapped?
71
+ !@to_subfields.empty?
72
+ end
73
+
74
+ # tag - regular alma field
75
+ # referred tag - got tag from subfield6 value of a 880 field
76
+ # nil rule caused by nil referred tag - eg. 880 subfild6 pass in a value in wrong format
77
+ def rule
78
+ tag = origin_mapping_tag(@from_datafield)
79
+ return nil unless tag
80
+
81
+ rules[Util.tag_symbol(tag)]
82
+ end
83
+
84
+ def all_subfields
85
+ @ready_to_mapping ? (subfields_from_single_map + subfields_from_combined_map) : []
86
+ end
87
+
88
+ # 1.subfields mapped with single rule, mapping one subfield to another subfield
89
+ # 2. one subfield is mapped to one subfield
90
+ # 3. When mutiple subfields with the same name found in an orignal field,
91
+ # they will be mapped one by one
92
+ def subfields_from_single_map
93
+ return [] if @single_mapping.empty?
94
+
95
+ mapped_subfields = []
96
+ @single_mapping.each do |from, to|
97
+ subfields = subfields_from_to(@from_datafield, from, to)
98
+ mapped_subfields.concat(subfields)
99
+ end
100
+ mapped_subfields
101
+ end
102
+
103
+ # return all subfields mapped with diferent combined rules - different destination subfield names
104
+ # mapped with all combined rules, exmaple: [[["a,b,c,d", "b", "--"],["o,p,q", "b", ""]],[["x,y,z", "a", "--"]]]
105
+ # mapping using above example rules will return two subfield: $b, $a
106
+ def subfields_from_combined_map
107
+ all_rules = @mapping_rule.combined_rules
108
+ return [] if all_rules.empty?
109
+
110
+ mapped_subfields = []
111
+ all_rules.each do |rules|
112
+ subfield = subfield_on_same_tosubfieldname(rules)
113
+ mapped_subfields.push(subfield) if subfield
114
+ end
115
+ mapped_subfields
116
+ end
117
+
118
+ # create one subfield with a desintaion subfield name
119
+ # input array of rules example: [["a,b,c,d", "b", "--"],["o,p,q", "b", ""]] -- all rules with the same destination subfield name "b"
120
+ # get a subfield$b with a concatenated value
121
+ def subfield_on_same_tosubfieldname(rules)
122
+ return nil if rules.empty?
123
+
124
+ val = subfield_value_on_rules(rules)
125
+ return nil if val.strip.empty?
126
+
127
+ subfield_name_to = rules[0][1]
128
+ Util.subfield(subfield_name_to, Util.remove_extra_symbol(rules, val))
129
+ end
130
+
131
+ # input an array of rules, example: [["a,b,c,d", "b", "--"],["o,p,q", "b", ""]]
132
+ # Theese rules have the same destination subfield name, for example "b" in above example
133
+ # get a value concatenated with values mapped using different rules
134
+ def subfield_value_on_rules(rules)
135
+ val = ''
136
+ rules.each { |rule| val << subfield_value_on_rule(rule) }
137
+ val
138
+ end
139
+
140
+ # input a rule (for example ["a,b,c,d", "b", "--"]),
141
+ # get a combined value of subfield a,b,c,d concatenated by " -- " as above example
142
+ # One subfield names may occurs mutiple times in a an orignal field
143
+ def subfield_value_on_rule(rule)
144
+ subfield_names_from = rule[0].strip.split(',')
145
+ symbol = Util.concatenation_symbol(rule[2])
146
+ val = ''
147
+ subfield_names_from.each do |subfield_name|
148
+ sub_val = combined_subfield_value(@from_datafield, subfield_name, symbol)
149
+ val << sub_val
150
+ end
151
+ val
152
+ end
153
+
154
+ # 880 datafield: reverse tag from 'to_tag' defined mapping rule to '880'
155
+ def reversed_880_field(f)
156
+ update_datafield6(f)
157
+ f.tag = '880'
158
+ f
159
+ end
160
+
161
+ # update subfield6 tag with destination tag from the rule
162
+ # since an origin tag may have been mapped a different tag - destination tag
163
+ def update_datafield6(f) # need test
164
+ f['6'].sub!(@mapping_rule.tag_origin, @mapping_rule.tag_destination)
165
+ end
166
+
167
+ end
168
+ end
169
+ end
170
+ end