ricardoo27-writeexcel 0.6.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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