Wordの表の各セルの文字列を利用しやすくする(2)

クラスのプロパティに二次元配列を持たせてみる

表の内容をそのまま配列にする

「表」ということは、二次元配列と同じ形なんである。

そこで、
クラスのプロパティを二次元配列にする
ことを試みた。

クラスの改造

まず、前回記事のリスト1のうち、フィールド・アクセサ部分を以下のように書き換える。

リスト1-1
'フィールド'
Private wordApp_ As Word.Application
Private wordDoc_ As Word.Document
Private wordTable_() As Word.Table
Private tableArray_() As String    '……(1)'
Private maxRow_ As Integer    '……(2)'
Private maxColumn_ As Integer
Private isReady_ As Boolean
'アクセサ'
Public Property Get wordApp() As Word.Application
  Set wordApp = wordApp_
End Property
Public Property Get wordDoc() As Word.Document
  Set wordDoc = wordDoc_
End Property
Public Property Get wordTable(ByVal i As Integer) As Word.Table
  Set wordTable = wordTable_(i)
End Property
Public Property Get tableArray(ByVal r As Integer, _
                               ByVal c As Integer) As String    '……(3)'
  tableArray = tableArray_(r, c)
End Property
Public Property Get maxRow() As Integer
  maxRow = maxRow_
End Property
Public Property Get maxColumn() As Integer
  maxColumn = maxColumn_
End Property
Public Property Get isReady() As Boolean
  isReady = isReady_
End Property

(1)の

Private tableArray_() As String

では、新たにtableArray_()というString型の配列変数を準備。

さらに、(2)からの2行

Private maxRow_ As Integer
Private maxColumn_ As Integer

でmaxRow_及びmaxColumn_という変数を準備した。この2つの変数には、取り扱うWordの表の行数と列数を格納する。

(3)の

Public Property Get tableArray(ByVal r As Integer, _
                               ByVal c As Integer) As String
  tableArray = tableArray_(r, c)
End Property

は、tableArrayの値にアクセスするためのアクセサメソッド。

二次元の配列なので、引数が2つ必要。

そして、メソッドも1つ追加する。

リスト1-2
Public Sub createArrayFromTable(ByVal tableNum As Integer, _
                                ByVal hasHeader As Boolean)    '……(1)'
On Error GoTo ErrorCatch
  Dim startRow As Integer                '……(2)'
  If hasHeader = True Then startRow = 2
  If hasHeader = False Then startRow = 1
  With wordTable_(tableNum)    '……(3)'
    maxRow_ = .Rows.Count        '……(4)'
    maxColumn_ = .Columns.Count
    ReDim tableArray_(maxRow_, maxColumn_)    '……(5)'
    Dim iRow As Integer    '……(6)'
    Dim iColumn As Integer
    Dim str As String
    Dim n As Integer
    n = 1
    For iRow = startRow To maxRow_                         '……(7)'
      For iColumn = 1 To maxColumn_    '……(8)'
        str = .Cell(iRow, iColumn).Range.Text    '……(9)'
        tableArray_(n, iColumn) = Left(str, Len(str) - 2)    '……(10)'
      Next
      n = n + 1    '……(11)'
    Next
  End With
  Exit Sub
ErrorCatch:
End Sub

まずは(1)の

Public Sub createArrayFromTable(ByVal tableNum As Integer, _
                                ByVal hasHeader As Boolean) 

でお分かりの通り、2つの引数を受け取るようにしている。

  • 第1引数:Wordドキュメント内の表の番号
  • 第2引数:1行目がラベル行なのかどうか

の2つを受け取って実行する。

(2)からの3行、

Dim startRow As Integer
If hasHeader = True Then startRow = 2
If hasHeader = False Then startRow = 1

では、引数hasHeaderの値によって変数startRowの値を切り替えている。

たとえば、hasHeaderがTrueであるということは、表の1行目は項目ラベルだということだから、startRowを「2」にして、表の2行目から値を取得しよう、というわけ。

(3)で

With wordTable_(tableNum)

このようにしているので、この後、End WithまではwordTable_(tableNum)に格納されたTableオブジェクト、すなわち「tableNum番目の表」が処理の対象となる。

(4)からの2行、

maxRow_ = .Rows.Count
maxColumn_ = .Columns.Count

では、TableオブジェクトのRows、ColumnsコレクションのCountプロパティを用いて、変数maxRow_、maxColumn_に表の行数・列数をセットしている。

(5)では、(4)で取得した表の行数・列数を用いて

ReDim tableArray_(maxRow_, maxColumn_)

tableArray_()をReDimしている。

さて、ここからがこのメソッドの中心。

まず、(6)からの5行、

