Find and replace placeholders in Word with values from Excel
up vote
1
down vote
favorite
I'm new at this and need assistance with shortening/streamlining the 2 pieces of code that is repeated three times each (this is a shortened version as these pieces of code is repeated many more times).
Sub CreateWordDocTest()
Dim wApp As Word.Application
Dim wDoc As Word.Document
Dim myStoryRange As Word.Range
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
TodaysDate = Now()
End If
Set wApp = CreateObject("Word.Application")
wApp.Visible = True 'Creates an instance of Word an makes it visible
Set wDoc = wApp.Documents.Open(TemplatePath, False) 'Opens the chosen full or summary RAD template
With wDoc 'Use the With statement to not repeat wDoc many times
'Start at the beginning of the Word document
.Application.Selection.HomeKey Unit:=wdStory 'Moves the selection to the beginning of the current story
InsName = Sheets("Parameters").Range("D4").Value
InsNumber = Sheets("Parameters").Range("D5").Value
CurrentYear = Sheets("Parameters").Range("D6").Value
Industry = Sheets("Parameters").Range("D7").Value
AnalysisToolPath = Sheets("Metadata").Range("D2").Value
FileNameFragment2 = InsNumber & " - " & InsName & " " & CurrentYear & ".xlsm"
AnalysisToolName = AnalysisToolPath & FileNameFragment2
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write financial year
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write insurer number
.Application.Selection.Find.Text = "<<InsurerNumber>>"
.Application.Selection.Find.Execute
.Application.Selection = Sheets("Parameters").Range("D5").Value
.Application.Selection.EndOf
'Write analyst name
.Application.Selection.Find.Text = "<<AnalystName>>"
.Application.Selection.Find.Execute
.Application.Selection = UserFullName
.Application.Selection.EndOf
'Write RiBS Wording
.Application.Selection.Find.Text = "<<RiBSWording>>"
.Application.Selection.Find.Execute
.Application.Selection = SignificantclassesRiBSTxt
.Application.Selection.EndOf
End With
End Sub
performance beginner vba
add a comment |
up vote
1
down vote
favorite
I'm new at this and need assistance with shortening/streamlining the 2 pieces of code that is repeated three times each (this is a shortened version as these pieces of code is repeated many more times).
Sub CreateWordDocTest()
Dim wApp As Word.Application
Dim wDoc As Word.Document
Dim myStoryRange As Word.Range
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
TodaysDate = Now()
End If
Set wApp = CreateObject("Word.Application")
wApp.Visible = True 'Creates an instance of Word an makes it visible
Set wDoc = wApp.Documents.Open(TemplatePath, False) 'Opens the chosen full or summary RAD template
With wDoc 'Use the With statement to not repeat wDoc many times
'Start at the beginning of the Word document
.Application.Selection.HomeKey Unit:=wdStory 'Moves the selection to the beginning of the current story
InsName = Sheets("Parameters").Range("D4").Value
InsNumber = Sheets("Parameters").Range("D5").Value
CurrentYear = Sheets("Parameters").Range("D6").Value
Industry = Sheets("Parameters").Range("D7").Value
AnalysisToolPath = Sheets("Metadata").Range("D2").Value
FileNameFragment2 = InsNumber & " - " & InsName & " " & CurrentYear & ".xlsm"
AnalysisToolName = AnalysisToolPath & FileNameFragment2
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write financial year
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write insurer number
.Application.Selection.Find.Text = "<<InsurerNumber>>"
.Application.Selection.Find.Execute
.Application.Selection = Sheets("Parameters").Range("D5").Value
.Application.Selection.EndOf
'Write analyst name
.Application.Selection.Find.Text = "<<AnalystName>>"
.Application.Selection.Find.Execute
.Application.Selection = UserFullName
.Application.Selection.EndOf
'Write RiBS Wording
.Application.Selection.Find.Text = "<<RiBSWording>>"
.Application.Selection.Find.Execute
.Application.Selection = SignificantclassesRiBSTxt
.Application.Selection.EndOf
End With
End Sub
performance beginner vba
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I'm new at this and need assistance with shortening/streamlining the 2 pieces of code that is repeated three times each (this is a shortened version as these pieces of code is repeated many more times).
Sub CreateWordDocTest()
Dim wApp As Word.Application
Dim wDoc As Word.Document
Dim myStoryRange As Word.Range
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
TodaysDate = Now()
End If
Set wApp = CreateObject("Word.Application")
wApp.Visible = True 'Creates an instance of Word an makes it visible
Set wDoc = wApp.Documents.Open(TemplatePath, False) 'Opens the chosen full or summary RAD template
With wDoc 'Use the With statement to not repeat wDoc many times
'Start at the beginning of the Word document
.Application.Selection.HomeKey Unit:=wdStory 'Moves the selection to the beginning of the current story
InsName = Sheets("Parameters").Range("D4").Value
InsNumber = Sheets("Parameters").Range("D5").Value
CurrentYear = Sheets("Parameters").Range("D6").Value
Industry = Sheets("Parameters").Range("D7").Value
AnalysisToolPath = Sheets("Metadata").Range("D2").Value
FileNameFragment2 = InsNumber & " - " & InsName & " " & CurrentYear & ".xlsm"
AnalysisToolName = AnalysisToolPath & FileNameFragment2
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write financial year
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write insurer number
.Application.Selection.Find.Text = "<<InsurerNumber>>"
.Application.Selection.Find.Execute
.Application.Selection = Sheets("Parameters").Range("D5").Value
.Application.Selection.EndOf
'Write analyst name
.Application.Selection.Find.Text = "<<AnalystName>>"
.Application.Selection.Find.Execute
.Application.Selection = UserFullName
.Application.Selection.EndOf
'Write RiBS Wording
.Application.Selection.Find.Text = "<<RiBSWording>>"
.Application.Selection.Find.Execute
.Application.Selection = SignificantclassesRiBSTxt
.Application.Selection.EndOf
End With
End Sub
performance beginner vba
I'm new at this and need assistance with shortening/streamlining the 2 pieces of code that is repeated three times each (this is a shortened version as these pieces of code is repeated many more times).
Sub CreateWordDocTest()
Dim wApp As Word.Application
Dim wDoc As Word.Document
Dim myStoryRange As Word.Range
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
TodaysDate = Now()
End If
Set wApp = CreateObject("Word.Application")
wApp.Visible = True 'Creates an instance of Word an makes it visible
Set wDoc = wApp.Documents.Open(TemplatePath, False) 'Opens the chosen full or summary RAD template
With wDoc 'Use the With statement to not repeat wDoc many times
'Start at the beginning of the Word document
.Application.Selection.HomeKey Unit:=wdStory 'Moves the selection to the beginning of the current story
InsName = Sheets("Parameters").Range("D4").Value
InsNumber = Sheets("Parameters").Range("D5").Value
CurrentYear = Sheets("Parameters").Range("D6").Value
Industry = Sheets("Parameters").Range("D7").Value
AnalysisToolPath = Sheets("Metadata").Range("D2").Value
FileNameFragment2 = InsNumber & " - " & InsName & " " & CurrentYear & ".xlsm"
AnalysisToolName = AnalysisToolPath & FileNameFragment2
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write financial year
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
'Write insurer number
.Application.Selection.Find.Text = "<<InsurerNumber>>"
.Application.Selection.Find.Execute
.Application.Selection = Sheets("Parameters").Range("D5").Value
.Application.Selection.EndOf
'Write analyst name
.Application.Selection.Find.Text = "<<AnalystName>>"
.Application.Selection.Find.Execute
.Application.Selection = UserFullName
.Application.Selection.EndOf
'Write RiBS Wording
.Application.Selection.Find.Text = "<<RiBSWording>>"
.Application.Selection.Find.Execute
.Application.Selection = SignificantclassesRiBSTxt
.Application.Selection.EndOf
End With
End Sub
performance beginner vba
performance beginner vba
asked Jun 22 at 9:44
MRK
61
61
add a comment |
add a comment |
3 Answers
3
active
oldest
votes
up vote
0
down vote
To streamline this and remove the abundant code duplication, you could extract the two distinct parts you repeat into Sub
s taking parameters and then call those.
Private Sub ReplacePlaceholderInDocument(document As Word.Document, placeholder As String, replacement As String)
You could also get all placeholder values in another function and put them as key value pairs into a Scripting.Dictionary
and call your new sub in a loop over the the dictionary keys.
There are a few more things that could be improved to enhance the maintainability of the code:
The code would really benefit from a better separation of concern. According to the single responsibility principle, each unit of program should be concerned with only one responsibility. Your sub seems to combine the responsibilities of choosing the template, getting the template, getting the replacement values (including getting the reference sheet and its name) and replacing the values. Each of these could go into its own sub or function after which the main sub only coordinates the use of the new subs and functions.
Next, I see that you are referring to the ActiveDocument
, which is a bad idea most of the time. This will be different depending on which Word document currently has focus. It is usually better to use an explicit document, unless the currently active document is really what you need.
You also take values from the reference sheets from explicit cell addresses. That is rather fragile: any layout change can make the code invalid. A better alternative is to use named ranges in Excel. These can be referred to using their name.
You also seem to take values from variables defined outside the sub, like UserFullName
. Unless this is a method to be called as a makro, you might consider to make these parameters of the sub. That would make the sub more self contained.
Finally, it is a good practice to state accessibilities exicitly. The sub is currently defined without one, which means that it is implicitly Public
.
There are probably a few more improvements one could make, but I will leave it with this.
add a comment |
up vote
0
down vote
You aren't giving a Type to most of these variables -
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
In fact, only TemplatePath
has a type. The rest are all variants. You need to explicitly type all of them e.g.
Dim InsName as String, InsNumber as String, CurrentYear as String, ...
When you don't define your variable, VBA will declare it as a Variant type that can hold any type of data. While this may be more flexible, it adds processing time to your macro as VBA decides or tests for the type. Additionally, since a Variant can be any type of data, you may miss out on valuable troubleshooting information on Type Mismatch
When you don't define your variable, VBA will declare it as a Variant, which are objects:
Performance. A variable you declare with the Object type is flexible
enough to contain a reference to any object. However, when you invoke
a method or property on such a variable, you always incur late binding
(at run time). To force early binding (at compile time) and better
performance, declare the variable with a specific class name, or cast
it to the specific data type.
This also includes not defining RADType
, NotificationWhenDone
etc
You have a bunch of comments. Comments - "code tell you how, comments tell you why". The code should speak for itself, if it needs a comment, it might need to be made more clear. If not, the comment should describe why you're doing something rather than how you're doing it. Here are a few reasons to avoid comments all together.
Dim wApp As Word.Application
Set wApp = CreateObject("Word.Application")
Are you using early binding or late binding, because you're doing both. Either
Dim wApp As Object
Set wApp = CreateObject("Word.Application")
or
Dim wApp As Word.Application
Set wApp = New Word.Application
As you know, When you repeat code you can most likely benefit from refactoring it. Throw it into a function or method and use the function each time you need the code - it will be a lot cleaner.
As addressed by the other answer something like
Public Sub CreateDocument(ByVal templateSheet As Worksheet, ByVal parameterSheet As Worksheet)
And you pass the target worksheets to it. Or maybe you pass the target word document, or whatever changes each time you need to call the sub.
Speaking of the worksheets - Worksheets have a CodeName
property - View Properties window (F4) and the (Name)
field (the one at the top) can be used as the worksheet name. This way you can avoid Sheets("mySheet")
and instead just use mySheet
.
I don't really have the time right now, but it seems you do the exact same thing over and over? I don't see anything changing - all the ranges are hard-coded.
add a comment |
up vote
0
down vote
Here's a way to apply some of the tricks the other answers mentioned:
You don't need the TodaysDate
line twice, do you?
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
End If
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Also, why do multiple loops?
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write financial year
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
add a comment |
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
To streamline this and remove the abundant code duplication, you could extract the two distinct parts you repeat into Sub
s taking parameters and then call those.
Private Sub ReplacePlaceholderInDocument(document As Word.Document, placeholder As String, replacement As String)
You could also get all placeholder values in another function and put them as key value pairs into a Scripting.Dictionary
and call your new sub in a loop over the the dictionary keys.
There are a few more things that could be improved to enhance the maintainability of the code:
The code would really benefit from a better separation of concern. According to the single responsibility principle, each unit of program should be concerned with only one responsibility. Your sub seems to combine the responsibilities of choosing the template, getting the template, getting the replacement values (including getting the reference sheet and its name) and replacing the values. Each of these could go into its own sub or function after which the main sub only coordinates the use of the new subs and functions.
Next, I see that you are referring to the ActiveDocument
, which is a bad idea most of the time. This will be different depending on which Word document currently has focus. It is usually better to use an explicit document, unless the currently active document is really what you need.
You also take values from the reference sheets from explicit cell addresses. That is rather fragile: any layout change can make the code invalid. A better alternative is to use named ranges in Excel. These can be referred to using their name.
You also seem to take values from variables defined outside the sub, like UserFullName
. Unless this is a method to be called as a makro, you might consider to make these parameters of the sub. That would make the sub more self contained.
Finally, it is a good practice to state accessibilities exicitly. The sub is currently defined without one, which means that it is implicitly Public
.
There are probably a few more improvements one could make, but I will leave it with this.
add a comment |
up vote
0
down vote
To streamline this and remove the abundant code duplication, you could extract the two distinct parts you repeat into Sub
s taking parameters and then call those.
Private Sub ReplacePlaceholderInDocument(document As Word.Document, placeholder As String, replacement As String)
You could also get all placeholder values in another function and put them as key value pairs into a Scripting.Dictionary
and call your new sub in a loop over the the dictionary keys.
There are a few more things that could be improved to enhance the maintainability of the code:
The code would really benefit from a better separation of concern. According to the single responsibility principle, each unit of program should be concerned with only one responsibility. Your sub seems to combine the responsibilities of choosing the template, getting the template, getting the replacement values (including getting the reference sheet and its name) and replacing the values. Each of these could go into its own sub or function after which the main sub only coordinates the use of the new subs and functions.
Next, I see that you are referring to the ActiveDocument
, which is a bad idea most of the time. This will be different depending on which Word document currently has focus. It is usually better to use an explicit document, unless the currently active document is really what you need.
You also take values from the reference sheets from explicit cell addresses. That is rather fragile: any layout change can make the code invalid. A better alternative is to use named ranges in Excel. These can be referred to using their name.
You also seem to take values from variables defined outside the sub, like UserFullName
. Unless this is a method to be called as a makro, you might consider to make these parameters of the sub. That would make the sub more self contained.
Finally, it is a good practice to state accessibilities exicitly. The sub is currently defined without one, which means that it is implicitly Public
.
There are probably a few more improvements one could make, but I will leave it with this.
add a comment |
up vote
0
down vote
up vote
0
down vote
To streamline this and remove the abundant code duplication, you could extract the two distinct parts you repeat into Sub
s taking parameters and then call those.
Private Sub ReplacePlaceholderInDocument(document As Word.Document, placeholder As String, replacement As String)
You could also get all placeholder values in another function and put them as key value pairs into a Scripting.Dictionary
and call your new sub in a loop over the the dictionary keys.
There are a few more things that could be improved to enhance the maintainability of the code:
The code would really benefit from a better separation of concern. According to the single responsibility principle, each unit of program should be concerned with only one responsibility. Your sub seems to combine the responsibilities of choosing the template, getting the template, getting the replacement values (including getting the reference sheet and its name) and replacing the values. Each of these could go into its own sub or function after which the main sub only coordinates the use of the new subs and functions.
Next, I see that you are referring to the ActiveDocument
, which is a bad idea most of the time. This will be different depending on which Word document currently has focus. It is usually better to use an explicit document, unless the currently active document is really what you need.
You also take values from the reference sheets from explicit cell addresses. That is rather fragile: any layout change can make the code invalid. A better alternative is to use named ranges in Excel. These can be referred to using their name.
You also seem to take values from variables defined outside the sub, like UserFullName
. Unless this is a method to be called as a makro, you might consider to make these parameters of the sub. That would make the sub more self contained.
Finally, it is a good practice to state accessibilities exicitly. The sub is currently defined without one, which means that it is implicitly Public
.
There are probably a few more improvements one could make, but I will leave it with this.
To streamline this and remove the abundant code duplication, you could extract the two distinct parts you repeat into Sub
s taking parameters and then call those.
Private Sub ReplacePlaceholderInDocument(document As Word.Document, placeholder As String, replacement As String)
You could also get all placeholder values in another function and put them as key value pairs into a Scripting.Dictionary
and call your new sub in a loop over the the dictionary keys.
There are a few more things that could be improved to enhance the maintainability of the code:
The code would really benefit from a better separation of concern. According to the single responsibility principle, each unit of program should be concerned with only one responsibility. Your sub seems to combine the responsibilities of choosing the template, getting the template, getting the replacement values (including getting the reference sheet and its name) and replacing the values. Each of these could go into its own sub or function after which the main sub only coordinates the use of the new subs and functions.
Next, I see that you are referring to the ActiveDocument
, which is a bad idea most of the time. This will be different depending on which Word document currently has focus. It is usually better to use an explicit document, unless the currently active document is really what you need.
You also take values from the reference sheets from explicit cell addresses. That is rather fragile: any layout change can make the code invalid. A better alternative is to use named ranges in Excel. These can be referred to using their name.
You also seem to take values from variables defined outside the sub, like UserFullName
. Unless this is a method to be called as a makro, you might consider to make these parameters of the sub. That would make the sub more self contained.
Finally, it is a good practice to state accessibilities exicitly. The sub is currently defined without one, which means that it is implicitly Public
.
There are probably a few more improvements one could make, but I will leave it with this.
edited Jun 22 at 11:39
answered Jun 22 at 10:30
M.Doerner
97638
97638
add a comment |
add a comment |
up vote
0
down vote
You aren't giving a Type to most of these variables -
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
In fact, only TemplatePath
has a type. The rest are all variants. You need to explicitly type all of them e.g.
Dim InsName as String, InsNumber as String, CurrentYear as String, ...
When you don't define your variable, VBA will declare it as a Variant type that can hold any type of data. While this may be more flexible, it adds processing time to your macro as VBA decides or tests for the type. Additionally, since a Variant can be any type of data, you may miss out on valuable troubleshooting information on Type Mismatch
When you don't define your variable, VBA will declare it as a Variant, which are objects:
Performance. A variable you declare with the Object type is flexible
enough to contain a reference to any object. However, when you invoke
a method or property on such a variable, you always incur late binding
(at run time). To force early binding (at compile time) and better
performance, declare the variable with a specific class name, or cast
it to the specific data type.
This also includes not defining RADType
, NotificationWhenDone
etc
You have a bunch of comments. Comments - "code tell you how, comments tell you why". The code should speak for itself, if it needs a comment, it might need to be made more clear. If not, the comment should describe why you're doing something rather than how you're doing it. Here are a few reasons to avoid comments all together.
Dim wApp As Word.Application
Set wApp = CreateObject("Word.Application")
Are you using early binding or late binding, because you're doing both. Either
Dim wApp As Object
Set wApp = CreateObject("Word.Application")
or
Dim wApp As Word.Application
Set wApp = New Word.Application
As you know, When you repeat code you can most likely benefit from refactoring it. Throw it into a function or method and use the function each time you need the code - it will be a lot cleaner.
As addressed by the other answer something like
Public Sub CreateDocument(ByVal templateSheet As Worksheet, ByVal parameterSheet As Worksheet)
And you pass the target worksheets to it. Or maybe you pass the target word document, or whatever changes each time you need to call the sub.
Speaking of the worksheets - Worksheets have a CodeName
property - View Properties window (F4) and the (Name)
field (the one at the top) can be used as the worksheet name. This way you can avoid Sheets("mySheet")
and instead just use mySheet
.
I don't really have the time right now, but it seems you do the exact same thing over and over? I don't see anything changing - all the ranges are hard-coded.
add a comment |
up vote
0
down vote
You aren't giving a Type to most of these variables -
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
In fact, only TemplatePath
has a type. The rest are all variants. You need to explicitly type all of them e.g.
Dim InsName as String, InsNumber as String, CurrentYear as String, ...
When you don't define your variable, VBA will declare it as a Variant type that can hold any type of data. While this may be more flexible, it adds processing time to your macro as VBA decides or tests for the type. Additionally, since a Variant can be any type of data, you may miss out on valuable troubleshooting information on Type Mismatch
When you don't define your variable, VBA will declare it as a Variant, which are objects:
Performance. A variable you declare with the Object type is flexible
enough to contain a reference to any object. However, when you invoke
a method or property on such a variable, you always incur late binding
(at run time). To force early binding (at compile time) and better
performance, declare the variable with a specific class name, or cast
it to the specific data type.
This also includes not defining RADType
, NotificationWhenDone
etc
You have a bunch of comments. Comments - "code tell you how, comments tell you why". The code should speak for itself, if it needs a comment, it might need to be made more clear. If not, the comment should describe why you're doing something rather than how you're doing it. Here are a few reasons to avoid comments all together.
Dim wApp As Word.Application
Set wApp = CreateObject("Word.Application")
Are you using early binding or late binding, because you're doing both. Either
Dim wApp As Object
Set wApp = CreateObject("Word.Application")
or
Dim wApp As Word.Application
Set wApp = New Word.Application
As you know, When you repeat code you can most likely benefit from refactoring it. Throw it into a function or method and use the function each time you need the code - it will be a lot cleaner.
As addressed by the other answer something like
Public Sub CreateDocument(ByVal templateSheet As Worksheet, ByVal parameterSheet As Worksheet)
And you pass the target worksheets to it. Or maybe you pass the target word document, or whatever changes each time you need to call the sub.
Speaking of the worksheets - Worksheets have a CodeName
property - View Properties window (F4) and the (Name)
field (the one at the top) can be used as the worksheet name. This way you can avoid Sheets("mySheet")
and instead just use mySheet
.
I don't really have the time right now, but it seems you do the exact same thing over and over? I don't see anything changing - all the ranges are hard-coded.
add a comment |
up vote
0
down vote
up vote
0
down vote
You aren't giving a Type to most of these variables -
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
In fact, only TemplatePath
has a type. The rest are all variants. You need to explicitly type all of them e.g.
Dim InsName as String, InsNumber as String, CurrentYear as String, ...
When you don't define your variable, VBA will declare it as a Variant type that can hold any type of data. While this may be more flexible, it adds processing time to your macro as VBA decides or tests for the type. Additionally, since a Variant can be any type of data, you may miss out on valuable troubleshooting information on Type Mismatch
When you don't define your variable, VBA will declare it as a Variant, which are objects:
Performance. A variable you declare with the Object type is flexible
enough to contain a reference to any object. However, when you invoke
a method or property on such a variable, you always incur late binding
(at run time). To force early binding (at compile time) and better
performance, declare the variable with a specific class name, or cast
it to the specific data type.
This also includes not defining RADType
, NotificationWhenDone
etc
You have a bunch of comments. Comments - "code tell you how, comments tell you why". The code should speak for itself, if it needs a comment, it might need to be made more clear. If not, the comment should describe why you're doing something rather than how you're doing it. Here are a few reasons to avoid comments all together.
Dim wApp As Word.Application
Set wApp = CreateObject("Word.Application")
Are you using early binding or late binding, because you're doing both. Either
Dim wApp As Object
Set wApp = CreateObject("Word.Application")
or
Dim wApp As Word.Application
Set wApp = New Word.Application
As you know, When you repeat code you can most likely benefit from refactoring it. Throw it into a function or method and use the function each time you need the code - it will be a lot cleaner.
As addressed by the other answer something like
Public Sub CreateDocument(ByVal templateSheet As Worksheet, ByVal parameterSheet As Worksheet)
And you pass the target worksheets to it. Or maybe you pass the target word document, or whatever changes each time you need to call the sub.
Speaking of the worksheets - Worksheets have a CodeName
property - View Properties window (F4) and the (Name)
field (the one at the top) can be used as the worksheet name. This way you can avoid Sheets("mySheet")
and instead just use mySheet
.
I don't really have the time right now, but it seems you do the exact same thing over and over? I don't see anything changing - all the ranges are hard-coded.
You aren't giving a Type to most of these variables -
Dim InsName, InsNumber, CurrentYear, Industry, AnalysisToolPath, AnalysisToolName, FileNameFragment2, TodaysDate, TemplatePath As String
In fact, only TemplatePath
has a type. The rest are all variants. You need to explicitly type all of them e.g.
Dim InsName as String, InsNumber as String, CurrentYear as String, ...
When you don't define your variable, VBA will declare it as a Variant type that can hold any type of data. While this may be more flexible, it adds processing time to your macro as VBA decides or tests for the type. Additionally, since a Variant can be any type of data, you may miss out on valuable troubleshooting information on Type Mismatch
When you don't define your variable, VBA will declare it as a Variant, which are objects:
Performance. A variable you declare with the Object type is flexible
enough to contain a reference to any object. However, when you invoke
a method or property on such a variable, you always incur late binding
(at run time). To force early binding (at compile time) and better
performance, declare the variable with a specific class name, or cast
it to the specific data type.
This also includes not defining RADType
, NotificationWhenDone
etc
You have a bunch of comments. Comments - "code tell you how, comments tell you why". The code should speak for itself, if it needs a comment, it might need to be made more clear. If not, the comment should describe why you're doing something rather than how you're doing it. Here are a few reasons to avoid comments all together.
Dim wApp As Word.Application
Set wApp = CreateObject("Word.Application")
Are you using early binding or late binding, because you're doing both. Either
Dim wApp As Object
Set wApp = CreateObject("Word.Application")
or
Dim wApp As Word.Application
Set wApp = New Word.Application
As you know, When you repeat code you can most likely benefit from refactoring it. Throw it into a function or method and use the function each time you need the code - it will be a lot cleaner.
As addressed by the other answer something like
Public Sub CreateDocument(ByVal templateSheet As Worksheet, ByVal parameterSheet As Worksheet)
And you pass the target worksheets to it. Or maybe you pass the target word document, or whatever changes each time you need to call the sub.
Speaking of the worksheets - Worksheets have a CodeName
property - View Properties window (F4) and the (Name)
field (the one at the top) can be used as the worksheet name. This way you can avoid Sheets("mySheet")
and instead just use mySheet
.
I don't really have the time right now, but it seems you do the exact same thing over and over? I don't see anything changing - all the ranges are hard-coded.
edited Jun 23 at 4:54
answered Jun 23 at 0:01
Raystafarian
5,7841047
5,7841047
add a comment |
add a comment |
up vote
0
down vote
Here's a way to apply some of the tricks the other answers mentioned:
You don't need the TodaysDate
line twice, do you?
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
End If
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Also, why do multiple loops?
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write financial year
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
add a comment |
up vote
0
down vote
Here's a way to apply some of the tricks the other answers mentioned:
You don't need the TodaysDate
line twice, do you?
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
End If
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Also, why do multiple loops?
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write financial year
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
add a comment |
up vote
0
down vote
up vote
0
down vote
Here's a way to apply some of the tricks the other answers mentioned:
You don't need the TodaysDate
line twice, do you?
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
End If
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Also, why do multiple loops?
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write financial year
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
Here's a way to apply some of the tricks the other answers mentioned:
You don't need the TodaysDate
line twice, do you?
If RADType = "Full" Then
TemplatePath = Sheets("Metadata").Range("D8").Value
NotificationWhenDone = "Full RAD done"
Else
TemplatePath = Sheets("Metadata").Range("D6").Value
NotificationWhenDone = "Summary RAD done"
End If
TodaysDate = Now() 'Variable called TodaysDate would now contain the current system date and time
Also, why do multiple loops?
'Write insurer name
For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerName>>" 'Find the exact text in the Word document
.Replacement.Text = InsName 'Replace this text with the insurername as defined
.Wrap = wdFindContinue 'The find operation continues when the beginning or end of the search range is reached
.Execute Replace:=wdReplaceAll 'Finds all occurences and executes the replacement
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf 'Selects until the end of the document
'Write insurer class
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<InsurerClass>>"
.Replacement.Text = Industry
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write financial year
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<CurrentYear>>"
.Replacement.Text = CurrentYear
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
'DONT NEED THIS: Next myStoryRange
.Application.Selection.EndOf
'Write significant classes
'DONT NEED THIS: For Each myStoryRange In ActiveDocument.StoryRanges
With myStoryRange.Find
.Text = "<<SignificantClasses>>"
.Replacement.Text = SignificantclassesTxt
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next myStoryRange
.Application.Selection.EndOf
answered Jun 27 at 0:18
Shawn V. Wilson
1514
1514
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197052%2ffind-and-replace-placeholders-in-word-with-values-from-excel%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown