Regex VBA in Access - finding text between two strings - regex

I am having a heck of a time with a RegEx question in Access VBA.
My goal is to extract the server from a linked database connection string. Basically, the connection string looks like
ODBC;DRIVER=SQL Server;SERVER=compName\sqlexpress;Trusted_Connection=Yes;APP=Microsoft Office 2010;DATABASE=databaseName
I am able to get the first regex to work, but it is returning
SERVER=compName\sqlexpress
I would like this to only return
compName\sqlexpress
My understanding is the ?<= operator should allow the RegEx to work correctly, but I get the following error "Method 'Execute' of object 'IRegExp2' failed."
The only documentation I can find for any Microsoft RegEx syntax is here which is not the runtime 5.5 VBScript library, but I'm not sure where else to get supported syntax.
Here is the code I'm using to test this. My database has a lot of linked tables.
Sub printServerStringInformation()
Dim rxPattern As String
rxPattern = "(?=SERVER)(.*)(?=;Trusted)"
Debug.Print RxMatch(CurrentDb.tableDefs(1).Connect, rxPattern, False)
rxPattern = "(?<=SERVER)(.*)(?=;Trusted)"
Debug.Print RxMatch(CurrentDb.tableDefs(1).Connect, rxPattern, False)
End Sub
Here is the function I am using:
Public Function RxMatch( _
ByVal SourceString As String, _
ByVal Pattern As String, _
Optional ByVal IgnoreCase As Boolean = True, _
Optional ByVal MultiLine As Boolean = True) As Variant
'Microsoft VBScript Regular Expressions 5.5
'http://www.zytrax.com/tech/web/regex.htm#more
'http://bytecomb.com/regular-expressions-in-vba/
'http://xkcd.com/1171/
On Error GoTo errHandler
Dim oMatches As MatchCollection
With New RegExp
.MultiLine = MultiLine
.IgnoreCase = IgnoreCase
.Global = False
.Pattern = Pattern
Set oMatches = .Execute(SourceString)
If oMatches.Count > 0 Then
RxMatch = oMatches(0).value
Else
RxMatch = ""
End If
End With
errHandler:
Debug.Print Err.Description
End Function

Here goes solution with RegEx (complete code which could be converted into function):
Sub qTest_3()
Dim objRE As New RegExp
Dim Tekst As String
Dim Wynik As Variant
Tekst = "ODBC;DRIVER=SQL Server;SERVER=compName\sqlexpress;Trusted_Connection=Yes;APP=Microsoft Office 2010;DATABASE=databaseName"
With objRE
.Global = True
.IgnoreCase = True
.Pattern = "(^.*;SERVER=)(.*)(;Trusted.*)"
Wynik = .Replace(Tekst, "$2") 'only 2nd part of the pattern will be returned
End With
Debug.Print Wynik
End Sub
Your function changed could be as follows (I added additional parameter setting part of the pattern which should be returned):
Public Function RxMatchReturn( _
ByVal SourceString As String, _
ByVal Pattern As String, _
StringPart As Byte, _
Optional ByVal IgnoreCase As Boolean = True, _
Optional ByVal MultiLine As Boolean = True) As Variant
'Microsoft VBScript Regular Expressions 5.5
On Error GoTo errHandler
Dim oMatches As MatchCollection
With New RegExp
.MultiLine = MultiLine
.IgnoreCase = IgnoreCase
.Global = True
.Pattern = Pattern
RxMatchReturn = .Replace(SourceString, "$" & StringPart)
End With
errHandler:
Debug.Print err.Description
End Function

Related

Regular expression for an Excel cell with R1C1 notation

