Reputation: 31
I tried to replace the text of a bookmark using openxml. It works just for the first line of each paragraph and for single lines.
my code :
foreach (BookmarkStart bookMarkStart in wordprocessingDocument.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
{
if (bookMarkStart.Name == "signet")
{
OpenXmlElement elem = bookMarkStart.NextSibling();
while (elem != null && !(elem is BookmarkEnd))
{
OpenXmlElement nextElem = elem.NextSibling();
elem.Remove();
elem = nextElem;
}
bookMarkStart.Parent.InsertAfter<Run>(new Run(new Text("teeeest")), bookMarkStart);
}
}
Below is the xml file which uses the tools. Here I have two paragraphs but only the first one is replaced and I am usinf the bookmark with id=0
, the second one is added automaticly
<w:body>
<w:p w:rsidR="0028616D" w:rsidRDefault="005537D9">
<w:bookmarkStart w:name="signet" w:id="0" />
<w:r>
<w:t>Test1</w:t>
</w:r>
</w:p>
<w:p w:rsidR="005537D9" w:rsidRDefault="005537D9">
<w:r>
<w:t>Test2</w:t>
</w:r>
<w:bookmarkStart w:name="_GoBack" w:id="1" />
<w:bookmarkEnd w:id="0" />
<w:bookmarkEnd w:id="1" />
</w:p>
<w:sectPr w:rsidR="005537D9">
<w:pgSz w:w="11906" w:h="16838" />
<w:pgMar w:top="1417" w:right="1417" w:bottom="1417" w:left="1417" w:header="708" w:footer="708" w:gutter="0" />
<w:cols w:space="708" />
<w:docGrid w:linePitch="360" />
</w:sectPr>
</w:body>
Upvotes: 2
Views: 2520
Reputation: 25693
Here's code that works for exactly the XML construction you show: The bookmark starts and ends within a paragraph, at the start and end of a paragraph. There are numerous other variations and each must be catered to explicitly.
A bookmark consists of a Start and an End point. You need both in order to get the content.
Since a document can have multiple bookmarks, and bookmarks can overlap, it's necessary to get the Id
of the bookmark in order to identify which end point matches the starting point. The name is only present in the BookmarkStart
element. Only the Id
is used in both start and end elements.
It's necessary to determine where (in what kind of structure) the bookmark start and end points lie as this provides information about what the parent, sibling and child elements can be. For this specific use case, as both bookmark start and end are within paragraphs the parents of both are Paragraph
elements. The code below determines this by checking the Parent.LocalName
.
In this case, the parent paragraphs of both start and end points are determined. In order to edit the content of all paragraphs within the bookmark a List
is created; the parent paragraph of the starting point is added to it. An additional Paragraph
object is created for checking the next sibling paragraphs and this is checked for the bookmark end point. As long as the bookmark end is not in the object for the next sibling paragraph the while
loop is executed; the next Sibling is added to the List
.
Once all the paragraphs up to and including that with the end of the bookmark are in the List
, the List
is looped to replace the text in each paragraph. The first Run
is copied in order to preserve the basic paragraph formatting. All Run
and Text
elements are then removed, the copied Run
is appended with the new text.
At the end, the bookmark end is set to the end of the last paragraph.
private void btnReplaceBookmarkText_Click(object sender, EventArgs e)
{
string fileNameDoc = "path name";
string bkmName = "signet";
string bkmID = "";
string parentTypeStart = "";
string parentTypeEnd = "";
using (WordprocessingDocument pkgDoc = WordprocessingDocument.Open(fileNameDoc, true))
{
Body body = pkgDoc.MainDocumentPart.Document.Body;
BookmarkStart bkmStart = body.Descendants<BookmarkStart>().Where(bkm => bkm.Name == bkmName).FirstOrDefault();
bkmID = bkmStart.Id;
BookmarkEnd bkmEnd = body.Descendants<BookmarkEnd>().Where(bkm => bkm.Id == bkmID).FirstOrDefault();
parentTypeStart = bkmStart.Parent.LocalName;
parentTypeEnd = bkmEnd.Parent.LocalName;
int counter = 0;
if (parentTypeStart == "p" && parentTypeEnd == "p")
{ //bookmark starts at a paragraph and ends within a paragraph
Paragraph bkmParaStart = (Paragraph) bkmStart.Parent;
Paragraph bkmParaEnd = (Paragraph) bkmEnd.Parent;
Paragraph bkmParaNext = (Paragraph) bkmParaStart;
List<Paragraph> paras = new List<Paragraph>();
paras.Add(bkmParaStart);
BookmarkEnd x = bkmParaNext.Descendants<BookmarkEnd>().Where(bkm => bkm.Id == bkmID).FirstOrDefault();
while (x==null)
{
Paragraph nextPara = (Paragraph) bkmParaNext.NextSibling();
if (nextPara != null)
{
paras.Add(nextPara);
bkmParaNext = (Paragraph)nextPara.Clone();
x = bkmParaNext.Descendants<BookmarkEnd>().Where(bkm => bkm.Id == bkmID).FirstOrDefault();
}
}
foreach (Paragraph para in paras)
{
string t = "changed string once more " + counter;
Run firstRun = para.Descendants<Run>().FirstOrDefault();
Run newRun = (Run) firstRun.Clone();
newRun.RemoveAllChildren<Text>();
para.RemoveAllChildren<Run>();
para.RemoveAllChildren<Text>();
para.AppendChild<Run>(newRun).AppendChild<Text>(new Text(t));
}
//After replacing the runs and text the bookmark is at the beginning
//of the paragraph, we want it at the end
BookmarkEnd newBkmEnd = new BookmarkEnd() { Id = bkmID };
Paragraph p = paras.Last<Paragraph>();
p.Descendants<BookmarkEnd>().Where(bkm => bkm.Id==bkmID).FirstOrDefault().Remove();
p.Append(newBkmEnd);
}
}
}
Note: As I'm more at home in the Word object model than XML it's possible the code could be more optimal, but it worked for me.
Upvotes: 2