2010-07-22 172 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; 
} 

希望这有助于

相关问题