dRuby を使って Excel ファイルの中身を配信

趣旨

私は Windows (ホストOS) + VWMare 上の Linux(ゲストOS) という構成で開発を行っているのだが、どうしても Linux 側から Excel ファイルを読みたくなった。そこで、dRuby を使って Windows 側から Excel ファイルの中身を読み出す Ruby プログラムを書いてみた。拙いコードだが皆さんの参考になれば幸いである。

使い方

サーバ側

下のソースコードexcel_server.rb というファイル名で Windows マシン上に保存する。いま仮に C:\ に保存したとする。次のようにして実行。

C:\>ruby excel_server.rb
druby://windows_pc_name:12345
Press any key to quit.

となるはずだ。これでサーバ側の準備 OK。

クライアント側
% irb --simple-prompt
>> require 'drb/drb'
=> true
>> server = DRbObject.new_with_uri('druby://windows_pc_name:12345')
=> #<DRb::DRbObject:0x401d07c8 @ref=nil, @uri="druby://windows_pc_name:12345">
>> server.read_worksheet('C:\test1.xls')
=> {"Sheet1"=>"d\r\nb\r\nd\r\n", "Sheet2"=>"a\tb\r\nc\td\r\n", "Sheet3"=>"a\r\nb\r\n"}

のように使う。ExcelServer#read_worksheet の第 1 引数に Excel ファイルへのパスを指定すると、「シート名 => シートの内容」というハッシュを UTF-8 で返してくれる。

その他の注意

サーバコマンド excel_server.rb は

C:\>ruby excel_server.rb my_password
C:\>ruby excel_server.rb nil druby://:22222
C:\>ruby excel_server.rb my_password druby://:22222

のように(パスワード) (URI) という2つの引数を取ることができる。パスワードを指定した場合は、クライアント側で read_worksheet の第2引数にパスワードを指定しないと Access denied エラーとなる。

server.read_worksheet('C:\test1.xls', 'my_password')

という感じに使う。まあ、貧弱なセキュリティ措置で焼け石に水という感じではあるが、ないよりはましだろう。

ソースコード

require 'rubygems'
require "win32/clipboard"
require 'win32ole'
require 'kconv'
require 'drb/drb'
require 'Win32API'

class ExcelServer

  def initialize(options = nil)
    options = options ? options.dup : {}
    @password = options[:password]
  end
  
  def authorized?(password)
    @password.nil? || @password == password
  end

  # シート名 => シート内容というハッシュを得る。
  def read_worksheet(file_path, password = nil, return_cache = false)
    raise "Access denied." unless authorized?(password)
    
    @workbook_content ||= {}
    return @workbook_content if return_cache && @workbook_content != {}

    @excel ||= WIN32OLE.new('Excel.Application') 
    @excel.visible = false
    @excel.displayAlerts = false
    
    @excel.workbooks.open 'filename' => file_path
    i = 1
    count = @excel.sheets.count 
    puts "Requested: file_path = #{file_path}, sheets.count = #{count}"
    while i <= count
      sheet = @excel.sheets(i)
      sheet.select
      @excel.cells.select
      @excel.selection.copy
      s = Win32::Clipboard.data
      @workbook_content[sheet.name] = s.toutf8
      i += 1  
    end
    @workbook_content    
  end
  
  def shutdown  
    @excel.quit
  end
  
  def self.start_service(options = nil)
    begin
      options = options ? options.dup : {}
      options[:uri] ||= "druby://:12345"
      server = ExcelServer.new(options)
      DRb.start_service(options[:uri], server) 
      puts DRb.uri
      kbhit = Win32API.new('msvcrt','_kbhit',[],'l')
      print 'Press any key to quit.'
      while true
         sleep 0.5
         if kbhit.call != 0
            break
         end
      end
      puts "\nA key is pressed."
    ensure
      server.shutdown if server
    end
  end
end

password = ARGV.shift
password = password == "nil" ? nil : password
uri = ARGV.shift 
ExcelServer.start_service(:uri => uri, :password => password)