spreadbase 0.1.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +25 -0
- data/.gitignore +3 -2
- data/.rspec +1 -0
- data/Gemfile +7 -0
- data/LICENSE +674 -0
- data/README.md +34 -18
- data/Rakefile +5 -0
- data/docs/STRUCTURE.md +3 -0
- data/docs/TESTING.md +11 -0
- data/lib/spreadbase.rb +7 -28
- data/lib/spreadbase/cell.rb +19 -0
- data/lib/spreadbase/codecs/open_document_12.rb +19 -44
- data/lib/spreadbase/codecs/open_document_12_modules/decoding.rb +99 -94
- data/lib/spreadbase/codecs/open_document_12_modules/encoding.rb +43 -72
- data/lib/spreadbase/document.rb +12 -33
- data/lib/spreadbase/helpers/helpers.rb +27 -61
- data/lib/spreadbase/table.rb +143 -82
- data/lib/spreadbase/version.rb +1 -3
- data/spec/codecs/open_document_12_spec.rb +128 -64
- data/spec/elements/document_spec.rb +56 -55
- data/spec/elements/table_spec.rb +202 -143
- data/spec/fixtures/test.ods +0 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/spec_helpers.rb +10 -30
- data/spreadbase.gemspec +15 -10
- data/utils/convert_sqlite_to_ods.rb +30 -49
- data/utils/test_ods_folder.rb +7 -26
- data/utils/test_recoding_file.rb +11 -27
- data/utils/test_recoding_from_content.rb +10 -29
- data/utils/utils_helpers.rb +19 -33
- metadata +53 -27
- data/COPYING.LESSER +0 -165
- data/utils/prettify_file.rb +0 -46
@@ -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(
|
85
|
-
root_node = REXML::Document.new(
|
86
|
-
spreadsheet_node = root_node.elements[
|
87
|
-
styles_node = root_node.elements[
|
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(
|
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(
|
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(
|
103
|
-
style_node = styles_node.add_element(
|
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(
|
84
|
+
style_node.add_element('style:table-column-properties', 'style:column-width' => column_width)
|
106
85
|
end
|
107
86
|
|
108
|
-
def encode_table(
|
109
|
-
table_node = spreadsheet_node.add_element(
|
87
|
+
def encode_table(table, spreadsheet_node)
|
88
|
+
table_node = spreadsheet_node.add_element('table:table')
|
110
89
|
|
111
|
-
table_node.attributes[
|
90
|
+
table_node.attributes['table:name'] = table.name
|
112
91
|
|
113
92
|
table.column_width_styles.each do | style_name |
|
114
|
-
encode_column(
|
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(
|
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(
|
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(
|
129
|
-
table_node.add_element(
|
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(
|
133
|
-
row_node = table_node.add_element(
|
111
|
+
def encode_row(row, table_node)
|
112
|
+
row_node = table_node.add_element('table:table-row')
|
134
113
|
|
135
|
-
row.each do |
|
136
|
-
encode_cell(
|
114
|
+
row.each do | cell |
|
115
|
+
encode_cell(cell.value, row_node)
|
137
116
|
end
|
138
117
|
end
|
139
118
|
|
140
|
-
def encode_cell(
|
141
|
-
|
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[
|
150
|
-
|
151
|
-
cell_value_node = cell_node.add_element( 'text:p' )
|
126
|
+
cell_node.attributes['office:value-type'] = 'string'
|
152
127
|
|
153
|
-
|
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[
|
162
|
-
cell_node.attributes[
|
132
|
+
cell_node.attributes['office:value-type'] = 'date'
|
133
|
+
cell_node.attributes['table:style-name'] = 'datetime'
|
163
134
|
|
164
|
-
encoded_value = value.strftime(
|
135
|
+
encoded_value = value.strftime('%Y-%m-%dT%H:%M:%S')
|
165
136
|
|
166
|
-
cell_node.attributes[
|
137
|
+
cell_node.attributes['office:date-value'] = encoded_value
|
167
138
|
when Date
|
168
|
-
cell_node.attributes[
|
169
|
-
cell_node.attributes[
|
139
|
+
cell_node.attributes['office:value-type'] = 'date'
|
140
|
+
cell_node.attributes['table:style-name'] = 'date'
|
170
141
|
|
171
|
-
encoded_value = value.strftime(
|
142
|
+
encoded_value = value.strftime('%Y-%m-%d')
|
172
143
|
|
173
|
-
cell_node.attributes[
|
144
|
+
cell_node.attributes['office:date-value'] = encoded_value
|
174
145
|
when BigDecimal
|
175
|
-
cell_node.attributes[
|
146
|
+
cell_node.attributes['office:value-type'] = 'float'
|
176
147
|
|
177
|
-
cell_node.attributes[
|
178
|
-
when Float,
|
179
|
-
cell_node.attributes[
|
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[
|
152
|
+
cell_node.attributes['office:value'] = value.to_s
|
182
153
|
when true, false
|
183
|
-
cell_node.attributes[
|
184
|
-
cell_node.attributes[
|
154
|
+
cell_node.attributes['office:value-type'] = 'boolean'
|
155
|
+
cell_node.attributes['table:style-name'] = 'boolean'
|
185
156
|
|
186
|
-
cell_node.attributes[
|
157
|
+
cell_node.attributes['office:boolean-value'] = value.to_s
|
187
158
|
when nil
|
188
159
|
# do nothing
|
189
160
|
else
|
data/lib/spreadbase/document.rb
CHANGED
@@ -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(
|
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.
|
53
|
-
document_archive = IO.read(
|
54
|
-
decoded_document = Codecs::OpenDocument12.new.decode_archive(
|
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(
|
73
|
-
options = @options.merge(
|
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(
|
57
|
+
document_archive = Codecs::OpenDocument12.new.encode_to_archive(self, options)
|
79
58
|
|
80
|
-
File.open(
|
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(
|
88
|
-
options.merge!(
|
66
|
+
def to_s(options={})
|
67
|
+
options.merge!(row_prefix: ' ')
|
89
68
|
|
90
|
-
tables.inject(
|
69
|
+
tables.inject('') do | output, table |
|
91
70
|
output << "#{ table.name }:" << "\n" << "\n"
|
92
71
|
|
93
|
-
output << table.to_s(
|
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(
|
32
|
-
(
|
11
|
+
def make_array_from_repetitions(instance, repetitions)
|
12
|
+
(1..repetitions).inject([]) do | cumulative_result, i |
|
33
13
|
case instance
|
34
|
-
when
|
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
|
-
|
58
|
-
|
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 = [
|
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(
|
50
|
+
formatted_value = pretty_print_value(value)
|
76
51
|
formatted_value_width = formatted_value.chars.to_a.size
|
77
52
|
|
78
|
-
max_column_sizes[
|
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(
|
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 = (
|
92
|
-
value = row[
|
66
|
+
formatted_row_values = (0...max_column_sizes.size).map do | column_index |
|
67
|
+
value = row[column_index]
|
93
68
|
|
94
|
-
pretty_print_value(
|
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(
|
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(
|
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(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
|
data/lib/spreadbase/table.rb
CHANGED
@@ -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
|
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
|
-
# +
|
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(
|
26
|
+
def initialize(name, raw_data=[])
|
47
27
|
raise "Table name required" if name.nil? || name == ''
|
48
28
|
|
49
29
|
@name = name
|
50
|
-
|
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 [](
|
64
|
-
|
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
|
-
|
54
|
+
column_index = decode_column_identifier(column_identifier)
|
68
55
|
|
69
|
-
|
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 []=(
|
81
|
-
|
82
|
-
|
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(
|
75
|
+
check_column_index(the_row, column_index)
|
85
76
|
|
86
|
-
|
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(
|
96
|
-
check_row_index(
|
86
|
+
def row(row_index, options={})
|
87
|
+
check_row_index(row_index)
|
97
88
|
|
98
|
-
|
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(
|
112
|
-
check_row_index(
|
106
|
+
def delete_row(row_index)
|
107
|
+
check_row_index(row_index)
|
113
108
|
|
114
|
-
@data.slice!(
|
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(
|
127
|
-
check_row_index(
|
127
|
+
def insert_row(row_index, row)
|
128
|
+
check_row_index(row_index, allow_append: true)
|
128
129
|
|
129
|
-
|
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(
|
135
|
-
insert_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(
|
148
|
-
|
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
|
-
|
151
|
-
|
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
|
-
#
|
177
|
+
# See Table#column for the indexing notes.
|
158
178
|
#
|
159
179
|
# _params_:
|
160
180
|
#
|
161
|
-
# +column_indentifier+::
|
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(
|
167
|
-
|
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
|
-
|
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
|
-
|
172
|
-
|
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(
|
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(
|
225
|
+
column_index = decode_column_identifier(column_identifier)
|
190
226
|
|
191
|
-
@column_width_styles.insert(
|
227
|
+
@column_width_styles.insert(column_index, nil)
|
192
228
|
|
193
229
|
if @data.size > 0
|
194
|
-
@data.zip(
|
195
|
-
|
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
|
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(
|
243
|
+
def append_column(column)
|
204
244
|
column_index = @data.size > 0 ? @data.first.size : 0
|
205
245
|
|
206
|
-
insert_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(
|
212
|
-
pretty_print_rows(
|
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(
|
225
|
-
allow_append = options [
|
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(
|
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(
|
246
|
-
if column_identifier.is_a?(
|
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(
|
257
|
-
letter_ord = letter.unpack(
|
258
|
-
sum * 26 + (
|
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
|