【テクニカル・上級編】【中級者向け】Word表をHTMLテーブルタグに変換し、Webサイト用データとしてクリップボードへ送る – Word VBA解析バイブル

スポンサーリンク
スポンサーリンク

境界を越えるデータ変換:Word TableからHTMLへ、Windows APIを用いた高精度な構造抽出術

ドキュメント作成の最終工程において、Wordの表データをWebシステムやCMSへ流し込む作業は、多くの現場で「手作業によるコピペ」という、極めて非効率かつ脆弱なプロセスに依存している。

しかし、我々エンジニアが目指すべきは、単なるデータの転送ではない。ドキュメントの持つ「構造(Structure)」を損なうことなく、Webのセマンティクス(意味論)へと昇華させる「変換(Transformation)」である。

本稿では、Word VBAを用いて表構造を解析し、HTMLテーブルへと精密に変換、さらに信頼性の低い`MSForms.DataObject`を避け、Windows APIを直接叩くことでクリップボードへ格納する、極限のデータ連携実装を解説する。

1. アーキテクチャの設計思想:なぜ「標準機能」では足りないのか

中級者以上のエンジニアであれば、一度は`MSForms.DataObject`を使ってクリップボード操作を試みたことがあるだろう。しかし、その挙動の不安定さに頭を抱えた経験も少なくないはずだ。特に64bit環境や、特定のWindows Update適用後の環境において、`DataObject`は極めて信頼性に欠ける。

また、Wordの`Cell.Range.Text`をそのまま取得しようとすると、セル末尾に含まれる特殊な制御文字(`Chr(13) & Chr(7)`)が混入し、HTMLの汚染を招く。

我々が実装すべきは、以下の3点を満たす堅牢なパイプラインである。

1. 構造の完全性: `

`および`

`タグを、WordのDOM構造に基づき正確に再構築する。
2. メモリの最適化: 大規模な表を扱う際、オブジェクトの参照を明示的に解放し、メモリリークを防止する。
3. 低レイヤーへのアクセス: Windows APIを直接呼び出し、OSレベルでクリップボードへデータを書き込む。

2. 実装:Windows APIによるクリップボード制御

まず、クリップボードへのデータ転送を確実に行うためのAPI宣言を行う。ここでは、64bit/32bit両環境に対応した`PtrSafe`属性を用いた宣言を記述する。

' --- Windows API Declarations ---

If VBA7 Then

Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long Private Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long Private Declare PtrSafe Function SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hMem As LongPtr) As LongPtr Private Declare PtrSafe Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As LongPtr) As LongPtr Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As Long Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal Dest As LongPtr, ByVal Src As LongPtr, ByVal Length As LongPtr) As Long

Else

Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long Private Declare Function EmptyClipboard Lib "user32" () As Long Private Declare Function CloseClipboard Lib "user32" () As Long Private Declare Function SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hMem As Long) As Long Private Declare Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As Long) As Long Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long Private Declare Function RtlMoveMemory Lib "kernel32" (ByVal Dest As Long, ByVal Src As Long, ByVal Length As Long) As Long

End If

Private Const CF_TEXT As Long = 1 Private Const GMEM_MOVEABLE As Long = &H2

3. ロジックの核心:表構造の走査とHTML生成

次に、表をループで解析するメインロジックを構築する。ここで重要なのは、`Cell.Range.Text`から末尾の制御文字を確実に除去すること、そして文字列結合のコストを意識することである。

''' <summary>
''' 選択中の表をHTMLに変換し、クリップボードへ送る
''' </summary>
Public Sub ConvertSelectedTableToHTML()
    Dim targetTable As Table
    Dim rowIdx As Long, colIdx As Long
    Dim htmlBuffer As String
    Dim cellText As String
    
    ' 1. コンテキストの検証
    If Selection.Information(wdWithInTable) = False Then
        MsgBox "表の中にカーソルを置いて実行してください。", vbExclamation
        Exit Sub
    End If
    
    Set targetTable = Selection.Tables(1)
    
    ' 2. HTML構築の開始
    ' StringBuilder的なアプローチとして、メモリ効率を考慮しつつ構築
    htmlBuffer = "<table border=""1"">" & vbCrLf
    
    On Error GoTo ErrorHandler
    
    ' 3. 行・列の走査
    For rowIdx = 1 To targetTable.Rows.Count
        htmlBuffer = htmlBuffer & "  <tr>" & vbCrLf
        
        For colIdx = 1 To targetTable.Columns.Count
            ' セルの存在確認(結合セルによるエラー回避)
            On Error Resume Next
            cellText = targetTable.Cell(rowIdx, colIdx).Range.Text
            
            If Err.Number = 0 Then
                ' Word特有の末尾制御文字 (Chr(13) & Chr(7)) を除去
                cellText = CleanCellText(cellText)
                htmlBuffer = htmlBuffer & "    <td>" & cellText & "</td>" & vbCrLf
            Else
                ' 結合セルなどでセルが取得できない場合は空セルとして扱う
                htmlBuffer = htmlBuffer & "    <td></td>" & vbCrLf
                Err.Clear
            End If
            On Error GoTo ErrorHandler
        Next colIdx
        
        htmlBuffer = htmlBuffer & "  </tr>" & vbCrLf
    Next rowIdx
    
    htmlBuffer = htmlBuffer & "</table>"
    
    ' 4. クリップボードへの転送
    Call PutToClipboard(htmlBuffer)
    
    MsgBox "HTML変換完了。クリップボードに格納しました。", vbInformation

CleanExit:
    ' 5. オブジェクトの明示的な解放(メモリ管理の鉄則)
    Set targetTable = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    Resume CleanExit
End Sub

''' <summary>
''' Wordセルの制御文字を除去する
''' </summary>
Private Function CleanCellText(ByVal txt As String) As String
    ' Chr(13) = CR, Chr(7) = Bell (Wordのセル終端記号)
    ' これを除去しないとHTMLが崩れる
    Dim clean As String
    clean = Replace(txt, Chr(13), "")
    clean = Replace(clean, Chr(7), "")
    CleanCellText = Trim(clean)
