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,1183 @@
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 'set'
22
+
23
+
24
+ module Sp
25
+ module Excel
26
+ module Loader
27
+ module Jrxml
28
+
29
+ class ExcelToJrxml < WorkbookLoader
30
+
31
+ @@CT_IndexedColors = [
32
+ '000000', # 0
33
+ 'FFFFFF', # 1
34
+ 'FF0000', # 2
35
+ '00FF00', # 3
36
+ '0000FF', # 4
37
+ 'FFFF00', # 5
38
+ 'FF00FF', # 6
39
+ '00FFFF', # 7
40
+ '000000', # 8
41
+ 'FFFFFF', # 9
42
+ 'FF0000', # 10
43
+ '00FF00', # 11
44
+ '0000FF', # 12
45
+ 'FFFF00', # 13
46
+ 'FF00FF', # 14
47
+ '00FFFF', # 15
48
+ '800000', # 16
49
+ '008000', # 17
50
+ '000080', # 18
51
+ '808000', # 19
52
+ '800080', # 20
53
+ '008080', # 21
54
+ 'C0C0C0', # 22
55
+ '808080', # 23
56
+ '9999FF', # 24
57
+ '993366', # 25
58
+ 'FFFFCC', # 26
59
+ 'CCFFFF', # 27
60
+ '660066', # 28
61
+ 'FF8080', # 29
62
+ '0066CC', # 30
63
+ 'CCCCFF', # 31
64
+ '000080', # 32
65
+ 'FF00FF', # 33
66
+ 'FFFF00', # 34
67
+ '00FFFF', # 35
68
+ '800080', # 36
69
+ '800000', # 37
70
+ '008080', # 38
71
+ '0000FF', # 39
72
+ '00CCFF', # 40
73
+ 'CCFFFF', # 41
74
+ 'CCFFCC', # 42
75
+ 'FFFF99', # 43
76
+ '99CCFF', # 44
77
+ 'FF99CC', # 45
78
+ 'CC99FF', # 46
79
+ 'FFCC99', # 47
80
+ '3366FF', # 48
81
+ '33CCCC', # 49
82
+ '99CC00', # 50
83
+ 'FFCC00', # 51
84
+ 'FF9900', # 52
85
+ 'FF6600', # 53
86
+ '666699', # 54
87
+ '969696', # 55
88
+ '003366', # 56
89
+ '339966', # 57
90
+ '003300', # 58
91
+ '333300', # 59
92
+ '993300', # 60
93
+ '993366', # 61
94
+ '333399', # 62
95
+ '333333' # 63
96
+ ]
97
+
98
+ attr_reader :report
99
+ attr_reader :bindings
100
+
101
+ def initialize (a_excel_filename, a_fields_map = nil, a_enable_cb_or_rb_edition=false, write_jrxml = true, a_allow_sub_bands = true)
102
+ super(a_excel_filename)
103
+ read_all_tables()
104
+ report_name = File.basename(a_excel_filename, '.xlsx')
105
+ @report = JasperReport.new(report_name)
106
+ @current_band = nil
107
+ @first_row_in_band = 0
108
+ @band_type = nil
109
+ @v_scale = 1
110
+ @detail_cols_auto_height = false
111
+ @auto_float = false
112
+ @auto_stretch = false
113
+ @band_split_type = nil
114
+ @basic_expressions = false
115
+ @allow_sub_bands = a_allow_sub_bands
116
+ @bindings = a_fields_map
117
+ @use_casper_bindings = false
118
+
119
+ # If the field map is not supplied load aux tables from the same excel
120
+ if @bindings.nil?
121
+ @bindings = Hash.new
122
+
123
+ # Load parameters config table if it exists
124
+ if respond_to?('params_def') and not params_def.nil?
125
+ params_def.each do |param|
126
+ param.presentation = Presentation.new(param.presentation)
127
+ @bindings[param.id] = param
128
+ end
129
+ end
130
+
131
+ # Load fields config table if it exists
132
+ if respond_to?('fields_def') and not fields_def.nil?
133
+ fields_def.each do |field|
134
+ field.presentation = Presentation.new(field.presentation)
135
+ @bindings[field.id] = field
136
+ end
137
+ end
138
+
139
+ # Load variable definition table if it exists
140
+ if respond_to? ('variables_def') and not variables_def.nil?
141
+ variables_def.each do |vdef|
142
+ next if vdef.name.nil? or vdef.name.empty?
143
+ variable = Variable.new(vdef.name)
144
+ variable.java_class = vdef.java_class unless vdef.java_class.nil? or vdef.java_class.empty?
145
+ variable.calculation = vdef.calculation unless vdef.calculation.nil? or vdef.calculation.empty?
146
+ variable.reset_type = vdef.reset unless vdef.reset.nil? or vdef.reset.empty?
147
+ variable.variable_expression = vdef.expression unless vdef.expression.nil? or vdef.expression.empty?
148
+ variable.initial_value_expression = vdef.initial_expression unless vdef.initial_expression.nil? or vdef.initial_expression.empty?
149
+ @report.variables[vdef.name] = variable
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ @widget_factory = WidgetFactory.new(@bindings)
156
+ @widget_factory.cb_editable = a_enable_cb_or_rb_edition
157
+ @widget_factory.rb_editable = a_enable_cb_or_rb_edition
158
+
159
+ generate_styles()
160
+
161
+ @px_width = @report.page_width - @report.left_margin - @report.right_margin
162
+
163
+ parse_sheets()
164
+ if write_jrxml
165
+ File.write(report_name + '.jrxml', @report.to_xml)
166
+ end
167
+ end
168
+
169
+ def generate_styles
170
+
171
+ (0 .. @workbook.cell_xfs.size - 1).each do |style_index|
172
+ style = xf_to_style(style_index)
173
+ @report.styles[style.name] = style
174
+ end
175
+
176
+ end
177
+
178
+ def xf_to_style (a_style_index)
179
+
180
+ # create a style
181
+ style = Style.new('style_' + (a_style_index + 1).to_s)
182
+
183
+ # grab cell format
184
+ xf = @workbook.cell_xfs[a_style_index]
185
+
186
+ # Format font
187
+ if xf.apply_font == true
188
+ xls_font = @workbook.fonts[xf.font_id]
189
+
190
+ # on PDF we only have one font
191
+ style.font_name = 'DejaVu Sans Condensed'
192
+ # if xls_font.name.val == 'Arial'
193
+ # style.font_name = 'DejaVu Sans Condensed'
194
+ # else
195
+ # style.font_name = xls_font.name.val
196
+ # end
197
+
198
+ unless xls_font.color.nil?
199
+ style.forecolor = convert_color(xls_font.color)
200
+ end
201
+
202
+ style.font_size = xls_font.sz.val unless xls_font.sz.nil?
203
+ style.is_bold = true unless xls_font.b.nil?
204
+ style.is_italic = true unless xls_font.i.nil?
205
+ end
206
+
207
+ # background
208
+ if xf.apply_fill == true
209
+ xls_fill = @workbook.fills[xf.fill_id]
210
+ if xls_fill.pattern_fill.pattern_type == 'solid'
211
+ style.backcolor = convert_color(xls_fill.pattern_fill.fg_color)
212
+ end
213
+ end
214
+
215
+ # borders
216
+ if xf.apply_border == true
217
+ xls_border = @workbook.borders[xf.border_id]
218
+
219
+ if xls_border.outline != nil
220
+
221
+ if xls_border.outline.style != nil
222
+ style.box ||= Box.new
223
+ style.box.left_pen = LeftPen.new
224
+ style.box.top_pen = TopPen.new
225
+ style.box.right_pen = RightPen.new
226
+ style.box.bottom = BottomPen.new
227
+ apply_border_style(style.box.left_pen , xls_border.outline)
228
+ apply_border_style(style.box.top_pen , xls_border.outline)
229
+ apply_border_style(style.box.right_pen , xls_border.outline)
230
+ apply_border_style(style.box.bottom_pen, xls_border.outline)
231
+ end
232
+
233
+ else
234
+
235
+ if xls_border.left != nil && xls_border.left.style != nil
236
+ style.box ||= Box.new
237
+ style.box.left_pen = LeftPen.new
238
+ apply_border_style(style.box.left_pen, xls_border.left)
239
+ end
240
+
241
+ if xls_border.top != nil && xls_border.top.style != nil
242
+ style.box ||= Box.new
243
+ style.box.top_pen = TopPen.new
244
+ apply_border_style(style.box.top_pen, xls_border.top)
245
+ end
246
+
247
+ if xls_border.right != nil && xls_border.right.style != nil
248
+ style.box ||= Box.new
249
+ style.box.right_pen = RightPen.new
250
+ apply_border_style(style.box.right_pen, xls_border.right)
251
+ end
252
+
253
+ if xls_border.bottom != nil && xls_border.bottom.style != nil
254
+ style.box ||= Box.new
255
+ style.box.bottom_pen = BottomPen.new
256
+ apply_border_style(style.box.bottom_pen, xls_border.bottom)
257
+ end
258
+
259
+ end
260
+ end
261
+
262
+ # Alignment
263
+ if xf.apply_alignment
264
+
265
+ #byebug if a_style_index == 111
266
+ unless xf.alignment.nil?
267
+ case xf.alignment.horizontal
268
+ when 'left', nil
269
+ style.h_text_align ='Left'
270
+ when 'center'
271
+ style.h_text_align ='Center'
272
+ when 'right'
273
+ style.h_text_align ='Right'
274
+ end
275
+
276
+ case xf.alignment.vertical
277
+ when 'top'
278
+ style.v_text_align ='Top'
279
+ when 'center'
280
+ style.v_text_align ='Middle'
281
+ when 'bottom', nil
282
+ style.v_text_align ='Bottom'
283
+ end
284
+
285
+ # rotation
286
+ case xf.alignment.text_rotation
287
+ when nil
288
+ style.rotation = nil
289
+ when 0
290
+ style.rotation = 'None'
291
+ when 90
292
+ style.rotation = 'Left'
293
+ when 180
294
+ style.rotation = 'UpsideDown'
295
+ when 270
296
+ style.rotation = 'Right'
297
+ end
298
+ end
299
+ end
300
+
301
+ return style
302
+
303
+ end
304
+
305
+ def apply_border_style (a_pen, a_xls_border_style)
306
+ case a_xls_border_style.style
307
+ when 'thin'
308
+ a_pen.line_width = 0.5
309
+ a_pen.line_style = 'Solid'
310
+ when 'medium'
311
+ a_pen.line_width = 1.0
312
+ a_pen.line_style = 'Solid'
313
+ when 'dashed'
314
+ a_pen.line_width = 1.0
315
+ a_pen.line_style = 'Dotted'
316
+ when 'dotted'
317
+ a_pen.line_width = 0.5
318
+ a_pen.line_style = 'Dotted'
319
+ when 'thick'
320
+ a_pen.line_width = 2.0
321
+ a_pen.line_style = 'Solid'
322
+ when 'double'
323
+ a_pen.line_width = 0.5
324
+ a_pen.line_style = 'Double'
325
+ when 'hair'
326
+ a_pen.line_width = 0.25
327
+ a_pen.line_style = 'Solid'
328
+ when 'mediumDashed'
329
+ a_pen.line_width = 1.0
330
+ a_pen.line_style = 'Dashed'
331
+ when 'dashDot'
332
+ a_pen.line_width = 0.5
333
+ a_pen.line_style = 'Dashed'
334
+ when 'mediumDashDot'
335
+ a_pen.line_width = 1.0
336
+ a_pen.line_style = 'Dashed'
337
+ when 'dashDotDot'
338
+ a_pen.line_width = 0.5
339
+ a_pen.line_style = 'Dotted'
340
+ when 'slantDashDot'
341
+ a_pen.line_width = 0.5
342
+ a_pen.line_style = 'Dotted'
343
+ else
344
+ a_pen.line_width = 1.0
345
+ a_pen.line_style = 'Solid'
346
+ end
347
+ a_pen.line_color = convert_color(a_xls_border_style.color)
348
+ end
349
+
350
+ def convert_color (a_xls_color)
351
+ if a_xls_color.indexed.nil?
352
+ if a_xls_color.theme != nil
353
+ cs = @workbook.theme.a_theme_elements.a_clr_scheme
354
+ case a_xls_color.theme
355
+ when 0
356
+ return tint_theme_color(cs.a_lt1, a_xls_color.tint)
357
+ when 1
358
+ return tint_theme_color(cs.a_dk1, a_xls_color.tint)
359
+ when 2
360
+ return tint_theme_color(cs.a_lt2, a_xls_color.tint)
361
+ when 3
362
+ return tint_theme_color(cs.a_dk2, a_xls_color.tint)
363
+ when 4
364
+ return tint_theme_color(cs.a_accent1, a_xls_color.tint)
365
+ when 5
366
+ return tint_theme_color(cs.a_accent2, a_xls_color.tint)
367
+ when 6
368
+ return tint_theme_color(cs.a_accent3, a_xls_color.tint)
369
+ when 7
370
+ return tint_theme_color(cs.a_accent4, a_xls_color.tint)
371
+ when 8
372
+ return tint_theme_color(cs.a_accent5, a_xls_color.tint)
373
+ when 9
374
+ return tint_theme_color(cs.a_accent6, a_xls_color.tint)
375
+ else
376
+ return '#c0c0c0'
377
+ end
378
+
379
+ elsif a_xls_color.auto or a_xls_color.rgb.nil?
380
+ return '#000000'
381
+ else
382
+ return '#' + a_xls_color.rgb[2..-1]
383
+ end
384
+ else
385
+ return '#' + @@CT_IndexedColors[a_xls_color.indexed]
386
+ end
387
+ end
388
+
389
+ def tint_theme_color (a_color, a_tint)
390
+ color = a_color.a_sys_clr.last_clr unless a_color.a_sys_clr.nil?
391
+ color ||= a_color.a_srgb_clr.val
392
+ r = color[0..1].to_i(16)
393
+ g = color[2..3].to_i(16)
394
+ b = color[4..5].to_i(16)
395
+ unless a_tint.nil?
396
+ if ( a_tint < 0 )
397
+ a_tint = 1 + a_tint;
398
+ r = r * a_tint
399
+ g = g * a_tint
400
+ b = b * a_tint
401
+ else
402
+ r = r + (a_tint * (255 - r))
403
+ g = g + (a_tint * (255 - g))
404
+ b = b + (a_tint * (255 - b))
405
+ end
406
+ end
407
+ r = 255 if r > 255
408
+ g = 255 if g > 255
409
+ b = 255 if b > 255
410
+ color = "#%02X%02X%02X" % [r, g, b]
411
+ color
412
+ end
413
+
414
+ def parse_sheets
415
+ @workbook.worksheets.each do |ws|
416
+ @worksheet = ws
417
+ @raw_width = 0
418
+ @current_band = nil
419
+ @band_type = nil
420
+ for col in (1 .. @worksheet.dimension.ref.col_range.end)
421
+ @raw_width += get_column_width(@worksheet, col)
422
+ end
423
+ generate_bands()
424
+ end
425
+ end
426
+
427
+ def generate_bands ()
428
+
429
+ for row in @worksheet.dimension.ref.row_range
430
+ next if @worksheet[row].nil?
431
+ next if @worksheet[row][0].nil?
432
+ row_tag = map_row_tag(@worksheet[row][0].value.to_s)
433
+ next if row_tag.nil?
434
+
435
+ if @band_type != row_tag
436
+ adjust_band_height()
437
+ process_row_mtag(row, row_tag)
438
+ @first_row_in_band = row
439
+ end
440
+ unless @current_band.nil?
441
+ generate_band_content(row)
442
+ end
443
+ end
444
+
445
+ adjust_band_height()
446
+ end
447
+
448
+ def process_row_mtag (a_row, a_row_tag)
449
+ if a_row_tag.nil? or a_row_tag.lines.size == 0
450
+ process_row_tag(a_row, a_row_tag)
451
+ else
452
+ a_row_tag.lines.each do |tag|
453
+ process_row_tag(a_row, tag)
454
+ end
455
+ end
456
+ end
457
+
458
+ def process_row_tag (a_row, a_row_tag)
459
+
460
+ case a_row_tag
461
+ when /CasperBinding:*/
462
+ @use_casper_bindings = a_row_tag.split(':')[1].strip == 'true'
463
+ when /BG\d*:/
464
+ @report.background ||= Background.new
465
+ @current_band = Band.new
466
+ @report.background.bands << @current_band
467
+ @band_type = a_row_tag
468
+ when /TL\d*:/
469
+ @report.title ||= Title.new
470
+ @current_band = Band.new
471
+ @report.title.bands << @current_band
472
+ @band_type = a_row_tag
473
+ when /PH\d*:/
474
+ @report.page_header ||= PageHeader.new
475
+ @current_band = Band.new
476
+ @report.page_header.bands << @current_band
477
+ @band_type = a_row_tag
478
+ when /CH\d*:/
479
+ @report.column_header ||= ColumnHeader.new
480
+ @current_band = Band.new
481
+ @report.column_header.bands << @current_band
482
+ @band_type = a_row_tag
483
+ when /DT\d*/
484
+ @report.detail ||= Detail.new
485
+ @current_band = Band.new
486
+ @report.detail.bands << @current_band
487
+ @band_type = a_row_tag
488
+ when /CF\d*:/
489
+ @report.column_footer ||= ColumnFooter.new
490
+ @current_band = Band.new
491
+ @report.column_footer.bands << @current_band
492
+ @band_type = a_row_tag
493
+ when /PF\d*:/
494
+ @report.page_footer ||= PageFooter.new
495
+ @current_band = Band.new
496
+ @report.page_footer.bands << @current_band
497
+ @band_type = a_row_tag
498
+ when /LPF\d*:/
499
+ @report.last_page_footer ||= LastPageFooter.new
500
+ @current_band = Band.new
501
+ @report.last_page_footer.bands << @current_band
502
+ @band_type = a_row_tag
503
+ when /SU\d*:/
504
+ @report.summary ||= Summary.new
505
+ @current_band = Band.new
506
+ @report.summary.bands << @current_band
507
+ @band_type = a_row_tag
508
+ when /ND\d*:/
509
+ @report.no_data ||= NoData.new
510
+ @current_band = Band.new
511
+ @report.no_data.bands << @current_band
512
+ @band_type = a_row_tag
513
+ when /GH\d*:/
514
+ @report.group ||= Group.new
515
+ @current_band = Band.new
516
+ @report.group.group_header.bands << @current_band
517
+ @band_type = a_row_tag
518
+ when /GF\d*:/
519
+ @report.group ||= Group.new
520
+ @current_band = Band.new
521
+ @report.group.group_footer.bands << @current_band
522
+ @band_type = a_row_tag
523
+ when /Orientation:.+/i
524
+ @report.orientation = a_row_tag.split(':')[1].strip
525
+ @report.update_page_size()
526
+ @px_width = @report.page_width - @report.left_margin - @report.right_margin
527
+ when /Size:.+/i
528
+ @report.paper_size = a_row_tag.split(':')[1].strip
529
+ @report.update_page_size()
530
+ @px_width = @report.page_width - @report.left_margin - @report.right_margin
531
+ when /Report.isTitleStartNewPage:.+/i
532
+ @report.is_title_new_page = a_row_tag.split(':')[1].strip == 'true'
533
+ when /Report.leftMargin:.+/i
534
+ @report.left_margin = a_row_tag.split(':')[1].strip.to_i
535
+ @px_width = @report.page_width - @report.left_margin - @report.right_margin
536
+ when /Report.rightMargin:.+/i
537
+ @report.right_margin = a_row_tag.split(':')[1].strip.to_i
538
+ @px_width = @report.page_width - @report.left_margin - @report.right_margin
539
+ when /Report.topMargin:.+/i
540
+ @report.top_margin = a_row_tag.split(':')[1].strip.to_i
541
+ when /Report.bottomMargin:.+/i
542
+ @report.bottom_margin = a_row_tag.split(':')[1].strip.to_i
543
+ when /VScale:.+/i
544
+ @v_scale = a_row_tag.split(':')[1].strip.to_f
545
+ when /Query:.+/i
546
+ @report.query_string = a_row_tag.split(':')[1].strip
547
+ when /Id:.+/i
548
+ @report.id = a_row_tag.split(':')[1].strip
549
+ when /Group.expression:.+/i
550
+ @report.group ||= Group.new
551
+ @report.group.group_expression = a_row_tag.split(':')[1].strip
552
+ declare_expression_entities(@report.group.group_expression)
553
+ when /Group.isStartNewPage:.+/i
554
+ @report.group ||= Group.new
555
+ @report.group.is_start_new_page = a_row_tag.split(':')[1].strip == 'true'
556
+ when /Group.isReprintHeaderOnEachPage:.+/i
557
+ @report.group ||= Group.new
558
+ @report.group.is_reprint_header_on_each_page = a_row_tag.split(':')[1].strip == 'true'
559
+ when /BasicExpressions:.+/i
560
+ @widget_factory.basic_expressions = a_row_tag.split(':')[1].strip == 'true'
561
+ when /Style:.+/i
562
+ @report.update_extension_style(a_row_tag.split(':')[1].strip, @worksheet[a_row][2])
563
+ @current_band = nil
564
+ @band_type = nil
565
+ else
566
+ @current_band = nil
567
+ @band_type = nil
568
+ end
569
+
570
+ if @current_band != nil && @worksheet.comments != nil && @worksheet.comments.size > 0 && @worksheet.comments[0].comment_list != nil
571
+
572
+ @worksheet.comments[0].comment_list.each do |comment|
573
+ if comment.ref.col_range.begin == 0 && comment.ref.row_range.begin == a_row
574
+ comment.text.to_s.lines.each do |text|
575
+ text.strip!
576
+ next if text == ''
577
+ tag, value = text.split(':')
578
+ next if value.nil? || tag.nil?
579
+ tag.strip!
580
+ value.strip!
581
+ if tag == 'PE' or tag == 'printWhenExpression'
582
+ if @current_band.print_when_expression.nil?
583
+ @current_band.print_when_expression = value
584
+ transform_expression(value) # to force declaration of paramters/fields/variables
585
+ end
586
+ elsif tag == 'lineParentIdField'
587
+ @current_band.properties ||= Array.new
588
+ @current_band.properties << Property.new("epaper.casper.band.patch.op.add.attribute.name", value)
589
+ elsif tag == 'AF' or tag == 'autoFloat'
590
+ @current_band.auto_float = to_b(value)
591
+ elsif tag == 'AS' or tag == 'autoStretch'
592
+ @current_band.auto_stretch = to_b(value)
593
+ elsif tag == 'splitType'
594
+ @current_band.split_type = value
595
+ elsif tag == 'stretchType'
596
+ @current_band.stretch_type = value
597
+ elsif tag == 'dataRowTypeAttrName'
598
+ @current_band.properties ||= Array.new
599
+ @current_band.properties << Property.new("epaper.casper.band.patch.op.add.attribute.data_row_type.name", value)
600
+ end
601
+ end
602
+ end
603
+ end
604
+ end
605
+
606
+ end
607
+
608
+ def map_row_tag (a_row_tag)
609
+ unless @allow_sub_bands
610
+ match = a_row_tag.match(/\A(TL|SU|BG|PH|CH|DT|CF|PF|LPF|ND)\d*:\z/)
611
+ if match != nil and match.size == 2
612
+ return match[1] + ':'
613
+ end
614
+ end
615
+ a_row_tag
616
+ end
617
+
618
+ def to_b (a_value)
619
+ a_value.match(/(true|t|yes|y|1)$/i) != nil
620
+ end
621
+
622
+ def generate_band_content (a_row_idx)
623
+
624
+ row = @worksheet[a_row_idx]
625
+
626
+ max_cell_height = 0
627
+ col_idx = 1
628
+
629
+ while col_idx < row.size do
630
+
631
+ col_span, row_span, cell_width, cell_height = measure_cell(a_row_idx, col_idx)
632
+
633
+ if cell_width != nil
634
+
635
+ if row[col_idx].nil? || row[col_idx].style_index.nil?
636
+ col_idx += col_span
637
+ next
638
+ end
639
+
640
+ if @use_casper_bindings
641
+ field = create_field_with_casper_binding(row[col_idx])
642
+ else
643
+ field = create_field_legacy_mode(row[col_idx])
644
+ end
645
+ field.report_element.x = x_for_column(col_idx)
646
+ field.report_element.y = y_for_row(a_row_idx)
647
+ field.report_element.width = cell_width
648
+ field.report_element.height = cell_height
649
+ field.report_element.style = 'style_' + (row[col_idx].style_index + 1).to_s
650
+
651
+
652
+ if @current_band.stretch_type
653
+ field.report_element.stretch_type = @current_band.stretch_type
654
+ end
655
+
656
+ if @current_band.auto_float and field.report_element.y != 0
657
+ field.report_element.position_type = 'Float'
658
+ end
659
+
660
+ if @current_band.auto_stretch and field.respond_to?('is_stretch_with_overflow')
661
+ field.is_stretch_with_overflow = true
662
+ end
663
+
664
+ # overide here with field by field directives
665
+ process_field_comments(a_row_idx, col_idx, field)
666
+
667
+
668
+ # If the field is from a horizontally merged cell we need to check the right side border
669
+ if col_span > 1
670
+ field.box ||= Box.new
671
+ xf = @workbook.cell_xfs[row[col_idx + col_span - 1].style_index]
672
+ if xf.apply_border
673
+ xls_border = @workbook.borders[xf.border_id]
674
+
675
+ if xls_border.right != nil && xls_border.right.style != nil
676
+ field.box ||= Box.new
677
+ field.box.right_pen = RightPen.new
678
+ apply_border_style(field.box.right_pen, xls_border.right)
679
+ end
680
+ end
681
+ end
682
+
683
+ # If the field is from a vertically merged cell we need to check the bottom side border
684
+ if row_span > 1
685
+ field.box ||= Box.new
686
+ xf = @workbook.cell_xfs[@worksheet[a_row_idx + row_span - 1][col_idx].style_index]
687
+ if xf.apply_border
688
+ xls_border = @workbook.borders[xf.border_id]
689
+
690
+ if xls_border.bottom != nil && xls_border.bottom.style != nil
691
+ field.box ||= Box.new
692
+ field.box.bottom_pen = BottomPen.new
693
+ apply_border_style(field.box.bottom_pen, xls_border.bottom)
694
+ end
695
+ end
696
+ end
697
+ if field_has_graphics(field)
698
+ @current_band.children << field
699
+ @report.style_set.add(field.report_element.style)
700
+ end
701
+ end
702
+ col_idx += col_span
703
+ end
704
+
705
+ end
706
+
707
+ def field_has_graphics (a_field)
708
+ text_empty = false
709
+ has_border = false
710
+ opaque = false
711
+
712
+ if a_field.instance_of?(StaticText)
713
+ if a_field.text.nil? || a_field.text.length == 0
714
+ text_empty = true
715
+ end
716
+ end
717
+
718
+ if a_field.instance_of?(TextField)
719
+ if a_field.text_field_expression.nil? || a_field.text_field_expression.length == 0
720
+ text_empty = true
721
+ end
722
+ end
723
+
724
+ if a_field.box != nil
725
+ if a_field.box.right_pen != nil ||
726
+ a_field.box.left_pen != nil ||
727
+ a_field.box.top_pen != nil ||
728
+ a_field.box.bottom_pen != nil
729
+ has_border = true
730
+ end
731
+ end
732
+
733
+ style = @report.styles[a_field.report_element.style]
734
+ if style != nil
735
+ if style.box != nil
736
+ if style.box.right_pen != nil ||
737
+ style.box.left_pen != nil ||
738
+ style.box.top_pen != nil ||
739
+ style.box.bottom_pen != nil
740
+ has_border = true
741
+ end
742
+ end
743
+ if (style.mode != nil && style.mode == 'Opaque') || style.backcolor
744
+ opaque = true
745
+ end
746
+ end
747
+
748
+ return true if opaque
749
+
750
+ return true if has_border
751
+
752
+ return true unless text_empty
753
+
754
+ return false
755
+ end
756
+
757
+ def create_field_with_casper_binding (a_cell)
758
+
759
+ fid = nil
760
+ expression = a_cell.value.to_s.strip
761
+ binding = @bindings[expression]
762
+
763
+ unless binding.nil?
764
+ case binding.widget
765
+ when 'Combo'
766
+ rv = CasperCombo.new(self, expression)
767
+ when 'Date'
768
+ rv = CasperDate.new(self, expression)
769
+ parameter = Parameter.new('i18n_date_format', 'java.lang.String')
770
+ parameter.default_value_expression = '"dd/MM/yyyy"'
771
+ @report.parameters['i18n_date_format'] = parameter
772
+ when '',nil
773
+ # No widget fall trought
774
+ else
775
+ raise "Unknown widget type: '#{binding.widget}' on binding '#{binding.id}'"
776
+ end
777
+ end
778
+
779
+ unless rv
780
+ case expression
781
+ when /\A\$CB{.+}\z/
782
+ rv = CasperCheckbox.new(self, expression)
783
+
784
+ when /\A\$RB{.+}\z/
785
+ rv = CasperRadioButton.new(self, expression)
786
+
787
+ when /\A\$SE{.+}\z/
788
+ rv = CasperTextField.new(self, expression[4..-2])
789
+
790
+ when /\A\$P{([a-zA-Z0-9_\-#]+)}\z/,
791
+ /\A\$F{([a-zA-Z0-9_\-#]+)}\z/,
792
+ /\A\$V{([a-zA-Z0-9_\-#]+)}\z/
793
+ rv = CasperTextField.new(self, expression)
794
+
795
+ when /.*\$[PFV]{.+}.*/
796
+ rv = CasperTextField.new(self, transform_expression(expression))
797
+
798
+ when /\A\$I{.+}\z/
799
+ rv = Image.new()
800
+
801
+ # copy cell alignment to image
802
+ style = @report.styles['style_' + (a_cell.style_index + 1).to_s]
803
+ rv.v_align = style.v_text_align
804
+ rv.h_align = style.h_text_align
805
+
806
+ unless expression.nil?
807
+ rv.image_expression = transform_expression(expression[3..expression.length-2])
808
+ end
809
+
810
+ end
811
+
812
+ end
813
+
814
+ byebug if not rv.nil? and rv.text_field_expression.nil?
815
+
816
+ unless rv
817
+ rv = StaticText.new
818
+ rv.text = expression
819
+ end
820
+
821
+ return rv
822
+
823
+ end
824
+
825
+ def create_field_legacy_mode (a_cell)
826
+
827
+ fid = nil
828
+ expression = a_cell.value.to_s
829
+ if ! (m = /\A\$P{([a-zA-Z0-9_\-#]+)}\z/.match expression.strip).nil?
830
+
831
+ # parameter
832
+ f_id = expression.strip
833
+ rv = @widget_factory.new_for_field(f_id, self)
834
+ rv.text_field_expression = expression
835
+
836
+ add_parameter(f_id, m[1])
837
+
838
+ elsif ! (m = /\A\$F{([a-zA-Z0-9_\-#]+)}\z/.match expression.strip).nil?
839
+
840
+ # field
841
+ f_id = expression.strip
842
+ rv = @widget_factory.new_for_field(f_id, self)
843
+ rv.text_field_expression = expression
844
+
845
+ add_field(f_id.strip, m[1])
846
+
847
+ elsif ! (m = /\A\$V{([a-zA-Z0-9_\-#]+)}\z/.match expression.strip).nil?
848
+
849
+ # variable
850
+ f_id = expression.strip
851
+ rv = @widget_factory.new_for_field(f_id, self)
852
+ rv.text_field_expression = expression
853
+
854
+ add_variable(f_id, m[1])
855
+
856
+ elsif ! (m = /\A\$C{(.+)}\z/.match expression.strip).nil?
857
+
858
+ # combo
859
+ combo = @widget_factory.new_combo(expression.strip)
860
+ rv = combo[:widget]
861
+ f_id = combo[:field]
862
+ f_nm = f_id[3..f_id.length-2]
863
+ d_fld = nil != combo[:display_field] ? combo[:display_field] : "name"
864
+
865
+ if f_id.match(/^\$P{/)
866
+ add_parameter(f_id, f_nm)
867
+ elsif combo[:field].match(/^\$F{/)
868
+ add_field(f_id, f_nm)
869
+ elsif combo[:field].match(/^\$V{/)
870
+ add_variable(f_id, f_nm)
871
+ else
872
+ raise ArgumentError, "Don't know how to add '#{f_id}'!"
873
+ end
874
+
875
+ rv.text_field_expression = "TABLE_ITEM(\"#{combo[:id]}\";\"id\";#{f_id};\"#{d_fld}\")"
876
+
877
+ elsif expression.match(/^\$CB{/)
878
+
879
+ # checkbox
880
+ checkbox = @widget_factory.new_checkbox(expression.strip)
881
+ declare_expression_entities(expression.strip)
882
+ rv = checkbox[:widget]
883
+
884
+ elsif expression.match(/^\$RB{/)
885
+
886
+ # radio button
887
+ declare_expression_entities(expression.strip)
888
+ radio_button = @widget_factory.new_radio_button(expression.strip)
889
+ rv = radio_button[:widget]
890
+
891
+ elsif expression.match(/^\$DE{/)
892
+
893
+ declare_expression_entities(expression.strip)
894
+ de = expression.strip.split(',')
895
+ de[0] = de[0][4..de[0].length-1]
896
+ de[1] = de[1][0..de[1].length-2]
897
+
898
+ properties = [
899
+ Property.new("epaper.casper.text.field.editable", "false"),
900
+ Property.new("epaper.casper.text.field.editable.field_name", de[0][3..de[0].length-2])
901
+ ]
902
+
903
+ rv = TextField.new(a_properties = properties, a_pattern = nil, a_pattern_expression = nil)
904
+ rv.text_field_expression = de[1]
905
+
906
+ elsif expression.match(/^\$SE{/)
907
+
908
+ declare_expression_entities(expression.strip)
909
+ expression = expression.strip
910
+ rv = TextField.new(a_properties = nil, a_pattern = nil, a_pattern_expression = nil)
911
+ rv.text_field_expression = expression[4..expression.length-2]
912
+
913
+ elsif expression.match(/^\$I{/)
914
+
915
+ rv = Image.new()
916
+
917
+ # copy cell alignment to image
918
+ style = @report.styles['style_' + (a_cell.style_index + 1).to_s]
919
+ rv.v_align = style.v_text_align
920
+ rv.h_align = style.h_text_align
921
+
922
+ unless expression.nil?
923
+ expression = expression.strip
924
+ rv.image_expression = transform_expression(expression[3..expression.length-2])
925
+ end
926
+
927
+ elsif expression.include? '$P{' or expression.include? '$F{' or expression.include? '$V{'
928
+
929
+ expression = transform_expression(expression)
930
+ rv = TextField.new(a_properties = nil, a_pattern = nil, a_pattern_expression = nil)
931
+ rv.text_field_expression = expression.strip
932
+
933
+ else
934
+
935
+ rv = StaticText.new
936
+ rv.text = expression
937
+
938
+ end
939
+
940
+ if !f_id.nil? && rv.is_a?(TextField)
941
+ if @widget_factory.java_class(f_id) == 'java.util.Date'
942
+ rv.text_field_expression = "DateFormat.parse(#{rv.text_field_expression},\"yyyy-MM-dd\")"
943
+ rv.pattern_expression = "$P{i18n_date_format}"
944
+ rv.report_element.properties << Property.new('epaper.casper.text.field.patch.pattern', 'yyyy-MM-dd') unless rv.report_element.properties.nil?
945
+ parameter = Parameter.new('i18n_date_format', 'java.lang.String')
946
+ parameter.default_value_expression = '"dd/MM/yyyy"'
947
+ @report.parameters['i18n_date_format'] = parameter
948
+ end
949
+ end
950
+ return rv
951
+ end
952
+
953
+ def declare_expression_entities (a_expression)
954
+
955
+ a_expression.scan(/\$[A-Z]{[a-z_0-9\.\-#]+}/) { |v|
956
+ f_id = (/\A\$[PFV]{(.+)}\z/.match v).to_s
957
+ if false == f_id.nil?
958
+ f_nm = f_id[3..-2]
959
+ if f_id.match(/^\$P{/)
960
+ add_parameter(f_id, f_nm)
961
+ elsif f_id.match(/^\$F{/)
962
+ add_field(f_id, f_nm)
963
+ elsif f_id.match(/^\$V{/)
964
+ add_variable(f_id, f_nm)
965
+ else
966
+ raise ArgumentError, "Don't know how to add '#{f_id}'!"
967
+ end
968
+ end
969
+ }
970
+ nil
971
+ end
972
+
973
+ def process_field_comments (a_row, a_col, a_field)
974
+
975
+ if @worksheet.comments != nil && @worksheet.comments.size > 0 && @worksheet.comments[0].comment_list != nil
976
+
977
+ @worksheet.comments[0].comment_list.each do |comment|
978
+ if comment.ref.col_range.begin == a_col && comment.ref.row_range.begin == a_row
979
+ comment.text.to_s.lines.each do |text|
980
+ text.strip!
981
+ next if text == '' or text.nil?
982
+ idx = text.index(':')
983
+ next if idx.nil?
984
+ tag = text[0..(idx-1)]
985
+ value = text[(idx+1)..-1]
986
+ next if tag.nil? or value.nil?
987
+ tag.strip!
988
+ value.strip!
989
+
990
+ if tag == 'PE' or tag == 'printWhenExpression'
991
+ a_field.report_element.print_when_expression = value
992
+ transform_expression(value) # to force declaration of paramters/fields/variables
993
+ elsif tag == 'AF' or tag == 'autoFloat'
994
+ a_field.report_element.position_type = to_b(value) ? 'Float' : 'FixRelativeToTop'
995
+ elsif tag == 'AS' or tag == 'autoStretch' and a_field.respond_to?(:is_stretch_with_overflow)
996
+ a_field.is_stretch_with_overflow = to_b(value)
997
+ elsif tag == 'ST' or tag == 'stretchType'
998
+ a_field.report_element.stretch_type = value
999
+ elsif tag == 'BN' or tag == 'blankIfNull' and a_field.respond_to?(:is_blank_when_null)
1000
+ a_field.is_blank_when_null = to_b(value)
1001
+ elsif tag == 'PT' or tag == 'pattern' and a_field.respond_to?(:pattern)
1002
+ a_field.pattern = value
1003
+ elsif tag == 'ET' or tag == 'evaluationTime' and a_field.respond_to?(:evaluation_time)
1004
+ a_field.evaluation_time = value
1005
+ elsif tag == 'DE' or tag == 'disabledExpression'
1006
+ if a_field.respond_to? :disabled_conditional
1007
+ a_field.disabled_conditional(value)
1008
+ else
1009
+ a_field.report_element.properties ||= Array.new
1010
+ a_field.report_element.properties << PropertyExpression.new('epaper.casper.text.field.disabled.if', value)
1011
+ end
1012
+ transform_expression(value) # to force declaration of parameters/fields/variables
1013
+ elsif tag == 'SE' or tag == 'styleExpression'
1014
+ if a_field.respond_to? :style_expression
1015
+ a_field.style_expression(value)
1016
+ else
1017
+ a_field.report_element.properties ||= Array.new
1018
+ a_field.report_element.properties << PropertyExpression.new('epaper.casper.style.condition', value)
1019
+ end
1020
+ transform_expression(value) # to force declaration of parameters/fields/variables
1021
+ elsif tag == 'RIC' or tag == 'reloadIfChanged'
1022
+ if a_field.respond_to? :reload_if_changed
1023
+ a_field.reload_if_changed(value)
1024
+ else
1025
+ a_field.report_element.properties ||= Array.new
1026
+ a_field.report_element.properties << Property.new('epaper.casper.text.field.reload.if_changed', value)
1027
+ end
1028
+ elsif tag == 'EE' or tag == 'editableExpression'
1029
+ if a_field.respond_to? :enabled_conditional
1030
+ a_field.enabled_conditional(value)
1031
+ else
1032
+ a_field.report_element.properties ||= Array.new
1033
+ a_field.report_element.properties << PropertyExpression.new('epaper.casper.text.field.editable.if', value)
1034
+ end
1035
+ transform_expression(value) # to force declaration of paramters/fields/variables
1036
+ end
1037
+ end
1038
+
1039
+ end
1040
+ end
1041
+ end
1042
+ end
1043
+
1044
+ def transform_expression (a_expression)
1045
+ matches = a_expression.split(/(\$[PVF]{[a-zA-Z0-9\._]+})/)
1046
+ if matches.nil?
1047
+ return a_expression
1048
+ end
1049
+ terms = Array.new
1050
+ matches.each do |match|
1051
+ if match.length == 0
1052
+ next
1053
+ elsif match.start_with?('$P{')
1054
+ terms << match
1055
+ add_parameter(match, match[3..-2])
1056
+ elsif match.start_with?('$F{')
1057
+ terms << match
1058
+ add_field(match, match[3..-2])
1059
+ elsif match.start_with?('$V{')
1060
+ terms << match
1061
+ add_variable(match, match[3..-2])
1062
+ else
1063
+ terms << '"' + match + '"'
1064
+ end
1065
+ end
1066
+ terms.join(' + ')
1067
+ end
1068
+
1069
+ def add_parameter (a_id, a_name)
1070
+ unless @report.parameters.has_key? a_name
1071
+ parameter = Parameter.new(a_name, @widget_factory.java_class(a_id))
1072
+ if @bindings.has_key? a_id
1073
+ binding = @bindings[a_id]
1074
+ if binding.respond_to? 'default' and binding.default != nil and binding.default.strip != ''
1075
+ if binding.java_class == 'java.lang.String'
1076
+ parameter.default_value_expression = "\"#{binding.default.strip}\""
1077
+ else
1078
+ parameter.default_value_expression = binding.default.strip
1079
+ end
1080
+ end
1081
+ end
1082
+ @report.parameters[a_name] = parameter
1083
+ end
1084
+ end
1085
+
1086
+ def add_field (a_id, a_name)
1087
+ unless @report.fields.has_key? a_name
1088
+ field = Field.new(a_name, @widget_factory.java_class(a_id))
1089
+ @report.fields[a_name] = field
1090
+ end
1091
+ end
1092
+
1093
+ def add_variable (a_id, a_name)
1094
+ if "PAGE_NUMBER" != a_name
1095
+ unless @report.variables.has_key? a_name
1096
+ variable = Variable.new(a_name, @widget_factory.java_class(a_id))
1097
+ @report.variables[a_name] = variable
1098
+ end
1099
+ end
1100
+ end
1101
+
1102
+ def get_column_width (a_worksheet, a_index)
1103
+ width = a_worksheet.get_column_width_raw(a_index)
1104
+ width ||= RubyXL::ColumnRange::DEFAULT_WIDTH
1105
+ return width
1106
+ end
1107
+
1108
+
1109
+ def x_for_column (a_col_idx)
1110
+
1111
+ width = 0
1112
+ for idx in (1 .. a_col_idx - 1) do
1113
+ width += get_column_width(@worksheet, idx)
1114
+ end
1115
+ return scale_x(width).round
1116
+
1117
+ end
1118
+
1119
+ def y_for_row (a_row_idx)
1120
+ height = 0
1121
+ for idx in (@first_row_in_band .. a_row_idx - 1) do
1122
+ height += @worksheet.get_row_height(idx)
1123
+ end
1124
+ return scale_y(height).round
1125
+ end
1126
+
1127
+ def adjust_band_height ()
1128
+
1129
+ return if @current_band.nil?
1130
+
1131
+ height = 0
1132
+ for row in @worksheet.dimension.ref.row_range
1133
+ unless @worksheet[row].nil? or @worksheet[row][0].nil? or @worksheet[row][0].value.nil? or map_row_tag(@worksheet[row][0].value) != @band_type
1134
+ height += y_for_row(row + 1) - y_for_row(row)
1135
+ end
1136
+ end
1137
+
1138
+ @current_band.height = height
1139
+ end
1140
+
1141
+ def measure_cell (a_row_idx, a_col_idx)
1142
+
1143
+ @worksheet.merged_cells.each do |merged_cell|
1144
+
1145
+ col_span = merged_cell.ref.col_range.size
1146
+ row_span = merged_cell.ref.row_range.size
1147
+
1148
+ if a_row_idx == merged_cell.ref.row_range.begin && a_col_idx == merged_cell.ref.col_range.begin
1149
+
1150
+ cell_height = y_for_row(merged_cell.ref.row_range.end + 1) - y_for_row(merged_cell.ref.row_range.begin)
1151
+ cell_width = x_for_column(merged_cell.ref.col_range.end + 1) - x_for_column(merged_cell.ref.col_range.begin)
1152
+
1153
+ return col_span, row_span, cell_width, cell_height
1154
+
1155
+ elsif merged_cell.ref.row_range.include?(a_row_idx) and merged_cell.ref.col_range.include?(a_col_idx)
1156
+
1157
+ # The cell is overlaped by a merged cell
1158
+ return col_span, row_span, nil, nil
1159
+
1160
+ end
1161
+ end
1162
+
1163
+ cell_height = y_for_row(a_row_idx + 1) - y_for_row(a_row_idx)
1164
+ cell_width = x_for_column(a_col_idx + 1) - x_for_column(a_col_idx)
1165
+ return 1, 1, cell_width, cell_height
1166
+
1167
+ end
1168
+
1169
+ def scale_x (a_width)
1170
+ return (a_width * @px_width / @raw_width)
1171
+ end
1172
+
1173
+ def scale_y (a_height)
1174
+ return (a_height * @v_scale)
1175
+ end
1176
+
1177
+
1178
+ end # class ExcelToJrxml
1179
+
1180
+ end
1181
+ end
1182
+ end
1183
+ end