Hello Team,
I’m using the following code to replace bookmark content from one document with content from another:
public void ReplaceBookmarks(string sourceDocPath, string targetDocPath, string outputDocPath)
{
Document src = new Document(sourceDocPath);
Document dst = new Document(targetDocPath);
DocumentBuilder builder = new DocumentBuilder(dst);
List<Bookmark> bookmakrs = dst.Range.Bookmarks.Where(b => !b.Name.StartsWith("_")).ToList();
foreach (Bookmark bk in bookmakrs)
{
Console.WriteLine(bk.Name);
bk.Text = "";
Bookmark srcBookmark = src.Range.Bookmarks[bk.Name];
if (srcBookmark != null)
{
List<Node> extractedNodes = ExtractContentHelper.ExtractContent(srcBookmark.BookmarkStart, srcBookmark.BookmarkEnd, false);
Document extractedContent = ExtractContentHelper.GenerateDocument(src, extractedNodes);
builder.MoveToBookmark(bk.Name, true, true);
builder.InsertDocument(extractedContent, ImportFormatMode.UseDestinationStyles);
}
}
dst.Save(outputDocPath);
}
I’m facing two issues with this code:
- After replacing a bookmark, an extra blank line is being inserted.
- When a bookmark is located inside a table cell, the table borders are also being inserted, which is incorrect.
Could you please help me resolve these issues and provide a corrected version of the code?
DOC_1.docx (13.9 KB)
DOC_2.docx (13.9 KB)
output.docx (11.3 KB)
@KeerthanaRamesh214
Summary
When you clear a bookmark with bk.Text = "" the paragraph that contains the bookmark remains in the document. Later, when you insert the new content a new paragraph is added after that empty one, which shows up as an unwanted blank line.
If the source bookmark is inside a table cell, the node list you extract also includes the table’s row and cell nodes. Inserting that list reproduces the whole table structure (borders, shading, etc.) in the destination document.
Why it happens
| Symptom |
Underlying cause |
| Extra blank line |
bk.Text = "" removes only the text inside the bookmark. The surrounding Paragraph node is left untouched, so an empty paragraph stays in the document. |
| Duplicate table borders |
The extraction routine returns every node between the bookmark’s start and end markers. When the bookmark surrounds a table cell, the returned collection contains the <Table>, <Row> and <Cell> nodes, which are then inserted as a whole table into the target document. |
What to do next
Below is a self‑contained example that works with Aspose.Words for .NET (any recent version). It:
- Removes the entire paragraph that holds the destination bookmark.
- Finds the matching bookmark in the source document.
- Collects only the inner nodes of the source bookmark (excluding the bookmark start/end nodes and any surrounding table hierarchy).
- Imports those nodes into the destination document one‑by‑one, preserving styles but without recreating the surrounding table.
using Aspose.Words;
using System.Collections.Generic;
public class BookmarkReplacer
{
/// <summary>
/// Replaces each bookmark in the target document with the content of the
/// bookmark that has the same name in the source document.
/// </summary>
public void ReplaceBookmarks(string sourcePath,
string targetPath,
string outputPath)
{
// Load the two documents
Document srcDoc = new Document(sourcePath);
Document dstDoc = new Document(targetPath);
// Builder for the destination document
DocumentBuilder builder = new DocumentBuilder(dstDoc);
// Re‑use a single importer to avoid repeated style resolution
NodeImporter importer = new NodeImporter(srcDoc, dstDoc, ImportFormatMode.UseDestinationStyles);
// Process every bookmark in the destination document
foreach (Bookmark dstBookmark in dstDoc.Range.Bookmarks)
{
// 1️⃣ Remove the whole paragraph that contains the destination bookmark
// (the bookmark start node is always inside a Paragraph)
Paragraph para = (Paragraph)dstBookmark.BookmarkStart.ParentNode;
para.Remove();
// 2️⃣ Find the matching bookmark in the source document
Bookmark srcBookmark = srcDoc.Range.Bookmarks[dstBookmark.Name];
if (srcBookmark == null)
continue; // No matching source – nothing to insert
// 3️⃣ Collect the inner nodes of the source bookmark
List<Node> innerNodes = new List<Node>();
Node curNode = srcBookmark.BookmarkStart.NextSibling;
while (curNode != null && curNode != srcBookmark.BookmarkEnd)
{
// Store the node, then move to the next sibling before we import
Node next = curNode.NextSibling;
innerNodes.Add(curNode);
curNode = next;
}
// 4️⃣ Move the builder to the position of the (now removed) bookmark
builder.MoveToBookmark(dstBookmark.Name);
// 5️⃣ Import each collected node and insert it
foreach (Node node in innerNodes)
{
Node imported = importer.ImportNode(node, true);
builder.InsertNode(imported);
}
}
// Save the updated document
dstDoc.Save(outputPath);
}
}
How the code solves the two issues
- No extra blank line – By removing the entire paragraph that held the original bookmark (
para.Remove()), there is no empty paragraph left to appear after the insertion.
- No duplicated table borders – The loop that gathers
innerNodes stops at the bookmark’s end marker, so only the content inside the bookmark (paragraphs, runs, pictures, etc.) is collected. Table‑level nodes (Table, Row, Cell) are not part of that collection, therefore the existing table in the target document remains unchanged.
Optional clean‑up (if stray empty paragraphs still appear)
foreach (Paragraph p in dstDoc.GetChildNodes(NodeType.Paragraph, true))
{
if (string.IsNullOrWhiteSpace(p.GetText()) && p.GetChildNodes(NodeType.Any, true).Count == 0)
p.Remove();
}
Next steps for you
- Add the class above to your project.
- Call
ReplaceBookmarks("source.docx", "target.docx", "result.docx");.
- Open
result.docx and verify that the blank line and duplicate table borders are gone.
- If you encounter special cases (nested tables, complex fields, etc.), let the team know so we can refine the node‑collection logic accordingly.
I could not fully verify this explanation against the retrieved sources. Please double-check the behaviour in your environment and confirm the exact SDK/version.
@KeerthanaRamesh214
To avoid this please use InsertDocumentInline method instead of InsertDocument. Please see the following modified code:
Document src = new Document(@"C:\Temp\DOC_1.docx");
Document dst = new Document(@"C:\Temp\DOC_2.docx");
DocumentBuilder builder = new DocumentBuilder(dst);
List<Bookmark> bookmakrs = dst.Range.Bookmarks.Where(b => !b.Name.StartsWith("_")).ToList();
foreach (Bookmark bk in bookmakrs)
{
Console.WriteLine(bk.Name);
bk.Text = "";
Bookmark srcBookmark = src.Range.Bookmarks[bk.Name];
if (srcBookmark != null)
{
List<Node> extractedNodes = ExtractContentHelper.ExtractContent(srcBookmark.BookmarkStart, srcBookmark.BookmarkEnd, false);
Document extractedContent = ExtractContentHelper.GenerateDocument(src, extractedNodes);
builder.MoveToBookmark(bk.Name, true, true);
builder.InsertDocumentInline(extractedContent, ImportFormatMode.UseDestinationStyles, new ImportFormatOptions());
}
}
dst.Save(@"C:\Temp\out.docx");
This occurs because in ExtractContentHelper demo code, the content is extracted on body level, and in case of BK_4 and BK_5 body level parent is table. See the following lines of code:
// Extract content based on block-level nodes (paragraphs and tables). Traverse through parent nodes to find them.
// We will split the first and last nodes' content, depending if the marker nodes are inline.
startNode = GetAncestorInBody(startNode);
endNode = GetAncestorInBody(endNode);
So the extracted content is wrapped into the table. You are free to modify ExtractContentHelper to fit your needs.
In our particular case the bookmarks content is simple text, so there is no need to use ExtractContentHelper and InsertDocument, you can simple set textual value of the bookmark.
Can you please help me with the code for table cell alone. I have tried fewer code which is not working.
@KeerthanaRamesh214 You can modify GetAncestorInBody like this:
private static Node GetAncestorInBody(Node startNode)
{
while (startNode.ParentNode.NodeType != NodeType.Body)
{
startNode = startNode.ParentNode;
if (startNode.NodeType == NodeType.Paragraph)
break;
}
return startNode;
}
Here is the produced output: out.docx (11.1 KB)
Thank you @alexey.noskov. One more thing can you give me the code for extracting bookmark and replacing bookmark in header and footers as well.
@KeerthanaRamesh214 The technique is the same and you use for bookmarks in the document’s body.
@alexey.noskov so with same
List<Node> extractedNodes = ExtractContentHelper.ExtractContent(srcBookmark.BookmarkStart, srcBookmark.BookmarkEnd, false);
Document extractedContent = ExtractContentHelper.GenerateDocument(src, extractedNodes);
I should be able to achieve without any modification on ExtractContentHelper class…?
@KeerthanaRamesh214 ExtractContentHelper is a simple demo. If it does not work for your real scenarios, you are free to modify it to fit your needs. But, yes, the technique for extracting content between nodes is the same.
I tried with same code but I am getting an issue
Start node and end node must be a child or descendant of a body
can you help me with this. I have attached the document for your reference
DOC_1.docx (16.7 KB)
DOC_2.docx (16.7 KB)
@KeerthanaRamesh214 The exception is thrown in the VerifyParameterNodes by the following condition:
if (startNode.GetAncestor(NodeType.Body) == null || endNode.GetAncestor(NodeType.Body) == null)
throw new ArgumentException("Start node and end node must be a child or descendant of a body");
You can remove this condition and modify GetAncestorInBody method like this:
private static Node GetAncestorInBody(Node startNode)
{
while (startNode.ParentNode.NodeType != NodeType.Body && startNode.ParentNode.NodeType != NodeType.HeaderFooter)
{
startNode = startNode.ParentNode;
if (startNode.NodeType == NodeType.Paragraph)
break;
}
return startNode;
}
You can easily detect this by stepping the code through debugger.
Thanks @alexey.noskov But with this can you check the output document for the same document provided. BK_6 and BK_7 is created in output document but content was not there.
output.docx (12.0 KB)
@KeerthanaRamesh214 Most likely you have mixed your source and destination documents. Please check.
@alexey.noskov I don’t think so I used the below documents
DOC_1.docx (16.7 KB)
DOC_2.docx (16.7 KB)
@KeerthanaRamesh214 In my test code:
Document src = new Document(@"C:\Temp\DOC_1.docx");
Document dst = new Document(@"C:\Temp\DOC_2.docx");
i.e. DOC_1.docx is source document and DOC_2.docx is destination document.
DOC_1.docx (source) document looks like this:
DOC_2.docx (destination) document looks like this:
The output looks like this:
out.docx (12.0 KB)
The values from source document replaced the bookmarks in the destination document.
@alexey.noskov if you view the bookmarks in the output word document BK_6 and BK_7 alone is not highlighting the content instead it shows the start of bookmark why is that so I need bookmark to content need to be highlighted
@KeerthanaRamesh214 Try modifying your code like this:
builder.MoveToBookmark(bk.Name, true, true);
builder.InsertDocumentInline(extractedContent, ImportFormatMode.UseDestinationStyles, new ImportFormatOptions());
if (builder.CurrentNode != bk.BookmarkEnd)
builder.InsertNode(bk.BookmarkEnd);
Yes Thanks for that @alexey.noskov its fine now I have one more issue for the below document
SOURCEDOCUMENT.docx (16.9 KB)
TARGETDOC.docx (18.6 KB)
output.docx (13.3 KB)
U can see in the output document BK_0 an empty table boarder is added I don’t want that kind of issues can you help me on this like how to resolve this with code
@KeerthanaRamesh214 This occurs because BK_0 end is inside the first cell of the table:
To get the expected output you should place bookmark start and end correctly in both source and target documents.