Replace Bookmark in One Document From another

Hello Team,

I have 2 documents having same number and name of Bookmarks and in one document the bookmark content will change so I want to replace the content of one document’s Bookmark to another document with same formatting all.
How to achieve this in Aspose.Word .NET

To replace the content of a bookmark in one Word document with the content from a bookmark in another document using Aspose.Words for .NET, you can follow these steps:

  1. Load the Source Document: Begin by loading the document that contains the bookmark whose content you want to copy.

  2. Identify the Bookmark: Locate the specific bookmark in the source document. Make sure to replace the placeholder with the actual name of your bookmark.

  3. Create the Destination Document: Prepare the document where you want to paste the copied bookmark content.

  4. Import Bookmarked Content: Use the NodeImporter class to import the content from the source document to the destination document while preserving the formatting.

  5. Define a Method for Copying: Create a method that handles the copying of the bookmarked text, ensuring that the formatting is maintained.

  6. Save the Destination Document: Finally, save the destination document to confirm that the content has been copied successfully.

Here’s a sample code snippet to illustrate the process:

using Aspose.Words;
using Aspose.Words.Import;
using Aspose.Words.Bookmarks;

// Load the source document
Document sourceDoc = new Document("SourceDocument.docx");
Document destinationDoc = new Document();

// Find the bookmark in the source document
Bookmark sourceBookmark = sourceDoc.Range.Bookmarks["MyBookmark1"];
if (sourceBookmark != null)
{
    // Create a NodeImporter to import the content
    NodeImporter importer = new NodeImporter(sourceDoc, destinationDoc, ImportFormatMode.KeepSourceFormatting);
    
    // Import the content of the bookmark
    Node importedNode = importer.ImportNode(sourceBookmark.BookmarkStart.ParentNode, true);
    
    // Insert the imported node into the destination document
    destinationDoc.FirstSection.Body.AppendChild(importedNode);
}

// Save the destination document
destinationDoc.Save("DestinationDocument.docx");

This code will effectively copy the content from the specified bookmark in the source document to the destination document while keeping the original formatting intact. Make sure to handle cases where the bookmark might not be found to avoid exceptions.

For more detailed guidance, you can refer to the tutorial on copying bookmarked text in Word documents using Aspose.Words for .NET here.

If you have any further questions or need additional assistance, feel free to ask!

@Raghul214 Please see our documentation to learn how to extract content of the bookmark into a separate document.
https://docs.aspose.com/words/net/how-to-extract-selected-content-between-nodes-in-a-document/

After extracting the content, you can use DocumentBuilder.InsertDocument method to insert the extracting content into the destination document.

Yes this I understood and I need to replace the bookmark will that work…?
Like copy content of one document’s Bookmark and replace in another Document’s Bookmark…

@Raghul214 Yes, this will work. You should simple set bookmarks text in the destination document to empty string in order to remove old content. Them move DocumentBuilder inside the bookmark and insert the content extracted from another document.

1 Like

Ok this makes sense will ty and let you know

Thanks for that

1 Like
public async Task<string> UpdateDocumentAsync(string baseDocumentPath, string generatedDocumentPath)
{
    return await Task.Run(() =>
    {
        string updatedBaseDocumentPath = Path.Combine(Path.GetTempPath(), "UpdatedBaseDocument.docx");
        // Load documents synchronously
        Document baseTemplate = new Document(baseDocumentPath);
        Document generatedTemplate = new Document(generatedDocumentPath);

        // Create a DocumentBuilder for the generated template
        var builder = new DocumentBuilder(baseTemplate);

        // Iterate through each bookmark in the base template
        foreach (Bookmark baseBookmark in baseTemplate.Range.Bookmarks)
        {
            // Find the corresponding bookmark in the generated template
            Bookmark generatedBookmark = generatedTemplate.Range.Bookmarks[baseBookmark.Name];
            if (generatedBookmark != null)
            {
                // Clear the existing content in the base bookmark
                baseBookmark.Text = "";  // Set text to empty to clear content

                // Move the DocumentBuilder to the base bookmark
                builder.MoveTo(baseBookmark.BookmarkStart);

                // Create a NodeImporter to import the content from the generated template
                NodeImporter importer = new NodeImporter(generatedTemplate, baseTemplate, ImportFormatMode.KeepSourceFormatting);

                // Import all nodes from the generated bookmark
                Node currentNode = generatedBookmark.BookmarkStart.ParentNode.FirstChild;

                while (currentNode != null)
                {
                    // Import each node
                    Node importedNode = importer.ImportNode(currentNode, true);
                    builder.InsertNode(importedNode); // Insert the imported node

                    // Move to the next node
                    currentNode = currentNode.NextSibling;
                }
            }
        }

        // Save the updated base document
        baseTemplate.Save(updatedBaseDocumentPath);

        return updatedBaseDocumentPath;
    });
}

