MailMerge. excute combine two datasource

I have two beans one is list object and other one object. Can i merge both these datasources and pass to execute method

Program:-

ApiResponse<Template> response = new ApiResponse<>();
Template template = dao.getLatestTemplate(templateID);
Document doc = new Document(new ByteArrayInputStream(template.getTemplateData()));
doc.getMailMerge().setUseNonMergeFields(true);

List<Document> documents = new ArrayList<Document>();
documnets = dao.getDocuments();

DataSource dataSource = dao.getTemplateData();

doc.getMailMerge().execute(dataSource);


PdfSaveOptions pso = new PdfSaveOptions();
pso.setCompliance(PdfCompliance.PDF_17);
Document doc2 = new Document(new ByteArrayInputStream(template.getTemplateData()));
doc.appendDocument(doc2, ImportFormatMode.KEEP_SOURCE_FORMATTING);
doc.save("I:/ASPOSE-TEMPLATES/Doc/ASPOSE-Docx-To-PDF.pdf", pso);
response.setMessage("PDF Generated Successfully ");

return response;

@Smital279 You can implement your custom IMailMergeDataSource and combine your data sources according your requirements in your implementation.

Can you please provide me some sample for Custom ImailMergeDatSource with two data sources .

I tried but could not figure it out . Your help is much needed

@Smital279 Here is simple code example, where two maps are used as a single datasource:

Map<String, String> firstDataSource = new HashMap<String, String>();
firstDataSource.put("first", "first data");

Map<String, String> secondDataSource = new HashMap<String, String>();
firstDataSource.put("second", "second data");

CustomDataSource ds = new CustomDataSource(firstDataSource, secondDataSource);

Document doc = new Document("C:\\Temp\\in.docx");
doc.getMailMerge().execute(ds);
doc.save("C:\\Temp\\out.docx");
private static class CustomDataSource implements IMailMergeDataSource
{
    public CustomDataSource(Map<String, String> firstDataSource, Map<String, String> secondDataSource)
    {
        mFirstDataSource =  firstDataSource;
        mSecondDataSource = secondDataSource;
    }
        
    @Override
    public String getTableName() throws Exception {
        // Required if mail merge wit regions is used.
        return null;
    }
    
    @Override
    public boolean moveNext() throws Exception {
        // there is only one row in our demo data source.
        if(mFirstRow) {
            mFirstRow = false;
            return true;
        }
        return false;
    }
    
    @Override
    public boolean getValue(String fieldName, Ref<Object> ref) throws Exception {
        if(mFirstDataSource.containsKey(fieldName)) {
            ref.set(mFirstDataSource.get(fieldName));
            return true;
        }
        else if (mSecondDataSource.containsKey(fieldName))
        {
            ref.set(mSecondDataSource.get(fieldName));
            return true;
        }
        // There is no data for requested field
        return false;
    }
    
    @Override
    public IMailMergeDataSource getChildDataSource(String s) throws Exception {
        return null;
    }
        
    private Map<String, String> mFirstDataSource;
    private Map<String, String> mSecondDataSource;
    private boolean mFirstRow = true;
}

Thanks Alexey.I have tried below example

But in my case , I have map(datasource ) like :-

private Map<String,  List<Document>> documentsMap;

so here my value is list of documents and i wan to set into Ref but i am not able to do this

How can i pass list<Object> in Ref. Below is sample program which i am trying to set

@Override
public boolean getValue(String fieldName, Ref<Object> fieldValue) throws Exception {

        if(documentsMap.containsKey("documents")) {
            List<Document> ds = documentsMap.get("documents");
            fieldValue.set(ds);
        return true;
    }
    return false;
}

@Smital279 I do not see any problems with setting list of documents and field value. For example see the following code:

Map<String, List<Document>> documentsMap = new HashMap<String, List<Document>>();
documentsMap.put("documents", new ArrayList<Document>());
for (int i = 0; i < 5; i++)
{
    DocumentBuilder testBuilder = new DocumentBuilder();
    testBuilder.write("This is " + i + " document.");
    documentsMap.get("documents").add(testBuilder.getDocument());
}

CustomDataSource ds = new CustomDataSource(documentsMap);
Document doc = new Document("C:\\Temp\\in.docx");
doc.getMailMerge().setFieldMergingCallback(new CustomFieldMergingCallback());
doc.getMailMerge().execute(ds);
doc.save("C:\\Temp\\out.docx");
private static class CustomDataSource implements IMailMergeDataSource
{
    public CustomDataSource(Map<String, List<Document>> dataSource)
    {
        mDataSource = dataSource;
    }

