Mail Merge with Page Breaks

Thanks for the sample code… My code is in VB so I had to convert it. I get no errors, however no data at all is saved into the template… Does the conversion look correct to you? I feel something in the handler may have gotten converted wrong.

Sub
Dim doc As New Document("d:\hart11\templates\canvassing2.doc")
    Dim adapterTemplate1 As New SQLDataAdapter("SELECT ‘’ AS SalesAmount, CompanyTxt AS Company, ‘’ AS EmploymentThisSite, FaxDirectTxt AS Fax, ‘’ AS Ownership, PhoneDirectTxt AS Phone, ‘’ AS [Plant/FacilitySize], PhoneGeneralTxt AS Phone2, WebURL AS WebAddress, ‘’ AS YearEstablished, ‘’ AS OwnsRents, Address1Txt AS Address1, Address2Txt AS Address2, CityTxt As City, StateCode AS State, PostalCodeTxt As Zip, ShippingAddress1Txt AS AltAddress, ShippingCityTxt As AltCity, ShippingStateCode AS AltState, ShippingPostalCodeTxt AS AltZip, CountyTxt AS County, ContactSIC1Code AS SIC1, ‘’ AS SIC1Description, ContactSIC2Code AS SIC2, ‘’ AS Sic2Description, ContactSIC3Code AS SIC3, ‘’ AS Sic3Description, ContactParentCompanyTxt AS Parent, PrefixTxt AS Prefix, FirstTxt AS First, LastTxt As Last, SuffixTxt AS Suffix, TitleTxt AS Title, DivisionTxt AS Division, ExportNameTxt AS Canvassed, ExportPreparedFor AS Salesperson FROM ViewContact INNER JOIN ExportDetail ON ExportDetail.ExportPrimaryRecordID = ViewContact.ContactID INNER JOIN ExportMaster ON ExportMaster.ExportID = ExportDetail.ExportID WHERE ExportDetail.ExportID=’" & Request.QueryString("ID") & "’ UNION SELECT [Sales Amount] AS SalesAmount, Company, EmploymentThisSite, REPLACE(Fax, ‘-’, ‘’) AS Fax, Ownership, REPLACE(Phone,’-’,’’) AS Phone, [Plant/FacilitySize], TollFree AS Phone2, WebAddress, YearEstablished, OwnsRents, MailAddress AS Address1, ‘’ AS Address2, MailCity AS City, MailState AS State, MailZipPlusExtension AS Zip, StreetAddress AS AltAddress, StreetCity AS AltCity, StreetState AS AltState, StreetZip AS AltZip, StreetCounty AS County, PrimarySic AS SIC1, PrimarySicDescription AS SIC1Description, Sic2 AS SIC2, Sic2Description, Sic3 AS SIC3, Sic3Description, ParentName AS Parent, RankedExecutive1Prefix AS Prefix, RankedExecutive1FirstName AS First, RankedExecutive1LastName AS Last, RankedExecutive1Suffix AS Suffix, RankedExecutive1TitleDescription AS Title, ‘’ AS Division, ExportNameTxt AS Canvassed, ExportPreparedFor AS Salesperson FROM ContactHarris INNER JOIN ExportMaster ON ExportMaster.ExportID = ContactHarris.ExportID WHERE ContactHarris.ExportID=’" & Request.QueryString("ID") & "’ AND ContactHarris.Duplicate=’’ ORDER BY City ASC", myConnection)
    Dim dtTemplate1 As New DataTable()
    adapterTemplate1.Fill(dtTemplate1)
    'doc.MailMerge.Execute(dtTemplate1)
    'doc.MailMerge.DeleteFields()
    doc.MailMerge.FieldMergingCallback = New HandleMergeField()
    doc.MailMerge.ExecuteWithRegions(dtTemplate1)
    doc.Save(SavePath)

End Sub

