数式取得

Option Explicit
' Option Base 1 が設定されていても問題なく動作するように調整

' エラー処理用の定数
Private Const C_ERR_FILE_NOT_FOUND As Long = 53
Private Const C_ERR_TOO_MANY_FORMULAS As Long = 1000 ' カスタムエラー番号
Private Const C_MAX_ROWS As Long = 1000000 ' Excel行制限に近い値

'--------------------------------------------------
'プロシージャの概要:
'処理名:getAllFormulas
'概要:選択したExcelブックの全シートから数式を抽出し、一覧シートに出力する
'--------------------------------------------------
Sub getAllFormulas()
    Dim wbSource As Workbook
    Dim wsFormulaList As Worksheet
    Dim strSourceBookPath As String
    Dim strSourceBookName As String
    Dim lngStartTime As Long
    Dim lngElapsedTime As Long
    Dim lngTotalSheetCount As Long
    Dim lngTotalFormulaCount As Long
    
    On Error GoTo ErrorHandler
    
    ' 処理開始時間を記録
    lngStartTime = Timer
    
    ' ソースブックのパスを取得
    strSourceBookPath = getSourceBookPath()
    If strSourceBookPath = "" Then
        MsgBox "ファイルが選択されませんでした。処理を中止します。", vbInformation, "処理中止"
        Exit Sub
    End If
    
    ' ソースブックのファイル名のみを取得
    strSourceBookName = Mid(strSourceBookPath, InStrRev(strSourceBookPath, "\") + 1)
    
    ' 前提条件チェック
    If Not checkPrerequisites(strSourceBookPath, strSourceBookName) Then
        Exit Sub
    End If
    
    ' 数式一覧シートを初期化
    Set wsFormulaList = initializeFormulaSheet()
    If wsFormulaList Is Nothing Then
        Exit Sub
    End If
    
    ' アプリケーション設定
    setApplicationSettings True
    
    ' ソースブックを開く
    Set wbSource = openSourceBook(strSourceBookPath)
    If wbSource Is Nothing Then
        GoTo CleanExit
    End If
    
    ' 数式を処理
    lngTotalFormulaCount = processFormulas(wbSource, wsFormulaList, lngTotalSheetCount, lngStartTime)
    
    ' 経過時間の計算
    lngElapsedTime = Timer - lngStartTime
    
    ' 処理完了メッセージ
    MsgBox "数式の抽出が完了しました。" & vbCrLf & _
           "合計シート数: " & lngTotalSheetCount & vbCrLf & _
           "合計処理数式数: " & lngTotalFormulaCount & vbCrLf & _
           "処理時間: " & Format(lngElapsedTime \ 60, "00") & ":" & Format(lngElapsedTime Mod 60, "00"), _
           vbInformation, "処理完了"
    
CleanExit:
    ' ソースブックを保存せずに閉じる
    If Not wbSource Is Nothing Then
        wbSource.Close SaveChanges:=False
    End If
    
    ' 設定を元に戻す
    setApplicationSettings False
    Exit Sub
    
ErrorHandler:
    ' エラーの種類に応じて処理
    Select Case Err.Number
        Case C_ERR_FILE_NOT_FOUND
            MsgBox "指定されたファイルが見つかりません: " & strSourceBookPath, vbCritical, "エラー"
        Case Else
            MsgBox "エラーが発生しました: " & Err.Number & vbCrLf & Err.Description, vbCritical, "エラー"
    End Select
    
    ' クリーンアップへ
    Resume CleanExit
End Sub

'--------------------------------------------------
'プロシージャの概要:
'処理名:checkPrerequisites
'概要:処理の前提条件をチェックする
'引数リスト
'strSourceBookPath:ソースブックのパス
'strSourceBookName:ソースブックのファイル名
'戻り値:前提条件を満たしていればTrue、そうでなければFalse
'--------------------------------------------------
Private Function checkPrerequisites(ByVal strSourceBookPath As String, ByVal strSourceBookName As String) As Boolean
    ' パスの存在チェック
    If Dir(strSourceBookPath) = "" Then
        MsgBox "指定されたパス '" & strSourceBookPath & "' にファイルが存在しません。", vbExclamation, "エラー"
        checkPrerequisites = False
        Exit Function
    End If
    
    ' ソースブックが既に開かれているかチェック
    If existsWorkbookOpen(strSourceBookName) Then
        MsgBox "ブック '" & strSourceBookName & "' は既に開かれています。処理を中止します。", vbExclamation, "エラー"
        checkPrerequisites = False
        Exit Function
    End If
    
    checkPrerequisites = True
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:initializeFormulaSheet
'概要:数式一覧シートを初期化する
'戻り値:初期化した数式一覧シート。エラー時はNothing
'--------------------------------------------------
Private Function initializeFormulaSheet() As Worksheet
    Dim wsFormulaList As Worksheet
    
    ' 数式一覧シートを設定
    On Error Resume Next
    Set wsFormulaList = ThisWorkbook.Sheets("数式一覧")
    On Error GoTo 0
    
    If wsFormulaList Is Nothing Then
        MsgBox "このブックに '数式一覧' シートが存在しません。", vbExclamation, "エラー"
        Set initializeFormulaSheet = Nothing
        Exit Function
    End If
    
    ' 数式一覧シートをクリア
    With wsFormulaList
        .Cells.Clear
        
        ' タイトル行を設定
        With .Range("A1:D1")
            .Value = Array("シート名", "セル番地", "数式", "参照先")
            .Font.Bold = True
        End With
    End With
    
    Set initializeFormulaSheet = wsFormulaList
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:setApplicationSettings
'概要:アプリケーション設定を切り替える
'引数リスト
'binBeginProcessing:処理開始時はTrue、終了時はFalse
'--------------------------------------------------
Private Sub setApplicationSettings(ByVal binBeginProcessing As Boolean)
    With Application
        If binBeginProcessing Then
            ' 処理開始時の設定
            .DisplayStatusBar = True
            .StatusBar = "処理を開始しています..."
            .DisplayAlerts = False
            .EnableEvents = False
            .ScreenUpdating = False
            .Calculation = xlCalculationManual ' 計算を手動に設定
        Else
            ' 処理終了時の設定
            .DisplayAlerts = True
            .EnableEvents = True
            .ScreenUpdating = True
            .StatusBar = False
            .Calculation = xlCalculationAutomatic ' 計算を自動に戻す
        End If
    End With
End Sub

'--------------------------------------------------
'プロシージャの概要:
'処理名:openSourceBook
'概要:ソースブックを開く
'引数リスト
'strSourceBookPath:ソースブックのパス
'戻り値:開いたソースブック。エラー時はNothing
'--------------------------------------------------
Private Function openSourceBook(ByVal strSourceBookPath As String) As Workbook
    Dim wbSource As Workbook
    
    Application.StatusBar = "ソースブックを開いています..."
    
    On Error Resume Next
    Set wbSource = Workbooks.Open(strSourceBookPath, UpdateLinks:=False, ReadOnly:=True, IgnoreReadOnlyRecommended:=True)
    If Err.Number <> 0 Then
        On Error GoTo 0
        MsgBox "ブック '" & strSourceBookPath & "' を開くことができませんでした。" & vbCrLf & _
               "エラー: " & Err.Description, vbExclamation, "エラー"
        Set openSourceBook = Nothing
        Exit Function
    End If
    On Error GoTo 0
    
    Set openSourceBook = wbSource
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:processFormulas
'概要:数式処理のメイン処理
'引数リスト
'wbSource:ソースブック
'wsFormulaList:数式一覧シート
'lngTotalSheetCount:処理したシート数(参照渡し)
'lngStartTime:処理開始時間
'戻り値:処理した数式の総数
'--------------------------------------------------
Private Function processFormulas(ByRef wbSource As Workbook, ByRef wsFormulaList As Worksheet, _
                               ByRef lngTotalSheetCount As Long, ByVal lngStartTime As Long) As Long
    Dim wsSheet As Worksheet
    Dim lngSheetCount As Long
    Dim lngCurrentSheet As Long
    Dim lngFormulaCount As Long
    
    ' シート数を取得
    lngSheetCount = wbSource.Worksheets.Count
    lngTotalSheetCount = lngSheetCount
    lngCurrentSheet = 0
    lngFormulaCount = 0
    
    ' ソースブックの全シートをループ(非表示シートも含む)
    For Each wsSheet In wbSource.Worksheets
        ' 現在のシート番号を更新
        lngCurrentSheet = lngCurrentSheet + 1
        
        ' 各シート内の数式を処理
        lngFormulaCount = lngFormulaCount + processSheetFormulas(wsSheet, wsFormulaList, lngCurrentSheet, lngSheetCount, lngStartTime)
        
        ' シート処理完了のステータス表示
        Application.StatusBar = wsSheet.Name & "の処理が完了しました (" & lngCurrentSheet & "/" & lngSheetCount & " シート)"
    Next wsSheet
    
    processFormulas = lngFormulaCount
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:processSheetFormulas
'概要:各シート内の数式処理
'引数リスト
'wsSheet:処理対象シート
'wsFormulaList:数式一覧シート
'lngCurrentSheet:現在のシート番号
'lngSheetCount:シート総数
'lngStartTime:処理開始時間
'戻り値:シート内で処理した数式の数
'--------------------------------------------------
Private Function processSheetFormulas(ByRef wsSheet As Worksheet, ByRef wsFormulaList As Worksheet, _
                                    ByVal lngCurrentSheet As Long, ByVal lngSheetCount As Long, _
                                    ByVal lngStartTime As Long) As Long
    Dim rngCell As Range
    Dim rngUsedRange As Range
    Dim lngTotalCells As Long
    Dim lngProcessedCells As Long
    Dim arrVarFormulas() As Variant
    Dim lngFormulaCount As Long
    Dim lngSheetNumber As Long
    Dim strReferredCell As String
    Dim lngElapsedTime As Long
    
    ' 配列の初期化(高速化のため)
    lngFormulaCount = 0
    lngSheetNumber = 1
    ReDim arrVarFormulas(1 To 10000, 1 To 4) ' 初期サイズ(必要に応じて拡張)
    
    With wsSheet
        ' 使用範囲を取得
        Set rngUsedRange = .UsedRange
        
        ' 処理するセル数を取得
        lngTotalCells = rngUsedRange.Cells.Count
        lngProcessedCells = 0
        
        ' ステータスバーを更新
        Application.StatusBar = .Name & "処理中 (" & lngCurrentSheet & "/" & lngSheetCount & " シート) - 準備中..."
        
        ' 行→列の順でチェック
        For Each rngCell In rngUsedRange
            ' 処理済みセル数を更新
            lngProcessedCells = lngProcessedCells + 1
            
            ' 100セルごとにステータスバーを更新
            If lngProcessedCells Mod 100 = 0 Or lngProcessedCells = 1 Then
                ' 経過時間の計算
                lngElapsedTime = Timer - lngStartTime
                
                Application.StatusBar = .Name & "処理中 (" & lngCurrentSheet & "/" & lngSheetCount & " シート) - 現在のシート: " & _
                                        Format(lngProcessedCells / lngTotalCells, "0%") & "完了 (経過時間: " & _
                                        Format(lngElapsedTime \ 60, "00") & ":" & Format(lngElapsedTime Mod 60, "00") & ")"
                
                ' DoEventsを呼び出してUIの応答性を維持(最小限)
                DoEvents
            End If
            
            ' セルが数式を含むかチェック
            If rngCell.HasFormula Then
                ' 数式情報を処理
                lngFormulaCount = processFormulaCell(rngCell, wsSheet, arrVarFormulas, lngFormulaCount)
                
                ' 行数制限チェック(新しいシートが必要か)
                If lngFormulaCount >= C_MAX_ROWS Then
                    ' 現在の配列データを出力
                    outputFormulasToSheet wsFormulaList, arrVarFormulas, lngFormulaCount, lngSheetNumber
                    
                    ' 新しいシート番号を増やす
                    lngSheetNumber = lngSheetNumber + 1
                    
                    ' 配列をリセット
                    lngFormulaCount = 0
                    ReDim arrVarFormulas(1 To 10000, 1 To 4)
                    
                    ' 新しい数式一覧シートを作成
                    Set wsFormulaList = createNewFormulaSheet(lngSheetNumber)
                End If
            End If
        Next rngCell
    End With
    
    ' 残りの数式データを出力
    If lngFormulaCount > 0 Then
        outputFormulasToSheet wsFormulaList, arrVarFormulas, lngFormulaCount, lngSheetNumber
    End If
    
    processSheetFormulas = lngFormulaCount
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:processFormulaCell
'概要:数式セルの処理
'引数リスト
'rngCell:処理対象セル
'wsSheet:処理対象シート
'arrVarFormulas:数式情報を格納する配列
'lngFormulaCount:現在の数式カウント
'戻り値:更新後の数式カウント
'--------------------------------------------------
Private Function processFormulaCell(ByRef rngCell As Range, ByRef wsSheet As Worksheet, _
                                  ByRef arrVarFormulas() As Variant, ByVal lngFormulaCount As Long) As Long
    Dim strReferredCell As String
    
    ' 配列が満杯になったら拡張
    If lngFormulaCount >= UBound(arrVarFormulas, 1) Then
        ReDim Preserve arrVarFormulas(1 To UBound(arrVarFormulas, 1) + 10000, 1 To 4)
    End If
    
    ' 数式の参照先を取得
    strReferredCell = getReferencedCells(rngCell)
    
    ' 数式情報を配列に格納
    lngFormulaCount = lngFormulaCount + 1
    arrVarFormulas(lngFormulaCount, 1) = wsSheet.Name
    arrVarFormulas(lngFormulaCount, 2) = rngCell.Address(False, False)
    arrVarFormulas(lngFormulaCount, 3) = "'" & rngCell.Formula
    arrVarFormulas(lngFormulaCount, 4) = strReferredCell
    
    processFormulaCell = lngFormulaCount
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:existsWorkbookOpen
'概要:指定されたブックが開かれているかを確認する
'引数リスト
'strWorkbookName:チェックするブック名
'戻り値:ブックが開かれている場合はTrue、そうでなければFalse
'--------------------------------------------------
Function existsWorkbookOpen(ByVal strWorkbookName As String) As Boolean
    Dim wbTarget As Workbook
    
    On Error Resume Next
    Set wbTarget = Workbooks(strWorkbookName)
    On Error GoTo 0
    
    existsWorkbookOpen = Not wbTarget Is Nothing
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:getSourceBookPath
'概要:ファイル選択ダイアログからソースブックのパスを取得する
'戻り値:選択されたファイルのパス、キャンセル時は空文字
'--------------------------------------------------
Function getSourceBookPath() As String
    Dim objFd As Office.FileDialog
    
    ' ファイル選択ダイアログを表示
    Set objFd = Application.FileDialog(msoFileDialogFilePicker)
    
    With objFd
        .Title = "数式を抽出するExcelブックを選択してください"
        .AllowMultiSelect = False
        .Filters.Clear
        .Filters.Add "Excelブック", "*.xlsx; *.xlsm; *.xls"
        
        If .Show = True Then
            getSourceBookPath = .SelectedItems(1)
        Else
            getSourceBookPath = ""
        End If
    End With
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:getReferencedCells
'概要:セルの参照先を取得する
'引数リスト
'rngCell:参照先を取得するセル
'戻り値:参照先セルの文字列表現
'--------------------------------------------------
Function getReferencedCells(ByRef rngCell As Range) As String
    Dim arrPrecedents As Variant
    Dim strResult As String
    Dim lngI As Long
    
    On Error Resume Next
    
    ' 参照先がある場合
    If rngCell.HasPrecedents Then
        ' 直接の参照セルを取得
        Set arrPrecedents = rngCell.DirectPrecedents
        
        ' 参照セルが存在する場合
        If Not arrPrecedents Is Nothing Then
            strResult = ""
            
            ' 参照セルの情報を文字列に追加
            For lngI = 1 To arrPrecedents.Count
                If strResult <> "" Then strResult = strResult & ", "
                
                ' 別シートの場合はシート名も追加
                If arrPrecedents(lngI).Parent.Name <> rngCell.Parent.Name Then
                    strResult = strResult & "'" & arrPrecedents(lngI).Parent.Name & "'!"
                End If
                
                strResult = strResult & arrPrecedents(lngI).Address(False, False)
            Next lngI
        Else
            strResult = "外部参照または複雑な数式"
        End If
    Else
        strResult = "直接の参照なし"
    End If
    
    On Error GoTo 0
    getReferencedCells = strResult
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:createNewFormulaSheet
'概要:新しい数式一覧シートを作成する
'引数リスト
'lngSheetNumber:シート番号
'戻り値:作成したワークシート
'--------------------------------------------------
Function createNewFormulaSheet(ByVal lngSheetNumber As Long) As Worksheet
    Dim wsNewSheet As Worksheet
    Dim strSheetName As String
    
    ' シート名を生成
    strSheetName = "数式一覧" & lngSheetNumber
    
    ' 既存のシートをチェック
    On Error Resume Next
    Set wsNewSheet = ThisWorkbook.Sheets(strSheetName)
    
    ' シートが存在しない場合は新規作成
    If wsNewSheet Is Nothing Then
        Set wsNewSheet = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
        wsNewSheet.Name = strSheetName
    Else
        ' シートが存在する場合はクリア
        wsNewSheet.Cells.Clear
    End If
    On Error GoTo 0
    
    ' タイトル行を設定
    With wsNewSheet.Range("A1:D1")
        .Value = Array("シート名", "セル番地", "数式", "参照先")
        .Font.Bold = True
    End With
    
    Set createNewFormulaSheet = wsNewSheet
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:outputFormulasToSheet
'概要:数式データを指定されたシートに出力する
'引数リスト
'wsTarget:出力先シート
'arrVarData:出力データ配列
'lngCount:データ数
'lngSheetNum:シート番号
'--------------------------------------------------
Sub outputFormulasToSheet(ByRef wsTarget As Worksheet, ByRef arrVarData() As Variant, _
                          ByVal lngCount As Long, ByVal lngSheetNum As Long)
    Dim rngOutput As Range
    Dim lngI As Long
    Dim arrVarOutput() As Variant
    
    ' 出力データを配列に整形(明示的にインデックスを1から開始)
    ReDim arrVarOutput(1 To lngCount, 1 To 4)
    
    For lngI = 1 To lngCount
        arrVarOutput(lngI, 1) = arrVarData(lngI, 1)
        arrVarOutput(lngI, 2) = arrVarData(lngI, 2)
        arrVarOutput(lngI, 3) = arrVarData(lngI, 3)
        arrVarOutput(lngI, 4) = arrVarData(lngI, 4)
    Next lngI
    
    ' 出力範囲を設定
    Set rngOutput = wsTarget.Range("A2").Resize(lngCount, 4)
    
    ' 一括で出力
    rngOutput.Value = arrVarOutput
    
    ' 列幅を自動調整
    wsTarget.Columns("A:D").AutoFit
    
    ' 書式設定
    With wsTarget
        .Columns("A:D").HorizontalAlignment = xlLeft
        .Rows("1:1").Font.Bold = True
    End With
End Sub

コメント