Comparing the first paragraph of two documents - vba

I'm trying to compare the first paragraph of two files with identical contents. One is the active file, one is a specified "Reference File."
The two ranges I'm using to store the contents keep coming up as not equal, even though using Documents.Compare to check the two files comes up with no differences.
Checking the variables as I step through it seems the two ranges' contents are identical at the time of comparison.
Apologies for inconsistent variable naming schemes.
Sub Test_Comparison()
Dim WorkingDoc As Document
Dim formatRef As Document
Dim rngDoc As Range
Dim refRnge As Range
Dim MacroViable As Boolean
Set WorkingDoc = Documents(ActiveDocument)
Set formatRef = Application.Documents.Open("[Reference FilePath]\ReferenceFile.docx", ReadOnly:=True, Visible:=False)
Set rngDoc = Documents(WorkingDoc).Paragraphs(1).Range
Set refRange = formatRef.Paragraphs(1).Range
If rngDoc.IsEqual(Range:=refRange) Then 'This is the bit not working. The contents of the variables seems identical. What gives??
MacroViable = True
End If
Documents("ReferenceFile.docx").Close
End Sub
I also tried setting the two ranges equal:
If refRange = rngDoc Then
MacroViable = True
End If

You can compare ranges but they won't be equal - as they are part of different documents (s. help for 'IsEqual': https://learn.microsoft.com/en-us/office/vba/api/word.range.isequal)
You have to compare the text of the two ranges:
If rngDoc.Text = refRange.Text Then
MacroViable = True
End If

Related

How can i change every occurence of a specific font ind a Word document?

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

How do I make the last two words of each footnote bold using Word VBA?

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

What's the best way to update LINK fields with path of current document using VBA?

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.

Retrieve info from Word tables

I've got a Word document with a section surrounded by hidden text tags < Answers > ...some tables... < /Answers >. A Word macro can return the range of the text between these tags (used to be bookmarks but they had to go).
What I want to do from Excel is open the Word document, get the range between the tags, iterate the tables in that block and retrieve some cells from each row. Those cell data is then written in some rows on a new Excel sheet.
I saw many Word/Excel automation but none that inspired me to retrieve that range between two pieces of text. Best would be to be able to run the Word macro RetrieveRange(strTagName, rngTextBlock) in Word to return the range in rngTextBlock for "Answers" but this seems impossible.
As background: the .docm file is an exam paper with answers and maximum points that I 'd like to transfer into Excel to contain gradings for each student.
Browsing though some more sites, I encountered a C# example that partly did what I needed: rather than using Word's SELECTION stick to ranges to find something. I now can find the text block between the two tags, but still fail on traversing its tables and table rows. No compiler error (and working in Word itself) but I must be missing an external link...
Function CreateSEWorksheet() As Boolean
' Find <ANSWERS> in Word Document, and traverse all tables and write them as rows in worksheet
Dim wdrngStart As Word.Range
Dim wdrngEnd As Word.Range
Dim wdrngAnswers As Word.Range
Dim wdTable As Word.Table
Dim wdRow As Word.Row
Dim strStr As String
Dim bGoOn As Boolean
' Following set elsewhere:
' Set WDApp = GetObject(class:="Application.Word")
' Set WDDoc = WDApp.Documents.Open(filename:="filespec", visible:=True)
Set wdrngStart = WDDoc.Range ' select entire document - will shrink later
Set wdrngEnd = WDDoc.Range
Set wdrngAnswers = WDDoc.Range
' don't use Word SELECT/SELECTION but use ranges instead when finding tags.
If wdrngStart.Find.Execute(findText:="<ANSWERS>", MatchCase:=False) Then
' found!
wdrngAnswers.Start = wdrngStart.End
If wdrngEnd.Find.Execute(findText:="</ANSWERS>", MatchCase:=False) Then
wdrngAnswers.End = wdrngEnd.Start
bGoOn = True
Else
' no closing tag found
bGoOn = False
End If
Else
'no opening tag found
bGoOn = False
End If
If bGoOn Then
For Each wdTable In wdrngAnswers.Tables
' ** below doesn't work anymore: object doesn't support this method **
For Each wdRow In wdTable
' as example, take column 4 of each row
strStr = wdRow.Cells(4).Range.Text
strStr = Left(strStr, Len(strStr) - 2) ' remove end of cell markers
Debug.Print strStr
Next
Next
CreateSEWorksheet = True
Else
CreateSEWorksheet = False
End If
End Function

Using Cross Reference Field Code to move selection to Target of Field Code

OP Update:
Thanks for the code KazJaw, it prompted me to change the approach I am trying to tackle the problem with. This is my current code:
Sub Method3()
Dim intFieldCount As Integer
Dim i As Integer
Dim vSt1 As String
intFieldCount = ActiveDocument.Fields.Count
For i = 1 To intFieldCount
ActiveDocument.Fields(i).Select 'selects the first field in the doc
Selection.Expand
vSt1 = Selection.Fields(1).Code
'MsgBox vSt1
vSt1 = Split(vSt1, " ")(2) 'Find out what the (2) does
MsgBox vSt1
ActiveDocument.Bookmarks(vSt1).Select 'Selects the current crossreference in the ref list
Next i
End Sub
Ok the so the Code currently finds the first field in the document, reads its field code and then jumps to the location in the document to mimic a CTRL+Click.
However, It does this for all types of fields Bookmarks, endnotes, figures, tables etc. I only want to find Reference fields. I thought I could deduce this from the field code but it turns out figures and bookmarks use the same field code layout ie.
A Reference/Boookmark has a field code {REF_REF4123123214\h}
A Figure cross ref has the field code {REF_REF407133655\h}
Is there an effective way to get VBA to distinguish between the two? I was thinking as reference fields in the document are written as (Reference 1) I could find the field and then string compare the word on the left to see if it says "Reference".
I was thinking of using the MoveLeft Method to do this
Selection.MoveLeft
But I can't work out how to move left 1 word from the current selection and select that word instead to do the strcomp
Or perhaps I can check the field type? with...
If Selection.Type = wdFieldRef Then
Do Something
End If
But I am not sure which "Type" i should be looking for.
Any advice is appreciated
All REF fields "reference" bookmarks. Word sets bookmarks on all objects that get a reference for a REF field: figures, headings, etc. There's no way to distinguish from the content of the field what's at the other end. You need to "inspect" that target, which you can do without actually selecting it. For example, you could check whether the first six letters are "Figure".
The code you have is inefficient - there's no need to use the Selection object to get the field code. The following is more efficient:
Sub Method3()
Dim fld As Word.Field
Dim rng as Word.Range
Dim vSt1 As String
ForEach fld in ActiveDocument.Fields
vSt1 = fld.Code
'MsgBox vSt1
vSt1 = Split(vSt1, " ")(2) 'Find out what the (2) does
MsgBox vSt1
Set rng = ActiveDocument.Bookmarks(vSt1).Range
If Left(rng.Text, 6) <> "Figure" Then
rng.Select
End If
Next
End Sub