スクリプトファイルを追加
This commit is contained in:
489
csv_maker_vba.txt
Normal file
489
csv_maker_vba.txt
Normal file
@@ -0,0 +1,489 @@
|
||||
Option Explicit
|
||||
|
||||
' ============================================
|
||||
' CSV取り込み + ヘッダ/ドロップダウン設定(列挙メタは改行不問で検知)
|
||||
' 実行時に新規シートを作成して結果を出力
|
||||
' ============================================
|
||||
|
||||
' ===== 設定定数 =====
|
||||
Private Const DV_USE_CODE_ONLY As Boolean = True ' True: ドロップダウンをコードのみ(例: 0,1)にする
|
||||
Private Const APPLY_DV_TO_ROW As Long = 50 ' データがない場合にDVを適用する最終行
|
||||
Private Const CSV_ENCODING As String = "auto" ' "auto"|"utf8"|"cp932"
|
||||
Private Const SHEET_NAME_PREFIX As String = "Import_" ' 作成シート名の接頭辞
|
||||
|
||||
'==== ボタンに割り当てるエントリーポイント ====
|
||||
Public Sub Run_ImportCsv_CreateNewSheet()
|
||||
Dim ctrlSheet As Worksheet
|
||||
Set ctrlSheet = ActiveSheet ' ボタンを置いたシート(操作用)
|
||||
|
||||
' CSVパスを選択
|
||||
Dim fpath As String
|
||||
fpath = PickCsvFilePath()
|
||||
If Len(fpath) = 0 Then
|
||||
MsgBox "CSVの選択がキャンセルされました。", vbInformation
|
||||
Exit Sub
|
||||
End If
|
||||
|
||||
' 出力シート名
|
||||
Dim outSheetName As String
|
||||
outSheetName = GetUniqueSheetName(BuildOutputSheetName(fpath))
|
||||
|
||||
' 新規シート作成(操作用シートの後ろ)
|
||||
Dim wsTarget As Worksheet
|
||||
Set wsTarget = ThisWorkbook.Worksheets.Add(After:=ctrlSheet)
|
||||
wsTarget.Name = outSheetName
|
||||
|
||||
' 取り込み実行(結果は wsTarget に出力)
|
||||
ImportCsvToTargetSheet fpath, wsTarget
|
||||
|
||||
' 完了メッセージ
|
||||
MsgBox "取り込み完了: " & wsTarget.Name, vbInformation
|
||||
End Sub
|
||||
|
||||
'==== メイン処理(指定のターゲットシートに出力) ====
|
||||
Private Sub ImportCsvToTargetSheet(ByVal fpath As String, ByVal wsTarget As Worksheet)
|
||||
Dim wsTemp As Worksheet
|
||||
Set wsTemp = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.count))
|
||||
wsTemp.Name = GetUniqueTempSheetName("CSV_RAW")
|
||||
|
||||
On Error GoTo CLEANUP_ON_ERROR
|
||||
|
||||
' CSVを一時シートに読み込み(Power Query)
|
||||
If Not ImportCsvToTempSheet(fpath, CSV_ENCODING, wsTemp) Then
|
||||
Err.Raise vbObjectError + 101, , "CSVの取り込みに失敗しました。"
|
||||
End If
|
||||
|
||||
' 使用範囲特定
|
||||
Dim lastRow As Long, lastCol As Long
|
||||
lastRow = GetLastRow(wsTemp)
|
||||
lastCol = GetLastCol(wsTemp)
|
||||
If lastCol = 0 Or lastRow = 0 Then
|
||||
Err.Raise vbObjectError + 102, , "CSVが空のようです。"
|
||||
End If
|
||||
|
||||
Application.ScreenUpdating = False
|
||||
Application.EnableEvents = False
|
||||
|
||||
' ターゲットシートをクリア
|
||||
wsTarget.Cells.Clear
|
||||
|
||||
' ------- 1) ヘッダ処理(列挙メタはセル内に含まれていれば検知) -------
|
||||
Dim colHasEnum() As Boolean
|
||||
Dim colEnumList() As String
|
||||
ReDim colHasEnum(1 To lastCol)
|
||||
ReDim colEnumList(1 To lastCol)
|
||||
|
||||
Dim c As Long
|
||||
For c = 1 To lastCol
|
||||
Dim headerRaw As String
|
||||
headerRaw = Nz(wsTemp.Cells(1, c).Value, "")
|
||||
|
||||
' ヘッダはそのまま出力(列挙メタ文字列も残す)
|
||||
wsTarget.Cells(1, c).Value = headerRaw
|
||||
|
||||
' ヘッダに列挙メタ [n:ラベル] が1つでも含まれていれば検知
|
||||
If ContainsEnumeration(headerRaw) Then
|
||||
colHasEnum(c) = True
|
||||
' ヘッダ全体から [n:ラベル] を抽出し、DV用のCSV文字列を構築
|
||||
colEnumList(c) = BuildValidationListFromEnum(headerRaw, DV_USE_CODE_ONLY)
|
||||
Else
|
||||
colHasEnum(c) = False
|
||||
colEnumList(c) = ""
|
||||
End If
|
||||
Next c
|
||||
|
||||
' ------- 2) データの貼付(CSV 2行目→Excel 2行目) -------
|
||||
Dim tgtDataStartRow As Long: tgtDataStartRow = 2
|
||||
Dim tgtLastRow As Long: tgtLastRow = 1
|
||||
|
||||
If lastRow >= 2 Then
|
||||
Dim dataRowCount As Long
|
||||
dataRowCount = lastRow - 1 ' 1行目はヘッダ
|
||||
wsTemp.Range(wsTemp.Cells(2, 1), wsTemp.Cells(lastRow, lastCol)).Copy
|
||||
wsTarget.Cells(tgtDataStartRow, 1).PasteSpecial xlPasteValues
|
||||
tgtLastRow = tgtDataStartRow + dataRowCount - 1
|
||||
Else
|
||||
tgtLastRow = 1 ' データ無し
|
||||
End If
|
||||
|
||||
' ------- 3) ドロップダウン(入力規則)設定 -------
|
||||
Dim applyToLast As Long
|
||||
applyToLast = IIf(tgtLastRow >= 2, tgtLastRow, APPLY_DV_TO_ROW)
|
||||
|
||||
For c = 1 To lastCol
|
||||
If colHasEnum(c) And Len(colEnumList(c)) > 0 Then
|
||||
Dim dvRange As Range
|
||||
Set dvRange = wsTarget.Range(wsTarget.Cells(2, c), wsTarget.Cells(applyToLast, c))
|
||||
ApplyValidationList dvRange, colEnumList(c)
|
||||
End If
|
||||
Next c
|
||||
|
||||
' ------- 4) 調整(見やすく) -------
|
||||
wsTarget.Columns("A:" & ColLetter(lastCol)).AutoFit
|
||||
|
||||
Application.CutCopyMode = False
|
||||
Application.ScreenUpdating = True
|
||||
Application.EnableEvents = True
|
||||
|
||||
' 一時シート削除
|
||||
Application.DisplayAlerts = False
|
||||
wsTemp.Delete
|
||||
Application.DisplayAlerts = True
|
||||
Exit Sub
|
||||
|
||||
CLEANUP_ON_ERROR:
|
||||
Application.CutCopyMode = False
|
||||
Application.ScreenUpdating = True
|
||||
Application.EnableEvents = True
|
||||
On Error Resume Next
|
||||
Application.DisplayAlerts = False
|
||||
If Not wsTemp Is Nothing Then wsTemp.Delete
|
||||
Application.DisplayAlerts = True
|
||||
On Error GoTo 0
|
||||
MsgBox "エラー: " & Err.Description, vbExclamation
|
||||
End Sub
|
||||
|
||||
'========================================================
|
||||
' 出力シート名をファイル名から生成(長すぎる・使用不可文字は除去)
|
||||
'========================================================
|
||||
Private Function BuildOutputSheetName(ByVal fpath As String) As String
|
||||
Dim fname As String
|
||||
fname = fpath
|
||||
Dim i As Long
|
||||
|
||||
' パスからファイル名抽出
|
||||
For i = Len(fpath) To 1 Step -1
|
||||
If Mid$(fpath, i, 1) = "\" Or Mid$(fpath, i, 1) = "/" Then
|
||||
fname = Mid$(fpath, i + 1)
|
||||
Exit For
|
||||
End If
|
||||
Next i
|
||||
|
||||
' 拡張子除去
|
||||
Dim dotPos As Long
|
||||
dotPos = InStrRev(fname, ".")
|
||||
If dotPos > 1 Then fname = Left$(fname, dotPos - 1)
|
||||
|
||||
' シート名禁止文字の除去 / の代替
|
||||
fname = Replace(fname, ":", " ")
|
||||
fname = Replace(fname, "/", " ")
|
||||
fname = Replace(fname, "\", " ")
|
||||
fname = Replace(fname, "?", " ")
|
||||
fname = Replace(fname, "*", " ")
|
||||
fname = Replace(fname, "[", " ")
|
||||
fname = Replace(fname, "]", " ")
|
||||
|
||||
' タイムスタンプ付与
|
||||
BuildOutputSheetName = SHEET_NAME_PREFIX & fname & "_" & Format(Now, "yymmdd_hhnnss")
|
||||
' 31文字制限対応(必要なら切り詰め)
|
||||
If Len(BuildOutputSheetName) > 31 Then
|
||||
BuildOutputSheetName = Left$(BuildOutputSheetName, 31)
|
||||
End If
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' シート名の一意化
|
||||
'========================================================
|
||||
Private Function GetUniqueSheetName(ByVal baseName As String) As String
|
||||
Dim nameTry As String, n As Long
|
||||
nameTry = baseName
|
||||
n = 1
|
||||
Do While SheetExists(nameTry)
|
||||
n = n + 1
|
||||
nameTry = Left$(baseName, 28) & "_" & CStr(n) ' 31文字制限に配慮
|
||||
Loop
|
||||
GetUniqueSheetName = nameTry
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' ファイルダイアログでCSVファイルを取得
|
||||
'========================================================
|
||||
Private Function PickCsvFilePath() As String
|
||||
Dim fd As FileDialog
|
||||
Set fd = Application.FileDialog(msoFileDialogFilePicker)
|
||||
|
||||
With fd
|
||||
.Title = "CSVファイルを選択"
|
||||
.Filters.Clear
|
||||
.Filters.Add "CSV ファイル", "*.csv", 1
|
||||
.AllowMultiSelect = False
|
||||
If .Show = -1 Then
|
||||
PickCsvFilePath = .SelectedItems(1)
|
||||
Else
|
||||
PickCsvFilePath = ""
|
||||
End If
|
||||
End With
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' 一時シート名をユニーク化
|
||||
'========================================================
|
||||
Private Function GetUniqueTempSheetName(ByVal baseName As String) As String
|
||||
Dim n As Long
|
||||
Dim nameTry As String
|
||||
n = 0
|
||||
Do
|
||||
n = n + 1
|
||||
nameTry = baseName & "_" & Format(Now, "yymmdd_hhnnss") & "_" & n
|
||||
If SheetExists(nameTry) = False Then
|
||||
GetUniqueTempSheetName = nameTry
|
||||
Exit Do
|
||||
End If
|
||||
Loop
|
||||
End Function
|
||||
|
||||
Private Function SheetExists(ByVal sName As String) As Boolean
|
||||
Dim ws As Worksheet
|
||||
On Error Resume Next
|
||||
Set ws = ThisWorkbook.Worksheets(sName)
|
||||
SheetExists = Not ws Is Nothing
|
||||
On Error GoTo 0
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' CSVを一時シートに読み込み(Power Query エンジン版)
|
||||
' encoding: "auto" | "utf8" | "cp932"
|
||||
'========================================================
|
||||
Private Function ImportCsvToTempSheet(ByVal fpath As String, ByVal encoding As String, ByVal wsTemp As Worksheet) As Boolean
|
||||
On Error GoTo ERRH
|
||||
wsTemp.Cells.Clear
|
||||
|
||||
Dim encCode As Long
|
||||
Select Case LCase$(encoding)
|
||||
Case "utf8": encCode = 65001
|
||||
Case "cp932": encCode = 932
|
||||
Case Else: encCode = 65001 ' autoはまずUTF-8で試行→失敗時に932で再試行
|
||||
End Select
|
||||
|
||||
If ImportCsvViaPowerQuery(fpath, encCode, wsTemp) Then
|
||||
ImportCsvToTempSheet = True
|
||||
Exit Function
|
||||
End If
|
||||
|
||||
' autoのときはフォールバック(UTF-8失敗→CP932)
|
||||
If LCase$(encoding) = "auto" Then
|
||||
If ImportCsvViaPowerQuery(fpath, 932, wsTemp) Then
|
||||
ImportCsvToTempSheet = True
|
||||
Exit Function
|
||||
End If
|
||||
End If
|
||||
|
||||
ImportCsvToTempSheet = False
|
||||
Exit Function
|
||||
ERRH:
|
||||
ImportCsvToTempSheet = False
|
||||
End Function
|
||||
|
||||
Private Function ImportCsvViaPowerQuery(ByVal fpath As String, ByVal encCode As Long, ByVal wsTemp As Worksheet) As Boolean
|
||||
On Error GoTo ERRH
|
||||
|
||||
' クエリ名をユニークに
|
||||
Randomize
|
||||
Dim pqName As String
|
||||
pqName = "PQ_CSV_" & Format(Now, "yymmdd_hhnnss_") & CStr(Int(Rnd() * 100000))
|
||||
|
||||
' --- Power Query (M) 式を定義 ---
|
||||
Dim m As String
|
||||
|
||||
|
||||
m = _
|
||||
"let" & vbCrLf & _
|
||||
" Source = Csv.Document(File.Contents(""" & Replace(fpath, """", """""") & """)," & _
|
||||
" [Delimiter="","", Columns=null, Encoding=" & CStr(encCode) & ", QuoteStyle=QuoteStyle.Csv])," & vbCrLf & _
|
||||
" Promote = Table.PromoteHeaders(Source, [PromoteAllScalars=true])," & vbCrLf & _
|
||||
" AsText = Table.TransformColumnTypes(Promote, List.Transform(Table.ColumnNames(Promote), each {_, type text}))," & vbCrLf & _
|
||||
" AllCols = Table.ColumnNames(AsText)," & vbCrLf & _
|
||||
" // ""Column1"" のような自動列名 かつ 列内の全値が Null または 空白のみ なら削除対象にする" & vbCrLf & _
|
||||
" Trim = (v as any) as text => Text.Trim(Text.From(v, Culture=null))," & vbCrLf & _
|
||||
" IsEmptyCol = (tbl as table, col as text) as logical =>" & vbCrLf & _
|
||||
" List.AllTrue(List.Transform(Table.Column(tbl, col), (v) => v = null or Trim(v) = """" ))," & vbCrLf & _
|
||||
" ToRemove = List.Select(AllCols, (cn) => Text.StartsWith(cn, ""Column"") and IsEmptyCol(AsText, cn))," & vbCrLf & _
|
||||
" Cleaned = if List.Count(ToRemove) > 0 then Table.RemoveColumns(AsText, ToRemove) else AsText" & vbCrLf & _
|
||||
"in" & vbCrLf & _
|
||||
" Cleaned"
|
||||
|
||||
' 既存の内容を一旦クリア
|
||||
wsTemp.Cells.Clear
|
||||
|
||||
' --- クエリの追加(ブック内に定義) ---
|
||||
Dim q As WorkbookQuery
|
||||
Set q = ThisWorkbook.Queries.Add(Name:=pqName, Formula:=m)
|
||||
|
||||
' --- OLEDB 接続文字列(Power Query への橋渡し) ---
|
||||
Dim connStr As String
|
||||
connStr = "OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=" & pqName & ";Extended Properties="""""
|
||||
|
||||
' --- QueryTable を使ってシートにロード(SQL 明示)---
|
||||
Dim qt As QueryTable
|
||||
Set qt = wsTemp.QueryTables.Add(Connection:=connStr, Destination:=wsTemp.Range("A1"))
|
||||
With qt
|
||||
.CommandType = xlCmdSql
|
||||
.CommandText = "SELECT * FROM [" & pqName & "]"
|
||||
.PreserveFormatting = True
|
||||
.AdjustColumnWidth = False
|
||||
.RefreshStyle = xlOverwriteCells
|
||||
.BackgroundQuery = False
|
||||
.Refresh ' ← ここで実行
|
||||
End With
|
||||
|
||||
' 値として固定(再解釈を防ぐ)
|
||||
wsTemp.UsedRange.Value = wsTemp.UsedRange.Value
|
||||
|
||||
' --- 後片付け(接続・クエリの削除) ---
|
||||
On Error Resume Next
|
||||
Dim cn As WorkbookConnection
|
||||
For Each cn In ThisWorkbook.Connections
|
||||
If InStr(1, cn.Name, pqName, vbTextCompare) > 0 Then cn.Delete
|
||||
If Not cn.OLEDBConnection Is Nothing Then
|
||||
If InStr(1, cn.OLEDBConnection.Connection, "Location=" & pqName, vbTextCompare) > 0 Then cn.Delete
|
||||
End If
|
||||
Next
|
||||
ThisWorkbook.Queries(pqName).Delete
|
||||
On Error GoTo 0
|
||||
|
||||
ImportCsvViaPowerQuery = True
|
||||
Exit Function
|
||||
|
||||
ERRH:
|
||||
' クリーンアップ
|
||||
On Error Resume Next
|
||||
Dim cn2 As WorkbookConnection
|
||||
For Each cn2 In ThisWorkbook.Connections
|
||||
If InStr(1, cn2.Name, pqName, vbTextCompare) > 0 Then cn2.Delete
|
||||
If Not cn2.OLEDBConnection Is Nothing Then
|
||||
If InStr(1, cn2.OLEDBConnection.Connection, "Location=" & pqName, vbTextCompare) > 0 Then cn2.Delete
|
||||
End If
|
||||
Next
|
||||
ThisWorkbook.Queries(pqName).Delete
|
||||
On Error GoTo 0
|
||||
|
||||
ImportCsvViaPowerQuery = False
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' 行・列の最終位置
|
||||
'========================================================
|
||||
Private Function GetLastRow(ByVal ws As Worksheet) As Long
|
||||
Dim f As Range
|
||||
On Error Resume Next
|
||||
Set f = ws.Cells.Find(What:="*", After:=ws.Range("A1"), LookAt:=xlPart, _
|
||||
LookIn:=xlFormulas, SearchOrder:=xlByRows, _
|
||||
SearchDirection:=xlPrevious, MatchCase:=False)
|
||||
On Error GoTo 0
|
||||
If f Is Nothing Then
|
||||
GetLastRow = 0
|
||||
Else
|
||||
GetLastRow = f.Row
|
||||
End If
|
||||
End Function
|
||||
|
||||
Private Function GetLastCol(ByVal ws As Worksheet) As Long
|
||||
Dim f As Range
|
||||
On Error Resume Next
|
||||
Set f = ws.Cells.Find(What:="*", After:=ws.Range("A1"), LookAt:=xlPart, _
|
||||
LookIn:=xlFormulas, SearchOrder:=xlByColumns, _
|
||||
SearchDirection:=xlPrevious, MatchCase:=False)
|
||||
On Error GoTo 0
|
||||
If f Is Nothing Then
|
||||
GetLastCol = 0
|
||||
Else
|
||||
GetLastCol = f.Column
|
||||
End If
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' ヘッダ文字列の中に [n:ラベル] パターンが含まれるか判定(改行不問)
|
||||
'========================================================
|
||||
Private Function ContainsEnumeration(ByVal s As String) As Boolean
|
||||
Dim re As Object
|
||||
Set re = CreateObject("VBScript.RegExp")
|
||||
re.Pattern = "\[\d+:[^\]]+\]"
|
||||
re.IgnoreCase = True
|
||||
re.Global = True
|
||||
ContainsEnumeration = re.Test(s)
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' ヘッダ全体から [n:ラベル] を抽出して、入力規則のリスト文字列を生成
|
||||
' DV_USE_CODE_ONLY=False: "0:無効,1:有効"
|
||||
' DV_USE_CODE_ONLY=True : "0,1"
|
||||
'========================================================
|
||||
Private Function BuildValidationListFromEnum(ByVal s As String, ByVal codeOnly As Boolean) As String
|
||||
Dim re As Object, mc As Object, m As Object
|
||||
Set re = CreateObject("VBScript.RegExp")
|
||||
re.Pattern = "\[(\d+):([^\]]+)\]"
|
||||
re.IgnoreCase = True
|
||||
re.Global = True
|
||||
|
||||
Dim parts As Collection
|
||||
Set parts = New Collection
|
||||
|
||||
If re.Test(s) Then
|
||||
Set mc = re.Execute(s)
|
||||
Dim code As String, label As String
|
||||
For Each m In mc
|
||||
code = m.SubMatches(0)
|
||||
label = m.SubMatches(1)
|
||||
If codeOnly Then
|
||||
parts.Add code
|
||||
Else
|
||||
parts.Add code & ":" & label
|
||||
End If
|
||||
Next m
|
||||
End If
|
||||
|
||||
Dim i As Long, buf As String
|
||||
For i = 1 To parts.count
|
||||
buf = buf & IIf(Len(buf) > 0, ",", "") & CStr(parts(i))
|
||||
Next i
|
||||
|
||||
BuildValidationListFromEnum = buf
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' 入力規則(リスト)を範囲に適用
|
||||
'========================================================
|
||||
Private Sub ApplyValidationList(ByVal rng As Range, ByVal listCsv As String)
|
||||
On Error Resume Next
|
||||
rng.Validation.Delete
|
||||
On Error GoTo 0
|
||||
|
||||
If Len(listCsv) = 0 Then Exit Sub
|
||||
|
||||
With rng.Validation
|
||||
.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, _
|
||||
Formula1:=listCsv
|
||||
.IgnoreBlank = True
|
||||
.InCellDropdown = True
|
||||
.ErrorTitle = "入力値エラー"
|
||||
.ErrorMessage = "リストから選択してください。"
|
||||
.ShowError = True
|
||||
End With
|
||||
End Sub
|
||||
|
||||
'========================================================
|
||||
' Null/Emptyを空文字にする補助
|
||||
'========================================================
|
||||
Private Function Nz(ByVal v As Variant, Optional ByVal defaultValue As String = "") As String
|
||||
If IsError(v) Then
|
||||
Nz = defaultValue
|
||||
ElseIf IsNull(v) Or IsEmpty(v) Then
|
||||
Nz = defaultValue
|
||||
Else
|
||||
Nz = CStr(v)
|
||||
End If
|
||||
End Function
|
||||
|
||||
'========================================================
|
||||
' 列番号 → 列記号
|
||||
'========================================================
|
||||
Private Function ColLetter(ByVal colNum As Long) As String
|
||||
Dim s As String
|
||||
Do While colNum > 0
|
||||
s = Chr(((colNum - 1) Mod 26) + 65) & s
|
||||
colNum = (colNum - 1) \ 26
|
||||
Loop
|
||||
ColLetter = s
|
||||
End Function
|
||||
|
||||
|
||||
Reference in New Issue
Block a user