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.
Related
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
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
I have been trying to get the elements of an array in my following code.
Public SetDeviceInfo(ByVal TLPTestVoltage As String, ByVal rng As Range = Nothing)
TLPTestVoltage = rng.Cells(7) 'Value passed is 7-50-21
Dim TLPTV As String
Dim LArray() As String 'Dim for output array
LArray = Split(TLPTestVoltage, "-") 'split on basis of ":" char
If Application.WorksheetFunction.IsNumber(TLPTestVoltage) = True Then
TLPTV = LArray(1)
End If
LogInfo "Set TLPTestVoltage: " & TLPTestVoltage
LogInfo "TLPTV:" & TLPTV
End Sub
The o/p for the above code is:
Set TLPTestVoltage: 7-50-21
TLPTV:
Can anyone explain what's wrong in getting the elements of the array from the above code. The output is nil. I din't error from my Vba too.
The problem is that Application.WorksheetFunction.IsNumber(TLPTestVoltage) will always be false, because TLPTestVoltage is a string. If you set a breakpoint on the line inside that if statement, you'll see that the breakpoint is never reached. You'll have to rethink what you're trying to do with that if statement. Since I don't know what your application's needs are, I'm not sure what specifically to suggest.
When controlling mainframe from Excel there are several functions one can use to navigate and scrape data. Some of these functions include, .GetString(x, y), .Putstring(x, y) and .MoveTo(x, y), just to name a few. (x denotes row #, y denotes column #, think of these like coordinates)
I have looked extensively online; (IBM redbooks, whitepapers, google, the stack, reflection api/vba manauls, tek-tips, websites strictly devoted to mainframe, etc)
One function I have been unable to find; a function that returns the cursor's current position within Mainframe's window.
Is there any way this can be done?
I figured out how to obtain cursor position by running the following code in reflection's VBE
Sub getCoordinates()
Dim ibmCurrentTerminal As IbmTerminal
Dim ibmCurrentScreen As IbmScreen
Dim returnValue As Integer
Set ibmCurrentTerminal = ThisFrame.SelectedView.control
Set ibmCurrentScreen = ibmCurrentTerminal.Screen
'---------------------------------------------------------------------
ibmCurrentTerminal.Productivity.ScreenHistory.ClearAllScreens
ibmCurrentTerminal.Productivity.RecentTyping.ClearAllItems
Dim CursorColumn As Integer
Dim valueColumn As Integer
Dim CursorRow As Integer
Dim valueRow As Integer
ibmCurrentScreen.CursorColumn = valueColumn
ibmCurrentScreen.CursorRow = valueRow
valueColumn = ibmCurrentScreen.CursorColumn
valueRow = ibmCurrentScreen.CursorRow
MsgBox "(" & valueRow & ", " & valueColumn & ")"
End Sub
This works if you want to retrieve cursor position, albeit via msgbox
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