keeguon-spreadsheet 0.9.3

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +619 -0
  3. data/Manifest.txt +85 -0
  4. data/bin/xlsopcodes +18 -0
  5. data/lib/parseexcel.rb +27 -0
  6. data/lib/parseexcel/parseexcel.rb +75 -0
  7. data/lib/parseexcel/parser.rb +11 -0
  8. data/lib/spreadsheet.rb +80 -0
  9. data/lib/spreadsheet/column.rb +71 -0
  10. data/lib/spreadsheet/compatibility.rb +23 -0
  11. data/lib/spreadsheet/datatypes.rb +161 -0
  12. data/lib/spreadsheet/encodings.rb +57 -0
  13. data/lib/spreadsheet/excel.rb +88 -0
  14. data/lib/spreadsheet/excel/error.rb +26 -0
  15. data/lib/spreadsheet/excel/internals.rb +458 -0
  16. data/lib/spreadsheet/excel/internals/biff5.rb +17 -0
  17. data/lib/spreadsheet/excel/internals/biff8.rb +19 -0
  18. data/lib/spreadsheet/excel/offset.rb +41 -0
  19. data/lib/spreadsheet/excel/password_hash.rb +24 -0
  20. data/lib/spreadsheet/excel/reader.rb +1302 -0
  21. data/lib/spreadsheet/excel/reader/biff5.rb +42 -0
  22. data/lib/spreadsheet/excel/reader/biff8.rb +231 -0
  23. data/lib/spreadsheet/excel/rgb.rb +122 -0
  24. data/lib/spreadsheet/excel/row.rb +98 -0
  25. data/lib/spreadsheet/excel/sst_entry.rb +46 -0
  26. data/lib/spreadsheet/excel/workbook.rb +80 -0
  27. data/lib/spreadsheet/excel/worksheet.rb +115 -0
  28. data/lib/spreadsheet/excel/writer.rb +1 -0
  29. data/lib/spreadsheet/excel/writer/biff8.rb +75 -0
  30. data/lib/spreadsheet/excel/writer/format.rb +264 -0
  31. data/lib/spreadsheet/excel/writer/n_worksheet.rb +888 -0
  32. data/lib/spreadsheet/excel/writer/workbook.rb +735 -0
  33. data/lib/spreadsheet/excel/writer/worksheet.rb +940 -0
  34. data/lib/spreadsheet/font.rb +115 -0
  35. data/lib/spreadsheet/format.rb +209 -0
  36. data/lib/spreadsheet/formula.rb +9 -0
  37. data/lib/spreadsheet/helpers.rb +11 -0
  38. data/lib/spreadsheet/link.rb +43 -0
  39. data/lib/spreadsheet/note.rb +23 -0
  40. data/lib/spreadsheet/noteObject.rb +17 -0
  41. data/lib/spreadsheet/row.rb +151 -0
  42. data/lib/spreadsheet/workbook.rb +143 -0
  43. data/lib/spreadsheet/worksheet.rb +326 -0
  44. data/lib/spreadsheet/writer.rb +30 -0
  45. data/test/data/test_adding_data_to_existing_file.xls +0 -0
  46. data/test/data/test_borders.xls +0 -0
  47. data/test/data/test_changes.xls +0 -0
  48. data/test/data/test_comment.xls +0 -0
  49. data/test/data/test_copy.xls +0 -0
  50. data/test/data/test_datetime.xls +0 -0
  51. data/test/data/test_empty.xls +0 -0
  52. data/test/data/test_formula.xls +0 -0
  53. data/test/data/test_long_sst_record.xls +0 -0
  54. data/test/data/test_margin.xls +0 -0
  55. data/test/data/test_merged_and_protected.xls +0 -0
  56. data/test/data/test_merged_cells.xls +0 -0
  57. data/test/data/test_missing_row.xls +0 -0
  58. data/test/data/test_pagesetup.xls +0 -0
  59. data/test/data/test_version_excel5.xls +0 -0
  60. data/test/data/test_version_excel95.xls +0 -0
  61. data/test/data/test_version_excel97.xls +0 -0
  62. data/test/data/test_version_excel97_2010.xls +0 -0
  63. data/test/data/test_worksheet_visibility.xls +0 -0
  64. data/test/excel/reader.rb +30 -0
  65. data/test/excel/row.rb +40 -0
  66. data/test/excel/writer/workbook.rb +95 -0
  67. data/test/excel/writer/worksheet.rb +81 -0
  68. data/test/font.rb +163 -0
  69. data/test/format.rb +95 -0
  70. data/test/integration.rb +1390 -0
  71. data/test/row.rb +33 -0
  72. data/test/suite.rb +18 -0
  73. data/test/workbook.rb +55 -0
  74. data/test/workbook_protection.rb +19 -0
  75. data/test/worksheet.rb +112 -0
  76. metadata +148 -0
