spreadbase 0.1.2 → 0.3.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.
@@ -1,27 +1,6 @@
1
- # encoding: UTF-8
2
-
3
- =begin
4
- Copyright 2012 Saverio Miroddi saverio.pub2 <a-hat!> gmail.com
5
-
6
- This file is part of SpreadBase.
7
-
8
- SpreadBase is free software: you can redistribute it and/or modify it under the
9
- terms of the GNU Lesser General Public License as published by the Free Software
10
- Foundation, either version 3 of the License, or (at your option) any later
11
- version.
12
-
13
- SpreadBase is distributed in the hope that it will be useful, but WITHOUT ANY
14
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
-
17
- You should have received a copy of the GNU Lesser General Public License along
18
- with SpreadBase. If not, see <http://www.gnu.org/licenses/>.
19
- =end
20
-
21
1
  require 'rexml/document'
22
2
  require 'date'
23
3
  require 'bigdecimal'
24
- require 'iconv' if RUBY_VERSION < '1.9'
25
4
 
26
5
  module SpreadBase # :nodoc:
27
6
 
@@ -81,17 +60,17 @@ module SpreadBase # :nodoc:
81
60
 
82
61
  # Returns the XML root node
83
62
  #
84
- def encode_to_document_node( el_document, options={} )
85
- root_node = REXML::Document.new( BASE_CONTENT_XML )
86
- spreadsheet_node = root_node.elements[ '//office:document-content/office:body/office:spreadsheet' ]
87
- styles_node = root_node.elements[ '//office:document-content/office:automatic-styles' ]
63
+ def encode_to_document_node(el_document)
64
+ root_node = REXML::Document.new(BASE_CONTENT_XML)
65
+ spreadsheet_node = root_node.elements['//office:document-content/office:body/office:spreadsheet']
66
+ styles_node = root_node.elements['//office:document-content/office:automatic-styles']
88
67
 
89
68
  el_document.column_width_styles.each do | style_name, column_width |
90
- encode_style( styles_node, style_name, column_width )
69
+ encode_style(styles_node, style_name, column_width)
91
70
  end
92
71
 
93
72
  el_document.tables.each do | table |
94
- encode_table( table, spreadsheet_node, options )
73
+ encode_table(table, spreadsheet_node)
95
74
  end
96
75
 
97
76
  root_node
@@ -99,91 +78,83 @@ module SpreadBase # :nodoc:
99
78
 
100
79
  # Currently only encodes column width styles
101
80
  #
102
- def encode_style( styles_node, style_name, column_width )
103
- style_node = styles_node.add_element( 'style:style', 'style:name' => style_name, 'style:family' => 'table-column' )
81
+ def encode_style(styles_node, style_name, column_width)
82
+ style_node = styles_node.add_element('style:style', 'style:name' => style_name, 'style:family' => 'table-column')
104
83
 
105
- style_node.add_element( 'style:table-column-properties', 'style:column-width' => column_width )
84
+ style_node.add_element('style:table-column-properties', 'style:column-width' => column_width)
106
85
  end
107
86
 
108
- def encode_table( table, spreadsheet_node, options={} )
109
- table_node = spreadsheet_node.add_element( 'table:table' )
87
+ def encode_table(table, spreadsheet_node)
88
+ table_node = spreadsheet_node.add_element('table:table')
110
89
 
111
- table_node.attributes[ 'table:name' ] = table.name
90
+ table_node.attributes['table:name'] = table.name
112
91
 
113
92
  table.column_width_styles.each do | style_name |
114
- encode_column( table_node, style_name ) if style_name
93
+ encode_column(table_node, style_name) if style_name
115
94
  end
116
95
 
117
96
  # At least one column element is required
118
97
  #
119
- table_node.add_element( 'table:table-column' ) if table.column_width_styles.size == 0
98
+ table_node.add_element('table:table-column') if table.column_width_styles.size == 0
120
99
 
121
- table.data.each do | row |
122
- encode_row( row, table_node, options )
100
+ table.data(as_cell: true).each do | row |
101
+ encode_row(row, table_node)
123
102
  end
124
103
  end
125
104
 
126
105
  # Currently only encodes column width styles
127
106
  #
