【全角半角・大文字小文字正規化】VBA非依存の組み込み関数のみで構築する文字列クリーニングモジュール
レガシーシステムの深部、あるいは異なる組織間を繋ぐデータインテグレーションの現場において、最も開発者の精神を蝕むもの何か。それは「入力データの揺れ」である。
全角英数、半角カタカナ、全角スペース、そして大文字・小文字の不統一。これらはデータベースのユニーク制約を破壊し、マージ処理を沈黙させ、夜間バッチを容赦なく異常終了へと導く。
通常、Microsoft Office環境やVBA(Visual Basic for Applications)であれば、迷うことなく `StrConv` 関数を使用するだろう。しかし、私たちを待ち受けるVBScript(Visual Basic Scripting Edition)のランタイム環境には、`StrConv` は存在しない。WSH(Windows Script Host)のサンドボックス、あるいは純粋なWSC(Windows Script Component)において、VBAの利便性は最初から剥奪されているのだ。
外部DLLの参照設定や、セキュリティポリシーで阻まれるCOMコンポーネントの動的ロードに頼るべきではない。今回は、VBScriptのプリミティブな文字コード関数(`AscW` / `ChrW`)のみを極限までチューニングし、メモリ効率と実行速度を最適化した文字列クリーニングモジュールの全貌を解き明かす。
—
1. なぜ `StrConv` が使えない環境で苦悩するのか
VBScriptは、VB6の系譜を引く軽量スクリプトエンジンである。だが、その軽量性と引き換えに、多くの標準関数が削ぎ落とされた。
文字列の半角・全角変換を行うために、よく見かけるアンチパターンとして以下の手法がある。
- RegExpオブジェクトの乱用: 置換のために毎回 `RegExp` を生成・破棄し、V8やJScriptエンジンをも凌ぐメモリリークとパフォーマンス低下を招く。
- IE(Internet Explorer)のDOMオブジェクト経由: `htmlfile` を生成してパースする手法。時代遅れであり、現代のWindows環境ではセキュリティパッチやブラウザ廃止の影響で動作が不安定極まりない。
シニアエンジニアとして、我々が選択すべきは「CPUキャッシュヒット率を意識した、文字コードベースの直接変換アルゴリズム」である。
—
2. 文字コード(Unicode)の数学的規則性
全角英数と半角英数、そして大文字と小文字の間には、Unicode(UTF-16)の空間上において、美しくも冷徹な数学的距離(オフセット)が存在する。
1. 半角英数字 (ASCII) と 全角英数字 のオフセット:
- 半角 `A` (U+0041) と 全角A (U+FF21) の差は常に `&HFEE0`(十進数で `-65280` の逆、つまり `+65280`)である。
- 数字や記号も同様に、特定の範囲(U+0021 ~ U+007E と U+FF01 ~ U+FF5E)で一定のオフセット(`+65280` または `-65280`)を維持している。
2. 半角カタカナ と 全角カタカナ:
- 濁点・半濁点を含む複雑な結合文字を除き、基本文字の多くも一定のコードポイントの規則性を持つが、ここでは文字化けの温床となりやすい「半角カタカナの全角化」および「全角英数の半角化、大文字小文字の統一」にフォーカスする。
3. 大文字と小文字のオフセット:
- 小文字 `a` (U+0061) と 大文字 `A` (U+0041) の差は `32`(`&H0020`)である。
この規則性を `AscW`(UTF-16コードポイントを返す)と `ChrW`(コードポイントから文字を生成する)を用いて、メモリ上の配列操作で一気に処理する。
—
3. 実装:VBScript文字列クリーニングモジュール
以下のコードは、外部依存を一切排除し、VBScript単体で動作する堅牢な文字列正規化クラス・モジュールである。実務の巨大なテキストストリーム処理に耐えうるよう、文字列結合のオーバーヘッドを避けるために `InStr` や配列処理の概念を応用している。
‘ ==============================================================================
‘ Module: StringNormalizer.vbs
‘ Description: VBAのStrConvに依存しない、高効率文字列クリーニングモジュール
‘ Author: Chief Architect
‘ ==============================================================================
Option Explicit
Class StringNormalizer
‘ ————————————————————————–
‘ 処理モード定数
‘ ————————————————————————–
Public Const MODE_NONE = 0
Public Const MODE_UPPER = 1
Public Const MODE_LOWER = 2
‘ ————————————————————————–
‘ 全角英数を半角に変換する
‘ @param {String} targetStr 変換対象文字列
‘ @return {String} 変換済み文字列
‘ ————————————————————————–
Public Function ToHalfWidth(ByVal targetStr)
If IsNull(targetStr) Or targetStr = “” Then
ToHalfWidth = “”
Exit Function
End If
Dim i, c, code, buffer
Dim length: length = Len(targetStr)
‘ パフォーマンス最適化のため、配列または配列的処理を想定
‘ VBScriptではMid()の繰り返し結合はO(N^2)になるため、
‘ 大規模データではJoin/Arrayパターンを使用するが、ここでは簡潔さと速度のバランスをとる
Dim result()
ReDim result(length – 1)
For i = 1 To length
c = Mid(targetStr, i, 1)
code = AscW(c)
‘ 負の値(高位サロゲート等)の補正(必要に応じて)
If code < 0 Then code = code + 65536
' 全角英数 (U+FF01 ~ U+FF5E) の場合
If code >= &HFF01 And code <= &HFF5E Then
code = code - &HFEE0
result(i - 1) = ChrW(code)
' 全角スペース (U+3000) の場合
ElseIf code = &H3000 Then
result(i - 1) = " "
Else
result(i - 1) = c
End If
Next
ToHalfWidth = Join(result, "")
End Function
' --------------------------------------------------------------------------
' 半角英数を全角に変換する
' @param {String} targetStr 変換対象文字列
' @return {String} 変換済み文字列
' --------------------------------------------------------------------------
Public Function ToFullWidth(ByVal targetStr)
If IsNull(targetStr) Or targetStr = "" Then
ToFullWidth = ""
Exit Function
End If
Dim i, c, code
Dim length: length = Len(targetStr)
Dim result()
ReDim result(length - 1)
For i = 1 To length
c = Mid(targetStr, i, 1)
code = AscW(c)
If code < 0 Then code = code + 65536
' 半角英数 (U+0021 ~ U+007E) の場合
If code >= &H0021 And code <= &H007E Then
code = code + &HFEE0
result(i - 1) = ChrW(code)
' 半角スペース (U+0020) の場合
ElseIf code = &H0020 Then
result(i - 1) = ChrW(&H3000)
Else
result(i - 1) = c
End If
Next
ToFullWidth = Join(result, "")
End Function
' --------------------------------------------------------------------------
' ケース統一(大文字/小文字)と全角半角の複合クリーニング
' @param {String} targetStr 変換対象文字列
' @param {Boolean} toHalf Trueなら半角化、Falseなら全角化
' @param {Integer} caseMode MODE_NONE, MODE_UPPER, MODE_LOWER
' @return {String} クリーニング済み文字列
' --------------------------------------------------------------------------
Public Function CleanString(ByVal targetStr, ByVal toHalf, ByVal caseMode)
Dim temp
If toHalf Then
temp = ToHalfWidth(targetStr)
Else
temp = ToFullWidth(targetStr)
End If
Select Case caseMode
Case MODE_UPPER
temp = UCase(temp)
Case MODE_LOWER
temp = LCase(temp)
Case Else
' 変更なし
End Select
CleanString = temp
End Function
End Class
---
4. チーフアーキテクトが語る:極限のパフォーマンスとメモリ最適化
上記のコードをプロダクション環境へ投入するにあたり、シニアエンジニアとして知っておくべき「VBScriptの闇と最適化の定石」を共有する。
1. `AscW` の負値トラップ
VBScriptの `AscW` は、内部でUTF-16の符号付き16ビット整数(`Integer`型)として値を返す。そのため、U+8000以上の文字コード(多くの日本語や全角記号)を渡すと、負の値(`-32768` ~ `-1`)として評価される。
これを回避するため、コード内では以下の処理を挟んでいる。
If code < 0 Then code = code + 65536 このビット演算の視点が欠落していると、全角文字の判定ロジックは完全に破綻する。レガシー環境における文字コード処理の基本中の基本である。
2. 文字列結合の計算量爆発を防ぐ `Join` の活用
VBScriptにおいて、ループ内で `str = str & c` を行うと、文字列が生成されるたびにメモリの再割り当てとコピーが発生し、オーダーは $O(N^2)$ へと悪化する。数万行のCSVデータを処理する際、これによりスクリプトが数分間フリーズする現象が多発する。
本モジュールでは、一度 `ReDim` で確保した配列 (`result()`) に文字を格納し、最後に `Join(result, “”)` で一括結合する $O(N)$ のアルゴリズムを採用している。これにより、巨大なテキストであってもミリ秒単位での処理速度を担保する。
3. オブジェクトのライフサイクル管理
WSH環境でVBScriptを実行する場合、メモリリークはスクリプトホスト(`wscript.exe` / `cscript.exe`)のプロセス寿命に直結する。特にCOMオブジェクトや外部インスタンスを生成・破棄する際は、明示的な解放(`Set obj = Nothing`)が不可欠である。
今回作成したクラスは純粋なVBScriptのカスタムクラスであるため、スコープを抜ければガベージコレクタによって自動解放されるが、長期稼働する常駐型WSCやタスクランナーから呼び出す場合は、インスタンスの使い回し(プーリング思想)を意識設計に組み込むべきである。
—
5. 実行例(ドライバスクリプト)
このモジュールを実務でどのようにインクルードし、活用するか。以下にテスト用のドライバコードを示す。
‘ — テスト実行スクリプト (TestRunner.vbs) —
Option Explicit
‘ 外部ファイルとしてインクルードする場合のイディオム
‘ (ExecuteGlobalとFileSystemObjectを組み合わせる)
Dim fso, stream, codeText
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set stream = fso.OpenTextFile(“StringNormalizer.vbs”, 1)
codeText = stream.ReadAll
stream.Close
Set stream = Nothing
Set fso = Nothing
‘ グローバル空間にクラスをロード
ExecuteGlobal codeText
‘ ————————————————————————–
‘ 検証
‘ ————————————————————————–
Dim normalizer
Set normalizer = New StringNormalizer
Dim rawData, cleanedData
rawData = “ABCDEFG 1234567 abcdefg”
‘ 全角英数を半角にし、大文字に統一する
cleanedData = normalizer.CleanString(rawData, True, StringNormalizer.MODE_UPPER)
WScript.Echo “入力: ” & rawData
WScript.Echo “出力: ” & cleanedData
‘ 期待される出力: ABCDEFG 1234567 ABCDEFG
Set normalizer = Nothing
—
総括
VBScriptという枯れた言語、そして制限された実行環境。しかし、そこにどのような困難な制約があろうとも、文字コードの物理構造とメモリの挙動さえ完全に掌握していれば、VBA依存から完全に脱却した堅牢なシステムを構築することは容易い。
「ライブラリがないからできない」のではない。「構造を理解していないから書けない」のだ。
このモジュールをあなたのレガシー基盤の武器庫に加え、データ揺れによるシステム障害の夜間呼び出しから解放されることを願う。技術者としての誇りをかけたコードを、現場の最前線で役立ててほしい。
