rspreadsheet 0.0.5 → 0.0.6
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 +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
|