Private Class HandleMergeField
    Implements IFieldMergingCallback
    Private Sub FieldMerging(args As FieldMergingArgs) Implements IFieldMergingCallback.FieldMerging
        If args.FieldName = "City" Then

            mRecordIndex += 1

            Dim currentCity As String = args.FieldValue.ToString()
            If (Not String.IsNullOrEmpty(mCity) AndAlso mCity <> currentCity) OrElse mRecordIndex > 2 Then

                mRecordIndex = 0
                ' Move to the paragraph where the mergefied is located and set PageBreakBefore property.
                Dim builder As New DocumentBuilder(args.Document)
                builder.MoveToField(args.Field, False)
                builder.CurrentParagraph.ParagraphFormat.PageBreakBefore = True

            End If
            mCity = currentCity
        End If
    End Sub

    Private Sub ImageFieldMerging(e As ImageFieldMergingArgs) Implements IFieldMergingCallback.ImageFieldMerging

        ' Do nothing.
    End Sub

    Private mCity As String Private mRecordIndex As Integer
End Class

Sorry, I found my issue. Was missing the TableName definition…

data.TableName = "Data"

What exactly does that line do? Is that the name of something within the template…

One thing I noticed is my merge fields in the header section of my document are not getting updated now. Do I have to create another Region area for handle that portion of the document?

Hi

Thanks for your inquiry.

  1. Yes, “Data” in your case is name of region in the document. Regions is part of document between “TableStart:XXX” and “TableEnd:XXX” merge fields, where XXX is name of region.
  2. No, you do not need to create a separate region. Just execute simple mail merge after execution Mail Merge With Regions.

Best regards,

I get over 8000 page document when I add the normal mail merge info after the regions code…

Dim dtTemplate1 As New DataTable()
adapterTemplate1.Fill(dtTemplate1)
dtTemplate1.TableName = "Data"
doc.MailMerge.FieldMergingCallback = New HandleMergeField()
doc.MailMerge.ExecuteWithRegions(dtTemplate1)
doc.MailMerge.Execute(dtTemplate1)
'doc.MailMerge.DeleteFields()
doc.Save(SavePath)

Hi

Thank you for additional information. Actually you do not need to execute simple mail merge with the same data source. As I remember in the header you have only few merge fields. So you need to select data for these merge fields into a separate data source (with only one data row) and execute simple mail merge with this data source.
Best regards.

Got it… Thanks!

I’ve got another request. I now have my document created successfully. My users would like for the a table of contents to be inserted. They simply want the page number where each company is located at for quick reference. How would something like this be accomplished?

I was trying to come up with a way to do in in the SQL query (basically count how many people and estimate which page they should be on but it hasnt worked out yet).

I attached a completed canvassing file for reference.

BTW, still cant get over how amazing this product is!

It seems maybe a Table of Contents could solve this for me? I dont know how the table of contents should work though with the merge fields. I would like To have each Company name listed along with the page number…

Hi

Thanks for your inquiry. I think the best way to achieve what you need it building TOC using TOC entries. You can insert TOC entries (TC fields) from the code during executing mail merge.
The only one thing you need to change in your template is inserting TOC field like the following:
{ TOC \f \h }
Here is the modified code:

// Get data.
string connectionsString = @"Driver={Microsoft Excel Driver (*.xls)};DriverId=790;Dbq=C:\Temp\datasource.xls;DefaultDir=C:\Temp;";
string queryString = "SELECT * FROM Data";
OdbcConnection connection = new OdbcConnection(connectionsString);
OdbcDataAdapter adapter = new OdbcDataAdapter(queryString, connection);
DataTable data = new DataTable();
adapter.Fill(data);
data.TableName = "Data";
// Open template.
Document doc = new Document(@"C:\Temp\in.doc");
// Add MergeField callback.
doc.MailMerge.FieldMergingCallback = new HandleMergeField();
// Execute mail merge with regions.
doc.MailMerge.ExecuteWithRegions(data);
// Execute simple mail merge to fill the header with data.
doc.MailMerge.Execute(new string[]
{
    "Canvassed",
    "Salesperson"
}, new object[]
{
    "test",
    "James Bond"
});
// Update page layout to update page numbers in TOC.
doc.UpdatePageLayout();
// Save output.
doc.Save(@"C:\Temp\out.doc");
private class HandleMergeField: IFieldMergingCallback
{
    void IFieldMergingCallback.FieldMerging(FieldMergingArgs args)
    {
        DocumentBuilder builder = new DocumentBuilder(args.Document);
        if (args.FieldName == "City")
        {
            string currentCity = args.FieldValue.ToString();
            // Move to the paragraph where the mergefied is located.
            builder.MoveToField(args.Field, false);
            mRecordIndex++;
            if ((!string.IsNullOrEmpty(mCity) && mCity != currentCity) || mRecordIndex> 2)
            {
                mRecordIndex = 0;
                // Set PageBreakBefore property.
                builder.CurrentParagraph.ParagraphFormat.PageBreakBefore = true;
            }
            // Insert TC field to build TOC. Insert TC only if this is the first time when City occur.
            if (mCity != currentCity)
            {
                // Get current cell and move builder to the beginning of it.
                Cell cell = (Cell) builder.CurrentParagraph.GetAncestor(NodeType.Cell);
                builder.MoveTo(cell.FirstParagraph.FirstChild);
                builder.InsertField(string.Format("TC \"{0}\" \\l 1", args.FieldValue));
            }
            mCity = currentCity;
        }
        // Also insert TC field for each company.
        if (args.FieldName == "Company")
        {
            // Move to the paragraph where the mergefied is located.
            builder.MoveToField(args.Field, false);
            // Companies is the second level of TOC.
            builder.InsertField(string.Format("TC \"{0}\" \\l 2", args.FieldValue));
        }
    }
    void IFieldMergingCallback.ImageFieldMerging(ImageFieldMergingArgs e)
    {
        // Do nothing.
    }
    private string mCity;
    private int mRecordIndex;
}

I highlighted my changes in the code and attached the modified template. Hope this helps.
Best regards,

Getting the following error. I assume it has somethign to do with Aspose Cells and Words being on this page?

Compiler Error Message: BC30311: Value of type ‘Aspose.Words.Node’ cannot be converted to ‘Aspose.Cells.Cell’.

Source Error:

Line 542: If mCity <> currentCity Then
Line 543: ’ Get current cell and move builder to the beginning of it.
Line 544: Dim cell As Cell = DirectCast(builder.CurrentParagraph.GetAncestor(NodeType.Cell), Cell)
Line 545: builder.MoveTo(cell.FirstParagraph.FirstChild)
Line 546: builder.InsertField(String.Format(“TC “”{0}”" \l 1", args.FieldValue))

If I comments out the following it runs, and when the document opens it shows the TOC, but then it quickly dissapears along with the merge data in the header of the document.

' Insert TC field to build TOC. Insert TC only if this is the first time when City occur.
'If mCity <> currentCity Then
' Get current cell and move builder to the beginning of it.
' Dim cell As Cell = DirectCast(builder.CurrentParagraph.GetAncestor(NodeType.Cell), Cell)
' builder.MoveTo(cell.FirstParagraph.FirstChild)
' builder.InsertField(String.Format("TC ""{0}"" \l 1", args.FieldValue))
'End If

This seems wierd. The TOC and page numbers show if I print, but if I just look at the document, they dont show…

Do you know if its possible to Sort a TOC by the Company field ascending? I know this really makes it an index. They just need a quick reference as to where a company name is in the document.

Hi Ryan,
Thanks for this additonal information.
That runtime error is occuring because there are Cell classes in both Aspose.Words and Aspose.Cells but the wrong types are being matched in your code. To fix this you should give the fully qualified names of the classes as to avoid any confusion. i.e instead of just Cell have instead Aspose.Words.Tables.Cell.
Regarding your question on sorting, there is no easy way to sort a TOC as it is the order of the entries are suppost to reflect the positition relative to the order they appear in the document. We can provide you with some code to do this using an index if you need this functionality.
If things still aren’t working with your program above could you paste your full code here and I will take a closer look.
Thanks,

I modified the template and code to use an index. What I’m confused about is he index is not automatically built. After the word document is created and opened, it says index has no entries found (or something like that), but if you right click on the index and select update, it appears. Can Aspose.Words automatically do this? I attached my template.

