Replace Keywords or Merge Fields in Word DOC with Text using Java | Reporting using DataTable List XML Data

Hi All,

I have been working on legacy application where i am using Aspose.Words.jdk15.jar to create the .doc file . I have a requirement where i am getting list of value then we have to loop & print it in the doc file.

for(int i=0;i<detail.size();i++)
{
doc.getRange().replace("$COMPONENT_NAME" , checkNull(detail.get(i).getComponentName()) + “,”, false, false);
doc.getRange().replace("$EFFECTIVE_DATE" , checkNull(detail.get(i).getBillEffectiveDate()) + “,”, false, false);
doc.getRange().replace("$END_DATE" , checkNull(detail.get(i).getBillEndDate()) + “,”, false, false);
doc.getRange().replace("$RATE" , checkNull(detail.get(i).getRate()) + “,”, false, false);
doc.getRange().replace("$CURRENCY" , checkNull(detail.get(i).getCurrencycode()) + “,”, false, false);
}

I have to print these values number of times in the doc as per the size of the list. I have kept these values in my doc as attached image. Could someone please help me on this?
I am getting value only one time after looping.

image.png (4.1 KB)

@sumitit04,

Please ZIP and attach the following resources here for testing:

  • Your simplified input Word document
  • Aspose.Words for Java 20.9 generated output DOCX file showing the undesired behavior
  • Your expected DOCX file showing the desired output. You can create this document manually by using MS Word.
  • Please also create a standalone simple Java application (source code without compilation errors) that helps us to reproduce your current problem on our end and attach it here for testing. Please do not include Aspose.Words’ JAR files in it to reduce the file size.

As soon as you get these pieces of information ready, we will start investigation into your particular scenario/issue and provide you more information.

Hi @awais.hafeez ,

Thanks for your response. As i mentioned in the query this is a legacy application, so i am making a DB call and in response i am getting List of object then looping through the list, getting the value.

And that value we are replacing in doc using range.replace() method . This doc already exists in my workspace where we have mapped the value like this

Component Name:$COMPONENT_NAME
Billing Effective Date:$EFFECTIVE_DATE
Billing End Date:$END_DATE

and replacing inside the loop using this

for(int i=0;i<details.size();i++)
{

doc.getRange().replace("$COMPONENT_NAME" , checkNull(details.get(i).getComponentName()) + “,”, false, false);
doc.getRange().replace("$EFFECTIVE_DATE" , checkNull(details.get(i).getBillEffectiveDate()) + “,”, false, false);
doc.getRange().replace("$END_DATE" , checkNull(details.get(i).getBillEndDate()) + “,”, false, false);
}

@sumitit04,

After an initial test with the licensed latest (20.9) version of Aspose.Words for Java, we were unable to reproduce this issue on our end. We suggest you to please simply upgrade to the latest version of Aspose.Words.

We used the following test documents and Java code to test the scsneaio on our end:

218825 test DOCX files.zip (18.4 KB)

Document doc = new Document("C:\\Temp\\test input document.docx");

FindReplaceOptions findReplaceOptions = new FindReplaceOptions();
findReplaceOptions.setDirection(FindReplaceDirection.BACKWARD);

doc.getRange().replace("$COMPONENT_NAME", "COMPONENT_NAME Value", findReplaceOptions);
doc.getRange().replace("$EFFECTIVE_DATE", "EFFECTIVE_DATE Value", findReplaceOptions);
doc.getRange().replace("$END_DATE", "END_DATE Value", findReplaceOptions);
doc.getRange().replace("$RATE", "RATE Value", findReplaceOptions);
doc.getRange().replace("$CURRENCY", "CURRENCY Value", findReplaceOptions);

doc.save("C:\\temp\\awjava-20.9.docx");

Thanks @awais.hafeez for your response.

Actually my question is different , i am looping through list and as per the size of list, these values should get print in the doc. Lets say list has size 2 then these value should get print two time in doc. This is my code reference :

for(int i=0;i<details.size();i++)
{

doc.getRange().replace("$COMPONENT_NAME" , checkNull(details.get(i).getComponentName()) + “,”, false, false);
doc.getRange().replace("$EFFECTIVE_DATE" , checkNull(details.get(i).getBillEffectiveDate()) + “,”, false, false);
doc.getRange().replace("$END_DATE" , checkNull(details.get(i).getBillEndDate()) + “,”, false, false);
}

But what is happening here if list has any number of size it is printing only one time. I am attaching the screenshot of doc file.

image.png (3.6 KB)

@sumitit04,

Suppose you have this DOCX Word template.zip (9.4 KB) file and list size of 3. For each iteration, the following Java code will repeat the template document’s content, replace keywords with list values and append it to final output.

Document doc = new Document("C:\\Temp\\DOCX Word template.docx");
Document finalDoc = (Document) doc.deepClone(false);
finalDoc.removeAllChildren();

FindReplaceOptions findReplaceOptions = new FindReplaceOptions();
findReplaceOptions.setDirection(FindReplaceDirection.BACKWARD);

