VBA Adding Single Quotes Around Text in Word - vba

I have created the following to comma separate a list of numbers within a ms-word document. However, I also want to wrap each number with single qoutation marks - which I am struggling to get right. I have managed to wrap the first number however I cant execute it for the others. Each number is 12 digits long. Can someone assist?
Sub Macro1()
With Selection
.Find.Text = "^p"
.Find.Replacement.Text = ","
.Find.Execute Replace:=wdReplaceAll
.TypeText Text:="'"
.MoveRight Unit:=wdCharacter, Count:=12
.TypeText Text:="'"
End With
End Sub

Alternative and quicker solution is to use wildcards for another find-replace. Complete code will look as follows:
Sub Macro1()
With Selection
'1st step- replacement paragraph marks into commas
.Find.Text = "^p"
.Find.Replacement.Text = ","
.Find.MatchWildcards = False
.Find.Execute Replace:=wdReplaceAll
'2nd step- adding single quotation marks
.Find.Text = "([0-9]{12})"
.Find.MatchWildcards = True
.Find.Replacement.Text = "'\1'"
.Find.Execute Replace:=wdReplaceAll
End With
End Sub

You need to move the transformation outside of the With block, I think.
I am not a Word VBA expert, so this is an approach that uses common string functions, rather than replicating keyboard input. So, there are likely different approaches to accomplish the same task :) I suspect this is the "correct" way to do it, since it's generally advisable not to mimic "input" but rather to work directly with the objects.
Revised so that you do not need to physically "Select" the text
Note: this uses all text in the document, so you may need to modify.
Sub Test()
Dim doc As Document
Dim arr As Variant
Dim txtRange As Range
Dim i As Long
Set doc = ActiveDocument
Set txtRange = doc.Range(0, doc.Characters.Count)
'First, replace the paragraph breaks with commas
With txtRange.Find
.Text = "^p"
.Replacement.Text = ","
.Execute Replace:=wdReplaceAll
End With
arr = Split(Left(txtRange.Text, Len(txtRange.Text) - 1), ",")
For i = LBound(arr) To UBound(arr)
arr(i) = "'" & arr(i) & "'"
Next
txtRange.Text = Join(arr, ",")
End Sub
UPDATE FROM COMMENTS
I have confirmed absolutely that this is working. Please double-check that you have implemented the code correctly. Here is an example of some text which meets your description:
I select that text, and run the macro, stepping through it using F8. At the end of the With block, observe that the paragraph breaks have been replaced with commas:
After the For...Next loop has exited, I confirm using the Locals window, that the new array now contains numbers enclosed in single quotes:
The next line prints out those items from the array, and replaces the Selection.Text, confirmed:

I have re-worked my original code, and this runs really quickly over large data sets. I've now added in brackets at the start and end of the code too.
Sub TestN()
Options.AutoFormatAsYouTypeReplaceQuotes = False
'Replace the paragraph breaks with commas and single quotes
With ActiveDocument.Range(0, ActiveDocument.Range.End - 1).Find
.Text = "^p"
.Replacement.Text = "','"
.Execute Replace:=wdReplaceAll
End With
Options.AutoFormatAsYouTypeReplaceQuotes = True
'Cap body of data with single quotes and brackets
ActiveDocument.Content.Text = "('" & ActiveDocument.Range(0, ActiveDocument.Range.End - 1).Text & "')"
End Sub

Related

VBA Word: How to improve my algorithm to correct specific characters and punctuation in texts with precise tracked revisions (no "Find and Replace")?

