使用Open XML SDK替换Word文件中的书签文本。

时间:2021-09-17 09:12:39

I assume v2.0 is better... they have some nice "how to:..." examples but bookmarks don't seem to act as obviously as say a Table... a bookmark is defined by two XML elements BookmarkStart & BookmarkEnd. We have some templates with text in as bookmarks and we simply want to replace bookmarks with some other text... no weird formatting is going on but how do I select/replace bookmark text?

我认为v2.0更好……他们有一些不错的“how to:…”的例子,但是书签似乎并没有像说一个表格那样明显……书签由两个XML元素BookmarkStart和BookmarkEnd定义。我们有一些以文本作为书签的模板,我们只是想用其他文本替换书签……没有奇怪的格式,但如何选择/替换书签文本?

11 个解决方案

#1


13  

Here's my approach after using you guys as inspiration:

这是我的方法,用你们作为灵感:

  IDictionary<String, BookmarkStart> bookmarkMap = 
      new Dictionary<String, BookmarkStart>();

  foreach (BookmarkStart bookmarkStart in file.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
  {
      bookmarkMap[bookmarkStart.Name] = bookmarkStart;
  }

  foreach (BookmarkStart bookmarkStart in bookmarkMap.Values)
  {
      Run bookmarkText = bookmarkStart.NextSibling<Run>();
      if (bookmarkText != null)
      {
          bookmarkText.GetFirstChild<Text>().Text = "blah";
      }
  }

#2


4  

Replace bookmarks with a single content (possibly multiple text blocks).

用一个内容替换书签(可能是多个文本块)。

public static void InsertIntoBookmark(BookmarkStart bookmarkStart, string text)
{
    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(text)), bookmarkStart);
}

First, the existing content between start and end is removed. Then a new run is added directly behind the start (before the end).

首先,删除开始和结束之间的现有内容。然后在开始(结束之前)直接添加一个新的运行。

However, not sure if the bookmark is closed in another section when it was opened or in different table cells, etc. ..

但是,不确定在打开或在不同的表格单元中,书签是否会关闭。

For me it's sufficient for now.

对我来说这已经足够了。

#3


4  

I just figured this out 10 minutes ago so forgive the hackish nature of the code.

我在10分钟前就知道了,所以原谅代码的黑客性质。

First I wrote a helper recursive helper function to find all the bookmarks:

首先,我编写了一个helper递归帮助函数来查找所有的书签:

private static Dictionary<string, BookmarkEnd> FindBookmarks(OpenXmlElement documentPart, Dictionary<string, BookmarkEnd> results = null, Dictionary<string, string> unmatched = null )
{
    results = results ?? new Dictionary<string, BookmarkEnd>();
    unmatched = unmatched ?? new Dictionary<string,string>();

    foreach (var child in documentPart.Elements())
    {
        if (child is BookmarkStart)
        {
            var bStart = child as BookmarkStart;
            unmatched.Add(bStart.Id, bStart.Name);
        }

        if (child is BookmarkEnd)
        {
            var bEnd = child as BookmarkEnd;
            foreach (var orphanName in unmatched)
            {
                if (bEnd.Id == orphanName.Key)
                    results.Add(orphanName.Value, bEnd);
            }
        }

        FindBookmarks(child, results, unmatched);
    }

    return results;
}

That returns me a Dictionary that I can use to part through my replacement list and add the text after the bookmark:

这就给了我一个字典,我可以用它来替换我的替换列表,并在书签之后添加文本:

var bookMarks = FindBookmarks(doc.MainDocumentPart.Document);

foreach( var end in bookMarks )
{
    var textElement = new Text("asdfasdf");
    var runElement = new Run(textElement);

    end.Value.InsertAfterSelf(runElement);
}

From what I can tell inserting into and replacing the bookmarks looks harder. When I used InsertAt instead of InsertIntoSelf I got: "Non-composite elements do not have child elements." YMMV

从我所能看出的插入和替换书签看起来更困难。当我使用InsertAt而不是InsertIntoSelf时,我得到了:“非复合元素没有子元素。”YMMV

#4


3  

After a lot of hours, I have written this method:

