rspreadsheet 0.2.14 → 0.2.15
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/.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
|