Here is UDF that allows to check if a FormatCondition
is True/False
for one of the cells it applies to. It is intended only to FormatConditions
with an Operator
property of xlExpression
, to complete Chip Pearson version, which only tests the "fixed" Formula1
of a FormatCondition
on the first cell in its AppliesTo
Range
.
VBA UDF version:
Function CheckFC_VBA(fc As FormatCondition, rng As Range) As Variant 'fc must must be a member of rng.FormatConditions, and rng must be in fc.AppliesTo Range
Set c = rng(1)
If Intersect(c, fc.AppliesTo) Is Nothing Then Exit Function
rng.Parent.Activate 'Application.Evaluate will work off the active sheet, whereas the fc may have come from another sheet at the moment of the call to this function
'this function can be called only from VBA because if called from a sheet it won't be able to change the active sheet
If Application.LanguageSettings.LanguageID(MsoAppLanguageID.msoLanguageIDUI) <> 1033 Then
'Français; France; fr-FR; 1036
Set temp = Cells(Selection.Parent.UsedRange.SpecialCells(xlCellTypeLastCell).Row + 1, "A")
temp.FormulaLocal = fc.Formula1
strFormulaMoved$ = Application.ConvertFormula( _
Application.ConvertFormula(temp.Formula, XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, , fc.AppliesTo(1)), _
XlReferenceStyle.xlR1C1, XlReferenceStyle.xlA1, , c)
temp.ClearContents
Else
'English; United States; fr-FR; 1033
strFormulaMoved$ = Application.ConvertFormula( _
Application.ConvertFormula(Replace(fc.Formula1, Application.International(xlListSeparator), ","), XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, , fc.AppliesTo(1)), _
XlReferenceStyle.xlR1C1, XlReferenceStyle.xlA1, , c)
End If
CheckFC_VBA = Application.Evaluate(strFormulaMoved)
End Function
a Name
could also be used to do the translation, but with the same limitations than using the temp
Range
ThisSheet UDF version (only if local is set to English in settings):
Function CheckFC_ThisSheet_EN(fc_index As Integer, rng As Range) As Variant
Set c = rng(1)
Set fc = c.FormatConditions(fc_index)
If Intersect(c, fc.AppliesTo) Is Nothing Then Exit Function
'If Not rng.Parent Is Application.Caller.Parent Then Exit Function
If Not rng.Parent Is ActiveSheet Then Exit Function
If Application.LanguageSettings.LanguageID(MsoAppLanguageID.msoLanguageIDUI) <> 1033 Then
'cannot translate automatically
Else
'English; United States; fr-FR; 1033
strFormulaMoved$ = Application.ConvertFormula( _
Application.ConvertFormula(Replace(fc.Formula1, Application.International(xlListSeparator), ","), XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, , fc.AppliesTo(1)), _
XlReferenceStyle.xlR1C1, XlReferenceStyle.xlA1, , c)
End If
CheckFC_ThisSheet_EN = Application.Evaluate(strFormulaMoved)
End Function
Example in VBA:
?CheckFC_VBA(Selection.FormatConditions(1), Selection)
Example in a Sheet (depending on the current list separator):
=CheckFC_ThisSheet_EN(1, A1)
=CheckFC_ThisSheet_EN(1; A1)
The VBA UDF version is more potent than the ThisSheet UDF version because it can translate a local formula to english, which is required with this solution, and it works in any cases, while the ThisSheet version can only test FCs on the sheet where it is being used (unless all references in the FC are fully qualified with a sheet name like Sheet1!A1
instead of just A1
...). Thanks @Gserg for pointing all that out.
TO DO: to upgrade the VBA version (by removing the .Activate()
method) and the ThisSheet version (to a Sheet version capable to check FC in other sheets), one could parse the FC formula and replace any implicit references to explicit references with its parent sheet name, before calling the Application.Evaluate()
function. There are no built-in way to parse a formula and get the reference as Excel does it. There are several ideas to do that, none 100% correct:
EDIT: to reply my own TO DO, and as an example, here is a Sheet UDF version:
Function CheckFC_Sheet_EN(fc_index As Integer, rng As Range) As Variant
Set c = rng(1)
Set fc = c.FormatConditions(fc_index)
If Intersect(c, fc.AppliesTo) Is Nothing Then Exit Function
If Application.LanguageSettings.LanguageID(MsoAppLanguageID.msoLanguageIDUI) <> 1033 Then
'cannot translate automatically
'Français; France; fr-FR; 1036
Else
'English; United States; fr-FR; 1033
strFormulaMoved$ = Application.ConvertFormula( _
Application.ConvertFormula(MakeImplicitReferencesExplicit$(Replace(fc.Formula1, Application.International(xlListSeparator), ","), fc.Parent.Parent.Name), XlReferenceStyle.xlA1, XlReferenceStyle.xlR1C1, , fc.AppliesTo(1)), _
XlReferenceStyle.xlR1C1, XlReferenceStyle.xlA1, , c)
End If
CheckFC_Sheet_EN = Application.Evaluate(strFormulaMoved)
End Function
Function MakeImplicitReferencesExplicit$(strFormula$, strExternalLink$)
'USAGE:
'strExternalLink = "Feuil1"
'strExternalLink = "[Book1.xlsm]Feuil1 bis"
Set objRegEx = CreateObject("VBScript.RegExp")
With objRegEx
.IgnoreCase = True
.Global = True
.MultiLine = True
End With
'objRegEx.Pattern = """.*?""" 'remove expressions (with lazy quantifier)
'strFormula = objRegEx.Replace(strFormula, "")
objRegEx.Pattern = """.*?"""
'replace without changing the position of each character in the string
If objRegEx.test(strFormula) Then 'matches
Set vResult = objRegEx.Execute(strFormula)
If vResult.Count > 0 Then
For Each vMatch In vResult
strFormula2 = Left(strFormula, vMatch.FirstIndex) & """" & String(vMatch.Length - 2, " ") & """" & Mid(strFormula, vMatch.FirstIndex + 1 + vMatch.Length)
'strFormula2 = Left(strFormula, vMatch.FirstIndex) & String(vMatch.Length, "_") & Mid(strFormula, vMatch.FirstIndex + 1 + vMatch.Length)
Next
Else
strFormula2 = strFormula
End If
Else
strFormula2 = strFormula
End If
'basic:
'strSearchPattern$ = "(([A-Z])+(\d)+)"
'better:
strSearchPattern$ = _
"(['].*?['!])?" & _
"" & _
"([[A-Z0-9_]+[!])?" & _
"" & _
"(\$?[A-Z]+\$?\d+:\$?[A-Z]+\$?\d+" & _
"|" & _
"\$?[A-Z]+:\$?[A-Z]+" & _
"|" & _
"\$?\d+:\$?\d+" & _
"|" & _
"\$?[A-Z]+\$?\d+)"
'- match an optional External link: (['].*?['!])?
'- match an optional Sheet name: ([[A-Z0-9_]+[!])?
'- match the following alternation in prioritized order (*) (with optional $ symbols)
'- a range with row numbers and column letters: \$?[A-Z]+\$?(\d)+:\$?[A-Z]+\$?(\d)+
'- a range without row numbers (entire columns): \$?[A-Z]+:\$?[A-Z]+
'- a range without column letters (entire rows): \$?(\d)+:\$?(\d)+
'- single-cell references: \$?[A-Z]+\$?(\d)+
'*
'because the RegEx engine is eager
'https://www.regular-expressions.info/alternation.html
objRegEx.Pattern = strSearchPattern
If objRegEx.test(strFormula2) Then 'matches
Set vResult = objRegEx.Execute(strFormula2)
If vResult.Count > 0 Then
Dim lngOffset&
strFormula3$ = strFormula
strExternalLink = "'" & strExternalLink & "'" & "!"
For Each vMatch In vResult
If IsEmpty(vMatch.SubMatches(0)) And IsEmpty(vMatch.SubMatches(1)) Then
strExplicitRef$ = strExternalLink & vMatch.Value
Else
strExplicitRef$ = vMatch.Value
End If
strFormula3 = Left(strFormula3, lngOffset + vMatch.FirstIndex) & strExplicitRef & Mid(strFormula3, lngOffset + vMatch.FirstIndex + 1 + vMatch.Length)
lngOffset = lngOffset + Len(strExternalLink)
Next
MakeImplicitReferencesExplicit = strFormula3
Else
MakeImplicitReferencesExplicit = strFormula
End If
End If
End Function
Now, using the workaround mentioned in the post pointed out by @GSerg, I expect it is possible to actually make an international Sheet UDF capable of translating Excel functions in the formula. With a temp Name
created on the fly instead of temp Range
, I think the risk of a crash due to circular referencing would be reduce to zero...