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