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,24 @@
1
+ module Writeexcel
2
+
3
+ class Worksheet < BIFFWriter
4
+ require 'writeexcel/helper'
5
+
6
+ class Outline
7
+ attr_accessor :row_level, :style, :below, :right
8
+ attr_writer :visible
9
+
10
+ def initialize
11
+ @row_level = 0
12
+ @style = 0
13
+ @below = 1
14
+ @right = 1
15
+ @visible = true
16
+ end
17
+
18
+ def visible?
19
+ !!@visible
20
+ end
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,242 @@
1
+ # -*- coding: utf-8 -*-
2
+ ###############################################################################
3
+ #
4
+ # Properties - A module for creating Excel property sets.
5
+ #
6
+ #
7
+ # Used in conjunction with WriteExcel
8
+ #
9
+ # Copyright 2000-2010, John McNamara.
10
+ #
11
+ # original written in Perl by John McNamara
12
+ # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
13
+ #
14
+
15
+ require 'date'
16
+
17
+ ###############################################################################
18
+ #
19
+ # create_summary_property_set().
20
+ #
21
+ # Create the SummaryInformation property set. This is mainly used for the
22
+ # Title, Subject, Author, Keywords, Comments, Last author keywords and the
23
+ # creation date.
24
+ #
25
+ def create_summary_property_set(properties) #:nodoc:
26
+ byte_order = [0xFFFE].pack('v')
27
+ version = [0x0000].pack('v')
28
+ system_id = [0x00020105].pack('V')
29
+ class_id = ['00000000000000000000000000000000'].pack('H*')
30
+ num_property_sets = [0x0001].pack('V')
31
+ format_id = ['E0859FF2F94F6810AB9108002B27B3D9'].pack('H*')
32
+ offset = [0x0030].pack('V')
33
+ num_property = [properties.size].pack('V')
34
+ property_offsets = ''
35
+
36
+ # Create the property set data block and calculate the offsets into it.
37
+ property_data, offsets = pack_property_data(properties)
38
+
39
+ # Create the property type and offsets based on the previous calculation.
40
+ 0.upto(properties.size - 1) do |i|
41
+ property_offsets += [properties[i][0], offsets[i]].pack('VV')
42
+ end
43
+
44
+ # Size of size (4 bytes) + num_property (4 bytes) + the data structures.
45
+ size = 8 + (property_offsets).bytesize + property_data.bytesize
46
+ size = [size].pack('V')
47
+
48
+ byte_order +
49
+ version +
50
+ system_id +
51
+ class_id +
52
+ num_property_sets +
53
+ format_id +
54
+ offset +
55
+ size +
56
+ num_property +
57
+ property_offsets +
58
+ property_data
59
+ end
60
+
61
+
62
+ ###############################################################################
63
+ #
64
+ # Create the DocSummaryInformation property set. This is mainly used for the
65
+ # Manager, Company and Category keywords.
66
+ #
67
+ # The DocSummary also contains a stream for user defined properties. However
68
+ # this is a little arcane and probably not worth the implementation effort.
69
+ #
70
+ def create_doc_summary_property_set(properties) #:nodoc:
71
+ byte_order = [0xFFFE].pack('v')
72
+ version = [0x0000].pack('v')
73
+ system_id = [0x00020105].pack('V')
74
+ class_id = ['00000000000000000000000000000000'].pack('H*')
75
+ num_property_sets = [0x0002].pack('V')
76
+
77
+ format_id_0 = ['02D5CDD59C2E1B10939708002B2CF9AE'].pack('H*')
78
+ format_id_1 = ['05D5CDD59C2E1B10939708002B2CF9AE'].pack('H*')
79
+ offset_0 = [0x0044].pack('V')
80
+ num_property_0 = [properties.size].pack('V')
81
+ property_offsets_0 = ''
82
+
83
+ # Create the property set data block and calculate the offsets into it.
84
+ property_data_0, offsets = pack_property_data(properties)
85
+
86
+ # Create the property type and offsets based on the previous calculation.
87
+ 0.upto(properties.size-1) do |i|
88
+ property_offsets_0 += [properties[i][0], offsets[i]].pack('VV')
89
+ end
90
+
91
+ # Size of size (4 bytes) + num_property (4 bytes) + the data structures.
92
+ data_len = 8 + (property_offsets_0).bytesize + property_data_0.bytesize
93
+ size_0 = [data_len].pack('V')
94
+
95
+ # The second property set offset is at the end of the first property set.
96
+ offset_1 = [0x0044 + data_len].pack('V')
97
+
98
+ # We will use a static property set stream rather than try to generate it.
99
+ property_data_1 = [%w(
100
+ 98 00 00 00 03 00 00 00 00 00 00 00 20 00 00 00
101
+ 01 00 00 00 36 00 00 00 02 00 00 00 3E 00 00 00
102
+ 01 00 00 00 02 00 00 00 0A 00 00 00 5F 50 49 44
103
+ 5F 47 55 49 44 00 02 00 00 00 E4 04 00 00 41 00
104
+ 00 00 4E 00 00 00 7B 00 31 00 36 00 43 00 34 00
105
+ 42 00 38 00 33 00 42 00 2D 00 39 00 36 00 35 00
106
+ 46 00 2D 00 34 00 42 00 32 00 31 00 2D 00 39 00
107
+ 30 00 33 00 44 00 2D 00 39 00 31 00 30 00 46 00
108
+ 41 00 44 00 46 00 41 00 37 00 30 00 31 00 42 00
109
+ 7D 00 00 00 00 00 00 00 2D 00 39 00 30 00 33 00
110
+ ).join('')].pack('H*')
111
+
112
+ byte_order +
113
+ version +
114
+ system_id +
115
+ class_id +
116
+ num_property_sets +
117
+ format_id_0 +
118
+ offset_0 +
119
+ format_id_1 +
120
+ offset_1 +
121
+ size_0 +
122
+ num_property_0 +
123
+ property_offsets_0 +
124
+ property_data_0 +
125
+ property_data_1
126
+ end
127
+
128
+
129
+ ###############################################################################
130
+ #
131
+ # _pack_property_data().
132
+ #
133
+ # Create a packed property set structure. Strings are null terminated and
134
+ # padded to a 4 byte boundary. We also use this function to keep track of the
135
+ # property offsets within the data structure. These offsets are used by the
136
+ # calling functions. Currently we only need to handle 4 property types:
137
+ # VT_I2, VT_LPSTR, VT_FILETIME.
138
+ #
139
+ def pack_property_data(properties, offset = 0) #:nodoc:
140
+ packed_property = ''
141
+ data = ''
142
+ offsets = []
143
+
144
+ # Get the strings codepage from the first property.
145
+ codepage = properties[0][2]
146
+
147
+ # The properties start after 8 bytes for size + num_properties + 8 bytes
148
+ # for each propety type/offset pair.
149
+ offset += 8 * (properties.size + 1)
150
+
151
+ properties.each do |property|
152
+ offsets.push(offset)
153
+
154
+ property_type = property[1]
155
+
156
+ if property_type == 'VT_I2'
157
+ packed_property = pack_VT_I2(property[2])
158
+ elsif property_type == 'VT_LPSTR'
159
+ packed_property = pack_VT_LPSTR(property[2], codepage)
160
+ elsif property_type == 'VT_FILETIME'
161
+ packed_property = pack_VT_FILETIME(property[2])
162
+ else
163
+ raise "Unknown property type: '#{property_type}'\n"
164
+ end
165
+
166
+ offset += packed_property.bytesize
167
+ data += packed_property
168
+ end
169
+
170
+ [data, offsets]
171
+ end
172
+
173
+ ###############################################################################
174
+ #
175
+ # _pack_VT_I2().
176
+ #
177
+ # Pack an OLE property type: VT_I2, 16-bit signed integer.
178
+ #
179
+ def pack_VT_I2(value) #:nodoc:
180
+ type = 0x0002
181
+ data = [type, value].pack('VV')
182
+ end
183
+
184
+ ###############################################################################
185
+ #
186
+ # _pack_VT_LPSTR().
187
+ #
188
+ # Pack an OLE property type: VT_LPSTR, String in the Codepage encoding.
189
+ # The strings are null terminated and padded to a 4 byte boundary.
190
+ #
191
+ def pack_VT_LPSTR(str, codepage) #:nodoc:
192
+ type = 0x001E
193
+ string =
194
+ ruby_18 { "#{str}\0" } ||
195
+ ruby_19 { str.force_encoding('BINARY') + "\0".encode('BINARY') }
196
+
197
+ if codepage == 0x04E4
198
+ # Latin1
199
+ length = string.bytesize
200
+ elsif codepage == 0xFDE9
201
+ # utf8
202
+ length = string.bytesize
203
+ else
204
+ raise "Unknown codepage: #{codepage}\n"
205
+ end
206
+
207
+ # Pack the data.
208
+ data = [type, length].pack('VV')
209
+ data += string
210
+
211
+ # The packed data has to null padded to a 4 byte boundary.
212
+ if (extra = length % 4) != 0
213
+ data += "\0" * (4 - extra)
214
+ end
215
+ data
216
+ end
217
+
218
+ ###############################################################################
219
+ #
220
+ # _pack_VT_FILETIME().
221
+ #
222
+ # Pack an OLE property type: VT_FILETIME.
223
+ #
224
+ def pack_VT_FILETIME(localtime) #:nodoc:
225
+ type = 0x0040
226
+
227
+ epoch = DateTime.new(1601, 1, 1)
228
+
229
+ datetime = DateTime.new(
230
+ localtime.year,
231
+ localtime.mon,
232
+ localtime.mday,
233
+ localtime.hour,
234
+ localtime.min,
235
+ localtime.sec,
236
+ localtime.usec
237
+ )
238
+ bignum = (datetime - epoch) * 86400 * 1e7.to_i
239
+ high, low = bignum.divmod 1 << 32
240
+
241
+ [type].pack('V') + [low, high].pack('V2')
242
+ end
@@ -0,0 +1,153 @@
1
+ class Workbook < BIFFWriter
2
+ require 'writeexcel/properties'
3
+ require 'writeexcel/helper'
4
+
5
+ class SharedString
6
+ attr_reader :string, :str_id
7
+
8
+ def initialize(string, str_id)
9
+ @string, @str_id = string, str_id
10
+ end
11
+ end
12
+
13
+ class SharedStringTable
14
+ attr_reader :str_total
15
+
16
+ def initialize
17
+ @shared_string_table = []
18
+ @string_to_shared_string = {}
19
+ @str_total = 0
20
+ end
21
+
22
+ def has_string?(string)
23
+ !!@string_to_shared_string[string]
24
+ end
25
+
26
+ def <<(string)
27
+ @str_total += 1
28
+ unless has_string?(string)
29
+ shared_string = SharedString.new(string, str_unique)
30
+ @shared_string_table << shared_string
31
+ @string_to_shared_string[string] = shared_string
32
+ end
33
+ id(string)
34
+ end
35
+
36
+ def strings
37
+ @shared_string_table.collect { |shared_string| shared_string.string }
38
+ end
39
+
40
+ def id(string)
41
+ @string_to_shared_string[string].str_id
42
+ end
43
+
44
+ def str_unique
45
+ @shared_string_table.size
46
+ end
47
+
48
+ def block_sizes
49
+ @block_sizes ||= calculate_block_sizes
50
+ end
51
+
52
+ #
53
+ # Handling of the SST continue blocks is complicated by the need to include an
54
+ # additional continuation byte depending on whether the string is split between
55
+ # blocks or whether it starts at the beginning of the block. (There are also
56
+ # additional complications that will arise later when/if Rich Strings are
57
+ # supported). As such we cannot use the simple CONTINUE mechanism provided by
58
+ # the add_continue() method in BIFFwriter.pm. Thus we have to make two passes
59
+ # through the strings data. The first is to calculate the required block sizes
60
+ # and the second, in store_shared_strings(), is to write the actual strings.
61
+ # The first pass through the data is also used to calculate the size of the SST
62
+ # and CONTINUE records for use in setting the BOUNDSHEET record offsets. The
63
+ # downside of this is that the same algorithm repeated in store_shared_strings.
64
+ #
65
+ def calculate_block_sizes
66
+ # Iterate through the strings to calculate the CONTINUE block sizes.
67
+ #
68
+ # The SST blocks requires a specialised CONTINUE block, so we have to
69
+ # ensure that the maximum data block size is less than the limit used by
70
+ # add_continue() in BIFFwriter.pm. For simplicity we use the same size
71
+ # for the SST and CONTINUE records:
72
+ # 8228 : Maximum Excel97 block size
73
+ # -4 : Length of block header
74
+ # -8 : Length of additional SST header information
75
+ # -8 : Arbitrary number to keep within add_continue() limit
76
+ # = 8208
77
+ #
78
+ continue_limit = 8208
79
+ block_length = 0
80
+ written = 0
81
+ block_sizes = []
82
+ continue = 0
83
+
84
+ strings.each do |string|
85
+ string_length = string.bytesize
86
+
87
+ # Block length is the total length of the strings that will be
88
+ # written out in a single SST or CONTINUE block.
89
+ #
90
+ block_length += string_length
91
+
92
+ # We can write the string if it doesn't cross a CONTINUE boundary
93
+ if block_length < continue_limit
94
+ written += string_length
95
+ next
96
+ end
97
+
98
+ # Deal with the cases where the next string to be written will exceed
99
+ # the CONTINUE boundary. If the string is very long it may need to be
100
+ # written in more than one CONTINUE record.
101
+ encoding = string.unpack("xx C")[0]
102
+ split_string = 0
103
+ while block_length >= continue_limit
104
+ header_length, space_remaining, align, split_string =
105
+ Workbook.split_string_setup(encoding, split_string, continue_limit, written, continue)
106
+
107
+ if space_remaining > header_length
108
+ # Write as much as possible of the string in the current block
109
+ written += space_remaining
110
+
111
+ # Reduce the current block length by the amount written
112
+ block_length -= continue_limit -continue -align
113
+
114
+ # Store the max size for this block
115
+ block_sizes.push(continue_limit -align)
116
+
117
+ # If the current string was split then the next CONTINUE block
118
+ # should have the string continue flag (grbit) set unless the
119
+ # split string fits exactly into the remaining space.
120
+ #
121
+ if block_length > 0
122
+ continue = 1
123
+ else
124
+ continue = 0
125
+ end
126
+ else
127
+ # Store the max size for this block
128
+ block_sizes.push(written +continue)
129
+
130
+ # Not enough space to start the string in the current block
131
+ block_length -= continue_limit -space_remaining -continue
132
+ continue = 0
133
+ end
134
+
135
+ # If the string (or substr) is small enough we can write it in the
136
+ # new CONTINUE block. Else, go through the loop again to write it in
137
+ # one or more CONTINUE blocks
138
+ #
139
+ if block_length < continue_limit
140
+ written = block_length
141
+ else
142
+ written = 0
143
+ end
144
+ end
145
+ end
146
+
147
+ # Store the max size for the last block unless it is empty
148
+ block_sizes.push(written +continue) if written +continue != 0
149
+
150
+ block_sizes
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,984 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # OLE::Storage_Lite
4
+ # by Kawai, Takanori (Hippo2000) 2000.11.4, 8, 14
5
+ # This Program is Still ALPHA version.
6
+ #//////////////////////////////////////////////////////////////////////////////
7
+ #
8
+ # converted from CPAN's OLE::Storage_Lite.
9
+ # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
10
+ #
11
+
12
+ require 'tempfile'
13
+ require 'stringio'
14
+
15
+ class OLEStorageLite #:nodoc:
16
+ PPS_TYPE_ROOT = 5
17
+ PPS_TYPE_DIR = 1
18
+ PPS_TYPE_FILE = 2
19
+ DATA_SIZE_SMALL = 0x1000
20
+ LONG_INT_SIZE = 4
21
+ PPS_SIZE = 0x80
22
+
23
+ attr_reader :file
24
+
25
+ def initialize(file = nil)
26
+ @file = file
27
+ end
28
+
29
+ def getPpsTree(data)
30
+ info = _initParse(file)
31
+ info ? _getPpsTree(0, info, data) : nil
32
+ end
33
+
34
+ def getPpsSearch(name, data, icase)
35
+ info = _initParse(file)
36
+ info ? _getPpsSearch(0, info, name, data, icase) : nil
37
+ end
38
+
39
+ def getNthPps(no, data)
40
+ info = _initParse(file)
41
+ info ? _getNthPps(no, info, data) : nil
42
+ end
43
+
44
+ def _initParse(file)
45
+ io = file.respond_to?(:to_str) ? open(file, 'rb') : file
46
+ _getHeaderInfo(io)
47
+ end
48
+ private :_initParse
49
+
50
+ def _getPpsTree(no, info, data, done)
51
+ if done
52
+ return [] if done.include?(no)
53
+ else
54
+ done = []
55
+ end
56
+ done << no
57
+
58
+ rootblock = info[:root_start]
59
+
60
+ #1. Get Information about itself
61
+ pps = _getNthPps(no, info, data)
62
+
63
+ #2. Child
64
+ if pps.dir_pps != 0xFFFFFFFF
65
+ pps.child = _getPpsTree(pps.dir_pps, info, data, done)
66
+ else
67
+ pps.child = nil
68
+ end
69
+
70
+ #3. Previous,Next PPSs
71
+ list = []
72
+ list << _getPpsTree(pps.prev_pps, info, data, done) if pps.prev_pps != 0xFFFFFFFF
73
+ list << pps
74
+ list << _getPpsTree(pps.next_pps, info, data, done) if pps.next_pps != 0xFFFFFFFF
75
+ end
76
+ private :_getPpsTree
77
+
78
+ def _getPpsSearch(no, info, name, data, icase, done = nil)
79
+ rootblock = info[:root_start]
80
+ #1. Check it self
81
+ if done
82
+ return [] if done.include?(no)
83
+ else
84
+ done = []
85
+ end
86
+ done << no
87
+ pps = _getNthPps(no, info, nil)
88
+
89
+ re = Regexp.new("^\Q#{pps.name}\E$", Regexp::IGNORECASE)
90
+ if (icase && !name.select { |v| v =~ re }.empty?) || name.include?(pps.name)
91
+ pps = _getNthPps(no, info, data) if data
92
+ res = [pps]
93
+ else
94
+ res = []
95
+ end
96
+
97
+ #2. Check Child, Previous, Next PPSs
98
+ res +=
99
+ _getPpsSearch(pps.dir_pps, info, name, data, icase, done) if pps.dir_pps != 0xFFFFFFFF
100
+ res +=
101
+ _getPpsSearch(pps.prev_pps, info, name, data, icase, done) if pps.prev_pps != 0xFFFFFFFF
102
+ res +=
103
+ _getPpsSearch(pps.next_pps, info, name, data, icase, done) if pps.next_pps != 0xFFFFFFFF
104
+ res
105
+ end
106
+ private :_getPpsSearch
107
+
108
+ def _getHeaderInfo(io)
109
+ info = { :fileh => io }
110
+
111
+ #0. Check ID
112
+ info[:fileh].seek(0, 0)
113
+ return nil unless info[:fileh].read(8) == "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
114
+
115
+ # BIG BLOCK SIZE
116
+ val = _getInfoFromFile(info[:fileh], 0x1E, 2, "v")
117
+ return nil if val.nil?
118
+ info[:big_block_size] = 2 ** val
119
+
120
+ # SMALL BLOCK SIZE
121
+ val = _getInfoFromFile(info[:fileh], 0x20, 2, "v")
122
+ return nil if val.nil?
123
+ info[:small_block_size] = 2 ** val
124
+
125
+ # BDB Count
126
+ val = _getInfoFromFile(info[:fileh], 0x2C, 4, "V")
127
+ return nil if val.nil?
128
+ info[:bdb_count] = val
129
+
130
+ # START BLOCK
131
+ val = _getInfoFromFile(info[:fileh], 0x30, 4, "V")
132
+ return nil if val.nil?
133
+ info[:root_start] = val
134
+
135
+ # SMALL BD START
136
+ val = _getInfoFromFile(info[:fileh], 0x3C, 4, "V")
137
+ return nil if val.nil?
138
+ info[:sbd_start] = val
139
+
140
+ # SMALL BD COUNT
141
+ val = _getInfoFromFile(info[:fileh], 0x40, 4, "V")
142
+ return nil if val.nil?
143
+ info[:sbd_count] = val
144
+
145
+ # EXTRA BBD START
146
+ val = _getInfoFromFile(info[:fileh], 0x44, 4, "V")
147
+ return nil if val.nil?
148
+ info[:extra_bbd_start] = val
149
+
150
+ # EXTRA BBD COUNT
151
+ val = _getInfoFromFile(info[:fileh], 0x48, 4, "V")
152
+ return nil if val.nil?
153
+ info[:extra_bbd_count] = val
154
+
155
+ #GET BBD INFO
156
+ info[:bbd_info] = _getBbdInfo(info)
157
+
158
+ # GET ROOT PPS
159
+ root = _getNthPps(0, info, nil)
160
+ info[:sb_start] = root.start_block
161
+ info[:sb_size] = root.size
162
+ info
163
+ end
164
+ private :_getHeaderInfo
165
+
166
+ def _getInfoFromFile(io, pos, len, fmt)
167
+ io.seek(pos, 0)
168
+ str = io.read(len)
169
+ if str.bytesize != len
170
+ nil
171
+ else
172
+ str.unpack(fmt)[0]
173
+ end
174
+ end
175
+ private :_getInfoFromFile
176
+
177
+ def _getBbdInfo(info)
178
+ bdlist = []
179
+ iBdbCnt = info[:bdb_count]
180
+ i1stCnt = (info[:big_block_size] - 0x4C) / LONG_INT_SIZE
181
+ iBdlCnt = info[:big_block_size] / LONG_INT_SIZE - 1
182
+
183
+ #1. 1st BDlist
184
+ info[:fileh].seek(0x4C, 0)
185
+ iGetCnt = iBdbCnt < i1stCnt ? iBdbCnt : i1stCnt
186
+ str = info[:fileh].read(LONG_INT_SIZE * iGetCnt)
187
+ bdlist += str.unpack("V#{iGetCnt}")
188
+ iBdbCnt -= iGetCnt
189
+
190
+ #2. Extra BDList
191
+ iBlock = info[:extra_bbd_start]
192
+ while iBdbCnt> 0 && _isNormalBlock(iBlock)
193
+ _setFilePos(iBlock, 0, info)
194
+ iGetCnt = iBdbCnt < iBdlCnt ? iBdbCnt : iBdlCnt
195
+ str = info[:fileh].read(LONG_INT_SIZE * iGetCnt)
196
+ bdlist += str.unpack("V#{iGetCnt}")
197
+ iBdbCnt -= iGetCnt
198
+ str = info[:fileh].read(LONG_INT_SIZE)
199
+ iBlock = str.unpack("V")
200
+ end
201
+
202
+ #3.Get BDs
203
+ hBd = Hash.new
204
+ iBlkNo = 0
205
+ iBdCnt = info[:big_block_size] / LONG_INT_SIZE
206
+ bdlist.each do |iBdL|
207
+ _setFilePos(iBdL, 0, info)
208
+ str = info[:fileh].read(info[:big_block_size])
209
+ arr = str.unpack("V#{iBdCnt}")
210
+ (0...iBdCnt).each do |i|
211
+ hBd[iBlkNo] = arr[i] if arr[i] != iBlkNo + 1
212
+ iBlkNo += 1
213
+ end
214
+ end
215
+ hBd
216
+ end
217
+ private :_getBbdInfo
218
+
219
+ def _getNthPps(pos, info, data)
220
+ ppsstart = info[:root_start]
221
+
222
+ basecnt = info[:big_block_size] / PPS_SIZE
223
+ ppsblock = pos / basecnt
224
+ ppspos = pos % basecnt
225
+
226
+ block = _getNthBlockNo(ppsstart, ppsblock, info)
227
+ return nil if block.nil?
228
+
229
+ _setFilePos(block, PPS_SIZE * ppspos, info)
230
+ str = info[:fileh].read(PPS_SIZE)
231
+ return nil if str.nil? || str == ''
232
+ nmsize = str[0x40, 2].unpack('v')[0]
233
+ nmsize -= 2 if nmsize > 2
234
+ nm = str[0, nmsize]
235
+ type = str[0x42, 2].unpack('C')[0]
236
+ ppsprev = str[0x44, LONG_INT_SIZE].unpack('V')[0]
237
+ ppsnext = str[0x48, LONG_INT_SIZE].unpack('V')[0]
238
+ dirpps = str[0x4C, LONG_INT_SIZE].unpack('V')[0]
239
+ time1st =
240
+ (type == PPS_TYPE_ROOT || type == PPS_TYPE_DIR) ? oleData2Local(str[0x64, 8]) : nil
241
+ time2nd =
242
+ (type == PPS_TYPE_ROOT || type == PPS_TYPE_DIR) ? oleData2Local(str[0x6C, 8]) : nil
243
+ start, size = str[0x74, 8].unpack('VV')
244
+ if data
245
+ sdata = _getData(type, start, size, info)
246
+ OLEStorageLitePPS.new(pos, nm, type, ppsprev, ppsnext, dirpps,
247
+ time1st, time2nd, start, size, sdata, nil)
248
+ else
249
+ OLEStorageLitePPS.new(pos, nm, type, ppsprev, ppsnext, dirpps,
250
+ time1st, time2nd, start, size, nil, nil)
251
+ end
252
+ end
253
+ private :_getNthPps
254
+
255
+ def _setFilePos(iBlock, iPos, info)
256
+ info[:fileh].seek((iBlock + 1) * info[:big_block_size] + iPos, 0)
257
+ end
258
+ private :_setFilePos
259
+
260
+ def _getNthBlockNo(stblock, nth, info)
261
+ inext = stblock
262
+ (0...nth).each do |i|
263
+ sv = inext
264
+ inext = _getNextBlockNo(sv, info)
265
+ return nil unless _isNormalBlock(inext)
266
+ end
267
+ inext
268
+ end
269
+ private :_getNthBlockNo
270
+
271
+ def _getData(iType, iBlock, iSize, info)
272
+ if iType == PPS_TYPE_FILE
273
+ if iSize < DATA_SIZE_SMALL
274
+ return _getSmallData(iBlock, iSize, info)
275
+ else
276
+ return _getBigData(iBlock, iSize, info)
277
+ end
278
+ elsif iType == PPS_TYPE_ROOT # Root
279
+ return _getBigData(iBlock, iSize, info)
280
+ elsif iType == PPS_TYPE_DIR # Directory
281
+ return nil
282
+ end
283
+ end
284
+ private :_getData
285
+
286
+ def _getBigData(iBlock, iSize, info)
287
+ return '' unless _isNormalBlock(iBlock)
288
+ iRest = iSize
289
+ sRes = ''
290
+ aKeys = info[:bbd_info].keys.sort
291
+
292
+ while iRest > 0
293
+ aRes = aKeys.select { |key| key >= iBlock }
294
+ iNKey = aRes[0]
295
+ i = iNKey - iBlock
296
+ iNext = info[:bbd_info][iNKey]
297
+ _setFilePos(iBlock, 0, info)
298
+ iGetSize = info[:big_block_size] * (i + 1)
299
+ iGetSize = iRest if iRest < iGetSize
300
+ sRes += info[:fileh].read(iGetSize)
301
+ iRest -= iGetSize
302
+ iBlock = iNext
303
+ end
304
+ sRes
305
+ end
306
+ private :_getBigData
307
+
308
+ def _getNextBlockNo(iBlockNo, info)
309
+ iRes = info[:bbd_info][iBlockNo]
310
+ iRes ? iRes : iBlockNo + 1
311
+ end
312
+ private :_getNextBlockNo
313
+
314
+ def _isNormalBlock(iBlock)
315
+ iBlock < 0xFFFFFFFC ? 1 : nil
316
+ end
317
+ private :_isNormalBlock
318
+
319
+ def _getSmallData(iSmBlock, iSize, info)
320
+ iRest = iSize
321
+ sRes = ''
322
+ while iRest > 0
323
+ _setFilePosSmall(iSmBlock, info)
324
+ sRes += info[:fileh].read(
325
+ iRest >= info[:small_block_size] ? info[:small_block_size] : iRest)
326
+ iRest -= info[:small_block_size]
327
+ iSmBlock = _getNextSmallBlockNo(iSmBlock, info)
328
+ end
329
+ sRes
330
+ end
331
+ private :_getSmallData
332
+
333
+ def _setFilePosSmall(iSmBlock, info)
334
+ iSmStart = info[:sb_start]
335
+ iBaseCnt = info[:big_block_size] / info[:small_block_size]
336
+ iNth = iSmBlock / iBaseCnt
337
+ iPos = iSmBlock % iBaseCnt
338
+
339
+ iBlk = _getNthBlockNo(iSmStart, iNth, info)
340
+ _setFilePos(iBlk, iPos * info[:small_block_size], info)
341
+ end
342
+ private :_setFilePosSmall
343
+
344
+ def _getNextSmallBlockNo(iSmBlock, info)
345
+ iBaseCnt = info[:big_block_size] / LONG_INT_SIZE
346
+ iNth = iSmBlock / iBaseCnt
347
+ iPos = iSmBlock % iBaseCnt
348
+ iBlk = _getNthBlockNo(info[:sbd_start], iNth, info)
349
+ _setFilePos(iBlk, iPos * LONG_INT_SIZE, info)
350
+ info[:fileh].read(LONG_INT_SIZE).unpack('V')
351
+ end
352
+ private :_getNextSmallBlockNo
353
+
354
+ def asc2ucs(str)
355
+ str.split(//).join("\0") + "\0"
356
+ end
357
+
358
+ def ucs2asc(str)
359
+ ary = str.unpack('v*').map { |s| [s].pack('c')}
360
+ ary.join('')
361
+ end
362
+
363
+ #------------------------------------------------------------------------------
364
+ # OLEDate2Local()
365
+ #
366
+ # Convert from a Window FILETIME structure to a localtime array. FILETIME is
367
+ # a 64-bit value representing the number of 100-nanosecond intervals since
368
+ # January 1 1601.
369
+ #
370
+ # We first convert the FILETIME to seconds and then subtract the difference
371
+ # between the 1601 epoch and the 1970 Unix epoch.
372
+ #
373
+ def oleData2Local(oletime)
374
+ # Unpack the FILETIME into high and low longs.
375
+ lo, hi = oletime.unpack('V2')
376
+
377
+ # Convert the longs to a double.
378
+ nanoseconds = hi * 2 ** 32 + lo
379
+
380
+ # Convert the 100 nanosecond units into seconds.
381
+ time = nanoseconds / 1e7
382
+
383
+ # Subtract the number of seconds between the 1601 and 1970 epochs.
384
+ time -= 11644473600
385
+
386
+ # Convert to a localtime (actually gmtime) structure.
387
+ if time >= 1
388
+ ltime = Time.at(time).getgm.to_a[0, 9]
389
+ ltime[4] -= 1 # month
390
+ ltime[5] -= 1900 # year
391
+ ltime[7] -= 1 # past from 1, Jan
392
+ ltime[8] = ltime[8] ? 1 : 0
393
+ ltime
394
+ else
395
+ []
396
+ end
397
+ end
398
+
399
+ #------------------------------------------------------------------------------
400
+ # LocalDate2OLE()
401
+ #
402
+ # Convert from a a localtime array to a Window FILETIME structure. FILETIME is
403
+ # a 64-bit value representing the number of 100-nanosecond intervals since
404
+ # January 1 1601.
405
+ #
406
+ # We first convert the localtime (actually gmtime) to seconds and then add the
407
+ # difference between the 1601 epoch and the 1970 Unix epoch. We convert that to
408
+ # 100 nanosecond units, divide it into high and low longs and return it as a
409
+ # packed 64bit structure.
410
+ #
411
+ def localDate2OLE(localtime)
412
+ return "\x00" * 8 unless localtime
413
+
414
+ # Convert from localtime (actually gmtime) to seconds.
415
+ args = localtime.reverse
416
+ args[0] += 1900 # year
417
+ args[1] += 1 # month
418
+ time = Time.gm(*args)
419
+
420
+ # Add the number of seconds between the 1601 and 1970 epochs.
421
+ time = time.to_i + 11644473600
422
+
423
+ # The FILETIME seconds are in units of 100 nanoseconds.
424
+ nanoseconds = time * 10000000
425
+
426
+ # Pack the total nanoseconds into 64 bits...
427
+ hi, lo = nanoseconds.divmod 1 << 32
428
+
429
+ [lo, hi].pack("VV") # oletime
430
+ end
431
+ end
432
+
433
+ class OLEStorageLitePPS < OLEStorageLite #:nodoc:
434
+ attr_accessor :no, :name, :type, :prev_pps, :next_pps, :dir_pps
435
+ attr_accessor :time_1st, :time_2nd, :start_block, :size, :data, :child
436
+ attr_reader :pps_file
437
+
438
+ def initialize(iNo, sNm, iType, iPrev, iNext, iDir,
439
+ raTime1st, raTime2nd, iStart, iSize, sData, raChild)
440
+ @no = iNo
441
+ @name = sNm
442
+ @type = iType
443
+ @prev_pps = iPrev
444
+ @next_pps = iNext
445
+ @dir_pps = iDir
446
+ @time_1st = raTime1st
447
+ @time_2nd = raTime2nd
448
+ @start_block = iStart
449
+ @size = iSize
450
+ @data = sData
451
+ @child = raChild
452
+ @pps_file = nil
453
+ end
454
+
455
+ def _datalen
456
+ return 0 if @data.nil?
457
+ if @pps_file
458
+ return @pps_file.lstat.size
459
+ else
460
+ return @data.bytesize
461
+ end
462
+ end
463
+ protected :_datalen
464
+
465
+ def _makeSmallData(aList, rh_info)
466
+ file = rh_info[:fileh]
467
+ iSmBlk = 0
468
+ sRes = ''
469
+
470
+ aList.each do |pps|
471
+ #1. Make SBD, small data string
472
+ if pps.type == PPS_TYPE_FILE
473
+ next if pps.size <= 0
474
+ if pps.size < rh_info[:small_size]
475
+ iSmbCnt = pps.size / rh_info[:small_block_size]
476
+ iSmbCnt += 1 if pps.size % rh_info[:small_block_size] > 0
477
+ #1.1 Add to SBD
478
+ 0.upto(iSmbCnt-1-1) do |i|
479
+ file.write([i + iSmBlk+1].pack("V"))
480
+ end
481
+ file.write([-2].pack("V"))
482
+
483
+ #1.2 Add to Data String(this will be written for RootEntry)
484
+ #Check for update
485
+ if pps.pps_file
486
+ pps.pps_file.seek(0) #To The Top
487
+ while sBuff = pps.pps_file.read(4096)
488
+ sRes << sBuff
489
+ end
490
+ else
491
+ sRes << pps.data
492
+ end
493
+ if pps.size % rh_info[:small_block_size] > 0
494
+ cnt = rh_info[:small_block_size] - (pps.size % rh_info[:small_block_size])
495
+ sRes << "\0" * cnt
496
+ end
497
+ #1.3 Set for PPS
498
+ pps.start_block = iSmBlk
499
+ iSmBlk += iSmbCnt
500
+ end
501
+ end
502
+ end
503
+ iSbCnt = rh_info[:big_block_size] / LONG_INT_SIZE
504
+ file.write([-1].pack("V") * (iSbCnt - (iSmBlk % iSbCnt))) if iSmBlk % iSbCnt > 0
505
+ #2. Write SBD with adjusting length for block
506
+ sRes
507
+ end
508
+ private :_makeSmallData
509
+
510
+ def _savePpsWk(rh_info)
511
+ #1. Write PPS
512
+ file = rh_info[:fileh]
513
+ data = [
514
+ @name,
515
+ ("\x00" * (64 - @name.bytesize)), #64
516
+ [@name.bytesize + 2].pack("v"), #66
517
+ [@type].pack("c"), #67
518
+ [0x00].pack("c"), #UK #68
519
+ [@prev_pps].pack("V"), #Prev #72
520
+ [@next_pps].pack("V"), #Next #76
521
+ [@dir_pps].pack("V"), #Dir #80
522
+ "\x00\x09\x02\x00", #84
523
+ "\x00\x00\x00\x00", #88
524
+ "\xc0\x00\x00\x00", #92
525
+ "\x00\x00\x00\x46", #96
526
+ "\x00\x00\x00\x00", #100
527
+ localDate2OLE(@time_1st), #108
528
+ localDate2OLE(@time_2nd) #116
529
+ ]
530
+ file.write(
531
+ ruby_18 { data.join('') } ||
532
+ ruby_19 { data.collect { |d| d.force_encoding(Encoding::BINARY) }.join('') }
533
+ )
534
+ if @start_block != 0
535
+ file.write([@start_block].pack('V'))
536
+ else
537
+ file.write([0].pack('V'))
538
+ end
539
+ if @size != 0 #124
540
+ file.write([@size].pack('V'))
541
+ else
542
+ file.write([0].pack('V'))
543
+ end
544
+ file.write([0].pack('V')) #128
545
+ end
546
+ protected :_savePpsWk
547
+ end
548
+
549
+ class OLEStorageLitePPSRoot < OLEStorageLitePPS #:nodoc:
550
+ def initialize(raTime1st, raTime2nd, raChild)
551
+ super(
552
+ nil,
553
+ asc2ucs('Root Entry'),
554
+ PPS_TYPE_ROOT,
555
+ nil,
556
+ nil,
557
+ nil,
558
+ raTime1st,
559
+ raTime2nd,
560
+ nil,
561
+ nil,
562
+ nil,
563
+ raChild)
564
+ end
565
+
566
+ def save(sFile, bNoAs = nil, rh_info = nil)
567
+ #0.Initial Setting for saving
568
+ rh_info = Hash.new unless rh_info
569
+ if rh_info[:big_block_size]
570
+ rh_info[:big_block_size] = 2 ** adjust2(rh_info[:big_block_size])
571
+ else
572
+ rh_info[:big_block_size] = 2 ** 9
573
+ end
574
+ if rh_info[:small_block_size]
575
+ rh_info[:small_block_size] = 2 ** adjust2(rh_info[:small_block_size])
576
+ else
577
+ rh_info[:small_block_size] = 2 ** 6
578
+ end
579
+ rh_info[:small_size] = 0x1000
580
+ rh_info[:pps_size] = 0x80
581
+
582
+ close_file = true
583
+
584
+ #1.Open File
585
+ #1.1 sFile is Ref of scalar
586
+ if sFile.respond_to?(:to_str)
587
+ rh_info[:fileh] = open(sFile, "wb")
588
+ else
589
+ rh_info[:fileh] = sFile.binmode
590
+ end
591
+
592
+ iBlk = 0
593
+ #1. Make an array of PPS (for Save)
594
+ aList=[]
595
+ if bNoAs
596
+ _savePpsSetPnt2([self], aList, rh_info)
597
+ else
598
+ _savePpsSetPnt([self], aList, rh_info)
599
+ end
600
+ iSBDcnt, iBBcnt, iPPScnt = _calcSize(aList, rh_info)
601
+
602
+ #2.Save Header
603
+ _saveHeader(rh_info, iSBDcnt, iBBcnt, iPPScnt)
604
+
605
+ #3.Make Small Data string (write SBD)
606
+ # Small Datas become RootEntry Data
607
+ @data = _makeSmallData(aList, rh_info)
608
+
609
+ #4. Write BB
610
+ iBBlk = iSBDcnt
611
+ _saveBigData(iBBlk, aList, rh_info)
612
+
613
+ #5. Write PPS
614
+ _savePps(aList, rh_info)
615
+
616
+ #6. Write BD and BDList and Adding Header informations
617
+ _saveBbd(iSBDcnt, iBBcnt, iPPScnt, rh_info)
618
+
619
+ #7.Close File
620
+ rh_info[:fileh].close if close_file
621
+ end
622
+
623
+ def _calcSize(aList, rh_info)
624
+ #0. Calculate Basic Setting
625
+ iSBDcnt, iBBcnt, iPPScnt = [0,0,0]
626
+ iSmallLen = 0
627
+ iSBcnt = 0
628
+ aList.each do |pps|
629
+ if pps.type == PPS_TYPE_FILE
630
+ pps.size = pps._datalen #Mod
631
+ if pps.size < rh_info[:small_size]
632
+ iSBcnt += pps.size / rh_info[:small_block_size]
633
+ iSBcnt += 1 if pps.size % rh_info[:small_block_size] > 0
634
+ else
635
+ iBBcnt += pps.size / rh_info[:big_block_size]
636
+ iBBcnt += 1 if pps.size % rh_info[:big_block_size] > 0
637
+ end
638
+ end
639
+ end
640
+ iSmallLen = iSBcnt * rh_info[:small_block_size]
641
+ iSlCnt = rh_info[:big_block_size] / LONG_INT_SIZE
642
+ iSBDcnt = iSBcnt / iSlCnt
643
+ iSBDcnt += 1 if iSBcnt % iSlCnt > 0
644
+ iBBcnt += iSmallLen / rh_info[:big_block_size]
645
+ iBBcnt += 1 if iSmallLen % rh_info[:big_block_size] > 0
646
+ iCnt = aList.size
647
+ iBdCnt = rh_info[:big_block_size] / PPS_SIZE
648
+ iPPScnt = iCnt / iBdCnt
649
+ iPPScnt += 1 if iCnt % iBdCnt > 0
650
+ [iSBDcnt, iBBcnt, iPPScnt]
651
+ end
652
+ private :_calcSize
653
+
654
+ def _adjust2(i2)
655
+ iWk = Math.log(i2)/Math.log(2)
656
+ iWk > Integer(iWk) ? Integer(iWk) + 1 : iWk
657
+ end
658
+ private :_adjust2
659
+
660
+ def _saveHeader(rh_info, iSBDcnt, iBBcnt, iPPScnt)
661
+ file = rh_info[:fileh]
662
+
663
+ #0. Calculate Basic Setting
664
+ iBlCnt = rh_info[:big_block_size] / LONG_INT_SIZE
665
+ i1stBdL = (rh_info[:big_block_size] - 0x4C) / LONG_INT_SIZE
666
+ i1stBdMax = i1stBdL * iBlCnt - i1stBdL
667
+ iBdExL = 0
668
+ iAll = iBBcnt + iPPScnt + iSBDcnt
669
+ iAllW = iAll
670
+ iBdCntW = iAllW / iBlCnt
671
+ iBdCntW += 1 if iAllW % iBlCnt > 0
672
+ iBdCnt = 0
673
+ #0.1 Calculate BD count
674
+ iBlCnt -= 1 #the BlCnt is reduced in the count of the last sect is used for a pointer the next Bl
675
+ iBBleftover = iAll - i1stBdMax
676
+ if iAll >i1stBdMax
677
+ iBdCnt, iBdExL, iBBleftover = calc_idbcnt_idbexl_ibbleftover(iBBleftover, iBlCnt, iBdCnt, iBdExL)
678
+ end
679
+ iBdCnt += i1stBdL
680
+ #print "iBdCnt = iBdCnt \n"
681
+
682
+ #1.Save Header
683
+ data = [
684
+ "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1",
685
+ "\x00\x00\x00\x00" * 4,
686
+ [0x3b].pack("v"),
687
+ [0x03].pack("v"),
688
+ [-2].pack("v"),
689
+ [9].pack("v"),
690
+ [6].pack("v"),
691
+ [0].pack("v"),
692
+ "\x00\x00\x00\x00" * 2,
693
+ [iBdCnt].pack("V"),
694
+ [iBBcnt+iSBDcnt].pack("V"), #ROOT START
695
+ [0].pack("V"),
696
+ [0x1000].pack("V"),
697
+ [0].pack("V"), #Small Block Depot
698
+ [1].pack("V")
699
+ ]
700
+ file.write(
701
+ ruby_18 { data.join('') } ||
702
+ ruby_19 { data.collect { |d| d.force_encoding(Encoding::BINARY) }.join('') }
703
+ )
704
+ #2. Extra BDList Start, Count
705
+ if iAll <= i1stBdMax
706
+ file.write(
707
+ [-2].pack("V") + #Extra BDList Start
708
+ [0].pack("V") #Extra BDList Count
709
+ )
710
+ else
711
+ file.write(
712
+ [iAll + iBdCnt].pack("V") +
713
+ [iBdExL].pack("V")
714
+ )
715
+ end
716
+
717
+ #3. BDList
718
+ cnt = i1stBdL
719
+ cnt = iBdCnt if iBdCnt < i1stBdL
720
+ 0.upto(cnt-1) do |i|
721
+ file.write([iAll + i].pack("V"))
722
+ end
723
+ file.write([-1].pack("V") * (i1stBdL - cnt)) if cnt < i1stBdL
724
+ end
725
+ private :_saveHeader
726
+
727
+ def _saveBigData(iStBlk, aList, rh_info)
728
+ iRes = 0
729
+ file = rh_info[:fileh]
730
+
731
+ #1.Write Big (ge 0x1000) Data into Block
732
+ aList.each do |pps|
733
+ if pps.type != PPS_TYPE_DIR
734
+ #print "PPS: pps DEF:", defined(pps->{Data}), "\n"
735
+ pps.size = pps._datalen #Mod
736
+ if (pps.size >= rh_info[:small_size]) ||
737
+ ((pps.type == PPS_TYPE_ROOT) && !pps.data.nil?)
738
+ #1.1 Write Data
739
+ #Check for update
740
+ if pps.pps_file
741
+ iLen = 0
742
+ pps.pps_file.seek(0, 0) #To The Top
743
+ while sBuff = pps.pps_file.read(4096)
744
+ iLen += sBuff.bytesize
745
+ file.write(sBuff) #Check for update
746
+ end
747
+ else
748
+ file.write(pps.data)
749
+ end
750
+ if pps.size % rh_info[:big_block_size] > 0
751
+ file.write(
752
+ "\x00" *
753
+ (rh_info[:big_block_size] -
754
+ (pps.size % rh_info[:big_block_size]))
755
+ )
756
+ end
757
+ #1.2 Set For PPS
758
+ pps.start_block = iStBlk
759
+ iStBlk += pps.size / rh_info[:big_block_size]
760
+ iStBlk += 1 if pps.size % rh_info[:big_block_size] > 0
761
+ end
762
+ end
763
+ end
764
+ end
765
+
766
+ def _savePps(aList, rh_info)
767
+ #0. Initial
768
+ file = rh_info[:fileh]
769
+ #2. Save PPS
770
+ aList.each do |oItem|
771
+ oItem._savePpsWk(rh_info)
772
+ end
773
+ #3. Adjust for Block
774
+ iCnt = aList.size
775
+ iBCnt = rh_info[:big_block_size] / rh_info[:pps_size]
776
+ if iCnt % iBCnt > 0
777
+ file.write("\x00" * ((iBCnt - (iCnt % iBCnt)) * rh_info[:pps_size]))
778
+ end
779
+ (iCnt / iBCnt) + ((iCnt % iBCnt) > 0 ? 1: 0)
780
+ end
781
+ private :_savePps
782
+
783
+ def _savePpsSetPnt(pps_array, aList, rh_info)
784
+ #1. make Array as Children-Relations
785
+ #1.1 if No Children
786
+ if pps_array.nil? || pps_array.size == 0
787
+ return 0xFFFFFFFF
788
+ #1.2 Just Only one
789
+ elsif pps_array.size == 1
790
+ aList << pps_array[0]
791
+ pps_array[0].no = aList.size - 1
792
+ pps_array[0].prev_pps = 0xFFFFFFFF
793
+ pps_array[0].next_pps = 0xFFFFFFFF
794
+ pps_array[0].dir_pps = _savePpsSetPnt(pps_array[0].child, aList, rh_info)
795
+ return pps_array[0].no
796
+ #1.3 Array
797
+ else
798
+ iCnt = pps_array.size
799
+ #1.3.1 Define Center
800
+ iPos = Integer(iCnt / 2.0) #$iCnt
801
+
802
+ aList.push(pps_array[iPos])
803
+ pps_array[iPos].no = aList.size - 1
804
+
805
+ aWk = pps_array.dup
806
+ #1.3.2 Devide a array into Previous,Next
807
+ aPrev = aWk[0, iPos]
808
+ aWk[0..iPos-1] = nil
809
+ aNext = aWk[1, iCnt - iPos - 1]
810
+ aWk[1..(1 + iCnt - iPos -1 -1)] = nil
811
+ pps_array[iPos].prev_pps = _savePpsSetPnt(aPrev, aList, rh_info)
812
+ pps_array[iPos].next_pps = _savePpsSetPnt(aNext, aList, rh_info)
813
+ pps_array[iPos].dir_pps = _savePpsSetPnt(pps_array[iPos].child, aList, rh_info)
814
+ return pps_array[iPos].no
815
+ end
816
+ end
817
+ private :_savePpsSetPnt
818
+
819
+ def _savePpsSetPnt2(pps_array, aList, rh_info)
820
+ #1. make Array as Children-Relations
821
+ #1.1 if No Children
822
+ if pps_array.nil? || pps_array.size == 0
823
+ return 0xFFFFFFFF
824
+ #1.2 Just Only one
825
+ elsif pps_array.size == 1
826
+ aList << pps_array[0]
827
+ pps_array[0].no = aList.size - 1
828
+ pps_array[0].prev_pps = 0xFFFFFFFF
829
+ pps_array[0].next_pps = 0xFFFFFFFF
830
+ pps_array[0].dir_pps = _savePpsSetPnt2(pps_array[0].child, aList, rh_info)
831
+ return pps_array[0].no
832
+ #1.3 Array
833
+ else
834
+ iCnt = pps_array.size
835
+ #1.3.1 Define Center
836
+ iPos = 0 #int($iCnt/ 2); #$iCnt
837
+
838
+ aWk = pps_array.dup
839
+ aPrev = aWk[1, 1]
840
+ aWk[1..1] = nil
841
+ aNext = aWk[1..aWk.size] #, $iCnt - $iPos -1);
842
+ pps_array[iPos].prev_pps = _savePpsSetPnt2(pps_array, aList, rh_info)
843
+ aList.push(pps_array[iPos])
844
+ pps_array[iPos].no = aList.size
845
+
846
+ #1.3.2 Devide a array into Previous,Next
847
+ pps_array[iPos].next_pps = _savePpsSetPnt2(aNext, aList, rh_info)
848
+ pps_array[iPos].dir_pps = _savePpsSetPnt2(pps_array[iPos].child, aList, rh_info)
849
+ return pps_array[iPos].no
850
+ end
851
+ end
852
+ private :_savePpsSetPnt2
853
+
854
+ def _saveBbd(iSbdSize, iBsize, iPpsCnt, rh_info)
855
+ file = rh_info[:fileh]
856
+ #0. Calculate Basic Setting
857
+ iBbCnt = rh_info[:big_block_size] / LONG_INT_SIZE
858
+ iBlCnt = iBbCnt - 1
859
+ i1stBdL = (rh_info[:big_block_size] - 0x4C) / LONG_INT_SIZE
860
+ i1stBdMax = i1stBdL * iBbCnt - i1stBdL
861
+ iBdExL = 0
862
+ iAll = iBsize + iPpsCnt + iSbdSize
863
+ iAllW = iAll
864
+ iBdCntW = iAllW / iBbCnt
865
+ iBdCntW += 1 if iAllW % iBbCnt > 0
866
+ iBdCnt = 0
867
+ #0.1 Calculate BD count
868
+ iBBleftover = iAll - i1stBdMax
869
+ if iAll >i1stBdMax
870
+ iBdCnt, iBdExL, iBBleftover = calc_idbcnt_idbexl_ibbleftover(iBBleftover, iBlCnt, iBdCnt, iBdExL)
871
+ end
872
+ iAllW += iBdExL
873
+ iBdCnt += i1stBdL
874
+ #print "iBdCnt = iBdCnt \n"
875
+
876
+ #1. Making BD
877
+ #1.1 Set for SBD
878
+ if iSbdSize > 0
879
+ 0.upto(iSbdSize-1-1) do |i|
880
+ file.write([i + 1].pack('V'))
881
+ end
882
+ file.write([-2].pack('V'))
883
+ end
884
+ #1.2 Set for B
885
+ 0.upto(iBsize-1-1) do |i|
886
+ file.write([i + iSbdSize + 1].pack('V'))
887
+ end
888
+ file.write([-2].pack('V'))
889
+
890
+ #1.3 Set for PPS
891
+ 0.upto(iPpsCnt-1-1) do |i|
892
+ file.write([i+iSbdSize+iBsize+1].pack("V"))
893
+ end
894
+ file.write([-2].pack('V'))
895
+ #1.4 Set for BBD itself ( 0xFFFFFFFD : BBD)
896
+ 0.upto(iBdCnt-1) do |i|
897
+ file.write([0xFFFFFFFD].pack("V"))
898
+ end
899
+ #1.5 Set for ExtraBDList
900
+ 0.upto(iBdExL-1) do |i|
901
+ file.write([0xFFFFFFFC].pack("V"))
902
+ end
903
+ #1.6 Adjust for Block
904
+ if (iAllW + iBdCnt) % iBbCnt > 0
905
+ file.write([-1].pack('V') * (iBbCnt - ((iAllW + iBdCnt) % iBbCnt)))
906
+ end
907
+ #2.Extra BDList
908
+ if iBdCnt > i1stBdL
909
+ iN = 0
910
+ iNb = 0
911
+ i1stBdL.upto(iBdCnt-1) do |i|
912
+ if iN >= iBbCnt-1
913
+ iN = 0
914
+ iNb += 1
915
+ file.write([iAll+iBdCnt+iNb].pack("V"))
916
+ end
917
+ file.write([iBsize+iSbdSize+iPpsCnt+i].pack("V"))
918
+ iN += 1
919
+ end
920
+ if (iBdCnt-i1stBdL) % (iBbCnt-1) > 0
921
+ file.write([-1].pack("V") * ((iBbCnt-1) - ((iBdCnt-i1stBdL) % (iBbCnt-1))))
922
+ end
923
+ file.write([-2].pack('V'))
924
+ end
925
+ end
926
+
927
+ def calc_idbcnt_idbexl_ibbleftover(iBBleftover, iBlCnt, iBdCnt, iBdExL)
928
+ while true
929
+ iBdCnt = iBBleftover / iBlCnt
930
+ iBdCnt += 1 if iBBleftover % iBlCnt > 0
931
+ iBdExL = iBdCnt / iBlCnt
932
+ iBdExL += 1 if iBdCnt % iBlCnt > 0
933
+ iBBleftover += iBdExL
934
+ break if iBdCnt == iBBleftover / iBlCnt + (iBBleftover % iBlCnt > 0 ? 1 : 0)
935
+ end
936
+ [iBdCnt, iBdExL, iBBleftover]
937
+ end
938
+ private :calc_idbcnt_idbexl_ibbleftover
939
+ end
940
+
941
+ class OLEStorageLitePPSFile < OLEStorageLitePPS #:nodoc:
942
+ def initialize(sNm, data = '')
943
+ super(
944
+ nil,
945
+ sNm || '',
946
+ PPS_TYPE_FILE,
947
+ nil,
948
+ nil,
949
+ nil,
950
+ nil,
951
+ nil,
952
+ nil,
953
+ nil,
954
+ data || '',
955
+ nil
956
+ )
957
+ end
958
+
959
+ def set_file(sFile = '')
960
+ if sFile.nil? or sFile == ''
961
+ @pps_file = Tempfile.new('OLEStorageLitePPSFile')
962
+ elsif sFile.respond_to?(:write)
963
+ @pps_file = sFile
964
+ elsif sFile.respond_to?(:to_str)
965
+ #File Name
966
+ @pps_file = open(sFile, "r+")
967
+ return nil unless @pps_file
968
+ else
969
+ return nil
970
+ end
971
+ @pps_file.seek(0, IO::SEEK_END)
972
+ @pps_file.binmode
973
+ end
974
+
975
+ def append (data)
976
+ return if data.nil?
977
+ if @pps_file
978
+ @pps_file << data
979
+ @pps_file.flush
980
+ else
981
+ @data << data
982
+ end
983
+ end
984
+ end