Excel output value based on multiple strings in cell [duplicate] - excel

I want to find if a string contains a ","(comma) in it. Do we have any other option other than reading char-by-char?

Use the Instr function (old version of MSDN doc found here)
Dim pos As Integer
pos = InStr("find the comma, in the string", ",")
will return 15 in pos
If not found it will return 0
If you need to find the comma with an excel formula you can use the =FIND(",";A1) function.
Notice that if you want to use Instr to find the position of a string case-insensitive use the third parameter of Instr and give it the const vbTextCompare (or just 1 for die-hards).
Dim posOf_A As Integer
posOf_A = InStr(1, "find the comma, in the string", "A", vbTextCompare)
will give you a value of 14.
Note that you have to specify the start position in this case as stated in the specification I linked: The start argument is required if compare is specified.

You can also use the special word like:
Public Sub Search()
If "My Big String with, in the middle" Like "*,*" Then
Debug.Print ("Found ','")
End If
End Sub

There is also the InStrRev function which does the same type of thing, but starts searching from the end of the text to the beginning.
Per #rene's answer...
Dim pos As Integer
pos = InStrRev("find the comma, in the string", ",")
...would still return 15 to pos, but if the string has more than one of the search string, like the word "the", then:
Dim pos As Integer
pos = InStrRev("find the comma, in the string", "the")
...would return 20 to pos, instead of 6.

Building on Rene's answer, you could also write a function that returned either TRUE if the substring was present, or FALSE if it wasn't:
Public Function Contains(strBaseString As String, strSearchTerm As String) As Boolean
'Purpose: Returns TRUE if one string exists within another
On Error GoTo ErrorMessage
Contains = InStr(strBaseString, strSearchTerm)
Exit Function
ErrorMessage:
MsgBox "The database has generated an error. Please contact the database administrator, quoting the following error message: '" & Err.Description & "'", vbCritical, "Database Error"
End
End Function

You wouldn't really want to do this given the existing Instr/InstrRev functions but there are times when it is handy to use EVALUATE to return the result of Excel worksheet functions within VBA
Option Explicit
Public Sub test()
Debug.Print ContainsSubString("bc", "abc,d")
End Sub
Public Function ContainsSubString(ByVal substring As String, ByVal testString As String) As Boolean
'substring = string to test for; testString = string to search
ContainsSubString = Evaluate("=ISNUMBER(FIND(" & Chr$(34) & substring & Chr$(34) & ", " & Chr$(34) & testString & Chr$(34) & "))")
End Function

Related

Get the tooltip text contained showing the argument list of a sub or function using Application.MacroOptions

