What to do if you need to remove bookmark and its entire content from the document altogether. The only method existing in API for that is setting Bookmark.Text to an empty string. But that will work only for the simplest of bookmarks. For bookmark spanning multiple nodes with mixed content, like paragraph, tables, it will fail with a dreaded “Start and end node should have the same grand parent” exception. It will also fail when bookmark spans several sections and bookmark content is crossing the section border.
Taking into account this and the fact that lots of users are requesting the robust bookmark removal capability we have decided to provide a sample code to achieve this functionality. We have already tested this code on several ‘problem’ documents and it works fine as far as I can see. However, it is up to you to try it for yourself and find out if it works for your documents. Please report all encountered problems in the forum. We will try to fix them as soon as they will be reported. Then, after some time and community testing we are going to include this method into Aspose.Words API.
C# Code to Remove Bookmarks in Word Document
private void RemoveBookmarkWithContent(Bookmark bookmark) { // We need to store other bookmark nodes here, to move them away from the removed area. Hashtable bookmarkNames = new Hashtable(); Hashtable bookmarkStarts = new Hashtable(); Hashtable bookmarkEnds = new Hashtable(); ArrayList nodesToRemove = new ArrayList(); BookmarkStart bookmarkStart = bookmark.BookmarkStart; BookmarkEnd bookmarkEnd = bookmark.BookmarkEnd; Document doc = bookmarkStart.Document; Paragraph lastParagraph = doc.LastSection.Body.LastParagraph; Node node = SeekFirstNodeOfBookmarkRange(bookmarkStart); Paragraph endPara = FindParagraphNextAfterBookmark(bookmarkEnd); // Iterate over all nodes that contain or are between bookmark start and end nodes. while(node != bookmarkEnd) { node = node.NextPreOrder(doc); // BookmarkStart/BookmarkEnd are saved to be handled separately later. // All other nodes are collected as candidates for removal. if (!StoreIfBookmark(bookmarkNames, bookmarkStarts, bookmarkEnds, node)) nodesToRemove.Add(node); } foreach (string name in bookmarkNames.Keys) { if ((bookmarkStarts.ContainsKey(name)) && (bookmarkEnds.ContainsKey(name))) { // If bookmark is nested, remove it altogether. RemoveBookmarkNode(name, bookmarkStarts); RemoveBookmarkNode(name, bookmarkEnds); } else { // If bookmark is overlapping, move the contained start/end node to the next paragraph after removed range. if (bookmarkStarts.ContainsKey(name)) MoveBookmarkNode(name, bookmarkStarts, endPara); else MoveBookmarkNode(name, bookmarkEnds, endPara); } } bool hasNodesToRemove = true; while(hasNodesToRemove) { hasNodesToRemove = false; for (int i = 0; i < nodesToRemove.Count; i++) { Node nodeToRemove = (Node)nodesToRemove[i]; // Skip already removed nodes. if (nodeToRemove.ParentNode == null) continue; // Skip nodes that have child nodes. if (nodeToRemove.IsComposite && (nodeToRemove as CompositeNode).HasChildNodes) continue; // Do not remove node if it is the last paragraph in the document. if (nodeToRemove == lastParagraph) continue; // Remove node. nodeToRemove.Remove(); // If at least one node was removed in loop, then the loop will be repeated. hasNodesToRemove = true; } } } private Node SeekFirstNodeOfBookmarkRange(BookmarkStart bookmarkStart) { Node node = bookmarkStart; Document doc = node.Document; // Bookmark nodes located immediately before start of our bookmark should also be included in the removal process. do { node = node.PreviousPreOrder(doc); } while(IsBookmarkNode(node)); // Look back from the bookmark start node to include containing nodes into removal process. while(node.IsComposite) { Node prevNode = node.PreviousPreOrder(doc); if (prevNode == null) break; else node = prevNode; } return node; } private Paragraph FindParagraphNextAfterBookmark(BookmarkEnd bookmarkEnd) { // Find the paragraph that is next to removed bookmark range. // It will be used to move all bookmark start/end nodes belonging to bookmarks overlapping our bookmark, // so that they will be preserved after this bookmark removal. Paragraph para; Node node = bookmarkEnd; Document doc = node.Document; // It can be that the paragraph containing bookmark end node if the last paragraph in the bookmark range // contains other nodes beside BookmarkEnd or is the last unremovable paragraph in the document. if (node.NextSibling != null || node.ParentNode == doc.LastSection.Body.LastParagraph) { para = (Paragraph)bookmarkEnd.ParentNode; } // Or it can be the paragraph next to it. else { while(node.NodeType != NodeType.Paragraph) { node = node.NextPreOrder(doc); } para = (Paragraph)node; } return para; } private bool IsBookmarkNode(Node node) { return (node.NodeType == NodeType.BookmarkStart) || (node.NodeType == NodeType.BookmarkEnd); } private bool StoreIfBookmark(Hashtable bookmarkNames, Hashtable bookmarkStarts, Hashtable bookmarkEnds, Node node) { if (node.NodeType == NodeType.BookmarkStart) { BookmarkStart bookmarkStart = (BookmarkStart)node; bookmarkNames[bookmarkStart.Name] = null; bookmarkStarts.Add(bookmarkStart.Name, bookmarkStart); return true; } else if (node.NodeType == NodeType.BookmarkEnd) { BookmarkEnd bookmarkEnd = (BookmarkEnd)node; bookmarkNames[bookmarkEnd.Name] = null; bookmarkEnds.Add(bookmarkEnd.Name, bookmarkEnd); return true; } return false; } private Node RemoveBookmarkNode(string name, Hashtable bookmarkNodes) { Node node = (Node)bookmarkNodes[name]; node.Remove(); bookmarkNodes.Remove(name); return node; } private void MoveBookmarkNode(string name, Hashtable bookmarkNodes, Paragraph para) { para.PrependChild(RemoveBookmarkNode(name, bookmarkNodes)); } [VB .NET] Private Sub RemoveBookmarkWithContent(ByVal bookmark As Bookmark) ‘ We need to store other bookmark nodes here, to move them away from the removed area. Dim bookmarkNames As Hashtable = New Hashtable Dim bookmarkStarts As Hashtable = New Hashtable Dim bookmarkEnds As Hashtable = New Hashtable Dim nodesToRemove As ArrayList = New ArrayList Dim bookmarkStart As BookmarkStart = bookmark.BookmarkStart Dim bookmarkEnd As BookmarkEnd = bookmark.BookmarkEnd Dim node As Node = bookmarkStart Dim doc As Document = node.Document Dim lastParagraph As Paragraph = doc.LastSection.Body.LastParagraph ‘ Bookmark nodes located immediately before start of our bookmark should also be included in the removal process. Do node = node.PreviousPreOrder(doc) Loop While IsBookmarkNode(node) ‘ Look back from the bookmark start node to include containing nodes into removal process. Do While node.IsComposite Dim prevNode As Node = node.PreviousPreOrder(doc) If prevNode Is Nothing Then Exit Do Else node = prevNode End If Loop ‘ Find the paragraph that is next to removed bookmark range. ‘ It will be used to move all bookmark start/end nodes belonging to bookmarks overlapping our bookmark, ‘ so that they will be preserved after this bookmark removal. Dim endPara As Paragraph ‘ It can be the paragraph containing bookmark end node if the last paragraph in the bookmark range ‘ contains other nodes beside BookmarkEnd or is the last unremovable paragraph in the document. If Not bookmarkEnd.NextSibling Is Nothing OrElse bookmarkEnd.ParentNode Is lastParagraph Then endPara = CType(bookmarkEnd.ParentNode, Paragraph) ‘ Or it can be the paragraph next to it. Else Do While node.NodeType <> NodeType.Paragraph node = node.NextPreOrder(doc) Loop endPara = CType(node, Paragraph) End If ‘ Iterate over all nodes that contain or are between bookmark start and end nodes. Do While Not node Is bookmarkEnd node = node.NextPreOrder(doc) ‘ BookmarkStart/BookmarkEnd are saved to be handled separately later. ‘ All other nodes are collected as candidates for removal. If (Not StoreIfBookmark(bookmarkNames, bookmarkStarts, bookmarkEnds, node)) Then nodesToRemove.Add(node) End If Loop For Each name As String In bookmarkNames.Keys If (bookmarkStarts.ContainsKey(name)) AndAlso (bookmarkEnds.ContainsKey(name)) Then ‘ If bookmark is nested, remove it altogether. RemoveBookmarkNode(name, bookmarkStarts) RemoveBookmarkNode(name, bookmarkEnds) Else ‘ If bookmark is overlapping, move the contained start/end node to the next paragraph after removed range. If bookmarkStarts.ContainsKey(name) Then MoveBookmarkNode(name, bookmarkStarts, endPara) Else MoveBookmarkNode(name, bookmarkEnds, endPara) End If End If Next name Dim hasNodesToRemove As Boolean = True Do While hasNodesToRemove hasNodesToRemove = False Dim i As Integer = 0 Do While i < nodesToRemove.Count Dim nodeToRemove As Node = CType(nodesToRemove(i), Node) ‘ Skip already removed nodes. ‘ Skip nodes that have child nodes. ‘ Do not remove node if it is the last paragraph in the document. If Not (nodeToRemove.ParentNode Is Nothing) And _ Not (nodeToRemove.IsComposite AndAlso CType(nodeToRemove, CompositeNode).HasChildNodes) And _ Not (nodeToRemove Is lastParagraph) Then ‘ Remove node. nodeToRemove.Remove() ‘ If at least one node was removed in loop, then the loop will be repeated. hasNodesToRemove = True End If i += 1 Loop Loop End Sub Private Function IsBookmarkNode(ByVal node As Node) As Boolean Return (node.NodeType = NodeType.BookmarkStart) OrElse (node.NodeType = NodeType.BookmarkEnd) End Function Private Function StoreIfBookmark(ByVal bookmarkNames As Hashtable, ByVal bookmarkStarts As Hashtable, ByVal bookmarkEnds As Hashtable, ByVal node As Node) As Boolean If node.NodeType = NodeType.BookmarkStart Then Dim bookmarkStart As BookmarkStart = CType(node, BookmarkStart) bookmarkNames(bookmarkStart.Name) = Nothing bookmarkStarts.Add(bookmarkStart.Name, bookmarkStart) Return True ElseIf node.NodeType = NodeType.BookmarkEnd Then Dim bookmarkEnd As BookmarkEnd = CType(node, BookmarkEnd) bookmarkNames(bookmarkEnd.Name) = Nothing bookmarkEnds.Add(bookmarkEnd.Name, bookmarkEnd) Return True End If Return False End Function Private Function RemoveBookmarkNode(ByVal name As String, ByVal bookmarkNodes As Hashtable) As Node Dim node As Node = CType(bookmarkNodes(name), Node) node.Remove() bookmarkNodes.Remove(name) Return node End Function Private Sub MoveBookmarkNode(ByVal name As String, ByVal bookmarkNodes As Hashtable, ByVal para As Paragraph) para.PrependChild(RemoveBookmarkNode(name, bookmarkNodes)) End Sub
Private Sub MoveBookmarkNode(ByVal name As String, ByVal bookmarkNodes As Hashtable, ByVal para As Paragraph)
para.PrependChild(RemoveBookmarkNode(name, bookmarkNodes))
End Sub