surpass 0.0.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 (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