それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

EXCELを扱って言語比較してみる。js,py,rb,vbs,php

仕事でEXCELを弄るのでどの言語が楽ちんか比較してみた。

試したもの

言語 ライブラリ
JScript(WSH) activeX*1
ruby win32ole
python win32com
vbscript createobject*2
php(PECL) COM

JavaScript(WSH)とRubyのソースファイルを発掘した。Javaも何処かにあったと思うんだけれど。見つからない。

次のようなシートをCSVにする。

作成日 更新日 ファイル名 説明 サイズ
2008-12-31 2008-12-31 Sample.jpg 兼六園にいったときの写真 122222
2008-12-31 2008-12-31 0801222.jpg 兼六園にいったときの写真
 家族全員で。
104532
  1. セル内部改行は<br>に置換。
  2. 空白はTrim。
  3. タブはスペースに置換。

EXCEL処理やバッチ処理に向いているスクリプト言語を考えてみました。

RubyでEXCEL処理サンプル

るびま」とRubyOnWindows(Cuzic)を参考に。というか写経。

require 'win32ole'

def getAbsPath filename
  fso = WIN32OLE.new('Scripting.FileSystemObject')
  return fso.GetAbsolutePathName(filename)
end

name = getAbsPath("Book1.xls")
x1 = WIN32OLE.new('Excel.Application')
book = x1.Workbooks.Open( name )

begin
  book.Worksheets.each do |sheet|
    sheet.UsedRange.Rows.each do | row |
      record = []
      row.Columns.each do |cell|
        val = cell.Value
        val.gsub!(/\r\n|\r|\n/, '<br/>' ) if val != nil and val.class == String
        val.gsub!(/\t/, '  ' ) if val != nil and val.class == String
        val.strip! if val != nil and val.class == String
        if val then
          record << "'#{val}'"
        else
          record << "''"
        end
      end
      puts record.join(',')
    end
  end
ensure
  book.Close
  x1.Quit
end

JavaScript(WSH)でEXCEL

JScriptはWindowsでバッチに使える。COMも使える。当然EXCEL処理ができる。
Rubyサンプルと同じルーチンにした。WSHのCOMでは(for i in obj)でループを書けない。Enumerableを使うのがかなり面倒。
Enumerableがもっとシンプルになれば、JSでやる気がでる。JavaScriptなのでid:amachangあたりが何とかしてくれると期待。

//EXCEL処理サンプル
function getAbsPath ( filename ){
  fso = new ActiveXObject('Scripting.FileSystemObject')
  return fso.GetAbsolutePathName(filename)
}

var name = getAbsPath("Book1.xls")
var x1 = new ActiveXObject("EXCEL.Application");
var book = x1.Workbooks.Open( name );


try {
  
  for( var p_sheet = new Enumerator( book.Worksheets );!p_sheet.atEnd();p_sheet.moveNext() ){
    var sheet = p_sheet.item();
    for(var p_row = new Enumerator(sheet.UsedRange.Rows);!p_row.atEnd();p_row.moveNext() ){
      var row = p_row.item();
      var record = [];
      for( var p_cell = new Enumerator(row.Columns);!p_cell.atEnd();p_cell.moveNext()){
        var cell = p_cell.item();
        val = cell.Value;
        val= (typeof val == "string") ? val.replace( /\r\n|\r|\n/g, '<br/>' ) : val;
        val= (typeof val == "string") ? val.replace( /\t/g, '  ' ) : val;
        val= (typeof val == "string") ? val.replace(/^\s*(.*?)\s*$/, "$1"): val ;
        if(val){
          record.push("'"+val+"'");
        }else {
          record.push("''");
        }
      }
      WScript.Echo( record.join(',')+"\n" );
    }
  }
}finally{
  book.Close();
  x1.Quit();
}

PHP (PECL)でEXCEL

変態言語PHPも、COMを使うことができる。
php 5.2xにはfinallyがない。本当の話。"finallyは存在しない"。
あと、日付と数値が全部文字列になった。16.0⇒16, "2008/12/31 00:00:00"⇒"2008/12/31"
内部エンコード使わないから、変なスクリプト言語なのに文字コード気にしなくてイイ。

<?php

function getAbsPath ($filename){
  $fso = new COM('Scripting.FileSystemObject');
  return $fso->GetAbsolutePathName($filename);
}

$name = getAbsPath("Book1.xls");
$x1 = new COM('Excel.Application');
$book = $x1->Workbooks->Open( $name );
try {
  foreach ( $book->Worksheets as $sheet ) {
    foreach ( $sheet->UsedRange->Rows as $row  ) {
      $record = array();
      foreach ( $row->Columns as $cell ){
        $val = $cell->Value;
        $val = preg_replace( '/\r\n|\r|\n/', "<br/>", $val );
        $val = preg_replace( '/\t/', "  ", $val );
        $val = trim($val);
        if($val){
          $record[] = "'{$val}'";
        }else{
          $record[] = "''";
        }
      }
      echo implode(",", $record). "\n";
    }
  }
}catch(Exception $e){
  print($e->message);
}
//php 5.2xにはfinallyがない。
//本当の話。"finallyは存在しない"。
$book->Close();
$x1->Quit();

VbScriptの例