@@ -0,0 +1,42 @@
1
+ module Spreadsheet
2
+ module Excel
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
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
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,231 @@
1
+ module Spreadsheet
2
+ module Excel
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
175
+
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 = ''
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
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,122 @@
1
+ # A quick and dirty class for converting color palette values to RGB values.
2
+ # The values below have the form 0xRRGGBB, where RR is the red level, GG the
3
+ # green level, and BB the blue level. Each level is a value from 0 to 255,
4
+ # just as one would expect in HTML markup.
5
+
6
+ # Future directions may include:
7
+ # - support for mapping RGB values to "best fit" palette values
8
+ #
9
+ # by Dan Caugherty https://github.com/dancaugherty/spreadsheet/compare/master...rgb
10
+
11
+ module Spreadsheet
12
+ module Excel
13
+ class Rgb
14
+ attr_accessor :r, :g, :b
15
+
16
+ @@RGB_MAP = {
17
+ :xls_color_0 => 0x000000,
18
+ :xls_color_1 => 0xffffff,
19
+ :xls_color_2 => 0xff0000,
20
+ :xls_color_3 => 0x00ff00,
21
+ :xls_color_4 => 0x0000ff,
22
+ :xls_color_5 => 0xffff00,
23
+ :xls_color_6 => 0xff00ff,
24
+ :xls_color_7 => 0x00ffff,
25
+ :xls_color_8 => 0x800000,
26
+ :xls_color_9 => 0x008000,
27
+ :xls_color_10 => 0x008000,
28
+ :xls_color_11 => 0x000080,
29
+ :xls_color_12 => 0x808080,
30
+ :xls_color_13 => 0x008080,
31
+ :xls_color_14 => 0xc0c0c0,
32
+ :xls_color_15 => 0x808080,
33
+ :xls_color_16 => 0x9999ff,
34
+ :xls_color_17 => 0x993366,
35
+ :xls_color_18 => 0xffffcc,
36
+ :xls_color_19 => 0xccffff,
37
+ :xls_color_20 => 0x660066,
38
+ :xls_color_21 => 0xff8080,
39
+ :xls_color_22 => 0x0066cc,
40
+ :xls_color_23 => 0xccccff,
41
+ :xls_color_24 => 0x000080,
42
+ :xls_color_25 => 0xff00ff,
43
+ :xls_color_26 => 0xffff00,
44
+ :xls_color_27 => 0x00ffff,
45
+ :xls_color_28 => 0x800080,
46
+ :xls_color_29 => 0x800000,
47
+ :xls_color_30 => 0x008080,
48
+ :xls_color_31 => 0x0000ff,
49
+ :xls_color_32 => 0x00ccff,
50
+ :xls_color_33 => 0xccffff,
51
+ :xls_color_34 => 0xccffcc,
52
+ :xls_color_35 => 0xffff99,
53
+ :xls_color_36 => 0x99ccff,
54
+ :xls_color_37 => 0xff99cc,
55
+ :xls_color_38 => 0xcc99ff,
56
+ :xls_color_39 => 0xffcc99,
57
+ :xls_color_40 => 0x3366ff,
58
+ :xls_color_41 => 0x33cccc,
59
+ :xls_color_42 => 0x99cc00,
60
+ :xls_color_43 => 0xffcc00,
61
+ :xls_color_44 => 0xff9900,
62
+ :xls_color_45 => 0xff6600,
63
+ :xls_color_46 => 0x666699,
64
+ :xls_color_47 => 0x969696,
65
+ :xls_color_48 => 0x003366,
66
+ :xls_color_49 => 0x339966,
67
+ :xls_color_50 => 0x003300,
68
+ :xls_color_51 => 0x333300,
69
+ :xls_color_52 => 0x993300,
70
+ :xls_color_53 => 0x993366,
71
+ :xls_color_54 => 0x333399,
72
+ :xls_color_55 => 0x333333,
73
+ :builtin_black => 0x000000,
74
+ :builtin_white => 0xffffff,
75
+ :builtin_red => 0xff0000,
76
+ :builtin_green => 0x00ff00,
77
+ :builtin_blue => 0x0000ff,
78
+ :builtin_yellow => 0xffff00,
79
+ :builtin_magenta => 0xff00ff,
80
+ :builtin_cyan => 0x00ffff,
81
+ :aqua => 0x00ffff,
82
+ :black => 0x000000,
83
+ :blue => 0x0000ff,
84
+ :cyan => 0x00ffff,
85
+ :brown => 0x800000,
86
+ :fuchsia => 0xff00ff,
87
+ :gray => 0x808080,
88
+ :grey => 0x808080,
89
+ :green => 0x008000,
90
+ :lime => 0x00ff00,
91
+ :magenta => 0xff00ff,
92
+ :navy => 0x000080,
93
+ :orange => 0xff9900,
94
+ :purple => 0x800080,
95
+ :red => 0xff0000,
96
+ :silver => 0xc0c0c0,
97
+ :white => 0xffffff,
98
+ :yellow => 0xffff00
99
+ }
100
+
101
+ def self.to_rgb color_symbol
102
+ col = @@RGB_MAP[color_symbol]
103
+ return Rgb.new(col >> 16, (col & 0xff00) >> 8, col & 0xff) if col
104
+ nil
105
+ end
106
+
107
+ def initialize(r,g,b)
108
+ @r = r & 0xff
109
+ @g = g & 0xff
110
+ @b = b & 0xff
111
+ end
112
+
113
+ def to_i
114
+ (r * (256 * 256)) + (g * 256) + b
115
+ end
116
+
117
+ def as_hex
118
+ to_i.to_s(16)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,98 @@
1
+ require 'date'
2
+ require 'spreadsheet/row'
3
+
4
+ module Spreadsheet
5
+ module Excel
6
+ ##
7
+ # Excel-specific Row methods
8
+ class Row < Spreadsheet::Row
9
+ ##
10
+ # The Excel date calculation erroneously assumes that 1900 is a leap-year. All
11
+ # Dates after 28.2.1900 are off by one.
12
+ LEAP_ERROR = Date.new 1900, 2, 28
13
+ ##
14
+ # Force convert the cell at _idx_ to a Date
15
+ def date idx
16
+ _date at(idx)
17
+ end
18
+ ##
19
+ # Force convert the cell at _idx_ to a DateTime
20
+ def datetime idx
21
+ _datetime at(idx)
22
+ end
23
+ def each
24
+ size.times do |idx|
25
+ yield self[idx]
26
+ end
27
+ end
28
+ ##
29
+ # Access data in this Row like you would in an Array. If a cell is formatted
30
+ # as a Date or DateTime, the decoded Date or DateTime value is returned.
31
+ def [] idx, len=nil
32
+ if len
33
+ idx = idx...(idx+len)
34
+ end
35
+ if idx.is_a? Range
36
+ data = []
37
+ idx.each do |i|
38
+ data.push enriched_data(i, at(i))
39
+ end
40
+ data
41
+ else
42
+ enriched_data idx, at(idx)
43
+ end
44
+ end
45
+ ##
46
+ # Returns data as an array. If a cell is formatted as a Date or DateTime, the
47
+ # decoded Date or DateTime value is returned.
48
+ def to_a
49
+ self[0...length]
50
+ end
51
+ private
52
+ def _date data # :nodoc:
53
+ return data if data.is_a?(Date)
54
+ datetime = _datetime data
55
+ Date.new datetime.year, datetime.month, datetime.day
56
+ end
57
+ def _datetime data # :nodoc:
58
+ return data if data.is_a?(DateTime)
59
+ base = @worksheet.date_base
60
+ date = base + data.to_f
61
+ hour = (data.to_f % 1) * 24
62
+ min = (hour % 1) * 60
63
+ sec = ((min % 1) * 60).round
64
+ min = min.floor
65
+ hour = hour.floor
66
+ if sec > 59
67
+ sec = 0
68
+ min += 1
69
+ end
70
+ if min > 59
71
+ min = 0
72
+ hour += 1
73
+ end
74
+ if hour > 23
75
+ hour = 0
76
+ date += 1
77
+ end
78
+ if LEAP_ERROR > base
79
+ date -= 1
80
+ end
81
+ DateTime.new(date.year, date.month, date.day, hour, min, sec)
82
+ end
83
+ def enriched_data idx, data # :nodoc:
84
+ res = nil
85
+ if link = @worksheet.links[[@idx, idx]]
86
+ res = link
87
+ elsif data.is_a?(Numeric) && fmt = format(idx)
88
+ res = if fmt.datetime? || fmt.time?
89
+ _datetime data
90
+ elsif fmt.date?
91
+ _date data
92
+ end
93
+ end
94
+ res || data
95
+ end
96
+ end
97
+ end
98
+ end