rspreadsheet 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/DEVEL_BLOG.md +10 -6
- data/README.md +6 -0
- data/lib/rspreadsheet/.row.rb.kate-swp.bak +0 -0
- data/lib/rspreadsheet/cell.rb +139 -105
- data/lib/rspreadsheet/row.rb +235 -87
- data/lib/rspreadsheet/tools.rb +31 -7
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/worksheet.rb +117 -7
- data/reinstall.sh +2 -1
- data/rspreadsheet.gemspec +1 -1
- data/spec/cell_spec.rb +34 -8
- data/spec/row_spec.rb +50 -27
- data/spec/rspreadsheet_spec.rb +1 -13
- data/spec/testfile1.ods +0 -0
- data/spec/tools_spec.rb +25 -0
- data/spec/workbook_spec.rb +22 -4
- data/spec/worksheet_spec.rb +13 -11
- data/spec/xml_tied_array_spec.rb +9 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a45451dc3599fbe6519acf7c0d27bb277743a782
|
4
|
+
data.tar.gz: 25b449a6d2efd44fcbf5396275bd0ebb22dbc85f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6ed38a381b781d6b543285a783fc54346a70f61d6c99c4e1c7e18ea5e1ab3674ec0fc27cc8af647298fcbc5cc2bc66c58dda0af127967eec2db9647894e46d7
|
7
|
+
data.tar.gz: 341cce44687eeb226acc0b27df38b00951d5d2067a28898962cf9aaee4216d1a5318cb3b54784a8661a4083b5f9ec8d9660701c1656a96666a9fc42cf14a793a
|
data/DEVEL_BLOG.md
CHANGED
@@ -10,6 +10,8 @@ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
|
|
10
10
|
* Allow any of these:
|
11
11
|
* ``book['Spring 2014']`` as alternative to ``book.worksheets('Spring 2014')`` ?
|
12
12
|
* ``sheet.cells.F13`` as alternative to ``sheet.cells[14,5]`` ?
|
13
|
+
* We may have problem with the @parent_row concept in cells. If cell has repeated row as parent and it is detached in some other code, the cell never finds out that its parent_row was changed. A cure to this might be, when there are no "unatacched" cells, and when all cells belong to some row. This way if row detaches it can let know to all its cells.
|
14
|
+
* Document that there is a little distinction betwean RSpreadsheet and RSpreadsheet::Workbook. The former delegates everythink to the other.
|
13
15
|
|
14
16
|
Guiding ideas
|
15
17
|
* xml document is always synchronized with the data. So the save is trivial.
|
@@ -31,15 +33,17 @@ Guiding ideas
|
|
31
33
|
|
32
34
|
### Local manual testing and releasing (to github released, ).
|
33
35
|
|
34
|
-
gem build rspreadsheet.gemspec
|
35
|
-
sudo gem install rspreadsheet-x.y.z.gem
|
36
|
-
gem push rspreadsheet-x.y.z.gem
|
36
|
+
gem build rspreadsheet.gemspec -\ These two lines together are in install.sh
|
37
|
+
sudo gem install rspreadsheet-x.y.z.gem -/ which should be invoked from parent directory
|
38
|
+
gem push rspreadsheet-x.y.z.gem releases the gem, do not forgot to update version in rspreadsheet.gemspec before doing this
|
37
39
|
|
38
40
|
alternative way using ``rake`` command - release is more automatic
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
1. build and install locally
|
43
|
+
* ``rake build`` - builds the gem to pkg directory.
|
44
|
+
* ``sudo rake install`` - If this fails with "mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h" you may want to ``sudo aptitude install ruby-dev``
|
45
|
+
* Now can locally and manually use/test the gem. This should not be replacement for automated testing. If you make some changes, repeat step 1.
|
46
|
+
* When happy, increment the version number and deploy in next step.
|
43
47
|
* ``rake release`` - creates a version tag in git and pushes the code to github + Rubygems. After this is succesfull the new version appears as release in Github and RubyGems.
|
44
48
|
|
45
49
|
|
data/README.md
CHANGED
@@ -53,6 +53,12 @@ Or install it yourself as:
|
|
53
53
|
|
54
54
|
gem is hosted on Rubygems - https://rubygems.org/gems/rspreadsheet
|
55
55
|
|
56
|
+
If you get this or similar error
|
57
|
+
|
58
|
+
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h
|
59
|
+
|
60
|
+
then you might not have installed libxml for ruby. I.e. in debian something like <code>sudo aptitude install ruby-libxml</code> or using equivalent command in other package managers.
|
61
|
+
|
56
62
|
## Motivation and Ideas
|
57
63
|
|
58
64
|
This project arised from the necessity. Alhought it is not true that there are no ruby gems allowing to acess OpenDOcument spreadsheet, I did not find another decent one which would suit my needs. Most of them also look abandoned and inactive. I have investigated these options:
|
Binary file
|
data/lib/rspreadsheet/cell.rb
CHANGED
@@ -2,110 +2,44 @@ require 'andand'
|
|
2
2
|
|
3
3
|
module Rspreadsheet
|
4
4
|
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class RowOrNode
|
6
|
+
def mode
|
7
|
+
case
|
8
|
+
when xmlnode.nil? then :outbound
|
9
|
+
when repeated>1 then :repeated
|
10
|
+
else :regular
|
11
|
+
end
|
11
12
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@
|
20
|
-
@col = coli
|
13
|
+
end
|
14
|
+
|
15
|
+
class Cell < RowOrNode
|
16
|
+
attr_accessor :worksheet, :rowi, :coli
|
17
|
+
def initialize(aworksheet,arowi,acoli)
|
18
|
+
@worksheet = aworksheet
|
19
|
+
@rowi = arowi
|
20
|
+
@coli = acoli
|
21
21
|
end
|
22
|
-
def
|
23
|
-
def
|
24
|
-
|
25
|
-
def
|
26
|
-
def cell_xml; self.xmlnode.to_s; end
|
27
|
-
def xml; self.xmlnode.children.first.andand.inner_xml end
|
28
|
-
def coordinates; [row,col]; end
|
29
|
-
def row; @parent_row.row; end
|
30
|
-
def worksheet; @parent_row.worksheet; end
|
22
|
+
def row; @worksheet.rows(rowi) end
|
23
|
+
def coordinates; [rowi,coli] end
|
24
|
+
|
25
|
+
def xmlnode; @worksheet.cellxmlnode(@rowi,@coli) end
|
31
26
|
def value
|
32
27
|
gt = guess_cell_type
|
33
|
-
if (
|
28
|
+
if (self.mode == :regular) or (self.mode == :repeated)
|
34
29
|
case
|
35
30
|
when gt == nil then nil
|
36
|
-
when gt == Float then
|
37
|
-
when gt == String then
|
38
|
-
when gt == Date then Date.strptime(
|
39
|
-
when gt == 'percentage' then
|
31
|
+
when gt == Float then xmlnode.attributes['value'].to_f
|
32
|
+
when gt == String then xmlnode.elements.first.andand.content.to_s
|
33
|
+
when gt == Date then Date.strptime(xmlnode.attributes['date-value'].to_s, '%Y-%m-%d')
|
34
|
+
when gt == 'percentage' then xmlnode.attributes['value'].to_f
|
40
35
|
end
|
41
|
-
elsif
|
42
|
-
|
43
|
-
nil
|
44
|
-
else
|
45
|
-
parent_row.normalize.cells(@col).value
|
46
|
-
end
|
36
|
+
elsif self.mode == :outbound
|
37
|
+
nil
|
47
38
|
else
|
48
|
-
raise "Unknown cell mode #{
|
49
|
-
end
|
50
|
-
end
|
51
|
-
def value=(avalue)
|
52
|
-
if @mode == :regular
|
53
|
-
gt = guess_cell_type(avalue)
|
54
|
-
case
|
55
|
-
when gt == nil then raise 'This value type is not storable to cell'
|
56
|
-
when gt == Float
|
57
|
-
set_type_attribute('float')
|
58
|
-
remove_all_value_attributes_and_content(@xmlnode)
|
59
|
-
Tools.set_ns_attribute(@xmlnode,'office','value', avalue.to_s)
|
60
|
-
@xmlnode << LibXML::XML::Node.new('p', avalue.to_f.to_s, ns_text)
|
61
|
-
when gt == String then
|
62
|
-
set_type_attribute('string')
|
63
|
-
remove_all_value_attributes_and_content(@xmlnode)
|
64
|
-
@xmlnode << LibXML::XML::Node.new('p', avalue.to_s, ns_text)
|
65
|
-
when gt == Date then
|
66
|
-
set_type_attribute('date')
|
67
|
-
remove_all_value_attributes_and_content(@xmlnode)
|
68
|
-
Tools.set_ns_attribute(@xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
|
69
|
-
@xmlnode << LibXML::XML::Node.new('p', avalue.strftime('%Y-%m-%d'), ns_text)
|
70
|
-
when gt == 'percentage'
|
71
|
-
set_type_attribute('float')
|
72
|
-
remove_all_value_attributes_and_content(@xmlnode)
|
73
|
-
Tools.set_ns_attribute(@xmlnode,'office','value', avalue.to_f.to_s)
|
74
|
-
@xmlnode << LibXML::XML::Node.new('p', (avalue.to_f*100).round.to_s+'%', ns_text)
|
75
|
-
end
|
76
|
-
elsif (@mode == :repeated) or (@mode == :outbound ) # Cell did not exist individually yet, detach row and create editable cell
|
77
|
-
row = @parent_row.detach
|
78
|
-
row.cells(@col).value = avalue
|
79
|
-
else
|
80
|
-
raise "Unknown cell mode #{@mode}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
def remove_all_value_attributes_and_content(node)
|
84
|
-
if att = Tools.get_ns_attribute(@xmlnode, 'office','value') then att.remove! end
|
85
|
-
if att = Tools.get_ns_attribute(@xmlnode, 'office','date-value') then att.remove! end
|
86
|
-
@xmlnode.content=''
|
87
|
-
end
|
88
|
-
def set_type_attribute(typestring)
|
89
|
-
Tools.set_ns_attribute(@xmlnode,'office','value-type',typestring)
|
90
|
-
end
|
91
|
-
def type
|
92
|
-
case
|
93
|
-
when guess_cell_type == Float then :float
|
94
|
-
when guess_cell_type == String then :string
|
95
|
-
when guess_cell_type == Date then :date
|
96
|
-
when guess_cell_type == 'percentage' then :percentage
|
97
|
-
when guess_cell_type == nil then :empty
|
98
|
-
else :unknown
|
39
|
+
raise "Unknown cell mode #{self.mode}"
|
99
40
|
end
|
100
41
|
end
|
101
|
-
|
102
|
-
def xmlfindall(path)
|
103
|
-
xmlnode.find(path)
|
104
|
-
end
|
105
|
-
def xmlfindfirst(path)
|
106
|
-
xmlfindall(path).first
|
107
|
-
end
|
108
|
-
# based on @xmlnode and optionally value which is about to be assigned, guesses which type the result should be
|
42
|
+
def repeated; (Tools.get_ns_attribute_value(xmlnode, 'table', 'number-columns-repeated') || 1 ).to_i end
|
109
43
|
def guess_cell_type(avalue=nil)
|
110
44
|
# try guessing by value
|
111
45
|
valueguess = case avalue
|
@@ -115,23 +49,25 @@ class Cell
|
|
115
49
|
else nil
|
116
50
|
end
|
117
51
|
result = valueguess
|
118
|
-
|
52
|
+
|
119
53
|
if valueguess.nil? # valueguess is most important
|
120
|
-
# if not succesfull then try guessing by type
|
121
|
-
|
122
|
-
typeguess = case
|
54
|
+
# if not succesfull then try guessing by type from node xml
|
55
|
+
typ = xmlnode.nil? ? 'N/A' : xmlnode.attributes['value-type']
|
56
|
+
typeguess = case typ
|
57
|
+
when nil then nil
|
123
58
|
when 'float' then Float
|
124
59
|
when 'string' then String
|
125
60
|
when 'date' then Date
|
126
61
|
when 'percentage' then :percentage
|
62
|
+
when 'N/A' then :unassigned
|
127
63
|
else
|
128
|
-
if
|
64
|
+
if xmlnode.children.size == 0
|
129
65
|
nil
|
130
66
|
else
|
131
|
-
raise "Unknown type from #{
|
67
|
+
raise "Unknown type at #{coordinates.to_s} from #{xmlnode.to_s} / children size=#{xmlnode.children.size.to_s} / type=#{xmlnode.attributes['value-type'].to_s}"
|
132
68
|
end
|
133
69
|
end
|
134
|
-
|
70
|
+
|
135
71
|
result =
|
136
72
|
if !typeguess.nil? # if not certain by value, but have a typeguess
|
137
73
|
if !avalue.nil? # with value we may try converting
|
@@ -139,7 +75,7 @@ class Cell
|
|
139
75
|
typeguess
|
140
76
|
elsif (String(avalue) rescue false) # otherwise try string
|
141
77
|
String
|
142
|
-
else # if not convertible to
|
78
|
+
else # if not convertible to anything concious then nil
|
143
79
|
nil
|
144
80
|
end
|
145
81
|
else # without value we just beleive typeguess
|
@@ -155,13 +91,111 @@ class Cell
|
|
155
91
|
end
|
156
92
|
result
|
157
93
|
end
|
158
|
-
def
|
159
|
-
|
94
|
+
def detach_if_needed
|
95
|
+
if (self.mode == :repeated) or (self.mode == :outbound ) # Cell did not exist individually yet, detach row and create editable cell
|
96
|
+
@worksheet.detach_cell_in_xml(rowi,coli)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
def value=(avalue)
|
100
|
+
detach_if_needed
|
101
|
+
if self.mode == :regular
|
102
|
+
gt = guess_cell_type(avalue)
|
103
|
+
case
|
104
|
+
when gt == nil then raise 'This value type is not storable to cell'
|
105
|
+
when gt == Float
|
106
|
+
set_type_attribute('float')
|
107
|
+
remove_all_value_attributes_and_content(xmlnode)
|
108
|
+
Tools.set_ns_attribute(xmlnode,'office','value', avalue.to_s)
|
109
|
+
xmlnode << LibXML::XML::Node.new('p', avalue.to_f.to_s, ns_text)
|
110
|
+
when gt == String then
|
111
|
+
set_type_attribute('string')
|
112
|
+
remove_all_value_attributes_and_content(xmlnode)
|
113
|
+
xmlnode << LibXML::XML::Node.new('p', avalue.to_s, ns_text)
|
114
|
+
when gt == Date then
|
115
|
+
set_type_attribute('date')
|
116
|
+
remove_all_value_attributes_and_content(xmlnode)
|
117
|
+
Tools.set_ns_attribute(xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
|
118
|
+
xmlnode << LibXML::XML::Node.new('p', avalue.strftime('%Y-%m-%d'), ns_text)
|
119
|
+
when gt == 'percentage'
|
120
|
+
set_type_attribute('float')
|
121
|
+
remove_all_value_attributes_and_content(xmlnode)
|
122
|
+
Tools.set_ns_attribute(xmlnode,'office','value', avalue.to_f.to_s)
|
123
|
+
xmlnode << LibXML::XML::Node.new('p', (avalue.to_f*100).round.to_s+'%', ns_text)
|
124
|
+
end
|
125
|
+
else
|
126
|
+
raise "Unknown cell mode #{self.mode}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
def set_type_attribute(typestring)
|
130
|
+
Tools.set_ns_attribute(xmlnode,'office','value-type',typestring)
|
160
131
|
end
|
132
|
+
def remove_all_value_attributes_and_content(node)
|
133
|
+
if att = Tools.get_ns_attribute(node, 'office','value') then att.remove! end
|
134
|
+
if att = Tools.get_ns_attribute(node, 'office','date-value') then att.remove! end
|
135
|
+
node.content=''
|
136
|
+
end
|
137
|
+
def ns_table; xmlnode.doc.root.namespaces.find_by_prefix('table') end
|
138
|
+
def ns_office; xmlnode.doc.root.namespaces.find_by_prefix('office') end
|
139
|
+
def ns_text; xmlnode.doc.root.namespaces.find_by_prefix('text') end
|
161
140
|
def relative(rowdiff,coldiff)
|
162
|
-
worksheet.cells(self.
|
141
|
+
@worksheet.cells(self.rowi+rowdiff, self.coli+coldiff)
|
142
|
+
end
|
143
|
+
def is_repeated?; mode == :repeated end
|
144
|
+
def type
|
145
|
+
gct = guess_cell_type
|
146
|
+
case
|
147
|
+
when gct == Float then :float
|
148
|
+
when gct == String then :string
|
149
|
+
when gct == Date then :date
|
150
|
+
when gct == :percentage then :percentage
|
151
|
+
when gct == :unassigned then :unassigned
|
152
|
+
when gct == nil then :unknown
|
153
|
+
else :unknown
|
154
|
+
end
|
163
155
|
end
|
164
156
|
end
|
157
|
+
|
158
|
+
|
159
|
+
# class Cell
|
160
|
+
# attr_reader :col, :xmlnode
|
161
|
+
# attr_reader :parent_row # for debug only
|
162
|
+
# attr_accessor :mode
|
163
|
+
# def self.empty_cell_node
|
164
|
+
# LibXML::XML::Node.new('table-cell',nil, Tools.get_namespace('table'))
|
165
|
+
# end
|
166
|
+
# def initialize(aparent_row,coli,axmlnode=nil)
|
167
|
+
# raise "First parameter should be Row object not #{aparent_row.class}" unless aparent_row.kind_of?(Rspreadsheet::Row)
|
168
|
+
# @parent_row = aparent_row
|
169
|
+
# if axmlnode.nil?
|
170
|
+
# axmlnode = Cell.empty_cell_node
|
171
|
+
# end
|
172
|
+
# @xmlnode = axmlnode
|
173
|
+
# @col = coli
|
174
|
+
# # set @mode
|
175
|
+
# @mode = case
|
176
|
+
# when !@parent_row.used_col_range.include?(coli) then :outbound
|
177
|
+
# when Tools.get_ns_attribute_value(@xmlnode, 'table', 'number-columns-repeated').to_i>1 then :repeated
|
178
|
+
# else:regular
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
# def to_s; value end
|
182
|
+
# def cell_xml; self.xmlnode.to_s end
|
183
|
+
# def xml; self.xmlnode.children.first.andand.inner_xml end
|
184
|
+
# def address; Rspreadsheet::Tools.c2a(row,col) end
|
185
|
+
# def row; @parent_row.row end
|
186
|
+
# def worksheet; @parent_row.worksheet end
|
187
|
+
# # use this to find node in cell xml. ex. xmlfind('.//text:a') finds all link nodes
|
188
|
+
# def xmlfindall(path)
|
189
|
+
# xmlnode.find(path)
|
190
|
+
# end
|
191
|
+
# def xmlfindfirst(path)
|
192
|
+
# xmlfindall(path).first
|
193
|
+
# end
|
194
|
+
# # based on @xmlnode and optionally value which is about to be assigned, guesses which type the result should be
|
195
|
+
# def inspect
|
196
|
+
# "#<Rspreadsheet::Cell:Cell\n row:#{row}, col:#{col} address:#{address}\n type: #{guess_cell_type.to_s}, value:#{value}\n mode: #{mode}\n>"
|
197
|
+
# end
|
198
|
+
# end
|
165
199
|
|
166
200
|
end
|
167
201
|
|
data/lib/rspreadsheet/row.rb
CHANGED
@@ -1,28 +1,120 @@
|
|
1
1
|
require('rspreadsheet/cell')
|
2
2
|
require('forwardable')
|
3
3
|
|
4
|
-
|
5
4
|
# Currently this is only syntax sugar for cells and contains no functionality
|
6
5
|
|
7
6
|
module Rspreadsheet
|
8
7
|
|
9
|
-
class
|
10
|
-
|
8
|
+
class Row < RowOrNode
|
9
|
+
attr_reader :worksheet, :rowi
|
10
|
+
def initialize(aworksheet,arowi)
|
11
11
|
@worksheet = aworksheet
|
12
|
-
@
|
12
|
+
@rowi = arowi
|
13
|
+
end
|
14
|
+
def xmlnode; @worksheet.rowxmlnode(@rowi) end
|
15
|
+
def cells(coli)
|
16
|
+
coli = coli.to_i
|
17
|
+
if coli.to_i<=0
|
18
|
+
nil
|
19
|
+
else
|
20
|
+
Cell.new(@worksheet,@rowi,coli)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def self.empty_xmlnode(repeats=1)
|
24
|
+
node = LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
|
25
|
+
Tools.set_ns_attribute(node,'table','number-rows-repeated',repeats, 1)
|
26
|
+
node
|
27
|
+
end
|
28
|
+
def detach_if_needed;
|
29
|
+
detach if repeated?
|
30
|
+
end
|
31
|
+
def detach
|
32
|
+
@worksheet.detach_row_in_xml(@rowi)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
def repeated
|
36
|
+
(Tools.get_ns_attribute_value(self.xmlnode, 'table', 'number-rows-repeated') || 1).to_i
|
37
|
+
end
|
38
|
+
def repeated?; mode==:repeated || mode==:outbound end
|
39
|
+
alias :is_repeated? :repeated?
|
40
|
+
def style_name=(value);
|
41
|
+
detach_if_needed
|
42
|
+
Tools.set_ns_attribute(xmlnode,'table','style-name',value)
|
43
|
+
end
|
44
|
+
def used_range
|
45
|
+
@worksheet.rowrange(@rowi)
|
46
|
+
end
|
47
|
+
def nonemptycells
|
48
|
+
nonemptycellsindexes.collect{ |index| cells(index) }
|
49
|
+
end
|
50
|
+
def nonemptycellsindexes
|
51
|
+
@worksheet.row_nonempty_cells_col_indexes(@rowi)
|
52
|
+
end
|
53
|
+
end
|
13
54
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
55
|
+
# class Row
|
56
|
+
# def initialize
|
57
|
+
# @readonly = :unknown
|
58
|
+
# @cells = {}
|
59
|
+
# end
|
60
|
+
# def worksheet; @parent_array.worksheet end
|
61
|
+
# def parent_array; @parent_array end # for debug only
|
62
|
+
# def used_col_range; 1..first_unused_column_index-1 end
|
63
|
+
# def used_range; used_col_range end
|
64
|
+
# def first_unused_column_index; raise 'this should be redefined in subclasses' end
|
65
|
+
# end
|
66
|
+
|
67
|
+
|
68
|
+
# --------------------------
|
69
|
+
|
70
|
+
|
71
|
+
# XmlTiedArrayItemGroup is internal representation of repeated items in XmlTiedArray.
|
72
|
+
class XmlTiedArrayItemGroup
|
73
|
+
# extend Forwardable
|
74
|
+
# delegate [:normalize ] => :@row_group
|
75
|
+
|
76
|
+
def normalize; @rowgroup.normalize end
|
77
|
+
def range; @rowgroup.range end
|
78
|
+
def repeated?; self.range.size>1 end
|
79
|
+
def xmlnode; @rowgroup.xmlnode end
|
80
|
+
|
81
|
+
def initialize(aparent_array,arange,axmlnode=nil)
|
82
|
+
@rowgroup = RowGroup.new(aparent_array,arange,axmlnode)
|
83
|
+
end
|
84
|
+
def self.new_from_xml
|
85
|
+
end
|
86
|
+
def to_rowgroup
|
87
|
+
@rowgroup
|
88
|
+
end
|
89
|
+
def range=(arange)
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# array which synchronizes with xml structure and reflects. number-xxx-repeated attributes
|
95
|
+
# also caches returned objects for indexes.
|
96
|
+
# options must contain
|
97
|
+
# :xml_items, :xml_repeated_attribute, :object_type
|
98
|
+
|
99
|
+
class XmlTiedArray < Array
|
100
|
+
def initialize(axmlnode, options={}) # TODO get rid of XmlTiedArray
|
101
|
+
@xmlnode = axmlnode
|
102
|
+
@options = options
|
103
|
+
|
104
|
+
missing_options = [:xml_repeated_attribute,:xml_items_node_name,:object_type]-@options.keys
|
105
|
+
raise "Some options missing (#{missing_options.inspect})" unless missing_options.empty?
|
106
|
+
|
107
|
+
unless @xmlnode.nil?
|
108
|
+
@xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |group_source_node|
|
109
|
+
self << parse_xml_to_group(group_source_node) # it is in @xmlnode so suffices to add object to @rowgroups
|
19
110
|
end
|
20
111
|
end
|
112
|
+
@itemcache=Hash.new()
|
21
113
|
end
|
22
|
-
def
|
114
|
+
def parse_xml_to_group(size_or_xmlnode) # parses xml to new RowGroup which can be added at the end
|
23
115
|
# reading params
|
24
116
|
if size_or_xmlnode.kind_of? LibXML::XML::Node
|
25
|
-
size = (size_or_xmlnode[
|
117
|
+
size = (size_or_xmlnode[@options[:xml_repeated_attribute]] || 1).to_i
|
26
118
|
node = size_or_xmlnode
|
27
119
|
elsif size_or_xmlnode.to_i>0
|
28
120
|
size = size_or_xmlnode.to_i
|
@@ -30,66 +122,61 @@ class RowArray
|
|
30
122
|
else
|
31
123
|
return nil
|
32
124
|
end
|
33
|
-
index =
|
34
|
-
|
125
|
+
index = first_unused_index
|
35
126
|
# construct result
|
36
|
-
|
127
|
+
Rspreadsheet::XmlTiedArrayItemGroup.new(self,index..index+size-1,node)
|
37
128
|
end
|
38
|
-
def
|
39
|
-
result =
|
40
|
-
|
41
|
-
@
|
129
|
+
def add_item_group(size_or_xmlnode)
|
130
|
+
result = parse_xml_to_group(size_or_xmlnode)
|
131
|
+
self << result
|
132
|
+
@xmlnode << result.xmlnode
|
42
133
|
result
|
43
134
|
end
|
44
|
-
def
|
45
|
-
|
46
|
-
end
|
47
|
-
def get_row(rowi)
|
48
|
-
rg = get_row_group(rowi).andand.normalize
|
49
|
-
case rg
|
50
|
-
when SingleRow then rg
|
51
|
-
when RowGroup then MemberOfRowGroup.new(rowi, rg)
|
52
|
-
when nil
|
53
|
-
if rowi>0 then UninitializedEmptyRow.new(self,rowi) else nil end
|
54
|
-
else raise
|
55
|
-
end
|
135
|
+
def first_unused_index
|
136
|
+
empty? ? 1 : last.range.end+1
|
56
137
|
end
|
57
138
|
# prolonges the RowArray to cantain rowi and returns it
|
58
|
-
def
|
59
|
-
fill_row_group_size =
|
139
|
+
def detach_of_bound_item(index)
|
140
|
+
fill_row_group_size = index-first_unused_index
|
60
141
|
if fill_row_group_size>0
|
61
|
-
|
142
|
+
add_item_group(fill_row_group_size)
|
62
143
|
end
|
63
|
-
|
64
|
-
|
144
|
+
add_item_group(1)
|
145
|
+
get_item(index) # aby se odpoved nacacheovala
|
146
|
+
end
|
147
|
+
def get_item_group(index)
|
148
|
+
find{ |item_group| item_group.range.cover?(index) }
|
65
149
|
end
|
66
|
-
def
|
67
|
-
|
68
|
-
|
150
|
+
def detach_item(index); get_item(index) end # TODO předělat do lazy podoby, kdy tohle nebude stejny
|
151
|
+
def get_item(index)
|
152
|
+
if index>= first_unused_index
|
153
|
+
nil
|
69
154
|
else
|
70
|
-
@
|
155
|
+
@itemcache[index] ||= Rspreadsheet::XmlTiedArrayItem.new(self,index)
|
71
156
|
end
|
72
157
|
end
|
73
|
-
# This detaches
|
158
|
+
# This detaches item index from the group and perhaps splits the RowGroup
|
74
159
|
# into two pieces. This makes the row individually editable.
|
75
|
-
def detach(
|
76
|
-
|
77
|
-
|
78
|
-
range =
|
160
|
+
def detach(index)
|
161
|
+
group_index = get_group_index(index)
|
162
|
+
item_group = self[group_index]
|
163
|
+
range = item_group.range
|
164
|
+
return self if range==(index..index)
|
79
165
|
|
80
166
|
# prepare new components
|
81
167
|
replaceby = []
|
82
|
-
replaceby << RowGroup.new(self,range.begin..
|
83
|
-
replaceby << (result = SingleRow.new(self,
|
84
|
-
replaceby << RowGroup.new(self,
|
168
|
+
replaceby << RowGroup.new(self,range.begin..index-1)
|
169
|
+
replaceby << (result = SingleRow.new(self,index))
|
170
|
+
replaceby << RowGroup.new(self,index+1..range.end)
|
85
171
|
|
86
172
|
# put original range somewhere in replaceby and shorten it
|
87
|
-
|
88
|
-
|
89
|
-
|
173
|
+
|
174
|
+
if index>range.begin
|
175
|
+
replaceby[0] = item_group
|
176
|
+
item_group.range = range.begin..index-1
|
90
177
|
else
|
91
|
-
replaceby[2] =
|
92
|
-
|
178
|
+
replaceby[2] = item_group
|
179
|
+
item_group.range = index+1..range.end
|
93
180
|
end
|
94
181
|
|
95
182
|
# normalize and delete empty parts
|
@@ -97,40 +184,98 @@ class RowArray
|
|
97
184
|
|
98
185
|
# do the replacement in xml
|
99
186
|
marker = LibXML::XML::Node.new('temporarymarker')
|
100
|
-
|
101
|
-
|
187
|
+
item_group.xmlnode.next = marker
|
188
|
+
item_group.xmlnode.remove!
|
102
189
|
replaceby.each{ |rg|
|
103
190
|
marker.prev = rg.xmlnode
|
104
191
|
}
|
105
192
|
marker.remove!
|
106
193
|
|
107
194
|
# do the replacement in array
|
108
|
-
|
195
|
+
self[group_index..group_index]=replaceby
|
109
196
|
result
|
110
197
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@rowgroups.find_index{ |rowgroup| rowgroup.range.cover?(rowi) }
|
198
|
+
private
|
199
|
+
def get_group_index(index)
|
200
|
+
self.find_index{ |rowgroup| rowgroup.range.cover?(index) }
|
115
201
|
end
|
116
202
|
end
|
117
203
|
|
118
|
-
class
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
204
|
+
class XmlTiedArrayItem
|
205
|
+
attr_reader :index
|
206
|
+
def initialize(aarray,aindex)
|
207
|
+
@array = aarray
|
208
|
+
@index = aindex
|
209
|
+
if self.virtual?
|
210
|
+
@object = nil
|
211
|
+
else
|
212
|
+
@object = @array.options[:object_type].new(group.xmlnode)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
def group; @array.get_item_group(index) end
|
216
|
+
def repeated?; group.repeated? end
|
217
|
+
def virtual?; ! self.repeated? end
|
218
|
+
def array
|
219
|
+
raise 'Group empty' if @group.nil?
|
220
|
+
@array
|
221
|
+
end
|
126
222
|
end
|
127
223
|
|
224
|
+
class RowArray < XmlTiedArray
|
225
|
+
attr_reader :row_array_cache
|
226
|
+
def initialize(aworksheet,aworksheet_node)
|
227
|
+
@worksheet = aworksheet
|
228
|
+
@row_array_cache = Hash.new()
|
229
|
+
super(aworksheet_node, :xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated', :object_type=>Row)
|
230
|
+
end
|
231
|
+
def get_row(rowi)
|
232
|
+
if @row_array_cache.has_key?(rowi)
|
233
|
+
return @row_array_cache[rowi]
|
234
|
+
end
|
235
|
+
item = self.get_item(rowi)
|
236
|
+
@row_array_cache[rowi] = if item.nil?
|
237
|
+
if rowi>0 then Rspreadsheet::UninitializedEmptyRow.new(self,rowi) else nil end
|
238
|
+
else
|
239
|
+
if item.repeated?
|
240
|
+
Rspreadsheet::MemberOfRowGroup.new(item.index, item.group.to_rowgroup)
|
241
|
+
else
|
242
|
+
Rspreadsheet::SingleRow.new_from_rowgroup(item.group.to_rowgroup)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
# aliases
|
247
|
+
def first_unused_row_index; first_unused_index end
|
248
|
+
def worksheet; @worksheet end
|
249
|
+
def detach_of_bound_row_group(index)
|
250
|
+
super(index)
|
251
|
+
return get_row(index)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# class Row
|
256
|
+
# def initialize
|
257
|
+
# @readonly = :unknown
|
258
|
+
# @cells = {}
|
259
|
+
# end
|
260
|
+
# def self.empty_row_node
|
261
|
+
# LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
|
262
|
+
# end
|
263
|
+
# def worksheet; @parent_array.worksheet end
|
264
|
+
# def parent_array; @parent_array end # for debug only
|
265
|
+
# def used_col_range; 1..first_unused_column_index-1 end
|
266
|
+
# def used_range; used_col_range end
|
267
|
+
# def first_unused_column_index; raise 'this should be redefined in subclasses' end
|
268
|
+
# def cells(coli)
|
269
|
+
# coli = coli.to_i
|
270
|
+
# return nil if coli.to_i<=0
|
271
|
+
# @cells[coli] ||= get_cell(coli)
|
272
|
+
# end
|
273
|
+
# end
|
274
|
+
|
128
275
|
class RowWithXMLNode < Row
|
129
276
|
attr_accessor :xmlnode
|
130
277
|
def style_name=(value); Tools.set_ns_attribute(@xmlnode,'table','style-name',value) end
|
131
|
-
def
|
132
|
-
coli = coli.to_i
|
133
|
-
return nil if coli.to_i<=0
|
278
|
+
def get_cell(coli)
|
134
279
|
Cell.new(self,coli,cellnodes(coli))
|
135
280
|
end
|
136
281
|
def nonemptycells
|
@@ -143,9 +288,6 @@ class RowWithXMLNode < Row
|
|
143
288
|
!cellnode.attributes.to_a.reject{ |attr| attr.name == 'number-columns-repeated'}.empty?
|
144
289
|
end
|
145
290
|
end
|
146
|
-
def used_col_range
|
147
|
-
1..first_unused_column_index-1
|
148
|
-
end
|
149
291
|
def cellnodes(coli)
|
150
292
|
cellnode = nil
|
151
293
|
while true
|
@@ -167,10 +309,6 @@ class RowWithXMLNode < Row
|
|
167
309
|
@xmlnode << cell.xmlnode
|
168
310
|
cell
|
169
311
|
end
|
170
|
-
def used_range
|
171
|
-
fu = first_unused_column_index
|
172
|
-
(fu>1) ? 1..fu : nil
|
173
|
-
end
|
174
312
|
def first_unused_column_index
|
175
313
|
1 + @xmlnode.elements.select{|n| n.name=='table-cell'}.reduce(0) do |sum, el|
|
176
314
|
sum + (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
|
@@ -183,6 +321,7 @@ class RowGroup < RowWithXMLNode
|
|
183
321
|
attr_reader :range
|
184
322
|
attr_accessor :parent_array, :xmlnode
|
185
323
|
def initialize(aparent_array,arange,axmlnode=nil)
|
324
|
+
super()
|
186
325
|
@parent_array = aparent_array
|
187
326
|
@range = arange
|
188
327
|
if axmlnode.nil?
|
@@ -212,6 +351,7 @@ class SingleRow < RowWithXMLNode
|
|
212
351
|
attr_accessor :xmlnode
|
213
352
|
# index Integer
|
214
353
|
def initialize(aparent_array,aindex,axmlnode=nil)
|
354
|
+
super()
|
215
355
|
@parent_array = aparent_array
|
216
356
|
@index = aindex
|
217
357
|
if axmlnode.nil?
|
@@ -222,21 +362,21 @@ class SingleRow < RowWithXMLNode
|
|
222
362
|
def self.new_from_rowgroup(rg)
|
223
363
|
anode = rg.xmlnode
|
224
364
|
Tools.remove_ns_attribute(anode,'table','number-rows-repeated')
|
225
|
-
|
226
365
|
SingleRow.new(rg.parent_array,rg.range.begin,anode)
|
227
366
|
end
|
228
367
|
def normalize; self end
|
229
368
|
def repeated?; false end
|
230
369
|
def repeated; 1 end
|
231
370
|
def range; (@index..@index) end
|
232
|
-
def detach;
|
371
|
+
def detach; self end
|
233
372
|
def row; @index end
|
234
|
-
|
373
|
+
def still_out_of_used_range?; false end
|
235
374
|
end
|
236
375
|
|
237
376
|
class LazyDetachableRow < Row
|
238
377
|
@readonly = :yes_but_detachable
|
239
378
|
def initialize(rowi)
|
379
|
+
super()
|
240
380
|
@index = rowi.to_i
|
241
381
|
end
|
242
382
|
def add_cell; detach.add_cell end
|
@@ -256,13 +396,18 @@ class MemberOfRowGroup < LazyDetachableRow
|
|
256
396
|
def initialize(arowi,arow_group)
|
257
397
|
super(arowi)
|
258
398
|
@row_group = arow_group
|
259
|
-
raise 'Wrong parameter given' unless @row_group.is_a? RowGroup
|
399
|
+
raise 'Wrong parameter given - class is '+@row_group.class.to_a unless @row_group.is_a? RowGroup
|
260
400
|
end
|
261
401
|
def detach # detaches MemberOfRowGroup from its RowGroup perhaps splitting RowGroup
|
262
402
|
@row_group.parent_array.detach(@index)
|
263
403
|
end
|
264
|
-
def
|
265
|
-
Cell.new(self,coli,@row_group.cellnodes(coli))
|
404
|
+
def get_cell(coli)
|
405
|
+
c = Cell.new(self,coli,@row_group.cellnodes(coli))
|
406
|
+
c.mode = :repeated
|
407
|
+
c
|
408
|
+
end
|
409
|
+
def first_unused_column_index
|
410
|
+
@row_group.first_unused_column_index
|
266
411
|
end
|
267
412
|
def nonemptycells
|
268
413
|
@row_group.nonemptycellsindexes.collect{ |coli| cells(coli) }
|
@@ -277,9 +422,9 @@ class UninitializedEmptyRow < LazyDetachableRow
|
|
277
422
|
super(arowi)
|
278
423
|
@parent_array = aparent_array
|
279
424
|
end
|
280
|
-
def
|
425
|
+
def get_cell(coli)
|
281
426
|
if still_out_of_used_range?
|
282
|
-
Cell.new(self,coli,
|
427
|
+
Cell.new(self,coli,nil)
|
283
428
|
else
|
284
429
|
@parent_array.get_row(@index).cells(coli)
|
285
430
|
end
|
@@ -291,10 +436,13 @@ class UninitializedEmptyRow < LazyDetachableRow
|
|
291
436
|
@parent_array.get_row(@index)
|
292
437
|
end
|
293
438
|
end
|
294
|
-
def detach; @parent_array.
|
439
|
+
def detach; @parent_array.detach_item(@index) end
|
440
|
+
def detach_cell(col)
|
441
|
+
self.detach.detach_cell(col)
|
442
|
+
end
|
295
443
|
def still_out_of_used_range?; @index >= @parent_array.first_unused_row_index end
|
296
444
|
def xmlnode; Row.empty_row_node end
|
297
|
-
def
|
445
|
+
def first_unused_column_index; 1 end
|
298
446
|
end
|
299
447
|
|
300
448
|
end
|