Duplicating internal bookmarks

Hi there,

I am trying to clone the contents of a bookmark into the same document. I need to loop a given number of times, and for each iteration of the loop, get the contents of a specific bookmark and duplicate them in the document.

I have tried the code you provided on this post: <a href="

It’s working fine and I am able to duplicate the contents of the bookmark, however, if there are bookmarks or fields inside the original bookmarks, they do not get duplicated. Is there any way to duplicate the whole contents of the bookmark, including not only paragraphs and text, but also other bookmarks, and fields ?

Thanks,

Joao Maia

Hi Joao,

Thanks for your inquiry.

Did you test the code from this article here? Did you encounter the same problem? I believe the problem you may be running into when duplicating the bookmarks is caused by trying to insert a cloned bookmark with an identical name as one already in the Document. This will cause no error but the second bookmark will not be inserted into the document.

I’m not sure what could be wrong with the fields though. Are you able to post the implementation and template you are using here, and I will take a look into it for you.

Thanks,

Hi there,

Thanks for your reply.

Fields are not a problem, the problem indeed is with the bookmarks. I thought about the problem being with the names of the bookmarks, and I tried to change the name inside the Duplicate function but the Name property is read only. Is there a way of changing the name of the BookmarkStart and BookmarkEnd nodes?

Anyway, here’s my code.

internal void test(int n)
{
Bookmark bm = wordDocument.Range.Bookmarks[“PARECER”];
ArrayList nodes = ExtractContent(bm.BookmarkStart, bm.BookmarkEnd, true);
Bookmark newBookmark = DuplicateContentInBookmark(wordDocument, nodes, bm, “PARECER_1”);

for (int i = 1; i < n - 1; i++)
{
bm = wordDocument.Range.Bookmarks[“PARECER_” + i.ToString()];
nodes = ExtractContent(bm.BookmarkStart, bm.BookmarkEnd, true);
newBookmark = DuplicateContentInBookmark(wordDocument, nodes, bm, “PARECER_” + (i + 1).ToString());
}
}

private static Bookmark DuplicateContentInBookmark(Document doc, ArrayList nodes, Bookmark startbookmark, string newBookmarkName)
{
// Get the parent at the end of the bookmark
CompositeNode bookmarkEndNode = startbookmark.BookmarkEnd.ParentNode;

// Find the block level node to start at
while (bookmarkEndNode.ParentNode.NodeType != NodeType.Body)
bookmarkEndNode = bookmarkEndNode.ParentNode;

DocumentBuilder builder = new DocumentBuilder(doc);
builder.MoveTo(bookmarkEndNode);
builder.StartBookmark(newBookmarkName);

Node currentNode = bookmarkEndNode;
foreach (Node node in nodes)
{
currentNode = currentNode.ParentNode.InsertAfter(node, currentNode);
builder.MoveTo(currentNode);
}

builder.EndBookmark(newBookmarkName);

// We want to return the entire bookmark class so find it from the document
return doc.Range.Bookmarks[newBookmarkName];
}

The ExtractContent method is the same as you mentioned on your reply. I modified the Duplicate method slightly not to reverse the nodes list, because if I did when I ran in in a loop several times, the content would not be in the right order.

There is another slight problem when I run this in a loop (which is what I will have to do in my application). The new bookmark that gets inserted in the document does not start immediately before the paragraph (“This is…”), it starts, then there is a new line, then the paragraph starts. When it runs in a loop, for each iteration there is one more empty line between each copy of the bookmarks content.

I am attaching the simple file I’m using for testing.

Thank you for your help,

Joao Maia

Hi again,

I managed to sort out one of the problems I had: the extra line spacing between each cloning of the original bookmark. If I do the ExtractContent non-inclusive the extra line does not get added each time I duplicate the bookmark’s content.

Thus,

ArrayList nodes = ExtractContent(bm.BookmarkStart, bm.BookmarkEnd, false);

This does the trick.

However I still can’t manage to duplicate the internal bookmarks.

I tried looping through each extracted node’s ChildNodes and checking if they are BookmarkStart or BookmarkEnd nodes, and changing their names, but the Name property is read only. I also tried creating a new bookmark for each inner bookmark found, but still no luck…

Any help you can give me would be appreciated.

Thanks,

Joao Maia

Hi Joao,

Thanks for this additional information.

I have taken a look into this and was able to reproduce the problem. The name property of the BookmarkStart and BookmarkEnd nodes are read only so that they can’t be changed independently (and made invalid). Instead the Bookmark facade allows you to work with both nodes and would allow you to achieve this. i.e bookmarkStart.Bookmark.Name = “New Name” will work.

I had a play with this but ran into some unexpected behaviour from the facade class due to the presense of duplicate bookmarks in the Document. Instead I was able to correctly implement this by searching for the other bookmark node explicitly and then reinserting a new bookmark at the same place.

The code to achieve this is below, I have reworked some of your original code, you may want to change it to suit your needs.

Bookmark bm = doc2.Range.Bookmarks[“PARECER”];

for (int i = 1; i < n - 1; i++)

{

ArrayList nodes = NodeExtractor.ExtractContent(bm.BookmarkStart, bm.BookmarkEnd, true);

DuplicateContentInBookmark(doc2, nodes, bm, i);

}

private static void DuplicateContentInBookmark(Document doc, ArrayList nodes, Bookmark startbookmark, int id)

