Insert position problem after Word replace

I have a Word document, there is a variable in the header, I want to replace this variable with FieldPage, but when I execute the following code, the inserted FieldPage is at the front of the Node, how can I insert it at the found position? I tried the MoveToParagraph method, but this method seems to only move to paragraphs in the Body.

void Main()
{
    var doc =  new Document(@"C:\Users\54390\Desktop\demo.docx");
	var finder = new FindReplaceOptions();
	finder.ReplacingCallback = new WordReplaceWithPageIndexCallback();
	var replaceCount = doc.Range.Replace("$num", "", finder);

	doc.Save(@"C:\Users\54390\Desktop\output.docx");
}

public class WordReplaceWithPageIndexCallback : IReplacingCallback
{
	public ReplaceAction Replacing(ReplacingArgs e)
	{
		var builder = new DocumentBuilder((Document)e.MatchNode.Document);
		e.MatchNode.GetText().Dump();
		builder.MoveTo(e.MatchNode);
        //This following code can't move to header paragraph
        //var paragraph = e.MatchNode.GetAncestor(Aspose.Words.NodeType.Paragraph);
        //var index = paragraph.Document.GetChildNodes(NodeType.Paragraph, true).IndexOf(paragraph);
        //builder.MoveToParagraph(index, e.MatchOffset);
		builder.InsertField(Aspose.Words.Fields.FieldType.FieldPage, false);
		e.Replacement = string.Empty;
		return ReplaceAction.Replace;
	}
}

Demo file

demo.docx (19.3 KB)

Screenshot

@sullivan There are two ways to achieve what you need.

  1. Replace placeholder two times.
Document doc = new Document(@"C:\Temp\in.docx");
            
// Replace placeholder first time to make the matched text to be in a separate Run.
doc.Range.Replace("$num", "$0", new FindReplaceOptions() { UseSubstitutions = true });

// Use WordReplaceWithPageIndexCallback to replace placeholder with a field.
FindReplaceOptions finder = new FindReplaceOptions();
finder.ReplacingCallback = new WordReplaceWithPageIndexCallback();
doc.Range.Replace("$num", "", finder);

doc.Save(@"C:\Temp\out.docx");
public class WordReplaceWithPageIndexCallback : IReplacingCallback
{
    public ReplaceAction Replacing(ReplacingArgs e)
    {
        var builder = new DocumentBuilder((Document)e.MatchNode.Document);
        builder.MoveTo(e.MatchNode);
        builder.InsertField(Aspose.Words.Fields.FieldType.FieldPage, false);
        e.Replacement = string.Empty;
        return ReplaceAction.Replace;
    }
}
  1. You can split the matched Run into parts in the IReplacingCallback implemetation:
Document doc = new Document(@"C:\Temp\in.docx");

FindReplaceOptions finder = new FindReplaceOptions();
finder.ReplacingCallback = new ReplaceEvaluatorFindAndInsertField();
// Use field code as a replacement.
doc.Range.Replace("$num", "PAGE", finder);

doc.Save(@"C:\Temp\out.docx");
internal class ReplaceEvaluatorFindAndInsertField : IReplacingCallback
{
    /// <summary>
    /// This method is called by the Aspose.Words find and replace engine for each match.
    /// </summary>
    ReplaceAction IReplacingCallback.Replacing(ReplacingArgs e)
    {
        // This is a Run node that contains either the beginning or the complete match.
        Node currentNode = e.MatchNode;

        // 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 deleting.
        List<Run> runs = new List<Run>();

        // 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((Run)currentNode);
            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((Run)currentNode);
        }

        // Create DocumentBuilder to insert a field.
        DocumentBuilder builder = new DocumentBuilder((Document)e.MatchNode.Document);
        // Move builder to the first run.
        builder.MoveTo(runs[0]);
        // Insert field
        builder.InsertField(e.Replacement);

        // Delete matched runs
        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);
        run.ParentNode.InsertAfter(afterRun, run);
        afterRun.Text = run.Text.Substring(position);
        run.Text = run.Text.Substring((0), (0) + (position));
        return afterRun;
    }
}