spreadshoot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spreadshoot.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Mladen Jablanović
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Spreadshoot
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'spreadshoot'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install spreadshoot
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/examples/basic.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'spreadshoot'
2
+ require 'date'
3
+
4
+ spreadsheet = Spreadshoot.new do |s|
5
+ s.worksheet('Simple') do |w|
6
+ w.row do |r|
7
+ r.cell Date.today
8
+ r.cell 'foo'
9
+ @foo = r.cell 2
10
+ end
11
+ w.row do |r|
12
+ r.cell Date.today + 1
13
+ r.cell 'bar', :font => 'Times New Roman'
14
+ @bar = r.cell 3
15
+ end
16
+ w.row # empty one
17
+ w.row(:border => :top, :bold => true) do |r|
18
+ r.cell('total', :align => :center)
19
+ r.cell # empty cell
20
+ r.cell :formula => "#{@foo} + #{@bar}"
21
+ end
22
+ end
23
+
24
+ s.worksheet('Tables') do |w|
25
+ w.table(:direction => :horizontal) do |t|
26
+ t.row do |r|
27
+ r.cell 'foo'
28
+ @foo = r.cell 2
29
+ end
30
+ t.row do |r|
31
+ r.cell 'bar'
32
+ @bar = r.cell 3
33
+ end
34
+ t.row # empty one
35
+ t.row(:line => :above, :bold => true) do |r|
36
+ r.cell 'total'
37
+ r.cell # empty cell
38
+ @total1 = r.cell :formula => "#{@foo} + #{@bar}"
39
+ end
40
+ end
41
+ w.row # empty row
42
+ w.table do |t| # another table
43
+ t.row do |r|
44
+ r.cell 'foo'
45
+ @foo2 = r.cell 6
46
+ end
47
+ t.row do |r|
48
+ r.cell 'bar'
49
+ @bar2 = r.cell 7
50
+ end
51
+ t.row # empty one
52
+ t.row(:line => :above, :bold => true) do |r|
53
+ r.cell 'total'
54
+ r.cell # empty cell
55
+ @total2 = r.cell :formula => "#{@foo2} + #{@bar2}"
56
+ end
57
+ end
58
+ w.row do |r|
59
+ r.cell 'Grand total:'
60
+ r.cell
61
+ r.cell :formula => "#{@total1} + #{@total2}"
62
+ end
63
+ end
64
+
65
+ s.worksheet('Inverted Tables') do |w|
66
+ w.table(:direction => :horizontal) do |t|
67
+ t.row do |r|
68
+ r.cell 'foo'
69
+ @foo = r.cell 2
70
+ end
71
+ t.row do |r|
72
+ r.cell 'bar'
73
+ @bar = r.cell 3
74
+ end
75
+ t.row # empty one
76
+ t.row(:line => :above, :bold => true) do |r|
77
+ r.cell 'total'
78
+ r.cell # empty cell
79
+ r.cell :formula => "#{@foo} + #{@bar}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ spreadsheet.dump
86
+ spreadsheet.save(ARGV[0])
@@ -0,0 +1,3 @@
1
+ module Spreadshoot
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,528 @@
1
+ require 'set'
2
+ require 'date'
3
+ require 'builder'
4
+ require 'fileutils'
5
+
6
+ class Spreadshoot
7
+
8
+ # Create a new sheet, with given default formatting options
9
+ def initialize options = {}, &block
10
+ @worksheets = []
11
+ @ss = {}
12
+ @borders = {}
13
+ @fonts = {}
14
+ @styles = {}
15
+ @components = Set.new
16
+ yield(self)
17
+ end
18
+
19
+ # Gets the shared index of a given string
20
+ def ss_index string
21
+ @components << :ss
22
+ unless i = @ss[string]
23
+ i = @ss.length
24
+ @ss[string] = i
25
+ end
26
+ i
27
+ end
28
+
29
+ # gets the shared index of a border set (one or more of :top, :bottom, :left, :right)
30
+ def border borders
31
+ borders = [borders] unless borders.is_a?(Array)
32
+ borders.sort!
33
+ unless i = @borders[borders]
34
+ i = @borders.length
35
+ @borders[borders] = i
36
+ end
37
+ i
38
+ end
39
+
40
+ def font options
41
+ unless i = @fonts[options]
42
+ i = @fonts.length
43
+ @fonts[options] = i
44
+ end
45
+ i
46
+ end
47
+
48
+ # gets the shared index of a cell style (given by options hash)
49
+ def style options = {}
50
+ font = {}
51
+ style = options.each_with_object({}) do |(option, value), acc|
52
+ case option
53
+ when :border
54
+ acc[:border] = self.border(value)
55
+ when :align
56
+ acc[:align] = value
57
+ when :bold, :italic, :font
58
+ font[option] = value
59
+ when :format
60
+ acc[:format] = case value
61
+ when :date
62
+ 14
63
+ when :percent
64
+ 10
65
+ else
66
+ value
67
+ end
68
+ end
69
+ end
70
+ style[:font] = self.font(font) unless font.empty?
71
+ return nil if style.empty?
72
+
73
+ unless i = @styles[style]
74
+ i = @styles.length
75
+ @styles[style] = i
76
+ end
77
+ i
78
+ end
79
+
80
+ # Create a new worksheet within a spreadsheet
81
+ def worksheet title, options = {}, &block
82
+ ws = Worksheet.new(self, title, options, &block)
83
+ @worksheets << ws
84
+ end
85
+
86
+ # Saves the spreadsheet to an XLSX file with a given name
87
+ def save filename
88
+ dir = '/tmp/spreadshoot/'
89
+ FileUtils.rm_rf(dir)
90
+ FileUtils.mkdir_p(dir)
91
+ FileUtils.mkdir_p(File.join(dir, '_rels'))
92
+ FileUtils.mkdir_p(File.join(dir, 'xl', 'worksheets'))
93
+ FileUtils.mkdir_p(File.join(dir, 'xl', '_rels'))
94
+ File.open(File.join(dir, '[Content_Types].xml'), 'w') do |f|
95
+ f.write content_types
96
+ end
97
+ File.open(File.join(dir, '_rels', '.rels'), 'w') do |f|
98
+ f.write rels
99
+ end
100
+ File.open(File.join(dir, 'xl', 'workbook.xml'), 'w') do |f|
101
+ f.write workbook
102
+ end
103
+ @worksheets.each_with_index do |ws, i|
104
+ File.open(File.join(dir, 'xl', 'worksheets', "sheet#{i+1}.xml"), 'w') do |f|
105
+ f.write(ws)
106
+ end
107
+ end
108
+ File.open(File.join(dir, 'xl', 'sharedStrings.xml'), 'w') do |f|
109
+ f.write shared_strings
110
+ end if @components.member?(:ss)
111
+ File.open(File.join(dir, 'xl', 'styles.xml'), 'w') do |f|
112
+ f.write styles
113
+ end
114
+ File.open(File.join(dir, 'xl', '_rels', 'workbook.xml.rels'), 'w') do |f|
115
+ f.write xl_rels
116
+ end
117
+
118
+ filename = File.absolute_path(filename)
119
+ FileUtils.chdir(dir)
120
+ File.delete(filename) if File.exists?(filename)
121
+ # zip the result
122
+ puts `zip -r #{filename} ./`
123
+ # FileUtils.rmdir_rf(dir)
124
+ end
125
+
126
+ # Dumps main XMLs to the stdout (for debugging purposes)
127
+ def dump
128
+ puts @xml
129
+
130
+ @worksheets.each do |ws|
131
+ puts '==='
132
+ puts ws
133
+ end
134
+
135
+ puts '==='
136
+ puts shared_strings
137
+
138
+ puts '==='
139
+ puts styles
140
+ end
141
+
142
+ private
143
+
144
+ # Outputs final workbook XML
145
+ # <workbook
146
+ # xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
147
+ # xmlns:r="http:// schemas.openxmlformats.org /officeDocument/2006/relationships">
148
+ # <sheets>
149
+ # <sheet name="Sheet1" sheetId="1" r:id="rId1" />
150
+ # </sheets>
151
+ # </workbook>
152
+ def workbook
153
+ Builder::XmlMarkup.new.workbook(:xmlns => "http://schemas.openxmlformats.org/spreadsheetml/2006/main", :"xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships") do |wb|
154
+ wb.sheets do |sheets|
155
+ @worksheets.each_with_index do |ws, i|
156
+ sheets.sheet(:name => ws.title, :sheetId => i+1, :"r:id" => "rId#{i+1}")
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ # <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
163
+ # <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
164
+ # <Default Extension="bin" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"/>
165
+ # <Override PartName="/customXml/itemProps2.xml" ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml"/>
166
+ # <Override PartName="/customXml/itemProps3.xml" ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml"/>
167
+ # <Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
168
+ # <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
169
+ # <Override PartName="/customXml/itemProps1.xml" ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml"/>
170
+ # <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
171
+ # <Default Extension="xml" ContentType="application/xml"/>
172
+ # <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
173
+ # <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>
174
+ # <Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
175
+ # <Override PartName="/xl/worksheets/sheet3.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
176
+ # <Override PartName="/xl/drawings/drawing1.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/>
177
+ # <Override PartName="/xl/comments2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>
178
+ # <Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/>
179
+ # <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
180
+ # <Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>
181
+ # <Override PartName="/xl/comments1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"/>
182
+ # <Override PartName="/xl/calcChain.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"/>
183
+ # <Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/>
184
+ # <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>
185
+ # </Types>
186
+ def content_types
187
+ Builder::XmlMarkup.new.Types(:xmlns => "http://schemas.openxmlformats.org/package/2006/content-types") do |xt|
188
+ xt.Default(:Extension => "bin", :ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings")
189
+ xt.Default(:Extension => "rels", :ContentType => "application/vnd.openxmlformats-package.relationships+xml")
190
+ xt.Default(:Extension => "xml", :ContentType => "application/xml")
191
+ xt.Default(:Extension => "vml", :ContentType => "application/vnd.openxmlformats-officedocument.vmlDrawing")
192
+ xt.Override :PartName => "/xl/workbook.xml", :ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"
193
+ xt.Override :PartName => "/xl/sharedStrings.xml", :ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
194
+ xt.Override :PartName => "/xl/styles.xml", :ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
195
+ @worksheets.count.times do |i|
196
+ xt.Override :PartName => "/xl/worksheets/sheet#{i+1}.xml", :ContentType => "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
197
+ end
198
+ end
199
+ end
200
+
201
+ # <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
202
+ # <si>
203
+ # <t>Region</t>
204
+ # </si>
205
+ # <si>
206
+ # <t>Sales Person</t>
207
+ # </si>
208
+ # <si>
209
+ # <t>Sales Quota</t>
210
+ # </si>
211
+ # ...12 more items ...
212
+ # </sst>
213
+ def shared_strings
214
+ Builder::XmlMarkup.new.sst(:xmlns => "http://schemas.openxmlformats.org/spreadsheetml/2006/main") do |xsst|
215
+ @ss.keys.each do |str|
216
+ xsst.si do |xsi|
217
+ xsi.t(str)
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ # <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
224
+ # <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
225
+ # </Relationships>
226
+ def rels
227
+ Builder::XmlMarkup.new.Relationships(:xmlns => "http://schemas.openxmlformats.org/package/2006/relationships") do |rs|
228
+ rs.Relationship :Id => "rId1", :Type => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument", :Target => "xl/workbook.xml"
229
+ end
230
+ end
231
+
232
+ # <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
233
+ # <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
234
+ # <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
235
+ # <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
236
+ # <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" Target="sharedStrings.xml"/>
237
+ # <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain" Target="calcChain.xml"/>
238
+ # </Relationships>
239
+ def xl_rels
240
+ Builder::XmlMarkup.new.Relationships(:xmlns => "http://schemas.openxmlformats.org/package/2006/relationships") do |rs|
241
+ count = @worksheets.count
242
+ count.times do |i|
243
+ rs.Relationship :Id => "rId#{i+1}", :Type => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet", :Target => "worksheets/sheet#{i+1}.xml"
244
+ end
245
+ rs.Relationship :Id => "rId#{count+1}", :Type => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings", :Target => "sharedStrings.xml"
246
+ rs.Relationship(:Id => "rId#{count+2}", :Type => "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles", :Target => "styles.xml")
247
+ end
248
+ end
249
+
250
+ def styles
251
+ Builder::XmlMarkup.new.styleSheet(:xmlns => "http://schemas.openxmlformats.org/spreadsheetml/2006/main") do |xs|
252
+ xs.fonts do |xf|
253
+ xf.font
254
+ @fonts.keys.each do |font|
255
+ xf.font do |x|
256
+ x.b(:val => 1) if font[:bold]
257
+ x.i(:val => 1) if font[:italic]
258
+ x.name(:val => font[:font]) if font[:font]
259
+ end
260
+ end
261
+ end
262
+
263
+ xs.fills do |xf|
264
+ xf.fill do |x|
265
+ x.patternFill(:patternType => 'none')
266
+ end
267
+ end
268
+
269
+ xs.borders do |xbs|
270
+ xbs.border do |xb|
271
+ [:left, :right, :top, :bottom, :diagonal].each do |kind|
272
+ xb.tag!(kind)
273
+ end
274
+ end
275
+ @borders.keys.each do |border_set|
276
+ xbs.border do |xb|
277
+ [:left, :right, :top, :bottom, :diagonal].each do |kind|
278
+ if border_set.member?(kind)
279
+ xb.tag!(kind, :style => 'thin') do |x|
280
+ x.color(:rgb => 'FF000000')
281
+ end
282
+ else
283
+ xb.tag!(kind)
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ xs.cellStyleXfs do |xcsx|
291
+ xcsx.xf(:fillId => 0, :borderId => 0, :numFmtId => 0)
292
+ end
293
+
294
+ xs.cellXfs do |xcx|
295
+ xcx.xf(:fillId => 0, :numFmtId => 0, :borderId => 0, :xfId => 0)
296
+ @styles.keys.each do |style|
297
+ align = style[:align]
298
+ border = style[:border]
299
+ font = style[:font]
300
+ options = {:fillId => 0, :xfId => 0} # default
301
+ options[:applyAlignment] = align ? 1 : 0
302
+ options[:applyBorder] = border ? 1 : 0
303
+ options[:borderId] = border ? border + 1 : 0
304
+ options[:fontId] = font + 1 if font
305
+ options[:applyFont] = font ? 1 : 0
306
+ options[:numFmtId] = style[:format] ? style[:format] : 0
307
+ options[:applyNumberFormat] = style[:format] ? 1 : 0
308
+ xcx.xf(options) do |xf|
309
+ if align = style[:align]
310
+ xf.alignment(:horizontal => align)
311
+ end
312
+ end
313
+ end
314
+ end
315
+
316
+ # xs.cellStyles do |xcs|
317
+ # xcs.cellStyle(:name => "Normal", :xfId => "0", :builtinId => "0")
318
+ # end
319
+ end
320
+ end
321
+
322
+ class Worksheet
323
+ # <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" >
324
+ # <sheetData>
325
+ # <row>
326
+ # <c>
327
+ # <v>1234</v>
328
+ # </c>
329
+ # </row>
330
+ # </sheetData>
331
+ # </worksheet>
332
+
333
+ attr_reader :title, :xml, :spreadsheet, :row_index, :col_index, :cells
334
+ def initialize spreadsheet, title, options = {}, &block
335
+ @cells = {}
336
+ @spreadsheet = spreadsheet
337
+ @title = title
338
+ @options = options
339
+ @row_index = 0
340
+ @col_index = 0
341
+ @column_widths = {}
342
+ # default table, if none defined
343
+ @current_table = Table.new(self, options)
344
+
345
+ yield self
346
+ end
347
+
348
+ # outputs the worksheet as OOXML
349
+ def to_s
350
+ @xml ||= Builder::XmlMarkup.new.worksheet(:xmlns => "http://schemas.openxmlformats.org/spreadsheetml/2006/main") do |ws|
351
+ unless @column_widths.empty?
352
+ ws.cols do |xcols|
353
+ @column_widths.keys.sort.each do |i|
354
+ width = @column_widths[i]
355
+ params = {:min => i+1, :max => i+1, :bestFit => 1}
356
+ params.merge!({:customWidth => 1, :width => width}) if width
357
+ xcols.col(params)
358
+ end
359
+ end
360
+ end
361
+ ws.sheetData do |sd|
362
+ @cells.keys.sort.each do |row|
363
+ sd.row(:r => row+1) do |xr|
364
+
365
+ @cells[row].keys.sort.each do |col|
366
+ cell = @cells[row][col]
367
+ cell.output(xr)
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
374
+
375
+ def row options = {}, &block
376
+ row = @current_table.row options, &block
377
+ @row_index += 1
378
+ row
379
+ end
380
+
381
+ def table options = {}
382
+ @current_table = table = Table.new(self, options)
383
+ yield table
384
+ @row_index += table.row_max
385
+ @col_index = 0
386
+ @current_table = Table.new(self, @options) # preparing one in case row directly called next
387
+ table
388
+ end
389
+
390
+ def set_col_width col, width
391
+ @column_widths[col] = width
392
+ end
393
+ end # Worksheet
394
+
395
+
396
+ # Allows you to group cells to a logical table within a worksheet. Makes putting several tables
397
+ # to the same worksheet easier.
398
+ class Table
399
+ attr_reader :worksheet, :direction, :col_max, :row_max, :col_index, :row_index
400
+
401
+ def initialize worksheet, options = {}
402
+ @worksheet = worksheet
403
+ @options = options
404
+ @direction = options[:direction] || :vertical
405
+ @row_index = 0
406
+ @col_index = 0
407
+ @row_max = 0
408
+ @col_max = 0
409
+ @row_topleft = options[:row_topleft] || @worksheet.row_index
410
+ @col_topleft = options[:col_topleft] || @worksheet.col_index
411
+ end
412
+
413
+ def col_index= val
414
+ @col_max = val if val > @col_max
415
+ @col_index = val
416
+ end
417
+
418
+ def row_index= val
419
+ @row_max = val if val > @row_max
420
+ @row_index = val
421
+ end
422
+
423
+ def row options = {}
424
+ row = Row.new(self, options)
425
+ yield(row) if block_given?
426
+
427
+ if @direction == :vertical
428
+ self.row_index += 1
429
+ self.col_index = 0
430
+ else
431
+ self.col_index += 1
432
+ self.row_index = 0
433
+ end
434
+ row
435
+ end
436
+
437
+ def current_row
438
+ @row_topleft + @row_index
439
+ end
440
+
441
+ def current_col
442
+ @col_topleft + @col_index
443
+ end
444
+
445
+ # alphanumeric representation of coordinates
446
+ def coords
447
+ "#{Cell.alpha_index(current_col)}#{current_row+1}"
448
+ end
449
+
450
+ end
451
+
452
+ # A row of a table. The table could be horizontal oriented, or vertical oriented.
453
+ class Row
454
+ def initialize table, options = {}
455
+ @table = table
456
+ @options = options
457
+ end
458
+
459
+ def cell value = nil, options = {}
460
+ cell = Cell.new(@table, value, @options.merge(options))
461
+ @table.worksheet.cells[@table.current_row] ||= {}
462
+ @table.worksheet.cells[@table.current_row][@table.current_col] = cell
463
+ if @table.direction == :vertical
464
+ @table.col_index += 1
465
+ else
466
+ @table.row_index += 1
467
+ end
468
+ cell
469
+ end
470
+ end
471
+
472
+ # A cell within a row.
473
+ class Cell
474
+ # maps numeric column indices to letter based:
475
+ # 0 -> 'A', 1 -> 'B', 26 -> 'AA' and so on
476
+ def self.alpha_index i
477
+ @alpha_indices ||= ('A'..'ZZ').to_a
478
+ @alpha_indices[i]
479
+ end
480
+
481
+ def initialize table, value, options = {}
482
+ @table = table
483
+ @value = value
484
+ @options = options
485
+ @coords = @table.coords
486
+ @table.worksheet.set_col_width(@table.current_col, @options[:width]) if @options.has_key?(:width)
487
+ @options[:format] ||= :date if @value.is_a?(Date) || @value.is_a?(Time)
488
+ end
489
+
490
+ def current_col
491
+ @table.current_col
492
+ end
493
+
494
+ def current_row
495
+ @table.current_row
496
+ end
497
+
498
+ # outputs the cell into the resulting xml
499
+ def output xn_parent
500
+ r = {:r => @coords}
501
+ if style = @table.worksheet.spreadsheet.style(@options)
502
+ r.merge!(:s => style + 1)
503
+ end
504
+ case @value
505
+ when String
506
+ i = @table.worksheet.spreadsheet.ss_index(@value)
507
+ xn_parent.c(r.merge(:t => 's')){ |xc| xc.v(i) }
508
+ when Hash # no @value, formula in options
509
+ @options = @value
510
+ xn_parent.c(r) do |xc|
511
+ xc.f(@options[:formula])
512
+ end
513
+ when Date, Time
514
+ xn_parent.c(r){|xc| xc.v((@value - Date.new(1899,12,30)).to_i)}
515
+ when nil
516
+ xn_parent.c(r)
517
+ else
518
+ xn_parent.c(r){ |xc| xc.v(@value) }
519
+ end
520
+ end
521
+
522
+ def to_s
523
+ @coords
524
+ end
525
+
526
+ end
527
+
528
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/spreadshoot/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Mladen Jablanovic"]
6
+ gem.email = ["jablan@radioni.ca"]
7
+ gem.description = %q{Create XLSX files from scratch using Ruby}
8
+ gem.summary = %q{Ruby DSL for creating Excel xlsx spreadsheets}
9
+ gem.homepage = "https://github.com/jablan/spreadshoot"
10
+
11
+ gem.add_dependency "builder"
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.name = "spreadshoot"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = Spreadshoot::VERSION
19
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spreadshoot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mladen Jablanovic
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: builder
16
+ requirement: &79609200 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *79609200
25
+ description: Create XLSX files from scratch using Ruby
26
+ email:
27
+ - jablan@radioni.ca
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - examples/basic.rb
38
+ - lib/spreadshoot.rb
39
+ - lib/spreadshoot/version.rb
40
+ - spreadshoot.gemspec
41
+ homepage: https://github.com/jablan/spreadshoot
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.11
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Ruby DSL for creating Excel xlsx spreadsheets
65
+ test_files: []