Mail Merge Word document from XML data in Node.js basics

I am just trying out the asposewordscloud npm package for the first time, specifically the mail merge feature. I am able to successfully call the service and get a response, but I don’t understand how to read the response. I tried saving the response body as a file, but it just contains the template, not the finished, merged output. Here is my code:

const wordsApi = new WordsApi(clientId, secret);

let requestTemplate = fs.createReadStream("TestExecuteTemplate.doc");
let requestData = fs.createReadStream("TestExecuteTemplateData.xml");

const mailMergeRequest = new ExecuteMailMergeOnlineRequest({
    template: requestTemplate,
    data: requestData
});

wordsApi.executeMailMergeOnline(mailMergeRequest)
.then((mailMergeRequestResult) => {
   fs.writeFile('output.doc', mailMergeRequestResult.body, function (err) {
        if (err) return console.log(err);
         // tslint:disable-next-line:no-console
        console.log('saved file');
      });
});

Why doesn’t mailMergeRequestResult.body contain the merged output? Where is it?

Thanks, Dan

@danobri

Usually, it happens when your template has regions and you do not set withRegions property true. Please double check your template and set the property accordingly. However, if the issue persists then please share your template and data file for investigation.

const request = new ExecuteMailMergeOnlineRequest({
            template: fs.createReadStream(localDocumentFile),
            data: fs.createReadStream(localDataFile),
 	        withRegions: true
		});

That did the trick, although I am just using the files provided with the mail merge example, and the example does not include the withRegions: true option.

What are regions in Word, and how do I know if a Word doc has regions? When I google “Microsoft Word regions,” I don’t find much. We will be building a system that will need to support templates that the system has no knowledge of - is there any reason not to have withRegions: true all the time? Why is it set to false by default?

The documentation should be updated either to include a document that doesn’t require withRegions: true, or include that option in the example code. It would also be helpful if the documentation made clear how to get the merged file out of the result object. Logging it to the console isn’t a very useful example.

@danobri

I wonder why the sample template and data did not work at your end without the withRegions parameter. Have you made any changes to the template? As the template(SampleMailMergeTemplate.docx) does not contain any region(TableStart/TableEnd block) and working fine at my end without the withRegions parameter.

Secondly, for more details about Mail Merge regions, you can check Aspose.Words for .NET documentation.SampleMailMergeTemplate.zip (20.9 KB)


const { WordsApi, ExecuteMailMergeOnlineRequest } = require("asposewordscloud");
var fs = require('fs');

// Please get your App Key and App SID from https://dashboard.aspose.cloud/#/apps.
wordsApi = new WordsApi("xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxxxx");

const localDocumentFile = "C:/Temp/SampleMailMergeTemplate.docx";
const localDataFile = "C:/Temp/SampleMailMergeTemplateData.xml";

    const request = new ExecuteMailMergeOnlineRequest({
            template: fs.createReadStream(localDocumentFile),
            data: fs.createReadStream(localDataFile)
	    //withRegions: true
		});
                       

wordsApi.executeMailMergeOnline(request).then((result) => {    
    console.log(result.body.code);   
	fs.writeFileSync('C:/Temp/MailMerge.docx', result.body);
    console.log('Completed...');	
}).catch(function(err) {
    // Deal with an error
    console.log(err);
});

I switched to using my own template to try out some more features, and I am finding if I include a #foreach tag, that section does not got evaluated unless withRegions is true, while all the other tags in the document only get evaluated when withRegions is false. How do I get them both to work in one document? Here’s the template: coverageChangeTemplate.docx (25.3 KB)

And here’s the JSON data:

    {
    "participant": {
        "firstName": "John",
        "lastName": "Smith",
        "address": {
            "addressLine1": "123 Test Street",
            "addressLine2": null,
            "addressLine3": null,
            "city": "Madison",
            "state": "WI",
            "country": "US",
            "zip": "55555"
        }
    },
    "letterDate": "06/20/2021",
    "clientName": "Test Co",
    "futureStartDate": "10/10/2021",
    "continuationBenefits": [
        {
            "planName": "Future Medical Plan",
            "coverageLevel": "Single",
            "premiumAmount": "561.00"
        },
        {
            "planName": "Future Vision Plan",
            "coverageLevel": "Single",
            "premiumAmount": "115.00"
        }
    ],
    "creditAmount": "17.76",
    "currentBenefits": [
        {
            "planName": "Current Medical Plan",
            "coverageLevel": "Single",
            "premiumAmount": "511.20",
            "paidThruDate": "07/01/2021"
        },
        {
            "planName": "Current Vision Plan",
            "coverageLevel": "Single",
            "premiumAmount": "100.00",
            "paidThruDate": "07/01/2021"
        }
    ],
    "company": {
        "name": "ACME",
        "csrName": "ACME Customer Care",
        "csrPhone": "(800) 555-1234",
        "csrHours": "8:00 a.m. to 500 p.m, Monday through Friday",
        "departmentName": "Customer Service",
        "address": {
            "addressLine1": "1234 International Way",
            "addressLine2": null,
            "addressLine3": null,
            "city": "Madison",
            "state": "WI",
            "country": "US",
            "zip": "55555"
        },
        "webSite": "www.acme.com",
        "fax": null
    },
    "footerText": null
}

