Mail Merge JSON

Hi,

Based on some of the old threads I have created my own implementation of IMailMergeDataSource to parse json and use that for the mail merge. However, what I’m not sure how to get dynamic rows of a table populated in the merged document.

I have attached the implementation and the template.

attachments.zip (24.8 KB)

Here is the code which generates the merged document using the template
public byte[] mergeWordDocWithRegions(byte[] templateStream, IMailMergeDataSource ds) {
ByteArrayOutputStream docOutStream = new ByteArrayOutputStream();
try {
Document doc = new Document(new ByteArrayInputStream(templateStream));

		doc.getMailMerge().setUseNonMergeFields(true);
		// This is to cleanup the mergeFields in the word template for which data has not been filled in
		doc.getMailMerge().setCleanupOptions(MailMergeCleanupOptions.REMOVE_UNUSED_FIELDS);

		// Execute the mail merge.
		//doc.getMailMerge().execute(fieldNames, fieldValues);
		doc.getMailMerge().execute(ds);
		LogHelper.info(LOG, "Mail merge performed successfully.");

		doc.save(docOutStream, SaveFormat.PDF);
		LogHelper.info(LOG, "Survey has been saved as PDF format");
	} catch (Exception e) {
		LogHelper.error(LOG, "Exception stack" + ExceptionUtils.getStackTrace(e));
	}
	return docOutStream.toByteArray();
}

Regards,
Sandesh

@sandesh.dsouza

In your case, we suggest you please implement IFieldMergingCallback interface and get the table’s row as shown below. Hope this helps you.

class FieldMergeCallback implements IFieldMergingCallback {

    public void fieldMerging(FieldMergingArgs e) throws Exception {
        Row row = (Row)e.getField().getStart().getAncestor(NodeType.ROW);
    }

    public void imageFieldMerging(ImageFieldMergingArgs args) throws Exception {
    }
}

Hi Tahir,

Thanks for your quick response. I tried to do this. But may be I’m missing something here.

The JSON Data is as below;
“other”: [
{
“pc”: “IDT”,
“ni”: “test1”
},
{
“pc”: “FBT”,
“ni”: “test2”
}
],

I need to put this in the tabular format as below as rows.

IDT test
FBT test2

I tried to using mail merge field in Word template using following
«TableStart:other»«pc» «ni»«TableEnd:other»

It just prints the same in the merged document. It doesn’t replace it. In fact, I don’t even get the field names when I try to do e.getFieldName() inside fieldMerging() method.

It’ll be of great help if you can point me to an working example of how to populate data in a table using mail merge fields.

I tried to use LINQ Reporting Engine as per below link. But still I can’t get through it.

using tags such as
<<foreach [
c in other
]>><<[c.pc]>> <<[c.ni]>><>

In fact, if I put mail merge fields in the word template as below, it just puts the last row entry into the merged document.

{ MERGEFIELD other.pc * MERGEFORMAT } { MERGEFIELD other.ni * MERGEFORMAT }

Regards,
Sandesh

@sandesh.dsouza

Could you please share some more detail about your requirement along with following detail?

  • Your input Word document.
  • Please attach the output Word file that shows the undesired behavior.
  • Please attach the expected output Word file that shows the desired behavior.
  • Please create a simple Java application ( source code without compilation errors ) that helps us to reproduce your problem on our end and attach it here for testing.

As soon as you get these pieces of information ready, we will start investigation into your issue and provide you more information. Thanks for your cooperation.

PS: To attach these resources, please zip and upload them.

Hi Tahir,

Please see the attached zip file. I have added following;

  1. Input Word Document - Template.docx
  2. output word file that shows the undesired behaviour - MergedDocument.pdf
  3. simple Java application - Source files are there in the zip file. Please run DocumentConverterControllerTest unit test which will generate the merged document in …\Sample\converter\src\test\resources
  4. Expected Output - Data is coming but table formatting is not done correctly.

Now the point is if you look at FieldMergeCallback class, I have done a bit of coding to create a table and not used Row row = (Row)e.getField().getStart().getAncestor(NodeType.ROW); provided by you.

Since the data is in json format (attached in the zip file - data.json), I had to do this. So I’m just wondering if there is any better way to handle this.

Because I’ll have various tables in my json. All of them will have different names for the table. So I can’t hard code that in the code.

Regards,
SandeshSample.zip (181.5 KB)

@sandesh.dsouza

Thanks for sharing the detail. In your case, we suggest you please call Document.UpdateTableLayout method before saving the document.

If you still face problem, you can implement following solution to achieve your requirement.

  1. Move the cursor to the first cell of row and insert the field TableStart:TableName.
  2. Move the cursor to the last cell of row and insert the field TableEnd:TableName.
  3. You can use DocumentBuilder.InsertField method to insert the field. In your shared case, the table is should be “other”.
  4. Call the MailMerge.ExecuteWithRegions.

Hi Tahir,

  1. Document.updateTableLayout() didn’t work.

  2. I did try TableStart:TableName solution but doesn’t work. I believe one needs to implement IMailMergeDataSourceRoot interface.

Since you already have the source which I provided earlier, can you please update that and show me how exactly this issue can be sorted out?

Regards,
Sandesh

Hi Tahir,

I added TableStart:other and TableEnd:other as 2 merge fields in the word template one appearing in the first cell and the other in 2nd.

I did invoke doc.getMailMerge().executeWithRegions(ds).

For some reason it doesn’t call getChildDataSource() method in the data source class I have created. Based on the documentation - IMailMergeDataSource | Aspose.Words for Java, I believe the engine must call this method. But it doesn’t.

So I’m not sure what I need to do to make sure engine invokes this method.

Hi Tahir,