// Simulate list size = 3
for (int i = 0; i < 3; i++) {
    Document tempDoc = (Document) doc.deepClone(true);

    tempDoc.getRange().replace("$COMPONENT_NAME", "COMPONENT_NAME Value " + i, findReplaceOptions);
    tempDoc.getRange().replace("$EFFECTIVE_DATE", "EFFECTIVE_DATE Value " + i, findReplaceOptions);
    tempDoc.getRange().replace("$END_DATE", "END_DATE Value " + i, findReplaceOptions);
    tempDoc.getRange().replace("$RATE", "RATE Value " + i, findReplaceOptions);
    tempDoc.getRange().replace("$CURRENCY", "CURRENCY Value " + i, findReplaceOptions);

    finalDoc.appendDocument(tempDoc, ImportFormatMode.USE_DESTINATION_STYLES);
}

finalDoc.save("C:\\temp\\awjava-20.9.docx");

If this does still not help, then please provide your sample input and expected Word documents here for our referemce.

Thanks @awais.hafeez for your response.

My requirement is little different . In my doc i have Address Details that i am getting through different DB call & we have only one address details , it wont be repeated.

And another is Charges that i am getting through different DB call & here i am getting list of charges , so those charge detail should be repeated in the doc .

So as a conclusion Address Details will be only once in doc & Charges will be multiple times in same doc.

I am unable to attach doc file here , so i am attaching the screenshot for your reference.

image.png (10.6 KB)

@sumitit04,

We are checking this scenario on our end and will get back to you soon.

Hi @awais.hafeez,

Is there any update on this?

Thanks,
Sumit

@sumitit04,

Perhaps you will be able to meet this requirement by using the Mail Merge and Reporting features of Aspose.Words for Java. As an example, please see Nested-MailMerge-Documents.zip (96.5 KB) and try running the following simple code:

DataSet pizzaDs = new DataSet();
pizzaDs.readXml("C:\\Temp\\Nested-MailMerge-Documents\\CustomerData.xml");
Document doc = new Document("C:\\Temp\\Nested-MailMerge-Documents\\Invoice Template.doc");
doc.getMailMerge().executeWithRegions(pizzaDs);
doc.save("C:\\Temp\\Nested-MailMerge-Documents\\awjava-20.9.docx");

And the sample code to turn plain text placeholders in Word document into real merge fields is as follows:

Document doc = new Document("C:\\Temp\\218825 test DOCX files\\DOCX Word template.docx");

FindReplaceOptions opts = new FindReplaceOptions();
opts.setDirection(FindReplaceDirection.BACKWARD);
opts.setReplacingCallback(new ReplaceWithMergefield());

// here you can pass a regular expression as a pattern that you want to turn into rea mere fields
doc.getRange().replace("$COMPONENT_NAME", "", opts);

doc.save("C:\\Temp\\218825 test DOCX files\\awjava-20.9.docx");

static class ReplaceWithMergefield implements IReplacingCallback {
    public int replacing(ReplacingArgs e) throws Exception {
        // This is a Run node that contains either the beginning or the complete match.
        Node currentNode = e.getMatchNode();

        // The first (and may be the only) run can contain text before the match,
        // in this case it is necessary to split the run.
        if (e.getMatchOffset() > 0)
            currentNode = splitRun((Run) currentNode, e.getMatchOffset());

        ArrayList runs = new ArrayList();

        // Find all runs that contain parts of the match string.
        int remainingLength = e.getMatch().group().length();
        while ((remainingLength > 0) && (currentNode != null) && (currentNode.getText().length() <= remainingLength)) {
            runs.add(currentNode);
            remainingLength = remainingLength - currentNode.getText().length();

            // Select the next Run node.
            // Have to loop because there could be other nodes such as BookmarkStart etc.
            do {
                currentNode = currentNode.getNextSibling();
            } while ((currentNode != null) && (currentNode.getNodeType() != NodeType.RUN));
        }

        // Split the last run that contains the match if there is any text left.
        if ((currentNode != null) && (remainingLength > 0)) {
            splitRun((Run) currentNode, remainingLength);
            runs.add(currentNode);
        }

        // to do
        DocumentBuilder builder = new DocumentBuilder((Document) e.getMatchNode().getDocument());
        builder.moveTo((Run) runs.get(runs.size() - 1));
        builder.insertField("MERGEFIELD \"" + e.getMatch().group(0) + "\"", null);

        System.out.println("MERGEFIELD \"" + e.getMatch().group(0) + "\"");

        for (Run run : (Iterable<Run>) runs)
            run.remove();

        // Signal to the replace engine to do nothing because we have already done all what we wanted.
        return ReplaceAction.SKIP;
    }

    /**
     * Splits text of the specified run into two runs. Inserts the new run just
     * after the specified run.
     */
    private Run splitRun(Run run, int position) throws Exception {
        Run afterRun = (Run) run.deepClone(true);
        afterRun.setText(run.getText().substring(position));
        run.setText(run.getText().substring((0), (0) + (position)));
        run.getParentNode().insertAfter(afterRun, run);
        return afterRun;
    }
}

Once you have real merge fields in your input Word document, you will then be able to make use of reporting features of Aspose.Words.

Hi @awais.hafeez,

Thanks for your response but i am little confuse with your solution . Do i need to create xml in my requirement & then i have to do the mail merge with another document ? In my case i don’t have document which has tabular format . So do i need to create tabular format doc ?
Sorry i am little confuse . Can you please explain bit more?

@sumitit04,

The data in XML form and the table structure in template Word document were for demonstration purpose only. You can create any layout in your template document. Also, you can transform your list data source into com.aspose.words.net.System.Data.DataSet or com.aspose.words.net.System.Data.DataTable objects (loop through list items and add rows in DataTable etc) and then pass them to Mail Merge engine to build report. For more information, please refer to following sections of documentation: