I'm a long-time beneficiary of the helpful advice folks on here have offered to others (sincere thanks, everyone), but this is my first time actually posting. My VBA skills are lackluster at best.
This is driving me nuts: I have an Outlook macro that's supposed to comb through an email message before it's sent and replace citations to our policy documents, all of which have a prefix of "A&P-" with a link to that document.
It actually works great, except that it stubbornly refuses to capture citations if they're the first word in the email. So, for example, if I have a list of entries that looks like this:
A&P-I-A-1-100, A&P-II-B-2-200, A&P-III-C-3-300
the script will add links to the second and third entry (and as many more as I want to add) but skip the first one entirely. Weirdly, if I add any characters at all to the start of the email (even just hitting the space bar a couple of times) this doesn't happen. It doesn't impact citations at the start of subsequent paragraphs either. Just that first word.
Here's the code I'm using. Any help very, very appreciated!:
Sub Citation3()
Dim myInspector As Outlook.Inspector
Dim myObject As Object
Dim myItem As Outlook.MailItem
Dim rngStory As Word.Range
Dim myDoc As Word.Document
Dim mySelection As Word.Selection
Dim strItem As String
Dim strItem2 As String
Dim strLink As String
Set myInspector = Application.ActiveInspector
Set myObject = myInspector.CurrentItem
Set myDoc = myInspector.WordEditor
Set mySelection = myDoc.Application.Selection
For Each rngStory In ActiveDocument.StoryRanges
With mySelection.Find
.ClearFormatting
.Text = "A&P-*-*-[0-9]{3}"
.Replacement.Text = ""
.Wrap = wdFindContinue
.MatchWildcards = True
If mySelection.Find.Execute = True Then
While mySelection.Find.Execute
strItem = mySelection.Text
strItem2 = Replace(strItem, "A&P", "AP")
mySelection.Hyperlinks.Add Anchor:=mySelection.Range, _
Address:="http://www.google.com/" & strItem2 & "_document.shtml", _
TextToDisplay:=strItem
End If
End With
Next rngStory
End Sub
The problem lines are:
If mySelection.Find.Execute = True Then
While mySelection.Find.Execute
When they're isolated like that, you can probably see what's happening.
You're doing one Execute in the If statement which finds the first occurrence of the pattern you're looking for and then immediately do another Execute in the While loop that now finds the second occurrence of that pattern, therefore ignoring the first match completely. After that, the code loops around the While loop and picks up any remaining matches correctly.
To fix this, simply remove the If and corresponding End If lines, because you don't need them. The While loop will take care of everything.
Now, for some observations.
In your code I don't see Next statement for your For Each loop, or a End With statement for your With block, or a Wend statement for your While loop, or a End Sub for that matter. I can only assume you've cut and pasted only selected bits of your code, so I'll ignore that.
You should probably indent your code a little better to make it easier on others (and on you!) to read and identify any potential errors.
Finally, I'm a little confused by your matching pattern: "A&P-*-*-[0-9]{3}". It would work equally well as "A&P*[0-9]{3}" or "A&P-*-*-*-[0-9]{3}". In the latter, note that there are 3 dashed bits of text between the P and the 3-digit number (according to your format specification for citations), whereas your original pattern only accounted for 2 dashed bits.
Related
Would appreciate some help with this problem.
I need to find all occurrences of a string in a Word document. When the string is found some complicated editing is performed on it. Sometimes no editing is needed and the string is left untouched. When all that is taken care of, I continue looking for the next occurrence of the string. Until the end of the document.
I wrote a routine to do that :
It starts by defining a Range (myRange) that covers the whole document.
Then a Find.Execute is performed.
When an occurrence is found I do the editing work.
Meanwhile myRange has been automatically redefined to cover only the found region (this is well documented in the VBA WORD documentation > FIND Object).
Then I redefine myRange to cover the portion of the text from the end of the previous found region down to the end of the text.
I iterate this until the end of the document.
This routine works well EXCEPT when an occurrence of the string is found in a TABLE. Then it is impossible to redefine myRange to cover the region from the end of the previous found down to the end of the text. In the redefinition VBA insists on including the previous found region (actually the whole TABLE). So when I iterate it keeps finding the same occurrence again and again and looping for ever.
What follows is a simplified version of my routine. It does nothing it is just to illustrate the problem. If you run it on a document where the string "abc" appears you will see it running happily to completion. But if your document has an occurrence of "abc" in a TABLE the routine loops for ever.
Sub moreTests()
Dim myRange As Range
Dim lastCharPos As Integer
Set myRange = ActiveDocument.Range
lastCharPos = myRange.End
myRange.Find.ClearFormatting
With myRange.Find
.Text = "abc"
End With
While myRange.Find.Execute = True
'An occurrence of "abc" has been found
MsgBox (myRange.Text)
MsgBox ("Range starts at : " & myRange.Start & "; Range ends at : " & myRange.End)
'myRange has been redefined to encompass only the found region (the "abc" string)
'Perform whatever editing work is needed on the string myRange.Text ("abc")
'Now redefine myRange to cover the remainder of the document
myRange.Start = myRange.End
myRange.End = lastCharPos
MsgBox ("Range starts at : " & myRange.Start & "; Range ends at : " & myRange.End)
Wend
End Sub 'moreTests
I have several ways in mind to circumvent this problem. But none of them is simple, let alone 'elegant'. Does someone know if there is a 'standard' / 'proven' way of avoiding this problem ?
Many many thanks in advance.
i have following problem. Im currently creating a Macro that gets every font thats been used in a Word document. Afterwards it checks, if this font is even installed and changes the font into predefined fonts. (As the Microsoft auto-font-change in Word is pretty bad and changes my fonts into Comic Sans (no joke ...).
Everything works as intended except for one thing.
This here is the code i am using to exchange every occurence of the found
font in the document:
For i = 0 To UBound(missingFont)
For Each oCharacter In ActiveDocument.Range.Characters
If oCharacter.Font.name = missingFont(i) Then
oCharacter.Font.name = fontToUse
If InStr(missingFont(i), "bold") Then
oCharacter.Font.Bold = True
End If
If InStr(missingFont(i), "italic") Then
oCharacter.Font.Italic = True
End If
End If
Next oCharacter
Next i
So basically im checking every Character in my document and change it if needed. Now this only works for Characters that are not inside of textfields, the header or footer. How can i check every, EVERY, character inside of the Document?
First i've tried to use ActiveDocument.Range.Paragraphs instead of ActiveDocument.Range.Characters. I've also tried using the macro given here: http://www.vbaexpress.com/forum/showthread.php?55726-find-replace-fonts-macro but couldnt get this to work at all.
It's not clear what is meant by "textfield" as that could be any of five or six different things in Word...
But there is a way to access almost everything (excluding ActiveX controls) in a Word document by looping all StoryRanges. A StoryRange includes the main body of the document, headers, footers, footnotes, text ranges in Shapes, etc.
The following code sample demonstrates how to loop all the "Stories" in a document. I've put the code provided in the question in a separate procedure that's called from the "Stories" loop. (Note that I am not able to test, not having access to either the documents or relevant portions of code used in the question.)
Sub ProcessAllStories()
Dim doc as Word.Document
Dim missingFont as Variant
Dim myStoryRange as Word.StoryRange
'Define missingFont
Set doc = ActiveDocument
For Each myStoryRange In doc.StoryRanges
CheckFonts myStoryRange, missingFont
Do While Not (myStoryRange.NextStoryRange Is Nothing)
Set myStoryRange = myStoryRange.NextStoryRange
CheckFonts myStoryRange, missingFont
Loop
Next myStoryRange
End Sub
Sub CheckFonts(rng as Word.Range, missingFont as Variant)
Dim oCharacter as Word.Range
For i = 0 To UBound(missingFont)
For Each oCharacter In rng.Characters
If oCharacter.Font.name = missingFont(i) Then
oCharacter.Font.name = fontToUse
If InStr(missingFont(i), "bold") Then
oCharacter.Font.Bold = True
End If
If InStr(missingFont(i), "italic") Then
oCharacter.Font.Italic = True
End If
End If
Next oCharacter
Next i
End Sub
I've changed balloon comments to footnotes, taking the author's name too. I need the author's name to be in bold but I can't get my code to read the footnotes. My problem is in setting : oFootnote
I've tried calling on the strAuthor and making that bold but because it is no longer a comment.author I can no longer set it as it's now in the footnote. I've tried many examples on the internet but I just can't get them to work:
StackOverflow's How do i make a string bold; Insert bold text into Word using VBA
also
Set oFootnote = oDoc.Footnotes.Add(Range:=Selection.Range, Text:="Some text")
I am a trainee so please don't judge me too harshly
'Convert comments to footnotes with Author name in bold
Dim i As Long
Dim oDoc As Document
dim oComment as Comments
Dim oFootnote As Footnotes
'Document is the ActiveDocument
Set oDoc = Application.ActiveDocument
'the author's name needs to be bold (the last two words in each footnote)
Set oFootnote = oDoc.Footnotes
With oFootnote
Selection.Range.Words.Last.Words (2)
'Make the last two words bold'
With Selection.Find
.Text = ""
.Replacement.Text = ""
.Font.bold = True
End With
End With
Selection.Find.Execute
'Set oFootnote = Nothing
Next
I tried
Set oFootnote = oDoc.Footnotes Range:=Selection.Words.Last.Words(2)
but it doesn't like "Range:= onwards" so I did
Selection.Range.Words.Last.Words (2) invalid use of a property
There is usually more than one way to achieve something like this, but the key is usually to work with a dedicated Range object.
In the code below, that bases on the code in the question, the Range object is assigned to each individual Footnote object in a loop of the Footnotes. It is then collapsed to its end-point and the start extended backwards by two words. (To better understand how this works, think of selecting the footnote, pressing right-arrow, then pressing ctrl+shift+left arrow twice to select the last two words.)
Dim oDoc As Document
Dim oFootnotes As Footnotes
Dim Ftnote As Footnote
Dim rngFootnote As Word.Range
'Document is the ActiveDocument
Set oDoc = Application.ActiveDocument
'the author's name needs to be bold (the last two words in each footnote)
Set oFootnotes = oDoc.Footnotes
For Each Ftnote In oFootnotes
Set rngFootnote = Ftnote.Range
rngFootnote.Collapse wdCollapseEnd
rngFootnote.MoveStart wdWord, -2
rngFootnote.Font.Bold = True
Next
Note that the reason for one of the errors in the question is because Words.Last returns a Range object containing the last word. Since it contains only one word - the last - Words(2) can't find anything it can work with.
The reason for the other error is that it's not possible to assign a Range to a Footnote or Footnotes object. They're different things, entirely...
Not super familiar with word objects, but try this. Worked for my couple of tests.
Basically it loops through all foot notes. And uses the index of the word to set that word's bold property to true.
Sub Test()
Dim oFootNote As Footnote
Dim oLastIndex As Long
For Each oFootNote In ActiveDocument.Footnotes
oLastIndex = oFootNote.Range.Words.Count
If oLastIndex > 2 Then
oFootNote.Range.Words(oLastIndex).Bold = True
oFootNote.Range.Words(oLastIndex - 1).Bold = True
End If
Next
End Sub
I'm trying to clean up my Word document using VBA.
What i need to do is to find a specific word (usually a website) then select the line it is in and then select and then remove text line above(only 1 line), the lines under that website line as well (sometimes more than 2 - if the text is longer). I'll try to show you how the line looks now.
Something happend at someplace!
website.com 08.01.2019
Something happend at someplace and it was a bad person doing it.
He used spaces instead of tabs in his code.
TAG-important stuff
The website 99% of times doesn't show in the 1st line, so im trying to find the 2nd line.
There are other websites and texts i would like to keep (so it would skip newsbetter.com)
In every document there are about 30-100 pharagraphs like the one I've typed earlier (the ones do delete)
I've been searching on the internet for a possible solution but they usually are for Excel. I think that strings are not working for me here.
Sub ScratchMacroII()
Dim oRng As Word.Range
Set oRng = ActiveDocument.Range
With oRng.Find
.Text = "news.pl"
While .Execute
While oRng.Find.Found
oRng.Select
Selection.Expand Unit:=wdParagraph
Selection.Delete
Wend
End With
End Sub
I expected the result to delete the whole pharagraph, but it justs deletes one line and leaves the other ones. I need some pointers since I'm new at VBA.
The following code, based on the sample in the question, searches the term from the beginning to the end of the document. When found, the paragraphs following and preceding the term are deleted. The search Range is then set to the document content following the found instance so that the same instance is not picked up repeatedly.
Note that I included Find.Wrap = wdFindStop to prevent the code from cycling through the document again. It's also necessary to repeat the Execute method within the loop, rather than trying to loop on it. While...Wend is an old type of loop; preferred is Do While...Loop.
Sub ScratchMacroII()
Dim oRng As Word.Range
Dim para As Word.Paragraph
Dim found As Boolean
Set oRng = ActiveDocument.Range
With oRng.Find
.Text = "news.pl"
.wrap = wdFindStop
found = .Execute
Do While found
Set para = oRng.Next(wdParagraph, 1).Paragraphs(1)
para.Range.Delete
Set para = oRng.Next(wdParagraph, -1).Paragraphs(1)
para.Range.Delete
oRng.Collapse wdCollapseEnd
oRng.End = ActiveDocument.content.End
found = oRng.Find.Execute
Loop
End With
End Sub
So I have 4 documents, 3 excel spreadsheets and 1 document. All four are in the same directory "test." All four will always remain in the same directory no matter what. However, the goal of the document is to build a report out of the three spreadsheets for multiple properties. This means that the paths would be different for every different computer that it was used on. I want a macro that will auto-update the LINK fields with the current path but I'm running into some trouble.
So far I have
SendKeys "%{F9}"
Dim path As String
path = ActiveDocument.path
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = "C:\\Users\\Gianni\\Desktop"
.Replacement.Text = path
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
SendKeys "%{F9}"
There are two problems with this from what I can tell. If I just view the fields manually and run the code without the first SendKeys command, the find & replace works. With the first SendKeys command, however, the code doesn't replace the text with the new path. Still, the path that pastes ends up breaking the link anyway. How do I go about fixing these?
Often, it's better in Word to work with the underlying object model of a Word document, than trying to reproduce exactly what you do as a user. Understanding how Word works, from a user point-of-view is very important and there are many things you you're able to do by converting those steps into a macro. But digging into the object model is generally faster and more accurate.
Changing a LINK field code is one of those things - and like many things, there's more than one way to go about it. Here are two possibilities.
The first is close to how you're approaching it, by manipulating the field code. Note that it's not necessary, using VBA, to actually display the field code. The object model lets you manipulate it "behind the scenes".
This procedure loops all the Fields in the document, checks whether each is a LINK field. If it is, the alternate path is substituted in the field code for the original path using the VBA Replace function, then this is written to the field code.
'Assumes the linked Excel workbook is an inline shape
Sub ChangePathInLinkField()
Dim doc As word.Document
Dim fld As word.Field
Dim strSearchPath As String
Dim strReplacePath As String
Dim strNewFieldCode As String
Set doc = ActiveDocument
strSearchPath = "C:\\Users\\[user name]\\Documents\\SampleChart.xlsx"
strReplacePath = "C:\\Test\\SampleChart.xlsx"
For Each fld In doc.Fields
If fld.Type = wdFieldLink Then
strNewFieldCode = Replace(fld.code.Text, strSearchPath, strReplacePath)
fld.code.Text = strNewFieldCode
End If
Next
doc.Fields.Update
End Sub
The second procedure shows how the link path can be changed for Shapes as well as InlineShapes (if you have a Shape you can't see the LINK field). It can also be used only on InlineShapes, of course. This loops the collection, checks whether the object is a linked OLE object and, if it is, changes the path.
Which one to use will depend on your situation - test them both and decide based on that.
'Alternate: works with OLE object
Sub ChangePathInLinkedObject()
Dim doc As word.Document
Dim ils As word.InlineShape
Dim shp As word.Shape
Dim strReplacePath As String
Dim i As Long
Set doc = ActiveDocument
strReplacePath = "C:\Users\Cindy Meister\Documents\SampleChart.xlsx"
strReplacePath = "C:\Test\SampleChart.xlsx"
'For Each doesn't work because updating the field
'destroys the object, so it loops over the same object
'For this reason it's also necessary to work backwards through the document
For i = doc.InlineShapes.Count To 1 Step -1
Set ils = doc.InlineShapes(i)
If ils.Type = wdInlineShapeLinkedOLEObject Then
ils.LinkFormat.SourceFullName = strReplacePath
ils.LinkFormat.Update
End If
Next
For i = doc.shapes.Count To 1 Step -1
Set shp = doc.shapes(i)
If shp.Type = msoLinkedOLEObject Then
shp.LinkFormat.SourceFullName = strReplacePath
shp.LinkFormat.Update
End If
Next
End Sub
Instead of using SendKeys you can show field codes with:
ActiveDocument.ActiveWindow.View.ShowFieldCodes = True
and to show field values
ActiveDocument.ActiveWindow.View.ShowFieldCodes = False
That may help with your first problem.
To see how to implement relative paths in Word, check out the solution I've posted at:
http://windowssecrets.com/forums/showthread.php/154379-Word-Fields-and-Relative-Paths-to-External-Files
Since you're working with LINK fields, you'll need the macro solution there.