MailMerge.ExecuteWithRegions throws for specific mergefield/iffield arrangement

Hi,

I’m seeing an odd issue where some mergefields within iffields are throwing an error on document.MailMerge.ExecuteWithRegions, I’ve attached an example. to give a bit of background on the example:

-MockMailMergeDataSourceNoChildren - is just a stand-in for any actual data I might use, as the issue seems to happen no matter what merge data we use
-MailMergePreparer - wraps mergefield with tableStart and tableEnd tags reflecting the main mergefield table - this is actual production logic we use, the merge data we get sent is wrapped in a root tag and we need to reflect that on the document for the mailmerges to correctly find the data for each field in the document
-The mailMerge settings in DoMailMerge reflect our settings from production code

as per the example, this only happens for the mergefield and if structure in the template.dotx file, and not for the templateModified.dotx. As manually going through every template we use and changing the if structure isn’t really possible, I wanted to check if there is something I can do programatically on my side to avoid running into this error? I’m expecting a tweak to the MailMergePreparer would be needed, but can’t really pinpoint where it causes the issue just by looking at the list of nodes and was hoping it would be easier on your side if you could debug into the library code. This seems to be happening on both aspose.words for .net 21.8 (our current production version) and on the latest.

Thanks,
Tomek

mergefieldsExample.zip (38.6 KB)

@acturisaspose The problem occurs because structure of created opening and closing TableStart/TableEnd merge fields is incorrect.
You can fix the problem, by modifying the code like this:

public void PrepareDocument(Document document, IMailMergeDataSource mergeData)
{
    var builder = new DocumentBuilder(document);
    var primaryRecordSetName = mergeData.TableName;
    var documentFields = builder.Document.Range.Fields.ToList();
    foreach (var field in documentFields)
    {
        if (!IsNamedMergefield(field))
        {
            continue;
        }

        var fieldName = ((FieldMergeField)field).FieldName;
        Console.WriteLine(fieldName);

        if (fieldName.StartsWith("TableStart"))
        {
            builder.MoveToField(field, true); // Insert nested table start after TableStart
            builder.InsertField($"MERGEFIELD TableStart:{primaryRecordSetName}");
            continue;
        }
        if (fieldName.StartsWith("TableEnd"))
        {
            builder.MoveToField(field, false); // Insert nested table end before TableEnd
            builder.InsertField($"MERGEFIELD TableEnd:{primaryRecordSetName}");
        }
    }
}

Hi Alexey,

The primaryRecordSet is the outer-most table in our data extracts, which is why we wrap it around all other mergefields - it is not that the primaryRecordSet is nested inside something else, but instead all our data is nested inside primaryRecordSet. I think the mock data set code I sent didn’t really show that, but in our production scenario we have all our data wrapped inside a primaryRecordSetTable and we put the tags around the mergefields in the document so that we can take them from records within the primary record set. Is the description enough or should I update the example project with another implementation of the merge data source that would closer reflect the structure we use in our actual code?

Thanks,
Tomek

@acturisaspose Thank you for additional information. If I understand your requirements properly you need to wrap all your regions into the main region. To achieve this your can use the following code:

private static void PrepareDocument(Document doc, IMailMergeDataSource mergeData)
{
    string primaryRecordSetName = mergeData.TableName;
    DocumentBuilder builder = new DocumentBuilder(doc);

    // Wrap the region hierarchy of the document into top most region.
    MailMergeRegionInfo info = doc.MailMerge.GetRegionsHierarchy();

    // Start the region before the start of the first region.
    builder.MoveToField(info.Regions[0].StartField, false);
    builder.InsertField($"MERGEFIELD TableStart:{primaryRecordSetName}");

    // End the region after the end of the last region.
    builder.MoveToField(info.Regions[^1].EndField, true);
    builder.InsertField($"MERGEFIELD TableEnd:{primaryRecordSetName}");
}

Hi Alexey,

Thanks for the .GetRegionsHierarchy() approach you proposed, it is definitely much nicer than the one I was using previously and will simplify my code significantly. Unfortunately, while it does fix the error on the sample code, after applying it to my production code it is still throwing an error, however this time it’s much more informative:
“Found end of mail merge region ‘ALAI’ without matching start.”
I’ve modified the implementation of IMailMergeDataSource to resemble my prod situation more closely so that the issue can be reflected in the sample code. Could you please advise on how I can change it so that when the ifField’s condition is not met, the TableStart before the IF and TableEnd after it will match each other?

Thanks,
Tomek

ConsoleApp2.zip (23.5 KB)

@acturisaspose I think the template should be modified, since you have TableStart before IF condition and TableEnd inside IF condition; and TableStart inside IF condition and TableEnd after the IF condition. Like this:

TableStart:ALAI
IF
    TableEnd:ALAI
    TableStart:ALAI
TableEnd:ALAI

The difference in filed nesting level between TableStart and TableEnd seems causes the problems. I think you should redesign your template such way where TableStart and TableEnd are placed on the same fields nesting level to avoid further issues.