数式の取得 新バージョン

'■定数定義
Private Const C_PROGRESS_UPDATE_INTERVAL As Long = 1000    ' 進捗表示更新間隔

'--------------------------------------------------
'プロシージャの概要:
'処理名:全シート数式解析処理
'概要:ワークブック内の全シートの数式を解析する
'db:データベース接続オブジェクト
'xlApp:エクセルアプリケーションオブジェクト
'wbTarget:対象ワークブック
'戻り値:処理結果(Boolean)
'--------------------------------------------------
Function analyzeAllSheetsFormula(db As DAO.Database, xlApp As Excel.Application, wbTarget As Excel.Workbook) As Boolean
    Dim wsSheet As Worksheet
    Dim binResult As Boolean
    Dim intSheetCount As Integer
    Dim intProcessedSheetCount As Integer
    Dim lngTotalFormulaCount As Long
    Dim lngProcessedFormulaCount As Long
    Dim dtmStartTime As Date
    Dim dtmEndTime As Date
    
    On Error GoTo ErrorHandler
    
    ' 初期化
    analyzeAllSheetsFormula = False
    intSheetCount = 0
    intProcessedSheetCount = 0
    lngProcessedFormulaCount = 0
    dtmStartTime = Now()
    
    ' 事前スキャン:総数式数を取得
    lngTotalFormulaCount = getTotalFormulaCount(wbTarget)
    
    If lngTotalFormulaCount = 0 Then
        Application.SysCmd acSysCmdSetStatus, "処理対象の数式が見つかりませんでした"
        analyzeAllSheetsFormula = True
        Exit Function
    End If
    
    ' ステータス表示
    Application.SysCmd acSysCmdSetStatus, _
        "数式情報取得開始... 総数式数: " & lngTotalFormulaCount & "件"
    
    ' 全シートを処理
    For Each wsSheet In wbTarget.Worksheets
        intSheetCount = intSheetCount + 1
        
        ' 数式データ取得処理
        binResult = getFormulaDataHighSpeed(wsSheet.Name, db, wbTarget, lngProcessedFormulaCount, lngTotalFormulaCount)
        
        If binResult Then
            intProcessedSheetCount = intProcessedSheetCount + 1
            Debug.Print "処理完了: " & wsSheet.Name
        Else
            Debug.Print "処理エラー: " & wsSheet.Name
        End If
    Next wsSheet
    
    ' 処理完了
    dtmEndTime = Now()
    Application.SysCmd acSysCmdSetStatus, _
        "処理完了: " & lngProcessedFormulaCount & "件の数式を処理しました " & _
        "(" & intProcessedSheetCount & "/" & intSheetCount & "シート) " & _
        "処理時間: " & Format(dtmEndTime - dtmStartTime, "nn:ss")
    
    Debug.Print "処理結果: " & intProcessedSheetCount & "/" & intSheetCount & " シート完了"
    Debug.Print "数式処理数: " & lngProcessedFormulaCount & "/" & lngTotalFormulaCount & " 件完了"
    Debug.Print "処理時間: " & Format(dtmEndTime - dtmStartTime, "hh:nn:ss")
    
    analyzeAllSheetsFormula = True
    Exit Function
    
