ruby-spreadsheet 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/GUIDE.txt +267 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +20 -0
- data/History.txt +307 -0
- data/LICENSE.txt +619 -0
- data/README.txt +91 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/xlsopcodes +18 -0
- data/lib/parseexcel.rb +27 -0
- data/lib/parseexcel/parseexcel.rb +75 -0
- data/lib/parseexcel/parser.rb +11 -0
- data/lib/spreadsheet.rb +79 -0
- data/lib/spreadsheet/column.rb +71 -0
- data/lib/spreadsheet/compatibility.rb +23 -0
- data/lib/spreadsheet/datatypes.rb +110 -0
- data/lib/spreadsheet/encodings.rb +46 -0
- data/lib/spreadsheet/excel.rb +88 -0
- data/lib/spreadsheet/excel/error.rb +26 -0
- data/lib/spreadsheet/excel/internals.rb +386 -0
- data/lib/spreadsheet/excel/internals/biff5.rb +17 -0
- data/lib/spreadsheet/excel/internals/biff8.rb +19 -0
- data/lib/spreadsheet/excel/offset.rb +41 -0
- data/lib/spreadsheet/excel/reader.rb +1173 -0
- data/lib/spreadsheet/excel/reader/biff5.rb +22 -0
- data/lib/spreadsheet/excel/reader/biff8.rb +193 -0
- data/lib/spreadsheet/excel/row.rb +92 -0
- data/lib/spreadsheet/excel/sst_entry.rb +46 -0
- data/lib/spreadsheet/excel/workbook.rb +80 -0
- data/lib/spreadsheet/excel/worksheet.rb +100 -0
- data/lib/spreadsheet/excel/writer.rb +1 -0
- data/lib/spreadsheet/excel/writer/biff8.rb +75 -0
- data/lib/spreadsheet/excel/writer/format.rb +253 -0
- data/lib/spreadsheet/excel/writer/workbook.rb +652 -0
- data/lib/spreadsheet/excel/writer/worksheet.rb +948 -0
- data/lib/spreadsheet/font.rb +92 -0
- data/lib/spreadsheet/format.rb +177 -0
- data/lib/spreadsheet/formula.rb +9 -0
- data/lib/spreadsheet/helpers.rb +11 -0
- data/lib/spreadsheet/link.rb +43 -0
- data/lib/spreadsheet/row.rb +132 -0
- data/lib/spreadsheet/workbook.rb +120 -0
- data/lib/spreadsheet/worksheet.rb +279 -0
- data/lib/spreadsheet/writer.rb +30 -0
- data/ruby-spreadsheet.gemspec +126 -0
- data/test/data/test_changes.xls +0 -0
- data/test/data/test_copy.xls +0 -0
- data/test/data/test_datetime.xls +0 -0
- data/test/data/test_empty.xls +0 -0
- data/test/data/test_formula.xls +0 -0
- data/test/data/test_missing_row.xls +0 -0
- data/test/data/test_version_excel5.xls +0 -0
- data/test/data/test_version_excel95.xls +0 -0
- data/test/data/test_version_excel97.xls +0 -0
- data/test/excel/row.rb +35 -0
- data/test/excel/writer/worksheet.rb +23 -0
- data/test/font.rb +163 -0
- data/test/integration.rb +1281 -0
- data/test/row.rb +33 -0
- data/test/suite.rb +14 -0
- data/test/workbook.rb +21 -0
- data/test/worksheet.rb +80 -0
- metadata +203 -0
@@ -0,0 +1 @@
|
|
1
|
+
require 'spreadsheet/excel/writer/workbook'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spreadsheet/encodings'
|
2
|
+
|
3
|
+
module Spreadsheet
|
4
|
+
module Excel
|
5
|
+
module Writer
|
6
|
+
##
|
7
|
+
# This Module collects writer methods such as unicode_string that are specific
|
8
|
+
# to Biff8. This Module is likely to be expanded as Support for older Versions
|
9
|
+
# of Excel grows and methods get moved here for disambiguation.
|
10
|
+
module Biff8
|
11
|
+
include Spreadsheet::Encodings
|
12
|
+
##
|
13
|
+
# Check whether the string _data_ can be compressed (i.e. every second byte
|
14
|
+
# is a Null-byte) and perform compression.
|
15
|
+
# Returns the data and compression_status (0/1)
|
16
|
+
def compress_unicode_string data
|
17
|
+
compressed = ''
|
18
|
+
expect_null = false
|
19
|
+
data.each_byte do |byte|
|
20
|
+
if expect_null
|
21
|
+
if byte != 0
|
22
|
+
return [data, 1] # 1 => Data consists of wide Chars
|
23
|
+
end
|
24
|
+
expect_null = false
|
25
|
+
else
|
26
|
+
compressed << byte
|
27
|
+
expect_null = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
[compressed, 0] # 0 => Data consists of compressed Chars
|
31
|
+
end
|
32
|
+
##
|
33
|
+
# Encode _string_ into a Biff8 Unicode String. Header and body are encoded
|
34
|
+
# separately by #_unicode_string. This method simply combines the two.
|
35
|
+
def unicode_string string, count_length=1
|
36
|
+
header, data, _ = _unicode_string string, count_length
|
37
|
+
header << data
|
38
|
+
end
|
39
|
+
@@bytesize = RUBY_VERSION >= '1.9' ? :bytesize : :size
|
40
|
+
##
|
41
|
+
# Encode _string_ into a Biff8 Unicode String Header and Body.
|
42
|
+
def _unicode_string string, count_length=1
|
43
|
+
data = internal string
|
44
|
+
size = data.send(@@bytesize) / 2
|
45
|
+
fmt = count_length == 1 ? 'C2' : 'vC'
|
46
|
+
data, wide = compress_unicode_string data
|
47
|
+
opts = wide
|
48
|
+
header = [
|
49
|
+
size, # Length of the string (character count, ln)
|
50
|
+
opts, # Option flags:
|
51
|
+
# Bit Mask Contents
|
52
|
+
# 0 0x01 Character compression (ccompr):
|
53
|
+
# 0 = Compressed (8-bit characters)
|
54
|
+
# 1 = Uncompressed (16-bit characters)
|
55
|
+
# 2 0x04 Asian phonetic settings (phonetic):
|
56
|
+
# 0 = Does not contain Asian phonetic settings
|
57
|
+
# 1 = Contains Asian phonetic settings
|
58
|
+
# 3 0x08 Rich-Text settings (richtext):
|
59
|
+
# 0 = Does not contain Rich-Text settings
|
60
|
+
# 1 = Contains Rich-Text settings
|
61
|
+
#0x00,# (optional, only if richtext=1) Number of Rich-Text
|
62
|
+
# formatting runs (rt)
|
63
|
+
#0x00,# (optional, only if phonetic=1) Size of Asian phonetic
|
64
|
+
# settings block (in bytes, sz)
|
65
|
+
].pack fmt
|
66
|
+
data << '' # (optional, only if richtext=1)
|
67
|
+
# List of rt formatting runs (➜ 3.2)
|
68
|
+
data << '' # (optional, only if phonetic=1)
|
69
|
+
# Asian Phonetic Settings Block (➜ 3.4.2)
|
70
|
+
[header, data, wide]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'spreadsheet/format'
|
3
|
+
require 'spreadsheet/excel/internals'
|
4
|
+
|
5
|
+
module Spreadsheet
|
6
|
+
module Excel
|
7
|
+
module Writer
|
8
|
+
##
|
9
|
+
# This class encapsulates everything that is needed to write an XF record.
|
10
|
+
class Format < DelegateClass Spreadsheet::Format
|
11
|
+
include Spreadsheet::Excel::Internals
|
12
|
+
def Format.boolean *args
|
13
|
+
args.each do |key|
|
14
|
+
define_method key do
|
15
|
+
@format.send("#{key}?") ? 1 : 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def Format.color key, default
|
20
|
+
define_method key do
|
21
|
+
color_code(@format.send(key) || default)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
boolean :hidden, :locked, :merge_range, :shrink, :text_justlast, :text_wrap,
|
25
|
+
:cross_down, :cross_up, :left, :right, :top, :bottom
|
26
|
+
color :left_color, :border
|
27
|
+
color :right_color, :border
|
28
|
+
color :top_color, :border
|
29
|
+
color :bottom_color, :border
|
30
|
+
color :diagonal_color, :border
|
31
|
+
color :pattern_fg_color, :pattern_bg
|
32
|
+
color :pattern_bg_color, :pattern_bg
|
33
|
+
attr_accessor :xf_index
|
34
|
+
attr_reader :format
|
35
|
+
def initialize writer, workbook, format=workbook.default_format, opts={}
|
36
|
+
@opts = { :type => :format }.merge opts
|
37
|
+
@format = format
|
38
|
+
@writer = writer
|
39
|
+
@workbook = workbook
|
40
|
+
super format
|
41
|
+
end
|
42
|
+
def color_code color
|
43
|
+
SEDOC_ROLOC[color]
|
44
|
+
end
|
45
|
+
def font_index
|
46
|
+
@writer.font_index @workbook, font.key
|
47
|
+
end
|
48
|
+
def horizontal_align
|
49
|
+
XF_H_ALIGN.fetch @format.horizontal_align, 0
|
50
|
+
end
|
51
|
+
def num_format
|
52
|
+
@writer.number_format_index @workbook, @format.number_format
|
53
|
+
end
|
54
|
+
def text_direction
|
55
|
+
XF_TEXT_DIRECTION.fetch @format.text_direction, 0
|
56
|
+
end
|
57
|
+
def vertical_align
|
58
|
+
XF_V_ALIGN.fetch @format.vertical_align, 2
|
59
|
+
end
|
60
|
+
def write_op writer, op, *args
|
61
|
+
data = args.join
|
62
|
+
writer.write [op,data.size].pack("v2")
|
63
|
+
writer.write data
|
64
|
+
end
|
65
|
+
def write_xf writer, type=@opts[:type]
|
66
|
+
xf_type = xf_type_prot type
|
67
|
+
data = [
|
68
|
+
font_index, # Index to FONT record (➜ 6.43)
|
69
|
+
num_format, # Index to FORMAT record (➜ 6.45)
|
70
|
+
xf_type, # Bit Mask Contents
|
71
|
+
# 2-0 0x0007 XF_TYPE_PROT – XF type, cell protection
|
72
|
+
# Bit Mask Contents
|
73
|
+
# 0 0x01 1 = Cell is locked
|
74
|
+
# 1 0x02 1 = Formula is hidden
|
75
|
+
# 2 0x04 0 = Cell XF; 1 = Style XF
|
76
|
+
# 15-4 0xfff0 Index to parent style XF
|
77
|
+
# (always 0xfff in style XFs)
|
78
|
+
xf_align, # Bit Mask Contents
|
79
|
+
# 2-0 0x07 XF_HOR_ALIGN – Horizontal alignment
|
80
|
+
# Value Horizontal alignment
|
81
|
+
# 0x00 General
|
82
|
+
# 0x01 Left
|
83
|
+
# 0x02 Centred
|
84
|
+
# 0x03 Right
|
85
|
+
# 0x04 Filled
|
86
|
+
# 0x05 Justified (BIFF4-BIFF8X)
|
87
|
+
# 0x06 Centred across selection
|
88
|
+
# (BIFF4-BIFF8X)
|
89
|
+
# 0x07 Distributed (BIFF8X)
|
90
|
+
# 3 0x08 1 = Text is wrapped at right border
|
91
|
+
# 6-4 0x70 XF_VERT_ALIGN – Vertical alignment
|
92
|
+
# Value Vertical alignment
|
93
|
+
# 0x00 Top
|
94
|
+
# 0x01 Centred
|
95
|
+
# 0x02 Bottom
|
96
|
+
# 0x03 Justified (BIFF5-BIFF8X)
|
97
|
+
# 0x04 Distributed (BIFF8X)
|
98
|
+
xf_rotation, # XF_ROTATION: Text rotation angle
|
99
|
+
# Value Text rotation
|
100
|
+
# 0 Not rotated
|
101
|
+
# 1-90 1 to 90 degrees counterclockwise
|
102
|
+
# 91-180 1 to 90 degrees clockwise
|
103
|
+
# 255 Letters are stacked top-to-bottom,
|
104
|
+
# but not rotated
|
105
|
+
xf_indent, # Bit Mask Contents
|
106
|
+
# 3-0 0x0f Indent level
|
107
|
+
# 4 0x10 1 = Shrink content to fit into cell
|
108
|
+
# 5 0x40 1 = Merge Range (djberger)
|
109
|
+
# 7-6 0xc0 Text direction (BIFF8X only)
|
110
|
+
# 0 = According to context
|
111
|
+
# 1 = Left-to-right
|
112
|
+
# 2 = Right-to-left
|
113
|
+
xf_used_attr, # Bit Mask Contents
|
114
|
+
# 7-2 0xfc XF_USED_ATTRIB – Used attributes
|
115
|
+
# Each bit describes the validity of a
|
116
|
+
# specific group of attributes. In cell XFs
|
117
|
+
# a cleared bit means the attributes of the
|
118
|
+
# parent style XF are used (but only if the
|
119
|
+
# attributes are valid there), a set bit
|
120
|
+
# means the attributes of this XF are used.
|
121
|
+
# In style XFs a cleared bit means the
|
122
|
+
# attribute setting is valid, a set bit
|
123
|
+
# means the attribute should be ignored.
|
124
|
+
# Bit Mask Contents
|
125
|
+
# 0 0x01 Flag for number format
|
126
|
+
# 1 0x02 Flag for font
|
127
|
+
# 2 0x04 Flag for horizontal and
|
128
|
+
# vertical alignment, text wrap,
|
129
|
+
# indentation, orientation,
|
130
|
+
# rotation, and text direction
|
131
|
+
# 3 0x08 Flag for border lines
|
132
|
+
# 4 0x10 Flag for background area style
|
133
|
+
# 5 0x20 Flag for cell protection (cell
|
134
|
+
# locked and formula hidden)
|
135
|
+
xf_borders, # Cell border lines and background area:
|
136
|
+
# Bit Mask Contents
|
137
|
+
# 3- 0 0x0000000f Left line style (➜ 3.10)
|
138
|
+
# 7- 4 0x000000f0 Right line style (➜ 3.10)
|
139
|
+
# 11- 8 0x00000f00 Top line style (➜ 3.10)
|
140
|
+
# 15-12 0x0000f000 Bottom line style (➜ 3.10)
|
141
|
+
# 22-16 0x007f0000 Colour index (➜ 6.70)
|
142
|
+
# for left line colour
|
143
|
+
# 29-23 0x3f800000 Colour index (➜ 6.70)
|
144
|
+
# for right line colour
|
145
|
+
# 30 0x40000000 1 = Diagonal line
|
146
|
+
# from top left to right bottom
|
147
|
+
# 31 0x80000000 1 = Diagonal line
|
148
|
+
# from bottom left to right top
|
149
|
+
xf_brdcolors, # Bit Mask Contents
|
150
|
+
# 6- 0 0x0000007f Colour index (➜ 6.70)
|
151
|
+
# for top line colour
|
152
|
+
# 13- 7 0x00003f80 Colour index (➜ 6.70)
|
153
|
+
# for bottom line colour
|
154
|
+
# 20-14 0x001fc000 Colour index (➜ 6.70)
|
155
|
+
# for diagonal line colour
|
156
|
+
# 24-21 0x01e00000 Diagonal line style (➜ 3.10)
|
157
|
+
# 31-26 0xfc000000 Fill pattern (➜ 3.11)
|
158
|
+
xf_pattern # Bit Mask Contents
|
159
|
+
# 6-0 0x007f Colour index (➜ 6.70)
|
160
|
+
# for pattern colour
|
161
|
+
# 13-7 0x3f80 Colour index (➜ 6.70)
|
162
|
+
# for pattern background
|
163
|
+
]
|
164
|
+
write_op writer, 0x00e0, data.pack(binfmt(:xf))
|
165
|
+
end
|
166
|
+
def xf_align
|
167
|
+
align = horizontal_align
|
168
|
+
align |= text_wrap << 3
|
169
|
+
align |= vertical_align << 4
|
170
|
+
align |= text_justlast << 7
|
171
|
+
align
|
172
|
+
end
|
173
|
+
def xf_borders
|
174
|
+
border = left
|
175
|
+
border |= right << 4
|
176
|
+
border |= top << 8
|
177
|
+
border |= bottom << 12
|
178
|
+
border |= left_color << 16
|
179
|
+
border |= right_color << 23
|
180
|
+
border |= cross_down << 30
|
181
|
+
border |= cross_up << 31
|
182
|
+
border
|
183
|
+
end
|
184
|
+
def xf_brdcolors
|
185
|
+
border = top_color
|
186
|
+
border |= bottom_color << 7
|
187
|
+
border |= diagonal_color << 14
|
188
|
+
border |= pattern << 26
|
189
|
+
border
|
190
|
+
end
|
191
|
+
def xf_indent
|
192
|
+
indent = indent_level & 0x0f
|
193
|
+
indent |= shrink << 4
|
194
|
+
indent |= merge_range << 5
|
195
|
+
indent |= text_direction << 6
|
196
|
+
indent
|
197
|
+
end
|
198
|
+
def xf_pattern
|
199
|
+
ptrn = pattern_fg_color
|
200
|
+
ptrn |= pattern_bg_color << 7
|
201
|
+
ptrn
|
202
|
+
end
|
203
|
+
def xf_rotation
|
204
|
+
rot = @format.rotation
|
205
|
+
if @format.rotation_stacked?
|
206
|
+
rot = 255
|
207
|
+
elsif rot >= -90 or rotation <= 90
|
208
|
+
rot = -rot + 90 if rot < 0
|
209
|
+
else
|
210
|
+
warn "rotation outside -90..90; rotation set to 0"
|
211
|
+
rot = 0
|
212
|
+
end
|
213
|
+
rot
|
214
|
+
end
|
215
|
+
def xf_type_prot type
|
216
|
+
type = type.to_s.downcase == 'style' ? 0xfff5 : 0x0000
|
217
|
+
type |= locked
|
218
|
+
type |= hidden << 1
|
219
|
+
type
|
220
|
+
end
|
221
|
+
def xf_used_attr
|
222
|
+
atr_num = num_format & 1
|
223
|
+
atr_fnt = font_index & 1
|
224
|
+
atr_fnt = 1 unless @format.font.color == :text
|
225
|
+
atr_alc = 0
|
226
|
+
if horizontal_align != 0 \
|
227
|
+
|| vertical_align != 2 \
|
228
|
+
|| indent_level > 0 \
|
229
|
+
|| shrink? || merge_range? || text_wrap?
|
230
|
+
then
|
231
|
+
atr_alc = 1
|
232
|
+
end
|
233
|
+
atr_bdr = [top, bottom, left, right, cross_up, cross_down].max
|
234
|
+
atr_pat = 0
|
235
|
+
if @format.pattern_fg_color != :border \
|
236
|
+
|| @format.pattern_bg_color != :pattern_bg \
|
237
|
+
|| pattern != 0x00
|
238
|
+
then
|
239
|
+
atr_pat = 1
|
240
|
+
end
|
241
|
+
atr_prot = hidden? || locked? ? 1 : 0
|
242
|
+
attrs = atr_num
|
243
|
+
attrs |= atr_fnt << 1
|
244
|
+
attrs |= atr_alc << 2
|
245
|
+
attrs |= atr_bdr << 3
|
246
|
+
attrs |= atr_pat << 4
|
247
|
+
attrs |= atr_prot << 5
|
248
|
+
attrs << 2
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,652 @@
|
|
1
|
+
require 'spreadsheet/excel/internals'
|
2
|
+
require 'spreadsheet/writer'
|
3
|
+
require 'spreadsheet/excel/writer/biff8'
|
4
|
+
require 'spreadsheet/excel/writer/format'
|
5
|
+
require 'spreadsheet/excel/writer/worksheet'
|
6
|
+
require 'ole/storage'
|
7
|
+
|
8
|
+
module Spreadsheet
|
9
|
+
module Excel
|
10
|
+
module Writer
|
11
|
+
##
|
12
|
+
# Writer class for Excel Workbooks. Most write_* method correspond to an
|
13
|
+
# Excel-Record/Opcode. Designed to be able to write several Workbooks in
|
14
|
+
# parallel (just because I can't imagine why you would want to do that
|
15
|
+
# doesn't mean it shouldn't be possible ;). You should not need to call any of
|
16
|
+
# its methods directly. If you think you do, look at #write_workbook
|
17
|
+
class Workbook < Spreadsheet::Writer
|
18
|
+
include Spreadsheet::Excel::Writer::Biff8
|
19
|
+
include Spreadsheet::Excel::Internals
|
20
|
+
attr_reader :fonts, :date_base
|
21
|
+
def initialize *args
|
22
|
+
super
|
23
|
+
@biff_version = 0x0600
|
24
|
+
@bof = 0x0809
|
25
|
+
@build_id = 3515
|
26
|
+
@build_year = 1996
|
27
|
+
@bof_types = {
|
28
|
+
:globals => 0x0005,
|
29
|
+
:visual_basic => 0x0006,
|
30
|
+
:worksheet => 0x0010,
|
31
|
+
:chart => 0x0020,
|
32
|
+
:macro_sheet => 0x0040,
|
33
|
+
:workspace => 0x0100,
|
34
|
+
}
|
35
|
+
@worksheets = {}
|
36
|
+
@sst = {}
|
37
|
+
@recordsize_limit = 8224
|
38
|
+
@fonts = {}
|
39
|
+
@formats = {}
|
40
|
+
@number_formats = {}
|
41
|
+
end
|
42
|
+
def cleanup workbook
|
43
|
+
worksheets(workbook).each do |worksheet|
|
44
|
+
@sst.delete worksheet
|
45
|
+
end
|
46
|
+
@fonts.delete workbook
|
47
|
+
@formats.delete workbook
|
48
|
+
@number_formats.delete workbook
|
49
|
+
@worksheets.delete workbook
|
50
|
+
end
|
51
|
+
def collect_formats workbook, opts={}
|
52
|
+
# The default cell format is always present in an Excel file, described by
|
53
|
+
# the XF record with the fixed index 15 (0-based). By default, it uses the
|
54
|
+
# worksheet/workbook default cell style, described by the very first XF
|
55
|
+
# record (index 0).
|
56
|
+
formats = []
|
57
|
+
unless opts[:existing_document]
|
58
|
+
15.times do
|
59
|
+
formats.push Format.new(self, workbook, workbook.default_format,
|
60
|
+
:type => :style)
|
61
|
+
end
|
62
|
+
formats.push Format.new(self, workbook)
|
63
|
+
end
|
64
|
+
workbook.formats.each do |fmt|
|
65
|
+
formats.push Format.new(self, workbook, fmt)
|
66
|
+
end
|
67
|
+
formats.each_with_index do |fmt, idx|
|
68
|
+
fmt.xf_index = idx
|
69
|
+
end
|
70
|
+
@formats[workbook] = formats
|
71
|
+
end
|
72
|
+
def complete_sst_update? workbook
|
73
|
+
stored = workbook.sst.collect do |entry| entry.content end
|
74
|
+
current = worksheets(workbook).inject [] do |memo, worksheet|
|
75
|
+
memo.concat worksheet.strings
|
76
|
+
end
|
77
|
+
total = current.size
|
78
|
+
current.uniq!
|
79
|
+
current.delete ''
|
80
|
+
if (stored - current).empty? && !stored.empty?
|
81
|
+
## if all previously stored strings are still needed, we don't have to
|
82
|
+
# rewrite all cells because the sst-index of such string does not change.
|
83
|
+
additions = current - stored
|
84
|
+
[:partial_update, total, stored + additions]
|
85
|
+
else
|
86
|
+
[:complete_update, total, current]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
def font_index workbook, font_key
|
90
|
+
idx = @fonts[workbook][font_key] || 0
|
91
|
+
## this appears to be undocumented: the first 4 fonts seem to be accessed
|
92
|
+
# with a 0-based index, but all subsequent font indices are 1-based.
|
93
|
+
idx > 3 ? idx.next : idx
|
94
|
+
end
|
95
|
+
def number_format_index workbook, format
|
96
|
+
@number_formats[workbook][format] || 0
|
97
|
+
end
|
98
|
+
def sanitize_worksheets sheets
|
99
|
+
found_selected = false
|
100
|
+
sheets.each do |sheet|
|
101
|
+
found_selected ||= sheet.selected
|
102
|
+
sheet.format_dates!
|
103
|
+
end
|
104
|
+
unless found_selected
|
105
|
+
sheets.first.selected = true
|
106
|
+
end
|
107
|
+
sheets
|
108
|
+
end
|
109
|
+
def worksheets workbook
|
110
|
+
@worksheets[workbook] ||= workbook.worksheets.collect do |worksheet|
|
111
|
+
Excel::Writer::Worksheet.new self, worksheet
|
112
|
+
end
|
113
|
+
end
|
114
|
+
def write_bof workbook, writer, type
|
115
|
+
data = [
|
116
|
+
@biff_version, # BIFF version (always 0x0600 for BIFF8)
|
117
|
+
@bof_types[type], # Type of the following data:
|
118
|
+
# 0x0005 = Workbook globals
|
119
|
+
# 0x0006 = Visual Basic module
|
120
|
+
# 0x0010 = Worksheet
|
121
|
+
# 0x0020 = Chart
|
122
|
+
# 0x0040 = Macro sheet
|
123
|
+
# 0x0100 = Workspace file
|
124
|
+
@build_id, # Build identifier
|
125
|
+
@build_year, # Build year
|
126
|
+
0x000, # File history flags
|
127
|
+
0x006, # Lowest Excel version that can read
|
128
|
+
# all records in this file
|
129
|
+
]
|
130
|
+
write_op writer, @bof, data.pack("v4V2")
|
131
|
+
end
|
132
|
+
def write_bookbool workbook, writer
|
133
|
+
write_placeholder writer, 0x00da
|
134
|
+
end
|
135
|
+
def write_boundsheets workbook, writer, offset
|
136
|
+
worksheets = worksheets(workbook)
|
137
|
+
worksheets.each do |worksheet|
|
138
|
+
# account for boundsheet-entry
|
139
|
+
offset += worksheet.boundsheet_size
|
140
|
+
end
|
141
|
+
worksheets.each do |worksheet|
|
142
|
+
data = [
|
143
|
+
offset, # Absolute stream position of the BOF record of the sheet
|
144
|
+
# represented by this record. This field is never encrypted
|
145
|
+
# in protected files.
|
146
|
+
0x00, # Visibility: 0x00 = Visible
|
147
|
+
# 0x01 = Hidden
|
148
|
+
# 0x02 = Strong hidden (see below)
|
149
|
+
0x00, # Sheet type: 0x00 = Worksheet
|
150
|
+
# 0x02 = Chart
|
151
|
+
# 0x06 = Visual Basic module
|
152
|
+
]
|
153
|
+
write_op writer, 0x0085, data.pack("VC2"), worksheet.name
|
154
|
+
offset += worksheet.size
|
155
|
+
end
|
156
|
+
end
|
157
|
+
##
|
158
|
+
# Copy unchanged data verbatim, adjust offsets and write new records for
|
159
|
+
# changed data.
|
160
|
+
def write_changes workbook, io
|
161
|
+
sanitize_worksheets workbook.worksheets
|
162
|
+
collect_formats workbook, :existing_document => true
|
163
|
+
reader = workbook.ole
|
164
|
+
sheet_data = {}
|
165
|
+
sst_status, sst_total, sst_strings = complete_sst_update? workbook
|
166
|
+
sst = {}
|
167
|
+
sst_strings.each_with_index do |str, idx| sst.store str, idx end
|
168
|
+
sheets = worksheets(workbook)
|
169
|
+
positions = []
|
170
|
+
newsheets = []
|
171
|
+
sheets.each do |sheet|
|
172
|
+
@sst[sheet] = sst
|
173
|
+
pos, len = workbook.offsets[sheet.worksheet]
|
174
|
+
if pos
|
175
|
+
positions.push pos
|
176
|
+
sheet.write_changes reader, pos + len, sst_status
|
177
|
+
else
|
178
|
+
newsheets.push sheet
|
179
|
+
sheet.write_from_scratch
|
180
|
+
end
|
181
|
+
sheet_data[sheet.worksheet] = sheet.data
|
182
|
+
end
|
183
|
+
Ole::Storage.open io do |ole|
|
184
|
+
ole.file.open 'Workbook', 'w' do |writer|
|
185
|
+
reader.seek lastpos = 0
|
186
|
+
workbook.offsets.select do |key, pair|
|
187
|
+
workbook.changes.include? key
|
188
|
+
end.sort_by do |key, (pos, len)|
|
189
|
+
pos
|
190
|
+
end.each do |key, (pos, len)|
|
191
|
+
data = reader.read(pos - lastpos)
|
192
|
+
writer.write data
|
193
|
+
case key
|
194
|
+
when Spreadsheet::Worksheet
|
195
|
+
writer.write sheet_data[key]
|
196
|
+
when :boundsheets
|
197
|
+
## boundsheets are hard to calculate. The offset below is only
|
198
|
+
# correct if there are no more changes in the workbook globals
|
199
|
+
# string after this.
|
200
|
+
oldoffset = positions.min - len
|
201
|
+
lastpos = pos + len
|
202
|
+
bytechange = 0
|
203
|
+
buffer = StringIO.new ''
|
204
|
+
if tuple = workbook.offsets[:sst]
|
205
|
+
write_sst_changes workbook, buffer, writer.pos,
|
206
|
+
sst_total, sst_strings
|
207
|
+
pos, len = tuple
|
208
|
+
if offset = workbook.offsets[:extsst]
|
209
|
+
len += offset[1].to_i
|
210
|
+
end
|
211
|
+
bytechange = buffer.size - len
|
212
|
+
write_boundsheets workbook, writer, oldoffset + bytechange
|
213
|
+
reader.seek lastpos
|
214
|
+
writer.write reader.read(pos - lastpos)
|
215
|
+
buffer.rewind
|
216
|
+
writer.write buffer.read
|
217
|
+
elsif sst.empty? || workbook.biff_version < 8
|
218
|
+
write_boundsheets workbook, writer, oldoffset + bytechange
|
219
|
+
else
|
220
|
+
write_sst workbook, buffer, writer.pos
|
221
|
+
write_boundsheets workbook, writer, oldoffset + buffer.size
|
222
|
+
pos = lastpos
|
223
|
+
len = positions.min - lastpos
|
224
|
+
if len > OPCODE_SIZE
|
225
|
+
reader.seek pos
|
226
|
+
writer.write reader.read(len - OPCODE_SIZE)
|
227
|
+
end
|
228
|
+
buffer.rewind
|
229
|
+
writer.write buffer.read
|
230
|
+
write_eof workbook, writer
|
231
|
+
end
|
232
|
+
else
|
233
|
+
send "write_#{key}", workbook, writer
|
234
|
+
end
|
235
|
+
lastpos = [pos + len, reader.size - 1].min
|
236
|
+
reader.seek lastpos
|
237
|
+
end
|
238
|
+
writer.write reader.read
|
239
|
+
newsheets.each do |sheet|
|
240
|
+
writer.write sheet.data
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
def write_datemode workbook, writer
|
246
|
+
mode = @date_base.year == 1899 ? 0x00 : 0x01
|
247
|
+
data = [
|
248
|
+
mode, # 0 = Base date is 1899-Dec-31
|
249
|
+
# (the cell value 1 represents 1900-Jan-01)
|
250
|
+
# 1 = Base date is 1904-Jan-01
|
251
|
+
# (the cell value 1 represents 1904-Jan-02)
|
252
|
+
]
|
253
|
+
write_op writer, 0x0022, data.pack('v')
|
254
|
+
end
|
255
|
+
def write_dsf workbook, writer
|
256
|
+
data = [
|
257
|
+
0x00, # 0 = Only the BIFF8 “Workbook” stream is present
|
258
|
+
# 1 = Additional BIFF5/BIFF7 “Book” stream is in the file
|
259
|
+
]
|
260
|
+
write_op writer, 0x0161, data.pack('v')
|
261
|
+
end
|
262
|
+
def write_encoding workbook, writer
|
263
|
+
enc = workbook.encoding || 'UTF-16LE'
|
264
|
+
if RUBY_VERSION >= '1.9' && enc.is_a?(Encoding)
|
265
|
+
enc = enc.name.upcase
|
266
|
+
end
|
267
|
+
cp = SEGAPEDOC[enc] or raise "Invalid or Unknown Codepage '#{enc}'"
|
268
|
+
write_op writer, 0x0042, [cp].pack('v')
|
269
|
+
end
|
270
|
+
def write_eof workbook, writer
|
271
|
+
write_op writer, 0x000a
|
272
|
+
end
|
273
|
+
def write_extsst workbook, offsets, writer
|
274
|
+
header = [SST_CHUNKSIZE].pack('v')
|
275
|
+
data = offsets.collect do |pair| pair.push(0).pack('Vv2') end
|
276
|
+
write_op writer, 0x00ff, header, data
|
277
|
+
end
|
278
|
+
def write_font workbook, writer, font
|
279
|
+
# TODO: Colors/Palette index
|
280
|
+
size = font.size * TWIPS
|
281
|
+
color = SEDOC_ROLOC[font.color] || SEDOC_ROLOC[:text]
|
282
|
+
weight = FONT_WEIGHTS.fetch(font.weight, font.weight)
|
283
|
+
weight = [[weight, 1000].min, 100].max
|
284
|
+
esc = SEPYT_TNEMEPACSE.fetch(font.escapement, 0)
|
285
|
+
underline = SEPYT_ENILREDNU.fetch(font.underline, 0)
|
286
|
+
family = SEILIMAF_TNOF.fetch(font.family, 0)
|
287
|
+
encoding = SGNIDOCNE_TNOF.fetch(font.encoding, 0)
|
288
|
+
options = 0
|
289
|
+
options |= 0x0001 if weight > 600
|
290
|
+
options |= 0x0002 if font.italic?
|
291
|
+
options |= 0x0004 if underline > 0
|
292
|
+
options |= 0x0008 if font.strikeout?
|
293
|
+
options |= 0x0010 if font.outline?
|
294
|
+
options |= 0x0020 if font.shadow?
|
295
|
+
data = [
|
296
|
+
size, # Height of the font (in twips = 1/20 of a point)
|
297
|
+
options, # Option flags:
|
298
|
+
# Bit Mask Contents
|
299
|
+
# 0 0x0001 1 = Characters are bold (redundant, see below)
|
300
|
+
# 1 0x0002 1 = Characters are italic
|
301
|
+
# 2 0x0004 1 = Characters are underlined (redundant)
|
302
|
+
# 3 0x0008 1 = Characters are struck out
|
303
|
+
# 4 0x0010 1 = Characters are outlined (djberger)
|
304
|
+
# 5 0x0020 1 = Characters are shadowed (djberger)
|
305
|
+
color, # Palette index (➜ 6.70)
|
306
|
+
weight, # Font weight (100-1000). Standard values are
|
307
|
+
# 0x0190 (400) for normal text and
|
308
|
+
# 0x02bc (700) for bold text.
|
309
|
+
esc, # Escapement type: 0x0000 = None
|
310
|
+
# 0x0001 = Superscript
|
311
|
+
# 0x0002 = Subscript
|
312
|
+
underline,# Underline type: 0x00 = None
|
313
|
+
# 0x01 = Single
|
314
|
+
# 0x02 = Double
|
315
|
+
# 0x21 = Single accounting
|
316
|
+
# 0x22 = Double accounting
|
317
|
+
family, # Font family: 0x00 = None (unknown or don't care)
|
318
|
+
# 0x01 = Roman (variable width, serifed)
|
319
|
+
# 0x02 = Swiss (variable width, sans-serifed)
|
320
|
+
# 0x03 = Modern (fixed width,
|
321
|
+
# serifed or sans-serifed)
|
322
|
+
# 0x04 = Script (cursive)
|
323
|
+
# 0x05 = Decorative (specialised,
|
324
|
+
# e.g. Old English, Fraktur)
|
325
|
+
encoding, # Character set: 0x00 = 0 = ANSI Latin
|
326
|
+
# 0x01 = 1 = System default
|
327
|
+
# 0x02 = 2 = Symbol
|
328
|
+
# 0x4d = 77 = Apple Roman
|
329
|
+
# 0x80 = 128 = ANSI Japanese Shift-JIS
|
330
|
+
# 0x81 = 129 = ANSI Korean (Hangul)
|
331
|
+
# 0x82 = 130 = ANSI Korean (Johab)
|
332
|
+
# 0x86 = 134 = ANSI Chinese Simplified GBK
|
333
|
+
# 0x88 = 136 = ANSI Chinese Traditional BIG5
|
334
|
+
# 0xa1 = 161 = ANSI Greek
|
335
|
+
# 0xa2 = 162 = ANSI Turkish
|
336
|
+
# 0xa3 = 163 = ANSI Vietnamese
|
337
|
+
# 0xb1 = 177 = ANSI Hebrew
|
338
|
+
# 0xb2 = 178 = ANSI Arabic
|
339
|
+
# 0xba = 186 = ANSI Baltic
|
340
|
+
# 0xcc = 204 = ANSI Cyrillic
|
341
|
+
# 0xde = 222 = ANSI Thai
|
342
|
+
# 0xee = 238 = ANSI Latin II (Central European)
|
343
|
+
# 0xff = 255 = OEM Latin I
|
344
|
+
]
|
345
|
+
name = unicode_string font.name # Font name: Unicode string,
|
346
|
+
# 8-bit string length (➜ 3.4)
|
347
|
+
write_op writer, opcode(:font), data.pack(binfmt(:font)), name
|
348
|
+
end
|
349
|
+
def write_fonts workbook, writer
|
350
|
+
fonts = @fonts[workbook] = {}
|
351
|
+
@formats[workbook].each do |format|
|
352
|
+
if(font = format.font) && !fonts.include?(font.key)
|
353
|
+
fonts.store font.key, fonts.size
|
354
|
+
write_font workbook, writer, font
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
def write_formats workbook, writer
|
359
|
+
# From BIFF5 on, the built-in number formats will be omitted. The built-in
|
360
|
+
# formats are dependent on the current regional settings of the operating
|
361
|
+
# system. BUILTIN_FORMATS shows which number formats are used by
|
362
|
+
# default in a US-English environment. All indexes from 0 to 163 are
|
363
|
+
# reserved for built-in formats.
|
364
|
+
# The first user-defined format starts at 164 (0xa4).
|
365
|
+
formats = @number_formats[workbook] = {}
|
366
|
+
BUILTIN_FORMATS.each do |idx, str|
|
367
|
+
formats.store client(str, 'UTF-8'), idx
|
368
|
+
end
|
369
|
+
## Ensure at least a 'GENERAL' format is written
|
370
|
+
formats.delete client('GENERAL', 'UTF-8')
|
371
|
+
idx = 0xa4
|
372
|
+
workbook.formats.each do |fmt|
|
373
|
+
str = fmt.number_format
|
374
|
+
unless formats[str]
|
375
|
+
formats.store str, idx
|
376
|
+
# Number format string (Unicode string, 16-bit string length, ➜ 3.4)
|
377
|
+
write_op writer, opcode(:format), [idx].pack('v'), unicode_string(str, 2)
|
378
|
+
idx += 1
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
##
|
383
|
+
# Write a new Excel file.
|
384
|
+
def write_from_scratch workbook, io
|
385
|
+
sanitize_worksheets workbook.worksheets
|
386
|
+
collect_formats workbook
|
387
|
+
sheets = worksheets workbook
|
388
|
+
buffer1 = StringIO.new ''
|
389
|
+
# ● BOF Type = workbook globals (➜ 6.8)
|
390
|
+
write_bof workbook, buffer1, :globals
|
391
|
+
# ○ File Protection Block ➜ 4.19
|
392
|
+
# ○ WRITEACCESS User name (BIFF3-BIFF8, ➜ 5.112)
|
393
|
+
# ○ FILESHARING File sharing options (BIFF3-BIFF8, ➜ 5.44)
|
394
|
+
# ○ CODEPAGE ➜ 6.17
|
395
|
+
write_encoding workbook, buffer1
|
396
|
+
# ○ DSF ➜ 6.32
|
397
|
+
write_dsf workbook, buffer1
|
398
|
+
# ○ TABID
|
399
|
+
write_tabid workbook, buffer1
|
400
|
+
# ○ FNGROUPCOUNT
|
401
|
+
# ○ Workbook Protection Block ➜ 4.18
|
402
|
+
# ○ WINDOWPROTECT Window settings: 1 = protected (➜ 5.111)
|
403
|
+
# ○ PROTECT Cell contents: 1 = protected (➜ 5.82)
|
404
|
+
write_protect workbook, buffer1
|
405
|
+
# ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
|
406
|
+
# ○ PASSWORD Hash value of the password; 0 = No password (➜ 5.76)
|
407
|
+
write_password workbook, buffer1
|
408
|
+
# ○ BACKUP ➜ 5.5
|
409
|
+
# ○ HIDEOBJ ➜ 5.56
|
410
|
+
# ● WINDOW1 ➜ 5.109
|
411
|
+
write_window1 workbook, buffer1
|
412
|
+
# ○ DATEMODE ➜ 5.28
|
413
|
+
write_datemode workbook, buffer1
|
414
|
+
# ○ PRECISION ➜ 5.79
|
415
|
+
write_precision workbook, buffer1
|
416
|
+
# ○ REFRESHALL
|
417
|
+
write_refreshall workbook, buffer1
|
418
|
+
# ○ BOOKBOOL ➜ 5.9
|
419
|
+
write_bookbool workbook, buffer1
|
420
|
+
# ●● FONT ➜ 5.45
|
421
|
+
write_fonts workbook, buffer1
|
422
|
+
# ○○ FORMAT ➜ 5.49
|
423
|
+
write_formats workbook, buffer1
|
424
|
+
# ●● XF ➜ 5.115
|
425
|
+
write_xfs workbook, buffer1
|
426
|
+
# ●● STYLE ➜ 5.103
|
427
|
+
write_styles workbook, buffer1
|
428
|
+
# ○ PALETTE ➜ 5.74
|
429
|
+
# ○ USESELFS ➜ 5.106
|
430
|
+
buffer1.rewind
|
431
|
+
# ●● BOUNDSHEET ➜ 5.95
|
432
|
+
buffer2 = StringIO.new ''
|
433
|
+
# ○ COUNTRY ➜ 5.22
|
434
|
+
# ○ Link Table ➜ 4.10.3
|
435
|
+
# ○○ NAME ➜ 6.66
|
436
|
+
# ○ Shared String Table ➜ 4.11
|
437
|
+
# ● SST ➜ 5.100
|
438
|
+
# ● EXTSST ➜ 5.42
|
439
|
+
write_sst workbook, buffer2, buffer1.size
|
440
|
+
# ● EOF ➜ 5.37
|
441
|
+
write_eof workbook, buffer2
|
442
|
+
buffer2.rewind
|
443
|
+
# worksheet data can only be assembled after write_sst
|
444
|
+
sheets.each do |worksheet| worksheet.write_from_scratch end
|
445
|
+
Ole::Storage.open io do |ole|
|
446
|
+
ole.file.open 'Workbook', 'w' do |writer|
|
447
|
+
writer.write buffer1.read
|
448
|
+
write_boundsheets workbook, writer, buffer1.size + buffer2.size
|
449
|
+
writer.write buffer2.read
|
450
|
+
sheets.each do |worksheet|
|
451
|
+
writer.write worksheet.data
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
def write_op writer, op, *args
|
457
|
+
data = args.join
|
458
|
+
limited = data.slice!(0...@recordsize_limit)
|
459
|
+
writer.write [op,limited.size].pack("v2")
|
460
|
+
writer.write limited
|
461
|
+
data
|
462
|
+
end
|
463
|
+
def write_password workbook, writer
|
464
|
+
write_placeholder writer, 0x0013
|
465
|
+
end
|
466
|
+
def write_placeholder writer, op, value=0x0000, fmt='v'
|
467
|
+
write_op writer, op, [value].pack(fmt)
|
468
|
+
end
|
469
|
+
def write_precision workbook, writer
|
470
|
+
# 0 = Use displayed values; 1 = Use real cell values
|
471
|
+
write_placeholder writer, 0x000e, 0x0001
|
472
|
+
end
|
473
|
+
def write_protect workbook, writer
|
474
|
+
write_placeholder writer, 0x0012
|
475
|
+
end
|
476
|
+
def write_refreshall workbook, writer
|
477
|
+
write_placeholder writer, 0x01b7
|
478
|
+
end
|
479
|
+
def write_sst workbook, writer, offset
|
480
|
+
# Offset Size Contents
|
481
|
+
# 0 4 Total number of strings in the workbook (see below)
|
482
|
+
# 4 4 Number of following strings (nm)
|
483
|
+
# 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
|
484
|
+
strings = worksheets(workbook).inject [] do |memo, worksheet|
|
485
|
+
memo.concat worksheet.strings
|
486
|
+
end
|
487
|
+
total = strings.size
|
488
|
+
strings.uniq!
|
489
|
+
_write_sst workbook, writer, offset, total, strings
|
490
|
+
end
|
491
|
+
def _write_sst workbook, writer, offset, total, strings
|
492
|
+
sst = {}
|
493
|
+
worksheets(workbook).each do |worksheet|
|
494
|
+
offset += worksheet.boundsheet_size
|
495
|
+
@sst[worksheet] = sst
|
496
|
+
end
|
497
|
+
sst_size = strings.size
|
498
|
+
data = [total, sst_size].pack 'V2'
|
499
|
+
op = 0x00fc
|
500
|
+
wide = 0
|
501
|
+
offsets = []
|
502
|
+
strings.each_with_index do |string, idx|
|
503
|
+
sst.store string, idx
|
504
|
+
op_offset = data.size + 4
|
505
|
+
if idx % SST_CHUNKSIZE == 0
|
506
|
+
offsets.push [offset + writer.pos + op_offset, op_offset]
|
507
|
+
end
|
508
|
+
header, packed, next_wide = _unicode_string string, 2
|
509
|
+
# the first few bytes (header + first character) must not be split
|
510
|
+
must_fit = header.size + wide + 1
|
511
|
+
while data.size + must_fit > @recordsize_limit
|
512
|
+
op, data, wide = write_string_part writer, op, data, wide
|
513
|
+
end
|
514
|
+
wide = next_wide
|
515
|
+
data << header << packed
|
516
|
+
end
|
517
|
+
until data.empty?
|
518
|
+
op, data, wide = write_string_part writer, op, data, wide
|
519
|
+
end
|
520
|
+
write_extsst workbook, offsets, writer
|
521
|
+
end
|
522
|
+
def write_sst_changes workbook, writer, offset, total, strings
|
523
|
+
_write_sst workbook, writer, offset, total, strings
|
524
|
+
end
|
525
|
+
def write_string_part writer, op, data, wide
|
526
|
+
bef = data.size
|
527
|
+
## if we're writing wide characters, we need to make sure we don't cut
|
528
|
+
# characters in half
|
529
|
+
if wide > 0 && data.size > @recordsize_limit
|
530
|
+
remove = @recordsize_limit - data.size
|
531
|
+
remove -= remove % 2
|
532
|
+
rest = data.slice!(remove..-1)
|
533
|
+
write_op writer, op, data
|
534
|
+
data = rest
|
535
|
+
else
|
536
|
+
data = write_op writer, op, data
|
537
|
+
end
|
538
|
+
op = 0x003c
|
539
|
+
# Unicode strings are split in a special way. At the beginning of each
|
540
|
+
# CONTINUE record the option flags byte is repeated. Only the
|
541
|
+
# character size flag will be set in this flags byte, the Rich-Text
|
542
|
+
# flag and the Far-East flag are set to zero.
|
543
|
+
unless data.empty?
|
544
|
+
if wide == 1
|
545
|
+
# check if we can compress the rest of the string
|
546
|
+
data, wide = compress_unicode_string data
|
547
|
+
end
|
548
|
+
data = [wide].pack('C') << data
|
549
|
+
end
|
550
|
+
[op, data, wide]
|
551
|
+
end
|
552
|
+
def write_styles workbook, writer
|
553
|
+
# TODO: Style implementation. The following is simply a standard builtin
|
554
|
+
# style.
|
555
|
+
# TODO: User defined styles
|
556
|
+
data = [
|
557
|
+
0x8000, # Bit Mask Contents
|
558
|
+
# 11- 0 0x0fff Index to style XF record (➜ 6.115)
|
559
|
+
# 15 0x8000 Always 1 for built-in styles
|
560
|
+
0x00, # Identifier of the built-in cell style:
|
561
|
+
# 0x00 = Normal
|
562
|
+
# 0x01 = RowLevel_lv (see next field)
|
563
|
+
# 0x02 = ColLevel_lv (see next field)
|
564
|
+
# 0x03 = Comma
|
565
|
+
# 0x04 = Currency
|
566
|
+
# 0x05 = Percent
|
567
|
+
# 0x06 = Comma [0] (BIFF4-BIFF8)
|
568
|
+
# 0x07 = Currency [0] (BIFF4-BIFF8)
|
569
|
+
# 0x08 = Hyperlink (BIFF8)
|
570
|
+
# 0x09 = Followed Hyperlink (BIFF8)
|
571
|
+
0xff, # Level for RowLevel or ColLevel style (zero-based, lv),
|
572
|
+
# 0xff otherwise
|
573
|
+
# The RowLevel and ColLevel styles specify the formatting of
|
574
|
+
# subtotal cells in a specific outline level. The level is
|
575
|
+
# specified by the last field in the STYLE record. Valid values
|
576
|
+
# are 0…6 for the outline levels 1…7.
|
577
|
+
]
|
578
|
+
write_op writer, 0x0293, data.pack('vC2')
|
579
|
+
end
|
580
|
+
def write_tabid workbook, writer
|
581
|
+
write_op writer, 0x013d, [1].pack('v')
|
582
|
+
end
|
583
|
+
def write_window1 workbook, writer
|
584
|
+
selected = workbook.worksheets.find do |sheet| sheet.selected end
|
585
|
+
actidx = workbook.worksheets.index selected
|
586
|
+
data = [
|
587
|
+
0x0000, # Horizontal position of the document window
|
588
|
+
# (in twips = 1/20 of a point)
|
589
|
+
0x0000, # Vertical position of the document window
|
590
|
+
# (in twips = 1/20 of a point)
|
591
|
+
0x4000, # Width of the document window (in twips = 1/20 of a point)
|
592
|
+
0x2000, # Height of the document window (in twips = 1/20 of a point)
|
593
|
+
0x0038, # Option flags:
|
594
|
+
# Bit Mask Contents
|
595
|
+
# 0 0x0001 0 = Window is visible
|
596
|
+
# 1 = Window is hidden
|
597
|
+
# 1 0x0002 0 = Window is open
|
598
|
+
# 1 = Window is minimised
|
599
|
+
# 3 0x0008 0 = Horizontal scroll bar hidden
|
600
|
+
# 1 = Horizontal scroll bar visible
|
601
|
+
# 4 0x0010 0 = Vertical scroll bar hidden
|
602
|
+
# 1 = Vertical scroll bar visible
|
603
|
+
# 5 0x0020 0 = Worksheet tab bar hidden
|
604
|
+
# 1 = Worksheet tab bar visible
|
605
|
+
actidx, # Index to active (displayed) worksheet
|
606
|
+
0x0000, # Index of first visible tab in the worksheet tab bar
|
607
|
+
0x0001, # Number of selected worksheets
|
608
|
+
# (highlighted in the worksheet tab bar)
|
609
|
+
0x00e5, # Width of worksheet tab bar (in 1/1000 of window width).
|
610
|
+
# The remaining space is used by the horizontal scrollbar.
|
611
|
+
]
|
612
|
+
write_op writer, 0x003d, data.pack('v*')
|
613
|
+
end
|
614
|
+
##
|
615
|
+
# The main writer method. Calls #write_from_scratch or #write_changes
|
616
|
+
# depending on the class and state of _workbook_.
|
617
|
+
def write_workbook workbook, io
|
618
|
+
unless workbook.is_a?(Excel::Workbook) && workbook.io
|
619
|
+
@date_base = Date.new 1899, 12, 31
|
620
|
+
write_from_scratch workbook, io
|
621
|
+
else
|
622
|
+
@date_base = workbook.date_base
|
623
|
+
if workbook.changes.empty?
|
624
|
+
super
|
625
|
+
else
|
626
|
+
write_changes workbook, io
|
627
|
+
end
|
628
|
+
end
|
629
|
+
ensure
|
630
|
+
cleanup workbook
|
631
|
+
end
|
632
|
+
def write_xfs workbook, writer
|
633
|
+
# The default cell format is always present in an Excel file, described by
|
634
|
+
# the XF record with the fixed index 15 (0-based). By default, it uses the
|
635
|
+
# worksheet/workbook default cell style, described by the very first XF
|
636
|
+
# record (index 0).
|
637
|
+
@formats[workbook].each do |fmt| fmt.write_xf writer end
|
638
|
+
end
|
639
|
+
def sst_index worksheet, str
|
640
|
+
@sst[worksheet][str]
|
641
|
+
end
|
642
|
+
def xf_index workbook, format
|
643
|
+
if fmt = @formats[workbook].find do |fm| fm.format == format end
|
644
|
+
fmt.xf_index
|
645
|
+
else
|
646
|
+
0
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|