sp-excel-loader 0.3.40

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +661 -0
  4. data/README.md +8 -0
  5. data/lib/sp-excel-loader.rb +6 -0
  6. data/lib/sp/excel/loader.rb +61 -0
  7. data/lib/sp/excel/loader/jrxml/band.rb +80 -0
  8. data/lib/sp/excel/loader/jrxml/band_container.rb +229 -0
  9. data/lib/sp/excel/loader/jrxml/box.rb +75 -0
  10. data/lib/sp/excel/loader/jrxml/casper_checkbox.rb +97 -0
  11. data/lib/sp/excel/loader/jrxml/casper_combo.rb +86 -0
  12. data/lib/sp/excel/loader/jrxml/casper_date.rb +54 -0
  13. data/lib/sp/excel/loader/jrxml/casper_radio_button.rb +48 -0
  14. data/lib/sp/excel/loader/jrxml/casper_text_field.rb +157 -0
  15. data/lib/sp/excel/loader/jrxml/client_combo_text_field.rb +72 -0
  16. data/lib/sp/excel/loader/jrxml/excel_to_jrxml.rb +1183 -0
  17. data/lib/sp/excel/loader/jrxml/extensions.rb +330 -0
  18. data/lib/sp/excel/loader/jrxml/field.rb +65 -0
  19. data/lib/sp/excel/loader/jrxml/group.rb +71 -0
  20. data/lib/sp/excel/loader/jrxml/image.rb +63 -0
  21. data/lib/sp/excel/loader/jrxml/jasper.rb +228 -0
  22. data/lib/sp/excel/loader/jrxml/parameter.rb +73 -0
  23. data/lib/sp/excel/loader/jrxml/pen.rb +97 -0
  24. data/lib/sp/excel/loader/jrxml/property.rb +52 -0
  25. data/lib/sp/excel/loader/jrxml/property_expression.rb +52 -0
  26. data/lib/sp/excel/loader/jrxml/report_element.rb +92 -0
  27. data/lib/sp/excel/loader/jrxml/static_text.rb +59 -0
  28. data/lib/sp/excel/loader/jrxml/style.rb +99 -0
  29. data/lib/sp/excel/loader/jrxml/text_field.rb +83 -0
  30. data/lib/sp/excel/loader/jrxml/variable.rb +77 -0
  31. data/lib/sp/excel/loader/json_to_xlsx.rb +159 -0
  32. data/lib/sp/excel/loader/model_exporter.rb +249 -0
  33. data/lib/sp/excel/loader/payrollexporter.rb +168 -0
  34. data/lib/sp/excel/loader/rubyxl_table_patch.rb +91 -0
  35. data/lib/sp/excel/loader/version.rb +26 -0
  36. data/lib/sp/excel/loader/workbookloader.rb +480 -0
  37. data/spec/calc_spec.rb +87 -0
  38. data/spec/model.xls +0 -0
  39. metadata +151 -0
