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.
- 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
|