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,91 @@
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
+ require 'rubyXL'
22
+ require 'rubyXL/objects/ooxml_object'
23
+
24
+ # Monkey patch to RubyXL
25
+ module RubyXL
26
+
27
+ class SortCondition < OOXMLObject
28
+ define_attribute(:ref , :string, :required => true)
29
+ define_element_name 'sortCondition'
30
+ end
31
+
32
+ class SortState < OOXMLObject
33
+ define_attribute(:ref , :string, :required => true)
34
+ define_element_name 'sortState'
35
+ define_child_node(RubyXL::SortCondition)
36
+ end
37
+
38
+ class TableColumn < OOXMLObject
39
+ define_attribute(:id , :int , :required => true)
40
+ define_attribute(:name , :string, :required => true)
41
+ define_attribute(:totalsRowShown, :int , :default => 0 )
42
+ define_element_name 'tableColumn'
43
+ end
44
+
45
+ class TableColumns < OOXMLContainerObject
46
+ define_attribute(:count, :int, :default => 0)
47
+ define_child_node(RubyXL::TableColumn, :collection => true)
48
+ define_element_name 'tableColumns'
49
+ end
50
+
51
+ class TableStyleInfo < OOXMLObject
52
+ define_attribute(:name , :string , :required => true)
53
+ define_attribute(:showColumnStripes , :string , :default => 0)
54
+ define_attribute(:showFirstColumn , :string , :default => 0)
55
+ define_attribute(:showLastColumn , :string , :default => 0)
56
+ define_attribute(:showRowStripes , :string , :default => 0)
57
+ define_element_name 'tableStyleInfo'
58
+ end
59
+
60
+ class Table < OOXMLTopLevelObject
61
+ CONTENT_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml'
62
+ REL_TYPE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table'
63
+
64
+ define_attribute(:id , :int , :required => true)
65
+ define_attribute(:name , :string, :required => true)
66
+ define_attribute(:ref , :string, :required => true)
67
+ define_attribute(:displayName , :string, :required => true)
68
+ define_child_node(RubyXL::AutoFilter)
69
+ define_child_node(RubyXL::SortState)
70
+ define_child_node(RubyXL::TableColumns)
71
+ define_child_node(RubyXL::TableStyleInfo)
72
+ define_element_name 'table'
73
+ set_namespaces('http://schemas.openxmlformats.org/spreadsheetml/2006/main' => '',
74
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' => 'r')
75
+
76
+ def xlsx_path
77
+ ROOT.join('xl', 'tables', "table#{file_index}.xml")
78
+ end
79
+
80
+ end
81
+
82
+ class Tables < OOXMLContainerObject
83
+ define_child_node(RubyXL::Table, :collection => true)
84
+ define_element_name 'tables'
85
+ end
86
+
87
+ class Worksheet
88
+ define_relationship(RubyXL::Table)
89
+ end
90
+
91
+ end
@@ -0,0 +1,26 @@
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
+ module Sp
21
+ module Excel
22
+ module Loader
23
+ VERSION = File.read(File.expand_path('../../../../../VERSION', __FILE__)).strip
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,480 @@
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
+ require 'bigdecimal'
22
+ require 'date'
23
+ require File.expand_path(File.join(File.dirname(__FILE__), 'rubyxl_table_patch'))
24
+
25
+ module Sp
26
+ module Excel
27
+ module Loader
28
+
29
+ class TableRow
30
+ def self.factory(klass_name = nil)
31
+ klass = Class.new(self)
32
+ Object.const_set(klass_name.capitalize, klass) unless klass_name.nil?
33
+ klass
34
+ end
35
+
36
+ def add_attr(a_name, a_value)
37
+ a_name = a_name.tr(' ', '_')
38
+ self.class.send(:attr_accessor, a_name)
39
+ instance_variable_set("@#{a_name}", a_value)
40
+ end
41
+ end
42
+
43
+ class WorkbookLoader < TableRow
44
+
45
+ attr_accessor :workbook
46
+
47
+ def initialize (a_file)
48
+ @workbook = RubyXL::Parser.parse(a_file)
49
+ @cellnames = Hash.new
50
+ @shared_formulas = Hash.new
51
+ @table_names = []
52
+ end
53
+
54
+ def read_all_tables ()
55
+ @workbook.worksheets.each do |ws|
56
+ ws.generic_storage.each do |tbl|
57
+ next unless tbl.is_a? RubyXL::Table
58
+ read_typed_table(ws, tbl, tbl.name)
59
+ @table_names << tbl.name
60
+ end
61
+ end
62
+ end
63
+
64
+ def write_typed_table (a_records, a_table_name, a_style_filter = nil, a_keep_formulas = false)
65
+ ws, tbl, ref = find_table(a_table_name)
66
+
67
+ header_row = ref.row_range.begin()
68
+ dst_row = header_row + 1
69
+ type_row = header_row - 1
70
+
71
+ a_records.each do |record|
72
+
73
+ ref.col_range.each do |col|
74
+
75
+ datatype, value = type_n_value_toxls(ws[type_row][col].value, record.send(ws[header_row][col].value))
76
+
77
+ # Add or create cell
78
+ if ws[dst_row].nil? || ws[dst_row][col].nil?
79
+ ws.add_cell(dst_row, col, value)
80
+ else
81
+ if a_keep_formulas then
82
+ ws[dst_row][col].change_contents(value)
83
+ else
84
+ style_index = ws[dst_row][col].style_index
85
+ ws.delete_cell(dst_row, col)
86
+ ws.add_cell(dst_row, col, value)
87
+ ws[dst_row][col].style_index = style_index
88
+ end
89
+ end
90
+ # Only change datatype for number, other values make excel bark ...
91
+ if [RubyXL::DataType::NUMBER].include? datatype
92
+ ws[dst_row][col].datatype = datatype
93
+ end
94
+
95
+ # Call formater hook
96
+ unless a_style_filter.nil?
97
+ a_style_filter.call(record, ws, dst_row, col)
98
+ end
99
+
100
+ end
101
+ dst_row += 1
102
+ end
103
+
104
+ # Adjust the table size
105
+ previous_last_row = ref.col_range.end()
106
+ tbl.ref = RubyXL::Reference.ind2ref(ref.row_range.begin(),
107
+ ref.col_range.begin()) + ":" +
108
+ RubyXL::Reference.ind2ref(ref.row_range.begin() + a_records.size(),
109
+ ref.col_range.end())
110
+ for row in dst_row..previous_last_row
111
+ ws.delete_row(row)
112
+ end
113
+ end
114
+
115
+ #
116
+ # @brief Convert the database value to the Excel cell type and value
117
+ #
118
+ # @param a_type The value in the types header row
119
+ # @param a_value Value from the database
120
+ #
121
+ # @return XLS type and value
122
+ #
123
+ def type_n_value_toxls (a_type, a_value)
124
+
125
+ if a_value.nil?
126
+ datatype = RubyXL::DataType::RAW_STRING
127
+ else
128
+ case a_type
129
+ when 'INTEGER', 'INTEGER_NULLABLE'
130
+ datatype = RubyXL::DataType::NUMBER
131
+ a_value = a_value.to_i
132
+ when 'MONEY', 'MONEY_NULLABLE', 'DECIMAL', 'DECIMAL_NULLABLE'
133
+ datatype = RubyXL::DataType::NUMBER
134
+ a_value = a_value.to_f
135
+ when 'TEXT', 'TEXT_NULLABLE'
136
+ datatype = RubyXL::DataType::RAW_STRING
137
+ when 'BOOLEAN', 'BOOLEAN_NULLABLE'
138
+ datatype = RubyXL::DataType::BOOLEAN
139
+ unless a_value.nil?
140
+ a_value = a_value ? 'TRUE' : 'FALSE'
141
+ end
142
+ when 'DATE', 'DATE_NULLABLE'
143
+ datatype = RubyXL::DataType::DATE
144
+ a_value = @workbook.date_to_num(Date.iso8601(a_value))
145
+ when 'DATETIME', 'DATETIME_NULLABLE'
146
+ datatype = RubyXL::DataType::DATE
147
+ a_value = @workbook.date_to_num(DateTime.parse(a_value))
148
+ else
149
+ datatype = RubyXL::DataType::RAW_STRING
150
+ end
151
+ end
152
+ return datatype, a_value
153
+ end
154
+
155
+ def read_typed_table (a_worksheet, a_table, a_table_name)
156
+ ref = RubyXL::Reference.new(a_table.ref)
157
+ header_row = ref.row_range.begin()
158
+ type_row = header_row - 1
159
+ records = Array.new
160
+
161
+ klass = TableRow.factory a_table_name
162
+
163
+ for row in ref.row_range.begin()+1..ref.row_range.end()
164
+ record = klass.new
165
+
166
+ ref.col_range.each do |col|
167
+ cell = a_worksheet[row][col] unless a_worksheet[row].nil?
168
+ unless cell.nil?
169
+ unless a_worksheet[type_row].nil? || a_worksheet[type_row][col].nil? || a_worksheet[type_row][col].value.nil?
170
+ type = a_worksheet[type_row][col].value
171
+ else
172
+ type = 'TEXT'
173
+ end
174
+ case type
175
+ when 'TEXT', 'TEXT_NULLABLE'
176
+ value = cell.value.to_s
177
+ when 'SQL', 'SQL_NULLABLE'
178
+ value = cell.value.to_s
179
+ when 'INTEGER', 'INTEGER_NULLABLE'
180
+ value = cell.value.to_i
181
+ when 'DECIMAL', 'MONEY', 'DECIMAL_NULLABLE', 'MONEY_NULLABLE'
182
+ value = BigDecimal.new(cell.value.to_s)
183
+ when 'BOOLEAN', 'BOOLEAN_NULLABLE'
184
+ value = cell.value.to_i == 0 ? false : true
185
+ when 'DATE', 'DATE_NULLABLE'
186
+ begin
187
+ if cell.is_date?
188
+ value = cell.value
189
+ else
190
+ value = Date.parse cell.value
191
+ end
192
+ rescue => e
193
+ if type == 'DATE_NULLABLE'
194
+ value = nil
195
+ else
196
+ puts "Error in #{a_worksheet.sheet_name}!#{RubyXL::Reference.ind2ref(row,col)} #{e.message}"
197
+ end
198
+ end
199
+ when 'DATETIME', 'DATETIME_NULLABLE'
200
+ begin
201
+ if cell.is_date?
202
+ value = cell.value
203
+ else
204
+ value = Date.parse cell.value
205
+ end
206
+ rescue => e
207
+ if type == 'DATETIME_NULLABLE'
208
+ value = nil
209
+ else
210
+ puts "Error in #{a_worksheet.sheet_name}!#{RubyXL::Reference.ind2ref(row,col)} #{e.message}"
211
+ end
212
+ end
213
+ else
214
+ value = cell.value.to_s
215
+ end
216
+ else
217
+ value = nil
218
+ end
219
+ if value.kind_of?(String)
220
+ value.gsub!(/\A\u00A0+/, '')
221
+ value.gsub!(/\u00A0+\z/, '')
222
+ value.strip!
223
+ end
224
+ begin
225
+ record.add_attr(a_worksheet[header_row][col].value.strip, value)
226
+ rescue => e
227
+ puts "Error in #{a_worksheet.sheet_name}!#{RubyXL::Reference.ind2ref(header_row,col)} #{e.message}"
228
+ end
229
+ end
230
+ records << record
231
+ end
232
+ add_attr(a_table_name, records)
233
+ end
234
+
235
+ def find_table (a_table_name)
236
+ @workbook.worksheets.each do |ws|
237
+ ws.generic_storage.each do |tbl|
238
+ next unless tbl.is_a? RubyXL::Table
239
+ next unless tbl.name == a_table_name
240
+
241
+ return ws, tbl, RubyXL::Reference.new(tbl.ref)
242
+ end
243
+ end
244
+ raise "Table '#{a_table_name}' not found in the workbook"
245
+ end
246
+
247
+ def read_named_cells (a_sheet_name)
248
+ cellnames = Hash.new
249
+ ref_regexp = a_sheet_name + '!\$*([A-Z]+)\$*(\d+)'
250
+ @workbook.defined_names.each do |dn|
251
+ next unless dn.local_sheet_id.nil?
252
+ match = dn.reference.match(ref_regexp)
253
+ if match and match.size == 3
254
+ matched_name = match[1].to_s + match[2].to_s
255
+ if cellnames[matched_name]
256
+ raise "**** Fatal error:\n duplicate cellname for #{matched_name}: #{@cellnames[matched_name]} and #{dn.name}"
257
+ end
258
+ cellnames[dn.name] = matched_name
259
+ end
260
+ end
261
+ cellnames
262
+ end
263
+
264
+ ####################################################################################################################
265
+ # #
266
+ # Methods that write XLS table into to the database #
267
+ # #
268
+ ####################################################################################################################
269
+
270
+ def export_table_to_pg (a_conn, a_schema, a_prefix, a_table_name)
271
+ export_table_to_pg_with_othername(a_conn, a_schema, a_prefix, a_table_name, a_table_name)
272
+ end
273
+
274
+ def export_table_to_pg_with_othername (a_conn, a_schema, a_prefix, a_table_name, a_xls_table_name)
275
+ table = a_schema
276
+ table ||= 'public'
277
+ table += '.'
278
+ table += a_prefix unless a_prefix.nil?
279
+ table += a_table_name
280
+ export_table_to_pg_other(a_conn, table, a_xls_table_name)
281
+ end
282
+
283
+ def export_table_to_pg_other (a_conn, a_db_table_name, a_xls_table_name)
284
+
285
+ ws, tbl, ref = find_table(a_xls_table_name)
286
+
287
+ header_row = ref.row_range.begin()
288
+ dst_row = header_row + 1
289
+ type_row = header_row - 1
290
+
291
+ column_names = Array.new
292
+ column_type = Hash.new
293
+ columns = Array.new
294
+ ref.col_range.each do |col|
295
+
296
+ next if ws[type_row].nil?
297
+ next if ws[type_row][col].nil?
298
+ next if ws[type_row][col].value == 'VOID'
299
+
300
+ column_type[col] = ws[type_row][col].value
301
+ columns << col
302
+ column_names << ws[header_row][col].value
303
+ end
304
+
305
+ rows = Array.new
306
+ for row in ref.row_range.begin()+1..ref.row_range.end()
307
+
308
+ next if ws[row].nil?
309
+ row_values = Array.new
310
+ columns.each do |col|
311
+
312
+ cell = ws[row][col]
313
+ if cell.nil?
314
+ value = 'NULL'
315
+ elsif column_type[col] =~ /_NULLABLE$/ && 0 == cell.value.to_s.strip.size
316
+ value = 'NULL'
317
+ else
318
+ case column_type[col]
319
+ when 'TEXT', 'TEXT_NULLABLE'
320
+ value = '\'' + a_conn.escape_string(cell.value.to_s.strip) + '\''
321
+ when 'SQL', 'SQL_NULLABLE'
322
+ value = cell.value.to_s.strip
323
+ when 'INTEGER', 'INTEGER_NULLABLE'
324
+ value = cell.value.to_i
325
+ when 'DECIMAL', 'DECIMAL_NULLABLE', 'MONEY', 'MONEY_NULLABLE'
326
+ value = BigDecimal.new(cell.value.to_s.strip)
327
+ when 'BOOLEAN', 'BOOLEAN_NULLABLE'
328
+ value = cell.value.to_i == 0 ? 'false' : 'true'
329
+ when 'DATE', 'DATE_NULLABLE'
330
+ begin
331
+ value = '\'' + cell.value.strftime('%Y-%m-%d') + '\''
332
+ rescue => e
333
+ puts "Error in table #{a_xls_table_name} #{RubyXL::Reference.ind2ref(row,col)} #{e.message} value=#{cell.value.to_s}"
334
+ end
335
+ when 'DATETIME', 'DATETIME_NULLABLE'
336
+ begin
337
+ value = '\'' + cell.value.strftime('%Y-%m-%d %H:%M:%S') + '\''
338
+ rescue => e
339
+ puts "Error in table #{a_xls_table_name} #{RubyXL::Reference.ind2ref(row,col)} #{e.message} value=#{cell.value.to_s}"
340
+ end
341
+ else
342
+ value = '\'' + a_conn.escape_string(cell.value.to_s.strip) + '\''
343
+ end
344
+ end
345
+
346
+ row_values << value
347
+ end
348
+ rows << '(' + row_values.join(',') + ')'
349
+ end
350
+ a_conn.exec("INSERT INTO #{a_db_table_name} (#{column_names.join(',')}) VALUES #{rows.join(",\n")}")
351
+ end
352
+
353
+ def write (a_filename)
354
+ @workbook.calculation_chain = nil
355
+ @workbook.calc_pr.full_calc_on_load = true
356
+ @workbook.write(a_filename)
357
+ end
358
+
359
+ ####################################################################################################################
360
+ # #
361
+ # Methods related to cloning of calculation tables that use templates #
362
+ # #
363
+ ####################################################################################################################
364
+
365
+
366
+ #
367
+ # @brief This method takes a set of parameters read from the database and writes each parameter to excel cell on
368
+ # the specified sheet. The target cell must be named the column's name and have *no* formula
369
+ #
370
+ # @param a_sheet_name Name of the sheet where the replacements should be made
371
+ # @param a_scalars Hash with the values read from the database
372
+ #
373
+ def replace_scalars_in_sheet(a_sheet_name, a_scalars)
374
+
375
+ ws = @workbook[a_sheet_name]
376
+ cellnames = read_named_cells(a_sheet_name)
377
+
378
+ a_scalars.each do |name, value|
379
+ cell_id = cellnames[name]
380
+ unless cell_id.nil?
381
+ col, row = RubyXL::Reference.ref2ind(cell_id)
382
+ cell = ws[col][row]
383
+ if cell.formula.nil?
384
+ if cell.is_date?
385
+ if value.nil? or value.length == 0
386
+ value = nil
387
+ else
388
+ value = @workbook.date_to_num(DateTime.iso8601(value))
389
+ end
390
+ elsif (cell.datatype.nil? or cell.datatype == RubyXL::DataType::NUMBER)
391
+ begin
392
+ value = value.to_f
393
+ rescue
394
+ # Not a number? let it pass as it is
395
+ end
396
+ end
397
+ cell.change_contents(value)
398
+ end
399
+ end
400
+ end
401
+ end
402
+
403
+ def parse_shared_formulas (a_worksheet)
404
+ @shared_formulas = Hash.new
405
+ ref = a_worksheet.dimension.ref
406
+ ref.row_range.each do |row|
407
+ ref.col_range.each do |col|
408
+ next if a_worksheet[row].nil?
409
+ cell = a_worksheet[row][col]
410
+ next if cell.nil?
411
+ formula = cell.formula
412
+ next if formula.nil?
413
+
414
+ if formula.t == 'shared' and not formula.expression.nil? and formula.expression.length != 0
415
+ @shared_formulas[formula.si] = formula.expression
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ def read_formula_expression (a_cell)
422
+ return nil if (a_cell.formula.nil?)
423
+ return @shared_formulas[a_cell.formula.si] if a_cell.formula.t == 'shared'
424
+ return a_cell.formula.expression
425
+ end
426
+
427
+ def clone_lines_table (a_records, a_table_name, a_lines, a_template_column, a_closed_column = nil)
428
+ ws, tbl, ref = find_table(a_table_name)
429
+
430
+ header_row = ref.row_range.begin()
431
+ style_row = header_row + a_records.size
432
+ dst_row = style_row + 1
433
+ type_row = header_row - 1
434
+
435
+ template_index = Hash.new
436
+ a_records.each_with_index do |record, index|
437
+ template = record.send(a_template_column.to_sym)
438
+ template_index[template] = header_row + index + 1
439
+ end
440
+
441
+ parse_shared_formulas(ws)
442
+
443
+ a_lines.each_with_index do |line, index|
444
+ if template_index.has_key?(line[a_template_column]) == false
445
+ puts "Template #{line[a_template_column]} of line #{index+1} does exist in the model"
446
+ next
447
+ end
448
+
449
+ src_row = template_index[line[a_template_column]]
450
+ closed = line[a_closed_column] == 't'
451
+
452
+ ref.col_range.each do |col|
453
+
454
+ datatype, value = type_n_value_toxls(ws[type_row][col].value, line[ws[header_row][col].value])
455
+
456
+ # Copy formula if the line is open
457
+ expression = read_formula_expression(ws[src_row][col])
458
+ if closed == false and not expression.nil? and expression.length != 0
459
+ ws.add_cell(dst_row, col, '', expression)
460
+ else
461
+ ws.add_cell(dst_row, col, value)
462
+ end
463
+ ws[dst_row][col].style_index = ws[style_row][col].style_index
464
+ end
465
+ ws.change_row_height(dst_row, ws.get_row_height(src_row))
466
+ dst_row += 1
467
+ end
468
+
469
+ # Adjust the table size
470
+ previous_last_row = ref.col_range.end()
471
+ tbl.ref = RubyXL::Reference.ind2ref(ref.row_range.begin(),
472
+ ref.col_range.begin()) + ":" +
473
+ RubyXL::Reference.ind2ref(ref.row_range.begin() + a_records.size() + a_lines.ntuples,
474
+ ref.col_range.end())
475
+ end
476
+
477
+ end
478
+ end
479
+ end
480
+ end