Try..Catch..Finallyがなかったり、可変長配列が無かったり。とても面倒。もう二度とやりたくない。
実際やるなら、.NetFrameWorkのCollectionをCreateObjectで利用するとよさそう。

'EXCEL処理サンプル
Option Explicit


Function getAbsPath ( filename )
  Dim fso
  Set fso = CreateObject("Scripting.FileSystemObject")
  getAbsPath=fso.GetAbsolutePathName(filename)
End Function
Dim name
name = getAbsPath("Book1.xls")
Dim xl,book
Set xl= CreateObject("Excel.Application")
Set book = xl.Workbooks.Open(name)


'VBScriptにはTry..Cacth..Finallyが無いみたい。
'Try関数を作って実装するといいそうです。
'http://scripting.cocolog-nifty.com/blog/2006/12/on_error_resume_d841.html
Sub Try
  Dim sheet
  Dim record()
  For Each sheet In book.Worksheets
    Dim row
      For Each row In sheet.UsedRange.Rows
        Dim cell,idx
        reDim record(row.Columns.Count)
        idx=0
      For Each cell in row.Columns
          Dim val,regx
          Set regx = new RegExp
          regx.Global = True
          val = cell.Value
          regx.pattern= Chr(10)&Chr(13)&"|"&Chr(10)&"|"&Chr(13)'"\r\n|\r|\n"
          val = regx.Replace(val,"<br/>")
          regx.pattern= Chr(9) ' "\t"
          val = regx.Replace(val,"  ")
          val = Trim(val)
          record(idx) = val
          idx=idx+1
        Next
        WScript.Echo Join( record, "," )
      Next
  Next

End Sub
On Error Resume Next
Call Try()
Sub Catch
  If Err<>0 Then
    WScript.Echo Err.Description
  End If
  On Error Resume Next
End Sub
Call Catch()
Sub Finally
  book.Close()
  xl.Quit()
End Sub
Call Finally()

pythonでEXCEL処理

文字列エンコードが基本的にキモい。isinstanceもキモい。文字列がstr/unicodeのTYPEなのでありオブジェクトではない。Javaでいうプリミティブ型ようなもの。これがオブジェクト指向にならないし。なんか調べにくい。Python3000ならもっと楽だろうな。

#coding:utf-8
import win32com.client
import re
def getAbsPath( filename ) :
  fso = win32com.client.Dispatch('Scripting.FileSystemObject')
  return fso.GetAbsolutePathName(filename)

name = getAbsPath("Book1.xls");
xl = win32com.client.Dispatch("Excel.Application")
book = xl.WorkBooks.Open( name )

try:
  for sheet in book.Worksheets :
    for row in sheet.UsedRange.Rows:
      record = []
      for cell in row.Columns :
        val =cell.Value
        val = re.sub(r'\r\n|\r|\n', "<br/>",val)   if val and isinstance(val,basestring) else val
        val = re.sub(r'\t/g', " ",val)             if val and isinstance(val,basestring) else val
        val = val.strip()                          if val and isinstance(val,basestring) else val
        if val:
          record.append( "'"+unicode(val).encode("cp932",'replace')+"'" )
        else:
          record.append( "''" )
      print ",".join( record )

finally:
  book.Close();
  xl.Quit()

言語別の感想

言語 感想 ハマリどころ
PHP 暴走野郎。勝手にゴリゴリ進む。 日付・数値セルが文字列になる。他言語はちゃんとFloatやDateになってた。*3
JScript 書きやすい。ループコード汚い ActiveXオブジェクトはEnumerableを使うのがネック
Python 文字列とエンコードがキモい。相変わらずドキュメント稀少 joinがキモい。内部エンコードに暗黙変換するが、出力は暗黙変換しない。頼んでもいないエンコで容赦なくエラー。win32comのインストールが面倒だった。
VbScript 良くこんなもので仕事できるな VBと別物がネック。ハッシュ無し。可変長フィールドなし。エラー処理不可。*4
ruby ループが独特。あとは理解しやすい。行儀がいいね。 破壊的メソッドと非破壊メソッドではまるかもね。
perl そのうち試す。 ActivePerlを入れてる人少ない。
IronPython そのうち試す。 .NetFrameWorkは行儀がよい予感??
Java ソース行方不明 相変わらずタイピングの量が鬼。あとデザインパターン知らないと厳しいかも

EXCELを各種言語で処理利する処理スクリプト。

EXCEL⇒CSVする。EmEditorがあれば、コピペで終わる。だけど、各種言語から扱うことで、
COMの基本とか言語の基本勉強になってちょうどいい。

エディタにコピペするとセル内改行が!!!

セル内で改行は便利だけど、テキストエディタと相性が悪い。改行\r\nを<br/>に変換したい。


ファイルIOと、パス、正規表現、そしてオブジェクト扱い。基本的要素が詰まっている、
基本入出力ルーチン比較になる。


結論。JScript/rubyがすごくいい。

*1:ライブラリではないけど、ActiveXインスタンス化ってことで

*2:ライブラリでないけど、CreateObjectでインスタンス化ってことで

*3:正確にはミリ秒まで反映した文字列。小数点まで配慮した数値。PHPは容赦なく表示形式と同じ文字列になってて驚いた。

*4:2009-04-08追記:VBScriptはScripting.Dictionary オブジェクトを使うのが正統っぽい。CreateObject("Scripting.Dictionary ");