@@ -0,0 +1,159 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
4
+ #
5
+ # This file is part of sp-excel-loader.
6
+ #
7
+ # sp-excel-loader is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # sp-excel-loader is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with sp-excel-loader. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ module Sp
22
+ module Excel
23
+ module Loader
24
+
25
+ class JsonToXlsx < WorkbookLoader
26
+
27
+ attr_accessor :json_data
28
+ attr_accessor :fields
29
+
30
+ def initialize (a_excel_template, a_json)
31
+ super(a_excel_template)
32
+ @json_data = a_json
33
+ end
34
+
35
+ def convert_to_xls ()
36
+ ws, tbl, ref = find_table('lines')
37
+
38
+ # Detect optional report mode
39
+ is_report = false
40
+ if !ws[0].nil? && !ws[0][0].nil?
41
+ ws[0][0].value.lines.each do |line|
42
+ directive, value = line.split(':')
43
+ if directive.strip == 'IsReport' and value.strip == 'true'
44
+ is_report = true
45
+ end
46
+ end
47
+ end
48
+
49
+ headers_idx = 0 .. ref.row_range.begin() - 1
50
+ footers_idx = ref.row_range.end() + 1 .. ws.count - 1
51
+
52
+ # Replace parameters in header and footer rows
53
+ (headers_idx.to_a + footers_idx.to_a).each do |row|
54
+ ref.col_range.each do |col|
55
+ next if ws[row].nil?
56
+ next if ws[row][col].nil?
57
+ value = ws[row][col].value
58
+ next if value.nil?
59
+
60
+ value = value.to_s
61
+ @json_data['data']['attributes'].each do |key, val|
62
+ value = value.gsub('$P{' + key +'}', val.to_s)
63
+ end
64
+ value = value.gsub('$V{PAGE_NUMBER}', '1')
65
+
66
+ unless @json_data['included'].nil? or @json_data['included'].size == 0
67
+ @json_data['included'][0]['attributes'].each do |key,val|
68
+ value = value.gsub('$FN?{' + key +'}', val.to_s)
69
+ end
70
+ end
71
+ ws[row][col].change_contents(value)
72
+ end
73
+ end
74
+
75
+ # Collect mapped fields
76
+ header_row = ref.row_range.begin()
77
+ dst_row = header_row + 1
78
+ fields = Hash.new
79
+ null_fields = Array.new
80
+
81
+ ref.col_range.each do |col|
82
+ cell = ws[dst_row][col]
83
+ next if cell.nil?
84
+ next if cell.value.nil?
85
+ m = /\A\$(FN?){(.+)}\z/.match cell.value.strip()
86
+ next if m.nil?
87
+ null_fields[col] = m[1] == 'FN' ? true : false
88
+ fields[col] = m[2]
89
+ end
90
+
91
+ # Make space for the expanded data table, shift the merged cells down
92
+ if is_report && !@json_data['included'].nil? && @json_data['included'].size != 0
93
+ row_cnt = @json_data['included'].size
94
+ if row_cnt != 0
95
+ (row_cnt - 1).times { ws.insert_row(dst_row + 1) }
96
+ end
97
+ ws.merged_cells.each do |cell|
98
+ next unless cell.ref.row_range.min >= dst_row
99
+ cell.ref.instance_variable_set(:"@row_range", Range.new(cell.ref.row_range.min + row_cnt - 1, cell.ref.row_range.max + row_cnt - 1))
100
+ end
101
+ end
102
+
103
+ # In report mode empty the first row and column
104
+ if is_report
105
+ ws.change_row_height(0, 6)
106
+ ws.change_column_width(0, 1)
107
+ ws.each_with_index do |row, ridx|
108
+ ws.delete_cell(ridx, 0)
109
+ end
110
+ end
111
+
112
+ # Create the table rows
113
+ if @json_data['included'].nil?
114
+ ref.col_range.each do |col|
115
+ ws[dst_row][col].change_contents('')
116
+ ws[dst_row][col].style_index = ws[header_row + 1][col].style_index
117
+ end
118
+ else
119
+ @json_data['included'].each do |line|
120
+ fields.each do |col,field|
121
+
122
+ value = line['attributes'][field]
123
+ if value.nil? and false == null_fields[col]
124
+ value = 0
125
+ end
126
+
127
+ if ws[dst_row].nil? || ws[dst_row][col].nil?
128
+ ws.add_cell(dst_row, col, value)
129
+ else
130
+ ws[dst_row][col].change_contents(value)
131
+ end
132
+ ws[dst_row][col].style_index = ws[header_row + 1][col].style_index
133
+ end
134
+ dst_row += 1
135
+ end
136
+ # Update the table size
137
+ tbl.ref = RubyXL::Reference.ind2ref(ref.row_range.begin(),
138
+ ref.col_range.begin()) + ":" +
139
+ RubyXL::Reference.ind2ref(ref.row_range.begin() + (dst_row - header_row - 1),
140
+ ref.col_range.end())
141
+ end
142
+
143
+ # In report mode delete all worksheets except the one that contains the lines table and wipe comments
144
+ if is_report
145
+ @workbook.worksheets.delete_if {|sheet| sheet.sheet_name != ws.sheet_name}
146
+ ws.comments = Array.new
147
+ end
148
+
149
+ end
150
+
151
+ def workbook
152
+ @workbook
153
+ end
154
+
155
+ end
156
+
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,249 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
4
+ #
5
+ # This file is part of sp-excel-loader.
6
+ #
7
+ # sp-excel-loader is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # sp-excel-loader is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with sp-excel-loader. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ module Sp
22
+ module Excel
23
+ module Loader
24
+
25
+ class ModelExporter < WorkbookLoader
26
+
27
+ attr_accessor :model
28
+
29
+ def initialize (a_file, a_typed_export)
30
+ super(a_file)
31
+ @model = Hash.new
32
+ @typed_export = a_typed_export
33
+ end
34
+
35
+ def read_model(a_sheet_name, a_table_name)
36
+ read_model_with_typed_option(a_sheet_name, a_table_name, @typed_export)
37
+ end
38
+
39
+ def read_model_with_typed_option(a_sheet_name, a_table_name, a_typed_export)
40
+
41
+ read_cell_names(a_sheet_name)
42
+ col_names = Hash.new
43
+ header_columns = Hash.new
44
+ scalar_formulas = Hash.new
45
+ formula_lines = Array.new
46
+ scalar_values = Hash.new
47
+ value_lines = Array.new
48
+
49
+ worksheet = @workbook[a_sheet_name]
50
+ ref = nil
51
+
52
+ parse_shared_formulas(worksheet)
53
+
54
+ # Capture the columns names
55
+ worksheet.generic_storage.each do |tbl|
56
+ next unless tbl.is_a? RubyXL::Table and tbl.name == a_table_name
57
+
58
+ ref = RubyXL::Reference.new(tbl.ref)
59
+ type_row = ref.row_range.first() - 1
60
+ i = ref.col_range.first()
61
+ tbl.table_columns.each do |table_col|
62
+ if a_typed_export
63
+ col_names[i] = { 'name' => table_col.name, 'type' => get_column_type(worksheet, type_row, i) }
64
+ else
65
+ col_names[i] = table_col.name
66
+ end
67
+ i += 1
68
+ end
69
+
70
+ col_names.sort.map do |key, value|
71
+ header_columns[RubyXL::Reference.new(ref.row_range.first(),ref.col_range.first() + key - 1)] = value
72
+ end
73
+ end
74
+
75
+ # Build the formula and value arrays
76
+ for row in ref.row_range.begin()+1..ref.row_range.end()
77
+ formula = Hash.new
78
+ value = Hash.new
79
+ col_index = 1
80
+
81
+ ref.col_range.each do |col|
82
+
83
+ cell = worksheet[row][col]
84
+ if a_typed_export
85
+ column = col_names[col_index]['name']
86
+ else
87
+ column = col_names[col_index]
88
+ end
89
+ if cell
90
+ key, expression = cell_expression(cell)
91
+ if cell.formula
92
+ formula[column] = expression
93
+ else
94
+ value[column] = expression unless expression.nil?
95
+ end
96
+ end
97
+ col_index += 1
98
+ end
99
+ formula_lines << formula
100
+ value_lines << value
101
+ end
102
+
103
+ # Read scalar values and formulas, from the rows that are not part of the lines table
104
+ renum = Array.new
105
+ renum = (0..ref.row_range.begin()).to_a
106
+ renum += (ref.row_range.end()+1..worksheet.dimension.ref.row_range.end()).to_a
107
+ for idx in renum
108
+ next if worksheet[idx].nil?
109
+ next if worksheet[idx].cells.nil?
110
+ worksheet[idx].cells.each do |cell|
111
+ if cell
112
+ key, expression = cell_expression(cell)
113
+
114
+ if cell.formula
115
+ scalar_formulas[key] = a_typed_export ? get_typed_scalar(cell, expression, worksheet) : expression
116
+ else
117
+ unless expression.nil?
118
+ scalar_values[key] = a_typed_export ? get_typed_scalar(cell, expression, worksheet) : expression
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ @model = {
126
+ 'values' => scalar_values,
127
+ 'formulas' => scalar_formulas,
128
+ 'lines' => {
129
+ 'header' => header_columns,
130
+ 'formulas' => formula_lines,
131
+ 'values' => value_lines
132
+ },
133
+ }
134
+ end
135
+
136
+ def cell_expression (a_cell)
137
+
138
+ cell_reference = RubyXL::Reference.ind2ref(a_cell.row, a_cell.column)
139
+ name = @cellnames[cell_reference]
140
+ formula = read_formula_expression(a_cell)
141
+
142
+ if formula != nil
143
+ if name
144
+ expression = "#{name}=#{formula}"
145
+ else
146
+ expression = "#{cell_reference}=#{formula}"
147
+ end
148
+ elsif a_cell.value
149
+ if name
150
+ if a_cell.is_date?
151
+ expression = "#{name}=excel_date(#{a_cell.value})"
152
+ else
153
+ begin
154
+ Float(a_cell.value)
155
+ expression = "#{name}=#{a_cell.value}"
156
+ rescue
157
+ expression = "#{name}=\"#{a_cell.value}\""
158
+ end
159
+ end
160
+ else
161
+ if a_cell.is_date?
162
+ expression = "#{cell_reference}=excel_date(#{a_cell.value})"
163
+ else
164
+ begin
165
+ Float(a_cell.value)
166
+ expression = "#{cell_reference}=#{a_cell.value}"
167
+ rescue
168
+ expression = "#{cell_reference}=\"#{a_cell.value}\""
169
+ end
170
+ end
171
+ end
172
+ end
173
+ return cell_reference, expression
174
+ end
175
+
176
+ def read_cell_names (a_sheet_name)
177
+ @cellnames = {}
178
+ ref_regexp = a_sheet_name + '!\$*([A-Z]+)\$*(\d+)'
179
+ @workbook.defined_names.each do |dn|
180
+ next unless dn.local_sheet_id.nil?
181
+ match = dn.reference.match(ref_regexp)
182
+ if match and match.size == 3
183
+ matched_name = match[1].to_s + match[2].to_s
184
+ if @cellnames[matched_name]
185
+ raise "**** Fatal error:\n duplicate cellname for #{matched_name}: #{@cellnames[matched_name]} and #{dn.name}"
186
+ end
187
+ @cellnames[matched_name] = dn.name
188
+ end
189
+ end
190
+ end
191
+
192
+ def get_column_type (a_worksheet, a_row_idx, a_column)
193
+ return 'TEXT' if a_worksheet[a_row_idx].nil? or a_worksheet[a_row_idx][a_column].nil? or a_worksheet[a_row_idx][a_column].value.nil?
194
+ return a_worksheet[a_row_idx][a_column].value
195
+ end
196
+
197
+ def get_typed_scalar (a_cell, a_expression, a_worksheet)
198
+
199
+ type = get_type_from_comment(a_cell.row, a_cell.column, a_worksheet)
200
+ unless type.nil?
201
+ return { 'type' => type, 'value' => a_expression }
202
+ end
203
+
204
+ if a_cell.is_date?
205
+ return { 'type' => 'DATE', 'value' => a_expression }
206
+ end
207
+ case a_cell.datatype
208
+ when RubyXL::DataType::NUMBER, nil
209
+ return { 'type' => 'DECIMAL', 'value' => a_expression }
210
+ when RubyXL::DataType::BOOLEAN
211
+ return { 'type' => 'BOOLEAN', 'value' => a_expression }
212
+ when RubyXL::DataType::DATE # Only available in Office2010
213
+ return { 'type' => 'DATE', 'value' => a_expression }
214
+ end
215
+ return { 'type' => 'TEXT', 'value' => a_expression }
216
+ end
217
+
218
+
219
+ def get_type_from_comment (a_row, a_col, a_worksheet)
220
+
221
+ if a_worksheet.comments != nil && a_worksheet.comments.size > 0 && a_worksheet.comments[0].comment_list != nil
222
+
223
+ a_worksheet.comments[0].comment_list.each do |comment|
224
+ if comment.ref.col_range.begin == a_col && comment.ref.row_range.begin == a_row
225
+ comment.text.to_s.lines.each do |text|
226
+ text.strip!
227
+ next if text == '' or text.nil?
228
+ idx = text.index(':')
229
+ next if idx.nil?
230
+ tag = text[0..(idx-1)]
231
+ value = text[(idx+1)..-1]
232
+ next if tag.nil? or value.nil?
233
+ tag.strip!
234
+ value.strip!
235
+
236
+ if tag == 'type'
237
+ return value
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+ return nil
244
+ end
245
+
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,168 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
4
+ #
5
+ # This file is part of sp-excel-loader.
6
+ #
7
+ # sp-excel-loader is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # sp-excel-loader is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with sp-excel-loader. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ module Sp
22
+ module Excel
23
+ module Loader
24
+
25
+ class PayrollExporter < ModelExporter
26
+
27
+ attr_accessor :tables
28
+ attr_accessor :model
29
+
30
+ def initialize (a_file, a_typed_export)
31
+ super(a_file, a_typed_export)
32
+ @tables = Hash.new
33
+ @pretty = true
34
+ end
35
+
36
+ def read_untyped_table (a_worksheet, a_table, a_table_name)
37
+
38
+ tbl = Array.new
39
+ ref = RubyXL::Reference.new(a_table.ref)
40
+
41
+ ref.col_range.each do |col|
42
+
43
+ col_obj = Hash.new
44
+ col_data = Array.new
45
+
46
+ type = 'number'
47
+ for row in ref.row_range.begin()+1..ref.row_range.end()
48
+ next if a_worksheet[row][col].nil?
49
+ next if a_worksheet[row][col].value.nil?
50
+ next if a_worksheet[row][col].value.is_a? Numeric
51
+ next if a_worksheet[row][col].value.is_a? String and a_worksheet[row][col].value.length() == 0
52
+ begin
53
+ Float(a_worksheet[row][col].value)
54
+ rescue
55
+ type = 'text'
56
+ break
57
+ end
58
+ end
59
+
60
+ col_obj['name'] = a_worksheet[ref.row_range.begin()][col].value.to_s
61
+ col_obj['type'] = type
62
+ col_obj['data'] = col_data
63
+
64
+ for row in ref.row_range.begin()+1..ref.row_range.end()
65
+ if type == 'number'
66
+ if a_worksheet[row][col].nil?
67
+ col_data << 0.0
68
+ else
69
+ col_data << a_worksheet[row][col].value.to_f
70
+ end
71
+ else
72
+ if a_worksheet[row][col].nil?
73
+ col_data << ''
74
+ else
75
+ col_data << a_worksheet[row][col].value.to_s
76
+ end
77
+ end
78
+ end
79
+
80
+ tbl << col_obj
81
+ end
82
+ @tables[a_table.name] = tbl
83
+
84
+ end
85
+
86
+ def read_see_table (a_table_name)
87
+ tbl = []
88
+
89
+ tbl_instances = self.send "#{a_table_name}"
90
+
91
+ # Get all columns reading all getter methods from first instance of a_table_name
92
+ columns = tbl_instances.first.class.instance_methods(false).select { |method| method.to_s[-1] != '=' }
93
+
94
+ columns.each do |column|
95
+ col_obj = Hash.new
96
+ col_data = Array.new
97
+
98
+ tbl_instances.each do |line|
99
+ column_value = line.send(column)
100
+ is_numeric = column_value.class.in?([Fixnum, BigDecimal])
101
+
102
+ # We are at the first line of table, so prepare the structure
103
+ if col_obj.empty?
104
+ col_obj['name'] = column.to_s
105
+ col_obj['type'] = is_numeric ? 'number' : 'text'
106
+ col_obj['data'] = col_data
107
+ end
108
+
109
+ col_data << (is_numeric ? column_value.to_f : column_value.to_s)
110
+ end
111
+
112
+ tbl << col_obj
113
+ end
114
+
115
+ tbl
116
+ end
117
+
118
+ def read_all_untyped_tables ()
119
+ @workbook.worksheets.each do |ws|
120
+ ws.generic_storage.each do |tbl|
121
+ next unless tbl.is_a? RubyXL::Table
122
+ next if tbl.name == 'LINES'
123
+ read_untyped_table(ws, tbl, tbl.name)
124
+ end
125
+ end
126
+ end
127
+
128
+ def write_json_file (a_directory, a_name, a_object)
129
+ FileUtils::mkdir_p(a_directory)
130
+ File.open(File.join(a_directory, a_name + '.json'),"w") do |f|
131
+ if @pretty
132
+ f.write(JSON.pretty_generate(a_object))
133
+ else
134
+ f.write(a_object.to_json)
135
+ end
136
+ end
137
+ end
138
+
139
+ def write_json_tables (a_directory)
140
+ a_directory = File.join(a_directory, 'tables')
141
+ @tables.each do |name, table|
142
+ write_json_file(a_directory, name, table)
143
+ end
144
+ end
145
+
146
+ def write_typed_json_tables (a_directory)
147
+ a_directory = File.join(a_directory, 'tables')
148
+ @table_names.each do |name|
149
+ next if name == 'LINES'
150
+ next if name != 'TABELA_RETROATIVOS'
151
+
152
+ write_json_file(a_directory, name, read_see_table(name))
153
+ end
154
+ end
155
+
156
+ def export(a_directory)
157
+ self.read_all_untyped_tables # Legacy code when all tables of excel are typed
158
+ self.read_all_tables
159
+ self.write_json_file(a_directory, 'model', self.read_model_with_typed_option('PROCESSAMENTO', 'LINES', false))
160
+ self.write_json_file(a_directory, 'model_typed', self.read_model_with_typed_option('PROCESSAMENTO', 'LINES', true))
161
+ self.write_json_tables(a_directory) # Legacy code when all tables of excel are typed
162
+ self.write_typed_json_tables(a_directory)
163
+ self
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end