rubyexcel 0.0.1 → 0.0.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.
- data/lib/rubyexcel/address.rb +65 -0
- data/lib/rubyexcel/data.rb +172 -0
- data/lib/rubyexcel/element.rb +68 -0
- data/lib/rubyexcel/rubyexcel_components.rb +10 -342
- data/lib/rubyexcel/section.rb +144 -0
- data/lib/rubyexcel.rb +78 -13
- metadata +6 -3
- data/lib/rubyexcel/rubyexcel_advanced.rb +0 -164
@@ -0,0 +1,65 @@
|
|
1
|
+
module RubyExcel
|
2
|
+
|
3
|
+
module Address
|
4
|
+
|
5
|
+
def address_to_col_index( address )
|
6
|
+
col_index( column_id( address ) )
|
7
|
+
end
|
8
|
+
|
9
|
+
def address_to_indices( address )
|
10
|
+
[ row_id( address ), address_to_col_index( address ) ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def col_index( letter )
|
14
|
+
return letter if letter.is_a? Fixnum
|
15
|
+
letter !~ /[^A-Z]/ && [1,2,3].include?( letter.length ) or fail ArgumentError, "Invalid column reference: #{ letter }"
|
16
|
+
idx, a = 1, 'A'
|
17
|
+
loop { return idx if a == letter; idx+=1; a.next! }
|
18
|
+
end
|
19
|
+
|
20
|
+
def col_letter( index )
|
21
|
+
return index if index.is_a? String
|
22
|
+
index > 0 or fail ArgumentError, 'Indexing is 1-based'
|
23
|
+
a = 'A' ; return a if index == 1
|
24
|
+
(index - 1).times{ a.next! }; a
|
25
|
+
end
|
26
|
+
|
27
|
+
def column_id( address )
|
28
|
+
address[/[A-Z]+/] or fail ArgumentError, "Invalid address: #{ address }"
|
29
|
+
end
|
30
|
+
|
31
|
+
def expand( address )
|
32
|
+
return [[address]] unless address.include? ':'
|
33
|
+
start_col, end_col, start_row, end_row = [ address[/^[A-Z]+/], address[/(?<=:)[A-Z]+/] ].sort + [ address.match(/(\d+):/).captures.first, address[/\d+$/] ].sort
|
34
|
+
(start_row..end_row).map { |r| (start_col..end_col).map { |c| "#{ c }#{ r }" } }
|
35
|
+
end
|
36
|
+
|
37
|
+
def indices_to_address( row_idx, column_idx )
|
38
|
+
[ row_idx, column_idx ].all? { |a| a.is_a?( Fixnum ) } or fail ArgumentError, 'Input must be Fixnum'
|
39
|
+
col_letter( column_idx ) + row_idx.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def multi_array?( obj )
|
43
|
+
obj.all? { |el| el.is_a?( Array ) } && obj.is_a?( Array ) rescue false
|
44
|
+
end
|
45
|
+
|
46
|
+
def offset(address, row, col)
|
47
|
+
( col_letter( address_to_col_index( address ) + col ) ) + ( row_id( address ) + row ).to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_range_address( obj1, obj2 )
|
51
|
+
if obj2
|
52
|
+
obj2.is_a?( String ) ? ( obj1 + ':' + obj2 ) : "#{ obj1.address }:#{ obj2.address }"
|
53
|
+
else
|
54
|
+
obj1.is_a?( String ) ? obj1 : obj1.address
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def row_id( address )
|
59
|
+
return nil unless address
|
60
|
+
address[/\d+/].to_i
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module RubyExcel
|
2
|
+
|
3
|
+
require_relative 'address.rb'
|
4
|
+
|
5
|
+
class Data
|
6
|
+
attr_reader :rows, :cols
|
7
|
+
attr_accessor :sheet
|
8
|
+
alias parent sheet
|
9
|
+
|
10
|
+
include Address
|
11
|
+
|
12
|
+
def initialize( sheet, input_data )
|
13
|
+
@sheet = sheet
|
14
|
+
( input_data.kind_of?( Array ) && input_data.all? { |el| el.kind_of?( Array ) } ) or fail ArgumentError, 'Input must be Array of Arrays'
|
15
|
+
@data = input_data.dup
|
16
|
+
calc_dimensions
|
17
|
+
end
|
18
|
+
|
19
|
+
def all
|
20
|
+
@data.dup
|
21
|
+
end
|
22
|
+
|
23
|
+
def append( multi_array )
|
24
|
+
@data
|
25
|
+
@data += multi_array
|
26
|
+
calc_dimensions
|
27
|
+
end
|
28
|
+
|
29
|
+
def colref_by_header( header )
|
30
|
+
sheet.header_rows > 0 or fail NoMethodError, 'No header rows present'
|
31
|
+
@data[ 0..sheet.header_rows-1 ].each do |r|
|
32
|
+
if ( idx = r.index( header ) )
|
33
|
+
return col_letter( idx+1 )
|
34
|
+
end
|
35
|
+
end
|
36
|
+
fail IndexError, "#{ header } is not a valid header"
|
37
|
+
end
|
38
|
+
|
39
|
+
def compact!
|
40
|
+
compact_columns!
|
41
|
+
compact_rows!
|
42
|
+
end
|
43
|
+
|
44
|
+
def compact_columns!
|
45
|
+
ensure_shape
|
46
|
+
@data = @data.transpose.delete_if { |ar| ar.all? { |el| el.to_s.empty? } || ar.empty? }.transpose
|
47
|
+
calc_dimensions
|
48
|
+
@data
|
49
|
+
end
|
50
|
+
|
51
|
+
def compact_rows!
|
52
|
+
@data.delete_if { |ar| ar.all? { |el| el.to_s.empty? } || ar.empty? }
|
53
|
+
calc_dimensions
|
54
|
+
@data
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete( object )
|
58
|
+
case object
|
59
|
+
when Row
|
60
|
+
@data.slice!( object.idx - 1 )
|
61
|
+
when Column
|
62
|
+
idx = col_index( object.idx ) - 1
|
63
|
+
@data.each { |r| r.slice! idx }
|
64
|
+
when Element
|
65
|
+
addresses = expand( object.address )
|
66
|
+
indices = [ address_to_indices( addresses.first.first ), address_to_indices( addresses.last.last ) ].flatten.map { |n| n-1 }
|
67
|
+
@data[ indices[0]..indices[2] ].each { |r| r.slice!( indices[1], indices[3] - indices[1] + 1 ) }
|
68
|
+
@data.delete_if.with_index { |r,i| r.empty? && i.between?( indices[0], indices[2] ) }
|
69
|
+
else
|
70
|
+
fail NoMethodError, "#{ object.class } is not supported"
|
71
|
+
end
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete_column( ref )
|
76
|
+
delete( Column.new( sheet, ref ) )
|
77
|
+
end
|
78
|
+
|
79
|
+
def delete_row( ref )
|
80
|
+
delete( Row.new( sheet, ref ) )
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_range( ref )
|
84
|
+
delete( Element.new( sheet, ref ) )
|
85
|
+
end
|
86
|
+
|
87
|
+
def dup
|
88
|
+
Data.new( sheet, @data.dup )
|
89
|
+
end
|
90
|
+
|
91
|
+
def empty?
|
92
|
+
no_headers.empty?
|
93
|
+
end
|
94
|
+
|
95
|
+
def filter!( header )
|
96
|
+
hrows = sheet.header_rows
|
97
|
+
idx = col_index( hrows > 0 ? colref_by_header( header ) : header )
|
98
|
+
@data = @data.select.with_index { |row, i| hrows > i || yield( row[ idx -1 ] ) }
|
99
|
+
calc_dimensions
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_columns!( *headers )
|
104
|
+
hrow = sheet.header_rows - 1
|
105
|
+
ensure_shape
|
106
|
+
@data = @data.transpose.select{ |col| headers.include?( col[hrow] ) }
|
107
|
+
ensure_shape
|
108
|
+
@data = @data.sort_by{ |col| headers.index( col[hrow] ) || col[hrow] }.transpose
|
109
|
+
calc_dimensions
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def insert_columns( before, number=1 )
|
114
|
+
a = Array.new( number, nil )
|
115
|
+
before = col_index( before ) - 1
|
116
|
+
@data.map! { |row| row.insert( before, *a ) }
|
117
|
+
end
|
118
|
+
|
119
|
+
def insert_rows( before, number=1 )
|
120
|
+
@data = @data.insert( ( col_index( before ) - 1 ), *Array.new( number, [nil] ) )
|
121
|
+
end
|
122
|
+
|
123
|
+
def no_headers
|
124
|
+
if sheet.header_cols.zero?
|
125
|
+
@data[ sheet.header_rows..-1 ].dup
|
126
|
+
else
|
127
|
+
@data[ sheet.header_rows..-1 ].map { |row| row[ sheet.header_cols..-1 ] }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def read( addr )
|
132
|
+
row_idx, col_idx = address_to_indices( addr )
|
133
|
+
@data[ row_idx-1 ][ col_idx-1 ]
|
134
|
+
end
|
135
|
+
alias [] read
|
136
|
+
|
137
|
+
def uniq!( header )
|
138
|
+
column = col_index( colref_by_header( header ) )
|
139
|
+
@data = @data.uniq { |row| row[ column - 1 ] }
|
140
|
+
self
|
141
|
+
end
|
142
|
+
alias unique! uniq!
|
143
|
+
|
144
|
+
def write( addr, val )
|
145
|
+
row_idx, col_idx = address_to_indices( addr )
|
146
|
+
( row_idx - rows ).times { @data << [] }
|
147
|
+
@data[ row_idx-1 ][ col_idx-1 ] = val
|
148
|
+
calc_dimensions
|
149
|
+
end
|
150
|
+
alias []= write
|
151
|
+
|
152
|
+
include Enumerable
|
153
|
+
|
154
|
+
def each
|
155
|
+
@data.each { |ar| yield ar }
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def calc_dimensions
|
161
|
+
@rows, @cols = @data.length, @data.max_by(&:length).length
|
162
|
+
end
|
163
|
+
|
164
|
+
def ensure_shape
|
165
|
+
calc_dimensions
|
166
|
+
@data.map! { |ar| ar.length == cols ? ar : ar + Array.new( cols - ar.length, nil) }
|
167
|
+
@data
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RubyExcel
|
2
|
+
|
3
|
+
class Element
|
4
|
+
|
5
|
+
attr_reader :sheet, :address, :data, :column, :row
|
6
|
+
alias parent sheet
|
7
|
+
|
8
|
+
def initialize( sheet, addr )
|
9
|
+
fail ArgumentError, "Invalid range: #{ addr }" unless addr =~ /\A[A-Z]+\d+:[A-Z]+\d+\z|\A[A-Z]+\d+\z/
|
10
|
+
@sheet = sheet
|
11
|
+
@data = sheet.data
|
12
|
+
@address = addr
|
13
|
+
@column = column_id( addr )
|
14
|
+
@row = row_id( addr )
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete
|
18
|
+
data.delete( self )
|
19
|
+
end
|
20
|
+
|
21
|
+
include Address
|
22
|
+
|
23
|
+
def value
|
24
|
+
if address.include? ':'
|
25
|
+
expand( address ).map { |ar| ar.map { |addr| data[ addr ] } }
|
26
|
+
else
|
27
|
+
data[ address ]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def value=( val )
|
32
|
+
if address.include? ':'
|
33
|
+
if multi_array?( val )
|
34
|
+
expand( address ).each_with_index { |row,idx| row.each_with_index { |el,i| data[ el ] = val[idx][i] } }
|
35
|
+
else
|
36
|
+
expand( address ).each { |ar| ar.each { |addr| data[ addr ] = val } }
|
37
|
+
end
|
38
|
+
else
|
39
|
+
data[ address ] = val
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
value.is_a?( Array ) ? value.map { |ar| ar.join "\t" }.join($/) : value.to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ address }"
|
50
|
+
end
|
51
|
+
|
52
|
+
include Enumerable
|
53
|
+
|
54
|
+
def each
|
55
|
+
expand( address ).flatten.each { |addr| yield data[ addr ] }
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_cell
|
59
|
+
expand( address ).flatten.each { |addr| yield Element.new( sheet, addr ) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def map!
|
63
|
+
expand( address ).flatten.each { |addr| data[ addr ] = yield data[ addr ] }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -1,348 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def address_to_col_index( address )
|
6
|
-
col_index( column_id( address ) )
|
7
|
-
end
|
8
|
-
|
9
|
-
def address_to_indices( address )
|
10
|
-
[ row_id( address ), address_to_col_index( address ) ]
|
11
|
-
end
|
12
|
-
|
13
|
-
def col_index( letter )
|
14
|
-
return letter if letter.is_a? Fixnum
|
15
|
-
letter !~ /[^A-Z]/ && [1,2,3].include?( letter.length ) or fail ArgumentError, "Invalid column reference: #{ letter }"
|
16
|
-
idx, a = 1, 'A'
|
17
|
-
loop { return idx if a == letter; idx+=1; a.next! }
|
18
|
-
end
|
19
|
-
|
20
|
-
def col_letter( index )
|
21
|
-
return index if index.is_a? String
|
22
|
-
index > 0 or fail ArgumentError, 'Indexing is 1-based'
|
23
|
-
a = 'A' ; return a if index == 1
|
24
|
-
(index - 1).times{ a.next! }; a
|
25
|
-
end
|
26
|
-
|
27
|
-
def column_id( address )
|
28
|
-
address[/[A-Z]+/] or fail ArgumentError, "Invalid address: #{ address }"
|
29
|
-
end
|
30
|
-
|
31
|
-
def expand( address )
|
32
|
-
return [[address]] unless address.include? ':'
|
33
|
-
start_col, end_col, start_row, end_row = [ address[/^[A-Z]+/], address[/(?<=:)[A-Z]+/] ].sort + [ address.match(/(\d+):/).captures.first, address[/\d+$/] ].sort
|
34
|
-
(start_row..end_row).map { |r| (start_col..end_col).map { |c| "#{ c }#{ r }" } }
|
35
|
-
end
|
36
|
-
|
37
|
-
def indices_to_address( row_idx, column_idx )
|
38
|
-
[ row_idx, column_idx ].all? { |a| a.is_a?( Fixnum ) } or fail ArgumentError, 'Input must be Fixnum'
|
39
|
-
col_letter( column_idx ) + row_idx.to_s
|
40
|
-
end
|
41
|
-
|
42
|
-
def multi_array?( obj )
|
43
|
-
obj.all? { |el| el.is_a?( Array ) } && obj.is_a?( Array ) rescue false
|
44
|
-
end
|
45
|
-
|
46
|
-
def offset(address, row, col)
|
47
|
-
( col_letter( address_to_col_index( address ) + col ) ) + ( row_id( address ) + row ).to_s
|
48
|
-
end
|
49
|
-
|
50
|
-
def to_range_address( obj1, obj2 )
|
51
|
-
if obj2
|
52
|
-
obj2.is_a?( String ) ? ( obj1 + ':' + obj2 ) : "#{ obj1.address }:#{ obj2.address }"
|
53
|
-
else
|
54
|
-
obj1.is_a?( String ) ? obj1 : obj1.address
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def row_id( address )
|
59
|
-
return nil unless address
|
60
|
-
address[/\d+/].to_i
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
class Data
|
66
|
-
attr_reader :rows, :cols
|
67
|
-
attr_accessor :sheet
|
68
|
-
alias parent sheet
|
69
|
-
|
70
|
-
include Address
|
71
|
-
|
72
|
-
def initialize( sheet, input_data )
|
73
|
-
@sheet = sheet
|
74
|
-
( input_data.kind_of?( Array ) && input_data.all? { |el| el.kind_of?( Array ) } ) or fail ArgumentError, 'Input must be Array of Arrays'
|
75
|
-
@data = input_data.dup
|
76
|
-
calc_dimensions
|
77
|
-
end
|
78
|
-
|
79
|
-
def all
|
80
|
-
@data.dup
|
81
|
-
end
|
82
|
-
|
83
|
-
def append( multi_array )
|
84
|
-
@data
|
85
|
-
@data += multi_array
|
86
|
-
calc_dimensions
|
87
|
-
end
|
88
|
-
|
89
|
-
def colref_by_header( header )
|
90
|
-
sheet.header_rows > 0 or fail NoMethodError, 'No header rows present'
|
91
|
-
@data[ 0..sheet.header_rows-1 ].each do |r|
|
92
|
-
if ( idx = r.index( header ) )
|
93
|
-
return col_letter( idx+1 )
|
94
|
-
end
|
95
|
-
end
|
96
|
-
fail IndexError, "#{ header } is not a valid header"
|
97
|
-
end
|
98
|
-
|
99
|
-
def compact!
|
100
|
-
compact_columns!
|
101
|
-
compact_rows!
|
102
|
-
end
|
103
|
-
|
104
|
-
def compact_columns!
|
105
|
-
ensure_shape
|
106
|
-
@data = @data.transpose.delete_if { |ar| ar.all? { |el| el.to_s.empty? } || ar.empty? }.transpose
|
107
|
-
calc_dimensions
|
108
|
-
@data
|
109
|
-
end
|
110
|
-
|
111
|
-
def compact_rows!
|
112
|
-
@data.delete_if { |ar| ar.all? { |el| el.to_s.empty? } || ar.empty? }
|
113
|
-
calc_dimensions
|
114
|
-
@data
|
115
|
-
end
|
116
|
-
|
117
|
-
def dup
|
118
|
-
Data.new( sheet, @data.dup )
|
119
|
-
end
|
120
|
-
|
121
|
-
def empty?
|
122
|
-
no_headers.empty?
|
123
|
-
end
|
124
|
-
|
125
|
-
def insert_columns( before, number=1 )
|
126
|
-
a = Array.new( number, nil )
|
127
|
-
before = col_index( before ) - 1
|
128
|
-
@data.map! { |row| row.insert( before, *a ) }
|
129
|
-
end
|
130
|
-
|
131
|
-
def insert_rows( before, number=1 )
|
132
|
-
@data = @data.insert( ( col_index( before ) - 1 ), *Array.new( number, [nil] ) )
|
133
|
-
end
|
134
|
-
|
135
|
-
def no_headers
|
136
|
-
if sheet.header_cols.zero?
|
137
|
-
@data[ sheet.header_rows..-1 ].dup
|
138
|
-
else
|
139
|
-
@data[ sheet.header_rows..-1 ].map { |row| row[ sheet.header_cols..-1 ] }
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def read( addr )
|
144
|
-
row_idx, col_idx = address_to_indices( addr )
|
145
|
-
@data[ row_idx-1 ][ col_idx-1 ]
|
146
|
-
end
|
147
|
-
alias [] read
|
148
|
-
|
149
|
-
def write( addr, val )
|
150
|
-
row_idx, col_idx = address_to_indices( addr )
|
151
|
-
( row_idx - rows ).times { @data << [] }
|
152
|
-
@data[ row_idx-1 ][ col_idx-1 ] = val
|
153
|
-
calc_dimensions
|
154
|
-
end
|
155
|
-
alias []= write
|
156
|
-
|
157
|
-
include Enumerable
|
158
|
-
|
159
|
-
def each
|
160
|
-
@data.each { |ar| yield ar }
|
161
|
-
end
|
162
|
-
|
163
|
-
private
|
164
|
-
|
165
|
-
def calc_dimensions
|
166
|
-
@rows, @cols = @data.length, @data.max_by(&:length).length
|
167
|
-
end
|
168
|
-
|
169
|
-
def ensure_shape
|
170
|
-
calc_dimensions
|
171
|
-
@data.map! { |ar| ar.length == cols ? ar : ar + Array.new( cols - ar.length, nil) }
|
172
|
-
@data
|
173
|
-
end
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
|
-
class Section
|
178
|
-
|
179
|
-
include Address
|
1
|
+
require_relative 'address.rb'
|
2
|
+
require_relative 'data.rb'
|
3
|
+
require_relative 'element.rb'
|
4
|
+
require_relative 'section.rb'
|
180
5
|
|
181
|
-
|
182
|
-
alias parent sheet
|
183
|
-
|
184
|
-
def initialize( sheet )
|
185
|
-
@sheet = sheet
|
186
|
-
@data = sheet.data
|
187
|
-
end
|
188
|
-
|
189
|
-
def delete
|
190
|
-
data.delete( self )
|
191
|
-
end
|
192
|
-
|
193
|
-
def empty?
|
194
|
-
all? { |val| val.to_s.empty? }
|
195
|
-
end
|
196
|
-
|
197
|
-
def find
|
198
|
-
each.with_index { |val,i| return translate_address( i + 1 ) if yield val }; nil
|
199
|
-
end
|
200
|
-
|
201
|
-
def inspect
|
202
|
-
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ idx }"
|
203
|
-
end
|
204
|
-
|
205
|
-
def read( id )
|
206
|
-
data[ translate_address( id ) ]
|
207
|
-
end
|
208
|
-
alias [] read
|
209
|
-
|
210
|
-
def to_s
|
211
|
-
to_a.join ( self.is_a?( Row ) ? "\t" : "\n" )
|
212
|
-
end
|
213
|
-
|
214
|
-
def write( id, val )
|
215
|
-
data[ translate_address( id ) ] = val
|
216
|
-
end
|
217
|
-
alias []= write
|
218
|
-
|
219
|
-
include Enumerable
|
220
|
-
|
221
|
-
def each
|
222
|
-
return to_enum(:each) unless block_given?
|
223
|
-
each_address { |addr| yield data[ addr ] }
|
224
|
-
end
|
225
|
-
|
226
|
-
def each_cell
|
227
|
-
return to_enum(:each_cell) unless block_given?
|
228
|
-
each_address { |addr| yield Element.new( sheet, addr ) }
|
229
|
-
end
|
230
|
-
|
231
|
-
def map!
|
232
|
-
return to_enum(:map!) unless block_given?
|
233
|
-
each_address { |addr| data[addr] = ( yield data[addr] ) }
|
234
|
-
end
|
235
|
-
|
236
|
-
private
|
237
|
-
|
238
|
-
def translate_address( addr )
|
239
|
-
case self
|
240
|
-
when Row
|
241
|
-
col_letter( addr ) + idx.to_s
|
242
|
-
when Column
|
243
|
-
addr = addr.to_s unless addr.is_a?( String )
|
244
|
-
fail ArgumentError, "Invalid address : #{ addr }" if addr =~ /[^\d]/
|
245
|
-
idx + addr
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
end
|
250
|
-
|
251
|
-
class Row < Section
|
252
|
-
|
253
|
-
attr_reader :idx
|
254
|
-
|
255
|
-
def initialize( sheet, idx )
|
256
|
-
@idx = idx.to_i
|
257
|
-
super( sheet )
|
258
|
-
end
|
259
|
-
|
260
|
-
private
|
261
|
-
|
262
|
-
def each_address
|
263
|
-
( 'A'..col_letter( data.cols ) ).each { |col_id| yield "#{col_id}#{idx}" }
|
264
|
-
end
|
6
|
+
module RubyExcel
|
265
7
|
|
8
|
+
def self.sample_data
|
9
|
+
a=[];8.times{|t|b=[];c='A';5.times{b<<"#{c}#{t+1}";c.next!};a<<b};a
|
266
10
|
end
|
267
|
-
|
268
|
-
class Column < Section
|
269
11
|
|
270
|
-
|
271
|
-
|
272
|
-
def initialize( sheet, idx )
|
273
|
-
@idx = idx
|
274
|
-
super( sheet )
|
275
|
-
end
|
276
|
-
|
277
|
-
private
|
278
|
-
|
279
|
-
def each_address
|
280
|
-
1.upto( data.rows ) { |row_id| yield idx + row_id.to_s }
|
281
|
-
end
|
282
|
-
|
12
|
+
def self.sample_sheet
|
13
|
+
Workbook.new.load RubyExcel.sample_data
|
283
14
|
end
|
284
|
-
|
285
|
-
class Element
|
286
|
-
|
287
|
-
attr_reader :sheet, :address, :data
|
288
|
-
alias parent sheet
|
289
|
-
|
290
|
-
def initialize( sheet, addr )
|
291
|
-
fail ArgumentError, "Invalid range: #{ addr }" unless addr =~ /\A[A-Z]+\d+:[A-Z]+\d+\z|\A[A-Z]+\d+\z/
|
292
|
-
@sheet = sheet
|
293
|
-
@data = sheet.data
|
294
|
-
@address = addr
|
295
|
-
end
|
296
|
-
|
297
|
-
def delete
|
298
|
-
data.delete( self )
|
299
|
-
end
|
300
|
-
|
301
|
-
include Address
|
302
|
-
|
303
|
-
def value
|
304
|
-
if address.include? ':'
|
305
|
-
expand( address ).map { |ar| ar.map { |addr| data[ addr ] } }
|
306
|
-
else
|
307
|
-
data[ address ]
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
def value=( val )
|
312
|
-
if address.include? ':'
|
313
|
-
if multi_array?( val )
|
314
|
-
expand( address ).each_with_index { |row,idx| row.each_with_index { |el,i| data[ el ] = val[idx][i] } }
|
315
|
-
else
|
316
|
-
expand( address ).each { |ar| ar.each { |addr| data[ addr ] = val } }
|
317
|
-
end
|
318
|
-
else
|
319
|
-
data[ address ] = val
|
320
|
-
end
|
321
|
-
self
|
322
|
-
end
|
323
|
-
|
324
|
-
def to_s
|
325
|
-
value.is_a?( Array ) ? value.map { |ar| ar.join "\t" }.join($/) : value.to_s
|
326
|
-
end
|
327
|
-
|
328
|
-
def inspect
|
329
|
-
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ address }"
|
330
|
-
end
|
331
|
-
|
332
|
-
include Enumerable
|
333
|
-
|
334
|
-
def each
|
335
|
-
expand( address ).flatten.each { |addr| yield data[ addr ] }
|
336
|
-
end
|
337
|
-
|
338
|
-
def each_cell
|
339
|
-
expand( address ).flatten.each { |addr| yield Element.new( sheet, addr ) }
|
340
|
-
end
|
341
|
-
|
342
|
-
def map!
|
343
|
-
expand( address ).flatten.each { |addr| data[ addr ] = yield data[ addr ] }
|
344
|
-
end
|
345
|
-
|
346
|
-
end
|
347
|
-
|
15
|
+
|
348
16
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module RubyExcel
|
2
|
+
|
3
|
+
class Section
|
4
|
+
|
5
|
+
include Address
|
6
|
+
|
7
|
+
attr_reader :sheet, :idx, :data
|
8
|
+
alias parent sheet
|
9
|
+
|
10
|
+
def initialize( sheet )
|
11
|
+
@sheet = sheet
|
12
|
+
@data = sheet.data
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<( value )
|
16
|
+
if self.is_a? Row
|
17
|
+
lastone = ( col_index( idx ) == 1 ? data.cols + 1 : data.cols )
|
18
|
+
else
|
19
|
+
lastone = ( col_index( idx ) == 1 ? data.rows + 1 : data.rows )
|
20
|
+
end
|
21
|
+
data[ translate_address( lastone ) ] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete
|
25
|
+
data.delete( self )
|
26
|
+
end
|
27
|
+
|
28
|
+
def empty?
|
29
|
+
all? { |val| val.to_s.empty? }
|
30
|
+
end
|
31
|
+
|
32
|
+
def find
|
33
|
+
each_cell { |ce| return ce.address if yield ce.value }; nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
"#{ self.class }:0x#{ '%x' % (object_id << 1) }: #{ idx }"
|
38
|
+
end
|
39
|
+
|
40
|
+
def read( id )
|
41
|
+
data[ translate_address( id ) ]
|
42
|
+
end
|
43
|
+
alias [] read
|
44
|
+
|
45
|
+
def summarise
|
46
|
+
h = Hash.new(0)
|
47
|
+
each_wh { |v| h[v]+=1 }; h
|
48
|
+
end
|
49
|
+
alias summarize summarise
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
to_a.join ( self.is_a?( Row ) ? "\t" : "\n" )
|
53
|
+
end
|
54
|
+
|
55
|
+
def write( id, val )
|
56
|
+
data[ translate_address( id ) ] = val
|
57
|
+
end
|
58
|
+
alias []= write
|
59
|
+
|
60
|
+
include Enumerable
|
61
|
+
|
62
|
+
def each
|
63
|
+
return to_enum(:each) unless block_given?
|
64
|
+
each_address { |addr| yield data[ addr ] }
|
65
|
+
end
|
66
|
+
|
67
|
+
def each_without_headers
|
68
|
+
return to_enum(:each_without_headers) unless block_given?
|
69
|
+
each_address_without_headers { |addr| yield data[ addr ] }
|
70
|
+
end
|
71
|
+
alias each_wh each_without_headers
|
72
|
+
|
73
|
+
def each_cell
|
74
|
+
return to_enum(:each_cell) unless block_given?
|
75
|
+
each_address { |addr| yield Element.new( sheet, addr ) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def map!
|
79
|
+
return to_enum(:map!) unless block_given?
|
80
|
+
each_address { |addr| data[addr] = ( yield data[addr] ) }
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def translate_address( addr )
|
86
|
+
case self
|
87
|
+
when Row
|
88
|
+
col_letter( addr ) + idx.to_s
|
89
|
+
when Column
|
90
|
+
addr = addr.to_s unless addr.is_a?( String )
|
91
|
+
fail ArgumentError, "Invalid address : #{ addr }" if addr =~ /[^\d]/
|
92
|
+
idx + addr
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class Row < Section
|
99
|
+
|
100
|
+
attr_reader :idx
|
101
|
+
|
102
|
+
def initialize( sheet, idx )
|
103
|
+
@idx = idx.to_i
|
104
|
+
super( sheet )
|
105
|
+
end
|
106
|
+
|
107
|
+
def getref( header )
|
108
|
+
column_id( sheet.row(1).find &/#{header}/ )
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def each_address
|
114
|
+
( 'A'..col_letter( data.cols ) ).each { |col_id| yield "#{col_id}#{idx}" }
|
115
|
+
end
|
116
|
+
|
117
|
+
def each_address_without_headers
|
118
|
+
( col_letter( sheet.header_cols+1 )..col_letter( data.cols ) ).each { |col_id| yield "#{col_id}#{idx}" }
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
class Column < Section
|
124
|
+
|
125
|
+
attr_reader :idx
|
126
|
+
|
127
|
+
def initialize( sheet, idx )
|
128
|
+
@idx = idx
|
129
|
+
super( sheet )
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def each_address
|
135
|
+
( 1..data.rows ).each { |row_id| yield idx + row_id.to_s }
|
136
|
+
end
|
137
|
+
|
138
|
+
def each_address_without_headers
|
139
|
+
( sheet.header_rows+1 ).upto( data.rows ) { |row_id| yield idx + row_id.to_s }
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
data/lib/rubyexcel.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require_relative 'rubyexcel/rubyexcel_components.rb'
|
2
|
-
require_relative 'rubyexcel/rubyexcel_advanced.rb'
|
3
2
|
require_relative 'rubyexcel/excel_tools.rb'
|
4
3
|
|
5
4
|
class Regexp
|
@@ -18,8 +17,8 @@ module RubyExcel
|
|
18
17
|
|
19
18
|
def <<( other )
|
20
19
|
case other
|
21
|
-
when
|
22
|
-
other.
|
20
|
+
when Workbook
|
21
|
+
other.inject( @sheets, :<< )
|
23
22
|
when Sheet
|
24
23
|
@sheets << other
|
25
24
|
end
|
@@ -124,6 +123,33 @@ module RubyExcel
|
|
124
123
|
range( addr ).value = val
|
125
124
|
end
|
126
125
|
|
126
|
+
|
127
|
+
def +( other )
|
128
|
+
dup << other
|
129
|
+
end
|
130
|
+
|
131
|
+
def -( other )
|
132
|
+
case other
|
133
|
+
when Array
|
134
|
+
Workbook.new.load( data.all - other )
|
135
|
+
when Sheet
|
136
|
+
Workbook.new.load( data.all - other.data.no_headers )
|
137
|
+
else
|
138
|
+
fail ArgumentError, "Unsupported class: #{ other.class }"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def <<( other )
|
143
|
+
case other
|
144
|
+
when Array
|
145
|
+
load( data.all + other, header_rows, header_cols )
|
146
|
+
when Sheet
|
147
|
+
load( data.all + other.data.no_headers, header_rows, header_cols )
|
148
|
+
else
|
149
|
+
fail ArgumentError, "Unsupported class: #{ other.class }"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
127
153
|
def cell( row, col )
|
128
154
|
Element.new( self, indices_to_address( row, col ) )
|
129
155
|
end
|
@@ -133,24 +159,21 @@ module RubyExcel
|
|
133
159
|
Column.new( self, col_letter( index ) )
|
134
160
|
end
|
135
161
|
|
162
|
+
def column_by_header( header )
|
163
|
+
Column.new( self, data.colref_by_header( header ) )
|
164
|
+
end
|
165
|
+
alias ch column_by_header
|
166
|
+
|
136
167
|
def columns( start_column = 'A', end_column = data.cols )
|
137
168
|
start_column, end_column = col_letter( start_column ), col_letter( end_column )
|
138
169
|
return to_enum(:columns, start_column, end_column) unless block_given?
|
139
170
|
( start_column..end_column ).each { |idx| yield column( idx ) }
|
140
171
|
end
|
141
172
|
|
142
|
-
def
|
143
|
-
|
144
|
-
s.data.get_columns!( *headers )
|
145
|
-
s
|
146
|
-
end
|
147
|
-
alias gc get_columns
|
148
|
-
|
149
|
-
def get_columns!( *headers )
|
150
|
-
data.get_columns!( *headers )
|
173
|
+
def compact!
|
174
|
+
data.compact!
|
151
175
|
self
|
152
176
|
end
|
153
|
-
alias gc! get_columns!
|
154
177
|
|
155
178
|
def delete
|
156
179
|
workbook.delete self
|
@@ -167,6 +190,32 @@ module RubyExcel
|
|
167
190
|
s
|
168
191
|
end
|
169
192
|
|
193
|
+
def empty?
|
194
|
+
data.empty?
|
195
|
+
end
|
196
|
+
|
197
|
+
def filter( ref, &block )
|
198
|
+
dup.filter!( ref, &block )
|
199
|
+
end
|
200
|
+
|
201
|
+
def filter!( ref, &block )
|
202
|
+
data.filter!( ref, &block )
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
def get_columns( *headers )
|
207
|
+
s = dup
|
208
|
+
s.data.get_columns!( *headers )
|
209
|
+
s
|
210
|
+
end
|
211
|
+
alias gc get_columns
|
212
|
+
|
213
|
+
def get_columns!( *headers )
|
214
|
+
data.get_columns!( *headers )
|
215
|
+
self
|
216
|
+
end
|
217
|
+
alias gc! get_columns!
|
218
|
+
|
170
219
|
def insert_columns( *args )
|
171
220
|
data.insert_columns( *args )
|
172
221
|
self
|
@@ -213,6 +262,16 @@ module RubyExcel
|
|
213
262
|
( start_row..end_row ).each { |idx| yield row( idx ) }
|
214
263
|
end
|
215
264
|
|
265
|
+
def sumif( find_header, sum_header )
|
266
|
+
col1 = column_by_header( find_header )
|
267
|
+
col2 = column_by_header( sum_header )
|
268
|
+
total = 0
|
269
|
+
col1.each_cell do |ce|
|
270
|
+
total += col2[ ce.row ].to_i if yield( ce.value ) && ce.row >= header_rows
|
271
|
+
end
|
272
|
+
total
|
273
|
+
end
|
274
|
+
|
216
275
|
def to_a
|
217
276
|
data.all
|
218
277
|
end
|
@@ -225,6 +284,12 @@ module RubyExcel
|
|
225
284
|
data.nil? ? '' : data.map { |ar| ar.join "\t" }.join( $/ )
|
226
285
|
end
|
227
286
|
|
287
|
+
def uniq!( header )
|
288
|
+
data.uniq!( header )
|
289
|
+
self
|
290
|
+
end
|
291
|
+
alias unique! uniq!
|
292
|
+
|
228
293
|
end
|
229
294
|
|
230
295
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyexcel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-02 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A tabular data structure, mixing Ruby with some of Excel's API.
|
15
15
|
email:
|
@@ -20,7 +20,10 @@ files:
|
|
20
20
|
- lib/rubyexcel.rb
|
21
21
|
- lib/rubyexcel/excel_tools.rb
|
22
22
|
- lib/rubyexcel/rubyexcel_components.rb
|
23
|
-
- lib/rubyexcel/
|
23
|
+
- lib/rubyexcel/data.rb
|
24
|
+
- lib/rubyexcel/element.rb
|
25
|
+
- lib/rubyexcel/section.rb
|
26
|
+
- lib/rubyexcel/address.rb
|
24
27
|
homepage:
|
25
28
|
licenses: []
|
26
29
|
post_install_message:
|
@@ -1,164 +0,0 @@
|
|
1
|
-
module RubyExcel
|
2
|
-
|
3
|
-
def self.sample_data
|
4
|
-
a=[];8.times{|t|b=[];c='A';5.times{b<<"#{c}#{t+1}";c.next!};a<<b};a
|
5
|
-
end
|
6
|
-
|
7
|
-
def self.sample_sheet
|
8
|
-
Workbook.new.load RubyExcel.sample_data
|
9
|
-
end
|
10
|
-
|
11
|
-
class Sheet
|
12
|
-
|
13
|
-
def +( other )
|
14
|
-
dup << other
|
15
|
-
end
|
16
|
-
|
17
|
-
def -( other )
|
18
|
-
case other
|
19
|
-
when Array
|
20
|
-
Workbook.new.load( data.all - other )
|
21
|
-
when Sheet
|
22
|
-
Workbook.new.load( data.all - other.data.no_headers )
|
23
|
-
else
|
24
|
-
fail ArgumentError, "Unsupported class: #{ other.class }"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def <<( other )
|
29
|
-
case other
|
30
|
-
when Array
|
31
|
-
load( data.all + other, header_rows, header_cols )
|
32
|
-
when Sheet
|
33
|
-
load( data.all + other.data.no_headers, header_rows, header_cols )
|
34
|
-
else
|
35
|
-
fail ArgumentError, "Unsupported class: #{ other.class }"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def compact!
|
40
|
-
data.compact!
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
44
|
-
def empty?
|
45
|
-
data.empty?
|
46
|
-
end
|
47
|
-
|
48
|
-
def column_by_header( header )
|
49
|
-
Column.new( self, data.colref_by_header( header ) )
|
50
|
-
end
|
51
|
-
alias ch column_by_header
|
52
|
-
|
53
|
-
def filter!( ref, &block )
|
54
|
-
data.filter!( ref, &block )
|
55
|
-
self
|
56
|
-
end
|
57
|
-
|
58
|
-
def sumif( find_header, sum_header )
|
59
|
-
col1 = column_by_header( find_header )
|
60
|
-
col2 = column_by_header( sum_header )
|
61
|
-
total = 0
|
62
|
-
col1.each.with_index do |val,idx|
|
63
|
-
total += ( ( n = col2[ idx + 1 ] ).is_a?( String ) ? n.to_i : n ) if yield( val ) && idx >= header_rows
|
64
|
-
end
|
65
|
-
total
|
66
|
-
end
|
67
|
-
|
68
|
-
def uniq!( header )
|
69
|
-
data.uniq!( header )
|
70
|
-
self
|
71
|
-
end
|
72
|
-
alias unique! uniq!
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
class Data
|
77
|
-
|
78
|
-
def delete( object )
|
79
|
-
case object
|
80
|
-
when Row
|
81
|
-
@data.slice!( object.idx - 1 )
|
82
|
-
when Column
|
83
|
-
idx = col_index( object.idx ) - 1
|
84
|
-
@data.each { |r| r.slice! idx }
|
85
|
-
when Element
|
86
|
-
addresses = expand( object.address )
|
87
|
-
indices = [ address_to_indices( addresses.first.first ), address_to_indices( addresses.last.last ) ].flatten.map { |n| n-1 }
|
88
|
-
@data[ indices[0]..indices[2] ].each { |r| r.slice!( indices[1], indices[3] - indices[1] + 1 ) }
|
89
|
-
@data.delete_if.with_index { |r,i| r.empty? && i.between?( indices[0], indices[2] ) }
|
90
|
-
else
|
91
|
-
fail NoMethodError, "#{ object.class } is not supported"
|
92
|
-
end
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
|
-
def delete_column( ref )
|
97
|
-
delete( Column.new( sheet, ref ) )
|
98
|
-
end
|
99
|
-
|
100
|
-
def delete_row( ref )
|
101
|
-
delete( Row.new( sheet, ref ) )
|
102
|
-
end
|
103
|
-
|
104
|
-
def delete_range( ref )
|
105
|
-
delete( Element.new( sheet, ref ) )
|
106
|
-
end
|
107
|
-
|
108
|
-
def filter!( header )
|
109
|
-
hrows = sheet.header_rows
|
110
|
-
idx = col_index( hrows > 0 ? colref_by_header( header ) : header )
|
111
|
-
@data = @data.select.with_index { |row, i| hrows > i || yield( row[ idx -1 ] ) }
|
112
|
-
calc_dimensions
|
113
|
-
self
|
114
|
-
end
|
115
|
-
|
116
|
-
def get_columns!( *headers )
|
117
|
-
hrow = sheet.header_rows - 1
|
118
|
-
ensure_shape
|
119
|
-
@data = @data.transpose.select{ |col| headers.include?( col[hrow] ) }
|
120
|
-
ensure_shape
|
121
|
-
@data = @data.sort_by{ |col| headers.index( col[hrow] ) || col[hrow] }.transpose
|
122
|
-
calc_dimensions
|
123
|
-
self
|
124
|
-
end
|
125
|
-
|
126
|
-
def uniq!( header )
|
127
|
-
column = col_index( colref_by_header( header ) )
|
128
|
-
@data = @data.uniq { |row| row[ column - 1 ] }
|
129
|
-
self
|
130
|
-
end
|
131
|
-
alias unique! uniq!
|
132
|
-
|
133
|
-
end
|
134
|
-
|
135
|
-
class Section
|
136
|
-
|
137
|
-
def <<( value )
|
138
|
-
if self.is_a? Row
|
139
|
-
lastone = ( col_index( idx ) == 1 ? data.cols + 1 : data.cols )
|
140
|
-
else
|
141
|
-
lastone = ( col_index( idx ) == 1 ? data.rows + 1 : data.rows )
|
142
|
-
end
|
143
|
-
data[ translate_address( lastone ) ] = value
|
144
|
-
end
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
class Row < Section
|
149
|
-
|
150
|
-
def getref( header )
|
151
|
-
column_id( sheet.row(1).find &/#{header}/ )
|
152
|
-
end
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
class Column < Section
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
class Element
|
161
|
-
|
162
|
-
end
|
163
|
-
|
164
|
-
end
|