workbook 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +22 -0
- data/Rakefile +12 -0
- data/lib/workbook/book.rb +122 -0
- data/lib/workbook/cell.rb +143 -0
- data/lib/workbook/format.rb +35 -0
- data/lib/workbook/modules/raw_objects_storage.rb +31 -0
- data/lib/workbook/modules/table_diff_sort.rb +140 -0
- data/lib/workbook/modules/type_parser.rb +97 -0
- data/lib/workbook/readers/csv_reader.rb +31 -0
- data/lib/workbook/readers/txt_reader.rb +17 -0
- data/lib/workbook/readers/xls_reader.rb +161 -0
- data/lib/workbook/row.rb +101 -0
- data/lib/workbook/sheet.rb +22 -0
- data/lib/workbook/table.rb +67 -0
- data/lib/workbook/template.rb +52 -0
- data/lib/workbook/writers/csv_table_writer.rb +23 -0
- data/lib/workbook/writers/xls_writer.rb +172 -0
- data/lib/workbook.rb +14 -0
- data/readme.markdown +99 -0
- data/test/artifacts/book_with_tabs_and_colours.xls +0 -0
- data/test/artifacts/complex_types.xls +0 -0
- data/test/artifacts/medewerkers.xls +0 -0
- data/test/artifacts/simple_csv.csv +4 -0
- data/test/artifacts/simple_excel_csv.csv +6 -0
- data/test/artifacts/simple_sheet.xls +0 -0
- data/test/artifacts/xls_with_txt_extension.txt +0 -0
- data/test/helper.rb +3 -0
- data/test/test_book.rb +56 -0
- data/test/test_cell.rb +82 -0
- data/test/test_format.rb +59 -0
- data/test/test_functional.rb +30 -0
- data/test/test_modules_table_diff_sort.rb +120 -0
- data/test/test_modules_type_parser.rb +53 -0
- data/test/test_readers_csv_reader.rb +37 -0
- data/test/test_readers_txt_reader.rb +49 -0
- data/test/test_readers_xls_reader.rb +26 -0
- data/test/test_row.rb +114 -0
- data/test/test_sheet.rb +26 -0
- data/test/test_table.rb +43 -0
- data/test/test_template.rb +24 -0
- data/test/test_writers_xls_writer.rb +37 -0
- data/workbook.gemspec +26 -0
- metadata +165 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
workbook (0.1.0)
|
5
|
+
fastercsv
|
6
|
+
rchardet (~> 1.3)
|
7
|
+
spreadsheet (>= 0.6.8)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
fastercsv (1.5.4)
|
13
|
+
rchardet (1.3)
|
14
|
+
ruby-ole (1.2.11.3)
|
15
|
+
spreadsheet (0.6.8)
|
16
|
+
ruby-ole (>= 1.0)
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
workbook!
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
task :default => [:test]
|
7
|
+
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs << "test"
|
10
|
+
t.test_files = FileList['test/test*.rb','test/writers/test*.rb']
|
11
|
+
t.verbose = false
|
12
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'workbook/writers/xls_writer'
|
2
|
+
require 'workbook/readers/xls_reader'
|
3
|
+
require 'workbook/readers/csv_reader'
|
4
|
+
require 'workbook/readers/txt_reader'
|
5
|
+
require 'rchardet'
|
6
|
+
|
7
|
+
module Workbook
|
8
|
+
class Book < Array
|
9
|
+
include Workbook::Writers::XlsWriter
|
10
|
+
include Workbook::Readers::XlsReader
|
11
|
+
include Workbook::Readers::CsvReader
|
12
|
+
include Workbook::Readers::TxtReader
|
13
|
+
|
14
|
+
attr_accessor :title
|
15
|
+
attr_accessor :template
|
16
|
+
attr_accessor :default_rewrite_header
|
17
|
+
|
18
|
+
# @param [Workbook::Sheet, Array] Create a new workbook based on an existing sheet, or initialize a sheet based on the array
|
19
|
+
# @return [Workbook::Book]
|
20
|
+
def initialize sheet=Workbook::Sheet.new([], self, options={})
|
21
|
+
if sheet.is_a? Workbook::Sheet
|
22
|
+
push sheet
|
23
|
+
else
|
24
|
+
push Workbook::Sheet.new(sheet, self, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Workbook::Format] returns the template describing how the document should be/is formatted
|
29
|
+
def template
|
30
|
+
@template ||= Workbook::Template.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [Workbook::Format] a template describing how the document should be/is formatted
|
34
|
+
def template= template
|
35
|
+
raise ArgumentError, "format should be a Workboot::Format" unless template.is_a? Workbook::Template
|
36
|
+
@template = template
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String] the title of the workbook
|
40
|
+
def title
|
41
|
+
@title ? @title : "untitled document"
|
42
|
+
end
|
43
|
+
|
44
|
+
def push sheet=Workbook::Sheet.new
|
45
|
+
super(sheet)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Workbook::Sheet] The first sheet, and creates an empty one if one doesn't exists
|
49
|
+
def sheet
|
50
|
+
push Workbook::Sheet.new unless first
|
51
|
+
first
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_contents?
|
55
|
+
sheet.has_contents?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Loads an external file into an existing worbook
|
59
|
+
# @param [String] a string with a reference to the file to be opened
|
60
|
+
# @param [String] an optional string enforcing a certain parser (based on the file extension, e.g. 'txt', 'csv' or 'xls')
|
61
|
+
def open filename, ext=nil
|
62
|
+
ext = file_extension(filename) unless ext
|
63
|
+
if ['txt','csv','xml'].include?(ext)
|
64
|
+
open_text filename, ext
|
65
|
+
else
|
66
|
+
open_binary filename, ext
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Open the file in binary, read-only mode, do not read it, but pas it throug to the extension determined loaded
|
71
|
+
# @param [String] a string with a reference to the file to be opened
|
72
|
+
# @param [String] an optional string enforcing a certain parser (based on the file extension, e.g. 'txt', 'csv' or 'xls')
|
73
|
+
def open_binary filename, ext=nil
|
74
|
+
ext = file_extension(filename) unless ext
|
75
|
+
f = File.open(filename,'rb')
|
76
|
+
send("load_#{ext}".to_sym,f)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Open the file in non-binary, read-only mode, read it and parse it to UTF-8
|
80
|
+
# @param [String] a string with a reference to the file to be opened
|
81
|
+
# @param [String] an optional string enforcing a certain parser (based on the file extension, e.g. 'txt', 'csv' or 'xls')
|
82
|
+
def open_text filename, ext=nil
|
83
|
+
ext = file_extension(filename) unless ext
|
84
|
+
f = File.open(filename,'r')
|
85
|
+
t = f.read
|
86
|
+
detected_encoding = CharDet.detect(t)['encoding']
|
87
|
+
t = Iconv.conv("UTF-8//TRANSLIT//IGNORE",detected_encoding,t)
|
88
|
+
send("load_#{ext}".to_sym,t)
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param [String] The full filename, or path
|
92
|
+
# @return [String] The file extension
|
93
|
+
def file_extension(filename)
|
94
|
+
File.extname(filename).gsub('.','').downcase if filename
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create an instance from a file, using open.
|
98
|
+
# @return [Workbook::Book] A new instance, based on the filename
|
99
|
+
def self.open filename, ext=nil
|
100
|
+
wb = self.new
|
101
|
+
wb.open filename, ext
|
102
|
+
return wb
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_or_open_sheet_at index
|
106
|
+
s = self[index]
|
107
|
+
s = self[index] = Workbook::Sheet.new if s == nil
|
108
|
+
s.book = self
|
109
|
+
s
|
110
|
+
end
|
111
|
+
|
112
|
+
def sort
|
113
|
+
raise Exception("Books can't be sorted")
|
114
|
+
end
|
115
|
+
|
116
|
+
def default_rewrite_header?
|
117
|
+
return true if default_rewrite_header.nil?
|
118
|
+
default_rewrite_header
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'workbook/modules/type_parser'
|
4
|
+
|
5
|
+
module Workbook
|
6
|
+
class Cell
|
7
|
+
include Workbook::Modules::TypeParser
|
8
|
+
|
9
|
+
attr_accessor :value
|
10
|
+
attr_accessor :format
|
11
|
+
attr_accessor :formula
|
12
|
+
|
13
|
+
# Note that these types are sorted by 'importance'
|
14
|
+
VALID_TYPES = [Numeric,String,Time,Date,TrueClass,FalseClass,NilClass]
|
15
|
+
|
16
|
+
def valid_value? value
|
17
|
+
valid_type = false
|
18
|
+
VALID_TYPES.each {|t| return true if value.is_a? t}
|
19
|
+
valid_type
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize value=nil, options={}
|
23
|
+
if valid_value? value
|
24
|
+
format = options[:format]
|
25
|
+
@value = value
|
26
|
+
else
|
27
|
+
raise ArgumentError, "value should be of a primitive type, e.g. a string, or an integer, not a #{value.class} (is_a? [TrueClass,FalseClass,Date,Time,Numeric,String, NilClass])"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def format= f
|
32
|
+
if f.is_a? Workbook::Format
|
33
|
+
@format = f
|
34
|
+
elsif f.is_a? Hash
|
35
|
+
@format = Workbook::Format.new(f)
|
36
|
+
elsif f.class == NilClass
|
37
|
+
@format = Workbook::Format.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def format
|
42
|
+
@format ||= Workbook::Format.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def ==(other)
|
46
|
+
if other.is_a? Cell
|
47
|
+
other.value == self.value
|
48
|
+
else
|
49
|
+
other == self.value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def nil?
|
54
|
+
return value.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_sym
|
58
|
+
#mb_chars.normalize(:kd).
|
59
|
+
v = nil
|
60
|
+
if value
|
61
|
+
v = value.to_s.downcase
|
62
|
+
v = v.gsub(' (j/?/leeg)','').gsub(/dd-mm-(.*)/,'').gsub(/\ja\/nee/,'').gsub(/\(\)/,'').gsub(/[\(\)]+/, '')
|
63
|
+
v = v.strip.gsub(/(\.|\?|,|\=)/,'').
|
64
|
+
gsub('$','').
|
65
|
+
gsub(/\&/,'en').
|
66
|
+
gsub(/\+/,'_plus_').
|
67
|
+
gsub(/\s/, "_").
|
68
|
+
gsub('–_','').
|
69
|
+
gsub('-_','').
|
70
|
+
gsub('+_','').
|
71
|
+
gsub('/_','_').
|
72
|
+
gsub('/','_').
|
73
|
+
gsub('__','_').
|
74
|
+
gsub('-','')
|
75
|
+
|
76
|
+
accents = {
|
77
|
+
['á','à','â','ä','ã'] => 'a',
|
78
|
+
['Ã','Ä','Â','À','�?'] => 'A',
|
79
|
+
['é','è','ê','ë'] => 'e',
|
80
|
+
['Ë','É','È','Ê'] => 'E',
|
81
|
+
['í','ì','î','ï'] => 'i',
|
82
|
+
['�?','Î','Ì','�?'] => 'I',
|
83
|
+
['ó','ò','ô','ö','õ'] => 'o',
|
84
|
+
['Õ','Ö','Ô','Ò','Ó'] => 'O',
|
85
|
+
['ú','ù','û','ü'] => 'u',
|
86
|
+
['Ú','Û','Ù','Ü'] => 'U',
|
87
|
+
['ç'] => 'c', ['Ç'] => 'C',
|
88
|
+
['ñ'] => 'n', ['Ñ'] => 'N'
|
89
|
+
}
|
90
|
+
accents.each do |ac,rep|
|
91
|
+
ac.each do |s|
|
92
|
+
v = v.gsub(s, rep)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
v = v.gsub(/[^\x00-\x7F]/n,'').downcase.to_sym
|
97
|
+
end
|
98
|
+
v
|
99
|
+
end
|
100
|
+
|
101
|
+
def <=> other
|
102
|
+
rv = nil
|
103
|
+
begin
|
104
|
+
rv = self.value <=> other.value
|
105
|
+
rescue NoMethodError => e
|
106
|
+
rv = compare_on_class other
|
107
|
+
end
|
108
|
+
if rv == nil
|
109
|
+
rv = compare_on_class other
|
110
|
+
end
|
111
|
+
return rv
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
def compare_on_class other
|
116
|
+
other_value = nil
|
117
|
+
other_value = other.value if other
|
118
|
+
self_value = importance_of_class self.value
|
119
|
+
other_value = importance_of_class other_value
|
120
|
+
self_value <=> other_value
|
121
|
+
end
|
122
|
+
|
123
|
+
def importance_of_class value
|
124
|
+
VALID_TYPES.each_with_index do |c,i|
|
125
|
+
return i if value.is_a? c
|
126
|
+
end
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def inspect
|
131
|
+
"<Workbook::Cell @value=#{value}>"
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_s
|
135
|
+
if (value.is_a? Date or value.is_a? Time) and format[:number_format]
|
136
|
+
value.strftime(format[:number_format])
|
137
|
+
else
|
138
|
+
value.to_s
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'workbook/modules/raw_objects_storage'
|
2
|
+
|
3
|
+
module Workbook
|
4
|
+
class Format < Hash
|
5
|
+
include Workbook::Modules::RawObjectsStorage
|
6
|
+
alias_method :merge_hash, :merge
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
|
10
|
+
def initialize options={}
|
11
|
+
options.each {|k,v| self[k]=v}
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_background_color? color=:any
|
15
|
+
if self[:background_color]
|
16
|
+
return (self[:background_color].downcase==color.to_s.downcase or (!(self[:background_color]==nil or (self[:background_color].is_a? String and (self[:background_color].downcase=='#ffffff' or self[:background_color]=='#000000'))) and color==:any))
|
17
|
+
else
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a string that can be used as inline cell styling (e.g. `<td style="<%=cell.format.to_css%>"><%=cell%></td>`)
|
23
|
+
def to_css
|
24
|
+
css_parts = []
|
25
|
+
css_parts.push("background: #{self[:background_color].to_s} #{self[:background].to_s}".strip) if self[:background] or self[:background_color]
|
26
|
+
css_parts.push("color: #{self[:color].to_s}") if self[:color]
|
27
|
+
css_parts.join("; ")
|
28
|
+
end
|
29
|
+
|
30
|
+
def merge(a)
|
31
|
+
self.remove_all_raws!
|
32
|
+
self.merge_hash(a)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Workbook
|
2
|
+
module Modules
|
3
|
+
module RawObjectsStorage
|
4
|
+
|
5
|
+
# A raw is a 'raw' object, representing a workbook, or cell, or whatever... in a particular format (defined by its class)
|
6
|
+
def add_raw raw_object
|
7
|
+
raws[raw_object.class]=raw_object
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true if there is a template for a certain class, otherwise false
|
11
|
+
def has_raw_for? raw_object_class
|
12
|
+
raws.each { |tc,t| return true if tc == raw_object_class}
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
|
16
|
+
def return_raw_for raw_object_class
|
17
|
+
raws.each { |tc,t| return t if tc == raw_object_class}
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove_all_raws!
|
22
|
+
@raws = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def raws
|
26
|
+
@raws = {} unless defined? @raws
|
27
|
+
@raws
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# Adds diffing and sorting functions
|
2
|
+
module Workbook
|
3
|
+
module Modules
|
4
|
+
module TableDiffSort
|
5
|
+
# create an overview of the differences between itself with another table, returns a book with a single sheet and table (containing the diffs)
|
6
|
+
def diff other, options={:sort=>true,:ignore_headers=>false}
|
7
|
+
|
8
|
+
aligned = align(other, options)
|
9
|
+
aself = aligned[:self]
|
10
|
+
aother = aligned[:other]
|
11
|
+
iteration_cols = []
|
12
|
+
if options[:ignore_headers]
|
13
|
+
iteration_cols = [aother.first.count,aself.first.count].max.times.collect
|
14
|
+
else
|
15
|
+
iteration_cols = (aother.header.to_symbols+aother.header.to_symbols).uniq
|
16
|
+
end
|
17
|
+
diff_table = diff_template.sheet.table
|
18
|
+
maxri = (aself.count-1)
|
19
|
+
for ri in 0..maxri do
|
20
|
+
row = diff_table[ri]
|
21
|
+
row = diff_table[ri] = Workbook::Row.new(nil, diff_table)
|
22
|
+
srow = aself[ri]
|
23
|
+
orow = aother[ri]
|
24
|
+
|
25
|
+
iteration_cols.each_with_index do |ch, ci|
|
26
|
+
scell = srow[ch]
|
27
|
+
ocell = orow[ch]
|
28
|
+
dcell = scell.nil? ? Workbook::Cell.new(nil) : scell
|
29
|
+
if (scell == ocell)
|
30
|
+
dcell.format = scell.format if scell
|
31
|
+
elsif scell.nil?
|
32
|
+
dcell = Workbook::Cell.new "(was: #{ocell.to_s})"
|
33
|
+
dcell.format = diff_template.template.create_or_find_format_by 'destroyed'
|
34
|
+
elsif ocell.nil?
|
35
|
+
dcell = scell.clone
|
36
|
+
fmt = scell.nil? ? :default : scell.format[:number_format]
|
37
|
+
f = diff_template.template.create_or_find_format_by 'created', fmt
|
38
|
+
f[:number_format] = scell.format[:number_format]
|
39
|
+
dcell.format = f
|
40
|
+
elsif scell != ocell
|
41
|
+
dcell = Workbook::Cell.new "#{scell.to_s} (was: #{ocell.to_s})"
|
42
|
+
f = diff_template.template.create_or_find_format_by 'updated'
|
43
|
+
dcell.format = f
|
44
|
+
end
|
45
|
+
|
46
|
+
row[ci]=dcell
|
47
|
+
end
|
48
|
+
end
|
49
|
+
if !options[:ignore_headers]
|
50
|
+
diff_table[0].format = diff_template.template.create_or_find_format_by 'header'
|
51
|
+
end
|
52
|
+
|
53
|
+
diff_template
|
54
|
+
end
|
55
|
+
|
56
|
+
def diff_template
|
57
|
+
return @diff_template if @diff_template
|
58
|
+
diffbook = Workbook::Book.new
|
59
|
+
difftable = diffbook.sheet.table
|
60
|
+
template = diffbook.template
|
61
|
+
f = template.create_or_find_format_by 'destroyed'
|
62
|
+
f[:background_color]=:red
|
63
|
+
f = template.create_or_find_format_by 'updated'
|
64
|
+
f[:background_color]=:yellow
|
65
|
+
f = template.create_or_find_format_by 'created'
|
66
|
+
f[:background_color]=:lime
|
67
|
+
f = template.create_or_find_format_by 'header'
|
68
|
+
f[:rotation] = 72
|
69
|
+
f[:font_weight] = :bold
|
70
|
+
f[:height] = 80
|
71
|
+
@diff_template = diffbook
|
72
|
+
return diffbook
|
73
|
+
end
|
74
|
+
|
75
|
+
# aligns itself with another table, used by diff
|
76
|
+
def align other, options={:sort=>true,:ignore_headers=>false}
|
77
|
+
|
78
|
+
options = {:sort=>true,:ignore_headers=>false}.merge(options)
|
79
|
+
|
80
|
+
iteration_cols = nil
|
81
|
+
sother = other.clone.remove_empty_lines!
|
82
|
+
sself = self.clone.remove_empty_lines!
|
83
|
+
|
84
|
+
if options[:ignore_headers]
|
85
|
+
sother.header = false
|
86
|
+
sself.header = false
|
87
|
+
end
|
88
|
+
|
89
|
+
sother = options[:sort] ? Workbook::Table.new(sother.sort) : sother
|
90
|
+
sself = options[:sort] ? Workbook::Table.new(sself.sort) : sself
|
91
|
+
|
92
|
+
iteration_rows = [sother.count,sself.count].max.times.collect
|
93
|
+
|
94
|
+
row_index = 0
|
95
|
+
while row_index < [sother.count,sself.count].max and row_index < other.count+self.count do
|
96
|
+
row_index = align_row(sself, sother, row_index)
|
97
|
+
end
|
98
|
+
|
99
|
+
{:self=>sself, :other=>sother}
|
100
|
+
end
|
101
|
+
|
102
|
+
# for use in the align 'while' loop
|
103
|
+
def align_row sself, sother, row_index
|
104
|
+
asd = 0
|
105
|
+
if sself[row_index] and sother[row_index]
|
106
|
+
asd = sself[row_index].key <=> sother[row_index].key
|
107
|
+
elsif sself[row_index]
|
108
|
+
asd = -1
|
109
|
+
elsif sother[row_index]
|
110
|
+
asd = 1
|
111
|
+
end
|
112
|
+
if asd == -1 and insert_placeholder?(sother, sself, row_index)
|
113
|
+
sother.insert row_index, placeholder_row
|
114
|
+
row_index -=1
|
115
|
+
elsif asd == 1 and insert_placeholder?(sother, sself, row_index)
|
116
|
+
sself.insert row_index, placeholder_row
|
117
|
+
row_index -=1
|
118
|
+
end
|
119
|
+
|
120
|
+
row_index += 1
|
121
|
+
end
|
122
|
+
|
123
|
+
def insert_placeholder? sother, sself, row_index
|
124
|
+
(sother[row_index].nil? or !sother[row_index].placeholder?) and
|
125
|
+
(sself[row_index].nil? or !sself[row_index].placeholder?)
|
126
|
+
end
|
127
|
+
|
128
|
+
# returns a placeholder row, for internal use only
|
129
|
+
def placeholder_row
|
130
|
+
if @placeholder_row != nil
|
131
|
+
return @placeholder_row
|
132
|
+
else
|
133
|
+
@placeholder_row = Workbook::Row.new [nil]
|
134
|
+
placeholder_row.placeholder = true
|
135
|
+
return @placeholder_row
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Workbook
|
2
|
+
module Modules
|
3
|
+
module TypeParser
|
4
|
+
def strip_win_chars csv_raw
|
5
|
+
csv_raw.gsub(/(\n\r|\r\n|\r)/,"\n")
|
6
|
+
end
|
7
|
+
|
8
|
+
def string_parsers
|
9
|
+
@string_parsers ||= [:string_cleaner,:string_nil_converter,:string_integer_converter,:string_boolean_converter]
|
10
|
+
end
|
11
|
+
|
12
|
+
def string_parsers= arr
|
13
|
+
@string_parsers = arr
|
14
|
+
end
|
15
|
+
|
16
|
+
def string_parsers_as_procs
|
17
|
+
string_parsers.collect{|c| c.is_a?(Proc) ? c : self.send(c)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse options={}
|
21
|
+
options = {:detect_date=>false}.merge(options)
|
22
|
+
string_parsers.push :string_optimistic_date_converter if options[:detect_date]
|
23
|
+
v = value
|
24
|
+
string_parsers_as_procs.each do |p|
|
25
|
+
if v.is_a? String
|
26
|
+
v = p.call(v)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
v
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse! options={}
|
33
|
+
self.value = parse(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def clean! options={}
|
37
|
+
parse! options
|
38
|
+
end
|
39
|
+
|
40
|
+
def string_cleaner
|
41
|
+
proc do |v|
|
42
|
+
v = v.strip
|
43
|
+
v.gsub('mailto:','')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def string_nil_converter
|
48
|
+
proc do |v|
|
49
|
+
return v == "" ? nil : v
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def string_integer_converter
|
54
|
+
proc do |v|
|
55
|
+
if v.to_i.to_s == v
|
56
|
+
return v.to_i
|
57
|
+
else
|
58
|
+
v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def string_optimistic_date_converter
|
64
|
+
proc do |v|
|
65
|
+
rv = v
|
66
|
+
starts_with_nr = v.chars.first.to_i.to_s == v.chars.first #it should at least start with a number...
|
67
|
+
no_spaced_dash = v.to_s.match(" - ") ? false : true
|
68
|
+
normal_date_length = v.to_s.length <= 25
|
69
|
+
if no_spaced_dash and starts_with_nr and normal_date_length
|
70
|
+
begin
|
71
|
+
rv = (v.length > 10) ? DateTime.parse(v) : Date.parse(v)
|
72
|
+
rescue ArgumentError
|
73
|
+
rv = v
|
74
|
+
end
|
75
|
+
begin
|
76
|
+
rv = Date.parse(v.to_i.to_s) == rv ? v : rv # disqualify is it is only based on the first number
|
77
|
+
rescue ArgumentError
|
78
|
+
end
|
79
|
+
end
|
80
|
+
rv
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def string_boolean_converter
|
85
|
+
proc do |v|
|
86
|
+
dv = v.downcase
|
87
|
+
if dv == "true"
|
88
|
+
return true
|
89
|
+
elsif dv == "false"
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
v
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faster_csv'
|
2
|
+
|
3
|
+
module Workbook
|
4
|
+
module Readers
|
5
|
+
module CsvReader
|
6
|
+
def load_csv text
|
7
|
+
csv = text
|
8
|
+
parse_csv csv
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_csv csv_raw
|
12
|
+
custom_date_converter = Workbook::Cell.new.string_optimistic_date_converter
|
13
|
+
converters = [:float,:integer,:date,:date_time,custom_date_converter]
|
14
|
+
csv=nil
|
15
|
+
begin
|
16
|
+
csv = FasterCSV.parse(csv_raw,{:converters=>converters})
|
17
|
+
rescue
|
18
|
+
# we're going to have another shot at it...
|
19
|
+
end
|
20
|
+
|
21
|
+
if csv==nil or csv[0].count == 1
|
22
|
+
csv_excel = FasterCSV.parse(csv_raw,{:converters=>converters,:col_sep=>';'})
|
23
|
+
csv = csv_excel if csv_excel[0].count > 1
|
24
|
+
end
|
25
|
+
|
26
|
+
self[0]=Workbook::Sheet.new(csv,self) unless sheet.has_contents?
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'faster_csv'
|
2
|
+
module Workbook
|
3
|
+
module Readers
|
4
|
+
module TxtReader
|
5
|
+
def load_txt text
|
6
|
+
csv = text
|
7
|
+
parse_txt csv
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse_txt csv_raw
|
11
|
+
csv = []
|
12
|
+
csv_raw.split("\n").each {|l| csv << FasterCSV.parse_line(l,{:col_sep=>"\t"});nil}
|
13
|
+
self[0]=Workbook::Sheet.new(csv,self,{:parse_cells_on_batch_creation=>true, :cell_parse_options=>{:detect_date=>true}}) unless sheet.has_contents?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|