水道屋がRubyを活用する(その3)

うちでは仕事で使う書類は図面を除いてMS Officeを使って作っています。TeXを使うとかFOPを使うとか色々考えたのですが、他の会社などとのやり取りを考えると、MS Officeを使うのが一番かなと思います。さらに、MS OfficeではOLEを使ってアプリケーションをあたかもライブラリのように扱うことができます。Rubyではwin32oleというライブラリがあってこれがものすごくいい仕事をしてくれるわけです。もともと、S式大好き人間だったのですが、OLEを扱える言語で一番Lispに近い言語*1ということでRubyを使い始めて気が付くと離れられなくなったということで、win32oleはすばらしいというより恐ろしい子というイメージです。あと、cygwinから離れられなくなったのも同様の理由です。

さてすばらしい、OLE/win32oleですがRubyに完全に溶け込んでいるかというとちょっと残念という感じです。例えば、メソッド名が大文字で始まるとかコレクションの要素を取り出すのにitem()なるメソッドを使わなければならないとかです。
そこで、Excelを扱うためのライブラリを作ってそれを使っています。ExcelをOLE経由で操作するとものすごくいろいろなことができますが、私が行いたいことはごく限られたことなので、ものすごく限定された機能しかないです。そのやりたいこととは、各行は同じような構造になっているExcelのセルの値を取り出したい、ということです。つまり、Excelを配列として扱いたいということです。

これだけなんですが、なかなか使い道が多くて結構重宝しています。実は、書き込み機能をつけた上位版もあるのですが、これはまた別の機会に紹介したいと思います。

ソースコードはここに置いておきます。
http://gist.github.com/105486

サンプルプログラムとして、こんな感じの見積書がいっぱいあったとき、部品ごとの単価の表を作るプログラムです。

no    名称      規格    数量  単位   単価     金額     備考  

単価は単価表を引いたり、問屋さんに聞いたり、カタログを調べたりして見つけるのですが、一回調べた単価は何度も調べるのは面倒です。かといって見積書を書くたびに単価表を更新していくのは私にはできません。でも、こんなプログラムを時々動かせば、単価表が勝手に更新されていくわけです。

#!/usr/local/bin/ruby -Ks
# -*- coding: cp932 -*-
require 'find'
require 'nkf'
require 'excelscan'

=begin
   単価表を作るプログラム

     見積書をスキャンして部品のリストを作る

=end

# 見積書のあるディレクトリを指定する
# 安全の為コピーを利用する
CURDIR = "c:/見積書/copy"

tanka_list = {}

flist = []
Find::find(CURDIR) do |fname|
  if /.*\.xls$/ =~ fname then
    flist.push [fname, File.mtime(fname)]
  end
end

# 価格が改定される為、新しい見積が前に来るように
# ソートする
flist.sort_by {|item| item[1]}.each do |it|
  fname = it[0]
  if /.*\.xls$/ =~ fname then
    # ExcelScanオブジェクトの生成
    # 引数は、ファイル名、シート名(指定しないのでnil)、Excelを
    # ウインドウ表示するか(falseは表示しない、ハングったらタスク
    # マネージャーの出番)
    scan = ExcelScan.new(fname.gsub(/\//, "\\"), nil, false)

    key = ""
    tan = 0

    # すべてのシートを連結して各行ごとに繰り返す。
    # eleにある行の列が配列(実際には違うけど)のような形式で渡ってくる。
    scan.each_sheet do |ele|
      # 1列目(0から始まる)に品名が2列目に規格(サイズとか材質)とかが入る。
      # 品名と規格でセットで単価が決まるので適当にまとめる
      # ■は区切り文字。品名と仕様で出てこないであろう文字
      key = ele[1].to_s + "" + ele[2].to_s

      # 5列目に単価が入る
      tan = ele[5].to_i

      key = NKF::nkf('-Ss', key)

      if tan == 0 then
        # 単価が入っていない行は何もしない
      elsif tanka_list[key] == nil then
        # まだエントリーが無いときは作る
        tanka_list[key] = [tan]
        
      elsif not tanka_list[key].include?(tan) then
        # すでにあるときは単価をリストの後ろにつける
        tanka_list[key].push(tan)
        
      end
    end
    # ファイルを閉じる。Excelはそのまま残る。!をつけるとファイルが更新されていても
    # 保存しますかと聞かない。なぜか、何も書き込んでいなくても更新しますか?って聞かれる
    # ことがあるから。
    scan.close!
  end
end

# Excelを終了する。表示していないのでこれは絶対必要。
TheExcel.quit

# 結果の表示
tanka_list.keys.sort.each do |key|
	print key, "■■"
	print tanka_list[key].join("")
	print "\n"
end

*1:Allegro Common Lispはあるみたいですが、ソースが手に入る言語処理系がいいなということで・・・