Dim iRow As Integer    '……(6)'
Dim iColumn As Integer
Dim str As String
Dim n As Integer
n = 1

は、変数の準備。

iRow

Forループ(外側)のカウンタとして使用。Wordの表の行数指定を兼ねる。

iColumn

Forループ(内側)のカウンタとして使用。配列二次元目のインデックス、及びWordの表の列数指定を兼ねる。

str

表の各セルの文字列の受け取りに使用。

n

配列一次元目のインデックスの指定に使用。

Wordの表から値を取得するとき、1行目が項目ラベルなのかどうかによって、1行目から値を取得しはじめる場合と2行目から値を取得しはじめる場合の2通りがあるので、Forループのカウンタ以外に別途Wordの表の行数を指定するための変数nを準備し、「1」で初期化している。

そして、このメソッドの処理の中心が(7)からの7行。

For iRow = startRow To maxRow_    '……(7)'
  For iColumn = 1 To maxColumn_    '……(8)'
    str = .Cell(iRow, iColumn).Range.Text    '……(9)'
    tableArray_(n, iColumn) = Left(str, Len(str) - 2)    '……(10)'
  Next
  n = n + 1    '……(11)'
Next

Forループがネストしているので、ちょっと見づらいかも知れないが、行方向のループと列方向のループだけなので、許容範囲だと思う。

まず、(7)、

For iRow = startRow To maxRow_

行方向のループ指定だが、開始値を変数startRowにしているのがミソ。

言うまでもなく、表の1行目から読み取る場合と2行目から読み取る場合の2種類に対応するためだ。

次の(8)、

For iColumn = 1 To maxColumn_

は、列方向のループ。各行につき、1列目から右へ右へと値を取得しては配列に格納していく、というイメージ。

(9)の

str = .Cell(iRow, iColumn).Range.Text

で、セルの文字列をstrに格納し、

(10)の

tableArray_(n, iColumn) = Left(str, Len(str) - 2)

で「ハナクソ」と改行文字を除去した上で配列にセットしている。

1行分セットし終えたら、すなわち、(8)のForループが完了したら、次の行に進むために、(11)の

n = n + 1

で変数nをインクリメントする。

このようにすれば、表内の全ての値が配列tableArray_()セットされるはずだ。

動作確認

このクラスの動作確認用に、次のコードを標準モジュールに書く。

スト2
Public Sub testTableArray()
  Dim wtOperator As WordTableOperator
  Set wtOperator = New WordTableOperator
  wtOperator.createArrayFromTable 2, True    '……(1)'
  Dim iRow As Integer
  Dim iColumn As Integer
  With wtOperator                                  '……(2)'
    For iRow = 1 To .maxRow
      For iColumn = 1 To .maxColumn
        ActiveSheet.Cells(iRow, iColumn).Value = _
                   .tableArray(iRow, iColumn)
      Next
    Next
  End With
  Debug.Print wtOperator.tableArray(3, 3)    '……(3)'
End Sub

(1)の

wtOperator.createArrayFromTable 2, True

では、createArrayFromTableメソッドを、引数「2」と「True」の2つを渡して実行。

日本語訳すると、「2番目の表の値を、1行目が項目ラベルであるとみなして二次元配列としてプロパティに格納せよ」ぐらいか。

(2)からの8行(正味7行)、

With wtOperator
  For iRow = 1 To .maxRow
    For iColumn = 1 To .maxColumn
      ActiveSheet.Cells(iRow, iColumn).Value = _
                 .tableArray(iRow, iColumn)
    Next
  Next
End With

は、もはや説明不要だろう。

二重のForループを用いて、tavleArrayプロパティにセットした値をアクティブシートに書き込んでいるだけだ。

あと、(3)の

Debug.Print wtOperator.tableArray(3, 3)

は、二次元のインデックスを渡して値を取得する例。

実行結果

f:id:akashi_keirin:20170506201246j:plain

このWordドキュメントがアクティブの状態で実行した。

f:id:akashi_keirin:20170506201252j:plain

Excelシート上に各値が転記されている。

f:id:akashi_keirin:20170506201255j:plain

イミディエイト・ウインドウには、2番目の表の3行3列目の値が表示されている。

おわりに

せっかく二次元配列として表のデータを取得しているのだから、tableArrayプロパティに表のデータを読み込ませた後、

With wtOperator
  ActiveSheet.Range("A1").Resize(.maxRow, .maxColumn).Value = .tableArray
End With

とでも書けば、一発でExcelワークシートに転記できそうなものだが、

f:id:akashi_keirin:20170506201258j:plain

こんなふうにコンパイル・エラーになって、実行すらさせてくれない。

プロパティはあくまでもプロパティであって、配列変数ではない、ということなのかなあ?

@akashi_keirin on Twitter