読者です 読者をやめる 読者になる 読者になる

アーキテクチャをスマートに。

株式会社ネオジニア代表。ITアーキテクトとしてのお仕事や考えていることなどをたまに綴っています。(記事の内容は個人の見解に基づくものであり、所属組織を代表するものではありません)

Excelシートに入力されたテキストと画像をSQLiteデータベースに登録する(5)

UTF-8変換

UTF-8SQLiteにデータ登録する方法を考えます。

単純に FileSystemObject ではダメです。UTF-8でファイル出力できません。

ADODB.Stream を使えば UTF-8でファイル出力できるようですが、BOMがついてしまうため、これを回避する必要があります。ストリームのモード変更とストリーム間コピーを駆使して、以下のような手順で実現できました。
・まず ADODB.Stream を使って charset = "UTF-8" でストリームを開く。
・ストリームにテキストを書きだす。
・書き終わったら、ストリームをバイナリモードに変更(type = 1)し、BOMのバイト分スキップ(position = 3)する。バイナリモードに変更する前に、先頭位置に戻しておく(position = 3)必要がある。
・別のストリームを新たに作り、ストリーム間でデータコピーする。BOM以降のバイトだけがコピーされる。
・別のストリームを SaveToFile でファイル保存する。

あとは保存されたファイルを sqlite3.exe に食わせます。リダイレクトするか、-init オプションでファイルを指定すれば読み込んでくれます。(-initオプションで指定する場合も日本語パスはNGなので注意が必要)

こんな感じのコードになりました。

Option Explicit

Dim objFSO :Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
Dim objWshShell : Set objWshShell = WScript.CreateObject("WScript.Shell")

Dim dbpath : dbpath = "C:\テスト\test.db"

MsgBox "SQLite3 のDBパス=" & dbpath

'----- STEP1 -----
'UTF-8のストリームバッファを開く
Dim utf8Stream : Set utf8Stream = CreateObject("ADODB.Stream")
utf8Stream.type = 2
utf8Stream.charset = "UTF-8" '出力ファイルの文字コード設定
utf8Stream.open


'ストリームにテキストを書き出す
utf8Stream.WriteText "CREATE TABLE table2 ( id INTEGER, dat BLOB);" & vbCrLf
utf8Stream.WriteText "INSERT INTO table2 (id, dat) VALUES (1, x'0003070b0f');" & vbCrLf
utf8Stream.WriteText "INSERT INTO table2 (id, dat) VALUES (2, 'テストてすと');" & vbCrLf


'----- STEP2 -----
'UTF-8のストリームを一時ファイルに書き出す。
'ただし、そのまま SaveToFile で書き出すと BOM が付いてしまうため、
'いったんバイナリモードにしてBOM以降のバイトを別のストリームに移す。

'UTF-8からバイナリモードに変更し、BOMを飛ばす
utf8stream.position = 0  'Typeを変更するためいったん先頭へ戻す必要がある
utf8stream.type = 1      '1:バイナリデータ 2:テキストデータ
utf8stream.position = 3  'BOMを飛ばすため3バイトスキップ

'別のストリームを作り、バイナリモードにする
Dim stream2 : Set stream2 = CreateObject("ADODB.Stream")
stream2.type = 1 '1:バイナリデータ 2:テキストデータ
stream2.Open

'ストリーム間データコピー
stream2.Write utf8stream.Read
utf8stream.Close : Set utf8stream = Nothing

'ストリーム内のデータを一時ファイルに書き出す
objWshShell.CurrentDirectory = objFSO.GetParentFolderName(dbpath)  'ここでCDしておく
Dim tempFilePath : tempFilePath = "temp.sql"
stream2.SaveToFile tempFilePath, 2   'これでBOMが付かない

stream2.Close : Set stream2 = Nothing

MsgBox "UTF-8でファイルを保存しました。 " & tempFilePath


'----- STEP3 -----
'SQLite にファイルを食わせる
'★注意:引数のファイルのパスに日本語が含まれていると開いてくれない!文字コードの問題っぽい。
'★回避するためにあらかじめCDしておき、ファイル名のみを指定してコマンド起動する。
Dim cmd : cmd = "C:\sqlite3.exe -init """ & tempFilePath & """ """ & objFSO.GetFileName(dbpath) & """"
MsgBox "コマンド=" & cmd

'コマンド実行!
Dim objExec : Set objExec = objWshShell.Exec(cmd)
objExec.StdIn.Close     '標準入力を閉じる
objExec.StdOut.ReadAll  '出力をすべて読み捨てる
	

'一時ファイルを削除
'objFSO.DeleteFile tempFilePath, True

MsgBox "終了"

役者がそろったところで

さぁ、ここまでで必要な処理がすべて実現できました。

あとはこれまでのコードを組み合わせて、そもそもやりたかったことが実現できます。

こういう処理手順です。

  1. Excelからテキストと画像データを取り出す。
  2. 画像はチャートオブジェクトに貼りつけてExportし、JPEGファイルにしたものをADODB.StreamでByte()として読み込む。
  3. Byte() はそのままでは使えないので Microsoft.XMLDOM を利用して16進文字列に変換する。
  4. SQL INSERT文を組み立て、UTF-8でファイル出力する。BOMがついてしまうので別のストリームにコピーして回避する。
  5. sqlite3.exe にファイルを食わせる。


ややこしいので処理フロー図を描いてみました。

結果的にODBCドライバをインストールする必要もなくなり、sqlite3.exe さえあれば動かすことが出来るので、いい感じのアーキテクチャになって満足しています。

所感

久しぶりに VBScript さわりましたが、なんとも独特の不自由感があり、ちょっとしたことで数時間ハマったりすることが多くて、「使えん」レッテルをはらざるを得ないですね。

プログラミング言語としても好きにはなれませんし、Microsoftのドキュメントも非常に探しにくて読みにくく、とてもじゃないけど生産性の高いプラットフォームとは思えないです。

WindowsのCOMアーキテクチャに則った世界であれば、ActiveXがあれば何でも出来る、と言える反面、ActiveXがなければ何もできないわけです。

ただしUNIX使いなら、UNIX の文化にならって、外部コマンドとの組み合わせや、一時ファイルをデータとして使うような発想ができれば、WindowsのCOMアーキテクチャに則っていない領域でも活用できる可能性が開けるのではないでしょうか。

何事もアイデア次第、というか。ActiveXがないから無理だという発想から抜け出せない限り、VBSでものづくり出来る範囲はかなり限られてきます。

要は「道具をどのように組み合わせて使うか」ということを考えられるかどうか、ですよね。