Automating Mail Merge - vb.net

I need to dynamically generate word documents using text from an Access database table. The caveat here is that some of the text coming from the database will need to be modified into MergeFields. I am currently using Interop.Word (VBA Macro) and VB.NET to generate the document.
My steps so far look like this:
Pull standard .docx Template
Fill In template with pre-defined filler text from table
Add MergeFields by replacing pieces of the filler text with actual mergefields
Attach Datasource and execute Mail Merge
After testing, I noticed that I cannot simply store MergeFields into the access database as a string, they do not feed over into the actual document. What I am thinking then is creating a table of known strings that should be replaced with MergeFields by the coding.
For Example:
Step 2 will insert "After #INACTIVE_DATE# your account will no longer be active." which will be in the database text.
Step 3 will find/replace #INACTIVE_DATE# with a MergeField «INACTIVE_DATE». I am using Word Interop to do this, so theoretically I can loop through the document.
I wasnt able to do a "Find And Replace" from text to MergeField, so how should I go about implementing this?
Tagging VBA additionally as I am seeking a "VBA" style answer (Word Interop).

You've left out quite a lot of detail, so I'll go about answering this in somewhat general terms.
What you want to do is definitely achievable. Two possible solutions immediately come to mind:
Replacing ranges using Find
Inserting tokens using TypeText
Replacing ranges using Find
Assuming the text has already been inserted, you can search the document for the given pattern and replace it with a merge field. For instance:
Sub FindInsertMerge()
Dim rng As Range
Set rng = ActiveDocument.Range
With rng.Find
.Text = "(\#*\#)"
.MatchWildcards = True
.Execute
If .Found Then
ActiveDocument.MailMerge.Fields.Add rng, Mid(rng.Text, 2, Len(rng.Text) - 2)
End If
End With
End Sub
Will find the first occurence of text starting with #, matches any string and ends with #. The contents of the found range will then be replaced by a merge field. The code above can easily be extended to loop for all fields.
Inserting tokens using TypeText
While I would normally advice against using Selection to insert data, this solution makes things simple. Say you have a target range, rng, you tokenize the database text to be inserted, select the range, start typing and whenever a designated mail merge field is found, insert a field instead of the text.
For instance:
Private Sub InsertMergeText(rng As Range, txt As String)
Dim i As Integer
Dim t As String
Dim tokens() As String
tokens = Split(txt, " ")
rng.Select
For i = 0 To UBound(tokens)
t = tokens(i)
If Left(t, 1) = "#" And Right(t, 1) = "#" Then
'Insert field if it's a mail merge label.
ActiveDocument.MailMerge.Fields.Add Selection.Range, Mid(t, 2, Len(t) - 2)
Else
'Simply insert text.
Selection.TypeText t
End If
'Insert the whitespace we replaced earlier.
If i < UBound(tokens) Then Selection.TypeText " "
Next
End Sub
Call example:
InsertMergeText Selection.Range, "After #INACTIVE_DATE# your account will no longer be active which will be in the database text."

Related

Word VBA - format and re-link footnotes and endnotes in hyperlinks

I need to find footnote and endnote marks (in the body, to format them and re-link them) etc, in a macro I have written to clean up documents imported from the web.
My macro works fine, except where part of the find string is hyperlink display text and part is not.
Searching for ([a-z])([0-9]) (to locate endnote marks in the body, as they are not formatted as endnotes) does not find them because [0-9] is hyperlink display text and [a-z] is not.
My question here: how to search for [normal string] adjacent to [hyperlink display text string]?
If found, the next step is how to replace the hyperlink display text (format endnotes superscript, delete the para mark) keeping it in the hyperlink (I can work on that - maybe the subject of another question?)
You could, of course, simply convert your hyperlinks to superscripted numbers then use whatever other code you have to replace the superscripted numbers with endnotes.
Sub Demo()
Dim h As Long
With ActiveDocument
For h = .Hyperlinks.Count To 1 Step -1
With .Hyperlinks(h)
If IsNumeric(.TextToDisplay) = True Then
With .Range
If .Characters.First.Previous Like "[a-z]" Then
.Fields.Unlink
.Font.Reset
.Font.Superscript = True
End If
End With
End If
End With
Next
End With
End Sub
Depending on what you're doing, you could simply insert your endnoting code into the inner If ... End If construct in place of the font manipulations.

