【実務・中級編】Word VBAで『表(Table)』を操作する:セル結合・分割時のRange制御とエラーハンドリング – Word VBA解析バイブル

スポンサーリンク

Word VBAで「表(Table)」を完全に掌握する:セル結合・分割時のRange制御とエラーハンドリングの極意

Word VBA開発において、最大の難所の一つが「表(Tableオブジェクト)」の操作である。

多くの開発者がExcel VBAの感覚でWordの表を操作しようとして、無残なランタイムエラーの洗礼を受ける。「行の高さが不揃いなため、このコレクションにはアクセスできません(エラー 5992)」や「要求されたコレクションのメンバは存在しません(エラー 5941)」といったエラーに頭を抱えた経験は、あなたにもあるはずだ。

Excelのセルシートが「二次元グリッド」として厳密に管理されているのに対し、Wordの表は「テキストストリーム(Range)の内部に埋め込まれたレイアウト構造」に過ぎない。特にセルの結合や分割が行われた瞬間、表の論理的なインデックス構造は崩壊する。

本記事では、Wordの内部構造を解き明かし、結合セルが混在する複雑な表でも絶対に落ちない堅牢なWord VBAコードを実装するための設計思想と実用コードを伝授する。

1. 結合セルの罠:なぜ `Table.Rows` や `Table.Columns` は崩壊するのか

まずは、なぜWordの表でエラーが多発するのか、その構造的な原因を脳裏に叩き込んでほしい。

ExcelとWordの決定的な「思想の違い」

  • Excel: セル自体が絶対的な座標(A1, B2など)を持つ。セルが結合されていても、裏側のグリッド構造は維持される。
  • Word: 表は「行(Row)」の連なりであり、各行の中に「セル(Cell)」が横に並んでいるだけである。

ここで、ある行の1カラム目と2カラム目を結合したとしよう。

【結合前】
[ Row 1 ] -> [Cell 1,1] [Cell 1,2] [Cell 1,3]
[ Row 2 ] -> [Cell 2,1] [Cell 2,2] [Cell 2,3]

【結合後(Row 1 の 1と2を結合)】
[ Row 1 ] -> [Cell 1,1 (結合)] [Cell 1,2] <-- Row 1のCell数は「2つ」に減少する! [ Row 2 ] -> [Cell 2,1] [Cell 2,2] [Cell 2,3] <-- Row 2のCell数は「3つ」のまま この結合が行われた瞬間、Wordの内部では以下の現象が発生する。 1. 列(Column)の概念の喪失: `Table.Columns(1)` にアクセスしようとすると、1行目と2行目でセルの境界線(グリッドライン)が一致しないため、Wordは「列」を特定できなくなり、エラー 5992 を吐いて即死する。
2. インデックスのズレ: `Table.Cell(Row, Column)` メソッドは、指定した行の「左からn番目のセル」を指す。結合によってセル数が減った行では、`Table.Cell(1, 3)` は存在しないため、エラー 5941 が発生する。

黄金律:インデックス(Row, Column)指定を捨て、「フラット走査」せよ

結合・分割の可能性がある表を安全にループ処理する場合、`Table.Rows` や `Table.Columns` を使って二次元的にアクセスしてはならない。
唯一信頼できるのは、`Table.Range.Cells` コレクションを用いた、一次元配列としてのフラットな走査である。

2. 実践:Word Tableオブジェクトモデルの本質的制御

Wordのセルを操作する上で、避けて通れないのが「セル内改行(Chr(13))」「セル終端マーカー(ベル文字: Chr(7))」の存在だ。

セル内テキスト抽出時の「末尾2文字」問題

Wordの `Cell.Range.Text` を取得すると、末尾に必ず `\r\x07`(キャリッジリターン + ベル文字)という特殊な制御文字が2文字付着してくる。これをそのままデータベースや外部ファイル(CSV等)に出力すると、データが汚染されバグの原因となる。

この終端文字を完璧に削ぎ落とすには、以下の設計パターンを徹底すること。

‘ セルのRangeから純粋なテキストのみを抽出する頑健なプロシージャ
Public Function GetCleanCellText(ByVal targetCell As Word.Cell) As String
Dim cellRange As Word.Range
Set cellRange = targetCell.Range

‘ 末尾の2文字(Chr(13) & Chr(7))を除外するようにRangeの終端を1つ手前にシフトする
‘ Wordの内部処理において、セル終端マーカーは1文字分の長さを持つ
cellRange.MoveEnd Unit:=wdCharacter, Count:=-1

GetCleanCellText = cellRange.Text
End Function

`MoveEnd` メソッドでRangeの終端を左に1つずらすだけで、メモリを余計に消費することなく、美しくクリーンな文字列が取得できる。`Replace` 関数で強引に消去するよりも、オブジェクトレベルで制御する方が圧倒的に高速かつエレガントだ。

