【URLエンコード実装】MSXML2.DOMDocument の nodeTypedValue を利用した文字列の相互変換
レガシーシステムの保全、あるいはWindows環境における軽量な自動化スクリプトの基盤として、VBScript(Visual Basic Scripting Edition)いまだその生命力を失ってはいない。特に、サードパーティ製ライブラリのインストールが厳しく制限されるエンタープライズ環境において、WSH(Windows Script Host)と標準COMコンポーネントのみで完結するソリューションは、システムアーキテクトにとって最後の砦となる。
近代的なWeb APIとの連携において避けて通れないのが「URLエンコード(Percent Encoding)」およびそのデコード処理である。標準のVBScriptランタイムには、JavaScriptの `encodeURIComponent` や .NETの `HttpUtility.UrlEncode` に相当するネイティブ関数が存在しない。
かつては `Server.URLEncode`(ASP環境に依存)や、JScriptエンジンを `Eval` 経由で呼び出すというハックが多用されたが、前者はIISが必須であり、後者はパフォーマンスとセキュリティ面で難がある。
我々はここで、Windows標準コンポーネントである `MSXML2.DOMDocument` のバイナリ処理能力、とりわけ `nodeTypedValue` を駆使した極限の文字列・バイト相互変換メカニズムを解剖する。外部依存ゼロ、純粋なCOMの奥義によるURLエンコード実装の真髄を示す。
—
1. なぜ `MSXML2.DOMDocument` なのか?
VBScriptでバイト配列(`Byte()`)を扱う場合、通常はADODB.StreamやCAPICOM(既に廃止)が検討される。しかし、MSXML2.DOMDocumentのXMLDOMNodeが持つ `dataType` プロパティと `nodeTypedValue` を組み合わせることで、UTF-8などの任意の文字コードにおけるバイト列表現と、Base64や16進数(Hex)文字列との間のシームレスな変換が可能になる。
URLエンコードの本質は以下の2点に集約される。
1. 対象文字列を UTF-8バイト列 に変換する。
2. 非安全な文字(アルファベット・数字・一部の記号を除く)を `%XX` 形式の16進数表現に置換する。
DOMDocumentの `bin.hex` データ型を利用すれば、VBScriptの貧弱な文字列操作から脱却し、高速かつ確実にバイトストリームを操作できる。
—
2. 実装コード:標準コンポーネントによる完結型ライブラリ
以下のコードは、外部依存を一切排除し、WSH(cscript.exe / wscript.exe)単体で動作するURLエンコード・デコードのフル実装である。
プロセスのライフサイクル管理、COMオブジェクトの適切な解放(メモリリークの根絶)といった、チーフアーキテクトとしての最低限の作法も網羅している。
‘ ==============================================================================
‘ File: UrlCodec.vbs
‘ Description: MSXML2.DOMDocumentを利用した純粋なURLエンコード/デコード実装
‘ Author: Chief Architect
‘ ==============================================================================
Option Explicit
‘ テスト実行
Sub Main()
Dim targetStr, encodedStr, decodedStr
targetStr = “こんにちは、VBScriptの世界へ!URL Encode/Decode #1?test=1&val=2”
WScript.Echo “— Original —”
WScript.Echo targetStr
encodedStr = UrlEncode(targetStr)
WScript.Echo vbCrLf & “— Encoded —”
WScript.Echo encodedStr
decodedStr = UrlDecode(encodedStr)
WScript.Echo vbCrLf & “— Decoded —”
WScript.Echo decodedStr
End Sub
‘ ——————————————————————————
‘ URLエンコード関数 (UTF-8ベース)
‘ ——————————————————————————
Function UrlEncode(ByVal strVal)
If Len(strVal) = 0 Then
UrlEncode = “”
Exit Function
End If
Dim dom, node
Set dom = WScript.CreateObject(“MSXML2.DOMDocument.6.0”)
dom.async = False
‘ 1. XMLノードを作成し、バイナリ(hex)としてUTF-8バイト列を保持させる
Set node = dom.createElement(“data”)
node.dataType = “bin.hex”
‘ 文字列をUTF-8のバイト配列に変換し、さらにHex文字列へ変換するプロセス
‘ MSXMLを利用して文字列をUTF-8エンコードされたHex表現に変換する
node.nodeTypedValue = StringToUTF8Bytes(strVal, dom)
Dim hexStr, i, b1, b2, hexPair
hexStr = node.text
‘ オブジェクトの明示的解放(メモリ最適化)
Set node = Nothing
Set dom = Nothing
‘ 2. Hex文字列を走査し、RFC 3986に基づくURLエンコードルールを適用
Dim result
result = “”
For i = 1 To Len(hexStr) Step 2
hexPair = Mid(hexStr, i, 2)
b1 = CInt(“&h” & hexPair)
‘ 予約文字・非予約文字の判定 (RFC 3986 Unreserved Characters: A-Z, a-z, 0-9, -, _, ., ~)
If (b1 >= 65 And b1 <= 90) Or _
(b1 >= 97 And b1 <= 122) Or _
(b1 >= 48 And b1 <= 57) Or _
b1 = 45 Or b1 = 95 Or b1 = 46 Or b1 = 126 Then
result = result & Chr(b1)
Else
result = result & "%" & UCase(hexPair)
End If
Next
UrlEncode = result
End Function
' ------------------------------------------------------------------------------
' URLデコード関数
' ------------------------------------------------------------------------------
Function UrlDecode(ByVal strVal)
If Len(strVal) = 0 Then
UrlDecode = ""
Exit Function
End If
Dim i, c, hexVal, hexStream
i = 1
hexStream = ""
' 1. "%XX" 形式をパースしてHexストリームに再構築
Do While i <= Len(strVal)
c = Mid(strVal, i, 1)
If c = "%" Then
If i + 2 <= Len(strVal) Then
hexVal = Mid(strVal, i + 1, 2)
hexStream = hexStream & hexVal
i = i + 3
Else
' 不正なフォーマットの場合はそのままスルー
hexStream = hexStream & "25" ' "%"のASCII
i = i + 1
End If
ElseIf c = "+" Then
' フォーム送受信の仕様に合わせ "+" をスペースに変換
hexStream = hexStream & "20"
i = i + 1
Else
' 通常文字は文字コードを2桁のHexに変換してストリームへ
Dim ascCode
ascCode = AscW(c)
If ascCode > 255 Then
‘ マルチバイト文字のフォールバック(簡易処理)
‘ 完全なデコードにはUTF-8バイナリとしての再構築が必要
‘ ここでは標準的なASCII範囲外の処理をバイナリ変換経由で行う
Dim subDom, subNode
Set subDom = WScript.CreateObject(“MSXML2.DOMDocument.6.0”)
Set subNode = subDom.createElement(“data”)
subNode.dataType = “bin.hex”
subNode.nodeTypedValue = StringToUTF8Bytes(c, subDom)
hexStream = hexStream & subNode.text
Set subNode = Nothing
Set subDom = Nothing
i = i + 1
Else
hexStream = hexStream & Right(“0” & Hex(ascCode), 2)
i = i + 1
End If
End If
Loop
‘ 2. DOMDocumentを用いてHexバイナリをUTF-8文字列に戻す
Dim dom, node
Set dom = WScript.CreateObject(“MSXML2.DOMDocument.6.0”)
Set node = dom.createElement(“data”)
node.dataType = “bin.hex”
On Error Resume Next
node.text = hexStream
If Err.Number = 0 Then
UrlDecode = UTF8BytesToString(node.nodeTypedValue)
Else
UrlDecode = “” ‘ デコード失敗時
End If
On Error GoTo 0
Set node = Nothing
Set dom = Nothing
End Function
‘ ——————————————————————————
‘ ヘルパー関数: 文字列をUTF-8バイト配列(Variant/Byte())に変換
‘ ADODB.Stream を利用(Windows標準)
‘ ——————————————————————————
Function StringToUTF8Bytes(ByVal str, ByVal dom)
Dim stream
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 2 ‘ adTypeText
stream.Charset = “utf-8”
stream.Open
stream.WriteText str
stream.Position = 0
stream.Type = 1 ‘ adTypeBinary
‘ ストリームからバイト配列を取得
StringToUTF8Bytes = stream.Read
stream.Close
Set stream = Nothing
End Function
‘ ——————————————————————————
‘ ヘルパー関数: UTF-8バイト配列を文字列に変換
‘ ——————————————————————————
Function UTF8BytesToString(ByVal bts)
Dim stream
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 1 ‘ adTypeBinary
stream.Open
stream.Write bts
stream.Position = 0
stream.Type = 2 ‘ adTypeText
stream.Charset = “utf-8”
UTF8BytesToString = stream.ReadText
stream.Close
Set stream = Nothing
End Function
‘ エントリポイント実行
Main
—
3. アーキテクトの視点:パフォーマンスとメモリ管理の極意
この実装において、シニアエンジニアが押さえておくべき技術的ポイントを解説する。
オブジェクトのライフサイクルと解放の強制
VBScriptのガベージコレクタは参照カウント方式(COMの `AddRef` / `Release`)に依存しているが、スクリプト終了間際までオブジェクトがメモリ上に居座る傾向がある。特に、ループ内で `MSXML2.DOMDocument` や `ADODB.Stream` を生成・破棄する場合、明示的な `Set obj = Nothing` の呼び出しを怠ると、メモリリークや「リソース不足 (Out of Memory)」エラーを引き起こす。
上記のコードでは、関数スコープごとにオブジェクトを確実に破棄する設計としている。
`MSXML2.DOMDocument.6.0` の選択
レガシーコードでは `MSXML2.DOMDocument`(バージョン非指定)や `Microsoft.XMLDOM` が見受けられるが、これらは脆弱性のある古いコンポーネント(MSXML 3.0等)を指す場合がある。現代のWindows環境(Windows 7以降、Server 2008以降)においては、セキュリティと安定性が担保された `MSXML2.DOMDocument.6.0` を名指しで生成すべきである。もし存在しない環境を考慮せざるを得ない場合でも、少なくとも `.3.0` ではなく `.6.0` を第一候補とすべきだ。
文字コードの決定版:ADODB.Stream との協調
VBScriptの内部文字列はUTF-16(Unicode)で保持されている。これをWeb標準のUTF-8へ変換する最も堅牢な方法は、`ADODB.Stream` のテキスト・バイナリ変換モードを利用することだ。
DOMDocumentの `nodeTypedValue` は、VBScriptの `Variant` 型のバイト配列(`Byte()`)と直接バインドできるため、余計な中間ファイルを生成することなく、インメモリで超高速にバイナリ変換を完結させることができる。
—
4. 結び:レガシーの制約を武器に変える
「古い技術」とやゆされがちなVBScriptだが、その背後にあるWindows COMのアーキテクチャは、今なおOSの深部で強固に動いている。外部のサードパーティ製DLLやPythonなどのランタイムを持ち込めない閉域網や、キオスク端末、レガシーバッチサーバーの保守において、今回解説した `MSXML2.DOMDocument` と `nodeTypedValue` を駆使したバイナリ・文字列操作の知見は、確実にエンジニアの武器となる。
小手先のハックに頼らず、OSの標準仕様の奥底まで潜り込み、リソースを完全に掌握してエレガントに問題を解決する――それこそが、真のプロフェッショナルエンジニアの姿である。