128
- def encode_column( table_node, style_name )
129
- table_node.add_element( 'table:table-column', 'table:style-name' => style_name )
107
+ def encode_column(table_node, style_name)
108
+ table_node.add_element('table:table-column', 'table:style-name' => style_name)
130
109
  end
131
110
 
132
- def encode_row( row, table_node, options={} )
133
- row_node = table_node.add_element( 'table:table-row' )
111
+ def encode_row(row, table_node)
112
+ row_node = table_node.add_element('table:table-row')
134
113
 
135
- row.each do | value |
136
- encode_cell( value, row_node, options )
114
+ row.each do | cell |
115
+ encode_cell(cell.value, row_node)
137
116
  end
138
117
  end
139
118
 
140
- def encode_cell( value, row_node, options={} )
141
- force_18_strings_encoding = options[ :force_18_strings_encoding ] || 'UTF-8'
142
-
143
- cell_node = row_node.add_element( 'table:table-cell' )
119
+ def encode_cell(value, row_node)
120
+ cell_node = row_node.add_element('table:table-cell')
144
121
 
145
122
  # WATCH OUT!!! DateTime.new.is_a?( Date )!!!
146
123
  #
147
124
  case value
148
125
  when String
149
- cell_node.attributes[ 'office:value-type' ] = 'string'
150
-
151
- cell_value_node = cell_node.add_element( 'text:p' )
126
+ cell_node.attributes['office:value-type'] = 'string'
152
127
 
153
- if RUBY_VERSION >= '1.9'
154
- value = value.encode( 'UTF-8' )
155
- else
156
- value = Iconv.conv( 'UTF-8', force_18_strings_encoding, value )
157
- end
128
+ cell_value_node = cell_node.add_element('text:p')
158
129
 
159
- cell_value_node.text = value
130
+ cell_value_node.text = value.encode('UTF-8')
160
131
  when Time, DateTime
161
- cell_node.attributes[ 'office:value-type' ] = 'date'
162
- cell_node.attributes[ 'table:style-name' ] = 'datetime'
132
+ cell_node.attributes['office:value-type'] = 'date'
133
+ cell_node.attributes['table:style-name'] = 'datetime'
163
134
 
164
- encoded_value = value.strftime( '%Y-%m-%dT%H:%M:%S' )
135
+ encoded_value = value.strftime('%Y-%m-%dT%H:%M:%S')
165
136
 
166
- cell_node.attributes[ 'office:date-value' ] = encoded_value
137
+ cell_node.attributes['office:date-value'] = encoded_value
167
138
  when Date
168
- cell_node.attributes[ 'office:value-type' ] = 'date'
169
- cell_node.attributes[ 'table:style-name' ] = 'date'
139
+ cell_node.attributes['office:value-type'] = 'date'
140
+ cell_node.attributes['table:style-name'] = 'date'
170
141
 
171
- encoded_value = value.strftime( '%Y-%m-%d' )
142
+ encoded_value = value.strftime('%Y-%m-%d')
172
143
 
173
- cell_node.attributes[ 'office:date-value' ] = encoded_value
144
+ cell_node.attributes['office:date-value'] = encoded_value
174
145
  when BigDecimal
175
- cell_node.attributes[ 'office:value-type' ] = 'float'
146
+ cell_node.attributes['office:value-type'] = 'float'
176
147
 
177
- cell_node.attributes[ 'office:value' ] = value.to_s( 'F' )
178
- when Float, Fixnum
179
- cell_node.attributes[ 'office:value-type' ] = 'float'
148
+ cell_node.attributes['office:value'] = value.to_s('F')
149
+ when Float, Integer
150
+ cell_node.attributes['office:value-type'] = 'float'
180
151
 
181
- cell_node.attributes[ 'office:value' ] = value.to_s
152
+ cell_node.attributes['office:value'] = value.to_s
182
153
  when true, false
183
- cell_node.attributes[ 'office:value-type' ] = 'boolean'
184
- cell_node.attributes[ 'table:style-name' ] = 'boolean'
154
+ cell_node.attributes['office:value-type'] = 'boolean'
155
+ cell_node.attributes['table:style-name'] = 'boolean'
185
156
 
