berkeley_library-tind 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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