write_xlsx 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (261) hide show
  1. data/.document +5 -0
  2. data/.gitattributes +1 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +82 -0
  6. data/Rakefile +78 -0
  7. data/VERSION +1 -0
  8. data/examples/a_simple.rb +45 -0
  9. data/examples/array_formula.rb +33 -0
  10. data/examples/autofilter.rb +235 -0
  11. data/examples/chart_area.rb +59 -0
  12. data/examples/chart_bar.rb +59 -0
  13. data/examples/chart_column.rb +58 -0
  14. data/examples/chart_line.rb +59 -0
  15. data/examples/chart_pie.rb +49 -0
  16. data/examples/chart_scatter.rb +59 -0
  17. data/examples/chart_stock.rb +65 -0
  18. data/examples/colors.rb +130 -0
  19. data/examples/comments1.rb +12 -0
  20. data/examples/comments2.rb +335 -0
  21. data/examples/conditional_format.rb +67 -0
  22. data/examples/data_validate.rb +279 -0
  23. data/examples/defined_name.rb +28 -0
  24. data/examples/demo.rb +104 -0
  25. data/examples/diag_border.rb +26 -0
  26. data/examples/headers.rb +119 -0
  27. data/examples/hide_sheet.rb +30 -0
  28. data/examples/hyperlink1.rb +58 -0
  29. data/examples/indent.rb +28 -0
  30. data/examples/merge1.rb +38 -0
  31. data/examples/merge2.rb +48 -0
  32. data/examples/merge3.rb +43 -0
  33. data/examples/merge4.rb +82 -0
  34. data/examples/merge5.rb +70 -0
  35. data/examples/merge6.rb +48 -0
  36. data/examples/outline.rb +252 -0
  37. data/examples/properties.rb +33 -0
  38. data/examples/protection.rb +34 -0
  39. data/examples/rich_strings.rb +42 -0
  40. data/examples/right_to_left.rb +24 -0
  41. data/examples/tab_colors.rb +26 -0
  42. data/lib/write_xlsx.rb +77 -0
  43. data/lib/write_xlsx/chart.rb +3027 -0
  44. data/lib/write_xlsx/chart/area.rb +52 -0
  45. data/lib/write_xlsx/chart/bar.rb +126 -0
  46. data/lib/write_xlsx/chart/column.rb +132 -0
  47. data/lib/write_xlsx/chart/line.rb +51 -0
  48. data/lib/write_xlsx/chart/pie.rb +210 -0
  49. data/lib/write_xlsx/chart/scatter.rb +252 -0
  50. data/lib/write_xlsx/chart/stock.rb +134 -0
  51. data/lib/write_xlsx/chartsheet.rb +173 -0
  52. data/lib/write_xlsx/colors.rb +65 -0
  53. data/lib/write_xlsx/compatibility.rb +71 -0
  54. data/lib/write_xlsx/drawing.rb +547 -0
  55. data/lib/write_xlsx/format.rb +683 -0
  56. data/lib/write_xlsx/package/app.rb +218 -0
  57. data/lib/write_xlsx/package/comments.rb +221 -0
  58. data/lib/write_xlsx/package/content_types.rb +189 -0
  59. data/lib/write_xlsx/package/core.rb +196 -0
  60. data/lib/write_xlsx/package/packager.rb +510 -0
  61. data/lib/write_xlsx/package/relationships.rb +98 -0
  62. data/lib/write_xlsx/package/shared_strings.rb +96 -0
  63. data/lib/write_xlsx/package/styles.rb +705 -0
  64. data/lib/write_xlsx/package/theme.rb +45 -0
  65. data/lib/write_xlsx/package/vml.rb +386 -0
  66. data/lib/write_xlsx/package/xml_writer_simple.rb +90 -0
  67. data/lib/write_xlsx/utility.rb +113 -0
  68. data/lib/write_xlsx/workbook.rb +1488 -0
  69. data/lib/write_xlsx/worksheet.rb +6578 -0
  70. data/lib/write_xlsx/zip_file_utils.rb +98 -0
  71. data/test/chart/test_add_series.rb +113 -0
  72. data/test/chart/test_process_names.rb +27 -0
  73. data/test/chart/test_write_auto.rb +15 -0
  74. data/test/chart/test_write_ax_id.rb +15 -0
  75. data/test/chart/test_write_ax_pos.rb +15 -0
  76. data/test/chart/test_write_chart_space.rb +15 -0
  77. data/test/chart/test_write_cross_ax.rb +15 -0
  78. data/test/chart/test_write_crosses.rb +15 -0
  79. data/test/chart/test_write_format_code.rb +15 -0
  80. data/test/chart/test_write_idx.rb +15 -0
  81. data/test/chart/test_write_label_align.rb +15 -0
  82. data/test/chart/test_write_label_offset.rb +15 -0
  83. data/test/chart/test_write_lang.rb +15 -0
  84. data/test/chart/test_write_layout.rb +15 -0
  85. data/test/chart/test_write_legend.rb +16 -0
  86. data/test/chart/test_write_legend_pos.rb +15 -0
  87. data/test/chart/test_write_major_gridlines.rb +15 -0
  88. data/test/chart/test_write_marker.rb +17 -0
  89. data/test/chart/test_write_marker_size.rb +15 -0
  90. data/test/chart/test_write_marker_value.rb +16 -0
  91. data/test/chart/test_write_num_cache.rb +16 -0
  92. data/test/chart/test_write_num_fmt.rb +16 -0
  93. data/test/chart/test_write_number_format.rb +15 -0
  94. data/test/chart/test_write_order.rb +15 -0
  95. data/test/chart/test_write_orientation.rb +15 -0
  96. data/test/chart/test_write_page_margins.rb +15 -0
  97. data/test/chart/test_write_page_setup.rb +15 -0
  98. data/test/chart/test_write_plot_vis_only.rb +15 -0
  99. data/test/chart/test_write_pt.rb +16 -0
  100. data/test/chart/test_write_pt_count.rb +16 -0
  101. data/test/chart/test_write_series_formula.rb +16 -0
  102. data/test/chart/test_write_style.rb +41 -0
  103. data/test/chart/test_write_symbol.rb +16 -0
  104. data/test/chart/test_write_tick_lbl_pos.rb +16 -0
  105. data/test/chart/test_write_v.rb +16 -0
  106. data/test/drawing/test_drawing_chart_01.rb +50 -0
  107. data/test/drawing/test_drawing_image_01.rb +59 -0
  108. data/test/helper.rb +90 -0
  109. data/test/package/app/test_app01.rb +44 -0
  110. data/test/package/app/test_app02.rb +46 -0
  111. data/test/package/app/test_app03.rb +53 -0
  112. data/test/package/comments/test_comments01.rb +36 -0
  113. data/test/package/comments/test_write_text_t.rb +44 -0
  114. data/test/package/content_types/test_content_types.rb +35 -0
  115. data/test/package/content_types/test_write_default.rb +13 -0
  116. data/test/package/content_types/test_write_override.rb +13 -0
  117. data/test/package/core/test_core01.rb +28 -0
  118. data/test/package/core/test_core02.rb +42 -0
  119. data/test/package/relationships/test_relationships.rb +28 -0
  120. data/test/package/relationships/test_sheet_rels.rb +22 -0
  121. data/test/package/shared_strings/test_shared_strings01.rb +30 -0
  122. data/test/package/shared_strings/test_shared_strings02.rb +30 -0
  123. data/test/package/shared_strings/test_write_si.rb +13 -0
  124. data/test/package/shared_strings/test_write_sst.rb +15 -0
  125. data/test/package/styles/test_styles_01.rb +69 -0
  126. data/test/package/styles/test_styles_02.rb +104 -0
  127. data/test/package/styles/test_styles_03.rb +90 -0
  128. data/test/package/styles/test_styles_04.rb +216 -0
  129. data/test/package/styles/test_styles_05.rb +150 -0
  130. data/test/package/styles/test_styles_06.rb +104 -0
  131. data/test/package/styles/test_styles_07.rb +104 -0
  132. data/test/package/styles/test_styles_08.rb +109 -0
  133. data/test/package/styles/test_styles_09.rb +95 -0
  134. data/test/package/vml/test_vml_01.rb +42 -0
  135. data/test/package/vml/test_write_anchor.rb +14 -0
  136. data/test/package/vml/test_write_auto_fill.rb +14 -0
  137. data/test/package/vml/test_write_column.rb +14 -0
  138. data/test/package/vml/test_write_div.rb +14 -0
  139. data/test/package/vml/test_write_fill.rb +14 -0
  140. data/test/package/vml/test_write_idmap.rb +14 -0
  141. data/test/package/vml/test_write_move_with_cells.rb +14 -0
  142. data/test/package/vml/test_write_path.rb +22 -0
  143. data/test/package/vml/test_write_row.rb +14 -0
  144. data/test/package/vml/test_write_shadow.rb +14 -0
  145. data/test/package/vml/test_write_shapelayout.rb +14 -0
  146. data/test/package/vml/test_write_shapetype.rb +14 -0
  147. data/test/package/vml/test_write_size_with_cells.rb +14 -0
  148. data/test/package/vml/test_write_stroke.rb +14 -0
  149. data/test/package/vml/test_write_textbox.rb +14 -0
  150. data/test/perl_output/a_simple.xlsx +0 -0
  151. data/test/perl_output/array_formula.xlsx +0 -0
  152. data/test/perl_output/autofilter.xlsx +0 -0
  153. data/test/perl_output/chart_area.xlsx +0 -0
  154. data/test/perl_output/chart_bar.xlsx +0 -0
  155. data/test/perl_output/chart_column.xlsx +0 -0
  156. data/test/perl_output/chart_line.xlsx +0 -0
  157. data/test/perl_output/chart_pie.xlsx +0 -0
  158. data/test/perl_output/chart_scatter.xlsx +0 -0
  159. data/test/perl_output/chart_stock.xlsx +0 -0
  160. data/test/perl_output/comments1.xlsx +0 -0
  161. data/test/perl_output/comments2.xlsx +0 -0
  162. data/test/perl_output/conditional_format.xlsx +0 -0
  163. data/test/perl_output/data_validate.xlsx +0 -0
  164. data/test/perl_output/defined_name.xlsx +0 -0
  165. data/test/perl_output/demo.xlsx +0 -0
  166. data/test/perl_output/diag_border.xlsx +0 -0
  167. data/test/perl_output/fit_to_pages.xlsx +0 -0
  168. data/test/perl_output/headers.xlsx +0 -0
  169. data/test/perl_output/hide_sheet.xlsx +0 -0
  170. data/test/perl_output/hyperlink.xlsx +0 -0
  171. data/test/perl_output/indent.xlsx +0 -0
  172. data/test/perl_output/merge1.xlsx +0 -0
  173. data/test/perl_output/merge2.xlsx +0 -0
  174. data/test/perl_output/merge3.xlsx +0 -0
  175. data/test/perl_output/merge4.xlsx +0 -0
  176. data/test/perl_output/merge5.xlsx +0 -0
  177. data/test/perl_output/merge6.xlsx +0 -0
  178. data/test/perl_output/outline.xlsx +0 -0
  179. data/test/perl_output/print_scale.xlsx +0 -0
  180. data/test/perl_output/properties.xlsx +0 -0
  181. data/test/perl_output/protection.xlsx +0 -0
  182. data/test/perl_output/rich_strings.xlsx +0 -0
  183. data/test/perl_output/right_to_left.xlsx +0 -0
  184. data/test/perl_output/tab_colors.xlsx +0 -0
  185. data/test/test_delete_files.rb +37 -0
  186. data/test/test_example_match.rb +2281 -0
  187. data/test/test_xml_writer_simple.rb +63 -0
  188. data/test/workbook/test_get_chart_range.rb +59 -0
  189. data/test/workbook/test_sort_defined_names.rb +77 -0
  190. data/test/workbook/test_workbook_01.rb +29 -0
  191. data/test/workbook/test_workbook_02.rb +31 -0
  192. data/test/workbook/test_workbook_03.rb +31 -0
  193. data/test/workbook/test_workbook_new.rb +18 -0
  194. data/test/workbook/test_write_defined_name.rb +17 -0
  195. data/test/workbook/test_write_defined_names.rb +41 -0
  196. data/test/worksheet/test_calculate_spans.rb +58 -0
  197. data/test/worksheet/test_convert_date_time_01.rb +439 -0
  198. data/test/worksheet/test_convert_date_time_02.rb +478 -0
  199. data/test/worksheet/test_convert_date_time_03.rb +435 -0
  200. data/test/worksheet/test_extract_filter_tokens.rb +109 -0
  201. data/test/worksheet/test_parse_filter_expression.rb +143 -0
  202. data/test/worksheet/test_position_object.rb +50 -0
  203. data/test/worksheet/test_repeat_formula.rb +55 -0
  204. data/test/worksheet/test_worksheet_01.rb +32 -0
  205. data/test/worksheet/test_worksheet_02.rb +38 -0
  206. data/test/worksheet/test_worksheet_03.rb +44 -0
  207. data/test/worksheet/test_worksheet_04.rb +45 -0
  208. data/test/worksheet/test_write_array_formula_01.rb +99 -0
  209. data/test/worksheet/test_write_autofilter.rb +260 -0
  210. data/test/worksheet/test_write_brk.rb +18 -0
  211. data/test/worksheet/test_write_cell.rb +49 -0
  212. data/test/worksheet/test_write_cell_value.rb +33 -0
  213. data/test/worksheet/test_write_col_breaks.rb +27 -0
  214. data/test/worksheet/test_write_col_info.rb +95 -0
  215. data/test/worksheet/test_write_conditional_formatting.rb +72 -0
  216. data/test/worksheet/test_write_custom_filter.rb +18 -0
  217. data/test/worksheet/test_write_custom_filters.rb +25 -0
  218. data/test/worksheet/test_write_data_validation_01.rb +113 -0
  219. data/test/worksheet/test_write_data_validation_02.rb +528 -0
  220. data/test/worksheet/test_write_dimension.rb +94 -0
  221. data/test/worksheet/test_write_ext.rb +18 -0
  222. data/test/worksheet/test_write_ext_lst.rb +18 -0
  223. data/test/worksheet/test_write_filter.rb +18 -0
  224. data/test/worksheet/test_write_filter_column.rb +18 -0
  225. data/test/worksheet/test_write_filters.rb +32 -0
  226. data/test/worksheet/test_write_header_footer.rb +53 -0
  227. data/test/worksheet/test_write_hyperlink.rb +39 -0
  228. data/test/worksheet/test_write_hyperlinks.rb +27 -0
  229. data/test/worksheet/test_write_legacy_drawing.rb +19 -0
  230. data/test/worksheet/test_write_merge_cell.rb +18 -0
  231. data/test/worksheet/test_write_merge_cells.rb +192 -0
  232. data/test/worksheet/test_write_methods.rb +353 -0
  233. data/test/worksheet/test_write_mx_plv.rb +19 -0
  234. data/test/worksheet/test_write_page_margins.rb +98 -0
  235. data/test/worksheet/test_write_page_set_up_pr.rb +19 -0
  236. data/test/worksheet/test_write_page_setup.rb +54 -0
  237. data/test/worksheet/test_write_pane.rb +123 -0
  238. data/test/worksheet/test_write_phonetic_pr.rb +19 -0
  239. data/test/worksheet/test_write_print_options.rb +77 -0
  240. data/test/worksheet/test_write_row_breaks.rb +27 -0
  241. data/test/worksheet/test_write_row_element.rb +69 -0
  242. data/test/worksheet/test_write_selection.rb +18 -0
  243. data/test/worksheet/test_write_sheet_calc_pr.rb +18 -0
  244. data/test/worksheet/test_write_sheet_data.rb +18 -0
  245. data/test/worksheet/test_write_sheet_format_pr.rb +18 -0
  246. data/test/worksheet/test_write_sheet_pr.rb +36 -0
  247. data/test/worksheet/test_write_sheet_protection.rb +174 -0
  248. data/test/worksheet/test_write_sheet_view.rb +62 -0
  249. data/test/worksheet/test_write_sheet_view1.rb +64 -0
  250. data/test/worksheet/test_write_sheet_view2.rb +56 -0
  251. data/test/worksheet/test_write_sheet_view3.rb +83 -0
  252. data/test/worksheet/test_write_sheet_view4.rb +83 -0
  253. data/test/worksheet/test_write_sheet_view5.rb +74 -0
  254. data/test/worksheet/test_write_sheet_view6.rb +51 -0
  255. data/test/worksheet/test_write_sheet_view7.rb +71 -0
  256. data/test/worksheet/test_write_sheet_view8.rb +51 -0
  257. data/test/worksheet/test_write_sheet_view9.rb +51 -0
  258. data/test/worksheet/test_write_tab_color.rb +23 -0
  259. data/test/worksheet/test_write_worksheet.rb +19 -0
  260. data/write_xlsx.gemspec +308 -0
  261. metadata +363 -0