I am using this piece of code for the same and its working for paragraphs but if my Bookmark is table its not working…

Not only that If I add image as bookmark its not working

@Raghul214 Have you tried using ExtractContentHelper as suggested in our documentation?
https://docs.aspose.com/words/net/how-to-extract-selected-content-between-nodes-in-a-document/

Also, please attach your sample input, target and expected output documents here for our reference? We will check the issue and provide you more information.

GeneratedDocument.docx (179.2 KB)

BaseDocument.docx (16.8 KB)

@Raghul214 Implementation of ExtractContentHelper class can be found on our GitHub.

private void ClearBookmarkContent(Bookmark bookmark)
{
CompositeNode parent = bookmark.BookmarkStart.ParentNode;

Node currentNode = bookmark.BookmarkStart.NextSibling;

while (currentNode != null && !currentNode.Equals(bookmark.BookmarkEnd))
{
    Node nextNode = currentNode.NextSibling; 
    parent.RemoveChild(currentNode); 
    currentNode = nextNode; 
}

}

I am using this to remove the bookmark content not bookmark but its not working…
Extraction and replacing for all happens only for tables its not working

Basically I have a table and that is a bookmark if i remove the data its not removed entirely

@Raghul214 You can try using the following code:

Document src = new Document(@"C:\Temp\GeneratedDocument.docx");
Document dst = new Document(@"C:\Temp\BaseDocument.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);

    // Move bookmark outside the table.
    if (bk.BookmarkStart.GetAncestor(NodeType.Table) != null || bk.BookmarkEnd.GetAncestor(NodeType.Table) != null)
    {
        Node parentTable = bk.BookmarkStart.GetAncestor(NodeType.Table) ?? bk.BookmarkEnd.GetAncestor(NodeType.Table);
                   
        parentTable.ParentNode.InsertBefore(bk.BookmarkStart, parentTable);
        parentTable.ParentNode.InsertBefore(bk.BookmarkEnd, parentTable);
        // Remove the table
        parentTable.Remove();
    }

    // Remove dst bookmarks content.
    bk.Text = "";

    Bookmark srcBookmark = src.Range.Bookmarks[bk.Name];
    if (srcBookmark != null)
    {
        // Extract content from the bookmark.
        List<Node> extractedNodes = ExtractContentHelper.ExtractContent(srcBookmark.BookmarkStart, srcBookmark.BookmarkEnd, true);
        Document extractedContent = ExtractContentHelper.GenerateDocument(src, extractedNodes);

        // insert extracted content into the bookmark.
        builder.MoveToBookmark(bk.Name, true, true);
        builder.InsertDocument(extractedContent, ImportFormatMode.UseDestinationStyles);
    }
}

dst.Save(@"C:\Temp\out.docx");

Also, I would recommend you to lean more about Aspose.Words Document Object Model. This will give you more understanding about internal document representation.

Sure will look into that, And this piece of code is not working for me. For the same document which I shared

Its working for some case
Let me see what’s the issue Thank you