3. 動的な行操作におけるメモリ管理とオブジェクト参照の極意

実務ツールで頻出するのが、「特定の条件に合致する行を動的に追加・削除する」という要件だ。ここで多くの開発者が「削除漏れ」や「オブジェクトの参照エラー」を起こす。

原則1:削除は「後ろから(逆順)」行う

これはコレクション操作の鉄則だが、Word Tableでも同様である。前から削除すると、削除した瞬間にインデックスが繰り上がり、ループカウンタと実際のオブジェクトが乖離してエラーになるか、処理がスキップされる。

原則2:メモリリークを防ぐライフサイクル管理

VBAは参照カウンタ方式のGC(ガベージコレクション)を採用している。ループ内で `Dim` された `Cell` や `Range` オブジェクトは、ループのスコープを抜けるまでメモリに残り続ける。
特に数千行に及ぶ表を処理する場合、不要になったオブジェクトは明示的に `Set xxx = Nothing` で解放しなければ、あっという間にWordのメモリ空間を圧迫し、強制終了(Out of Memory)を引き起こす。

4. プロダクションコード:完全堅牢なWord表操作エンジン

それでは、これまで解説したアーキテクチャ設計をすべて具現化した、実務でそのまま使えるプロダクションコード(堅牢なラッパークラス/モジュール)を提示する。

このコードは、以下の高度な処理をノーエラーで実行する。
1. 結合セルが混在する複雑なテーブルの安全な解析。
2. 各セルのクリーンなテキストの抽出。
3. 特定の条件(例:特定のキーワードが含まれる行)に基づく、行の安全な動的削除(逆順走査)。
4. メモリリークを完全に防ぐ、厳密なオブジェクトライフサイクル管理。

Attribute VB_Name = “ModTableController”
Option Explicit

”’

”’ 結合セルが混在するWordテーブルから、安全かつクリーンに2次元配列としてデータを抽出する
”’

”’ 対象のTableオブジェクト ”’ クレンジング済みの2次元文字列配列。エラー時は空の配列を返す
Public Function ExtractTableDataSafely(ByRef targetTable As Word.Table) As String()
On Error GoTo ErrorHandler

If targetTable Is Nothing Then Err.Raise 5, , “引数 ‘targetTable’ が Nothing です。”

Dim totalCells As Long
totalCells = targetTable.Range.Cells.Count

If totalCells = 0 Then
ExtractTableDataSafely = Split(vbNullString)
Exit Function
End If

‘ 結合セルが存在する場合、行ごとの列数が異なるため、
‘ 一旦フラットな1次元配列に格納し、必要に応じて呼び出し側でハンドリングする設計にする
Dim resultData() As String
ReDim resultData(1 To totalCells, 1 To 3) ‘ [セルの通し番号, 所属行インデックス, セル内容]

Dim currentCell As Word.Cell
Dim cellRange As Word.Range
Dim cellIndex As Long
cellIndex = 1

‘ Table.Range.Cells によるフラット走査。これが結合セル対策の唯一の正解。
For Each currentCell In targetTable.Range.Cells
Set cellRange = currentCell.Range

‘ セル終端マーカー(Chr(13) & Chr(7))をRangeレベルで排除
cellRange.MoveEnd Unit:=wdCharacter, Count:=-1

resultData(cellIndex, 1) = CStr(cellIndex)
resultData(cellIndex, 2) = CStr(currentCell.RowIndex) ‘ 結合されていても所属する物理行インデックスは取得可能
resultData(cellIndex, 3) = cellRange.Text

cellIndex = cellIndex + 1

‘ ループ内でのメモリ解放を徹底
Set cellRange = Nothing
Set currentCell = Nothing
Next currentCell

ExtractTableDataSafely = resultData
Exit Function

ErrorHandler:
‘ 頑健なエラーハンドリングとリソース解放
Set cellRange = Nothing
Set currentCell = Nothing
Err.Raise Err.Number, “ExtractTableDataSafely”, “テーブル解析中にエラーが発生しました: ” & Err.Description
End Function

”’

”’ 結合セルが存在する表から、特定の条件に合致する行を安全に削除する(逆順走査)
”’

”’ 対象のTableオブジェクト ”’ このキーワードが含まれる行を削除 Public Sub DeleteRowsByKeyword(ByRef targetTable As Word.Table, ByVal targetKeyword As String)
On Error GoTo ErrorHandler

If targetTable Is Nothing Then Exit Sub
If Len(targetKeyword) = 0 Then Exit Sub

Dim totalRows As Long
totalRows = targetTable.Rows.Count

