write_xlsx 0.0.2

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