    @Override
    public String getTableName() throws Exception {
        // Required if mail merge wit regions is used.
        return null;
    }

    @Override
    public boolean moveNext() throws Exception {
        // there is only one row in our demo data source.
        if(mFirstRow) {
            mFirstRow = false;
            return true;
        }
        return false;
    }

    @Override
    public boolean getValue(String fieldName, Ref<Object> ref) throws Exception {
        if(mDataSource.containsKey(fieldName)) {
            ref.set(mDataSource.get(fieldName));
            return true;
        }
        // There is no data for requested field
        return false;
    }

    @Override
    public IMailMergeDataSource getChildDataSource(String s) throws Exception {
        return null;
    }

    private Map<String,  List<Document>> mDataSource;
    private boolean mFirstRow = true;
}
private static class CustomFieldMergingCallback implements IFieldMergingCallback
{
    @Override
    public void fieldMerging(FieldMergingArgs args) throws Exception {
        
        if(args.getFieldName().equals("documents"))
        {
            DocumentBuilder builder = new DocumentBuilder(args.getDocument());
            builder.moveToField(args.getField(), true);
                
            List<Document> docs = (List<Document>)args.getFieldValue();
            for (Document doc : docs)
            {
                builder.insertDocument(doc, ImportFormatMode.KEEP_SOURCE_FORMATTING);
            }
            args.setFieldValue(null);
        }
            
    }
    
    @Override
    public void imageFieldMerging(ImageFieldMergingArgs args) throws Exception {
        
    }
}

in.docx (12.2 KB)
out.docx (10.0 KB)

In the example I use IFieldMergingCallback to process field value, in this particular case I insert content of each document in the list at the merge field.

Hello alexey. Thanks for your help , I have tried to incorporate changes like below but in templates it is not getting updates. Also, System.out.println is not printing anywhere .

Please Note :- i am getting compiler error in

 builder.insertDocument(doc, ImportFormatMode.KEEP_SOURCE_FORMATTING);

Could you please check . What is not correct .

Main Class:-

Template template = dao.getLatestTemplate(templateID);
com.aspose.words.Document doc = new com.aspose.words.Document(new ByteArrayInputStream(template.getTemplateData()));


List<Documents> documents = dao.getDocumentNames();
Map<String, List<Documents>> documentsMap = new HashMap<String, List<Documents>>();
documentsMap.put("documents", new ArrayList<Documents>());
for (Documents document : documents)
{
    DocumentBuilder testBuilder;
    testBuilder = new DocumentBuilder();
    testBuilder.write(document.getDocName());
    documentsMap.get("documents").add(document);
}
CustomDataSource ds = new CustomDataSource(documentsMap);
doc.getMailMerge().setFieldMergingCallback(new CustomFieldMergingCallback());
doc.getMailMerge().execute(ds);

PdfSaveOptions pso = new PdfSaveOptions();
pso.setCompliance(PdfCompliance.PDF_17);
doc.save("I:/ASPOSE-TEMPLATES/Doc/ASPOSE-Docx-To-PDF.pdf", pso);
public class CustomDataSource implements IMailMergeDataSource {

    private Map<String, DataSource> dataSourceMap;
    private Map<String,  List<Documents>> documentsMap;

    private boolean mFirstRow = true;
    public CustomDataSource(Map<String,  List<Documents>> documentsMap) {

            this.documentsMap = documentsMap;
    }

    @Override
    public String getTableName() throws Exception {
        return null;
    }

    @Override
    public boolean moveNext() throws Exception {
        if(mFirstRow) {
            mFirstRow = false;
            return true;
        }
        return false;
    }


    @Override
    public boolean getValue(String fieldName, Ref<Object> fieldValue) throws Exception {

        System.out.println("documentsMap.containsKey(fieldName)  "+documentsMap.containsKey(fieldName));
        System.out.println("documentsMap.get(fieldName) "+documentsMap.get(fieldName));

        if(documentsMap.containsKey(fieldName)) {
            fieldValue.set(documentsMap.get(fieldName));
            return true;
        }
        // There is no data for requested field
        return false;
    }

    @Override
    public IMailMergeDataSource getChildDataSource(String s) throws Exception {
        return null;
    }
}

