diff_json 0.1.2 → 1.1.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.
@@ -0,0 +1,201 @@
1
+ module JsonDiffing
2
+ private
3
+
4
+ def diff_check(old_element, new_element, base_path = '')
5
+ diff_operations = {}
6
+
7
+ if (old_element.is_a?(Array) and new_element.is_a?(Array)) or (old_element.is_a?(Hash) and new_element.is_a?(Hash))
8
+ element_operations = case old_element.class.name
9
+ when 'Array'
10
+ diff_array(old_element, new_element, base_path)
11
+ when 'Hash'
12
+ diff_hash(old_element, new_element, base_path)
13
+ end
14
+
15
+ if @opts[:track_structure_updates]
16
+ element_operations[base_path] = [{op: :update}] if element_operations.select{|k,v| count_path?(k, "#{base_path}/*")}.length > 0
17
+ end
18
+
19
+ diff_operations.merge!(element_operations)
20
+ else
21
+ diff_operations[base_path] = [{op: :replace, path: base_path, from: old_element, value: new_element}] unless old_element == new_element
22
+ end
23
+
24
+ return diff_operations
25
+ end
26
+
27
+ def diff_array(old_array, new_array, base_path)
28
+ return {} if old_array == new_array
29
+
30
+ diff_operations = {}
31
+ add_drop_operations = {}
32
+ last_shared_index = (old_array.length - 1)
33
+
34
+ if @opts[:replace_primitives_arrays]
35
+ if @old_map[base_path][:array_type] == :primitives and @new_map[base_path][:array_type] == :primitives
36
+ diff_operations[base_path] = [{op: :replace, path: base_path, from: old_array, value: new_array}]
37
+ return diff_operations
38
+ end
39
+ end
40
+
41
+ if @opts[:track_array_moves]
42
+ old_array_map = old_array.each_with_index.map{|v,i| [i, v]}
43
+ new_array_map = new_array.each_with_index.map{|v,i| [i, v]}
44
+ shared_elements = (old_array_map & new_array_map)
45
+ old_move_check = (old_array_map - shared_elements)
46
+ new_move_check = (new_array_map - shared_elements)
47
+ possible_moves = []
48
+ max_moves = (old_move_check.length < new_move_check.length ? old_move_check.length : new_move_check.length)
49
+
50
+ if max_moves > 0
51
+ old_move_check.each do |omc|
52
+ destinations = new_move_check.map{|v| omc[1] == v[1] ? [(omc[0] - v[0]).abs, omc[0], v[0]] : nil}.compact.sort_by{|x| x[0]}
53
+ if !destinations.empty? and possible_moves.length < max_moves
54
+ possible_moves << {op: :move, from: "#{base_path}/#{destinations.first[1]}", path: "#{base_path}/#{destinations.first[2]}"}
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ if new_array.length > old_array.length
61
+ new_array[(old_array.length)..(new_array.length - 1)].each_with_index do |value, i|
62
+ element_path = "#{base_path}/#{(old_array.length + i)}"
63
+ add_drop_operations[element_path] = [{op: :add, path: element_path, value: value}]
64
+
65
+ if @opts[:track_array_moves]
66
+ element_move_search = possible_moves.select{|x| x[:path] == element_path}
67
+ add_drop_operations[element_path] += element_move_search
68
+ end
69
+ end
70
+ elsif old_array.length > new_array.length
71
+ last_shared_index = new_array.length - 1
72
+
73
+ old_array[(new_array.length)..(old_array.length - 1)].each_with_index do |value, i|
74
+ element_index = (new_array.length + i)
75
+ element_path = "#{base_path}/#{element_index}"
76
+ add_drop_operations[element_path] = [{op: :remove, path: element_path, value: old_array[element_index]}]
77
+
78
+ if @opts[:track_array_moves]
79
+ element_move_search = possible_moves.select{|x| x[:from] == element_path}
80
+ add_drop_operations[element_path] += element_move_search
81
+ end
82
+ end
83
+ end
84
+
85
+ (0..last_shared_index).each do |i|
86
+ index_path = "#{base_path}/#{i}"
87
+
88
+ if @opts[:track_array_moves]
89
+ element_move_search = possible_moves.select{|x| x[:from] == index_path or x[:path] == index_path}
90
+ element_move_search << {op: :replace, path: index_path, value: new_array[i]} if element_move_search.length == 1
91
+ diff_operations.merge!(element_move_search.empty? ? diff_check(old_array[i], new_array[i], index_path) : {index_path => element_move_search})
92
+ else
93
+ unless @opts[:ignore_paths].include?(index_path)
94
+ diff_operations.merge!(diff_check(old_array[i], new_array[i], index_path))
95
+ else
96
+ diff_operations[index_path] = [{op: :ignore}]
97
+ end
98
+ end
99
+ end
100
+
101
+ diff_operations.merge!(add_drop_operations)
102
+
103
+ return diff_operations
104
+ end
105
+
106
+ def diff_hash(old_hash, new_hash, base_path)
107
+ return {} if old_hash == new_hash
108
+
109
+ diff_operations = {}
110
+ old_keys, new_keys = old_hash.keys, new_hash.keys
111
+ common_keys, added_keys, dropped_keys = (old_keys & new_keys), (new_keys - old_keys), (old_keys - new_keys)
112
+
113
+ common_keys.each do |ck|
114
+ element_path = "#{base_path}/#{ck}"
115
+
116
+ unless @opts[:ignore_paths].include?(element_path)
117
+ diff_operations.merge!(diff_check(old_hash[ck], new_hash[ck], element_path))
118
+ else
119
+ diff_operations[element_path] = [{op: :ignore}]
120
+ end
121
+ end
122
+
123
+ added_keys.each do |ak|
124
+ element_path = "#{base_path}/#{ak}"
125
+ diff_operations[element_path] = [{op: :add, path: element_path, value: new_hash[ak]}]
126
+ end
127
+
128
+ dropped_keys.each do |dk|
129
+ element_path = "#{base_path}/#{dk}"
130
+ diff_operations[element_path] = [{op: :remove, path: element_path}]
131
+ end
132
+
133
+ return diff_operations
134
+ end
135
+
136
+ def generate_sub_diffs
137
+ sub_diffs = {}
138
+
139
+ @opts[:sub_diffs].each do |k,v|
140
+ sub_diff_paths = @all_paths.select{|x| count_path?(x, k)}
141
+ old_elements = @old_map.select{|k,v| sub_diff_paths.include?(k)}.values.map{|x| {x[:value][v[:key]] => x[:value]}}.reduce(:merge)
142
+ new_elements = @new_map.select{|k,v| sub_diff_paths.include?(k)}.values.map{|x| {x[:value][v[:key]] => x[:value]}}.reduce(:merge)
143
+
144
+ (old_elements.keys + new_elements.keys).uniq.each do |sub_diff_id|
145
+ sub_diffs["#{k}::#{sub_diff_id}"] = DiffJson::Diff.new((old_elements[sub_diff_id] || {}), (new_elements[sub_diff_id] || {}), **v[:opts]) unless old_elements[sub_diff_id] == new_elements[sub_diff_id]
146
+ end
147
+ end
148
+
149
+ return sub_diffs
150
+ end
151
+
152
+ def find_counts(diff_structure)
153
+ counts = {
154
+ ignore: 0,
155
+ add: 0,
156
+ replace: 0,
157
+ remove: 0
158
+ }
159
+ counts[:move] = 0 if @opts[:track_array_moves]
160
+ counts[:update] = 0 if @opts[:track_structure_updates]
161
+
162
+ diff_structure.each do |path, operations|
163
+ inclusion = path_inclusion(path)
164
+
165
+ operations.each do |op|
166
+ counts[op[:op]] += 1 if (inclusion.include?(op[:op]) and ([:ignore, :add, :replace, :remove, :update].include?(op[:op]) or (op[:op] == :move and path == op[:from])))
167
+ end
168
+ end
169
+
170
+ return counts
171
+ end
172
+
173
+ def path_inclusion(path)
174
+ if @opts[:count_operations].include?('**')
175
+ return @opts[:count_operations]['**']
176
+ else
177
+ @opts[:count_operations].each do |path_set, operations|
178
+ return operations if count_path?(path, path_set)
179
+ end
180
+
181
+ return []
182
+ end
183
+ end
184
+
185
+ def count_path?(path, inclusion)
186
+ inclusion_base = inclusion.gsub(/\/?\**$/, '')
187
+ inclusion_wildcard = /\**$/.match(inclusion)[0]
188
+
189
+ if path.include?(inclusion_base)
190
+ trailing_elements = path.gsub(/^#{inclusion_base}\/?/, '').split('/').length
191
+
192
+ return true if (
193
+ (trailing_elements == 0 and inclusion_wildcard == '') or
194
+ (trailing_elements == 1 and inclusion_wildcard == '*') or
195
+ (trailing_elements > 0 and inclusion_wildcard == '**')
196
+ )
197
+ end
198
+
199
+ return false
200
+ end
201
+ end
@@ -0,0 +1,122 @@
1
+ module JsonMapping
2
+ private
3
+
4
+ def path_indentation(path)
5
+ return 0 if path.empty?
6
+ return path.sub('/', '').split('/').length
7
+ end
8
+
9
+ def sortable_path(path)
10
+ return [''] if path.empty?
11
+ return path.split('/').map{|p| (p =~ /^\d+$/).nil? ? p : p.to_i}
12
+ end
13
+
14
+ def gather_paths(old_paths, new_paths, sort = false)
15
+ gathered_paths = []
16
+
17
+ if sort
18
+ sortable_paths = (old_paths | new_paths).map{|path| sortable_path(path)}
19
+
20
+ sortable_paths.sort! do |x,y|
21
+ last_index = x.length > y.length ? (x.length - 1) : (y.length - 1)
22
+ sort_value = nil
23
+
24
+ (0..last_index).each do |i|
25
+ next if x[i] == y[i]
26
+
27
+ sort_value = case [x[i].class.name, y[i].class.name]
28
+ when ['NilClass', 'Fixnum'], ['NilClass', 'Integer'], ['NilClass', 'String'], ['Fixnum', 'String'], ['Integer', 'String']
29
+ -1
30
+ when ['Fixnum', 'NilClass'], ['Integer', 'NilClass'], ['String', 'NilClass'], ['String', 'Fixnum'], ['String', 'Integer']
31
+ 1
32
+ else
33
+ x[i] <=> y[i]
34
+ end
35
+
36
+ break unless sort_value.nil?
37
+ end
38
+
39
+ sort_value
40
+ end
41
+
42
+ return sortable_paths.map{|path| path.join('/')}
43
+ else
44
+ ### Implementation in progress, for now, raise error
45
+ raise 'Natural sort order is WIP, for now, do not override the :path_sort option'
46
+ end
47
+
48
+ return gathered_paths
49
+ end
50
+
51
+ def is_structure?(value)
52
+ return (value.is_a?(Array) or value.is_a?(Hash))
53
+ end
54
+
55
+ def element_metadata(path, value, **overrides)
56
+ hash_list = (value.is_a?(Array) ? value.map{|x| x.hash} : [])
57
+ is_structure = is_structure?(value)
58
+ array_type = nil
59
+
60
+ if is_structure and value.is_a?(Array)
61
+ structure_detection = value.map{|v| is_structure?(v)}.uniq
62
+
63
+ array_type = if structure_detection.empty?
64
+ :empty
65
+ elsif structure_detection.length > 1
66
+ :mixed
67
+ else
68
+ (structure_detection.first ? :structures : :primitives)
69
+ end
70
+ end
71
+
72
+ return {
73
+ :hash_list => hash_list,
74
+ :indentation => path_indentation(path),
75
+ :index => 0,
76
+ :key => nil,
77
+ :length => (is_structure ? value.length : nil),
78
+ :trailing_comma => false,
79
+ :type => (is_structure ? value.class.name.downcase.to_sym : :primitive),
80
+ :array_type => array_type,
81
+ :value => value
82
+ }.merge(overrides)
83
+ end
84
+
85
+ def map_json(json, base_path, index, parent_length = 1, **metadata_overrides)
86
+ map = {}
87
+ map[base_path] = element_metadata(base_path, json, index: index, trailing_comma: (index < (parent_length - 1)), **metadata_overrides)
88
+
89
+ if json.is_a?(Array)
90
+ json.each_with_index do |value, i|
91
+ index_path = "#{base_path}/#{i}"
92
+
93
+ if value.is_a?(Array)
94
+ map.merge!(map_json(value, index_path, i, value.length))
95
+ elsif value.is_a?(Hash)
96
+ map.merge!(map_json(value, index_path, i, value.keys.length))
97
+ else
98
+ map[index_path] = element_metadata(index_path, value, index: i, trailing_comma: (i < (json.length - 1)))
99
+ end
100
+ end
101
+ elsif json.is_a?(Hash)
102
+ json = (@opts[:path_sort] == :sorted ? json.to_a.sort.to_h : json)
103
+ key_index = 0
104
+
105
+ json.each do |key, value|
106
+ key_path = "#{base_path}/#{key}"
107
+
108
+ if value.is_a?(Array)
109
+ map.merge!(map_json(value, key_path, key_index, value.length, key: key))
110
+ elsif value.is_a?(Hash)
111
+ map.merge!(map_json(value, key_path, key_index, value.keys.length, key: key))
112
+ else
113
+ map[key_path] = element_metadata(key_path, value, index: key_index, trailing_comma: (key_index < (json.keys.length - 1)), key: key)
114
+ end
115
+
116
+ key_index = key_index.next
117
+ end
118
+ end
119
+
120
+ return map
121
+ end
122
+ end
@@ -0,0 +1,268 @@
1
+ module DiffJson
2
+ class HtmlOutput
3
+ def initialize(diff, **opts)
4
+ @diff = diff
5
+ @opts = {
6
+ table_id_prefix: 'diff_json_view_0',
7
+ markup_type: :bootstrap
8
+ }
9
+ @markup = build
10
+ end
11
+
12
+ def diff
13
+ return @diff
14
+ end
15
+
16
+ def markup
17
+ return @markup
18
+ end
19
+
20
+ private
21
+
22
+ def build
23
+ new_markup = {main: table_markup(@diff, @opts[:table_id_prefix]), sub_diffs: {}}
24
+
25
+ @diff.sub_diffs.each do |sdid, sub_diff|
26
+ new_markup[:sub_diffs][sdid] = HtmlOutput.new(sub_diff, @opts)
27
+ end
28
+
29
+ return new_markup
30
+ end
31
+
32
+ def table_markup(table_diff, table_id_prefix)
33
+ markup_lines = {left: "", right: "", full: "", sub_diffs: {}}
34
+
35
+ html_opener = self.method("html_#{@opts[:markup_type]}_opener".to_sym).call(table_id_prefix)
36
+ markup_lines[:left] = html_opener[:left]
37
+ markup_lines[:right] = html_opener[:right]
38
+ markup_lines[:full] = html_opener[:full]
39
+
40
+ hierarchy_lock = nil
41
+ structure_queue = []
42
+
43
+ table_diff.paths.each_with_index do |path, i|
44
+ skip_path = (!hierarchy_lock.nil? and !(path =~ /^#{hierarchy_lock}.+$/).nil?)
45
+ unless skip_path
46
+ if !hierarchy_lock.nil?
47
+ hierarchy_lock = nil
48
+ end
49
+
50
+ old_element, new_element = table_diff.json_map(:old)[path], table_diff.json_map(:new)[path]
51
+
52
+ operations = (table_diff.diff[path] || []).map{|op|
53
+ case op[:op]
54
+ when :ignore, :add, :replace, :remove
55
+ op[:op]
56
+ when :move
57
+ op[:from] == path ? :send_move : :receive_move
58
+ end
59
+ }.compact
60
+
61
+ if operations.empty? or operations.include?(:ignore)
62
+ left_operators = ' '
63
+ right_operators = ' '
64
+ else
65
+ left_operators = [operations.include?(:send_move) ? 'M' : ' ', (operations & [:replace, :remove]).length > 0 ? '-' : ' '].join
66
+ right_operators = [operations.include?(:receive_move) ? 'M' : ' ', (operations & [:add, :replace]).length > 0 ? '+' : ' '].join
67
+ end
68
+
69
+ if !old_element.nil? and !new_element.nil?
70
+ if (old_element[:value] == new_element[:value]) or (operations & [:ignore, :replace]).length > 0 or (operations & [:send_move, :receive_move]).length == 2
71
+ old_lines, new_lines = balance_output(old_element[:value], new_element[:value], indentation: old_element[:indentation], old_key: old_element[:key], new_key: new_element[:key], old_comma: old_element[:trailing_comma], new_comma: new_element[:trailing_comma])
72
+ hierarchy_lock = path unless old_element[:type] == :primitive and new_element[:type] == :primitive
73
+ else
74
+ old_lines = jpg(nil, structure: true, structure_position: :open, structure_type: old_element[:type], indentation: old_element[:indentation], key: old_element[:key], trailing_comma: false)
75
+ new_lines = jpg(nil, structure: true, structure_position: :open, structure_type: new_element[:type], indentation: new_element[:indentation], key: new_element[:key], trailing_comma: false)
76
+ structure_queue.push({path: path, type: old_element[:type], indentation: old_element[:indentation], old_comma: old_element[:trailing_comma], new_comma: new_element[:trailing_comma]})
77
+ end
78
+ elsif old_element.nil?
79
+ old_lines, new_lines = balance_output(UndefinedValue.new, new_element[:value], indentation: new_element[:indentation], new_key: new_element[:key], new_comma: new_element[:trailing_comma])
80
+ hierarchy_lock = path unless new_element[:type] == :primitive
81
+ else
82
+ old_lines, new_lines = balance_output(old_element[:value], UndefinedValue.new, indentation: old_element[:indentation], old_key: old_element[:key], old_comma: old_element[:trailing_comma])
83
+ hierarchy_lock = path unless old_element[:type] == :primitive
84
+ end
85
+
86
+ compiled_lines = self.method("html_#{@opts[:markup_type]}_lines".to_sym).call(left_operators, old_lines, right_operators, new_lines)
87
+ markup_lines[:left] << compiled_lines[:left]
88
+ markup_lines[:right] << compiled_lines[:right]
89
+ markup_lines[:full] << compiled_lines[:full]
90
+ end
91
+
92
+ unless structure_queue.empty?
93
+ if i == (table_diff.paths.length - 1)
94
+ structure_queue.reverse.each do |sq|
95
+ old_lines = jpg(nil, structure: true, structure_position: :close, structure_type: sq[:type], indentation: sq[:indentation], trailing_comma: sq[:old_comma])
96
+ new_lines = jpg(nil, structure: true, structure_position: :close, structure_type: sq[:type], indentation: sq[:indentation], trailing_comma: sq[:new_comma])
97
+ compiled_lines = self.method("html_#{@opts[:markup_type]}_lines".to_sym).call(' ', old_lines, ' ', new_lines)
98
+ markup_lines[:left] << compiled_lines[:left]
99
+ markup_lines[:right] << compiled_lines[:right]
100
+ markup_lines[:full] << compiled_lines[:full]
101
+ end
102
+ else
103
+ if (table_diff.paths[(i+1)] =~ /^#{structure_queue.last[:path]}.+$/).nil?
104
+ sq = structure_queue.pop
105
+ old_lines = jpg(nil, structure: true, structure_position: :close, structure_type: sq[:type], indentation: sq[:indentation], trailing_comma: sq[:old_comma])
106
+ new_lines = jpg(nil, structure: true, structure_position: :close, structure_type: sq[:type], indentation: sq[:indentation], trailing_comma: sq[:new_comma])
107
+ compiled_lines = self.method("html_#{@opts[:markup_type]}_lines".to_sym).call(' ', old_lines, ' ', new_lines)
108
+ markup_lines[:left] << compiled_lines[:left]
109
+ markup_lines[:right] << compiled_lines[:right]
110
+ markup_lines[:full] << compiled_lines[:full]
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ html_closer = self.method("html_#{@opts[:markup_type]}_closer".to_sym).call
117
+ markup_lines[:left] << html_closer[:left]
118
+ markup_lines[:right] << html_closer[:right]
119
+ markup_lines[:full] << html_closer[:full]
120
+
121
+ return markup_lines
122
+ end
123
+
124
+ def html_table_opener(table_id_prefix)
125
+ compiled_lines = {}
126
+ compiled_lines[:left] = "<table id=\"#{table_id_prefix}_left\" class=\"diff-json-view diff-json-split-view-left\">\n"
127
+ compiled_lines[:right] = "<table id=\"#{table_id_prefix}_right\" class=\"diff-json-view diff-json-split-view-right\">\n"
128
+ compiled_lines[:full] = "<table id=\"#{table_id_prefix}_full\" class=\"diff-json-view diff-json-full-view\">\n"
129
+
130
+ return compiled_lines
131
+ end
132
+
133
+ def html_table_lines(left_operators, left_lines, right_operators, right_lines)
134
+ compiled_lines = {left: "", right: "", full: ""}
135
+
136
+ (0..(left_lines.length - 1)).each do |i|
137
+ compiled_lines[:left] << <<-EOL
138
+ <tr class="diff-json-view-line">
139
+ <td class="diff-json-view-line-operator"><pre>#{left_operators unless left_lines[i].empty?}</pre></td>
140
+ <td class="diff-json-view-line-content"><pre class="diff-json-line-breaker">#{left_lines[i]}</pre></td>
141
+ </tr>
142
+ EOL
143
+ compiled_lines[:right] << <<-EOL
144
+ <tr class="diff-json-view-line">
145
+ <td class="diff-json-view-line-operator"><pre>#{right_operators unless right_lines[i].empty?}</pre></td>
146
+ <td class="diff-json-view-line-content"><pre class="diff-json-line-breaker">#{right_lines[i]}</pre></td>
147
+ </tr>
148
+ EOL
149
+ compiled_lines[:full] << <<-EOL
150
+ <tr class="diff-json-view-line">
151
+ <div class="row">
152
+ <td class="diff-json-view-line-operator"><pre>#{left_operators unless left_lines[i].empty?}</pre></td>
153
+ <td class="diff-json-view-line-content"><pre class="diff-json-line-breaker">#{left_lines[i]}</pre></td>
154
+ <td class="diff-json-view-column-break"></td>
155
+ <td class="diff-json-view-line-operator"><pre>#{right_operators unless right_lines[i].empty?}</pre></td>
156
+ <td class="diff-json-view-line-content"><pre class="diff-json-line-breaker">#{right_lines[i]}</pre></td>
157
+ </div>
158
+ </tr>
159
+ EOL
160
+ end
161
+
162
+ return compiled_lines
163
+ end
164
+
165
+ def html_table_closer
166
+ compiled_lines = {}
167
+ compiled_lines[:left] = "</table>"
168
+ compiled_lines[:right] = "</table>"
169
+ compiled_lines[:full] = "</table>"
170
+
171
+ return compiled_lines
172
+ end
173
+
174
+ def html_bootstrap_opener(table_id_prefix)
175
+ compiled_lines = {}
176
+ compiled_lines[:left] = "<div id=\"#{table_id_prefix}_left\" class=\"diff-json-view diff-json-split-view-left col-xs-6 col-6\">\n"
177
+ compiled_lines[:right] = "<div id=\"#{table_id_prefix}_right\" class=\"diff-json-view diff-json-split-view-right col-xs-6 col-6\">\n"
178
+ compiled_lines[:full] = "<div id=\"#{table_id_prefix}_full\" class=\"diff-json-view diff-json-full-view col-xs-12 col-12\">\n"
179
+
180
+ return compiled_lines
181
+ end
182
+
183
+ def html_bootstrap_lines(left_operators, left_lines, right_operators, right_lines)
184
+ compiled_lines = {left: "", right: "", full: ""}
185
+
186
+ (0..(left_lines.length - 1)).each do |i|
187
+ compiled_lines[:left] << <<-EOL
188
+ <div class="diff-json-view-line row">
189
+ <div class="diff-json-view-line-operator col-xs-1"><pre>#{left_operators unless left_lines[i].empty?}</pre></div>
190
+ <div class="diff-json-view-line-content col-xs-11"><pre class="diff-json-line-breaker #{highlight_class(left_operators) unless left_lines[i].empty?}">#{left_lines[i]}</pre></div>
191
+ </div>
192
+ EOL
193
+ compiled_lines[:right] << <<-EOL
194
+ <div class="diff-json-view-line row">
195
+ <div class="diff-json-view-line-operator col-xs-1"><pre>#{right_operators unless right_lines[i].empty?}</pre></div>
196
+ <div class="diff-json-view-line-content col-xs-11"><pre class="diff-json-line-breaker #{highlight_class(right_operators) unless right_lines[i].empty?}">#{right_lines[i]}</pre></div>
197
+ </div>
198
+ EOL
199
+ compiled_lines[:full] << <<-EOL
200
+ <div class="diff-json-view-line row">
201
+ <div class="diff-json-view-line-left col-xs-6">
202
+ <pre class="diff-json-line-breaker #{highlight_class(left_operators) unless left_lines[i].empty?}">#{left_operators unless left_lines[i].empty?} #{left_lines[i]}</pre>
203
+ </div>
204
+ <div class="diff-json-view-line-right col-xs-6">
205
+ <pre class="diff-json-line-breaker #{highlight_class(right_operators) unless right_lines[i].empty?}">#{right_operators unless right_lines[i].empty?} #{right_lines[i]}</pre>
206
+ </div>
207
+ </div>
208
+ EOL
209
+ end
210
+
211
+ return compiled_lines
212
+ end
213
+
214
+ def html_bootstrap_closer
215
+ compiled_lines = {}
216
+ compiled_lines[:left] = "</div>"
217
+ compiled_lines[:right] = "</div>"
218
+ compiled_lines[:full] = "</div>"
219
+
220
+ return compiled_lines
221
+ end
222
+
223
+ def highlight_class(operators)
224
+ return '' if operators.empty?
225
+ return 'diff-json-content-ins' if operators.include?('+')
226
+ return 'diff-json-content-del' if operators.include?('-')
227
+ return 'diff-json-content-mov' if operators.include?('M')
228
+ end
229
+
230
+ def balance_output(old_element, new_element, indentation: 0, old_key: nil, new_key: nil, old_comma: false, new_comma: false)
231
+ old_lines, new_lines = jpg(old_element, indentation: indentation, key: old_key, trailing_comma: old_comma), jpg(new_element, indentation: indentation, key: new_key, trailing_comma: new_comma)
232
+ return old_lines, new_lines if old_lines.length == new_lines.length
233
+
234
+ if old_lines.length > new_lines.length
235
+ (old_lines.length - new_lines.length).times do
236
+ new_lines << ''
237
+ end
238
+ else
239
+ (new_lines.length - old_lines.length).times do
240
+ old_lines << ''
241
+ end
242
+ end
243
+
244
+ return old_lines, new_lines
245
+ end
246
+
247
+ def jpg(json_element, structure: false, structure_position: :open, structure_type: :array, indentation: 0, key: nil, trailing_comma: false)
248
+ return [] if json_element.is_a?(DiffJson::UndefinedValue)
249
+ if structure
250
+ generated_element = case [structure_position, structure_type]
251
+ when [:open, :array]
252
+ ['[']
253
+ when [:close, :array]
254
+ [']']
255
+ when [:open, :hash]
256
+ ['{']
257
+ when [:close, :hash]
258
+ ['}']
259
+ end
260
+ else
261
+ generated_element = JSON.pretty_generate(json_element, max_nesting: false, quirks_mode: true).lines
262
+ end
263
+ generated_element[0].prepend("#{key}: ") unless key.nil?
264
+ generated_element.last << ',' if trailing_comma
265
+ return generated_element.map{|line| line.prepend(' ' * indentation)}
266
+ end
267
+ end
268
+ end