经过许多小时,我写了这个方法:

    Public static void ReplaceBookmarkParagraphs(WordprocessingDocument doc, string bookmark, string text)
    {
        //Find all Paragraph with 'BookmarkStart' 
        var t = (from el in doc.MainDocumentPart.RootElement.Descendants<BookmarkStart>()
                 where (el.Name == bookmark) &&
                 (el.NextSibling<Run>() != null)
                 select el).First();
        //Take ID value
        var val = t.Id.Value;
        //Find the next sibling 'text'
        OpenXmlElement next = t.NextSibling<Run>();
        //Set text value
        next.GetFirstChild<Text>().Text = text;

        //Delete all bookmarkEnd node, until the same ID
        deleteElement(next.GetFirstChild<Text>().Parent, next.GetFirstChild<Text>().NextSibling(), val, true);
    }

After that, I call:

之后,我叫:

Public static bool deleteElement(OpenXmlElement parentElement, OpenXmlElement elem, string id, bool seekParent)
{
    bool found = false;

    //Loop until I find BookmarkEnd or null element
    while (!found && elem != null && (!(elem is BookmarkEnd) || (((BookmarkEnd)elem).Id.Value != id)))
    {
        if (elem.ChildElements != null && elem.ChildElements.Count > 0)
        {
            found = deleteElement(elem, elem.FirstChild, id, false);
        }

        if (!found)
        {
            OpenXmlElement nextElem = elem.NextSibling();
            elem.Remove();
            elem = nextElem;
        }
    }

    if (!found)
    {
        if (elem == null)
        {
            if (!(parentElement is Body) && seekParent)
            {
                //Try to find bookmarkEnd in Sibling nodes
                found = deleteElement(parentElement.Parent, parentElement.NextSibling(), id, true);
            }
        }
        else
        {
            if (elem is BookmarkEnd && ((BookmarkEnd)elem).Id.Value == id)
            {
                found = true;
            }
        }
    }

    return found;
}

This code is working good if u have no empty Bookmarks. I hope it can help someone.

如果没有空的书签,这段代码运行良好。我希望它能帮助别人。

#5


2  

Most solutions here assume a regular bookmarking pattern of starting before and ending after runs, which is not always true e.g. if bookmark starts in a para or table and ends somewhere in another para (like others have noted). How about using document order to cope with the case where bookmarks are not placed in a regular structure - the document order will still find all the relevant text nodes in between which can then be replaced. Just do root.DescendantNodes().Where(xtext or bookmarkstart or bookmark end) which will traverse in document order, then one can replace text nodes that appear after seeing a bookmark start node but before seeing an end node.

这里的大多数解决方案都假定在运行之前和结束后都有一个常规的书签模式,这并不总是正确的,例如,如果书签从一个para或表开始,并在另一个para(如其他人所注意到)的某个地方结束。如何使用文档命令来处理没有放置在常规结构中的书签的情况——文档顺序仍然会找到所有相关的文本节点,然后可以替换它们。只做root.DescendantNodes()。其中(xtext或bookmarkstart或bookmark end)将以文档顺序遍历,然后可以替换在看到书签开始节点后出现的文本节点,但在看到结束节点之前。

#6


1  

Here is how i do it and VB to add/replace text between bookmarkStart and BookmarkEnd.

下面是我如何使用它和VB来添加/替换bookmarkStart和BookmarkEnd之间的文本。

<w:bookmarkStart w:name="forbund_kort" w:id="0" /> 
        - <w:r>
          <w:t>forbund_kort</w:t> 
          </w:r>
<w:bookmarkEnd w:id="0" />


Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Wordprocessing

    Public Class PPWordDocx

        Public Sub ChangeBookmarks(ByVal path As String)
            Try
                Dim doc As WordprocessingDocument = WordprocessingDocument.Open(path, True)
                 'Read the entire document contents using the GetStream method:

                Dim bookmarkMap As IDictionary(Of String, BookmarkStart) = New Dictionary(Of String, BookmarkStart)()
                Dim bs As BookmarkStart
                For Each bs In doc.MainDocumentPart.RootElement.Descendants(Of BookmarkStart)()
                    bookmarkMap(bs.Name) = bs
                Next
                For Each bs In bookmarkMap.Values
                    Dim bsText As DocumentFormat.OpenXml.OpenXmlElement = bs.NextSibling
                    If Not bsText Is Nothing Then
                        If TypeOf bsText Is BookmarkEnd Then
                            'Add Text element after start bookmark
                            bs.Parent.InsertAfter(New Run(New Text(bs.Name)), bs)
                        Else
                            'Change Bookmark Text
                            If TypeOf bsText Is Run Then
                                If bsText.GetFirstChild(Of Text)() Is Nothing Then
                                    bsText.InsertAt(New Text(bs.Name), 0)
                                End If
                                bsText.GetFirstChild(Of Text)().Text = bs.Name
                            End If
                        End If

                    End If
                Next
                doc.MainDocumentPart.RootElement.Save()
                doc.Close()
            Catch ex As Exception
                Throw ex
            End Try
        End Sub

    End Class