@Raghul214 The code shared above works with the documents you have attached earlier. But this is not a general solution, that will work in all possible cases. The code simply gives the hint about the actual implementation. You are free to adjust it according to your needs.

Ok let me see

1 Like

Hello @alexey.noskov I have a doubt I have set a part of table as bookmark and in the cell and row properties I can see only the first cell and first row contains bookmark property not all the cells coming under a bookmark. If thats the case how can I remove those cells…

And also the ExtractContentHelper class doesn’t support Header and Footer content

@Raghul214

I would suggest to try using DocumentExplorer demo to investigate your document internal structure.

headers/footers are applied on the section level. the implementation provided on GitHub does not take in account sections. So in the extracted content document there will be no headers/footers. Here is the modified implementation that takes sections into account:

internal class ExtractContentHelper
{
    //ExStart:CommonExtractContent
    public static List<Node> ExtractContent(Node startNode, Node endNode, bool isInclusive)
    {
        // First, check that the nodes passed to this method are valid for use.
        VerifyParameterNodes(startNode, endNode);

        // Create a list to store the extracted nodes.
        List<Node> nodes = new List<Node>();

        // If either marker is part of a comment, including the comment itself, we need to move the pointer
        // forward to the Comment Node found after the CommentRangeEnd node.
        if (endNode.NodeType == NodeType.CommentRangeEnd && isInclusive)
        {
            Node node = FindNextNode(NodeType.Comment, endNode.NextSibling);
            if (node != null)
                endNode = node;
        }

        // Keep a record of the original nodes passed to this method to split marker nodes if needed.
        Node originalStartNode = startNode;
        Node originalEndNode = endNode;

        // Add the section where the start node is placed.
        nodes.Add(startNode.GetAncestor(NodeType.Section));

        // 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);

        bool isExtracting = true;
        bool isStartingNode = true;
        // The current node we are extracting from the document.
        Node currNode = startNode;

        // Begin extracting content. Process all block-level nodes and specifically split the first
        // and last nodes when needed, so paragraph formatting is retained.
        // Method is a little more complicated than a regular extractor as we need to factor
        // in extracting using inline nodes, fields, bookmarks, etc. to make it useful.
        while (isExtracting)
        {
            // Clone the current node and its children to obtain a copy.
            Node cloneNode = currNode.Clone(true);
            bool isEndingNode = currNode.Equals(endNode);

            if (isStartingNode || isEndingNode)
            {
                // We need to process each marker separately, so pass it off to a separate method instead.
                // End should be processed at first to keep node indexes.
                if (isEndingNode)
                {
                    // !isStartingNode: don't add the node twice if the markers are the same node.
                    ProcessMarker(cloneNode, nodes, originalEndNode, currNode, isInclusive,
                        false, !isStartingNode, false);
                    isExtracting = false;
                }

                // Conditional needs to be separate as the block level start and end markers, maybe the same node.
                if (isStartingNode)
                {
                    ProcessMarker(cloneNode, nodes, originalStartNode, currNode, isInclusive,
                        true, true, false);
                    isStartingNode = false;
                }
            }
            else
                // Node is not a start or end marker, simply add the copy to the list.
                nodes.Add(cloneNode);

            // Move to the next node and extract it. If the next node is null,
            // the rest of the content is found in a different section.
            if (currNode.NextSibling == null && isExtracting)
            {
                // Move to the next section.
                Section nextSection = (Section)currNode.GetAncestor(NodeType.Section).NextSibling;
                nodes.Add(nextSection.Clone(true));
                currNode = nextSection.Body.FirstChild;
            }
            else
            {
                // Move to the next node in the body.
                currNode = currNode.NextSibling;
            }
        }

        // For compatibility with mode with inline bookmarks, add the next paragraph (empty).
        if (isInclusive && originalEndNode == endNode && !originalEndNode.IsComposite)
            IncludeNextParagraph(endNode, nodes);

        // Return the nodes between the node markers.
        return nodes;
    }
    //ExEnd:CommonExtractContent