public class CustomFieldMergingCallback implements IFieldMergingCallback {
    @Override
    public void fieldMerging(FieldMergingArgs args) throws Exception {
        if(args.getFieldName().equals("documents"))
        {
            DocumentBuilder builder = new DocumentBuilder(args.getDocument());
            builder.moveToField(args.getField(), true);
            List<Documents> docs = (List<Documents>)args.getFieldValue();
            System.out.println("docs----- "+docs);
            for (Documents doc : docs)
            {
                System.out.println("doc---- "+doc);
//               builder.insertDocument(doc, ImportFormatMode.KEEP_SOURCE_FORMATTING);
            }
            args.setFieldValue(null);
        }
    }

    @Override
    public void imageFieldMerging(ImageFieldMergingArgs imageFieldMergingArgs) throws Exception {

    }
}

@Smital279 Could you please attach your template?
Also, you have List<Documents>. What is Documents class?

Most likely, you get a compiler error because doc variable is of Documents class, but should be Aspose.Words.Document.

how can i insert custom document text or how i can convert documents object into aspose.words.document

attached is template.docxtemplate.docx (35.0 KB)


Below is Documents class

package com.jpmchase.cib.ta.aspose.bean;

import lombok.Data;

@Data
public class Documents {
    String docName;
 }

@Smital279 If I understand properly your Documents class contains file name of the actual document file. If so , you should simply create Document object from the file:

Document doc = new Document(documentFileName);

Or you can create a document from stream:

Document doc = new Document(documentStream);

Please see our documentation for more information:
https://docs.aspose.com/words/java/create-or-load-a-document/

Hello Alerey ,

Could you please look into below application and correct me what is wrong .

i have incorporated all suggested changes but document text is not printing (not even sysout)

attached is docx and pdf file


List<Documents> documents = dao.getDocumentNames();
Map<String, List<Documents>> documentsMap = new HashMap<String, List<Documents>>();
documentsMap.put("documents", new ArrayList<Documents>());
for (Documents document : documents)
{
    DocumentBuilder testBuilder;
    testBuilder = new DocumentBuilder();
    testBuilder.write(document.getDocName());
    documentsMap.get("documents").add(document);
}
CustomDataSource ds = new CustomDataSource(documentsMap);
doc.getMailMerge().setFieldMergingCallback(new CustomFieldMergingCallback());
doc.getMailMerge().execute(ds);

PdfSaveOptions pso = new PdfSaveOptions();
pso.setCompliance(PdfCompliance.PDF_17);
doc.save("I:/ASPOSE-TEMPLATES/Doc/ASPOSE-Docx-To-PDF.pdf", pso);


public class CustomDataSource implements IMailMergeDataSource {

    private Map<String, DataSource> dataSourceMap;
    private Map<String,  List<Documents>> documentsMap;

    private boolean mFirstRow = true;
    public CustomDataSource(Map<String,  List<Documents>> documentsMap) {
            this.documentsMap = documentsMap;
    }

    @Override
    public String getTableName() throws Exception {
        return null;
    }

    @Override
    public boolean moveNext() throws Exception {
        if(mFirstRow) {
            mFirstRow = false;
            return true;
        }
        return false;
    }


    @Override
    public boolean getValue(String fieldName, Ref<Object> fieldValue) throws Exception {
        System.out.println("documentsMap.containsKey(fieldName)  "+documentsMap.containsKey(fieldName));
        System.out.println("documentsMap.get(fieldName) "+documentsMap.get(fieldName));

        if(documentsMap.containsKey(fieldName)) {
            fieldValue.set(documentsMap.get(fieldName));
            return true;
        }
        // There is no data for requested field
        return false;
    }

    @Override
    public IMailMergeDataSource getChildDataSource(String s) throws Exception {
        return null;
    }
}

public class CustomDataSource implements IMailMergeDataSource {

    private Map<String, DataSource> dataSourceMap;
    private Map<String,  List<Documents>> documentsMap;

    private boolean mFirstRow = true;
    public CustomDataSource(Map<String,  List<Documents>> documentsMap) {
            this.documentsMap = documentsMap;
    }

    @Override
    public String getTableName() throws Exception {
        return null;
    }

    @Override
    public boolean moveNext() throws Exception {
        if(mFirstRow) {
            mFirstRow = false;
            return true;
        }
        return false;
    }