186
- cell_node.attributes[ 'office:boolean-value' ] = value.to_s
157
+ cell_node.attributes['office:boolean-value'] = value.to_s
187
158
  when nil
188
159
  # do nothing
189
160
  else
@@ -1,23 +1,3 @@
1
- # encoding: UTF-8
2
-
3
- =begin
4
- Copyright 2012 Saverio Miroddi saverio.pub2 <a-hat!> gmail.com
5
-
6
- This file is part of SpreadBase.
7
-
8
- SpreadBase is free software: you can redistribute it and/or modify it under the
9
- terms of the GNU Lesser General Public License as published by the Free Software
10
- Foundation, either version 3 of the License, or (at your option) any later
11
- version.
12
-
13
- SpreadBase is distributed in the hope that it will be useful, but WITHOUT ANY
14
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
-
17
- You should have received a copy of the GNU Lesser General Public License along
18
- with SpreadBase. If not, see <http://www.gnu.org/licenses/>.
19
- =end
20
-
21
1
  module SpreadBase # :nodoc:
22
2
 
23
3
  # Represents the abstraction of a document, merging both the file and the
@@ -42,16 +22,15 @@ module SpreadBase # :nodoc:
42
22
  #
43
23
  # _options_:
44
24
  #
45
- # +force_18_strings_encoding+:: ('UTF-8') on ruby 1.8, when converting to UTF-8, assume the strings are using the specified format.
46
25
  # +floats_as_bigdecimal+:: (false) decode floats as BigDecimal instead of Float
47
26
  #
48
- def initialize( document_path=nil, options={} )
27
+ def initialize(document_path=nil, options={})
49
28
  @document_path = document_path
50
29
  @options = options.clone
51
30
 
52
- if @document_path && File.exists?( document_path )
53
- document_archive = IO.read( document_path )
54
- decoded_document = Codecs::OpenDocument12.new.decode_archive( document_archive, options )
31
+ if @document_path && File.exist?(document_path)
32
+ document_archive = IO.read(document_path)
33
+ decoded_document = Codecs::OpenDocument12.new.decode_archive(document_archive, options)
55
34
 
56
35
  @column_width_styles = decoded_document.column_width_styles
57
36
  @tables = decoded_document.tables
@@ -69,28 +48,28 @@ module SpreadBase # :nodoc:
69
48
  #
70
49
  # +prettify+:: Prettifies the content.xml file before saving.
71
50
  #
72
- def save( options={} )
73
- options = @options.merge( options )
51
+ def save(options={})
52
+ options = @options.merge(options)
74
53
 
75
54
  raise "At least one table must be present" if @tables.empty?
76
55
  raise "Document path not specified" if @document_path.nil?
77
56
 
78
- document_archive = Codecs::OpenDocument12.new.encode_to_archive( self, options )
57
+ document_archive = Codecs::OpenDocument12.new.encode_to_archive(self, options)
79
58
 
80
- File.open( @document_path, 'wb' ) { | file | file << document_archive }
59
+ File.open(@document_path, 'wb') { | file | file << document_archive }
81
60
  end
82
61
 
83
62
  # _options_:
84
63
  #
85
64
  # +with_headers+:: Print the tables with headers.
86
65
  #
87
- def to_s( options={} )
88
- options.merge!( :row_prefix => ' ' )
66
+ def to_s(options={})
67
+ options.merge!(row_prefix: ' ')
89
68
 
90
- tables.inject( '' ) do | output, table |
69
+ tables.inject('') do | output, table |
91
70
  output << "#{ table.name }:" << "\n" << "\n"
92
71
 
93
- output << table.to_s( options ) << "\n"
72
+ output << table.to_s(options) << "\n"
94
73
  end
95
74
  end
96
75
 
@@ -1,23 +1,3 @@
1
- # encoding: UTF-8
2
-
3
- =begin
4
- Copyright 2012 Saverio Miroddi saverio.pub2 <a-hat!> gmail.com
5
-
6
- This file is part of SpreadBase.
7
-
8
- SpreadBase is free software: you can redistribute it and/or modify it under the
9
- terms of the GNU Lesser General Public License as published by the Free Software
10
- Foundation, either version 3 of the License, or (at your option) any later
11
- version.
12
-
13
- SpreadBase is distributed in the hope that it will be useful, but WITHOUT ANY
14
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
-
17
- You should have received a copy of the GNU Lesser General Public License along
18
- with SpreadBase. If not, see <http://www.gnu.org/licenses/>.
19
- =end
20
-
21
1
  module SpreadBase # :nodoc:
