I'm trying to write some mail merge code where I open a docx
file (as a zip) replace tags with data and then create a new docx
file (as a zip) and iterate over the old zip file either adding my new replaced data or pulling the existing file from the old docx
file and adding that instead.
The problem I'm getting is anytime I try to access the out.get_output_stream
method, I'm getting the following error:
cannot open entry for reading while its open for writing - [Content_Types].xml (StandardError)
[Content_Types].xml
happens to be first file in the docx
so that's why its bombing on that particular file. What am I doing wrong?
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")
The following is the stack trace:
/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>'
You need to change your update code to below
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
And then the file will get created
Edit-1
Below is the final code that I had used for testing
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