I’m trying to apply custom format for merge field of number. It works fine for normal number field. However, it doesn’t work for formula field. Please see the code snippet below and for further details please see the attachment which including test code, test template and sample output
Note: Please note that I have tried it on Aspose Word 20.3 and 20.11
public class HandleMailMergeCalculateNumbers {
public static void main(String[] args) throws Exception {
// The path to the documents directory.
String dataDir = Utils.getSharedDataDir(HandleMailMergeCalculateNumbers.class) + "MailMerge/";
// Open an existing document.
Document doc = new Document(dataDir + "MailMergeCalculateNumbers.docx");
//Remove merge field with empty value and if the row are empty=> remove paragraph
doc.getMailMerge().setCleanupOptions(MailMergeCleanupOptions.REMOVE_UNUSED_FIELDS
| MailMergeCleanupOptions.REMOVE_EMPTY_PARAGRAPHS
| MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS
| MailMergeCleanupOptions.REMOVE_CONTAINING_FIELDS);
doc.getMailMerge().setFieldMergingCallback(new CustomedFieldMergingCallback());
doc.getMailMerge().setUnconditionalMergeFieldsAndRegions(true);
// Fill the fields in the document with user data.
doc.getMailMerge().execute(
new String[]{"expected", "evaluation"},
new Object[]{1840.0, 230.0});
dataDir = dataDir + "MergeNumbers_out.doc";
doc.save(dataDir);
System.out.println("\nSimple Mail merge performed with array data successfully.\nFile saved at " + dataDir);
}
}
You are facing the expected behavior of Aspose.Words. Please note that Aspose.Words mimics the behavior of MS Word. If you perform the mail merge using MS Word, you will get the same output.
We suggest you please remove ‘fr_ch’ from number format of formula field.
The ‘fr_ch’ added there as an indicator for custom field merging callback to know which region/locale of formatter (Eg. fr_ch for French Swiss) should be used. So if it’s removed how do the callback knows that?
Please note that I don’t want to use the locale of the server/JDK for the formatter because we are providing our services to customers from difference locales/regions Eg. French, Italia, etc.
Expected: 1’840.00 <++ group separator of single quote
Actual: 1,840.00 <++ group separator of comma
We have tested the scenario using the latest version of Aspose.Words for Java 21.1 and have not found the shared issue with mail merge field. Please check the attached image. Formula Field.png (3.7 KB)
The second field is Formula Field. You need to use the same approach for this field to set the field result as you are doing in IFieldMergingCallback.fieldMerging method.
To format the formula field, please do not set the following cleanup options.
So, your CustomedFieldMergingCallback does not work for second mail merge field (evaluation).
Secondly, if you add fr_ch in mail merge field (evaluation), the code of CustomedFieldMergingCallback is executed and Double.parseDouble returns 230.00 without number separator. When this value is multiplied by 8 in MS Word formula field, the value is formatted according to field switches of formula field i.e. \# fr_ch:#,##0.00 and output value is fr_ch:1,840.00.
You need to perform the same operation for formula field in your code.
You can use following code example to format the formula field using same approach. Hope this helps you.
Document doc = new Document(MyDir + "MailMergeCalculateNumbers.docx");
DocumentBuilder builder = new DocumentBuilder(doc);
//Remove merge field with empty value and if the row are empty=> remove paragraph
/*doc.getMailMerge().setCleanupOptions(MailMergeCleanupOptions.REMOVE_UNUSED_FIELDS
| MailMergeCleanupOptions.REMOVE_EMPTY_PARAGRAPHS
| MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS
| MailMergeCleanupOptions.REMOVE_CONTAINING_FIELDS);*/
doc.getMailMerge().setFieldMergingCallback(new CustomedFieldMergingCallback());
doc.getMailMerge().setUnconditionalMergeFieldsAndRegions(true);
// Fill the fields in the document with user data.
doc.getMailMerge().execute(
new String[]{"expected", "evaluation"},
new Object[]{1840.0, 230.0});
doc.updateFields();
//The first field is formula field in the document, so you can get it from field collection.
Field fma = doc.getRange().getFields().get(0);
String languageTag = LanguageCode.FRENCH.getCode();
String formatString = fma.getFormat().getNumericFormat();
String formatPattern = formatString;
if (formatString.contains(":")) {
String[] formatParts = formatString.split(":");
languageTag = formatParts[0];
formatPattern = formatParts[1];
}
DecimalFormatSymbols formatSymbol = new DecimalFormatSymbols(Locale.forLanguageTag(languageTag));
if (Locale.forLanguageTag(languageTag).getLanguage().isEmpty()) {
formatSymbol = new DecimalFormatSymbols(Locale.forLanguageTag(LanguageCode.FRENCH.getCode()));
}
// This is to handle for Swiss French and undefined case
if ("fr_ch".equalsIgnoreCase(languageTag)) {
formatSymbol.setGroupingSeparator('\'');
formatSymbol.setDecimalSeparator('.');
}
DecimalFormat formatter = new DecimalFormat(formatPattern, formatSymbol);
System.out.println(fma.getResult());
Double value = Double.parseDouble(fma.getResult().toString().replace("fr_ch:", "").replace(",", ""));
builder.moveTo(fma.remove());
builder.write(formatter.format(value));
System.out.println(formatter.format(value));
doc.save(MyDir + "output 21.1.docx");
Further to my previous post, the FieldOptions.ResultFormatter property allows to control how the field result is formatted. You can implement IFieldResultFormatter interface to control how the field result is formatted.
Following code example shows how to automatically apply a custom format to field results as the fields are updated.
Document doc = new Document(MyDir + "MailMergeCalculateNumbers.docx");
DocumentBuilder builder = new DocumentBuilder(doc);
doc.getMailMerge().setFieldMergingCallback(new CustomedFieldMergingCallback());
doc.getMailMerge().setUnconditionalMergeFieldsAndRegions(true);
// Fill the fields in the document with user data.
doc.getMailMerge().execute(
new String[]{"expected", "evaluation"},
new Object[]{1840.0, 230.0});
doc.getFieldOptions().setResultFormatter(new FieldResultFormatter("\\# fr_ch:#,##0.00", null));
doc.updateFields();
doc.getRange().getFields().get(0).unlink();
doc.save(MyDir + "FormatFieldResult_out.docx");
import java.util.ArrayList;
import java.util.Date;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
/**
* Created by Home on 5/29/2017.
*/
//ExStart:FieldResultFormatter
public class FieldResultFormatter implements IFieldResultFormatter {
private final String mNumberFormat;
private final String mDateFormat;
private final ArrayList mNumberFormatInvocations = new ArrayList();
private final ArrayList mDateFormatInvocations = new ArrayList();
public FieldResultFormatter(String numberFormat, String dateFormat) {
mNumberFormat = numberFormat;
mDateFormat = dateFormat;
}
public FieldResultFormatter() {
mNumberFormat = null;
mDateFormat = null;
}
public String format(String arg0, int arg1) {
// TODO Auto-generated method stub
return null;
}
public String format(double arg0, int arg1) {
// TODO Auto-generated method stub
return null;
}
public String formatNumeric(double value, String format) {
// TODO Auto-generated method stub
mNumberFormatInvocations.add(new Object[]{value, format});
if(mNumberFormat.contains(LanguageCode.FRENCH.getCode()))
{
String languageTag = LanguageCode.FRENCH.getCode();
String formatString = format;
String formatPattern = formatString;
if (formatString.contains(":")) {
String[] formatParts = formatString.split(":");
languageTag = formatParts[0];
formatPattern = formatParts[1];
}
DecimalFormatSymbols formatSymbol = new DecimalFormatSymbols(Locale.forLanguageTag(languageTag));
if (Locale.forLanguageTag(languageTag).getLanguage().isEmpty()) {
formatSymbol = new DecimalFormatSymbols(Locale.forLanguageTag(LanguageCode.FRENCH.getCode()));
}
// This is to handle for Swiss French and undefined case
if ("fr_ch".equalsIgnoreCase(languageTag)) {
formatSymbol.setGroupingSeparator('\'');
formatSymbol.setDecimalSeparator('.');
}
DecimalFormat formatter = new DecimalFormat(formatPattern, formatSymbol);
Double value1 = Double.parseDouble(value+"");
return formatter.format(value1);
}
return (mNumberFormat.isEmpty() || mNumberFormat == null) ? null
: String.format(mNumberFormat, value);
}
public String formatDateTime(Date value, String format, int calendarType) {
mDateFormatInvocations
.add(new Object[]{value, format, calendarType});
return (mDateFormat.isEmpty() || mDateFormat == null) ? null : String
.format(mDateFormat, value);
}
}
Thanks for your feedback. it works for formula field now. However, I still want to remove unused fields, empty paragraphs, unused regions and containing fields from the output after all. Can you show me how to do that?
Yes, you can use the cleanup options in your code. However, you need to remove the MailMergeCleanupOptions.REMOVE_CONTAINING_FIELDS from the code. For IF field or Formula field, you need to unlink them after mail merge using Field.Unlink method.
//Remove merge field with empty value and if the row are empty=> remove paragraph
doc.getMailMerge().setCleanupOptions(MailMergeCleanupOptions.REMOVE_UNUSED_FIELDS
| MailMergeCleanupOptions.REMOVE_EMPTY_PARAGRAPHS
| MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS);
//| MailMergeCleanupOptions.REMOVE_CONTAINING_FIELDS);
Regarding the Field.Unlink it work fines for such a simple template as provided previously. However, as I try to apply it to our system I got the error of “Document structure was changed”. Do you have any idea/suggestion for this problem. Please see the full log in attachment. Below is a code snippet from our system.
// The formatting number process in HandleMergeFieldInsert does not work on the formula field
// then it need to handle after merging process with ResultFormatter
// Ref: https://forum.aspose.com/t/custom-format-for-formula-field-doesnt-work/224707/10
// This formatter callback will be invoked as calling template.updateFields
document.getFieldOptions().setResultFormatter(new FieldResultFormatter());
try {
document.updateFields();
// This is to remove conditional field Eg. IF and formula field
// This is an alternative to the clean option of MailMergeCleanupOptions.REMOVE_CONTAINING_FIELDS
for (Field field : document.getRange().getFields()) {
field.unlink();
}
} catch (Exception e) {
e.printStackTrace();
throw new MailMergeException(ComponentErrorCodes.CMPT_014.name(), e.getMessage(), e.getCause());
}
mappingFields(document);
return document;
Please make sure that you are moving the cursor to the correct position and inserting the correct values.
If you still face problem, please attach the following resources here for testing:
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.
Cookie Notice
To provide you with the best experience, we use cookies for personalization, analytics, and ads. By using our site, you agree to our cookie policy.
Enables storage, such as cookies, related to analytics.
Sets consent for sending user data to Google for online advertising purposes.
Sets consent for personalized advertising.
Cookie Notice
To provide you with the best experience, we use cookies for personalization, analytics, and ads. By using our site, you agree to our cookie policy.
More info
Enables storage, such as cookies, related to analytics.
Enables storage, such as cookies, related to advertising.
Sets consent for sending user data to Google for online advertising purposes.
Sets consent for personalized advertising.
Cookie Notice
To provide you with the best experience, we use cookies for personalization, analytics, and ads. By using our site, you agree to our cookie policy.
More info
Enables storage, such as cookies, related to analytics.
Enables storage, such as cookies, related to advertising.
Sets consent for sending user data to Google for online advertising purposes.