Using Excel VBA: Is it possibile to get the text contained in the tooltip which shows the argument list of a sub or function?
The Application.MacroOptions method knows the argument "ArgumentDescriptions" but it is possibly only set. Is there any way to read this info?
"Get the tooltip text contained showing the argument list of a sub or function ... The Application.MacroOptions Method has (the) argument ArgumentDescriptions but it Is possibile(!) only set. Is there any way to read this info?"
► Afaik there is no built-in way.
Possible workaround
As you "need this info in VBA code for a function/sub created in other module or class.",
you might want to analyze your code modules by referencing the
"Microsoft Visual Basic for Applications Extensibility 5.3" library in the VB Editor's menu.
Caveats:
Security: Requires to trust access to the VBA project object model.
Rights: If not only for your personal use, consider that other corporate users may
not have enough rights to turn that feature on.
Self reflection: Mirrors the currently compiled/saved code only, so it might not reflect the latest code when the searched procedure body line has been changed.
Line breaks: The following approach assumes that the entire procedure info is coded in one line -
not regarding closing line breaks via "_";
it should be easy to extend the .Lines result in these cases by your own (e.g. benefitting from the count argument or by additional loops through the next lines).
The following code doesn't intend neither to cover or to optimize all possibilities,
but to direct you to a solution keeping it short & simple.
Function GetSyntax()
Function GetSyntax(wb As Workbook, Optional ByVal srchProcName As String = "GetCookie") As String
'Purp: Show name & arguments of a given procedure
'1) escape a locked project
If wb.VBProject.Protection = vbext_pp_locked Then Exit Function ' escape locked projects
'2) loop through all modules
Dim component As VBIDE.VBComponent
For Each component In wb.VBProject.VBComponents
' Debug.Print "***"; component.Name, component.Type
Dim found As Boolean
'3) loop through procedures (as well as Let/Set/Get properties)
Dim pk As Long ' proc kind enumeration
For pk = vbext_pk_Proc To vbext_pk_Get
'a) get the essential body line of the search procedure
Dim lin As String
lin = getLine(component.CodeModule, srchProcName, pk)
'b) found non-empty code line?
found = Len(lin) <> 0
If found And pk = 0 Then GetArgs = lin: Exit For
'c) get proc info(s) - in case of Let/Set/Get properties
Dim Delim As String
GetSyntax = GetSyntax & IIf(found, Delim & lin, "")
Delim = vbNewLine ' don't change line order
Next pk
'If found Then Exit For ' if unique proc names only
Next component
End Function
Help function getLine()
Function getLine(module As VBIDE.CodeModule, ByVal srchProcName As String, ByVal pk As Long) As String
'a) define procedure kind
Dim ProcKind As VBIDE.vbext_ProcKind
ProcKind = pk
'b) get effective row number of proc/prop body line
On Error Resume Next
Dim effectiveRow As Long
effectiveRow = module.ProcBodyLine(srchProcName, ProcKind) ' find effective row of search procedure
'c) provide for non-findings or return function result (Case 0)
Select Case Err.Number
Case 0 ' Found
Dim lin As String
'Syntax: obj.Lines (startline, count) As String
lin = Trim(module.Lines(effectiveRow, 1))
getLine = lin
Case 35 ' Not found
Err.Clear: On Error GoTo 0
Case Else
Debug.Print "** " & " Error " & Err.Number & " " & Err.Description: Err.Clear: On Error GoTo 0
End Select
End Function
Possible Test call
Dim procList, proc
procList = Split("getCookie,foo,myNewFunction", ",")
For Each proc In procList
MsgBox GetSyntax(ThisWorkbook, proc), vbInformation, proc
Next

UDF for a combination of Excel Formulas [duplicate]

How do I count the number of times one string appears within another in Access VBA? For example, how would I count how many times "the" occurs in "The quick brown fox jumps over the lazy dog."?
As you are ok with substrings/case sensitivity
matches = (len(lookin) - len(replace$(lookin, find, ""))) / len(find)
You can simplify the function with far less variables and avoid the usage of For by the following function.
Public Function getOccuranceCount(Expression As String, Find As String) As Long
'*******************************************************************************
'Code Courtesy of
' Paul Eugin
'
' Input - Expression, the String to check
' Find, the String pattern to be checked for
' Output - The number of occurance of the Find String in the Expression String
' Usage - getOccuranceCount("The quick brown fox jumped over the lazy dog.","saw")
' 0
' getOccuranceCount("The quick brown fox jumped over the lazy dog.","the")
' 2
'*******************************************************************************
On Error GoTo errDisplay
Dim strArr() As String, i As Long
strArr = Split(Expression, Find)
getOccuranceCount = UBound(strArr)
errExit:
Exit Function
errDisplay:
MsgBox "The following error has occured while trying to get the count." & vbCrLf & vbCrLf & _
"Error (" & Err.Number & ") - " & Err.Description, vbCritical, "Contact the DB Admin."
Resume errExit
End Function
The function will Split it into an array then all you need is the Count of the Boundaries. Hope this helps.
Dim lookin As String
Dim tofind As String
lookin = "The quick brown fox jumps over the lazy dog."
tofind = "the "
Dim r As Object, matches
Set r = CreateObject("VBScript.RegExp")
r.Pattern = tofind
r.IgnoreCase = True
r.Multiline = False
r.Global = True
Set matches = r.Execute(lookin)
matches finds two hits. One at index = 0 and one at index = 31.
You can use this function, which uses InStr:
Function CountStringOccurances(strStringToCheck As String, strValueToCheck As String) As Integer
'Purpose: Counts the number of times a string appears in another string.
On Error GoTo ErrorMessage
Dim intStringPosition As Integer
Dim intCursorPosition As Integer
CountStringOccurances = 0
intCursorPosition = 1
For i = 0 To Len(strStringToCheck)
intStringPosition = InStr(intCursorPosition, strStringToCheck, strValueToCheck)
If intStringPosition = 0 Then
Exit Function
Else
CountStringOccurances = CountStringOccurances + 1
intCursorPosition = intStringPosition + Len(strValueToCheck)
End If
Next i
Exit Function
ErrorMessage:
MsgBox "The database has generated an error. Please contact the database administrator, quoting the following error message: '" & Err.Description & "'", vbCritical, "Database Error"
End
End Function
The function returns the position of your value within the string. If that's 0, it exits and returns the number. If it returns a positive value (and it will if the value is present), then it adds one to the number of occurances, and then checks again, moving its starting position to just after the previous occurance. This function is case insenstive.
Using the examples above:
CountStringOccurances("The quick brown fox jumped over the lazy dog.","the")
would return 2.
You can use Split in the same solution as this question has. It's the tidiest solution that I've seen so far.

