I'm generating some security report in Microsoft Word - importing SOAP xml requests and responses...
I want to automate this process as much as I can and I need to highlight some text in these requests/responses. How to do that? In general I need to highlight non-standart inputs in requests (every time different - bad data types and so on) and fault strings in responses (in majority looks like this <faultstring>some error</faultstring>).
Heres code Im trying:
Sub BoldBetweenQuotes()
' base for a quotes finding macro
Dim blnSearchAgain As Boolean
' move to start of doc
Selection.HomeKey Unit:=wdStory
' start of loop
Do
' set up find of first of quote pair
With Selection.Find
.ClearFormatting
.Text = "<faultstring>"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Execute
End With
If Selection.Find.Found Then
Selection.MoveRight Unit:=wdCharacter, Count:=1
' switch on selection extend mode
Selection.Extend
' find second quote of this pair
Selection.Find.Text = "</faultstring>"
Selection.Find.Execute
If Selection.Find.Found Then
Selection.MoveLeft Unit:=wdCharacter, Count:=Len(Selection.Find.Text)
' make it bold
Selection.Font.Bold = True
Selection.Collapse Direction:=wdCollapseEnd
Selection.MoveRight Unit:=wdCharacter, Count:=1
blnSearchAgain = True
Else
blnSearchAgain = False
End If
Else
blnSearchAgain = False
End If
Loop While blnSearchAgain = True
End Sub
It highlights just the first faultstring, but other appearences stay unformated nad I dont know why.... Thanks for your reply.
The most efficient way to do this is to work with multiple Range objects. Think of a Range as being like an invisible selection, with the important difference that, while there can be but one Selection object there can be multiple Range objects in your code.
I've adapted your code, adding three Range objects: one for the entire document; one for finding the starting tag; one for finding the end tag. The Duplicate property is used to "copy" one Range from another (this due to an oddity in Word if you Set one Range to another, which links them).
For clarity I also added a couple more Boolean test values for your Ifcomparisons. In my experience, it's more reliable to pick up the "success" directly from Execute than to rely on Find.Found after-the-fact.
Sub BoldBetweenQuotes()
' base for a quotes finding macro
Dim blnSearchAgain As Boolean
Dim blnFindStart As Boolean
Dim blnFindEnd As Boolean
Dim rngFind As word.Range
Dim rngFindStart As word.Range
Dim rngFindEnd As word.Range
Set rngFind = ActiveDocument.content
Set rngFindStart = rngFind.Duplicate
Do
' set up find of first of quote pair
With rngFindStart.Find
.ClearFormatting
.Text = "<faultstring>"
.Replacement.Text = ""
.Forward = True
.wrap = wdFindStop
blnFindStart = .Execute
End With
If blnFindStart Then
rngFindStart.Collapse wdCollapseEnd
Set rngFindEnd = rngFindStart.Duplicate
rngFindEnd.Find.Text = "</faultstring>"
blnFindEnd = rngFindEnd.Find.Execute
If blnFindEnd Then
rngFindStart.End = rngFindEnd.Start
' make it bold
rngFindStart.Font.Bold = True
rngFindStart.Start = rngFindEnd.End
rngFindStart.End = rngFind.End
blnSearchAgain = True
Else
blnSearchAgain = False
End If
Else
blnSearchAgain = False
End If
Loop While blnSearchAgain = True
End Sub
Related
We have an array of documents to be formatted for better visibility.
As the output of our speech to text protocols we get a transcript.
The VBA script should format the text bold after every (return), and the text after a (:) not bold until the next return.
Example:
Speaker1 Question1: Answer Answer Answer
Speaker1 Question2: Answer Answer Answer
This is not working as expected already at the first part of the function.
Sub BoldGenerator()
' BoldGenerator Macro
Selection.WholeStory
'Make each .Method belong to Selection.Find for readability
With Selection.Find
'Set search criteria for break font
.Text = "^l"
'Find next occurrence
.Execute
Do While .Found
Selection.Text = Selection.Font.Bold = True
.Execute
Loop
End With
'
Call BoldGenerator
End Sub
This should bold everything between a (return) (actually it is a new line or a carriage return) and a colon (:)
It is not an easy VBA. Regular expressions are used which are not native in VBA, so we need to get them from VBScript library. We use regular expressions to find all instances starting after a carriage return and ending with a colon. Regular expressions are not able to change the format (to bold). So we need to use .Find method too. We again find what we previously found, but this time we make it bold.
You will see the first instance will not become bold, because it does not start after a carriage return.
Sub BoldGenerator()
Dim RE As Object
Dim REMatches As Object
Dim mch As Object
Selection.HomeKey wdStory
Selection.WholeStory
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = True
.Pattern = "\r(.+?:)"
End With
Set REMatches = RE.Execute(Selection.Text)
If REMatches.Count > 0 Then
Selection.HomeKey wdStory
With Selection.Find
.ClearFormatting
.Forward = True
.Format = False
.MatchCase = True
For Each mch In REMatches
.Text = mch
.Execute
Selection.Font.Bold = True
Selection.MoveRight wdCharacter
Next
End With
Selection.HomeKey wdStory
End If
Set RE = Nothing
Set REMatches = Nothing
Set mch = Nothing
End Sub
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
My goal is to merge multiple paragraph based on predefined string (in an arrary) such as period (.) or question mark (?)
My sample sentence is as below (refer the image please):
Input
The expected result will be something like this.
////////////////////////////////////////////////////////
Output
////////////////////////////////////////////////////////
In the below code, I could achieve it using period (.), but for every other end string, I have separate macro. Depends on the end string, I run different macro. Is there way to put all these search string (. / ? / ;) in a single array and ask the code to run until it find either of them and exit from the loop and do the merge?
Blockquote
Sub FindDotToJoinParagraph()
Dim xRange As Range
Dim Srt As Variant
Dim Endee As Variant
Dim currentPosition As Range
Selection.HomeKey Unit:=wdLine, Extend:=wdMove
ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="Srt" 'Bookmark
With Selection.Find
.Text = "." 'Here not just period alone, but others too
.Replacement.Text = ""
.Forward = True
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = True
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
Selection.EndKey Unit:=wdLine, Extend:=wdMove
ActiveDocument.Bookmarks.Add Range:=Selection.Range, Name:="Ends" 'Bookmark
ActiveDocument.Range( _
ActiveDocument.Bookmarks("Srt").Range.Start, _
ActiveDocument.Bookmarks("Ends").Range.Start) _
.Select
call MergeParaAndLineBreaks() 'An another sub-routine to merge
End Sub
//////////////////////////////////
Sub MergeParaAndLineBreaks()
Dim oRng As Range
Set oRng = Selection.Range
Dim oFind As Range
Set oFind = Selection.Range
With oFind.Find
Do While .Execute(findtext:="[^13^l]{1,}", MatchWildcards:=True)
If oFind.InRange(oRng) Then
oFind.Text = ""
End If
Loop
End With
Set oFind = oRng
With oFind.Find
Do While .Execute(findtext:="[ ]{2,}", MatchWildcards:=True)
If oFind.InRange(oRng) Then
oFind.Text = Chr(32)
oFind.Collapse 0
End If
Loop
End With
lbl_Exit:
Set oRng = Nothing
Set oFind = Nothing
Exit Sub
End Sub
Blockquote
Could someone help me out, please!
As noted in my comments, paragraphs don't necessarily end at punctuations marks. That said, a crude but effective solution to achieve the results you've described would be to use a wildcard Find/Replace, where:
Find = ([!.\!\?;:])^13
Replace = ^32\1
IOW, you don't even need a macro. Since we don't know whether there are spaces preceding the paragraph breaks in your source text, you may end up with some extra spaces - which you could clean up with another Find/Replace (or you might omit the ^32 from the Replace expression).
I am trying to find a word and save the selection in an array and then find again and then save the next selection in the array. And in the end try to select all the selections in the array.
I am trying this but its with half knowledge. I am not able to get it. Can some one help.
Sub Macro6()
'
' Macro6 Macro
'
'
Selection.HomeKey Unit:=wdStory
Dim selecttest(2) As Selection
For I = 1 To 2
Selection.Find.ClearFormatting
With Selection.Find
.Text = "PQXY"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
End With
Selection.Find.Execute
Set selecttest(I) = Selection
Selection.MoveRight Unit:=wdCharacter, Count:=1
Next I
For I = 1 To 2
selecttest(I).Select
Next I
End Sub
I want to keep the selection in the loop and show them in the end.
Solution i tried:
Sub Macro61()
'
' Macro6 Macro
'
'
Selection.HomeKey Unit:=wdStory
Dim selecttest(2) As Range
For i = 1 To 2
Selection.Find.ClearFormatting
With Selection.Find
.Text = "PQXY"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
End With
Selection.Find.Execute
Set selecttest(i) = Selection.Range
Selection.MoveRight Unit:=wdCharacter, Count:=1
Next i
For i = 1 To 2
selecttest(i).Select
Next i
End Sub
Problem above is selecttest(2) is only selected. I want the end result with both selecttest(1) and selecttest(2) selected
I also tried
Dim totalselect as Range
For i = 1 to 2
set totalselect = totalselect + selectest(i)
Next i
totalselect.select
It shows error that "+" (plus) operation does not exist
Solution: Not Possible
Found some articles regarding discontinous range selections is not possible by VBA whereas its possible by FindAll
Find All in VBA: https://forums.windowssecrets.com/showthread.php/124485-Find-All-in-VBA
Reason 1: which talks about findall
Unfortunately, Microsoft omitted to add support for "Find All" in the
VBA object model for Word. In other words, Find All cannot be executed
from a macro.
You can loop through all occurrences of the search text in VBA, but
that's not the same as Find All.
Reason 2: findall is inderectly related to discontiguous selections which is not possible
Probably the reason a Find All isn't in VBA is that VBA also has never
had any way to deal with discontiguous selections (the kind you can
make with Ctrl and the mouse), which is what Find All would produce.
The KB article here explains the few things that can be done. Every
version since 2002 (including 2010) has made no changes in this area.
Reason 3: computationally expensive, in terms of both processing and memory. if done by VBA
I suspect this omission was intentional and carefully considered. In
the visual context of an open document window, Find All is a perfectly
sensible concept. However, in the procedural world of VBA, it is a tad
more difficult to work with sets of things, and, often,
computationally less efficient.
This isn't to say that it can't be done in VBA, only that I can
understand why it wasn't done. For what it's worth, the same is true
of the Find object in Excel, with which I have much more intimate, and
recent, experience.
To support Find All in VBA would require the Execute method to return
a collection of Range objects, which could be computationally
expensive, in terms of both processing and memory
Try with two simple changes replacing Selection into Range object:
Sub Macro6()
...
...
Dim selecttest(2) As Range 'not Selection
...
...
Set selecttest(i) = Selection.Range 'not just a Selection
...
End sub
If all you want to do is show the matches, you only need:
ActiveDocument.Range.Find.HitHighlight FindText:="PQXY"
Dim oRng As Word.Range
Set oRng = Selection.Range
oRng.Find.ClearFormatting
With oRng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "shhada"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchKashida = False
.MatchDiacritics = False
.MatchAlefHamza = False
.MatchControl = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
While .Execute
oRng.Editors.Add wdEditorEveryone
Wend
ActiveDocument.SelectAllEditableRanges wdEditorEveryone
ActiveDocument.DeleteAllEditableRanges wdEditorEveryone
End With
End Sub
UPDATE: Following the suggestion of Cindy below, I used the InRange function. My function iterates fine through the Find operation. But the function is failing to return FALSE when the selection is outside the named range. See "FAILING HERE" below. Thanks.
Using Visual Basic, I need to validate whether the selection location in a Word document is within a named range. Many years ago, I used this code to do that:
ActiveDocument.Bookmarks("typdef").Select
While ((WordBasic.CmpBookmarks("\Sel", "typedef") = 8 _
Or WordBasic.CmpBookmarks("\Sel", "typedef") = 6 _
Or WordBasic.CmpBookmarks("\Sel", "typedef") = 10) _
And leaveloop <> 1
...
If WordBasic.CmpBookmarks("\Sel", "\EndOfDoc") = 0 Then
leaveloop = 1
End If
Wend
Here's the updated function I wrote:
Function FormatSpecHeadReturn(strStyle)
Dim rngBookmark As Word.Range
Dim rngSelection As Word.Range
Set rngBookmark = ActiveDocument.Bookmarks("SpecBodyPairRange").Range
Set rngSelection = Selection.Range
var = rngSelection.InRange(rngBookmark)
Debug.Print var
Do While rngSelection.InRange(rngBookmark) = True
Selection.Find.Style = ActiveDocument.Styles(strStyle)
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
Selection.HomeKey
' FAILING HERE: Returns TRUE when selection point
' is outside SpecBodyPairRange
var = rngSelection.InRange(rngBookmark)
Debug.Print var
Selection.HomeKey Unit:=wdLine, Extend:=wdMove
Selection.InsertBefore Chr(182)
Selection.EndKey
Selection.InsertAfter vbTab
Selection.MoveDown Unit:=wdLine, Count:=1, Extend:=wdMove
If rngSelection.InRange(rngBookmark) <> True Then Exit Do
Loop
End Function
I was using CmpBookmarks in this current project, but it did not reliably returning the value of the current location. When the selection point is within the named range, it returns 8 for two loops, and then returns 6. When the selection point is outside the named range, CmpBookmarks returns 6.
Obviously, CmpBookmarks is deprecated. I can't find the return values that CmpBookmarks produces, and I can't find a modern equivalent function.
I confess I don't understand the difference between the named "SpecBodyPairRange" range and the range assigned to r, here:
Dim r As Range
I can see that "r" in this instance appears to hold the entire document. I studied Range Interface and Selection Interface on Microsoft.Office.Interop.Word, which I don't yet fully understand. I'm not a programmer, only a semi-technical writer self-taught in some coding who has the task of automating document conversion.
There must be a better way to compare the selection point to validate if it's within a named range, but I can't find it. Any pointers you can give me are sincerely appreciated!
Not a big Word VBA person but can you just compare the Start and End properties?
Dim bm As Bookmark
Set bm = ActiveDocument.Bookmarks("tester")
Debug.Print "Bookmark", bm.Start, bm.End
Debug.Print "Selection", Selection.Start, Selection.End
In order to determine whether one Range is within another use the InRange method:
Dim rngBookmark as Word.Range
Dim rngSelection as Word.Range
Set rngBookmark = ActiveDocument.Bookmarks("typeDef").Range
Set rngSelection = Selection.Range
If rngSelection.InRange(rngBookmark) = True Then
'Do something
End If
You could use VBA's InRange Method. For example:
Function FormatSpecHeadReturn(strStyle)
Dim Rng As Range
With ActiveDocument
Set Rng = .Bookmarks("SpecBodyPairRange").Range
With .Bookmarks("SpecBodyPairRange").Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "^p"
.Replacement.Text = ""
.Style = strStyle
.Format = True
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = False
.Execute
End With
Do While .Find.Found
If .InRange(Rng) = False Then Exit Do
.Style = "SpecHead"
.Paragraphs.First.InsertBefore Chr(182)
.InsertAfter vbTab
.Collapse wdCollapseEnd
.Find.Execute
Loop
End With
End With
End Function