We're sorry Aspose doesn't work properply without JavaScript enabled.

Free Support Forum - aspose.com

Custom Template Engine

I am trying to implement something similar to mail merge with regions, but more complex. Region start is indicated with text [Group(p1,p2)], region end is indicated with [/Group], and in between there are parameterized data templates such as [Data(arg1,arg2)].


First, I attempted to implement this using Region.Replace method with a custom IReplacingCallback and a regular expression “[Group(\d+,[\d\w_]+)].*?[/Group]”. This works well, but when start and end of the region are in different cells of a table I don’t get a match, and replacing callback never fires.

Do you have any suggestions on how to implement this? I looked into mail merge and LINQ report functionality but neither allows me to execute custom code to evaluate data templates.

Appreciate your help!
Riyad Mammadov
Hi Riyad,

Thanks for your inquiry. To ensure a timely and accurate response, 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 standalone console 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'll start investigation into your issue and provide you more information. Thanks for your cooperation.

PS: To attach these resources, please zip them and Click 'Reply' button that will bring you to the 'reply page' and there at the bottom you can include any attachments with that post by clicking the 'Add/Update' button.

Hi Tahir,


Thanks for replying. Attached ZIP file contains the code and the three Word documents (under ConsoleApplication2/bin/Debug): IN.docx, OUT.docx, and OUT - Expected.docx.

Appreciate your help!

Regards,
Riyad Mammadov
Hi Riyad,

Thanks for sharing the detail. In your case, you need to move the cursor to the matched node or desired location and insert the contents. If the matched node is inside table, please clone the table's row, append it at the end of table, and insert the content inside table's cell. Please check the highlighted code snippet. Hope this helps you.

class Program

{

const string _pattern = @"\[Group\(\d+.*?(.*)[\/Group]";

static void Main(string[] args)

{

var doc = new Aspose.Words.Document("IN.docx");

doc.JoinRunsWithSameFormatting();

MergeTemplate(doc);

doc.Save("out v17.2.docx");

}

private static void MergeTemplate(Document doc)

{

var options = new FindReplaceOptions();

options.ReplacingCallback = new GroupEval();

doc.Range.Replace(new Regex(_pattern, RegexOptions.Multiline), "", options);

}

}

public class GroupEval : IReplacingCallback

{

ReplaceAction IReplacingCallback.Replacing(ReplacingArgs e)

{

// This is a Run node that contains either the beginning or the complete match.

Node currentNode = e.MatchNode;

Console.WriteLine(currentNode.GetText());

// 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.MatchOffset > 0)

currentNode = SplitRun((Run)currentNode, e.MatchOffset);

// This array is used to store all nodes of the match for further removing.

ArrayList runs = new ArrayList();

// Find all runs that contain parts of the match string.

int remainingLength = e.Match.Value.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.NextSibling;

}

while ((currentNode != null) && (currentNode.NodeType != 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);

}

// Create instance of DocumentBuilder and insert contents

DocumentBuilder builder = new DocumentBuilder(e.MatchNode.Document as Document);

builder.MoveTo((Run)runs[runs.Count - 1]);

//If the matched node is inside table, add a new row and

//insert the contents according to your requirements.

Row row = (Row)e.MatchNode.GetAncestor(NodeType.Row);

if (row != null)

{

Row cloneRow = (Row)row.Clone(true);

row.ParentTable.AppendChild(cloneRow);

foreach (Cell cell in row.Cells)

{

cell.RemoveAllChildren();

cell.EnsureMinimum();

builder.MoveTo(cell.FirstParagraph);

builder.Write("Some text");

}

foreach (Cell cell in cloneRow.Cells)

{

cell.RemoveAllChildren();

cell.EnsureMinimum();

builder.MoveTo(cell.FirstParagraph);

builder.Write("Some text");

}

}

else

{

builder.Writeln("John Doe 12/01/2000");

builder.Writeln("Jane Doe 01/30/2001");

}

// Now remove all runs in the sequence.

foreach (Run run in runs)

run.Remove();

// Signal to the replace engine to do nothing because we have already done all what we wanted.

return ReplaceAction.Skip;

}

private static Run SplitRun(Run run, int position)

{

Run afterRun = (Run)run.Clone(true);

afterRun.Text = run.Text.Substring(position);

run.Text = run.Text.Substring(0, position);

run.ParentNode.InsertAfter(afterRun, run);

return afterRun;

}

}

Thank you, Tahir, this gave me a good starting point.

Hi Riyad,

Thanks for your feedback. Please feel free to ask if you have any question about Aspose.Words, we will be happy to help you.