Excelで大量のCSVファイルを読み込んで1つのファイルにしてほしいという要望があったので作ったものです。
結合したいCSVファイルのデータ量が数百MBありましたので,それなりに高速に動くように配慮しました。
(ReDim Preserveの削減。大きいデータは参照渡し。繰り返し使う処理の削減。)
このExcelファイルでできること |
・複数のCSVファイルを1つのExcelブックに結合することができます。 |
・1つのシートに結合することも,CSVファイルごとにシートを分割することもできます。 |
・CSVファイルがあるフォルダ階層がわかれていても,再帰的にサブフォルダを探索してCSVファイルを結合することができます。 |
・Excelシートの最大行(1,048,576 行)を超える場合は次のシートを追加して処理を継続することもできます。 |
注意:大量のCSVファイルを結合するとメモリ不足となることや,メモリ使用量の上限付近になると処理が著しく遅くなることがあります。(特に32bit版のExcel,32bit版のWindowsの場合は使用可能なメモリの上限が低くなります。) |
参考:Excelシートの最終行(1,048,576 行)が空行となりますが,これは仕様です。(手抜きです) |
・文字コードを判別して結合することができます。 |
このExcelファイルではできないこと |
・Excelシートの最大列(16384列)を超えるCSVファイルは結合できません。 |
VBAのコード
UserForm1
Option Explicit
' フォルダの選択
Private Sub CommandButton1_Click()
With Application.FileDialog(msoFileDialogFolderPicker)
If .Show = True Then
Me.TextBox1.Text = .SelectedItems(1)
End If
End With
End Sub
' CSV結合の実行
Private Sub CommandButton2_Click()
Me.CommandButton2.Enabled = False
Application.ScreenUpdating = False
' 取込み設定の確認
Dim MODE_Sheet分割 As Boolean
Dim MODE_FILENAME_1行目 As Boolean
Dim MODE_FILENAME_AB列 As Boolean
If Me.OptionButton1.Value Then
MODE_Sheet分割 = False
MODE_FILENAME_1行目 = False
MODE_FILENAME_AB列 = True
ElseIf Me.OptionButton2.Value Then
MODE_Sheet分割 = False
MODE_FILENAME_1行目 = False
MODE_FILENAME_AB列 = False
ElseIf Me.OptionButton3.Value Then
MODE_Sheet分割 = False
MODE_FILENAME_1行目 = True
MODE_FILENAME_AB列 = False
ElseIf Me.OptionButton4.Value Then
MODE_Sheet分割 = True
MODE_FILENAME_1行目 = False
MODE_FILENAME_AB列 = False
ElseIf Me.OptionButton5.Value Then
MODE_Sheet分割 = True
MODE_FILENAME_1行目 = True
MODE_FILENAME_AB列 = False
End If
Dim MODE_SUBDIR As Boolean
MODE_SUBDIR = Me.CheckBox1.Value
Dim MODE_SUBSHEET As Boolean
MODE_SUBSHEET = Me.CheckBox2.Value
' フォルダの存在チェック
Dim fso As New FileSystemObject
If Not fso.FolderExists(TextBox1.Text) Then
MsgBox "フォルダが存在しません"
Exit Sub
End If
' ***処理開始***
' Workbookを追加
Dim b As Workbook
Set b = Workbooks.Add
' 不要なシートを削除
Dim i As Integer
Application.DisplayAlerts = False
Dim sCount As Long
sCount = b.Sheets.count
For i = 2 To sCount
b.Sheets(2).Delete
Next
b.Sheets(1).Name = "Sheet1"
Application.DisplayAlerts = True
' フォルダの探索・CSVファイル取り込みを開始
ThisWorkbook.Activate
Call GetSubFolderCSV(b, b.Sheets(1).Range("A1"), fso.GetFolder(TextBox1.Text), MODE_SUBDIR, MODE_SUBSHEET, MODE_Sheet分割, MODE_FILENAME_AB列, MODE_FILENAME_1行目, fso.GetFolder(TextBox1.Text))
'完了
Me.CommandButton2.Enabled = True
Me.Hide
Application.StatusBar = False
Application.ScreenUpdating = True
MsgBox "完了しました。"
End Sub
' フォルダを探索してCSVファイルがあれば,CSVの取り込み関数へ渡す。
' book As Workbook, 読み込み先のブック _
ByRef r As Range, 読み込み先のセル _
dir As Folder, 読み込むフォルダ _
m_subdir As Boolean, サブディレクトリを探索するかどうか _
m_subsheet As Boolean, ワークシートの上限を超える場合に次のシートを追加して処理を継続するか _
m_sheet分割 As Boolean, CSVファイルごとにシートを分割するか _
m_fn_col_AB As Boolean, A・B列にフォルダパス・ファイル名を追加するか _
m_fn_1strow As Boolean, 1行目にファイルパスを追加するか _
base_dir As Folder 基準フォルダ(シート名にファイルの相対パスを設定する際に使用)
Sub GetSubFolderCSV( _
book As Workbook, _
ByRef r As Range, _
dir As Folder, _
m_subdir As Boolean, _
m_subsheet As Boolean, _
m_sheet分割 As Boolean, _
m_fn_col_AB As Boolean, _
m_fn_1strow As Boolean, _
base_dir As Folder)
Dim f As File
Dim d As Folder
Dim sh As Worksheet
Set sh = book.Sheets(1)
' フォルダ内のファイルを探索
For Each f In dir.Files
' 拡張子がCSVなら処理する
If f.Name Like "*.csv" Then
If m_sheet分割 Then ' CSVファイルごとにシート分割する場合
If book.Sheets.count = 1 Then
If book.Sheets(1).Name = "Sheet1" Then
Set sh = book.Sheets(1) ' 最初のシートはそのまま使う
Else
Set sh = book.Sheets.Add(After:=book.Sheets(book.Sheets.count)) ' 2つめ以降はシートを末尾に追加
End If
Else
Set sh = book.Sheets.Add(After:=book.Sheets(book.Sheets.count)) ' 2つめ以降はシートを末尾に追加
End If
' シート名の変更
If m_fn_1strow Then
' 1行目にファイルパスを出力する設定の場合はシート名は単純にシート番号とする
sh.Name = book.Sheets.count
Else
' シート名をファイルパスとする場合は,相対パスを付ける。
ChangeSheetName sh, f.path, base_dir.path
End If
' シートの先頭を出力先セルとする
Set r = sh.Range("A1")
Else
' シート分割しない場合はシート名はCSVとする。
sh.Name = "CSV"
End If
' 1行目にファイルパスを入力する
If m_fn_1strow Then
sh.Cells(r.Row, 1).Formula = f.path
Set r = r.Offset(1, 0)
End If
' CSVを取り込む
Dim count As Long
Windows(book.Name).WindowState = xlMinimized
DoEvents
count = CsvToRange(r, f, m_fn_col_AB, m_subsheet)
End If
Next
' サブディレクトリを再帰的に探索
If m_subdir Then
For Each d In dir.SubFolders
Call GetSubFolderCSV(book, r, d, m_subdir, m_subsheet, m_sheet分割, m_fn_col_AB, m_fn_1strow, base_dir)
Next
End If
End Sub
' Worksheetの名前を相対パスとする _
sh As Worksheet: worksheet _
fPath As String: ファイルパス(絶対パス) _
dPath As String: フォルダパス(基準ディレクトリ)
Sub ChangeSheetName(sh As Worksheet, fPath As String, dPath As String)
' Worksheetの名前には31文字制限がありファイルパスが長すぎる場合は名前が重複する場合がある
' その場合に末尾に~1,~2,~3,…,~A,~B,~C,…,~a,~b,~c,…~zを付けて名前の重複を回避する。
Dim indx(0 To 61) As String
Dim i As Integer
indx(0) = ""
For i = 1 To 9
indx(i) = "~" & Chr(Asc("1") + i - 1)
Next
For i = 10 To 35
indx(i) = "~" & Chr(Asc("A") + i - 10)
Next
For i = 36 To 61
indx(i) = "~" & Chr(Asc("a") + i - 36)
Next
' 相対パスの取得
Dim rPath As String
rPath = Mid(GetRelativePath(dPath, fPath), 3)
' 禁則文字の削除
rPath = Replace(rPath, "\", "|") ' フォルダ区切りは | に置き換える
rPath = Replace(rPath, "?", "")
rPath = Replace(rPath, ":", "")
rPath = Replace(rPath, "[", "")
rPath = Replace(rPath, "]", "")
rPath = Replace(rPath, "/", "")
rPath = Replace(rPath, "*", "")
rPath = Replace(rPath, "'", "")
rPath = Replace(rPath, "¥", "")
Dim sheetName As String
sheetName = Left(Right(rPath, 29 + 4), 29) ' 拡張子を除いて末尾29文字を取得し,これをシート名とする。
' 重複の判断はシート名変更の成否で判断
i = 0
Dim sheetNameChanged As Boolean
sheetNameChanged = False
On Error Resume Next
Do While sheetNameChanged = False
sh.Name = sheetName & indx(i)
If Err.Number = 0 Then
sheetNameChanged = True
Else
i = i + 1 ' Err.Number <> 0 の時はシート名の変更でエラーが出ているので,添え字をカウントアップする。
If i > 61 Then
MsgBox "シート名に付ける添え字がオーバーフローしました。重複するシート名となるファイル名を減らしてください。" ' 61超えるのは想定していない。
Stop
End If
Err.Clear
End If
Loop
On Error GoTo 0
End Sub
Module1
参考サイト:https://excel-ubara.com/excelvba5/EXCELVBA271.html
参考サイト:https://dobon.net/vb/dotnet/string/detectcode.html
Option Explicit
' https://excel-ubara.com/excelvba5/EXCELVBA271.html を参考に作成
' CSVの読み込み方法(ジャグ配列)(改)
' Redim Preserve などの重い処理は極力回避して高速に処理できるように配慮
' 1列読み込み時の格納セル
' Excel2013のワークシートの最大列数である16384列としてある。
' putjagArrayにて使用。putjagArrayでも16384を超える場合は処理中止するようにしている。
Private Const COLUMN_MAX As Long = 16384
Private colArray(1 To COLUMN_MAX) As Variant
'CSVファイルを指定のセルから出力する(戻り値は出力した行数)
' ByRef r As Range, 出力先のセル。出力後は次に出力すべきセルが設定される
' ByVal f As File, CSVファイル
' Optional ByVal AddFileInfo As Boolean = False, これがTrueのとき,A,B列にはフォルダパスおよびファイル名を出力する
' Optional ByVal UseSubSheet As Boolean = False, これがTrueのとき,ワークシートの最終行まで出力したら次のワークシートを作成して出力する
' Optional ByVal CharSet As String = "Auto") As Long 文字コードの指定
' 文字コードの種類は"SHIFT-JIS","EUC-JP","UTF-8N","UTF-8","UTF-16 LE","UTF-16 BE","UTF-32 LE","UTF-32 BE"
Public Function CsvToRange( _
ByRef r As Range, _
ByVal f As File, _
Optional ByVal AddFileInfo As Boolean = False, _
Optional ByVal UseSubSheet As Boolean = False, _
Optional ByVal CharSet As String = "Auto") As Long
' AddFileInfoする場合はCSV出力の列をC列からとする必要がある
Dim colOffset As Integer ' CSV出力先のオフセット
If AddFileInfo Then
colOffset = 2
Else
colOffset = 0
End If
Debug.Print f.path
Debug.Print " 000_Start:" & Timer & " sec."
Application.StatusBar = Right(f.path, 64) & "..." & "CSVファイルの読み込み中"
DoEvents
'readCsvでCSVを読み込み。文字コードチェックもここで行われる。
Dim strRec As String ' CSVファイルの文字列データ
strRec = readCsv(f.path, CharSet)
Debug.Print " 100_StartCSVToJagged:" & Timer & " sec."
Application.StatusBar = Right(f.path, 64) & "..." & "CSVファイルの解析中"
DoEvents
'CsvToJaggedで行・列に分割してジャグ配列に
Dim jagRows As Long ' ジャグ配列の行数を受取る
Dim jaggedArray() As Variant ' ジャグ配列
jaggedArray = CsvToJagged(strRec, jagRows, UseSubSheet)
strRec = ""
Debug.Print " 200_StartJaggedTo2D:" & Timer & " sec."
Application.StatusBar = Right(f.path, 64) & "..." & "Excelワークシートに書込み中"
DoEvents
'JaggedTo2Dでジャグ配列を2次元配列に変換
Dim myArray() As Variant ' 2次元配列
Call JaggedTo2D(jaggedArray, jagRows, myArray)
Erase jaggedArray
Debug.Print " 300_StartSetExcelSheet:" & Timer & " sec."
'2次元配列→セルへ書き込み
'最大列数のチェック
If r.Column + colOffset + UBound(myArray, 2) > Columns.count Then
MsgBox "Excelの最大列数(" & Columns.count & "列)を超えました。" & vbCrLf & r.Column + UBound(myArray, 2)
Stop
End If
'最大行数のチェック
If r.Row + UBound(myArray, 1) >= Rows.count Then
'最大行を超えた場合は,次のシートへ行くかどうか判定
If UseSubSheet Then
'次のシートへ行く必要がある。まずはこのシートに出力できる分を出力しておく。
Debug.Print " 301_AddSubSheet:" & Timer & " sec."
Dim index As Integer ' 追加されるシートの枚数。最大で61までとしている。
Dim partArray() As Variant 'このシートで出力しておくべき部分を格納する部分配列
Dim partUBound As Long '部分配列に格納された元の2次元配列の範囲(最大値)
Dim partLBound As Long '部分配列に格納された元の2次元配列の範囲(最小値)
Dim partLBound_1 As Long '部分配列に格納された元の2次元配列の範囲(最小値-1)
Dim partArrayCount As Long '部分配列の行数
Dim i As Long
Dim j As Long
partLBound = 1
partUBound = 0
index = 0
Do While partUBound < UBound(myArray, 1) ' すべての配列の中身を出力し終わるまで繰り返す
Debug.Print " 301_AddSubSheet:" & Timer & " sec."
DoEvents
' 最終行までに残りの部分が全て出力できるか。
If UBound(myArray, 1) - partUBound < Rows.count - r.Row Then
partUBound = UBound(myArray, 1) ' できるなら最後まで
Else
partUBound = partLBound - r.Row + Rows.count - 1 ' できないなら最終行-1まで
End If
' 部分配列の確保とコピー
partArrayCount = partUBound - partLBound + 1
partLBound_1 = partLBound - 1
ReDim partArray(1 To partArrayCount, UBound(myArray, 2))
For i = 1 To partArrayCount
For j = 1 To UBound(myArray, 2)
partArray(i, j) = myArray(partLBound_1 + i, j)
Next
Next
' 出力
r.Offset(0, colOffset).Resize(UBound(partArray, 1), UBound(partArray, 2)) = partArray
' フォルダパスとファイル名の出力
If AddFileInfo Then
r.Offset(0, 0).Resize(UBound(partArray, 1), 1) = f.ParentFolder.path
r.Offset(0, 1).Resize(UBound(partArray, 1), 1) = f.Name
End If
' 出力完了したのでセルを次の出力先にセット
Set r = r.Offset(partArrayCount, 0)
' 次の出力先=最終行になっていた場合は,Worksheet追加して次のシートに次の出力先を設定
If r.Row = Rows.count Then
Dim sh As Worksheet
index = index + 1
Set sh = AddSubSheet(r.Worksheet.Parent, r.Worksheet.Name, index) ' シートの追加とシート名の設定を行う
Set r = sh.Cells(1, r.Column)
Application.StatusBar = Right(f.path, 64) & "..." & "Excelワークシートを追加して書込み中"
End If
' 次に格納する2次元配列の下限を設定
partLBound = partUBound + 1
Loop
Else
' 次のシートは使わないので中止
MsgBox "Excelの最大行数(" & Rows.count & "行)を超えました。" & vbCrLf & r.Row + UBound(myArray, 1)
Stop
End If
Else
' 最大行を超えていないのでそのまま出力する
r.Offset(0, colOffset).Resize(UBound(myArray, 1), UBound(myArray, 2)) = myArray
' フォルダパスとファイル名の出力
If AddFileInfo Then
r.Offset(0, 0).Resize(UBound(myArray, 1), 1) = f.ParentFolder.path
r.Offset(0, 1).Resize(UBound(myArray, 1), 1) = f.Name
End If
' 出力完了したのでセルを次の出力先にセット
Set r = r.Offset(UBound(myArray, 1), 0)
End If
Debug.Print " 400_EndSetExcelSheet:" & Timer & " sec."
CsvToRange = UBound(myArray, 1)
Erase myArray
Debug.Print " 500_Complete:" & Timer & " sec."
End Function
' シートの追加とシート名の設定を行う
' book As Workbook, シートを追加するワークブック
' shName As String, シート名
' index As Integer シート名につける添え字を生成するための数字。これに基づき ~1,~2,~3,…,~A,~B,~C,…,~a,~b,~c,…~zまでの添え字をつける。最大値は61
Private Function AddSubSheet(book As Workbook, shName As String, index As Integer) As Worksheet
Dim indx(0 To 61) As String
' 入力チェック
If index > 61 Then
MsgBox "シート数に付ける添え字がオーバーフローしました。"
Stop
ElseIf index < 0 Then
Stop
End If
Dim i As Integer
' 添え字初期化
indx(0) = ""
For i = 1 To 9
indx(i) = "~" & Chr(Asc("1") + i - 1)
Next
For i = 10 To 35
indx(i) = "~" & Chr(Asc("A") + i - 10)
Next
For i = 36 To 61
indx(i) = "~" & Chr(Asc("a") + i - 36)
Next
' Worksheetの追加と名前の変更
Dim sh As Worksheet
Set sh = book.Sheets.Add(After:=book.Sheets(book.Sheets.count))
sh.Name = Right(shName, 29) & indx(index)
Set AddSubSheet = sh
End Function
'ジャグ配列を2次元配列に変換
' ByRef jagArray() As Variant, ' ジャグ配列
' ByVal jagRows As Long, ' ジャグ配列の行数を受取る
' ByRef twoDArray As Variant) ' 2次元配列を返す
Private Sub JaggedTo2D( _
ByRef jagArray() As Variant, _
ByVal jagRows As Long, _
ByRef twoDArray As Variant)
'ジャグ配列の最大列数取得
Debug.Print " 210_JaggedTo2D_GetMaxCol:" & Timer & " sec."
Dim maxCol As Long, v As Variant
Dim i As Long
maxCol = 0
For i = 1 To jagRows
If UBound(jagArray(i)) > maxCol Then
maxCol = UBound(jagArray(i))
End If
Next
'ジャグ配列→2次元配列
Debug.Print " 211_JaggedTo2D_Make2DArray:" & Timer & " sec."
Dim i1 As Long, i2 As Long
ReDim twoDArray(1 To jagRows, 1 To maxCol)
For i1 = 1 To jagRows
For i2 = 1 To UBound(jagArray(i1))
twoDArray(i1, i2) = jagArray(i1)(i2)
Next
Next
End Sub
' CSVの文字列をジャグ配列にして返す
' ByRef strRec As String, CSV(文字列データ)
' ByRef jagRows As Long) ジャグ配列の行数を返す
' As Variant() ジャグ配列を返す
Private Function CsvToJagged( _
ByRef strRec As String, _
ByRef jagRows As Long, _
ByVal UseSubSheet As Boolean) _
As Variant()
Dim lngQuate As Long 'ダブルクォーテーションの数(この関数内でカウントアップされ,putcolArray関数内でリセットされる)
Dim strCell As New StringBuilder '1フィールド文字列(この関数内で文字列が追加され,putcolArray関数内でクリアされる)
Dim i As Long '行位置
Dim j As Long '列位置(putcolArray関数内でカウントアップされ,putjagArray関数内でリセットされる)
Dim k As Long
' CSV文字列長
Dim lenStrRec As Long
lenStrRec = Len(strRec)
' ステータスバー表示用
Dim dispk As Long
dispk = lenStrRec / 20
Dim disppercent As Integer
Dim c As String ' 分析中の文字
Dim d As String ' cの1文字前
ReDim CsvToJagged(1 To Rows.count) 'ジャグ配列の初期化(ReDim Preserveは処理が遅いのでExcelのシートで取り扱える最大行数としている。)
i = 1 'シートの1行目から出力
j = 0 '列位置(putcolArray関数内でカウントアップされ,putjagArray関数内でリセットされる)
lngQuate = 0 'ダブルクォーテーションの数(この関数内でカウントアップされ,putcolArray関数内でリセットされる)
' ステータスバー表示用
Dim stat_len As Integer
stat_len = Len(Application.StatusBar)
disppercent = 0
Application.StatusBar = Left(Application.StatusBar, stat_len) & " [0%]"
DoEvents
For k = 1 To lenStrRec
' ステータスバー表示用
If k Mod dispk = 0 Then
disppercent = disppercent + 5
Application.StatusBar = Left(Application.StatusBar, stat_len) & " [" & disppercent & "%] (" & k & "/" & lenStrRec & ")"
DoEvents
End If
c = Mid(strRec, k, 1)
Select Case c
Case vbCr '「"」が偶数なら改行、奇数ならただの文字
If lngQuate Mod 2 = 0 Then
Call putcolArray(j, strCell, lngQuate) ' 列を挿入
Call putjagArray(CsvToJagged, i, j, lngQuate, strCell, UseSubSheet) ' 行を挿入
Else
strCell.Append1 c ' 文字を追加
End If
Case vbLf '「"」が偶数なら改行、奇数ならただの文字
If lngQuate Mod 2 = 0 Then
If d <> vbCr Then '前の文字が CR の場合はCRLFであり,CRの処理にてやるべきことは完了しているので無視する
Call putcolArray(j, strCell, lngQuate) ' 列を挿入
Call putjagArray(CsvToJagged, i, j, lngQuate, strCell, UseSubSheet) ' 行を挿入
End If
Else
strCell.Append1 c ' 文字を追加
End If
Case "," '「"」が偶数なら区切り、奇数ならただの文字
If lngQuate Mod 2 = 0 Then
Call putcolArray(j, strCell, lngQuate) ' 列を挿入
Else
strCell.Append1 c ' 文字を追加
End If
Case """" '「"」のカウントをとる
lngQuate = lngQuate + 1
strCell.Append1 c ' 文字を追加
Case Else
strCell.Append1 c ' 文字を追加
End Select
d = c
Next
'最終行の最終列の処理
If j > 0 And strCell.length <> 0 Then
Call putcolArray(j, strCell, lngQuate) ' 列を挿入
Call putjagArray(CsvToJagged, i, j, lngQuate, strCell, UseSubSheet) ' 行を挿入
End If
jagRows = i - 1 'ジャグ配列の行数を返す
End Function
' CSVの行を挿入(ジャグ配列に追加する)
' ByRef jagArray() As Variant, ジャグ配列 _
ByRef i As Long, 追加するジャグ配列の行番号 _
ByRef j As Long, 追加される列配列の長さ(ここでリセットされる) _
ByRef lngQuate As Long, 'ダブルクォーテーション数(ここでリセットしている) _
ByRef strCell As StringBuilder) フィールド文字列(ここでクリアしている) _
ByVal UseSubSheet As Boolean) ワークシートの上限を超える場合True
Private Sub putjagArray(ByRef jagArray() As Variant, _
ByRef i As Long, _
ByRef j As Long, _
ByRef lngQuate As Long, _
ByRef strCell As StringBuilder, _
ByVal UseSubSheet As Boolean)
If j > COLUMN_MAX Then
' 列配列の最大長を超えるので中止
MsgBox "Excelの最大列数(" & COLUMN_MAX & "列)を超えました。" & vbCrLf & "(" & j & "列目が入力されました)"
Stop
Else
If UseSubSheet Then ' もしワークシートの上限である行数を超えても良い設定がされており
If i > UBound(jagArray) Then ' 実際にワークシートの上限を超える行数が入力された場合
ReDim Preserve jagArray(1 To i + Rows.count) ' 配列の長さを増やす。(Excelのシートで取り扱える最大行数分だけ追加)(未テスト)
End If
End If
' ジャグ配列へ格納する子配列の作成
Dim childArray() As Variant
ReDim childArray(1 To j)
'子配列をジャグ配列に入れる
jagArray(i) = childArray ' もししワークシートの上限である行数を超えても良い設定がされておらず,上限を超える行数が入力された場合はここエラーになります。(手抜きで例外は拾ってない)
Erase childArray
' 列配列からのコピー
Dim k As Long
For k = 1 To j
jagArray(i)(k) = colArray(k)
Next
End If
i = i + 1 '行番号のカウントアップ
j = 0 '列位置のリセット
lngQuate = 0 'ダブルクォーテーション数のリセット
strCell.Clear '1フィールド文字列のクリア
End Sub
'CSVの列を挿入(列配列へのフィールドの追加)
' ByRef j As Long, 追加先の列番号マイナス1(ここでカウントアップする) _
ByRef strCell As StringBuilder, 追加するフィールド文字列(これは,クリアして返す) _
ByRef lngQuate As Long ' ダブルクオーテーションの数(ここでリセットされる) )
Private Sub putcolArray(ByRef j As Long, _
ByRef strCell As StringBuilder, _
ByRef lngQuate As Long)
' 列番号のカウントアップ
j = j + 1
' フィールド文字列の設定
Dim s As String
Dim lens As Long
s = strCell.ToString
lens = strCell.length
' フィールド文字列のクリア
strCell.Clear
'前後の「"」を削除
If Left(s, 1) = """" And Right(s, 1) = """" Then
If lens <= 2 Then
s = ""
Else
s = Mid(s, 2, lens - 2)
End If
lngQuate = lngQuate - 2
End If
'「""」を「"」で置換
If lngQuate >= 2 Then
s = Replace(s, """""", """")
End If
colArray(j) = s 'CSVの列を挿入(列配列へのフィールドの追加)
lngQuate = 0 ' ダブルクオーテーションの数をリセット
End Sub
'文字コードを自動判別Stringに入れる
Private Function readCsv(ByVal strFile As String, _
ByVal CharSet As String) As String
' Dim objFSO As New FileSystemObject
' Dim inTS As TextStream
' Dim adoSt As New ADODB.Stream
Dim objFSO As Object
Set objFSO = CreateObject("Scripting.FileSystemObject")
Dim inTS As Object
Dim adoSt As Object
Set adoSt = CreateObject("ADODB.Stream")
Dim strRec As String
Dim i As Long
Dim aryRec() As String
Debug.Print " 010_StartGetCharSet:" & Timer & " sec."
If CharSet = "Auto" Then
CharSet = getCharSet(strFile)
End If
Debug.Print " 020_StartReadingCSV:" & Timer & " sec."
Application.StatusBar = strFile & "..." & "CSVファイルの読み込み中(" & CharSet & ")"
DoEvents
Select Case UCase(CharSet)
Case "UTF-8", "UTF-8N"
'ADOを使って読込、その後の処理を統一するため全レコードをCrLfで結合
'Set inTS = objFSO.OpenTextFile(strFile, ForAppending)
Set inTS = objFSO.OpenTextFile(strFile, 8)
i = inTS.Line - 1
inTS.Close
ReDim aryRec(i)
With adoSt
'.Type = adTypeText
.Type = 2
.CharSet = "utf-8"
.Open
.LoadFromFile strFile
i = 0
Do While Not (.EOS)
'aryRec(i) = .ReadText(adReadLine)
aryRec(i) = .ReadText(-2)
i = i + 1
Loop
.Close
strRec = Join(aryRec, vbCrLf)
End With
Case "EUC-JP" 'EUC-JPはADOで読み込めると思うけど未テスト
Set inTS = objFSO.OpenTextFile(strFile, 8)
i = inTS.Line - 1
inTS.Close
ReDim aryRec(i)
With adoSt
'.Type = adTypeText
.Type = 2
.CharSet = "euc-jp"
.Open
.LoadFromFile strFile
i = 0
Do While Not (.EOS)
'aryRec(i) = .ReadText(adReadLine)
aryRec(i) = .ReadText(-2)
i = i + 1
Loop
.Close
strRec = Join(aryRec, vbCrLf)
End With
Case "UTF-16 LE", "UTF-16 BE"
'Set inTS = objFSO.OpenTextFile(strFile, , , TristateTrue)
Set inTS = objFSO.OpenTextFile(strFile, , , -1)
strRec = inTS.ReadAll
inTS.Close
Case "SHIFT-JIS"
Set inTS = objFSO.OpenTextFile(strFile)
strRec = inTS.ReadAll
inTS.Close
Case Else
' UTF-32 LE,UTF-32 BEについては未実装
' 文字コードが分からないときは _autodetect で読んでみても良いかも。
MsgBox "文字コードを確認してください。" & vbLf & CharSet
Stop
' _autodetect を試す場合は F5キーを押して継続してね。
Set inTS = objFSO.OpenTextFile(strFile, 8)
i = inTS.Line - 1
inTS.Close
ReDim aryRec(i)
With adoSt
'.Type = adTypeText
.Type = 2
.CharSet = "_autodetect"
.Open
.LoadFromFile strFile
i = 0
Do While Not (.EOS)
'aryRec(i) = .ReadText(adReadLine)
aryRec(i) = .ReadText(-2)
i = i + 1
Loop
.Close
strRec = Join(aryRec, vbCrLf)
End With
MsgBox Left(strRec, 256)
Stop
' どうかな?読み込めたかな?
End Select
Set inTS = Nothing
Set objFSO = Nothing
Set adoSt = Nothing
readCsv = strRec
End Function
'文字コードの自動判別
Private Function getCharSet(strFileName As String) As String
Application.StatusBar = strFileName & "..." & "CSVファイルの読み込み中(文字コード解析中)"
DoEvents
Dim bytes() As Byte
Dim intFileNo As Integer
Debug.Print " 011_GetCharset_StartReadFile:" & Timer & " sec."
' 時間かかるので先頭64KBで判定する。
If FileLen(strFileName) > 65535 Then
ReDim bytes(65535)
Else
ReDim bytes(FileLen(strFileName))
End If
' ファイル読み込み(先頭64KBのみ)
intFileNo = FreeFile
Open strFileName For Binary As #intFileNo
Get #intFileNo, , bytes
Close intFileNo
Debug.Print " 012_GetCharset_FromBOM:" & Timer & " sec."
'BOMによる判断
getCharSet = getCharFromBOM(bytes)
'BOMなしをデータの文字コードで判別
If getCharSet = "" Then
Debug.Print " 013_GetCharset_FromCode:" & Timer & " sec."
getCharSet = getCharFromCode(bytes)
End If
Erase bytes
Debug.Print " 014_GetCharset_Complete(" & getCharSet & "):" & Timer & " sec."
End Function
'文字コードの自動判別(BOMによる判断)
Private Function getCharFromBOM(ByRef bytes() As Byte) As String
getCharFromBOM = ""
If UBound(bytes) < 3 Then Exit Function
Select Case True
Case bytes(0) = &HEF And _
bytes(1) = &HBB And _
bytes(2) = &HBF
getCharFromBOM = "UTF-8"
Exit Function
Case bytes(0) = &HFF And _
bytes(1) = &HFE
If bytes(2) = &H0 And _
bytes(3) = &H0 Then
getCharFromBOM = "UTF-32 LE"
Exit Function
End If
getCharFromBOM = "UTF-16 LE"
Exit Function
Case bytes(0) = &HFE And _
bytes(1) = &HFF
getCharFromBOM = "UTF-16 BE"
Exit Function
Case bytes(0) = &H0 And _
bytes(1) = &H0 And _
bytes(2) = &HFE And _
bytes(3) = &HFF
getCharFromBOM = "UTF-32 BE"
Exit Function
End Select
End Function
'文字コードの自動判別(BOMなしデータから推定)
'以下は下記サイトのコードをVBAに移植
'https://dobon.net/vb/dotnet/string/detectcode.html
'BOMなしをデータの文字コードで判別⇒"SHIFT-JIS","UTF-8N","EUC-JP"
'これは元はJcode.pmのgetcodeメソッドを移植したものです。
''' Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html)
''' Jcode.pmの著作権情報
''' Copyright 1999-2005 Dan Kogai <dankogai@dan.co.jp>
''' This library is free software; you can redistribute it and/or modify it
''' under the same terms as Perl itself.
Private Function getCharFromCode(ByRef bytes() As Byte) As String
Const bEscape As Byte = &H1B
Const bAt As Byte = &H40
Const bDollar As Byte = &H24
Const bAnd As Byte = &H26
Const bOpen As Byte = &H28
Const bB As Byte = &H42
Const bD As Byte = &H44
Const bJ As Byte = &H4A
Const bI As Byte = &H49
Dim bLen As Long: bLen = UBound(bytes)
Dim b1 As Byte, b2 As Byte, b3 As Byte, b4 As Byte
Dim isBinary As Boolean: isBinary = False
Dim i As Long
For i = 0 To bLen - 1
b1 = bytes(i)
If b1 <= &H6 Or b1 = &H7F Or b1 = &HFF Then
' binary
isBinary = True
If b1 = &H0 And i < bLen - 1 And bytes(i + 1) <= &H7F Then
'smells like raw unicode
getCharFromCode = "SHIFT-JIS"
Exit Function
End If
End If
Next
If isBinary Then
getCharFromCode = ""
Exit Function
End If
' Not Japanese (ASCII)
Dim notJapanese As Boolean
notJapanese = True
For i = 0 To bLen - 1
b1 = bytes(i)
If b1 = bEscape Or &H80 <= b1 Then
notJapanese = False
Exit For
End If
Next
If notJapanese Then
getCharFromCode = "SHIFT-JIS"
Exit Function
End If
For i = 0 To bLen - 3
b1 = bytes(i)
b2 = bytes(i + 1)
b3 = bytes(i + 2)
If b1 = bEscape Then
If b2 = bDollar And b3 = bAt Then
'JIS_0208 1978
getCharFromCode = "SHIFT-JIS"
Exit Function
ElseIf b2 = bDollar And b3 = bB Then
'JIS_0208 1983
getCharFromCode = "SHIFT-JIS"
Exit Function
ElseIf b2 = bOpen And (b3 = bB Or b3 = bJ) Then
'JIS_ASC
getCharFromCode = "SHIFT-JIS"
Exit Function
ElseIf b2 = bOpen And b3 = bI Then
'JIS_KANA
getCharFromCode = "SHIFT-JIS"
Exit Function
End If
If i < bLen - 3 Then
b4 = bytes(i + 3)
If b2 = bDollar And b3 = bOpen And b4 = bD Then
'JIS_0212
getCharFromCode = "SHIFT-JIS"
Exit Function
End If
If i < bLen - 5 And _
b2 = bAnd And b3 = bAt And b4 = bEscape And _
bytes(i + 4) = bDollar And bytes(i + 5) = bB Then
'JIS_0208 1990
getCharFromCode = "SHIFT-JIS"
Exit Function
End If
End If
End If
Next
'should be euc|sjis|utf8
'use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp>
Dim sjis As Long: sjis = 0
Dim euc As Long: euc = 0
Dim utf8 As Long: utf8 = 0
For i = 0 To bLen - 2
b1 = bytes(i)
b2 = bytes(i + 1)
If ((&H81 <= b1 And b1 <= &H9F) Or (&HE0 <= b1 And b1 <= &HFC)) And _
((&H40 <= b2 And b2 <= &H7E) Or (&H80 <= b2 And b2 <= &HFC)) Then
'SJIS_C
sjis = sjis + 2
i = i + 1
End If
Next
For i = 0 To bLen - 2
b1 = bytes(i)
b2 = bytes(i + 1)
If ((&HA1 <= b1 And b1 <= &HFE) And _
(&HA1 <= b2 And b2 <= &HFE)) Or _
(b1 = &H8E And (&HA1 <= b2 And b2 <= &HDF)) Then
'EUC_C
'EUC_KANA
euc = euc + 2
i = i + 1
ElseIf i < bLen - 2 Then
b3 = bytes(i + 2)
If b1 = &H8F And (&HA1 <= b2 And b2 <= &HFE) And _
(&HA1 <= b3 And b3 <= &HFE) Then
'EUC_0212
euc = euc + 3
i = i + 2
End If
End If
Next
For i = 0 To bLen - 2
b1 = bytes(i)
b2 = bytes(i + 1)
If (&HC0 <= b1 And b1 <= &HDF) And _
(&H80 <= b2 And b2 <= &HBF) Then
'UTF8
utf8 = utf8 + 2
i = i + 1
ElseIf i < bLen - 2 Then
b3 = bytes(i + 2)
If (&HE0 <= b1 And b1 <= &HEF) And _
(&H80 <= b2 And b2 <= &HBF) And _
(&H80 <= b3 And b3 <= &HBF) Then
'UTF8
utf8 = utf8 + 3
i = i + 2
End If
End If
Next
'M. Takahashi's suggestion
'utf8 += utf8 / 2;
Select Case True
Case euc > sjis And euc > utf8
getCharFromCode = "EUC-JP"
Case utf8 > euc And utf8 > sjis
getCharFromCode = "UTF-8N"
Case sjis > euc And sjis > utf8
getCharFromCode = "SHIFT-JIS"
Case Else '判定できない場合
getCharFromCode = ""
End Select
End Function
Module2
Option Explicit
' Formの呼出
Sub CSVの結合()
UserForm1.Show
UserForm1.CommandButton2.Enabled = True
End Sub
Module3
Option Explicit
' Fromで指定されたパスからToで指定されたパスを参照する相対パスを作成しPathに返却する。
' ByVal relative As String 取得結果の相対パス
' ByVal pathFrom As String 参照元のパス
' ByVal attribFrom As Long Fromで指定したパスのファイル属性。特に、Fromがファイルを表すか、ディレクトリを表すかを判定するために使用される
' ByVal pathTo As String 参照先のパス
' ByVal attribTo As Long Toで指定したパスのファイル属性。特に、Toがファイルを表すか、ディレクトリを表すかを判定するために使用される
Private Declare PtrSafe Function PathRelativePathTo _
Lib "Shlwapi.dll" _
Alias "PathRelativePathToA" ( _
ByVal relative As String, _
ByVal pathFrom As String, _
ByVal attribFrom As Long, _
ByVal pathTo As String, _
ByVal attribTo As Long) _
As Long
Private Const MAX_PATH As Integer = 260
Private Const FILE_ATTRIBUTE_DIRECTORY As Integer = 16
' 相対パスを取得
Public Function GetRelativePath(ByVal pathFrom As String, ByVal path As String) As String
Dim length As Integer
Dim relative As String
relative = String(MAX_PATH, Chr(0))
If PathRelativePathTo(relative, pathFrom, FILE_ATTRIBUTE_DIRECTORY, path, 0) Then
length = InStr(relative, Chr(0)) - 1
GetRelativePath = Left(relative, length)
Else
GetRelativePath = path
End If
End Function
StringBuilderクラス
参考サイト:https://excel-ubara.com/excelvba4/EXCEL271.html
Option Explicit
' https://excel-ubara.com/excelvba4/EXCEL271.html を参考に作成
' 遅い文字列結合を最速処理する方法について
' 文字列の結合を高速化している
Private iBuf As Long ' 実際に格納している文字列の長さ
Private sBuf As String ' 文字列の実体
Private sBufLen As Long ' 確保している文字列の実体の長さ
Private Const initLen As Integer = 1024 ' 初期に確保する文字列の実体の長さ
' 文字列を初期化
Private Sub Class_Initialize()
sBuf = String$(initLen, vbNullChar)
sBufLen = Len(sBuf)
iBuf = 0
End Sub
' 文字列の末尾に文字列を追加
Public Sub Append(ByRef sValue As String)
Dim lens As Long
lens = Len(sValue)
If iBuf + lens > sBufLen Then ' 確保した実体の長さを超える場合は拡大する
sBufLen = sBufLen * 2
sBuf = sBuf & String$(sBufLen + lens, vbNullChar)
End If
Mid(sBuf, iBuf + 1) = sValue
iBuf = iBuf + lens
End Sub
' 文字列の末尾に文字列を1文字だけ追加(今回のCSV処理では多用するので)
Public Sub Append1(ByRef sValue As String)
Dim iBuf_1 As Long
iBuf_1 = iBuf + 1
If iBuf_1 > sBufLen Then ' 確保した実体の長さを超える場合は拡大する
sBufLen = sBufLen * 2
sBuf = sBuf & String$(sBufLen + 1, vbNullChar)
End If
Mid(sBuf, iBuf_1) = sValue
iBuf = iBuf_1
End Sub
' 文字列として切り出す
Public Function ToString() As String
ToString = Left$(sBuf, iBuf)
End Function
' 文字の実体はそのままに長さを0とする
Public Sub Clear()
iBuf = 0
End Sub
' 長さを返す
Public Function length() As Long
length = iBuf
End Function
0件のコメント