rspreadsheet 0.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7c4756f4b74164e9aeafb35af79f923aff8a5fa
4
- data.tar.gz: 04814801b5e23bd0d4077ffbe39a65fbc7401d67
3
+ metadata.gz: 88cd9c36fae6899582d7fed02831bb3b07d0e1a2
4
+ data.tar.gz: 3c1101531b2fb416d20968aea2d927f303be9269
5
5
  SHA512:
6
- metadata.gz: e9e8fd002a0fdd21bd453b62088df71ece09d107eb6b6b8217f6805e989e0c18d63d376d8e7c338b2b8c292f7d46deec8dab8b161372a59978b10b33990de417
7
- data.tar.gz: 59db224cb0928ee5494f79583e6f340ecba729670688e39f0a64216647947a274022e73740f801bbe0e639984d7806d610484742805f469a2c8df0d033dfebaa
6
+ metadata.gz: e90c357bf816cdfc28c6e35abab33558b20c20467a45ed187e9a2d2edda9db186052d66d37a988001feaa645b0010857d3cf5c9fea067b162553b044a867b1d1
7
+ data.tar.gz: ac8a7b2def1b7ace32c901e4ef0f71a1059f13b2076ea6e7fb81736b2610892095048fd514a2a70b51e9e784271de9c028b2d55d6e622405e9e1aee71c86b892
data/DEVEL_BLOG.md CHANGED
@@ -75,8 +75,9 @@ RSpreadsheet.generate('pricelist.ods') do
75
75
  ## Release notes
76
76
 
77
77
  2017-01
