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/style.rb ADDED
@@ -0,0 +1,179 @@
1
+ class StyleFormat
2
+ attr_accessor :number_format_string
3
+ attr_accessor :font
4
+ attr_accessor :alignment
5
+ attr_accessor :borders
6
+ attr_accessor :pattern
7
+ attr_accessor :protection
8
+
9
+ def initialize(hash = {})
10
+ @number_format_string = hash[:number_format_string] || 'General'
11
+
12
+ @font = Font.new(hash_select(hash, /^font_/))
13
+ @alignment = Alignment.new(hash_select(hash, /^text_/))
14
+ @borders = Borders.new(hash_select(hash, /^border_/))
15
+ @pattern = Pattern.new(hash_select(hash, /^fill_/))
16
+ @protection = Protection.new
17
+ end
18
+
19
+ def hash_select(hash, pattern)
20
+ new_hash = {}
21
+ hash.keys.each do |k|
22
+ next unless k.to_s =~ pattern
23
+ new_key = k.to_s.gsub(pattern, '').to_sym
24
+ new_hash[new_key] = hash[k]
25
+ end
26
+ new_hash
27
+ end
28
+ end
29
+
30
+ class StyleCollection
31
+ attr_accessor :fonts
32
+ attr_accessor :number_formats
33
+ attr_accessor :styles
34
+ attr_accessor :default_style
35
+ attr_accessor :default_format
36
+
37
+ FIRST_USER_DEFINED_NUM_FORMAT_INDEX = 164
38
+
39
+ STANDARD_NUMBER_FORMATS = [
40
+ 'General',
41
+ '0',
42
+ '0.00',
43
+ '#,##0',
44
+ '#,##0.00',
45
+ '"$"#,##0_);("$"#,##',
46
+ '"$"#,##0_);[Red]("$"#,##',
47
+ '"$"#,##0.00_);("$"#,##',
48
+ '"$"#,##0.00_);[Red]("$"#,##',
49
+ '0%',
50
+ '0.00%',
51
+ '0.00E+00',
52
+ '# ?/?',
53
+ '# ??/??',
54
+ 'M/D/YY',
55
+ 'D-MMM-YY',
56
+ 'D-MMM',
57
+ 'MMM-YY',
58
+ 'h:mm AM/PM',
59
+ 'h:mm:ss AM/PM',
60
+ 'h:mm',
61
+ 'h:mm:ss',
62
+ 'M/D/YY h:mm',
63
+ '_(#,##0_);(#,##0)',
64
+ '_(#,##0_);[Red](#,##0)',
65
+ '_(#,##0.00_);(#,##0.00)',
66
+ '_(#,##0.00_);[Red](#,##0.00)',
67
+ '_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
68
+ '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
69
+ '_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
70
+ '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
71
+ 'mm:ss',
72
+ '[h]:mm:ss',
73
+ 'mm:ss.0',
74
+ '##0.0E+0',
75
+ '@'
76
+ ]
77
+
78
+ def initialize
79
+ # Populate default font list.
80
+ @fonts = {}
81
+ # Initialize blank fonts into slots 0,1,2,3,5 in order to skip slot 4.
82
+ [0,1,2,3,5].each do |i|
83
+ @fonts[i] = Font.new
84
+ end
85
+
86
+ # Populate default number format list.
87
+ @number_formats = {}
88
+ STANDARD_NUMBER_FORMATS.each_with_index do |s, i|
89
+ index = (i <= 23) ? i : i + 14
90
+ @number_formats[index] = s
91
+ end
92
+
93
+ @styles = {}
94
+ @default_style = StyleFormat.new
95
+
96
+ # Store the 6 parameters of the default_style
97
+ @default_format = add_style(@default_style)[0]
98
+ end
99
+
100
+ def add(style)
101
+ if style.nil?
102
+ 0x10 # Return the index of the default style.
103
+ else
104
+ add_style(style)[1] # Return the index of the style just stored.
105
+ end
106
+ end
107
+
108
+ def number_format_index(number_format_string)
109
+ index = @number_formats.index(number_format_string)
110
+ if index.nil?
111
+ # TODO implement regex to check if valid string
112
+ index = FIRST_USER_DEFINED_NUM_FORMAT_INDEX + @number_formats.length - STANDARD_NUMBER_FORMATS.length
113
+ @number_formats[index] = number_format_string
114
+ end
115
+ index
116
+ end
117
+
118
+ def font_index(font)
119
+ index = @fonts.index(font)
120
+ if index.nil?
121
+ index = @fonts.length + 1
122
+ @fonts[index] = font
123
+ end
124
+ index
125
+ end
126
+
127
+ def format_index(format)
128
+ index = @styles.index(format)
129
+ if index.nil?
130
+ index = 0x10 + @styles.length
131
+ @styles[index] = format
132
+ end
133
+ index
134
+ end
135
+
136
+ private
137
+ # This is private, please use add(style) instead.
138
+ def add_style(style)
139
+ number_format_index = number_format_index(style.number_format_string)
140
+ font_index = font_index(style.font)
141
+
142
+ format = [font_index, number_format_index, style.alignment, style.borders, style.pattern, style.protection]
143
+ [format, format_index(format)]
144
+ end
145
+
146
+ public
147
+ def to_biff
148
+ fonts_biff + number_formats_biff + cell_styles_biff + StyleRecord.new.to_biff
149
+ end
150
+
151
+ # TODO use inject here?
152
+ def fonts_biff
153
+ result = ''
154
+ @fonts.sort.each do |i, f|
155
+ result += f.to_biff
156
+ end
157
+ result
158
+ end
159
+
160
+ def number_formats_biff
161
+ result = ''
162
+ @number_formats.sort.each do |i, f|
163
+ next if i < FIRST_USER_DEFINED_NUM_FORMAT_INDEX
164
+ result += NumberFormatRecord.new(i, f).to_biff
165
+ end
166
+ result
167
+ end
168
+
169
+ def cell_styles_biff
170
+ result = ''
171
+ 0.upto(15) do |i|
172
+ result += XFRecord.new(@default_format, 'style').to_biff
173
+ end
174
+ @styles.sort.each do |i, f|
175
+ result += XFRecord.new(f).to_biff
176
+ end
177
+ result
178
+ end
179
+ end
data/lib/surpass.rb ADDED
@@ -0,0 +1,51 @@
1
+ module Surpass
2
+
3
+ # :stopdoc:
4
+ VERSION = '0.0.3'
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ VERSION
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args )
20
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ end
22
+
23
+ # Returns the lpath for the module. If any arguments are given,
24
+ # they will be joined to the end of the path using
25
+ # <tt>File.join</tt>.
26
+ #
27
+ def self.path( *args )
28
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
29
+ end
30
+
31
+ # Utility method used to require all files ending in .rb that lie in the
32
+ # directory below this file that has the same name as the filename passed
33
+ # in. Optionally, a specific _directory_ name can be passed in such that
34
+ # the _filename_ does not have to be equivalent to the directory.
35
+ #
36
+ def self.require_all_libs_relative_to( fname, dir = "." )
37
+ dir ||= ::File.basename(fname, '.*')
38
+ search_me = ::File.expand_path(
39
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
40
+
41
+ Dir.glob(search_me).sort.each do |rb|
42
+ next if File.basename(rb) === File.basename(__FILE__) # skip surpass.rb
43
+ require rb
44
+ end
45
+ end
46
+
47
+ end # module Surpass
48
+
49
+ Surpass.require_all_libs_relative_to(__FILE__)
50
+
51
+ require 'date'
data/lib/utilities.rb ADDED
@@ -0,0 +1,86 @@
1
+ module Utilities
2
+ # For ease of comparing with pyExcelerator output values
3
+ # python seems to automatically decode to hex values
4
+ def hex_array_to_binary_string(array_of_hex_values)
5
+ [array_of_hex_values.collect {|h| [sprintf("%02x", h)]}.join].pack('H*')
6
+ end
7
+
8
+ def binary_string_to_hex_array(binary_string)
9
+ binary_string.unpack("H*")
10
+ end
11
+
12
+ def points_to_pixels(points)
13
+ points*(4.0/3)
14
+ end
15
+
16
+ def pixels_to_points(pixels)
17
+ pixels * (3.0 / 4)
18
+ end
19
+
20
+ def twips_to_pixels(twips)
21
+ twips / 15.0
22
+ end
23
+
24
+ def pixels_to_twips(pixels)
25
+ pixels * 15.0
26
+ end
27
+
28
+ def as_excel_date(date)
29
+ date = DateTime.parse(date.strftime("%c")) if date.is_a?(Time)
30
+ excel_date = (date - Date.civil(1899, 12, 31)).to_f
31
+ excel_date += 1 if excel_date > 59 # Add a day for Excel's missing leap day in 1900
32
+ excel_date
33
+ end
34
+
35
+ def mock_unicode_string(s)
36
+ [s.length, 0].pack('vC') + s
37
+ end
38
+
39
+
40
+ def as_boolean(input)
41
+ case input
42
+ when 1, true
43
+ true
44
+ when 0, false
45
+ false
46
+ else
47
+ raise "Can't convert #{input} from excel boolean!"
48
+ end
49
+ end
50
+
51
+ def as_numeric(input)
52
+ case input
53
+ when true, 1
54
+ 1
55
+ when false, 0
56
+ 0
57
+ else
58
+ raise "Can't convert #{input} to excel boolean!"
59
+ end
60
+ end
61
+
62
+ # Mimic python's "hex" function 0x00
63
+ def hex(value)
64
+ "0x" + value.to_s(16)
65
+ end
66
+ end
67
+
68
+ def String.random_alphanumeric(size=16)
69
+ s = ""
70
+ size.times { s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }
71
+ s
72
+ end
73
+
74
+ class TrueClass
75
+ def to_i
76
+ 1
77
+ end
78
+ end
79
+
80
+ class FalseClass
81
+ def to_i
82
+ 0
83
+ end
84
+ end
85
+
86
+ include Utilities
data/lib/workbook.rb ADDED
@@ -0,0 +1,206 @@
1
+ class Workbook
2
+ MACROS = {
3
+ 'Consolidate_Area' => 0x00,
4
+ 'Auto_Open' => 0x01,
5
+ 'Auto_Close' => 0x02,
6
+ 'Extract' => 0x03,
7
+ 'Database' => 0x04,
8
+ 'Criteria' => 0x05,
9
+ 'Print_Area' => 0x06,
10
+ 'Print_Titles' => 0x07, # in the docs it says Pint_Titles, I think its a mistake
11
+ 'Recorder' => 0x08,
12
+ 'Data_Form' => 0x09,
13
+ 'Auto_Activate' => 0x0A,
14
+ 'Auto_Deactivate' => 0x0B,
15
+ 'Sheet_Title' => 0x0C,
16
+ '_FilterDatabase' => 0x0D
17
+ }
18
+
19
+ attr_accessor :owner
20
+ attr_accessor :country_code
21
+ attr_accessor :wnd_protect
22
+ attr_accessor :obj_protect
23
+ attr_accessor :protect
24
+ attr_accessor :backup_on_save
25
+ attr_accessor :styles
26
+ attr_accessor :sst
27
+
28
+ def hpos_twips=(value)
29
+ @hpos_twips = value & 0xFFFF
30
+ end
31
+
32
+ attr_reader :vpos_twips
33
+ def vpos_twips=(value)
34
+ @vpos_twips = value & 0xFFFF
35
+ end
36
+
37
+ attr_reader :width_twips
38
+ def width_twips=(value)
39
+ @width_twips = value & 0xFFFF
40
+ end
41
+
42
+ attr_reader :height_twips
43
+ def height_twips=(value)
44
+ @height_twips = value & 0xFFFF
45
+ end
46
+
47
+ attr_reader :active_sheet
48
+ def active_sheet=(value)
49
+ @active_sheet = value & 0xFFFF
50
+ @first_tab_index = @active_sheet
51
+ end
52
+
53
+ attr_reader :tab_width_twips
54
+ def tab_width_twips=(value)
55
+ @tab_width_twips = value & 0xFFFF
56
+ end
57
+
58
+ attr_reader :default_style
59
+
60
+ def initialize(filename = nil)
61
+ @owner = 'None'
62
+ @wnd_protect = 0
63
+ @obj_protect = 0
64
+ @protect = 0
65
+ @backup_on_save = 0
66
+
67
+ @hpos_twips = 0x01E0
68
+ @vpos_twips = 0x005A
69
+ @width_twips = 0x3FCF
70
+ @height_twips = 0x2A4E
71
+
72
+ @active_sheet = 0
73
+ @first_tab_index = 0
74
+ @selected_tabs = 0x01
75
+ @tab_width_twips = 0x0258
76
+
77
+ @wnd_hidden = false
78
+ @wnd_mini = false
79
+ @hscroll_visible = true
80
+ @vscroll_visible = true
81
+ @tabs_visible = true
82
+
83
+ @styles = ::StyleCollection.new
84
+
85
+ @dates_1904 = false
86
+ @use_cell_values = true
87
+
88
+ @sst = SharedStringTable.new
89
+
90
+ @worksheets = []
91
+ @names = []
92
+ @refs = []
93
+
94
+ @filename = filename
95
+ end
96
+
97
+ def add_sheet(name = nil)
98
+ name ||= "Sheet#{@worksheets.length + 1}"
99
+ s = Worksheet.new(name, self)
100
+ @worksheets << s
101
+ s
102
+ end
103
+
104
+ def print_area(sheetnum, rstart, rend, cstart, cend)
105
+ if !sheetnum.is_a?(Integer)
106
+ i = 0
107
+ @worksheets.each_with_index do |w, i|
108
+ sheetnum = i+1 if w.name === sheetnum
109
+ break if sheetnum.is_a?(Integer)
110
+ end
111
+ end
112
+
113
+ options = 0x0020 # see Options Flags for Name record
114
+
115
+ # FIXME: this is just a bad hack, need to use Formula to make the rpn
116
+ #~ rpn = ExcelFormula.Formula('').rpn()[2:] # minus the size field
117
+ rpn = [0x3B, 0x0000, rstart, rend, cstart, cend].pack('Cv5')
118
+ args = [options, 0x00, MACROS['Print_Area'], sheetnum, rpn]
119
+ @names << NameRecord.new(*args).to_biff
120
+ end
121
+
122
+ def to_biff
123
+ raise "You cannot save a workbook with no worksheets" if @worksheets.empty?
124
+
125
+ section_1_array = []
126
+ section_1_array << Biff8BOFRecord.new(Biff8BOFRecord::BOOK_GLOBAL).to_biff
127
+ section_1_array << InterfaceHeaderRecord.new.to_biff
128
+ section_1_array << MMSRecord.new.to_biff
129
+ section_1_array << InterfaceEndRecord.new.to_biff
130
+ section_1_array << WriteAccessRecord.new(owner).to_biff
131
+ section_1_array << CodepageBiff8Record.new.to_biff
132
+ section_1_array << DSFRecord.new.to_biff
133
+ section_1_array << TabIDRecord.new(@worksheets.length).to_biff
134
+ section_1_array << FnGroupCountRecord.new.to_biff
135
+ section_1_array << WindowProtectRecord.new(as_numeric(@wnd_protect)).to_biff
136
+ section_1_array << ProtectRecord.new(as_numeric(@protect)).to_biff
137
+ section_1_array << ObjectProtectRecord.new(as_numeric(@obj_protect)).to_biff
138
+ section_1_array << PasswordRecord.new.to_biff
139
+ section_1_array << Prot4RevRecord.new.to_biff
140
+ section_1_array << Prot4RevPassRecord.new.to_biff
141
+ section_1_array << BackupRecord.new(@backup_on_save).to_biff
142
+ section_1_array << HideObjRecord.new.to_biff
143
+ section_1_array << window_1_record
144
+ section_1_array << DateModeRecord.new(@dates_1904).to_biff
145
+ section_1_array << PrecisionRecord.new(@use_cell_values).to_biff
146
+ section_1_array << RefreshAllRecord.new.to_biff
147
+ section_1_array << BookBoolRecord.new.to_biff
148
+ section_1_array << @styles.to_biff
149
+ section_1_array << '' # Palette
150
+ section_1_array << UseSelfsRecord.new.to_biff
151
+ section_1 = section_1_array.join
152
+
153
+ section_3_array = []
154
+ section_3_array << CountryRecord.new(@country_code, @country_code).to_biff unless @country_code.nil?
155
+ # section_3_array << InternalReferenceSupBookRecord.new(@worksheets.length).to_biff
156
+ # section_3_array << ExternSheetRecord.new(@refs).to_biff
157
+ # section_3_array << @names.collect {|n| n.to_biff}.join
158
+ section_3_array << @sst.to_biff
159
+ section_3 = section_3_array.join
160
+
161
+ section_4 = '' # ExtSSTRecord
162
+ section_5 = EOFRecord.new.to_biff
163
+
164
+ @worksheets[@active_sheet].selected = true
165
+ worksheet_biff_data = @worksheets.collect {|w| w.to_biff }
166
+ worksheet_biff_data_lengths = worksheet_biff_data.collect {|w| w.length }
167
+ section_6 = worksheet_biff_data.join
168
+
169
+ # Need to know how long the bound sheet records will be
170
+ boundsheet_data_lengths = @worksheets.collect {|w| BoundSheetRecord.new(0x00, w.visibility, w.name).to_biff.length }
171
+ total_boundsheet_data_length = boundsheet_data_lengths.inject(0) {|sum, l| sum + l}
172
+ start_position = section_1.length + total_boundsheet_data_length + section_3.length + section_4.length + section_5.length
173
+
174
+ boundsheet_records = []
175
+ @worksheets.each_with_index do |w, i|
176
+ boundsheet_records << BoundSheetRecord.new(start_position, w.visibility, w.name).to_biff
177
+ start_position += worksheet_biff_data_lengths[i]
178
+ end
179
+
180
+ section_2 = boundsheet_records.join
181
+ section_1 + section_2 + section_3 + section_4 + section_5 + section_6
182
+ end
183
+
184
+ def window_1_record
185
+ flags = 0
186
+ flags |= (as_numeric(@wnd_hidden)) << 0
187
+ flags |= (as_numeric(@wnd_mini)) << 1
188
+ flags |= (as_numeric(@hscroll_visible)) << 3
189
+ flags |= (as_numeric(@vscroll_visible)) << 4
190
+ flags |= (as_numeric(@tabs_visible)) << 5
191
+
192
+ args = [@hpos_twips, @vpos_twips, @width_twips, @height_twips, flags, @active_sheet, @first_tab_index, @selected_tabs, @tab_width_twips]
193
+ Window1Record.new(*args).to_biff
194
+ end
195
+
196
+ def data
197
+ doc = ExcelDocument.new
198
+ doc.data(to_biff)
199
+ end
200
+
201
+ def save(filename = nil)
202
+ @filename = filename unless filename.nil?
203
+ doc = ExcelDocument.new
204
+ doc.save(@filename, to_biff)
205
+ end
206
+ end