@@ -0,0 +1,113 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Writexlsx
3
+ module Utility
4
+ #
5
+ # xl_rowcol_to_cell($row, $col, $row_absolute, $col_absolute)
6
+ #
7
+ def xl_rowcol_to_cell(row, col, row_absolute = false, col_absolute = false)
8
+ row += 1 # Change from 0-indexed to 1 indexed.
9
+ row_abs = row_absolute ? '$' : ''
10
+ col_abs = col_absolute ? '$' : ''
11
+ col_str = xl_col_to_name(col, col_absolute)
12
+ "#{col_str}#{absolute_char(row_absolute)}#{row}"
13
+ end
14
+
15
+ #
16
+ # Returns: ($row, $col, $row_absolute, $col_absolute)
17
+ #
18
+ # The $row_absolute and $col_absolute parameters aren't documented because they
19
+ # mainly used internally and aren't very useful to the user.
20
+ #
21
+ def xl_cell_to_rowcol(cell)
22
+ cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/
23
+
24
+ col_abs = $1 != ""
25
+ col = $2
26
+ row_abs = $3 != ""
27
+ row = $4.to_i
28
+
29
+ # Convert base26 column string to number
30
+ # All your Base are belong to us.
31
+ chars = col.split(//)
32
+ expn = 0
33
+ col = 0
34
+
35
+ chars.reverse.each do |char|
36
+ col += (char.ord - 'A'.ord + 1) * (26 ** expn)
37
+ expn += 1
38
+ end
39
+
40
+ # Convert 1-index to zero-index
41
+ row -= 1
42
+ col -= 1
43
+
44
+ return [row, col, row_abs, col_abs]
45
+ end
46
+
47
+ def xl_col_to_name(col, col_absolute)
48
+ # Change from 0-indexed to 1 indexed.
49
+ col += 1
50
+ col_str = ''
51
+
52
+ while col > 0
53
+ # Set remainder from 1 .. 26
54
+ remainder = col % 26
55
+ remainder = 26 if remainder == 0
56
+
57
+ # Convert the remainder to a character. C-ishly.
58
+ col_letter = ("A".ord + remainder - 1).chr
59
+
60
+ # Accumulate the column letters, right to left.
61
+ col_str = col_letter + col_str
62
+
63
+ # Get the next order of magnitude.
64
+ col = (col - 1) / 26
65
+ end
66
+
67
+ "#{absolute_char(col_absolute)}#{col_str}"
68
+ end
69
+
70
+ def xl_range(row_1, row_2, col_1, col_2,
71
+ row_abs_1 = false, row_abs_2 = false, col_abs_1 = false, col_abs_2 = false)
72
+ range1 = xl_rowcol_to_cell(row_1, col_1, row_abs_1, col_abs_1)
73
+ range2 = xl_rowcol_to_cell(row_2, col_2, row_abs_2, col_abs_2)
74
+
75
+ "#{range1}:#{range2}"
76
+ end
77
+
78
+ def xl_range_formula(sheetname, row_1, row_2, col_1, col_2)
79
+ # Use Excel's conventions and quote the sheet name if it contains any
80
+ # non-word character or if it isn't already quoted.
81
+ sheetname = "'#{sheetname}'" if sheetname =~ /\W/ && !(sheetname =~ /^'/)
82
+
83
+ range1 = xl_rowcol_to_cell( row_1, col_1, 1, 1 )
84
+ range2 = xl_rowcol_to_cell( row_2, col_2, 1, 1 )
85
+
86
+ "=#{sheetname}!#{range1}:#{range2}"
87
+ end
88
+
89
+ def absolute_char(absolute)
90
+ absolute ? '$' : ''
91
+ end
92
+
93
+ def xml_str
94
+ @writer.string
95
+ end
96
+
97
+ def self.delete_files(path)
98
+ if FileTest.file?(path)
99
+ File.delete(path)
100
+ elsif FileTest.directory?(path)
101
+ Dir.foreach(path) do |file|
102
+ next if file =~ /^\.\.?$/ # '.' or '..'
103
+ delete_files(path.sub(/\/+$/,"") + '/' + file)
104
+ end
105
+ Dir.rmdir(path)
106
+ end
107
+ end
108
+
109
+ def put_deprecate_message(method)
110
+ $stderr.puts("Warning: calling deprecated method #{method}. This method will be removed in a future release.")
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,1488 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'write_xlsx/package/xml_writer_simple'
3
+ require 'write_xlsx/package/packager'
4
+ require 'write_xlsx/worksheet'
5
+ require 'write_xlsx/format'
6
+ require 'write_xlsx/utility'
7
+ require 'write_xlsx/chart'
8
+ require 'write_xlsx/zip_file_utils'
9
+ require 'tmpdir'
10
+ require 'tempfile'
11
+ require 'digest/md5'
12
+
13
+ module Writexlsx
14
+ class Workbook
15
+
16
+ include Writexlsx::Utility
17
+
18
+ attr_accessor :str_total, :str_unique
19
+ attr_writer :firstsheet
20
+ attr_reader :palette
21
+ attr_reader :font_count, :num_format_count, :border_count, :fill_count, :custom_colors
22
+ attr_reader :worksheets, :sheetnames, :charts, :drawings, :num_comment_files, :named_ranges
23
+ attr_reader :str_array, :doc_properties
24
+ attr_reader :image_types, :images
25
+
26
+ #
27
+ # A new Excel workbook is created using the new() constructor which accepts either a filename
28
+ # or a IO object as a parameter.
29
+ # The following example creates a new Excel file based on a filename:
30
+ #
31
+ # workbook = WriteXLSX.new('filename.xlsx')
32
+ # worksheet = workbook.add_worksheet
33
+ # worksheet.write(0, 0, 'Hi Excel!')
34
+ # workbook.close
35
+ #
36
+ # Here are some other examples of using new() with filenames:
37
+ #
38
+ # workbook1 = WriteXLSX.new(filename)
39
+ # workbook2 = WriteXLSX.new('/tmp/filename.xlsx')
40
+ # workbook3 = WriteXLSX.new("c:\\tmp\\filename.xlsx")
41
+ # workbook4 = WriteXLSX.new('c:\tmp\filename.xlsx')
42
+ #
43
+ # The last two examples demonstrates how to create a file on DOS or Windows where it is
44
+ # necessary to either escape the directory separator \ or to use single quotes to ensure
45
+ # that it isn't interpolated.
46
+ #
47
+ # It is recommended that the filename uses the extension .xlsx rather than .xls since
48
+ # the latter causes an Excel warning when used with the XLSX format.
49
+ #
50
+ # The new() constructor returns a WriteXLSX object that you can use to add worksheets and
51
+ # store data.
52
+ #
53
+ # You can also pass a valid IO object to the new() constructor.
54
+ #
55
+ # xlsx = StringIO.new
56
+ # workbook = WriteXLSX.new(xlsx)
57
+ # ....
58
+ # workbook.close
59
+ # # you can get XLSX binary data as xlsx.string
60
+ #
61
+ # And you can pass default_formats parameter like this:
62
+ #
63
+ # formats = {
64
+ # :font => 'Arial',
65
+ # :size => 10.5
66
+ # }
67
+ # workbook = WriteXLSX.new('file.xlsx', formats)
68
+ #
69
+ def initialize(file, default_formats = {})
70
+ @writer = Package::XMLWriterSimple.new
71
+
72
+ @tempdir = File.join(Dir.tmpdir, Digest::MD5.hexdigest(Time.now.to_s))
73
+ setup_filename(file)
74
+ @date_1904 = false
75
+ @activesheet = 0
76
+ @firstsheet = 0
77
+ @selected = 0
78
+ @fileclosed = false
79
+ @sheet_name = 'Sheet'
80
+ @chart_name = 'Chart'
81
+ @sheetname_count = 0
82
+ @chartname_count = 0
83
+ @worksheets = []
84
+ @charts = []
85
+ @drawings = []
86
+ @sheetnames = []
87
+ @formats = []
88
+ @xf_formats = []
89
+ @xf_format_indices = {}
90
+ @dxf_formats = []
91
+ @dxf_format_indices = {}
92
+ @font_count = 0
93
+ @num_format_count = 0
94
+ @defined_names = []
95
+ @named_ranges = []
96
+ @custom_colors = []
97
+ @doc_properties = {}
98
+ @local_time = Time.now
99
+ @num_comment_files = 0
100
+ @image_types = {}
101
+ @images = []
102
+
103
+ # Structures for the shared strings data.
104
+ @str_total = 0
105
+ @str_unique = 0
106
+ @str_table = {}
107
+ @str_array = []
108
+
109
+ add_format(default_formats.merge(:xf_index => 0))
110
+ set_color_palette
111
+ end
112
+
113
+ #
114
+ # write XLSX data to file or IO object.
115
+ #
116
+ def close
117
+ # In case close() is called twice, by user and by DESTROY.
118
+ return if @fileclosed
119
+
120
+ @fileclosed = 1
121
+ store_workbook
122
+ end
123
+
124
+ # get array of Worksheet objects
125
+ #
126
+ # :call-seq:
127
+ # sheets -> array of all Wordsheet object
128
+ # sheets(1, 3, 4) -> array of spcified Worksheet object.
129
+ #
130
+ # The sheets() method returns a array, or a sliced array, of the worksheets
131
+ # in a workbook.
132
+ #
133
+ # If no arguments are passed the method returns a list of all the worksheets
134
+ # in the workbook. This is useful if you want to repeat an operation on each
135
+ # worksheet:
136
+ #
137
+ # workbook.sheets.each do |worksheet|
138
+ # print worksheet.get_name
139
+ # end
140
+ #
141
+ # You can also specify a slice list to return one or more worksheet objects:
142
+ #
143
+ # worksheet = workbook.sheets(0)
144
+ # worksheet.write('A1', 'Hello')
145
+ #
146
+ # you can write the above example as:
147
+ #
148
+ # workbook.sheets(0).write('A1', 'Hello')
149
+ #
150
+ # The following example returns the first and last worksheet in a workbook:
151
+ #
152
+ # workbook.sheets(0, -1).each do |sheet|
153
+ # # Do something
154
+ # end
155
+ #
156
+ def sheets(*args)
157
+ if args.empty?
158
+ @worksheets
159
+ else
160
+ args.collect{|i| @worksheets[i] }
161
+ end
162
+ end
163
+
164
+ #
165
+ # Set the date system: false = 1900 (the default), true = 1904
166
+ #
167
+ # Excel stores dates as real numbers where the integer part stores
168
+ # the number of days since the epoch and the fractional part stores
169
+ # the percentage of the day. The epoch can be either 1900 or 1904.
170
+ # Excel for Windows uses 1900 and Excel for Macintosh uses 1904.
171
+ # However, Excel on either platform will convert automatically between
172
+ # one system and the other.
173
+ #
174
+ # WriteXLSX stores dates in the 1900 format by default. If you wish to
175
+ # change this you can call the set_1904() workbook method.
176
+ # You can query the current value by calling the get_1904() workbook method.
177
+ # This returns 0 for 1900 and 1 for 1904.
178
+ #
179
+ # In general you probably won't need to use set_1904().
180
+ #
181
+ def set_1904(mode = true)
182
+ unless sheets.empty?
183
+ raise "set_1904() must be called before add_worksheet()"
184
+ end
185
+ @date_1904 = (!mode || mode == 0) ? false : true
186
+ end
187
+
188
+ def get_1904
189
+ @date_1904
190
+ end
191
+
192
+ #
193
+ # user must not use. it is internal method.
194
+ #
195
+ def set_xml_writer(filename) #:nodoc:
196
+ @writer.set_xml_writer(filename)
197
+ end
198
+
199
+ #
200
+ # user must not use. it is internal method.
201
+ #
202
+ def xml_str #:nodoc:
203
+ @writer.string
204
+ end
205
+
206
+ #
207
+ # user must not use. it is internal method.
208
+ #
209
+ def assemble_xml_file #:nodoc:
210
+ return unless @writer
211
+
212
+ # Prepare format object for passing to Style.pm.
213
+ prepare_format_properties
214
+
215
+ write_xml_declaration
216
+
217
+ # Write the root workbook element.
218
+ write_workbook
219
+
220
+ # Write the XLSX file version.
221
+ write_file_version
222
+
223
+ # Write the workbook properties.
224
+ write_workbook_pr
225
+
226
+ # Write the workbook view properties.
227
+ write_book_views
228
+
229
+ # Write the worksheet names and ids.
230
+ write_sheets
231
+
232
+ # Write the workbook defined names.
233
+ write_defined_names
234
+
235
+ # Write the workbook calculation properties.
236
+ write_calc_pr
237
+
238
+ # Write the workbook extension storage.
239
+ #write_ext_lst
240
+
241
+ # Close the workbook tag.
242
+ write_workbook_end
243
+
244
+ # Close the XML writer object and filehandle.
245
+ @writer.crlf
246
+ @writer.close
247
+ end
248
+
249
+ #
250
+ # At least one worksheet should be added to a new workbook. A worksheet is used to write data into cells:
251
+ #
252
+ # worksheet1 = workbook.add_worksheet # Sheet1
253
+ # worksheet2 = workbook.add_worksheet('Foglio2') # Foglio2
254
+ # worksheet3 = workbook.add_worksheet('Data') # Data
255
+ # worksheet4 = workbook.add_worksheet # Sheet4
256
+ # If name is not specified the default Excel convention will be followed, i.e. Sheet1, Sheet2, etc.
257
+ #
258
+ # The worksheet name must be a valid Excel worksheet name,
259
+ # i.e. it cannot contain any of the following characters,
260
+ # [ ] : * ? / \
261
+ #
262
+ # and it must be less than 32 characters.
263
+ # In addition, you cannot use the same, case insensitive,
264
+ # sheetname for more than one worksheet.
265
+ #
266
+ def add_worksheet(name = '')
267
+ index = @worksheets.size
268
+ name = check_sheetname(name)
269
+
270
+ worksheet = Worksheet.new(self, index, name)
271
+ @worksheets[index] = worksheet
272
+ @sheetnames[index] = name
273
+
274
+ worksheet
275
+ end
276
+
277
+ #
278
+ # This method is use to create a new chart either as a standalone worksheet
279
+ # (the default) or as an embeddable object that can be inserted into
280
+ # a worksheet via the insert_chart() Worksheet method.
281
+ #
282
+ # chart = workbook.add_chart(:type => 'column')
283
+ #
284
+ # The properties that can be set are:
285
+ #
286
+ # :type (required)
287
+ # :subtype (optional)
288
+ # :name (optional)
289
+ # :embedded (optional)
290
+ #
291
+ # :type
292
+ #
293
+ # This is a required parameter. It defines the type of chart that will be created.
294
+ #
295
+ # chart = workbook.add_chart(:type => 'line')
296
+ #
297
+ # The available types are:
298
+ #
299
+ # area
300
+ # bar
301
+ # column
302
+ # line
303
+ # pie
304
+ # scatter
305
+ # stock
306
+ #
307
+ # :subtype
308
+ #
309
+ # Used to define a chart subtype where available.
310
+ #
311
+ # chart = workbook.add_chart(:type => 'bar', :subtype => 'stacked')
312
+ #
313
+ # Currently only Bar and Column charts support subtypes
314
+ # (stacked and percent_stacked). See the documentation for those chart
315
+ # types.
316
+ #
317
+ # :name
318
+ #
319
+ # Set the name for the chart sheet. The name property is optional and
320
+ # if it isn't supplied will default to Chart1 .. n. The name must be
321
+ # a valid Excel worksheet name. See add_worksheet for more details on
322
+ # valid sheet names. The name property can be omitted for embedded charts.
323
+ #
324
+ # chart = workbook.add_chart(:type => 'line', :name => 'Results Chart')
325
+ #
326
+ # :embedded
327
+ #
328
+ # Specifies that the Chart object will be inserted in a worksheet
329
+ # via the insert_chart Worksheet method. It is an error to try insert
330
+ # a Chart that doesn't have this flag set.
331
+ #
332
+ # chart = workbook.add_chart(:type => 'line', :embedded => 1)
333
+ #
334
+ # # Configure the chart.
335
+ # ...
336
+ #
337
+ # # Insert the chart into the a worksheet.
338
+ # worksheet.insert_chart('E2', chart)
339
+ #
340
+ # See Chart for details on how to configure the chart object
341
+ # once it is created. See also the chart_*.pl programs in the examples
342
+ # directory of the distro.
343
+ #
344
+ def add_chart(params = {})
345
+ name = ''
346
+ index = @worksheets.size
347
+
348
+ # Type must be specified so we can create the required chart instance.
349
+ type = params[:type]
350
+ raise "Must define chart type in add_chart()" unless type
351
+
352
+ # Ensure that the chart defaults to non embedded.
353
+ embedded = params[:embedded] || 0
354
+
355
+ # Check the worksheet name for non-embedded charts.
356
+ name = check_sheetname(params[:name], 1) unless embedded
357
+
358
+ chart = Chart.factory(type)
359
+
360
+ # Get an incremental id to use for axes ids.
361
+ chart.id = @charts.size
362
+
363
+ # If the chart isn't embedded let the workbook control it.
364
+ if embedded
365
+ # Set index to 0 so that the activate() and set_first_sheet() methods
366
+ # point back to the first worksheet if used for embedded charts.
367
+ chart.index = 0
368
+ chart.palette = @palette
369
+ chart.set_embedded_config_data
370
+ @charts << chart
371
+
372
+ return chart
373
+ else
374
+ chartsheet = Chartsheet.new(self, name, index)
375
+ chart.palette = @palette
376
+ chartsheet.chart = chart
377
+ chartsheet.drawing = Drawing.new
378
+ @worksheets.index = chartsheet
379
+ @sheetnames.index = name
380
+
381
+ @charts << chart
382
+
383
+ return chartsheet
384
+ end
385
+ end
386
+
387
+ #
388
+ # The add_format method can be used to create new Format objects
389
+ # which are used to apply formatting to a cell. You can either define
390
+ # the properties at creation time via a hash of property values
391
+ # or later via method calls.
392
+ #
393
+ # format1 = workbook.add_format(property_hash) # Set properties at creation
394
+ # format2 = workbook.add_format # Set properties later
395
+ #
396
+ # See the Format Class's rdoc for more details about Format properties and how to set them.
397
+ #
398
+ def add_format(properties = {})
399
+ init_data = [
400
+ @xf_format_indices,
401
+ @dxf_format_indices,
402
+ properties
403
+ ]
404
+
405
+ format = Format.new(*init_data)
406
+
407
+ @formats.push(format) # Store format reference
408
+
409
+ format
410
+ end
411
+
412
+ #
413
+ # Create a defined name in Excel. We handle global/workbook level names and
414
+ # local/worksheet names.
415
+ #
416
+ # This method is used to defined a name that can be used to represent a value,
417
+ # a single cell or a range of cells in a workbook.
418
+ #
419
+ # For example to set a global/workbook name:
420
+ #
421
+ # # Global/workbook names.
422
+ # workbook.define_name('Exchange_rate', '=0.96')
423
+ # workbook.define_name('Sales', '=Sheet1!$G$1:$H$10')
424
+ #
425
+ # It is also possible to define a local/worksheet name by prefixing the name
426
+ # with the sheet name using the syntax sheetname!definedname:
427
+ #
428
+ # # Local/worksheet name.
429
+ # workbook.define_name('Sheet2!Sales', '=Sheet2!$G$1:$G$10')
430
+ # If the sheet name contains spaces or special characters
431
+ # you must enclose it in single quotes like in Excel:
432
+ #
433
+ # workbook.define_name("'New Data'!Sales", '=Sheet2!$G$1:$G$10')
434
+ #
435
+ # See the defined_name.rb program in the examples dir of the distro.
436
+ #
437
+ def define_name(name, formula)
438
+ sheet_index = nil
439
+ sheetname = ''
440
+ full_name = name
441
+
442
+ # Remove the = sign from the formula if it exists.
443
+ formula.sub!(/^=/, '')
444
+
445
+ # Local defined names are formatted like "Sheet1!name".
446
+ if name =~ /^(.*)!(.*)$/
447
+ sheetname = $1
448
+ name = $2
449
+ sheet_index = get_sheet_index(sheetname)
450
+ else
451
+ sheet_index =-1 # Use -1 to indicate global names.
452
+ end
453
+
454
+ # Warn if the sheet index wasn't found.
455
+ if !sheet_index
456
+ raise "Unknown sheet name #{sheetname} in defined_name()\n"
457
+ return -1
458
+ end
459
+
460
+ # Warn if the sheet name contains invalid chars as defined by Excel help.
461
+ if name !~ %r!^[a-zA-Z_\\][a-zA-Z_.]+!
462
+ raise "Invalid characters in name '#{name}' used in defined_name()\n"
463
+ return -1
464
+ end
465
+
466
+ # Warn if the sheet name looks like a cell name.
467
+ if name =~ %r(^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$)
468
+ raise "Invalid name '#{name}' looks like a cell name in defined_name()\n"
469
+ return -1
470
+ end
471
+
472
+ @defined_names.push([ name, sheet_index, formula])
473
+ end
474
+
475
+ #
476
+ # The set_properties method can be used to set the document properties
477
+ # of the Excel file created by WriteXLSX. These properties are visible
478
+ # when you use the Office Button -> Prepare -> Properties option in Excel
479
+ # and are also available to external applications that read or index windows files.
480
+ #
481
+ # The properties should be passed in hash format as follows:
482
+ #
483
+ # workbook.set_properties(
484
+ # :title => 'This is an example spreadsheet',
485
+ # :author => 'Hideo NAKAMURA',
486
+ # :comments => 'Created with Ruby and WriteXLSX'
487
+ # )
488
+ #
489
+ # The properties that can be set are:
490
+ #
491
+ # :title
492
+ # :subject
493
+ # :author
494
+ # :manager
495
+ # :company
496
+ # :category
497
+ # :keywords
498
+ # :comments
499
+ # :status
500
+ #
501
+ # See also the properties.rb program in the examples directory of the distro.
502
+ #
503
+ def set_properties(params)
504
+ # Ignore if no args were passed.
505
+ return -1 if params.empty?
506
+
507
+ # List of valid input parameters.
508
+ valid = {
509
+ :title => 1,
510
+ :subject => 1,
511
+ :author => 1,
512
+ :keywords => 1,
513
+ :comments => 1,
514
+ :last_author => 1,
515
+ :created => 1,
516
+ :category => 1,
517
+ :manager => 1,
518
+ :company => 1,
519
+ :status => 1
520
+ }
521
+
522
+ # Check for valid input parameters.
523
+ params.each_key do |key|
524
+ return -1 unless valid.has_key?(key)
525
+ end
526
+
527
+ # Set the creation time unless specified by the user.
528
+ params[:created] = @local_time unless params.has_key?(:created)
529
+
530
+ @doc_properties = params.dup
531
+ end
532
+
533
+ #
534
+ # Change the RGB components of the elements in the colour palette.
535
+ #
536
+ # The set_custom_color() method can be used to override one of the built-in
537
+ # palette values with a more suitable colour.
538
+ #
539
+ # The value for _index_ should be in the range 8..63, see "COLOURS IN EXCEL".
540
+ #
541
+ # The default named colours use the following indices:
542
+ #
543
+ # 8 => black
544
+ # 9 => white
545
+ # 10 => red
546
+ # 11 => lime
547
+ # 12 => blue
548
+ # 13 => yellow
549
+ # 14 => magenta
550
+ # 15 => cyan
551
+ # 16 => brown
552
+ # 17 => green
553
+ # 18 => navy
554
+ # 20 => purple
555
+ # 22 => silver
556
+ # 23 => gray
557
+ # 33 => pink
558
+ # 53 => orange
559
+ #
560
+ # A new colour is set using its RGB (red green blue) components. The red,
561
+ # green and blue values must be in the range 0..255. You can determine the
562
+ # required values in Excel using the Tools->Options->Colors->Modify dialog.
563
+ #
564
+ # The set_custom_color() workbook method can also be used with a HTML style
565
+ # #rrggbb hex value:
566
+ #
567
+ # workbook.set_custom_color(40, 255, 102, 0 ) # Orange
568
+ # workbook.set_custom_color(40, 0xFF, 0x66, 0x00) # Same thing
569
+ # workbook.set_custom_color(40, '#FF6600' ) # Same thing
570
+ #
571
+ # font = workbook.add_format(:color => 40) # Use the modified colour
572
+ #
573
+ # The return value from set_custom_color() is the index of the colour that
574
+ # was changed:
575
+ #
576
+ # ferrari = workbook.set_custom_color(40, 216, 12, 12)
577
+ #
578
+ # format = workbook.add_format(
579
+ # :bg_color => ferrari,
580
+ # :pattern => 1,
581
+ # :border => 1
582
+ # )
583
+ #
584
+ # Note, In the XLSX format the color palette isn't actually confined to 53
585
+ # unique colors. The WriteXLSX gem will be extended at a later stage to
586
+ # support the newer, semi-infinite, palette.
587
+ #
588
+ def set_custom_color(index, red = 0, green = 0, blue = 0)
589
+ # Match a HTML #xxyyzz style parameter
590
+ if !red.nil? && red =~ /^#(\w\w)(\w\w)(\w\w)/
591
+ red = $1.hex
592
+ green = $2.hex
593
+ blue = $3.hex
594
+ end
595
+
596
+ # Check that the colour index is the right range
597
+ if index < 8 || index > 64
598
+ raise "Color index #{index} outside range: 8 <= index <= 64"
599
+ end
600
+
601
+ # Check that the colour components are in the right range
602
+ if (red < 0 || red > 255) ||
603
+ (green < 0 || green > 255) ||
604
+ (blue < 0 || blue > 255)
605
+ raise "Color component outside range: 0 <= color <= 255"
606
+ end
607
+
608
+ index -=8 # Adjust colour index (wingless dragonfly)
609
+
610
+ # Set the RGB value
611
+ @palette[index] = [red, green, blue]
612
+
613
+ # Store the custome colors for the style.xml file.
614
+ @custom_colors << sprintf("FF%02X%02X%02X", red, green, blue)
615
+
616
+ index + 8
617
+ end
618
+
619
+ def activesheet=(worksheet) #:nodoc:
620
+ @activesheet = worksheet
621
+ end
622
+
623
+ def writer #:nodoc:
624
+ @writer
625
+ end
626
+
627
+ def date_1904? #:nodoc:
628
+ @date_1904 ||= false
629
+ !!@date_1904
630
+ end
631
+
632
+ #
633
+ # Add a string to the shared string table, if it isn't already there, and
634
+ # return the string index.
635
+ #
636
+ def shared_string_index(str) #:nodoc:
637
+ # Add the string to the shared string table.
638
+ unless @str_table[str]
639
+ @str_table[str] = @str_unique
640
+ @str_unique += 1
641
+ end
642
+
643
+ @str_total += 1
644
+ @str_table[str]
645
+ end
646
+
647
+ def xf_formats # :nodoc:
648
+ @xf_formats.dup
649
+ end
650
+
651
+ def dxf_formats # :nodoc:
652
+ @dxf_formats.dup
653
+ end
654
+
655
+ private
656
+
657
+ def setup_filename(file) #:nodoc:
658
+ if file.respond_to?(:to_str) && file != ''
659
+ @filename = file
660
+ @fileobj = nil
661
+ elsif file.respond_to?(:write)
662
+ @filename = File.join(@tempdir, Digest::MD5.hexdigest(Time.now.to_s) + '.xlsx.tmp')
663
+ @fileobj = file
664
+ else
665
+ raise "'file' must be valid filename String of IO object."
666
+ end
667
+ end
668
+
669
+ #
670
+ # Sets the colour palette to the Excel defaults.
671
+ #
672
+ def set_color_palette #:nodoc:
673
+ @palette = [
674
+ [ 0x00, 0x00, 0x00, 0x00 ], # 8
675
+ [ 0xff, 0xff, 0xff, 0x00 ], # 9
676
+ [ 0xff, 0x00, 0x00, 0x00 ], # 10
677
+ [ 0x00, 0xff, 0x00, 0x00 ], # 11
678
+ [ 0x00, 0x00, 0xff, 0x00 ], # 12
679
+ [ 0xff, 0xff, 0x00, 0x00 ], # 13
680
+ [ 0xff, 0x00, 0xff, 0x00 ], # 14
681
+ [ 0x00, 0xff, 0xff, 0x00 ], # 15
682
+ [ 0x80, 0x00, 0x00, 0x00 ], # 16
683
+ [ 0x00, 0x80, 0x00, 0x00 ], # 17
684
+ [ 0x00, 0x00, 0x80, 0x00 ], # 18
685
+ [ 0x80, 0x80, 0x00, 0x00 ], # 19
686
+ [ 0x80, 0x00, 0x80, 0x00 ], # 20
687
+ [ 0x00, 0x80, 0x80, 0x00 ], # 21
688
+ [ 0xc0, 0xc0, 0xc0, 0x00 ], # 22
689
+ [ 0x80, 0x80, 0x80, 0x00 ], # 23
690
+ [ 0x99, 0x99, 0xff, 0x00 ], # 24
691
+ [ 0x99, 0x33, 0x66, 0x00 ], # 25
692
+ [ 0xff, 0xff, 0xcc, 0x00 ], # 26
693
+ [ 0xcc, 0xff, 0xff, 0x00 ], # 27
694
+ [ 0x66, 0x00, 0x66, 0x00 ], # 28
695
+ [ 0xff, 0x80, 0x80, 0x00 ], # 29
696
+ [ 0x00, 0x66, 0xcc, 0x00 ], # 30
697
+ [ 0xcc, 0xcc, 0xff, 0x00 ], # 31
698
+ [ 0x00, 0x00, 0x80, 0x00 ], # 32
699
+ [ 0xff, 0x00, 0xff, 0x00 ], # 33
700
+ [ 0xff, 0xff, 0x00, 0x00 ], # 34
701
+ [ 0x00, 0xff, 0xff, 0x00 ], # 35
702
+ [ 0x80, 0x00, 0x80, 0x00 ], # 36
703
+ [ 0x80, 0x00, 0x00, 0x00 ], # 37
704
+ [ 0x00, 0x80, 0x80, 0x00 ], # 38
705
+ [ 0x00, 0x00, 0xff, 0x00 ], # 39
706
+ [ 0x00, 0xcc, 0xff, 0x00 ], # 40
707
+ [ 0xcc, 0xff, 0xff, 0x00 ], # 41
708
+ [ 0xcc, 0xff, 0xcc, 0x00 ], # 42
709
+ [ 0xff, 0xff, 0x99, 0x00 ], # 43
710
+ [ 0x99, 0xcc, 0xff, 0x00 ], # 44
711
+ [ 0xff, 0x99, 0xcc, 0x00 ], # 45
712
+ [ 0xcc, 0x99, 0xff, 0x00 ], # 46
713
+ [ 0xff, 0xcc, 0x99, 0x00 ], # 47
714
+ [ 0x33, 0x66, 0xff, 0x00 ], # 48
715
+ [ 0x33, 0xcc, 0xcc, 0x00 ], # 49
716
+ [ 0x99, 0xcc, 0x00, 0x00 ], # 50
717
+ [ 0xff, 0xcc, 0x00, 0x00 ], # 51
718
+ [ 0xff, 0x99, 0x00, 0x00 ], # 52
719
+ [ 0xff, 0x66, 0x00, 0x00 ], # 53
720
+ [ 0x66, 0x66, 0x99, 0x00 ], # 54
721
+ [ 0x96, 0x96, 0x96, 0x00 ], # 55
722
+ [ 0x00, 0x33, 0x66, 0x00 ], # 56
723
+ [ 0x33, 0x99, 0x66, 0x00 ], # 57
724
+ [ 0x00, 0x33, 0x00, 0x00 ], # 58
725
+ [ 0x33, 0x33, 0x00, 0x00 ], # 59
726
+ [ 0x99, 0x33, 0x00, 0x00 ], # 60
727
+ [ 0x99, 0x33, 0x66, 0x00 ], # 61
728
+ [ 0x33, 0x33, 0x99, 0x00 ], # 62
729
+ [ 0x33, 0x33, 0x33, 0x00 ], # 63
730
+ ]
731
+ end
732
+
733
+ #
734
+ # Check for valid worksheet names. We check the length, if it contains any
735
+ # invalid characters and if the name is unique in the workbook.
736
+ #
737
+ def check_sheetname(name, chart = nil) #:nodoc:
738
+ name ||= ''
739
+ invalid_char = /[\[\]:*?\/\\]/
740
+
741
+ # Increment the Sheet/Chart number used for default sheet names below.
742
+ if chart
743
+ @chartname_count += 1
744
+ else
745
+ @sheetname_count += 1
746
+ end
747
+
748
+ # Supply default Sheet/Chart name if none has been defined.
749
+ if name == ''
750
+ if chart
751
+ name = "#{@chart_name}#{@chartname_count}"
752
+ else
753
+ name = "#{@sheet_name}#{@sheetname_count}"
754
+ end
755
+ end
756
+
757
+ # Check that sheet name is <= 31. Excel limit.
758
+ raise "Sheetname #{name} must be <= 31 chars" if name.bytesize > 31
759
+
760
+ # Check that sheetname doesn't contain any invalid characters
761
+ if name =~ invalid_char
762
+ raise 'Invalid character []:*?/\\ in worksheet name: ' + name
763
+ end
764
+
765
+ # Check that the worksheet name doesn't already exist since this is a fatal
766
+ # error in Excel 97. The check must also exclude case insensitive matches.
767
+ @worksheets.each do |worksheet|
768
+ name_a = name
769
+ name_b = worksheet.name
770
+
771
+ if name_a.downcase == name_b.downcase
772
+ raise "Worksheet name '#{name}', with case ignored, is already used."
773
+ end
774
+ end
775
+
776
+ name
777
+ end
778
+
779
+ #
780
+ # Convert a range formula such as Sheet1!$B$1:$B$5 into a sheet name and cell
781
+ # range such as ( 'Sheet1', 0, 1, 4, 1 ).
782
+ #
783
+ def get_chart_range(range) #:nodoc:
784
+ # Split the range formula into sheetname and cells at the last '!'.
785
+ pos = range.rindex('!')
786
+ return nil unless pos
787
+
788
+ if pos > 0
789
+ sheetname = range[0, pos]
790
+ cells = range[pos + 1 .. -1]
791
+ end
792
+
793
+ # Split the cell range into 2 cells or else use single cell for both.
794
+ if cells =~ /:/
795
+ cell_1, cell_2 = cells.split(/:/)
796
+ else
797
+ cell_1, cell_2 = cells, cells
798
+ end
799
+
800
+ # Remove leading/trailing apostrophes and convert escaped quotes to single.
801
+ sheetname.sub!(/^'/, '')
802
+ sheetname.sub!(/'$/, '')
803
+ sheetname.gsub!(/''/, "'")
804
+
805
+ row_start, col_start = xl_cell_to_rowcol(cell_1)
806
+ row_end, col_end = xl_cell_to_rowcol(cell_2)
807
+
808
+ # Check that we have a 1D range only.
809
+ return nil if row_start != row_end && col_start != col_end
810
+ return [sheetname, row_start, col_start, row_end, col_end]
811
+ end
812
+
813
+ def write_xml_declaration #:nodoc:
814
+ @writer.xml_decl
815
+ end
816
+
817
+ def write_workbook #:nodoc:
818
+ schema = 'http://schemas.openxmlformats.org'
819
+ attributes = [
820
+ 'xmlns',
821
+ schema + '/spreadsheetml/2006/main',
822
+ 'xmlns:r',
823
+ schema + '/officeDocument/2006/relationships'
824
+ ]
825
+ @writer.start_tag('workbook', attributes)
826
+ end
827
+
828
+ def write_workbook_end #:nodoc:
829
+ @writer.end_tag('workbook')
830
+ end
831
+
832
+ def write_file_version #:nodoc:
833
+ attributes = [
834
+ 'appName', 'xl',
835
+ 'lastEdited', 4,
836
+ 'lowestEdited', 4,
837
+ 'rupBuild', 4505
838
+ ]
839
+ @writer.empty_tag('fileVersion', attributes)
840
+ end
841
+
842
+ def write_workbook_pr #:nodoc:
843
+ attributes = date_1904? ? ['date1904', 1] : []
844
+ attributes << 'defaultThemeVersion' << 124226
845
+ @writer.empty_tag('workbookPr', attributes)
846
+ end
847
+
848
+ def write_book_views #:nodoc:
849
+ @writer.start_tag('bookViews') << write_workbook_view << @writer.end_tag('bookViews')
850
+ end
851
+
852
+ def write_workbook_view #:nodoc:
853
+ attributes = [
854
+ 'xWindow', 240,
855
+ 'yWindow', 15,
856
+ 'windowWidth', 16095,
857
+ 'windowHeight', 9660
858
+ ]
859
+ if @firstsheet > 0
860
+ attributes << 'firstSheet' << @firstsheet
861
+ end
862
+ if @activesheet > 0
863
+ attributes << 'activeTab' << @activesheet
864
+ end
865
+ @writer.empty_tag('workbookView', attributes)
866
+ end
867
+
868
+ def write_sheets #:nodoc:
869
+ str = @writer.start_tag('sheets')
870
+ id_num = 1
871
+ @worksheets.each do |sheet|
872
+ str << write_sheet(sheet.name, id_num, sheet.hidden)
873
+ id_num += 1
874
+ end
875
+ str << @writer.end_tag('sheets')
876
+ end
877
+
878
+ def write_sheet(name, sheet_id, hidden = false) #:nodoc:
879
+ attributes = [
880
+ 'name', name,
881
+ 'sheetId', sheet_id
882
+ ]
883
+
884
+ if hidden
885
+ attributes << 'state' << 'hidden'
886
+ end
887
+ attributes << 'r:id' << "rId#{sheet_id}"
888
+ @writer.empty_tag('sheet', attributes)
889
+ end
890
+
891
+ def write_calc_pr #:nodoc:
892
+ attributes = ['calcId', 124519]
893
+ @writer.empty_tag('calcPr', attributes)
894
+ end
895
+
896
+ def write_ext_lst #:nodoc:
897
+ tag = 'extLst'
898
+ @writer.start_tag(tag) << write_ext << @writer.end_tag(tag)
899
+ end
900
+
901
+ def write_ext #:nodoc:
902
+ tag = 'ext'
903
+ attributes = [
904
+ 'xmlns:mx', 'http://schemas.microsoft.com/office/mac/excel/2008/main',
905
+ 'uri', 'http://schemas.microsoft.com/office/mac/excel/2008/main'
906
+ ]
907
+ @writer.start_tag(tag, attributes) << write_mx_arch_id << @writer.end_tag(tag)
908
+ end
909
+
910
+ def write_mx_arch_id #:nodoc:
911
+ @writer.empty_tag('mx:ArchID', ['Flags', 2])
912
+ end
913
+
914
+ def write_defined_names #:nodoc:
915
+ return if @defined_names.nil? || @defined_names.empty?
916
+ tag = 'definedNames'
917
+ str = @writer.start_tag(tag)
918
+ @defined_names.each { |defined_name| str << write_defined_name(defined_name) }
919
+ str << @writer.end_tag(tag)
920
+ end
921
+
922
+ def write_defined_name(data) #:nodoc:
923
+ name, id, range, hidden = data
924
+
925
+ attributes = ['name', name]
926
+ attributes << 'localSheetId' << "#{id}" unless id == -1
927
+ attributes << 'hidden' << '1' if hidden
928
+
929
+ @writer.data_element('definedName', range, attributes)
930
+ end
931
+
932
+ def write_io(str) #:nodoc:
933
+ @writer << str
934
+ str
935
+ end
936
+
937
+ def firstsheet #:nodoc:
938
+ @firstsheet ||= 0
939
+ end
940
+
941
+ def activesheet #:nodoc:
942
+ @activesheet ||= 0
943
+ end
944
+
945
+ # for test
946
+ def defined_names #:nodoc:
947
+ @defined_names ||= []
948
+ end
949
+
950
+ #
951
+ # Assemble worksheets into a workbook.
952
+ #
953
+ def store_workbook #:nodoc:
954
+ packager = Package::Packager.new
955
+
956
+ # Add a default worksheet if non have been added.
957
+ add_worksheet if @worksheets.empty?
958
+
959
+ # Ensure that at least one worksheet has been selected.
960
+ @worksheets.first.select if @activesheet == 0
961
+
962
+ # Set the active sheet.
963
+ @worksheets.each { |sheet| sheet.activate if sheet.index == @activesheet }
964
+
965
+ # Convert the SST strings data structure.
966
+ prepare_sst_string_data
967
+
968
+ # Prepare the worksheet cell comments.
969
+ prepare_comments
970
+
971
+ # Set the defined names for the worksheets such as Print Titles.
972
+ prepare_defined_names
973
+
974
+ # Prepare the drawings, charts and images.
975
+ prepare_drawings
976
+
977
+ # Add cached data to charts.
978
+ add_chart_data
979
+
980
+ # Package the workbook.
981
+ packager.add_workbook(self)
982
+ packager.set_package_dir(@tempdir)
983
+ packager.create_package
984
+
985
+ # Free up the Packager object.
986
+ packager = nil
987
+
988
+ # Store the xlsx component files with the temp dir name removed.
989
+ ZipFileUtils.zip("#{@tempdir}", @filename)
990
+ IO.copy_stream(@filename, @fileobj) if @fileobj
991
+ Writexlsx::Utility.delete_files(@tempdir)
992
+ end
993
+
994
+ #
995
+ # Convert the SST string data from a hash to an array.
996
+ #
997
+ def prepare_sst_string_data #:nodoc:
998
+ strings = []
999
+
1000
+ @str_table.each_key { |key| strings[@str_table[key]] = key }
1001
+
1002
+ # The SST data could be very large, free some memory (maybe).
1003
+ @str_table = nil
1004
+ @str_array = strings
1005
+ end
1006
+
1007
+ #
1008
+ # Prepare all of the format properties prior to passing them to Styles.pm.
1009
+ #
1010
+ def prepare_format_properties #:nodoc:
1011
+ # Separate format objects into XF and DXF formats.
1012
+ prepare_formats
1013
+
1014
+ # Set the font index for the format objects.
1015
+ prepare_fonts
1016
+
1017
+ # Set the number format index for the format objects.
1018
+ prepare_num_formats
1019
+
1020
+ # Set the border index for the format objects.
1021
+ prepare_borders
1022
+
1023
+ # Set the fill index for the format objects.
1024
+ prepare_fills
1025
+ end
1026
+
1027
+ #
1028
+ # Iterate through the XF Format objects and separate them into XF and DXF
1029
+ # formats.
1030
+ #
1031
+ def prepare_formats #:nodoc:
1032
+ @formats.each do |format|
1033
+ xf_index = format.xf_index
1034
+ dxf_index = format.dxf_index
1035
+
1036
+ @xf_formats[xf_index] = format if xf_index
1037
+ @dxf_formats[dxf_index] = format if dxf_index
1038
+ end
1039
+ end
1040
+
1041
+ #
1042
+ # Set the default index for each format. This is mainly used for testing.
1043
+ #
1044
+ def set_default_xf_indices #:nodoc:
1045
+ @formats.each { |format| format.get_xf_index }
1046
+ end
1047
+
1048
+ #
1049
+ # Iterate through the XF Format objects and give them an index to non-default
1050
+ # font elements.
1051
+ #
1052
+ def prepare_fonts #:nodoc:
1053
+ fonts = {}
1054
+ index = 0
1055
+
1056
+ @xf_formats.each do |format|
1057
+ key = format.get_font_key
1058
+
1059
+ if fonts[key]
1060
+ # Font has already been used.
1061
+ format.font_index = fonts[key]
1062
+ format.has_font = 0
1063
+ else
1064
+ # This is a new font.
1065
+ fonts[key] = index
1066
+ format.font_index = index
1067
+ format.has_font = 1
1068
+ index += 1
1069
+ end
1070
+ end
1071
+
1072
+ @font_count = index
1073
+
1074
+ # For the DXF formats we only need to check if the properties have changed.
1075
+ @dxf_formats.each do |format|
1076
+ # The only font properties that can change for a DXF format are: color,
1077
+ # bold, italic, underline and strikethrough.
1078
+ if format.color || format.bold? || format.italic? || format.underline? || format.strikeout?
1079
+ format.has_dxf_font = 1
1080
+ end
1081
+ end
1082
+ end
1083
+
1084
+ #
1085
+ # Iterate through the XF Format objects and give them an index to non-default
1086
+ # number format elements.
1087
+ #
1088
+ # User defined records start from index 0xA4.
1089
+ #
1090
+ def prepare_num_formats #:nodoc:
1091
+ num_formats = {}
1092
+ index = 164
1093
+ num_format_count = 0
1094
+
1095
+ (@xf_formats + @dxf_formats).each do |format|
1096
+ num_format = format.num_format
1097
+
1098
+ # Check if num_format is an index to a built-in number format.
1099
+ # Also check for a string of zeros, which is a valid number format
1100
+ # string but would evaluate to zero.
1101
+ #
1102
+ if num_format.to_s =~ /^\d+$/ && num_format.to_s !~ /^0+\d/
1103
+ # Index to a built-in number format.
1104
+ format.num_format_index = num_format
1105
+ next
1106
+ end
1107
+
1108
+ if num_formats[num_format]
1109
+ # Number format has already been used.
1110
+ format.num_format_index = num_formats[num_format]
1111
+ else
1112
+ # Add a new number format.
1113
+ num_formats[num_format] = index
1114
+ format.num_format_index = index
1115
+ index += 1
1116
+
1117
+ # Only increase font count for XF formats (not for DXF formats).
1118
+ num_format_count += 1 unless format.xf_index == 0
1119
+ end
1120
+ end
1121
+
1122
+ @num_format_count = num_format_count
1123
+ end
1124
+
1125
+ #
1126
+ # Iterate through the XF Format objects and give them an index to non-default
1127
+ # border elements.
1128
+ #
1129
+ def prepare_borders #:nodoc:
1130
+ borders = {}
1131
+ index = 0
1132
+
1133
+ @xf_formats.each do |format|
1134
+ key = format.get_border_key
1135
+
1136
+ if borders[key]
1137
+ # Border has already been used.
1138
+ format.border_index = borders[key]
1139
+ format.has_border = 0
1140
+ else
1141
+ # This is a new border.
1142
+ borders[key] = index
1143
+ format.border_index = index
1144
+ format.has_border = 1
1145
+ index += 1
1146
+ end
1147
+ end
1148
+
1149
+ @border_count = index
1150
+
1151
+ # For the DXF formats we only need to check if the properties have changed.
1152
+ @dxf_formats.each do |format|
1153
+ key = format.get_border_key
1154
+ format.has_dxf_border = 1 if key =~ /[^0:]/
1155
+ end
1156
+ end
1157
+
1158
+ #
1159
+ # Iterate through the XF Format objects and give them an index to non-default
1160
+ # fill elements.
1161
+ #
1162
+ # The user defined fill properties start from 2 since there are 2 default
1163
+ # fills: patternType="none" and patternType="gray125".
1164
+ #
1165
+ def prepare_fills #:nodoc:
1166
+ fills = {}
1167
+ index = 2 # Start from 2. See above.
1168
+
1169
+ # Add the default fills.
1170
+ fills['0:0:0'] = 0
1171
+ fills['17:0:0'] = 1
1172
+
1173
+ @xf_formats.each do |format|
1174
+ # The following logical statements jointly take care of special cases
1175
+ # in relation to cell colours and patterns:
1176
+ # 1. For a solid fill (_pattern == 1) Excel reverses the role of
1177
+ # foreground and background colours, and
1178
+ # 2. If the user specifies a foreground or background colour without
1179
+ # a pattern they probably wanted a solid fill, so we fill in the
1180
+ # defaults.
1181
+ #
1182
+ if format.pattern <= 1 && format.bg_color != 0 && format.fg_color == 0
1183
+ format.fg_color = format.bg_color
1184
+ format.bg_color = 0
1185
+ format.pattern = 1
1186
+ end
1187
+
1188
+ if format.pattern <= 1 && format.bg_color == 0 && format.fg_color != 0
1189
+ format.bg_color = 0
1190
+ format.pattern = 1
1191
+ end
1192
+
1193
+ key = format.get_fill_key
1194
+
1195
+ if fills[key]
1196
+ # Fill has already been used.
1197
+ format.fill_index = fills[key]
1198
+ format.has_fill = 0
1199
+ else
1200
+ # This is a new fill.
1201
+ fills[key] = index
1202
+ format.fill_index = index
1203
+ format.has_fill = 1
1204
+ index += 1
1205
+ end
1206
+ end
1207
+
1208
+ @fill_count = index
1209
+
1210
+ # For the DXF formats we only need to check if the properties have changed.
1211
+ @dxf_formats.each do |format|
1212
+ format.has_dxf_fill = 1 if format.pattern || format.bg_color || format.fg_color
1213
+ end
1214
+ end
1215
+
1216
+ #
1217
+ # Iterate through the worksheets and store any defined names in addition to
1218
+ # any user defined names. Stores the defined names for the Workbook.xml and
1219
+ # the named ranges for App.xml.
1220
+ #
1221
+ def prepare_defined_names #:nodoc:
1222
+ defined_names = @defined_names
1223
+
1224
+ @worksheets.each do |sheet|
1225
+ # Check for Print Area settings.
1226
+ if sheet.autofilter_area
1227
+ range = sheet.autofilter_area
1228
+ hidden = 1
1229
+
1230
+ # Store the defined names.
1231
+ defined_names << ['_xlnm._FilterDatabase', sheet.index, range, hidden]
1232
+ end
1233
+
1234
+ # Check for Print Area settings.
1235
+ if !sheet.print_area.empty?
1236
+ range = sheet.print_area
1237
+
1238
+ # Store the defined names.
1239
+ defined_names << ['_xlnm.Print_Area', sheet.index, range]
1240
+ end
1241
+
1242
+ # Check for repeat rows/cols. aka, Print Titles.
1243
+ if !sheet.print_repeat_cols.empty? || !sheet.print_repeat_rows.empty?
1244
+ range = ''
1245
+
1246
+ if !sheet.print_repeat_cols.empty? && !sheet.print_repeat_rows.empty?
1247
+ range = sheet.print_repeat_cols + ',' + sheet.print_repeat_rows
1248
+ else
1249
+ range = sheet.print_repeat_cols + sheet.print_repeat_rows
1250
+ end
1251
+
1252
+ # Store the defined names.
1253
+ defined_names << ['_xlnm.Print_Titles', sheet.index, range]
1254
+ end
1255
+ end
1256
+
1257
+ defined_names = sort_defined_names(defined_names)
1258
+ @defined_names = defined_names
1259
+ @named_ranges = extract_named_ranges(defined_names)
1260
+ end
1261
+
1262
+ #
1263
+ # Iterate through the worksheets and set up the comment data.
1264
+ #
1265
+ def prepare_comments #:nodoc:
1266
+ comment_id = 0
1267
+ vml_data_id = 1
1268
+ vml_shape_id = 1024
1269
+
1270
+ @worksheets.each do |sheet|
1271
+ next unless sheet.has_comments?
1272
+
1273
+ comment_id += 1
1274
+ count = sheet.prepare_comments( vml_data_id, vml_shape_id, comment_id)
1275
+
1276
+ # Each VML file should start with a shape id incremented by 1024.
1277
+ vml_data_id += 1 * ( ( 1024 + count ) / 1024.0 ).to_i
1278
+ vml_shape_id += 1024 * ( ( 1024 + count ) / 1024.0 ).to_i
1279
+ end
1280
+
1281
+ @num_comment_files = comment_id
1282
+
1283
+ # Add a font format for cell comments.
1284
+ if comment_id > 0
1285
+ format = Format.new(
1286
+ @xf_format_indices,
1287
+ @dxf_format_indices,
1288
+ :font => 'Tahoma',
1289
+ :size => 8,
1290
+ :color_indexed => 81,
1291
+ :font_only => 1
1292
+ )
1293
+
1294
+ format.get_xf_index
1295
+
1296
+ @formats << format
1297
+ end
1298
+ end
1299
+
1300
+ #
1301
+ # Add "cached" data to charts to provide the numCache and strCache data for
1302
+ # series and title/axis ranges.
1303
+ #
1304
+ def add_chart_data #:nodoc:
1305
+ worksheets = {}
1306
+ seen_ranges = {}
1307
+
1308
+ # Map worksheet names to worksheet objects.
1309
+ @worksheets.each { |worksheet| worksheets[worksheet.name] = worksheet }
1310
+
1311
+ @charts.each do |chart|
1312
+ chart.formula_ids.each do |range, id|
1313
+ # Skip if the series has user defined data.
1314
+ if chart.formula_data[id]
1315
+ if !seen_ranges.has_key?(range) || seen_ranges[range]
1316
+ data = chart.formula_data[id]
1317
+ seen_ranges[range] = data
1318
+ end
1319
+ next
1320
+ end
1321
+
1322
+ # Check to see if the data is already cached locally.
1323
+ if seen_ranges.has_key?(range)
1324
+ chart.formula_data[id] = seen_ranges[range]
1325
+ next
1326
+ end
1327
+
1328
+ # Convert the range formula to a sheet name and cell range.
1329
+ sheetname, *cells = get_chart_range(range)
1330
+
1331
+ # Skip if we couldn't parse the formula.
1332
+ next unless sheetname
1333
+
1334
+ # Skip if the name is unknown. Probably should throw exception.
1335
+ next unless worksheets[sheetname]
1336
+
1337
+ # Find the worksheet object based on the sheet name.
1338
+ worksheet = worksheets[sheetname]
1339
+
1340
+ # Get the data from the worksheet table.
1341
+ data = worksheet.get_range_data(*cells)
1342
+
1343
+ # Convert shared string indexes to strings.
1344
+ data.collect! do |token|
1345
+ if token.kind_of?(Hash)
1346
+ token = @str_array[token[:sst_id]]
1347
+
1348
+ # Ignore rich strings for now. Deparse later if necessary.
1349
+ token = '' if token =~ %r!^<r>! && token =~ %r!</r>$!
1350
+ end
1351
+ token
1352
+ end
1353
+
1354
+ # Add the data to the chart.
1355
+ chart.formula_data[id] = data
1356
+
1357
+ # Store range data locally to avoid lookup if seen again.
1358
+ seen_ranges[range] = data
1359
+ end
1360
+ end
1361
+ end
1362
+
1363
+ #
1364
+ # Sort internal and user defined names in the same order as used by Excel.
1365
+ # This may not be strictly necessary but unsorted elements caused a lot of
1366
+ # issues in the the Spreadsheet::WriteExcel binary version. Also makes
1367
+ # comparison testing easier.
1368
+ #
1369
+ def sort_defined_names(names) #:nodoc:
1370
+ names.sort do |a, b|
1371
+ name_a = normalise_defined_name(a[0])
1372
+ name_b = normalise_defined_name(b[0])
1373
+ sheet_a = normalise_sheet_name(a[2])
1374
+ sheet_b = normalise_sheet_name(b[2])
1375
+ # Primary sort based on the defined name.
1376
+ if name_a > name_b
1377
+ 1
1378
+ elsif name_a < name_b
1379
+ -1
1380
+ else # name_a == name_b
1381
+ # Secondary sort based on the sheet name.
1382
+ if sheet_a >= sheet_b
1383
+ 1
1384
+ else
1385
+ -1
1386
+ end
1387
+ end
1388
+ end
1389
+ end
1390
+
1391
+ # Used in the above sort routine to normalise the defined names. Removes any
1392
+ # leading '_xmln.' from internal names and lowercases the strings.
1393
+ def normalise_defined_name(name) #:nodoc:
1394
+ name.sub(/^_xlnm./, '').downcase
1395
+ end
1396
+
1397
+ # Used in the above sort routine to normalise the worksheet names for the
1398
+ # secondary sort. Removes leading quote and lowercases the strings.
1399
+ def normalise_sheet_name(name) #:nodoc:
1400
+ name.sub(/^'/, '').downcase
1401
+ end
1402
+
1403
+ #
1404
+ # Extract the named ranges from the sorted list of defined names. These are
1405
+ # used in the App.xml file.
1406
+ #
1407
+ def extract_named_ranges(defined_names) #:nodoc:
1408
+ named_ranges = []
1409
+
1410
+ defined_names.each do |defined_name|
1411
+ name, index, range = defined_name
1412
+
1413
+ # Skip autoFilter ranges.
1414
+ next if name == '_xlnm._FilterDatabase'
1415
+
1416
+ # We are only interested in defined names with ranges.
1417
+ if range =~ /^([^!]+)!/
1418
+ sheet_name = $1
1419
+
1420
+ # Match Print_Area and Print_Titles xlnm types.
1421
+ if name =~ /^_xlnm\.(.*)$/
1422
+ xlnm_type = $1
1423
+ name = sheet_name + '!' + xlnm_type
1424
+ elsif index != -1
1425
+ name = sheet_name + '!' + name
1426
+ end
1427
+
1428
+ named_ranges << name
1429
+ end
1430
+ end
1431
+
1432
+ named_ranges
1433
+ end
1434
+
1435
+ #
1436
+ # Iterate through the worksheets and set up any chart or image drawings.
1437
+ #
1438
+ def prepare_drawings #:nodoc:
1439
+ chart_ref_id = 0
1440
+ image_ref_id = 0
1441
+ drawing_id = 0
1442
+ @worksheets.each do |sheet|
1443
+ chart_count = sheet.charts.size
1444
+ image_count = sheet.images.size
1445
+ next if chart_count + image_count == 0
1446
+
1447
+ drawing_id += 1
1448
+
1449
+ (0 .. chart_count - 1).each do |index|
1450
+ chart_ref_id += 1
1451
+ sheet.prepare_chart(index, chart_ref_id, drawing_id)
1452
+ end
1453
+
1454
+ (0 .. image_count - 1).each do |index|
1455
+ filename = sheet.images[index][2]
1456
+
1457
+ image_id, type, width, height, name = get_image_properties(filename)
1458
+
1459
+ image_ref_id += 1
1460
+
1461
+ sheet.prepare_image(index, image_ref_id, drawing_id, width, height, name, type)
1462
+ end
1463
+
1464
+ drawing = sheet.drawing
1465
+ @drawings << drawing
1466
+ end
1467
+
1468
+ @drawing_count = drawing_id
1469
+ end
1470
+
1471
+ #
1472
+ # Convert a sheet name to its index. Return undef otherwise.
1473
+ #
1474
+ def get_sheet_index(sheetname) #:nodoc:
1475
+ sheet_count = @sheetnames.size
1476
+ sheet_index = nil
1477
+
1478
+ sheetname.sub!(/^'/, '')
1479
+ sheetname.sub!(/'$/, '')
1480
+
1481
+ ( 0 .. sheet_count - 1 ).each do |i|
1482
+ sheet_index = i if sheetname == @sheetnames[i]
1483
+ end
1484
+
1485
+ sheet_index
1486
+ end
1487
+ end
1488
+ end