rspreadsheet 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/DEVEL_BLOG.md +12 -2
- data/GUIDE.md +3 -3
- data/Guardfile +1 -1
- data/investigate.rb +19 -0
- data/lib/class_extensions.rb +29 -17
- data/lib/rspreadsheet/cell.rb +152 -40
- data/lib/rspreadsheet/empty_file_template.ods +0 -0
- data/lib/rspreadsheet/empty_file_template.old.ods +0 -0
- data/lib/rspreadsheet/row.rb +283 -4
- data/lib/rspreadsheet/tools.rb +79 -5
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +28 -20
- data/lib/rspreadsheet/worksheet.rb +23 -29
- data/rspreadsheet.gemspec +3 -2
- data/spec/cell_spec.rb +49 -0
- data/spec/row_spec.rb +127 -0
- data/spec/rspreadsheet_spec.rb +40 -109
- data/spec/spec_helper.rb +7 -0
- data/spec/testfile1.ods +0 -0
- data/spec/workbook_spec.rb +19 -0
- data/spec/worksheet_spec.rb +50 -0
- metadata +20 -10
data/lib/rspreadsheet/row.rb
CHANGED
@@ -1,17 +1,296 @@
|
|
1
1
|
require('rspreadsheet/cell')
|
2
|
+
include Forwardable
|
2
3
|
|
3
4
|
# Currently this is only syntax sugar for cells and contains no functionality
|
4
5
|
|
5
6
|
module Rspreadsheet
|
6
7
|
|
8
|
+
class RowArray
|
9
|
+
def initialize(aworksheet_node)
|
10
|
+
@worksheet_node = aworksheet_node
|
11
|
+
|
12
|
+
# initialize @rowgroups from @worksheet_node
|
13
|
+
@rowgroups = []
|
14
|
+
unless @worksheet_node.nil?
|
15
|
+
@worksheet_node.elements.select{|node| node.name == 'table-row'}.each do |row_source_node|
|
16
|
+
@rowgroups << prepare_row_group(row_source_node) # it is in @worksheet_node so suffices to add object to @rowgroups
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
def prepare_row_group(size_or_xmlnode) # appends new RowGroup at the end
|
21
|
+
# reading params
|
22
|
+
if size_or_xmlnode.kind_of? LibXML::XML::Node
|
23
|
+
size = (size_or_xmlnode['number-rows-repeated'] || 1).to_i
|
24
|
+
node = size_or_xmlnode
|
25
|
+
elsif size_or_xmlnode.to_i>0
|
26
|
+
size = size_or_xmlnode.to_i
|
27
|
+
node = nil
|
28
|
+
else
|
29
|
+
return nil
|
30
|
+
end
|
31
|
+
index = first_unused_row_index
|
32
|
+
|
33
|
+
# construct result
|
34
|
+
RowGroup.new(self,index..index+size-1,node).normalize
|
35
|
+
end
|
36
|
+
def add_row_group(size_or_xmlnode)
|
37
|
+
result = prepare_row_group(size_or_xmlnode)
|
38
|
+
@rowgroups << result
|
39
|
+
@worksheet_node << result.xmlnode
|
40
|
+
result
|
41
|
+
end
|
42
|
+
def get_row_group(rowi)
|
43
|
+
@rowgroups.find{ |rowgroup| rowgroup.range.cover?(rowi) }
|
44
|
+
end
|
45
|
+
def get_row(rowi)
|
46
|
+
rg = get_row_group(rowi).andand.normalize
|
47
|
+
case rg
|
48
|
+
when SingleRow then rg
|
49
|
+
when RowGroup then MemberOfRowGroup.new(rowi, rg)
|
50
|
+
when nil
|
51
|
+
if rowi>0 then UninitializedEmptyRow.new(self,rowi) else nil end
|
52
|
+
else raise
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# prolonges the RowArray to cantain rowi and returns it
|
56
|
+
def detach_of_bound_row_group(rowi)
|
57
|
+
fill_row_group_size = rowi-first_unused_row_index
|
58
|
+
if fill_row_group_size>0
|
59
|
+
add_row_group(fill_row_group_size)
|
60
|
+
end
|
61
|
+
add_row_group(1)
|
62
|
+
return get_row(rowi)
|
63
|
+
end
|
64
|
+
def first_unused_row_index
|
65
|
+
if @rowgroups.empty?
|
66
|
+
1
|
67
|
+
else
|
68
|
+
@rowgroups.last.range.end+1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# This detaches row rowi from the group and perhaps splits the RowGroup
|
72
|
+
# into two pieces. This makes the row individually editable.
|
73
|
+
def detach(rowi)
|
74
|
+
index = get_row_group_index(rowi)
|
75
|
+
row_group = @rowgroups[index]
|
76
|
+
range = row_group.range
|
77
|
+
|
78
|
+
# prepare new components
|
79
|
+
replaceby = []
|
80
|
+
replaceby << RowGroup.new(self,range.begin..rowi-1)
|
81
|
+
replaceby << (result = SingleRow.new(self,rowi))
|
82
|
+
replaceby << RowGroup.new(self,rowi+1..range.end)
|
83
|
+
|
84
|
+
# put original range somewhere in replaceby and shorten it
|
85
|
+
if rowi>range.begin
|
86
|
+
replaceby[0] = row_group
|
87
|
+
row_group.range = range.begin..rowi-1
|
88
|
+
else
|
89
|
+
replaceby[2] = row_group
|
90
|
+
row_group.range = rowi+1..range.end
|
91
|
+
end
|
92
|
+
|
93
|
+
# normalize and delete empty parts
|
94
|
+
replaceby = replaceby.map(&:normalize).compact
|
95
|
+
|
96
|
+
# do the replacement in xml
|
97
|
+
marker = LibXML::XML::Node.new('temporarymarker')
|
98
|
+
row_group.xmlnode.next = marker
|
99
|
+
row_group.xmlnode.remove!
|
100
|
+
replaceby.each{ |rg|
|
101
|
+
marker.prev = rg.xmlnode
|
102
|
+
}
|
103
|
+
marker.remove!
|
104
|
+
|
105
|
+
# do the replacement in array
|
106
|
+
@rowgroups[index..index]=replaceby
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def get_row_group_index(rowi)
|
112
|
+
@rowgroups.find_index{ |rowgroup| rowgroup.range.cover?(rowi) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
7
116
|
class Row
|
8
|
-
|
9
|
-
|
10
|
-
|
117
|
+
@readonly = :unknown
|
118
|
+
# ? @rowindex
|
119
|
+
def self.empty_row_node
|
120
|
+
LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
|
11
121
|
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class RowWithXMLNode < Row
|
125
|
+
attr_accessor :xmlnode
|
126
|
+
def style_name=(value); Tools.set_ns_attribute(@xmlnode,'table','style-name',value) end
|
12
127
|
def cells(coli)
|
13
|
-
|
128
|
+
coli = coli.to_i
|
129
|
+
return nil if coli.to_i<=0
|
130
|
+
Cell.new(self,coli,cellnodes(coli))
|
131
|
+
end
|
132
|
+
def nonemptycells
|
133
|
+
nonemptycellsindexes.collect{ |index| cells(index) }
|
134
|
+
end
|
135
|
+
def nonemptycellsindexes
|
136
|
+
used_col_range.to_a.select do |coli|
|
137
|
+
cellnode = cellnodes(coli)
|
138
|
+
!(cellnode.content.nil? or cellnode.content.empty? or cellnode.content =='') or
|
139
|
+
!cellnode.attributes.to_a.reject{ |attr| attr.name == 'number-columns-repeated'}.empty?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
def used_col_range
|
143
|
+
1..first_unused_column_index-1
|
144
|
+
end
|
145
|
+
def cellnodes(coli)
|
146
|
+
cellnode = nil
|
147
|
+
while true
|
148
|
+
curr_coli=1
|
149
|
+
cellnode = @xmlnode.elements.select{|n| n.name=='table-cell'}.find do |el|
|
150
|
+
curr_coli += (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
|
151
|
+
curr_coli > coli
|
152
|
+
end
|
153
|
+
unless cellnode.nil?
|
154
|
+
return cellnode
|
155
|
+
else
|
156
|
+
add_cell
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
def add_cell(repeated=1)
|
161
|
+
cell = Cell.new(self,first_unused_column_index)
|
162
|
+
Tools.set_ns_attribute(cell.xmlnode,'table','number-columns-repeated',repeated) if repeated>1
|
163
|
+
@xmlnode << cell.xmlnode
|
164
|
+
cell
|
165
|
+
end
|
166
|
+
def used_range
|
167
|
+
fu = first_unused_column_index
|
168
|
+
(fu>1) ? 1..fu : nil
|
169
|
+
end
|
170
|
+
def first_unused_column_index
|
171
|
+
1 + @xmlnode.elements.select{|n| n.name=='table-cell'}.reduce(0) do |sum, el|
|
172
|
+
sum + (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
|
173
|
+
end
|
14
174
|
end
|
15
175
|
end
|
16
176
|
|
177
|
+
class RowGroup < RowWithXMLNode
|
178
|
+
@readonly = :yes_always
|
179
|
+
attr_reader :range
|
180
|
+
attr_accessor :parent_array, :xmlnode
|
181
|
+
def initialize(aparent_array,arange,axmlnode=nil)
|
182
|
+
@parent_array = aparent_array
|
183
|
+
@range = arange
|
184
|
+
if axmlnode.nil?
|
185
|
+
axmlnode = Row.empty_row_node
|
186
|
+
Tools.set_ns_attribute(axmlnode,'table','number-rows-repeated',range.size) if range.size>1
|
187
|
+
end
|
188
|
+
@xmlnode = axmlnode
|
189
|
+
end
|
190
|
+
# returns SingleRow if size of range is 1 and nil if it is 0 or less
|
191
|
+
def normalize
|
192
|
+
case range.size
|
193
|
+
when 2..Float::INFINITY then self
|
194
|
+
when 1 then SingleRow.new_from_rowgroup(self)
|
195
|
+
else nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
def repeated; range.size end
|
199
|
+
def repeated?; range.size>1 end
|
200
|
+
def range=(arange)
|
201
|
+
@range=arange
|
202
|
+
Tools.set_ns_attribute(@xmlnode,'table','number-rows-repeated',range.size, 1)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class SingleRow < RowWithXMLNode
|
207
|
+
@readonly = :no
|
208
|
+
attr_accessor :xmlnode
|
209
|
+
# index Integer
|
210
|
+
def initialize(aparent_array,aindex,axmlnode=nil)
|
211
|
+
@parent_array = aparent_array
|
212
|
+
@index = aindex
|
213
|
+
if axmlnode.nil?
|
214
|
+
axmlnode = Row.empty_row_node
|
215
|
+
end
|
216
|
+
@xmlnode = axmlnode
|
217
|
+
end
|
218
|
+
def self.new_from_rowgroup(rg)
|
219
|
+
anode = rg.xmlnode
|
220
|
+
Tools.remove_ns_attribute(anode,'table','number-rows-repeated')
|
221
|
+
|
222
|
+
SingleRow.new(rg.parent_array,rg.range.begin,anode)
|
223
|
+
end
|
224
|
+
def normalize; self end
|
225
|
+
def repeated?; false end
|
226
|
+
def repeated; 1 end
|
227
|
+
def range; (@index..@index) end
|
228
|
+
def detach; true end
|
229
|
+
def row; @index end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
class LazyDetachableRow < Row
|
234
|
+
@readonly = :yes_but_detachable
|
235
|
+
def initialize(rowi)
|
236
|
+
@index = rowi.to_i
|
237
|
+
end
|
238
|
+
def add_cell; detach.add_cell end
|
239
|
+
def style_name=(value); detach.style_name=value end
|
240
|
+
def row; @index end
|
17
241
|
end
|
242
|
+
|
243
|
+
## there are not data in this object, they are taken from RowGroup, but this is only readonly
|
244
|
+
class MemberOfRowGroup < LazyDetachableRow
|
245
|
+
@readonly = :yes_but_detachable
|
246
|
+
extend Forwardable
|
247
|
+
delegate [:repeated?, :repeated, :xmlnode, :parent_array] => :@row_group
|
248
|
+
attr_accessor :row_group # for dubugging
|
249
|
+
|
250
|
+
# @index Integer
|
251
|
+
# @row_group RepeatedRow
|
252
|
+
def initialize(arowi,arow_group)
|
253
|
+
super(arowi)
|
254
|
+
@row_group = arow_group
|
255
|
+
raise 'Wrong parameter given' unless @row_group.is_a? RowGroup
|
256
|
+
end
|
257
|
+
def detach # detaches MemberOfRowGroup from its RowGroup perhaps splitting RowGroup
|
258
|
+
@row_group.parent_array.detach(@index)
|
259
|
+
end
|
260
|
+
def cells(coli)
|
261
|
+
Cell.new(self,coli,@row_group.cellnodes(coli)).tap{|n| n.mode = :repeated}
|
262
|
+
end
|
263
|
+
def nonemptycells
|
264
|
+
@row_group.nonemptycellsindexes.collect{ |coli| cells(coli) }
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
## this is a row outside the used bounds. the main purpose of this object is to magically synchronize to existing data, once they are created
|
269
|
+
class UninitializedEmptyRow < LazyDetachableRow
|
270
|
+
@readonly = :yes_but_detachable
|
271
|
+
attr_reader :parent_array # debug only
|
272
|
+
def initialize(aparent_array,arowi)
|
273
|
+
super(arowi)
|
274
|
+
@parent_array = aparent_array
|
275
|
+
end
|
276
|
+
def cells(coli)
|
277
|
+
if still_out_of_used_range?
|
278
|
+
Cell.new(self,coli,Cell.empty_cell_node).tap{|n| n.mode = :outbound}
|
279
|
+
else
|
280
|
+
@parent_array.get_row(@index).cells(coli)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
def normalize
|
284
|
+
if still_out_of_used_range?
|
285
|
+
self
|
286
|
+
else
|
287
|
+
@parent_array.get_row(@index)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
def detach; @parent_array.detach_of_bound_row_group(@index) end
|
291
|
+
def still_out_of_used_range?; @index >= @parent_array.first_unused_row_index end
|
292
|
+
def xmlnode; Row.empty_row_node end
|
293
|
+
def nonemptycells; [] end
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
data/lib/rspreadsheet/tools.rb
CHANGED
@@ -20,13 +20,87 @@ module Tools
|
|
20
20
|
row = rowname.to_i
|
21
21
|
return [row,col]
|
22
22
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def self.get_namespace(prefix)
|
24
|
+
ns_array = {
|
25
|
+
'office'=>"urn:oasis:names:tc:opendocument:xmlns:office:1.0",
|
26
|
+
'style'=>"urn:oasis:names:tc:opendocument:xmlns:style:1.0",
|
27
|
+
'text'=>"urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
28
|
+
'table'=>"urn:oasis:names:tc:opendocument:xmlns:table:1.0",
|
29
|
+
'draw'=>"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
|
30
|
+
'fo'=>"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
31
|
+
'xlink'=>"http://www.w3.org/1999/xlink",
|
32
|
+
'dc'=>"http://purl.org/dc/elements/1.1/",
|
33
|
+
'meta'=>"urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
|
34
|
+
'number'=>"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
|
35
|
+
'presentation'=>"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
|
36
|
+
'svg'=>"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
|
37
|
+
'chart'=>"urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
|
38
|
+
'dr3d'=>"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
|
39
|
+
'math'=>"http://www.w3.org/1998/Math/MathML",
|
40
|
+
'form'=>"urn:oasis:names:tc:opendocument:xmlns:form:1.0",
|
41
|
+
'script'=>"urn:oasis:names:tc:opendocument:xmlns:script:1.0",
|
42
|
+
'ooo'=>"http://openoffice.org/2004/office",
|
43
|
+
'ooow'=>"http://openoffice.org/2004/writer",
|
44
|
+
'oooc'=>"http://openoffice.org/2004/calc",
|
45
|
+
'dom'=>"http://www.w3.org/2001/xml-events",
|
46
|
+
'xforms'=>"http://www.w3.org/2002/xforms",
|
47
|
+
'xsd'=>"http://www.w3.org/2001/XMLSchema",
|
48
|
+
'xsi'=>"http://www.w3.org/2001/XMLSchema-instance",
|
49
|
+
'rpt'=>"http://openoffice.org/2005/report",
|
50
|
+
'of'=>"urn:oasis:names:tc:opendocument:xmlns:of:1.2",
|
51
|
+
'xhtml'=>"http://www.w3.org/1999/xhtml",
|
52
|
+
'grddl'=>"http://www.w3.org/2003/g/data-view#",
|
53
|
+
'tableooo'=>"http://openoffice.org/2009/table",
|
54
|
+
'drawooo'=>"http://openoffice.org/2010/draw",
|
55
|
+
'calcext'=>"urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
|
56
|
+
'loext'=>"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
|
57
|
+
'field'=>"urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
|
58
|
+
'formx'=>"urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
|
59
|
+
'css3t'=>"http://www.w3.org/TR/css3-text/"
|
60
|
+
}
|
61
|
+
if @pomnode.nil?
|
62
|
+
@pomnode = LibXML::XML::Node.new('xxx')
|
63
|
+
end
|
64
|
+
if @ns.nil? then @ns={} end
|
65
|
+
if @ns[prefix].nil?
|
66
|
+
@ns[prefix] = LibXML::XML::Namespace.new(@pomnode, prefix, ns_array[prefix])
|
67
|
+
end
|
68
|
+
return @ns[prefix]
|
69
|
+
end
|
70
|
+
# sets namespaced attribute "ns_prefix:key" in node to value. if value == delete_value then remove the attribute
|
71
|
+
def self.set_ns_attribute(node,ns_prefix,key,value,delete_value=nil)
|
72
|
+
ns = Tools.get_namespace(ns_prefix)
|
73
|
+
attr = node.attributes.get_attribute_ns(ns.href, key)
|
27
74
|
|
28
|
-
|
75
|
+
unless value==delete_value # set attribute
|
76
|
+
if attr.nil? # create attribute if needed
|
77
|
+
attr = LibXML::XML::Attr.new(node, key,'temporarilyempty')
|
78
|
+
attr.namespaces.namespace = ns
|
79
|
+
end
|
80
|
+
attr.value = value.to_s
|
81
|
+
attr
|
82
|
+
else # remove attribute
|
83
|
+
attr.remove! unless attr.nil?
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
def self.get_ns_attribute(node,ns_prefix,key)
|
88
|
+
node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
|
89
|
+
end
|
90
|
+
def self.get_ns_attribute_value(node,ns_prefix,key)
|
91
|
+
Tools.get_ns_attribute(node,ns_prefix,key).andand.value
|
92
|
+
end
|
93
|
+
def self.remove_ns_attribute(node,ns_prefix,key)
|
94
|
+
node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
|
95
|
+
attr.remove! unless attr.nil?
|
29
96
|
end
|
30
97
|
end
|
31
98
|
|
99
|
+
end
|
100
|
+
|
101
|
+
class Range
|
102
|
+
def size
|
103
|
+
res = self.end-self.begin+1
|
104
|
+
res>0 ? res : 0
|
105
|
+
end
|
32
106
|
end
|
data/lib/rspreadsheet/version.rb
CHANGED
@@ -4,22 +4,16 @@ require 'libxml'
|
|
4
4
|
module Rspreadsheet
|
5
5
|
class Workbook
|
6
6
|
attr_reader :worksheets, :filename
|
7
|
+
attr_reader :xmlnode # debug
|
7
8
|
def initialize(afilename=nil)
|
8
9
|
@worksheets={}
|
9
10
|
@filename = afilename
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
ndx = 0
|
17
|
-
@content_xml.find_first('//office:spreadsheet').each_element { |node|
|
18
|
-
sheet = Worksheet.new(node)
|
19
|
-
@worksheets[ndx]=sheet
|
20
|
-
@worksheets[node.name]=sheet
|
21
|
-
ndx+=1
|
22
|
-
}
|
11
|
+
@content_xml = Zip::File.open(@filename || './lib/rspreadsheet/empty_file_template.ods') do |zip|
|
12
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
13
|
+
end
|
14
|
+
@xmlnode = @content_xml.find_first('//office:spreadsheet')
|
15
|
+
@xmlnode.find('./table:table').each do |node|
|
16
|
+
create_worksheet_from_node(node)
|
23
17
|
end
|
24
18
|
end
|
25
19
|
def save(new_filename=nil)
|
@@ -30,20 +24,31 @@ class Workbook
|
|
30
24
|
@filename = new_filename
|
31
25
|
end
|
32
26
|
Zip::File.open(@filename) do |zip|
|
33
|
-
# it is easy, because @
|
27
|
+
# it is easy, because @xmlnode in in sync with contents all the time
|
34
28
|
zip.get_output_stream('content.xml') do |f|
|
35
29
|
f.write @content_xml
|
36
30
|
end
|
37
31
|
end
|
38
32
|
end
|
39
|
-
def
|
40
|
-
sheet = Worksheet.new(
|
41
|
-
|
42
|
-
@worksheets[node.name]=sheet unless node.nil?
|
33
|
+
def create_worksheet_from_node(source_node)
|
34
|
+
sheet = Worksheet.new(source_node)
|
35
|
+
register_worksheet(sheet)
|
43
36
|
return sheet
|
44
37
|
end
|
45
|
-
def
|
46
|
-
|
38
|
+
def create_worksheet_with_name(name)
|
39
|
+
sheet = Worksheet.new(name)
|
40
|
+
register_worksheet(sheet)
|
41
|
+
return sheet
|
42
|
+
end
|
43
|
+
def create_worksheet
|
44
|
+
index = worksheets_count
|
45
|
+
create_worksheet_with_name("Strana #{index}")
|
46
|
+
end
|
47
|
+
def register_worksheet(worksheet)
|
48
|
+
index = worksheets_count+1
|
49
|
+
@worksheets[index]=worksheet
|
50
|
+
@worksheets[worksheet.name]=worksheet unless worksheet.name.nil?
|
51
|
+
@xmlnode << worksheet.xmlnode if worksheet.xmlnode.doc != @xmlnode.doc
|
47
52
|
end
|
48
53
|
def worksheets_count
|
49
54
|
@worksheets.keys.select{ |k| k.kind_of? Numeric }.size #TODO: ?? max
|
@@ -51,5 +56,8 @@ class Workbook
|
|
51
56
|
def worksheet_names
|
52
57
|
@worksheets.keys.reject{ |k| k.kind_of? Numeric }
|
53
58
|
end
|
59
|
+
def xmldoc
|
60
|
+
@xmlnode.doc
|
61
|
+
end
|
54
62
|
end
|
55
63
|
end
|
@@ -1,47 +1,38 @@
|
|
1
1
|
require 'rspreadsheet/row'
|
2
2
|
require 'rspreadsheet/tools'
|
3
|
-
require 'forwardable'
|
3
|
+
# require 'forwardable'
|
4
4
|
|
5
5
|
module Rspreadsheet
|
6
6
|
|
7
7
|
class Worksheet
|
8
|
-
attr_accessor :name
|
9
|
-
|
10
|
-
|
8
|
+
attr_accessor :name, :xmlnode
|
9
|
+
# extend Forwardable
|
10
|
+
# def_delegators :nonemptycells
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
coli = 1
|
24
|
-
row_source_node.elements.select{ |node| node.name == 'table-cell'}.each do |cell_source_node|
|
25
|
-
initialize_cell(rowi,coli,cell_source_node)
|
26
|
-
coli += 1
|
27
|
-
end
|
28
|
-
rowi += 1
|
29
|
-
end
|
12
|
+
def initialize(xmlnode_or_sheet_name)
|
13
|
+
# set up the @xmlnode according to parameter
|
14
|
+
case xmlnode_or_sheet_name
|
15
|
+
when LibXML::XML::Node
|
16
|
+
@xmlnode = xmlnode_or_sheet_name
|
17
|
+
when String
|
18
|
+
@xmlnode = LibXML::XML::Node.new('table')
|
19
|
+
ns = LibXML::XML::Namespace.new(@xmlnode, 'table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0')
|
20
|
+
@xmlnode .namespaces.namespace = ns
|
21
|
+
@xmlnode['table:name'] = xmlnode_or_sheet_name
|
22
|
+
else raise 'Provide name or xml node to create a Worksheet object'
|
30
23
|
end
|
24
|
+
|
31
25
|
## initialize rows
|
32
|
-
@spredsheetrows=
|
33
|
-
end
|
34
|
-
def initialize_cell(r,c,source_node)
|
35
|
-
@cells[[r,c]]=Cell.new(r,c,source_node)
|
26
|
+
@spredsheetrows=RowArray.new(@xmlnode)
|
36
27
|
end
|
37
28
|
def cells(r,c)
|
38
|
-
|
29
|
+
rows(r).andand.cells(c)
|
39
30
|
end
|
40
31
|
def nonemptycells
|
41
|
-
|
32
|
+
used_rows_range.collect{ |rowi| rows(rowi) }.collect { |row| row.nonemptycells }.flatten
|
42
33
|
end
|
43
34
|
def rows(rowi)
|
44
|
-
@spredsheetrows
|
35
|
+
@spredsheetrows.get_row(rowi)
|
45
36
|
end
|
46
37
|
## syntactic sugar follows
|
47
38
|
def [](r,c)
|
@@ -64,6 +55,9 @@ class Worksheet
|
|
64
55
|
super
|
65
56
|
end
|
66
57
|
end
|
58
|
+
def used_rows_range
|
59
|
+
1..@spredsheetrows.first_unused_row_index-1
|
60
|
+
end
|
67
61
|
end
|
68
62
|
|
69
63
|
end
|
data/rspreadsheet.gemspec
CHANGED
@@ -28,12 +28,13 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "bundler", "~> 1.5"
|
29
29
|
spec.add_development_dependency "rake", '~>0.9'
|
30
30
|
# testig - see http://bit.ly/1n5yM51
|
31
|
-
spec.add_development_dependency "rspec", '~>2'
|
31
|
+
spec.add_development_dependency "rspec", '~>2' # testing
|
32
|
+
spec.add_development_dependency 'pry-nav' # enables pry 'next', 'step' commands
|
32
33
|
|
33
34
|
# optional and testing
|
34
35
|
spec.add_development_dependency "coveralls", '~>0.7'
|
35
|
-
spec.add_development_dependency "test_notifier", '~>2.0' # test notifier for kde and other platforms
|
36
36
|
spec.add_development_dependency "guard", '~>2.6'
|
37
37
|
spec.add_development_dependency "guard-rspec", '~>2.6'
|
38
|
+
# spec.add_development_dependency 'equivalent-xml' # implementing xml diff
|
38
39
|
|
39
40
|
end
|
data/spec/cell_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rspreadsheet::Cell do
|
4
|
+
before do
|
5
|
+
book1 = Rspreadsheet.new
|
6
|
+
@sheet1 = book1.create_worksheet
|
7
|
+
book2 = Rspreadsheet.new($test_filename)
|
8
|
+
@sheet2 = book2.worksheets[1]
|
9
|
+
end
|
10
|
+
it 'contains good row and col coordinates' do
|
11
|
+
@cell = @sheet1.cells(1,3)
|
12
|
+
@cell.row.should == 1
|
13
|
+
@cell.col.should == 3
|
14
|
+
@cell.coordinates.should == [1,3]
|
15
|
+
|
16
|
+
@cell = @sheet2.cells(7,2)
|
17
|
+
@cell.row.should == 7
|
18
|
+
@cell.col.should == 2
|
19
|
+
@cell.coordinates.should == [7,2]
|
20
|
+
end
|
21
|
+
it 'can be referenced by more vars and both are synchronized' do
|
22
|
+
@cell = @sheet1.cells(1,1)
|
23
|
+
@sheet1[1,1] = 'novinka'
|
24
|
+
@cell.value.should == 'novinka'
|
25
|
+
end
|
26
|
+
it 'can be modified by more ways and all are identical' do
|
27
|
+
@cell = @sheet1.cells(2,2)
|
28
|
+
@sheet1[2,2] = 'zaprve'
|
29
|
+
@cell.value.should == 'zaprve'
|
30
|
+
@sheet1.cells(2,2).value = 'zadruhe'
|
31
|
+
@cell.value.should == 'zadruhe'
|
32
|
+
@sheet1.B2 = 'zatreti'
|
33
|
+
@cell.value.should == 'zatreti'
|
34
|
+
end
|
35
|
+
it 'can include links' do
|
36
|
+
@sheet2.A12.should == '[http://example.org/]'
|
37
|
+
end
|
38
|
+
it 'contains good row and col coordinates even after table:number-columns-repeated cells' do
|
39
|
+
@cell = @sheet2.cells(13,5)
|
40
|
+
@cell.value.should == 'afterrepeated'
|
41
|
+
@cell.row.should == 13
|
42
|
+
@cell.col.should == 5
|
43
|
+
end
|
44
|
+
it 'does not accept negative and zero coordinates' do
|
45
|
+
@sheet2.cells(0,5).should be(nil)
|
46
|
+
@sheet2.cells(2,-5).should be(nil)
|
47
|
+
@sheet2.cells(-2,-5).should be(nil)
|
48
|
+
end
|
49
|
+
end
|