workbook 0.8.0 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +21 -0
- data/.gitignore +4 -1
- data/.ruby-version +1 -1
- data/.travis.yml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile +4 -2
- data/README.md +9 -7
- data/Rakefile +9 -7
- data/lib/workbook/book.rb +74 -61
- data/lib/workbook/cell.rb +59 -12
- data/lib/workbook/column.rb +33 -29
- data/lib/workbook/format.rb +24 -23
- data/lib/workbook/generatetypes.rb +6 -5
- data/lib/workbook/modules/cache.rb +8 -7
- data/lib/workbook/modules/cell.rb +78 -99
- data/lib/workbook/modules/diff_sort.rb +93 -82
- data/lib/workbook/modules/raw_objects_storage.rb +7 -7
- data/lib/workbook/modules/type_parser.rb +31 -21
- data/lib/workbook/nil_value.rb +5 -9
- data/lib/workbook/readers/csv_reader.rb +7 -9
- data/lib/workbook/readers/ods_reader.rb +49 -49
- data/lib/workbook/readers/txt_reader.rb +7 -7
- data/lib/workbook/readers/xls_reader.rb +22 -32
- data/lib/workbook/readers/xls_shared.rb +107 -116
- data/lib/workbook/readers/xlsx_reader.rb +47 -46
- data/lib/workbook/row.rb +100 -83
- data/lib/workbook/sheet.rb +48 -37
- data/lib/workbook/table.rb +97 -71
- data/lib/workbook/template.rb +13 -14
- data/lib/workbook/types/date.rb +2 -1
- data/lib/workbook/types/false.rb +1 -1
- data/lib/workbook/types/false_class.rb +2 -1
- data/lib/workbook/types/nil.rb +1 -1
- data/lib/workbook/types/nil_class.rb +3 -2
- data/lib/workbook/types/numeric.rb +3 -2
- data/lib/workbook/types/string.rb +3 -2
- data/lib/workbook/types/time.rb +3 -2
- data/lib/workbook/types/true.rb +1 -1
- data/lib/workbook/types/true_class.rb +3 -2
- data/lib/workbook/version.rb +3 -2
- data/lib/workbook/writers/csv_table_writer.rb +11 -12
- data/lib/workbook/writers/html_writer.rb +35 -37
- data/lib/workbook/writers/json_table_writer.rb +9 -10
- data/lib/workbook/writers/xls_writer.rb +31 -35
- data/lib/workbook/writers/xlsx_writer.rb +46 -28
- data/lib/workbook.rb +17 -14
- data/test/helper.rb +8 -5
- data/test/test_book.rb +43 -38
- data/test/test_column.rb +29 -25
- data/test/test_format.rb +53 -55
- data/test/test_functional.rb +9 -8
- data/test/test_modules_cache.rb +20 -17
- data/test/test_modules_cell.rb +49 -46
- data/test/test_modules_table_diff_sort.rb +56 -63
- data/test/test_modules_type_parser.rb +63 -31
- data/test/test_readers_csv_reader.rb +50 -42
- data/test/test_readers_ods_reader.rb +30 -31
- data/test/test_readers_txt_reader.rb +23 -23
- data/test/test_readers_xls_reader.rb +22 -23
- data/test/test_readers_xls_shared.rb +5 -4
- data/test/test_readers_xlsx_reader.rb +46 -37
- data/test/test_row.rb +107 -109
- data/test/test_sheet.rb +43 -35
- data/test/test_table.rb +84 -60
- data/test/test_template.rb +18 -15
- data/test/test_types_date.rb +7 -7
- data/test/test_writers_csv_writer.rb +24 -0
- data/test/test_writers_html_writer.rb +44 -41
- data/test/test_writers_json_writer.rb +11 -9
- data/test/test_writers_xls_writer.rb +52 -35
- data/test/test_writers_xlsx_writer.rb +64 -34
- data/workbook.gemspec +26 -27
- metadata +93 -27
data/lib/workbook/format.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
3
|
-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "workbook/modules/raw_objects_storage"
|
4
5
|
|
5
6
|
module Workbook
|
6
7
|
# Format is an object used for maintinaing a cell's formatting. It can belong to many cells. It maintains a relation to the raw template's equivalent, to preserve attributes Workbook cannot modify/access.
|
@@ -24,25 +25,25 @@ module Workbook
|
|
24
25
|
#
|
25
26
|
# @param [Workbook::Format, Hash] options (e.g. :background, :color, :background_color, :font_weight (integer or css-type labels)
|
26
27
|
# @return [String] the name of the format, default: nil
|
27
|
-
def initialize options={}, name=nil
|
28
|
+
def initialize options = {}, name = nil
|
28
29
|
if options.is_a? String
|
29
30
|
name = options
|
30
31
|
else
|
31
|
-
options.each {|k,v| self[k]=v}
|
32
|
+
options.each { |k, v| self[k] = v }
|
32
33
|
end
|
33
34
|
self.name = name
|
34
35
|
end
|
35
36
|
|
36
37
|
# Does the current format feature a background *color*? (not black or white or transparant).
|
37
|
-
def has_background_color? color
|
38
|
+
def has_background_color? color = :any
|
38
39
|
bg_color = flattened[:background_color] ? flattened[:background_color].to_s.downcase : nil
|
39
40
|
|
40
|
-
if color != :any
|
41
|
-
|
41
|
+
if (color != :any) && bg_color
|
42
|
+
bg_color == color.to_s.downcase
|
42
43
|
elsif bg_color
|
43
|
-
|
44
|
+
!((flattened[:background_color].downcase == "#ffffff") || (flattened[:background_color] == "#000000"))
|
44
45
|
else
|
45
|
-
|
46
|
+
false
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
@@ -50,9 +51,9 @@ module Workbook
|
|
50
51
|
# @return String very basic CSS styling string
|
51
52
|
def to_css
|
52
53
|
css_parts = []
|
53
|
-
background = [flattened[:background_color].to_s,flattened[:background].to_s].join(" ").strip
|
54
|
-
css_parts.push("background: #{background}") if background
|
55
|
-
css_parts.push("color: #{flattened[:color]
|
54
|
+
background = [flattened[:background_color].to_s, flattened[:background].to_s].join(" ").strip
|
55
|
+
css_parts.push("background: #{background}") if background && (background != "")
|
56
|
+
css_parts.push("color: #{flattened[:color]}") if flattened[:color]
|
56
57
|
css_parts.join("; ")
|
57
58
|
end
|
58
59
|
|
@@ -60,22 +61,22 @@ module Workbook
|
|
60
61
|
# @param [Workbook::Format] other_format
|
61
62
|
# @return [Workbook::Format] a new resulting Workbook::Format
|
62
63
|
def merge(other_format)
|
63
|
-
|
64
|
-
|
64
|
+
remove_all_raws!
|
65
|
+
merge_hash(other_format)
|
65
66
|
end
|
66
67
|
|
67
68
|
# Applies the formatting options of self with another, removes as a consequence the reference to the raw object's equivalent.
|
68
69
|
# @param [Workbook::Format] other_format
|
69
70
|
# @return [Workbook::Format] self
|
70
71
|
def merge!(other_format)
|
71
|
-
|
72
|
-
|
72
|
+
remove_all_raws!
|
73
|
+
merge_hash!(other_format)
|
73
74
|
end
|
74
75
|
|
75
76
|
# returns an array of all formats this style is inheriting from (including itself)
|
76
77
|
# @return [Array<Workbook::Format>] an array of Workbook::Formats
|
77
78
|
def formats
|
78
|
-
formats=[]
|
79
|
+
formats = []
|
79
80
|
f = self
|
80
81
|
formats << f
|
81
82
|
while f.parent
|
@@ -88,23 +89,23 @@ module Workbook
|
|
88
89
|
# returns an array of all format-names this style is inheriting from (and this style)
|
89
90
|
# @return [Array<String>] an array of Workbook::Formats
|
90
91
|
def all_names
|
91
|
-
formats.collect{|a| a.name}
|
92
|
+
formats.collect { |a| a.name }
|
92
93
|
end
|
93
94
|
|
94
95
|
# Applies the formatting options of self with its parents until no parent can be found
|
95
96
|
# @return [Workbook::Format] new Workbook::Format that is the result of merging current style with all its parent's styles.
|
96
97
|
def flattened
|
97
|
-
ff=Workbook::Format.new
|
98
|
-
formats.each{|a| ff.merge!(a) }
|
99
|
-
|
98
|
+
ff = Workbook::Format.new
|
99
|
+
formats.each { |a| ff.merge!(a) }
|
100
|
+
ff
|
100
101
|
end
|
101
102
|
|
102
103
|
# Formatting is sometimes the only way to detect the cells' type.
|
103
104
|
def derived_type
|
104
105
|
if self[:numberformat]
|
105
|
-
if self[:numberformat].to_s.match("h")
|
106
|
+
if self[:numberformat].to_s.match?("h")
|
106
107
|
:time
|
107
|
-
elsif self[:numberformat].to_s
|
108
|
+
elsif /y/i.match?(self[:numberformat].to_s)
|
108
109
|
:date
|
109
110
|
end
|
110
111
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
["Numeric", "String", "Time", "Date", "TrueClass", "FalseClass", "NilClass"].each do |type|
|
4
|
+
f = File.open(File.join(File.dirname(__FILE__), "types", "#{type}.rb"), "w+")
|
4
5
|
puts f.inspect
|
5
|
-
doc="require 'workbook/cell'
|
6
|
+
doc = "require 'workbook/cell'
|
6
7
|
|
7
8
|
module Workbook
|
8
9
|
module Types
|
@@ -11,5 +12,5 @@ module Workbook
|
|
11
12
|
end
|
12
13
|
end
|
13
14
|
end"
|
14
|
-
f.write(doc)
|
15
|
-
end
|
15
|
+
f.write(doc)
|
16
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
3
4
|
module Workbook
|
4
5
|
module Modules
|
5
6
|
# Adds simple caching
|
@@ -31,12 +32,12 @@ module Workbook
|
|
31
32
|
|
32
33
|
# Check if currently stored key is available and still valid
|
33
34
|
# @return [Boolean]
|
34
|
-
def valid_cache_key?(key, expires=nil)
|
35
|
+
def valid_cache_key?(key, expires = nil)
|
35
36
|
cache_valid_from
|
36
|
-
|
37
|
-
rv
|
37
|
+
@cache[key] && (@cache[key][:inserted_at] > cache_valid_from) && (expires.nil? || (@cache[key][:inserted_at] < expires))
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
|
+
def fetch_cache(key, expires = nil)
|
40
41
|
@cache ||= {}
|
41
42
|
if valid_cache_key?(key, expires)
|
42
43
|
return @cache[key][:value]
|
@@ -46,8 +47,8 @@ module Workbook
|
|
46
47
|
inserted_at: Time.now
|
47
48
|
}
|
48
49
|
end
|
49
|
-
|
50
|
+
@cache[key][:value]
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
53
|
-
end
|
54
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
# -*- encoding : utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
3
|
-
|
4
|
-
|
5
|
-
require
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "workbook/modules/type_parser"
|
5
|
+
require "workbook/nil_value"
|
6
|
+
require "date"
|
6
7
|
|
7
8
|
module Workbook
|
8
9
|
module Modules
|
@@ -10,50 +11,51 @@ module Workbook
|
|
10
11
|
include Workbook::Modules::TypeParser
|
11
12
|
|
12
13
|
CHARACTER_REPACEMENTS = {
|
13
|
-
[/[
|
14
|
-
[
|
15
|
-
[/\+/] =>
|
16
|
-
[/\s/,
|
17
|
-
[
|
18
|
-
[
|
19
|
-
[
|
20
|
-
[
|
21
|
-
[
|
22
|
-
[
|
23
|
-
[
|
24
|
-
[
|
25
|
-
[
|
26
|
-
[
|
27
|
-
[
|
28
|
-
[
|
29
|
-
[
|
30
|
-
[
|
31
|
-
[
|
32
|
-
[
|
33
|
-
[
|
34
|
-
[
|
35
|
-
[
|
36
|
-
[
|
37
|
-
[
|
38
|
-
[
|
39
|
-
[
|
40
|
-
[
|
14
|
+
[/[().?,!=$:]/] => "",
|
15
|
+
[/&/] => "amp",
|
16
|
+
[/\+/] => "_plus_",
|
17
|
+
[/\s/, "/_", "/", "\\"] => "_",
|
18
|
+
["–_", "-_", "+_", "-"] => "",
|
19
|
+
["__"] => "_",
|
20
|
+
[">"] => "gt",
|
21
|
+
["<"] => "lt",
|
22
|
+
["á", "à", "â", "ä", "ã", "å"] => "a",
|
23
|
+
["Ã", "Ä", "Â", "À", "�?", "Å"] => "A",
|
24
|
+
["é", "è", "ê", "ë"] => "e",
|
25
|
+
["Ë", "É", "È", "Ê"] => "E",
|
26
|
+
["í", "ì", "î", "ï"] => "i",
|
27
|
+
["�?", "Î", "Ì", "�?"] => "I",
|
28
|
+
["ó", "ò", "ô", "ö", "õ"] => "o",
|
29
|
+
["Õ", "Ö", "Ô", "Ò", "Ó"] => "O",
|
30
|
+
["ú", "ù", "û", "ü"] => "u",
|
31
|
+
["Ú", "Û", "Ù", "Ü"] => "U",
|
32
|
+
["ç"] => "c",
|
33
|
+
["Ç"] => "C",
|
34
|
+
["š", "ś"] => "s",
|
35
|
+
["Š", "Ś"] => "S",
|
36
|
+
["ž", "ź"] => "z",
|
37
|
+
["Ž", "Ź"] => "Z",
|
38
|
+
["ñ"] => "n",
|
39
|
+
["Ñ"] => "N",
|
40
|
+
["#"] => "hash",
|
41
|
+
["*"] => "asterisk"
|
41
42
|
}
|
42
43
|
CLASS_CELLTYPE_MAPPING = {
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
"BigDecimal" => :decimal,
|
45
|
+
"Numeric" => :integer,
|
46
|
+
"Integer" => :integer,
|
47
|
+
"Fixnum" => :integer,
|
48
|
+
"Float" => :float,
|
49
|
+
"String" => :string,
|
50
|
+
"Symbol" => :string,
|
51
|
+
"Time" => :time,
|
52
|
+
"Date" => :date,
|
53
|
+
"DateTime" => :datetime,
|
54
|
+
"ActiveSupport::TimeWithZone" => :datetime,
|
55
|
+
"TrueClass" => :boolean,
|
56
|
+
"FalseClass" => :boolean,
|
57
|
+
"NilClass" => :nil,
|
58
|
+
"Workbook::NilValue" => :nil
|
57
59
|
}
|
58
60
|
# Note that these types are sorted by 'importance'
|
59
61
|
|
@@ -78,7 +80,7 @@ module Workbook
|
|
78
80
|
end
|
79
81
|
|
80
82
|
def row= r
|
81
|
-
@row= r
|
83
|
+
@row = r
|
82
84
|
end
|
83
85
|
|
84
86
|
# Change the current value
|
@@ -107,18 +109,25 @@ module Workbook
|
|
107
109
|
@value
|
108
110
|
end
|
109
111
|
|
112
|
+
# Returns the column object for the cell
|
113
|
+
#
|
114
|
+
# @return [Workbook::Column] the column the cell belongs to
|
115
|
+
def column
|
116
|
+
table.columns[index]
|
117
|
+
end
|
118
|
+
|
110
119
|
# Returns the sheet its at.
|
111
120
|
#
|
112
121
|
# @return [Workbook::Table]
|
113
122
|
def table
|
114
|
-
row
|
123
|
+
row&.table
|
115
124
|
end
|
116
125
|
|
117
126
|
# Quick assessor to the book's template, if it exists
|
118
127
|
#
|
119
128
|
# @return [Workbook::Template]
|
120
129
|
def template
|
121
|
-
row
|
130
|
+
row&.template
|
122
131
|
end
|
123
132
|
|
124
133
|
# Change the current format
|
@@ -129,7 +138,7 @@ module Workbook
|
|
129
138
|
@workbook_format = f
|
130
139
|
elsif f.is_a? Hash
|
131
140
|
@workbook_format = Workbook::Format.new(f)
|
132
|
-
elsif f.
|
141
|
+
elsif f.instance_of?(NilClass)
|
133
142
|
@workbook_format = Workbook::Format.new
|
134
143
|
end
|
135
144
|
end
|
@@ -139,7 +148,7 @@ module Workbook
|
|
139
148
|
# @return [Workbook::Format] the current format
|
140
149
|
def format
|
141
150
|
# return @workbook_format if @workbook_format
|
142
|
-
if row
|
151
|
+
if row && template && row.header? && !defined?(@workbook_format)
|
143
152
|
@workbook_format = template.create_or_find_format_by(:header)
|
144
153
|
else
|
145
154
|
@workbook_format ||= Workbook::Format.new
|
@@ -153,9 +162,9 @@ module Workbook
|
|
153
162
|
# @return [Boolean]
|
154
163
|
def ==(other)
|
155
164
|
if other.is_a? Cell
|
156
|
-
other.value ==
|
165
|
+
other.value == value
|
157
166
|
else
|
158
|
-
other ==
|
167
|
+
other == value
|
159
168
|
end
|
160
169
|
end
|
161
170
|
|
@@ -166,7 +175,7 @@ module Workbook
|
|
166
175
|
end
|
167
176
|
|
168
177
|
def nil_or_empty?
|
169
|
-
value.nil? || value.to_s == ""
|
178
|
+
value.nil? || value.strip.to_s == ""
|
170
179
|
end
|
171
180
|
|
172
181
|
def value_to_s
|
@@ -179,46 +188,24 @@ module Workbook
|
|
179
188
|
#
|
180
189
|
# <Workbook::Cell value="yet another value">.to_sym # returns :yet_another_value
|
181
190
|
def to_sym
|
182
|
-
|
183
|
-
v = nil
|
184
|
-
unless nil_or_empty?
|
185
|
-
if cell_type == :integer
|
186
|
-
v = "num#{value}".to_sym
|
187
|
-
elsif cell_type == :float
|
188
|
-
v = "num#{value}".sub(".","_").to_sym
|
189
|
-
else
|
190
|
-
v = value_to_s.strip
|
191
|
-
ends_with_exclamationmark = (v[-1] == '!')
|
192
|
-
ends_with_questionmark = (v[-1] == '?')
|
193
|
-
|
194
|
-
v = _replace_possibly_problematic_characters_from_string(v)
|
195
|
-
|
196
|
-
v = v.encode(Encoding.find('ASCII'), {:invalid => :replace, :undef => :replace, :replace => ''})
|
197
|
-
|
198
|
-
v = "#{v}!" if ends_with_exclamationmark
|
199
|
-
v = "#{v}?" if ends_with_questionmark
|
200
|
-
v = v.downcase.to_sym
|
201
|
-
end
|
202
|
-
end
|
203
|
-
@to_sym = v
|
204
|
-
return @to_sym
|
191
|
+
@to_sym ||= ::Workbook::Cell.value_to_sym(value)
|
205
192
|
end
|
206
193
|
|
207
194
|
# Compare
|
208
195
|
#
|
209
196
|
# @param [Workbook::Cell] other cell to compare against (based on value), can compare different value-types using #compare_on_class
|
210
|
-
# @return [
|
197
|
+
# @return [Integer] -1, 0, 1
|
211
198
|
def <=> other
|
212
199
|
rv = nil
|
213
200
|
begin
|
214
|
-
rv =
|
201
|
+
rv = value <=> other.value
|
215
202
|
rescue NoMethodError
|
216
203
|
rv = compare_on_class other
|
217
204
|
end
|
218
|
-
if rv
|
205
|
+
if rv.nil?
|
219
206
|
rv = compare_on_class other
|
220
207
|
end
|
221
|
-
|
208
|
+
rv
|
222
209
|
end
|
223
210
|
|
224
211
|
# Compare on class level
|
@@ -227,7 +214,7 @@ module Workbook
|
|
227
214
|
def compare_on_class other
|
228
215
|
other_value = nil
|
229
216
|
other_value = other.value if other
|
230
|
-
self_value = importance_of_class
|
217
|
+
self_value = importance_of_class value
|
231
218
|
other_value = importance_of_class other_value
|
232
219
|
self_value <=> other_value
|
233
220
|
end
|
@@ -243,14 +230,14 @@ module Workbook
|
|
243
230
|
#
|
244
231
|
# @return [Boolean] index of the cell
|
245
232
|
def format?
|
246
|
-
format
|
233
|
+
format && (format.keys.count > 0)
|
247
234
|
end
|
248
235
|
|
249
236
|
# Returns the index of the cell within the row, returns nil if no row is present
|
250
237
|
#
|
251
238
|
# @return [Integer, NilClass] index of the cell
|
252
239
|
def index
|
253
|
-
row
|
240
|
+
row&.index self
|
254
241
|
end
|
255
242
|
|
256
243
|
# Returns the key (a Symbol) of the cell, based on its table's header
|
@@ -263,6 +250,7 @@ module Workbook
|
|
263
250
|
def inspect
|
264
251
|
txt = "<Workbook::Cell @value=#{value}"
|
265
252
|
txt += " @format=#{format}" if format?
|
253
|
+
txt += " @cell_type=#{cell_type}"
|
266
254
|
txt += ">"
|
267
255
|
txt
|
268
256
|
end
|
@@ -270,9 +258,9 @@ module Workbook
|
|
270
258
|
# convert value to string, and in case of a Date or Time value, apply formatting
|
271
259
|
# @return [String]
|
272
260
|
def to_s
|
273
|
-
if (
|
261
|
+
if (is_a?(Date) || is_a?(Time)) && format[:number_format]
|
274
262
|
value.strftime(format[:number_format])
|
275
|
-
elsif (
|
263
|
+
elsif instance_of?(Workbook::Cell)
|
276
264
|
value.to_s
|
277
265
|
else
|
278
266
|
super
|
@@ -282,26 +270,17 @@ module Workbook
|
|
282
270
|
def colspan= c
|
283
271
|
@colspan = c
|
284
272
|
end
|
273
|
+
|
285
274
|
def rowspan= r
|
286
275
|
@rowspan = r
|
287
276
|
end
|
288
277
|
|
289
278
|
def colspan
|
290
|
-
@colspan.to_i if defined?(@colspan)
|
291
|
-
end
|
292
|
-
def rowspan
|
293
|
-
@rowspan.to_i if defined?(@rowspan) and @rowspan.to_i > 1
|
279
|
+
@colspan.to_i if defined?(@colspan) && (@colspan.to_i > 1)
|
294
280
|
end
|
295
281
|
|
296
|
-
|
297
|
-
|
298
|
-
def _replace_possibly_problematic_characters_from_string(string)
|
299
|
-
Workbook::Modules::Cell::CHARACTER_REPACEMENTS.each do |ac,rep|
|
300
|
-
ac.each do |s|
|
301
|
-
string = string.gsub(s, rep)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
string
|
282
|
+
def rowspan
|
283
|
+
@rowspan.to_i if defined?(@rowspan) && (@rowspan.to_i > 1)
|
305
284
|
end
|
306
285
|
end
|
307
286
|
end
|