Ville
Ville

Reputation: 119

Replace bookmark contents in Word using OpenXml

I can't find any working code examples for replacing bookmark contents. The code should be able to handle both the case replace empty bookmark and replace bookmark with preexisting content.

For example: If I have this text in a Word document:

"Between the following periods comes Bookmark1.. Between next periods comes Bookmark2.."

and I want to insert the text "BM1" between the first periods, and "BM2" between the next.

After the first replacement run, the replacements are inserted correctly.

But after the next replacement run, all of the text on the line after Bookmark1 gets deleted, and then the replacement for Bookmark2 gets inserted.

This is my c# code:

    var doc = WordprocessingDocument.Open(@"file.docx", true);

    public static Dictionary<string, wd.BookmarkStart> FindAllBookmarksInWordFile(WordprocessingDocument file)
    {
        var bookmarkMap = new Dictionary<String, wd.BookmarkStart>();


        foreach (var headerPart in file.MainDocumentPart.HeaderParts)
        {
            foreach (var bookmarkStart in headerPart.RootElement.Descendants<wd.BookmarkStart>())
            {
                if (!bookmarkStart.Name.ToString().StartsWith("_"))
                    bookmarkMap[bookmarkStart.Name] = bookmarkStart;
            }
        }

        foreach (var bookmarkStart in file.MainDocumentPart.RootElement.Descendants<wd.BookmarkStart>())
        {
            if (!bookmarkStart.Name.ToString().StartsWith("_"))
                bookmarkMap[bookmarkStart.Name] = bookmarkStart;
        }


        return bookmarkMap;
    }
    /*extension methods*/
    public static bool IsEndBookmark(this OpenXmlElement element, BookmarkStart startBookmark)
    {
        return IsEndBookmark(element as BookmarkEnd, startBookmark);
    }

    public static bool IsEndBookmark(this BookmarkEnd endBookmark, BookmarkStart startBookmark)
    {
        if (endBookmark == null)
            return false;

        return endBookmark.Id.Value == startBookmark.Id.Value;
    }
    /* end of extension methods */

    public static void SetText(BookmarkStart bookmark, string value)
    {
        RemoveAllTexts(bookmark);

        bookmark.Parent.InsertAfter(new Run(new Text(value)), bookmark);
    }

    private static void RemoveAllTexts(BookmarkStart bookmark)
    {
        if (bookmark.ColumnFirst != null) return;

        var nextSibling = bookmark.NextSibling();

        while (nextSibling != null)
        {
            if (nextSibling.IsEndBookmark(bookmark) || nextSibling.GetType() == typeof(BookmarkStart))
                break;

            foreach (var item in nextSibling.Descendants<Text>())
            {
                item.Remove();
            }
            nextSibling = nextSibling.NextSibling();
        }
    }

I have looked around a long time for a general solution. Any help is appreciated! -Victor

Upvotes: 2

Views: 5429

Answers (2)

yuxin
yuxin

Reputation: 21

Maybe this can help you first:delete bookmarkContent second:find bookMark => insert value

        public static void InsertTest1(WordprocessingDocument doc, string bookMark, string txt)
            {
                try
                {
                    RemoveBookMarkContent(doc, bookMark);

                    MainDocumentPart mainPart = doc.MainDocumentPart;

                    BookmarkStart bmStart = findBookMarkStart(doc, bookMark);
                    if (bmStart == null)
                    {
                        return;
                    }
                    Run run = new Run(new Text(txt));
                    bmStart.Parent.InsertAfter<Run>(run, bmStart);
                }
                catch (Exception c)
                {
                    //not Exception
                }
            }
    public static void RemoveBookMarkContent(WordprocessingDocument doc, string bmName)
            {
                BookmarkStart bmStart = findBookMarkStart(doc, bmName);
                BookmarkEnd bmEnd = findBookMarkEnd(doc, bmStart.Id);
                while (true)
                {
                    var run = bmStart.NextSibling();
                    if (run == null)
                    {
                        break;
                    }
                    if (run is BookmarkEnd && (BookmarkEnd)run == bmEnd)
                    {
                        break;
                    }

                    run.Remove();
                }
            }
private static BookmarkStart findBookMarkStart(WordprocessingDocument doc, string bmName)
        {
            foreach (var footer in doc.MainDocumentPart.FooterParts)
            {
                foreach (var inst in footer.Footer.Descendants<BookmarkStart>())
                {
                    if (inst.Name == bmName)
                    {
                        return inst;
                    }
                }
            }

            foreach (var header in doc.MainDocumentPart.HeaderParts)
            {
                foreach (var inst in header.Header.Descendants<BookmarkStart>())
                {
                    if (inst.Name == bmName)
                    {
                        return inst;
                    }
                }
            }
            foreach (var inst in doc.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
            {
                if (inst is BookmarkStart)
                {
                    if (inst.Name == bmName)
                    {
                        return inst;
                    }
                }
            }

            return null;
        }

Upvotes: 1

Lasse S
Lasse S

Reputation: 1

This code works but not when the bookmark is placed within a field/formtext (a gray box).

    private static void SetNewContents(wd.BookmarkStart bookmarkStart, string text)
    {
        if (bookmarkStart.ColumnFirst != null) return;

        var itemsToRemove = new List<OpenXmlElement>();

        var nextSibling = bookmarkStart.NextSibling();

        while (nextSibling != null)
        {

            if (IsEndBookmark(nextSibling, bookmarkStart))
                break;

            if (nextSibling is wd.Run)
                itemsToRemove.Add(nextSibling);

            nextSibling = nextSibling.NextSibling();
        }

        foreach (var item in itemsToRemove)
        {
            item.RemoveAllChildren();
            item.Remove();
        }

        bookmarkStart.Parent.InsertAfter(new wd.Run(new wd.Text(text)), bookmarkStart);
    }

Upvotes: 0

Related Questions