Как редактировать docx с помощью nokogiri и rubyzip - PullRequest
8 голосов
/ 08 октября 2010

Я использую комбинацию rubyzip и nokogiri для редактирования файла .docx.Я использую rubyzip, чтобы распаковать файл .docx, а затем использую nokogiri, чтобы проанализировать и изменить тело файла word / document.xml, но каждый раз, когда я закрываю rubyzip в конце, он повреждает файл, и я не могу открыть его иливосстановить его.Я распаковываю файл .docx на рабочем столе и проверяю файл word / document.xml, и содержимое обновляется до того, что я изменил, но все остальные файлы испорчены.Может ли кто-нибудь помочь мне с этим вопросом?Вот мой код:

require 'rubygems'  
require 'zip/zip'  
require 'nokogiri'  
zip = Zip::ZipFile.open("test.docx")  
doc = zip.find_entry("word/document.xml")  
xml = Nokogiri::XML.parse(doc.get_input_stream)  
wt = xml.root.xpath("//w:t", {"w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}).first  
wt.content = "New Text"  
zip.get_output_stream("word/document.xml") {|f| f << xml.to_s}  
zip.close

Ответы [ 3 ]

12 голосов
/ 12 января 2011

Вчера вечером я столкнулся с той же проблемой коррупции с rubyzip. Я решил это, скопировав все в новый zip-файл, заменив файлы по мере необходимости.

Вот мое рабочее доказательство концепции:

#!/usr/bin/env ruby

require 'rubygems'
require 'zip/zip' # rubyzip gem
require 'nokogiri'

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

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

  def merge(rec)
    xml = @zip.read("word/document.xml")
    doc = Nokogiri::XML(xml) {|x| x.noent}
    (doc/"//w:fldSimple").each do |field|
      if field.attributes['instr'].value =~ /MERGEFIELD (\S+)/
        text_node = (field/".//w:t").first
        if text_node
          text_node.inner_html = rec[$1].to_s
        else
          puts "No text node for #{$1}"
        end
      end
    end
    @replace["word/document.xml"] = doc.serialize :save_with => 0
  end

  def save(path)
    Zip::ZipFile.open(path, Zip::ZipFile::CREATE) do |out|
      @zip.each do |entry|
        out.get_output_stream(entry.name) do |o|
          if @replace[entry.name]
            o.write(@replace[entry.name])
          else
            o.write(@zip.read(entry.name))
          end
        end
      end
    end
    @zip.close
  end
end

if __FILE__ == $0
  file = ARGV[0]
  out_file = ARGV[1] || file.sub(/\.docx/, ' Merged.docx')
  w = WordXmlFile.open(file) 
  w.force_settings
  w.merge('First_Name' => 'Eric', 'Last_Name' => 'Mason')
  w.save(out_file)
end
1 голос
/ 01 февраля 2014

Согласно официальной документации Github , вам следует Use write_buffer instead open. Также по ссылке приведен пример кода.

1 голос
/ 08 ноября 2010

Я наткнулся на пост и ничего не знаю о рубине или нокогири, но ...

Похоже, вы неправильно распаковываете новый контент. Я не знаю насчет rubyzip, но вам нужно как-то сказать ему обновить слово ввода / document.xml а затем повторно сохраните / распакуйте файл.

Похоже, вы просто перезаписываете запись новыми данными, которые, разумеется, будут другого размера и полностью испортят оставшуюся часть zip-файла.

Я привожу пример для Excel в этом посте Разбор текстового файла и создание отчета в Excel

, который может быть полезен, хотя я использую другую библиотеку zip и VB (я все еще делаю именно то, что вы пытаетесь сделать, мой код примерно на полпути)

вот та часть, которая применяется

Using z As ZipFile = ZipFile.Read(xlStream.BaseStream) 
'Grab Sheet 1 out of the file parts and read it into a string. 
Dim myEntry As ZipEntry = z("xl/worksheets/sheet1.xml") 
Dim msSheet1 As New MemoryStream 
myEntry.Extract(msSheet1) 
msSheet1.Position = 0 
Dim sr As New StreamReader(msSheet1) 
Dim strXMLData As String = sr.ReadToEnd 

'Grab the data in the empty sheet and swap out the data that I want  
Dim str2 As XElement = CreateSheetData(tbl) 
Dim strReplace As String = strXMLData.Replace("<sheetData/>", str2.ToString) 
z.UpdateEntry("xl/worksheets/sheet1.xml", strReplace) 
'This just rezips the file with the new data it doesnt save to disk 
z.Save(fiRet.FullName) 
End Using 
...