Multiple lines Issue after replacement

Hi,
We have word files that we use as templates and we have variables in them that will be replaced with real data later on. We use Aspose.Words in order to replace variables, but we have issues with appearance.

For example:

Template word:
image.png (7.6 KB)

After we replaced variables on it:
image.png (10.1 KB)

What we did in c# code:

FindAndReplace(document, "[RetailerLongName]", "Retailer Long Name");
FindAndReplace(document, "[RetailerAddress]", "Retailer Address Will Be Here"); 
FindAndReplace(document, "[RetailerTelephone]", "+90123456789"); 
FindAndReplace(document, "[RetailerEmail]", "test@testandtest.com");
FindAndReplace(document, "[RetailerVatNumber]", "123456789"); 
FindAndReplace(document, "[RentalContractNumber]", "123456789"); 
FindAndReplace(document, "[CurrentDate]", DateTime.Now.ToShortDateString()); 
FindAndReplace(document, "[CompanyName]", "Company Name");
FindAndReplace(document, "[CustomerName]", "Customer Name");
FindAndReplace(document, "[CustomerPostcode]", "+12345");
FindAndReplace(document, "[CustomerTown]", "Customer Town");

private static void FindAndReplace(Node doc, string findText, string replaceWithText)
    {
        replaceWithText = replaceWithText ?? string.Empty;
        doc.Range.Replace(findText, replaceWithText, new FindReplaceOptions(FindReplaceDirection.Forward));
    }

Text goes to the next line (multiple lines issue) at the right side of the page. we want to keep the texts in the positions where the variables stood before.

Could you help us at this?

Attachments:
ChangedDocx.docx (32.5 KB)
ConvertedFromPDFtoDocxUsingAdobe.docx (21.6 KB)

@ilvarol please, note that the output you obtained is the expected result. However, there seems to be an issue with the original document where some line breaks are missing. To achieve the desired result, please make use of the following template::
ConvertedFromPDFtoDocxUsingAdobe.docx (23.8 KB)

Thanks, I understood what you mean clearly but we can not do this one by one because we have so many templates. Is there a way to do this programmatically using aspose.words functions?

@ilvarol Sure, I will prepare a code example for you. However, please note that this type of solution tends to be highly specific to the target document, as it depends entirely on the desired structure of the final document.

1 Like

@ilvarol you can use the following code to add the required line breaks in your document:

Document doc = new Document("C:\\Temp\\input.docx");
FindReplaceOptions options = new FindReplaceOptions();
options.ReplacingCallback = new AddLinebreakAtEndOfLineHandler();

options.UseSubstitutions = true;
options.IgnoreDeleted = true;
options.MatchCase = false;

var findText = "[RetailerLongName]";
var replaceWithText = "Retailer Long Name";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[RetailerAddress]";
replaceWithText = "Retailer Address Will Be Here";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[RetailerTelephone]";
replaceWithText = "+90123456789";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[RetailerEmail]";
replaceWithText = "test@testandtest.com";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[RetailerVatNumber]";
replaceWithText = "123456789";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[RentalContractNumber]";
replaceWithText = "123456789";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[CurrentDate]";
replaceWithText = DateTime.Now.ToShortDateString();
doc.Range.Replace(findText, replaceWithText, options);

findText = "[CompanyName]";
replaceWithText = "Company Name";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[CustomerName]";
replaceWithText = "Customer Name";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[CustomerPostcode]";
replaceWithText = "+12345";
doc.Range.Replace(findText, replaceWithText, options);

findText = "[CustomerTown]";
replaceWithText = "Customer Town";
doc.Range.Replace(findText, replaceWithText, options);

doc.Save("C:\\Temp\\output.docx");
public class AddLinebreakAtEndOfLineHandler : IReplacingCallback
{
    private bool _addLineBreak;

    public bool AddLineBreak 
    { 
        get => _addLineBreak; 
        set
        {
            _addLineBreak = value;
        }
    }

    public AddLinebreakAtEndOfLineHandler(bool addLineBreak = true)
    {
        _addLineBreak = addLineBreak;
    }

