spreadsheet 1.3.3 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/parseexcel/parseexcel.rb +66 -58
  3. data/lib/parseexcel/parser.rb +1 -1
  4. data/lib/parseexcel.rb +1 -1
  5. data/lib/spreadsheet/column.rb +11 -9
  6. data/lib/spreadsheet/compatibility.rb +3 -1
  7. data/lib/spreadsheet/datatypes.rb +149 -147
  8. data/lib/spreadsheet/encodings.rb +20 -16
  9. data/lib/spreadsheet/errors.rb +2 -2
  10. data/lib/spreadsheet/excel/error.rb +23 -22
  11. data/lib/spreadsheet/excel/internals/biff5.rb +11 -11
  12. data/lib/spreadsheet/excel/internals/biff8.rb +13 -13
  13. data/lib/spreadsheet/excel/internals.rb +451 -451
  14. data/lib/spreadsheet/excel/offset.rb +32 -31
  15. data/lib/spreadsheet/excel/password_hash.rb +18 -18
  16. data/lib/spreadsheet/excel/reader/biff5.rb +34 -35
  17. data/lib/spreadsheet/excel/reader/biff8.rb +234 -222
  18. data/lib/spreadsheet/excel/reader.rb +1320 -1274
  19. data/lib/spreadsheet/excel/rgb.rb +91 -91
  20. data/lib/spreadsheet/excel/row.rb +99 -91
  21. data/lib/spreadsheet/excel/sst_entry.rb +40 -38
  22. data/lib/spreadsheet/excel/workbook.rb +86 -76
  23. data/lib/spreadsheet/excel/worksheet.rb +125 -107
  24. data/lib/spreadsheet/excel/writer/biff8.rb +56 -55
  25. data/lib/spreadsheet/excel/writer/format.rb +273 -256
  26. data/lib/spreadsheet/excel/writer/n_worksheet.rb +837 -798
  27. data/lib/spreadsheet/excel/writer/workbook.rb +671 -635
  28. data/lib/spreadsheet/excel/writer/worksheet.rb +898 -861
  29. data/lib/spreadsheet/excel/writer.rb +1 -1
  30. data/lib/spreadsheet/excel.rb +18 -11
  31. data/lib/spreadsheet/font.rb +30 -26
  32. data/lib/spreadsheet/format.rb +74 -59
  33. data/lib/spreadsheet/link.rb +7 -5
  34. data/lib/spreadsheet/note.rb +6 -6
  35. data/lib/spreadsheet/noteObject.rb +5 -5
  36. data/lib/spreadsheet/row.rb +33 -23
  37. data/lib/spreadsheet/version.rb +1 -1
  38. data/lib/spreadsheet/workbook.rb +27 -13
  39. data/lib/spreadsheet/worksheet.rb +102 -68
  40. data/lib/spreadsheet/writer.rb +3 -0
  41. data/lib/spreadsheet.rb +12 -15
  42. data/test/excel/reader.rb +8 -8
  43. data/test/excel/row.rb +35 -31
  44. data/test/excel/writer/workbook.rb +18 -16
  45. data/test/excel/writer/worksheet.rb +10 -8
  46. data/test/font.rb +44 -32
  47. data/test/format.rb +38 -33
  48. data/test/integration.rb +627 -598
  49. data/test/row.rb +5 -3
  50. data/test/suite.rb +7 -7
  51. data/test/workbook.rb +15 -14
  52. data/test/workbook_protection.rb +5 -5
  53. data/test/worksheet.rb +36 -34
  54. metadata +48 -6
@@ -1,41 +1,42 @@
1
- require 'spreadsheet/compatibility'
1
+ require "spreadsheet/compatibility"
2
2
 
3
3
  module Spreadsheet
4
4
  module Excel