Finding and adding code into a module via a macro

I am trying to create code in VBA that will search thru a module, find specific text, then add a string BEFORE that text in the same line. For example, every time it says "yo" in the module, I want it changed to say "Add This yo".
The code below successfully finds instances where it says "yo" in the module, but it doesn't add the text where I want it to. Instead, text is added at the very top of the module (not even inside a sub). How do I get this text to be added before "yo"?
Public Sub Edit()
Dim vb As VBComponent
Dim i As Long
Dim intFoundLine As Integer
Dim strSearchPhrase As String
Set vb = ThisWorkbook.VBProject.VBComponents("Module2")
strSearchPhrase = "yo"
intLinesNr = vb.CodeModule.CountOfLines
For i = 1 To intLinesNr
If vb.CodeModule.Find(strSearchPhrase, i, 1, -1, -1) Then
intFoundLine = i
MsgBox "Found at " & intFoundLine
vb.CodeModule.AddFromString ("Add This")
End If
Next
End Sub
Replace the line with a the new text:
vb.CodeModule.ReplaceLine i, "Add This" & vb.CodeModule.Lines(i, 1)
Based on Mathieu Guindon's answer, here is how I would handle all the instances of the search phrase:
Do While vb.CodeModule.Find(strSearchPhrase, i, 1, -1, -1)
vb.CodeModule.ReplaceLine i, "Add This" & vb.CodeModule.Lines(i, 1)
i = i + 1
Loop
'
Iterating all lines of a module seems a poor use of the Find method, which is capable of finding text anywhere in a module and takes ByRef arguments that, if the function returns True, will contain the exact location of the found text - that's a great use case for a user-defined Type:
Option Explicit
Private Type CodeStringLocation
StartLine As Long
EndLine As Long
StartColumn As Long
EndColumn As Long
End Type
Sub test()
Dim module As CodeModule
Set module = ThisWorkbook.VBProject.VBComponents("Module1").CodeModule
Dim foundAt As CodeStringLocation
If module.Find("test", foundAt.StartLine, foundAt.StartColumn, foundAt.EndLine, foundAt.EndColumn) Then
'L9C5-L9C9
Debug.Print "L" & foundAt.StartLine & "C" & foundAt.StartColumn & "-L" & foundAt.EndLine & "C" & foundAt.EndColumn
End If
End Sub
Now that you have the in-editor line number you want to rewrite, use CodeModule.ReplaceLine to rewrite it - for example by replacing the Debug.Print statement above with this:
Dim newLine As String
newLine = Replace(module.Lines(foundAt.StartLine, 1), "test", "Renamed")
module.ReplaceLine foundAt.StartLine, newLine
If you need to replace all occurrences of the search text in the module, simply run the search until CodeModule.Find returns False - like this:
Dim foundAt As CodeStringLocation
Do While module.Find("test", foundAt.StartLine, foundAt.StartColumn, foundAt.EndLine, foundAt.EndColumn)
Dim newLine As String
newLine = Replace(module.Lines(foundAt.StartLine, 1), "test", "Renamed")
module.ReplaceLine foundAt.StartLine, newLine
Loop
Key point being that everything but the search text is an output parameter; by hard-coding any of these arguments, you lose the reference to the value they return. If you want to limit the search to a specific scope or range of lines, the right way to do it would be to configure the foundAt values before running the search.
Dim foundAt As CodeStringLocation
foundAt.StartLine = 10
Do While module.Find("test", foundAt.StartLine, foundAt.StartColumn, ...
That way you leverage the actual bidirectional nature of the arguments, without losing the reference to the output values - and without iterating up to 10K lines of code when you don't need to.
Note that this is purely text-based search and takes absolutely zero syntactical considerations: the API will not care if the search string is found in an identifier, a comment, a string literal, or a keyword.

Use comma separated string as function parameters

I have created a comma separated string. I want to use this string as input in an Application.Run procedure.
The string looks exactly like below - with quotes..
MyString = "First_Number", "Second_Number", "Third_Number"
I want to use this string as parameter input:
Application.Run("'" & "Book2.xlsm" & "'!MySub", *MyString*)
It doesn't work.. I get an "argument not optimal".
I build the string with the following code, and the range is just a row with the parameters.
Function csvRange(myRange As Range)
Dim csvRangeOutput
For Each entry In myRange
csvRangeOutput = csvRangeOutput & entry.Value & """, " & """"
Next
csvRange = Left(csvRangeOutput, Len(csvRangeOutput) - 4)
End Function
Hope someone can help.
Evaluate might do the trick. Look at this piece of test-code:
Sub Test()
Dim params As String
params = "3,5"
Debug.Print Evaluate("addNumbers(" & params & ")")
End Sub
Function addNumbers(a As Integer, b As Integer) As Integer
addNumbers = a + b
End Function
It's code that writes code, how exiting! :D
maybe you can use something like this.
String MyString = "First_Number, Second_Number, Third_Number";
String words[] = MyString.split(",");
Then use the individual words as words[0],words[1], etc.
or you can loop through the array words[] and use the individual terms as you like.

Check if a string contains another string

I want to find if a string contains a ","(comma) in it. Do we have any other option other than reading char-by-char?
Use the Instr function (old version of MSDN doc found here)
Dim pos As Integer
pos = InStr("find the comma, in the string", ",")
will return 15 in pos
If not found it will return 0
If you need to find the comma with an excel formula you can use the =FIND(",";A1) function.
Notice that if you want to use Instr to find the position of a string case-insensitive use the third parameter of Instr and give it the const vbTextCompare (or just 1 for die-hards).
Dim posOf_A As Integer
posOf_A = InStr(1, "find the comma, in the string", "A", vbTextCompare)
will give you a value of 14.
Note that you have to specify the start position in this case as stated in the specification I linked: The start argument is required if compare is specified.
You can also use the special word like:
Public Sub Search()
If "My Big String with, in the middle" Like "*,*" Then
Debug.Print ("Found ','")
End If
End Sub
There is also the InStrRev function which does the same type of thing, but starts searching from the end of the text to the beginning.
Per #rene's answer...
Dim pos As Integer
pos = InStrRev("find the comma, in the string", ",")
...would still return 15 to pos, but if the string has more than one of the search string, like the word "the", then:
Dim pos As Integer
pos = InStrRev("find the comma, in the string", "the")
...would return 20 to pos, instead of 6.
Building on Rene's answer, you could also write a function that returned either TRUE if the substring was present, or FALSE if it wasn't:
Public Function Contains(strBaseString As String, strSearchTerm As String) As Boolean
'Purpose: Returns TRUE if one string exists within another
On Error GoTo ErrorMessage
Contains = InStr(strBaseString, strSearchTerm)
Exit Function
ErrorMessage:
MsgBox "The database has generated an error. Please contact the database administrator, quoting the following error message: '" & Err.Description & "'", vbCritical, "Database Error"
End
End Function
You wouldn't really want to do this given the existing Instr/InstrRev functions but there are times when it is handy to use EVALUATE to return the result of Excel worksheet functions within VBA
Option Explicit
Public Sub test()
Debug.Print ContainsSubString("bc", "abc,d")
End Sub
Public Function ContainsSubString(ByVal substring As String, ByVal testString As String) As Boolean
'substring = string to test for; testString = string to search
ContainsSubString = Evaluate("=ISNUMBER(FIND(" & Chr$(34) & substring & Chr$(34) & ", " & Chr$(34) & testString & Chr$(34) & "))")
End Function

Resources