Document.range.replace not working correctly

I want to find the particular word in sentence and replace it with another.
Written replace evaluator for finding the word in document and replace it with another word.
When replace callback runs, it will create the run list, in that runs word is breaking.
For eg. If I have Word “Highest” then first run has value “high” and second run has value “est” like this.
What is the solution for this?
It should not break the words while splitting runs into multiple runs.

@Deepali_Shirude

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 simple 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.

Not able to upload application. It is giving maximum size error.
So uploading code for method I have written and replace evaluator code as well.
Also attached sample document.

Can you please check and let me know.

Code files.zip (2.7 KB)
Documents.zip (117.7 KB)

@Deepali_Shirude

Could you please share the values of ruleText and actionText variables? We will then be able to execute your code example. We will investigate the issue and provide you more information on it.

For string redline1 RuleText = “highest” and ActionText = “Professional”
For string redline2 RuleText = “act” and ActionText = “negligent act”

@Deepali_Shirude

We have tested the scenario using the latest version of Aspose.Words for .NET 21.10 and have not found the shared issue. So, please use Aspose.Words for .NET 21.10. Please check the attached output DOCX.
21.10.docx (49.4 KB)

Thanks for your response.
Will check and revert back to you on same.

Hi,

I am able to reproduce the issue with Aspose Words for .NET 21.10.
I guess issue is because of base64.
Actually I am not loading the document from directory.
In Power automate, used Get file content action and output of that action is base64string which is passed to API. From base64, reading the document from stream.
So updated the method accordingly. Uploading the same.Code.zip (58.1 KB)

Can you please try with this updated method.

@Deepali_Shirude

We have tested the scenario and managed to reproduce the same issue at our side. For the sake of correction, we have logged this problem in our issue tracking system as WORDSNET-22867. You will be notified via this forum thread once this issue is resolved.

We apologize for your inconvenience.

Thanks for you response.
Will wait for the resolution.

Any update on this issue?
We have product deliverables in next week.
It will be really helpful if we got resolution for this issue before that.

@Deepali_Shirude

We try our best to deal with every customer request in a timely fashion, we unfortunately cannot guarantee a delivery date to every customer issue. We work on issues on a first come, first served basis. We feel this is the fairest and most appropriate way to satisfy the needs of the majority of our customers.

Currently, your issue is under analysis phase. Once we complete the analysis of your issue, we will then be able to provide you an estimate.

Thanks for you response.
Will wait for further actions.

@Deepali_Shirude

It is to inform you that the issue which you are facing is actually not a bug in Aspose.Words. So, we have closed this issue (WORDSNET-22867) as ‘Not a Bug’.

Please use following modified code to get the desired output. We have attached the output document with this post for your kind reference.
21.10.docx (54.6 KB)

public static void  FindAndReplace()
{
    //string directory = @"C:\File\";
    //string fileName = "SampleDoc.docx";
    //Document document = new Document(directory + fileName);
    string base64 = ".... document....";
    var bytes = Convert.FromBase64String(base64);
    MemoryStream stream = new MemoryStream(bytes);
    Document document = new Document(stream);
    document.JoinRunsWithSameFormatting();

    List<string> redlines = new List<string>();
    string redline1 = "If level highest standard of care are not followed or the Department determines that the arrangements result in a lack of independence for the DBE involved, no credit for the DBE’s participation as it relates to the material cost will be used toward the Contract goal requirement, and the Contractor will need to make up the difference elsewhere on the project.";
    string redline2 = "(vi)                each Non-Lead Securitization Note Holder shall be entitled to the same indemnity as the Lead Securitization Note Holder under the Lead Securitization Servicing Agreement with respect to the following items; each of the Master Servicer, the Special Servicer, the Trustee, the Certificate Administrator, the Operating Advisor, and the Custodian shall be required to indemnify each Certifying Person and each Non-Lead Depositor for any public Other Securitization Trust, and their respective directors and officers and controlling persons, to the same extent that they indemnify the Depositor (as depositor in respect of the Lead Securitization) and each Certifying Person for (a) its failure to deliver the items in clause (vii) below in a timely manner, (b) its failure to perform its obligations to such Non-Lead Depositor or applicable Non-Lead Trustee under Article XI (or any article substantially similar thereto that addresses Exchange act reporting and Regulation AB compliance) of the Lead Securitization Servicing Agreement by the  indemnification time required after giving effect to any applicable grace period or cure period, (c) the failure of any Servicing Function Participant or Additional Servicer retained by it (other than any Initial Sub-Servicer) to perform its obligations to such Non-Lead Depositor or Non-Lead Trustee under Article XI (or any article substantially similar thereto that addresses Exchange act reporting and Regulation AB compliance) of the Lead Securitization Servicing Agreement by the time required after giving effect to any applicable grace period or cure period; and/or (d) any deficient Exchange act report regarding, and delivered by or on behalf of, such party;";
    redlines.Add(redline1);
    redlines.Add(redline2);

    string actionText = "Professional";
    string ruleText = "highest";


    ReplaceEvaluatorFindAndReplace replaceEvaluatorFindAndReplace = new ReplaceEvaluatorFindAndReplace(document, ruleText);

    FindReplaceOptions options = new FindReplaceOptions
    {
        ReplacingCallback = replaceEvaluatorFindAndReplace,
        Direction = FindReplaceDirection.Forward,
        FindWholeWordsOnly = true
    };

    if (redlines != null && redlines.Any())
    {
        foreach (var r in redlines)
        {
            if (!string.IsNullOrEmpty(r) && !string.IsNullOrEmpty(actionText) && !string.IsNullOrEmpty(ruleText))
            {
                document.Range.Replace(r, actionText, options);
                document.UpdatePageLayout();
            }
        }
    }

    actionText = "negligent act";
    ruleText = "act";

    replaceEvaluatorFindAndReplace = new ReplaceEvaluatorFindAndReplace(document, ruleText);

    options = new FindReplaceOptions
    {
        ReplacingCallback = replaceEvaluatorFindAndReplace,
        Direction = FindReplaceDirection.Forward,
        FindWholeWordsOnly = true
    };

    if (redlines != null && redlines.Any())
    {
        foreach (var r in redlines)
        {
            if (!string.IsNullOrEmpty(r) && !string.IsNullOrEmpty(actionText) && !string.IsNullOrEmpty(ruleText))
            {
                document.Range.Replace(r, actionText, options);
                document.UpdatePageLayout();
            }
        }
    }

    document.Save(MyDir + "21.10.docx");
}

