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