surpass 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/History.txt +0 -0
  2. data/README.txt +133 -0
  3. data/Rakefile +35 -0
  4. data/examples/big-16mb.rb +25 -0
  5. data/examples/big-random-strings.rb +28 -0
  6. data/examples/blanks.rb +34 -0
  7. data/examples/col_width.rb +16 -0
  8. data/examples/dates.rb +31 -0
  9. data/examples/format.rb +23 -0
  10. data/examples/hello-world.rb +9 -0
  11. data/examples/image.rb +10 -0
  12. data/examples/merged.rb +36 -0
  13. data/examples/merged0.rb +27 -0
  14. data/examples/merged1.rb +99 -0
  15. data/examples/num_formats.rb +55 -0
  16. data/examples/numbers.rb +24 -0
  17. data/examples/outline.rb +110 -0
  18. data/examples/panes.rb +48 -0
  19. data/examples/protection.rb +132 -0
  20. data/examples/python.bmp +0 -0
  21. data/examples/row_styles.rb +16 -0
  22. data/examples/row_styles_empty.rb +15 -0
  23. data/examples/set_cell_and_range_style.rb +12 -0
  24. data/examples/wrapped-text.rb +13 -0
  25. data/examples/write_arrays.rb +16 -0
  26. data/examples/ws_props.rb +80 -0
  27. data/lib/biff_record.rb +2168 -0
  28. data/lib/bitmap.rb +218 -0
  29. data/lib/cell.rb +214 -0
  30. data/lib/chart.rb +16 -0
  31. data/lib/column.rb +40 -0
  32. data/lib/document.rb +406 -0
  33. data/lib/excel_formula.rb +6 -0
  34. data/lib/excel_magic.rb +1013 -0
  35. data/lib/formatting.rb +554 -0
  36. data/lib/row.rb +137 -0
  37. data/lib/style.rb +179 -0
  38. data/lib/surpass.rb +51 -0
  39. data/lib/utilities.rb +86 -0
  40. data/lib/workbook.rb +206 -0
  41. data/lib/worksheet.rb +561 -0
  42. data/spec/biff_record_spec.rb +268 -0
  43. data/spec/cell_spec.rb +56 -0
  44. data/spec/data/random-strings.txt +10000 -0
  45. data/spec/document_spec.rb +168 -0
  46. data/spec/excel_formula_spec.rb +0 -0
  47. data/spec/formatting_spec.rb +53 -0
  48. data/spec/reference/P-0508-0000507647-3280-5298.xls +0 -0
  49. data/spec/reference/all-cell-styles.bin +0 -0
  50. data/spec/reference/all-number-formats.bin +0 -0
  51. data/spec/reference/all-styles.bin +0 -0
  52. data/spec/reference/mini.xls +0 -0
  53. data/spec/row_spec.rb +19 -0
  54. data/spec/spec_helper.rb +10 -0
  55. data/spec/style_spec.rb +89 -0
  56. data/spec/utilities_spec.rb +57 -0
  57. data/spec/workbook_spec.rb +48 -0
  58. data/spec/worksheet_spec.rb +0 -0
  59. data/stats/cloc.txt +8 -0
  60. data/stats/rcov.txt +0 -0
  61. data/stats/specdoc.txt +158 -0
  62. data/surpass-manual-0-0-3.pdf +0 -0
  63. data/surpass.gemspec +34 -0
  64. data/tasks/ann.rake +80 -0
  65. data/tasks/bones.rake +20 -0
  66. data/tasks/excel.rake +6 -0
  67. data/tasks/gem.rake +201 -0
  68. data/tasks/git.rake +40 -0
  69. data/tasks/metrics.rake +42 -0
  70. data/tasks/notes.rake +27 -0
  71. data/tasks/post_load.rake +34 -0
  72. data/tasks/rdoc.rake +51 -0
  73. data/tasks/rubyforge.rake +55 -0
  74. data/tasks/setup.rb +292 -0
  75. data/tasks/spec.rake +54 -0
  76. data/tasks/svn.rake +47 -0
  77. data/tasks/test.rake +40 -0
  78. metadata +144 -0