Batch add formatted autocorrects with VBA in Word

I use a long Excel spreadsheet containing incorrect and correct terms to check consistency between documents (e.g. anti-citrullinated is always hyphenated). I've added quite a few of these as autocorrect entries via the AutoCorrect Options feature in Word but it's time-consuming .
I came across the following code that will add long lists of autocorrects.
Sub BatchAddAutoCorrectEntries()
Dim objTable As Table
Dim objOriginalWord As Cell
Dim objOriginalWordRange As Range
Dim objReplaceWordRange As Range
Dim nRowNumber As Integer
Set objTable = ActiveDocument.Tables(1)
nRowNumber = 1
For Each objOriginalWord In objTable.Columns(1).Cells
Set objOriginalWordRange = objOriginalWord.Range
objOriginalWordRange.MoveEnd Unit:=wdCharacter, Count:=-1
Set objReplaceWordRange = objTable.Cell(nRowNumber, 2).Range
objReplaceWordRange.MoveEnd Unit:=wdCharacter, Count:=-1
AutoCorrect.Entries.Add Name:=objOriginalWordRange.Text, Value:=objReplaceWordRange.Text
nRowNumber = nRowNumber + 1
Next objOriginalWord
MsgBox ("All autocorrect items in the table1 are added.")
End Sub
It doesn't preserve any formatting: super- or subscripts, etc. Formatting autocorrect entries are stored in the Normal.dotm file and not in the regular .acl file so I haven't been able to figure out a way around this.
In a similar post, someone suggested a Find and Replace macro but Find and Replace doesn't allow me to replace with super- or subscripts.
There are two methods of adding Auto Correct Entries, Add and AddRichText. It is this second one that you use for formatted entries.
When faced with an issue like this my first resort is to check the Object Brower in the VBA editor (press F2 to display) to see what methods and properties may be available. My next step is to look them up in the VBA technical reference, aka Help, to check the usage.
If the problem is just sub/superscribt, then you could use uni-codes. Those are also available in autocorrect. Fx writing the unicodes ₁₂₃₄₅₆₇₈₉ instead of using formating on a normal 2. Most (but not all) characters exist in super and sub unicode.
The program is not working. It is giving an error message
Compile Error Expected Function or Variable
It is showing the following line as error
Autocorrect.Entries.Add Name:=objOriginalWordRange.Text, Value:=objReplaceWordRange.Text

Converting Headings to Bookmarks

