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/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