5
- ##
6
- # This module is used to keep track of offsets in modified Excel documents.
7
- # Considered internal and subject to change without notice.
8
- module Offset
9
- include Compatibility
10
- attr_reader :changes, :offsets
11
- def initialize *args
12
- super
13
- @changes = {}
14
- @offsets = {}
15
- end
16
- def Offset.append_features mod
17
- super
18
- mod.module_eval do
19
- class << self
20
- include Compatibility
21
- def offset *keys
22
- keys.each do |key|
23
- attr_reader key unless instance_methods.include? method_name(key)
24
- define_method "#{key}=" do |value|
25
- @changes.store key, true
26
- instance_variable_set ivar_name(key), value
27
- end
28
- define_method "set_#{key}" do |value, pos, len|
29
- instance_variable_set ivar_name(key), value
30
- @offsets.store key, [pos, len]
31
- havename = "have_set_#{key}"
32
- send(havename, value, pos, len) if respond_to? havename
5
+ ##
6
+ # This module is used to keep track of offsets in modified Excel documents.
7
+ # Considered internal and subject to change without notice.
8
+ module Offset
9
+ include Compatibility
10
+ attr_reader :changes, :offsets
11
+ def initialize *args
12
+ super
13
+ @changes = {}
14
+ @offsets = {}
15
+ end
16
+
17
+ def self.append_features mod
18
+ super
19
+ mod.module_eval do
20
+ class << self
21
+ include Compatibility
22
+ def offset *keys
23
+ keys.each do |key|
24
+ attr_reader key unless instance_methods.include? method_name(key)
25
+ define_method :"#{key}=" do |value|
26
+ @changes.store key, true
27
+ instance_variable_set ivar_name(key), value
28
+ end
29
+ define_method :"set_#{key}" do |value, pos, len|
30
+ instance_variable_set ivar_name(key), value
31
+ @offsets.store key, [pos, len]
32
+ havename = "have_set_#{key}"
33
+ send(havename, value, pos, len) if respond_to? havename
34
+ end
35
+ end
33
36
  end
34
37
  end
35
38
  end
36
39
  end
37
40
  end
38
41
  end
39
- end
40
- end
41
42
  end
@@ -1,24 +1,24 @@
1
1
  module Spreadsheet
2
2
  module Excel
3
- module Password
4
- class <<self
5
- ##
6
- # Makes an excel-compatible hash
7
- def password_hash(password)
8
- hash = 0
9
- password.chars.reverse_each { |chr| hash = rol15(hash ^ chr[0].ord) }
10
- hash ^ password.size ^ 0xCE4B
11
- end
3
+ module Password
4
+ class << self
5
+ ##
6
+ # Makes an excel-compatible hash
7
+ def password_hash(password)
8
+ hash = 0
9
+ password.chars.reverse_each { |chr| hash = rol15(hash ^ chr[0].ord) }
10
+ hash ^ password.size ^ 0xCE4B
11
+ end
12
12
 
13
- private
14
- ##
15
- # rotates hash 1 bit left, using lower 15 bits
16
- def rol15(hash)
17
- new_hash = hash << 1
18
- (new_hash & 0x7FFF) | (new_hash >> 15)
19
- end
20
- end
13
+ private
21
14
 
22
- end
15
+ ##
16
+ # rotates hash 1 bit left, using lower 15 bits
17
+ def rol15(hash)
18
+ new_hash = hash << 1
19
+ (new_hash & 0x7FFF) | (new_hash >> 15)
20
+ end
21
+ end
22
+ end
23
23
  end
24
24
  end
@@ -1,42 +1,41 @@
1
1
  module Spreadsheet
2
2
  module Excel
3
3
  class Reader
4
- ##
5
- # This Module collects reader methods such as read_string that are specific to
6
- # Biff5. This Module is likely to be expanded as Support for older Versions
7
- # of Excel grows.
8
- module Biff5
9
- ##
10
- # Read a String of 8-bit Characters
11
- def read_string work, count_length=1
12
- # Offset Size Contents
13
- # 0 1 or 2 Length of the string (character count, ln)
14
- # 1 or 2 ln Character array (8-bit characters)
15
- fmt = count_length == 1 ? 'C' : 'v'
16
- length, = work.unpack fmt
17
- work[count_length, length]
18
- end
4
+ ##
5
+ # This Module collects reader methods such as read_string that are specific to
6
+ # Biff5. This Module is likely to be expanded as Support for older Versions
7
+ # of Excel grows.
8
+ module Biff5
9
+ ##
10
+ # Read a String of 8-bit Characters
11
+ def read_string work, count_length = 1
12
+ # Offset Size Contents
13
+ # 0 1 or 2 Length of the string (character count, ln)
14
+ # 1 or 2 ln Character array (8-bit characters)
15
+ fmt = (count_length == 1) ? "C" : "v"
16
+ length, = work.unpack fmt
17
+ work[count_length, length]
18
+ end
19
19
 