    //ExStart:CommonGenerateDocument
    public static Document GenerateDocument(Document srcDoc, List<Node> nodes)
    {
        // Clone source document to preserve source styles.
        Document dstDoc = (Document)srcDoc.Clone(false);

        // Import each node from the list into the new document. Keep the original formatting of the node.
        NodeImporter importer = new NodeImporter(srcDoc, dstDoc, ImportFormatMode.UseDestinationStyles);

        foreach (Node node in nodes)
        {
            if (node.NodeType == NodeType.Section)
            {
                Section srcSection = (Section)node;
                Section importedSection = (Section)importer.ImportNode(srcSection, false);
                importedSection.AppendChild(importer.ImportNode(srcSection.Body, false));
                foreach (HeaderFooter hf in srcSection.HeadersFooters)
                    importedSection.HeadersFooters.Add(importer.ImportNode(hf, true));

                dstDoc.AppendChild(importedSection);
            }
            else
            {
                Node importNode = importer.ImportNode(node, true);
                dstDoc.LastSection.Body.AppendChild(importNode);
            }
        }

        return dstDoc;
    }
    //ExEnd:CommonGenerateDocument

    public static void FixCommnetIds(Document doc)
    {
        NodeCollection comments = doc.GetChildNodes(NodeType.Comment, true);
        List<CommentRangeStart> commentRangeStarts = doc.GetChildNodes(NodeType.CommentRangeStart, true).Cast<CommentRangeStart>().ToList();
        List<CommentRangeEnd> commentRangeEnds = doc.GetChildNodes(NodeType.CommentRangeEnd, true).Cast<CommentRangeEnd>().ToList();

        foreach (Comment c in comments)
        {
            CommentRangeStart start = commentRangeStarts.Where(s => s.CustomNodeId == c.CustomNodeId).FirstOrDefault();
            CommentRangeEnd end = commentRangeEnds.Where(e => e.CustomNodeId == c.CustomNodeId).FirstOrDefault();

            start.Id = c.Id;
            end.Id = c.Id;
        }
    }

    //ExStart:CommonExtractContentHelperMethods
    private static void VerifyParameterNodes(Node startNode, Node endNode)
    {
        // The order in which these checks are done is important.
        if (startNode == null)
            throw new ArgumentException("Start node cannot be null");
        if (endNode == null)
            throw new ArgumentException("End node cannot be null");

        if (!startNode.Document.Equals(endNode.Document))
            throw new ArgumentException("Start node and end node must belong to the same document");

        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");

        // Check the end node is after the start node in the DOM tree.
        // First, check if they are in different sections, then if they're not,
        // check their position in the body of the same section.
        Section startSection = (Section)startNode.GetAncestor(NodeType.Section);
        Section endSection = (Section)endNode.GetAncestor(NodeType.Section);

        int startIndex = startSection.ParentNode.IndexOf(startSection);
        int endIndex = endSection.ParentNode.IndexOf(endSection);

        if (startIndex == endIndex)
        {
            if (startSection.Body.IndexOf(GetAncestorInBody(startNode)) >
                endSection.Body.IndexOf(GetAncestorInBody(endNode)))
                throw new ArgumentException("The end node must be after the start node in the body");
        }
        else if (startIndex > endIndex)
            throw new ArgumentException("The section of end node must be after the section start node");
    }

    private static Node FindNextNode(NodeType nodeType, Node fromNode)
    {
        if (fromNode == null || fromNode.NodeType == nodeType)
            return fromNode;

        if (fromNode.IsComposite)
        {
            Node node = FindNextNode(nodeType, ((CompositeNode)fromNode).FirstChild);
            if (node != null)
                return node;
        }

        return FindNextNode(nodeType, fromNode.NextSibling);
    }