Dim rowIndex As Long
Dim rowObj As Word.Row
Dim cellObj As Word.Cell
Dim shouldDelete As Boolean
Dim cleanText As String

‘ 結合セルがある場合、targetTable.Rows.Count 自体がエラーになる可能性がある
‘ そのため、事前にエラーをトラップしてユーザーに通知するか、処理をバイパスする

‘ 逆順でのループ処理を徹底
For rowIndex = totalRows To 1 Step -1
‘ 結合セルが原因で行アクセス時にエラーが発生するのを防ぐ
On Error Resume Next
Set rowObj = targetTable.Rows(rowIndex)

If Err.Number = 5992 Then
‘ 行の高さや結合によりアクセスできない場合、この行の個別セル走査に切り替える
Err.Clear
‘ 警告ログ(イミディエイトウィンドウ)を出力し、次の行へ安全にスキップするか、
‘ あるいは特定のリカバリー処理を行う
Debug.Print “Warning: 行 ” & rowIndex & ” は結合セルのため直接アクセスできません。スキップします。”
GoTo ContinueLoop
End If
On Error GoTo ErrorHandler

shouldDelete = False

‘ 行内のセルを走査
For Each cellObj In rowObj.Cells
‘ セルテキストのクレンジング
Dim tempRange As Word.Range
Set tempRange = cellObj.Range
tempRange.MoveEnd Unit:=wdCharacter, Count:=-1
cleanText = tempRange.Text
Set tempRange = Nothing

‘ 部分一致判定
If InStr(cleanText, targetKeyword) > 0 Then
shouldDelete = True
Exit For ‘ 1つでもヒットすればその行は削除対象
End If
Next cellObj

‘ 行の削除
If shouldDelete Then
rowObj.Delete
End If

ContinueLoop:
Set rowObj = Nothing
Set cellObj = Nothing
Next rowIndex

Exit Sub

ErrorHandler:
Set rowObj = Nothing
Set cellObj = Nothing
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical, “行削除エラー”
End Sub

5. データベース・外部ファイル連携時のボトルネックと最適化

この堅牢なコードをベースに、さらに上流の「システム連携」を実装する場合、設計者は以下のパフォーマンスボトルネックを意識しなければならない。

1. 「画面描画」という最大の足枷を外せ

Word VBAが遅い最大の理由は、表にデータを1セルずつ書き込む(または読み込む)たびに、Wordが画面を再描画(リパブリッシュ)し、ページの自動割り付け(Repagination)を裏で実行することだ。

データベースから数千件のレコードを表に流し込む際は、必ず以下のコードで描画と自動割り付けをサスペンドすること。

‘ 高速化処理の開始
Application.ScreenUpdating = False
ActiveDocument.ActiveWindow.View.ShowRevisionsAndComments = False
‘ 自動改ページを一時的に停止(これが最も効く)
Application.Options.Pagination = False

‘ — ここで表の大量操作を実行 —

‘ 高速化処理の終了と復元
Application.ScreenUpdating = True
Application.Options.Pagination = True

2. DB連携時は「二次元配列」をクッションにせよ

セルにアクセスするたびに `Cell.Range.Text = “値”` を呼び出すのは、COM(Component Object Model)の境界を何度も往復することになり、極めて非効率だ。

  • アンチパターン: DBレコードセットをループし、都度 `Table.Cell(r, c).Range.Text` に書き込む。
  • ベストプラクティス:

1. DBから `Recordset.GetRows` などを用いて、一旦すべてのデータをVBAのメモリ内(二次元配列)に展開する。
2. Word側に「結合のないプレーンな一時テーブル」を高速作成し、配列から一気に書き込む。
3. 書き込み完了後に、必要最小限のセル結合処理を「下から上へ」向かって一括適用する。

6. まとめ:堅牢なWord VBA構築のために

WordのTableオブジェクトは、一見すると直感的だが、その内実は「テキストストリームに無理やり格子をあてがった」極めて危うい構造体である。

プロフェッショナルとして堅牢なツールを設計するなら、以下の3大原則を開発チームに徹底してほしい。

1. 結合セルがある表では、絶対に `Rows` / `Columns` / `Cell(Row, Col)` を過信しない。 `Table.Range.Cells` による一元走査が唯一の正解である。
2. セルからテキストを取り出す際は、`MoveEnd` を用いて「ベル文字(Chr(7))」をオブジェクトレベルで排除する。
3. 動的削除は「逆順(Step -1)」、メモリ解放は「ループ内での明示的 `Nothing`」を徹底する。

この設計思想をマスターすれば、仕様変更や複雑なレイアウト変更にもびくともしない、本質的に「落ちない」Word自動化システムが構築可能となる。あなたのプロジェクトの品質を、もう一段上の次元へと引き上げてほしい。

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