スクリプトファイルを追加

This commit is contained in:
2026-04-03 18:22:33 +09:00
commit 809d3bc3e4
14 changed files with 1956 additions and 0 deletions

489
csv_maker_vba.txt Normal file
View 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