I did not alter the sample template file. However, it appears you are not using the mustache templates. Note the difference in name “SampleMailMergeTemplate” vs “TestExecuteTemplate” Case 1 vs Case 2 described in the URL I referenced earlier.

1 Like

@danobri

We are investigating your template along with the data and will update you shortly.

@danobri

We have checked your template and noticed it includes two types of mail merge fields, simple mail merge fields and mail merge fields with region. So you need to call the Mail Merge API twice. Once for normal fields and later for region fields.

Can you please post an example? Using the output of withRegions: false as the input for a withRegions: true request is not working for me. The second call just hangs - it never completes, but does not throw an error.

    const wordsApi = new WordsApi(clientId, secret);

    let requestTemplate = fs.createReadStream("templates/coverageChangeTemplate.docx");
    let requestData = fs.createReadStream("data/coverageChange.json");

    const mailMergeRequest = new ExecuteMailMergeOnlineRequest({
        template: requestTemplate,
        data: requestData,
    });

    wordsApi.executeMailMergeOnline(mailMergeRequest)
    .then((mailMergeRequestResult) => {
        console.log("merge complete");
        const regionMergeRequest = new ExecuteMailMergeOnlineRequest({
            template: mailMergeRequestResult.body,
            data: requestData,
            withRegions: true
        });
        wordsApi.executeMailMergeOnline(regionMergeRequest)
        .then((regionMergeRequestResult) => {
            console.log("region merge complete");
            fs.writeFile('output/coverageChange.docx', regionMergeRequestResult.body, function (err) {
                if (err) return console.log(err);
                console.log('file saved');
            });
        }) 
        .catch((err) => {
            console.log(err);
        });
    })
    .catch((err) => {
        console.log(err);
    });

@danobri

Please check this sample code and output, it is working fine at my end.
mailmrege_output.docx (19.4 KB)


const { WordsApi, ExecuteMailMergeOnlineRequest } = require("asposewordscloud");
var fs = require('fs');


// Please get your App Key and App SID from https://dashboard.aspose.cloud/#/apps.
wordsApi = new WordsApi("xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxx");

const localDocumentFile = "C:/Users/hp 840 g3/Downloads/coverageChangeTemplate.docx";
const localDataFile = "C:/Users/hp 840 g3/Downloads/coverageChangeTemplate.json";

    const mailMergeRequest = new ExecuteMailMergeOnlineRequest({
            template: fs.createReadStream(localDocumentFile),
            data: fs.createReadStream(localDataFile)
	    //withRegions: true
		});
                       

wordsApi.executeMailMergeOnline(mailMergeRequest)
.then((mailMergeRequestResult) => {
    console.log("mailmerge complete");
    const mailMergeRequestRegion = new ExecuteMailMergeOnlineRequest({
            template: mailMergeRequestResult.body,
            data: fs.createReadStream(localDataFile),
	    withRegions: true
		});
    wordsApi.executeMailMergeOnline(mailMergeRequestRegion)
    .then((mailMergeRequestRegionResult) => {
        console.log("mailmrege region complete");
        fs.writeFile('mailmrege_output.docx', mailMergeRequestRegionResult.body, function (err) {
            if (err) return console.log(err);
            console.log('file saved');
        });
    })
    .catch((err) => {
        console.log(err);
    });

})
.catch((err) => {
    console.log(err);
});

Looks like the issue was that I had to call createReadStream on the data file again. Thanks!

So in order to merge a typical template and convert to a PDF you have to make 3 chained API calls. Is there any way to reduce the number of API calls required for this extremely common scenario?

@danobri

Usually, your scenario needs only two API calls one for Mail Merge and the other for Conversion(PDF). However, as stated above your shared template needs two Mail Merge API calls because of two different types of mail merge fields in the same template.

It’s unfortunate that the template can’t be processed in a single request. Seems like an extremely common case for a mail merge to involve some enumerable data and some flat data. Aspose.Words is the only product on the market that I have seen that requires multiple merges to handle enumerable data and flat data in a single template. Do you know if this is something Aspose plans to improve?

@danobri

You can achieve this in a single call but after changing your template and data, adding a top level region. Please check the attached template and data for reference.
Invoice Template.zip (90.5 KB)

However, we have logged a ticket(WORDSCLOUD-1749 ) to investigate your template and data further and will share our findings asap.

Thanks, but that’s not a viable option since it would mean passing the problem on to our consumers. There is no logical reason to make consumers shape data for a single record into an array and wrap all templates in an arbitrary foreach statement. Do I have access to the ticket to monitor status? If so, could you provide a link?

@danobri

Thanks for your feedback, we have will consider it in the issue investigation.

I am afraid you can not access the internal ticket. However, we will keep you updated about the issue investigation.

@danobri

We have analyzed the above reported issue WORDSCLOUD-1749 and would like to inform you that we need to call API twice for ordinary mail merge fields and mail merge region fields. Because this is how the parent API Aspose.Words for .NET is designed. However, you may use batch API to call multiple API requests with dependsOn option.

I would strongly recommend you change the API. This is a major shortcoming of the Aspose merge functionality. I have evaluated a number of competing products, and none of them require two operations to process enumerable and non-enumerable data. We have decided to go with a different vendor largely because this limitation effectively doubles the cost of your service.

@danobri

Thanks for your feedback. We have recorded your concern and will further look into the requirement.