rspreadsheet 0.2.14 → 0.2.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/lib/helpers/class_extensions.rb +39 -95
- data/lib/rspreadsheet/cell.rb +3 -5
- data/lib/rspreadsheet/row.rb +349 -0
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +17 -11
- data/lib/rspreadsheet/worksheet.rb +0 -1
- data/lib/rspreadsheet/xml_tied.rb +0 -4
- data/spec/io_spec.rb +38 -0
- data/spec/rspreadsheet_spec.rb +1 -21
- data/spec/testfile1.ods +0 -0
- data/spec/worksheet_spec.rb +0 -1
- metadata +5 -5
- data/spec/class_extensions_spec.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c4d7239e35070d3406af3f5c7aadb872d5c0038
|
4
|
+
data.tar.gz: e06d7243078d7531984714f29ed403ce62fcc9df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfe7532c19164f3f96611793df3eb75e2c5d67c9c3763cfaee72e88f1e060ec54d9c0978c3e59183520ddc05f0ab5a88d09f8e5e03d15fc4397de5a78740c81f
|
7
|
+
data.tar.gz: f84bbe8045dfe515ac18e50df5a7482e7e6ba0282b57ba2f1ffa50991d3aec3b45a60c7080d7183a867a3839bd7f639a801b0b7e90b9d7aec9a1a59ab1a5fa34
|
data/.travis.yml
CHANGED
@@ -1,105 +1,49 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module ClassExtensions
|
4
|
-
|
5
|
-
refine Array do
|
6
|
-
def sum(identity = 0, &block)
|
7
|
-
if block_given?
|
8
|
-
map(&block).sum(identity)
|
9
|
-
else
|
10
|
-
inject(0){ |sum, element| sum.to_f + element.to_f } || identity
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
refine LibXML::XML::Node do
|
16
|
-
def ==(node2)
|
17
|
-
self.simplification_of?(node2) and node2.simplification_of?(self)
|
18
|
-
end
|
19
|
-
# if node2 contains at least all that I do
|
20
|
-
def simplification_of?(node2)
|
21
|
-
first_diff(node2).nil?
|
22
|
-
end
|
23
|
-
# return first difference where self has something more than node2 does
|
24
|
-
def first_diff(node2)
|
25
|
-
where = self.path.split('/').last
|
26
|
-
|
27
|
-
return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
|
28
|
-
return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
|
29
|
-
self.attributes.each do |attr|
|
30
|
-
return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
|
31
|
-
end
|
32
|
-
|
33
|
-
elems1 = self.elements
|
34
|
-
elems2 = node2.elements
|
35
|
-
# return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
|
36
|
-
elems1.length.times do |i|
|
37
|
-
if (elems1[i].node_type_name == 'text') && ((elems1[i].to_s != elems2[i].to_s) )
|
38
|
-
return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
|
39
|
-
elsif (elems1[i].node_type_name == 'element') && (!elems1[i].simplification_of?(elems2[i]))
|
40
|
-
return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
return nil
|
45
|
-
end
|
46
|
-
def elements
|
47
|
-
result = []
|
48
|
-
each_element { |e| result << e }
|
49
|
-
return result
|
50
|
-
end
|
1
|
+
# @private
|
51
2
|
|
3
|
+
class LibXML::XML::Node
|
4
|
+
def elements
|
5
|
+
result = []
|
6
|
+
each_element { |e| result << e }
|
7
|
+
return result
|
8
|
+
end
|
9
|
+
# if node2 contains at least all that I do
|
10
|
+
def simplification_of?(node2)
|
11
|
+
first_diff(node2).nil?
|
12
|
+
end
|
13
|
+
# return first difference where self has something more than node2 does
|
14
|
+
def first_diff(node2)
|
15
|
+
where = self.path.split('/').last
|
16
|
+
|
17
|
+
return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
|
18
|
+
return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
|
19
|
+
self.attributes.each do |attr|
|
20
|
+
return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
|
52
21
|
end
|
53
22
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
inject(0){ |sum, element| sum.to_f + element.to_f } || identity
|
23
|
+
elems1 = self.elements
|
24
|
+
elems2 = node2.elements
|
25
|
+
# return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
|
26
|
+
elems1.length.times do |i|
|
27
|
+
if (elems1[i].node_type_name == 'text') && ((elems1[i].to_s != elems2[i].to_s) )
|
28
|
+
return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
|
29
|
+
elsif (elems1[i].node_type_name == 'element') && (!elems1[i].simplification_of?(elems2[i]))
|
30
|
+
return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
|
63
31
|
end
|
64
32
|
end
|
33
|
+
|
34
|
+
return nil
|
65
35
|
end
|
36
|
+
def equals?(node2) #TODO redefine == with this
|
37
|
+
self.simplification_of?(node2) and node2.simplification_of?(self)
|
38
|
+
end
|
39
|
+
end
|
66
40
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
first_diff(node2).nil?
|
41
|
+
class Array
|
42
|
+
def sum(identity = 0, &block)
|
43
|
+
if block_given?
|
44
|
+
map(&block).sum(identity)
|
45
|
+
else
|
46
|
+
inject(0){ |sum, element| sum.to_f + element.to_f } || identity
|
74
47
|
end
|
75
|
-
# return first difference where self has something more than node2 does
|
76
|
-
def first_diff(node2)
|
77
|
-
where = self.path.split('/').last
|
78
|
-
|
79
|
-
return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
|
80
|
-
return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
|
81
|
-
self.attributes.each do |attr|
|
82
|
-
return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
|
83
|
-
end
|
84
|
-
|
85
|
-
elems1 = self.elements
|
86
|
-
elems2 = node2.elements
|
87
|
-
# return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
|
88
|
-
elems1.length.times do |i|
|
89
|
-
if (elems1[i].node_type_name == 'text') && ((elems1[i].to_s != elems2[i].to_s) )
|
90
|
-
return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
|
91
|
-
elsif (elems1[i].node_type_name == 'element') && (!elems1[i].simplification_of?(elems2[i]))
|
92
|
-
return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
return nil
|
97
|
-
end
|
98
|
-
def elements
|
99
|
-
result = []
|
100
|
-
each_element { |e| result << e }
|
101
|
-
return result
|
102
|
-
end
|
103
|
-
|
104
48
|
end
|
105
49
|
end
|
data/lib/rspreadsheet/cell.rb
CHANGED
@@ -7,10 +7,8 @@ require 'rspreadsheet/xml_tied'
|
|
7
7
|
require 'date'
|
8
8
|
require 'bigdecimal'
|
9
9
|
require 'bigdecimal/util' # for to_d method
|
10
|
-
require 'helpers/class_extensions'
|
11
10
|
|
12
11
|
module Rspreadsheet
|
13
|
-
using ClassExtensions if RUBY_VERSION > '2.1'
|
14
12
|
|
15
13
|
###
|
16
14
|
# Represents a cell in spreadsheet which has coordinates, contains value, formula and can be formated.
|
@@ -42,7 +40,7 @@ class Cell < XMLTiedItem
|
|
42
40
|
def coordinates; [rowi,coli] end
|
43
41
|
def to_s; value.to_s end
|
44
42
|
def valuexml; self.valuexmlnode.andand.inner_xml end
|
45
|
-
def valuexmlnode; self.xmlnode.
|
43
|
+
def valuexmlnode; self.xmlnode.children.first end
|
46
44
|
# use this to find node in cell xml. ex. xmlfind('.//text:a') finds all link nodes
|
47
45
|
def valuexmlfindall(path)
|
48
46
|
valuexmlnode.nil? ? [] : valuexmlnode.find(path)
|
@@ -152,10 +150,10 @@ class Cell < XMLTiedItem
|
|
152
150
|
when 'N/A' then :unassigned
|
153
151
|
when 'currency' then :currency
|
154
152
|
else
|
155
|
-
if xmlnode.
|
153
|
+
if xmlnode.children.size == 0
|
156
154
|
nil
|
157
155
|
else
|
158
|
-
raise "Unknown type at #{coordinates.to_s} from #{xmlnode.to_s} /
|
156
|
+
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}"
|
159
157
|
end
|
160
158
|
end
|
161
159
|
|
data/lib/rspreadsheet/row.rb
CHANGED
@@ -120,4 +120,353 @@ class Row < XMLTiedItem
|
|
120
120
|
def set_index(value); @rowi=value end
|
121
121
|
end
|
122
122
|
|
123
|
+
# class Row
|
124
|
+
# def initialize
|
125
|
+
# @readonly = :unknown
|
126
|
+
# @cells = {}
|
127
|
+
# end
|
128
|
+
# def worksheet; @parent_array.worksheet end
|
129
|
+
# def parent_array; @parent_array end # for debug only
|
130
|
+
# def used_col_range; 1..first_unused_column_index-1 end
|
131
|
+
# def used_range; used_col_range end
|
132
|
+
# def first_unused_column_index; raise 'this should be redefined in subclasses' end
|
133
|
+
# end
|
134
|
+
|
135
|
+
|
136
|
+
# --------------------------
|
137
|
+
|
138
|
+
|
139
|
+
# # XmlTiedArrayItemGroup is internal representation of repeated items in XmlTiedArray.
|
140
|
+
# class XmlTiedArrayItemGroup
|
141
|
+
# # extend Forwardable
|
142
|
+
# # delegate [:normalize ] => :@row_group
|
143
|
+
#
|
144
|
+
# def normalize; @rowgroup.normalize end
|
145
|
+
|
146
|
+
# end
|
147
|
+
|
148
|
+
# array which synchronizes with xml structure and reflects. number-xxx-repeated attributes
|
149
|
+
# also caches returned objects for indexes.
|
150
|
+
# options must contain
|
151
|
+
# :xml_items, :xml_repeated_attribute, :object_type
|
152
|
+
|
153
|
+
# class XmlTiedArray < Array
|
154
|
+
# def initialize(axmlnode, options={}) # TODO get rid of XmlTiedArray
|
155
|
+
# @xmlnode = axmlnode
|
156
|
+
# @options = options
|
157
|
+
#
|
158
|
+
# missing_options = [:xml_repeated_attribute,:xml_items_node_name,:object_type]-@options.keys
|
159
|
+
# raise "Some options missing (#{missing_options.inspect})" unless missing_options.empty?
|
160
|
+
#
|
161
|
+
# unless @xmlnode.nil?
|
162
|
+
# @xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |group_source_node|
|
163
|
+
# self << parse_xml_to_group(group_source_node) # it is in @xmlnode so suffices to add object to @rowgroups
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
# @itemcache=Hash.new()
|
167
|
+
# end
|
168
|
+
# def parse_xml_to_group(size_or_xmlnode) # parses xml to new RowGroup which can be added at the end
|
169
|
+
# # reading params
|
170
|
+
# if size_or_xmlnode.kind_of? LibXML::XML::Node
|
171
|
+
# size = (size_or_xmlnode[@options[:xml_repeated_attribute]] || 1).to_i
|
172
|
+
# node = size_or_xmlnode
|
173
|
+
# elsif size_or_xmlnode.to_i>0
|
174
|
+
# size = size_or_xmlnode.to_i
|
175
|
+
# node = nil
|
176
|
+
# else
|
177
|
+
# return nil
|
178
|
+
# end
|
179
|
+
# index = first_unused_index
|
180
|
+
# # construct result
|
181
|
+
# Rspreadsheet::XmlTiedArrayItemGroup.new(self,index..index+size-1,node)
|
182
|
+
# end
|
183
|
+
# def add_item_group(size_or_xmlnode)
|
184
|
+
# result = parse_xml_to_group(size_or_xmlnode)
|
185
|
+
# self << result
|
186
|
+
# @xmlnode << result.xmlnode
|
187
|
+
# result
|
188
|
+
# end
|
189
|
+
# def first_unused_index
|
190
|
+
# empty? ? 1 : last.range.end+1
|
191
|
+
# end
|
192
|
+
# # prolonges the RowArray to cantain rowi and returns it
|
193
|
+
# def detach_of_bound_item(index)
|
194
|
+
# fill_row_group_size = index-first_unused_index
|
195
|
+
# if fill_row_group_size>0
|
196
|
+
# add_item_group(fill_row_group_size)
|
197
|
+
# end
|
198
|
+
# add_item_group(1)
|
199
|
+
# get_item(index) # aby se odpoved nacacheovala
|
200
|
+
# end
|
201
|
+
# def get_item_group(index)
|
202
|
+
# find{ |item_group| item_group.range.cover?(index) }
|
203
|
+
# end
|
204
|
+
# def detach_item(index); get_item(index) end # TODO předělat do lazy podoby, kdy tohle nebude stejny
|
205
|
+
# def get_item(index)
|
206
|
+
# if index>= first_unused_index
|
207
|
+
# nil
|
208
|
+
# else
|
209
|
+
# @itemcache[index] ||= Rspreadsheet::XmlTiedArrayItem.new(self,index)
|
210
|
+
# end
|
211
|
+
# end
|
212
|
+
# # This detaches item index from the group and perhaps splits the RowGroup
|
213
|
+
# # into two pieces. This makes the row individually editable.
|
214
|
+
# def detach(index)
|
215
|
+
# group_index = get_group_index(index)
|
216
|
+
# item_group = self[group_index]
|
217
|
+
# range = item_group.range
|
218
|
+
# return self if range==(index..index)
|
219
|
+
#
|
220
|
+
# # prepare new components
|
221
|
+
# replaceby = []
|
222
|
+
# replaceby << RowGroup.new(self,range.begin..index-1)
|
223
|
+
# replaceby << (result = SingleRow.new(self,index))
|
224
|
+
# replaceby << RowGroup.new(self,index+1..range.end)
|
225
|
+
#
|
226
|
+
# # put original range somewhere in replaceby and shorten it
|
227
|
+
#
|
228
|
+
# if index>range.begin
|
229
|
+
# replaceby[0] = item_group
|
230
|
+
# item_group.range = range.begin..index-1
|
231
|
+
# else
|
232
|
+
# replaceby[2] = item_group
|
233
|
+
# item_group.range = index+1..range.end
|
234
|
+
# end
|
235
|
+
#
|
236
|
+
# # normalize and delete empty parts
|
237
|
+
# replaceby = replaceby.map(&:normalize).compact
|
238
|
+
#
|
239
|
+
# # do the replacement in xml
|
240
|
+
# marker = LibXML::XML::Node.new('temporarymarker')
|
241
|
+
# item_group.xmlnode.next = marker
|
242
|
+
# item_group.xmlnode.remove!
|
243
|
+
# replaceby.each{ |rg|
|
244
|
+
# marker.prev = rg.xmlnode
|
245
|
+
# }
|
246
|
+
# marker.remove!
|
247
|
+
#
|
248
|
+
# # do the replacement in array
|
249
|
+
# self[group_index..group_index]=replaceby
|
250
|
+
# result
|
251
|
+
# end
|
252
|
+
# private
|
253
|
+
# def get_group_index(index)
|
254
|
+
# self.find_index{ |rowgroup| rowgroup.range.cover?(index) }
|
255
|
+
# end
|
256
|
+
# end
|
257
|
+
|
258
|
+
# class XmlTiedArrayItem
|
259
|
+
# attr_reader :index
|
260
|
+
# def initialize(aarray,aindex)
|
261
|
+
# @array = aarray
|
262
|
+
# @index = aindex
|
263
|
+
# if self.virtual?
|
264
|
+
# @object = nil
|
265
|
+
# else
|
266
|
+
# @object = @array.options[:object_type].new(group.xmlnode)
|
267
|
+
# end
|
268
|
+
# end
|
269
|
+
# def group; @array.get_item_group(index) end
|
270
|
+
# def repeated?; group.repeated? end
|
271
|
+
# def virtual?; ! self.repeated? end
|
272
|
+
# def array
|
273
|
+
# raise 'Group empty' if @group.nil?
|
274
|
+
# @array
|
275
|
+
# end
|
276
|
+
# end
|
277
|
+
|
278
|
+
# class RowArray < XmlTiedArray
|
279
|
+
# attr_reader :row_array_cache
|
280
|
+
# def initialize(aworksheet,aworksheet_node)
|
281
|
+
# @worksheet = aworksheet
|
282
|
+
# @row_array_cache = Hash.new()
|
283
|
+
# super(aworksheet_node, :xml_items_node_name => 'table-row', :xml_repeated_attribute => xml_repeated_attribute, :object_type=>Row)
|
284
|
+
# end
|
285
|
+
# def get_row(rowi)
|
286
|
+
# if @row_array_cache.has_key?(rowi)
|
287
|
+
# return @row_array_cache[rowi]
|
288
|
+
# end
|
289
|
+
# item = self.get_item(rowi)
|
290
|
+
# @row_array_cache[rowi] = if item.nil?
|
291
|
+
# if rowi>0 then Rspreadsheet::UninitializedEmptyRow.new(self,rowi) else nil end
|
292
|
+
# else
|
293
|
+
# if item.repeated?
|
294
|
+
# Rspreadsheet::MemberOfRowGroup.new(item.index, item.group.to_rowgroup)
|
295
|
+
# else
|
296
|
+
# Rspreadsheet::SingleRow.new_from_rowgroup(item.group.to_rowgroup)
|
297
|
+
# end
|
298
|
+
# end
|
299
|
+
# end
|
300
|
+
# # aliases
|
301
|
+
# def first_unused_row_index; first_unused_index end
|
302
|
+
# def worksheet; @worksheet end
|
303
|
+
# def detach_of_bound_row_group(index)
|
304
|
+
# super(index)
|
305
|
+
# return get_row(index)
|
306
|
+
# end
|
307
|
+
# end
|
308
|
+
|
309
|
+
# class Row
|
310
|
+
# def initialize
|
311
|
+
# @readonly = :unknown
|
312
|
+
# @cells = {}
|
313
|
+
# end
|
314
|
+
# def self.empty_row_node
|
315
|
+
# LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
|
316
|
+
# end
|
317
|
+
# def worksheet; @parent_array.worksheet end
|
318
|
+
# def parent_array; @parent_array end # for debug only
|
319
|
+
# def used_col_range; 1..first_unused_column_index-1 end
|
320
|
+
# def used_range; used_col_range end
|
321
|
+
# def first_unused_column_index; raise 'this should be redefined in subclasses' end
|
322
|
+
# def cells(coli)
|
323
|
+
# coli = coli.to_i
|
324
|
+
# return nil if coli.to_i<=0
|
325
|
+
# @cells[coli] ||= get_cell(coli)
|
326
|
+
# end
|
327
|
+
# end
|
328
|
+
|
329
|
+
# class RowWithXMLNode < Row
|
330
|
+
# attr_accessor :xmlnode
|
331
|
+
# def style_name=(value); Tools.set_ns_attribute(@xmlnode,'table','style-name',value) end
|
332
|
+
# def get_cell(coli)
|
333
|
+
# Cell.new(self,coli,cellnodes(coli))
|
334
|
+
# end
|
335
|
+
# def nonemptycells
|
336
|
+
# nonemptycellsindexes.collect{ |index| cells(index) }
|
337
|
+
# end
|
338
|
+
# def nonemptycellsindexes
|
339
|
+
# used_col_range.to_a.select do |coli|
|
340
|
+
# cellnode = cellnodes(coli)
|
341
|
+
# !(cellnode.content.nil? or cellnode.content.empty? or cellnode.content =='') or
|
342
|
+
# !cellnode.attributes.to_a.reject{ |attr| attr.name == 'number-columns-repeated'}.empty?
|
343
|
+
# end
|
344
|
+
# end
|
345
|
+
# def cellnodes(coli)
|
346
|
+
# cellnode = nil
|
347
|
+
# while true
|
348
|
+
# curr_coli=1
|
349
|
+
# cellnode = @xmlnode.elements.select{|n| n.name=='table-cell'}.find do |el|
|
350
|
+
# curr_coli += (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
|
351
|
+
# curr_coli > coli
|
352
|
+
# end
|
353
|
+
# unless cellnode.nil?
|
354
|
+
# return cellnode
|
355
|
+
# else
|
356
|
+
# add_cell
|
357
|
+
# end
|
358
|
+
# end
|
359
|
+
# end
|
360
|
+
# def add_cell(repeated=1)
|
361
|
+
# cell = Cell.new(self,first_unused_column_index)
|
362
|
+
# Tools.set_ns_attribute(cell.xmlnode,'table','number-columns-repeated',repeated) if repeated>1
|
363
|
+
# @xmlnode << cell.xmlnode
|
364
|
+
# cell
|
365
|
+
# end
|
366
|
+
# def first_unused_column_index
|
367
|
+
# 1 + @xmlnode.elements.select{|n| n.name=='table-cell'}.reduce(0) do |sum, el|
|
368
|
+
# sum + (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
|
369
|
+
# end
|
370
|
+
# end
|
371
|
+
# end
|
372
|
+
|
373
|
+
# class RowGroup < RowWithXMLNode
|
374
|
+
# @readonly = :yes_always
|
375
|
+
# attr_reader :range
|
376
|
+
# attr_accessor :parent_array, :xmlnode
|
377
|
+
# def initialize(aparent_array,arange,axmlnode=nil)
|
378
|
+
# super()
|
379
|
+
# @parent_array = aparent_array
|
380
|
+
# @range = arange
|
381
|
+
# if axmlnode.nil?
|
382
|
+
# axmlnode = Row.empty_row_node
|
383
|
+
# Tools.set_ns_attribute(axmlnode,'table','number-rows-repeated',range.size) if range.size>1
|
384
|
+
# end
|
385
|
+
# @xmlnode = axmlnode
|
386
|
+
# end
|
387
|
+
# # returns SingleRow if size of range is 1 and nil if it is 0 or less
|
388
|
+
# def normalize
|
389
|
+
# case range.size
|
390
|
+
# when 2..Float::INFINITY then self
|
391
|
+
# when 1 then SingleRow.new_from_rowgroup(self)
|
392
|
+
# else nil
|
393
|
+
# end
|
394
|
+
# end
|
395
|
+
# def repeated; range.size end
|
396
|
+
# def repeated?; range.size>1 end
|
397
|
+
# def range=(arange)
|
398
|
+
# @range=arange
|
399
|
+
# Tools.set_ns_attribute(@xmlnode,'table','number-rows-repeated',range.size, 1)
|
400
|
+
# end
|
401
|
+
# end
|
402
|
+
|
403
|
+
# class SingleRow < RowWithXMLNode
|
404
|
+
# @readonly = :no
|
405
|
+
# attr_accessor :xmlnode
|
406
|
+
# # index Integer
|
407
|
+
# def initialize(aparent_array,aindex,axmlnode=nil)
|
408
|
+
# super()
|
409
|
+
# @parent_array = aparent_array
|
410
|
+
# @index = aindex
|
411
|
+
# if axmlnode.nil?
|
412
|
+
# axmlnode = Row.empty_row_node
|
413
|
+
# end
|
414
|
+
# @xmlnode = axmlnode
|
415
|
+
# end
|
416
|
+
# def self.new_from_rowgroup(rg)
|
417
|
+
# anode = rg.xmlnode
|
418
|
+
# Tools.remove_ns_attribute(anode,'table','number-rows-repeated')
|
419
|
+
# SingleRow.new(rg.parent_array,rg.range.begin,anode)
|
420
|
+
# end
|
421
|
+
# def normalize; self end
|
422
|
+
# def repeated?; false end
|
423
|
+
# def repeated; 1 end
|
424
|
+
# def range; (@index..@index) end
|
425
|
+
# def detach; self end
|
426
|
+
# def row; @index end
|
427
|
+
# def still_out_of_used_range?; false end
|
428
|
+
# end
|
429
|
+
|
430
|
+
# class LazyDetachableRow < Row
|
431
|
+
# @readonly = :yes_but_detachable
|
432
|
+
# def initialize(rowi)
|
433
|
+
# super()
|
434
|
+
# @index = rowi.to_i
|
435
|
+
# end
|
436
|
+
# def add_cell; detach.add_cell end
|
437
|
+
# def style_name=(value); detach.style_name=value end
|
438
|
+
# def row; @index end
|
439
|
+
# end
|
440
|
+
|
441
|
+
# ## there are not data in this object, they are taken from RowGroup, but this is only readonly
|
442
|
+
# class MemberOfRowGroup < LazyDetachableRow
|
443
|
+
# @readonly = :yes_but_detachable
|
444
|
+
# extend Forwardable
|
445
|
+
# delegate [:repeated?, :repeated, :xmlnode, :parent_array] => :@row_group
|
446
|
+
# attr_accessor :row_group # for dubugging
|
447
|
+
#
|
448
|
+
# # @index Integer
|
449
|
+
# # @row_group RepeatedRow
|
450
|
+
# def initialize(arowi,arow_group)
|
451
|
+
# super(arowi)
|
452
|
+
# @row_group = arow_group
|
453
|
+
# raise 'Wrong parameter given - class is '+@row_group.class.to_a unless @row_group.is_a? RowGroup
|
454
|
+
# end
|
455
|
+
# def detach # detaches MemberOfRowGroup from its RowGroup perhaps splitting RowGroup
|
456
|
+
# @row_group.parent_array.detach(@index)
|
457
|
+
# end
|
458
|
+
# def get_cell(coli)
|
459
|
+
# c = Cell.new(self,coli,@row_group.cellnodes(coli))
|
460
|
+
# c.mode = :repeated
|
461
|
+
# c
|
462
|
+
# end
|
463
|
+
# def first_unused_column_index
|
464
|
+
# @row_group.first_unused_column_index
|
465
|
+
# end
|
466
|
+
# def nonemptycells
|
467
|
+
# @row_group.nonemptycellsindexes.collect{ |coli| cells(coli) }
|
468
|
+
# end
|
469
|
+
# end
|
470
|
+
|
471
|
+
|
123
472
|
end
|
data/lib/rspreadsheet/version.rb
CHANGED
@@ -56,17 +56,23 @@ class Workbook
|
|
56
56
|
end
|
57
57
|
# @param [String] Optional new filename
|
58
58
|
# Saves the worksheet. Optionally you can provide new filename.
|
59
|
-
def save(
|
60
|
-
if @filename.nil? and
|
61
|
-
|
62
|
-
if
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
def save(new_filename_or_io_object=nil)
|
60
|
+
if @filename.nil? and new_filename_or_io_object.nil? then raise 'New file should be named on first save.' end
|
61
|
+
|
62
|
+
if new_filename_or_io_object.kind_of? StringIO
|
63
|
+
new_filename_or_io_object.write(@content_xml.to_s(indent: false))
|
64
|
+
elsif new_filename_or_io_object.nil? or new_filename_or_io_object.kind_of? String
|
65
|
+
|
66
|
+
if new_filename_or_io_object.kind_of? String # the filename has changed
|
67
|
+
# first copy the original file to new location (or template if it is a new file)
|
68
|
+
FileUtils.cp(@filename || File.dirname(__FILE__)+'/empty_file_template.ods', new_filename_or_io_object)
|
69
|
+
@filename = new_filename_or_io_object
|
70
|
+
end
|
71
|
+
Zip::File.open(@filename) do |zip|
|
72
|
+
# it is easy, because @xmlnode in in sync with contents all the time
|
73
|
+
zip.get_output_stream('content.xml') do |f|
|
74
|
+
f.write @content_xml.to_s(:indent => false)
|
75
|
+
end
|
70
76
|
end
|
71
77
|
end
|
72
78
|
end
|
data/spec/io_spec.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rspreadsheet do
|
4
|
+
it 'can open spreadsheet and save it to file, resulting file has same content as original' do
|
5
|
+
spreadsheet = Rspreadsheet.new($test_filename) # open a file
|
6
|
+
|
7
|
+
# save it to temp file
|
8
|
+
tmp_filename = '/tmp/testfile1.ods'
|
9
|
+
File.delete(tmp_filename) if File.exists?(tmp_filename) # first delete temp file
|
10
|
+
spreadsheet.save(tmp_filename) # and save spreadsheet as temp file
|
11
|
+
|
12
|
+
# now compare content saved file to original
|
13
|
+
contents_of_files_are_identical($test_filename,tmp_filename)
|
14
|
+
end
|
15
|
+
|
16
|
+
# it 'can open spreadsheet and store it to IO object', :xpending => 'Under development' do
|
17
|
+
# spreadsheet = Rspreadsheet.new($test_filename) # open a file
|
18
|
+
#
|
19
|
+
# stringio = StringIO.new
|
20
|
+
# spreadsheet.save(stringio)
|
21
|
+
# raise stringio.read
|
22
|
+
#
|
23
|
+
# end
|
24
|
+
end
|
25
|
+
|
26
|
+
def contents_of_files_are_identical(filename1,filename2)
|
27
|
+
@content_xml1 = Zip::File.open(filename1) do |zip|
|
28
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
29
|
+
end
|
30
|
+
@content_xml2 = Zip::File.open(filename2) do |zip|
|
31
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
32
|
+
end
|
33
|
+
|
34
|
+
@content_xml2.root.first_diff(@content_xml1.root).should be_nil
|
35
|
+
@content_xml1.root.first_diff(@content_xml2.root).should be_nil
|
36
|
+
|
37
|
+
@content_xml1.root.equals?(@content_xml2.root).should == true
|
38
|
+
end
|
data/spec/rspreadsheet_spec.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
using ClassExtensions if RUBY_VERSION > '2.1'
|
3
2
|
|
4
3
|
describe Rspreadsheet do
|
5
4
|
it 'can open ods testfile and reads its content correctly' do
|
@@ -26,25 +25,6 @@ describe Rspreadsheet do
|
|
26
25
|
@sheet2[cell.rowi,cell.coli].should == cell.value
|
27
26
|
end
|
28
27
|
end
|
29
|
-
it 'can open and save file, and saved file is exactly same as original' do
|
30
|
-
tmp_filename = '/tmp/testfile1.ods' # first delete temp file
|
31
|
-
File.delete(tmp_filename) if File.exists?(tmp_filename)
|
32
|
-
book = Rspreadsheet.new($test_filename) # than open test file
|
33
|
-
book.save(tmp_filename) # and save it as temp file
|
34
|
-
|
35
|
-
# now compare them
|
36
|
-
@content_xml1 = Zip::File.open($test_filename) do |zip|
|
37
|
-
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
38
|
-
end
|
39
|
-
@content_xml2 = Zip::File.open(tmp_filename) do |zip|
|
40
|
-
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
41
|
-
end
|
42
|
-
|
43
|
-
@content_xml2.root.first_diff(@content_xml1.root).should be_nil
|
44
|
-
@content_xml1.root.first_diff(@content_xml2.root).should be_nil
|
45
|
-
|
46
|
-
@content_xml1.root.should == @content_xml2.root
|
47
|
-
end
|
48
28
|
it 'when open and save file modified, than the file is different' do
|
49
29
|
tmp_filename = '/tmp/testfile1.ods' # first delete temp file
|
50
30
|
File.delete(tmp_filename) if File.exists?(tmp_filename)
|
@@ -88,7 +68,7 @@ describe Rspreadsheet do
|
|
88
68
|
sheet.cells(5,2).format.bold = true
|
89
69
|
sheet.cells(5,2).format.background_color = '#FF0000'
|
90
70
|
}.not_to raise_error
|
91
|
-
|
71
|
+
|
92
72
|
sheet.rows(4).cellvalues.sum{|val| val.to_f}.should eq 4+7*4
|
93
73
|
sheet.rows(4).cells.sum{ |cell| cell.value.to_f }.should eq 4+7*4
|
94
74
|
|
data/spec/testfile1.ods
CHANGED
Binary file
|
data/spec/worksheet_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspreadsheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub A.Těšínský
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: libxml-ruby
|
@@ -191,8 +191,8 @@ files:
|
|
191
191
|
- reinstall_local_gem.sh
|
192
192
|
- rspreadsheet.gemspec
|
193
193
|
- spec/cell_spec.rb
|
194
|
-
- spec/class_extensions_spec.rb
|
195
194
|
- spec/column_spec.rb
|
195
|
+
- spec/io_spec.rb
|
196
196
|
- spec/row_spec.rb
|
197
197
|
- spec/rspreadsheet_spec.rb
|
198
198
|
- spec/spec_helper.rb
|
@@ -220,15 +220,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
220
|
version: '0'
|
221
221
|
requirements: []
|
222
222
|
rubyforge_project:
|
223
|
-
rubygems_version: 2.
|
223
|
+
rubygems_version: 2.2.2
|
224
224
|
signing_key:
|
225
225
|
specification_version: 4
|
226
226
|
summary: Manipulating spreadsheets with Ruby (read / create / modify OpenDocument
|
227
227
|
Spreadsheet).
|
228
228
|
test_files:
|
229
229
|
- spec/cell_spec.rb
|
230
|
-
- spec/class_extensions_spec.rb
|
231
230
|
- spec/column_spec.rb
|
231
|
+
- spec/io_spec.rb
|
232
232
|
- spec/row_spec.rb
|
233
233
|
- spec/rspreadsheet_spec.rb
|
234
234
|
- spec/spec_helper.rb
|
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
if RUBY_VERSION > '2.1'
|
4
|
-
using ClassExtensions
|
5
|
-
|
6
|
-
describe Array do
|
7
|
-
it 'can sum simple array' do
|
8
|
-
a = [1,2,3,4]
|
9
|
-
a.sum.should == 10
|
10
|
-
end
|
11
|
-
it 'ignores text and nils while summing' do
|
12
|
-
a = [1,nil, nil,2,3,'foo',5.0]
|
13
|
-
a.sum.should == 11
|
14
|
-
[nil, 'nic'].sum.should == 0
|
15
|
-
[].sum.should == 0
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe LibXML::XML::Node do
|
20
|
-
before do
|
21
|
-
@n = LibXML::XML::Node.new('a')
|
22
|
-
@n << LibXML::XML::Node.new('i','italic')
|
23
|
-
b = LibXML::XML::Node.new('p','paragraph')
|
24
|
-
b << LibXML::XML::Node.new('b','boldtext')
|
25
|
-
@n << b
|
26
|
-
@n << LibXML::XML::Node.new_text('textnode')
|
27
|
-
|
28
|
-
@m = LibXML::XML::Node.new('a')
|
29
|
-
@m << LibXML::XML::Node.new('i','italic')
|
30
|
-
c = LibXML::XML::Node.new('p','paragraph')
|
31
|
-
c << LibXML::XML::Node.new('b','boldtext')
|
32
|
-
@m << c
|
33
|
-
@m << LibXML::XML::Node.new_text('textnode')
|
34
|
-
|
35
|
-
@m2 = LibXML::XML::Node.new('a')
|
36
|
-
end
|
37
|
-
it 'can compare nodes' do
|
38
|
-
@n.should == @m
|
39
|
-
@n.should_not == @m2
|
40
|
-
end
|
41
|
-
it 'has correct elements' do
|
42
|
-
# raise @n.first_diff(@m).inspect
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|