    @Override
    public boolean getValue(String fieldName, Ref<Object> fieldValue) throws Exception {
        System.out.println("documentsMap.containsKey(fieldName)  "+documentsMap.containsKey(fieldName));
        System.out.println("documentsMap.get(fieldName) "+documentsMap.get(fieldName));

        if(documentsMap.containsKey(fieldName)) {
            fieldValue.set(documentsMap.get(fieldName));
            return true;
        }
        // There is no data for requested field
        return false;
    }

    @Override
    public IMailMergeDataSource getChildDataSource(String s) throws Exception {
        return null;
    }
}

ASPOSE-Docx-To-PDF.pdf (55.0 KB)
template.docx (35.0 KB)

import lombok.Data;

@Data
public class Documents {
    String docName;
 }

@Smital279 In your template you use mustache syntax, not normal merge field:
https://docs.aspose.com/words/java/mail-merge-template-from-mustache-syntax/

To make Aspose.Words to recognize mustache syntax as merge fields, you should specify MailMerge.UseNonMergeFields property. Please modify your code like this:

doc.getMailMerge().setUseNonMergeFields(true);
doc.getMailMerge().execute(ds);

I have set the NonMergeFields true but in CustomFieldMergingCallback

builder.insertDocument(document, ImportFormatMode.KEEP_SOURCE_FORMATTING);

Here :- I want to add text field (String data type)

Below code throwing FileNotFound exception :- Doc object looking for file

 public void fieldMerging(FieldMergingArgs args) throws Exception {
        if(args.getFieldName().equals("documents"))
        {
            DocumentBuilder builder = new DocumentBuilder(args.getDocument());
            builder.moveToField(args.getField(), true);
            List<Documents> docs = (List<Documents>)args.getFieldValue();
            System.out.println("docs----- "+docs);
            for (Documents doc : docs)
            {
                System.out.println("doc---- "+doc);
                 Document document = new Document(doc.getDocName());
             **builder.insertDocument(document, ImportFormatMode.KEEP_SOURCE_FORMATTING);**
            }
            args.setFieldValue(null);
        }
    }

Template template = dao.getLatestTemplate(templateID);
com.aspose.words.Document doc = new com.aspose.words.Document(new ByteArrayInputStream(template.getTemplateData()));


List<Documents> documents = dao.getDocumentNames();
Map<String, List<Documents>> documentsMap = new HashMap<String, List<Documents>>();
documentsMap.put("documents", new ArrayList<Documents>());
for (Documents document : documents)
{
    DocumentBuilder testBuilder;
    testBuilder = new DocumentBuilder();
    testBuilder.write(document.getDocName());
    documentsMap.get("documents").add(document);
}
CustomDataSource ds = new CustomDataSource(documentsMap);
doc.getMailMerge().setFieldMergingCallback(new CustomFieldMergingCallback());
doc.getMailMerge().setUseNonMergeFields(true);
doc.getMailMerge().execute(ds);

PdfSaveOptions pso = new PdfSaveOptions();
pso.setCompliance(PdfCompliance.PDF_17);
doc.save("I:/ASPOSE-TEMPLATES/Doc/ASPOSE-Docx-To-PDF.pdf", pso);
response.setMessage("PDF Generated Successfully ");

Thanks for support . It is working now.

One doubt here. Now i am getting document object like

[Documents(docName=Original Address verification / utility bill or email confirmation that there has
been No Change to Address on file), Documents(docName=Original Intermediary Due Diligence
Questionnaire), Documents(docName=Original Application Form), Documents(docName=Original
Beneficial Ownership Register extract or evidence of registration with national Beneficial Ownership
Register dated less than 3 months), Documents(docName=Original Investor Response Form),
Documents(docName=Original AML letter from regulated entity (Intermediary) or regulated parent
(Nominee)), Documents(docName=Original Certificate of appointment), Documents(docName=Original
Company AML letter)]


How can i insert next line between and remove Document(docName------)

only want to print docName

I have created new topic for this issue.

Thanks for your support.

@Smital279 The thrown exception clearly says the there is no the specified file. What does doc.getDocName() return? Is it a path to the document or something else? You are standing in a better position to resolve this issue, since it is related to your data, not to Aspose.Words.

If you need to insert a simple string value, you should use DocumentBuilder.write method instead of DocumentBuilder.insertDocument method.

Yes. I resolved that issue.
request you pls ignore