rspreadsheet 0.4.9 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/GUIDE.md +18 -2
- data/README.md +3 -10
- data/TROUBLESHOOTING.md +25 -0
- data/lib/helpers/class_extensions.rb +21 -15
- data/lib/rspreadsheet.rb +27 -4
- data/lib/rspreadsheet/empty_file_template.fods +9 -0
- data/lib/rspreadsheet/tools.rb +45 -3
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +94 -46
- data/lib/rspreadsheet/worksheet.rb +0 -4
- data/spec/cell_spec.rb +6 -1
- data/spec/class_extensions_spec.rb +16 -3
- data/spec/fods_spec.rb +70 -0
- data/spec/rspreadsheet_spec.rb +22 -4
- data/spec/spec_helper.rb +1 -3
- data/spec/testfile1.fods +1039 -0
- data/spec/testfile1.ods +0 -0
- data/spec/tools_spec.rb +13 -0
- data/spec/worksheet_spec.rb +14 -0
- metadata +19 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 345981780a0e704579af26d413e53cb49dc671fbdcef278ae1fbdcbccf2bcbdb
|
4
|
+
data.tar.gz: 24b298d8ccde36019e3fa06f5acfdc8e9370e909c5b621663b6b23ca7bc438fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41fbd52d0c125cd5e4c6555b1de2dfc0360775986b73aff3e7eb947c17dfa03c0dbe85dadded72a00561e98ad40dca9e3052b2d42b2ea7746d818351e98933c1
|
7
|
+
data.tar.gz: 120436ad22dfd37afada167cb837172ebfe393940f58da85b5e6acfed127e275957f202a3a9e4e5ac5df7848baba3aa5554c11ddefa1b119960657ce6ef88805
|
data/GUIDE.md
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
You can open ODS file (OpenDocument Spreadsheet) like this
|
5
5
|
````ruby
|
6
6
|
workbook = Rspreadsheet.open('./test.ods')
|
7
|
+
workbook = Rspreadsheet.open('./test.fods') # gem supports flast OpenDocument format
|
7
8
|
````
|
8
9
|
and access its first sheet like this
|
9
10
|
````ruby
|
@@ -37,9 +38,24 @@ i.move_to('100mm','99.98mm')
|
|
37
38
|
The file needs to be saved after doing changes.
|
38
39
|
````ruby
|
39
40
|
workbook.save
|
40
|
-
workbook.save('new_filename.ods')
|
41
|
-
workbook.save(any_io_object)
|
41
|
+
workbook.save('new_filename.ods') # changes filename and saves
|
42
|
+
workbook.save(any_io_object) # file can be saved to any IO like object as well
|
43
|
+
workbook.to_io # coverts it to IO object which can be used to
|
44
|
+
anotherIO.write(workbook.to_io.read) # send file over internet without saving it first
|
42
45
|
````
|
46
|
+
|
47
|
+
### Creating fresh new file
|
48
|
+
You may name the spreadsheet on creation or at first save.
|
49
|
+
|
50
|
+
````ruby
|
51
|
+
workbook = Rspreadsheet.new
|
52
|
+
workbook.save('./filename.ods') # filename nust be provided at least on first save
|
53
|
+
workbook2 = Rspreadsheet.new('./filename2.fods', format: :flat)
|
54
|
+
workbook2.save
|
55
|
+
```
|
56
|
+
|
57
|
+
If you want to use the fods flat format, you must create it as such.
|
58
|
+
|
43
59
|
### Date and Time
|
44
60
|
OpenDocument and ruby have different models of date, time and datetime. Ruby containg three different objects. Time and DateTime cover all cases, Date covers dates only. OpenDocument distinguishes two groups - time of a day (time) and everything else (date). To simplify things a little we return cell values containg time of day as Time object and cell values containg datetime of date as DateTime. I am aware that this is very arbitrary choice, but it is very practical. This way and to some extend the types of values from OpenDocument are preserved when read from files, beeing acted upon and written back to spreadshhet.
|
45
61
|
|
data/README.md
CHANGED
@@ -44,8 +44,9 @@ book.save
|
|
44
44
|
book.save('different_filename.ods')
|
45
45
|
```
|
46
46
|
|
47
|
+
* Detailed [Guide to using Rspreadsheet](GUIDE.md) is available.
|
47
48
|
* [More examples](https://gist.github.com/gorn/b432e6a69e82628349e6) of lots of alternative syntax **you can leave you comments and suggestions there**
|
48
|
-
|
49
|
+
|
49
50
|
|
50
51
|
## Installation and Configuration
|
51
52
|
|
@@ -63,15 +64,7 @@ Or install it yourself as:
|
|
63
64
|
|
64
65
|
$ gem install rspreadsheet
|
65
66
|
|
66
|
-
If you get
|
67
|
-
|
68
|
-
checking for libxml/xmlversion.h in /opt/include/libxml2,/opt/local/include/libxml2,/usr/local/include/libxml2,/usr/include/libxml2... no
|
69
|
-
*** extconf.rb failed ***
|
70
|
-
or
|
71
|
-
|
72
|
-
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h
|
73
|
-
|
74
|
-
then you might not have installed libxml for ruby. I.e. in debian something like <code>sudo aptitude install ruby-libxml</code> or using equivalent command in other package managers.
|
67
|
+
If you get any error, have a look at [troubleshooting](TROUBLESHOOTING.md)
|
75
68
|
|
76
69
|
## Contibutions, ideas and wishes welcomed
|
77
70
|
|
data/TROUBLESHOOTING.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Troubleshooting
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
### libxml
|
6
|
+
|
7
|
+
The gem detects natively installed libxml and uses instead of installing new one from rubygems. There might be some errors connected to this. If you get this error concering libxml, like
|
8
|
+
|
9
|
+
checking for libxml/xmlversion.h in /opt/include/libxml2,/opt/local/include/libxml2,/usr/local/include/libxml2,/usr/include/libxml2... no
|
10
|
+
*** extconf.rb failed ***
|
11
|
+
or
|
12
|
+
|
13
|
+
mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h
|
14
|
+
|
15
|
+
then you might not have installed libxml for ruby. I.e. in debian something like
|
16
|
+
|
17
|
+
sudo aptitude install ruby-libxml
|
18
|
+
|
19
|
+
or using equivalent command in other package managers.
|
20
|
+
|
21
|
+
### if you do NOT want to use netively installed libxml
|
22
|
+
|
23
|
+
If you want to use libxml from rubygems, than uninstall the native one or add this line to your Gemfile
|
24
|
+
|
25
|
+
'libxml-ruby', '3.0'
|
@@ -14,7 +14,7 @@ if RUBY_VERSION > '2.1'
|
|
14
14
|
|
15
15
|
refine LibXML::XML::Node do
|
16
16
|
def equals?(node2); raise 'nic' end
|
17
|
-
def
|
17
|
+
def ===(node2)
|
18
18
|
self.simplification_of?(node2) and node2.simplification_of?(self)
|
19
19
|
end
|
20
20
|
# if node2 contains at least all that I do
|
@@ -31,15 +31,15 @@ if RUBY_VERSION > '2.1'
|
|
31
31
|
return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
|
32
32
|
end
|
33
33
|
|
34
|
-
elems1 = self.
|
35
|
-
elems2 = node2.
|
36
|
-
return "#{where}> elements have different number of
|
34
|
+
elems1 = self.children
|
35
|
+
elems2 = node2.children
|
36
|
+
return "#{where}> elements have different number of children #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
|
37
37
|
|
38
38
|
elems1.each_index do |i|
|
39
39
|
raise "Nil for i=#{i}" if elems1[i].nil?
|
40
40
|
if (elems1[i].node_type_name == 'text')
|
41
41
|
if elems2[i].nil? || (elems1[i].to_s) != (elems2[i].to_s)
|
42
|
-
return "#{where}> #{i+1}th text
|
42
|
+
return "#{where}> #{i+1}th text children are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
|
43
43
|
end
|
44
44
|
elsif (elems1[i].node_type_name == 'element')
|
45
45
|
unless elems1[i].simplification_of?(elems2[i])
|
@@ -72,7 +72,7 @@ else # Monkeypatching
|
|
72
72
|
end
|
73
73
|
|
74
74
|
class LibXML::XML::Node
|
75
|
-
def
|
75
|
+
def ===(node2)
|
76
76
|
self.simplification_of?(node2) and node2.simplification_of?(self)
|
77
77
|
end
|
78
78
|
# if node2 contains at least all that I do
|
@@ -88,15 +88,21 @@ else # Monkeypatching
|
|
88
88
|
self.attributes.each do |attr|
|
89
89
|
return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
|
90
90
|
end
|
91
|
+
|
92
|
+
elems1 = self.children
|
93
|
+
elems2 = node2.children
|
94
|
+
return "#{where}> elements have different number of children #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
|
91
95
|
|
92
|
-
elems1
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
elsif (elems1[i].node_type_name == 'element')
|
99
|
-
|
96
|
+
elems1.each_index do |i|
|
97
|
+
raise "Nil for i=#{i}" if elems1[i].nil?
|
98
|
+
if (elems1[i].node_type_name == 'text')
|
99
|
+
if elems2[i].nil? || (elems1[i].to_s) != (elems2[i].to_s)
|
100
|
+
return "#{where}> #{i+1}th text children are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
|
101
|
+
end
|
102
|
+
elsif (elems1[i].node_type_name == 'element')
|
103
|
+
unless elems1[i].simplification_of?(elems2[i])
|
104
|
+
return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
|
105
|
+
end
|
100
106
|
end
|
101
107
|
end
|
102
108
|
|
@@ -113,4 +119,4 @@ else # Monkeypatching
|
|
113
119
|
# end
|
114
120
|
end
|
115
121
|
|
116
|
-
end
|
122
|
+
end
|
data/lib/rspreadsheet.rb
CHANGED
@@ -7,13 +7,36 @@ require 'helpers/class_extensions'
|
|
7
7
|
|
8
8
|
module Rspreadsheet
|
9
9
|
extend Configuration
|
10
|
+
|
10
11
|
define_setting :raise_on_negative_coordinates, true
|
11
12
|
|
12
13
|
# makes creating new workbooks as easy as `Rspreadsheet.new` or `Rspreadsheet.open('filename.ods')
|
13
|
-
def self.new(
|
14
|
-
|
14
|
+
def self.new(*params)
|
15
|
+
raise ArgumentError.new("wrong number of arguments (given #{params.size}, expected 0-2)") if params.size >2
|
16
|
+
|
17
|
+
case params.last
|
18
|
+
when Hash then options = params.pop
|
19
|
+
else options = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
if options[:format].nil? # automatické heuristické rozpoznání formátu
|
23
|
+
options[:format] = :standard
|
24
|
+
unless params.first.nil?
|
25
|
+
begin
|
26
|
+
Zip::File.open(params.first)
|
27
|
+
rescue
|
28
|
+
options[:format] = :flat
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
case options[:format]
|
34
|
+
when :flat , :fods then WorkbookFlat.new(*params)
|
35
|
+
when :standard then Workbook.new(*params)
|
36
|
+
else raise 'format of the file not recognized'
|
37
|
+
end
|
15
38
|
end
|
16
|
-
def self.open(filename)
|
17
|
-
|
39
|
+
def self.open(filename, options = {})
|
40
|
+
self.new(filename, options)
|
18
41
|
end
|
19
42
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
|
3
|
+
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
|
4
|
+
<office:meta><meta:generator>Rspreadshhet gem by Gorn</meta:generator></office:meta>
|
5
|
+
<office:automatic-styles />
|
6
|
+
<office:body>
|
7
|
+
<office:spreadsheet />
|
8
|
+
</office:body>
|
9
|
+
</office:document>
|
data/lib/rspreadsheet/tools.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
module Rspreadsheet
|
2
4
|
|
3
5
|
# this module contains methods used bz several objects
|
4
6
|
module Tools
|
7
|
+
using ClassExtensions if RUBY_VERSION > '2.1'
|
8
|
+
|
5
9
|
def self.only_letters?(x); x.kind_of?(String) and x.match(/^[A-Za-z]*$/) != nil end
|
6
10
|
def self.kind_of_integer?(x)
|
7
11
|
(x.kind_of?(Numeric) and x.to_i==x) or
|
@@ -162,7 +166,8 @@ module Tools
|
|
162
166
|
end
|
163
167
|
end
|
164
168
|
def self.remove_ns_attribute(node,ns_prefix,key)
|
165
|
-
|
169
|
+
ns = Tools.get_namespace(ns_prefix)
|
170
|
+
attr = node.attributes.get_attribute_ns(ns.href, key)
|
166
171
|
attr.remove! unless attr.nil?
|
167
172
|
end
|
168
173
|
def self.prepare_ns_node(ns_prefix,nodename,value=nil)
|
@@ -176,7 +181,6 @@ module Tools
|
|
176
181
|
end
|
177
182
|
end
|
178
183
|
|
179
|
-
|
180
184
|
def self.get_unused_filename(zip,prefix, extension)
|
181
185
|
(1000..9999).each do |ndx|
|
182
186
|
filename = prefix + ndx.to_s + ((Time.now.to_r*1000000000).to_i.to_s(16)) + extension
|
@@ -188,7 +192,45 @@ module Tools
|
|
188
192
|
def self.new_time_value(h,m,s)
|
189
193
|
Time.new(StartOfEpoch.year,StartOfEpoch.month,StartOfEpoch.day,h,m,s)
|
190
194
|
end
|
195
|
+
|
196
|
+
def self.output_to_zip_stream(io,&block)
|
197
|
+
if io.kind_of? File or io.kind_of? String
|
198
|
+
Zip::File.open(io, 'br+') do |zip|
|
199
|
+
yield zip
|
200
|
+
end
|
201
|
+
elsif io.kind_of? StringIO # or io.kind_of? IO
|
202
|
+
Zip::OutputStream.write_buffer(io) do |zip|
|
203
|
+
yield zip
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
191
207
|
|
208
|
+
def self.content_xml_diff(filename1,filename2)
|
209
|
+
content_xml1 = Zip::File.open(filename1) do |zip|
|
210
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
211
|
+
end
|
212
|
+
content_xml2 = Zip::File.open(filename2) do |zip|
|
213
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
214
|
+
end
|
215
|
+
|
216
|
+
return xml_diff(content_xml1.root,content_xml2.root)
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.xml_file_diff(filename1,filename2)
|
220
|
+
content_xml1 = LibXML::XML::Document.file(filename1).root
|
221
|
+
content_xml2 = LibXML::XML::Document.file(filename2).root
|
222
|
+
return xml_diff(content_xml1, content_xml2)
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.xml_diff(xml_node1,xml_node2)
|
226
|
+
message = []
|
227
|
+
message << xml_node2.first_diff(xml_node1)
|
228
|
+
message << xml_node1.first_diff(xml_node2)
|
229
|
+
message << 'content XML not equal' unless xml_node1.to_s.should == xml_node2.to_s
|
230
|
+
message = message.compact.join('; ')
|
231
|
+
message = nil if message == ''
|
232
|
+
message
|
233
|
+
end
|
192
234
|
end
|
193
235
|
|
194
236
|
end
|
data/lib/rspreadsheet/version.rb
CHANGED
@@ -2,12 +2,13 @@ require 'zip'
|
|
2
2
|
require 'libxml'
|
3
3
|
|
4
4
|
module Rspreadsheet
|
5
|
+
|
5
6
|
class Workbook
|
6
7
|
attr_reader :filename
|
7
8
|
attr_reader :xmlnode # debug
|
8
9
|
def xmldoc; @xmlnode.doc end
|
9
10
|
|
10
|
-
#@!group
|
11
|
+
#@!group Worksheet methods
|
11
12
|
def create_worksheet_from_node(source_node)
|
12
13
|
sheet = Worksheet.new(source_node,self)
|
13
14
|
register_worksheet(sheet)
|
@@ -21,10 +22,11 @@ class Workbook
|
|
21
22
|
alias :add_worksheet :create_worksheet
|
22
23
|
# @return [Integer] number of sheets in the workbook
|
23
24
|
def worksheets_count; @worksheets.length end
|
25
|
+
alias :worksheet_count :worksheets_count
|
24
26
|
# @return [String] names of sheets in the workbook
|
25
27
|
def worksheet_names; @worksheets.collect{ |ws| ws.name } end
|
26
28
|
# @param [Integer,String]
|
27
|
-
# @return [
|
29
|
+
# @return [Worksheet] worksheet with given index or name
|
28
30
|
def worksheets(index_or_name)
|
29
31
|
case index_or_name
|
30
32
|
when Integer then begin
|
@@ -61,8 +63,7 @@ class Workbook
|
|
61
63
|
@xmlnode.find('./table:table').each do |node|
|
62
64
|
create_worksheet_from_node(node)
|
63
65
|
end
|
64
|
-
end
|
65
|
-
|
66
|
+
end
|
66
67
|
|
67
68
|
# @param [String] Optional new filename
|
68
69
|
# Saves the worksheet. Optionally you can provide new filename or IO stream to which the file should be saved.
|
@@ -70,47 +71,53 @@ class Workbook
|
|
70
71
|
case
|
71
72
|
when @filename.nil? && io.nil?
|
72
73
|
raise 'New file should be named on first save.'
|
73
|
-
when @filename.nil? && (io.kind_of?(String) || io.kind_of?(File) || io.kind_of?(IO) || io.kind_of?(StringIO))
|
74
|
-
Zip::File.open(TEMPLATE_FILE_NAME) do |empty_template_zip| # open empty_template file
|
75
|
-
write_zip_to(io) do |output_zip| # open output stream of file
|
76
|
-
copy_internally_without_content(empty_template_zip,output_zip) # copy empty_template internals
|
77
|
-
update_manifest_and_content_xml(empty_template_zip,output_zip) # update xmls + pictures
|
78
|
-
end
|
79
|
-
end
|
80
74
|
when @filename.kind_of?(String) && io.nil?
|
81
|
-
|
82
|
-
|
75
|
+
Tools.output_to_zip_stream(@filename) do |input_and_output_zip| # open old file
|
76
|
+
update_zip_manifest_and_content_xml(input_and_output_zip,input_and_output_zip) # input and output are identical
|
83
77
|
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
Zip::File.open(@filename) do | old_zip | # open old file
|
93
|
-
write_zip_to(io) do |output_zip_stream| # open output stream
|
94
|
-
copy_internally_without_content(old_zip,output_zip_stream) # copy the old internals
|
95
|
-
update_manifest_and_content_xml(old_zip,output_zip_stream) # update xmls + pictures
|
96
|
-
end
|
78
|
+
when (@filename.kind_of?(String) && (io.kind_of?(String) || io.kind_of?(File)))
|
79
|
+
io = io.path if io.kind_of?(File) # convert file to its filename
|
80
|
+
FileUtils.cp(@filename , io) # copy file externally
|
81
|
+
@filename = io # remember new name
|
82
|
+
save_to_io(nil) # continue modyfying file on spot
|
83
|
+
when io.kind_of?(IO) || io.kind_of?(String) || io.kind_of?(StringIO)
|
84
|
+
Tools.output_to_zip_stream(io) do |output_io| # open output stream of file
|
85
|
+
write_ods_to_io(output_io)
|
97
86
|
end
|
98
|
-
|
99
|
-
|
100
|
-
else
|
87
|
+
io.rewind if io.kind_of?(StringIO)
|
88
|
+
else raise 'Ivalid combinations of parameter types in save'
|
101
89
|
end
|
102
90
|
end
|
103
|
-
alias :to_io :save
|
104
91
|
alias :save_to_io :save
|
92
|
+
alias :save_as :save
|
93
|
+
def to_io
|
94
|
+
WorkbookIO.new(self)
|
95
|
+
end
|
96
|
+
|
97
|
+
def write_ods_to_io(io)
|
98
|
+
if @filename.nil?
|
99
|
+
Zip::File.open(TEMPLATE_FILE_NAME) do |empty_template_zip| # open empty_template file
|
100
|
+
copy_internally_without_content(empty_template_zip,io) # copy empty_template internals
|
101
|
+
update_zip_manifest_and_content_xml(empty_template_zip,io) # update xmls + pictures
|
102
|
+
end
|
103
|
+
else
|
104
|
+
Zip::File.open(@filename) do | old_zip | # open old file
|
105
|
+
copy_internally_without_content(old_zip,io) # copy the old internals
|
106
|
+
update_zip_manifest_and_content_xml(old_zip,io) # update xmls + pictures
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
105
110
|
|
111
|
+
def flat_format?; false end
|
112
|
+
def normal_format?; true end
|
113
|
+
|
106
114
|
private
|
107
115
|
|
108
|
-
def
|
116
|
+
def update_zip_manifest_and_content_xml(input_zip,output_zip)
|
109
117
|
update_manifest_xml(input_zip,output_zip)
|
110
118
|
update_content_xml(output_zip)
|
111
119
|
end
|
112
120
|
|
113
|
-
|
114
121
|
def update_content_xml(zip)
|
115
122
|
save_entry_to_zip(zip,CONTENT_FILE_NAME,@content_xml.to_s(indent: false))
|
116
123
|
end
|
@@ -162,7 +169,6 @@ class Workbook
|
|
162
169
|
|
163
170
|
def save_entry_to_zip(zip,internal_filename,contents)
|
164
171
|
if zip.kind_of? Zip::File
|
165
|
-
# raise [internal_filename,contents].inspect unless File.exists?(internal_filename)
|
166
172
|
zip.get_output_stream(internal_filename) do |f|
|
167
173
|
f.write contents
|
168
174
|
end
|
@@ -171,19 +177,7 @@ class Workbook
|
|
171
177
|
zip.write(contents)
|
172
178
|
end
|
173
179
|
end
|
174
|
-
|
175
|
-
def write_zip_to(io,&block)
|
176
|
-
if io.kind_of? File or io.kind_of? String
|
177
|
-
Zip::File.open(io, 'br+') do |zip|
|
178
|
-
yield zip
|
179
|
-
end
|
180
|
-
elsif io.kind_of? StringIO # or io.kind_of? IO
|
181
|
-
Zip::OutputStream.write_buffer(io) do |zip|
|
182
|
-
yield zip
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
180
|
+
|
187
181
|
CONTENT_FILE_NAME = 'content.xml'
|
188
182
|
MANIFEST_FILE_NAME = 'META-INF/manifest.xml'
|
189
183
|
TEMPLATE_FILE_NAME = (File.dirname(__FILE__)+'/empty_file_template.ods').freeze
|
@@ -192,5 +186,59 @@ class Workbook
|
|
192
186
|
@worksheets[index-1]=worksheet
|
193
187
|
@xmlnode << worksheet.xmlnode if worksheet.xmlnode.doc != @xmlnode.doc
|
194
188
|
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
class WorkbookFlat < Workbook
|
193
|
+
def initialize(afilename=nil)
|
194
|
+
@worksheets=[]
|
195
|
+
@filename = afilename
|
196
|
+
@xml_doc = LibXML::XML::Document.file(@filename || FLAT_TEMPLATE_FILE_NAME)
|
197
|
+
@xmlnode = @xml_doc.find_first('//office:spreadsheet')
|
198
|
+
@xmlnode.find('./table:table').each do |node|
|
199
|
+
create_worksheet_from_node(node)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def save(io=nil)
|
204
|
+
case
|
205
|
+
when @filename.nil? && io.nil?
|
206
|
+
raise 'New file should be named on first save, please provide filename (or IO).'
|
207
|
+
when @filename.kind_of?(String) && io.nil?
|
208
|
+
@xml_doc.save(@filename)
|
209
|
+
when (@filename.kind_of?(String) && (io.kind_of?(String) || io.kind_of?(File)))
|
210
|
+
@filename = (io.kind_of?(File)) ? io.path : io
|
211
|
+
@xml_doc.save(@filename)
|
212
|
+
when io.kind_of?(IO) || io.kind_of?(String) || io.kind_of?(StringIO)
|
213
|
+
IO.write(io,@xml_doc.to_s)
|
214
|
+
io.rewind if io.kind_of?(StringIO)
|
215
|
+
else raise 'Invalid combinations of parameter types in save'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
alias :save_to_io :save
|
219
|
+
alias :save_as :save
|
220
|
+
|
221
|
+
def flat_format?; true end
|
222
|
+
def normal_format?; false end
|
223
|
+
|
224
|
+
private
|
225
|
+
FLAT_TEMPLATE_FILE_NAME = (File.dirname(__FILE__)+'/empty_file_template.fods').freeze
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
class WorkbookIO
|
230
|
+
def initialize(workbook)
|
231
|
+
@workbook = workbook
|
232
|
+
end
|
233
|
+
def read
|
234
|
+
buffer.string
|
235
|
+
end
|
236
|
+
private
|
237
|
+
def buffer
|
238
|
+
Zip::OutputStream.write_buffer do |io|
|
239
|
+
@workbook.write_ods_to_io(io)
|
240
|
+
end
|
241
|
+
end
|
195
242
|
end
|
243
|
+
|
196
244
|
end
|