ricardoo27-writeexcel 0.6.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (245) hide show
  1. data/.document +5 -0
  2. data/.gitattributes +1 -0
  3. data/README.rdoc +136 -0
  4. data/Rakefile +52 -0
  5. data/VERSION +1 -0
  6. data/charts/chartex.rb +316 -0
  7. data/charts/demo1.rb +46 -0
  8. data/charts/demo101.bin +0 -0
  9. data/charts/demo2.rb +65 -0
  10. data/charts/demo201.bin +0 -0
  11. data/charts/demo3.rb +117 -0
  12. data/charts/demo301.bin +0 -0
  13. data/charts/demo4.rb +119 -0
  14. data/charts/demo401.bin +0 -0
  15. data/charts/demo5.rb +48 -0
  16. data/charts/demo501.bin +0 -0
  17. data/examples/a_simple.rb +43 -0
  18. data/examples/autofilter.rb +265 -0
  19. data/examples/bigfile.rb +30 -0
  20. data/examples/chart_area.rb +121 -0
  21. data/examples/chart_bar.rb +120 -0
  22. data/examples/chart_column.rb +120 -0
  23. data/examples/chart_line.rb +120 -0
  24. data/examples/chart_pie.rb +108 -0
  25. data/examples/chart_scatter.rb +121 -0
  26. data/examples/chart_stock.rb +148 -0
  27. data/examples/chess.rb +142 -0
  28. data/examples/colors.rb +129 -0
  29. data/examples/comments1.rb +27 -0
  30. data/examples/comments2.rb +352 -0
  31. data/examples/copyformat.rb +52 -0
  32. data/examples/data_validate.rb +279 -0
  33. data/examples/date_time.rb +87 -0
  34. data/examples/defined_name.rb +32 -0
  35. data/examples/demo.rb +124 -0
  36. data/examples/diag_border.rb +36 -0
  37. data/examples/formats.rb +490 -0
  38. data/examples/formula_result.rb +30 -0
  39. data/examples/header.rb +137 -0
  40. data/examples/hide_sheet.rb +29 -0
  41. data/examples/hyperlink.rb +43 -0
  42. data/examples/images.rb +63 -0
  43. data/examples/indent.rb +31 -0
  44. data/examples/merge1.rb +40 -0
  45. data/examples/merge2.rb +45 -0
  46. data/examples/merge3.rb +66 -0
  47. data/examples/merge4.rb +83 -0
  48. data/examples/merge5.rb +80 -0
  49. data/examples/merge6.rb +67 -0
  50. data/examples/outline.rb +255 -0
  51. data/examples/outline_collapsed.rb +209 -0
  52. data/examples/panes.rb +113 -0
  53. data/examples/password_protection.rb +33 -0
  54. data/examples/properties.rb +34 -0
  55. data/examples/properties_jp.rb +33 -0
  56. data/examples/protection.rb +47 -0
  57. data/examples/regions.rb +53 -0
  58. data/examples/repeat.rb +43 -0
  59. data/examples/republic.png +0 -0
  60. data/examples/right_to_left.rb +27 -0
  61. data/examples/row_wrap.rb +53 -0
  62. data/examples/set_first_sheet.rb +14 -0
  63. data/examples/stats.rb +74 -0
  64. data/examples/stocks.rb +81 -0
  65. data/examples/store_formula.rb +15 -0
  66. data/examples/tab_colors.rb +31 -0
  67. data/examples/utf8.rb +15 -0
  68. data/examples/write_arrays.rb +83 -0
  69. data/html/en/doc_en.html +5946 -0
  70. data/html/images/a_simple.jpg +0 -0
  71. data/html/images/area1.jpg +0 -0
  72. data/html/images/bar1.jpg +0 -0
  73. data/html/images/chart_area.xls +0 -0
  74. data/html/images/column1.jpg +0 -0
  75. data/html/images/data_validation.jpg +0 -0
  76. data/html/images/line1.jpg +0 -0
  77. data/html/images/pie1.jpg +0 -0
  78. data/html/images/regions.jpg +0 -0
  79. data/html/images/scatter1.jpg +0 -0
  80. data/html/images/stats.jpg +0 -0
  81. data/html/images/stock1.jpg +0 -0
  82. data/html/images/stocks.jpg +0 -0
  83. data/html/index.html +16 -0
  84. data/html/style.css +433 -0
  85. data/lib/writeexcel.rb +1159 -0
  86. data/lib/writeexcel/biffwriter.rb +223 -0
  87. data/lib/writeexcel/caller_info.rb +12 -0
  88. data/lib/writeexcel/cell_range.rb +332 -0
  89. data/lib/writeexcel/chart.rb +1968 -0
  90. data/lib/writeexcel/charts/area.rb +154 -0
  91. data/lib/writeexcel/charts/bar.rb +177 -0
  92. data/lib/writeexcel/charts/column.rb +156 -0
  93. data/lib/writeexcel/charts/external.rb +66 -0
  94. data/lib/writeexcel/charts/line.rb +154 -0
  95. data/lib/writeexcel/charts/pie.rb +169 -0
  96. data/lib/writeexcel/charts/scatter.rb +192 -0
  97. data/lib/writeexcel/charts/stock.rb +213 -0
  98. data/lib/writeexcel/col_info.rb +87 -0
  99. data/lib/writeexcel/colors.rb +68 -0
  100. data/lib/writeexcel/comments.rb +460 -0
  101. data/lib/writeexcel/compatibility.rb +65 -0
  102. data/lib/writeexcel/convert_date_time.rb +117 -0
  103. data/lib/writeexcel/data_validations.rb +370 -0
  104. data/lib/writeexcel/debug_info.rb +41 -0
  105. data/lib/writeexcel/embedded_chart.rb +35 -0
  106. data/lib/writeexcel/excelformula.y +139 -0
  107. data/lib/writeexcel/excelformulaparser.rb +587 -0
  108. data/lib/writeexcel/format.rb +1575 -0
  109. data/lib/writeexcel/formula.rb +987 -0
  110. data/lib/writeexcel/helper.rb +78 -0
  111. data/lib/writeexcel/image.rb +218 -0
  112. data/lib/writeexcel/olewriter.rb +305 -0
  113. data/lib/writeexcel/outline.rb +24 -0
  114. data/lib/writeexcel/properties.rb +242 -0
  115. data/lib/writeexcel/shared_string_table.rb +153 -0
  116. data/lib/writeexcel/storage_lite.rb +984 -0
  117. data/lib/writeexcel/workbook.rb +2478 -0
  118. data/lib/writeexcel/worksheet.rb +6925 -0
  119. data/lib/writeexcel/worksheets.rb +25 -0
  120. data/lib/writeexcel/write_file.rb +63 -0
  121. data/test/excelfile/Chart1.xls +0 -0
  122. data/test/excelfile/Chart2.xls +0 -0
  123. data/test/excelfile/Chart3.xls +0 -0
  124. data/test/excelfile/Chart4.xls +0 -0
  125. data/test/excelfile/Chart5.xls +0 -0
  126. data/test/helper.rb +31 -0
  127. data/test/perl_output/Chart1.xls.data +0 -0
  128. data/test/perl_output/Chart2.xls.data +0 -0
  129. data/test/perl_output/Chart3.xls.data +0 -0
  130. data/test/perl_output/Chart4.xls.data +0 -0
  131. data/test/perl_output/Chart5.xls.data +0 -0
  132. data/test/perl_output/README +31 -0
  133. data/test/perl_output/a_simple.xls +0 -0
  134. data/test/perl_output/autofilter.xls +0 -0
  135. data/test/perl_output/biff_add_continue_testdata +0 -0
  136. data/test/perl_output/chart_area.xls +0 -0
  137. data/test/perl_output/chart_bar.xls +0 -0
  138. data/test/perl_output/chart_column.xls +0 -0
  139. data/test/perl_output/chart_line.xls +0 -0
  140. data/test/perl_output/chess.xls +0 -0
  141. data/test/perl_output/colors.xls +0 -0
  142. data/test/perl_output/comments0.xls +0 -0
  143. data/test/perl_output/comments1.xls +0 -0
  144. data/test/perl_output/comments2.xls +0 -0
  145. data/test/perl_output/data_validate.xls +0 -0
  146. data/test/perl_output/date_time.xls +0 -0
  147. data/test/perl_output/defined_name.xls +0 -0
  148. data/test/perl_output/demo.xls +0 -0
  149. data/test/perl_output/demo101.bin +0 -0
  150. data/test/perl_output/demo201.bin +0 -0
  151. data/test/perl_output/demo301.bin +0 -0
  152. data/test/perl_output/demo401.bin +0 -0
  153. data/test/perl_output/demo501.bin +0 -0
  154. data/test/perl_output/diag_border.xls +0 -0
  155. data/test/perl_output/f_font_biff +0 -0
  156. data/test/perl_output/f_font_key +1 -0
  157. data/test/perl_output/f_xf_biff +0 -0
  158. data/test/perl_output/file_font_biff +0 -0
  159. data/test/perl_output/file_font_key +1 -0
  160. data/test/perl_output/file_xf_biff +0 -0
  161. data/test/perl_output/formula_result.xls +0 -0
  162. data/test/perl_output/headers.xls +0 -0
  163. data/test/perl_output/hidden.xls +0 -0
  164. data/test/perl_output/hide_zero.xls +0 -0
  165. data/test/perl_output/hyperlink.xls +0 -0
  166. data/test/perl_output/images.xls +0 -0
  167. data/test/perl_output/indent.xls +0 -0
  168. data/test/perl_output/merge1.xls +0 -0
  169. data/test/perl_output/merge2.xls +0 -0
  170. data/test/perl_output/merge3.xls +0 -0
  171. data/test/perl_output/merge4.xls +0 -0
  172. data/test/perl_output/merge5.xls +0 -0
  173. data/test/perl_output/merge6.xls +0 -0
  174. data/test/perl_output/ole_write_header +0 -0
  175. data/test/perl_output/outline.xls +0 -0
  176. data/test/perl_output/outline_collapsed.xls +0 -0
  177. data/test/perl_output/panes.xls +0 -0
  178. data/test/perl_output/password_protection.xls +0 -0
  179. data/test/perl_output/protection.xls +0 -0
  180. data/test/perl_output/regions.xls +0 -0
  181. data/test/perl_output/right_to_left.xls +0 -0
  182. data/test/perl_output/set_first_sheet.xls +0 -0
  183. data/test/perl_output/stats.xls +0 -0
  184. data/test/perl_output/stocks.xls +0 -0
  185. data/test/perl_output/store_formula.xls +0 -0
  186. data/test/perl_output/tab_colors.xls +0 -0
  187. data/test/perl_output/unicode_cyrillic.xls +0 -0
  188. data/test/perl_output/utf8.xls +0 -0
  189. data/test/perl_output/workbook1.xls +0 -0
  190. data/test/perl_output/workbook2.xls +0 -0
  191. data/test/perl_output/ws_colinfo +1 -0
  192. data/test/perl_output/ws_store_colinfo +0 -0
  193. data/test/perl_output/ws_store_dimensions +0 -0
  194. data/test/perl_output/ws_store_filtermode +0 -0
  195. data/test/perl_output/ws_store_filtermode_off +0 -0
  196. data/test/perl_output/ws_store_filtermode_on +0 -0
  197. data/test/perl_output/ws_store_selection +0 -0
  198. data/test/perl_output/ws_store_window2 +1 -0
  199. data/test/republic.png +0 -0
  200. data/test/test_00_IEEE_double.rb +13 -0
  201. data/test/test_01_add_worksheet.rb +10 -0
  202. data/test/test_02_merge_formats.rb +49 -0
  203. data/test/test_04_dimensions.rb +388 -0
  204. data/test/test_05_rows.rb +175 -0
  205. data/test/test_06_extsst.rb +74 -0
  206. data/test/test_11_date_time.rb +475 -0
  207. data/test/test_12_date_only.rb +525 -0
  208. data/test/test_13_date_seconds.rb +477 -0
  209. data/test/test_21_escher.rb +624 -0
  210. data/test/test_22_mso_drawing_group.rb +741 -0
  211. data/test/test_23_note.rb +57 -0
  212. data/test/test_24_txo.rb +74 -0
  213. data/test/test_25_position_object.rb +80 -0
  214. data/test/test_26_autofilter.rb +309 -0
  215. data/test/test_27_autofilter.rb +126 -0
  216. data/test/test_28_autofilter.rb +156 -0
  217. data/test/test_29_process_jpg.rb +670 -0
  218. data/test/test_30_validation_dval.rb +74 -0
  219. data/test/test_31_validation_dv_strings.rb +123 -0
  220. data/test/test_32_validation_dv_formula.rb +203 -0
  221. data/test/test_40_property_types.rb +188 -0
  222. data/test/test_41_properties.rb +235 -0
  223. data/test/test_42_set_properties.rb +434 -0
  224. data/test/test_50_name_stored.rb +295 -0
  225. data/test/test_51_name_print_area.rb +353 -0
  226. data/test/test_52_name_print_titles.rb +450 -0
  227. data/test/test_53_autofilter.rb +199 -0
  228. data/test/test_60_chart_generic.rb +574 -0
  229. data/test/test_61_chart_subclasses.rb +84 -0
  230. data/test/test_62_chart_formats.rb +268 -0
  231. data/test/test_63_chart_area_formats.rb +645 -0
  232. data/test/test_biff.rb +71 -0
  233. data/test/test_big_workbook.rb +17 -0
  234. data/test/test_compatibility.rb +12 -0
  235. data/test/test_example_match.rb +3246 -0
  236. data/test/test_format.rb +1189 -0
  237. data/test/test_formula.rb +61 -0
  238. data/test/test_ole.rb +102 -0
  239. data/test/test_storage_lite.rb +116 -0
  240. data/test/test_workbook.rb +146 -0
  241. data/test/test_worksheet.rb +106 -0
  242. data/utils/add_magic_comment.rb +80 -0
  243. data/writeexcel.gemspec +278 -0
  244. data/writeexcel.rdoc +1425 -0
  245. metadata +292 -0
