2012-03-26 91 views
8

我想從使用OpenXML的.docx文件中刪除段落(我正在使用一些佔位符文本來生成從docx模板樣文件生成),但是每當我刪除段落,它就會打破foreach循環我正在使用迭代低谷。C#openxml刪除段落

MainDocumentPart mainpart = doc.MainDocumentPart; 
IEnumerable<OpenXmlElement> elems = mainPart.Document.Body.Descendants(); 

foreach(OpenXmlElement elem in elems){ 
    if(elem is Text && elem.InnerText == "##MY_PLACE_HOLDER##") 
    { 
     Run run = (Run)elem.Parent; 
     Paragraph p = (Paragraph)run.Parent; 
     p.RemoveAllChildren(); 
     p.Remove(); 
    } 
} 

這工作,消除我的佔位符和段落它在,但foreach循環迭代停止。我需要在我的foreach循環中做更多的事情。

是使用的OpenXML刪除在C#中的段落此確定方式爲什麼我的foreach循環停止或如何使它不會停止?謝謝。

回答

10

這是「萬聖節問題」,所謂的,因爲它是由在萬聖節一些開發商注意到了,它看起來怪異給他們。這是使用聲明式代碼(查詢)和命令式代碼(刪除節點)的問題。如果你仔細想一想,你會迭代一個鏈表,如果你開始刪除鏈表中的節點,你完全搞亂了迭代器。避免此問題的一種更簡單的方法是在List中「查詢」查詢的結果,然後您可以遍歷列表並根據需要刪除節點。以下代碼唯一的區別在於它在調用Descendants軸後調用ToList。

MainDocumentPart mainpart = doc.MainDocumentPart; 
IEnumerable<OpenXmlElement> elems = mainPart.Document.Body.Descendants().ToList(); 

foreach(OpenXmlElement elem in elems){ 
    if(elem is Text && elem.InnerText == "##MY_PLACE_HOLDER##") 
    { 
     Run run = (Run)elem.Parent; 
     Paragraph p = (Paragraph)run.Parent; 
     p.RemoveAllChildren(); 
     p.Remove(); 
    } 
} 

但是,我必須注意到,我看到您的代碼中的另一個錯誤。沒有什麼可以阻止Word將文本節點從多次運行分解爲多個文本元素。儘管在大多數情況下,您的代碼遲早會工作正常,您或用戶將採取一些行動(如選擇一個字符,並意外擊中功能區上的粗體按鈕),然後您的代碼將不再工作。

如果你真的想在文本層面的工作,那麼你需要使用的代碼,如我在這個屏幕鑄介紹:http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2011/08/04/introducing-textreplacer-a-new-class-for-powertools-for-open-xml.aspx

事實上,你很可能使用這些代碼逐字處理您的我相信用例。

另一種方法,更加靈活和強大,在詳述:

http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2011/06/13/open-xml-presentation-generation-using-a-template-presentation.aspx

雖然該屏幕鑄約爲PresentationML中,同樣的原則也適用於WordprocessingML中。

但是,即使您使用的是WordprocessingML,使用內容控件也會更好。對於一個方法來生成文檔,請參閱:

http://ericwhite.com/blog/map/generating-open-xml-wordprocessingml-documents-blog-post-series/

而對於很多對一般使用的內容控制的信息,請參閱:

http://www.ericwhite.com/blog/content-controls-expanded

-Eric

+0

其實我已經完成.ToList(),因爲一些其他併發症使用以前解。此外,我知道單詞分成多次運行(這裏,這裏是一個不好的例子),所以我的佔位符沒有'_'。而且我的佔位符是硬編碼的,所以儘管我知道內容控制的優點,但我並沒有使用它們,因爲我不太瞭解它們,並且有短(小)項目時間表。感謝您的回答,這非常有見地,更完整。 – 2012-03-27 10:22:14

1

您必須首先使用兩個循環來存儲要刪除的項目,其次使用這兩個循環刪除項目。 是這樣的:

List<Paragraph> paragraphsToDelete = new List<Paragraph>(); 
foreach(OpenXmlElement elem in elems){ 
    if(elem is Text && elem.InnerText == "##MY_PLACE_HOLDER##") 
    { 
     Run run = (Run)elem.Parent; 
     Paragraph p = (Paragraph)run.Parent; 
     paragraphsToDelete.Add(p); 
    } 
} 

foreach (var p in paragraphsToDelete) 
{ 
     p.RemoveAllChildren(); 
     p.Remove(); 
} 
+1

神, 我真笨。謝謝。但是,爲什麼地獄它首先從循環中打破? (如果有人知道,所以我會留下一些時間來接受答案;斯里不能投票,代表太低) – 2012-03-26 16:43:18

+0

http://stackoverflow.com/questions/2545027/exception-during-iteration-on-collection-and- remove-items-from-that-c​​ollection – 2012-03-26 16:46:05

+0

謝謝。找到另一個很好的一個:http://stackoverflow.com/questions/604831/collection-was-modified-enumeration-operation-may-not-execute – 2012-03-26 16:50:43

0
Dim elems As IEnumerable(Of OpenXmlElement) = MainPart.Document.Body.Descendants().ToList() 
     For Each elem As OpenXmlElement In elems 
      If elem.InnerText.IndexOf("fullname") > 0 Then 
       elem.RemoveAllChildren() 
      End If 

     Next