22
2
 
23
3
  # Currently generic helper class
@@ -28,10 +8,10 @@ module SpreadBase # :nodoc:
28
8
  #
29
9
  # The instance is duplicated Object#clone, when necessary - note that this method is not meant to do a deep copy.
30
10
  #
31
- def make_array_from_repetitions( instance, repetitions )
32
- ( 1 .. repetitions ).inject( [] ) do | cumulative_result, i |
11
+ def make_array_from_repetitions(instance, repetitions)
12
+ (1..repetitions).inject([]) do | cumulative_result, i |
33
13
  case instance
34
- when Fixnum, Float, BigDecimal, Date, Time, TrueClass, FalseClass, NilClass #, DateTime is a Date
14
+ when Integer, Float, BigDecimal, Date, Time, TrueClass, FalseClass, NilClass #, DateTime is a Date
35
15
  cumulative_result << instance
36
16
  when String, Array
37
17
  cumulative_result << instance.clone
@@ -54,54 +34,49 @@ module SpreadBase # :nodoc:
54
34
  # +row_prefix+:: Prefix this string to each row.
55
35
  # +with_header+:: First row will be separated from the remaining ones.
56
36
  #
57
- # +formatting_block+:: If passed, values will be formatted by the block.
58
- # If no block is passed, or it returns nil or :standard, the standard formatting is used.
59
- #
60
- def pretty_print_rows( rows, options={}, &formatting_block )
61
- row_prefix = options[ :row_prefix ] || ''
62
- with_headers = options[ :with_headers ]
37
+ def pretty_print_rows(rows, options={})
38
+ row_prefix = options[:row_prefix] || ''
39
+ with_headers = options[:with_headers]
63
40
 
64
41
  output = ""
65
42
 
66
- formatting_block = lambda { | value | value.to_s } if ! block_given?
67
-
68
43
  if rows.size > 0
69
- max_column_sizes = [ 0 ] * rows.map( &:size ).max
44
+ max_column_sizes = [0] * rows.map(&:size).max
70
45
 
71
46
  # Compute maximum widths
72
47
 
73
48
  rows.each do | values |
74
49
  values.each_with_index do | value, i |
75
- formatted_value = pretty_print_value( value, &formatting_block )
50
+ formatted_value = pretty_print_value(value)
76
51
  formatted_value_width = formatted_value.chars.to_a.size
77
52
 
78
- max_column_sizes[ i ] = formatted_value_width if formatted_value_width > max_column_sizes[ i ]
53
+ max_column_sizes[i] = formatted_value_width if formatted_value_width > max_column_sizes[i]
79
54
  end
80
55
  end
81
56
 
82
57
  # Print!
83
58
 
84
- output << row_prefix << '+-' + max_column_sizes.map { | size | '-' * size }.join( '-+-' ) + '-+' << "\n"
59
+ output << row_prefix << '+-' + max_column_sizes.map { | size | '-' * size }.join('-+-') + '-+' << "\n"
85
60
 
86
- print_pattern = '| ' + max_column_sizes.map { | size | "%-#{ size }s" }.join( ' | ' ) + ' |'
61
+ print_pattern = '| ' + max_column_sizes.map { | size | "%-#{ size }s" }.join(' | ') + ' |'
87
62
 
88
63
  rows.each_with_index do | row, row_index |
89
64
  # Ensure that we always have a number of values equal to the max width
90
65
  #
91
- formatted_row_values = ( 0 ... max_column_sizes.size ).map do | column_index |
92
- value = row[ column_index ]
66
+ formatted_row_values = (0...max_column_sizes.size).map do | column_index |
67
+ value = row[column_index]
93
68
 
94
- pretty_print_value( value, &formatting_block )
69
+ pretty_print_value(value)
95
70
  end
96
71
 
97
72
  output << row_prefix << print_pattern % formatted_row_values << "\n"
98
73
 
99
74
  if with_headers && row_index == 0