Hi Tahir,

Thank u so much for your response.
The solution you provided is working as expected.

@Deepali_Shirude

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

Hi Tahir,

Getting another issue while replacing multiple words in the same paragraph.
Attaching method and sample input and output document.
Can you please check let me know.
Docs.zip (168.3 KB)

@Deepali_Shirude

You are facing this issue because your document contains non breaking spaces. Please check the attached image for non breaking spaces. spaces.png (83.7 KB)

Please replace the non breaking space in whole document with normal space as shown below to get the desired output.

private static Document FindAndReplace(string actionText, string ruleText)
{
	string base64 = "...your document..";
	var bytes = Convert.FromBase64String(base64);
	MemoryStream stream = new MemoryStream(bytes);
	Document document = new Document(stream);
	document.Range.Replace(ControlChar.NonBreakingSpace, " ", new FindReplaceOptions());
	document.JoinRunsWithSameFormatting();

        // your code...
}

Please note that when your perform the first find and replace operation with performance text and add revision in the paragraph, the next find and replace operation with act will not work. This is because the original text of paragraph is changed.

We suggest you please perform all find and replace process for specific paragraph in IReplacingCallback.Replacing method.

Hi Tahir,

Thanks for the response. The solution for replacing non breaking space with normal space is not working at my end.
Can you please let me know how to perform find and replace for specific paragraph in IReplacingCallback.Replacing method.

@Deepali_Shirude

The solution provided in my last post works as expected. Please check the attached output document and screenshot for detail.
FinalDoc.docx (56.2 KB)
output.png (106.1 KB)

Please find the paragraph text as you are already doing. To find and replace ruleText and actionText, please use the approach shared in following code. Hope this helps you.

public class ReplaceEvaluatorFindAndReplace : IReplacingCallback
{
    public Document document;
    public string ruleText; // Word to be replace
    public string actionText;
    public ReplaceEvaluatorFindAndReplace(Document doc, string ruleTxt)
    {
        document = doc;
        ruleText = ruleTxt;
    }

    /// <summary>
    /// This method is called by the Aspose.Words find and replace engine for each match.
    /// This method highlights the match string, even if it spans multiple runs.
    /// </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 highlighting.
        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);
        }

                

        ruleText = "performance";
        actionText = "negligent performance";
                
        document.StartTrackRevisions("Admin");

        // Now highlight all runs in the sequence.
        foreach (Run run in runs)
        {
            // Did this to Find and replace specific word in sentence.
            run.Range.Replace(ruleText, actionText);

            if (run.Range.Text.Contains(actionText))
            {
                run.Font.HighlightColor = Color.Yellow;
            }
        }
        document.StopTrackRevisions();


        ruleText = "act";
        actionText = "negligent act";

        document.StartTrackRevisions("Admin");

        // Now highlight all runs in the sequence.
        foreach (Run run in runs)
        {
            // Did this to Find and replace specific word in sentence.
            run.Range.Replace(ruleText, actionText);

            if (run.Range.Text.Contains(actionText))
            {
                run.Font.HighlightColor = Color.Yellow;
            }
        }
        document.StopTrackRevisions();

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

    /// <summary>
    /// Splits text of the specified run into two runs.
    /// Inserts the new run just after the specified run.
    /// </summary>
    private static Run SplitRun(Run run, int position)
    {
        Run afterRun = (Run)run.Clone(true);
        try
        {
            afterRun.Text = run.Text.Substring(position);

            run.Text = run.Text.Substring(0, position);
            run.ParentNode.InsertAfter(afterRun, run);
        }
        catch (Exception e)
        {
            throw new Exception(e.ToString());
        }

        return afterRun;
    }
}