data/lib/bitmap.rb ADDED
@@ -0,0 +1,218 @@
1
+ class ObjBmpRecord < BiffRecord
2
+ RECORD_ID = 0x005D # Record identifier
3
+
4
+ def initialize(row, col, sheet, im_data_bmp, x, y, scale_x, scale_y)
5
+ width = im_data_bmp.width * scale_x
6
+ height = im_data_bmp.height * scale_y
7
+
8
+ col_start, x1, row_start, y1, col_end, x2, row_end, y2 = position_image(sheet, row, col, x, y, width, height)
9
+
10
+ # Store the OBJ record that precedes an IMDATA record. This could be generalise
11
+ # to support other Excel objects.
12
+ cobj = 0x0001 # count of objects in file (set to 1)
13
+ ot = 0x0008 # object type. 8 = picture
14
+ id = 0x0001 # object id
15
+ grbit = 0x0614 # option flags
16
+ coll = col_start # col containing upper left corner of object
17
+ dxl = x1 # distance from left side of cell
18
+ rwt = row_start # row containing top left corner of object
19
+ dyt = y1 # distance from top of cell
20
+ colr = col_end # col containing lower right corner of object
21
+ dxr = x2 # distance from right of cell
22
+ rwb = row_end # row containing bottom right corner of object
23
+ dyb = y2 # distance from bottom of cell
24
+ cbmacro = 0x0000 # length of fmla structure
25
+ reserved1 = 0x0000 # reserved
26
+ reserved2 = 0x0000 # reserved
27
+ icvback = 0x09 # background colour
28
+ icvfore = 0x09 # foreground colour
29
+ fls = 0x00 # fill pattern
30
+ fauto = 0x00 # automatic fill
31
+ icv = 0x08 # line colour
32
+ lns = 0xff # line style
33
+ lnw = 0x01 # line weight
34
+ fautob = 0x00 # automatic border
35
+ frs = 0x0000 # frame style
36
+ cf = 0x0009 # image format, 9 = bitmap
37
+ reserved3 = 0x0000 # reserved
38
+ cbpictfmla = 0x0000 # length of fmla structure
39
+ reserved4 = 0x0000 # reserved
40
+ grbit2 = 0x0001 # option flags
41
+ reserved5 = 0x0000 # reserved
42
+
43
+ args = [cobj, ot, id, grbit, coll, dxl, rwt, dyt, colr, dxr, rwb, dyb, cbmacro, reserved1, reserved2, icvback, icvfore, fls, fauto, icv, lns, lnw, fautob, frs, cf, reserved3, cbpictfmla, reserved4, grbit2, reserved5]
44
+ @record_data = args.pack('L v12 L v C8 v L v4 L')
45
+ end
46
+
47
+ # Calculate the vertices that define the position of the image as required by
48
+ # the OBJ record.
49
+ #
50
+ # +------------+------------+
51
+ # | A | B |
52
+ # +-----+------------+------------+
53
+ # | |(x1,y1) | |
54
+ # | 1 |(A1)._______|______ |
55
+ # | | | | |
56
+ # | | | | |
57
+ # +-----+----| BITMAP |-----+
58
+ # | | | | |
59
+ # | 2 | |______________. |
60
+ # | | | (B2)|
61
+ # | | | (x2,y2)|
62
+ # +---- +------------+------------+
63
+ #
64
+ # Example of a bitmap that covers some of the area from cell A1 to cell B2.
65
+ #
66
+ # Based on the width and height of the bitmap we need to calculate 8 vars:
67
+ # col_start, row_start, col_end, row_end, x1, y1, x2, y2.
68
+ # The width and height of the cells are also variable and have to be taken into
69
+ # account.
70
+ # The values of col_start and row_start are passed in from the calling
71
+ # function. The values of col_end and row_end are calculated by subtracting
72
+ # the width and height of the bitmap from the width and height of the
73
+ # underlying cells.
74
+ # The vertices are expressed as a percentage of the underlying cell width as
75
+ # follows (rhs values are in pixels):
76
+ #
77
+ # x1 = X / W *1024
78
+ # y1 = Y / H *256
79
+ # x2 = (X-1) / W *1024
80
+ # y2 = (Y-1) / H *256
81
+ #
82
+ # Where: X is distance from the left side of the underlying cell
83
+ # Y is distance from the top of the underlying cell
84
+ # W is the width of the cell
85
+ # H is the height of the cell
86
+ #
87
+ # Note: the SDK incorrectly states that the height should be expressed as a
88
+ # percentage of 1024.
89
+ #
90
+ # col_start - Col containing upper left corner of object
91
+ # row_start - Row containing top left corner of object
92
+ # x1 - Distance to left side of object
93
+ # y1 - Distance to top of object
94
+ # width - Width of image frame
95
+ # height - Height of image frame
96
+ def position_image(sheet, row_start, col_start, x1, y1, width, height)
97
+ while x1 >= size_col(sheet, col_start) do
98
+ x1 -= size_col(sheet, col_start)
99
+ col_start += 1
100
+ end
101
+
102
+ # Adjust start row for offsets that are greater than the row height
103
+ while y1 >= size_row(sheet, row_start) do
104
+ y1 -= size_row(sheet, row_start)
105
+ row_start += 1
106
+ end
107
+
108
+ # Initialise end cell to the same as the start cell
109
+ row_end = row_start # Row containing bottom right corner of object
110
+ col_end = col_start # Col containing lower right corner of object
111
+ width = width + x1 - 1
112
+ height = height + y1 - 1
113
+
114
+ # Subtract the underlying cell widths to find the end cell of the image
115
+ while (width >= size_col(sheet, col_end)) do
116
+ width -= size_col(sheet, col_end)
117
+ col_end += 1
118
+ end
119
+
120
+ # Subtract the underlying cell heights to find the end cell of the image
121
+ while (height >= size_row(sheet, row_end)) do
122
+ height -= size_row(sheet, row_end)
123
+ row_end += 1
124
+ end
125
+
126
+ # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
127
+ # with zero height or width.
128
+ starts_or_ends_in_hidden_cell = ((size_col(sheet, col_start) == 0) or (size_col(sheet, col_end) == 0) or (size_row(sheet, row_start) == 0) or (size_row(sheet, row_end) == 0))
129
+ return if starts_or_ends_in_hidden_cell
130
+
131
+ # Convert the pixel values to the percentage value expected by Excel
132
+ x1 = (x1.to_f / size_col(sheet, col_start) * 1024).to_i
133
+ y1 = (y1.to_f / size_row(sheet, row_start) * 256).to_i
134
+ # Distance to right side of object
135
+ x2 = (width.to_f / size_col(sheet, col_end) * 1024).to_i
136
+ # Distance to bottom of object
137
+ y2 = (height.to_f / size_row(sheet, row_end) * 256).to_i
138
+
139
+ [col_start, x1, row_start, y1, col_end, x2, row_end, y2]
140
+ end
141
+
142
+ def size_col(sheet, col)
143
+ sheet.col_width(col)
144
+ end
145
+
146
+ def size_row(sheet, row)
147
+ sheet.row_height(row)
148
+ end
149
+ end
150
+
151
+ class ImDataBmpRecord < BiffRecord
152
+ RECORD_ID = 0x007F
153
+
154
+ attr_accessor :width
155
+ attr_accessor :height
156
+ attr_accessor :size
157
+
158
+ # Insert a 24bit bitmap image in a worksheet. The main record required is
159
+ # IMDATA but it must be proceeded by a OBJ record to define its position.
160
+ def initialize(filename)
161
+ @width, @height, @size, data = process_bitmap(filename)
162
+
163
+ cf = 0x09
164
+ env = 0x01
165
+ lcb = @size
166
+
167
+ @record_data = [cf, env, lcb].pack('v2L') + data
168
+ end
169
+
170
+ # Convert a 24 bit bitmap into the modified internal format used by Windows.
171
+ # This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
172
+ # MSDN library.
173
+ def process_bitmap(filename)
174
+ data = nil
175
+ File.open(filename, "rb") do |f|
176
+ data = f.read
177
+ end
178
+
179
+ raise "bitmap #{filename} doesn't contain enough data" if data.length <= 0x36
180
+ raise "bitmap #{filename} is not valid" unless data[0, 2] === "BM"
181
+
182
+ # Remove bitmap data: ID.
183
+ data = data[2..-1]
184
+
185
+ # Read and remove the bitmap size. This is more reliable than reading
186
+ # the data size at offset 0x22.
187
+ size = data[0,4].unpack('L')[0]
188
+ size -= 0x36 # Subtract size of bitmap header.
189
+ size += 0x0C # Add size of BIFF header.
190
+
191
+ data = data[4..-1]
192
+ # Remove bitmap data: reserved, offset, header length.
193
+ data = data[12..-1]
194
+ # Read and remove the bitmap width and height. Verify the sizes.
195
+ width, height = data[0,8].unpack('L2')
196
+ data = data[8..-1]
197
+ raise "bitmap #{filename} largest image width supported is 65k." if (width > 0xFFFF)
198
+ raise "bitmap #{filename} largest image height supported is 65k." if (height > 0xFFFF)
199
+
200
+ # Read and remove the bitmap planes and bpp data. Verify them.
201
+ planes, bitcount = data[0,4].unpack('v2')
202
+ data = data[4..-1]
203
+ raise "bitmap #{filename} isn't a 24bit true color bitmap." if (bitcount != 24)
204
+ raise "bitmap #{filename} only 1 plane supported in bitmap image." if (planes != 1)
205
+
206
+ # Read and remove the bitmap compression. Verify compression.
207
+ compression = data[0,4].unpack('L')[0]
208
+ data = data[4..-1]
209
+ raise "bitmap #{filename} compression not supported in bitmap image." if (compression != 0)
210
+
211
+ # Remove bitmap data: data size, hres, vres, colours, imp. colours.
212
+ data = data[20..-1]
213
+ # Add the BITMAPCOREHEADER data
214
+ header = [0x000c, width, height, 0x01, 0x18].pack('Lv4')
215
+
216
+ [width, height, size, header + data]
217
+ end
218
+ end
data/lib/cell.rb ADDED
@@ -0,0 +1,214 @@
1
+ class Cell
2
+ attr_reader :index
3
+
4
+ def set_style(style)
5
+ style = StyleFormat.new(style) if style.is_a?(Hash)
6
+ @format_index = @parent.parent_wb.styles.add(style)
7
+ end
8
+
9
+ def row
10
+ @parent
11
+ end
12
+
13
+ def col
14
+ @index
15
+ end
16
+ end
17
+
18
+ class StringCell < Cell
19
+ def initialize(parent, index, format_index, sst_index)
20
+ @parent = parent
21
+ @index = index
22
+ @format_index = format_index
23
+ @sst_index = sst_index
24
+ end
25
+
26
+ def to_biff
27
+ LabelSSTRecord.new(@parent.index, @index, @format_index, @sst_index).to_biff
28
+ end
29
+ end
30
+
31
+ class BlankCell < Cell
32
+ def initialize(parent, index, format_index)
33
+ @parent = parent
34
+ @index = index
35
+ @format_index = format_index
36
+ end
37
+
38
+ def to_biff
39
+ BlankRecord.new(@parent.index, @index, @format_index).to_biff
40
+ end
41
+ end
42
+
43
+ class NumberCell < Cell
44
+ def initialize(parent, index, format_index, number)
45
+ @parent = parent
46
+ @index = index
47
+ @format_index = format_index
48
+ @number = number
49
+ end
50
+
51
+ def rk_record(rk_encoded)
52
+ RKRecord.new(@parent.index, @index, @format_index, rk_encoded).to_biff
53
+ end
54
+
55
+ # TODO test this section to be sure numbers are categorized and packed correctly.
56
+ def to_biff
57
+ # 30 bit signed int
58
+ in_range = (-0x20000000 <= @number) && (@number < 0x20000000)
59
+ is_int = (@number.to_i == @number)
60
+ if in_range && is_int
61
+ rk_encoded = 2 | (@number.to_i << 2)
62
+ return rk_record(rk_encoded)
63
+ end
64
+
65
+ # try scaling by 100 then using a 30 bit signed int
66
+ in_range = (-0x20000000 <= @number * 100) && (@number * 100 < 0x20000000)
67
+ round_trip = (@number.to_i*100) == @number*100
68
+ if in_range && round_trip
69
+ rk_encoded = (3 | (@number.to_i*100 << 2))
70
+ return rk_record(rk_encoded)
71
+ end
72
+
73
+ w0, w1, w2, w3 = [@number].pack('E').unpack('v4')
74
+
75
+ is_float_rk = (w0 == 0) && (w1 == 0) && (w2 & 0xFFFC) == w2
76
+ if is_float_rk
77
+ rk_encoded = (w3 << 16) | w2
78
+ return rk_record(rk_encoded)
79
+ end
80
+
81
+ w0, w1, w2, w3 = [@number * 100].pack('E').unpack('v4')
82
+
83
+ is_float_rk_100 = w0 == 0 && w1 == 0 && w2 & 0xFFFC == w2
84
+ if is_float_rk_100
85
+ rk_encoded = 1 | (w3 << 16) | w2
86
+ return rk_record(rk_encoded)
87
+ end
88
+
89
+ # If not an RK value, use a NumberRecord instead.
90
+ NumberRecord.new(@parent.index, @index, @format_index, @number).to_biff
91
+ end
92
+ end
93
+
94
+ class MulNumberCell < Cell
95
+ def initialize(parent, index, format_index, sst_index)
96
+ @parent = parent
97
+ @index = index
98
+ @format_index = format_index
99
+ @sst_index = sst_index
100
+ end
101
+
102
+ def to_biff
103
+ raise "not implemented"
104
+ end
105
+ end
106
+
107
+ class MulBlankCell < Cell
108
+ def initialize(parent, col1, col2, xf_idx)
109
+ raise unless col1 < col2
110
+ @parent = parent
111
+ @col1 = col1
112
+ @col2 = col2
113
+ @xf_idx = xf_idx
114
+ end
115
+
116
+ def to_biff
117
+ MulBlankRecord.new(@parent.index, @col1, @col2, @xf_idx).to_biff
118
+ end
119
+ end
120
+
121
+ class FormulaCell < Cell
122
+ attr_accessor :result
123
+
124
+ def initialize(parent, index, format_index, formula, calc_flags = 0)
125
+ @str = nil
126
+ @parent = parent
127
+ @index = index
128
+ @format_index = format_index
129
+ @options = formula.options.nil? ? parent.formula_options : formula.options
130
+ @formula = formula
131
+ @result = convert_formula_value_to_result(formula.default)
132
+ @calc_flags = calc_flags
133
+ end
134
+
135
+ def to_biff
136
+ args = [@parent.index, @index, @format_index, @result, @options, @formula.rpn, @calc_flags]
137
+ formula_data = FormulaRecord.new(*args).to_biff
138
+ formula_data += StringRecord.new(@str).to_biff if @str
139
+ formula_data
140
+ end
141
+
142
+ # TODO move this elsewhere, either Utilities or Formula ?
143
+ def convert_formula_value_to_result(value)
144
+ @str = ''
145
+ if value.is_a?(Numeric)
146
+ ret = [value].pack('E')
147
+ else
148
+ case value
149
+ when TrueClass, FalseClass
150
+ ret = [0x01, value ? 0x01 : 0x00].pack('CxC3x')
151
+ when ErrorCode
152
+ ret = [0x02, value.to_i].pack('CxC3x')
153
+ when String
154
+ ret = [0x00, 'Cx5']
155
+ @str = value # TODO convert to unicode
156
+ when NilClass
157
+ ret = [0x03, 'Cx5']
158
+ else
159
+ raise
160
+ end
161
+ ret += [0xFFFF].pack('C')
162
+ end
163
+ ret.unpack('Q')[0]
164
+ end
165
+ end
166
+
167
+
168
+ class BooleanCell < Cell
169
+ def initialize(parent, index, format_index, number)
170
+ @parent = parent
171
+ @index = index
172
+ @format_index = format_index
173
+ @number = number
174
+ @is_error = 0
175
+ end
176
+
177
+ def to_biff
178
+ number = @number ? 1 : 0
179
+ BoolErrRecord.new(@parent.index, @index, @format_index, number, @is_error).to_biff
180
+ end
181
+ end
182
+
183
+ class ErrorCell < Cell
184
+ ERROR_CODES = {
185
+ 0x00 => 0, # Intersection of two cell ranges is empty
186
+ 0x07 => 7, # Division by zero
187
+ 0x0F => 15, # Wrong type of operand
188
+ 0x17 => 23, # Illegal or deleted cell reference
189
+ 0x1D => 29, # Wrong function or range name
190
+ 0x24 => 36, # Value range overflow
191
+ 0x2A => 42, # Argument or function not available
192
+ '#NULL!' => 0, # Intersection of two cell ranges is empty
193
+ '#DIV/0!' => 7, # Division by zero
194
+ '#VALUE!' => 36, # Wrong type of operand
195
+ '#REF!' => 23, # Illegal or deleted cell reference
196
+ '#NAME?' => 29, # Wrong function or range name
197
+ '#NUM!' => 36, # Value range overflow
198
+ '#N/A!' => 42 # Argument or function not available
199
+ }
200
+
201
+ def initialize(parent, index, format_index, error_string_or_code)
202
+ @parent = parent
203
+ @index = index
204
+ @format_index = format_index
205
+ @number = ERROR_CODES[error_string_or_code]
206
+ @is_error = 1
207
+
208
+ raise "invalid error code #{error_string_or_code}" if @number.nil?
209
+ end
210
+
211
+ def to_biff
212
+ BoolErrRecord.new(@parent.index, @index, @format_index, @number, @is_error)
213
+ end
214
+ end
data/lib/chart.rb ADDED
@@ -0,0 +1,16 @@
1
+ class Chart
2
+ def initialize
3
+ raise "not implemented"
4
+ end
5
+
6
+
7
+ # ● OBJ Object description for the chart
8
+ # ● BOF Type = chart (➜5.8)
9
+ # Chart records
10
+ # ● EOF End of the Chart Substream of the chart object (5.37)
11
+ def to_biff
12
+ result = []
13
+ result << Biff8BOFRecord.new(Biff8BOFRecord::CHART).to_biff
14
+ result.join
15
+ end
16
+ end
data/lib/column.rb ADDED
@@ -0,0 +1,40 @@
1
+ class Column
2
+ attr_accessor :index
3
+ attr_accessor :width
4
+ attr_accessor :hidden
5
+ attr_accessor :level
6
+ attr_accessor :collapse
7
+
8
+ def initialize(index, parent)
9
+ is_int = index.is_a?(Integer)
10
+ in_range = (index >= 0) && (index <= 255)
11
+ raise "column index #{index} is not valid" unless is_int && in_range
12
+
13
+ @index = index
14
+ @parent = parent
15
+ @parent_wb = parent.parent
16
+ @xf_index = 0x0F
17
+
18
+ @width = 0x0B92
19
+ @hidden = 0
20
+ @level = 0
21
+ @collapse = 0
22
+ end
23
+
24
+ def to_biff
25
+ options = (as_numeric(@hidden) & 0x01) << 0
26
+ options |= (@level & 0x07) << 8
27
+ options |= (as_numeric(@collapse) & 0x01) << 12
28
+
29
+ ColInfoRecord.new(@index, @index, @width, @xf_index, options).to_biff
30
+ end
31
+
32
+ def set_style(style)
33
+ @xf_index = @parent_wb.add_style(style)
34
+ end
35
+
36
+ def width_in_pixels
37
+ # *** Approximation ****
38
+ (self.width * 0.0272 + 0.446).round
39
+ end
40
+ end