スモールデータ集計の技

VBSを使ってExcelやCSVを操作し、ハイレベルなデータ集計技をお伝えします。

CSVファイル読み込み技

今回はいよいよCSVを取り上げます。
下記のような売り上げデータのCSVファイルがあります。

No.日時顧客名顧客属性来店種別利用金額
12020/10/4"住処商事"xxxxxxxxxx1300
22020/10/25"香料販売"xxxxxxxxxx14340
32020/10/25"Satoru co., Ltd"xxxxxxxxxx2340
.......................
CSVファイルの区分け記号(デリミタ)は一般的に","です。VBSでは特定の文字で、文字列を分割してくれるSplit関数があるので、1行をSplit関数を使って","で分割すれば良いように思えます。
しかし、上記データの"Satoru co., Ltd"のように、「一つのセルの中に","を含む」場合には、列がずれてしまいます。そこで、「文字列に","を含んでいても、ダブルクウォートでくくってあれば、一つのセルと解釈する。」関数を用意し、テンプレートに組み込みました。これを使えば、列のずれなくデータとして扱うことができます。
プログラムを紹介します。

const DQ = """"  'ダブルクォート
const Delimita = ","
const HeaderLine = 1
set fso = CreateObject("Scripting.FileSystemObject")
set args = WScript.Arguments

ParseCSV  args(arg)
WScript.Quit


Sub ParseCSV( fileName )
  DIM line, fileo, lineCount, schema, oneLine

  if fso.FileExists( filename ) then
	  set fileo = fso.OpenTextFile(fileName , 1, false, false)
	  set schema = CreateObject("Scripting.Dictionary")
	  set oneLine = CreateObject("Scripting.Dictionary")
	  linecount = 0
	  do until fileo.AtEndofStream
	    line = fileo.ReadLine()
	    if left( line, 1) <> "#" then
		    linecount = linecount + 1
		    if linecount = HeaderLine then  'タイトル行
		        call ParseSchema( line, schema )  
		    elseif linecount > HeaderLine then
		          if ParseLine( line, oneLine ) = 1 then '解析有効
		             '   処理したい内容をここに記述
		              WScript.echo Trim(oneLine.Item(schema.Item("顧客名")))
		              WScript.echo Trim(oneLine.Item(schema.Item("利用金額")))
		          end if
		    end if
	    end if
	  loop
	  fileo.close
  else
        WScript.echo "Can't file " & filename
  end if
end sub


Sub ParseSchema( line, schema )
  dim columns, i
  line = Replace( line, DQ, "" )
  schema.removeall
  columns = split(line, Delimita , -1)
  for i = 0 to Ubound(columns)
    if Trim(columns(i)) <> "" then
      schema.add Trim(columns(i)), i+1
    end if
  next
end sub

Function ParseLine( line, rowData )
  dim c, cp, qp, q, l, tmp
  rowData.RemoveAll()
  ParseLine = 0
  c = 1          'column ID
  q = 0          'quote mode
  if left(line,1) = "#" then
    ParseLine = false
    exit function
  end if
  l = trim(line)
  do while len(l) > 0
    if Left(l, 1) = DQ then
      q = 1
      l = Mid( l, 2, len(l)-1)    '左端の"をカット
    else
      q = 0
    end if
    if q then
      qp = InStr(l, DQ)
      if qp = 0 then 
         WScript.echo "フォーマットエラー:" & line
         exit function
      else
         tmp = left(l, qp -1 )
         if len(trim(tmp)) = 0 then
           rowData.add c, ""
         else
           rowData.add c, tmp
         end if
         l = mid( l, qp + 1, len(l)- qp )
         cp = InStr(l, ",")
         if cp = 0 then
           ParseLine = 1
           exit function
         end if
         l = mid( l, cp + 1, len(l)- cp )
         c = c + 1
      end if
    else
      cp = InStr(l, ",")
      if cp = 0 then
        rowData.add c, trim(l)
        ParseLine = 1
        exit function
      end if
      tmp = left(l, cp - 1)
      if len(trim(tmp)) = 0 then
        rowData.add c , ""
      else
        rowData.add c, tmp
      end if
      l = mid( l, cp + 1, len(l)- cp ) 
      c = c + 1
    end if
  loop
  ParseLine = 1
end Function

このスクリプト
>cscript CSV-Read.vbs Sample.csv
のように呼び出すと
 住友商事
 1300
香料販売
 14340
Satoru co., Ltd
2340
のように表示されるはずです。
ポイントは、”ParseSchema( line, schema)”という部分で、lineはヘッダーとなる行がはいっており、この関数を呼び出すとschemaディクショナリのキーが列名で、アイテムに列番号が保存されます。さらにデータ行はParseLine( line, oneLine)で、lineにはデータ行を渡し、この関数を呼び出すと、oneLineディクショナリにキーが列番号で、アイテムにデータが保存されます。
これによりoneLine.item(schema.item(列名))の戻り値が、その列のデータになります。プログラムでは列番号を直接指定しませんので、列が何列目かは関係ありません。プログラムを作成するうえで、何列目かを調べる必要はありませんし、データファイルの列の並びが変わってもプログラムを変更する必要はありません。
ここでParseSchemaはタイトル行を”,"で分割して解析をしています。タイトルの一部に","が来ることはないという想定です。しかし、データの文字列に","が含まれることはよくあるため、それを考慮して解析するのがParseLine関数です。関数の中身は解説しません。使用法だけわかっていれば十分と考えています。
この方法はかなり応用が広く、CSVの解析一般で利用可能です。是非自分で適当なCSVファイルについて、データを読み出してみてください。