    ReplaceAction IReplacingCallback.Replacing(ReplacingArgs args)
    {
        Run run = (Run)args.MatchNode;
        var nextNode = run.NextSibling;
        if (_addLineBreak && nextNode != null && nextNode.NodeType == NodeType.Run)
        {
            RemoveUnnecessaryRuns(args.Match.Length, run, args.MatchOffset);
            var nameMatch = Guid.NewGuid().ToString();
            var bookmarkStartMatch = new BookmarkStart(run.Document, nameMatch);
            var bookmarkEndMatch = new BookmarkEnd(run.Document, nameMatch);

            var nameAfterMatch = Guid.NewGuid().ToString();
            var bookmarkStartAfterMatch = new BookmarkStart(run.Document, nameAfterMatch);
            var bookmarkEndAfterMatch = new BookmarkEnd(run.Document, nameAfterMatch);

            Dictionary<string, Run> runsInBookmark = new Dictionary<string, Run>();

            run.ParentParagraph.InsertBefore(bookmarkStartMatch, run);
            run.ParentParagraph.InsertAfter(bookmarkEndMatch, run);
            runsInBookmark[nameMatch] = run;

            run.ParentParagraph.InsertBefore(bookmarkStartAfterMatch, nextNode);
            run.ParentParagraph.InsertAfter(bookmarkEndAfterMatch, nextNode);
            runsInBookmark[nameAfterMatch] = (Run)nextNode;

            LayoutCollector collector = new LayoutCollector((Document)run.Document);
            LayoutEnumerator enumerator = new LayoutEnumerator((Document)run.Document);

            var bcMatch = run.Document.Range.Bookmarks.FirstOrDefault(b => b.Name == nameMatch);
            enumerator.Current = collector.GetEntity(bcMatch.BookmarkStart);
            var startMatchRect = enumerator.Rectangle;

            var bcAfterMatch = run.Document.Range.Bookmarks.FirstOrDefault(b => b.Name == nameAfterMatch);
            enumerator.Current = collector.GetEntity(bcAfterMatch.BookmarkStart);
            var startAfterMatchRect = enumerator.Rectangle;

            // It is the last element in the line
            if (startMatchRect.Y != startAfterMatchRect.Y)
            {
                run.Text = args.Replacement + ControlChar.LineBreak;
                runsInBookmark[nameAfterMatch].Text = runsInBookmark[nameAfterMatch].Text.TrimStart();
            }
            else
            {
                run.Text = args.Replacement;
            }

            bcMatch.Remove();
            bcAfterMatch.Remove();

            return ReplaceAction.Skip;
        }

        return ReplaceAction.Replace;
    }

    private static void RemoveUnnecessaryRuns(int matchTextLength, Run startMatchNode, int matchOffset)
    {
        var remainingLength = matchTextLength;
        if(matchOffset > 0)
        {
            var prevText = startMatchNode.Text.Substring(0, matchOffset);
            Run newRun = (Run)startMatchNode.Clone(true);
            newRun.Text = prevText;
            startMatchNode.ParentParagraph.InsertBefore(newRun, startMatchNode);

            startMatchNode.Text = startMatchNode.Text.Substring(matchOffset);
        }

        remainingLength -= startMatchNode.Text.Length;
            
        //First node check
        if (remainingLength <= 0)
        {
            var nextText = startMatchNode.Text.Substring(matchTextLength);
            Run newRun = (Run)startMatchNode.Clone(true);
            newRun.Text = nextText;
            startMatchNode.ParentParagraph.InsertAfter(newRun, startMatchNode);

            startMatchNode.Text = startMatchNode.Text.Substring(0, matchTextLength);

            return;
        }

        var currentNode = startMatchNode.NextSibling;
        while (remainingLength > 0 && currentNode != null && currentNode.NodeType == NodeType.Run)
        {
            var currentRun = (Run)currentNode;
            startMatchNode.Text += currentRun.Text;
            remainingLength -= currentRun.Text.Length;
            if (remainingLength < 0)
            {
                var spltTextIndex = currentRun.Text.Length + remainingLength;
                var nextText = currentRun.Text.Substring(spltTextIndex);
                Run newRun = (Run)currentRun.Clone(true);
                newRun.Text = nextText;
                currentRun.ParentParagraph.InsertAfter(newRun, currentRun);
            }

            currentNode = currentNode.NextSibling;
            currentRun.Remove();
        }
    }
}

output.docx (15.8 KB)

1 Like

Thanks a lot for your effort. we will be sure variables are independent from each other at least with this handler.

@ilvarol If you require any further assistance, please feel free to contact us. We are here to help.

Additionally, please note that Find/Replace is not he most efficient way to fill the template with data. I would suggest you to take a look into LINQ Reporting Engine direction.

1 Like