change language ID in characters - vba

I have a word document that some chars (like "[") are in lang.charaters LanguageID=2057 and some LanguageID=1037
I tried to change it via the following code:
For Each oChr In pRange.Characters
If oChr.LanguageID = 2057 Then
oChr.LanguageID = 1037
End If
Loop Until pRange Is Nothing
but the LanguageID is not changing after the assignment
Why ? how can I make it work?

Here is a procedure I wrote a while back that changes the proofing language.
You are correct that the proofing language attribute can be applied to a character but you do not need to examine each character to reset. The following checks all stories of a document and sets the proofing language. You can change the constant to what you want instead of EnglishUS.
Sub ProofingLanguageEnglishUSAllStory() ' based on field updater by Greg Maxey
' https://gregmaxey.com/word_tip_pages/word_fields.html
' Charles Kenyon 6 November 2018
' https://answers.microsoft.com/en-us/msoffice/forum/all/force-all-documents-to-be-edited-in-uk-english/df6d1f8e-5426-49d9-bea0-5620d0208294
' Changes proofing language to English US in all stories of document
' Language IDs https://learn.microsoft.com/en-us/office/vba/api/word.wdlanguageid
Dim rngStory As Word.range
Dim lngValidate As Long ' do not know purpose of this
Dim oShp As Shape
lngValidate = ActiveDocument.Sections(1).Headers(1).range.StoryType
For Each rngStory In ActiveDocument.StoryRanges
'Iterate through all linked stories
Do
On Error Resume Next
rngStory.LanguageID = wdEnglishUS ' delete or comment out if you do not want to change the language ID
rngStory.NoProofing = False Select Case rngStory.StoryType
Case 6, 7, 8, 9, 10, 11
If rngStory.ShapeRange.Count > 0 Then
For Each oShp In rngStory.ShapeRange
If oShp.TextFrame.HasText Then
' Comment out or delete the next line if you do not want to change proofing language
oShp.TextFrame.TextRange.LanguageID = wdEnglishUS ' delete or comment out if you do not want to change the language ID
' Comment out or delete the next line if you do not want to change the "no proofing" setting
oShp.TextFrame.TextRange.NoProofing = False
End If
Next
End If
Case Else
'Do Nothing
End Select
On Error GoTo -1
'Get next linked story (if any)
Set rngStory = rngStory.NextStoryRange
Loop Until rngStory Is Nothing
Next
End Sub
This is one of three macros on the page that deal with different problems. The macros use the Word constants for the language IDs rather than the numerical equivalents. This is my Article on the Microsoft Answers site on the topic: Fixing Proofing Language Problems.

Work around:
With Selection.Find
.LanguageID = 3081
.Text = "'"
.Replacement.Text = "'"
.Replacement.LanguageID = 1037
.Forward = True
.Wrap = wdFindStop
.Format = True
.MatchCase = False
.MatchWholeWord = False
.MatchKashida = False
.MatchDiacritics = False
.MatchAlefHamza = False
.MatchControl = False
.MatchAllWordForms = False
.MatchSoundsLike = False
.MatchWildcards = False
End With

Related

Find each table whose first cell has one of two words followed by three digits