Dim doc As New Document("d:\hart11\templates\canvassing.doc")
Dim adapterTemplate1 As New SQLDataAdapter("SELECT ‘’ AS SalesAmount, ViewContact.CompanyTxt AS Company, ‘’ AS EmploymentThisSite, case when len(ltrim(rtrim(ViewContact.FaxDirectTxt)))=‘10’ then ‘’+SUBSTRING(ViewContact.FaxDirectTxt,1,3)+’.’+’’+SUBSTRING(ViewContact.FaxDirectTxt,4,3)+’.’+SUBSTRING(ViewContact.FaxDirectTxt,7,4) else ViewContact.FaxDirectTxt end AS Fax, ‘’ AS Ownership, case when len(ltrim(rtrim(ViewContact.PhoneDirectTxt)))=‘10’ then ‘’+SUBSTRING(ViewContact.PhoneDirectTxt,1,3)+’.’+’’+SUBSTRING(ViewContact.PhoneDirectTxt,4,3)+’.’+SUBSTRING(ViewContact.PhoneDirectTxt,7,4) else ViewContact.PhoneDirectTxt end AS Phone, ‘’ AS [Plant/FacilitySize], case when len(ltrim(rtrim(ViewContact.PhoneGeneralTxt)))=‘10’ then ‘’+SUBSTRING(ViewContact.PhoneGeneralTxt,1,3)+’.’+’’+SUBSTRING(ViewContact.PhoneGeneralTxt,4,3)+’.’+SUBSTRING(ViewContact.PhoneGeneralTxt,7,4) else ViewContact.PhoneGeneralTxt end AS Phone2, ViewContact.WebURL AS WebAddress, ‘’ AS YearEstablished, ‘’ AS OwnsRents, ViewContact.Address1Txt AS Address1, ViewContact.Address2Txt AS Address2, ViewContact.CityTxt As City, ViewContact.StateCode AS State, ViewContact.PostalCodeTxt As Zip, ViewContact.ShippingAddress1Txt AS AltAddress, ViewContact.ShippingCityTxt As AltCity, ViewContact.ShippingStateCode AS AltState, ViewContact.ShippingPostalCodeTxt AS AltZip, ViewContact.CountyTxt AS County, ViewContact.ContactSIC1Code AS SIC1, ‘’ AS SIC1Description, ViewContact.ContactSIC2Code AS SIC2, ‘’ AS Sic2Description, ViewContact.ContactSIC3Code AS SIC3, ‘’ AS Sic3Description, ViewContact.ContactParentCompanyTxt AS Parent, ViewContact.PrefixTxt AS Prefix, ViewContact.FirstTxt AS First, ViewContact.LastTxt As Last, ViewContact.SuffixTxt AS Suffix, ViewContact.TitleTxt AS Title, ViewContact.DivisionTxt AS Division, Contact.CanvassingTemplate1 FROM ViewContact INNER JOIN Contact ON Contact.ContactID = ViewContact.ParentContactID INNER JOIN ExportDetail ON ExportDetail.ExportPrimaryRecordID = ViewContact.ContactID INNER JOIN ExportMaster ON ExportMaster.ExportID = ExportDetail.ExportID WHERE ExportDetail.ExportID=’" & Request.QueryString("ID") & "’ UNION SELECT [Sales Amount] AS SalesAmount, Company, EmploymentThisSite, case when len(ltrim(rtrim(REPLACE(Fax,’-’,’’))))=‘10’ then ‘’+SUBSTRING(REPLACE(Fax,’-’,’’),1,3)+’.’+’’+SUBSTRING(REPLACE(Fax,’-’,’’),4,3)+’.’+SUBSTRING(REPLACE(Fax,’-’,’’),7,4) else Fax end AS Fax, Ownership, case when len(ltrim(rtrim(REPLACE(Phone,’-’,’’))))=‘10’ then ‘’+SUBSTRING(REPLACE(Phone,’-’,’’),1,3)+’.’+’’+SUBSTRING(REPLACE(Phone,’-’,’’),4,3)+’.’+SUBSTRING(REPLACE(Phone,’-’,’’),7,4) else Phone end AS Phone, [Plant/FacilitySize], case when len(ltrim(rtrim(REPLACE(TollFree,’-’,’’))))=‘10’ then ‘’+SUBSTRING(REPLACE(TollFree,’-’,’’),1,3)+’.’+’’+SUBSTRING(REPLACE(TollFree,’-’,’’),4,3)+’.’+SUBSTRING(REPLACE(TollFree,’-’,’’),7,4) else TollFree end AS Phone2, WebAddress, YearEstablished, OwnsRents, MailAddress AS Address1, ‘’ AS Address2, MailCity AS City, MailState AS State, MailZipPlusExtension AS Zip, StreetAddress AS AltAddress, StreetCity AS AltCity, StreetState AS AltState, StreetZip AS AltZip, StreetCounty AS County, PrimarySic AS SIC1, PrimarySicDescription AS SIC1Description, Sic2 AS SIC2, Sic2Description, Sic3 AS SIC3, Sic3Description, ParentName AS Parent, RankedExecutive1Prefix AS Prefix, RankedExecutive1FirstName AS First, RankedExecutive1LastName AS Last, RankedExecutive1Suffix AS Suffix, RankedExecutive1TitleDescription AS Title, ‘’ AS Division, ‘’ AS CanvassingTemplate1 FROM ContactHarris INNER JOIN ExportMaster ON ExportMaster.ExportID = ContactHarris.ExportID WHERE ContactHarris.ExportID=’" & Request.QueryString("ID") & "’ AND ContactHarris.Duplicate=’’ ORDER BY City ASC", myConnection)
Dim dtTemplate1 As New DataTable()
adapterTemplate1.Fill(dtTemplate1)
dtTemplate1.TableName = "Data"
doc.MailMerge.FieldMergingCallback = New HandleMergeFieldCanvassing()
doc.MailMerge.ExecuteWithRegions(dtTemplate1)
doc.MailMerge.Execute(New String() {"Canvassed", "Salesperson"}, New Object() {rcbExportNameTxt.Text, rcbPreparedFor.Text})
doc.UpdatePageLayout())
doc.Save(SavePath)
Private Class HandleMergeFieldCanvassing

    Implements IFieldMergingCallback
    Private Sub FieldMerging(args As FieldMergingArgs) Implements IFieldMergingCallback.FieldMerging
        Dim builder As New DocumentBuilder(args.Document)
        If args.FieldName = "City" Then
            Dim currentCity As String = args.FieldValue.ToString()

            builder.MoveToField(args.Field, False)


            mRecordIndex += 1
            If (Not String.IsNullOrEmpty(mCity) AndAlso mCity <> currentCity) OrElse mRecordIndex > 2 Then

                mRecordIndex = 0
                builder.CurrentParagraph.ParagraphFormat.PageBreakBefore = True

            End If

            mCity = currentCity
        End If
        If args.FieldName = "Company" Then
            builder.MoveToField(args.Field, False)
            builder.InsertField(String.Format("XE ""{0}"" \b", args.FieldValue))
        End If
    End Sub

    Private Sub ImageFieldMerging(e As ImageFieldMergingArgs) Implements IFieldMergingCallback.ImageFieldMerging

        ' Do nothing.
    End Sub

    Private mCity As String Private mRecordIndex As Integer
End Class

Hi Ryan,
Thanks for posting your code here.
Yes, you can automatically populate index and TOC fields by calling doc.UpdateFields() first, then doc.UpdatePagelayout().
Thanks,

I added the logic below. Still says the following when the document is opened: No index entries found.

doc.UpdateFields()
doc.UpdatePageLayout()

Does anything else look out of place to you?

Hi

Thanks for your inquiry. TOC is properly generated on my side. Could you please specify which version of Aspose.Words you use in your application? I used the latest version for testing.
Best regards,

Is there any difference between a TOC and index. I do not have a TOC on my page, only an index… No TC entries, using XE.

Hello

Thanks for your interest in Aspose.Words. Please just open your document using MS Word and press Alt-F9 to hide/show the field code. You will see TOC field code (Table of Content).
Best regards,