0

I am hoping someone here can help me. I have a document of 365 cover letters that I need to split into individual documents and save them with the name in the address block. Can someone help me modify this code? thought I figure it out but I am still getting errors.

This is the code that I have tried but not working.

Sub SplitIntoPages()
    Dim docMultiple As Document
    Dim docSingle As Document
    Dim rngPage As Range
    Dim iCurrentPage As Integer
    Dim iPageCount As Integer
    Dim strNewFileName As String

    Application.ScreenUpdating = False 'Makes the code run faster and reduces screen _
    flicker a bit.
    Set docMultiple = ActiveDocument 'Work on the active document _
    '(the one currently containing the Selection)
    Set rngPage = docMultiple.Range 'instantiate the range object
    iCurrentPage = 1
    'get the document's page count
    iPageCount = docMultiple.Content.ComputeStatistics(wdStatisticPages)

    Do Until iCurrentPage > iPageCount
        If iCurrentPage = iPageCount Then
            rngPage.End = ActiveDocument.Range.End 'last page (there won't be a next page)
        Else
'Find the beginning of the next page
'Must use the Selection object. The Range.Goto method will not work on a page
            Selection.GoTo wdGoToPage, wdGoToAbsolute, iCurrentPage + 1
        'Set the end of the range to the point between the pages
             rngPage.End = Selection.Start
        End If

    rngPage.Copy 'copy the page into the Windows clipboard
    Set docSingle = Documents.Add 'create a new document
    docSingle.Range.Paste 'paste the clipboard contents to the new document
    'remove any manual page break to prevent a second blank
    docSingle.Range.Find.Execute Findtext:="^m", ReplaceWith:=""
    'build a new sequentially-numbered file name based on the original multi-paged file name and path

    Set objFileName = objNewDoc.Range(Start:=10, End:=30 & ".doc") 'docSingle.SaveAs objNewDoc.Range(Start:=10, End:=30 & ".doc") 'save the new single-paged document

    iCurrentPage = iCurrentPage + 1 'move to the next page
    docSingle.Close 'close the new document
    rngPage.Collapse wdCollapseEnd 'go to the next page
    Loop 'go to the top of the do loop

    Application.ScreenUpdating = True 'restore the screen updating

    'Destroy the objects.
    Set docMultiple = Nothing
    Set docSingle = Nothing
    Set rngPage = Nothing
End Sub

This is the original code that I used but is naming the individual docs as the name of the original doc with the page number at the end.

I expect to get a code that will name the file as the name in the address block. Any help works thank you in advance.

Miles Fett
  • 711
  • 4
  • 17
  • This is what Word's mail merge feature is designed for. I'm not clear why you're trying to reinvent this. https://support.microsoft.com/en-us/help/318118/how-to-use-the-mail-merge-feature-in-word-to-create-and-to-print-form – John Korchok Sep 09 '19 at 20:38
  • It combined all into one document, i am trying to seperate them now. – Jennifer Vazquez Sep 09 '19 at 20:43
  • I think there are several ways to solve your problem. (1) Take the files you get from the code and rename the resulting files. Do you have a list of the address block = recipients = file names? Then it's just a simple "rename file problem"! (2) Are pdf files possible as a result? Is every cover letter one page? If yes for both: convert the big file to pdf and split the pdf into single page files. Then again rename the files. – simple-solution Sep 09 '19 at 20:46
  • Do you start from a mail merge / serial letter? Is this a solution? see https://stackoverflow.com/questions/12594828/how-to-split-a-mail-merge-and-save-files-with-a-merge-field-as-the-name – simple-solution Sep 09 '19 at 20:47
  • And how should the code know what the "Address Block" is? – Cindy Meister Sep 09 '19 at 21:12

1 Answers1

0

@JenniferVazquez You have a number of problems with your code.

  1. You have a number of undeclared variables. This is likely because you haven't put 'Option explicit' as the first line of your module. Always always put option explicit as the first line of any modules or classes you write.

Then always do Debug.Compile before trying to run your code.

In addition to 'Option Explicit' and doing Debug.COmpile, if your company allows you, then install the fantabulous RubberDuck addin and pay close attention to the Code Inspections this Addin can provide.

  1. You haven't actually given a name to your new file . In fact its unlikely that your code will run as given above.

  2. You should really have also provided a sample document against which we could check your code to see if we get the same results as yourself, and also to help us when we write new code or update your code.

I have written some code which I think does what your original code is trying to do. In my code I've split activities into different functions. If I tried harder I could split my code into even more smaller functions but I think you'll see the general idea.

Thank for putting lots of comments in your code, it did make it easier to work out what you are trying to do.

I hope the code below helps you.

Option Explicit


Public Sub Test()

    SplitIntoIndividualLetters ActiveDocument