I'm using Office 2016. I'd like to make a macro that loops through each heading in a document, and then creates a bookmark at the heading's location using the heading text (modified as necessary) as the bookmark name. Most of the headings are in X.X.X.X format, such as "3.3.4.1. sometexthere".
I'm still a beginner using VBA, but after a lot of googling I managed to adapt some Frankenstein code that almost works:
Sub HeadingsToBookmarks()
Dim heading As Range
Set heading = ActiveDocument.Range(Start:=0, End:=0)
Do
Dim current As Long
current = heading.Start
Set heading = heading.GoTo(What:=wdGoToHeading, Which:=wdGoToNext)
If heading.Start = current Then
Exit Do
End If
ActiveDocument.Bookmarks.Add MakeValidBMName(heading.Paragraphs(1).Range.Text), Range:=heading.Paragraphs(1).Range
Loop
End Sub
Function MakeValidBMName(strIn As String)
Dim pFirstChr As String
Dim i As Long
Dim tempStr As String
strIn = Trim(strIn)
pFirstChr = Left(strIn, 1)
If Not pFirstChr Like "[A-Za-z]" Then
strIn = "Section_" & strIn
End If
For i = 1 To Len(strIn)
Select Case Asc(Mid$(strIn, i, 1))
Case 49 To 58, 65 To 90, 97 To 122
tempStr = tempStr & Mid$(strIn, i, 1)
Case Else
tempStr = tempStr & "_"
End Select
Next i
tempStr = Replace(tempStr, " ", " ")
tempStr = Replace(tempStr, ":", "")
If Right(tempStr, 1) = "_" Then
tempStr = Left(tempStr, Len(tempStr) - 1)
End If
MakeValidBMName = tempStr
End Function
This code almost works, and makes appropriate bookmarks at some of the headings, but not all. Can anyone help me figure out what I need to fix here, or have other recommendations on how else I can clean up this code?
Edit: More information: The code above converts the first 5 or so headings in the document I've been testing it on, along with a few others scattered around. The second half of the code, which does the actual conversion, seems to work fine- the problem is located in the section that loops through each heading. The second half converts unusable characters to those that work with the requirements for bookmark names, and adds "Section_" to the beginning of bookmarks / headings that start with numbers (as bookmarks aren't allowed to start with numbers).
My goal is to be able to hyperlink to all sections within the document that has headings from a different word document. The standard Table of Contents creator allows only for links to be built within the same document, as far as I can tell. I'm aware that when word saves to PDF, it can convert headings to bookmarks; I would like to be able to do the same thing but retain the document in word format.
I unfortunately can't use the built in numbering. I'm working with documents that are already created and have a set and specific format.
You haven't described why you want bookmarks, or how a future user of the document would use/access the bookmarks.
MS Word has a number of built in features that act as bookmarks. The best way to do this is to use Styles. The built-in heading styles allow for some native navigation functionality (Word's own hidden bookmarks). Also, don't re-invent the wheel - use built in numbering.
This requires some document discipline. Use headings only for headings, and body text for the non-heading text.
The benefits make the discipline worth it. You can easily create tables of contents that use the headings (or even some of your custom styles), and headings show up in the navigation pane. When you save to PDF, you can use the headings as bookmarks in the PDF (show up on the Reader navigation bar).
Note that what I have described doesn't even touch VBA.
If you use set styles for your headings and you want to do a little more than what you can do natively, then you can simply:
Loop through all paragraphs in the document
See if that paragraph is set to your heading style
Place a bookmark (valid bookmark name!) over that paragraph
I have left the actual coding to you, but I think you will find it easy to do based on the pseudo code above. My pseudo code loop is not the only way to find the paragraphs, but it is the easiest to visualise.
Once you use the simplified method above and built-in numbering, you should find that you can modify your ValidBMName function - simplifying it. But, as noted and depending on why you want bookmarks, you may be able to avoid VBA altogether.
This code works for me:
Sub HeadingsToBookmarks()
Dim heading As Range
Set heading = ActiveDocument.Range(Start:=0, End:=0)
Do
Dim current As Long
current = heading.Start
Set heading = heading.GoTo(What:=wdGoToHeading, Which:=wdGoToNext)
If heading.Start = current Then
Exit Do
End If
'This is the part I changed: ListFormat.ListString
ActiveDocument.Bookmarks.Add MakeValidBMName(heading.Paragraphs(1).Range.ListFormat.ListString), Range:=heading.Paragraphs(1).Range
Loop
End Sub

Get all cross references in word with VBA

I have quite a large word document (> 400 pages) with lots of cross references to headings. So far, I have always referred to the title of the heading, but now I would like to change that and refer to the page the heading resides on.
I didn't find a solution to this via the GUI (except manual treatment, of course), so I was looking into writing some VBA. Unfortunately, I have only found a way to list all targets that can be cross referenced (via GetCrossReferenceItems), but I need a way to access the actual cross reference field.
Can you help me with that? Is a cross reference field the same as a hyperlink?
Cross-references are fields in a Word document, and can be accessed via the Fields collection (ActiveDocument.Fields). You can loop through them like any other collection and check their types to see if it's one you want to work on. It looks like cross-references to text are type 3 (wdFieldRef) and cross-references to page numbers are type 37 (wdFieldPageRef). Changing fields can be a little tricky; the following should get you started:
Sub ChangeFields()
Dim objDoc As Document
Dim objFld As Field
Dim sFldStr As String
Dim i As Long, lFldStart As Long
Set objDoc = ActiveDocument
' Loop through fields in the ActiveDocument
For Each objFld In objDoc.Fields
' If the field is a cross-ref, do something to it.
If objFld.Type = wdFieldRef Then
'Make sure the code of the field is visible. You could also just toggle this manually before running the macro.
objFld.ShowCodes = True
'I hate using Selection here, but it's probably the most straightforward way to do this. Select the field, find its start, and then move the cursor over so that it sits right before the 'R' in REF.
objFld.Select
Selection.Collapse wdCollapseStart
Selection.MoveStartUntil "R"
'Type 'PAGE' to turn 'REF' into 'PAGEREF'. This turns a text reference into a page number reference.
Selection.TypeText "PAGE"
'Update the field so the change is reflected in the document.
objFld.Update
objFld.ShowCodes = True
End If
Next objFld
End Sub

Distinguishing Table of Contents in Word document

Does anyone know how when programmatically iterating through a word document, you can tell if a paragraph forms part of a table of contents (or indeed, anything else that forms part of a field).
My reason for asking is that I have a VB program that is supposed to extract the first couple of paragraphs of substantive text from a document - it's doing so by iterating through the Word.Paragraphs collection. I don't want the results to include tables of contents or other fields, I only want stuff that a human being would recognize as a header, title or a normal text paragraph. However it turns out that if there's a table of contents, then not only the table of contents itself but EVERY line in the table of contents appears as a separate item in Word.Paragraphs. I don't want these but haven't been able to find any property on the Paragraph object that would allow me to distinguish and so ignore them (I'm guessing I need the solution to apply to other field types too, like table of figures and table of authorities, which I haven't yet actually encountered but I guess potentially would cause the same problem)
Because of the limitations in the Word object model I think the best way to achieve this would be to temporarily remove the TOC field code, iterate through the Word document, and then re-insert the TOC. In VBA, it would look like this:
Dim doc As Document
Dim fld As Field
Dim rng As Range
Set doc = ActiveDocument
For Each fld In doc.Fields
If fld.Type = wdFieldTOC Then
fld.Select
Selection.Collapse
Set rng = Selection.Range 'capture place to re-insert TOC later
fld.Cut
End If
Next
Iterate through the code to extract paragraphs and then
Selection.Range = rng
Selection.Paste
If you are coding in .NET this should translate pretty closely. Also, this should work for Word 2003 and earlier as is, but for Word 2007/2010 the TOC, depending on how it is created, sometimes has a Content Control-like region surrounding it that may require you to write additional detect and remove code.
This is not guaranteed, but if the standard Word styles are being used for the TOC (highly likely), and if no one has added their own style prefixed with "TOC", then it is OK. This is a crude approach, but workable.
Dim parCurrentParagraph As Paragraph
If Left(parCurrentParagraph.Format.Style.NameLocal, 3) = "TOC" Then
' Do something
End If
What you could do is create a custom style for each section of your document.
Custom styles in Word 2003 (not sure which version of Word you're using)
Then, when iterating through your paragraph collection you can check the .Style property and safely ignore it if it equals your TOCStyle.
I believe the same technique would work fine for Tables as well.
The following Function will return a Range object that begins after any Table of Contents or Table of Figures. You can then use the Paragraphs property of the returned Range:
Private Function GetMainTextRange() As Range
Dim toc As TableOfContents
Dim tof As TableOfFigures
Dim mainTextStart As Long
mainTextStart = 1
For Each toc In ActiveDocument.TablesOfContents
If toc.Range.End > mainTextStart Then
mainTextStart = toc.Range.End + 1
End If
Next
For Each tof In ActiveDocument.TablesOfFigures
If tof.Range.End > mainTextStart Then
mainTextStart = tof.Range.End + 1
End If
Next
Set GetMainTextRange = ActiveDocument.Range(mainTextStart, ActiveDocument.Range.End)
End Function