20
- def read_range_address_list work, len
21
- # Cell range address, BIFF2-BIFF5:
22
- # Offset Size Contents
23
- # 0 2 Index to first row
24
- # 2 2 Index to last row
25
- # 4 1 Index to first column
26
- # 5 1 Index to last column
27
- #
28
- offset = 0, results = []
29
- return results if len < 2
30
- count = work[0..1].unpack('v').first
31
- offset = 2
32
- count.times do |i|
33
- results << work[offset...offset+6].unpack('v2c2')
34
- offset += 6
35
- end
36
- results
37
- end
38
-
39
- end
20
+ def read_range_address_list work, len
21
+ # Cell range address, BIFF2-BIFF5:
22
+ # Offset Size Contents
23
+ # 0 2 Index to first row
24
+ # 2 2 Index to last row
25
+ # 4 1 Index to first column
26
+ # 5 1 Index to last column
27
+ #
28
+ results = []
29
+ return results if len < 2
30
+ count = work[0..1].unpack1("v")
31
+ offset = 2
32
+ count.times do |i|
33
+ results << work[offset...offset + 6].unpack("v2c2")
34
+ offset += 6
35
+ end
36
+ results
37
+ end
38
+ end
40
39
  end
41
40
  end
42
41
  end
@@ -1,231 +1,243 @@
1
1
  module Spreadsheet
2
2
  module Excel
3
3
  class Reader
