sp-excel-loader 0.3.40

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 (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