78
- * basic image handling implemented (issue [#24](https://github.com/gorn/rspreadsheet/issues/24))
79
- * bug corrected: inserted row was not empty, but rather copy of the row below.
78
+ - file can be saced to any IO now, making it suitable for creating files on fly.
79
+ - basic image handling implemented (issue [#24](https://github.com/gorn/rspreadsheet/issues/24))
80
+ - bug corrected: inserted row was not empty, but rather copy of the row below.
80
81
 
81
82
  ## Developing this gem
82
83
 
data/GUIDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ## Guide to basic functionality
2
2
  ### Opening the file
3
3
 
4
- You can open ODS file like this
4
+ You can open ODS file (OpenDocument Spreadsheet) like this
5
5
  ````ruby
6
6
  @workbook = Rspreadsheet.open('./test.ods')
7
7
  ````
@@ -33,6 +33,14 @@ You can mix these two at will, for example like this
33
33
  i = @sheet.images.first
34
34
  i.move_to('100mm','99.98mm')
35
35
 
36
+ ### Saving
37
+ The file needs to be saved after doing changes.
38
+ ````ruby
39
+ @workbook.save
40
+ @workbook.save('new_filename.ods') # changes filename and saves
41
+ @workbook.save(any_io_object) # file can be saved to any IO like object as well
42
+ ````
43
+
36
44
  ## Examples
37
45
 
38
46
  * [basic functionality](https://gist.github.com/gorn/42e33d086d9b4fda10ec)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # rspreadsheet
4
4
 
5
- Manipulating spreadsheets with Ruby. Read, **modify**, write or create new OpenDocument Spreadsheet files from ruby code.
5
+ Manipulating spreadsheets with Ruby. Read, **modify**, write or create new OpenDocument Spreadsheet files from ruby code.
6
6
 
7
7
  The gem allows you to acces your file and modify any cell of it, **without** touching the rest of the file, which makes it compatible with all advanced features of ODS files (both existing and future ones). You do not have to worry if it supports feature XY, if it does not, it won't touch it. This itself makes it distinct from most of [similar gems](#why-another-opendocument-spreadsheet-gem). Alhought this gem is still in beta stage I use in everyday and it works fine.
8
8
 
@@ -1,3 +1,3 @@
1
1
  module Rspreadsheet
2
- VERSION = "0.3"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -54,7 +54,7 @@ class Workbook
54
54
  def initialize(afilename=nil)
55
55
  @worksheets=[]
56
56
  @filename = afilename
57
- @content_xml = Zip::File.open(@filename || TEMPLATE_FILE) do |zip|
57
+ @content_xml = Zip::File.open(@filename || TEMPLATE_FILE_NAME) do |zip|
58
58
  LibXML::XML::Document.io zip.get_input_stream(CONTENT_FILE_NAME)
59
59
  end
60
60
  @xmlnode = @content_xml.find_first('//office:spreadsheet')
@@ -63,87 +63,128 @@ class Workbook
63
63
  end
64
64
  end
65
65
 
66
- # @param [String] Optional new filename
67
- # Saves the worksheet. Optionally you can provide new filename.
68
-
69
- def save(new_filename_or_io_object=nil)
70
- @par = new_filename_or_io_object
71
- if @filename.nil? and @par.nil? then raise 'New file should be named on first save.' end
72
-
73
- if @par.kind_of? StringIO
74
- @par.write(@content_xml.to_s(indent: false))
75
- elsif @par.nil? or @par.kind_of? String
76
-
77
- if @par.kind_of? String # the filename has changed
78
- # first copy the original file to new location (or template if it is a new file)
79
- FileUtils.cp(@filename || File.dirname(__FILE__)+'/empty_file_template.ods', @par)
80
- @filename = @par
81
- end
82
-
83
66
 
84
- Zip::File.open(@filename) do |zip|
85
- # open manifest
86
- @manifest_xml = LibXML::XML::Document.io zip.get_input_stream('META-INF/manifest.xml')
87
-
88
- # save all pictures - iterate through sheets and pictures and check if they are saved and if not, save them
89
- @worksheets.each do |sheet|
90
- sheet.images.each do |image|
91
- # check if it is saved
92
- @ifname = image.internal_filename
93
- if @ifname.nil? or zip.find_entry(@ifname).nil?
94
- # if it does not have name -> make up unused name
95
- if @ifname.nil?
96
- @ifname = image.internal_filename = Rspreadsheet::Tools.get_unused_filename(zip,'Pictures/',File.extname(image.original_filename))
97
- end
98
- raise 'Could not set up internal_filename correctly.' if @ifname.nil?
99
-
100
- # save it to zip file
101
- zip.add(@ifname, image.original_filename)
102
-
103
- # make sure it is in manifest
104
- if @manifest_xml.find("//manifest:file-entry[@manifest:full-path='#{@ifname}']").empty?
105
- node = Tools.prepare_ns_node('manifest','file-entry')
106
- Tools.set_ns_attribute(node,'manifest','full-path',@ifname)
107
- Tools.set_ns_attribute(node,'manifest','media-type',image.mime)
108
- @manifest_xml.find_first("//manifest:manifest") << node
109
- end
110
- end
67
+ # @param [String] Optional new filename
68
+ # Saves the worksheet. Optionally you can provide new filename or IO stream to which the file should be saved.
69
+ def save(io=nil)
70
+ case
71
+ when @filename.nil? && io.nil?
72
+ 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_and_manifest(empty_template_zip,output_zip) # copy empty_template internals
77
+ update_manifest_and_content_xml(empty_template_zip,output_zip) # update xmls + pictures
111
78
  end
112
79
  end
113
-
114
- zip.get_output_stream('content.xml') do |f|
115
- f.write @content_xml.to_s(:indent => false)
80
+ when @filename.kind_of?(String) && io.nil?
81
+ write_zip_to(@filename) do |input_and_output_zip| # open old file
82
+ update_manifest_and_content_xml(input_and_output_zip,input_and_output_zip) # input and output are identical
116
83
  end
117
84
 
118
- zip.get_output_stream('META-INF/manifest.xml') do |f|
119
- f.write @manifest_xml.to_s
85
+ when @filename.kind_of?(String) && (io.kind_of?(String) || io.kind_of?(File))
86
+ io = io.path if io.kind_of?(File) # convert file to its filename
87
+ FileUtils.cp(@filename , io) # copy file externally
88
+ @filename = io # remember new name
89
+ save_to_io(nil) # continue modyfying file on spot
90
+
91
+ when @filename.kind_of?(String) && (io.kind_of?(IO) || io.kind_of?(StringIO))
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_and_manifest(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
120
97
  end
121
- end
98
+ # rewind result
99
+ io.rewind
100
+ else
122
101
  end
123
102
  end
103
+ alias :to_io :save
104
+ alias :save_to_io :save
105
+
106
+ private
107
+
108
+ def update_manifest_and_content_xml(input_zip,output_zip)
109
+ update_manifest_xml(input_zip,output_zip)
110
+ update_content_xml(output_zip)
111
+ end
124
112
 
125
- # Saves the worksheet to IO stream.
126
- def save_to_io(io = ::StringIO.new)
127
- ::Zip::OutputStream.write_buffer(io) do |output|
128
- ::Zip::File.open(TEMPLATE_FILE) do |input|
129
- input.
130
- select { |entry| entry.file? }.
131
- select { |entry| entry.name != CONTENT_FILE_NAME }.
132
- each do |entry|
133
- output.put_next_entry(entry.name)
134
- output.write(entry.get_input_stream.read)
113
+
114
+ def update_content_xml(zip)
115
+ save_entry_to_zip(zip,CONTENT_FILE_NAME,@content_xml.to_s(indent: false))
116
+ end
117
+
118
+ def update_manifest_xml(input_zip,output_zip)
119
+ # read manifest
120
+ @manifest_xml = LibXML::XML::Document.io input_zip.get_input_stream(MANIFEST_FILE_NAME)
121
+
122
+ # save all pictures - iterate through sheets and pictures and check if they are saved and if not, save them
123
+ @worksheets.each do |sheet|
124
+ sheet.images.each do |image|
125
+ # check if it is saved
126
+ @ifname = image.internal_filename
127
+ if @ifname.nil? or input_zip.find_entry(@ifname).nil?
128
+ # if it does not have name -> make up unused name
129
+ if @ifname.nil?
130
+ @ifname = image.internal_filename = Rspreadsheet::Tools.get_unused_filename(input_zip,'Pictures/',File.extname(image.original_filename))
135
131
  end
132
+ raise 'Could not set up internal_filename correctly.' if @ifname.nil?
133
+ raise 'This should not happen' if image.original_filename.nil?
134
+
135
+ # save it to zip file
136
+ save_entry_to_zip(output_zip, @ifname, File.open(image.original_filename,'r').read)
137
+
138
+ # make sure it is in manifest
139
+ if @manifest_xml.find("//manifest:file-entry[@manifest:full-path='#{@ifname}']").empty?
140
+ node = Tools.prepare_ns_node('manifest','file-entry')
141
+ Tools.set_ns_attribute(node,'manifest','full-path',@ifname)
142
+ Tools.set_ns_attribute(node,'manifest','media-type',image.mime)
143
+ @manifest_xml.find_first("//manifest:manifest") << node
144
+ end
145
+ end
136
146
  end
147
+ end
137
148
 
138
- output.put_next_entry(CONTENT_FILE_NAME)
139
- output.write(@content_xml.to_s(indent: false))
149
+ # write manifest
150
+ save_entry_to_zip(output_zip, MANIFEST_FILE_NAME, @manifest_xml.to_s)
151
+ end
152
+
153
+ def copy_internally_without_content_and_manifest(input_zip,output_zip)
154
+ input_zip.each do |entry|
155
+ next unless entry.file?
156
+ next if entry.name == CONTENT_FILE_NAME || entry.name == MANIFEST_FILE_NAME
157
+ save_entry_to_zip(output_zip, entry.name, entry.get_input_stream.read)
158
+ end
159
+ end
160
+
161
+ def save_entry_to_zip(zip,internal_filename,contents)
162
+ if zip.kind_of? Zip::File
163
+ # raise [internal_filename,contents].inspect unless File.exists?(internal_filename)
164
+ zip.get_output_stream(internal_filename) do |f|
165
+ f.write contents
166
+ end
167
+ else
168
+ zip.put_next_entry(internal_filename)
169
+ zip.write(contents)
170
+ end
171
+ end
172
+
173
+ def write_zip_to(io,&block)
174
+ if io.kind_of? File or io.kind_of? String
175
+ Zip::File.open(io, 'br+') do |zip|
176
+ yield zip
177
+ end
178
+ elsif io.kind_of? StringIO # or io.kind_of? IO
179
+ Zip::OutputStream.write_buffer(io) do |zip|
180
+ yield zip
181
+ end
140
182
  end
141
183
  end
142
- alias :to_io :save_to_io
143
184
 
144
- private
145
185
  CONTENT_FILE_NAME = 'content.xml'
146
- TEMPLATE_FILE = (File.dirname(__FILE__)+'/empty_file_template.ods').freeze
186
+ MANIFEST_FILE_NAME = 'META-INF/manifest.xml'
187
+ TEMPLATE_FILE_NAME = (File.dirname(__FILE__)+'/empty_file_template.ods').freeze
147
188
  def register_worksheet(worksheet)
148
189
  index = worksheets_count+1
149
190
  @worksheets[index-1]=worksheet
data/rspreadsheet.gemspec CHANGED
@@ -30,19 +30,17 @@ Gem::Specification.new do |spec|
30
30
  spec.add_runtime_dependency 'andand', '~>1.3'
31
31
 
32
32
  # development dependencies
33
- spec.add_development_dependency "bundler", "~> 1.5"
33
+ spec.add_development_dependency "bundler", '~> 1.5'
34
34
  spec.add_development_dependency "rake", '~>0.9'
35
35
  # testig - see http://bit.ly/1n5yM51
36
- spec.add_development_dependency "rspec", '~>2' # testing
37
- spec.add_development_dependency 'pry-nav', '~>0' # enables pry 'next', 'step' commands
36
+ spec.add_development_dependency "rspec", '~>2.0' # running tests
37
+ spec.add_development_dependency 'pry-nav', '~>0.0' # enables pry 'next', 'step' commands
38
+ spec.add_development_dependency "coveralls", '~>0.7' # inspecting coverage of tests
38
39
 
39
40
  # optional and testing
40
- spec.add_development_dependency "coveralls", '~>0.7'
41
-
42
- if RUBY_VERSION.split('.')[0] != "1"
43
- # ruby_dep starts to require ruby 2.2.5 which raises errors with ruby 1.9.3
44
- # spec.add_development_dependency "guard", '~>2.13'
45
- # spec.add_development_dependency "guard-rspec", '~>4.6'
41
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.2.5')
42
+ spec.add_development_dependency "guard", '~>2.13'
43
+ spec.add_development_dependency "guard-rspec", '~>4.6'
46
44
  end
47
45
 
48
46
  # spec.add_development_dependency 'equivalent-xml' # implementing xml diff
data/spec/image_spec.rb CHANGED
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe Rspreadsheet::Image do
4
4
  before do
5
- @testfile_filename = './spec/testfile2-images.ods'
6
- @tmp_testfile_filename = '/tmp/testfile2.ods'
5
+ @testfile_filename = $test_filename_images
6
+ @tmp_testfile_filename = '/tmp/testfile2-image.ods'
7
7
  File.delete(@tmp_testfile_filename) if File.exists?(@tmp_testfile_filename) # delete temp file
8
8
 
9
9
  @testimage_filename = './spec/test-image-blue.png'
@@ -12,6 +12,9 @@ describe Rspreadsheet::Image do
12
12
  @sheet = @workbook.worksheets(1)
13
13
  @sheet2 = @workbook.worksheets(2)
14
14
  end
15
+ after do
16
+ File.delete(@tmp_testfile_filename) if File.exists?(@tmp_testfile_filename) # delete temp file
17
+ end
15
18
  it 'is accesible when included in spreadsheet', :xpending do
16
19
  @sheet.images_count.should == 1
17
20
  @image = @sheet.images(1)
data/spec/io_spec.rb CHANGED
@@ -3,48 +3,74 @@ using ClassExtensions if RUBY_VERSION > '2.1'
3
3
 
4
4
  describe Rspreadsheet do
5
5
  before do
6
- @tmp_filename = '/tmp/testfile.ods'
6
+ @tmp_filename = '/tmp/testfile.ods'
7
7
  File.delete(@tmp_filename) if File.exists?(@tmp_filename) # delete temp file
8
8
  end
9
9
 
10
10
 
11
- it 'can open spreadsheet and save it to file, resulting file has same content as original' do
11
+ it 'when saved to file is identical' do
12
12
  spreadsheet = Rspreadsheet.new($test_filename) # open a file
13
13
  spreadsheet.save(@tmp_filename) # and save spreadsheet as temp file
14
14
 
15
15
  # now compare content saved file to original
16
- contents_of_files_are_identical?($test_filename,@tmp_filename).should == true
16
+ contents_of_files_should_be_identical($test_filename,@tmp_filename).should == true
17
17
  end
18
18
 
19
- it 'can open spreadsheet and store it to IO object', :pending => 'Under development' do
19
+ it 'when saved to file via save_to_io is identical' do
20
+ @tmp_filename = '/tmp/testfile4.ods'
20
21
  spreadsheet = Rspreadsheet.new($test_filename) # open a file
22
+ File.open(@tmp_filename, 'w+') do |file|
23
+ spreadsheet.save_to_io(file) # and save spreadsheet as temp file
24
+ end
25
+
26
+ # now compare content saved file to original
27
+ contents_of_files_should_be_identical($test_filename,@tmp_filename)
28
+ end
29
+
30
+ it 'can be saved to IO object' do
31
+ @tmp_filename = '/tmp/testfile5.ods'
32
+ spreadsheet = Rspreadsheet.new($test_filename_images) # open a file
21
33
 
22
34
  stringio = StringIO.new
23
35
  spreadsheet.save_to_io(stringio)
24
- stringio.size.should > 300000
36
+ # stringio.size.should > 300000
25
37
 
26
38
  # save it to temp file
27
39
  File.open(@tmp_filename, "w") do |f|
28
40
  f.write stringio.read
29
41
  end
30
42
 
31
- contents_of_files_are_identical?($test_filename,@tmp_filename).should == true
43
+ contents_of_files_should_be_identical($test_filename_images,@tmp_filename)
32
44
  end
45
+
46
+
47
+ end
48
+
49
+ def xml_from_entry(zip,entryname)
50
+ LibXML::XML::Document.io(zip.get_input_stream(entryname))
33
51
  end
34
52
 
35
- def contents_of_files_are_identical?(filename1,filename2)
36
- @content_xml1 = Zip::File.open(filename1) do |zip|
37
- LibXML::XML::Document.io zip.get_input_stream('content.xml')
53
+ def contents_of_files_should_be_identical(filename1,filename2)
54
+ Zip::File.open(filename1) do |zip|
55
+ @content1_xml = xml_from_entry(zip,'content.xml')
56
+ @manifest1_xml = xml_from_entry(zip,'META-INF/manifest.xml')
57
+ @images1_count = zip.glob('Pictures/**').count
38
58
  end
39
- @content_xml2 = Zip::File.open(filename2) do |zip|
40
- LibXML::XML::Document.io zip.get_input_stream('content.xml')
59
+
60
+ Zip::File.open(filename2) do |zip|
61
+ @content2_xml = xml_from_entry(zip,'content.xml')
62
+ @manifest2_xml = xml_from_entry(zip,'META-INF/manifest.xml')
63
+ @images2_count = zip.glob('Pictures/**').count
41
64
  end
42
- return contents_are_identical?(@content_xml1,@content_xml2)
65
+
66
+ @images1_count.should == @images2_count
67
+ xmls_should_be_identical(@content1_xml,@content2_xml)
68
+ xmls_should_be_identical(@manifest1_xml,@manifest2_xml)
43
69
  end
44
70
 
45
- def contents_are_identical?(content_xml1,content_xml2)
46
- content_xml2.root.first_diff(content_xml1.root).should be_nil
47
- content_xml1.root.first_diff(content_xml2.root).should be_nil
71
+ def xmls_should_be_identical(xml1,xml2)
72
+ xml2.root.first_diff(xml1.root).should be_nil
73
+ xml1.root.first_diff(xml2.root).should be_nil
48
74
 
49
- return (content_xml1.root == content_xml2.root)
75
+ xml1.root.should == xml2.root
50
76
  end
data/spec/spec_helper.rb CHANGED
@@ -14,6 +14,7 @@ Coveralls.wear!
14
14
 
15
15
  # some variables used everywhere
16
16
  $test_filename = './spec/testfile1.ods'
17
+ $test_filename_images = './spec/testfile2-images.ods'
17
18
 
18
19
  # require my gem
19
20
  require 'rspreadsheet'
data/spec/testfile1.ods CHANGED
Binary file
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.3'
4
+ version: 0.4.1
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: 2017-01-18 00:00:00.000000000 Z
11
+ date: 2017-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -72,28 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2'
75
+ version: '2.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '2'
82
+ version: '2.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: pry-nav
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '0.0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '0.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: coveralls
99
99
  requirement: !ruby/object:Gem::Requirement