4
- ##
5
- # This Module collects reader methods such as read_string that are specific to
6
- # Biff8. This Module is likely to be expanded as Support for older Versions
7
- # of Excel grows and methods get moved here for disambiguation.
8
- module Biff8
9
- include Spreadsheet::Excel::Internals
10
- ##
11
- # When a String is too long for one Opcode, it is continued in a Continue
12
- # Opcode. Excel may reconsider compressing the remainder of the string.
13
- # This method appends the available remainder (decompressed if necessary) to
14
- # the incomplete string.
15
- def continue_string work, incomplete_string=@incomplete_string
16
- opts, _ = work.unpack 'C'
17
- wide = opts & 1
18
- head, chars = incomplete_string
19
- owing = chars - head.size / 2
20
- size = owing * (wide + 1)
21
- string = work[1, size]
22
- if wide == 0
23
- string = wide string
24
- end
25
- head << string
26
- if head.size >= chars * 2
27
- @incomplete_string = nil
28
- end
29
- size + 1
30
- end
31
- # When a String is too long for one Opcode, it is continued in a Continue
32
- # Opcode. Excel may reconsider compressing the remainder of the string.
33
- # This method appends the available remainder (decompressed if necessary) to
34
- # the incomplete string.
35
- def unpack_string work
36
- opts, _ = work.unpack 'C'
37
- wide = opts & 1
38
- string = work[1, -1]
39
- if wide == 0
40
- string = wide string
41
- end
42
- end
43
- ##
44
- # When a String is too long for one Opcode, it is continued in a Continue
45
- # Opcode. Excel may reconsider compressing the remainder of the string.
46
- # This method only evaluates the header and registers the address of the
47
- # continuation with the previous SstEntry.
48
- def continue_string_header work, oppos
49
- opts, _ = work.unpack 'C'
50
- wide = opts & 1
51
- owing = @incomplete_sst.continued_chars
52
- size = [work.size, owing * (1 + wide) + 1].min
53
- chars = (size - 1) / (1 + wide)
54
- skip = size
55
- @incomplete_sst.continue oppos + OPCODE_SIZE, size, chars
56
- unless @incomplete_sst.continued?
57
- @workbook.add_shared_string @incomplete_sst
58
- skip += @incomplete_skip
59
- @incomplete_sst = nil
60
- @incomplete_skip = nil
61
- end
62
- skip
63
- end
64
- ##
65
- # Read more data into the Shared String Table. (see also: #read_sst)
66
- # This method only evaluates the header, the actual work is done in #_read_sst
67
- def continue_sst work, oppos, len
68
- pos = 0
69
- if @incomplete_sst
70
- pos = continue_string_header work, oppos
71
- elsif !@incomplete_skip.nil?
72
- pos = @incomplete_skip
73
- @incomplete_skip = nil
74
- end
75
- @sst_offset[1] += len
76
- _read_sst work, oppos, pos
77
- end
78
- def postread_workbook # :nodoc:
79
- super
80
- @incomplete_string, @sst_size, @sst_offset, @incomplete_sst = nil, @incomplete_skip = nil
81
- end
82
- ##
83
- # Store the offset of extsst, so we can write a new extsst when the
84
- # sst has changed
85
- def read_extsst work, pos, len
86
- @workbook.offsets.store :extsst, [pos, len]
87
- end
88
- ##
89
- # Read the Shared String Table present in all Biff8 Files.
90
- # This method only evaluates the header, the actual work is done in #_read_sst
91
- def read_sst work, pos, len
92
- # Offset Size Contents
93
- # 0 4 Total number of strings in the workbook (see below)
94
- # 4 4 Number of following strings (nm)
95
- # 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
96
- _, @sst_size = work.unpack 'V2'
97
- @sst_offset = [pos, len]
98
- @workbook.offsets.store :sst, @sst_offset
99
- _read_sst work, pos, 8
100
- end
101
- ##
102
- # Read a string from the Spreadsheet, such as a Worksheet- or Font-Name, or a
103
- # Number-Format. See also #read_string_header and #read_string_body
104
- def read_string work, count_length=1
105
- # Offset Size Contents
106
- # 0 1 or 2 Length of the string (character count, ln)
107
- # 1 or 2 1 Option flags:
108
- # Bit Mask Contents
109
- # 0 0x01 Character compression (ccompr):
110
- # 0 = Compressed (8-bit characters)
111
- # 1 = Uncompressed (16-bit characters)
112
- # 2 0x04 Asian phonetic settings (phonetic):
113
- # 0 = Does not contain Asian phonetic settings
114
- # 1 = Contains Asian phonetic settings
115
- # 3 0x08 Rich-Text settings (richtext):
116
- # 0 = Does not contain Rich-Text settings
117
- # 1 = Contains Rich-Text settings
118
- # [2 or 3] 2 (optional, only if richtext=1)
119
- # Number of Rich-Text formatting runs (rt)
120
- # [var.] 4 (optional, only if phonetic=1)
121
- # Size of Asian phonetic settings block (in bytes, sz)
122
- # var. ln Character array (8-bit characters
123
- # or 2∙ln or 16-bit characters, dependent on ccompr)
124
- # [var.] 4∙rt (optional, only if richtext=1)
125
- # List of rt formatting runs (➜ 3.2)
126
- # [var.] sz (optional, only if phonetic=1)
127
- # Asian Phonetic Settings Block (➜ 3.4.2)
128
- chars, offset, wide, _, _, available, owing, _ = read_string_header work, count_length
129
- string, _ = read_string_body work, offset, available, wide > 0
130
- if owing > 0
131
- @incomplete_string = [string, chars]
132
- end
133
- string
134
- end
135
- ##
136
- # Read the body of a string. Returns the String (decompressed if necessary) and
137
- # the available data (unchanged).
138
- def read_string_body work, offset, available, wide
139
- data = work[offset, available]
140
- widened_data = wide ? data : wide(data)
141
- [widened_data, data]
142
- end
143
- ##
144
- # Read the header of a string. Returns the following information in an Array:
145
- # * The total number of characters in the string
146
- # * The offset of the actual string data (= the length of this header in bytes)
147
- # * Whether or not the string was compressed (0/1)
148
- # * Whether or not the string contains asian phonetic settings (0/1)
149
- # * Whether or not the string contains richtext formatting (0/1)
150
- # * The number of bytes containing characters in this chunk of data
151
- # * The number of characters missing from this chunk of data and expected to
152
- # follow in a Continue Opcode
153
- def read_string_header work, count_length=1, offset=0
154
- fmt = count_length == 1 ? 'C2' : 'vC'
155
- chars, opts = work[offset, 1 + count_length].unpack fmt
156
- wide = opts & 1
157
- phonetic = (opts >> 2) & 1
158
- richtext = (opts >> 3) & 1
159
- size = chars * (wide + 1)
160
- skip = 0
161
- if richtext > 0
162
- runs, = work[offset + 1 + count_length, 2].unpack 'v'
163
- skip = 4 * runs
164
- end
165
- if phonetic > 0
166
- psize, = work[offset + 1 + count_length + richtext * 2, 4].unpack 'V'
167
- skip += psize
168
- end
169
- flagsize = 1 + count_length + richtext * 2 + phonetic * 4
170
- avbl = [work.size - offset, flagsize + size].min
171
- have_chrs = (avbl - flagsize) / (1 + wide)
172
- owing = chars - have_chrs
173
- [chars, flagsize, wide, phonetic, richtext, avbl, owing, skip]
174
- end
4
+ ##
5
+ # This Module collects reader methods such as read_string that are specific to
6
+ # Biff8. This Module is likely to be expanded as Support for older Versions
7
+ # of Excel grows and methods get moved here for disambiguation.
8
+ module Biff8
9
+ include Spreadsheet::Excel::Internals
10
+ ##
11
+ # When a String is too long for one Opcode, it is continued in a Continue
12
+ # Opcode. Excel may reconsider compressing the remainder of the string.
13
+ # This method appends the available remainder (decompressed if necessary) to
14
+ # the incomplete string.
15
+ def continue_string work, incomplete_string = @incomplete_string
16
+ opts, _ = work.unpack "C"
17
+ wide = opts & 1
18
+ head, chars = incomplete_string
19
+ owing = chars - head.size / 2
20
+ size = owing * (wide + 1)
21
+ string = work[1, size]
22
+ if wide == 0
23
+ string = wide string
24
+ end
25
+ head << string
26
+ if head.size >= chars * 2
27
+ @incomplete_string = nil
28
+ end
29
+ size + 1
30
+ end
175
31
 