100
- output << row_prefix << '+-' + max_column_sizes.map { | size | '-' * size }.join( '-+-' ) + '-+' << "\n"
75
+ output << row_prefix << '+-' + max_column_sizes.map { | size | '-' * size }.join('-+-') + '-+' << "\n"
101
76
  end
102
77
  end
103
78
 
104
- output << row_prefix << '+-' + max_column_sizes.map { | size | '-' * size }.join( '-+-' ) + '-+' << "\n"
79
+ output << row_prefix << '+-' + max_column_sizes.map { | size | '-' * size }.join('-+-') + '-+' << "\n"
105
80
  end
106
81
 
107
82
  output
@@ -109,27 +84,18 @@ module SpreadBase # :nodoc:
109
84
 
110
85
  private
111
86
 
112
- def pretty_print_value( value, &formatting_block )
113
- custom_result = block_given? && yield( value )
114
-
115
- if custom_result && custom_result != :standard
116
- custom_result
87
+ def pretty_print_value(value)
88
+ case value
89
+ when BigDecimal
90
+ value.to_s('F')
91
+ when Time, DateTime
92
+ value.strftime('%Y-%m-%d %H:%M:%S %z')
93
+ when String, Date, Numeric, TrueClass, FalseClass
94
+ value.to_s
95
+ when nil
96
+ "NIL"
117
97
  else
118
- case value
119
- when BigDecimal
120
- value.to_s( 'F' )
121
- when Time, DateTime
122
- # Time#to_s renders differently between 1.8.7 and 1.9.3; 1.8.7's rendering is bizarrely
123
- # inconsistent with the Date and DateTime ones.
124
- #
125
- value.strftime( '%Y-%m-%d %H:%M:%S %z' )
126
- when String, Date, Numeric, TrueClass, FalseClass
127
- value.to_s
128
- when nil
129
- "NIL"
130
- else
131
- value.inspect
132
- end
98
+ value.inspect
133
99
  end
134
100
  end
135
101
 
@@ -1,23 +1,3 @@
1
- # encoding: UTF-8
2
-
3
- =begin
4
- Copyright 2012 Saverio Miroddi saverio.pub2 <a-hat!> gmail.com
5
-
6
- This file is part of SpreadBase.
7
-
8
- SpreadBase is free software: you can redistribute it and/or modify it under the
9
- terms of the GNU Lesser General Public License as published by the Free Software
10
- Foundation, either version 3 of the License, or (at your option) any later
11
- version.
12
-
13
- SpreadBase is distributed in the hope that it will be useful, but WITHOUT ANY
14
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
15
- PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
16
-
17
- You should have received a copy of the GNU Lesser General Public License along
18
- with SpreadBase. If not, see <http://www.gnu.org/licenses/>.
19
- =end
20
-
21
1
  module SpreadBase # :nodoc:
22
2
 
23
3
  # Represents the abstraction of a table and its contents.
@@ -32,7 +12,7 @@ module SpreadBase # :nodoc:
32
12
 
33
13
  include SpreadBase::Helpers
34
14
 
35
- attr_accessor :name, :data
15
+ attr_accessor :name
36
16
 
37
17
  # Array of style names; nil when not associated to any column width.
38
18
  #
@@ -41,16 +21,24 @@ module SpreadBase # :nodoc:
41
21
  # _params_:
42
22
  #
43
23
  # +name+:: (required) Name of the table
44
- # +data+:: (Array.new) 2d matrix of the data. if not empty, the rows need to be all of the same size
24
+ # +raw_data+:: (Array.new) 2d matrix of the data. if not empty, the rows need to be all of the same size
45
25
  #
46
- def initialize( name, data=[] )
26
+ def initialize(name, raw_data=[])
47
27
  raise "Table name required" if name.nil? || name == ''
48
28
 
49
29
  @name = name
50
- @data = data
30
+ self.data = raw_data
51
31
  @column_width_styles = []
52
32
  end
53
33
 
34
+ def data=(the_data)
35
+ @data = the_data.map { | the_row | array_to_cells(the_row) }
36
+ end
37
+
38
+ def data(options={})
39
+ @data.map { | the_row | the_row.map { | cell | cell_to_value(cell, options) } }
40
+ end
41
+
54
42
  # Access a cell value.