ErrorHandler:
    Application.SysCmd acSysCmdSetStatus, "エラーが発生しました: " & Err.Description
    analyzeAllSheetsFormula = False
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:総数式数取得処理
'概要:ワークブック内の全シートの数式セル総数を事前取得する
'wb:対象ワークブック
'戻り値:総数式数(Long)
'--------------------------------------------------
Function getTotalFormulaCount(wb As Excel.Workbook) As Long
    Dim ws As Excel.Worksheet
    Dim rngFormulaCells As Excel.Range
    Dim lngTotalCount As Long
    Dim blnWasProtected As Boolean
    Dim blnPasswordProtected As Boolean
    
    On Error GoTo ErrorHandler
    
    ' 初期化
    lngTotalCount = 0
    
    ' ステータス表示
    Application.SysCmd acSysCmdSetStatus, "数式セル総数を取得中..."
    
    ' 全シートをスキャン
    For Each ws In wb.Worksheets
        ' 保護状態をチェック
        blnWasProtected = ws.ProtectContents
        blnPasswordProtected = False
        
        ' 保護されている場合は一時解除を試行
        If blnWasProtected Then
            On Error Resume Next
            ws.Unprotect ""  ' 空パスワードで解除を試行
            If ws.ProtectContents Then
                ' 解除に失敗した場合はパスワード保護あり
                blnPasswordProtected = True
                ' パスワード保護のシートは1件としてカウント
                lngTotalCount = lngTotalCount + 1
                ' 進捗表示
                Application.SysCmd acSysCmdSetStatus, "数式セル総数を取得中... " & ws.Name & " (パスワード保護)"
                GoTo NextSheet
            End If
            On Error GoTo ErrorHandler
        End If
        
        ' 数式セルを取得
        On Error Resume Next
        Set rngFormulaCells = ws.Cells.SpecialCells(xlCellTypeFormulas)
        On Error GoTo ErrorHandler
        
        ' 数式セルが存在する場合はカウントを追加
        If Not rngFormulaCells Is Nothing Then
            lngTotalCount = lngTotalCount + rngFormulaCells.Count
            Set rngFormulaCells = Nothing
        End If
        
        ' 保護を復元
        If blnWasProtected And Not blnPasswordProtected Then
            ws.Protect ""
        End If
        
        ' 進捗表示
        Application.SysCmd acSysCmdSetStatus, "数式セル総数を取得中... " & ws.Name
        
NextSheet:
    Next ws
    
    getTotalFormulaCount = lngTotalCount
    Exit Function
    
