upinetree's memo

Web系技術の話題や日常について。

Rubyのwin32oleでWordファイルをPDFに一括変換する

WORD→PDF→結合という変換は結構な頻度で定期的に発生する作業だったのですが、 いい加減めんどくさいので自動化します。

Office2007以上であればPDF保存できるので、Win32OLEを使ってやりましょう。

下記エントリがとても参考になりました。

と、単に自動化したいのであれば上記エントリだけで済んでしまうので、ここでは僕がやったことを載せてみます。

やりたいこと

  • 指定したWordファイルを全てPDFに変換
  • 変換したら一つのPDFに結合

Word → PDF変換

WordはnewしたらQuitしないと開きっぱなしになるので、おっちょこちょいな僕としては怖いところです。 なので、確実にQuitできるようにちょっと細工します。

EAM(Execute Around Method)というのがあるらしいので、それを使いましょう。

※define_finalizerを使う手もありましたが、こっちのほうが面白そうだったので

require "win32ole"

class WordUtil
    def initialize
        @word_app = WIN32OLE.new("Word.Application")
    end

    def quit
        if @word_app then @word_app.Quit end
    end

    def to_pdf(doc_name)
        doc_fullpath = File.expand_path(doc_name)
        pdf_fullpath = doc_fullpath.gsub(/\.doc$/, ".pdf")

        begin
            doc = @word_app.Documents.Open(doc_fullpath)
            doc.ExportAsFixedFormat({
                "OutputFileName" => pdf_fullpath,
                "ExportFormat" => 17,            # wdExportFormatPDF
                "OpenAfterExport" => false
            })

            puts "success: #{doc_fullpath}"

        rescue
            puts "fail: #{doc_fullpath}"

        ensure
            doc.Close unless doc.nil?

        end
    end
end

class WordManager
    def WordManager.run_during(&block)
        begin
            word = WordUtil.new
            block.call(word)

        ensure
            word.quit

        end
    end
end


input_docs = ARGV[0]
input_docs = "*.doc" if input_docs.nil?

output_pdfs = input_docs.gsub(/\.doc$/, ".pdf")

puts "input docs: " + input_docs
puts "output pdfs: " + output_pdfs

# convert to pdf
WordManager.run_during do |word|
    Dir.glob(input_docs) do |fn|
        word.to_pdf(fn)
    end
end

WordManagerにブロックを渡して変換してもらっています。 これなら解放漏れしないね!

PDFを結合する

出力したPDFを結合するにはいくつか方法がありそうでしたが、今回は一番簡単そうだったpdftkを呼び出します。

今回やりたいPDFの結合はちょっと注意が必要でして。

  • 指定した順番で結合
  • ファイル名は毎回一定パターンで変わる

なので、yamlにキーワード並べて、その順番でパターンマッチングして結合、ということをしてます。 なんとめんどくさい。

require "yaml"

class PDFCat
    def initialize(pdf_names, pdf_keys)
        @sorted_pdf_names = pdf_keys.map do |key|
            Dir.glob(pdf_names) do |fn|
                break fn if /#{key}/ =~ fn
            end
        end
    end

    def concatenate(export_fname)
        cat_list = @sorted_pdf_names.join(" ")
        puts "files to be concatenated:\n    " + cat_list.split(" ").join("\n    ")
        puts "export file name: #{export_fname}"

        `"C:/Program Files/PDF Labs/PDFtk Server/bin/pdftk" #{cat_list} cat output #{export_fname}`
    end

end


# concatenate pdf files
yaml_fname = ENV['HOME'] + "/bin/pdf_keys.yaml"
yaml_data = YAML.load_file(yaml_fname)
pdfcat = PDFCat.new(output_pdfs, yaml_data['sorted_keyword_list'])

export_fname = 'catcatcat.pdf'
pdfcat.concatenate(export_fname)

もうちょいリファクタリングできそうですね。

でも疲れたから寝る。

参考サイト