{

// Get the parent at the end of the bookmark

CompositeNode bookmarkEndNode = startbookmark.BookmarkEnd.ParentNode;

// Text to add to the end of any duplicate bookmarks found

string suffix = “_” + id;

// Find the block level node to start at

while (bookmarkEndNode.ParentNode.NodeType != NodeType.Body)

bookmarkEndNode = bookmarkEndNode.ParentNode;

DocumentBuilder builder = new DocumentBuilder(doc);

Node currentNode = bookmarkEndNode;

foreach (Node node in nodes)

{

// Search inside composite nodes in the duplicated content

if(node.IsComposite){

foreach (Node inlineNode in ((CompositeNode)node))

{

if (inlineNode.NodeType == NodeType.BookmarkStart)

{

BookmarkStart start = (BookmarkStart)inlineNode;

BookmarkEnd end = FindBookmarkEnd(nodes, start.Name);

// New name for the bookmark.

string name = start.Bookmark.Name + suffix;

// Insert the new starting bookmark

builder.MoveTo(start);

builder.StartBookmark(name);

// Insert the new ending bookmark

builder.MoveTo(end);

builder.EndBookmark(name);

// Remove the old ones as they are not needed anymore

start.Remove();

end.Remove();

}

}

}

currentNode = currentNode.ParentNode.InsertAfter(node, currentNode);

}

}

public static BookmarkEnd FindBookmarkEnd(ArrayList nodes, string name)

{

// Search through all nodes in the ArrayList, including child nodes.

foreach (Node node in nodes)

{

if (node.NodeType == NodeType.BookmarkEnd)

{

BookmarkEnd bookmark = (BookmarkEnd)node;

// Found the bookmark

if (bookmark.Name.Equals(name))

return (BookmarkEnd)node;

}

// If this node has child nodes, recurse down into these nodes to check them.

if(node.IsComposite){

// Wrap child nodes into a new array list and call this method again recursively.

BookmarkEnd result = FindBookmarkEnd(new ArrayList(((CompositeNode)node).ChildNodes.ToArray()), name);

if (result != null)

return result;

}

}

return null;

}

Regarding the gap inbetween duplicated content, this was not actually any empty paragraph, this was simply that the ending paragraph of the content actually has a very large space after set (16pt while the rest of the paragraphs had 3pt from memory). If you highlight the last paragraph in your original document and remove this large spacing then it’s fine.

Also please note the current way you are inserting the duplicated content into your document means that the ordering of inserted blocks is reversed in a way, so that the most recent duplicated content is being inserted near the top of the document. You may want to look into this if it’s not expected, I am glad to help if you have any problems.

If you have any further queries, please feel free to ask.

Thanks,

Hi

Thanks for your request. Since duplicated bookmarks are removed just before saving the document, you can rename them just before saving. I created a simple code example, which can help you to achieve this:

// Open document that contain one or more bookmark.

Document doc = new Document(@“Test001\in.doc”);

// To emulate the situation with duplicated bookmarks

// we just clone convent of the document and insert it back into the document.

Section clone = (Section)doc.FirstSection.Clone();

Section clone1 = (Section)doc.FirstSection.Clone();

doc.FirstSection.AppendContent(clone);

doc.FirstSection.AppendContent(clone1);

// Now we have duplicated bookmarks in our document.

// To make sure we can loop over the bookmarks and see that there are duplicated.

foreach (Bookmark bookmark in doc.Range.Bookmarks)

Console.WriteLine(bookmark.Name);

// Duplicated bookmarks are removed from the document just before saving it.

// So we can rename them before saving.

// To rename bookmarks we create DocumentVisitor that will visit only BookmarkStart and BookmarkEnd.

// Also we should create DocumentBuilder that will help us to reinsert bookmarks.

DocumentBuilder builder = new DocumentBuilder(doc);

BoookmarksVisitor visitor = new BoookmarksVisitor(builder);

doc.Accept(visitor);

// Save output.

doc.Save(@“Test001\out.doc”);

==============================================================

private class BoookmarksVisitor : DocumentVisitor

{

public BoookmarksVisitor(DocumentBuilder builder)

{

mBuilder = builder;

}

public override VisitorAction VisitBookmarkStart(BookmarkStart bookmarkStart)

{

string newBookmarkName = GetBookmarkName(bookmarkStart.Name);

if(newBookmarkName!=bookmarkStart.Name)

{

mBuilder.MoveTo(bookmarkStart);

mBuilder.StartBookmark(newBookmarkName);

bookmarkStart.Remove();

}

return VisitorAction.Continue;

}

public override VisitorAction VisitBookmarkEnd(BookmarkEnd bookmarkEnd)

{

string newBookmarkName = mCurrentBookmarks.Pop().ToString();

if (newBookmarkName != bookmarkEnd.Name)

{

mBuilder.MoveTo(bookmarkEnd);

mBuilder.EndBookmark(newBookmarkName);

bookmarkEnd.Remove();

}

return VisitorAction.Continue;

}

///

/// The method chacks whether we already visited such bookmark

/// if so it generated new name for bookmark,

/// if not just returns the original name of bookmark.

///

private string GetBookmarkName(string bookmarkName)

{

if (!mBookmarks.ContainsKey(bookmarkName))

{

mBookmarks[bookmarkName] = 0;

mCurrentBookmarks.Push(bookmarkName);

}

else

{

mBookmarks[bookmarkName]++;

mCurrentBookmarks.Push(string.Format("{0}_{1}", bookmarkName, mBookmarks[bookmarkName]));

}

return mCurrentBookmarks.Peek().ToString();

}

private DocumentBuilder mBuilder;

// Here we will collect bookmarks names to avoid duplication.

private readonly Dictionary<string, int> mBookmarks = new Dictionary<string, int>();

private readonly Stack mCurrentBookmarks = new Stack();

}

Hope this helps.

Best regards,

Hi there,

Thank you for your replies. It’s working now…

Regards,

Joao Maia