I'm coding a macro to check French texts and correct typographical errors (such as punctuation, spaces before punctuation, etc.). Screen updating is off, but the revisions are tracked so that at the end of the execution, the author sees where spaces have been inserted or the punctuation changed. The program consists of a "main sub" that calls specific subs one by one (one sub to insert spaces, then one for changing the quotation marks, etc.).
Since I have to search for specific characters (and not entire words) and mostly insert or delete things (and not replace), I tried to avoid "Find and Replace", because with this method I would see revision marks on elements that are not wrong but only help me to find the element to correct (for example: insertion of a space before "?" => If I use "Find and Replace", the author will see that "word?" is replaced by "word ?", as if the word was wrong, instead of just seeing that a space has been inserted).
The solution I found is to search an expression with wildcards first to isolate the problematic segments (for example: "[a-zA-Z0-9]?) and then search again inside this segment for the right position to insert something or the right element to change. That way, I can aim at the exact element I have to change and the revisions are marked exactly (see code below).
Sub Guillemets()
'Index
Dim vKey As Variant
'Variables for the forloop
Dim rRng As Range
Dim bFound As Boolean
Dim rChange As Range
'#############################---FOR LOOP: quotation marks---#############################
Dim dictGuill1 As Scripting.Dictionary
Set dictGuill1 = New Scripting.Dictionary
'German and English quotation marks: dictionnary to cover all different cases
dictGuill1.Add Key:=ChrW(8220) & "([a-zA-Z0-9])", Item:=Array(ChrW(8220), Chr(171) & "^s")
dictGuill1.Add Key:=ChrW(34) & "([a-zA-Z0-9])", Item:=Array(ChrW(34), Chr(171) & "^s")
dictGuill1.Add Key:=Chr(171) & " ", Item:=Array(" ", "^s")
dictGuill1.Add Key:=" " & Chr(187), Item:=Array(" ", "^s")
For Each vKey In dictGuill1.Keys
'Debug.Print (vKey)
Set rRng = Selection.Range.Duplicate
Set rChange = rRng.Duplicate
rChange.Collapse (wdCollapseStart)
'Debug.Print ("rRng: " & rRng)
'Debug.Print ("rChange: " & rChange)
Do
Set rRng = Selection.Range.Duplicate
'Debug.Print ("Selection dupliquée: " & rRng)
rRng.Find.Replacement.ClearFormatting
rRng.Find.ClearFormatting
rRng.Start = rChange.End
'To prevent the search to start over and get stuck on the same element
With rRng.Find
'Debug.Print ("rRng: " & rRng)
.Text = vKey
.MatchWildcards = True
.MatchWholeWord = True
.Forward = True
.Wrap = wdFindStop
bFound = .Execute
End With
'Debug.Print ("Found? " & bFound)
'Debug.Print ("Found index: " & rRng.Start)
If bFound Then
Set rChange = rRng.Duplicate
'Debug.Print ("Before correction: " & rChange)
With rChange.Find
.Text = dictGuill1(vKey)(0)
.Replacement.Text = dictGuill1(vKey)(1)
.Execute Replace:=wdReplaceOne
End With
'Debug.Print ("After correction: " & rChange)
End If
Loop While bFound = True
Next vKey
The forloop works and bring the expected result. The only thing is that it is slow, especially when the text is long. Plus, for some subs, I have dozens of cases and have to deal with the fact that some cases leads to an insertion of space (range.InsertBefore, range.InsertAfter) and others to the replacement of a character.
I would like to find a way to improve this algorithm. Is there a way to avoid this second search or to fuse it with the first one to reduce the complexity of the algorithm and still have "correct" revision marks?
Thanks in advance for your help/advice.

Iterate through paragraphs and trim spaces in MS Word

I need to create a macros which removes whitespaces and indent before all paragraphs in the active MS Word document. I've tried following:
For Each p In ActiveDocument.Paragraphs
p.Range.Text = Trim(p.range.Text)
Next p
which sets macros into eternal loop. If I try to assign string literal to the paragraphs, vba always creates only 1 paragraph:
For Each p In ActiveDocument.Paragraphs
p.Range.Text = "test"
Next p
I think I have a general misconception about paragraph object. I would appreciate any enlightment on the subject.
The reason the code in the question is looping is because replacing one paragraph with the processed (trimmed) text is changing the paragraphs collection. So the code will continually process the same paragraph at some point.
This is normal behavior with objects that are getting deleted and recreated "behind the scenes". The way to work around it is to loop the collection from the end to the front:
For i = ActiveDocument.Paragraphs.Count To 1 Step -1
Set p = ActiveDocument.Paragraphs(i)
p.Range.Text = Trim(p.Range.Text)
Next
That said, if the paragraphs in the document contain any formatting this will be lost. String processing does not retain formatting.
An alternative would be to check the first character of each paragraph for the kinds of characters you consider to be "white space". If present, extend the range until no more of these characters are detected, and delete. That will leave the formatting intact. (Since this does not change the entire paragraph a "normal" loop works.)
Sub TestTrimParas()
Dim p As Word.Paragraph
Dim i As Long
Dim rng As Word.Range
For Each p In ActiveDocument.Paragraphs
Set rng = p.Range.Characters.First
'Test for a space or TAB character
If rng.Text = " " Or rng.Text = Chr(9) Then
i = rng.MoveEndWhile(" " + Chr(9))
Debug.Print i
rng.Delete
End If
Next p
End Sub
You could, of course, do this in a fraction of the time without a loop, using nothing fancier than Find/Replace. For example:
Find = ^p^w
Replace = ^p
and
Find = ^w^p
Replace = ^p
As a macro this becomes:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Range
.InsertBefore vbCr
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchWildcards = False
.Text = "^p^w"
.Replacement.Text = "^p"
.Execute Replace:=wdReplaceAll
.Text = "^w^p"
.Execute Replace:=wdReplaceAll
End With
.Characters.First.Text = vbNullString
End With
Application.ScreenUpdating = True
End Sub
Note also that trimming text the way you're doing is liable to destroy all intra-paragraph formatting, cross-reference fields, and the like; it also won't change indents. Indents can be removed by selecting the entire document and changing the paragraph format; better still, modify the underlying Styles (assuming they've been used correctly).
Entering "eternal" loop is a bit unpleasant. Only Chuck Norris can exit one. Anyway, try to make a check before trimming and it will not enter:
Sub TestMe()
Dim p As Paragraph
For Each p In ThisDocument.Paragraphs
If p.Range <> Trim(p.Range) Then p.Range = Trim(p.Range)
Next p
End Sub
As has been said by #Cindy Meister, I need to prevent endless creation of another paragraphs by trimming them. I bear in mind that paragraph range contains at least 1 character, so processing range - 1 character would be safe. Following has worked for me
Sub ProcessParagraphs()
Set docContent = ActiveDocument.Content
' replace TAB symbols throughout the document to single space (trim does not remove TAB)
docContent.Find.Execute FindText:=vbTab, ReplaceWith:=" ", Replace:=wdReplaceAll
For Each p In ActiveDocument.Paragraphs
' delete empty paragraph (delete operation is safe, we cannot enter enternal loop here)
If Len(p.range.Text) = 1 Then
p.range.Delete
' remove whitespaces
Else
Set thisRg = p.range
' shrink range by 1 character
thisRg.MoveEnd wdCharacter, -1
thisRg.Text = Trim(thisRg.Text)
End If
p.LeftIndent = 0
p.FirstLineIndent = 0
p.Reset
p.range.Font.Reset
Next
With Selection
.ClearFormatting
End With
End Sub
I saw a number of solutions here are what worked for me. Note I turn off track changes and then revert back to original document tracking status.
I hope this helps some.
Option Explicit
Public Function TrimParagraphSpaces()
Dim TrackChangeStatus: TrackChangeStatus = ActiveDocument.TrackRevisions
ActiveDocument.TrackRevisions = False
Dim oPara As Paragraph
For Each oPara In ActiveDocument.StoryRanges(wdMainTextStory).Paragraphs
Dim oRange As Range: Set oRange = oPara.Range
Dim endRange, startRange As Range
Set startRange = oRange.Characters.First
Do While (startRange = Space(1))
startRange.Delete 'Remove last space in each paragraphs
Set startRange = oRange.Characters.First
Loop
Set endRange = oRange
' NOTE: for end range must select the before last characted. endRange.characters.Last returns the chr(13) return
endRange.SetRange Start:=oRange.End - 2, End:=oRange.End - 1
Do While (endRange = Space(1))
'endRange.Delete 'NOTE delete somehow does not work for the last paragraph
endRange.Text = "" 'Remove last space in each paragraphs
Set endRange = oPara.Range
endRange.SetRange Start:=oRange.End - 1, End:=oRange.End
Loop
Next
ActiveDocument.TrackRevisions = TrackChangeStatus
End Function

VBA insert carriage return between bold and unbold text

I have a word document that is a transcription of an interview. The moderators comments are in bold and respondents comments are not bold. It is a long continuous run on of bold and un-bold text. I need to add a carriage return so that there is a blank line between the moderator and the respondents questions. I found the code below to insert a carriage return between specific text, but I don't know how to change it to insert between bold and un-bold text. Any help is greatly appreciated!
Sub Test()
ActiveDocument.Paragraphs(1).Range.Text = "Foo" & Chr(11) & "Bar"
End Sub
This is what I came up with, it uses one sub to insert a break after bold text, and then calls another sub to do the same for nonbold text. I used the constant 'vbCrLf' which stands for Visual Basic Carriage Return Line Feed, it's equal to Chr(13) + Chr(10), and I believe it's the best practice for compatibility when inserting a line break in a document as opposed to Chr(11).
Sub InsertBreakAfterBold()
'Select entire document
Selection.WholeStory
'Make each .Method belong to Selection.Find for readability
With Selection.Find
'Set search criteria for bold font
.Font.Bold = True
'Find next occurrence
.Execute
'Each time bold text is found add a line break to the end of it then find the next one
Do While .Found
Selection.Text = Selection.Text + vbCrLf
.Execute
Loop
End With
'Repeat process for nonbold text
Call InsertBreakAfterNonbold
End Sub
Sub InsertBreakAfterNonbold()
Selection.WholeStory
With Selection.Find
.Font.Bold = False
.Execute
Do While .Found
Selection.Text = Selection.Text + vbCrLf
.Execute
Loop
End With
End Sub
Microsoft's VBA reference was my biggest resource to make this: https://learn.microsoft.com/en-us/office/vba/api/overview/word

Sub fails to find long sentences if there is not a space between the period and endnote citations (superscripts)

I have a simple loop (below) that looks for sentences over 30 words long. If found, it adds a comment box to the selected sentence. It worked fine in testing. Then I added some test endnote citations...and it fails to find the long sentences.
However, it only fails when there is no space between the period and the citation superscript. If I add a space, it finds it and works perfectly. The problem is, there is not suposed to be a space between the period and the citation, per the style guide I have to follow at work.
This related Stack thread discusses the need for a space after a period to delineate the end of a sentence. I am assuming the space must be directly after the period, because I have spaces in my citations like this 1, 2, 3
Question
How can I find instances of period+superscript (with no space like this --> This is a sentence.1, 2, 3) and add a space? Ideally I would like this to happen within the below loop so I can remove the space after the comment gets added.
Sub Comment_on_Long_Sentences ()
Dim iWords as Integer
iWords = 0
For Each MySent in ActiveDocument.Sentences
If MySent.Words.Count > iWords Then
MySent.Select
'find and delete space
ActiveDocument.Comments.Add Range:= Selection.Range, Text:= "Long Sentence: " & iWords & " words"
'put the space back
End if
Next MySent
End Sub
There appears to be issues in VBA when trying to access Sentences that end with a superscript character. Your code also has problems with non-declared variables, so I have no idea how it ever worked for you in the first place.
Try this following VBA routine, it works in my environment. Also notice the special handling that I found is required for 1st sentences in paragraphs and when that sentence ends with a superscript character.
Sub Comment_on_Long_Sentences()
Dim doc As word.Document, rng As word.Range, para As word.Paragraph
Dim i As Long
Set doc = ActiveDocument
For Each para In doc.Paragraphs
Debug.Print para.Range.Sentences.Count
For i = 1 To para.Range.Sentences.Count
Set rng = para.Range.Sentences(i)
If i = 1 And rng.Characters.First.Font.Superscript = True Then
rng.MoveStart word.WdUnits.wdSentence, Count:=-1
End If
If rng.words.Count > 30 Then
doc.Comments.Add Range:=rng, Text:="Long Sentence: " & rng.words.Count & " words"
End If
Next
Next
End Sub
Here is an alternative solution. Note the option explicit at the start. Its good VBA practice to put this at the top of every module.
The problem you have is very common. Find something then rather than do a replace, do some other non replace related stuff. The subs to add and remove spaces before citations implement this pattern and are well worth studying.
If you don't understand anything then in the VBA IDE just put your cursor on the relevant keyword and press F1. This will bring up the relevant MS help page.
Option explicit
Sub Comment_on_Long_Sentences()
Dim iWords As Integer
Dim my_sentence As Variant
iWords = 30
AddSpaceBeforeCitations
For Each my_sentence In ActiveDocument.Sentences
If my_sentence.Words.Count > iWords Then
my_sentence.Comments.Add Range:=my_sentence, Text:="Long Sentence: " & iWords & " words"
End If
Next my_sentence
RemoveSpaceBeforeCitations
End Sub
Sub AddSpaceBeforeCitations()
With ActiveDocument.Content
With .Find
.ClearFormatting
.Format = True
.Text = ""
.Wrap = wdFindStop
.Font.Superscript = True
.Execute
End With
Do While .Find.Found
With .Previous(unit:=wdCharacter, Count:=1).characters
If .Last.Text = "." Then
.Last.Text = ". "
End If
End With
.Collapse direction:=wdCollapseEnd
.Move unit:=wdCharacter, Count:=1
.Find.Execute
Loop
End With
End Sub
Sub RemoveSpaceBeforeCitations()
With ActiveDocument.Content
With .Find
.ClearFormatting
.Format = True
.Text = ""
.Wrap = wdFindStop
.Font.Superscript = True
.Execute
End With
Do While .Find.Found
With .Previous(unit:=wdCharacter, Count:=2).characters
If (.Last.Text = ".") Then
.Last.Next(unit:=wdCharacter, Count:=1).characters.Last.Text = vbNullString
End If
End With
.Collapse direction:=wdCollapseEnd
.Move unit:=wdCharacter, Count:=1
.Find.Execute
Loop
End With
End Sub
No matter what approach you take, any code that relies on the VBA .Sentence property or .Word property is going to produce unreliable results. That's because .Sentence has no idea what a grammatical sentence and .Word has no idea what a grammatical word is. For example, consider the following:
Mr. Smith spent $1,234.56 at Dr. John's Grocery Store, to buy 10.25kg of potatoes, 10kg of avocados, and 15.1kg of Mrs. Green's Mt. Pleasant macadamia nuts.
For you and me, that would count as one, 26-word sentence; for VBA it counts as 5 sentences containing 45 words overall. For an accurate word count, use .ComputeStatistics(wdStatisticWords). Sadly there is no .ComputeStatistics(wdStatisticSentences) equivalent for sentences.

Word VBA - select the rest of the word from Find

I try to use Find.FindText in Word VBA to find the first few letters of a word, then select the rest of that word. For example, if I have:
"Hello , AB-1234-123 check"
I would find "AB-" then select the rest of the word to get "AB-1234-123". I cannot use space as my MoveEndUntil(" ") because, sometimes, the word ends with a period.
So far, my code is
SearchString = "AB-"
With Rng.Find
Do While .Execute(FindText:=SearchString, Forward:=True) = True
Rng.MoveEndUntil (" ")
MsgBox (Rng.Text)
Loop
End With
You can still use MoveEndUntil. If you look at the Help topic for the method, you'll see that the first parameter, named CSet, can hold multiple characters. So it can look for a space as well as a period, a comma, a semicolon, etc.
While testing your code, it also came to my attention that, as it stands, it will go into a continuous loop - always finding only the first instance. So I've taken care of that with the Collapse method so that the next Find sequence starts immediately after the last "found" range.
Dim rng As word.Range
Dim SearchString As String
Set rng = ActiveDocument.content
SearchString = "AB-"
With rng.Find
Do While .Execute(findText:=SearchString, Forward:=True) = True
rng.MoveEndUntil (" .,;!")
MsgBox rng.Text
rng.Collapse wdCollapseEnd
Loop
End With
Perhaps you can select the found word portion, then expand it to the whole word?
Selection.Expand Unit:=wdWord