spreet 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +10 -6
- data/lib/spreet.rb +12 -336
- data/lib/spreet/cell.rb +87 -0
- data/lib/spreet/document.rb +54 -0
- data/lib/spreet/handlers.rb +5 -21
- data/lib/spreet/handlers/base.rb +19 -0
- data/lib/spreet/handlers/csv.rb +3 -38
- data/lib/spreet/handlers/excel_csv.rb +36 -0
- data/lib/spreet/handlers/open_document.rb +9 -6
- data/lib/spreet/sheet.rb +125 -0
- data/lib/spreet/sheets.rb +63 -0
- data/lib/spreet/version.rb +10 -0
- data/test/helper.rb +25 -3
- data/test/test_coordinates.rb +1 -1
- data/test/test_csv.rb +9 -18
- data/test/test_duration.rb +1 -1
- data/test/test_open_document.rb +2 -2
- data/test/test_spreet.rb +8 -11
- metadata +76 -42
- data/VERSION +0 -1
- data/lib/big_array.rb +0 -100
- data/test/test_big_array.rb +0 -24
@@ -0,0 +1,54 @@
|
|
1
|
+
module Spreet
|
2
|
+
|
3
|
+
class Document
|
4
|
+
attr_reader :sheets
|
5
|
+
@@handlers = {}
|
6
|
+
@@associations = {}
|
7
|
+
|
8
|
+
def initialize(option={})
|
9
|
+
@sheets = Sheets.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(file, options={})
|
13
|
+
handler = self.class.extract_handler(file, options.delete(:format))
|
14
|
+
handler.write(self, file, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def register_handler(klass, name, options={})
|
20
|
+
if klass.respond_to?(:read) or klass.respond_to?(:write)
|
21
|
+
if name.is_a?(Symbol)
|
22
|
+
@@handlers[name] = klass # options.merge(:class=>klass)
|
23
|
+
elsif
|
24
|
+
raise ArgumentError.new("Name is invalid. Symbol expected, #{name.class.name} got.")
|
25
|
+
end
|
26
|
+
else
|
27
|
+
raise ArgumentError.new("Handler do not support :read or :write method.")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def read(file, options={})
|
32
|
+
handler = extract_handler(file, options.delete(:format))
|
33
|
+
return handler.read(file, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_handler(file, handler_name=nil)
|
37
|
+
file_path = Pathname.new(file)
|
38
|
+
extension = file_path.extname.to_s[1..-1]
|
39
|
+
if !handler_name and extension.size > 0
|
40
|
+
handler_name = extension.to_sym
|
41
|
+
end
|
42
|
+
if @@handlers[handler_name]
|
43
|
+
return @@handlers[handler_name]
|
44
|
+
else
|
45
|
+
raise ArgumentError.new("No corresponding handler (#{handler_name.inspect}). Available: #{@@handlers.keys.collect{|k| k.inspect}.join(', ')}.")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|
data/lib/spreet/handlers.rb
CHANGED
@@ -1,25 +1,9 @@
|
|
1
1
|
module Spreet
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
raise NotImplementedError.new
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.write(spreet, file, options={})
|
11
|
-
raise NotImplementedError.new
|
12
|
-
end
|
13
|
-
|
2
|
+
module Handlers
|
3
|
+
autoload :Base, 'spreet/handlers/base'
|
4
|
+
autoload :CSV, 'spreet/handlers/csv'
|
5
|
+
autoload :ExcelCSV, 'spreet/handlers/excel_csv'
|
6
|
+
autoload :OpenDocument, 'spreet/handlers/open_document'
|
14
7
|
end
|
15
|
-
|
16
8
|
end
|
17
9
|
|
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,19 @@
|
|
1
|
+
module Spreet
|
2
|
+
module Handlers
|
3
|
+
|
4
|
+
# Default handler
|
5
|
+
class Base
|
6
|
+
|
7
|
+
def self.read(file, options={})
|
8
|
+
raise NotImplementedError.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.write(spreet, file, options={})
|
12
|
+
raise NotImplementedError.new
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/spreet/handlers/csv.rb
CHANGED
@@ -1,21 +1,15 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require 'fastercsv'
|
3
1
|
require 'csv'
|
4
|
-
require 'iconv'
|
5
2
|
|
6
3
|
module Spreet
|
7
|
-
# Universal CSV support
|
8
|
-
CSV = (::CSV.const_defined?(:Reader) ? ::FasterCSV : ::CSV).freeze
|
9
|
-
|
10
4
|
module Handlers
|
11
5
|
|
12
|
-
class CSV < Spreet::
|
6
|
+
class CSV < Spreet::Handlers::Base
|
13
7
|
|
14
8
|
# Read a CSV file and create its Spreet document
|
15
9
|
def self.read(file, options={})
|
16
10
|
spreet = Spreet::Document.new
|
17
11
|
sheet = spreet.sheets.add
|
18
|
-
|
12
|
+
::CSV.foreach(file) do |row|
|
19
13
|
sheet.row *row
|
20
14
|
end
|
21
15
|
return spreet
|
@@ -25,7 +19,7 @@ module Spreet
|
|
25
19
|
# Write a Spreet to a CSV file
|
26
20
|
def self.write(spreet, file, options={})
|
27
21
|
sheet = spreet.sheets[options[:sheet]||0]
|
28
|
-
|
22
|
+
::CSV.open(file, "wb") do |csv|
|
29
23
|
sheet.each_row do |row|
|
30
24
|
csv << row.collect{|c| c.text}
|
31
25
|
end
|
@@ -34,35 +28,6 @@ module Spreet
|
|
34
28
|
|
35
29
|
end
|
36
30
|
|
37
|
-
class ExcelCSV < Spreet::Handler
|
38
|
-
|
39
|
-
# Read a CSV file and create its Spreet document
|
40
|
-
def self.read(file, options={})
|
41
|
-
spreet = Spreet::Document.new
|
42
|
-
sheet = spreet.sheets.add
|
43
|
-
options = {:col_sep=>';'}.merge(options)
|
44
|
-
ic = Iconv.new('utf-8', 'cp1252')
|
45
|
-
Spreet::CSV.foreach(file, options) do |row|
|
46
|
-
sheet.row *(row.collect{|v| ic.iconv(v.to_s)})
|
47
|
-
end
|
48
|
-
return spreet
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
# Write a Spreet to a CSV file
|
53
|
-
def self.write(spreet, file, options={})
|
54
|
-
sheet = spreet.sheets[options[:sheet]||0]
|
55
|
-
options = {:col_sep=>';'}.merge(options)
|
56
|
-
ic = Iconv.new('cp1252', 'utf-8')
|
57
|
-
Spreet::CSV.open(file, "wb", options) do |csv|
|
58
|
-
sheet.each_row do |row|
|
59
|
-
csv << row.collect{|c| ic.iconv(c.text)}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
31
|
end
|
67
32
|
end
|
68
33
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Spreet
|
4
|
+
module Handlers
|
5
|
+
|
6
|
+
class ExcelCSV < Spreet::Handlers::Base
|
7
|
+
|
8
|
+
# Read a CSV file and create its Spreet document
|
9
|
+
def self.read(file, options={})
|
10
|
+
spreet = Spreet::Document.new
|
11
|
+
sheet = spreet.sheets.add
|
12
|
+
options = {:col_sep=>';', :encoding => "CP1252"}.merge(options)
|
13
|
+
::CSV.foreach(file, options) do |row|
|
14
|
+
sheet.row *(row.map{|v| v.to_s.encode('utf-8')}) # collect{|v| v.to_s.encode('cp1252')}
|
15
|
+
end
|
16
|
+
return spreet
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Write a Spreet to a CSV file
|
21
|
+
def self.write(spreet, file, options={})
|
22
|
+
sheet = spreet.sheets[options[:sheet]||0]
|
23
|
+
options = {:col_sep=>';', :encoding => "CP1252"}.merge(options)
|
24
|
+
::CSV.open(file, "wb", options) do |csv|
|
25
|
+
sheet.each_row do |row|
|
26
|
+
csv << row.map(&:text)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'zip
|
2
|
+
require 'zip'
|
3
3
|
require 'libxml'
|
4
|
+
require 'money'
|
5
|
+
require 'time'
|
6
|
+
require 'duration'
|
4
7
|
|
5
8
|
module Spreet
|
6
9
|
module Handlers
|
7
|
-
class OpenDocument < Spreet::
|
10
|
+
class OpenDocument < Spreet::Handlers::Base
|
8
11
|
DATE_REGEXP = /\%./
|
9
12
|
DATE_ELEMENTS = {
|
10
13
|
"m" => "<number:month number:style=\"long\"/>",
|
@@ -62,7 +65,7 @@ module Spreet
|
|
62
65
|
|
63
66
|
def self.read(file, options={})
|
64
67
|
spreet = nil
|
65
|
-
Zip::
|
68
|
+
Zip::File.open(file) do |zile|
|
66
69
|
# Check mime_type
|
67
70
|
entry = zile.find_entry "mimetype"
|
68
71
|
if entry.nil?
|
@@ -135,7 +138,7 @@ module Spreet
|
|
135
138
|
elsif value_type == :currency
|
136
139
|
value = cell.attributes.get_attribute_ns(XMLNS_OFFICE, "value").value
|
137
140
|
currency = cell.attributes.get_attribute_ns(XMLNS_OFFICE, "currency").value
|
138
|
-
sheet[x,y] = Money.new(value, currency)
|
141
|
+
sheet[x,y] = Money.new(value.to_f, currency)
|
139
142
|
elsif value_type == :date
|
140
143
|
value = cell.attributes.get_attribute_ns(XMLNS_OFFICE, "date-value").value
|
141
144
|
if value.match(/\d{1,8}-\d{1,2}-\d{1,2}/)
|
@@ -202,9 +205,9 @@ module Spreet
|
|
202
205
|
xml_escape << ".force_encoding('US-ASCII')" if xml_escape.respond_to?(:force_encoding)
|
203
206
|
mime_type = MIME_ODS
|
204
207
|
# name = #{table.model.name}.model_name.human.gsub(/[^a-z0-9]/i,'_')
|
205
|
-
Zip::
|
208
|
+
Zip::OutputStream.open(file) do |zile|
|
206
209
|
# MimeType in first place
|
207
|
-
zile.put_next_entry('mimetype', nil, nil, Zip::
|
210
|
+
zile.put_next_entry('mimetype', nil, nil, Zip::Entry::STORED)
|
208
211
|
zile << mime_type
|
209
212
|
|
210
213
|
# Manifest
|
data/lib/spreet/sheet.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Spreet
|
2
|
+
|
3
|
+
class Sheet
|
4
|
+
attr_reader :document, :name, :columns
|
5
|
+
attr_accessor :current_row
|
6
|
+
|
7
|
+
def initialize(document, name=nil)
|
8
|
+
@document = document
|
9
|
+
self.name = name
|
10
|
+
raise ArgumentError.new("Must be a Document") unless document.is_a? Document
|
11
|
+
@current_row = 0
|
12
|
+
@cells = {} # BigArray::Cells.new
|
13
|
+
@bound = compute_bound
|
14
|
+
end
|
15
|
+
|
16
|
+
def name=(value)
|
17
|
+
unless value
|
18
|
+
value = (@document.sheets.count > 0 ? @document.sheets[-1].name.succ : "Sheet 1")
|
19
|
+
end
|
20
|
+
raise ArgumentError.new("Name of sheet must be given") if value.to_s.strip.size.zero?
|
21
|
+
if @document.sheets[value]
|
22
|
+
raise ArgumentError.new("Name of sheet must be unique")
|
23
|
+
end
|
24
|
+
@name = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def next_row(increment = 1)
|
28
|
+
@current_row += increment
|
29
|
+
end
|
30
|
+
|
31
|
+
def previous_row(increment = 1)
|
32
|
+
@current_row -= increment
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](*args)
|
36
|
+
coord = Coordinates.new(*args)
|
37
|
+
@cells[coord.to_i] ||= Cell.new(self, coord)
|
38
|
+
return @cells[coord.to_i]
|
39
|
+
end
|
40
|
+
|
41
|
+
def []=(*args)
|
42
|
+
value = args.delete_at(-1)
|
43
|
+
cell = self[*args]
|
44
|
+
cell.value = value
|
45
|
+
@updated = true
|
46
|
+
end
|
47
|
+
|
48
|
+
def row(*args)
|
49
|
+
options = {}
|
50
|
+
options = args.delete_at(-1) if args[-1].is_a? Hash
|
51
|
+
row = options[:row] || @current_row
|
52
|
+
args.each_index do |index|
|
53
|
+
self[index, row] = args[index]
|
54
|
+
end
|
55
|
+
next_row
|
56
|
+
end
|
57
|
+
|
58
|
+
def rows(index)
|
59
|
+
row = []
|
60
|
+
for i in 0..bound.x
|
61
|
+
row[i] = self[i, index]
|
62
|
+
end
|
63
|
+
return row
|
64
|
+
end
|
65
|
+
|
66
|
+
def each_row(&block)
|
67
|
+
for j in 0..bound.y
|
68
|
+
yield rows(j)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Find or build cell
|
73
|
+
def cell(*args)
|
74
|
+
return c
|
75
|
+
end
|
76
|
+
|
77
|
+
def bound
|
78
|
+
if @updated
|
79
|
+
compute_bound
|
80
|
+
else
|
81
|
+
@bound
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove!(coordinates)
|
86
|
+
raise ArgumentError.new("Must be a Coordinates") unless document.is_a?(Coordinates)
|
87
|
+
@cells.delete(coordinates.to_i)
|
88
|
+
@updated = true
|
89
|
+
end
|
90
|
+
|
91
|
+
# Moves the sheet to an other position in the list of sheets
|
92
|
+
def move_to(position)
|
93
|
+
@document.sheets.move_at(self, position)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Moves the sheet higher in the list of sheets
|
97
|
+
def move_higher(increment=1)
|
98
|
+
@document.sheets.move(self, increment)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Moves the sheet lower in the list of sheets
|
102
|
+
def move_lower(increment=1)
|
103
|
+
@document.sheets.move(self, -increment)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def compute_bound
|
109
|
+
bound = Coordinates.new(0,0)
|
110
|
+
for index, cell in @cells
|
111
|
+
# for cell in @cells.compact
|
112
|
+
unless cell.empty?
|
113
|
+
bound.x = cell.coordinates.x if cell.coordinates.x > bound.x
|
114
|
+
bound.y = cell.coordinates.y if cell.coordinates.y > bound.y
|
115
|
+
end
|
116
|
+
end
|
117
|
+
@updated = false
|
118
|
+
@bound = bound
|
119
|
+
return @bound
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Spreet
|
2
|
+
|
3
|
+
class Sheets
|
4
|
+
|
5
|
+
def initialize(document)
|
6
|
+
raise ArgumentError.new("Must be a Document") unless document.is_a?(Document)
|
7
|
+
@document = document
|
8
|
+
@array = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def count
|
12
|
+
@array.size
|
13
|
+
end
|
14
|
+
|
15
|
+
def index(name_or_sheet)
|
16
|
+
if name_or_sheet.is_a? String
|
17
|
+
@array.each_index do |i|
|
18
|
+
return i if @array[i].name == name_or_sheet
|
19
|
+
end
|
20
|
+
elsif name_or_sheet.is_a? Integer
|
21
|
+
return (@array[name_or_sheet].nil? ? nil : name_or_sheet)
|
22
|
+
else
|
23
|
+
return @array.index(name_or_sheet)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(name=nil, position=-1)
|
28
|
+
sheet = Sheet.new(@document, name)
|
29
|
+
@array.insert(position, sheet)
|
30
|
+
return sheet
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](sheet)
|
34
|
+
sheet = index(sheet)
|
35
|
+
return (sheet.is_a?(Integer) ? @array[sheet] : nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove(sheet)
|
39
|
+
@array.delete_at(index(sheet))
|
40
|
+
end
|
41
|
+
|
42
|
+
def move(sheet, shift=0)
|
43
|
+
position = index(sheet) + shift
|
44
|
+
position = 0 if position < 0
|
45
|
+
position = self.count-1 if position >= self.count
|
46
|
+
move_at(sheet, position)
|
47
|
+
end
|
48
|
+
|
49
|
+
def move_at(sheet, position=-1)
|
50
|
+
if i = index(sheet)
|
51
|
+
@array.insert(position, @array.delete_at(i))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def each(&block)
|
56
|
+
for item in @array
|
57
|
+
yield item
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|