#7


1  

I took the code from the answer, and had several problems with it for exceptional cases:

我从答案中提取了代码,在特殊情况下有几个问题:

  1. You might want to ignore hidden bookmarks. Bookmarks are hidden if the name starts with an _ (underscore)
  2. 您可能想要忽略隐藏的书签。如果名称以_(下划线)开头,则隐藏书签。
  3. If the bookmark is for one more more TableCell's, you will find it in the BookmarkStart in the first Cell of the row with the property ColumnFirst refering to the 0-based column index of the cell where the bookmark starts. ColumnLast refers to the cell where the bookmark ends, for my special case it was always ColumnFirst == ColumnLast (bookmarks marked only one column). In this case you also won't find a BookmarkEnd.
  4. 如果bookmark是一个更大的TableCell的,那么您将会在BookmarkStart中发现它在第一个单元格的第一个单元格中,它首先引用的是一个单元格的基于0的列索引。ColumnLast指的是书签结束的单元格,对于我的特殊情况,它始终是ColumnFirst == ColumnLast(书签只标记了一个列)。在这种情况下,您也不会找到一个BookmarkEnd。
  5. Bookmarks can be empty, so a BookmarkStart follows directly a BookmarkEnd, in this case you can just call bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  6. 书签可以是空的,所以BookmarkStart直接跟BookmarkEnd连接,在这种情况下,您可以直接调用BookmarkStart . parent。插入后(new Run(新文本(“Hello World”)),bookmarkStart)
  7. Also a bookmark can contain many Text-elements, so you might want to Remove all the other elements, otherwise parts of the Bookmark might be replaced, while other following parts will stay.
  8. 此外,书签可以包含许多文本元素,因此您可能需要删除所有其他元素,否则书签的部分可能会被替换,而其他的部分将保留。
  9. And I'm not sure if my last hack is necessary, since I don't know all the limitations of OpenXML, but after discovering the previous 4, I also didn't trust anymore that there will be a sibling of Run, with a child of Text. So instead I just look at all my siblings (until BookmarEnd which has the same ID as BookmarkStart) and check all the children until I find any Text. - Maybe somebody with more experience with OpenXML can answer if it is necessary?
  10. 我不确定我的最后一个hack是否必要,因为我不知道OpenXML的所有限制,但是在发现了前四个之后,我也不再相信会有一个运行的兄弟,有一个文本的孩子。所以,我只是看看我所有的兄弟姐妹(直到BookmarEnd和BookmarkStart有相同的ID),然后检查所有的孩子,直到我找到任何文本。-也许有更多OpenXML经验的人可以回答是否有必要?

You can view my specific implementation here)

您可以在这里查看我的具体实现)

Hope this helps some of you who experienced the same issues.

希望这能帮助你们中一些遇到同样问题的人。

#8


0  

Here is how I do it in VB.NET:

下面是我在VB.NET网站上的做法:

For Each curBookMark In contractBookMarkStarts

      ''# Get the "Run" immediately following the bookmark and then
      ''# get the Run's "Text" field
      runAfterBookmark = curBookMark.NextSibling(Of Wordprocessing.Run)()
      textInRun = runAfterBookmark.LastChild

      ''# Decode the bookmark to a contract attribute
      lines = DecodeContractDataToContractDocFields(curBookMark.Name, curContract).Split(vbCrLf)

      ''# If there are multiple lines returned then some work needs to be done to create
      ''# the necessary Run/Text fields to hold lines 2 thru n.  If just one line then set the
      ''# Text field to the attribute from the contract
      For ptr = 0 To lines.Count - 1
          line = lines(ptr)
          If ptr = 0 Then
              textInRun.Text = line.Trim()
          Else
              ''# Add a <br> run/text component then add next line
              newRunForLf = New Run(runAfterBookmark.OuterXml)
              newRunForLf.LastChild.Remove()
              newBreak = New Break()
              newRunForLf.Append(newBreak)

              newRunForText = New Run(runAfterBookmark.OuterXml)
              DirectCast(newRunForText.LastChild, Text).Text = line.Trim

              curBookMark.Parent.Append(newRunForLf)
              curBookMark.Parent.Append(newRunForText)
          End If
      Next
Next

#9


0  

