workbook 0.1.1
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.
- 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
|