workbook 0.8.0 → 0.8.2
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 +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
|