The accepted answer and some of the others make assumptions about where the bookmarks are in the document structure. Here's my C# code, which can deal with replacing bookmarks that stretch across multiple paragraphs and correctly replace bookmarks that do not start and end at paragraph boundaries. Still not perfect, but closer... hope it's useful. Edit if you find more ways to improve it!

被接受的答案和其他一些人对书签在文档结构中的位置做出了假设。这是我的c#代码,它可以处理跨多个段落的书签替换,并正确地替换不开始和结束于段落边界的书签。仍然不完美,但更接近……希望它是有用的。编辑如果你找到更多的方法来改进它!

    private static void ReplaceBookmarkParagraphs(MainDocumentPart doc, string bookmark, IEnumerable<OpenXmlElement> paras) {
        var start = doc.Document.Descendants<BookmarkStart>().Where(x => x.Name == bookmark).First();
        var end = doc.Document.Descendants<BookmarkEnd>().Where(x => x.Id.Value == start.Id.Value).First();
        OpenXmlElement current = start;
        var done = false;

        while ( !done && current != null ) {
            OpenXmlElement next;
            next = current.NextSibling();

            if ( next == null ) {
                var parentNext = current.Parent.NextSibling();
                while ( !parentNext.HasChildren ) {
                    var toRemove = parentNext;
                    parentNext = parentNext.NextSibling();
                    toRemove.Remove();
                }
                next = current.Parent.NextSibling().FirstChild;

                current.Parent.Remove();
            }

            if ( next is BookmarkEnd ) {
                BookmarkEnd maybeEnd = (BookmarkEnd)next;
                if ( maybeEnd.Id.Value == start.Id.Value ) {
                    done = true;
                }
            }
            if ( current != start ) {
                current.Remove();
            }

            current = next;
        }

        foreach ( var p in paras ) {
            end.Parent.InsertBeforeSelf(p);
        }
    }

#10


0  

Here's what I ended up with - not 100% perfect but works for simple bookmarks and simple text to insert:

这是我最后的结果——不是100%完美,但适用于简单的书签和简单的文本插入:

private void FillBookmarksUsingOpenXml(string sourceDoc, string destDoc, Dictionary<string, string> bookmarkData)
    {
        string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
        // Make a copy of the template file.
        File.Copy(sourceDoc, destDoc, true);

        //Open the document as an Open XML package and extract the main document part.
        using (WordprocessingDocument wordPackage = WordprocessingDocument.Open(destDoc, true))
        {
            MainDocumentPart part = wordPackage.MainDocumentPart;

            //Setup the namespace manager so you can perform XPath queries 
            //to search for bookmarks in the part.
            NameTable nt = new NameTable();
            XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
            nsManager.AddNamespace("w", wordmlNamespace);

            //Load the part's XML into an XmlDocument instance.
            XmlDocument xmlDoc = new XmlDocument(nt);
            xmlDoc.Load(part.GetStream());

            //Iterate through the bookmarks.
            foreach (KeyValuePair<string, string> bookmarkDataVal in bookmarkData)
            {
                var bookmarks = from bm in part.Document.Body.Descendants<BookmarkStart>()
                          select bm;

                foreach (var bookmark in bookmarks)
                {
                    if (bookmark.Name == bookmarkDataVal.Key)
                    {
                        Run bookmarkText = bookmark.NextSibling<Run>();
                        if (bookmarkText != null)  // if the bookmark has text replace it
                        {
                            bookmarkText.GetFirstChild<Text>().Text = bookmarkDataVal.Value;
                        }
                        else  // otherwise append new text immediately after it
                        {
                            var parent = bookmark.Parent;   // bookmark's parent element

                            Text text = new Text(bookmarkDataVal.Value);
                            Run run = new Run(new RunProperties());
                            run.Append(text);
                            // insert after bookmark parent
                            parent.Append(run);
                        }

                        //bk.Remove();    // we don't want the bookmark anymore
                    }
                }
            }

            //Write the changes back to the document part.
            xmlDoc.Save(wordPackage.MainDocumentPart.GetStream(FileMode.Create));
        }
    }

#11


0  

I needed to replace the text of a bookmark (bookmarks name is "Table") with a table. This is my approach:

我需要用一个表替换书签的文本(书签名称是“Table”)。这是我的方法:

public void ReplaceBookmark( DatasetToTable( ds ) )
{
    MainDocumentPart mainPart = myDoc.MainDocumentPart;
    Body body = mainPart.Document.GetFirstChild<Body>();
    var bookmark = body.Descendants<BookmarkStart>()
                        .Where( o => o.Name == "Table" )
                        .FirstOrDefault();
    var parent = bookmark.Parent; //bookmark's parent element
    if (ds!=null)
    {
        parent.InsertAfterSelf( DatasetToTable( ds ) );
        parent.Remove();
    }
    mainPart.Document.Save();
}


