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
Related
I have a word VBA that I want to make it a bit smarter. Right now, it serves me well but I still have to do a manual step, which I would like to eliminate. The macro replaces a string found in a selection. This string is "XXXX" which is found more than once. I prompt the user to enter a value for the variable that will replace the "XXXX". However, all the occurrences will be replaced by the same variable. I would like to be able to increase each instance by an increment of 2. So, if the user enters 402, I want the macro to find the first occurrence in the selection and replace it with 402, but the next occurrence should be replaced with 404, next 406, etc...
I tried to increment the "i" by two in the macro, but the macro goes ahead and replaces all the "XXXX" with the input variable. Any help or guidance would be very appreciated.
Here's my macro so far that needs to be enhanced.
Sub my_convert_to_PROCESS_steps_addBLOCKs()
'
' my_convert_to_PROCESS_steps Macro
'
'
Dim aRange As Range
Dim i As Integer
Dim intRowCount As Integer
Dim MyInput As Variant
intRowCount = 1
'Set aRange = ActiveDocument.Range
'Selection.WholeStory
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
Do
.Text = "XXXX " ' the word I am looking for
.Execute
If .Found Then
MyInput = InputBox(" ", "Process Block Sequence", "Enter Block Starting Number (e.g., 402)")
i = MyInput
.Replacement.Text = "At block " & i & ", the device may "
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = True
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
i = i + 2
End If
Selection.Find.Execute Replace:=wdReplaceAll
Loop While .Found
End With
End Sub
I was able to answer my own question. I had already worked on a similar one before. Here's the new code:
Sub my_convert_to_PROCESS_steps_addBLOCKs_new()
Application.ScreenUpdating = False
Dim i As Long
Dim MyInput As Variant
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "XXXX"
.Replacement.Text = ""
.Format = False
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = True
.Execute
End With
MyInput = InputBox(" ", "Process Block Sequence", "Enter Block Starting Number (e.g., 402)")
i = MyInput
Do While .Find.Found
.Text = "At block " & i & ", the device may"
.Collapse wdCollapseEnd
.Find.Execute
i = i + 2
Loop
End With
Application.ScreenUpdating = True
End Sub
I have a vba code for find the specific string found in table, as well as i need a vba code for select the list, if specified text found.
The code was got from here,
Microsoft Word VBA - Select table if cell contains specified string,
Sub Find_Text_in_table()
selection.Find.ClearFormatting
With selection.Find
.Text = "figure id:"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While selection.Find.Execute
If selection.Information(wdWithInTable) Then
MsgBox "Figure ID Found in Table"
Exit Sub
'now you are in table with text you searched
'be careful with changing Selection Object
'do what you need here
End If
Loop
Application.ScreenUpdating = True
End Sub
as well, if the text "Figure ID:" found in any list type, throw an alert message.
this is the list
this is the list
this is the list
Figure Id:
On the whole, it's preferable to work with Range objects instead of Selection. There can be only one selection, but code can work with as many Ranges as necessary. I've altered the original code accordingly. I also changed the Find.Wrap to wdFindStop so that the code searches the entire document, then stops.
The Range object has a ListParagraphs property that will return the ListParagraph object(s) of the Range. In this case, that would be paragraph in which the Find term is located if it belongs to a numbered list. If it does, the Count will be greater than 0 and the code continues to get the Paragraph.Range, from which it's possible to extract all paragraphs that belong to the list using Rnage.ListFormat.List.ListParagraphs.
In order to select the entire list it's necessary to get the Start point of the first list entry and the End point of the last list entry. In the code below, the range of the paragraph in which "Figure Id" was found is extended to these points so that it covers the entire list. Note that it's not clear what you want to do with this, once you have it, since the code loops. It may be that it should not be selected at all but that the action should be performed on the Range object, instead...
Sub Find_Text_withList_in_table()
Dim rngFind As Word.Range, rngFigureList As Word.Range
Dim lstParas As Word.ListParagraphs
Dim lFindCounter As Long 'for testing / debugging
Set rngFind = ActiveDocument.content
rngFind.Find.ClearFormatting
With rngFind.Find
.Text = "figure id:"
.Replacement.Text = ""
.Forward = True
.wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Do While rngFind.Find.Execute
lFindCounter = lFindCounter + 1
If rngFind.Information(wdWithInTable) Then
Debug.Print "Figure ID Found in Table"
Set lstParas = rngFind.ListParagraphs
If lstParas.Count > 0 Then
Set rngFigureList = lstParas.Item(1).Range
Set lstAllParas = rngFigureList.ListFormat.List.ListParagraphs
Debug.Print "Nr paragraphs in the list: " & lstAllParas.Count
rngFigureList.Start = lstAllParas(1).Range.Start
rngFigureList.End = lstAllParas(lstAllParas.Count).Range.End
rngFigureList.Select
MsgBox "Figure Id is in a numbered list, in a table"
End If
End If
Loop
Debug.Print "Nr Figure ID found: " & lFindCounter
Application.ScreenUpdating = True
End Sub
I would like to create a macro in MS Word that when run searches the document for text that appears in the body of the document that matches the mail merge field name. Once identified it would change the text in the document to the actual matching mail merge field name. For example, if there was a mail merge field named "project_date" and in the Word document there was the text "project_date" the macro would turn the text into the actual mail merge field "project_date".
Ideally, the macro would do this for all mail merge fields that exists at once.
Below is as far as I have come with formulating my desired code.
I found this code here ( https://answers.microsoft.com/en-us/msoffice/forum/msoffice_word-mso_other-mso_2007/how-do-i-replace-words-in-a-document-with-a-mail/da323980-7c7d-e011-9b4b-68b599b31bf5 ) but it only will do one specified mail merge field at a time.
Dim oRng As Range
Set oRng = ActiveDocument.Range
With oRng.Find
Do While .Execute(FindText:="(Player 1)")
oRng.Fields.Add oRng, wdFieldMergeField, "Player_1", False
oRng.Collapse wdCollapseEnd
Loop
End With
I recorded this myself, but am not sure how to search and replace text with desired merge field.
With Selection.Find
.Text = "project_name"
.Replacement.Text = "project_name"
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
The solution for this combines the code for inserting all merge fields into a document with the basic code you found / recorded. Inserting the merge field is moved into the Function that searches the field names in the document. I've set the function up to return the number of times the field is inserted.
The tricky, or special, part of the Function is setting up the Range after a successful Find to continue the search. The end-point of a merge field is still within the merge field, thus the line oRng.MoveStart wdCharacter, 2 is required after collapsing the Range. If the Range stays within the field, the merge field name inside it will be found again, and again, and again...
Sub InsertAllMergeFieldsAtPlaceholders()
Dim doc As word.Document
Dim rng As word.Range
Dim mm As word.MailMergeDataField
Set doc = ActiveDocument
Set rng = doc.content
If doc.MailMerge.MainDocumentType <> wdNotAMergeDocument Then
For Each mm In doc.MailMerge.DataSource.DataFields
Debug.Print ReplaceTextWithMergeField(mm.NAME, rng) & " merge fields inserted for " & mm.NAME
Set rng = doc.content
Next
End If
End Sub
Function ReplaceTextWithMergeField(sFieldName As String, _
ByRef oRng As word.Range) As Long
Dim iFieldCounter As Long
Dim fldMerge As word.Field
Dim bFound As Boolean
With oRng.Find
.ClearFormatting
.Forward = True
.wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
bFound = .Execute(findText:=sFieldName)
End With
Do While bFound
iFieldCounter = iFieldCounter + 1
Set fldMerge = oRng.Fields.Add(oRng, wdFieldMergeField, sFieldName, False)
Set oRng = fldMerge.result
oRng.Collapse wdCollapseEnd
oRng.MoveStart wdCharacter, 2
oRng.End = oRng.Document.content.End
bFound = oRng.Find.Execute(findText:=sFieldName)
Loop
ReplaceTextWithMergeField = iFieldCounter
End Function
I have hundreds of docx-documents which I would like to edit in bulk with a visual basic macro. They all share an id on the first line which looks like this:
9-ZKB-S
Or
12-JK-17
I would like to remove the '-' from the id so it will become like this:
9ZKBS
Or
12JK17
Then somewhere in the document I have a word followed by a number like this:
Productionnumber. 42-563-12
And I also would like to remove the minus character:
Productionnumber. 4256312
I've found a visual basic script which enables me to select a folder containing word-documents and to perform a search and replace. But I don't know how to do the specific things I've mentioned such as:
In each document, remove the - and the space between characters on the first line
In each document, remove the - and the space between characters after Productionnumber.
Sub CommandButton1_Click()
Dim MyDialog As FileDialog, GetStr(1 To 500) As String '100 files is the maximum applying this code
On Error Resume Next
Set MyDialog = Application.FileDialog(msoFileDialogFilePicker)
With MyDialog
.Filters.Clear
.Filters.Add "All WORD File ", "*.docx", 1
.AllowMultiSelect = True
i = 1
If .Show = -1 Then
For Each stiSelectedItem In .SelectedItems
GetStr(i) = stiSelectedItem
i = i + 1
Next
i = i - 1
End If
Application.ScreenUpdating = False
For j = 1 To i Step 1
Set Doc = Documents.Open(FileName:=GetStr(j), Visible:=True)
Windows(GetStr(j)).Activate
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "search" 'Find What
.Replacement.Text = "find" 'Replace With
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchByte = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Application.Run macroname:="NEWMACROS"
ActiveDocument.Save
ActiveWindow.Close
Next
Application.ScreenUpdating = True
End With
MsgBox "operation end, please view", vbInformation
End Sub
Look into Using wildcards and test the pattern in word first. For example (not tested):
With Doc.Range.Find
.MatchWildcards = True
.Text = "<(?*)>\-<(?*)>\-<(?*)>"
.Replacement.Text = "\1\2\3" 'Replace With
' ... the rest of the options
End With
I've been trying to modify the brilliant example given here with little success. Within the MSWord document, I need to be able to find text like <<TEST>> and recover the string found between << and >> which would return TEST. Ultimately I intend to use this to look up a value against TEST and return a string to be replaced within the Word document. ie. <<TEST>> becomes FRED for example.
Sub Sample()
Dim c As Range
Dim StartWord As String, EndWord As String, TheWord As String
StartWord = "<<": EndWord = ">>"
Set c = ActiveDocument.Content
c.Find.ClearFormatting
c.Find.Replacement.ClearFormatting
With c.Find
.Text = "[\<]{2}*[\>]{2}"
'.Replacement.Text = TheWord
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = True
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
c.Find.Execute
While c.Find.Found
Debug.Print c.Text
TheWord = Replace(Replace(c.Text, StartWord, ""), EndWord, "")
Debug.Print TheWord
c.Find.Replacement.Text = TheWord
' Future something here to lookup value based on 'TheWord'
c.Find.Execute Replace:=wdReplaceOne
Wend
End Sub
At the moment, I'm just trying to replace those words like <<TEST>> that are found with the string found within. Although it will find and replace the first instance of the text matching the pattern, it doesn't find others like the example will.
Thanks.
Even if sometimes it is not recommended to use Selection within your code I prefer to use it when running find >> replace actions.
In the following code you will find two solutions- 1st is to replace text with one inside << >> brackets, 2nd is to replace with any text. Do not run both at once, comment one to run the other.
Sub Sample()
Dim c As Range
Dim StartWord As String, EndWord As String, TheWord As String
StartWord = "<<": EndWord = ">>"
ActiveDocument.Range(0, 0).Select
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "[\<]{2}(*)[\>]{2}"
.Replacement.Text = "\1"
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = True
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
'DO NOT RUN BOTH OPTIONS TOGETHER, CHOOSE ONE
'OPTION 1. replace to inside text
'Selection.Find.Execute Replace:=wdReplaceAll
'OPTION 2. replace to any text, here- inside text found with replace function
Do While Selection.Find.Execute
Debug.Print Selection.Text
TheWord = Replace(Replace(Selection.Text, StartWord, ""), EndWord, "")
Debug.Print TheWord
Selection.Text = TheWord
Selection.Collapse WdCollapseDirection.wdCollapseEnd
Loop
End Sub