简体   繁体   中英

How I can replace text of bookmark when bookmark is many paragraphs (lines) with openxml and c#

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>

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM