I have several bookmarks in several Word Documents that need spaces added before and after all Bookmarks where a single space does not currently exist on either or both sides. I only want to be able to parse the current file.
I have tried several ways at doing this, several of which create infinate loops.
Using the following code, I have a level of success, however it creates an infinate loop in the process. I have tried looking through the Bookmark object, selecting each in turn and adding a space before and after, which causes spaces to be put within the bookmark or it ignores where the space should go and puts it after.
I have a macro that I run on the document that reveals the bookmarks and places it between more-than and less-than symbols like this "««bookmarkname»»" to make it easier to parse.
Here is my code:
Sub new_test()
Dim sT As String
Dim boo As Boolean
boo = False
Selection.Find.ClearFormatting
With Selection.Find
.Text = "««*»»[ ]"
.Forward = False
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
Do While .Execute
With Selection
'sT = Selection.Text
If (boo = False) Then
MsgBox "Added a character after bookmark"
Selection.InsertAfter (" ")
boo = True
End If
End With
boo = False
Loop
End With
End Sub
Ok - worked it out. Maybe it will prove of some use to someone.
Before I run this, I run another function over the document that reveals all of the bookmarks and puts more and less than signs around them like this: "««BOOKMARKNAME»»"
Sub bookmarks_ensure_space_beforeAfter()
' Before we can do any work, we need a list of bookmarks from the document
Dim bmks As Variant
bmks = create_array_of_bookmark_names() ' array of bookmark names
' This Assumes that there will not be more than 1000 bmks in the array fetched from the Word Doc
For i = 0 To 1000
If (bmks(i) <> "") Then
' if the 'bmk' is not null then process it
' there are likely to be several 100 that are empty
Dim wrd As String
Dim rng As Range
Call select_a_string("««" & bmks(i) & "»»") ' select the bookmark
wrd = "««" & bmks(i) & "»»"
Set rng = Selection.Range
' now move the cursor two places the left of the bookmark
Selection.MoveLeft Unit:=wdCharacter, count:=2
' now select the character infront of the cursor (which is now the character infront of the bmk)
Selection.MoveRight Unit:=wdCharacter, count:=1, Extend:=wdExtend
If (Selection.Text <> " ") Then
' if this character now selected is not a space - add one
rng.InsertBefore " "
End If
' now move the cursor to the right of the bookmark (using it's length as a character limit)
Selection.MoveRight Unit:=wdCharacter, count:=Len(wrd) + 1
' due to bookmarks being fiddly, recreate the same bmk directly after the original
With ActiveDocument.Bookmarks
.Add Range:=Selection.Range, Name:=bmks(i)
.DefaultSorting = wdSortByName
.ShowHidden = False
End With
' now we have a new bmk, select the character directly after the bmk)
Selection.MoveRight Unit:=wdCharacter, count:=1, Extend:=wdExtend
If (Selection.Text <> " ") Then
' if this character now selected is not a space - add one
rng.InsertAfter " "
End If
End If
Next
End Sub
Function create_array_of_bookmark_names() As Variant
' This function creates an array of bookmarks in the document and returns them as an array
Dim array_of_bmk(1000) As Variant
Dim c As Integer
c = 0
For Each mBookmark In ActiveDocument.Bookmarks()
array_of_bmk(c) = mBookmark.Name
c = c + 1
Next
' now return this array
create_array_of_bookmark_names = array_of_bmk
End Function
Sub select_a_string(str)
' This finds and selects a string of characters
Selection.Find.ClearFormatting
With Selection.Find
.Text = str
'.Replacement.Text = ""
.Forward = True
.MatchCase = True
.Wrap = wdFindContinue
.Format = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute
End Sub
Related
I am trying to create a Word macro that will:
Search for a specific word (i.e. "see")
Select the entire paragraph where that word appears
Make the whole paragraph a different style (i.e. make it all red text)
Do the same thing with a second word (i.e. "blacklist")
Select that whole paragraph and apply a different style (i.e. again, make the paragraph red text)
Copy all paragraphs with the red text style and paste them in to a new word document
Unfortunately, I'm no VBA expert and I'm trying to cobble things together from what I can find online. I have found a great example that will select to the start of the paragraph, but I can't seem to figure out how to select the entire paragraph. Any help is appreciated!
** Sorry - here is the code I currently have. It will find all instances of the word "see" and selects to the start of the paragraph, then changes the color to red... but that's as far as I've gotten, as I am stuck on trying to figure out how to get it to select to the end of the paragraph.
Sub TestOne()
'
' TestOne Macro
'
'
If MsgBox(Prompt:="Would you like to update selected paragraph styles?", Buttons:=vbYesNo + vbQuestion, _
Title:="Format MD Report") = vbNo Then
Exit Sub
End If
Application.ScreenUpdating = False
Dim i As Long
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "see"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = False
End With
Do While .Find.Execute
i = i + 1
.Start = .Paragraphs.First.Range.Start
.Font.Color = wdColorRed
.Start = .Paragraphs.First.Range.End
Loop
End With
Application.ScreenUpdating = True
MsgBox i & " instances processed."
End Sub
For example, without needing to create a second document:
Sub Demo()
Application.ScreenUpdating = False
Dim i As Long, StrFnd As String
StrFnd = "see|blacklist"
With ActiveDocument.Range
.Font.Hidden = True
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Replacement.Text = "^&"
.Font.Hidden = True
.Replacement.Font.Hidden = False
.Format = True
.Forward = True
.MatchWildcards = True
.Wrap = wdFindContinue
For i = 0 To UBound(Split(StrFnd, "|"))
.Text = "[!^13]#" & Split(StrFnd, "|")(i) & "*^13"
.Execute Replace:=wdReplaceAll
Next
.Replacement.ClearFormatting
.Text = ""
.Replacement.Text = ""
.Execute Replace:=wdReplaceAll
End With
End With
Application.ScreenUpdating = True
End Sub
You could, of course, add a line of code before the final 'End With' to save the document with a new name.
To select the entire paragraph, following the line
.Start = .Paragraphs.First.Range.Start
add
.End = .Paragraphs.First.Range.End
... then to match only whole words, after
.MatchWildcards = False
add
.MatchWholeWord = True
And to run the code for multiple words you should add a parameter to your Sub eg
Sub TestOne(theWord As String)
then replace
.Text = "see"
with
.Text = theWord
And to run your code for each required word, add a Sub such as
Sub RunMe()
TestOne "see"
TestOne "blacklist"
End Sub
... optionally, move your MsgBoxes into RunMe()
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
I found this code online to search and highlight multiple words. It takes roughly about 10 min to run it on a 15 page document. I was wondering if it could be made to run any faster.
Sub HighlightMultipleWords()
Dim Word As Range
Dim WordCollection(2) As String
Dim Words As Variant
'Define list.
'If you add or delete, change value above in Dim statement.
WordCollection(0) = "word1"
WordCollection(1) = "word2"
WordCollection(2) = "word3"
'Set highlight color.
Options.DefaultHighlightColorIndex = wdYellow
'Clear existing formatting and settings in Find feature.
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
'Set highlight to replace setting.
Selection.Find.Replacement.Highlight = True
'Cycle through document and find words in collection.
'Highlight words when found.
For Each Word In ActiveDocument.Words
For Each Words In WordCollection
With Selection.Find
.Text = Words
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Next
Next
End Sub
The comments are all correct here, you only need to run the find and replace once per item in your list, you are running it multiple times by the amount of words in the document.
Option Explicit
Sub HighlightMultipleWords()
Dim AryWords(2) As String
Dim VntStore As Variant
'Define list.
'If you add or delete, change value above in Dim statement.
AryWords(0) = "word1"
AryWords(1) = "word2"
AryWords(2) = "word3"
'Set highlight color.
Options.DefaultHighlightColorIndex = wdYellow
With Selection.Find
'Clear existing formatting and settings in Find feature.
.ClearFormatting
.Replacement.ClearFormatting
'Set highlight to replace setting.
Selection.Find.Replacement.Highlight = True
'Process the array
For Each VntStore In AryWords
.Execute FindText:=VntStore, _
MatchCase:=False, _
MatchWholeWord:=False, _
MatchWildcards:=False, _
MatchSoundsLike:=False, _
MatchAllWordForms:=False, _
Forward:=True, _
Wrap:=wdFindContinue, _
Format:=True, _
Replace:=wdReplaceAll
Next
End With
End Sub
I have a word document with tables containing hyperlinks to other word documents, see image below. The word documents are catogorized into groups, i.e 1 table for each group.
My problem is that sometimes people messes around with the formatting, such as adding a newline or removing a newline between the tables(so it becomes 1,2,3,4 newlines instead of 2 as my code requires) or change the order to not be alphabetic(rare and I can live with that).
So finally to my problem, In this case I created a new document PL_xxxx and the table PL does not exist, so it should insert a new table, but with SINGLE newline between tables this get inserted inside another table instead of in between tables.
' Now move up two lines, beyond the table end
Selection.MoveUp Unit:=wdLine, Count:=2
So how can I either ensure that it is always consistence newlines between tables? Is there a way to remove all newlines between tables and then recreate them, and then do the table insert? Or can I somehow loop through all tables in documents? Or is there some other way to make sure that mistakes not like this happens?
So here is my main code:
'here we alter the docout tables
If Not searchAll(dokType) Then
Call addList(dokType, Settings.documentTypeFile)
docNumber = "01"
Else
Below is my code that seach if PL exist, which will return false in this case:
' Moves cursor to the place the given string is found, or replace it
Function searchAll(searchText As String, Optional replaceText As String = "GGG") As Boolean
'default false
searchAll = False
If Not replaceText = "GGG" Then
With ActiveDocument.Range.Find
.Text = searchText
.forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = True
.MatchWholeWord = True
.Replacement.Text = replaceText
If .Execute(Replace:=wdReplaceAll) Then
searchAll = True
End If
End With
'just searching
Else
With Selection.Find
.Text = searchText
.forward = True
.Wrap = wdFindContinue
.Format = True
.MatchCase = True
.MatchWholeWord = True
If .Execute Then
searchAll = True
End If
End With
End If
End Function
Here is the code that actually figures out where to place the table and add its, and here is the problem(rewrite to loop through tables instead or modify the moveup function)
Sub addList(tableKey As String, filenameTypes As String)
Dim dict As Object
Dim addAtEnd As Boolean
Dim keyArray As Variant
Dim startSearching As Boolean
Dim element As Variant
'Dictionary with all types
Set dict = getTypes(filenameTypes)
With dict
addAtEnd = False
'extract keys into variant array
keyArray = .keys
startSearching = False
For Each element In keyArray
'looping untill we find the element we want to add
If element = tableKey Then
startSearching = True
End If
'Finding the next table after were we want to insert
If startSearching Then
If searchAll(CStr(element)) Then
addAtEnd = False
Exit For
Else
addAtEnd = True
End If
End If
Next
If addAtEnd Then
Selection.EndKey Unit:=wdStory
Else
Call HelpFunctions.moveCursorUp(CStr(element))
End If
Call addTable("UT", tableKey, .item(tableKey), Settings.docUtPath)
End With
Set dict = Nothing
End Sub
And finally the move up function which then obviously moves up to much and inside the next table.
'move cursor up
Function moveCursorUp(searchText As String)
If Not searchAll(searchText) Then
MsgBox "Failed to move cursor"
Else
'Selection.Tables(1).Select
If Selection.Information(wdWithInTable) Then
Selection.Tables(1).Range.Select
Selection.Collapse 1
' Now move up two lines, beyond the table end
Selection.MoveUp Unit:=wdLine, Count:=2
End If
'Selection.Collapse WdCollapseDirection.wdCollapseStart
End If
End Function
And here is the addtable code which basically has an empty tabley stored in a seperate file.
Function addTable(typeOfTable As String, category As String, description As String, templateFolder As String)
'Insert out table
If UCase(typeOfTable) = "UT" Then
Selection.InsertFile FileName:=templateFolder + "\Doklistut.doc", Range:="", _
ConfirmConversions:=False, link:=False, Attachment:=False
'insert inn table
ElseIf UCase(typeOfTable) = "INN" Then
Selection.InsertFile FileName:=templateFolder + "\Doklistinn.doc", Range:="", _
ConfirmConversions:=False, link:=False, Attachment:=False
Else
MsgBox "wrong argument given: either inn or ut is allowed"
Exit Function
End If
'Replace the DT with the category
If Not searchAll("DT", category) Then
MsgBox "Failed to replace category in table"
End If
'Replace the Dokumenttype with the category
If Not searchAll("Dokumenttype", description) Then
MsgBox "Failed to replace document type in table"
End If
End Function
So thanks to all the input I have now revised the code totally and it is now working as desired, It can probably be improved, especially the selection method.
Sub addList(tableKey As String, tableDescription As String)
Selection.EndKey Unit:=wdStory
Call addTable(tableKey, tableDescription)
Call SortTables
End Sub
Sub Deleemptylines()
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "^p"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchByte = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = False
.MatchFuzzy = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
End Sub
Sub SortTables()
Dim i As Long
Dim iMin As Long
Dim iMax As Long
Dim blnSwapped As Boolean
Call Deleemptylines
iMin = 1
iMax = ActiveDocument.Tables.Count - 1
Do
blnSwapped = False
For i = iMin To iMax
If ActiveDocument.Tables(i).Cell(1, 1).Range.Text > ActiveDocument.Tables(i + 1).Cell(1, 1).Range.Text Then
ActiveDocument.Tables(i).Range.Cut
ActiveDocument.Tables(i).Select
Selection.Collapse WdCollapseDirection.wdCollapseEnd
Selection.Paragraphs.Add
Selection.Paragraphs.Add
Selection.MoveDown Unit:=wdLine, Count:=1
Selection.MoveUp Unit:=wdLine, Count:=1
Selection.Paste
blnSwapped = True
End If
Next i
iMax = iMax - 1
Loop Until Not blnSwapped
Call Deleemptylines
End Sub
Function addTable(category As String, description As String)
'Insert out table
Selection.InsertFile FileName:=Settings.docUtPath + "\Doklistut.doc", Range:="", _
ConfirmConversions:=False, link:=False, Attachment:=False
'Replace the DT with the category
If Not searchAll("DT", category) Then
MsgBox "Failed to replace category in table"
End If
'Replace the Dokumenttype with the category
If Not searchAll("Dokumenttype", description) Then
MsgBox "Failed to replace document type in table"
End If
End Function
I have the below code that will search through a word document replacing any IDs it finds with a masked version of the number using RegEx (e.g. 412345678900 becomes 4123####8900). Each document could have multiple IDs in it. The IDs are sometimes scattered throughout the document text and not just in tables (so Excel is not an option).
I want to be able to write each of the replaced versions of the text found out to a log file with the file path and file name.
Sub Auto_Masking()
'Start at the very beginning. It's a very good place to start.
Selection.HomeKey Unit:=wdStory
With Selection.Find ' Locate and mask the 12 digit IDs
.Text = "<([4][0-9]{3})([0-9]{4})([0-9]{4})>"
.Replacement.Text = "\1####\3"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
End With
Selection.Find.Execute Replace:=wdReplaceAll
'Put the user back at the beginning of the document
Selection.HomeKey Unit:=wdStory
End Sub
How can I write/append each now masked number to a log file? I would like to have the log file show a list of all the IDs masked and the file they were in, so each line in the log file should look something like this:
filePath\fileName ; maskedID
with a line for each ID number masked (with one file potentially containing multiple IDs). e.g.:
c:\temp\test.docx;4123####8900
c:\temp\test.docx;4241####7629
c:\location\another.docx;4379####8478
I have a horrible feeling this is going to be impossible based on trying to get the value I want in the log file to display in a msgbox. After days of experimenting, I'm completely out of ideas.
I'm thinking a find and a find/replace may have to be used in a larger loop, one to do the replace, and one to find what was just replaced before moving on. Maybe based on Selection.Find.Found = True
Selection.Find.Text will display the regex
Selection.Text will display only the first character of the ID number string, but no more
Selection.Find.Replacement.Text will display the string as it appears in the With section, without replacing the /1 and /3 with the values it found
Not 10 minutes after giving up, I worked it out.
The code to solve the issue and successfully complete the above task, with logging of each masked ID, is as follows:
Sub mask_card_numbers()
'
Dim Counter As Long
' This next section prepares for log writing
Dim Log1 As Object
Set fso = CreateObject("Scripting.FileSystemObject")
' ForReading = 1, ForWriting = 2, ForAppending = 8
Set LogIDs = fso.OpenTextFile("C:\LogDIR\IDs_Masked_with_Word.txt", 8, True)
' Get the filename and path for the log file
FileName = ActiveDocument.Path & "\" & ActiveDocument.Name
' Mask IDs ####################################################
Selection.HomeKey Unit:=wdStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
' The first pass collects a single digit from the text to search for which would artificially increase the counter so reduce it by one in advance
Counter = Counter - 1
Do
With Selection.Find
.Text = "<([4][0-9]{3})([0-9]{4})([0-9]{4})>"
.Replacement.Text = "\1xxxxxxxx\3"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
Counter = Counter + 1
End With
' By keeping the selected text after the replacement, the masked
FoundID = Selection.Text
' Write masked ID to a logfile
If Len(FoundID) > 7 Then ' Anything greater than 1 will probably work
LogIDs.WriteLine FileName & ";" & FoundID
End If
Selection.Find.Execute Replace:=wdReplaceOne
Loop While Selection.Find.Found <> False
' Done Masking IDs ###########################################
End Sub
I really don't think you can do this with Word's Find & Replace if you want to intercept the values to log them to a file.
I suggest using the Find and iterating through them to manually mask the numbers and write them to a log file. I also tweaked your regex as it didn't work. The code below only works on one file at a time.
Sub Auto_Masking()
Dim oDoc As Document
Dim oSelection As Range
Dim cc As String
Dim bFound As Boolean
Application.ScreenUpdating = False
'Handle to the relevant document
Set oDoc = ActiveDocument
'Handle to the whole doc's text
Set oSelection = oDoc.Content
'Create your log file. Amend this to cope with Append if needed
Open "C:\Temp\ChangeLog.txt" For Output As #1
With oSelection.Find
.Text = "<([4])([0-9]{15})>" 'NOTE: this will only work for Visa cards
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = True
bFound = True
While bFound
'Look for the next occurrence
bFound = .Execute
If bFound Then
'Raw text
cc = oSelection.Text
'Manually scramble it
oSelection.Text = Left(cc, 4) & "xxxx" & Right(cc, 4)
Print #1, oDoc.FullName & ";" & oSelection.Text
'*** Remove for Production ***
'Show the result in the Immediate window whilst debugging.
Debug.Print cc & " => " & oSelection.Text
End If
Wend
End With
'Close the log file
Close #1
'Be a good memory citizen
Set oSelection = Nothing
Set oDoc = Nothing
Application.ScreenUpdating = False
End Sub