spreet 0.0.3 → 0.0.4
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.
- 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
|