    private static void ProcessMarker(Node cloneNode, List<Node> nodes, Node node, Node blockLevelAncestor,
        bool isInclusive, bool isStartMarker, bool canAdd, bool forceAdd)
    {
        // If we are dealing with a block-level node, see if it should be included and add it to the list.
        if (node == blockLevelAncestor)
        {
            if (canAdd && isInclusive)
                nodes.Add(cloneNode);
            return;
        }

        // cloneNode is a clone of blockLevelNode. If node != blockLevelNode, blockLevelAncestor
        // is the node's ancestor that means it is a composite node.
        System.Diagnostics.Debug.Assert(cloneNode.IsComposite);

        // If a marker is a FieldStart node check if it's to be included or not.
        // We assume for simplicity that the FieldStart and FieldEnd appear in the same paragraph.
        if (node.NodeType == NodeType.FieldStart)
        {
            // If the marker is a start node and is not included, skip to the end of the field.
            // If the marker is an end node and is to be included, then move to the end field so the field will not be removed.
            if (isStartMarker && !isInclusive || !isStartMarker && isInclusive)
            {
                while (node.NextSibling != null && node.NodeType != NodeType.FieldEnd)
                    node = node.NextSibling;
            }
        }

        // Support a case if the marker node is on the third level of the document body or lower.
        List<Node> nodeBranch = FillSelfAndParents(node, blockLevelAncestor);

        // Process the corresponding node in our cloned node by index.
        Node currentCloneNode = cloneNode;
        for (int i = nodeBranch.Count - 1; i >= 0; i--)
        {
            Node currentNode = nodeBranch[i];
            int nodeIndex = currentNode.ParentNode.IndexOf(currentNode);
            currentCloneNode = ((CompositeNode)currentCloneNode).GetChild(NodeType.Any, nodeIndex, false);

            RemoveNodesOutsideOfRange(currentCloneNode, isInclusive || (i > 0), isStartMarker);
        }

        //cloneNode.
        // After processing, the composite node may become empty if it has doesn't include it.
        if (canAdd &&
            (forceAdd || ((CompositeNode)cloneNode).HasChildNodes))
        {
            nodes.Add(cloneNode);
        }
    }

    private static void RemoveNodesOutsideOfRange(Node markerNode, bool isInclusive, bool isStartMarker)
    {
        bool isProcessing = true;
        bool isRemoving = isStartMarker;
        Node nextNode = markerNode.ParentNode.FirstChild;

        while (isProcessing && nextNode != null)
        {
            Node currentNode = nextNode;
            bool isSkip = false;

            if (currentNode.Equals(markerNode))
            {
                if (isStartMarker)
                {
                    isProcessing = false;
                    if (isInclusive)
                        isRemoving = false;
                }
                else
                {
                    isRemoving = true;
                    if (isInclusive)
                        isSkip = true;
                }
            }

            nextNode = nextNode.NextSibling;
            if (isRemoving && !isSkip)
                currentNode.Remove();
        }
    }

    private static List<Node> FillSelfAndParents(Node node, Node tillNode)
    {
        List<Node> list = new List<Node>();
        Node currentNode = node;

        while (currentNode != tillNode)
        {
            list.Add(currentNode);
            currentNode = currentNode.ParentNode;
        }

        return list;
    }

    private static void IncludeNextParagraph(Node node, List<Node> nodes)
    {
        Paragraph paragraph = (Paragraph)FindNextNode(NodeType.Paragraph, node.NextSibling);
        if (paragraph != null)
        {
            // Move to the first child to include paragraphs without content.
            Node markerNode = paragraph.HasChildNodes ? paragraph.FirstChild : paragraph;
            Node rootNode = GetAncestorInBody(paragraph);

            ProcessMarker(rootNode.Clone(true), nodes, markerNode, rootNode,
                markerNode == paragraph, false, true, true);
        }
    }

    private static Node GetAncestorInBody(Node startNode)
    {
        while (startNode.ParentNode.NodeType != NodeType.Body)
            startNode = startNode.ParentNode;
        return startNode;
    }
    //ExEnd:CommonExtractContentHelperMethods
}