keeguon-spreadsheet 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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