I have a document where I want to start start searching in section 3 to the end of the document and bold text based on my user-defined style since other styles used may contain the same word I don't want to bold - including the possibility of the Heading/section.
I do have VBA code that works by finding my user-defined style throughout the document and bolding it for one style match only. However, the .Style in the .find doesn't allow one to specify a wildcard style i.e. my user-defined style set of "Requirement1 thru Requirement9" which are based off the Heading1-9 style. I assume I'll have to create a for loop outside of the "with" that finds my user-defined style that is passed one at a time to bold the text to match one style at a time i.e Requirement1 thru Requirement9. Is there a better way? see code below.
The next issue is to focus the search of this text to bold to start in a particular section to the end of the document. For me, its section 3 to the end of the document or till it encounters another style defined as Appendix1 and stop. I do have user-defined styles Appendix1 thru Appendix9 that are also based off of Heading1-9. The reason to search on a style as a starting point is because these other sections before section3, or descriptive text in Section 3 and beyond may contain the word I'm trying to bold - which I don't want to do except when my particular user-defined style Requirment1-9 was applied that is used in section 3 and beyond. In trying to implement this part before doing the word searching and bolding part, I've been searching based on the "Heading1" style and when I found the third one, I know I'm in section 3. I can't use the VBA "sections" search since it includes all the subsections of a document which can vary from document to document and specifying sections.item(3) doesn't mean I'll get the third section of my document that corresponds to the third Heading1 style.
Since I'm not that adept at the various VBA sections,paragraphs,sentences, characters and everything else when applying the "range" either before, after, or both on any of these (never seems to work the way one expects. Even the VBA hint tools says one can do it, but runtime says error) I've started with the specifying the start/end of the whole activedocument.range. Then I perform a ".find" of the "Heading1" style in a for loop three times which does find them. I then try to assign the last Heading1 found range value as the start value to use in the next embedded "find" that will search and apply the bold to the "Requirement1" style. (May need to create a loop to search Requirement1-9 styles as stated above). To help me know where I'm at, I'm trying to dump via debug.print the range position, but it dumps the all whole document text to the debug window and not the integer value I thought I'd get. Whats the issue with doing this? I've even tried to use a selection.range and dump the retrieved text to the debug.print window without success. It makes it worse since the processing timing seems to run forever. The wdFindStop doesn't force the find to stop on the third find either.
So how to:
a). find and dump the range position to the debug window along with the text at this position. Both the number and text will tell me where I'm at.
c). How to assign the range position based on find of the third "Heading1" position in the document so that that the second "with" can search and apply the bolding to the text I want to bold.
d). How to do a wildcard search on the .Style user-defined Requirement1-9 style instead of having to create a for loop to search one at a time?
The following is the VBA code I have written.
Attribute VB_Name = "BoldMustShall2_M"
Option Explicit
Public Sub BoldMustShall2()
' If .Parent.Bold = True is used with wdReplaceall, the whole
' document is bolded even when the sentence doesn't have any of the words
' being searched for.
Dim myRange, rngSel As Range
Dim oDoc As Word.Document
Dim pos1, pos2 As Long
Dim numchars As Long
Set oDoc = ActiveDocument
Set rngSel = Selection.Range
Application.ScreenUpdating = False
'Set the starting Heading to search as Section 3
pos1 = oDoc.Range.Start
pos2 = oDoc.Range.End
Set myRange = oDoc.Range(Start:=pos1, End:=pos2)
'Another way is to find the "Heading1" style and set the range start to the third one found which is section 3.
'Counting Word sections can vary alot so its not the best way.
'The same would apply if Appendix1 style is used and assuming that requiremens start in the third one.
Debug.Print "My Start Range " + myRange
With myRange
.TextRetrievalMode.IncludeFieldCodes = False ' don't want to search fieldcodes for must/shall
.TextRetrievalMode.IncludeHiddenText = False ' don't want to search hiddentext for must/shall
' Get the range position for Heading1 style for section 3 of PRD.
Dim i As Integer
Dim ReqHDR As Range
Dim bFind As Boolean
With .Find
.ClearFormatting
.Forward = True
'.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.Style = "Heading1"
'Loop three times for Style
For i = 1 To 3 Step 1
'bFind = .Execute
.Execute Wrap:=wdFindStop
If .Found = True Then
rngSel = oDoc.Range 'This should be the current position in the Document for the requirements.
' When found it outputs the message once
Debug.Print "Found a Heading1 style "
.Replacement.Font.Size = 20
End If
'.Wrap = wdFindStop
Next i
End With
Debug.Print "End of Heading1 Search "
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = True
.MatchWildcards = False
.Replacement.Font.Bold = True
.Replacement.Font.Name = "Times New Roman"
.Replacement.Font.Size = 12
.Replacement.Font.Italic = True
'.Style = "Requirement1" ' A loop is needed to cycle thru all the Requirement1-9 styles. No wildcard.
'.Style = wdStyleNormal
.Replacement.Text = "^&" ' This is the contents of the find what box in word.
' In this case the .find.text "must" statement.
' Alternate is to specify "must" or "shall" but this
' would require two replace.text statements instead of
' just this one. The ^& is a special command that eliminates
' the need to set the replacement.Text info.
'.Text = "must"
.Execute FindText:="must", Replace:=wdReplaceAll
If .Found = True Then
' When found it outputs the message once
Debug.Print "Found one or more must "
End If
'.Text = "shall"
.Execute FindText:="shall", Replace:=wdReplaceAll
If .Found = True Then
' When found it outputs the message once
Debug.Print "Found one or more shall "
End If
End With
End With
Application.ScreenUpdating = True
Debug.Print "Completed searching for must/shall in document."
End Sub
Assuming that, when you refer to 'Sections', you're referring to parts of your document delineated by Section breaks, perhaps something along the lines of:
Sub Demo()
Application.ScreenUpdating = False
Dim Rng As Range, i As Long
With ActiveDocument
Set Rng = .Range(.Sections(3).Range.Start, .Range.End)
With .Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = True
.Format = True
.Wrap = wdFindContinue
.Style = "Appendix1"
.Execute
End With
Rng.End = .Start
End With
With Rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Format = True
.Replacement.Style = "Strong"
.Wrap = wdFindStop
.Replacement.Text = "^&"
.Text = "must"
For i = 1 To 9
.Style = "Requirement" & i
.Execute Replace:=wdReplaceAll
Next
.Text = "shall"
For i = 1 To 9
.Style = "Requirement" & i
.Execute Replace:=wdReplaceAll
Next
End With
End With
Application.ScreenUpdating = True
End Sub
Note that, instead of using hard formatting for the replacement, I've simply applied Word's built-in 'Strong' Style. Your own code should use a Style, too, whether it's Word's 'Strong' Style or another character Style of your own definition.
Related
In the Microsoft Word VBA editor, I'm trying to write a macro that finds and replaces a certain character with another character only within certain strings of text, not the whole document. For instance, I might want to replace decimal commas with decimal points (not all commas with periods), or a space with a hyphen in certain phrases. A big constraint is that the changes must be tracked via Track Changes, so finding and replacing the whole string of text isn't an option: Some customers think it looks weird and/or sloppy if I replace their numbers with themselves, and they have also worried that some of their data might have gotten changed. (It might also look like I let my computer make edits for me automatically, which I want to avoid.)
I can already do this clunkily by using Selection.Find to find certain strings (or patterns), doing Selection.Collapse, moving the cursor left or right, deleting a comma, and typing a period. I'm hoping there is a faster way to do this, possibly using ranges, but I have had little success finding or replacing anything using Word's Range object. Since I want to run several macros that total well over a hundred possible find-and-replace actions for each document, I'd like to streamline them all as much as possible.
What I've tried so far
For ease of illustration, I'll take the specific examples in which I want to find commas within statistical p-values written as "0,05", "0,01", or "0,001" and change them to periods, but not make this change anywhere else. I'm aware that in real life, searching for those strings could catch numbers in the thousands, millions, etc., but these are just simplified examples for learning/illustration purposes.
(1) The following works fine, it just strikes me as slow when done for many different Find strings in every document.
With Selection.Find
.ClearFormatting
.Text = "0,05"
.MatchWholeWord = True
.MatchWildcards = False
.Forward = True
.Wrap = wdFindContinue
End With
Do While Selection.Find.Execute
Selection.Collapse
Selection.MoveRight unit:=wdCharacter, count:=1
Selection.Delete unit:=wdCharacter, count:=1
Selection.TypeText (".")
Loop
(2) The most promising other way was adapted from VBA Word: I would like to find a phrase, select the words before it, and italicise the text:
Sub RangeTest()
Dim Rng As Range
Dim Fnd As Boolean
Set Rng = Selection.Range
With Rng.Find
.ClearFormatting
.Execute findText:="0,05", Forward:=True, _
format:=False, Wrap:=wdFindContinue
Fnd = .found
End With
If Fnd = True Then
With Rng
.Find.Wrap = wdFindContinue
.Find.Text = ","
.Find.Replacement.Text = "."
.Find.Execute Replace:=wdReplaceOne
End With
End If
End Sub
but it replaces the comma with a period in only the first "0,05" in the document, not all of them.
When I change wdReplaceOne to wdReplaceAll, then every comma in the document gets replaced with a period.
When I try every possible combination of wdFindContinue/wdFindStop (both times) and wdReplaceAll/wdReplaceOne, either one comma gets changed to a period or every one in the document does.
When I change the "If…Then" statement do a "Do While…Loop" statement, Word hangs:
Dim Rng As Range
Dim Fnd As Boolean
Set Rng = Selection.Range
With Rng.Find
.ClearFormatting
.Execute findText:="0,05", Forward:=True, _
format:=False, Wrap:=wdFindStop
Fnd = .found
End With
Do While Fnd = True
With Rng
.Find.Text = ","
.Find.Replacement.Text = "."
.Find.Execute Replace:=wdReplaceAll
End With
Loop
Is there any way to loop the "If…Then" statement or get the "Do While…Loop" method to work without hanging?
(3) I tried to adapt the code from this page https://www.techrepublic.com/article/macro-trick-how-to-highlight-multiple-search-strings-in-a-word-document/
Sub WordCollectionTest()
Dim Word As Word.Range
Dim WordCollection(2) As String
Dim Words As Variant
WordCollection(0) = "0,05"
WordCollection(1) = "0,01"
WordCollection(2) = "0,001"
'This macro behaves weirdly if insertions and deletions aren't hidden (more than one period gets inserted).
With ActiveWindow.view
.ShowInsertionsAndDeletions = False
For Each Word In ActiveDocument.Words
For Each Words In WordCollection
With Selection.Find
.ClearFormatting
.Text = Words
.Forward = True
.Wrap = wdFindContinue
.MatchWildcards = False
.MatchWholeWord = True
End With
Do While Selection.Find.Execute
Selection.Find.Text = ","
Selection.Find.Replacement.Text = "."
Selection.Find.Execute Replace:=wdReplaceAll
Loop
Next
Next
End With
End Sub
but this replaces every comma in the document with a period. (It's also kind of slow.)
(4) I tried putting the Find terms in an array rather than a word collection:
Sub ArrayTest()
Dim vDecimalCommas As Variant
Dim i As Long
vDecimalCommas = Array("0,05", "0,01", "0,001")
'This macro behaves weirdly if insertions and deletions aren't hidden:
With ActiveWindow.view
.ShowInsertionsAndDeletions = False
For i = LBound(vDecimalCommas) To UBound(vDecimalCommas)
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = vDecimalCommas(i)
.Forward = True
.Wrap = wdFindContinue
.matchcase = False
.MatchWholeWord = False
.MatchWildcards = True
End With
Do While Selection.Find.Execute
Selection.Find.Text = ","
Selection.Find.Replacement.Text = "."
Selection.Find.Execute Replace:=wdReplaceAll
Loop
Next
End With
End Sub
but this only replaces the comma with a period in the second of those numbers that it comes across, oddly enough.
I tried a variant of the Array method:
Sub ArrayTest()
For i = LBound(vDecimalCommas) To UBound(vDecimalCommas)
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ","
.Replacement.Text = "."
.Forward = True
.Wrap = wdFindContinue
.matchcase = False
.MatchWholeWord = False
.MatchWildcards = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Next
End Sub
But this replaces every comma in the document with a period (which isn't surprising, since I don't think the For i statement has any bearing on the Find and Replace commands in this version).
I've tried lots of other variants that I haven't mentioned here. I've tried combining the Array method with the Range/Boolean method. I've tried every variant I know of of the Find, Selection, For i, For Each, If Then, and Do While commands. And every time, only one comma gets replaced with a period or every one does.
Is there some way to define a range that consists of a certain string of text so that word will find and replace commas with periods within that range, every time, and nowhere else? Is there a way to define many such strings in one array or some other kind of list? Or any other way to find and replace commas with periods only within certain strings? I'm far from an expert, so a tiny variation of one of the above methods might work.
Try this:
Sub Tester()
Dim doc As Document
Set doc = ActiveDocument
'must turn off markup first or you'll end up in a loop...
If doc.TrackRevisions Then
doc.Windows(1).View.RevisionsFilter.Markup = wdRevisionsMarkupNone
End If
Debug.Print ReplaceAll(ActiveDocument, "0,001", ",", ".")
If doc.TrackRevisions Then
doc.Windows(1).View.RevisionsFilter.Markup = wdRevisionsMarkupAll
End If
End Sub
Function ReplaceAll(doc As Object, qText As String, _
qOld As String, qNew As String) As Long
Dim rng As Object, pos As Long, n As Long
Set rng = doc.Range
ResetFindParameters rng 'reset Find to defaults
With rng.Find
.Text = qText
Do While .Execute
pos = InStr(rng.Text, qOld)
Do While pos > 0
n = n + 1
rng.Characters(pos).Text = qNew
pos = InStr(rng.Text, qOld)
Loop
Loop
End With
ReplaceAll = n
End Function
'reset any Find settings
Sub ResetFindParameters(oRng As Object)
With oRng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = 1 'wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = True '<< adjust following to suit
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
End Sub
I have a list of several spelling errors. I´m looking a way to ignore all spelling errors for certain words from that list.
The code below it seems to work but only for text formatted with Style "Test"
ActiveDocument.Styles("Test").NoProofing = True
Is there a way via VBA to make something like this?
ActiveDocument.String("MyWord").SpellingErrors.IgnoreAll
It's possible to use Word's Find/Replace functionality to locate the word and apply the NoProofing formatting. The functionality allows searching for strings and through Replace applying formatting without affecting (deleting) the search term.
The following code snippet illustrates this. There are two variations:
Using Replace All to perform the action in one step
Searching and replacing one instance at a time
The first is faster, but in my tests the "squiggly red underline" denoting a spelling error is not removed from the document. The full Spell check ignores the terms, however.
The second (commented out), in my tests, does remove the error formatting, but will be slower in execution.
Sub FindToNoSpellCheck()
Dim rng As Word.Range
Dim findText As String
Dim bFound As Boolean
Set rng = ActiveDocument.content
findText = "InsertY"
Do
With rng.Find
.ClearFormatting
.Text = findText
.Format = True
.MatchCase = True
.Replacement.NoProofing = True
.Replacement.Text = ""
.Wrap = wdFindStop
.Execute Replace:=wdReplaceAll
' bFound = .Execute()
' If bFound Then
' rng.NoProofing = True
' rng.Collapse wdCollapseEnd
' End If
End With
Loop While bFound
End Sub
I'm working on a macro that parses a document and modifies style when needed. So far, one of my sub uses Find & Execute with a loop to go through all paragraph with a defined Style. It worked well enough and made it easy to know how many times an modification is made.
However, it appears that .Execute Replace:=wdReplaceAll is far more efficient, but doesn't return this latter information in VBA, even though it is displayed when used directly in Word (with Ctrl + H).
How can I bypass this issue to count the number of replacements?
Thanks a lot in advance.
You could do this with a combination of Word's built in find and replace and a search and replace using the regex library (Microsoft VBScript Regular Expressions 5.5).
The VBScript regular expressions cannot search for styles, only text but can provide the number of matches found.
Thus you first do a search and replace for the paragraph marker of the style in which you are interested (^p + style). You replace the paragraph marker with an amended paragraph marker such as '###^p' being careful to replace with the same style.
You then use the regex search and replace to find the paragraph marker modifier and replace it with nothing, thus restoring the original text. The regex has a method .Matches.Count which will give you the number of replacements of ### that were made.
You may find the following stack overflow link of help
How to Use/Enable (RegExp object) Regular Expression using VBA (MACRO) in word
Try something based on:
Sub Demo()
Application.ScreenUpdating = False
Dim i As Long
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = InputBox("What is the Text to Find")
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
End With
Do While .Find.Found
i = i + 1
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
Application.ScreenUpdating = True
MsgBox i & " instances found."
End Sub
The above code doesn't actually replace anything - it simply counts the instances of what's found.
I am working on a macro for Word that accesses a separately saved doc file with a long word list of several pages. The word list doc is formatted like,
FMS
CPR
Abc
...to separate each word by the line break.
The macro needs to highlight the first use of each word from the list.
Right now, the macro highlights every use of the word, and in addition, highlights that word when it's part of another word. For example, it highlights EZE in the word freeze, but it should only highlight when eze stands alone.
Can someone help with how to,
1. highlight first-use only, and
2. how to make sure it's only catching the actual word, not all the other words that contain that word? I can't seem to make that happen with VBA.
My current code:
Sub TD()
'
Dim sCheckDoc As String
Dim docRef As Document
Dim docCurrent As Document
Dim wrdRef As String
Dim wrdPara As Paragraph
sCheckDoc = "c:\check.docx"
Set docCurrent = Selection.Document
Set docRef = Documents.Open(sCheckDoc)
docCurrent.Activate
With Selection.Find
.ClearFormatting
.Replacement.ClearFormatting
.Replacement.Highlight = True
.Replacement.Text = "^&"
.Forward = True
.Format = True
.MatchWholeWord = True
.MatchCase = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
For Each wrdPara In docRef.Paragraphs
wrdRef = wrdPara.Range.Text
If Asc(Left(wrdRef, 1)) > 32 Then
' remove the paragraph mark:
wrdRef = Left(wrdRef, Len(wrdRef) - 1)
With Selection.Find
.Wrap = wdFindContinue
.Text = wrdRef
.Execute Replace:=wdReplaceAll
End With
End If
Next wrdPara
docRef.Close
docCurrent.Activate
End Sub
Try wdReplaceOne instead wdReplaceAll.
.MatchWholeWord = True should prevent highlighting embedded strings but it seems to be ignored.
I tested your original code in module behind ThisDocument and it highlighted all instances of only the last string from check document, ignoring the MatchWholeWord parameter. After the suggested edit, the code highlighted first instance of only the last string from check document. Now I can't get the procedure to work. It runs but words do not highlight. I've never used VBA behind Word. Hope this change works for you.
I'm working on a Word 2007 template with a macro that will apply character styles to the selected text. It seemed that the Find/Replace feature would be a good place to start, but I think I've found a bug/limitation that prevents the macro from working as desired.
Here's my vba code:
Sub restyleSelection()
Dim r As Range
Set r = Selection.Range
With r.Find
.Style = ActiveDocument.Styles("Default Paragraph Font")
.Text = ""
.Replacement.Text = ""
.Replacement.Style = ActiveDocument.Styles("Emphasis")
.Forward = True
.Wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute Replace:=wdReplaceAll
End With
End Sub
If I create a test document that contains a few paragraphs and select a few words in one of the paragraphs, then run the macro, the "Emphasis" style is applied not only to the selection, but beyond the end of the selection to the end of the document.
This behavior is the same using the actual GUI Find/Replace tool.
My question is: How can I overcome this bug/limitation and apply the character style ONLY within the selection/range?
A little more information:
What I really need the macro to do is apply certain formatting to the entire selection while maintaining the existing character styles in the selection. For example, if the selected text contains the Bold character style, the Italic character style, and the rest of it is Default Paragraph Font, the macro should replace Bold with "Revised Bold", replace "Italic" with "Revised Italic", and replace "Default Paragraph Font" with "Revised". That way, when I use the companion macro to "undo" the action of this macro, the original character styles (Bold, Italic, Default Paragraph Font) can be replaced.
SOLVED:
Here is the solution I finally arrived at:
Sub applyNewRevisedText
Dim r As Range ' Create a new Range object
Set r = Selection.Range ' Assign the current selection to the Range
Dim rng As Range
For Each rng In r.Words
Set rngStyle = rng.Style
Select Case rngStyle
Case "Bold"
rng.Style = ActiveDocument.Styles("New/Revised Text Bold")
Case "Italic"
rng.Style = ActiveDocument.Styles("New/Revised Text Emphasis")
Case Else
rng.Style = ActiveDocument.Styles("New/Revised Text")
End Select
Next rng
End Sub
To answer your direct question
My question is: How can I overcome this bug/limitation and apply the
character style ONLY within the selection/range?
Does this not meet the need?:
Sub restyleSelection()
Selection.Style = ActiveDocument.Styles("Emphasis")
End Sub
EDIT:
Ok, based on your comment, what about something like:
Dim rng As Range
For Each rng In Selection.Words
If rng.Bold 'do something
Next rng
.Words will break up each word in the range into a collection of ranges. Then you can perform styling on each individual word based on its current style.
I had a slightly different problem and solved it without resorting to a loop. The code works NOT for text which is formatted directly, but it does work for text which is formatted with character styles.
Consider a part of the text being selected, either including or not including strings to which already some character style has been assigned.
If within the selected range no character style has been assigned yet, after the search the start of the selection won't be the same. If however at least one character style has been assigned the start of the selection will be the same as before the search. Now you can treat those two cases separately. In both cases all characters within the selection to which no character style had been assigned previously will now be linked to "myStyle".
Vst_Style = "myStyle"
ActiveDocument.Bookmarks.Add Name:="Range"
V_BMstart = Selection.Range.Start
Selection.Find.ClearFormatting
Selection.Find.Style = ActiveDocument.Styles("Default Paragraph Font")
Selection.Find.Replacement.ClearFormatting
Selection.Find.Replacement.Style = ActiveDocument.Styles(Vst_Style)
With Selection.Find
.Text = ""
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = True
End With
Selection.Find.Execute
If Selection.Range.Start <> V_BMstart Then
Selection.GoTo what:=wdGoToBookmark, Name:="Range"
Selection.Style = Vst_Style
Else
Selection.GoTo what:=wdGoToBookmark, Name:="Range"
Selection.Find.Execute Replace:=wdReplaceAll
End If