Ruby Zip:在打开以写入时无法打开条目以进行读取

Ruby Zip: Cannot open entry for reading while its open for writing

我正在尝试编写一些邮件合并代码,其中我打开一个 docx 文件(作为 zip)用数据替换标签,然后创建一个新的 docx 文件(作为 zip)并遍历旧的 zip 文件,添加我的新替换数据或从旧的 docx 文件中提取现有文件并添加它。

我遇到的问题是每当我尝试访问 out.get_output_stream 方法时,我都会收到以下错误:

cannot open entry for reading while its open for writing - [Content_Types].xml (StandardError)

[Content_Types].xml 恰好是 docx 中的第一个文件,所以这就是它轰炸该特定文件的原因。我做错了什么?

require 'rubygems'
require 'zip' # rubyzip gem

class WordMailMerge
  def self.open(path, &block)
    self.new(path, &block)
  end

  def initialize(path, &block)
    @replace = {}
    if block_given?
      @zip = Zip::File.open(path)
      yield(self)
      @zip.close
    else
      @zip = Zip::File.open(path)
    end
  end

  def force_settings
    @replace["word/settings.xml"] = %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"><w:zoom w:percent="100"/></w:settings>}
  end

  def merge(rec)
    xml = @zip.read("word/document.xml")

    # replace tags with correct content

    @replace["word/document.xml"] = xml
  end

  def save(path)
    Zip::File.open(path, Zip::File::CREATE) do |out|
      @zip.each do |entry|

        if @replace[entry.name]
          # this line creates the error
          out.get_output_stream(entry.name).write(@replace[entry.name])
        else
          # this line also will do it.
          out.get_output_stream(entry.name).write(@zip.read(entry.name))
        end
      end
    end
  end

  def close
    @zip.close
  end
end

w = WordMailMerge.open("Option_2.docx")
w.force_settings
w.merge({})
w.save("Option_2_new.docx")

以下是堆栈跟踪:

/home/aaron/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/delegate.rb:85:in `call': cannot open entry for reading while its open for writing - [Content_Types].xml (StandardError)
    from /home/aaron/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/delegate.rb:85:in `method_missing'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/streamable_stream.rb:28:in `get_input_stream'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/streamable_stream.rb:45:in `write_to_zip_output_stream'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:313:in `block (3 levels) in commit'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/entry_set.rb:38:in `block in each'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/entry_set.rb:37:in `each'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/entry_set.rb:37:in `each'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:312:in `block (2 levels) in commit'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/output_stream.rb:53:in `open'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:311:in `block in commit'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:409:in `block in on_success_replace'
    from /home/aaron/.rvm/rubies/ruby-2.4.1/lib/ruby/2.4.0/tmpdir.rb:130:in `create'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:407:in `on_success_replace'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:310:in `commit'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:334:in `close'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:103:in `ensure in open'
    from /home/aaron/.rvm/gems/ruby-2.4.1@appt/gems/rubyzip-1.2.1/lib/zip/file.rb:103:in `open'
    from zip.rb:34:in `save'
    from zip.rb:56:in `<main>'

您需要将更新代码更改为以下内容

  def save(path)
    Zip::File.open(path, Zip::File::CREATE) do |out|
      @zip.each do |entry|

        if @replace[entry.name]
          # this line creates the error
          out.get_output_stream(entry.name){ |f| f.puts @replace[entry.name] }
        else
          # this line also will do it.
          # out.get_output_stream(entry.name).write(@zip.read(entry.name))
          out.get_output_stream(entry.name){ |f|  f.puts @zip.read(entry.name) }
        end
      end
    end
  end

然后文件将被创建

编辑-1

下面是我用来测试的最终代码

require 'rubygems'
require 'zip' # rubyzip gem

class WordMailMerge
  def self.open(path, &block)
    self.new(path, &block)
  end

  def initialize(path, &block)
    @replace = {}
    if block_given?
      @zip = Zip::File.open(path)
      yield(self)
      @zip.close
    else
      @zip = Zip::File.open(path)
    end
  end

  def force_settings
    @replace["word/settings.xml"] = %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:settings xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main"><w:zoom w:percent="100"/></w:settings>}
  end

  def merge(rec)
    xml = @zip.read("word/document.xml")

    # replace tags with correct content

    @replace["word/document.xml"] = xml.gsub("{name}", "Tarun lalwani")
  end

  def save(path)
    Zip::File.open(path, Zip::File::CREATE) do |out|
      @zip.each do |entry|

        if @replace[entry.name]
          # this line creates the error
          out.get_output_stream(entry.name){ |f| f.puts @replace[entry.name] }
        else
          # this line also will do it.
          # out.get_output_stream(entry.name).write(@zip.read(entry.name))
          out.get_output_stream(entry.name){ |f|  f.puts @zip.read(entry.name) }
        end
      end
    end
  end

  def close
    @zip.close
  end
end

w = WordMailMerge.open("Option_2.docx")
w.force_settings
w.merge({})
w.save("Option_2_new.docx")

Option_2.docx

Option_2_new.doc