【Ruby】Microsoft Wordを操作する

MS Wordのワードファイル(.doc)を操作することって結構あると思います。
テンプレートになるファイルを用意して、そこにDBから取ってきた値を書きこんで、、みたいに。


Word2003からはXML形式(.docx)でで記述できるのでだいぶ楽になるのですが、
大人の事情でWord2000を使ってるところも多いですよね(´・ω・`)

素の.docを操作するには、COM/OLEという魔窟を探検しないといけません。
これが結構大変で、、

  • Rubyの文献は少なく、、(´・ω・`)
  • Microsoftのサイト(MSDN)で読めるAPIリファレンスは場所がころころ変わってリンク切れが多い、、(´・ω・`)
  • その上、ようやく見つけたAPIリファレンスには返却値の型が書いてないとか親クラスがわかんないとか(´・ω・`)

魔物は深夜に牙を剥きます。。

なので、MyDocというラッパークラスを作りました。
MyDocを使えば、こんな簡単にWORDファイルを操作できます。

#(1)templete.docの先頭に「送付状.doc」をくっつけて、
#(2)Wordファイル内の「#サイト名#」という文字列を
#「ふわふわ Ruby on Rails」に置き換えて保存するスクリプトだよ。

require 'mydoc'
doc = MyDoc.new('template.doc')
#(1)
doc.concat('送付状.doc', MyDoc::BEGINPOS)
#(2)
doc.replace('サイト名', 'ふわふわ Ruby on Rails').save('ふわふわ Ruby on Rails.doc')
#終了処理
doc.dtor

ね?簡単でしょ♪
じゃあソースコードをご紹介!、、、。なのですが。
このプログラムは仮想キー入力をバリバリ使ってるので、実行中にパソコン操作禁止ですw
動作中はボケーっとしてスローライフを楽しみましょう(´・ω・`)
※一応ちゃんと動きますよw

# -*- encoding: UTF-8 -*-
#UTF8環境で動作します
require 'win32ole'
module Word; end

class MyDoc
	attr_accessor :dir, :crrDir, :basename, :extname
	#文書末尾
	ENDPOS = 1
	#文書先頭
	BEGINPOS = 0
	TAG_BEGIN_CHAR = '#'
	TAG_END_CHAR = '#'
	
	def expand_path(str)
		@fso.getAbsolutePathName(str.tosjis).toutf8
	end
	
	def initialize(path)
		@msword = WIN32OLE.new('Word.Application')
		@fso = WIN32OLE.new('Scripting.FileSystemObject')
		@wsh = WIN32OLE.new('Wscript.Shell')

		@msword.Visible = true
		@path = expand_path(path)
		@crrDir = crrDir = expand_path('.')
		@dir = File.dirname(@path)
		@basename = File.basename(@path, ".*")
		@extname = File.extname(@path)
		
		@msword.Documents.open(@path.tosjis)
	end
	
	#置換(テキストボックスは含まない)
	def replace(fromStr, toStr)
		text = "#{TAG_BEGIN_CHAR}#{fromStr}#{TAG_END_CHAR}".tosjis
		toStr = toStr.tosjis
		p "REPLACE from #{text} to #{toStr}"
		@msword.Selection.Find.ClearFormatting
		@msword.Selection.Find.Replacement.ClearFormatting
		@msword.Selection.Find.Text = text
		@msword.Selection.Find.Replacement.text = toStr
		@msword.Selection.Find.MatchCase = true
		@msword.Selection.Find.Execute(nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,2)
	end

	#WORDファイルの結合。pos:先頭0、末尾:1
	def concat(wordFile, pos = ENDPOS)
		oth = MyDoc.new(wordFile)
		oth.selectAll.copy
		oth.dtor
		cursorTo(pos)
		case pos
		when BEGINPOS
			paste
			@wsh.SendKeys("^{ENTER}")
		else
			@wsh.sendKeys("^{ENTER}")
			paste
		end
		return self
	end
	#カーソル位置の移動。pos:先頭0、末尾:1
	def cursorTo(pos = ENDPOS)
		activate
		case pos
		when BEGINPOS
			key = "{PGUP}"
		else
			#END
			key = "{PGDN}"
		end
		#[]todo 決め打ちは良くないが
		10.times do |i|
			@wsh.SendKeys(key)
			sleep(0.1)
		end
		return self
	end
	
	def selectAll
		p 'selectAll'
		activate
		#もっと良い実装ありそうだけど
		p @basename
		@wsh.SendKeys("^a")
		sleep(0.1)
		return self
	end
	
	def copy
		activate
		p 'copy'
		#もっと良い実装ありそうだけど
		@wsh.SendKeys("^c")
		sleep(1)
		return self
	end
	
	def paste
		activate
		#もっと良い実装ありそうだけど
		@wsh.SendKeys("^v")
		sleep(1)
		return self
	end
	
	def save(path)
		activate
		abPath = expand_path(path)
		@msword.ActiveDocument.SaveAs(abPath.tosjis)
	end
	#タグのところを画像に変換する。必要になったら実装する。。
	def replaceToImage
		raise 'NO IMPLEMENTATION ;-)'
		return self
	end
	
	def sanitize
		activate
		#ゴミのように残ってしまった#.*#を削除する
		sel = @msword.Selection.Range
		sel.Find.Text = "#{TAG_BEGIN_CHAR}*#{TAG_END_CHAR}"
		sel.Find.Replacement.text = ''
		sel.Find.MatchWildcards = true
		#引数「2」は'Replace'=>Word::WdReplaceAll相当
		sel.Find.Execute(nil,nil,nil,nil,nil,nil,nil,nil,nil,nil,2)

	end
	
	def activate
		@msword.Activate
	end
	
	#デストラクタ
	def dtor
		@msword.Quit
	end
end

ホントはちゃんと書きたいのですけど。。
誰か協力して書きませんか!?魔窟探検の協力者募集!(ぇ