7
\$\begingroup\$

I am using the following macro to insert a mini table of contents between two identical heading styles (e.g. Heading 1). It works fine on small documents, but is very slow on large documents.

Sub MiniTOCFields(HeadingNumber As Long, TOCLevel As Long)  
' Add mini TOC fields after all occurrences of a Heading style.  
' HeadingNumber defines which heading style to add miniTOCs to (e.g. Heading 1).  
' TOCLevel defines to what heading level the TOC shows (e.g. down to Heading 3). 

Dim bookmarkNumber As Long  
Dim bookmarkRange As Range  
Dim insertRange As Range  
Dim currentParagraph As Paragraph  
Dim paragraphNumber As Long  
Dim currentTOC As TableOfContents  

bookmarkNumber = 0  
paragraphNumber = 0  

Set bookmarkRange = ActiveDocument.Paragraphs(1).Range

' Cycle through each paragraph
For Each currentParagraph In ActiveDocument.Paragraphs
     paragraphNumber = paragraphNumber + 1

     ' Check for occurrences of Heading X
     If currentParagraph.Style = "Heading " & HeadingNumber Then

         ' If this is not the first occurrence, create a bookmark between this and the last one
         If bookmarkNumber > 0 Then
             ' Move the end of the bookmark to the end of the next paragraph
             bookmarkRange.End = ActiveDocument.Paragraphs(paragraphNumber - 1).Range.End
             ' Create bookmark
             ActiveDocument.Bookmarks.Add _
                 Name:="TOCHeading" & HeadingNumber & "_" & bookmarkNumber, _
                 Range:=bookmarkRange
         End If

     ' Move the start of the bookmark to the beginning of the next paragraph
     bookmarkRange.Start = ActiveDocument.Paragraphs(paragraphNumber + 1).Range.Start

     ' Add the TOC field         
     Set insertRange = currentParagraph.Range
     insertRange.Collapse direction:=wdCollapseEnd
     ' Add the new TOC field
     ActiveDocument.Fields.Add _
         Range:=insertRange, _
         Type:=wdFieldEmpty, _
         Text:=" TOC \h \o """ & HeadingNumber & "-" & TOCLevel & """ \b TOCHeading" & HeadingNumber & "_" & (bookmarkNumber + 1)
     bookmarkNumber = bookmarkNumber + 1
 End If

    ' Check for last paragraph and bookmark
    If paragraphNumber = ActiveDocument.Paragraphs.Count Then
        ' Move the end of the bookmark to the end of this paragraph
        bookmarkRange.End = currentParagraph.Range.End
        ' Create bookmark
        ActiveDocument.Bookmarks.Add _
            Name:="TOCHeading" & HeadingNumber & "_" & bookmarkNumber, _
            Range:=bookmarkRange
        bookmarkNumber = bookmarkNumber + 1
     End If

Next currentParagraph

' Update all TOC fields in document
For Each currentTOC In ActiveDocument.TablesOfContents
     currentTOC.Range.Select
     currentTOC.Update
Next currentTOC

End Sub
\$\endgroup\$
  • \$\begingroup\$ Is it the paragraph loop or the TOC updates that take too long? I don't think you need the .Range.Select call, just .Update should be enough to update the currentTOC. \$\endgroup\$ – Mathieu Guindon Jun 11 '14 at 23:29
  • \$\begingroup\$ The paragraph loop takes the bulk of the time. The TOC update is fairly quick. Thanks for the feedback. \$\endgroup\$ – David Jun 13 '14 at 3:09
8
\$\begingroup\$

Optimization:

You've done a good job of making sure you're not processing more than you have to, but there are a couple of things you can do here to improve performance. Instead of looping through all of the MiniTOCs you've just inserted, updating each one individually. Just update all of the fields in the entire document at once.

Replace This:

' Update all TOC fields in document
For Each currentTOC In ActiveDocument.TablesOfContents
     currentTOC.Range.Select
     currentTOC.Update
Next currentTOC

With This:

ActiveDocument.Fields.Update

It also turns out that there's no reason to to check every paragraph's style. We can find them all. Below is the uber simplified logic that you would need to implement. Also note that subs/functions should have a verb-noun type name. I changed it in my example.

Private Sub test()
    InsertMiniTOCFields wdStyleHeading1, 3
End Sub

Public Sub InsertMiniTOCFields(headingStyle As WdBuiltinStyle, TOClevel As Long)
    Dim bookmarkRange As Range
    Dim bookmarkNumber As Long
    Dim currentRange As Range
    Dim insertRange As Range
    Dim doc As Document

    Set doc = ActiveDocument
    Set currentRange = doc.Range 'start with the whole doc as the current range

    With currentRange.Find
        .Forward = True         'move forward only
        .Style = headingStyle   'the style given as an argument
        .Execute                'update currentRange to next found instance

        Do While .Found

            ' code to insert bookmarks

            ' Add the TOC field
            Set insertRange = currentRange 'I changed this assignment from currentParagraph to currentRange
            insertRange.Collapse direction:=wdCollapseEnd
            ' Add the new TOC field
            doc.Fields.Add _
                Range:=insertRange, _
                Type:=wdFieldEmpty, _
                Text:=" TOC \h \o """ & HeadingNumber & "-" & TOClevel & """ \b TOCHeading" & HeadingNumber & "_" & (bookmarkNumber + 1)
                bookmarkNumber = bookmarkNumber + 1

            ' update currentRange to next found instance
            .Execute
        Loop
    End With

    'update all newly inserted MiniTOCs
    doc.Fields.Update

End Sub

You can also turn screen updating off to pick up some more performance. You will need to introduce some error handling into your code if you decide to do that though. You'll want to make sure that screen updating is always turned back on when your sub exits; even if it errors. Truthfully, I imagine that this routine should really have an error handler anyway. Here is a pretty good error handling pattern to follow.

Other Notes:

  • You really shouldn't access the document with ActiveDocument each time. It's much better to store the reference in a variable. This way, you know for a fact that the document you started working on is the one you'll continue to work on.

    Dim doc as Document
    Set doc = ActiveDocument
    
  • Watch your line indentation. Ends should be at the same indentation level as the If they belong to.

  • Instead of declaring HeadingNumber as a long in your sub declaration, you should consider using WdBuiltInStyle.

So instead of this:

Sub MiniTOCFields(HeadingNumber As Long, TOCLevel As Long)  
    '......
    ' Check for occurrences of Heading X
     If currentParagraph.Style = "Heading " & HeadingNumber Then

You would have this:

Sub MiniTOCFields(headingStyle As WdBuiltInStyle, TOCLevel As Long)  
    '......
    ' Check for occurrences of Heading X
     If currentParagraph.Style = headingStyle Then

You write pretty clean code. I like your variable names. Very concise and easily understandable. You use line continuations judiciously, but it can be a slippery slope. Be careful with that.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your advice. I will try out your suggestions and see if I can cut the time down (over 30 minutes for one of my documents). \$\endgroup\$ – David Jun 13 '14 at 3:11
  • \$\begingroup\$ 30 minutes? How big is this document @David? \$\endgroup\$ – RubberDuck Jun 13 '14 at 3:13
  • \$\begingroup\$ Around 370 pages, with many headings and paragraphs. \$\endgroup\$ – David Jun 13 '14 at 4:06
  • \$\begingroup\$ I'll take another look to see if I can't find a better way to find all of the H1s. Hmmm... \$\endgroup\$ – RubberDuck Jun 13 '14 at 11:04
  • 1
    \$\begingroup\$ Thanks @ckuhn203! The macro now works in a few minutes. I just need to refine the code to insert the bookmarks, so that the next H1 is not included. \$\endgroup\$ – David Jun 17 '14 at 4:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.