これは、Visual Basic Advent Calendar 2016の12日目の記事です。
##1. テーマ
2次元以下の配列のデータをクリップボードに転送します。
行内はTab
で、行間はCrLf
で繋げた文字列形式で出力します。文字列にできない要素(NullやEmpty、配列など)は長さゼロの文字列""
とします。
a = makeM(3, 4, iota(1,12)) ' 配列の作成
a(0,1) = Null : a(1,2)= "az" : a(2,1)= "愛" ' Nullや文字を入れる
m2Clip a ' クリップボード転送
「m2Clip
」でクリップボードに転送し、エディタ等にペーストできます。
転送対象の文字列を作る部分が当記事のメインです。
##2. コード
最初にコードを示します。モジュールは、Y_IO_utiliy.bas(Github)またはY_IO_utiliy.basに置いています。
' 配列(2次元以下)をクリップボードに転送する
Public Sub m2Clip(ByRef data As Variant)
Dim s As String
' -------------------------
' ↓ 前半部分。クリップボードに貼り付けるための文字列を作る
Select Case Dimension(data)
Case 0
s = CStr_(data) & vbCrLf
Case 1
s = Join(mapF(p_CStr, data), vbTab) & vbCrLf
Case 2
Dim tmp As Variant
tmp = zipC(mapF(p_CStr, data))
tmp = mapF(p_join(, vbTab), tmp)
s = Join(tmp, vbCrLf) & vbCrLf
End Select
' ↑ 前半部分ここまで。
' -------------------------
' ↓ 後半部分。文字列をクリップボードに貼り付ける
Dim dOb As Object
'Set dOb = New MSFORMS.DataObject
Set dOb = CreateObject("new:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
With dOb
.SetText s
.PutInClipboard
End With
Set dOb = Nothing
End Sub
前半部分で、配列の次元 (0, 1, 2
) によって場合分けしています。
Case 0
は実際には配列ではなくてスカラーです。関数CStr_
はVBA組み込みのCStr
をちょっと変えたもので、文字列にできるデータは文字列化し、できないデータは長さゼロの文字列""
にするだけの関数です。
Case 1
は1次元配列、Case 2
は2次元配列で、ここに出てくるmapF
はVBAHaskellの関数で最も頻繁に使われるものです。
##3. 配列要素のString変換
data
が1次元配列の場合、本質的には Join(data, vbTab) & vbCrLf
とするだけですが、要素の型によってはJoin
がエラーになる1ので事前にString
に変換する必要があります。
mapF(p_CStr, data) 'p_CStr は CStr_ の関数オブジェクト
このマッピングによって、data =
{ x0, x1, x2, ... , xn } として
{x0, x1, x2, ... , xn} {CStr_
(x0) , CStr_
(x1) , CStr_
(x2) , ... , CStr_
(xn)}
と変換されます。
7日目の記事 でも書きましたが、関数本体であるCStr_
は普通のVBA関数で、p_CStr
はそのアドレス(AddressOfの値)を含む一種の関数オブジェクト(p_関数)です(実体は配列)。
data
が2次元配列x(i, j)
のときの手順は以下の通りです。
(1) 各要素を文字列化する
tmp = mapF(p_CStr, data)
(2) 行単位で配列化する
tmp = zipC(tmp)
(3) 各行に対してJoin( , vbTab)
する
tmp = mapF(p_join(, vbTab), tmp)
(4) Join( , vbCrLf)
し、vbCrLf
を追加する
s = Join(tmp, vbCrLf) & vbCrLf
(1) は前に説明した通りです。
(2) Join
は配列自体を引数に取るので、行を一つの単位として配列化する必要があります。zipC
はジャグ配列を作る関数の一つで、下にその概念を図で示しています。
(3) も前に説明した通りですが、mapF
を使ってループを書かずに済んでいます。
(4) 行間はTab
ではなくCrLf
で繋いでいます。
'ふたつの配列の対応する要素どうしをmakePairしてジャグ配列を作る
Public Function zip(ByRef a As Variant, ByRef b As Variant) As Variant
'2次元配列の各行ベクトルをzipVs
Public Function zipR(ByRef m As Variant, Optional ByRef target As Variant) As Variant
'2次元配列の各列ベクトルをzipVs
Public Function zipC(ByRef m As Variant, Optional ByRef target As Variant) As Variant
実際のコードでは(1)と(2)を合わせてtmp = zipC(mapF(p_CStr, data))
と書いています。
##4. クリップボード転送
Officeアプリによっては参照設定にMSFORMS.DataObject
が選べないようなので、New MSFORMS.DataObject
ではなくCreateObject
を使っています。
DataObject::SetText
メソッドはUnicodeで書き込まれるせいか、カナ漢字等が含まれている場合はVBAのモジュールやイミディエイト ウィンドウにはペーストできません。最初クリップボード転送自体が失敗しているのかと思いましたが、外部エディタやワークシートにはペーストできるのでよしとしています2。 イミディエイト ウィンドウに配列を出力するのはprintM
関数が使えます。
リンク
VBAHaskellのI/Oユーティリティ
Qiita VBAHaskellの紹介 その1 (最初はmapF)
ソースコード(Github)
VBAHaskellの関数リファレンス
dllバイナリ:
https://github.com/mYmd/VBA/blob/master/bin/mapM (32bit-Office用)
https://github.com/mYmd/VBA/blob/master/bin/mapM64 (64bit-Officey用)
VBAコード添付済みExcelブック
VBAHaskellほぼ全部入り.xlsm