ErrorHandler:
    ' 保護を復元
    If blnWasProtected And Not blnPasswordProtected Then
        On Error Resume Next
        ws.Protect ""
        On Error GoTo 0
    End If
    getTotalFormulaCount = 0
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:数式セル情報高速取得処理
'概要:ワークシートの数式セルを高速で取得しデータベースに一括登録する
'strSheetName:対象シートの名前
'db:データベース接続オブジェクト
'wb:対象ワークブック
'lngProcessedCount:処理済み数式数(参照渡し)
'lngTotalCount:総数式数
'戻り値:処理結果(Boolean)
'--------------------------------------------------
Function getFormulaDataHighSpeed(strSheetName As String, db As DAO.Database, wb As Excel.Workbook, _
                                ByRef lngProcessedCount As Long, lngTotalCount As Long) As Boolean
    Dim ws As Excel.Worksheet
    Dim rngFormulaCells As Excel.Range
    Dim arrFormulaData As Variant
    Dim arrTempData As Variant
    Dim arrValues As Variant
    Dim arrFormulas As Variant
    Dim lngRowCount As Long
    Dim lngColCount As Long
    Dim intI As Integer
    Dim intJ As Integer
    Dim intRowIndex As Integer
    Dim lngCurrentSheetCount As Long
    Dim blnWasProtected As Boolean
    Dim blnPasswordProtected As Boolean
    
    On Error GoTo ErrorHandler
    
    ' 初期化
    getFormulaDataHighSpeed = False
    blnWasProtected = False
    blnPasswordProtected = False
    
    ' 対象シートを取得
    Set ws = wb.Worksheets(strSheetName)
    
    ' 保護状態をチェック
    blnWasProtected = ws.ProtectContents
    
    ' 保護されている場合は一時解除を試行
    If blnWasProtected Then
        On Error Resume Next
        ws.Unprotect ""  ' 空パスワードで解除を試行
        If ws.ProtectContents Then
            ' 解除に失敗した場合はパスワード保護あり
            blnPasswordProtected = True
            On Error GoTo ErrorHandler
            ' パスワード保護ありメッセージを出力
            Call insertPasswordProtectedMessage(strSheetName, db, lngProcessedCount, lngTotalCount)
            getFormulaDataHighSpeed = True
            Exit Function
        End If
        On Error GoTo ErrorHandler
    End If
    
    ' 数式セルが存在するかチェック
    On Error Resume Next
    Set rngFormulaCells = ws.Cells.SpecialCells(xlCellTypeFormulas)
    On Error GoTo ErrorHandler
    
    ' 数式セルが存在しない場合はスキップ
    If rngFormulaCells Is Nothing Then
        ' 保護を復元
        If blnWasProtected And Not blnPasswordProtected Then
            ws.Protect ""
        End If
        getFormulaDataHighSpeed = True
        Exit Function
    End If
    
    ' 現在のシートの数式数を取得
    lngCurrentSheetCount = rngFormulaCells.Count
    
    ' 数式セルの情報を配列で一括取得
    arrValues = rngFormulaCells.Value
    arrFormulas = rngFormulaCells.Formula
    
    ' 単一セルの場合の配列化処理
    If Not IsArray(arrValues) Then
        ReDim arrFormulaData(1 To 1, 1 To 3)
        arrFormulaData(1, 1) = strSheetName
        arrFormulaData(1, 2) = rngFormulaCells.Address(False, False)
        arrFormulaData(1, 3) = arrFormulas
    Else
        ' 複数セルの場合の処理
        lngRowCount = UBound(arrValues, 1)
        lngColCount = UBound(arrValues, 2)
        
        ' データ格納用配列を準備
        ReDim arrFormulaData(1 To rngFormulaCells.Cells.Count, 1 To 3)
        intRowIndex = 1
        
        ' 各セルの情報を配列に格納
        For intI = 1 To lngRowCount
            For intJ = 1 To lngColCount
                If arrFormulas(intI, intJ) <> "" Then
                    arrFormulaData(intRowIndex, 1) = strSheetName
                    arrFormulaData(intRowIndex, 2) = rngFormulaCells.Cells(intI, intJ).Address(False, False)
                    arrFormulaData(intRowIndex, 3) = arrFormulas(intI, intJ)
                    intRowIndex = intRowIndex + 1
                End If
            Next intJ
        Next intI
        
        ' 実際に使用した行数が元の配列サイズより少ない場合は新しい配列を作成
        If intRowIndex - 1 < UBound(arrFormulaData, 1) Then
            Dim arrTempData As Variant
            ReDim arrTempData(1 To intRowIndex - 1, 1 To 3)
            
            ' データをコピー
            For intI = 1 To intRowIndex - 1
                arrTempData(intI, 1) = arrFormulaData(intI, 1)
                arrTempData(intI, 2) = arrFormulaData(intI, 2)
                arrTempData(intI, 3) = arrFormulaData(intI, 3)
            Next intI
            
            ' 配列を置き換え
            arrFormulaData = arrTempData
        End If
    End If
    
    ' データベースへの一括挿入処理
    If UBound(arrFormulaData, 1) > 0 Then
        Call insertFormulaDataBatch(arrFormulaData, db, lngProcessedCount, lngTotalCount)
    End If
    
    ' 保護を復元
    If blnWasProtected And Not blnPasswordProtected Then
        ws.Protect ""
    End If
    
    getFormulaDataHighSpeed = True
    Exit Function
    
ErrorHandler:
    ' 保護を復元
    If blnWasProtected And Not blnPasswordProtected Then
        On Error Resume Next
        ws.Protect ""
        On Error GoTo 0
    End If
    getFormulaDataHighSpeed = False
End Function