I did few more changes.

  1. Template changes
    OTHER CLASS NAME INSURED
    «TableStart:other»«other.pc» «other.ni»«TableEnd:other»

  2. Added another class called JSONMailMergeDataSet which implements IMailMergeDataSourceRoot

  3. using
    doc.getMailMerge().execute(ds);
    doc.getMailMerge().executeWithRegions(dataSet);

ds - JSONObjectMailMergeDataSource for the same json data
dataSet - JSONMailMergeDataSet for the same json data

After doing this it’s putting only the last row in the generated document. Please see the attached documents. Also, it’s not well formatted. Can you please check this and let me know what I’m not doing right?Converter.zip (180.7 KB)

@sandesh.dsouza

You are creating new table in fieldMerging method. This is the reason you are getting different table’s width. Please clone the table’s row as shown in following code. In this following method you can write the field’s value according to your requirement. Hope this helps you.

public void fieldMerging(FieldMergingArgs e) throws Exception {

	System.out.println("FieldName" + e.getFieldName());
	System.out.println("DocumentFieldName" + e.getDocumentFieldName());
	System.out.println("TableName" + e.getTableName());
	Row row = (Row) e.getField().getStart().getAncestor(NodeType.ROW);

	if (row != null) {
		row.getRowFormat().setHeightRule(HeightRule.AT_LEAST);
		System.out.println(">>>>"+row.getText());
		Row newRow = (Row)row.deepClone(true);

		if (e.getFieldName().equals("other"))
		{
			DocumentBuilder builder = new DocumentBuilder(e.getDocument());
			builder.moveToMergeField(e.getFieldName());
			builder.write("mail merge field's value");
		}
		row.getParentTable().getRows().add(newRow);
	}
	e.setText("");
}

The Aspose.Words mail merge engine invokes IMailMergeDataSource.GetChildDataSource method when it encounters a beginning of a nested mail merge region.

Hi Tahir,

I was able to finally get this sorted out using following code.

private void mergeTableData(FieldMergingArgs e) throws Exception {
	Row row = (Row) e.getField().getStart().getAncestor(NodeType.ROW);

	if (row != null) {

		row.getRowFormat().setHeightRule(HeightRule.AUTO);
		Row toBeCloned = (Row) row.deepClone(true);

		if (e.getFieldName().startsWith("TABLE_")){

			JsonElement table = (JsonElement) e.getFieldValue();
			if (table.isJsonArray()) {

				JsonArray tableData = table.getAsJsonArray();
				DocumentBuilder builder = new DocumentBuilder(e.getDocument());

				for (int i = 0; i < tableData.size(); i++) {
					JsonElement currentRow = tableData.get(i);
					Set<Entry<String, JsonElement>> rowData = currentRow.getAsJsonObject().entrySet();

					int cellIndex = 0;
					for (Entry<String, JsonElement> cell : rowData) {
						if (cellIndex == 0) {
							builder.moveToMergeField(e.getFieldName());
						} else {
							builder.moveTo(row.getCells().get(cellIndex).getFirstParagraph());
						}
						builder.write(cell.getValue().getAsString());
						cellIndex++;

					}

					// Check if more rows there; if yes then clone the template row 
					if (i < tableData.size() - 1) {
						Row newRow = (Row) toBeCloned.deepClone(true);
						row.getParentTable().getRows().add(newRow);
						row = newRow;
					}

				}

			}

		}
		e.setText("");
	}
}

However, I have a question here. Is this the good approach or there is a better approach like using regions and applying TableStart:. If using regions is a better approach, it will be good to see a working example. I’m sure lot of Aspose users will be using JSON data. So I’m pretty sure it will be beneficial to all of us to look at a working example for Json data source.

Regards,
Sandesh

@sandesh.dsouza

Please note that Aspose.Words does not provide API to process JSON data/string. For such cases, you need to implement IMailMergeDataSource interface and work with JSON according to your requirement.

Yes, the approach using mail merge with region is better in this case. You can also parse your JSON string using Java API, create com.aspose.words.net.System.Data.DataTable object and pass it to MailMerge.ExecuteWithRegions.

Hi Tahir,

Sorry for the late response. Thanks for the recommendation. Just one question - do you have plans to support json in the future? I mean that’s an industry standard now. So will be good to see if it can be supported by the library itself.

Regards,
Sandesh

@sandesh.dsouza

我在一个项目中实现merge filed 的做法供你参考, 比较灵活,也很简单:

  1. merge Callback

private class FieldMergeHandler implements IFieldMergingCallback {
public void fieldMerging(FieldMergingArgs e) throws Exception {
//根据e的参数,获取需要合并的数据
FieldAttribute fa = getFieldValueFromDataMap(e);
DocumentBuilder builder = new DocumentBuilder(e.getDocument());
builder.moveToMergeField(e.getDocumentFieldName(), true, true);
//builder write
handleFieldWriter(builder, fa);
}
}
}

此处的FieldMergingArgs e:
序号:e.getRecordIndex()
字段:e.getFieldName()
表名:e.getTableName());

@sandesh.dsouza

Currently, we do not have plans to implement this feature. Did you try this solution?

If you still want us to implement this feature, please ZIP and attach your complete detail of your use case along with JSON samples, input and expected output documents. We will then log it in our issue tracking system according to your requirement.

Hi Tahir,

I haven’t tried this approach as I’m able to work with IMailMergeDataSource approach you gave me in the first instance. But I strongly think, there should be in built support in Aspose.words for JSON data as that’s the standard industry follows anyway.

Some of the samples I provided in this thread has cases. But I’ll try to get all of them in one place and share with you soon.

Regards,
Sandesh

@sandesh.dsouza

Please share complete detail of your use cases along with JSON samples, input and expected output documents. We will then log it in our issue tracking system according to your requirement.