Advertisement
Guest User

Compiling dynamic Excel formulas to static references

a guest
Jan 30th, 2015
77
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ''Compiling' function from http://iluvspreadsheets.wordpress.com/
  2. 'Formula parser taken from https://fastexcel.wordpress.com/2013/10/27/parsing-functions-from-excel-formulas-using-vba-is-mid-or-a-byte-array-the-best-method/
  3.  
  4. Option Explicit
  5. Option Base 0
  6. Option Compare Binary
  7.  
  8. '"Compile" all dynamic formulas in the active workbook, so as to make a more static version of your spreadsheet with increased transparency.
  9. 'Specifically, INDEX/INDIRECT formulas are converted into their evaluated cell references
  10. 'Also converts ranges like $13:$13 into their evaluation from the current cell, i.e. to F13 for cell F21.
  11. Sub CompileReferences()
  12. Dim strFunc$, abFormula() As Byte, chars() As Byte, jStart&, jEnd&, c As Range, sheet, frm$, lFrom&, lTo&, lOpen&, sFnc$, rslt, sRslt$, sPars$, sEvRef$, rRef As Range, sLeft$, matches, match, r As Range
  13. chars = "+-,/*(=><& :!^" & vbLf
  14. speedUp
  15.  
  16. 'Set sheet = ActiveSheet
  17. For Each sheet In ActiveWorkbook.Sheets
  18.     sheet.Select
  19.     sheet.Activate
  20.     On Error GoTo nextSheet:
  21.     For Each c In sheet.UsedRange.SpecialCells(xlCellTypeFormulas, 23)
  22.         On Error GoTo 0
  23. 'Set c = ActiveCell
  24.        If CStr(c.Value) = "Error 2015" Then GoTo nextCell
  25.         frm = c.Formula 'R1C1
  26.        abFormula = frm
  27.         sLeft = frm
  28.         jStart = 0
  29.         jEnd = 0
  30.         lFrom = 0
  31.         Do
  32.             strFunc = GetFunc3(abFormula, chars, jStart, jEnd)
  33.             If LenB(strFunc) = 0 Then Exit Do
  34.             jStart = jEnd
  35.             lFrom = WorksheetFunction.Find(strFunc, frm, lFrom + 1)
  36.             lOpen = WorksheetFunction.Find("(", frm, lFrom + 1) + 1
  37.             lTo = lFrom
  38.            
  39.             Do
  40.                 lTo = WorksheetFunction.Find(")", frm, lTo + 1)   'FindB
  41.                sFnc = mid$(frm, lFrom, lTo - lFrom + 1)
  42.                 rslt = Evaluate(sFnc)
  43.                 sRslt = ""
  44.                 On Error Resume Next
  45.                 If Not IsArray(rslt) Then sRslt = CStr(rslt)
  46.                 On Error GoTo 0
  47.                 If TypeName(rslt) <> "Error" Or sRslt <> "Error 2015" Then
  48.                     sPars = mid$(frm, lOpen, lTo - lOpen)
  49.                     sEvRef = ""
  50.                     'Replace specific formulas with their evaluated references
  51.                    If strFunc = "INDIRECT" Then
  52.                         sEvRef = Evaluate(sPars)
  53.                         c.Formula = Replace(c.Formula, sFnc, sEvRef, 1, 1)
  54.                     ElseIf strFunc = "COLUMNS" Then
  55.                         If sRslt = c.Column Then c.Formula = Replace(c.Formula, sFnc, "COLUMN()", 1, 1)
  56.                     ElseIf strFunc = "ROWS" Then
  57.                         If sRslt = c.row Then c.Formula = Replace(c.Formula, sFnc, "ROW()", 1, 1)
  58.                     ElseIf strFunc = "INDEX" Then
  59.                         Set rRef = Evaluate(sFnc)
  60.                         sEvRef = IIf(rRef.Worksheet.Index = sheet.Index, "", "'" & rRef.Worksheet.name & "'!") & rRef.Address
  61.                         c.Formula = Replace(c.Formula, sFnc, sEvRef, 1, 1)
  62.                     'Else
  63.                    End If
  64.                     sLeft = Replace(sLeft, sFnc, sEvRef, 1, 1)
  65.                     GoTo skip
  66.                 End If
  67.             Loop
  68. skip:
  69.         Loop
  70.         'Also replace multi-cell references with exact references
  71.        '//How to get all multi-cell references? iterate through c.Precedents.Areas?
  72.        'Filter out references within functions
  73.        'Functions already filtered out
  74.        'and can also filter out strings...
  75.        sLeft = regReplace("""(("""")|([^""]))+""", sLeft)
  76.         'then match for multi-cell refs (just ignore any potential sheet prefixes)
  77.        Set matches = getMatches(sLeft, "(\$?\w+)?(\$?\d+)?\:(\$?\w+)?(\$?\d+)?")
  78.         For Each match In matches
  79.             Set r = Range(CStr(match))
  80.             'How to evaluate the broad range into their exact reference? check col/row intersect
  81.            On Error GoTo tryRow
  82.             Set rRef = Intersect(sheet.columns(c.Column), r)
  83.             On Error GoTo 0
  84.             'address with sheet ref if needed
  85.            sEvRef = IIf(rRef.Worksheet.Index = sheet.Index, "", "'" & r.Worksheet.name & "'!") & rRef.Address '(0, 0)
  86.            c.Formula = Replace(c.Formula, CStr(match), sEvRef)
  87.         Next
  88. nextCell:
  89.     Next
  90.     On Error GoTo 0
  91. nextSheet:
  92. Next
  93. slowDown
  94. Exit Sub
  95. tryRow:
  96. Set rRef = Intersect(sheet.rows(c.row), r)
  97. Resume Next
  98. End Sub
  99.  
  100. 'Does a regex replace
  101. Public Function regReplace$(patt$, inText$, Optional withText$ = "", Optional fuzzyCase As Boolean = False)
  102. regReplace = inText
  103. Dim regex As New RegExp
  104. With regex
  105.     .MultiLine = True
  106.     .ignorecase = fuzzyCase
  107.     .pattern = patt
  108.     .Global = True
  109.     On Error Resume Next    'for no hits
  110.    regReplace = .Replace(inText, withText)
  111. End With
  112. End Function
  113.  
  114. 'regex matching
  115. Function getMatches(s$, reg$, Optional warnOnError As Boolean = False, Optional fuzzyCase As Boolean = False)
  116. Dim regex As New RegExp
  117. regex.pattern = reg
  118. regex.ignorecase = fuzzyCase
  119. regex.MultiLine = True
  120. regex.Global = True
  121. Set getMatches = regex.execute(s)
  122. If warnOnError And getMatches.Count < 1 Then
  123.     Stop
  124. End If
  125. End Function
  126.  
  127. 'speed up processing by pausing other stuff
  128. Function speedUp()
  129. With Application
  130.     .Calculation = xlCalculationManual
  131.     .ScreenUpdating = False
  132.     .EnableEvents = False
  133. End With
  134. End Function
  135.  
  136. 'undo the settings of SpeedUp
  137. Function slowDown(Optional force As Boolean = False, Optional tally As Boolean = True)
  138. With Application
  139.     .Calculation = xlCalculationAutomatic
  140.     .ScreenUpdating = True
  141.     .EnableEvents = True
  142. End With
  143. Cells.Calculate
  144. End Function
  145.  
  146. 'Parse a formula to identify the names of functions used
  147. 'Original source: https://fastexcel.wordpress.com/2013/10/27/parsing-functions-from-excel-formulas-using-vba-is-mid-or-a-byte-array-the-best-method/
  148. Function GetFunc3(abFormula() As Byte, abStartChars() As Byte, jStart As Long, jEnd As Long) As String
  149. Dim j&, k&, jj&, jStartChar&, jFirst&, blStart As Boolean, blDoubleQ As Boolean, blSingleQ As Boolean, abFunc() As Byte
  150. jFirst = jStart + 2
  151. blDoubleQ = False
  152. For j = jStart + 2 To (UBound(abFormula) - 2) Step 2
  153.     If abFormula(j + 1) = 0 Then
  154.         If abFormula(j) = 39 Then blSingleQ = Not blSingleQ
  155.         If Not blSingleQ Then
  156.             If abFormula(j) = 34 Then blDoubleQ = Not blDoubleQ
  157.             If Not blDoubleQ Then
  158.                 If abFormula(j) = 40 Then
  159.                     blStart = False
  160.                     For jj = j - 2 To jStart Step -2
  161.                         For jStartChar = 0 To UBound(abStartChars) Step 2
  162.                             If abFormula(jj) = abStartChars(jStartChar) Then
  163.                                 blStart = True
  164.                                 jFirst = jj + 2
  165.                                 Exit For
  166.                             End If
  167.                         Next jStartChar
  168.                         If blStart Then Exit For
  169.                     Next
  170.                     If blStart Then
  171.                         If j > jFirst Then
  172.                             jEnd = j
  173.                             Exit For
  174.                         ElseIf abFormula(jFirst) = 40 Then
  175.                             jFirst = jFirst + 2
  176.                         End If
  177.                     End If
  178.                 End If
  179.             End If
  180.         End If
  181.     End If
  182. Next
  183. If blStart And jEnd > jFirst Then
  184.     ReDim abFunc(0 To (jEnd - jFirst - 1)) As Byte
  185.     For k = 0 To UBound(abFunc)
  186.         abFunc(k) = abFormula(jFirst + k)
  187.     Next k
  188.     GetFunc3 = abFunc
  189. End If
  190. End Function
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement