How to use forEach loop for Custom array with Mustache Syntax

I have below application , I am getting values in array format . I want to iterate over that with bullet pointer.
Could you please suggest on this .Also attached generated PDF and template filesASPOSE-Docx-To-PDF.pdf (60.3 KB)
template.docx (35.2 KB)

Current output is :----

[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)]

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

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", documents);

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 ");

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 {
        if(documentsMap.containsKey(fieldName)) {
            fieldValue.set(documentsMap.get(fieldName));
            return true;
        }
        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();
            List<Documents> docs = (List<Documents>)args.getFieldValue();
            for (Documents doc : docs)
            {
                 builder.write(doc.getDocName());
            }
        }
    }

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

    }
}

@Smital279 In your case you can use mail merge with regions. But anyways your template and IMailMergeDataSource implementation should be adjusted. Syntax in the template should look like this:
• {{ #foreach documents}}{{docName}}{{ /foreach documents }}
And here is the code:

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

// init documents with dummy data
List<Documents> documents = new ArrayList<Documents>();
for (String s : dummyData)
{
    Documents docs = new Documents();
    docs.docName = s;
    documents.add(docs);
}
Map<String, List<Documents>> documentsMap = new HashMap<String, List<Documents>>();
documentsMap.put("documents", documents);

// Create data source.
String tableName = "documents";
CustomDataSource ds = new CustomDataSource(tableName, documentsMap.get(tableName));

// Open template and execute mail merge with regions:
Document doc = new Document("C:\\Temp\\in.docx");
doc.getMailMerge().setUseNonMergeFields(true);
doc.getMailMerge().executeWithRegions(ds);

PdfSaveOptions pso = new PdfSaveOptions();
pso.setCompliance(PdfCompliance.PDF_17);
doc.save("C:\\Temp\\out.pdf", pso);
public static class CustomDataSource implements IMailMergeDataSource {
    
    private List<Documents> mDocumentsList;
    private String mTableName;
    private int mCurrentIndex = -1;
    
    public CustomDataSource(String tableName, List<Documents> documentsList) {
        mDocumentsList = documentsList;
        mTableName = tableName;
    }
    
    @Override
    public String getTableName() throws Exception {
        return mTableName;
    }
    
    @Override
    public boolean moveNext() throws Exception {
        mCurrentIndex++;
        return mCurrentIndex < mDocumentsList.size();
    }
    
    
    @Override
    public boolean getValue(String fieldName, Ref<Object> fieldValue) throws Exception {
        if(fieldName.equals("docName")){
            fieldValue.set(mDocumentsList.get(mCurrentIndex).docName);
            return true;
        }
        return false;
    }
    
    @Override
    public IMailMergeDataSource getChildDataSource(String s) throws Exception {
        return null;
    }
}

in.docx (35.4 KB)
out.pdf (48.4 KB)

Thanks . It is working now.

Now, My requirement is i have to pass two data Sources so below changes i have made

But it not executing both data sources . Could you please guide me what would be wrong.


Template template = dao.getLatestTemplate(templateID);
com.aspose.words.Document doc = new com.aspose.words.Document(new ByteArrayInputStream(template.getTemplateData()));
doc.getMailMerge().setUseNonMergeFields(true);
DataSource dataSource = dao.getTemplateData();
Map<String, DataSource> dataSourceMap = new HashMap<String, DataSource>();
dataSourceMap.put("dataSource", dataSource);

List<Documents> documents = dao.getDocumentNames();
Map<String, List<Documents>> documentsMap = new HashMap<String, List<Documents>>();
documentsMap.put("documents", documents);

String tableName = "documents";
CustomDataSource ds = new CustomDataSource(tableName, documentsMap.get(tableName), dataSourceMap);
doc.getMailMerge().executeWithRegions(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 ");

public class CustomDataSource implements IMailMergeDataSource {

    private Map<String, DataSource> dataSourceMap;
    private Map<String,  List<Documents>> documentsMap;
    boolean mFirstRow=true;
    private List<Documents> mDocumentsList;
    private int mCurrentIndex = -1;

    private String mTableName;

     public CustomDataSource(String tableName, List<Documents> documentsList,Map<String, DataSource> dataSourceMap) {
         this.mDocumentsList = documentsList;
         this.mTableName = tableName;
         this.dataSourceMap=dataSourceMap;

     }

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

    @Override
    public boolean moveNext() throws Exception {
        mCurrentIndex++;
        return mCurrentIndex < mDocumentsList.size();
    }


        @Override
    public boolean getValue(String fieldName, Ref<Object> fieldValue) throws Exception {
        if(dataSourceMap.containsKey("dataSource")) {
            DataSource ds = dataSourceMap.get("dataSource");
            switch (fieldName) {
                case "cName":
                    fieldValue.set(ds.getCName());
                    return true;
                case "addressLine1":
                    fieldValue.set(ds.getAddressLine1());
                    return true;
                case "addressLine2":
                    fieldValue.set(ds.getAddressLine2());
                    return true;
                case "addressLine3":
                    fieldValue.set(ds.getAddressLine3());
                    return true;
                case "addressLine4":
                    fieldValue.set(ds.getAddressLine4());
                    return true;

                case "addressLine5":
                    fieldValue.set(ds.getAddressLine5());
                    return true;

                case "country":
                    fieldValue.set(ds.getCountry());
                    return true;

                case "subject":
                    fieldValue.set(ds.getSubject());
                    return true;

                case "account":
                    fieldValue.set(ds.getAccount());
                    return true;

                case "attention":
                    fieldValue.set(ds.getAttention());
                    return true;

                case "attention1":
                    fieldValue.set(ds.getAttention1());
                    return true;

                case "attention2":
                    fieldValue.set(ds.getAttention2());
                    return true;

                case "days":
                    fieldValue.set(ds.getDays());
                    return true;
            }
        }

        if(fieldName.equals("docName")) {
            fieldValue.set(mDocumentsList.get(mCurrentIndex).getDocName());
            return true;
        }
        return false;
    }

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

@Data
@AllArgsConstructor
public class DataSource {
    Integer versionId;
    Integer templateID;
    String cName;
    String addressLine1;
    String addressLine2;
    String addressLine3;
    String addressLine4;
    String addressLine5;
    String country;
    String subject;

    String account;
    String attention;
    String attention1;
    String attention2;
    String days;

}

ASPOSE-Docx-To-PDF.pdf (89.7 KB)
template.docx (42.0 KB)

@Smital279 In your code you use executeWithRegions, but the template does not have any regions. The regions definition using mustache syntax should be defined using foreach tag, like this:
{{ #foreach documents}}{{docName}}{{ /foreach documents }}

It looks like in your case it is required to use both simple mail merge and mail merge with regions - simple mail merge to fill cName, addressLine1, addressLine2 etc. fields, and mail marge with regions to fill documents, as was demonstrated in my previous answer. So in your case you should use two separate data sources.