2010-07-22 193 views

回答

13

這裏是我的方法使用你們的靈感後:

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"; 
     } 
    } 
+1

你在這裏遵循一個非常簡單的模式,在任何情況下都不起作用。在許多情況下,書籤替換會變得複雜得多,而這種算法無法使用。 – Arvand 2013-03-10 13:35:44

+0

這對我不起作用,它不會給我任何錯誤,我確認它讀取書籤,但不會將其替換爲文本。 – 2017-07-27 15:14:14

0

這裏是我如何做到這一點在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 
4

我想通了這一點11分鐘前所以請原諒的代碼的hackish性質。

首先,我寫了一個輔助遞歸輔助函數來找到所有的書籤:

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; 
} 

返回我一個解釋,我可以使用通過我的更換名單部分和書籤後添加文字:

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); 
} 

從我可以告訴插入和更換書籤看起來更難。當我使用InsertAt而不是InsertIntoSelf時,我得到:「非複合元素沒有子元素。」因人而異

+0

我想我想要做的是使用的開始/結束書籤標記,讓我選擇文本的一部分(運行?),並對其進行修改。它似乎很隨機,雖然書籤存儲雖然,我的都在'doc.MainDocumentPart.Document.Body.Descendants' – 2010-07-23 08:06:20

+0

@John他們是在他們被添加的文檔中的地方在樹中。根本沒有任何關於它的隨機。一切都將在Body.Descendants。 Body.Elements只能獲得一級孩子。等等,也許我應該只是在尋找後裔... – jfar 2010-07-23 14:35:58

1

這裏是我如何做到這一點和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 
4

將書籤替換爲單個內容(可能爲多個文本塊)。

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); 
} 

首先,刪除開始和結束之間的現有內容。然後在開始後直接添加新的運行(結束之前)。

但是,如果沒有把握書籤會當它被打開了另一部分或在不同的表格單元格,等..

關閉對我來說足夠了。

+7

請注意,我翻譯了這個答案(來自Google的幫助)。請檢查它的準確性。將來,請以英文發佈。 – 2012-01-12 12:03:03

+0

這是爲我工作的一個,只需確保添加以下行以將更改保存迴文檔file.MainDocumentPart.Document.Save(); file.Close();文件是您使用WordprocessingDocument.Open打開的文件(「path」,true) – 2017-07-27 15:41:52

0

接受的答案和其他一些人對書籤在文檔結構中的位置做出了假設。這是我的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); 
     } 
    } 
0

這裏是我結束了 - 不是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)); 
     } 
    } 
2

這裏大部分的解決方案假設開始之前和之後結束的常規書籤模式運行,這並非總是如此,例如如果書籤在para或table中開始,並在另一個para的某個地方結束(就像其他人注意到的那樣)。如何使用文檔順序來處理書籤未放入常規結構的情況 - 文檔順序仍然會找到所有相關的文本節點,然後可以替換它們。只要執行root.DescendantNodes()。其中​​(xtext或bookmarkstart或書籤結束)將按文檔順序遍歷,然後可以替換在看到書籤開始節點之後但在看到結束節點之前出現的文本節點。

1

我把代碼答案,有幾個問題與它的例外情形:

  1. 您可能要忽略隱藏的書籤。如果名稱以_(下劃線)開頭,則隱藏書籤
  2. 如果書籤是多一個TableCell的書籤,您可以在行的第一個Cell中的BookmarkStart中找到它,並使用屬性ColumnFirst引用基於0的書籤開始處的單元格的列索引。 ColumnLast引用書籤結束的單元格,因爲我的特殊情況始終是ColumnFirst == ColumnLast(書籤只標記一列)。在這種情況下,你也不會找到一個BookmarkEnd。
  3. 書籤可以是空的,所以BookmarkStart直接遵循BookmarkEnd,在這種情況下,你可以叫 bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  4. 另外一個書籤可以包含很多文本的元素,所以你可能要刪除所有其他元素,否則書籤的部分可能會被替換,而其他以下部分將保留。
  5. 我不確定我的最後一次破解是否有必要,因爲我不知道OpenXML的所有限制,但是在發現前面的4之後,我也不再相信會有一個Run的兄弟,與文本的孩子。因此,我只是看看我所有的兄弟姐妹(直到BookmarEnd與BookmarkStart具有相同的ID),然後檢查所有孩子,直到找到任何文本。 - 也許有更多OpenXML經驗的人可以回答,如果有必要的話?

您可以查看我的具體實施here

希望這有助於你們中的一些誰經歷了同樣的問題。

+0

請注意,您應該在此處發佈答案的有用點數,或者您的帖子風險被刪除爲[「未答覆」] (http://meta.stackexchange.com/q/8259)。如果您願意,您可能仍然包含鏈接,但僅作爲「參考」。答案應該獨立,不需要鏈接。 – 2013-02-26 07:18:18

3

了很多小時後,我寫了這個方法:

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); 
    } 

在那之後,我打電話:

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; 
} 

此代碼工作好如果u有沒有空書籤。 我希望它可以幫助某人。

+0

那是唯一一個爲我工作的人。 – 2015-08-11 20:56:03

0

我需要用表格替換書籤的文本(書籤名稱是「表格」)。這是我的方法:

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; 
} 

希望這有助於