I would like to fix the following code to make it find each table in the document where it has the pattern ARC or MEC words followed by the wildcard digits [1-4][1-9]{2} without any leading/trailing characters, digits, spaces, etc.
The chosen table should have a total of 11 rows.
If possible, I need another version of the code to search for the pattern in the table first cell .Cell(1,1) while making sure the table has a total of 11 rows.
Sub FindTables()
Dim wdDoc As Word.Document, t As Long
Set wdDoc = ThisDocument
With wdDoc
For t = 1 To .Tables.Count
With .Tables(t).Range.Find
.ClearFormatting
.Format = FALSE
.Text = "(ARC)|(MEC)[1-4][1-9]{2}"
.Forward = TRUE
.Wrap = wdFindStop
.MatchCase = TRUE
.MatchWildcards = TRUE
.Execute
If .Found = TRUE Then
' some operations on the table
wdDoc.Tables(t).AutoFitBehavior (wdAutoFitWindow)
wdDoc.Tables(t).Range.Collapse wdCollapseEnd
End If
End With
Next
End With
End Sub
For example:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Format = False
.Forward = True
.Wrap = wdFindStop
.MatchWildcards = True
.Text = "<[ACEMR]{3}[1-4][1-9]{2}>"
.Replacement.Text = ""
End With
Do While .Find.Execute = True
If .Information(wdWithInTable) = True Then
If .Tables(1).Rows.Count = 11 Then
'If .Cells(1).RowIndex = 1 And .Cells(1).ColumnIndex = 1 Then
If Split(.Cells(1).Range.Text, vbCr)(0) = .Text Then
Select Case Left(.Text, 3)
Case "ARC", "MEC": .Tables(1).AutoFitBehavior (wdAutoFitWindow)
End Select
End If
'End If
End If
.Start = .Tables(1).Range.End
End If
.Collapse wdCollapseEnd
Loop
End With
Application.ScreenUpdating = True
End Sub
To process only those tables where the found content is in the first cell, delete the tick marks from the two comment-out lines.
Pattern:
"(ARC[1-4][1-9]{2})|(MEC[1-4][1-9]{2})"
Tested successfully with Microsoft VbScript Regular Expressions 5.5. (set this Reference on VBE).
Code sample - adapt it to suit your needs (working with tables - I didn't reproduce your scenario):
Function fnFindPatterns()
Dim objRegExp As RegExp
Dim ObjMatch As Match
Dim colMatches As MatchCollection
Dim strText As String
Dim strResult As String
Set objRegExp = New RegExp
objRegExp.Pattern = "(ARC[1-4][1-9]{2})|(MEC[1-4][1-9]{2})"
objRegExp.IgnoreCase = True
objRegExp.Global = True
Selection.WholeStory
strText = Selection.Text
If objRegExp.Test(strText) = True Then 'we have something there...
Set colMatches = objRegExp.Execute(strText)
For Each ObjMatch In colMatches 'Iterate on the collection
strResult = strResult & ObjMatch.Value & vbCrLf
Next
Else
End If
MsgBox strResult
End Function
Edited 2022 07 11:
I realized that the "|" (OR) do not work in MSWord . It doesn't exist on the limited "Regular Expressions" set of tools within MsWord, compared to VbScript.RegExp. Wich, in turn, is also limited set of tools, if compared with other (powerfull) programming languages. But with some coding we "simulate" this OR, using "Choose", testing each partial set of patterns that way:
Sub FindTables()
Dim wdDoc As Word.Document, t As Long, intChoose As Integer
Set wdDoc = ThisDocument
With wdDoc
For intChoose = 1 To 2
For t = 1 To .Tables.Count
With .Tables(t).Range.Find
.ClearFormatting
.Format = False
.Text = VBA.Choose(intChoose, "<[ARC]{3}[1-4][1-9]{2}>", "<(MEC)[1-4][1-9]{2}>")
.Forward = True
.Wrap = wdFindStop
.MatchCase = True
.MatchWildcards = True
.Execute
If .Found = True Then
' some operations on the table
wdDoc.Tables(t).AutoFitBehavior (wdAutoFitWindow)
wdDoc.Tables(t).Range.Collapse wdCollapseEnd
End If
End With
Next
Next
End With
End Sub
To test this code I mounted a Word Doc with 7 tables (varying dimensions from 1 x 11 to 1 x 13). To ensure the correct dimension of each table insert the suggestion posted in Macropod's code.

How to adjust the tracking settings for this macro?

I use the following macro to check documents for US spelling (referencing an installed custom dictionary) and run a ligatures check, replacing any applicable ligatures. It works well but a number of formatting changes show up on the right hand side of the document, including:
Formatted: Font 12 pt
Formatted: Font Not Italic
Field Code Changed
I'm OK with the above things being checked, but I don't want them to be shown in the tracking (only the ligature replacement tracking should be shown).
Could someone help me to adjust the following macro code so that only the replacement of the ligatures shows in the tracking? Many thanks in advance!
Sub USEnglish()
Selection.WholeStory
Dim i As Long, j As Long, k As Long
Dim StrFnd As String, StrRep As String
Dim ArrRep(), oSuggestions, Rng As Range
Application.CheckLanguage = False
Application.ResetIgnoreAll
Options.CheckGrammarAsYouType = True
Options.CheckGrammarWithSpelling = True
Options.ContextualSpeller = True
Options.CheckSpellingAsYouType = True
ArrRep = Array("ff", "fi", "fl", "ffi", "ffl")
With ActiveDocument
.TrackRevisions = True
.Range.LanguageID = wdEnglishUS
.SpellingChecked = False
.GrammarChecked = False
.ShowGrammaticalErrors = True
.ShowSpellingErrors = True
For i = 0 To UBound(ArrRep)
StrFnd = ChrW(&HFB00 + i)
StrRep = ArrRep(i)
For j = 1 To 3
On Error Resume Next
With .StoryRanges(j).Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = StrFnd
.Replacement.Text = StrRep
.Wrap = wdFindContinue
.MatchWildcards = True
.Execute Replace:=wdReplaceAll
End With
Next j
Next i
End With
End Sub
Just turn off formatting tracking and restore it when you've finished.
With ActiveDocument
.TrackFormatting = False
Field Code Changed is likely to be a result of the find and replace.

How can I apply sorting on certain tables in a word document?

I have a system that generate test results reports for me. I managed to create the right table templates for the use of this system. But, for some reason the report, which is about 950 pages long full of tables and diagrams, was generated while the tables are sorted in a descending order. I have tried to make the auto reports to output the tables in an ascending order without success.
Then I started to look for a solution to this problem. One of the solution that I have is the following VBA code. But, when I apply it on the whole report it gets stuck and Word turns to "Not Responding". I am completely new to VBA and don't see the reason. Can you please tell me why ?
Attribute VB_Name = "SortTable_Ascend"
Sub Find_Text_in_table()
Selection.Find.ClearFormatting
With Selection.Find
.Text = "Step"
.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
Selection.Tables(1).SortAscending
End If
Loop
End Sub
By the way, I only look for certain tables (the ones that have a column with the string "Step" in them) and apply the sorting on them. When I took only 100 pages of this document and applied this script it did the job and didn't get stuck.
The following should process a bit faster and will get through all tables.
Sub Find_Text_in_table()
Dim rng As word.Range, tbl As word.Table
ActiveDocument.ActiveWindow.View.Type = word.WdViewType.wdNormalView
Application.Options.Pagination = False
For Each tbl In ActiveDocument.Tables
Set rng = tbl.Range
rng.Find.ClearFormatting
With rng.Find
.Text = "Step"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Execute
If .found Then
If rng.InRange(tbl.Range) Then
tbl.SortAscending
End If
End If
End With
Next
ActiveDocument.ActiveWindow.View.Type = word.WdViewType.wdPrintView
Application.Options.Pagination = True
End Sub
Here is an alternative that avoids the word find and replace. It also uses the more general Sort method which replicates the dialog box you get when doing the sort in word. This might be helpful if you want to sort on multiple columns.
Option Explicit
Sub test()
SortTables_WithKey "Step"
End Sub
Sub SortTables_WithKey(this_key As String)
Dim myIndex As Long
Dim myLastTable As Long
myLastTable = ActiveDocument.Tables.Count
Application.ScreenUpdating = False
Application.Options.Pagination = False
For myIndex = 1 To myLastTable
' MS have deprecated the use of statusbar so if this line
' but it still appears to work in Word 2016
Application.StatusBar = "Table " & CStr(myIndex) & " of " & CStr(myLastTable)
If InStr(ActiveDocument.Tables(myIndex).Range.text, this_key) > 0 Then
' https://learn.microsoft.com/en-us/office/vba/api/word.table.sort
' Replicates the type of sort when done using Word
ActiveDocument.Tables(myIndex).Sort _
excludeheader:=True, _
fieldnumber:=1, _
sortfieldtype:=wdSortFieldAlphanumeric, _
sortorder:=wdSortOrderAscending
End If
DoEvents
Next
Application.ScreenUpdating = True
Application.Options.Pagination = True
End Sub
Edited to revise the sub to include the recommendations about screen updating, events and pagination (others beat me to it). I've also included code to put a message in the status bar (bottom left corner of the word window) which will show progress (Table x of y). I tested the above on a document I have with 125 tables and (without sorting the tables) it completed in around 5 seconds.
I also corrected one error I made
sortorder:=wdSortAscending
should have been
sortorder:=wdSortOrderAscending
Hence the addition of 'option explicit' at the start of the code.
Try:
Sub SortTables()
Application.ScreenUpdating = False
Dim t As Long, bfit As Boolean
With ActiveDocument
For t = 1 To .Tables.Count
With .Tables(t)
If InStr(1, .Range.Text, "Step", 0) > 0 Then
bfit = .AllowAutoFit
If bfit = True Then .AllowAutoFit = False
.SortAscending
If bfit = True Then .AllowAutoFit = True
End If
End With
If t Mod 100 = 0 Then DoEvents
Next
End With
Application.ScreenUpdating = True
End Sub
Turning off screen updating and the table autofit property will both enhance performance. Running DoEvents periodically on long operations also gives Word some breathing space.

To delete everything except for words between a start and end point

I happen to have problems trying to manipulate the below code to my liking.
First off, the code below deletes everything in between the start and end conditions I have stipulated in my program.
I would like to change this, to delete everything besides those stipulated between the start and end words.
Sub SomeSub()
Dim StartWord As String, EndWord As String
Dim Find1stRange As Range, FindEndRange As Range
Dim DelRange As Range, DelStartRange As Range, DelEndRange As Range
Application.ScreenUpdating = False
Application.DisplayAlerts = False
'Setting up the Ranges
Set Find1stRange = ActiveDocument.Range
Set FindEndRange = ActiveDocument.Range
Set DelRange = ActiveDocument.Range
'Set your Start and End Find words here to cleanup the script
StartWord = "From: Research.TA#traditionanalytics.com|Tradition Analytics Commentary| | |"
EndWord = "This message has been scanned for malware by Websense. www.websense.com"
'Starting the Find First Word
With Find1stRange.Find
.Text = StartWord
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
'Execute the Find
Do While .Execute
'If Found then do extra script
If .Found = True Then
'Setting the Found range to the DelStartRange
Set DelStartRange = Find1stRange
'Having these Selections during testing is benificial to test your script
DelStartRange.Select
'Setting the FindEndRange up for the remainder of the document form the end of the StartWord
FindEndRange.Start = DelStartRange.End
FindEndRange.End = ActiveDocument.Content.End
'Having these Selections during testing is benificial to test your script
FindEndRange.Select
'Setting the Find to look for the End Word
With FindEndRange.Find
.Text = EndWord
.Execute
'If Found then do extra script
If .Found = True Then
'Setting the Found range to the DelEndRange
Set DelEndRange = FindEndRange
'Having these Selections during testing is benificial to test your script
DelEndRange.Select
End If
End With
'Selecting the delete range
DelRange.Start = DelStartRange.Start
DelRange.End = DelEndRange.End
'Having these Selections during testing is benificial to test your script
DelRange.Select
'Remove comment to actually delete
DelRange.Delete
End If 'Ending the If Find1stRange .Found = True
Loop 'Ending the Do While .Execute Loop
End With 'Ending the Find1stRange.Find With Statement
End Sub
Hah! That's a new twist There's certainly more than one way to go about it; my inclination would be to work with (at least) three Ranges. Something like this:
Sub FindAndDeleteEverythingElse()
Dim strFind1 As String, strFind2 As String
Dim rngDoc As word.Range, rngFind1 As word.Range
Dim rngFind2 As word.Range
Dim bFound As Boolean
strFind1 = "You"
strFind2 = "directly."
Set rngDoc = ActiveDocument.content
Set rngFind1 = rngDoc.Duplicate
Set rngFind2 = rngDoc.Duplicate
With rngFind1.Find
.Text = strFind1
bFound = .Execute
End With
If bFound Then
With rngFind2.Find
.Text = strFind2
bFound = .Execute
End With
If bFound Then
rngDoc.End = rngFind1.Start
rngDoc.Delete
rngDoc.Start = rngFind2.End
rngDoc.End = ActiveDocument.content.End
rngDoc.Delete
End If
End If
End Sub
The "main" Range is that of the entire document: ActiveDocument.Content. The Range object is a bit different than other objects, if you set one Range to another it becomes that Range, not a copy. So you need the Duplicate method to make a copy of a Range. This lets you use Find independently for the various Ranges.
If the first Find is successful, the second one is executed. If that is also successful then the Document Range's end-point is set to the starting point of the successful Find and the content of the Range deleted. The Document Range is then re-defined to start at the end-point of the second found Range and end at the end of the Document, and deleted.
You will probably have to set more Range.Find properties than I did in this code snippet - I used the absolute minimum to make working with the Ranges clearer.
There maybe another way but till then you can do this.
try to add dummy character after and before your string like this
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchWildcards = False
.Text = "From: Research.TA#traditionanalytics.com|Tradition Analytics Commentary| | |"
.Replacement.Text = "From: Research.TA#traditionanalytics.com|Tradition Analytics Commentary| | |######"
.Execute Replace:=wdReplaceAll
.Text = "This message has been scanned for malware by Websense. www.websense.com"
.Replacement.Text = "######This message has been scanned for malware by Websense. www.websense.com"
.Execute Replace:=wdReplaceAll
End With
End With
End Sub
Then try to set range between ###### and ######
this is best answer to set range select a range of text from one Word document and copy into another Word document
Please note that in my word 2007 it is not possible to find within hyperlinks. Try to remove all hyperlink or within range before doing replacement.
another best answer for that: How do you remove hyperlinks from a Microsoft Word document?

Word Macro - Log changes made by a find and replace

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