public Table DatasetToTable( DataSet ds )
{
    Table table = new Table();
    //creating table;
    return table;
}

Hope this helps

希望这有助于

#1


13  

Here's my approach after using you guys as inspiration:

这是我的方法,用你们作为灵感:

  IDictionary<String, BookmarkStart> bookmarkMap = 
      new Dictionary<String, BookmarkStart>();

  foreach (BookmarkStart bookmarkStart in file.MainDocumentPart.RootElement.Descendants<BookmarkStart>())
  {
      bookmarkMap[bookmarkStart.Name] = bookmarkStart;
  }

  foreach (BookmarkStart bookmarkStart in bookmarkMap.Values)
  {
      Run bookmarkText = bookmarkStart.NextSibling<Run>();
      if (bookmarkText != null)
      {
          bookmarkText.GetFirstChild<Text>().Text = "blah";
      }
  }

#2


4  

Replace bookmarks with a single content (possibly multiple text blocks).

用一个内容替换书签(可能是多个文本块)。

public static void InsertIntoBookmark(BookmarkStart bookmarkStart, string text)
{
    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(text)), bookmarkStart);
}

First, the existing content between start and end is removed. Then a new run is added directly behind the start (before the end).

首先,删除开始和结束之间的现有内容。然后在开始(结束之前)直接添加一个新的运行。

However, not sure if the bookmark is closed in another section when it was opened or in different table cells, etc. ..

但是,不确定在打开或在不同的表格单元中,书签是否会关闭。

For me it's sufficient for now.

对我来说这已经足够了。

#3


4  

I just figured this out 10 minutes ago so forgive the hackish nature of the code.

我在10分钟前就知道了,所以原谅代码的黑客性质。

First I wrote a helper recursive helper function to find all the bookmarks:

首先,我编写了一个helper递归帮助函数来查找所有的书签:

private static Dictionary<string, BookmarkEnd> FindBookmarks(OpenXmlElement documentPart, Dictionary<string, BookmarkEnd> results = null, Dictionary<string, string> unmatched = null )
{
    results = results ?? new Dictionary<string, BookmarkEnd>();
    unmatched = unmatched ?? new Dictionary<string,string>();

    foreach (var child in documentPart.Elements())
    {
        if (child is BookmarkStart)
        {
            var bStart = child as BookmarkStart;
            unmatched.Add(bStart.Id, bStart.Name);
        }

        if (child is BookmarkEnd)
        {
            var bEnd = child as BookmarkEnd;
            foreach (var orphanName in unmatched)
            {
                if (bEnd.Id == orphanName.Key)
                    results.Add(orphanName.Value, bEnd);
            }
        }

        FindBookmarks(child, results, unmatched);
    }

    return results;
}

That returns me a Dictionary that I can use to part through my replacement list and add the text after the bookmark:

这就给了我一个字典,我可以用它来替换我的替换列表,并在书签之后添加文本:

var bookMarks = FindBookmarks(doc.MainDocumentPart.Document);

foreach( var end in bookMarks )
{
    var textElement = new Text("asdfasdf");
    var runElement = new Run(textElement);

    end.Value.InsertAfterSelf(runElement);
}

From what I can tell inserting into and replacing the bookmarks looks harder. When I used InsertAt instead of InsertIntoSelf I got: "Non-composite elements do not have child elements." YMMV

从我所能看出的插入和替换书签看起来更困难。当我使用InsertAt而不是InsertIntoSelf时,我得到了:“非复合元素没有子元素。”YMMV

#4


3  

After a lot of hours, I have written this method:

经过许多小时,我写了这个方法:

    Public static void ReplaceBookmarkParagraphs(WordprocessingDocument doc, string bookmark, string text)
    {
        //Find all Paragraph with 'BookmarkStart' 
        var t = (from el in doc.MainDocumentPart.RootElement.Descendants<BookmarkStart>()
                 where (el.Name == bookmark) &&
                 (el.NextSibling<Run>() != null)
                 select el).First();
        //Take ID value
        var val = t.Id.Value;
        //Find the next sibling 'text'
        OpenXmlElement next = t.NextSibling<Run>();
        //Set text value
        next.GetFirstChild<Text>().Text = text;

        //Delete all bookmarkEnd node, until the same ID
        deleteElement(next.GetFirstChild<Text>().Parent, next.GetFirstChild<Text>().NextSibling(), val, true);
    }