End Function

4. 低レイヤー実装:`PutToClipboard`の実装

最後に、APIを用いて文字列をメモリ空間に確保し、クリップボードに渡す実装だ。これは`MSForms.DataObject`を使用するよりも遥かに低レイヤーで、かつ確実な方法である。

''' <summary>
''' Windows APIを使用して文字列をクリップボードにコピーする
''' </summary>
Private Sub PutToClipboard(ByVal text As String)
    #If VBA7 Then
        Dim hMem As LongPtr
        Dim pMem As LongPtr
    #Else
        Dim hMem As Long
        Dim pMem As Long
    #End If
    
    Dim byteLen As Long
    Dim bText() As Byte
    
    ' 文字列をバイト配列に変換(UTF-8ではなく、システムのANSIとして扱う)
    bText = StrConv(text & vbCrLf, vbFromUnicode)
    byteLen = UBound(bText) + 1
    
    ' 1. クリップボードを開く
    If OpenClipboard(0&) = 0 Then Exit Sub
    
    ' 2. クリップボードを空にする
    EmptyClipboard
    
    ' 3. メモリを確保 (GlobalAlloc)
    hMem = GlobalAlloc(GMEM_MOVEABLE, byteLen)
    If hMem = 0 Then
        CloseClipboard
        Exit Sub
    End If
    
    ' 4. メモリのロックとデータのコピー
    pMem = GlobalLock(hMem)
    If pMem <> 0 Then
        RtlMoveMemory pMem, VarPtr(bText(0)), byteLen
        GlobalUnlock hMem
        
        ' 5. クリップボードにデータをセット
        SetClipboardData CF_TEXT, hMem
    End If
    
    ' 6. クリップボードを閉じる
    CloseClipboard
End Sub

5. アーキテクトの視点:運用と保守における知見

このコードは単なるスクリプトではない。システム間連携の「アダプター」である。実運用においては、以下の点に留意せよ。

結合セルのハンドリング

本実装では、結合セル(Merged Cells)に遭遇した場合、`Err.Number`をキャッチして空セルとして処理する。これは、結合セルのインデックス構造がWordの内部モデルにおいて非常に複雑であり、単純なループでは`Run-time error ‘5941’: The requested member of the collection does not exist`を引き起こすためだ。より高度な変換が必要な場合は、`Cell.ColumnIndex`や`Cell.RowIndex`を比較し、結合範囲を論理的に計算するアルゴリズムが必要となる。

文字コードの罠

本実装では`CF_TEXT`(ANSI)を使用している。もし、多言語対応(Unicode/UTF-16)が必須となる要件であれば、`CF_UNICODETEXT`を選択し、`StrConv`を介さずに直接文字列のメモリポインタを扱うように、実装を昇華させる必要がある。

パフォーマンスの限界

数千行に及ぶ巨大な表を扱う場合、`htmlBuffer = htmlBuffer & …` という文字列結合は、メモリの再確保を繰り返すため、指数関数的にパフォーマンスを低下させる。その場合は、`Collection`オブジェクトに各行の文字列を格納し、最後に一括で`Join`するか、あるいは更なる低レイヤーでのバッファ管理を検討すべきである。

結論

WordからWebへのデータ遷移は、一見すると単純なコピペに見える。しかし、その背後にあるオブジェクトのライフサイクル、メモリ管理、そしてOSのAPIとの対話を理解することで、初めて「壊れない、揺るがない」システムを構築できる。

技術の本質は、便利さの追求ではなく、不確実性の排除にあるのだ。

タイトルとURLをコピーしました