Search code examples
rubywindowsfile-permissionsrubyzip

RubyZip Unzipping .docx, modifying, and zipping back up throws Errno::EACCESS error


So, I'm using Nokogiri and Rubyzip to unzip a .docx file, modify the word/docoument.xml file in it (in this case just change every element wrapped in to say "Dreams!"), and then zip it back up.

require 'nokogiri'
require 'zip'

zip = Zip::File.open("apple.docx")
doc = zip.find_entry("word/document.xml")

xml = Nokogiri::XML.parse(doc.get_input_stream)

inputs = xml.root.xpath("//w:t")

inputs.each{|element| element.content = "DREAMS!"}

zip.get_output_stream("word/document.xml", "w") {|f| f.write(xml.to_s)}

zip.close

Running the code through IRB line by line works perfectly and makes the changes to the .docx file as I needed, but if I run the script from the command line

ruby xmltodoc.rb   

I receive the following error:

C:/Ruby193/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.7/lib/zip/file.rb:416:in `rename': Permission denied - (C:/Users/Bane/De
sktop/apple.docx20150326-6016-k9ff1n, apple.docx) (Errno::EACCES)
        from C:/Ruby193/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.7/lib/zip/file.rb:416:in `on_success_replace'
        from C:/Ruby193/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.7/lib/zip/file.rb:308:in `commit'
        from C:/Ruby193/lib/ruby/gems/1.9.1/gems/rubyzip-1.1.7/lib/zip/file.rb:332:in `close'
        from ./xmltodoc.rb:15:in `<main>' 

All users on my computer have all permissions for that .docx file. The file also doesn't have any special settings--just a new file with a paragraph. This error only shows up on Windows, but the script works perfectly on Mac and Ubuntu. Running Powershell as Admin throws the same error. Any ideas?


Solution

  • On my Windows 7 system the following works.

    require 'nokogiri'
    require 'zip'
    
    Zip::File.open("#{File.dirname(__FILE__)}/apple.docx") do |zipfile|
      doc = zipfile.read("word/document.xml")
      xml = Nokogiri::XML.parse(doc)
      inputs = xml.root.xpath("//w:t")
      inputs.each{|element| element.content = "DREAMS!"}
      zipfile.get_output_stream("word/document.xml") {|f| f.write(xml.to_s)}
    end
    

    Instead you also could use the gem docx, here is an example, the names of the bookmarks are in dutch because, well that's the language my MS Office is in.

    require 'docx'
    
    # Create a Docx::Document object for our existing docx file
    doc = Docx::Document.open('C:\Users\Gebruiker\test.docx'.gsub(/\\/,'/'))
    
    # Insert a single line of text after one of our bookmarks
    # p doc.bookmarks['bladwijzer1'].methods
    doc.bookmarks['bladwijzer1'].insert_text_after("Hello world.")
    
    # Insert multiple lines of text at our bookmark
    doc.bookmarks['bladwijzer3'].insert_multiple_lines(['Hello', 'World', 'foo'])
    
    # Save document to specified path
    doc.save('example-edited.docx')