Update bookmarks

Hi there,

I am having a problem updating bookmarks, using .NET 6 and Gembox.Document 35.0.1443.

Let me explain my use case.

My Word document needs to be alternately editable by the application and by a user making changes directly:

  • The application creates the document based on a template, adds paragraphs/tables, each delimited by a bookmark, and saves the document.
  • The user can then open the document, add paragraphs and save again.
  • If necessary, the application can open the latest version of the document, look up the bookmarks and update the paragraphs/tables.
  • The user can then continue working and so on.

Problem

Multiple bookmark updates are successful as long as no paragraphs are added by the user.
The bookmarks consisting of several blocks seem to have problems. The BookmarkEnd disappears and the next update throws an error (I added some colours to distinguish between BookmarkStart and BookmarkEnd).

Demo application

Below is a test console application. Type ‘1’ to create the document, type ‘2’ to update. Edit the file between updates to replicate the error.

Program.cs

using BookmarkUpdate;

Console.WriteLine("Bookmark test application");

var updateCycle = 0;

while (true)
{
    if (int.TryParse(Console.ReadLine(), out int x))
    {
        if (x == 1)
        {
            Export.Create();
            Console.WriteLine("Test document with bookmarks created");
        }

        if (x == 2)
        {
            updateCycle++;
            Export.Update(updateCycle);
            Console.WriteLine("Bookmarks updated");
        }
    }
}

Export.cs

using GemBox.Document;
using GemBox.Document.Tables;

namespace BookmarkUpdate
{
    public static class Export
    {
        public static void Create()
        {
            ComponentInfo.SetLicense("FREE-LIMITED-KEY");

            var document = new DocumentModel();

            var firstBookmarkName = "FirstBookmark";
            var secondBookmarkName = "SecondBookmark";
            var thirdBookmarkName = "ThirdBookmark";
            var fourthBookmarkName = "FourthBookmark";

            document.Sections.Add(
                new Section(document,
                    new Paragraph(document, new Run(document, "")),

                    new Paragraph(document,
                        new BookmarkStart(document, firstBookmarkName),
                        new Run(document, "This is a first bookmark."),
                        new BookmarkEnd(document, firstBookmarkName)
                    ) { ParagraphFormat = {BackgroundColor = Color.Yellow}},

                    new Paragraph(document, new Run(document, "")),

                    new Paragraph(document, new BookmarkStart(document, secondBookmarkName)) { ParagraphFormat = { BackgroundColor = Color.Green } },
                    new Paragraph(document, new Run(document, "This is a second bookmark.")),
                    new Paragraph(document, new BookmarkEnd(document, secondBookmarkName)) { ParagraphFormat = { BackgroundColor = Color.Red } },

                    new Paragraph(document, new Run(document, "")),

                    new Paragraph(document,new BookmarkStart(document, thirdBookmarkName)) { ParagraphFormat = { BackgroundColor = Color.Green } },
                    new Table(document, 2, 1, (r, c) => new TableCell(document, new Paragraph(document, $"Original Item {r}-{c}"))),
                    new Paragraph(document, new BookmarkEnd(document, thirdBookmarkName)) { ParagraphFormat = { BackgroundColor = Color.Red } },

                    new Paragraph(document, new Run(document, "")),

                    new Paragraph(document,
                        new BookmarkStart(document, fourthBookmarkName),
                        new Run(document, "This is a fourth bookmark."),
                        new BookmarkEnd(document, fourthBookmarkName)
                    ) { ParagraphFormat = { BackgroundColor = Color.Yellow } },

                    new Paragraph(document, new Run(document, ""))
                )
            );

            document.Save("BookmarkUpdate.docx");
        }

        public static void Update(int updateCycleNumber)
        {
            ComponentInfo.SetLicense("FREE-LIMITED-KEY");

            var document = DocumentModel.Load("BookmarkUpdate.docx");

            var firstBookmarkName = "FirstBookmark";
            var secondBookmarkName = "SecondBookmark";
            var thirdBookmarkName = "ThirdBookmark";
            var fourthBookmarkName = "FourthBookmark";

            var updatedFirst = $"Updated first bookmark, cycle {updateCycleNumber}.";
            document.Bookmarks[firstBookmarkName].GetContent(false).LoadText(updatedFirst);

            var updatedSecond = new Paragraph(document, new Run(document, $"This is an updated second bookmark, cycle {updateCycleNumber}."));
            document.Bookmarks[secondBookmarkName].GetContent(false).Set(updatedSecond.Content);

            var table = new Table(document, 2, 1, (r, c) => new TableCell(document, new Paragraph(document, $"Updated Item {r}-{c}, cycle {updateCycleNumber}")));
            document.Bookmarks[thirdBookmarkName].GetContent(false).Set(table.Content);

            var updatedFourth = new Paragraph(document, new Run(document, $"This is an updated fourth bookmark, cycle {updateCycleNumber}."));
            ContentRange range = document.Bookmarks[fourthBookmarkName].GetContent(false);
            range.LoadText("Test:");
            range.End.InsertRange(updatedFourth.Content);

            document.Save("BookmarkUpdate.docx");
        }
    }
}

Document output without changes made by the user:

After creation, I get the following output:

  • yellow = BookmarkStart and BookmarkEnd on same paragraph
  • green = paragraph with single BookmarkStart
  • red = paragraph with single BookmarkEnd

After first application update, without user changes, no more red paragraphs.

As long as the user doesn’t make any changes, multiple updates are possible.

Document output with changes made by the user:

However, if the user makes a manual change and adds a paragraph after creation, it won’t work:

The user added some text on the first not bookmarked paragraph:
createdAddParagraph

After first application update, one red paragraph is gone, output of table is wrong:

During the second application update, the broken bookmark is no longer found and the application crashes:

document.Bookmarks[secondBookmarkName].GetContent(false).Set(updatedSecond.Content);

System.NullReferenceException: ‘Object reference not set to an instance of an object.’
GemBox.Document.BookmarkCollection.this[string].get returned null.

Any ideas on what I am doing wrong?
What is the best solution for my use case?

Kind regards,
Christophe

Hi Christophe,

Please try again with this bugfix:

Install-Package GemBox.Document -Version 35.0.1465-hotfix

Does this solve your issue?

Regards,
Mario

Hi Mario,

Thank you for your answer!

I’ve tried the hotfix with my test application from the original post, but still have some problems.


For bookmarks with BookmarkStart and BookmarkEnd on the same block element everything works fine.
The LoadText method works fine and the BookmarkStart and BookmarkEnd remain visible on the GetContent(true), even after the bookmark has been updated by both the user and the application.

On creation:

new Paragraph(document,
    new BookmarkStart(document, firstBookmarkName),
    new Run(document, "This is a first bookmark."),
    new BookmarkEnd(document, firstBookmarkName)
)
document.Bookmarks[firstBookmarkName].GetContent(true).GetChildElements().ToList()
Count = 4
    [0]: Paragraph {This is a first bookmark.\r\n}
    [1]: BookmarkStart "FirstBookmark"
    [2]: Run {This is a first bookmark.}
    [3]: BookmarkEnd "FirstBookmark"

After the update:

var updatedFirst = $"This is an updated first bookmark, cycle {updateCycleNumber}.";
document.Bookmarks[firstBookmarkName].GetContent(false).LoadText(updatedFirst);
document.Bookmarks[firstBookmarkName].GetContent(true).GetChildElements().ToList()
Count = 4
    [0]: Paragraph {This is an updated first bookmark, cycle 1.\r\n}
    [1]: BookmarkStart "FirstBookmark"
    [2]: Run {This is an updated first bookmark, cycle 1.}
    [3]: BookmarkEnd "FirstBookmark"

For bookmarks with BookmarkStart and BookmarkEnd on different block elements, problems still occur.
The Set method and the alternative LoadText + InsertRange only work as long as the user does not make any changes. As soon as the user modifies/adds something in the Word document and saves it, the next modification by the application goes wrong.
The BookmarkEnd is never visible on the GetContent(true):

On creation:

new Paragraph(document, new BookmarkStart(document, secondBookmarkName)) 
    { ParagraphFormat = { BackgroundColor = Color.Green } },
new Paragraph(document, new Run(document, "This is a second bookmark.")),
new Paragraph(document, new BookmarkEnd(document, secondBookmarkName)) 
    { ParagraphFormat = { BackgroundColor = Color.Red } },
document.Bookmarks[secondBookmarkName].GetContent(true).GetChildElements().ToList()
Count = 4
    [0]: Paragraph {\r\n}
    [1]: BookmarkStart "SecondBookmark"
    [2]: Paragraph {This is a second bookmark.\r\n}
    [3]: Run {This is a second bookmark.}

After the update, the red colored BookmarkEnd is replaced by an extra green colored BookmarkStart, but this isn’t visible in the GetContent(true):

var updatedSecond = new Paragraph(document, new Run(document, $"This is an updated second bookmark, cycle {updateCycleNumber}."));
document.Bookmarks[secondBookmarkName].GetContent(false).Set(updatedSecond.Content);
document.Bookmarks[secondBookmarkName].GetContent(true).GetChildElements().ToList()
Count = 4
    [0]: Paragraph {\r\n}
    [1]: BookmarkStart "SecondBookmark"
    [2]: Paragraph {This is an updated second bookmark, cycle 1.\r\n}
    [3]: Run {This is an updated second bookmark, cycle 1.}

The third bookmark with the table shows the strangest behaviour. After a user interaction, an additional table is created on the next update.

After creation, the user inserts some paragraphs and saves it.

After the first update, an additional BookmarkStart and Table is created, while this is not visible in the GetContent(true):
thirdBookmark_withUserInput_afterUpdateCycle1

document.Bookmarks[thirdBookmarkName].GetContent(true).GetChildElements().ToList()
Count = 11
   [0]: Paragraph {\r\n}
   [1]: BookmarkStart "ThirdBookmark"
   [2]: Table, 2 rows x 0 columns {Updated Item 0-0, cycle 1\r\nUpdated Item 1-0, cycle 1\r\n}
   [3]: TableRow {Updated Item 0-0, cycle 1\r\n}
   [4]: TableCell {Updated Item 0-0, cycle 1\r\n}
   [5]: Paragraph {Updated Item 0-0, cycle 1\r\n}
   [6]: Run {Updated Item 0-0, cycle 1}
   [7]: TableRow {Updated Item 1-0, cycle 1\r\n}
   [8]: TableCell {Updated Item 1-0, cycle 1\r\n}
   [9]: Paragraph {Updated Item 1-0, cycle 1\r\n}
   [10]: Run {Updated Item 1-0, cycle 1}

The fourth bookmark still fails on the next update after user interaction.

I took a lot of screenshots but the gembox forum only allows me to upload 1 image, please let me know if anything is not clear to reproduce the behaviour.

Kind regards,
Christophe

Hi Christophe,

Unfortunately, this edge case issue occurs because our content model is a simplified representation of the actual Word document’s model:
https://www.gemboxsoftware.com/document/docs/content-model.html

I’m afraid we cannot provide a quick fix for this case.
However, note that we have an internal support ticket for addressing the problems with the ContentRange replacement (including some major refactorings to it) and I’ve also added your report to it.

I believe that we’ll start working on that ticket sometime soon and I’ll contact you again after.

Regards,
Mario