spreet 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/lib/spreet.rb +368 -0
- data/lib/spreet/handlers.rb +25 -0
- data/lib/spreet/handlers/csv.rb +68 -0
- data/lib/spreet/handlers/open_document.rb +76 -0
- data/spreet.gemspec +40 -0
- data/test/helper.rb +17 -0
- data/test/samples/pascal.csv +17 -0
- data/test/test_spreet.rb +91 -0
- metadata +95 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Brice Texier
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
Rake::TestTask.new(:test) do |test|
|
5
|
+
test.libs << 'lib' << 'test'
|
6
|
+
test.pattern = 'test/**/test_*.rb'
|
7
|
+
test.verbose = true
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
|
12
|
+
# Import all rake files
|
13
|
+
for rakefile in Dir.glob('lib/tasks/*.rake')
|
14
|
+
import(rakefile)
|
15
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/spreet.rb
ADDED
@@ -0,0 +1,368 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Spreet
|
4
|
+
|
5
|
+
module VERSION
|
6
|
+
version = nil
|
7
|
+
File.open("VERSION") {|f| version = f.read.split('.')}
|
8
|
+
MAJOR = version[0].to_i.freeze
|
9
|
+
MINOR = version[1].to_i.freeze
|
10
|
+
TINY = version[2].to_i.freeze
|
11
|
+
PATCH = TINY.freeze
|
12
|
+
PRE = version[3].freeze
|
13
|
+
STRING = version.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class Coordinates
|
18
|
+
# Limit coordinates x and y in 0..65535 but coordinates are in one integer of 32 bits
|
19
|
+
CPU_SEMI_WIDTH = 16 # ((RUBY_PLATFORM.match(/^[^\-]*[^\-0-9]64/) ? 64 : 32) / 2).freeze
|
20
|
+
Y_FILTER = ((1 << CPU_SEMI_WIDTH) - 1).freeze
|
21
|
+
|
22
|
+
BASE_26_BEF = "0123456789abcdefghijklmnop"
|
23
|
+
BASE_26_AFT = "abcdefghijklmnopqrstuvwxyz"
|
24
|
+
|
25
|
+
attr_accessor :x, :y
|
26
|
+
def initialize(*args)
|
27
|
+
value = (args.size == 1 ? args[0] : args)
|
28
|
+
@x, @y = 0, 0
|
29
|
+
if value.is_a? String
|
30
|
+
if value.downcase.match(/^[a-z]+[0-9]+$/)
|
31
|
+
value = value.downcase.split(/([A-Z]+|[0-9]+)/).delete_if{|x| x.size.zero?}
|
32
|
+
@x, @y = value[0].tr(BASE_26_AFT, BASE_26_BEF).to_i(26), value[1].to_i(10)-1
|
33
|
+
elsif value.downcase.match(/^[0-9]+[^0-9]+[0-9]+$/)
|
34
|
+
value = value.downcase.split(/[^0-9]+/)
|
35
|
+
@x, @y = value[0].to_i(10), value[1].to_i(10)
|
36
|
+
end
|
37
|
+
elsif value.is_a? Integer
|
38
|
+
@x, @y = (value >> CPU_SEMI_WIDTH), value & Y_FILTER
|
39
|
+
elsif value.is_a? Coordinates
|
40
|
+
@x, @y = value.x, value.y
|
41
|
+
elsif value.is_a? Array
|
42
|
+
@x, @y = value[0].to_i, value[1].to_i
|
43
|
+
elsif value.is_a? Hash
|
44
|
+
@x, @y = value[:x] || value[:column] || 0, value[:y] || value[:row] || 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
@x.to_s(26).tr(BASE_26_BEF, BASE_26_AFT).upcase+(@y+1).to_s(10)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_a
|
53
|
+
[@x, @y]
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_hash
|
57
|
+
{:x=>@x, :y=>@y}
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_i
|
61
|
+
(@x << CPU_SEMI_WIDTH) + @y
|
62
|
+
end
|
63
|
+
|
64
|
+
def ==(other_coordinate)
|
65
|
+
other_coordinate.x == self.x and other_coordinate.y == self.y
|
66
|
+
end
|
67
|
+
|
68
|
+
def <=>(other_coordinate)
|
69
|
+
self.to_i <=> other_coordinate.to_i
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Represents a cell in a sheet
|
74
|
+
class Cell
|
75
|
+
attr_reader :text, :value, :type, :sheet, :coordinates
|
76
|
+
|
77
|
+
def initialize(sheet, *args)
|
78
|
+
@sheet = sheet
|
79
|
+
@coordinates = Coordinates.new(*args)
|
80
|
+
self.value = nil
|
81
|
+
@empty = true
|
82
|
+
end
|
83
|
+
|
84
|
+
def value=(val)
|
85
|
+
@value = val
|
86
|
+
@type = determine_type
|
87
|
+
@text = val.to_s
|
88
|
+
@empty = false
|
89
|
+
end
|
90
|
+
|
91
|
+
def empty?
|
92
|
+
@empty
|
93
|
+
end
|
94
|
+
|
95
|
+
def clear!
|
96
|
+
self.value = nil
|
97
|
+
@empty = true
|
98
|
+
end
|
99
|
+
|
100
|
+
def remove!
|
101
|
+
@sheet.remove(self.coordinates)
|
102
|
+
end
|
103
|
+
|
104
|
+
def <=>(other_cell)
|
105
|
+
self.coordinates <=> other_cell.coordinates
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def determine_type
|
111
|
+
if value.is_a? Date
|
112
|
+
:date
|
113
|
+
elsif value.is_a? Integer
|
114
|
+
:integer
|
115
|
+
elsif value.is_a? Numeric
|
116
|
+
:decimal
|
117
|
+
elsif value.is_a? DateTime
|
118
|
+
:datetime
|
119
|
+
elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
|
120
|
+
:boolean
|
121
|
+
elsif value.nil?
|
122
|
+
:null
|
123
|
+
else
|
124
|
+
:string
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
class Sheet
|
132
|
+
attr_reader :document, :name, :columns
|
133
|
+
attr_accessor :current_row
|
134
|
+
|
135
|
+
def initialize(document, name=nil)
|
136
|
+
@document = document
|
137
|
+
self.name = name
|
138
|
+
raise ArgumentError.new("Must be a Document") unless document.is_a? Document
|
139
|
+
@current_row = 0
|
140
|
+
@cells = {}
|
141
|
+
end
|
142
|
+
|
143
|
+
def name=(value)
|
144
|
+
unless value
|
145
|
+
value = (@document.sheets.count > 0 ? @document.sheets[-1].name.succ : "Sheet 1")
|
146
|
+
end
|
147
|
+
raise ArgumentError.new("Name of sheet must be given") if value.to_s.strip.size.zero?
|
148
|
+
if @document.sheets[value]
|
149
|
+
raise ArgumentError.new("Name of sheet must be unique")
|
150
|
+
end
|
151
|
+
@name = value
|
152
|
+
end
|
153
|
+
|
154
|
+
def cells
|
155
|
+
@cells.delete_if{|k,v| v.empty?}
|
156
|
+
@cells.values
|
157
|
+
end
|
158
|
+
|
159
|
+
def next_row(increment = 1)
|
160
|
+
@current_row += increment
|
161
|
+
end
|
162
|
+
|
163
|
+
def previous_row(increment = 1)
|
164
|
+
@current_row -= increment
|
165
|
+
end
|
166
|
+
|
167
|
+
def [](*args)
|
168
|
+
coord = Coordinates.new(*args)
|
169
|
+
@cells[coord.to_i] ||= Cell.new(self, coord)
|
170
|
+
return @cells[coord.to_i]
|
171
|
+
end
|
172
|
+
|
173
|
+
def []=(*args)
|
174
|
+
value = args.delete_at(-1)
|
175
|
+
cell = self[*args]
|
176
|
+
cell.value = value
|
177
|
+
@bound = compute_bound
|
178
|
+
end
|
179
|
+
|
180
|
+
def row(*args)
|
181
|
+
options = {}
|
182
|
+
options = args.delete_at(-1) if args[-1].is_a? Hash
|
183
|
+
row = options[:row] || @current_row
|
184
|
+
args.each_index do |index|
|
185
|
+
self[index, row] = args[index]
|
186
|
+
end
|
187
|
+
next_row
|
188
|
+
end
|
189
|
+
|
190
|
+
def each_row(&block)
|
191
|
+
for j in 0..bound.y
|
192
|
+
row = []
|
193
|
+
for i in 0..bound.x
|
194
|
+
row[i] = self[i, j]
|
195
|
+
end
|
196
|
+
yield row
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Find or build cell
|
201
|
+
def cell(*args)
|
202
|
+
return c
|
203
|
+
end
|
204
|
+
|
205
|
+
def bound
|
206
|
+
@bound
|
207
|
+
end
|
208
|
+
|
209
|
+
def remove!(coordinates)
|
210
|
+
raise ArgumentError.new("Must be a Coordinates") unless document.is_a?(Coordinates)
|
211
|
+
@cells.delete(coordinates.to_i)
|
212
|
+
@bound = compute_bound
|
213
|
+
end
|
214
|
+
|
215
|
+
# Moves the sheet to an other position in the list of sheets
|
216
|
+
def move_to(position)
|
217
|
+
@document.sheets.move_at(self, position)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Moves the sheet higher in the list of sheets
|
221
|
+
def move_higher(increment=1)
|
222
|
+
@document.sheets.move(self, increment)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Moves the sheet lower in the list of sheets
|
226
|
+
def move_lower(increment=1)
|
227
|
+
@document.sheets.move(self, -increment)
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def compute_bound
|
233
|
+
bound = Coordinates.new
|
234
|
+
for id, cell in @cells
|
235
|
+
unless cell.empty?
|
236
|
+
bound.x = cell.coordinates.x if cell.coordinates.x > bound.x
|
237
|
+
bound.y = cell.coordinates.y if cell.coordinates.x > bound.y
|
238
|
+
end
|
239
|
+
end
|
240
|
+
return bound
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
class Sheets
|
247
|
+
|
248
|
+
def initialize(document)
|
249
|
+
raise ArgumentError.new("Must be a Document") unless document.is_a?(Document)
|
250
|
+
@document = document
|
251
|
+
@array = []
|
252
|
+
end
|
253
|
+
|
254
|
+
def count
|
255
|
+
@array.size
|
256
|
+
end
|
257
|
+
|
258
|
+
def index(name_or_sheet)
|
259
|
+
if name_or_sheet.is_a? String
|
260
|
+
@array.each_index do |i|
|
261
|
+
return i if @array[i].name == name_or_sheet
|
262
|
+
end
|
263
|
+
elsif name_or_sheet.is_a? Integer
|
264
|
+
return (@array[name_or_sheet].nil? ? nil : name_or_sheet)
|
265
|
+
else
|
266
|
+
return @array.index(name_or_sheet)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def add(name=nil, position=-1)
|
271
|
+
sheet = Sheet.new(@document, name)
|
272
|
+
@array.insert(position, sheet)
|
273
|
+
return sheet
|
274
|
+
end
|
275
|
+
|
276
|
+
def [](sheet)
|
277
|
+
sheet = index(sheet)
|
278
|
+
return (sheet.is_a?(Integer) ? @array[sheet] : nil)
|
279
|
+
end
|
280
|
+
|
281
|
+
def remove(sheet)
|
282
|
+
@array.delete(sheet)
|
283
|
+
end
|
284
|
+
|
285
|
+
def move(sheet, shift=0)
|
286
|
+
move_at(sheet, index(sheet) + shift)
|
287
|
+
end
|
288
|
+
|
289
|
+
def move_at(sheet, position=-1)
|
290
|
+
if i = index(sheet)
|
291
|
+
@array.insert(position, @array.delete_at(i))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def each(&block)
|
296
|
+
for item in @array
|
297
|
+
yield item
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
class Document
|
305
|
+
attr_reader :sheets
|
306
|
+
@@handlers = {}
|
307
|
+
@@associations = {}
|
308
|
+
|
309
|
+
def initialize(option={})
|
310
|
+
@sheets = Sheets.new(self)
|
311
|
+
end
|
312
|
+
|
313
|
+
def to_term
|
314
|
+
text = "Spreet (#{@sheets.count}):\n"
|
315
|
+
for sheet in @sheets
|
316
|
+
text << " - #{sheet.name}:\n"
|
317
|
+
for cell in sheet.cells.sort
|
318
|
+
text << " - #{cell.coordinates.to_s}: #{cell.text.inspect}\n"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
return text
|
322
|
+
end
|
323
|
+
|
324
|
+
def write(file, options={})
|
325
|
+
handler = self.class.extract_handler(file, options.delete(:format))
|
326
|
+
handler.write(self, file, options)
|
327
|
+
end
|
328
|
+
|
329
|
+
class << self
|
330
|
+
|
331
|
+
def register_handler(klass, name, options={})
|
332
|
+
if klass.respond_to?(:read) or klass.respond_to?(:write)
|
333
|
+
if name.is_a?(Symbol)
|
334
|
+
@@handlers[name] = klass # options.merge(:class=>klass)
|
335
|
+
elsif
|
336
|
+
raise ArgumentError.new("Name is invalid. Symbol expected, #{name.class.name} got.")
|
337
|
+
end
|
338
|
+
else
|
339
|
+
raise ArgumentError.new("Handler do not support :read or :write method.")
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def read(file, options={})
|
344
|
+
handler = extract_handler(file, options.delete(:format))
|
345
|
+
return handler.read(file, options)
|
346
|
+
end
|
347
|
+
|
348
|
+
def extract_handler(file, handler_name=nil)
|
349
|
+
file_path = Pathname.new(file)
|
350
|
+
extension = file_path.extname.to_s[1..-1]
|
351
|
+
if !handler_name and extension.size > 0
|
352
|
+
handler_name = extension.to_sym
|
353
|
+
end
|
354
|
+
if @@handlers[handler_name]
|
355
|
+
return @@handlers[handler_name]
|
356
|
+
else
|
357
|
+
raise ArgumentError.new("No corresponding handler (#{handler_name.inspect}). Available: #{@@handlers.keys.collect{|k| k.inspect}.join(', ')}.")
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
require 'spreet/handlers'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Spreet
|
2
|
+
|
3
|
+
# Default handler
|
4
|
+
class Handler
|
5
|
+
|
6
|
+
def self.read(file, options={})
|
7
|
+
raise NotImplementedError.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.write(spreet, file, options={})
|
11
|
+
raise NotImplementedError.new
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'spreet/handlers/csv'
|
19
|
+
require 'spreet/handlers/open_document'
|
20
|
+
|
21
|
+
Spreet::Document.register_handler Spreet::Handlers::CSV, :csv
|
22
|
+
Spreet::Document.register_handler Spreet::Handlers::ExcelCSV, :xcsv
|
23
|
+
# Spreet::Document.register_handler Spreet::Handlers::HTML, :html
|
24
|
+
Spreet::Document.register_handler Spreet::Handlers::OpenDocument, :ods
|
25
|
+
# Spreet::Document.register_handler Spreet::Handlers::PDF, :pdf
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'csv'
|
3
|
+
require 'iconv'
|
4
|
+
|
5
|
+
module Spreet
|
6
|
+
# Universal CSV support
|
7
|
+
CSV = (::CSV.const_defined?(:Reader) ? ::FasterCSV : ::CSV).freeze
|
8
|
+
|
9
|
+
module Handlers
|
10
|
+
|
11
|
+
class CSV < Spreet::Handler
|
12
|
+
|
13
|
+
# Read a CSV file and create its Spreet document
|
14
|
+
def self.read(file, options={})
|
15
|
+
spreet = Spreet::Document.new
|
16
|
+
sheet = spreet.sheets.add
|
17
|
+
Spreet::CSV.foreach(file) do |row|
|
18
|
+
sheet.row *row
|
19
|
+
end
|
20
|
+
return spreet
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# Write a Spreet to a CSV file
|
25
|
+
def self.write(spreet, file, options={})
|
26
|
+
sheet = spreet.sheets[options[:sheet]||0]
|
27
|
+
Spreet::CSV.open(file, "wb") do |csv|
|
28
|
+
sheet.each_row do |row|
|
29
|
+
csv << row.collect{|c| c.text}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class ExcelCSV < Spreet::Handler
|
37
|
+
|
38
|
+
# Read a CSV file and create its Spreet document
|
39
|
+
def self.read(file, options={})
|
40
|
+
spreet = Spreet::Document.new
|
41
|
+
sheet = spreet.sheets.add
|
42
|
+
options = {:col_sep=>';'}.merge(options)
|
43
|
+
ic = Iconv.new('utf-8', 'cp1252')
|
44
|
+
Spreet::CSV.foreach(file, options) do |row|
|
45
|
+
sheet.row *(row.collect{|v| ic.iconv(v.to_s)})
|
46
|
+
end
|
47
|
+
return spreet
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Write a Spreet to a CSV file
|
52
|
+
def self.write(spreet, file, options={})
|
53
|
+
sheet = spreet.sheets[options[:sheet]||0]
|
54
|
+
options = {:col_sep=>';'}.merge(options)
|
55
|
+
ic = Iconv.new('cp1252', 'utf-8')
|
56
|
+
Spreet::CSV.open(file, "wb", options) do |csv|
|
57
|
+
sheet.each_row do |row|
|
58
|
+
csv << row.collect{|c| ic.iconv(c.text)}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'zip/zip'
|
3
|
+
|
4
|
+
module Spreet
|
5
|
+
module Handlers
|
6
|
+
class OpenDocument < Spreet::Handler
|
7
|
+
DATE_REGEXP = /\%./
|
8
|
+
DATE_ELEMENTS = {
|
9
|
+
"m" => "<number:month number:style=\"long\"/>",
|
10
|
+
"d" => "<number:day number:style=\"long\"/>",
|
11
|
+
"Y" => "<number:year/>"
|
12
|
+
}
|
13
|
+
|
14
|
+
|
15
|
+
def self.mimetype
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.xmlec(string)
|
19
|
+
zs = string.to_s.gsub('&', '&').gsub('\'', ''').gsub('<', '<').gsub('>', '>')
|
20
|
+
zs.force_encoding('US-ASCII') if zs.respond_to?(:force_encoding)
|
21
|
+
return zs
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def self.write(spreet, file, options={})
|
26
|
+
xml_escape = "to_s.gsub('&', '&').gsub('\\'', ''').gsub('<', '<').gsub('>', '>')"
|
27
|
+
xml_escape << ".force_encoding('US-ASCII')" if xml_escape.respond_to?(:force_encoding)
|
28
|
+
mimetype = "application/vnd.oasis.opendocument.spreadsheet"
|
29
|
+
# name = #{table.model.name}.model_name.human.gsub(/[^a-z0-9]/i,'_')
|
30
|
+
Zip::ZipOutputStream.open(file) do |zile|
|
31
|
+
# MimeType in first place
|
32
|
+
zile.put_next_entry('mimetype', nil, nil, Zip::ZipEntry::STORED)
|
33
|
+
zile << mimetype
|
34
|
+
|
35
|
+
# Manifest
|
36
|
+
zile.put_next_entry('META-INF/manifest.xml')
|
37
|
+
zile << ("<?xml version=\"1.0\" encoding=\"UTF-8\"?><manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\"><manifest:file-entry manifest:media-type=\"#{mimetype}\" manifest:full-path=\"/\"/><manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/></manifest:manifest>")
|
38
|
+
zile.put_next_entry('content.xml')
|
39
|
+
|
40
|
+
zile << ("<?xml version=\"1.0\" encoding=\"UTF-8\"?><office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\" xmlns:math=\"http://www.w3.org/1998/Math/MathML\" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\" xmlns:ooo=\"http://openoffice.org/2004/office\" xmlns:ooow=\"http://openoffice.org/2004/writer\" xmlns:oooc=\"http://openoffice.org/2004/calc\" xmlns:dom=\"http://www.w3.org/2001/xml-events\" xmlns:xforms=\"http://www.w3.org/2002/xforms\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:field=\"urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:field:1.0\" office:version=\"1.1\"><office:scripts/>")
|
41
|
+
# Styles
|
42
|
+
default_date_format = '%d/%m%Y' # ::I18n.translate("date.formats.default")
|
43
|
+
zile << ("<office:automatic-styles><style:style style:name=\"co1\" style:family=\"table-column\"><style:table-column-properties fo:break-before=\"auto\" style:use-optimal-column-width=\"true\"/></style:style><style:style style:name=\"header\" style:family=\"table-cell\"><style:text-properties fo:font-weight=\"bold\" style:font-weight-asian=\"bold\" style:font-weight-complex=\"bold\"/></style:style><number:date-style style:name=\"K4D\" number:automatic-order=\"true\"><number:text>"+default_date_format.gsub(DATE_REGEXP){|x| "</number:text>"+DATE_ELEMENTS[x[1..1]]+"<number:text>"} +"</number:text></number:date-style><style:style style:name=\"ce1\" style:family=\"table-cell\" style:data-style-name=\"K4D\"/></office:automatic-styles>")
|
44
|
+
|
45
|
+
zile << ("<office:body><office:spreadsheet>")
|
46
|
+
# Tables
|
47
|
+
for sheet in spreet.sheets
|
48
|
+
zile << ("<table:table table:name=\"#{xmlec(sheet.name)}\">")
|
49
|
+
zile << ("<table:table-column table:number-columns-repeated=\"#{sheet.bound.x+1}\"/>")
|
50
|
+
# zile << ("<table:table-header-rows><table:table-row>"+columns_headers(table).collect{|h| "<table:table-cell table:style-name=\"header\" office:value-type=\"string\"><text:p>'+(#{h}).#{xml_escape}+'</text:p></table:table-cell>"}.join+"</table:table-row></table:table-header-rows>")
|
51
|
+
sheet.each_row do |row| # #{record} in #{table.records_variable_name}\n"
|
52
|
+
zile << "<table:table-row>"
|
53
|
+
for cell in row
|
54
|
+
zile << "<table:table-cell"+(if cell.type == :decimal
|
55
|
+
" office:value-type=\"float\" office:value=\"#{xmlec(cell.value)}\""
|
56
|
+
elsif cell.type == :boolean
|
57
|
+
" office:value-type=\"boolean\" office:boolean-value=\"#{xmlec(cell.value ? 'true' : 'false')}\""
|
58
|
+
elsif cell.type == :date
|
59
|
+
" office:value-type=\"date\" table:style-name=\"ce1\" office:date-value=\"#{xmlec(cell.value)}\""
|
60
|
+
else
|
61
|
+
" office:value-type=\"string\""
|
62
|
+
end)+"><text:p>"+xmlec(cell.text)+"</text:p></table:table-cell>"
|
63
|
+
end
|
64
|
+
zile << "</table:table-row>"
|
65
|
+
end
|
66
|
+
zile << ("</table:table>")
|
67
|
+
end
|
68
|
+
zile << ("</office:spreadsheet></office:body></office:document-content>")
|
69
|
+
end
|
70
|
+
# Zile is finished
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/spreet.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
s.name = "spreet"
|
4
|
+
File.open("VERSION", "rb") do |f|
|
5
|
+
s.version = f.read
|
6
|
+
end
|
7
|
+
|
8
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
s.authors = ["Brice Texier"]
|
10
|
+
s.date = "2011-09-19"
|
11
|
+
s.summary = "Spr[eadsh]eet handler"
|
12
|
+
s.description = "Spr[eadsh]eet handler for CSV(RW), Excel CSV(RW) and ODS(W). The goal is to read and write in many open formats."
|
13
|
+
s.email = "brice.texier@ekylibre.org"
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE.txt",
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.homepage = "http://github.com/burisu/spreet"
|
20
|
+
s.licenses = ["MIT"]
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
s.specification_version = 3
|
25
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_runtime_dependency("fastercsv", [">= 0"])
|
27
|
+
s.add_runtime_dependency("libxml-ruby", [">= 0"])
|
28
|
+
s.add_runtime_dependency("rubyzip", [">= 0.9.4"])
|
29
|
+
else
|
30
|
+
s.add_dependency("fastercsv", [">= 0"])
|
31
|
+
s.add_dependency("libxml-ruby", [">= 0"])
|
32
|
+
s.add_dependency("rubyzip", [">= 0.9.4"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency("fastercsv", [">= 0"])
|
36
|
+
s.add_dependency("libxml-ruby", [">= 0"])
|
37
|
+
s.add_dependency("rubyzip", [">= 0.9.4"])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
require 'spreet'
|
15
|
+
|
16
|
+
class Test::Unit::TestCase
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
2
|
+
1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
3
|
+
1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
4
|
+
1,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
5
|
+
1,4,6,4,1,0,0,0,0,0,0,0,0,0,0,0,0
|
6
|
+
1,5,10,10,5,1,0,0,0,0,0,0,0,0,0,0,0
|
7
|
+
1,6,15,20,15,6,1,0,0,0,0,0,0,0,0,0,0
|
8
|
+
1,7,21,35,35,21,7,1,0,0,0,0,0,0,0,0,0
|
9
|
+
1,8,28,56,70,56,28,8,1,0,0,0,0,0,0,0,0
|
10
|
+
1,9,36,84,126,126,84,36,9,1,0,0,0,0,0,0,0
|
11
|
+
1,10,45,120,210,252,210,120,45,10,1,0,0,0,0,0,0
|
12
|
+
1,11,55,165,330,462,462,330,165,55,11,1,0,0,0,0,0
|
13
|
+
1,12,66,220,495,792,924,792,495,220,66,12,1,0,0,0,0
|
14
|
+
1,13,78,286,715,1287,1716,1716,1287,715,286,78,13,1,0,0,0
|
15
|
+
1,14,91,364,1001,2002,3003,3432,3003,2002,1001,364,91,14,1,0,0
|
16
|
+
1,15,105,455,1365,3003,5005,6435,6435,5005,3003,1365,455,105,15,1,0
|
17
|
+
1,16,120,560,1820,4368,8008,11440,12870,11440,8008,4368,1820,560,120,16,1
|
data/test/test_spreet.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
class TestSpreet < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_coordinates
|
7
|
+
assert_equal Spreet::Coordinates.new(0,0), Spreet::Coordinates.new("A1")
|
8
|
+
assert_equal Spreet::Coordinates.new(0,0), Spreet::Coordinates.new("0-0")
|
9
|
+
assert_equal Spreet::Coordinates.new(1,1), Spreet::Coordinates.new("B2")
|
10
|
+
assert_equal Spreet::Coordinates.new(2,2), Spreet::Coordinates.new(:x=>2, :y=>2)
|
11
|
+
assert_equal Spreet::Coordinates.new(3,3), Spreet::Coordinates.new(3,3)
|
12
|
+
assert_equal Spreet::Coordinates.new(3,3), Spreet::Coordinates.new([3,3])
|
13
|
+
assert_equal Spreet::Coordinates.new(4,4), Spreet::Coordinates.new(Spreet::Coordinates.new(4,4).to_i)
|
14
|
+
assert_equal Spreet::Coordinates.new(5,5), Spreet::Coordinates.new(Spreet::Coordinates.new(5,5))
|
15
|
+
assert_equal("D25", Spreet::Coordinates.new(3,24).to_s)
|
16
|
+
assert_equal([3, 24], Spreet::Coordinates.new(3,24).to_a)
|
17
|
+
assert_equal({:x=>3, :y=>24}, Spreet::Coordinates.new(3,24).to_hash)
|
18
|
+
assert Spreet::Coordinates.new(0,0) <=> Spreet::Coordinates.new(0,1)
|
19
|
+
assert Spreet::Coordinates.new(0,1) <=> Spreet::Coordinates.new(1,0)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_spreet_version
|
23
|
+
assert_not_nil Spreet::VERSION
|
24
|
+
assert_not_nil Spreet::VERSION::MAJOR
|
25
|
+
assert_not_nil Spreet::VERSION::MINOR
|
26
|
+
assert_not_nil Spreet::VERSION::TINY
|
27
|
+
assert_not_nil Spreet::VERSION::PATCH
|
28
|
+
assert_equal(Spreet::VERSION::TINY, Spreet::VERSION::PATCH, "PATCH code must have the same value as TINY")
|
29
|
+
assert((Spreet::VERSION::MAJOR > 0 or Spreet::VERSION::MINOR > 0 or Spreet::VERSION::TINY > 0), "Version cannot be 0.0.0")
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_spreet
|
33
|
+
assert_not_nil Spreet
|
34
|
+
|
35
|
+
spreet = Spreet::Document.new
|
36
|
+
assert_not_nil spreet
|
37
|
+
sheet = spreet.sheets.add
|
38
|
+
assert_not_nil sheet
|
39
|
+
assert_not_nil spreet.sheets.add("Feuille 2")
|
40
|
+
assert_not_nil spreet.sheets.add("ソフト 3")
|
41
|
+
|
42
|
+
assert_equal Spreet::Sheet, spreet.sheets[1].class
|
43
|
+
assert_equal "Feuille 2", spreet.sheets[1].name
|
44
|
+
|
45
|
+
assert_equal Spreet::Sheet, spreet.sheets["ソフト 3"].class
|
46
|
+
assert_equal "ソフト 3", spreet.sheets["ソフト 3"].name
|
47
|
+
|
48
|
+
assert_not_nil sheet[0,0]
|
49
|
+
sheet[0,0] = "Cell A1"
|
50
|
+
spreet.sheets[1][0] = "Cellule A1"
|
51
|
+
spreet.sheets["ソフト 3"]["A1"] = "セル A1"
|
52
|
+
|
53
|
+
assert_not_nil sheet["C30"]
|
54
|
+
|
55
|
+
sheet["F20"] = Date.today
|
56
|
+
|
57
|
+
spreet.write("test/samples/cleaned-nothing.ods")
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def test_handlers
|
62
|
+
doc = nil
|
63
|
+
assert_nothing_raised do
|
64
|
+
doc = Spreet::Document.read("test/samples/pascal.csv")
|
65
|
+
end
|
66
|
+
|
67
|
+
sheet = doc.sheets[0]
|
68
|
+
sheet.each_row do |row|
|
69
|
+
for cell in row
|
70
|
+
if cell.text.to_i == 0
|
71
|
+
cell.clear!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
doc.write("test/samples/cleaned-pascal.csv", :format=>:xcsv)
|
77
|
+
doc.write("test/samples/cleaned-pascal.ods")
|
78
|
+
|
79
|
+
assert_nothing_raised do
|
80
|
+
doc = Spreet::Document.read("test/samples/cleaned-pascal.csv", :format=>:xcsv)
|
81
|
+
end
|
82
|
+
|
83
|
+
FileUtils.rm_f("test/samples/cleaned-pascal.csv")
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spreet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brice Texier
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-19 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fastercsv
|
16
|
+
requirement: &11182000 !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: *11182000
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: libxml-ruby
|
27
|
+
requirement: &11180960 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *11180960
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rubyzip
|
38
|
+
requirement: &11180240 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.4
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *11180240
|
47
|
+
description: Spr[eadsh]eet handler for CSV(RW), Excel CSV(RW) and ODS(W). The goal
|
48
|
+
is to read and write in many open formats.
|
49
|
+
email: brice.texier@ekylibre.org
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.rdoc
|
55
|
+
files:
|
56
|
+
- .gitignore
|
57
|
+
- Gemfile
|
58
|
+
- LICENSE.txt
|
59
|
+
- README.rdoc
|
60
|
+
- Rakefile
|
61
|
+
- VERSION
|
62
|
+
- lib/spreet.rb
|
63
|
+
- lib/spreet/handlers.rb
|
64
|
+
- lib/spreet/handlers/csv.rb
|
65
|
+
- lib/spreet/handlers/open_document.rb
|
66
|
+
- spreet.gemspec
|
67
|
+
- test/helper.rb
|
68
|
+
- test/samples/pascal.csv
|
69
|
+
- test/test_spreet.rb
|
70
|
+
homepage: http://github.com/burisu/spreet
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.11
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Spr[eadsh]eet handler
|
95
|
+
test_files: []
|