Excel VBAによるCSVインポート機能の自作(フィールド内の改行も取り込む)
標準のインポート機能の問題点など
ExcelでCSVをインポートするときの話。
システム開発で証跡を残すときに、DBデータをCSVに落としてExcelに貼り付けることが多い。
また、レイアウトを横長ではなく縦長にしたりすることも、少なからずある。
これをExcel標準機能だけでこなそうとすると、結構面倒だ。
それだけでなく、お節介な機能が働いてしまい、勝手にデータが変換されてしまう。さらに厄介なことに、このお節介機能を制御することができない。
もっと言いたいことがある。フィールドに改行コードが含まれている場合、標準のインポート機能ではそこを行端と判断してしまい、取り込み後のレイアウトが崩れてしまう。
自作インポート機能のイメージ
そこで右クリックメニューからCSVファイルを指定しただけで、データを即座に貼り付ける自作機能をVBAで作ってみた。
ここでは、1行のデータとして以下のような形式を想定している。
a,"b,c",d,"e,f,CRLFg",hLF
CRLFはwin系の改行コード、LFはUNIX系の改行コード。
CRLFはフィールド内の改行コードとして使用されている。
LFは行端を示す改行コードとして使用されている。
フィールドにカンマや改行コードが含まれる場合は、ダブルクォーテーションで括られている。
今回の自作機能は動作が重く、大量データを取り込もうとすると固まる。
数十行くらいなら、おそらく許容範囲の処理時間だと思う。
実装(右クリックメニュー)
マクロブックをアドインとして保存する
まず、右クリックメニューの追加。
VBEの「Microsoft Excel Objects」内の「ThisWorkbook」に以下を記述する。
これにより、このアドインを追加しておけば、次回Excel起動時からは右クリックメニューの一番下に追加表示される。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Private Sub Workbook_Open()
Application.CommandBars("Cell").Reset
Set newb = Application.CommandBars("Cell").Controls.Add()
With newb
.Caption = "csvインポート(横)"
.OnAction = "CsvImportHorizontal"
.BeginGroup = True
End With
Set newb = Application.CommandBars("Cell").Controls.Add()
With newb
.Caption = "csvインポート(縦)"
.OnAction = "CsvImportVertical"
End With
End Sub
|
実装(インポート本体)
次にインポート機能の本体部分。
一応これで概ね動作すると思うが、誤動作した場合はご容赦を。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
| Option Explicit
Const VERTICAL = 1
Const HORIZONTAL = 2
Const SP = "~~~~~" ' セパレータ
Const EOL = "@@@@@" ' 行端文字
Const DQ = """" ' ダブルクォーテーション
Sub CsvImportHorizontal()
Call CsvImport(HORIZONTAL)
End Sub
Sub CsvImportVertical()
Call CsvImport(VERTICAL)
End Sub
Sub CsvImport(hvFlg)
Dim csvFile, adoSt, buf, lineArr, fieldArr, tpl, i, j, r, c
On Error GoTo errH
' ダウンロードフォルダのCSVファイルを選択
ChDir CreateObject("Wscript.Shell").SpecialFolders("MyDocuments") & "\..\Downloads\"
csvFile = Application.GetOpenFilename("CSVファイル(*.csv), *.csv")
If VarType(csvFile) = vbBoolean Then Exit Sub
' ADODBストリームを生成してCSVファイルの内容をバッファに読み込み
Set adoSt = CreateObject("ADODB.Stream")
With adoSt
.Charset = "UTF-8"
.Type = 2
.LineSeparator = 10
.Open
.LoadFromFile (csvFile)
buf = .Readtext
.Close
End With
' バッファ内のセパレータをカンマから別文字に変更する
buf = rep(buf)
' バッファを行単位の配列にする
buf = Replace(buf, DQ, "")
buf = Replace(buf, vbCrLf, vbCr)
buf = Replace(buf, vbLf, EOL)
buf = Replace(buf, vbCr, vbCrLf)
lineArr = Split(buf, EOL)
' 最終行が空行の場合は除外する
If lineArr(UBound(lineArr)) = "" Then
ReDim Preserve lineArr(UBound(lineArr) - 1)
End If
' 行単位の配列をセパレータで分離して2次元配列を作成する
ReDim fieldArr(UBound(lineArr), UBound(Split(lineArr(0), SP)))
For i = 0 To UBound(lineArr)
tpl = Split(lineArr(i), SP)
For j = 0 To UBound(tpl)
fieldArr(i, j) = tpl(j)
Next
Next
' csvインポート(縦)の場合
If hvFlg = VERTICAL Then
' フィールド単位の配列の行要素と列要素を入れ替え
fieldArr = WorksheetFunction.Transpose(fieldArr)
' 貼り付け範囲を取得
r = UBound(fieldArr, 1)
c = UBound(fieldArr, 2)
' csvインポート(横)の場合
Else
' 貼り付け範囲を取得
r = UBound(fieldArr, 1) + 1
c = UBound(fieldArr, 2) + 1
End If
' 貼り付け範囲の書式を文字列に設定
Range(ActiveCell, ActiveCell.Offset(r - 1, c - 1)).NumberFormatLocal = "@"
' フィールド単位の配列をアクティブセルを基点にしてシートに貼り付け
ActiveCell.Resize(r, c).Value = fieldArr
Set adoSt = Nothing
Exit Sub
errH:
Set adoSt = Nothing
MsgBox "エラーが発生しました" & vbCrLf & "エラー番号:" & Err.Number & vbCrLf & "エラーの種類:" & Err.Description
End Sub
' セパレータを別文字に置換
Function rep(line)
Dim regex
Set regex = CreateObject("vbscript.regexp")
With regex
.ignorecase = True
.Global = True
.Pattern = ",(?=([^" & DQ & "]*" & DQ & "[^" & DQ & "]*" & DQ & ")*(?![^" & DQ & "]*" & DQ & "))"
rep = .Replace(line, SP)
End With
Set regex = Nothing
End Function
|
処理の流れを説明しておく。
1.CSVファイルを選択する。
2.ADOでCSV内のデータをバッファに取り込む。(ADOを使うのはUTF-8に対応するため)
3.バッファ内のカンマセパレータを別文字Aに置換。
4.バッファ内のフィールド内の改行コードをいったん別文字Bに置換。
次に行端を示す改行コードを別文字Cに置換して、Bを元の改行コードに戻す。
5.バッファをセパレータAと行端を示すBで分割し、2次元配列を生成。
6.縦形式のインポートの場合は、2次元配列の行と列を入れ替える。
7.2次元配列を右クリックしたセルを基点に貼り付ける。
3.は正規表現を使ってカンマセパレータを置換しているが、ここが肝だ。(ファンクションrep)
これさえできれば、あとはそんなに難しくない。
ここで説明すると長くなるので、別記事「CSVでフィールド内にカンマがあるときの話」で説明した。
4.も少しややこしいが、ここで説明しておく。
1行のデータが以下だとする。
a,"b,c",d,"e,f,CRLFg",hLF
ダブルクォーテーション内にカンマや改行コードが含まれているが、一つのフィールドデータだ。
上の4でやっている置換の流れはこうだ。(別文字A=~、別文字B=CR、別文字C=@とする)
★3の処理後から
a~"b,c"~d~"e,f,CRLFg"~hLF
↓
↓ (CRLFをCRに置換)
↓
a~"b,c"~d~"e,f,CRg"~hLF
↓
↓ (LFを@に置換)
↓
a~"b,c"~d~"e,f,CRg"~h@
↓
↓ (CRをCRLFに戻す)
↓
a~"b,c"~d~"e,f,CRLFg"~h@
ここで使用した「~」「CR」「@」などの別文字は、フィールドデータとして使用されないものであれば、なんでも良い。なお実際のコードではダブルクォートを削除しており、また別文字がフィールドデータと確実に被らないように「~~~~~」などとしている。
この置換の目的は、フィールド内の改行コードCRLFのLF部分と、行端を示すLFを区別するためだ。
この置換をしないと、フィールド内の改行コードが行端とみなされてしまって、エラーの原因となる。
また、いきなり行端のLFを別文字に置換しようとしても、同時にフィールド内のCRLFのLF部分も置換されてしまい、エラーの原因となる。
おわりに
もしデータ形式としてフィールド内の改行コードがCRLFではなく行端と同じLFが使われている場合は、このPGは正しく動作しない。
この場合は、正規表現を使うなどして行端のLFのみを置換する必要があるだろうが、別の機会で考えたい。
関連ページ