VBA optimization robust code - vba

So I'm completely new to VBA. I have a java-fetish so I'm not new to programming, however manipulating office documents just seemed easier with VBA.
Anyway, on topic:
I'm currently automating things in the company (This example is creating a contract). However, using Java, I always learned to make robust code and although the VBA code now works, I'm not happy with it because it requires a lot of 'friendliness' of the user. So my question is (I hope you don't mind), could you give me a nudge in the right direction to make my code way more robust?
Here's the code:
Function spaties(Name As String) As String
' Function used to ensure the length of a String (Working with Range)
Dim index As Integer
While (Len(Name) < 30)
Name = Name + " "
Wend
spaties = Name
End Function
Sub Macro3()
'
' Macro3 Macro
'
'
'ActiveDocument.Range(26101, 26102).Text = "d"
StartUndoSaver
Dim firma As String
firma = InputBox("Voor welke onderaannemer? (Zonder hoofdletters)" + Chr(10) + "(nicu, sorin of marius)")
Dim werf As String
werf = InputBox("Over welke Werf gaat het?")
Dim datum As String
datum = InputBox("Op welke datum spreekt het contract? (dd/mm/yyyy)")
With ActiveDocument
.Range(25882, 25899).Text = datum
ActiveDocument.Range(575, 605).Text = spaties(werf)
ActiveDocument.Range(1279, 1309).Text = spaties(werf)
End With
Select Case Len(firma)
Case 4
With ActiveDocument
.Range(26168, 26181).Text = "Nicu Dinita"
.Range(26062, 26088).Text = "Badi Woodconstruct SRL"
.Range(11359, 11371).Text = "Nicu Dinita"
End With
Case 5
With ActiveDocument
.Range(26168, 26181).Text = "Asavei Sorin"
.Range(26062, 26088).Text = "BELRO INTERIOR DESIGN SRL"
.Range(11359, 11371).Text = "Asavei Sorin"
End With
Case 6
With ActiveDocument
.Range(26168, 26181).Text = "Ivan Maricel"
.Range(26062, 26088).Text = "Solomon & Aaron Construct"
.Range(11359, 11371).Text = "Ivan Maricel"
End With
End Select
Dim prijs As String
Dim besch As String
Dim eenh As String
Dim hoev As Integer
hoev = InputBox("Hoeveel artikels zijn er?")
Dim index As Integer
index = 1
While (index <= hoev)
besch = InputBox("Beschrijving van het artikel (engels)")
prijs = InputBox("prijs van het artikel")
eenh = InputBox("Eenheid van het artikel")
With ActiveDocument
.Range(5701, 5702).Text = "" + vbTab + spaties2(besch, prijs, eenh) + Chr(10) + vbTab
End With
index = index + 1
Wend
With ActiveDocument.Sections(1)
.Headers(wdHeaderFooterPrimary).Range.Text = "Raes G. Schrijnwerken BVBA" + vbTab + vbTab + datum + Chr(10) + "Robert Klingstraat 5" + Chr(10) + "8940 Wervik"
.Footers(wdHeaderFooterPrimary).Range.Text = "Overeenkomst tot onderaanneming" + Chr(10) + "met betrekking tot:" + werf
.Footers(wdHeaderFooterPrimary).PageNumbers.Add PageNumberAlignment:=wdAlignPageNumberRight
End With
If firma = "sorin" Then
ActiveDocument.Range(254, 255).ImportFragment "Z:\Raes Netwerk DATA\professioneel\004 Sjablonen\belro.docx", False
Else
If firma = "nicu" Then
With ActiveDocument
.Range(254, 255).ImportFragment "Z:\Raes Netwerk DATA\professioneel\004 Sjablonen\Nicu.docx", False
End With
Else
If firma = "marius" Then
ActiveDocument.Range(254, 255).ImportFragment "Z:\Raes Netwerk DATA\professioneel\004 Sjablonen\Marius.docx", False
End If
End If
End If
ActiveDocument.PrintOut
ActiveDocument.PrintOut
End Sub
Function spaties2(artikel As String, prijs As String, eenh As String) As String
'Another function to ensure length of String
Dim index As Integer
Dim eind As String
eind = "" + artikel + vbTab + vbTab + prijs + "€/" + eenh
While (Len(eind) < 100)
eind = eind + " "
Wend
spaties2 = eind
End Function
As you can see, the code is very basic. And although it works, it's no good to deliver.
The two defined Functions are simply formatting the String of the user because obviously the name of something is not always the same length.
I'd like to cut out the Range properties, because in my opinion, that's what makes the program so sensitive to changes.
Any and all suggestions are welcome.
note: For the moment, the contract can have three different 'target parties' so that's why the Select Case statement is there. It's going to be completely useless if it should grow but for now it works.

Here's one:
sName = Left(sName & Space(30), 30)
And I think it's better to use bookmarks as placeholders instead of using Range(start, end)
How to change programmatically the text of a Word Bookmark

I think that your code needs some Trim's, in order to avoid mistaken spaces before and after the names (when you use some inputboxes, I mean).
And you need to verify input dates, too.
For string concatenation, use the ampersand (&) better than the plus sign (+), in order to avoid mistaken sums.
Instead of Chr(10) I have some recommendations in order to make your code more readable:
Chr(13) = vbCr
Chr(10) = vbLf
Chr(13) & Chr(10) = vbCrLf
Verify that the files you are indicating exist.

Using Range with numerical values is definitely not reliable. Bookmarks, as Tim suggests or content controls if this is Word 2007 or later. Content Controls are Microsoft's recommendation, going forward, but I don't see any particular advantage one way or the other for your purpose.
Looking at all the InputBox calls I have to wonder whether displaying a VBA UserForm for the input might not be better? All the input fields in one place, rather than flashing multiple prompts. You can validate for correct input before the UserForm is removed from the screen, etc.

Related

Conditional Formatting in word vba

Greeting to all Members and Experts, I am trying to automate
the formatting process in word. The formatting is done by applying styles. But before applying styles I need to trim extra spaces between characters of serial numbers, for example, 1. a. i. and insert tabs after dot(.) and then apply the style. I have attached a sample document. Plz have a look. I have tried to get the desired result by using the following code but it doesn't get the work done
I am new here so i dont know how to attach sample files so, here is the link for sample file. https://docs.google.com/document/d/1Z1dB6tvPKVrxHlw7qV8VNyiy49c5lRZN/edit?usp=sharing&ouid=101706223056224820285&rtpof=true&sd=true
Any help or suggestion would be of great help. Thanks in advance...
Sub formatts()
Dim a As Integer
Dim i As Integer, n As Long, para As Paragraph, rng As Range, doc As Document
Set doc = ActiveDocument
With doc
For i = 1 To .Range.Paragraphs.Count
For n = 1 To doc.Paragraphs(i).Range.Characters.Count
If .Paragraphs(i).Range.Characters(n).Text = " " Or .Paragraphs(i).Range.Characters(n).Text = Chr(9) Or .Paragraphs(i).Range.Characters(n).Text = Chr(160) Then
.Paragraphs(i).Range.Characters(n).Select
'This line checks whether the first character is whitespace character or not and delete it.
doc.Paragraphs(i).Range.Characters(n).Delete
ElseIf .Paragraphs(i).Range.Characters(n).Text = "." Then
.Paragraphs(i).Range.Characters(n).InsertAfter (vbTab)
n = n + 1
a = a + 1
ElseIf .Paragraphs(i).Range.Characters(n).Text Like "[a-z]." And .Paragraphs(i).Range.Characters(n).Next.Next.Text <> "i" Then
Exit For
End If
If a >= 3 Then Exit For
Next
For n = 1 To doc.Paragraphs(i).Range.Characters.Count
If .Paragraphs(i).Range.Characters(n).Text = "i" And .Paragraphs(i).Range.Characters(n).Next.Text = "." And .Paragraphs(i).Range.Characters(n).Next.Next.Text = " " Then
doc.Range.Paragraphs(i).Style = "shh"
Exit For:
ElseIf .Paragraphs(i).Range.Characters(n).Text = "a" Or .Paragraphs(i).Range.Characters(n).Text = "b" Or .Paragraphs(i).Range.Characters(n).Text = "c" And .Paragraphs(i).Range.Characters(n).Next.Text = "." And .Paragraphs(i).Range.Characters(n).Next.Next.Text = " " Then
doc.Range.Paragraphs(i).Style = "sh"
Exit For
End If
Next
Next
End With
End Sub

How to update multiple Word Content Controls through VBS at once?

I am trying to feed data from an Excel sheet array (Udaje) to populate a several word documents from a template (hence the For in the example). I would like to insert some of the data to several Content Controls (text) at the same time. I am calling them by Tags and I know that I have to specify by adding .Item() - but then I only update one of the Content Controls.
Is there any way to overcome this restriction? I was thinking about cycling the tags with for but it seems to be a bit clumsy as I do not know how many tags I have to go through. I am a beginner at the VBA.
Or should I be using bookmarks instead?
For i = 1 To LastRow
'.SelectContentControlsByTag("NapRozhodnuti").Item(1).Range.Text = Udaje(i, 4)
.SelectContentControlsByTag("ZeDne").Item(1).Range.Text = Udaje(i, 5)
.SelectContentControlsByTag("NapadRozkladu").Item(1).Range.Text = Udaje(i, 6)
.SelectContentControlsByTag("Ucastnik").Item(1).Range.Text = Udaje(i, 2)
.SelectContentControlsByTag("DatumRK").Item(1).Range.Text = DatumRK
.SelectContentControlsByTag("NavrhRK").Item(1).Range.Text = NavrhRK
.SelectContentControlsByTag("OblastRK").Item(1).Range.Text = OblastRK
.SelectContentControlsByTag("Tajemnik").Item(1).Range.Text = Tajemnik
.SelectContentControlsByTag("Gender").Item(1).Range.Text = Gender
.SaveAs2 Filename:= i & " - dokumenty_k_RK.docx", _
FileFormat:=wdFormatDocument
Next i
Edit: the solution I chose in the end was to go through CCs in the document according to their Index number and set the value of each CC according to its tag:
For i = 1 To LastRow
For y = 1 To CCNumber
Select Case .ContentControls(y).Tag
Case "NapRozhodnuti"
.ContentControls(y).Range.Text = Udaje(i, 4)
Case "ZeDne"
.ContentControls(y).Range.Text = Udaje(i, 5)
Case "NapadRozkladu"
.ContentControls(y).Range.Text = Udaje(i, 6)
Case "Ucastnik"
.ContentControls(y).Range.Text = Udaje(i, 2)
Case "DatumRK"
.ContentControls(y).Range.Text = DatumRK
Case "NavrhRK"
.ContentControls(y).Range.Text = NavrhRK
Case "OblastRK"
.ContentControls(y).Range.Text = OblastRK
Case "Tajemnik"
.ContentControls(y).Range.Text = Tajemnik
Case "Gender"
.ContentControls(y).Range.Text = Gender
End Select
Next y
.SaveAs2 Filename:="..." & i & " - dokumenty_k_RK.docx", _
FileFormat:=wdFormatDocument
Next i
Edit: loop code
...
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
objWord.Documents.Open "\\fs1\homes\rostislav.janda\Documents\320\pozvanka_prazdna.docx"
With objWord.ActiveDocument
Set ccs = .SelectContentControlsByTag("Spznrozkladu")
LoopCCs ccs, Udaje(i, 1)
.SaveAs2 Filename:="\\fs1\homes\rostislav.janda\Documents\320\výstup\pozvanka.docx", _
FileFormat:=wdFormatDocument 'uloží s formátem .docx
.Saved = True
End With
objWord.Quit
Set objWord = Nothing
End Sub
*Sub LoopCCs(ccs As Word.ContentControls, val As String)*
Dim cc As Word.ContentControl
For Each cc In ccs
cc.Range.Text = val
Next cc
End Sub
The Suprocedure declaration line is where the error ocurres.
Even though you've already found an approach that works for you, here's a tip that bases on the starting point you provide in your Question. You're using SelectContentControlsByTag, then only addressing the first of the controls found, using .Item(1).
This method returns an array of content controls and you don't have to know, going in, how many: you can use a For Each loop to cycle through as many as there are in the array. And so that you don't need to repeat the code of the loop for each tag, put that in a separate procedure, passing the array plus the value to be assigned to content controls with the same tag to it.
So something like this:
With doc
'Like this
Set ccs = .SelectContentControlsByTag("test")
LoopCCs ccs, Udaje(i, 4)
'Or like this
LoopCCs .SelectContentControlsByTag("ZeDne"), Udaje(i, 5)
End With
'Code is VBA and demonstrates the Word object model data types
'For VBS don't declare as types or type as Object
Sub LoopCCs(ccs as Word.ContentControls, val as String)
Dim cc as Word.ContentControl
For Each cc In ccs
cc.Range.Text = val
Next cc
End Sub
To do it using the Custom XML Part way, you could use the following code. As it stands, it needs to be in a single module.
You would use replaceAndLinkCxp to create/recreate the necessary Custom XML Part (i.e. it's a one off).
You would use linkedTaggedCcsToCxps to link/relink your Tagged content controls to the correct Cxp/Element (also a one-off). To work with the document, it would probably be simpler to create a Content Control for each tag, connect them using this routine, then create an autotext for the control.
You would use something based on populateCxpData to put the data in your Cxp.
There are quite a few assumptions (e.g. all the content controls are plain text, Element names are the same as tag names) and plenty of scope for improvement.
' This should be a name that belongs to you/your organisation
' It should also be unique for each different XML part structure
' you create. i.e. if you have one XML part with elements a,b,c
' and another with elements a,b,d, give them different namespace
' names.
Const sNameSpace = "hirulau"
' Specify the root element name for the part
Const sRootElementName = "ccdata"
Sub replaceAndLinkCxp()
' This deletes any existing CXP with the namespace specified
' in sOldNamespace, and creates a new CXP with the namespace
' in sNamespace. Any data in the CXP is lost.
' Then it links each Content Control with a tag name
' the same as an Element name in the part
' The old namespace (can be the same as the new one)
Const sOldNamespace = "hirulau"
Dim cc As Word.ContentControl
Dim ccs As Word.ContentControls
Dim cxp As Office.CustomXMLPart
Dim cxps As Office.CustomXMLParts
Dim i As Long
Dim s As String
' Specify the number and names of the elements and tags
' Each Element name should be unique, and a valid XML Element name
' and valid Content Control Tag Name
' (No nice way to do this in VBA - could just have a string and split it)
' NB, your CC tag names do not *have* to be the same as the XML Element
' names, but in this example we are making them that way
Dim sElementName(8) As String
sElementName(0) = "NapRozhodnuti"
sElementName(1) = "ZeDne"
sElementName(2) = "NapadRozkladu"
sElementName(3) = "Ucastnik"
sElementName(4) = "DatumRK"
sElementName(5) = "NavrhRK"
sElementName(6) = "OblastRK"
sElementName(7) = "Tajemnik"
sElementName(8) = "Gender"
' remove any existing CXPs with Namespace sOldNamespace
Set cxps = ActiveDocument.CustomXMLParts.SelectByNamespace(sOldNamespace)
For Each cxp In cxps
cxp.Delete
Next
Set cxps = Nothing
'Debug.Print ActiveDocument.CustomXMLParts.Count
' Build the XML for the part
s = "<" & sRootElementName & " xmlns=""" & sNameSpace & """>" & vbCrLf
For i = LBound(sElementName) To UBound(sElementName)
s = s & " <" & sElementName(i) & " />" & vbCrLf
Next
s = s & "</" & sRootElementName & ">"
'Debug.Print s
' Create the Part
Set cxp = ActiveDocument.CustomXMLParts.Add(s)
' For each element/tag name, find the ccs with the tag
' and connect them to the relevant element in the part
For i = LBound(sElementName) To UBound(sElementName)
For Each cc In ActiveDocument.SelectContentControlsByTag(sElementName(i))
' the "map:" is just a local mapping to the correct namespace.
' It doesn't have any meaning outside this method call.
cc.XMLMapping.SetMapping "/map:" & sRootElementName & "/map:" & sElementName(i) & "[1]", "xmlns:map=""" & sNameSpace & """", cxp
Next
Next
Set cxp = Nothing
End Sub
Sub linkTaggedCcsToCxps()
' Finds our Custom part, then relinks all controls with
' tag names that correspond to its *top level element names*
' So as long as you tag a suitable content control correctly,
' you can use this routine to make it point at the correct Cxp Element
Dim cc As Word.ContentControl
Dim cxn As Office.CustomXMLNode
Dim cxps As Office.CustomXMLParts
' Notice that we need the correct namespace name to do this
Set cxps = ActiveDocument.CustomXMLParts.SelectByNamespace(sNameSpace)
If cxps.Count = 0 Then
MsgBox "Could not find the expected Custom XML Part."
Else
' Iterate through all the *top-level* child Element nodes
For Each cxn In cxps(1).SelectNodes("/*/*")
For Each cc In ActiveDocument.SelectContentControlsByTag(cxn.BaseName)
' the "map:" is just a local mapping to the correct namespace.
' It doesn't have any meaning outside this method call.
cc.XMLMapping.SetMapping "/map:" & sRootElementName & "/map:" & cxn.BaseName & "[1]", "xmlns:map=""" & sNameSpace & """", cxps(1)
Next
Next
End If
Set cxps = Nothing
End Sub
Sub populateCxpData()
Dim sXpPrefix As String
' You would need to populate the following things
Dim i As Integer
Dim Udaje(1, 6) As String
Dim DatumRK As String
Dim NavrhRK As String
Dim OblastRK As String
Dim Tajemnik As String
Dim Gender As String
i = 1
' we need the namespace, but this time assume that we can use
' the first part with that namespace (and that it exists)
With ActiveDocument.CustomXMLParts.SelectByNamespace(sNameSpace)(1)
sXpPrefix = "/*/" & .NamespaceManager.LookupPrefix(sNameSpace) & ":"
.SelectSingleNode(sXpPrefix & "NapRozhodnuti[1]").Text = Udaje(i, 4)
.SelectSingleNode(sXpPrefix & "ZeDne[1]").Text = Udaje(i, 5)
.SelectSingleNode(sXpPrefix & "NapadRozkladu[1]").Text = Udaje(i, 6)
.SelectSingleNode(sXpPrefix & "Ucastnik[1]").Text = Udaje(i, 2)
.SelectSingleNode(sXpPrefix & "DatumRK[1]").Text = DatumRK
.SelectSingleNode(sXpPrefix & "NavrhRK[1]").Text = NavrhRK
.SelectSingleNode(sXpPrefix & "OblastRK[1]").Text = OblastRK
.SelectSingleNode(sXpPrefix & "Tajemnik[1]").Text = Tajemnik
.SelectSingleNode(sXpPrefix & "Gender[1]").Text = Gender
End With
End Sub

VBA Excel Dynamically Display Added Results

So I am modeling my question with a simple Grocery List applications.
Program GUI:
Now what I want is for the Customer to enter: Eggs, Milk, and Bread and for that to enter and output to a .txt file.
Current Code:
Private Sub CreateList_Click()
Dim myFile As String, myString As String
myFile = "C:\Reformatted.txt"
Open myFile For Output As #1
myString = First.Value + Second.Value + Third.Value + Fourth.Value + Fifth.Value
Print #1, myString
Close #1
Shell "C:\Windows\Notepad.exe C:\Reformatted.txt", 1
End Sub
Desired Operation:
What I want to happen is that ther enter there first 5 items. Then it prompts them if they want another 5. If they do then they can add another line.
So I understand that I can add a MsgBox in VB and just design a while loop for that. My question is how to display the results of their first/previous submissions?
Desired Result:
I understand that VB stores the values as variables, but how can I show them to the user while they still have a chance to enter more entries. Also how to add all this with the preferred formatting to a notepad file?
----------------------------After Miss Palmer's Answer--------------------------
Private Sub AddEntry_Click()
Dim UserEntry As String
UserEntry = First.Value + DDPP.Value + Filer.Value + EntryNumber.Value
myString = myString & Chr(13) & UserEntry
GroceryList.UserDisplay.Caption = "You have entered:" & myString
End Sub
Scenario 1 - First Addition
Scenario 2 - Second Addition
The two additions should be placed one after the other. But currently it just replaces it.
You can update a label on the form on each iteration of your while loop using something of the form:
FormName.LabelName.Caption = "you have entered:" & myString
and then add to the string each loop with
myString = myString & First.Value + Second.Value + Third.Value + Fourth.Value + Fifth.Value
EDIT
myString = myString & chr(13) & First.Value + Second.Value + Third.Value + Fourth.Value + Fifth.Value

How to find word between character and a number in VBA

For example, I have this string that reads "IRS150Sup2500Vup". It could also be "IRS250Sdown1250Vdown".
In my previous qn, I asked how to find a number between 2 characters. Now, I need to find the word up or down after the second S now. Since it appears between the character S and the number, how do I do it?
My code looks like this:
Dim pos, pos1,pos2 strString As String
pos = InStr(1, objFile.Name, "S") + 1
pos1 = InStr(pos, objFile.Name, "S")
pos2 = InStr(pos1, objFile.Name, ?)
pos1 returns the index of the second S. I am not sure what to place in ?
Using Regex.
Note: you need a reference to MS VBScripts Regular Expression library.
Dim r As VBScript_RegExp_55.RegExp
Dim sPattern As String, myString As String
Dim mc As VBScript_RegExp_55.MatchCollection, m As VBScript_RegExp_55.Match
myString = "IRS150Sup2500Vup"
sPattern = "\w?up+" 'searches for Sup, Vup, etc.
Set r = New VBScript_RegExp_55.RegExp
r.Pattern = sPattern
Set mc = r.Execute(myString)
For Each m In mc ' Iterate Matches collection.
MsgBox "word: '" & m.Value & "' founded at: " & m.FirstIndex & " length: " & m.Length
Next
For further information, please see:
How To Use Regular Expressions in Microsoft Visual Basic 6.0
Find and replace text by using regular expressions (Advanced)

Find and replace all names of variables in VBA module

Let's assume that we have one module with only one Sub in it, and there are no comments. How to identify all variable names ? Is it possible to identify names of variables which are not defined using Dim ? I would like to identify them and replace each with some random name to obfuscate my code (O0011011010100101 for example), replace part is much easier.
List of characters which could be use in names of macros, functions and variables :
ABCDEFGHIJKLMNOPQRSTUVWXYZdefghijklmnopqrstuvwxyzg€‚„…†‡‰Š‹ŚŤŽŹ‘’“”•–—™š›śťžź ˇ˘Ł¤Ą¦§¨©Ş«¬­®Ż°±˛ł´µ¶·¸ąş»Ľ˝ľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙ÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙
Below are my function I've wrote recenlty :
Function randomName(n as integer) as string
y="O"
For i = 2 To n:
If Rnd() > 0.5 Then
y = y & "0"
Else
y = y & "1"
End If
Next i
randomName=y
End Function
In goal to replace given strings in another string which represent the code of module I use below sub :
Sub substituteNames()
'count lines in "Module1" which is part of current workbook
linesCount = ActiveWorkbook.VBProject.VBComponents("Module1").CodeModule.CountOfLines
'read code from module
code = ActiveWorkbook.VBProject.VBComponents("Module1").CodeModule.Lines(StartLine:=1, Count:=linesCount)
inputStr = Array("name1", "name2", "name2") 'some hardwritten array with string to replace
namesLength = 20 'length of new variables names
For i = LBound(inputStr) To UBound(inputStr)
outputString = randomName(namesLength-1)
code = Replace(code, inputStr(i), outputString)
Next i
Debug.Print code 'view code
End Sub
then we simply substitute old code with new one, but how to identify strings with names of variables ?
Edition
Using **Option Explicit ** decrease safety of my simple method of obfuscation, because to reverse changes you only have to follow Dim statements and replace ugly names with something normal. Except that to make such substitution harder, I think it's good idea to break the line in the middle of variable name :
O0O000O0OO0O0000 _
0O00000O0OO0
the simple method is also replacing some strings with chains based on chr functions chr(104)&chr(101)&chr(108)&chr(108)&chr(111) :
Sub stringIntoChrChain()
strInput = "hello"
strOutput = ""
For i = 1 To Len(strInput)
strOutput = strOutput & "chr(" & Asc(Mid(strInput, i, 1)) & ")&"
Next i
Debug.Print Mid(strOutput, 1, Len(strOutput) - 1)
End Sub
comments like below could make impression on user and make him think that he does not poses right tool to deal with macro etc.:
'(k=Äó¬)w}ż^¦ů‡ÜOyúm=ěËnóÚŽb W™ÄQó’ (—*-ĹTIäb
'R“ąNPÔKZMţ†üÍQ‡
'y6ű˛Š˛ŁŽ¬=iýQ|˛^˙ ‡ńb ¬ĂÇr'ń‡e˘źäžŇ/âéç;1qýěĂj$&E!V?¶ßšÍ´cĆ$Âű׺Ůî’ﲦŔ?TáÄu[nG¦•¸î»éüĽ˙xVPĚ.|
'ÖĚ/łó®Üă9Ę]ż/ĹÍT¶Mµę¶mÍ
'q[—qëýY~Pc©=jÍ8˘‡,Ú+ń8ŐűŻEüńWü1ďëDZ†ć}ęńwŠbŢ,>ó’Űçµ™Š_…qÝăt±+‡ĽČg­řÍ!·eŠP âńđ:ŶOážű?őë®ÁšńýĎáËTbž}|Ö…ăË[®™
You can use a regular expression to find variable assignments by looking for the equals sign. You'll need to add a reference to the Microsoft VBScript Regular Expressions 5.5 and Microsoft Visual Basic for Applications Extensibility 5.3 libraries as I've used early binding.
Please be sure to back up your work and test this before using it. I could have gotten the regex wrong.
UPDATE:
I've refined the regular expressions so that it no longer catches datatypes of strongly typed constants (Const ImAConstant As String = "Oh Noes!" previously returned String). I've also added another regex to return those constants as well. The last version of the regex also mistakenly caught things like .Global = true. That was corrected. The code below should return all variable and constant names for a given code module. The regular expressions still aren't perfect, as you'll note that I was unable to stop false positives on double quotes. Also, my array handling could be done better.
Sub printVars()
Dim linesCount As Long
Dim code As String
Dim vbPrj As VBIDE.VBProject
Dim codeMod As VBIDE.CodeModule
Dim regex As VBScript_RegExp_55.RegExp
Dim m As VBScript_RegExp_55.match
Dim matches As VBScript_RegExp_55.MatchCollection
Dim i As Long
Dim j As Long
Dim isInDatatypes As Boolean
Dim isInVariables As Boolean
Dim datatypes() As String
Dim variables() As String
Set vbPrj = VBE.ActiveVBProject
Set codeMod = vbPrj.VBComponents("Module1").CodeModule
code = codeMod.Lines(1, codeMod.CountOfLines)
Set regex = New RegExp
With regex
.Global = True ' match all instances
.IgnoreCase = True
.MultiLine = True ' "code" var contains multiple lines
.Pattern = "(\sAs\s)([\w]*)(?=\s)" ' get list of datatypes we've used
' match any whole word after the word " As "
Set matches = .Execute(code)
End With
ReDim datatypes(matches.count - 1)
For i = 0 To matches.count - 1
datatypes(i) = matches(i).SubMatches(1) ' return second submatch so we don't get the word " As " in our array
Next i
With regex
.Pattern = "(\s)([^\.\s][\w]*)(?=\s\=)" ' list of variables
' begins with a space; next character is not a period (handles "with" assignments) or space; any alphanumeric character; repeat until... space
Set matches = .Execute(code)
End With
ReDim variables(matches.count - 1)
For i = 0 To matches.count - 1
isInDatatypes = False
isInVariables = False
' check to see if current match is a datatype
For j = LBound(datatypes) To UBound(datatypes)
If matches(i).SubMatches(1) = datatypes(j) Then
isInDatatypes = True
Exit For
End If
'Debug.Print matches(i).SubMatches(1)
Next j
' check to see if we already have this variable
For j = LBound(variables) To i
If matches(i).SubMatches(1) = variables(j) Then
isInVariables = True
Exit For
End If
Next j
' add to variables array
If Not isInDatatypes And Not isInVariables Then
variables(i) = matches(i).SubMatches(1)
End If
Next i
With regex
.Pattern = "(\sConst\s)(.*)(?=\sAs\s)" 'strongly typed constants
' match anything between the words " Const " and " As "
Set matches = .Execute(code)
End With
For i = 0 To matches.count - 1
'add one slot to end of array
j = UBound(variables) + 1
ReDim Preserve variables(j)
variables(j) = matches(i).SubMatches(1) ' again, return the second submatch
Next i
' print variables to immediate window
For i = LBound(variables) To UBound(variables)
If variables(i) <> "" And variables(i) <> Chr(34) Then ' for the life of me I just can't get the regex to not match doublequotes
Debug.Print variables(i)
End If
Next i
End Sub