After that, I call:

之后,我叫:

Public static bool deleteElement(OpenXmlElement parentElement, OpenXmlElement elem, string id, bool seekParent)
{
    bool found = false;

    //Loop until I find BookmarkEnd or null element
    while (!found && elem != null && (!(elem is BookmarkEnd) || (((BookmarkEnd)elem).Id.Value != id)))
    {
        if (elem.ChildElements != null && elem.ChildElements.Count > 0)
        {
            found = deleteElement(elem, elem.FirstChild, id, false);
        }

        if (!found)
        {
            OpenXmlElement nextElem = elem.NextSibling();
            elem.Remove();
            elem = nextElem;
        }
    }

    if (!found)
    {
        if (elem == null)
        {
            if (!(parentElement is Body) && seekParent)
            {
                //Try to find bookmarkEnd in Sibling nodes
                found = deleteElement(parentElement.Parent, parentElement.NextSibling(), id, true);
            }
        }
        else
        {
            if (elem is BookmarkEnd && ((BookmarkEnd)elem).Id.Value == id)
            {
                found = true;
            }
        }
    }

    return found;
}

This code is working good if u have no empty Bookmarks. I hope it can help someone.

如果没有空的书签,这段代码运行良好。我希望它能帮助别人。

#5


2  

Most solutions here assume a regular bookmarking pattern of starting before and ending after runs, which is not always true e.g. if bookmark starts in a para or table and ends somewhere in another para (like others have noted). How about using document order to cope with the case where bookmarks are not placed in a regular structure - the document order will still find all the relevant text nodes in between which can then be replaced. Just do root.DescendantNodes().Where(xtext or bookmarkstart or bookmark end) which will traverse in document order, then one can replace text nodes that appear after seeing a bookmark start node but before seeing an end node.

这里的大多数解决方案都假定在运行之前和结束后都有一个常规的书签模式,这并不总是正确的,例如,如果书签从一个para或表开始,并在另一个para(如其他人所注意到)的某个地方结束。如何使用文档命令来处理没有放置在常规结构中的书签的情况——文档顺序仍然会找到所有相关的文本节点,然后可以替换它们。只做root.DescendantNodes()。其中(xtext或bookmarkstart或bookmark end)将以文档顺序遍历,然后可以替换在看到书签开始节点后出现的文本节点,但在看到结束节点之前。

#6


1  

Here is how i do it and VB to add/replace text between bookmarkStart and BookmarkEnd.

下面是我如何使用它和VB来添加/替换bookmarkStart和BookmarkEnd之间的文本。

<w:bookmarkStart w:name="forbund_kort" w:id="0" /> 
        - <w:r>
          <w:t>forbund_kort</w:t> 
          </w:r>
<w:bookmarkEnd w:id="0" />


Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Wordprocessing

    Public Class PPWordDocx

        Public Sub ChangeBookmarks(ByVal path As String)
            Try
                Dim doc As WordprocessingDocument = WordprocessingDocument.Open(path, True)
                 'Read the entire document contents using the GetStream method:

                Dim bookmarkMap As IDictionary(Of String, BookmarkStart) = New Dictionary(Of String, BookmarkStart)()
                Dim bs As BookmarkStart
                For Each bs In doc.MainDocumentPart.RootElement.Descendants(Of BookmarkStart)()
                    bookmarkMap(bs.Name) = bs
                Next
                For Each bs In bookmarkMap.Values
                    Dim bsText As DocumentFormat.OpenXml.OpenXmlElement = bs.NextSibling
                    If Not bsText Is Nothing Then
                        If TypeOf bsText Is BookmarkEnd Then
                            'Add Text element after start bookmark
                            bs.Parent.InsertAfter(New Run(New Text(bs.Name)), bs)
                        Else
                            'Change Bookmark Text
                            If TypeOf bsText Is Run Then
                                If bsText.GetFirstChild(Of Text)() Is Nothing Then
                                    bsText.InsertAt(New Text(bs.Name), 0)
                                End If
                                bsText.GetFirstChild(Of Text)().Text = bs.Name
                            End If
                        End If

                    End If
                Next
                doc.MainDocumentPart.RootElement.Save()
                doc.Close()
            Catch ex As Exception
                Throw ex
            End Try
        End Sub

    End Class

#7


1  

I took the code from the answer, and had several problems with it for exceptional cases:

我从答案中提取了代码,在特殊情况下有几个问题:

  1. You might want to ignore hidden bookmarks. Bookmarks are hidden if the name starts with an _ (underscore)
  2. 您可能想要忽略隐藏的书签。如果名称以_(下划线)开头,则隐藏书签。
  3. If the bookmark is for one more more TableCell's, you will find it in the BookmarkStart in the first Cell of the row with the property ColumnFirst refering to the 0-based column index of the cell where the bookmark starts. ColumnLast refers to the cell where the bookmark ends, for my special case it was always ColumnFirst == ColumnLast (bookmarks marked only one column). In this case you also won't find a BookmarkEnd.
  4. 如果bookmark是一个更大的TableCell的,那么您将会在BookmarkStart中发现它在第一个单元格的第一个单元格中,它首先引用的是一个单元格的基于0的列索引。ColumnLast指的是书签结束的单元格,对于我的特殊情况,它始终是ColumnFirst == ColumnLast(书签只标记了一个列)。在这种情况下,您也不会找到一个BookmarkEnd。
  5. Bookmarks can be empty, so a BookmarkStart follows directly a BookmarkEnd, in this case you can just call bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  6. 书签可以是空的,所以BookmarkStart直接跟BookmarkEnd连接,在这种情况下,您可以直接调用BookmarkStart . parent。插入后(new Run(新文本(“Hello World”)),bookmarkStart)
  7. Also a bookmark can contain many Text-elements, so you might want to Remove all the other elements, otherwise parts of the Bookmark might be replaced, while other following parts will stay.
  8. 此外,书签可以包含许多文本元素,因此您可能需要删除所有其他元素,否则书签的部分可能会被替换,而其他的部分将保留。
  9. And I'm not sure if my last hack is necessary, since I don't know all the limitations of OpenXML, but after discovering the previous 4, I also didn't trust anymore that there will be a sibling of Run, with a child of Text. So instead I just look at all my siblings (until BookmarEnd which has the same ID as BookmarkStart) and check all the children until I find any Text. - Maybe somebody with more experience with OpenXML can answer if it is necessary?
  10. 我不确定我的最后一个hack是否必要,因为我不知道OpenXML的所有限制,但是在发现了前四个之后,我也不再相信会有一个运行的兄弟,有一个文本的孩子。所以,我只是看看我所有的兄弟姐妹(直到BookmarEnd和BookmarkStart有相同的ID),然后检查所有的孩子,直到我找到任何文本。-也许有更多OpenXML经验的人可以回答是否有必要?

You can view my specific implementation here)

您可以在这里查看我的具体实现)

Hope this helps some of you who experienced the same issues.

希望这能帮助你们中一些遇到同样问题的人。

#8


0  

Here is how I do it in VB.NET:

下面是我在VB.NET网站上的做法:

For Each curBookMark In contractBookMarkStarts

      ''# Get the "Run" immediately following the bookmark and then
      ''# get the Run's "Text" field
      runAfterBookmark = curBookMark.NextSibling(Of Wordprocessing.Run)()
      textInRun = runAfterBookmark.LastChild

      ''# Decode the bookmark to a contract attribute
      lines = DecodeContractDataToContractDocFields(curBookMark.Name, curContract).Split(vbCrLf)

      ''# If there are multiple lines returned then some work needs to be done to create
      ''# the necessary Run/Text fields to hold lines 2 thru n.  If just one line then set the
      ''# Text field to the attribute from the contract
      For ptr = 0 To lines.Count - 1
          line = lines(ptr)
          If ptr = 0 Then
              textInRun.Text = line.Trim()
          Else
              ''# Add a <br> run/text component then add next line
              newRunForLf = New Run(runAfterBookmark.OuterXml)
              newRunForLf.LastChild.Remove()
              newBreak = New Break()
              newRunForLf.Append(newBreak)

              newRunForText = New Run(runAfterBookmark.OuterXml)
              DirectCast(newRunForText.LastChild, Text).Text = line.Trim

              curBookMark.Parent.Append(newRunForLf)
              curBookMark.Parent.Append(newRunForText)
          End If
      Next
Next

#9


0  

The accepted answer and some of the others make assumptions about where the bookmarks are in the document structure. Here's my C# code, which can deal with replacing bookmarks that stretch across multiple paragraphs and correctly replace bookmarks that do not start and end at paragraph boundaries. Still not perfect, but closer... hope it's useful. Edit if you find more ways to improve it!

