ruby-spreadsheet 0.6.5
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/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
|