55
43
  #
56
44
  # _params_:
@@ -60,13 +48,14 @@ module SpreadBase # :nodoc:
60
48
  #
61
49
  # _returns_ the value, which is automatically converted to the Ruby data type.
62
50
  #
63
- def []( column_identifier, row_index )
64
- row = row( row_index )
65
- column_index = decode_column_identifier( column_identifier )
51
+ def [](column_identifier, row_index, options={})
52
+ the_row = row(row_index, options)
66
53
 
67
- check_column_index( row, column_index )
54
+ column_index = decode_column_identifier(column_identifier)
68
55
 
69
- row[ column_index ]
56
+ check_column_index(the_row, column_index)
57
+
58
+ the_row[column_index]
70
59
  end
71
60
 
72
61
  # Writes a value in a cell.
@@ -77,25 +66,31 @@ module SpreadBase # :nodoc:
77
66
  # +row_index+:: int (0-based). see notes about the rows indexing.
78
67
  # +value+:: value
79
68
  #
80
- def []=( column_identifier, row_index, value )
81
- row = row( row_index )
82
- column_index = decode_column_identifier( column_identifier )
69
+ def []=(column_identifier, row_index, value)
70
+ check_row_index(row_index)
71
+
72
+ the_row = @data[row_index]
73
+ column_index = decode_column_identifier(column_identifier)
83
74
 
84
- check_column_index( row, column_index )
75
+ check_column_index(the_row, column_index)
85
76
 
86
- row[ column_index ] = value
77
+ the_row[column_index] = value_to_cell(value)
87
78
  end
88
79
 
89
80
  # Returns an array containing the values of a single row.
90
81
  #
91
82
  # _params_:
92
83
  #
93
- # +row_index+:: int (0-based). see notes about the rows indexing.
84
+ # +row_index+:: int or range (0-based). see notes about the rows indexing.
94
85
  #
95
- def row( row_index )
96
- check_row_index( row_index )
86
+ def row(row_index, options={})
87
+ check_row_index(row_index)
97
88
 
98
- @data[ row_index ]
89
+ if row_index.is_a?(Range)
90
+ @data[row_index].map { | row | cells_to_array(row, options) }
91
+ else
92
+ cells_to_array(@data[row_index], options)
93
+ end
99
94
  end
100
95
 
101
96
  # Deletes a row.
@@ -104,14 +99,20 @@ module SpreadBase # :nodoc:
104
99
  #
105
100
  # _params_:
106
101
  #
107
- # +row_index+:: int (0-based). see notes about the rows indexing.
102
+ # +row_index+:: int or range (0-based). see notes about the rows indexing.
108
103
  #
109
- # _returns_ the deleted row
104
+ # _returns_ the deleted row[s]
110
105
  #
111
- def delete_row( row_index )
112
- check_row_index( row_index )
106
+ def delete_row(row_index)
107
+ check_row_index(row_index)
113
108
 
114
- @data.slice!( row_index )
109
+ deleted_cells = @data.slice!(row_index)
110
+
111
+ if row_index.is_a?(Range)
112
+ deleted_cells.map { | row | cells_to_array(row) }
113
+ else
114
+ cells_to_array(deleted_cells)
115
+ end
115
116
  end
116
117
 
117
118
  # Inserts a row.
@@ -123,16 +124,18 @@ module SpreadBase # :nodoc:
123
124
  # +row_index+:: int (0-based). must be between 0 and (including) the table rows size.
124
125
  # +row+:: array of values. if the table is not empty, must have the same size of the table width.
125
126
  #
126
- def insert_row( row_index, row )
127
- check_row_index( row_index, :allow_append => true )
127
+ def insert_row(row_index, row)
128
+ check_row_index(row_index, allow_append: true)
128
129
 
129
- @data.insert( row_index, row )
130
+ cells = array_to_cells(row)
131
+
132
+ @data.insert(row_index, cells)
130
133
  end
131
134
 
132
135
  # This operation won't modify the column width styles in any case.
133
136
  #
134
- def append_row( row )
135
- insert_row( @data.size, row )
137
+ def append_row(row)
138
+ insert_row(@data.size, row)
136
139
  end
137
140
 
138
141
  # Returns an array containing the values of a single column.