'--------------------------------------------------
'プロシージャの概要:
'処理名:数式データ一括挿入処理
'概要:配列に格納された数式データを1件ずつINSERTで挿入する
'arrData:挿入するデータの配列
'db:データベース接続オブジェクト
'lngProcessedCount:処理済み数式数(参照渡し)
'lngTotalCount:総数式数
'--------------------------------------------------
Sub insertFormulaDataBatch(arrData As Variant, db As DAO.Database, _
                          ByRef lngProcessedCount As Long, lngTotalCount As Long)
    Dim strSQL As String
    Dim intI As Integer
    Dim lngLastUpdateCount As Long
    Dim dblProgress As Double
    Dim intProgressPercent As Integer
    
    On Error GoTo ErrorHandler
    
    ' 初期化
    lngLastUpdateCount = lngProcessedCount
    
    ' トランザクション開始
    DBEngine.Workspaces(0).BeginTrans
    
    ' 1件ずつINSERT処理
    For intI = 1 To UBound(arrData, 1)
        ' INSERT文を作成
        strSQL = "INSERT INTO [数式テーブル] ([シート名], [セル番地], [数式]) VALUES " & _
                 "('" & Replace(arrData(intI, 1), "'", "''") & "', " & _
                 "'" & Replace(arrData(intI, 2), "'", "''") & "', " & _
                 "'" & Replace(arrData(intI, 3), "'", "''") & "')"
        
        ' INSERT実行
        db.Execute strSQL
        
        lngProcessedCount = lngProcessedCount + 1
        
        ' 進捗表示更新判定
        If (lngProcessedCount - lngLastUpdateCount) >= C_PROGRESS_UPDATE_INTERVAL Or _
           intI = UBound(arrData, 1) Then
            ' 進捗率計算
            dblProgress = (lngProcessedCount / lngTotalCount) * 100
            intProgressPercent = Int(dblProgress)
            
            ' ステータス表示更新
            Application.SysCmd acSysCmdSetStatus, _
                "数式情報取得中... " & lngProcessedCount & "/" & lngTotalCount & _
                "件処理中 (" & intProgressPercent & "%)"
            
            lngLastUpdateCount = lngProcessedCount
        End If
    Next intI
    
    ' トランザクション確定
    DBEngine.Workspaces(0).CommitTrans
    Exit Sub
    
ErrorHandler:
    ' エラー時はロールバック
    DBEngine.Workspaces(0).Rollback
    Err.Raise Err.Number, Err.Source, Err.Description
End Sub

'--------------------------------------------------
'プロシージャの概要:
'処理名:パスワード保護メッセージ出力処理
'概要:パスワード保護により解析できないシートの情報を出力する
'strSheetName:対象シートの名前
'db:データベース接続オブジェクト
'lngProcessedCount:処理済み数式数(参照渡し)
'lngTotalCount:総数式数
'--------------------------------------------------
Sub insertPasswordProtectedMessage(strSheetName As String, db As DAO.Database, _
                                  ByRef lngProcessedCount As Long, lngTotalCount As Long)
    Dim strSQL As String
    Dim dblProgress As Double
    Dim intProgressPercent As Integer
    
    On Error GoTo ErrorHandler
    
    ' パスワード保護メッセージを挿入
    strSQL = "INSERT INTO [数式テーブル] ([シート名], [セル番地], [数式]) VALUES " & _
             "('" & Replace(strSheetName, "'", "''") & "', " & _
             "'パスワード保護', " & _
             "'パスワード保護により情報取得不可')"
    
    db.Execute strSQL
    
    ' カウンタを1増加
    lngProcessedCount = lngProcessedCount + 1
    
    ' 進捗表示更新
    dblProgress = (lngProcessedCount / lngTotalCount) * 100
    intProgressPercent = Int(dblProgress)
    
    Application.SysCmd acSysCmdSetStatus, _
        "数式情報取得中... " & lngProcessedCount & "/" & lngTotalCount & _
        "件処理中 (" & intProgressPercent & "%) - " & strSheetName & " はパスワード保護"
    
    Exit Sub
    
ErrorHandler:
    ' エラーが発生してもスキップして続行
End Sub