End Sub

Public Sub SplitIntoIndividualLetters(Optional ByRef ipDocument As Word.Document = Nothing)

Dim myCurrentLetterRange                As Word.Range
Dim myClientName                        As String


    Set myCurrentLetterRange = GetNextLetterRange(IIf(ipDocument Is Nothing, ActiveDocument, ipDocument))
    Do While Not myCurrentLetterRange Is Nothing

        myClientName = GetClientname(myCurrentLetterRange.Duplicate)
        If Not TrySaveIndividualLetter(myCurrentLetterRange.Duplicate, myClientName) Then

            MsgBox "Something went wrong, the letter for " & myClientName & " was not saved", vbOKOnly
            Stop
        End If
        Set myCurrentLetterRange = GetNextLetterRange(IIf(ipDocument Is Nothing, ActiveDocument, ipDocument))
    Loop

End Sub

Private Function GetNextLetterRange(ByRef ipDocument As Word.Document) As Word.Range

' The use of Static means that the vairable will be remembered between calls
' so we don't need a module or global level variable to remeber it for us.
'
' On the first method call the variable myLetterRange will be 'nothing' as it won't
' yet have been initialised.

' This code uses the assumption that the individual letters are separated by
' a manual page break.  In a word document this is the equivalent of a character
' with the code of 12

Static myLetterRange                As Word.Range

    ' There are two special cases we need to deal with
    '1. the first use of this function
    '2. the end of the document

    ' On first use myLetterRange will not have been initialised so will be nothing
    If myLetterRange Is Nothing Then

        Set myLetterRange = ipDocument.StoryRanges(wdMainTextStory)
        ' Lets start at the beginning
        myLetterRange.Collapse direction:=wdCollapseStart

    ' If we have reached the end of the document then we return nothing
    ElseIf myLetterRange.End = ipDocument.Range.End Then

            Set myLetterRange = Nothing
            ' In this case we can go home early
            Exit Function

    ' If it not the start or the end of the document then we need to skip over the
    ' manual page break to get to the first character of the next letter
    Else

        myLetterRange.Collapse direction:=wdCollapseEnd
        myLetterRange.Move unit:=wdCharacter, Count:=1

    End If

    ' Now we can look for the manual page break that marks the end of the letter
    ' Moveenduntil will return the number of characters moved but will
    ' return 0 if we don't find any characters in cset
    ' This will happen at the last page of the document so to be able to return
    ' the range of the last page of the document we need to set the end of
    ' myLetterRange manually

    If myLetterRange.MoveEndUntil(cset:=Chr$(12), Count:=wdForward) = 0 Then

        myLetterRange.End = ipDocument.StoryRanges(wdMainTextStory).End

    End If

    'We don't want the user to corrupt our range so we return a copy

    Set GetNextLetterRange = myLetterRange.Duplicate

End Function

Private Function GetClientname(ByRef ipLetterRange As Word.Range) As String

' The problem we have here is that the only clue we have as to the address block is
' that the 'Client' name lives in characters 10 to 30 of the letter range
' For the purposes of this code we'll assume that characters
' 10 to 30 live in paragraph 1 of the document.
' if this isn't the case you'll need to change the pragraph number and possible
' the numbers describing the start and end of the range

Dim myNameRange                  As Word.Range

    Set myNameRange = ipLetterRange.Paragraphs(1).Range.Characters.First 'alternative is .characters(1)
    myNameRange.MoveStart unit:=wdCharacter, Count:=10
    ' In this case the move also moves the end of the range
    myNameRange.MoveEnd unit:=wdCharacter, Count:=20

    GetClientname = myNameRange.Text

End Function

Private Function TrySaveIndividualLetter(ByRef ipLetterRange As Word.Range, ByVal ipClientName As String) As Boolean

Dim myLetter                            As Word.Document
Dim myLetterName                        As String

    Set myLetter = Application.Documents.Add(Visible:=False)
    ' We give a name to the new letter as being the parentlettername_clientname
    ' delete the bits you don't want
    ' Use the docX extension that matches your multiple letter document
    ' in the line below
    myLetterName = _
        ipLetterRange.Document.Path _
        & "\" _
        & Replace(ipLetterRange.Document.Name, ".docm", vbNullString) _
        & "_" _
        & ipClientName _
        & ".docx"

    ' Copy the formatted text in the found letter range into the new document
    ' copy/paste mioght be a better apprach if the range contains graphics.
    myLetter.Range.FormattedText = ipLetterRange.FormattedText
    myLetter.SaveAs2 myLetterName
    TrySaveIndividualLetter = myLetter.Saved
    myLetter.Close

End Function

freeflow
  • 4,129
  • 3
  • 10
  • 18