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.
- data/.document +5 -0
- data/.gitattributes +1 -0
- data/README.rdoc +136 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/charts/chartex.rb +316 -0
- data/charts/demo1.rb +46 -0
- data/charts/demo101.bin +0 -0
- data/charts/demo2.rb +65 -0
- data/charts/demo201.bin +0 -0
- data/charts/demo3.rb +117 -0
- data/charts/demo301.bin +0 -0
- data/charts/demo4.rb +119 -0
- data/charts/demo401.bin +0 -0
- data/charts/demo5.rb +48 -0
- data/charts/demo501.bin +0 -0
- data/examples/a_simple.rb +43 -0
- data/examples/autofilter.rb +265 -0
- data/examples/bigfile.rb +30 -0
- data/examples/chart_area.rb +121 -0
- data/examples/chart_bar.rb +120 -0
- data/examples/chart_column.rb +120 -0
- data/examples/chart_line.rb +120 -0
- data/examples/chart_pie.rb +108 -0
- data/examples/chart_scatter.rb +121 -0
- data/examples/chart_stock.rb +148 -0
- data/examples/chess.rb +142 -0
- data/examples/colors.rb +129 -0
- data/examples/comments1.rb +27 -0
- data/examples/comments2.rb +352 -0
- data/examples/copyformat.rb +52 -0
- data/examples/data_validate.rb +279 -0
- data/examples/date_time.rb +87 -0
- data/examples/defined_name.rb +32 -0
- data/examples/demo.rb +124 -0
- data/examples/diag_border.rb +36 -0
- data/examples/formats.rb +490 -0
- data/examples/formula_result.rb +30 -0
- data/examples/header.rb +137 -0
- data/examples/hide_sheet.rb +29 -0
- data/examples/hyperlink.rb +43 -0
- data/examples/images.rb +63 -0
- data/examples/indent.rb +31 -0
- data/examples/merge1.rb +40 -0
- data/examples/merge2.rb +45 -0
- data/examples/merge3.rb +66 -0
- data/examples/merge4.rb +83 -0
- data/examples/merge5.rb +80 -0
- data/examples/merge6.rb +67 -0
- data/examples/outline.rb +255 -0
- data/examples/outline_collapsed.rb +209 -0
- data/examples/panes.rb +113 -0
- data/examples/password_protection.rb +33 -0
- data/examples/properties.rb +34 -0
- data/examples/properties_jp.rb +33 -0
- data/examples/protection.rb +47 -0
- data/examples/regions.rb +53 -0
- data/examples/repeat.rb +43 -0
- data/examples/republic.png +0 -0
- data/examples/right_to_left.rb +27 -0
- data/examples/row_wrap.rb +53 -0
- data/examples/set_first_sheet.rb +14 -0
- data/examples/stats.rb +74 -0
- data/examples/stocks.rb +81 -0
- data/examples/store_formula.rb +15 -0
- data/examples/tab_colors.rb +31 -0
- data/examples/utf8.rb +15 -0
- data/examples/write_arrays.rb +83 -0
- data/html/en/doc_en.html +5946 -0
- data/html/images/a_simple.jpg +0 -0
- data/html/images/area1.jpg +0 -0
- data/html/images/bar1.jpg +0 -0
- data/html/images/chart_area.xls +0 -0
- data/html/images/column1.jpg +0 -0
- data/html/images/data_validation.jpg +0 -0
- data/html/images/line1.jpg +0 -0
- data/html/images/pie1.jpg +0 -0
- data/html/images/regions.jpg +0 -0
- data/html/images/scatter1.jpg +0 -0
- data/html/images/stats.jpg +0 -0
- data/html/images/stock1.jpg +0 -0
- data/html/images/stocks.jpg +0 -0
- data/html/index.html +16 -0
- data/html/style.css +433 -0
- data/lib/writeexcel.rb +1159 -0
- data/lib/writeexcel/biffwriter.rb +223 -0
- data/lib/writeexcel/caller_info.rb +12 -0
- data/lib/writeexcel/cell_range.rb +332 -0
- data/lib/writeexcel/chart.rb +1968 -0
- data/lib/writeexcel/charts/area.rb +154 -0
- data/lib/writeexcel/charts/bar.rb +177 -0
- data/lib/writeexcel/charts/column.rb +156 -0
- data/lib/writeexcel/charts/external.rb +66 -0
- data/lib/writeexcel/charts/line.rb +154 -0
- data/lib/writeexcel/charts/pie.rb +169 -0
- data/lib/writeexcel/charts/scatter.rb +192 -0
- data/lib/writeexcel/charts/stock.rb +213 -0
- data/lib/writeexcel/col_info.rb +87 -0
- data/lib/writeexcel/colors.rb +68 -0
- data/lib/writeexcel/comments.rb +460 -0
- data/lib/writeexcel/compatibility.rb +65 -0
- data/lib/writeexcel/convert_date_time.rb +117 -0
- data/lib/writeexcel/data_validations.rb +370 -0
- data/lib/writeexcel/debug_info.rb +41 -0
- data/lib/writeexcel/embedded_chart.rb +35 -0
- data/lib/writeexcel/excelformula.y +139 -0
- data/lib/writeexcel/excelformulaparser.rb +587 -0
- data/lib/writeexcel/format.rb +1575 -0
- data/lib/writeexcel/formula.rb +987 -0
- data/lib/writeexcel/helper.rb +78 -0
- data/lib/writeexcel/image.rb +218 -0
- data/lib/writeexcel/olewriter.rb +305 -0
- data/lib/writeexcel/outline.rb +24 -0
- data/lib/writeexcel/properties.rb +242 -0
- data/lib/writeexcel/shared_string_table.rb +153 -0
- data/lib/writeexcel/storage_lite.rb +984 -0
- data/lib/writeexcel/workbook.rb +2478 -0
- data/lib/writeexcel/worksheet.rb +6925 -0
- data/lib/writeexcel/worksheets.rb +25 -0
- data/lib/writeexcel/write_file.rb +63 -0
- data/test/excelfile/Chart1.xls +0 -0
- data/test/excelfile/Chart2.xls +0 -0
- data/test/excelfile/Chart3.xls +0 -0
- data/test/excelfile/Chart4.xls +0 -0
- data/test/excelfile/Chart5.xls +0 -0
- data/test/helper.rb +31 -0
- data/test/perl_output/Chart1.xls.data +0 -0
- data/test/perl_output/Chart2.xls.data +0 -0
- data/test/perl_output/Chart3.xls.data +0 -0
- data/test/perl_output/Chart4.xls.data +0 -0
- data/test/perl_output/Chart5.xls.data +0 -0
- data/test/perl_output/README +31 -0
- data/test/perl_output/a_simple.xls +0 -0
- data/test/perl_output/autofilter.xls +0 -0
- data/test/perl_output/biff_add_continue_testdata +0 -0
- data/test/perl_output/chart_area.xls +0 -0
- data/test/perl_output/chart_bar.xls +0 -0
- data/test/perl_output/chart_column.xls +0 -0
- data/test/perl_output/chart_line.xls +0 -0
- data/test/perl_output/chess.xls +0 -0
- data/test/perl_output/colors.xls +0 -0
- data/test/perl_output/comments0.xls +0 -0
- data/test/perl_output/comments1.xls +0 -0
- data/test/perl_output/comments2.xls +0 -0
- data/test/perl_output/data_validate.xls +0 -0
- data/test/perl_output/date_time.xls +0 -0
- data/test/perl_output/defined_name.xls +0 -0
- data/test/perl_output/demo.xls +0 -0
- data/test/perl_output/demo101.bin +0 -0
- data/test/perl_output/demo201.bin +0 -0
- data/test/perl_output/demo301.bin +0 -0
- data/test/perl_output/demo401.bin +0 -0
- data/test/perl_output/demo501.bin +0 -0
- data/test/perl_output/diag_border.xls +0 -0
- data/test/perl_output/f_font_biff +0 -0
- data/test/perl_output/f_font_key +1 -0
- data/test/perl_output/f_xf_biff +0 -0
- data/test/perl_output/file_font_biff +0 -0
- data/test/perl_output/file_font_key +1 -0
- data/test/perl_output/file_xf_biff +0 -0
- data/test/perl_output/formula_result.xls +0 -0
- data/test/perl_output/headers.xls +0 -0
- data/test/perl_output/hidden.xls +0 -0
- data/test/perl_output/hide_zero.xls +0 -0
- data/test/perl_output/hyperlink.xls +0 -0
- data/test/perl_output/images.xls +0 -0
- data/test/perl_output/indent.xls +0 -0
- data/test/perl_output/merge1.xls +0 -0
- data/test/perl_output/merge2.xls +0 -0
- data/test/perl_output/merge3.xls +0 -0
- data/test/perl_output/merge4.xls +0 -0
- data/test/perl_output/merge5.xls +0 -0
- data/test/perl_output/merge6.xls +0 -0
- data/test/perl_output/ole_write_header +0 -0
- data/test/perl_output/outline.xls +0 -0
- data/test/perl_output/outline_collapsed.xls +0 -0
- data/test/perl_output/panes.xls +0 -0
- data/test/perl_output/password_protection.xls +0 -0
- data/test/perl_output/protection.xls +0 -0
- data/test/perl_output/regions.xls +0 -0
- data/test/perl_output/right_to_left.xls +0 -0
- data/test/perl_output/set_first_sheet.xls +0 -0
- data/test/perl_output/stats.xls +0 -0
- data/test/perl_output/stocks.xls +0 -0
- data/test/perl_output/store_formula.xls +0 -0
- data/test/perl_output/tab_colors.xls +0 -0
- data/test/perl_output/unicode_cyrillic.xls +0 -0
- data/test/perl_output/utf8.xls +0 -0
- data/test/perl_output/workbook1.xls +0 -0
- data/test/perl_output/workbook2.xls +0 -0
- data/test/perl_output/ws_colinfo +1 -0
- data/test/perl_output/ws_store_colinfo +0 -0
- data/test/perl_output/ws_store_dimensions +0 -0
- data/test/perl_output/ws_store_filtermode +0 -0
- data/test/perl_output/ws_store_filtermode_off +0 -0
- data/test/perl_output/ws_store_filtermode_on +0 -0
- data/test/perl_output/ws_store_selection +0 -0
- data/test/perl_output/ws_store_window2 +1 -0
- data/test/republic.png +0 -0
- data/test/test_00_IEEE_double.rb +13 -0
- data/test/test_01_add_worksheet.rb +10 -0
- data/test/test_02_merge_formats.rb +49 -0
- data/test/test_04_dimensions.rb +388 -0
- data/test/test_05_rows.rb +175 -0
- data/test/test_06_extsst.rb +74 -0
- data/test/test_11_date_time.rb +475 -0
- data/test/test_12_date_only.rb +525 -0
- data/test/test_13_date_seconds.rb +477 -0
- data/test/test_21_escher.rb +624 -0
- data/test/test_22_mso_drawing_group.rb +741 -0
- data/test/test_23_note.rb +57 -0
- data/test/test_24_txo.rb +74 -0
- data/test/test_25_position_object.rb +80 -0
- data/test/test_26_autofilter.rb +309 -0
- data/test/test_27_autofilter.rb +126 -0
- data/test/test_28_autofilter.rb +156 -0
- data/test/test_29_process_jpg.rb +670 -0
- data/test/test_30_validation_dval.rb +74 -0
- data/test/test_31_validation_dv_strings.rb +123 -0
- data/test/test_32_validation_dv_formula.rb +203 -0
- data/test/test_40_property_types.rb +188 -0
- data/test/test_41_properties.rb +235 -0
- data/test/test_42_set_properties.rb +434 -0
- data/test/test_50_name_stored.rb +295 -0
- data/test/test_51_name_print_area.rb +353 -0
- data/test/test_52_name_print_titles.rb +450 -0
- data/test/test_53_autofilter.rb +199 -0
- data/test/test_60_chart_generic.rb +574 -0
- data/test/test_61_chart_subclasses.rb +84 -0
- data/test/test_62_chart_formats.rb +268 -0
- data/test/test_63_chart_area_formats.rb +645 -0
- data/test/test_biff.rb +71 -0
- data/test/test_big_workbook.rb +17 -0
- data/test/test_compatibility.rb +12 -0
- data/test/test_example_match.rb +3246 -0
- data/test/test_format.rb +1189 -0
- data/test/test_formula.rb +61 -0
- data/test/test_ole.rb +102 -0
- data/test/test_storage_lite.rb +116 -0
- data/test/test_workbook.rb +146 -0
- data/test/test_worksheet.rb +106 -0
- data/utils/add_magic_comment.rb +80 -0
- data/writeexcel.gemspec +278 -0
- data/writeexcel.rdoc +1425 -0
- 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
|