176
- def read_range_address_list work, len
177
- # Cell range address, BIFF8:
178
- # Offset Size Contents
179
- # 0 2 Index to first row
180
- # 2 2 Index to last row
181
- # 4 2 Index to first column
182
- # 6 2 Index to last column
183
- # ! In several cases, BIFF8 still writes the BIFF2-BIFF5 format of a cell range address
184
- # (using 8-bit values for the column indexes). This will be mentioned at the respective place.
185
- #
186
- offset = 0, results = []
187
- return results if len < 2
188
- count = work[0..1].unpack('v').first
189
- offset = 2
190
- count.times do |i|
191
- results << work[offset...offset+8].unpack('v4')
192
- offset += 8
193
- end
194
- results
195
- end
196
- ##
197
- # Insert null-characters into a compressed UTF-16 string
198
- def wide string
199
- data = ''.dup
200
- string.each_byte do |byte| data << byte.chr << 0.chr end
201
- data
202
- end
203
- private
204
- ##
205
- # Read the Shared String Table present in all Biff8 Files.
206
- def _read_sst work, oppos, pos
207
- worksize = work.size
208
- while @workbook.sst_size < @sst_size && pos < worksize do
209
- sst = SstEntry.new :offset => oppos + OPCODE_SIZE + pos,
210
- :ole => @data,
211
- :reader => self
212
- sst.chars, sst.flags, wide, sst.phonetic, sst.richtext, sst.available,
213
- sst.continued_chars, skip = read_string_header work, 2, pos
214
- sst.wide = wide > 0
215
- if sst.continued?
216
- @incomplete_sst = sst
217
- @incomplete_skip = skip
218
- pos += sst.available
219
- else
220
- @workbook.add_shared_string sst
221
- pos += sst.available + skip
222
- if pos > worksize
223
- @incomplete_skip = pos - worksize
32
+ # When a String is too long for one Opcode, it is continued in a Continue
33
+ # Opcode. Excel may reconsider compressing the remainder of the string.
34
+ # This method appends the available remainder (decompressed if necessary) to
35
+ # the incomplete string.
36
+ def unpack_string work
37
+ opts, _ = work.unpack "C"
38
+ wide = opts & 1
39
+ string = work[1, -1]
40
+ if wide == 0
41
+ wide string
42
+ end
43
+ end
44
+
45
+ ##
46
+ # When a String is too long for one Opcode, it is continued in a Continue
47
+ # Opcode. Excel may reconsider compressing the remainder of the string.
48
+ # This method only evaluates the header and registers the address of the
49
+ # continuation with the previous SstEntry.
50
+ def continue_string_header work, oppos
51
+ opts, _ = work.unpack "C"
52
+ wide = opts & 1
53
+ owing = @incomplete_sst.continued_chars
54
+ size = [work.size, owing * (1 + wide) + 1].min
55
+ chars = (size - 1) / (1 + wide)
56
+ skip = size
57
+ @incomplete_sst.continue oppos + OPCODE_SIZE, size, chars
58
+ unless @incomplete_sst.continued?
59
+ @workbook.add_shared_string @incomplete_sst
60
+ skip += @incomplete_skip
61
+ @incomplete_sst = nil
62
+ @incomplete_skip = nil
63
+ end
64
+ skip
65
+ end
66
+
67
+ ##
68
+ # Read more data into the Shared String Table. (see also: #read_sst)
69
+ # This method only evaluates the header, the actual work is done in #_read_sst
70
+ def continue_sst work, oppos, len
71
+ pos = 0
72
+ if @incomplete_sst
73
+ pos = continue_string_header work, oppos
74
+ elsif !@incomplete_skip.nil?
75
+ pos = @incomplete_skip
76
+ @incomplete_skip = nil
77
+ end
78
+ @sst_offset[1] += len
79
+ _read_sst work, oppos, pos
80
+ end
81
+
82
+ def postread_workbook # :nodoc:
83
+ super
84
+ @incomplete_string, @sst_size, @sst_offset, @incomplete_sst = nil, @incomplete_skip = nil
85
+ end
86
+
87
+ ##
88
+ # Store the offset of extsst, so we can write a new extsst when the
89
+ # sst has changed
90
+ def read_extsst work, pos, len
91
+ @workbook.offsets.store :extsst, [pos, len]
92
+ end
93
+
94
+ ##
95
+ # Read the Shared String Table present in all Biff8 Files.
96
+ # This method only evaluates the header, the actual work is done in #_read_sst
97
+ def read_sst work, pos, len
98
+ # Offset Size Contents
99
+ # 0 4 Total number of strings in the workbook (see below)
100
+ # 4 4 Number of following strings (nm)
101
+ # 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
102
+ _, @sst_size = work.unpack "V2"
103
+ @sst_offset = [pos, len]
104
+ @workbook.offsets.store :sst, @sst_offset
105
+ _read_sst work, pos, 8
106
+ end
107
+
108
+ ##
109
+ # Read a string from the Spreadsheet, such as a Worksheet- or Font-Name, or a
110
+ # Number-Format. See also #read_string_header and #read_string_body
111
+ def read_string work, count_length = 1
112
+ # Offset Size Contents
113
+ # 0 1 or 2 Length of the string (character count, ln)
114
+ # 1 or 2 1 Option flags:
115
+ # Bit Mask Contents
116
+ # 0 0x01 Character compression (ccompr):
117
+ # 0 = Compressed (8-bit characters)
118
+ # 1 = Uncompressed (16-bit characters)
119
+ # 2 0x04 Asian phonetic settings (phonetic):
120
+ # 0 = Does not contain Asian phonetic settings
121
+ # 1 = Contains Asian phonetic settings
122
+ # 3 0x08 Rich-Text settings (richtext):
123
+ # 0 = Does not contain Rich-Text settings
124
+ # 1 = Contains Rich-Text settings
125
+ # [2 or 3] 2 (optional, only if richtext=1)
126
+ # Number of Rich-Text formatting runs (rt)
127
+ # [var.] 4 (optional, only if phonetic=1)
128
+ # Size of Asian phonetic settings block (in bytes, sz)
129
+ # var. ln Character array (8-bit characters
130
+ # or 2∙ln or 16-bit characters, dependent on ccompr)
131
+ # [var.] 4∙rt (optional, only if richtext=1)
132
+ # List of rt formatting runs (➜ 3.2)
133
+ # [var.] sz (optional, only if phonetic=1)
134
+ # Asian Phonetic Settings Block (➜ 3.4.2)
135
+ chars, offset, wide, _, _, available, owing, _ = read_string_header work, count_length
136
+ string, _ = read_string_body work, offset, available, wide > 0
137
+ if owing > 0
138
+ @incomplete_string = [string, chars]
139
+ end
140
+ string
141
+ end
142
+
143
+ ##
144
+ # Read the body of a string. Returns the String (decompressed if necessary) and
145
+ # the available data (unchanged).
146
+ def read_string_body work, offset, available, wide
147
+ data = work[offset, available]
148
+ widened_data = wide ? data : wide(data)
149
+ [widened_data, data]
150
+ end
151
+
152
+ ##
153
+ # Read the header of a string. Returns the following information in an Array:
154
+ # * The total number of characters in the string
155
+ # * The offset of the actual string data (= the length of this header in bytes)
156
+ # * Whether or not the string was compressed (0/1)
157
+ # * Whether or not the string contains asian phonetic settings (0/1)
158
+ # * Whether or not the string contains richtext formatting (0/1)
159
+ # * The number of bytes containing characters in this chunk of data
160
+ # * The number of characters missing from this chunk of data and expected to
161
+ # follow in a Continue Opcode
162
+ def read_string_header work, count_length = 1, offset = 0
163
+ fmt = (count_length == 1) ? "C2" : "vC"
164
+ chars, opts = work[offset, 1 + count_length].unpack fmt
165
+ wide = opts & 1
166
+ phonetic = (opts >> 2) & 1
167
+ richtext = (opts >> 3) & 1
168
+ size = chars * (wide + 1)
169
+ skip = 0
170
+ if richtext > 0
171
+ runs, = work[offset + 1 + count_length, 2].unpack "v"
172
+ skip = 4 * runs
173
+ end
174
+ if phonetic > 0
175
+ psize, = work[offset + 1 + count_length + richtext * 2, 4].unpack "V"
176
+ skip += psize
177
+ end
178
+ flagsize = 1 + count_length + richtext * 2 + phonetic * 4
179
+ avbl = [work.size - offset, flagsize + size].min
180
+ have_chrs = (avbl - flagsize) / (1 + wide)
181
+ owing = chars - have_chrs
182
+ [chars, flagsize, wide, phonetic, richtext, avbl, owing, skip]
183
+ end
184
+
185
+ def read_range_address_list work, len
186
+ # Cell range address, BIFF8:
187
+ # Offset Size Contents
188
+ # 0 2 Index to first row
189
+ # 2 2 Index to last row
190
+ # 4 2 Index to first column
191
+ # 6 2 Index to last column
192
+ # ! In several cases, BIFF8 still writes the BIFF2-BIFF5 format of a cell range address
193
+ # (using 8-bit values for the column indexes). This will be mentioned at the respective place.
194
+ #
195
+ results = []
196
+ return results if len < 2
197
+ count = work[0..1].unpack1("v")
198
+ offset = 2
199
+ count.times do |i|
200
+ results << work[offset...offset + 8].unpack("v4")
201
+ offset += 8
202
+ end
203
+ results
204
+ end
205
+
206
+ ##
207
+ # Insert null-characters into a compressed UTF-16 string
208
+ def wide string
209
+ data = "".dup
210
+ string.each_byte { |byte| data << byte.chr << 0.chr }
211
+ data
212
+ end
213
+
214
+ private
215
+
216
+ ##
217
+ # Read the Shared String Table present in all Biff8 Files.
218
+ def _read_sst work, oppos, pos
219
+ worksize = work.size
220
+ while @workbook.sst_size < @sst_size && pos < worksize
221
+ sst = SstEntry.new offset: oppos + OPCODE_SIZE + pos,
222
+ ole: @data,
223
+ reader: self
224
+ sst.chars, sst.flags, wide, sst.phonetic, sst.richtext, sst.available,
225
+ sst.continued_chars, skip = read_string_header work, 2, pos
226
+ sst.wide = wide > 0
227
+ if sst.continued?
228
+ @incomplete_sst = sst
229
+ @incomplete_skip = skip
230
+ pos += sst.available
231
+ else
232
+ @workbook.add_shared_string sst
233
+ pos += sst.available + skip
234
+ if pos > worksize
235
+ @incomplete_skip = pos - worksize
236
+ end
237
+ end
238
+ end
224
239
  end
225
240
  end
226
241
  end
227
242
  end
228
- end
229
- end
230
- end
231
243
  end