@@ -0,0 +1,2478 @@
1
+ # -*- coding: utf-8 -*-
2
+ ###############################################################################
3
+ #
4
+ # Workbook - A writer class for Excel Workbooks.
5
+ #
6
+ #
7
+ # Used in conjunction with WriteExcel
8
+ #
9
+ # Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
10
+ #
11
+ # original written in Perl by John McNamara
12
+ # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
13
+ #
14
+ require 'nkf'
15
+ require 'forwardable'
16
+ require 'writeexcel/biffwriter'
17
+ require 'writeexcel/worksheet'
18
+ require 'writeexcel/chart'
19
+ require 'writeexcel/format'
20
+ require 'writeexcel/formula'
21
+ require 'writeexcel/olewriter'
22
+ require 'writeexcel/storage_lite'
23
+ require 'writeexcel/compatibility'
24
+ require 'writeexcel/shared_string_table'
25
+ require 'writeexcel/worksheets'
26
+
27
+ class Workbook < BIFFWriter
28
+ require 'writeexcel/properties'
29
+ require 'writeexcel/helper'
30
+
31
+ extend Forwardable
32
+
33
+ attr_reader :url_format, :parser, :tempdir, :date_1904
34
+ attr_reader :compatibility, :palette
35
+ attr_reader :ext_refs
36
+ attr_reader :worksheets
37
+
38
+ BOF = 12 # :nodoc:
39
+ EOF = 4 # :nodoc:
40
+
41
+ #
42
+ # _file_ is a filename (as string) or io object where to out spreadsheet data.
43
+ # you can set default format of workbook using _default_formats_.
44
+ #
45
+ # A new Excel workbook is created using the new() constructor which accepts
46
+ # either a filename or an IO object as a parameter. The following example
47
+ # creates a new Excel file based on a filename:
48
+ #
49
+ # workbook = WriteExcel.new('filename.xls')
50
+ # worksheet = workbook.add_worksheet
51
+ # worksheet.write(0, 0, 'Hi Excel!')
52
+ #
53
+ # Here are some other examples of using new():
54
+ #
55
+ # workbook1 = WriteExcel.new(filename)
56
+ # workbook2 = WriteExcel.new('/tmp/filename.xls')
57
+ # workbook3 = WriteExcel.new("c:\\tmp\\filename.xls")
58
+ # workbook4 = WriteExcel.new('c:\tmp\filename.xls')
59
+ #
60
+ # The last two examples demonstrates how to create a file on DOS or
61
+ # Windows where it is necessary to either escape the directory
62
+ # separator \ or to use single quotes to ensure that it isn't interpolated.
63
+ #
64
+ # The new() constructor returns a WriteExcel object that you can use to add
65
+ # worksheets and store data.
66
+ #
67
+ # If the file cannot be created, due to file permissions or some other reason,
68
+ # new will raise Exception Errno::EXXX.
69
+ #
70
+ # You can also pass a valid IO object to the new() constructor.:
71
+ # require 'stringio'
72
+ #
73
+ # io = StringIO.new
74
+ # workbook = WriteExcel.new(io) # After workbook.close, you can get excel data as io.string
75
+ #
76
+ # And, you can also pass default format properties.
77
+ #
78
+ # workbook = WriteExcel.new(filename, :font => 'Courier New', :size => 11)
79
+ #
80
+ def initialize(file, default_formats = {})
81
+ super()
82
+ @file = file
83
+ @default_formats = default_formats
84
+ @parser = Writeexcel::Formula.new(@byte_order)
85
+ @tempdir = nil
86
+ @date_1904 = false
87
+ @xf_index = 0
88
+ @biffsize = 0
89
+ @sheet_count = 0
90
+ @chart_count = 0
91
+ @codepage = 0x04E4
92
+ @country = 1
93
+ @worksheets = Worksheets.new
94
+ @formats = []
95
+ @palette = []
96
+ @biff_only = 0
97
+
98
+ @internal_fh = 0
99
+ @fh_out = ""
100
+
101
+ @shared_string_table = SharedStringTable.new
102
+ @extsst_offsets = [] # array of [global_offset, local_offset]
103
+ @extsst_buckets = 0
104
+ @extsst_bucket_size = 0
105
+
106
+ @ext_refs = {}
107
+
108
+ @mso_clusters = []
109
+ @mso_size = 0
110
+
111
+ @hideobj = false
112
+
113
+ @summary = ''
114
+ @doc_summary = ''
115
+ @localtime = Time.now
116
+
117
+ @defined_names = []
118
+
119
+ setup_built_in_formats(default_formats)
120
+
121
+ # Add the default format for hyperlinks
122
+ @url_format = add_format(:color => 'blue', :underline => 1)
123
+
124
+ if file.respond_to?(:to_str) && file != ''
125
+ @fh_out = open(file, "wb")
126
+ @internal_fh = 1
127
+ else
128
+ @fh_out = file
129
+ end
130
+
131
+ # Set colour palette.
132
+ set_palette_xl97
133
+ end
134
+
135
+ # return custom format from array at index
136
+ def format(index)
137
+ @formats[22+index]
138
+ end
139
+
140
+ #
141
+ # Calls finalization methods and explicitly close the OLEwriter files
142
+ # handle.
143
+ #
144
+ # An explicit close() is required if the file must be closed prior to performing
145
+ # some external action on it such as copying it, reading its size or attaching
146
+ # it to an email.
147
+ #
148
+ # In general, if you create a file with a size of 0 bytes or you fail to create
149
+ # a file you need to call close().
150
+ #
151
+ def close
152
+ return if fileclosed? # Prevent close() from being called twice.
153
+
154
+ @fileclosed = true
155
+ store_workbook
156
+ cleanup
157
+ end
158
+
159
+ # get array of Worksheet objects
160
+ #
161
+ # :call-seq:
162
+ # sheets -> array of all Wordsheet object
163
+ # sheets(1, 3, 4) -> array of spcified Worksheet object.
164
+ #
165
+ # The sheets() method returns a array, or a sliced array, of the worksheets
166
+ # in a workbook.
167
+ #
168
+ # If no arguments are passed the method returns a list of all the worksheets
169
+ # in the workbook. This is useful if you want to repeat an operation on each
170
+ # worksheet:
171
+ #
172
+ # workbook.sheets.each do |worksheet|
173
+ # print worksheet.get_name
174
+ # end
175
+ #
176
+ # You can also specify a slice list to return one or more worksheet objects:
177
+ #
178
+ # worksheet = workbook.sheets(0)
179
+ # worksheet.write('A1', 'Hello')
180
+ #
181
+ # you can write the above example as:
182
+ #
183
+ # workbook.sheets(0).write('A1', 'Hello')
184
+ #
185
+ # The following example returns the first and last worksheet in a workbook:
186
+ #
187
+ # workbook.sheets(0, -1).each do |sheet|
188
+ # # Do something
189
+ # end
190
+ #
191
+ def sheets(*args)
192
+ if args.empty?
193
+ @worksheets
194
+ else
195
+ args.collect{|i| @worksheets[i] }
196
+ end
197
+ end
198
+
199
+ #
200
+ # Add a new worksheet to the Excel workbook.
201
+ #
202
+ # if _sheetname_ is UTF-16BE format, pass true as _name_utf16be_.
203
+ #
204
+ # At least one worksheet should be added to a new workbook. A worksheet is
205
+ # used to write data into cells:
206
+ #
207
+ # worksheet1 = workbook.add_worksheet # Sheet1
208
+ # worksheet2 = workbook.add_worksheet('Foglio2') # Foglio2
209
+ # worksheet3 = workbook.add_worksheet('Data') # Data
210
+ # worksheet4 = workbook.add_worksheet # Sheet4
211
+ #
212
+ # If _sheetname_ is not specified the default Excel convention will be followed,
213
+ # i.e. Sheet1, Sheet2, etc. The utf_16_be parameter is optional, see below.
214
+ #
215
+ # The worksheet name must be a valid Excel worksheet name, i.e. it cannot
216
+ # contain any of the following characters, [ ] : * ? / \
217
+ # and it must be less than 32 characters. In addition, you cannot use the same,
218
+ # case insensitive, _sheetname_ for more than one worksheet.
219
+ #
220
+ # This method will also handle strings in UTF-8 format.
221
+ #
222
+ # worksheet = workbook.add_worksheet("シート名")
223
+ #
224
+ # UTF-16BE worksheet names using an additional optional parameter:
225
+ #
226
+ # name = [0x263a].pack('n')
227
+ # worksheet = workbook.add_worksheet(name, true) # Smiley
228
+ #
229
+ def add_worksheet(sheetname = '', name_utf16be = false)
230
+ name, name_utf16be = check_sheetname(sheetname, name_utf16be)
231
+
232
+ init_data = [
233
+ self,
234
+ name,
235
+ name_utf16be
236
+ ]
237
+ worksheet = Writeexcel::Worksheet.new(*init_data)
238
+ @worksheets << worksheet # Store ref for iterator
239
+ @parser.set_ext_sheets(name, worksheet.index) # Store names in Formula.rb
240
+ worksheet
241
+ end
242
+
243
+ #
244
+ # Create a chart for embedding or as as new sheet.
245
+ #
246
+ # This method is use to create a new chart either as a standalone worksheet
247
+ # (the default) or as an embeddable object that can be inserted into a
248
+ # worksheet via the insert_chart() Worksheet method.
249
+ #
250
+ # chart = workbook.add_chart(:type => 'Chart::Column')
251
+ #
252
+ # The properties that can be set are:
253
+ #
254
+ # :type (required)
255
+ # :name (optional)
256
+ # :name_utf16be (optional)
257
+ # :embedded (optional)
258
+ #
259
+ # * :type
260
+ #
261
+ # This is a required parameter. It defines the type of chart that will be created.
262
+ #
263
+ # chart = workbook.add_chart(:type => 'Chart::Line')
264
+ #
265
+ # The available types are:
266
+ #
267
+ # 'Chart::Column'
268
+ # 'Chart::Bar'
269
+ # 'Chart::Line'
270
+ # 'Chart::Area'
271
+ # 'Chart::Pie'
272
+ # 'Chart::Scatter'
273
+ # 'Chart::Stock'
274
+ #
275
+ # * :name
276
+ #
277
+ # Set the name for the chart sheet. The name property is optional and
278
+ # if it isn't supplied will default to Chart1 .. n. The name must be
279
+ # a valid Excel worksheet name. See add_worksheet() for more details
280
+ # on valid sheet names. The :name property can be omitted for embedded
281
+ # charts.
282
+ #
283
+ # chart = workbook.add_chart(
284
+ # :type => 'Chart::Line',
285
+ # :name => 'Results Chart'
286
+ # )
287
+ #
288
+ # * :name_utf16be
289
+ #
290
+ # if :name is UTF-16BE format, pass true as :name_utf16be
291
+ #
292
+ # * :encoding
293
+ #
294
+ # if :name is UTF-16BE format, pass 1 as :encoding.
295
+ # This key is obsolete in v0.7 or later. Use :name_utf16be instead.
296
+ #
297
+ # * :embedded
298
+ #
299
+ # Specifies true that the Chart object will be inserted in a worksheet via
300
+ # the insert_chart() Worksheet method. It is an error to try insert a
301
+ # Chart that doesn't have this flag set.
302
+ #
303
+ # chart = workbook.add_chart(:type => 'Chart::Line', :embedded => true)
304
+ #
305
+ # # Configure the chart.
306
+ # ...
307
+ #
308
+ # # Insert the chart into the a worksheet.
309
+ # worksheet.insert_chart('E2', chart)
310
+ #
311
+ # See WriteExcel::Chart for details on how to configure the
312
+ # chart object once it is created. See also the chart_*.rb programs in the
313
+ # examples directory of the distro.
314
+ #
315
+ def add_chart(properties)
316
+ name = ''
317
+ name_utf16be = false
318
+
319
+ # Type must be specified so we can create the required chart instance.
320
+ type = properties[:type]
321
+ raise "Must define chart type in add_chart()" if type.nil?
322
+
323
+ # Ensure that the chart defaults to non embedded.
324
+ embedded = properties[:embedded]
325
+
326
+ # Check the worksheet name for non-embedded charts.
327
+ unless embedded
328
+ properties[:name_utf16be] = true if properties[:encoding] == 1
329
+ name, name_utf16be =
330
+ check_sheetname(properties[:name], properties[:name_utf16be], true)
331
+ end
332
+
333
+ init_data = [
334
+ self,
335
+ name,
336
+ name_utf16be
337
+ ]
338
+
339
+ chart = Writeexcel::Chart.factory(type, *init_data)
340
+ # If the chart isn't embedded let the workbook control it.
341
+ if !embedded
342
+ @worksheets << chart # Store ref for iterator
343
+ else
344
+ chart.set_embedded_config_data
345
+ end
346
+ chart
347
+ end
348
+
349
+ #
350
+ # Add an externally created chart.
351
+ #
352
+ # This method is use to include externally generated charts in a WriteExcel
353
+ # file.
354
+ #
355
+ # chart = workbook.add_chart_ext('chart01.bin', 'Chart1')
356
+ #
357
+ # This feature is semi-deprecated in favour of the "native" charts created
358
+ # using add_chart(). Read external_charts.txt in the external_charts
359
+ # directory of the distro for a full explanation.
360
+ #
361
+ def add_chart_ext(filename, chartname, name_utf16be = false)
362
+ type = 'extarnal'
363
+
364
+ name, name_utf16be = check_sheetname(chartname, name_utf16be)
365
+
366
+ init_data = [
367
+ filename,
368
+ name,
369
+ name_utf16be
370
+ ]
371
+
372
+ chart = Writeexcel::Chart.factory(self, type, init_data)
373
+ @worksheets << chart # Store ref for iterator
374
+ chart
375
+ end
376
+
377
+ #
378
+ # The add_format method can be used to create new Format objects which are
379
+ # used to apply formatting to a cell. You can either define the properties
380
+ # at creation time via a hash of property values or later via method calls.
381
+ #
382
+ # format1 = workbook.add_format(props) # Set properties at creation
383
+ # format2 = workbook.add_format # Set properties later
384
+ #
385
+ # See the "CELL FORMATTING" section for more details about Format properties and how to set them.
386
+ #
387
+ def add_format(*args)
388
+ fmts = {}
389
+ args.each { |arg| fmts = fmts.merge(arg) }
390
+ format = Writeexcel::Format.new(@xf_index, @default_formats.merge(fmts))
391
+ @xf_index += 1
392
+ @formats.push format # Store format reference
393
+ format
394
+ end
395
+
396
+ #
397
+ # Set the compatibility mode.
398
+ #
399
+ # This method is used to improve compatibility with third party
400
+ # applications that read Excel files.
401
+ #
402
+ # workbook.compatibility_mode
403
+ #
404
+ # An Excel file is comprised of binary records that describe properties of
405
+ # a spreadsheet. Excel is reasonably liberal about this and, outside of a
406
+ # core subset, it doesn't require every possible record to be present when
407
+ # it reads a file. This is also true of Gnumeric and OpenOffice.Org Calc.
408
+ #
409
+ # WriteExcel takes advantage of this fact to omit some records in order to
410
+ # minimise the amount of data stored in memory and to simplify and speed up
411
+ # the writing of files. However, some third party applications that read
412
+ # Excel files often expect certain records to be present. In
413
+ # "compatibility mode" WriteExcel writes these records and tries to be as
414
+ # close to an Excel generated file as possible.
415
+ #
416
+ # Applications that require compatibility_mode() are Apache POI,
417
+ # Apple Numbers, and Quickoffice on Nokia, Palm and other devices. You should
418
+ # also use compatibility_mode() if your Excel file will be used as an external
419
+ # data source by another Excel file.
420
+ #
421
+ # If you encounter other situations that require compatibility_mode(),
422
+ # please let me know.
423
+ #
424
+ # It should be noted that compatibility_mode() requires additional data to be
425
+ # stored in memory and additional processing. This incurs a memory and speed
426
+ # penalty and may not be suitable for very large files (>20MB).
427
+ #
428
+ # You must call compatibility_mode() before calling add_worksheet().
429
+ #
430
+ #
431
+ # Excel doesn't require every possible Biff record to be present in a file.
432
+ # In particular if the indexing records INDEX, ROW and DBCELL aren't present
433
+ # it just ignores the fact and reads the cells anyway. This is also true of
434
+ # the EXTSST record. Gnumeric and OOo also take this approach. This allows
435
+ # WriteExcel to ignore these records in order to minimise the amount of data
436
+ # stored in memory. However, other third party applications that read Excel
437
+ # files often expect these records to be present. In "compatibility mode"
438
+ # WriteExcel writes these records and tries to be as close to an Excel
439
+ # generated file as possible.
440
+ #
441
+ # This requires additional data to be stored in memory until the file is
442
+ # about to be written. This incurs a memory and speed penalty and may not be
443
+ # suitable for very large files.
444
+ #
445
+ def compatibility_mode(mode = true)
446
+ unless sheets.empty?
447
+ raise "compatibility_mode() must be called before add_worksheet()"
448
+ end
449
+ @compatibility = mode
450
+ end
451
+
452
+ #
453
+ # Set the date system: false = 1900 (the default), true = 1904
454
+ #
455
+ # Excel stores dates as real numbers where the integer part stores the
456
+ # number of days since the epoch and the fractional part stores the
457
+ # percentage of the day. The epoch can be either 1900 or 1904. Excel for
458
+ # Windows uses 1900 and Excel for Macintosh uses 1904. However, Excel on
459
+ # either platform will convert automatically between one system and
460
+ # the other.
461
+ #
462
+ # WriteExcel stores dates in the 1900 format by default. If you wish to
463
+ # change this you can call the set_1904() workbook method. You can query
464
+ # the current value by calling the get_1904() workbook method. This returns
465
+ # false for 1900 and true for 1904.
466
+ #
467
+ # See also "DATES AND TIME IN EXCEL" for more information about working
468
+ # with Excel's date system.
469
+ #
470
+ # In general you probably won't need to use set_1904().
471
+ #
472
+ def set_1904(mode = true)
473
+ unless sheets.empty?
474
+ raise "set_1904() must be called before add_worksheet()"
475
+ end
476
+ @date_1904 = (!mode || mode == 0) ? false : true
477
+ end
478
+
479
+ def get_1904
480
+ @date_1904
481
+ end
482
+
483
+ #
484
+ # Change the RGB components of the elements in the colour palette.
485
+ #
486
+ # The set_custom_color() method can be used to override one of the built-in
487
+ # palette values with a more suitable colour.
488
+ #
489
+ # The value for _index_ should be in the range 8..63, see "COLOURS IN EXCEL".
490
+ #
491
+ # The default named colours use the following indices:
492
+ #
493
+ # 8 => black
494
+ # 9 => white
495
+ # 10 => red
496
+ # 11 => lime
497
+ # 12 => blue
498
+ # 13 => yellow
499
+ # 14 => magenta
500
+ # 15 => cyan
501
+ # 16 => brown
502
+ # 17 => green
503
+ # 18 => navy
504
+ # 20 => purple
505
+ # 22 => silver
506
+ # 23 => gray
507
+ # 33 => pink
508
+ # 53 => orange
509
+ #
510
+ # A new colour is set using its RGB (red green blue) components. The red,
511
+ # green and blue values must be in the range 0..255. You can determine the
512
+ # required values in Excel using the Tools->Options->Colors->Modify dialog.
513
+ #
514
+ # The set_custom_color() workbook method can also be used with a HTML style
515
+ # #rrggbb hex value:
516
+ #
517
+ # workbook.set_custom_color(40, 255, 102, 0 ) # Orange
518
+ # workbook.set_custom_color(40, 0xFF, 0x66, 0x00) # Same thing
519
+ # workbook.set_custom_color(40, '#FF6600' ) # Same thing
520
+ #
521
+ # font = workbook.add_format(:color => 40) # Use the modified colour
522
+ #
523
+ # The return value from set_custom_color() is the index of the colour that
524
+ # was changed:
525
+ #
526
+ # ferrari = workbook.set_custom_color(40, 216, 12, 12)
527
+ #
528
+ # format = workbook.add_format(
529
+ # :bg_color => $ferrari,
530
+ # :pattern => 1,
531
+ # :border => 1
532
+ # )
533
+ #
534
+ def set_custom_color(index, red = nil, green = nil, blue = nil)
535
+ # Match a HTML #xxyyzz style parameter
536
+ if !red.nil? && red =~ /^#(\w\w)(\w\w)(\w\w)/
537
+ red = $1.hex
538
+ green = $2.hex
539
+ blue = $3.hex
540
+ end
541
+
542
+ # Check that the colour index is the right range
543
+ if index < 8 || index > 64
544
+ raise "Color index #{index} outside range: 8 <= index <= 64"
545
+ end
546
+
547
+ # Check that the colour components are in the right range
548
+ if (red < 0 || red > 255) ||
549
+ (green < 0 || green > 255) ||
550
+ (blue < 0 || blue > 255)
551
+ raise "Color component outside range: 0 <= color <= 255"
552
+ end
553
+
554
+ index -=8 # Adjust colour index (wingless dragonfly)
555
+
556
+ # Set the RGB value
557
+ @palette[index] = [red, green, blue, 0]
558
+
559
+ index + 8
560
+ end
561
+
562
+ #
563
+ # Change the default temp directory
564
+ #
565
+ # For speed and efficiency WriteExcel stores worksheet data in temporary
566
+ # files prior to assembling the final workbook.
567
+ #
568
+ # If WriteExcel is unable to create these temporary files it will store
569
+ # the required data in memory. This can be slow for large files.
570
+ #
571
+ # The problem occurs mainly with IIS on Windows although it could feasibly
572
+ # occur on Unix systems as well. The problem generally occurs because the
573
+ # default temp file directory is defined as C:/ or some other directory that
574
+ # IIS doesn't provide write access to.
575
+ #
576
+ # To check if this might be a problem on a particular system you can run a
577
+ # simple test program with -w or use warnings. This will generate a warning
578
+ # if the module cannot create the required temporary files:
579
+ #
580
+ # #!/usr/bin/ruby -w
581
+ #
582
+ # require 'WriteExcel'
583
+ #
584
+ # workbook = WriteExcel.new('test.xls')
585
+ # worksheet = workbook.add_worksheet
586
+ # workbook.close
587
+ #
588
+ # To avoid this problem the set_tempdir() method can be used to specify a
589
+ # directory that is accessible for the creation of temporary files.
590
+ #
591
+ # Even if the default temporary file directory is accessible you may wish
592
+ # to specify an alternative location for security or maintenance reasons:
593
+ #
594
+ # workbook.set_tempdir('/tmp/writeexcel')
595
+ # workbook.set_tempdir('c:\windows\temp\writeexcel')
596
+ #
597
+ # The directory for the temporary file must exist, set_tempdir() will not
598
+ # create a new directory.
599
+ #
600
+ # One disadvantage of using the set_tempdir() method is that on some Windows
601
+ # systems it will limit you to approximately 800 concurrent tempfiles. This
602
+ # means that a single program running on one of these systems will be limited
603
+ # to creating a total of 800 workbook and worksheet objects. You can run
604
+ # multiple, non-concurrent programs to work around this if necessary.
605
+ #
606
+ def set_tempdir(dir = '')
607
+ raise "#{dir} is not a valid directory" if dir != '' && !FileTest.directory?(dir)
608
+ raise "set_tempdir must be called before add_worksheet" unless sheets.empty?
609
+
610
+ @tempdir = dir
611
+ end
612
+
613
+ #
614
+ # The default code page or character set used by WriteExcel is ANSI. This is
615
+ # also the default used by Excel for Windows. Occasionally however it may be
616
+ # necessary to change the code page via the set_codepage() method.
617
+ #
618
+ # Changing the code page may be required if your are using WriteExcel on the
619
+ # Macintosh and you are using characters outside the ASCII 128 character set:
620
+ #
621
+ # workbook.set_codepage(1) # ANSI, MS Windows
622
+ # workbook.set_codepage(2) # Apple Macintosh
623
+ #
624
+ # The set_codepage() method is rarely required.
625
+ #
626
+ def set_codepage(type = 1)
627
+ if type == 2
628
+ @codepage = 0x8000
629
+ else
630
+ @codepage = 0x04E4
631
+ end
632
+ end
633
+
634
+ #
635
+ # store the country code.
636
+ #
637
+ # Some non-english versions of Excel may need this set to some value other
638
+ # than 1 = "United States". In general the country code is equal to the
639
+ # international dialling code.
640
+ #
641
+ def set_country(code = 1)
642
+ @country = code
643
+ end
644
+
645
+ #
646
+ # This method is used to defined a name that can be used to represent a
647
+ # value, a single cell or a range of cells in a workbook.
648
+ #
649
+ # workbook.define_name('Exchange_rate', '=0.96')
650
+ # workbook.define_name('Sales', '=Sheet1!$G$1:$H$10')
651
+ # workbook.define_name('Sheet2!Sales', '=Sheet2!$G$1:$G$10')
652
+ #
653
+ # See the defined_name.rb program in the examples dir of the distro.
654
+ #
655
+ # Note: This currently a beta feature. More documentation and examples
656
+ # will be added.
657
+ #
658
+ def define_name(name, formula, encoding = 0)
659
+ sheet_index = 0
660
+ full_name = name.downcase
661
+
662
+ if name =~ /^(.*)!(.*)$/
663
+ sheetname = $1
664
+ name = $2
665
+ sheet_index = 1 + @parser.get_sheet_index(sheetname)
666
+ end
667
+
668
+ # Strip the = sign at the beginning of the formula string
669
+ formula = formula.sub(/^=/, '')
670
+
671
+ # Parse the formula using the parser in Formula.pm
672
+ parser = @parser
673
+
674
+ # In order to raise formula errors from the point of view of the calling
675
+ # program we use an eval block and re-raise the error from here.
676
+ #
677
+ tokens = parser.parse_formula(formula)
678
+
679
+ # Force 2d ranges to be a reference class.
680
+ tokens.collect! { |t| t.gsub(/_ref3d/, '_ref3dR') }
681
+ tokens.collect! { |t| t.gsub(/_range3d/, '_range3dR') }
682
+
683
+ # Parse the tokens into a formula string.
684
+ formula = parser.parse_tokens(tokens)
685
+
686
+ defined_names.push(
687
+ {
688
+ :name => name,
689
+ :encoding => encoding,
690
+ :sheet_index => sheet_index,
691
+ :formula => formula
692
+ }
693
+ )
694
+
695
+ index = defined_names.size
696
+
697
+ parser.set_ext_name(name, index)
698
+ end
699
+
700
+ #
701
+ # Set the document properties such as Title, Author etc. These are written to
702
+ # property sets in the OLE container.
703
+ #
704
+ # The set_properties method can be used to set the document properties of
705
+ # the Excel file created by WriteExcel. These properties are visible when you
706
+ # use the File->Properties menu option in Excel and are also available to
707
+ # external applications that read or index windows files.
708
+ #
709
+ # The properties should be passed as a hash of values as follows:
710
+ #
711
+ # workbook.set_properties(
712
+ # :title => 'This is an example spreadsheet',
713
+ # :author => 'cxn03651',
714
+ # :comments => 'Created with Ruby and WriteExcel',
715
+ # )
716
+ #
717
+ # The properties that can be set are:
718
+ #
719
+ # * :title
720
+ # * :subject
721
+ # * :author
722
+ # * :manager
723
+ # * :company
724
+ # * :category
725
+ # * :keywords
726
+ # * :comments
727
+ #
728
+ # User defined properties are not supported due to effort required.
729
+ #
730
+ # You can also pass UTF-8 strings as properties.
731
+ #
732
+ # workbook.set_properties(
733
+ # :subject => "住所録"
734
+ # )
735
+ #
736
+ # Usually WriteExcel allows you to use UTF-16. However, document properties
737
+ # don't support UTF-16 for these type of strings.
738
+ #
739
+ # In order to promote the usefulness of Ruby and the WriteExcel module
740
+ # consider adding a comment such as the following when using document
741
+ # properties:
742
+ #
743
+ # workbook.set_properties(
744
+ # ...,
745
+ # :comments => 'Created with Ruby and writeexcel',
746
+ # ...,
747
+ # )
748
+ #
749
+ # See also the properties.rb program in the examples directory of the distro.
750
+ #
751
+ def set_properties(params)
752
+ # Ignore if no args were passed.
753
+ return -1 if !params.respond_to?(:to_hash) || params.empty?
754
+
755
+ params.each do |k, v|
756
+ params[k] = convert_to_ascii_if_ascii(v) if v.respond_to?(:to_str)
757
+ end
758
+
759
+ # Check for valid input parameters.
760
+ check_valid_params_for_properties(params)
761
+
762
+ # Set the creation time unless specified by the user.
763
+ params[:created] = @localtime unless params.has_key?(:created)
764
+
765
+ #
766
+ # Create the SummaryInformation property set.
767
+ #
768
+
769
+ # Get the codepage of the strings in the property set.
770
+ properties = [:title, :subject, :author, :keywords, :comments, :last_author]
771
+ params[:codepage] = get_property_set_codepage(params, properties)
772
+
773
+ # Create an array of property set values.
774
+ properties.unshift(:codepage)
775
+ properties.push(:created)
776
+
777
+ # Pack the property sets.
778
+ @summary =
779
+ create_summary_property_set(property_sets(properties, params))
780
+
781
+ #
782
+ # Create the DocSummaryInformation property set.
783
+ #
784
+
785
+ # Get the codepage of the strings in the property set.
786
+ properties = [:category, :manager, :company]
787
+ params[:codepage] = get_property_set_codepage(params, properties)
788
+
789
+ # Create an array of property set values.
790
+ properties.unshift(:codepage)
791
+
792
+ # Pack the property sets.
793
+ @doc_summary =
794
+ create_doc_summary_property_set(property_sets(properties, params))
795
+
796
+ # Set a flag for when the files is written.
797
+ @add_doc_properties = true
798
+ end
799
+
800
+ def extsst_buckets # :nodoc:
801
+ @extsst_buckets
802
+ end
803
+
804
+ def extsst_bucket_size # :nodoc:
805
+ @extsst_bucket_size
806
+ end
807
+
808
+ def biff_only=(val) # :nodoc:
809
+ @biff_only = val
810
+ end
811
+
812
+ def summary # :nodoc:
813
+ @summary
814
+ end
815
+
816
+ def localtime=(val) # :nodoc:
817
+ @localtime = val
818
+ end
819
+
820
+ def update_str_table(str)
821
+ @shared_string_table << str
822
+ end
823
+
824
+ #==========================================
825
+ # Internal method
826
+ #==========================================
827
+
828
+ private
829
+
830
+ # Add the in-built style formats and the default cell format.
831
+ def setup_built_in_formats(default_formats) # :nodoc:
832
+ add_format(:type => 1) # 0 Normal
833
+ add_format(:type => 1) # 1 RowLevel 1
834
+ add_format(:type => 1) # 2 RowLevel 2
835
+ add_format(:type => 1) # 3 RowLevel 3
836
+ add_format(:type => 1) # 4 RowLevel 4
837
+ add_format(:type => 1) # 5 RowLevel 5
838
+ add_format(:type => 1) # 6 RowLevel 6
839
+ add_format(:type => 1) # 7 RowLevel 7
840
+ add_format(:type => 1) # 8 ColLevel 1
841
+ add_format(:type => 1) # 9 ColLevel 2
842
+ add_format(:type => 1) # 10 ColLevel 3
843
+ add_format(:type => 1) # 11 ColLevel 4
844
+ add_format(:type => 1) # 12 ColLevel 5
845
+ add_format(:type => 1) # 13 ColLevel 6
846
+ add_format(:type => 1) # 14 ColLevel 7
847
+ add_format(default_formats) # 15 Cell XF
848
+ add_format(:type => 1, :num_format => 0x2B) # 16 Comma
849
+ add_format(:type => 1, :num_format => 0x29) # 17 Comma[0]
850
+ add_format(:type => 1, :num_format => 0x2C) # 18 Currency
851
+ add_format(:type => 1, :num_format => 0x2A) # 19 Currency[0]
852
+ add_format(:type => 1, :num_format => 0x09) # 20 Percent
853
+ end
854
+
855
+ def fileclosed?
856
+ @fileclosed || false
857
+ end
858
+
859
+ #
860
+ # Check for valid worksheet names. We check the length, if it contains any
861
+ # invalid characters and if the name is unique in the workbook.
862
+ #
863
+ def check_sheetname(name, name_utf16be = false, chart = nil) #:nodoc:
864
+ # name_utf16be parameter may set 0/1 in v0.6.6 or earlier
865
+ name_utf16be = false if name_utf16be == 0
866
+ name_utf16be = true if name_utf16be == 1
867
+
868
+ increment_sheet_chart_count(chart)
869
+ if name.nil? || name == ""
870
+ name_utf16be = false
871
+ name = default_sheet_chart_name(chart)
872
+ end
873
+
874
+ ruby_19 { name = convert_to_ascii_if_ascii(name) }
875
+ check_sheetname_length(name, name_utf16be)
876
+ check_sheetname_even(name) if name_utf16be
877
+ check_sheetname_valid_chars(name, name_utf16be)
878
+
879
+ # Handle utf8 strings
880
+ if is_utf8?(name)
881
+ name = utf8_to_16be(name)
882
+ name_utf16be = true
883
+ end
884
+
885
+ check_sheetname_uniq(name, name_utf16be)
886
+ [name, name_utf16be]
887
+ end
888
+
889
+ # Increment the Sheet/Chart number used for default sheet names below.
890
+ def increment_sheet_chart_count(chart)
891
+ if chart
892
+ @chart_count += 1
893
+ else
894
+ @sheet_count += 1
895
+ end
896
+ end
897
+
898
+ # Supply default Sheet/Chart name if none has been defined.
899
+ def default_sheet_chart_name(chart)
900
+ if chart
901
+ "Chart#{@chart_count}"
902
+ else
903
+ "Sheet#{@sheet_count}"
904
+ end
905
+ end
906
+
907
+ def check_sheetname_length(name, name_utf16be) #:nodoc:
908
+ # Check that sheetname is <= 31 (1 or 2 byte chars). Excel limit.
909
+ limit = name_utf16be ? 62 : 100 #31 Increased length limit of sheet name to 100
910
+ raise "Sheetname $name must be <= 100 chars" if name.bytesize > limit
911
+ end
912
+
913
+ def check_sheetname_even(name) #:nodoc:
914
+ # Check that Unicode sheetname has an even number of bytes
915
+ if (name.bytesize % 2 != 0)
916
+ raise "Odd number of bytes in Unicode worksheet name: #{name}"
917
+ end
918
+ end
919
+
920
+ def check_sheetname_valid_chars(name, name_utf16be) #:nodoc:
921
+ # Check that sheetname doesn't contain any invalid characters
922
+ invalid_char = %r![\[\]:*?/\\]!
923
+ if !name_utf16be && name =~ invalid_char
924
+ # Check ASCII names
925
+ raise "Invalid character []:*?/\\ in worksheet name: #{name}"
926
+ else
927
+ # Extract any 8bit clean chars from the UTF16 name and validate them.
928
+ str = name.dup
929
+ while str =~ /../m
930
+ hi, lo = $~[0].unpack('aa')
931
+ if hi == "\0" and lo =~ invalid_char
932
+ raise 'Invalid character []:*?/\\ in worksheet name: ' + name
933
+ end
934
+ str = $~.post_match
935
+ end
936
+ end
937
+ end
938
+
939
+ # Check that the worksheet name doesn't already exist since this is a fatal
940
+ # error in Excel 97. The check must also exclude case insensitive matches
941
+ # since the names 'Sheet1' and 'sheet1' are equivalent. The tests also have
942
+ # to take the name_utf16be into account.
943
+ #
944
+ def check_sheetname_uniq(name, name_utf16be) #:nodoc:
945
+ @worksheets.each do |worksheet|
946
+ name_a = name
947
+ encd_a = name_utf16be
948
+ name_b = worksheet.name
949
+ encd_b = worksheet.is_name_utf16be?
950
+ error = false
951
+
952
+ if !encd_a and !encd_b
953
+ error = (name_a.downcase == name_b.downcase)
954
+ elsif !encd_a and encd_b
955
+ name_a = ascii_to_16be(name_a)
956
+ error = (name_a.downcase == name_b.downcase)
957
+ elsif encd_a and !encd_b
958
+ name_b = ascii_to_16be(name_b)
959
+ error = (name_a.downcase == name_b.downcase)
960
+ elsif encd_a and encd_b
961
+ error = (name_a.downcase == name_b.downcase)
962
+ # TODO : not converted yet.
963
+
964
+ # We can't easily do a case insensitive test of the UTF16 names.
965
+ # As a special case we check if all of the high bytes are nulls and
966
+ # then do an ASCII style case insensitive test.
967
+ #
968
+ # Strip out the high bytes (funkily).
969
+ # my $hi_a = grep {ord} $name_a =~ /(.)./sg;
970
+ # my $hi_b = grep {ord} $name_b =~ /(.)./sg;
971
+ #
972
+ # if ($hi_a or $hi_b) {
973
+ # $error = 1 if $name_a eq $name_b;
974
+ # }
975
+ # else {
976
+ # $error = 1 if lc($name_a) eq lc($name_b);
977
+ # }
978
+ end
979
+ if error
980
+ raise "Worksheet name '#{name}', with case ignored, is already in use"
981
+ end
982
+ end
983
+ end
984
+
985
+ #
986
+ # Sets the colour palette to the Excel 97+ default.
987
+ #
988
+ def set_palette_xl97 #:nodoc:
989
+ @palette = [
990
+ [0x00, 0x00, 0x00, 0x00], # 8
991
+ [0xff, 0xff, 0xff, 0x00], # 9
992
+ [0xff, 0x00, 0x00, 0x00], # 10
993
+ [0x00, 0xff, 0x00, 0x00], # 11
994
+ [0x00, 0x00, 0xff, 0x00], # 12
995
+ [0xff, 0xff, 0x00, 0x00], # 13
996
+ [0xff, 0x00, 0xff, 0x00], # 14
997
+ [0x00, 0xff, 0xff, 0x00], # 15
998
+ [0x80, 0x00, 0x00, 0x00], # 16
999
+ [0x00, 0x80, 0x00, 0x00], # 17
1000
+ [0x00, 0x00, 0x80, 0x00], # 18
1001
+ [0x80, 0x80, 0x00, 0x00], # 19
1002
+ [0x80, 0x00, 0x80, 0x00], # 20
1003
+ [0x00, 0x80, 0x80, 0x00], # 21
1004
+ [0xc0, 0xc0, 0xc0, 0x00], # 22
1005
+ [0x80, 0x80, 0x80, 0x00], # 23
1006
+ [0x99, 0x99, 0xff, 0x00], # 24
1007
+ [0x99, 0x33, 0x66, 0x00], # 25
1008
+ [0xff, 0xff, 0xcc, 0x00], # 26
1009
+ [0xcc, 0xff, 0xff, 0x00], # 27
1010
+ [0x66, 0x00, 0x66, 0x00], # 28
1011
+ [0xff, 0x80, 0x80, 0x00], # 29
1012
+ [0x00, 0x66, 0xcc, 0x00], # 30
1013
+ [0xcc, 0xcc, 0xff, 0x00], # 31
1014
+ [0x00, 0x00, 0x80, 0x00], # 32
1015
+ [0xff, 0x00, 0xff, 0x00], # 33
1016
+ [0xff, 0xff, 0x00, 0x00], # 34
1017
+ [0x00, 0xff, 0xff, 0x00], # 35
1018
+ [0x80, 0x00, 0x80, 0x00], # 36
1019
+ [0x80, 0x00, 0x00, 0x00], # 37
1020
+ [0x00, 0x80, 0x80, 0x00], # 38
1021
+ [0x00, 0x00, 0xff, 0x00], # 39
1022
+ [0x00, 0xcc, 0xff, 0x00], # 40
1023
+ [0xcc, 0xff, 0xff, 0x00], # 41
1024
+ [0xcc, 0xff, 0xcc, 0x00], # 42
1025
+ [0xff, 0xff, 0x99, 0x00], # 43
1026
+ [0x99, 0xcc, 0xff, 0x00], # 44
1027
+ [0xff, 0x99, 0xcc, 0x00], # 45
1028
+ [0xcc, 0x99, 0xff, 0x00], # 46
1029
+ [0xff, 0xcc, 0x99, 0x00], # 47
1030
+ [0x33, 0x66, 0xff, 0x00], # 48
1031
+ [0x33, 0xcc, 0xcc, 0x00], # 49
1032
+ [0x99, 0xcc, 0x00, 0x00], # 50
1033
+ [0xff, 0xcc, 0x00, 0x00], # 51
1034
+ [0xff, 0x99, 0x00, 0x00], # 52
1035
+ [0xff, 0x66, 0x00, 0x00], # 53
1036
+ [0x66, 0x66, 0x99, 0x00], # 54
1037
+ [0x96, 0x96, 0x96, 0x00], # 55
1038
+ [0x00, 0x33, 0x66, 0x00], # 56
1039
+ [0x33, 0x99, 0x66, 0x00], # 57
1040
+ [0x00, 0x33, 0x00, 0x00], # 58
1041
+ [0x33, 0x33, 0x00, 0x00], # 59
1042
+ [0x99, 0x33, 0x00, 0x00], # 60
1043
+ [0x99, 0x33, 0x66, 0x00], # 61
1044
+ [0x33, 0x33, 0x99, 0x00], # 62
1045
+ [0x33, 0x33, 0x33, 0x00] # 63
1046
+ ]
1047
+ end
1048
+
1049
+ def property_set(property, params) #:nodoc:
1050
+ valid_properties[property][0..1] + [params[property]]
1051
+ end
1052
+
1053
+ def property_sets(properties, params) #:nodoc:
1054
+ properties.select { |property| params[property.to_sym] }.
1055
+ collect do |property|
1056
+ property_set(property.to_sym, params)
1057
+ end
1058
+ end
1059
+
1060
+ # List of valid input parameters.
1061
+ def valid_properties #:nodoc:
1062
+ {
1063
+ :codepage => [0x0001, 'VT_I2' ],
1064
+ :title => [0x0002, 'VT_LPSTR' ],
1065
+ :subject => [0x0003, 'VT_LPSTR' ],
1066
+ :author => [0x0004, 'VT_LPSTR' ],
1067
+ :keywords => [0x0005, 'VT_LPSTR' ],
1068
+ :comments => [0x0006, 'VT_LPSTR' ],
1069
+ :last_author => [0x0008, 'VT_LPSTR' ],
1070
+ :created => [0x000C, 'VT_FILETIME'],
1071
+ :category => [0x0002, 'VT_LPSTR' ],
1072
+ :manager => [0x000E, 'VT_LPSTR' ],
1073
+ :company => [0x000F, 'VT_LPSTR' ],
1074
+ :utf8 => 1
1075
+ }
1076
+ end
1077
+
1078
+ def check_valid_params_for_properties(params) #:nodoc:
1079
+ params.each_key do |k|
1080
+ unless valid_properties.has_key?(k)
1081
+ raise "Unknown parameter '#{k}' in set_properties()"
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ #
1087
+ # Get the character codepage used by the strings in a property set. If one of
1088
+ # the strings used is utf8 then the codepage is marked as utf8. Otherwise
1089
+ # Latin 1 is used (although in our case this is limited to 7bit ASCII).
1090
+ #
1091
+ def get_property_set_codepage(params, properties) #:nodoc:
1092
+ # Allow for manually marked utf8 strings.
1093
+ return 0xFDE9 unless params[:utf8].nil?
1094
+ properties.each do |property|
1095
+ next unless params.has_key?(property.to_sym)
1096
+ return 0xFDE9 if is_utf8?(params[property.to_sym])
1097
+ end
1098
+ return 0x04E4 # Default codepage, Latin 1.
1099
+ end
1100
+
1101
+ #
1102
+ # Assemble worksheets into a workbook and send the BIFF data to an OLE
1103
+ # storage.
1104
+ #
1105
+ def store_workbook #:nodoc:
1106
+ # Add a default worksheet if non have been added.
1107
+ add_worksheet if @worksheets.empty?
1108
+
1109
+ # Calculate size required for MSO records and update worksheets.
1110
+ calc_mso_sizes
1111
+
1112
+ # Ensure that at least one worksheet has been selected.
1113
+ unless @worksheets.activesheet
1114
+ @worksheets.activesheet = @worksheets.first
1115
+ @worksheets.activesheet.select
1116
+ end
1117
+
1118
+ # Add Workbook globals
1119
+ add_workbook_globals
1120
+
1121
+ # Calculate the offsets required by the BOUNDSHEET records
1122
+ calc_sheet_offsets
1123
+
1124
+ # Add BOUNDSHEET records.
1125
+ @worksheets.each do |sheet|
1126
+ store_boundsheet(sheet)
1127
+ end
1128
+
1129
+ # NOTE: If any records are added between here and EOF the
1130
+ # calc_sheet_offsets() should be updated to include the new length.
1131
+ store_country
1132
+ if @ext_refs.keys.size != 0
1133
+ store_supbook
1134
+ store_externsheet
1135
+ store_names
1136
+ end
1137
+ add_mso_drawing_group
1138
+ store_shared_strings
1139
+ store_extsst
1140
+
1141
+ # End Workbook globals
1142
+ store_eof
1143
+
1144
+ # Store the workbook in an OLE container
1145
+ store_ole_file
1146
+ end
1147
+
1148
+ def add_workbook_globals
1149
+ store_bof(0x0005)
1150
+ store_codepage
1151
+ store_window1
1152
+ store_hideobj
1153
+ store_1904
1154
+ store_all_fonts
1155
+ store_all_num_formats
1156
+ store_all_xfs
1157
+ store_all_styles
1158
+ store_palette
1159
+ end
1160
+
1161
+ #
1162
+ # Store the workbook in an OLE container using the default handler or using
1163
+ # OLE::Storage_Lite if the workbook data is > ~ 7MB.
1164
+ #
1165
+ def store_ole_file #:nodoc:
1166
+ maxsize = 7_087_104
1167
+ # maxsize = 1
1168
+
1169
+ if !add_doc_properties && @biffsize <= maxsize
1170
+ store_through_ole_writer
1171
+ else
1172
+ store_through_ole_storage_lite
1173
+ end
1174
+ end
1175
+
1176
+ def store_through_ole_writer
1177
+ # Write the OLE file using OLEwriter if data <= 7MB
1178
+ ole = OLEWriter.new(@fh_out)
1179
+
1180
+ # Write the BIFF data without the OLE container for testing.
1181
+ ole.biff_only = @biff_only
1182
+
1183
+ # Indicate that we created the filehandle and want to close it.
1184
+ ole.internal_fh = @internal_fh
1185
+
1186
+ ole.set_size(@biffsize)
1187
+ ole.write_header
1188
+
1189
+ while tmp = get_data
1190
+ ole.write(tmp)
1191
+ end
1192
+
1193
+ @worksheets.each do |worksheet|
1194
+ while tmp = worksheet.get_data
1195
+ ole.write(tmp)
1196
+ end
1197
+ end
1198
+
1199
+ return ole.close
1200
+ end
1201
+
1202
+ def store_through_ole_storage_lite
1203
+ # Create the Workbook stream.
1204
+ stream = 'Workbook'.unpack('C*').pack('v*')
1205
+ workbook = OLEStorageLitePPSFile.new(stream)
1206
+ workbook.set_file # use tempfile
1207
+
1208
+ while tmp = get_data
1209
+ workbook.append(tmp)
1210
+ end
1211
+
1212
+ @worksheets.each do |worksheet|
1213
+ while tmp = worksheet.get_data
1214
+ workbook.append(tmp)
1215
+ end
1216
+ end
1217
+
1218
+ streams = []
1219
+ streams << workbook
1220
+
1221
+ # Create the properties streams, if any.
1222
+ if add_doc_properties
1223
+ stream = "\5SummaryInformation".unpack('C*').pack('v*')
1224
+ summary = OLEStorageLitePPSFile.new(stream, @summary)
1225
+ streams << summary
1226
+ stream = "\5DocumentSummaryInformation".unpack('C*').pack('v*')
1227
+ summary = OLEStorageLitePPSFile.new(stream, @doc_summary)
1228
+ streams << summary
1229
+ end
1230
+ # Create the OLE root document and add the substreams.
1231
+ localtime = @localtime.to_a[0..5]
1232
+ localtime[4] -= 1 # month
1233
+ localtime[5] -= 1900
1234
+ ole_root = OLEStorageLitePPSRoot.new(
1235
+ localtime,
1236
+ localtime,
1237
+ streams
1238
+ )
1239
+ ole_root.save(@file)
1240
+
1241
+ # Close the filehandle if it was created internally.
1242
+ return @fh_out.close if @internal_fh != 0
1243
+ end
1244
+
1245
+ #
1246
+ # Calculate Worksheet BOF offsets records for use in the BOUNDSHEET records.
1247
+ #
1248
+ def calc_sheet_offsets #:nodoc:
1249
+ offset = @datasize
1250
+
1251
+ # Add the length of the COUNTRY record
1252
+ offset += 8
1253
+
1254
+ # Add the length of the SST and associated CONTINUEs
1255
+ offset += calculate_shared_string_sizes
1256
+
1257
+ # Add the length of the EXTSST record.
1258
+ offset += calculate_extsst_size(@shared_string_table.str_unique)
1259
+
1260
+ # Add the length of the SUPBOOK, EXTERNSHEET and NAME records
1261
+ offset += calculate_extern_sizes
1262
+
1263
+ # Add the length of the MSODRAWINGGROUP records including an extra 4 bytes
1264
+ # for any CONTINUE headers. See add_mso_drawing_group_continue().
1265
+ mso_size = @mso_size
1266
+ mso_size += 4 * Integer((mso_size -1) / Float(@limit))
1267
+ offset += mso_size
1268
+
1269
+ @worksheets.each do |sheet|
1270
+ offset += BOF + sheet.name.bytesize
1271
+ end
1272
+
1273
+ offset += EOF
1274
+ @worksheets.each do |sheet|
1275
+ sheet.offset = offset
1276
+ sheet.close
1277
+ offset += sheet.datasize
1278
+ end
1279
+
1280
+ @biffsize = offset
1281
+ end
1282
+
1283
+ #
1284
+ # Calculate the MSODRAWINGGROUP sizes and the indexes of the Worksheet
1285
+ # MSODRAWING records.
1286
+ #
1287
+ # In the following SPID is shape id, according to Escher nomenclature.
1288
+ #
1289
+ def calc_mso_sizes #:nodoc:
1290
+ mso_size = 0 # Size of the MSODRAWINGGROUP record
1291
+ max_spid = 1024 # spidMax
1292
+ drawings_saved = 0 # cdgSaved
1293
+ clusters = []
1294
+
1295
+ process_images
1296
+
1297
+ # Add Bstore container size if there are images.
1298
+ mso_size += 8 unless @images_data.empty?
1299
+
1300
+ # Iterate through the worksheets, calculate the MSODRAWINGGROUP parameters
1301
+ # and space required to store the record and the MSODRAWING parameters
1302
+ # required by each worksheet.
1303
+ #
1304
+ @worksheets.each do |sheet|
1305
+ next unless sheet.type == 0x0000
1306
+ next if sheet.num_shapes == 1
1307
+
1308
+ mso_size, drawings_saved, max_spid, start_spid =
1309
+ sheet.push_object_ids(mso_size, drawings_saved, max_spid, start_spid, clusters)
1310
+ end
1311
+
1312
+
1313
+ # Calculate the MSODRAWINGGROUP size if we have stored some shapes.
1314
+ mso_size += 86 if mso_size != 0 # Smallest size is 86+8=94
1315
+
1316
+ shapes_saved = sheets.collect { |sheet| sheet.num_shapes }.
1317
+ select { |num_shapes| num_shapes > 1 }.
1318
+ inject(0) { |result, num_shapes| result + num_shapes }
1319
+
1320
+ @mso_size = mso_size
1321
+ @mso_clusters = [
1322
+ max_spid, clusters.size + 1, shapes_saved,
1323
+ drawings_saved, clusters
1324
+ ]
1325
+ end
1326
+
1327
+ #
1328
+ # We need to process each image in each worksheet and extract information.
1329
+ # Some of this information is stored and used in the Workbook and some is
1330
+ # passed back into each Worksheet. The overall size for the image related
1331
+ # BIFF structures in the Workbook is calculated here.
1332
+ #
1333
+ # MSO size = 8 bytes for bstore_container +
1334
+ # 44 bytes for blip_store_entry +
1335
+ # 25 bytes for blip
1336
+ # = 77 + image size.
1337
+ #
1338
+ def process_images #:nodoc:
1339
+ images_seen = {}
1340
+ image_data = []
1341
+ previous_images = []
1342
+ image_id = 1
1343
+ images_size = 0
1344
+
1345
+ @worksheets.each do |sheet|
1346
+ next unless sheet.type == 0x0000
1347
+ next if sheet.images_size == 0
1348
+
1349
+ num_images = 0
1350
+ image_mso_size = 0
1351
+
1352
+ sheet.images_array.each do |image|
1353
+ num_images += 1
1354
+ unless images_seen[image.filename]
1355
+ # TODO should also match seen images based on checksum.
1356
+ image.id = image_id
1357
+ image.ref_count = 1
1358
+ image.import
1359
+
1360
+ # Also store new data for use in duplicate images.
1361
+ previous_images.push(image)
1362
+
1363
+ # Store information required by the Workbook.
1364
+ image_data.push(image)
1365
+
1366
+ # Keep track of overall data size.
1367
+ images_size += image.size + 61 # Size for bstore container.
1368
+ image_mso_size += image.size + 69 # Size for dgg container.
1369
+
1370
+ images_seen[image.filename] = image_id
1371
+ image_id += 1
1372
+ else
1373
+ # We've processed this file already.
1374
+ index = images_seen[image.filename] -1
1375
+
1376
+ # Increase image reference count.
1377
+ image_data[index].ref_count += 1
1378
+
1379
+ # Add previously calculated data back onto the Worksheet array.
1380
+ # image_id, type, width, height
1381
+ image.id = previous_images[index].id
1382
+ image.type = previous_images[index].type
1383
+ image.width = previous_images[index].width
1384
+ image.height = previous_images[index].height
1385
+ end
1386
+ end
1387
+
1388
+ # Store information required by the Worksheet.
1389
+ sheet.num_images = num_images
1390
+ sheet.image_mso_size = image_mso_size
1391
+ end
1392
+
1393
+ # Store information required by the Workbook.
1394
+ @images_size = images_size
1395
+ @images_data = image_data # Store the data for MSODRAWINGGROUP.
1396
+ end
1397
+
1398
+ #
1399
+ # Store the Excel FONT records.
1400
+ #
1401
+ def store_all_fonts #:nodoc:
1402
+ format = @formats[15] # The default cell format.
1403
+ font = format.get_font
1404
+
1405
+ # Fonts are 0-indexed. According to the SDK there is no index 4,
1406
+ 4.times { append(font) }
1407
+
1408
+ # Add the default fonts for charts and comments. This aren't connected
1409
+ # to XF formats. Note, the font size, and some other properties of
1410
+ # chart fonts are set in the FBI record of the chart.
1411
+
1412
+ # Index 5. Axis numbers.
1413
+ tmp_format = Writeexcel::Format.new(
1414
+ nil,
1415
+ :font_only => 1
1416
+ )
1417
+ append(tmp_format.get_font)
1418
+
1419
+ # Index 6. Series names.
1420
+ tmp_format = Writeexcel::Format.new(
1421
+ nil,
1422
+ :font_only => 1
1423
+ )
1424
+ append(tmp_format.get_font)
1425
+
1426
+ # Index 7. Title.
1427
+ tmp_format = Writeexcel::Format.new(
1428
+ nil,
1429
+ :font_only => 1,
1430
+ :bold => 1
1431
+ )
1432
+ append(tmp_format.get_font)
1433
+
1434
+ # Index 8. Axes.
1435
+ tmp_format = Writeexcel::Format.new(
1436
+ nil,
1437
+ :font_only => 1,
1438
+ :bold => 1
1439
+ )
1440
+ append(tmp_format.get_font)
1441
+
1442
+ # Index 9. Comments.
1443
+ tmp_format = Writeexcel::Format.new(
1444
+ nil,
1445
+ :font_only => 1,
1446
+ :font => 'Tahoma',
1447
+ :size => 8
1448
+ )
1449
+ append(tmp_format.get_font)
1450
+
1451
+ # Iterate through the XF objects and write a FONT record if it isn't the
1452
+ # same as the default FONT and if it hasn't already been used.
1453
+ #
1454
+ fonts = {}
1455
+ index = 10 # The first user defined FONT
1456
+
1457
+ key = format.get_font_key # The default font for cell formats.
1458
+ fonts[key] = 0 # Index of the default font
1459
+
1460
+ # Fonts that are marked as '_font_only' are always stored. These are used
1461
+ # mainly for charts and may not have an associated XF record.
1462
+
1463
+ @formats.each do |fmt|
1464
+ key = fmt.get_font_key
1465
+ if fmt.font_only == 0 and !fonts[key].nil?
1466
+ # FONT has already been used
1467
+ fmt.font_index = fonts[key]
1468
+ else
1469
+ # Add a new FONT record
1470
+
1471
+ if fmt.font_only == 0
1472
+ fonts[key] = index
1473
+ end
1474
+
1475
+ fmt.font_index = index
1476
+ index += 1
1477
+ font = fmt.get_font
1478
+ append(font)
1479
+ end
1480
+ end
1481
+ end
1482
+
1483
+ #
1484
+ # Store user defined numerical formats i.e. FORMAT records
1485
+ #
1486
+ def store_all_num_formats #:nodoc:
1487
+ num_formats = {}
1488
+ index = 164 # User defined FORMAT records start from 0xA4
1489
+
1490
+ # Iterate through the XF objects and write a FORMAT record if it isn't a
1491
+ # built-in format type and if the FORMAT string hasn't already been used.
1492
+ #
1493
+ @formats.each do |format|
1494
+ num_format = format.num_format
1495
+ encoding = format.num_format_enc
1496
+
1497
+ # Check if $num_format is an index to a built-in format.
1498
+ # Also check for a string of zeros, which is a valid format string
1499
+ # but would evaluate to zero.
1500
+ #
1501
+ unless num_format.to_s =~ /^0+\d/
1502
+ next if num_format.to_s =~ /^\d+$/ # built-in
1503
+ end
1504
+
1505
+ if num_formats[num_format]
1506
+ # FORMAT has already been used
1507
+ format.num_format = num_formats[num_format]
1508
+ else
1509
+ # Add a new FORMAT
1510
+ num_formats[num_format] = index
1511
+ format.num_format = index
1512
+ store_num_format(num_format, index, encoding)
1513
+ index += 1
1514
+ end
1515
+ end
1516
+ end
1517
+
1518
+ #
1519
+ # Write all XF records.
1520
+ #
1521
+ def store_all_xfs #:nodoc:
1522
+ @formats.each do |format|
1523
+ xf = format.get_xf
1524
+ append(xf)
1525
+ end
1526
+ end
1527
+
1528
+ #
1529
+ # Write all STYLE records.
1530
+ #
1531
+ def store_all_styles #:nodoc:
1532
+ # Excel adds the built-in styles in alphabetical order.
1533
+ built_ins = [
1534
+ [0x03, 16], # Comma
1535
+ [0x06, 17], # Comma[0]
1536
+ [0x04, 18], # Currency
1537
+ [0x07, 19], # Currency[0]
1538
+ [0x00, 0], # Normal
1539
+ [0x05, 20] # Percent
1540
+
1541
+ # We don't deal with these styles yet.
1542
+ #[0x08, 21], # Hyperlink
1543
+ #[0x02, 8], # ColLevel_n
1544
+ #[0x01, 1], # RowLevel_n
1545
+ ]
1546
+
1547
+ built_ins.each do |aref|
1548
+ type = aref[0]
1549
+ xf_index = aref[1]
1550
+
1551
+ store_style(type, xf_index)
1552
+ end
1553
+ end
1554
+
1555
+ #
1556
+ # Write the NAME record to define the print area and the repeat rows and cols.
1557
+ #
1558
+ def store_names # :nodoc:
1559
+ # Create the user defined names.
1560
+ defined_names.each do |defined_name|
1561
+ store_name(
1562
+ defined_name[:name],
1563
+ defined_name[:encoding],
1564
+ defined_name[:sheet_index],
1565
+ defined_name[:formula]
1566
+ )
1567
+ end
1568
+
1569
+ # Sort the worksheets into alphabetical order by name. This is a
1570
+ # requirement for some non-English language Excel patch levels.
1571
+ sorted_worksheets = @worksheets.sort_by{ |x| x.name }
1572
+
1573
+ # Create the autofilter NAME records
1574
+ create_autofilter_name_records(sorted_worksheets)
1575
+
1576
+ # Create the print area NAME records
1577
+ create_print_area_name_records(sorted_worksheets)
1578
+
1579
+ # Create the print title NAME records
1580
+ create_print_title_name_records(sorted_worksheets)
1581
+ end
1582
+
1583
+ def create_autofilter_name_records(sorted_worksheets) #:nodoc:
1584
+ sorted_worksheets.each do |worksheet|
1585
+ append(*worksheet.autofilter_name_record_short(true))
1586
+ end
1587
+ end
1588
+
1589
+ def create_print_area_name_records(sorted_worksheets) #:nodoc:
1590
+ sorted_worksheets.each do |worksheet|
1591
+ append(*worksheet.print_area_name_record_short)
1592
+ end
1593
+ end
1594
+
1595
+ def create_print_title_name_records(sorted_worksheets) #:nodoc:
1596
+ sorted_worksheets.each do |worksheet|
1597
+ # Determine if row + col, row, col or nothing has been defined
1598
+ # and write the appropriate record
1599
+ #
1600
+ if worksheet.title_range.row_min && worksheet.title_range.col_min
1601
+ # Row and column titles have been defined.
1602
+ # Row title has been defined.
1603
+ append(*worksheet.print_title_name_record_long)
1604
+ elsif worksheet.title_range.row_min
1605
+ # Row title has been defined.
1606
+ append(*worksheet.print_title_name_record_short)
1607
+ elsif worksheet.title_range.col_min
1608
+ # Column title has been defined.
1609
+ append(*worksheet.print_title_name_record_short)
1610
+ else
1611
+ # Nothing left to do
1612
+ end
1613
+ end
1614
+ end
1615
+
1616
+ ###############################################################################
1617
+ ###############################################################################
1618
+ #
1619
+ # BIFF RECORDS
1620
+ #
1621
+
1622
+
1623
+ #
1624
+ # Write Excel BIFF WINDOW1 record.
1625
+ #
1626
+ def store_window1 #:nodoc:
1627
+ record = 0x003D # Record identifier
1628
+ length = 0x0012 # Number of bytes to follow
1629
+
1630
+ x_pos = 0x0000 # Horizontal position of window
1631
+ y_pos = 0x0000 # Vertical position of window
1632
+ dx_win = 0x355C # Width of window
1633
+ dy_win = 0x30ED # Height of window
1634
+
1635
+ grbit = 0x0038 # Option flags
1636
+ ctabsel = @worksheets.selected_count # Number of workbook tabs selected
1637
+ tab_ratio = 0x0258 # Tab to scrollbar ratio
1638
+
1639
+ tab_cur = @worksheets.activesheet_index # Active worksheet
1640
+ tab_first = @worksheets.firstsheet_index # 1st displayed worksheet
1641
+ header = [record, length].pack("vv")
1642
+ data = [
1643
+ x_pos, y_pos, dx_win, dy_win,
1644
+ grbit,
1645
+ tab_cur, tab_first,
1646
+ ctabsel, tab_ratio
1647
+ ].pack("vvvvvvvvv")
1648
+
1649
+ append(header, data)
1650
+ end
1651
+
1652
+ #
1653
+ # Writes Excel BIFF BOUNDSHEET record.
1654
+ #
1655
+ # sheetname # Worksheet name
1656
+ # offset # Location of worksheet BOF
1657
+ # type # Worksheet type
1658
+ # hidden # Worksheet hidden flag
1659
+ # encoding # Sheet name encoding
1660
+ #
1661
+ def store_boundsheet(sheet) #:nodoc:
1662
+ append(sheet.boundsheet)
1663
+ end
1664
+
1665
+ #
1666
+ # Write Excel BIFF STYLE records.
1667
+ #
1668
+ # type # Built-in style
1669
+ # xf_index # Index to style XF
1670
+ #
1671
+ def store_style(type, xf_index) #:nodoc:
1672
+ record = 0x0293 # Record identifier
1673
+ length = 0x0004 # Bytes to follow
1674
+
1675
+ level = 0xff # Outline style level
1676
+
1677
+ xf_index |= 0x8000 # Add flag to indicate built-in style.
1678
+
1679
+ header = [record, length].pack("vv")
1680
+ data = [xf_index, type, level].pack("vCC")
1681
+
1682
+ append(header, data)
1683
+ end
1684
+
1685
+ #
1686
+ # Writes Excel FORMAT record for non "built-in" numerical formats.
1687
+ #
1688
+ # format # Custom format string
1689
+ # ifmt # Format index code
1690
+ # encoding # Char encoding for format string
1691
+ #
1692
+ def store_num_format(format, ifmt, encoding) #:nodoc:
1693
+ format = format.to_s unless format.respond_to?(:to_str)
1694
+ record = 0x041E # Record identifier
1695
+ # length # Number of bytes to follow
1696
+ # Char length of format string
1697
+ cch = format.bytesize
1698
+
1699
+ ruby_19 { format = convert_to_ascii_if_ascii(format) }
1700
+
1701
+ # Handle utf8 strings
1702
+ if is_utf8?(format)
1703
+ format = utf8_to_16be(format)
1704
+ encoding = 1
1705
+ end
1706
+
1707
+ # Handle Unicode format strings.
1708
+ if encoding == 1
1709
+ #raise "Uneven number of bytes in Unicode font name" if cch % 2 != 0
1710
+ #cch /= 2 if encoding != 0
1711
+ format = utf16be_to_16le(format)
1712
+ end
1713
+
1714
+ =begin
1715
+ # Special case to handle Euro symbol, 0x80, in non-Unicode strings.
1716
+ if encoding == 0 and format =~ /\x80/
1717
+ format = format.unpack('C*').pack('v*')
1718
+ format.gsub!(/\x80\x00/, "\xAC\x20")
1719
+ encoding = 1
1720
+ end
1721
+ =end
1722
+ length = 0x05 + format.bytesize
1723
+
1724
+ header = [record, length].pack("vv")
1725
+ data = [ifmt, cch, encoding].pack("vvC")
1726
+
1727
+ append(header, data, format)
1728
+ end
1729
+
1730
+ #
1731
+ # Write Excel 1904 record to indicate the date system in use.
1732
+ #
1733
+ def store_1904 #:nodoc:
1734
+ record = 0x0022 # Record identifier
1735
+ length = 0x0002 # Bytes to follow
1736
+
1737
+ f1904 = @date_1904 ? 1 : 0 # Flag for 1904 date system
1738
+
1739
+ header = [record, length].pack("vv")
1740
+ data = [f1904].pack("v")
1741
+
1742
+ append(header, data)
1743
+ end
1744
+
1745
+ #
1746
+ # Write BIFF record SUPBOOK to indicate that the workbook contains external
1747
+ # references, in our case, formula, print area and print title refs.
1748
+ #
1749
+ def store_supbook #:nodoc:
1750
+ record = 0x01AE # Record identifier
1751
+ length = 0x0004 # Number of bytes to follow
1752
+
1753
+ tabs = @worksheets.size # Number of worksheets
1754
+ virt_path = 0x0401 # Encoded workbook filename
1755
+
1756
+ header = [record, length].pack("vv")
1757
+ data = [tabs, virt_path].pack("vv")
1758
+
1759
+ append(header, data)
1760
+ end
1761
+
1762
+ #
1763
+ # Writes the Excel BIFF EXTERNSHEET record. These references are used by
1764
+ # formulas. TODO NAME record is required to define the print area and the
1765
+ # repeat rows and columns.
1766
+ #
1767
+ def store_externsheet # :nodoc:
1768
+ record = 0x0017 # Record identifier
1769
+
1770
+ # Get the external refs
1771
+ ext = @ext_refs.keys.sort
1772
+
1773
+ # Change the external refs from stringified "1:1" to [1, 1]
1774
+ ext.map! {|e| e.split(/:/).map! {|v| v.to_i} }
1775
+
1776
+ cxti = ext.size # Number of Excel XTI structures
1777
+ rgxti = '' # Array of XTI structures
1778
+
1779
+ # Write the XTI structs
1780
+ ext.each do |e|
1781
+ rgxti += [0, e[0], e[1]].pack("vvv")
1782
+ end
1783
+
1784
+ data = [cxti].pack("v") + rgxti
1785
+ header = [record, data.bytesize].pack("vv")
1786
+
1787
+ append(header, data)
1788
+ end
1789
+
1790
+ #
1791
+ # Store the NAME record used for storing the print area, repeat rows, repeat
1792
+ # columns, autofilters and defined names.
1793
+ #
1794
+ # TODO. This is a more generic version that will replace store_name_short()
1795
+ # and store_name_long().
1796
+ #
1797
+ def store_name(name, encoding, sheet_index, formula) # :nodoc:
1798
+ ruby_19 { formula = convert_to_ascii_if_ascii(formula) }
1799
+
1800
+ record = 0x0018 # Record identifier
1801
+
1802
+ text_length = name.bytesize
1803
+ formula_length = formula.bytesize
1804
+
1805
+ # UTF-16 string length is in characters not bytes.
1806
+ text_length /= 2 if encoding != 0
1807
+
1808
+ grbit = 0x0000 # Option flags
1809
+ shortcut = 0x00 # Keyboard shortcut
1810
+ ixals = 0x0000 # Unused index.
1811
+ menu_length = 0x00 # Length of cust menu text
1812
+ desc_length = 0x00 # Length of description text
1813
+ help_length = 0x00 # Length of help topic text
1814
+ status_length = 0x00 # Length of status bar text
1815
+
1816
+ # Set grbit built-in flag and the hidden flag for autofilters.
1817
+ if text_length == 1
1818
+ grbit = 0x0020 if name.ord == 0x06 # Print area
1819
+ grbit = 0x0020 if name.ord == 0x07 # Print titles
1820
+ grbit = 0x0021 if name.ord == 0x0D # Autofilter
1821
+ end
1822
+
1823
+ data = [grbit].pack("v")
1824
+ data += [shortcut].pack("C")
1825
+ data += [text_length].pack("C")
1826
+ data += [formula_length].pack("v")
1827
+ data += [ixals].pack("v")
1828
+ data += [sheet_index].pack("v")
1829
+ data += [menu_length].pack("C")
1830
+ data += [desc_length].pack("C")
1831
+ data += [help_length].pack("C")
1832
+ data += [status_length].pack("C")
1833
+ data += [encoding].pack("C")
1834
+ data += name
1835
+ data += formula
1836
+
1837
+ header = [record, data.bytesize].pack("vv")
1838
+
1839
+ append(header, data)
1840
+ end
1841
+
1842
+ #
1843
+ # Stores the PALETTE biff record.
1844
+ #
1845
+ def store_palette #:nodoc:
1846
+ record = 0x0092 # Record identifier
1847
+ length = 2 + 4 * @palette.size # Number of bytes to follow
1848
+ ccv = @palette.size # Number of RGB values to follow
1849
+ data = '' # The RGB data
1850
+
1851
+ # Pack the RGB data
1852
+ @palette.each do |p|
1853
+ data += p.pack('CCCC')
1854
+ end
1855
+
1856
+ header = [record, length, ccv].pack("vvv")
1857
+
1858
+ append(header, data)
1859
+ end
1860
+
1861
+ #
1862
+ # Stores the CODEPAGE biff record.
1863
+ #
1864
+ def store_codepage #:nodoc:
1865
+ record = 0x0042 # Record identifier
1866
+ length = 0x0002 # Number of bytes to follow
1867
+ cv = @codepage # The code page
1868
+
1869
+ store_common(record, length, cv)
1870
+ end
1871
+
1872
+ #
1873
+ # Stores the COUNTRY biff record.
1874
+ #
1875
+ def store_country #:nodoc:
1876
+ record = 0x008C # Record identifier
1877
+ length = 0x0004 # Number of bytes to follow
1878
+ country_default = @country
1879
+ country_win_ini = @country
1880
+
1881
+ store_common(record, length, country_default, country_win_ini)
1882
+ end
1883
+
1884
+ #
1885
+ # Stores the HIDEOBJ biff record.
1886
+ #
1887
+ def store_hideobj #:nodoc:
1888
+ record = 0x008D # Record identifier
1889
+ length = 0x0002 # Number of bytes to follow
1890
+ hide = @hideobj ? 1 : 0 # Option to hide objects
1891
+
1892
+ store_common(record, length, hide)
1893
+ end
1894
+
1895
+ def store_common(record, length, *data) #:nodoc:
1896
+ header = [record, length].pack("vv")
1897
+ add_data = [*data].pack("v*")
1898
+
1899
+ append(header, add_data)
1900
+ end
1901
+
1902
+ #
1903
+ # We need to calculate the space required by the SUPBOOK, EXTERNSHEET and NAME
1904
+ # records so that it can be added to the BOUNDSHEET offsets.
1905
+ #
1906
+ def calculate_extern_sizes # :nodoc:
1907
+ ext_refs = @parser.get_ext_sheets
1908
+ length = 0
1909
+ index = 0
1910
+
1911
+ unless defined_names.empty?
1912
+ index = 0
1913
+ key = "#{index}:#{index}"
1914
+
1915
+ add_ext_refs(ext_refs, key) unless ext_refs.has_key?(key)
1916
+ end
1917
+
1918
+ defined_names.each do |defined_name|
1919
+ length += 19 + defined_name[:name].bytesize + defined_name[:formula].bytesize
1920
+ end
1921
+
1922
+ @worksheets.each do |worksheet|
1923
+ key = "#{index}:#{index}"
1924
+ index += 1
1925
+
1926
+ # Add area NAME records
1927
+ #
1928
+ if worksheet.print_range.row_min
1929
+ add_ext_refs(ext_refs, key) unless ext_refs[key]
1930
+ length += 31
1931
+ end
1932
+
1933
+ # Add title NAME records
1934
+ #
1935
+ if worksheet.title_range.row_min && worksheet.title_range.col_min
1936
+ add_ext_refs(ext_refs, key) unless ext_refs[key]
1937
+ length += 46
1938
+ elsif worksheet.title_range.row_min || worksheet.title_range.col_min
1939
+ add_ext_refs(ext_refs, key) unless ext_refs[key]
1940
+ length += 31
1941
+ else
1942
+ # TODO, may need this later.
1943
+ end
1944
+
1945
+ # Add Autofilter NAME records
1946
+ #
1947
+ unless worksheet.filter_count == 0
1948
+ add_ext_refs(ext_refs, key) unless ext_refs[key]
1949
+ length += 31
1950
+ end
1951
+ end
1952
+
1953
+ # Update the ref counts.
1954
+ ext_ref_count = ext_refs.keys.size
1955
+ @ext_refs = ext_refs
1956
+
1957
+ # If there are no external refs then we don't write, SUPBOOK, EXTERNSHEET
1958
+ # and NAME. Therefore the length is 0.
1959
+
1960
+ return length = 0 if ext_ref_count == 0
1961
+
1962
+ # The SUPBOOK record is 8 bytes
1963
+ length += 8
1964
+
1965
+ # The EXTERNSHEET record is 6 bytes + 6 bytes for each external ref
1966
+ length += 6 * (1 + ext_ref_count)
1967
+
1968
+ length
1969
+ end
1970
+
1971
+ def add_ext_refs(ext_refs, key) #:nodoc:
1972
+ ext_refs[key] = ext_refs.keys.size
1973
+ end
1974
+
1975
+ #
1976
+ # Handling of the SST continue blocks is complicated by the need to include an
1977
+ # additional continuation byte depending on whether the string is split between
1978
+ # blocks or whether it starts at the beginning of the block. (There are also
1979
+ # additional complications that will arise later when/if Rich Strings are
1980
+ # supported). As such we cannot use the simple CONTINUE mechanism provided by
1981
+ # the add_continue() method in BIFFwriter.pm. Thus we have to make two passes
1982
+ # through the strings data. The first is to calculate the required block sizes
1983
+ # and the second, in store_shared_strings(), is to write the actual strings.
1984
+ # The first pass through the data is also used to calculate the size of the SST
1985
+ # and CONTINUE records for use in setting the BOUNDSHEET record offsets. The
1986
+ # downside of this is that the same algorithm repeated in store_shared_strings.
1987
+ #
1988
+ def calculate_shared_string_sizes #:nodoc:
1989
+ str_block_sizes = @shared_string_table.block_sizes
1990
+
1991
+ # Calculate the total length of the SST and associated CONTINUEs (if any).
1992
+ # The SST record will have a length even if it contains no strings.
1993
+ # This length is required to set the offsets in the BOUNDSHEET records since
1994
+ # they must be written before the SST records
1995
+ #
1996
+ # when array = [a, b, c] # array not empty?
1997
+ # length = 12 + a + 4 + b + 4 + c = 12 + a + b + c + 4 * (array.size - 1)
1998
+ #
1999
+ length = str_block_sizes.inject(12){|result, item| result + item}
2000
+ length += 4 * (str_block_sizes.size - 1) unless str_block_sizes.empty?
2001
+
2002
+ length
2003
+ end
2004
+
2005
+ def self.split_string_setup(encoding, split_string, continue_limit, written, continue) #:nodoc:
2006
+ # We need to avoid the case where a string is continued in the first
2007
+ # n bytes that contain the string header information.
2008
+ header_length = 3 # Min string + header size -1
2009
+ space_remaining = continue_limit - written - continue
2010
+
2011
+ # Unicode data should only be split on char (2 byte) boundaries.
2012
+ # Therefore, in some cases we need to reduce the amount of available
2013
+ # space by 1 byte to ensure the correct alignment.
2014
+ align = 0
2015
+
2016
+ # Only applies to Unicode strings
2017
+ if encoding == 1
2018
+ # Min string + header size -1
2019
+ header_length = 4
2020
+ if space_remaining > header_length
2021
+ # String contains 3 byte header => split on odd boundary
2022
+ if split_string == 0 and space_remaining % 2 != 1
2023
+ space_remaining -= 1
2024
+ align = 1
2025
+ # Split section without header => split on even boundary
2026
+ elsif split_string != 0 and space_remaining % 2 == 1
2027
+ space_remaining -= 1
2028
+ align = 1
2029
+ end
2030
+ split_string = 1
2031
+ end
2032
+ end
2033
+ [header_length, space_remaining, align, split_string]
2034
+ end
2035
+
2036
+ def_delegator self, :split_string_setup
2037
+
2038
+ #
2039
+ # Write all of the workbooks strings into an indexed array.
2040
+ #
2041
+ # See the comments in calculate_shared_string_sizes() for more information.
2042
+ #
2043
+ # We also use this routine to record the offsets required by the EXTSST table.
2044
+ # In order to do this we first identify the first string in an EXTSST bucket
2045
+ # and then store its global and local offset within the SST table. The offset
2046
+ # occurs wherever the start of the bucket string is written out via append().
2047
+ #
2048
+ def store_shared_strings #:nodoc:
2049
+ record = 0x00FC # Record identifier
2050
+ length = 0x0008 # Number of bytes to follow
2051
+ total = 0x0000
2052
+
2053
+ # Iterate through the strings to calculate the CONTINUE block sizes
2054
+ continue_limit = 8208
2055
+ block_length = 0
2056
+ written = 0
2057
+ continue = 0
2058
+
2059
+ # The SST and CONTINUE block sizes have been pre-calculated by
2060
+ # calculate_shared_string_sizes()
2061
+ block_sizes = @shared_string_table.block_sizes
2062
+
2063
+ # The SST record is required even if it contains no strings. Thus we will
2064
+ # always have a length
2065
+ #
2066
+ if block_sizes.size != 0
2067
+ length = 8 + block_sizes.shift
2068
+ else
2069
+ # No strings
2070
+ length = 8
2071
+ end
2072
+
2073
+ # Initialise variables used to track EXTSST bucket offsets.
2074
+ extsst_str_num = -1
2075
+ sst_block_start = @datasize
2076
+
2077
+ # Write the SST block header information
2078
+ header = [record, length].pack("vv")
2079
+ data = [@shared_string_table.str_total, @shared_string_table.str_unique].pack("VV")
2080
+ append(header, data)
2081
+
2082
+ # Iterate through the strings and write them out
2083
+ return if @shared_string_table.strings.empty?
2084
+ @shared_string_table.strings.each do |string|
2085
+
2086
+ string_length = string.bytesize
2087
+
2088
+ # Check if the string is at the start of a EXTSST bucket.
2089
+ extsst_str_num += 1
2090
+ # Used to track EXTSST bucket offsets.
2091
+ bucket_string = (extsst_str_num % @extsst_bucket_size == 0)
2092
+
2093
+ # Block length is the total length of the strings that will be
2094
+ # written out in a single SST or CONTINUE block.
2095
+ #
2096
+ block_length += string_length
2097
+
2098
+ # We can write the string if it doesn't cross a CONTINUE boundary
2099
+ if block_length < continue_limit
2100
+
2101
+ # Store location of EXTSST bucket string.
2102
+ if bucket_string
2103
+ @extsst_offsets.push([@datasize, @datasize - sst_block_start])
2104
+ bucket_string = false
2105
+ end
2106
+
2107
+ append(string)
2108
+ written += string_length
2109
+ next
2110
+ end
2111
+
2112
+ # Deal with the cases where the next string to be written will exceed
2113
+ # the CONTINUE boundary. If the string is very long it may need to be
2114
+ # written in more than one CONTINUE record.
2115
+ encoding = string.unpack("xx C")[0]
2116
+ split_string = 0
2117
+ while block_length >= continue_limit
2118
+ header_length, space_remaining, align, split_string =
2119
+ split_string_setup(encoding, split_string, continue_limit, written, continue)
2120
+
2121
+ if space_remaining > header_length
2122
+ # Write as much as possible of the string in the current block
2123
+ tmp = string[0, space_remaining]
2124
+
2125
+ # Store location of EXTSST bucket string.
2126
+ if bucket_string
2127
+ @extsst_offsets.push([@datasize, @datasize - sst_block_start])
2128
+ bucket_string = false
2129
+ end
2130
+
2131
+ append(tmp)
2132
+
2133
+ # The remainder will be written in the next block(s)
2134
+ string = string[space_remaining .. string.length-1]
2135
+
2136
+ # Reduce the current block length by the amount written
2137
+ block_length -= continue_limit -continue -align
2138
+
2139
+ # If the current string was split then the next CONTINUE block
2140
+ # should have the string continue flag (grbit) set unless the
2141
+ # split string fits exactly into the remaining space.
2142
+ #
2143
+ if block_length > 0
2144
+ continue = 1
2145
+ else
2146
+ continue = 0
2147
+ end
2148
+ else
2149
+ # Not enough space to start the string in the current block
2150
+ block_length -= continue_limit -space_remaining -continue
2151
+ continue = 0
2152
+ end
2153
+
2154
+ # Write the CONTINUE block header
2155
+ if block_sizes.size != 0
2156
+ sst_block_start= @datasize # Reset EXTSST offset.
2157
+
2158
+ record = 0x003C
2159
+ length = block_sizes.shift
2160
+
2161
+ header = [record, length].pack("vv")
2162
+ header += [encoding].pack("C") if continue != 0
2163
+
2164
+ append(header)
2165
+ end
2166
+
2167
+ # If the string (or substr) is small enough we can write it in the
2168
+ # new CONTINUE block. Else, go through the loop again to write it in
2169
+ # one or more CONTINUE blocks
2170
+ #
2171
+ if block_length < continue_limit
2172
+
2173
+ # Store location of EXTSST bucket string.
2174
+ if bucket_string
2175
+ @extsst_offsets.push([@datasize, @datasize - sst_block_start])
2176
+ bucket_string = false
2177
+ end
2178
+ append(string)
2179
+
2180
+ written = block_length
2181
+ else
2182
+ written = 0
2183
+ end
2184
+ end
2185
+ end
2186
+ end
2187
+
2188
+ #
2189
+ # The number of buckets used in the EXTSST is between 0 and 128. The number of
2190
+ # strings per bucket (bucket size) has a minimum value of 8 and a theoretical
2191
+ # maximum of 2^16. For "number of strings" < 1024 there is a constant bucket
2192
+ # size of 8. The following algorithm generates the same size/bucket ratio
2193
+ # as Excel.
2194
+ #
2195
+ def calculate_extsst_size(str_unique) #:nodoc:
2196
+ unique_strings = str_unique
2197
+
2198
+ if unique_strings < 1024
2199
+ bucket_size = 8
2200
+ else
2201
+ bucket_size = 1 + Integer(unique_strings / 128.0)
2202
+ end
2203
+
2204
+ buckets = Integer((unique_strings + bucket_size -1) / Float(bucket_size))
2205
+
2206
+ @extsst_buckets = buckets
2207
+ @extsst_bucket_size = bucket_size
2208
+
2209
+ 6 + 8 * buckets
2210
+ end
2211
+
2212
+ #
2213
+ # Write EXTSST table using the offsets calculated in store_shared_strings().
2214
+ #
2215
+ def store_extsst #:nodoc:
2216
+ offsets = @extsst_offsets
2217
+ bucket_size = @extsst_bucket_size
2218
+
2219
+ record = 0x00FF # Record identifier
2220
+ length = 2 + 8 * offsets.size # Bytes to follow
2221
+
2222
+ header = [record, length].pack('vv')
2223
+ data = [bucket_size].pack('v')
2224
+
2225
+ offsets.each do |offset|
2226
+ data += [offset[0], offset[1], 0].pack('Vvv')
2227
+ end
2228
+
2229
+ append(header, data)
2230
+ end
2231
+
2232
+ #
2233
+ # Methods related to comments and MSO objects.
2234
+ #
2235
+
2236
+ #
2237
+ # Write the MSODRAWINGGROUP record that keeps track of the Escher drawing
2238
+ # objects in the file such as images, comments and filters.
2239
+ #
2240
+ def add_mso_drawing_group #:nodoc:
2241
+ return unless @mso_size != 0
2242
+
2243
+ record = 0x00EB # Record identifier
2244
+ length = 0x0000 # Number of bytes to follow
2245
+
2246
+ data = store_mso_dgg_container
2247
+ data += store_mso_dgg(*@mso_clusters)
2248
+ data += store_mso_bstore_container
2249
+ @images_data.each do |image|
2250
+ data += store_mso_images(image)
2251
+ end
2252
+ data += store_mso_opt
2253
+ data += store_mso_split_menu_colors
2254
+
2255
+ length = data.bytesize
2256
+ header = [record, length].pack("vv")
2257
+
2258
+ add_mso_drawing_group_continue(header + data)
2259
+
2260
+ header + data # For testing only.
2261
+ end
2262
+
2263
+ #
2264
+ # See first BIFFwriter#add_continue() method.
2265
+ #
2266
+ # Add specialised CONTINUE headers to large MSODRAWINGGROUP data block.
2267
+ # We use the Excel 97 max block size of 8228 - 4 bytes for the header = 8224.
2268
+ #
2269
+ # The structure depends on the size of the data block:
2270
+ #
2271
+ # Case 1: <= 8224 bytes 1 MSODRAWINGGROUP
2272
+ # Case 2: <= 2*8224 bytes 1 MSODRAWINGGROUP + 1 CONTINUE
2273
+ # Case 3: > 2*8224 bytes 2 MSODRAWINGGROUP + n CONTINUE
2274
+ #
2275
+ def add_mso_drawing_group_continue(data) #:nodoc:
2276
+ limit = 8228 -4
2277
+ mso_group = 0x00EB # Record identifier
2278
+ continue = 0x003C # Record identifier
2279
+ block_count = 1
2280
+
2281
+ # Ignore the base class add_continue() method.
2282
+ @ignore_continue = true
2283
+
2284
+ # Case 1 above. Just return the data as it is.
2285
+ if data.bytesize <= limit
2286
+ append(data)
2287
+ return
2288
+ end
2289
+
2290
+ # Change length field of the first MSODRAWINGGROUP block. Case 2 and 3.
2291
+ tmp, data = devide_string(data, limit + 4)
2292
+ tmp[2, 2] = [limit].pack('v')
2293
+ append(tmp)
2294
+
2295
+ # Add MSODRAWINGGROUP and CONTINUE blocks for Case 3 above.
2296
+ while data.bytesize > limit
2297
+ if block_count == 1
2298
+ # Add extra MSODRAWINGGROUP block header.
2299
+ header = [mso_group, limit].pack("vv")
2300
+ block_count += 1
2301
+ else
2302
+ # Add normal CONTINUE header.
2303
+ header = [continue, limit].pack("vv")
2304
+ end
2305
+
2306
+ tmp, data = devide_string(data, limit)
2307
+ append(header, tmp)
2308
+ end
2309
+
2310
+ # Last CONTINUE block for remaining data. Case 2 and 3 above.
2311
+ header = [continue, data.bytesize].pack("vv")
2312
+ append(header, data)
2313
+
2314
+ # Turn the base class add_continue() method back on.
2315
+ @ignore_continue = false
2316
+ end
2317
+
2318
+ def devide_string(string, nth) #:nodoc:
2319
+ first_string = string[0, nth]
2320
+ latter_string = string[nth, string.size - nth]
2321
+ [first_string, latter_string]
2322
+ end
2323
+
2324
+ #
2325
+ # Write the Escher DggContainer record that is part of MSODRAWINGGROUP.
2326
+ #
2327
+ def store_mso_dgg_container #:nodoc:
2328
+ type = 0xF000
2329
+ version = 15
2330
+ instance = 0
2331
+ data = ''
2332
+ length = @mso_size -12 # -4 (biff header) -8 (for this).
2333
+
2334
+ add_mso_generic(type, version, instance, data, length)
2335
+ end
2336
+
2337
+ #
2338
+ # Write the Escher Dgg record that is part of MSODRAWINGGROUP.
2339
+ #
2340
+ def store_mso_dgg(max_spid, num_clusters, shapes_saved, drawings_saved, clusters) #:nodoc:
2341
+ type = 0xF006
2342
+ version = 0
2343
+ instance = 0
2344
+ data = ''
2345
+ length = nil # Calculate automatically.
2346
+
2347
+ data = [max_spid, num_clusters,
2348
+ shapes_saved, drawings_saved].pack("VVVV")
2349
+
2350
+ clusters.each do |aref|
2351
+ drawing_id = aref[0]
2352
+ shape_ids_used = aref[1]
2353
+
2354
+ data += [drawing_id, shape_ids_used].pack("VV")
2355
+ end
2356
+
2357
+ add_mso_generic(type, version, instance, data, length)
2358
+ end
2359
+
2360
+ #
2361
+ # Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP.
2362
+ #
2363
+ def store_mso_bstore_container #:nodoc:
2364
+ return '' if @images_size == 0
2365
+
2366
+ type = 0xF001
2367
+ version = 15
2368
+ instance = @images_data.size # Number of images.
2369
+ data = ''
2370
+ length = @images_size +8 *instance
2371
+
2372
+ add_mso_generic(type, version, instance, data, length)
2373
+ end
2374
+
2375
+ #
2376
+ # Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP.
2377
+ #
2378
+ def store_mso_images(image)
2379
+ blip_store_entry = store_mso_blip_store_entry(
2380
+ image.ref_count, image.type, image.size, image.checksum1
2381
+ )
2382
+
2383
+ blip = store_mso_blip(
2384
+ image.type, image.data, image.size, image.checksum1, image.checksum2
2385
+ )
2386
+
2387
+ blip_store_entry + blip
2388
+ end
2389
+
2390
+ #
2391
+ # Write the Escher BlipStoreEntry record that is part of MSODRAWINGGROUP.
2392
+ #
2393
+ def store_mso_blip_store_entry(ref_count, image_type, size, checksum1) #:nodoc:
2394
+ type = 0xF007
2395
+ version = 2
2396
+ instance = image_type
2397
+ length = size +61
2398
+ data = [image_type].pack('C') + # Win32
2399
+ [image_type].pack('C') + # Mac
2400
+ [checksum1].pack('H*') + # Uid checksum
2401
+ [0xFF].pack('v') + # Tag
2402
+ [size +25].pack('V') + # Next Blip size
2403
+ [ref_count].pack('V') + # Image ref count
2404
+ [0x00000000].pack('V') + # File offset
2405
+ [0x00].pack('C') + # Usage
2406
+ [0x00].pack('C') + # Name length
2407
+ [0x00].pack('C') + # Unused
2408
+ [0x00].pack('C') # Unused
2409
+
2410
+ add_mso_generic(type, version, instance, data, length)
2411
+ end
2412
+
2413
+ #
2414
+ # Write the Escher Blip record that is part of MSODRAWINGGROUP.
2415
+ #
2416
+ def store_mso_blip(image_type, image_data, size, checksum1, checksum2) #:nodoc:
2417
+ instance = 0x046A if image_type == 5 # JPG
2418
+ instance = 0x06E0 if image_type == 6 # PNG
2419
+ instance = 0x07A9 if image_type == 7 # BMP
2420
+
2421
+ # BMPs contain an extra checksum for the stripped data.
2422
+ if image_type == 7
2423
+ checksum1 = checksum2 + checksum1
2424
+ end
2425
+
2426
+ type = 0xF018 + image_type
2427
+ version = 0x0000
2428
+ length = size +17
2429
+ data = [checksum1].pack('H*') + # Uid checksum
2430
+ [0xFF].pack('C') + # Tag
2431
+ image_data # Image
2432
+
2433
+ add_mso_generic(type, version, instance, data, length)
2434
+ end
2435
+
2436
+ #
2437
+ # Write the Escher Opt record that is part of MSODRAWINGGROUP.
2438
+ #
2439
+ def store_mso_opt #:nodoc:
2440
+ type = 0xF00B
2441
+ version = 3
2442
+ instance = 3
2443
+ data = ''
2444
+ length = 18
2445
+
2446
+ data = ['BF0008000800810109000008C0014000'+'0008'].pack("H*")
2447
+
2448
+ add_mso_generic(type, version, instance, data, length)
2449
+ end
2450
+
2451
+ #
2452
+ # Write the Escher SplitMenuColors record that is part of MSODRAWINGGROUP.
2453
+ #
2454
+ def store_mso_split_menu_colors #:nodoc:
2455
+ type = 0xF11E
2456
+ version = 0
2457
+ instance = 4
2458
+ data = ''
2459
+ length = 16
2460
+
2461
+ data = ['0D0000080C00000817000008F7000010'].pack("H*")
2462
+
2463
+ add_mso_generic(type, version, instance, data, length)
2464
+ end
2465
+
2466
+ def cleanup #:nodoc:
2467
+ super
2468
+ sheets.each { |sheet| sheet.cleanup }
2469
+ end
2470
+
2471
+ def add_doc_properties
2472
+ @add_doc_properties ||= false
2473
+ end
2474
+
2475
+ def defined_names
2476
+ @defined_names
2477
+ end
2478
+ end