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