lib/rspreadsheet/workbook.rb in rspreadsheet-0.3 vs lib/rspreadsheet/workbook.rb in rspreadsheet-0.4.1
- old
+ new
@@ -52,99 +52,140 @@
alias :mime_default_extension :mime_preferred_extension
def initialize(afilename=nil)
@worksheets=[]
@filename = afilename
- @content_xml = Zip::File.open(@filename || TEMPLATE_FILE) do |zip|
+ @content_xml = Zip::File.open(@filename || TEMPLATE_FILE_NAME) do |zip|
LibXML::XML::Document.io zip.get_input_stream(CONTENT_FILE_NAME)
end
@xmlnode = @content_xml.find_first('//office:spreadsheet')
@xmlnode.find('./table:table').each do |node|
create_worksheet_from_node(node)
end
end
- # @param [String] Optional new filename
- # Saves the worksheet. Optionally you can provide new filename.
- def save(new_filename_or_io_object=nil)
- @par = new_filename_or_io_object
- if @filename.nil? and @par.nil? then raise 'New file should be named on first save.' end
-
- if @par.kind_of? StringIO
- @par.write(@content_xml.to_s(indent: false))
- elsif @par.nil? or @par.kind_of? String
-
- if @par.kind_of? String # the filename has changed
- # first copy the original file to new location (or template if it is a new file)
- FileUtils.cp(@filename || File.dirname(__FILE__)+'/empty_file_template.ods', @par)
- @filename = @par
- end
-
-
- Zip::File.open(@filename) do |zip|
- # open manifest
- @manifest_xml = LibXML::XML::Document.io zip.get_input_stream('META-INF/manifest.xml')
-
- # save all pictures - iterate through sheets and pictures and check if they are saved and if not, save them
- @worksheets.each do |sheet|
- sheet.images.each do |image|
- # check if it is saved
- @ifname = image.internal_filename
- if @ifname.nil? or zip.find_entry(@ifname).nil?
- # if it does not have name -> make up unused name
- if @ifname.nil?
- @ifname = image.internal_filename = Rspreadsheet::Tools.get_unused_filename(zip,'Pictures/',File.extname(image.original_filename))
- end
- raise 'Could not set up internal_filename correctly.' if @ifname.nil?
-
- # save it to zip file
- zip.add(@ifname, image.original_filename)
-
- # make sure it is in manifest
- if @manifest_xml.find("//manifest:file-entry[@manifest:full-path='#{@ifname}']").empty?
- node = Tools.prepare_ns_node('manifest','file-entry')
- Tools.set_ns_attribute(node,'manifest','full-path',@ifname)
- Tools.set_ns_attribute(node,'manifest','media-type',image.mime)
- @manifest_xml.find_first("//manifest:manifest") << node
- end
- end
+ # @param [String] Optional new filename
+ # Saves the worksheet. Optionally you can provide new filename or IO stream to which the file should be saved.
+ def save(io=nil)
+ case
+ when @filename.nil? && io.nil?
+ raise 'New file should be named on first save.'
+ when @filename.nil? && (io.kind_of?(String) || io.kind_of?(File) || io.kind_of?(IO) || io.kind_of?(StringIO))
+ Zip::File.open(TEMPLATE_FILE_NAME) do |empty_template_zip| # open empty_template file
+ write_zip_to(io) do |output_zip| # open output stream of file
+ copy_internally_without_content_and_manifest(empty_template_zip,output_zip) # copy empty_template internals
+ update_manifest_and_content_xml(empty_template_zip,output_zip) # update xmls + pictures
end
end
-
- zip.get_output_stream('content.xml') do |f|
- f.write @content_xml.to_s(:indent => false)
+ when @filename.kind_of?(String) && io.nil?
+ write_zip_to(@filename) do |input_and_output_zip| # open old file
+ update_manifest_and_content_xml(input_and_output_zip,input_and_output_zip) # input and output are identical
end
- zip.get_output_stream('META-INF/manifest.xml') do |f|
- f.write @manifest_xml.to_s
+ when @filename.kind_of?(String) && (io.kind_of?(String) || io.kind_of?(File))
+ io = io.path if io.kind_of?(File) # convert file to its filename
+ FileUtils.cp(@filename , io) # copy file externally
+ @filename = io # remember new name
+ save_to_io(nil) # continue modyfying file on spot
+
+ when @filename.kind_of?(String) && (io.kind_of?(IO) || io.kind_of?(StringIO))
+ Zip::File.open(@filename) do | old_zip | # open old file
+ write_zip_to(io) do |output_zip_stream| # open output stream
+ copy_internally_without_content_and_manifest(old_zip,output_zip_stream) # copy the old internals
+ update_manifest_and_content_xml(old_zip,output_zip_stream) # update xmls + pictures
+ end
end
- end
+ # rewind result
+ io.rewind
+ else
end
end
+ alias :to_io :save
+ alias :save_to_io :save
- # Saves the worksheet to IO stream.
- def save_to_io(io = ::StringIO.new)
- ::Zip::OutputStream.write_buffer(io) do |output|
- ::Zip::File.open(TEMPLATE_FILE) do |input|
- input.
- select { |entry| entry.file? }.
- select { |entry| entry.name != CONTENT_FILE_NAME }.
- each do |entry|
- output.put_next_entry(entry.name)
- output.write(entry.get_input_stream.read)
+ private
+
+ def update_manifest_and_content_xml(input_zip,output_zip)
+ update_manifest_xml(input_zip,output_zip)
+ update_content_xml(output_zip)
+ end
+
+
+ def update_content_xml(zip)
+ save_entry_to_zip(zip,CONTENT_FILE_NAME,@content_xml.to_s(indent: false))
+ end
+
+ def update_manifest_xml(input_zip,output_zip)
+ # read manifest
+ @manifest_xml = LibXML::XML::Document.io input_zip.get_input_stream(MANIFEST_FILE_NAME)
+
+ # save all pictures - iterate through sheets and pictures and check if they are saved and if not, save them
+ @worksheets.each do |sheet|
+ sheet.images.each do |image|
+ # check if it is saved
+ @ifname = image.internal_filename
+ if @ifname.nil? or input_zip.find_entry(@ifname).nil?
+ # if it does not have name -> make up unused name
+ if @ifname.nil?
+ @ifname = image.internal_filename = Rspreadsheet::Tools.get_unused_filename(input_zip,'Pictures/',File.extname(image.original_filename))
end
+ raise 'Could not set up internal_filename correctly.' if @ifname.nil?
+ raise 'This should not happen' if image.original_filename.nil?
+
+ # save it to zip file
+ save_entry_to_zip(output_zip, @ifname, File.open(image.original_filename,'r').read)
+
+ # make sure it is in manifest
+ if @manifest_xml.find("//manifest:file-entry[@manifest:full-path='#{@ifname}']").empty?
+ node = Tools.prepare_ns_node('manifest','file-entry')
+ Tools.set_ns_attribute(node,'manifest','full-path',@ifname)
+ Tools.set_ns_attribute(node,'manifest','media-type',image.mime)
+ @manifest_xml.find_first("//manifest:manifest") << node
+ end
+ end
end
+ end
- output.put_next_entry(CONTENT_FILE_NAME)
- output.write(@content_xml.to_s(indent: false))
+ # write manifest
+ save_entry_to_zip(output_zip, MANIFEST_FILE_NAME, @manifest_xml.to_s)
+ end
+
+ def copy_internally_without_content_and_manifest(input_zip,output_zip)
+ input_zip.each do |entry|
+ next unless entry.file?
+ next if entry.name == CONTENT_FILE_NAME || entry.name == MANIFEST_FILE_NAME
+ save_entry_to_zip(output_zip, entry.name, entry.get_input_stream.read)
end
end
- alias :to_io :save_to_io
- private
+ def save_entry_to_zip(zip,internal_filename,contents)
+ if zip.kind_of? Zip::File
+# raise [internal_filename,contents].inspect unless File.exists?(internal_filename)
+ zip.get_output_stream(internal_filename) do |f|
+ f.write contents
+ end
+ else
+ zip.put_next_entry(internal_filename)
+ zip.write(contents)
+ end
+ end
+
+ def write_zip_to(io,&block)
+ if io.kind_of? File or io.kind_of? String
+ Zip::File.open(io, 'br+') do |zip|
+ yield zip
+ end
+ elsif io.kind_of? StringIO # or io.kind_of? IO
+ Zip::OutputStream.write_buffer(io) do |zip|
+ yield zip
+ end
+ end
+ end
+
CONTENT_FILE_NAME = 'content.xml'
- TEMPLATE_FILE = (File.dirname(__FILE__)+'/empty_file_template.ods').freeze
+ MANIFEST_FILE_NAME = 'META-INF/manifest.xml'
+ TEMPLATE_FILE_NAME = (File.dirname(__FILE__)+'/empty_file_template.ods').freeze
def register_worksheet(worksheet)
index = worksheets_count+1
@worksheets[index-1]=worksheet
@xmlnode << worksheet.xmlnode if worksheet.xmlnode.doc != @xmlnode.doc
end