被接受的答案和其他一些人对书签在文档结构中的位置做出了假设。这是我的c#代码,它可以处理跨多个段落的书签替换,并正确地替换不开始和结束于段落边界的书签。仍然不完美,但更接近……希望它是有用的。编辑如果你找到更多的方法来改进它!

    private static void ReplaceBookmarkParagraphs(MainDocumentPart doc, string bookmark, IEnumerable<OpenXmlElement> paras) {
        var start = doc.Document.Descendants<BookmarkStart>().Where(x => x.Name == bookmark).First();
        var end = doc.Document.Descendants<BookmarkEnd>().Where(x => x.Id.Value == start.Id.Value).First();
        OpenXmlElement current = start;
        var done = false;

        while ( !done && current != null ) {
            OpenXmlElement next;
            next = current.NextSibling();

            if ( next == null ) {
                var parentNext = current.Parent.NextSibling();
                while ( !parentNext.HasChildren ) {
                    var toRemove = parentNext;
                    parentNext = parentNext.NextSibling();
                    toRemove.Remove();
                }
                next = current.Parent.NextSibling().FirstChild;

                current.Parent.Remove();
            }

            if ( next is BookmarkEnd ) {
                BookmarkEnd maybeEnd = (BookmarkEnd)next;
                if ( maybeEnd.Id.Value == start.Id.Value ) {
                    done = true;
                }
            }
            if ( current != start ) {
                current.Remove();
            }

            current = next;
        }

        foreach ( var p in paras ) {
            end.Parent.InsertBeforeSelf(p);
        }
    }

#10


0  

Here's what I ended up with - not 100% perfect but works for simple bookmarks and simple text to insert:

这是我最后的结果——不是100%完美,但适用于简单的书签和简单的文本插入:

private void FillBookmarksUsingOpenXml(string sourceDoc, string destDoc, Dictionary<string, string> bookmarkData)
    {
        string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
        // Make a copy of the template file.
        File.Copy(sourceDoc, destDoc, true);

        //Open the document as an Open XML package and extract the main document part.
        using (WordprocessingDocument wordPackage = WordprocessingDocument.Open(destDoc, true))
        {
            MainDocumentPart part = wordPackage.MainDocumentPart;

            //Setup the namespace manager so you can perform XPath queries 
            //to search for bookmarks in the part.
            NameTable nt = new NameTable();
            XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
            nsManager.AddNamespace("w", wordmlNamespace);

            //Load the part's XML into an XmlDocument instance.
            XmlDocument xmlDoc = new XmlDocument(nt);
            xmlDoc.Load(part.GetStream());

            //Iterate through the bookmarks.
            foreach (KeyValuePair<string, string> bookmarkDataVal in bookmarkData)
            {
                var bookmarks = from bm in part.Document.Body.Descendants<BookmarkStart>()
                          select bm;

                foreach (var bookmark in bookmarks)
                {
                    if (bookmark.Name == bookmarkDataVal.Key)
                    {
                        Run bookmarkText = bookmark.NextSibling<Run>();
                        if (bookmarkText != null)  // if the bookmark has text replace it
                        {
                            bookmarkText.GetFirstChild<Text>().Text = bookmarkDataVal.Value;
                        }
                        else  // otherwise append new text immediately after it
                        {
                            var parent = bookmark.Parent;   // bookmark's parent element

                            Text text = new Text(bookmarkDataVal.Value);
                            Run run = new Run(new RunProperties());
                            run.Append(text);
                            // insert after bookmark parent
                            parent.Append(run);
                        }

                        //bk.Remove();    // we don't want the bookmark anymore
                    }
                }
            }

            //Write the changes back to the document part.
            xmlDoc.Save(wordPackage.MainDocumentPart.GetStream(FileMode.Create));
        }
    }

#11


0  

I needed to replace the text of a bookmark (bookmarks name is "Table") with a table. This is my approach:

我需要用一个表替换书签的文本(书签名称是“Table”)。这是我的方法:

public void ReplaceBookmark( DatasetToTable( ds ) )
{
    MainDocumentPart mainPart = myDoc.MainDocumentPart;
    Body body = mainPart.Document.GetFirstChild<Body>();
    var bookmark = body.Descendants<BookmarkStart>()
                        .Where( o => o.Name == "Table" )
                        .FirstOrDefault();
    var parent = bookmark.Parent; //bookmark's parent element
    if (ds!=null)
    {
        parent.InsertAfterSelf( DatasetToTable( ds ) );
        parent.Remove();
    }
    mainPart.Document.Save();
}


public Table DatasetToTable( DataSet ds )
{
    Table table = new Table();
    //creating table;
    return table;
}

Hope this helps

希望这有助于