@@ -141,35 +144,68 @@ module SpreadBase # :nodoc:
141
144
  #
142
145
  # _params_:
143
146
  #
144
- # +column_indentifier+:: either an int (0-based) or the excel-format identifier (AA...).
147
+ # +column_indentifier+:: for single access, us either an int (0-based) or the excel-format identifier (AA...).
145
148
  # when int, follow the same idea of the rows indexing (ruby semantics).
149
+ # for multiple access, use a range either of int or excel-format identifiers - pay attention, because ( 'A'..'c' ) is not semantically correct.
150
+ # interestingly, ruby letter ranges convention is the same as the excel columns one.
146
151
  #
147
- def column( column_identifier )
148
- column_index = decode_column_identifier( column_identifier )
152
+ def column(column_identifier, options={})
153
+ if column_identifier.is_a?(Range)
154
+ min_index = decode_column_identifier(column_identifier.min)
155
+ max_index = decode_column_identifier(column_identifier.max)
156
+
157
+ (min_index..max_index).map do | column_index |
158
+ @data.map do | the_row |
159
+ cell = the_row[column_index]
160
+
161
+ cell_to_value(cell, options)
162
+ end
163
+ end
164
+ else
165
+ column_index = decode_column_identifier(column_identifier)
149
166
 
150
- @data.map do | row |
151
- row[ column_index ]
167
+ @data.map do | the_row |
168
+ cell = the_row[column_index]
169
+
170
+ cell_to_value(cell, options)
171
+ end
152
172
  end
153
173
  end
154
174
 
155
175
  # Deletes a column.
156
176
  #
157
- # WATCH OUT! This method doesn't have the range restrictions that axis indexes generally has, that is, it's possible to delete a column outside the boundaries of the rows - it will return nil for each of those values.
177
+ # See Table#column for the indexing notes.
158
178
  #
159
179
  # _params_:
160
180
  #
161
- # +column_indentifier+:: either an int (0-based) or the excel-format identifier (AA...).
162
- # when int, follow the same idea of the rows indexing (ruby semantics).
181
+ # +column_indentifier+:: See Table#column
163
182
  #
164
183
  # _returns_ the deleted column
165
184
  #
166
- def delete_column( column_identifier )
167
- column_index = decode_column_identifier( column_identifier )
185
+ def delete_column(column_identifier)
186
+ if column_identifier.is_a?(Range)
187
+ min_index = decode_column_identifier(column_identifier.min)
188
+ max_index = decode_column_identifier(column_identifier.max)
168
189
 
169
- @column_width_styles.slice!( column_index )
190
+ reverse_result = max_index.downto(min_index).map do | column_index |
191
+ @data.map do | row |
192
+ cell = row.slice!(column_index)
170
193
 
171
- @data.map do | row |
172
- row.slice!( column_index )
194
+ cell_to_value(cell)
195
+ end
196
+ end
197
+
198
+ reverse_result.reverse
199
+ else
200
+ column_index = decode_column_identifier(column_identifier)
201
+
202
+ @column_width_styles.slice!(column_index)
203
+
204
+ @data.map do | row |
205
+ cell = row.slice!(column_index)
206
+
207
+ cell_to_value(cell)
208
+ end
173
209
  end
174
210
  end
175
211
 
@@ -183,37 +219,63 @@ module SpreadBase # :nodoc:
183
219
  # when int, follow the same idea of the rows indexing (ruby semantics).
184
220
  # +column+:: array of values. if the table is not empty, it must have the same size of the table height.
185
221
  #
186
- def insert_column( column_identifier, column )
222
+ def insert_column(column_identifier, column)
187
223
  raise "Inserting column size (#{ column.size }) different than existing columns size (#{ @data.size })" if @data.size > 0 && column.size != @data.size
188
224
 
189
- column_index = decode_column_identifier( column_identifier )
225
+ column_index = decode_column_identifier(column_identifier)
190
226
 
191
- @column_width_styles.insert( column_index, nil )
227
+ @column_width_styles.insert(column_index, nil)
192
228
 
193
229
  if @data.size > 0
194
- @data.zip( column ).each do | row, value |
195
- row.insert( column_index, value )
230
+ @data.zip(column).each do | row, value |
231
+ cell = value_to_cell(value)
232
+
233
+ row.insert(column_index, cell)
196
234
  end
197
235
  else
198
- @data = column.map { | value | [ value ] }
236
+ @data = column.map do | value |
237
+ [value_to_cell(value)]
238
+ end
199
239
  end
200
240
 
201
241
  end
202
242
 
203
- def append_column( column )
243
+ def append_column(column)
204
244
  column_index = @data.size > 0 ? @data.first.size : 0
205
245
 
206
- insert_column( column_index, column )
246
+ insert_column(column_index, column)
207
247
  end
208
248
 
209
249
  # _returns_ a matrix representation of the tables, with the values being separated by commas.
210
250
  #
211
- def to_s( options={} )
212
- pretty_print_rows( @data, options )
251
+ def to_s(options={})
252
+ pretty_print_rows(data, options)
213
253
  end
214
254
 
215
255
  private
216
256
 
257
+ def array_to_cells(the_row)
258
+ the_row.map { | value | value_to_cell(value) }
259
+ end
260
+
261
+ def value_to_cell(value)
262
+ value.is_a?(Cell) ? value : Cell.new(value)
263
+ end
264
+
265
+ def cells_to_array(cells, options={})
266
+ cells.map { | cell | cell_to_value(cell, options) }
267
+ end
268
+
269
+ def cell_to_value(cell, options={})
270
+ as_cell = options[:as_cell]
271
+
272
+ if as_cell
273
+ cell
274
+ else
275
+ cell.value if cell
276
+ end
277
+ end
278
+
217
279
  # Check that row index points to an existing record, or, in case of :allow_append,
218
280
  # point to one unit above the last row.
219
281
  #
@@ -221,15 +283,17 @@ module SpreadBase # :nodoc:
221
283
  #
222
284
  # +allow_append+:: Allow pointing to one unit above the last row.
223
285
  #
224
- def check_row_index( row_index, options={} )
225
- allow_append = options [ :allow_append ]
286
+ def check_row_index(row_index, options={})
287
+ allow_append = options [:allow_append]
226
288
 
227
289
  positive_limit = allow_append ? @data.size : @data.size - 1
228
290
 
291
+ row_index = row_index.max if row_index.is_a?(Range)
292
+
229
293
  raise "Invalid row index (#{ row_index }) - allowed 0 to #{ positive_limit }" if row_index < 0 || row_index > positive_limit
230
294
  end
231
295
 
232
- def check_column_index( row, column_index )
296
+ def check_column_index(row, column_index)
233
297
  raise "Invalid column index (#{ column_index }) for the given row - allowed 0 to #{ row.size - 1 }" if column_index >= row.size
234
298
  end
235
299
 
@@ -238,12 +302,9 @@ module SpreadBase # :nodoc:
238
302
  # Raises an error for invalid identifiers/indexes.
239
303
  #
240
304
  # _returns_ a 0-based decimal number.
241
- #--
242
- # Motherf#### base-26 bijective numeration - I would have gladly saved my f* time. At least
243
- # there were a few cute ladies at the Charleston lesson.
244
305
  #
245
- def decode_column_identifier( column_identifier )
246
- if column_identifier.is_a?( Fixnum )
306
+ def decode_column_identifier(column_identifier)
307
+ if column_identifier.is_a?(Integer)
247
308
  raise "Negative column indexes not allowed: #{ column_identifier }" if column_identifier < 0
248
309
 
249
310
  column_identifier
@@ -253,9 +314,9 @@ module SpreadBase # :nodoc:
253
314
 
254
315
  raise "Invalid letter for in column identifier (allowed 'a/A' to 'z/Z')" if letters.any? { | letter | letter < 'A' || letter > 'Z' }
255
316
 
256
- base_10_value = letters.inject( 0 ) do | sum, letter |
257
- letter_ord = letter.unpack( 'C' ).first
258
- sum * 26 + ( letter_ord - upcase_a_ord + 1 )
317
+ base_10_value = letters.inject(0) do | sum, letter |
318
+ letter_ord = letter.unpack('C').first
319
+ sum * 26 + (letter_ord - upcase_a_ord + 1)
259
320
  end
260
321
 
261
322
  base_10_value -= 1