I need some code to test if a cell contains a formula with a reference to another cell.
I found the answer Find all used references in Excel formula but the solution matches wrongly also formula with references to table columns as :
=SearchValInCol2(Tabella1[articolo];[#articolo];Tabella1[b])
Then, I wrote the following VBA code using the Like operator, but surely a solution with a regular expression would be more solid (I think the following code won't work in many scenarios).
Private Function TestIfCellContainsAFormula(cellToTest As Variant) As Boolean
Dim result As Object
Dim r As Range
Dim testExpression As String
Dim objRegEx As Object
Set r = cellToTest ' INPUT THE CELL HERE , e.g. RANGE("A1")
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.IgnoreCase = True
objRegEx.Global = True
objRegEx.Pattern = """.*?""" ' remove expressions
testExpression = CStr(r.FormulaR1C1)
' search for pattern "=R[-3]C+4"
If testExpression Like "*R[[]*[]]*C*" Then
TestIfCellContainsAFormula2 = True
Exit Function
End If
' search for pattern "=RC[2]"
If testExpression Like "*R*C[[]*[]]*" Then
'If InStr(1, testExpression, "C[", vbTextCompare) <> 0 Then
TestIfCellContainsAFormula2 = True
Exit Function
End If
TestIfCellContainsAFormula2 = False
End Function
Option 1
To match R1C1 style references you can use this regex:
R(\[-?\d+\])C(\[-?\d+\])|R(\[-?\d+\])C|RC(\[-?\d+\])
See the railroad diagram for a visual explanation:
At the core is the 'offset' which is -?\d+ which is optional - followed by a digit or more. This sequence goes in the brackets ([]) to give \[-?\d+\]. Then the regex allows combinations of:
R[offset]C[offset]
R[offset]C or (|)
RC[offset] or (|)
Option 2
The regex above won't match R, C, or RC. It will match R[0], C[0], R[0]C, RC[0], and R[0]C[0] which are kind of equivalent. To eliminate those matches you might use this regex:
R(\[-?[1-9][0-9]*\])C(\[-?[1-9][0-9]*\])|R(\[-?[1-9][0-9]*\])C|RC(\[-?[1-9][0-9]*\])
Which is this:
But it seems entering R[0], C[0] and R[0]C[0] in my Excel (v2013) turns them into R, C and RC anyways - so you can avoid the additional complexity if this is not a concern.
Option 3
If you want to allow R, C and RC you can use a simpler regex:
R(\[-?\d+\])?C(\[-?\d+\])?
VBA test code
This uses Option 1.
Option Explicit
Sub Test()
Dim varTests As Variant
Dim varTest As Variant
Dim varMatches As Variant
Dim varMatch As Variant
varTests = Array("RC", _
"R[1]C", _
"RC[1]", _
"R[1]C[1]", _
"R[-1]C", _
"RC[-1]", _
"R[-1]C[-1]", _
"=SUM(A1:B2)", _
"RC[1]+R[-1]C+R[2]C[-99]", _
"R[-1]C-R[1]C[-44]-RC[999]+R[0]C[0]", _
"SearchValInCol2(Tabella1[articolo];[#articolo];Tabella1[b])")
For Each varTest In varTests
varMatches = FormulaContainsR1C1Reference(CStr(varTest))
Debug.Print "Input: " & CStr(varTest)
Debug.Print VBA.String(Len(CStr(varTest)) + 7, "-")
If IsEmpty(varMatches) Then
Debug.Print "No matches"
Else
Debug.Print UBound(varMatches) & " matches"
For Each varMatch In varMatches
Debug.Print varMatch
Next varMatch
End If
Debug.Print vbCrLf
Next varTest
End Sub
Function FormulaContainsR1C1Reference(ByVal strFormula As String) As Variant
Dim objRegex As Object
Dim strPattern As String
Dim objMatches As Object
Dim varMatches As Variant
Dim lngCounter As Long
Set objRegex = CreateObject("VBScript.RegExp")
With objRegex
' setup regex
.Global = True
.IgnoreCase = False
.Pattern = "R(\[-?\d+\])C(\[-?\d+\])|R(\[-?\d+\])C|RC(\[-?\d+\])"
' get matches
Set objMatches = .Execute(strFormula)
' iterate matches
If objMatches.Count > 0 Then
ReDim varMatches(1 To objMatches.Count)
For lngCounter = 1 To objMatches.Count
varMatches(lngCounter) = objMatches.Item(lngCounter - 1)
Next lngCounter
Else
varMatches = Empty
End If
End With
FormulaContainsR1C1Reference = varMatches
End Function
A1 style references
I posted a regex here for A1 style references:
^(?:[A-Z]|[A-Z][A-Z]|[A-X][A-F][A-D])(?:[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9][0-9]|10[0-3][0-9][0-9][0-9][0-9]|104[0-7][0-9][0-9][0-9]|1048[0-4][0-9][0-9]|10485[0-6][0-9]|104857[0-6])$

Several parameters function VBA ( Tried with call and parenthesis and without parenthesis and still not working)

I know this will be some stupid thing I overlooked, but I swear I have no clue what is wrong with this code:
Public Sub sustituirExpresion(ByVal Exp As String, ByVal Str As String, ByVal cell As String, ByVal hoja As String)
Dim strPattern As String: strPattern = Exp
Dim strReplace As String: strReplace = Str
Dim regEx As New RegExp
Dim strInput As String
Dim Myrange As Range
Set Myrange = Sheets(hoja).Range(cell)
If strPattern <> "" Then
strInput = Myrange.Value
With regEx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = strPattern
End With
sustituirExpresion = (regEx.Replace(strInput, strReplace))
End If
End Sub
Sub limpiarDescripcion()
Dim resultado As String
resultado = sustituirExpresion "/s+", " ", "AD2", "Hoja1"
MsgBox resultado
End Sub
I tried to use the Call form too:
resultado = Call sustituirExpresion ("/s+", " ", "AD2", "Hoja1")
But it still throw me an error "Expected function or varibale" and I can't understand why.
¿Any leads?
Thanks for your time.
There are several things here:
Replace the signature with Public Function sustituirExpresion(ByVal Exp As String, ByVal Str As String, ByVal cell As String, ByVal hoja As String) As String to turn a Sub to a Function
You do not need .MultiLine = True, keep it False because your regex /\s+ does not contain any ^ or $ to re-define the behavior of (they will match start/end of line if you set Multiline to True).
You do not need the parentheses around the regEx.Replace(strInput, strReplace), use sustituirExpresion = regEx.Replace(strInput, strReplace)

Visual Basic Excel Regular Expression {}

I have some trouble with {}. When i get max value like this {1,8} it not work and i don't now why. Min vale is valid well
Private Sub Highlvl_Expression()
Dim strPattern As String: strPattern = "[a-zA-Z0-9_]{1,8}"
Dim strReplace As String: strReplace = ""
Dim regEx As New RegExp
Dim Test As Boolean
With regEx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = strPattern
End With
Test = regEx.Test(Highlvl.Value)
If regEx.Test(Highlvl.Value) Then
MsgBox ("Validate")
Else
MsgBox ("Not Validate")
End If
End Sub
You specified the pattern that looks for 1 to 8 alphanumeric characters inside a string. If you run the regex against a 9-character string "ABCDE6789" (regEx.Execute("ABCDE6789")), you will have 2 matches: ABCDE678 and 9.
If you want to validate a string that should have a minimum or a maximum number of characters, you need to use anchors, i.e. start and end of string assertions ^ and $. So, use
Dim strPattern As String: strPattern = "^[a-zA-Z0-9_]{1,8}$"
And
.Global = False
The global flag is not necessary since we are not looking for multiple matches, but for a single true or false result with test.

How to include variable in Regular expression pattern

I am working on a vba macro which uses regular expression to search for a string pattern in another string.
Regex pattern includes a string (APR24 in code below) which varies. I need to know how to include a variable in the pattern.Could any one please help.
My code is as below
Public Function Regexsrch(ByVal str2bsrchd As String, ByVal str2srch As String) As Boolean
Dim Regex As New VBScript_RegExp_55.RegExp
Dim matches, s
Regex.Pattern = "(\.|\s)APR24(,|\s|\()"
Regex.IgnoreCase = True
If Regex.Test(str2bsrchd) Then
Regexsrch = True
Else
Regexsrch = False
End If
End Function
So str2srch is "APR24" or some variation? If that is the case you just use concatenation to build up your pattern string.
Public Function Regexsrch(ByVal str2bsrchd As String, ByVal str2srch As String) As Boolean
Dim Regex As New VBScript_RegExp_55.RegExp
Dim matches, s
Regex.Pattern = "(\.|\s)" + str2srch + "(,|\s|\()"
Regex.IgnoreCase = True
If Regex.Test(str2bsrchd) Then
Regexsrch = True
Else
Regexsrch = False
End If
End Function
You can specify whatever pattern you want in str2srch and then assign that to Regex.Pattern
For example
Sub Sample()
Debug.Print Regexsrch("APR24ddd", "APR24") '<~~ Returns True
Debug.Print Regexsrch("APR277ddd", "APR24") '<~~ Returns False
End Sub
Public Function Regexsrch(ByVal str2bsrchd As String, ByVal str2srch As String) As Boolean
Dim Regex As New VBScript_RegExp_55.RegExp
Dim matches, s
Regex.Pattern = str2srch
Regex.IgnoreCase = True
If Regex.Test(str2bsrchd) Then
Regexsrch = True
Else
Regexsrch = False
End If
End Function
FOLLOWUP
Even if it is dynamic you can always pass the pattern as
Debug.Print Regexsrch("APR24ddd", "(\.|\s)" & VARIABLE & "(,|\s|\()").
This gives you the flexibility of using whatever pattern you want to pass to the function and you are not limited to one pattern...

Excel VBA Regex Match Position

How do I grab the position of the first matched result in a regular expression? See below.
Function MYMATCH(strValue As String, strPattern As String, Optional blnCase As Boolean = True, Optional blnBoolean = True) As String
Dim objRegEx As Object
Dim strPosition As Integer
' Create regular expression.
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Pattern = strPattern
objRegEx.IgnoreCase = blnCase
' Do the search match.
strPosition = objRegEx.Match(strValue)
MYMATCH = strPosition
End Function
For one, I'm not entirely certain what .Match is returning (string, integer, etc.). The one solution I found said I should create a Match object to and then grab the position from there, but unlike vb, vba does not recognize the Match object. I've also seen some code like the following, but I'm not necessarily looking for the value, just the first string placement:
If allMatches.count <> 0 Then
result = allMatches.Item(0).submatches.Item(0)
End If
Somewhat ignoring any of the possible syntax errors above (mostly due to me changing variable types right and left), how do I easily/simply accomplish this?
Thanks!
You can use FirstIndex to return the position of matches using the Execute method, ie
Function MYMATCH(strValue As String, strPattern As String, Optional blnCase As Boolean = True, Optional blnBoolean = True) As String
Dim objRegEx As Object
Dim strPosition As Integer
Dim RegMC
' Create regular expression.
Set objRegEx = CreateObject("VBScript.RegExp")
With objRegEx
.Pattern = strPattern
.IgnoreCase = blnCase
If .test(strValue) Then
Set RegMC = .Execute(strValue)
MYMATCH = RegMC(0).firstindex + 1
Else
MYMATCH = "no match"
End If
End With
End Function
Sub TestMe()
MsgBox MYMATCH("test 1", "\d+")
End Sub
For the benefit of others who may be having this problem, I finally figured it out.
Option Explicit
Function CHAMATCH(strValue As String, strPattern As String, Optional blnCase As Boolean = True, Optional blnBoolean = True) As String
Dim objRegEx As Object
Dim objPosition As Object
Dim strPosition As String
' Create regular expression.
Set objRegEx = CreateObject("VBScript.RegExp")
objRegEx.Pattern = strPattern
objRegEx.IgnoreCase = blnCase
' Do the search match.
Set objPosition = objRegEx.Execute(strValue)
strPosition = objPosition(0).FirstIndex
CHAMATCH = strPosition
End Function
Instead of a Match type, just a regular Object type will do (considering all it's returning is a class). Then, if you want to grab the index location, just use .FirstIndex on the match [of your choice], or if you want the value, us .Value