2012-03-26 10 views
8

Estoy tratando de eliminar el párrafo (estoy usando un texto de marcador de posición para hacer la generación de un archivo tipo plantilla de docx) del archivo .docx usando OpenXML, pero cada vez que elimino el párrafo se rompe el ciclo foreach que Estoy usando para iterar a través.C# openxml eliminación del párrafo

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

Esto funciona, quita mi marcador de posición y el párrafo que se encuentra, pero bucle foreach detiene la iteración. Y necesito más cosas para hacer en mi ciclo foreach.

¿Es esta bien manera de eliminar el párrafo en C# usando OpenXML y por qué es mi parada foreach bucle o cómo hacer que se detiene? Gracias.

Respuesta

10

Este es el "Problema de Halloween", llamada así porque fue observado por algunos desarrolladores en Halloween, y parecía espeluznante a ellos. Es el problema de usar código declarativo (consultas) con código imperativo (eliminar nodos) al mismo tiempo. Si lo piensas bien, estás iterando a través de una lista vinculada, y si comienzas a eliminar nodos en la lista enlazada, arruinas completamente el iterador. Una manera más simple de evitar este problema es "materializar" los resultados de la consulta en una lista, y luego puede recorrer la lista y eliminar nodos a voluntad. La única diferencia en el siguiente código es que llama a ToList después de llamar al eje Descendientes.

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

Sin embargo, debo tener en cuenta que veo otro error en su código. No hay nada que impida a Word dividir ese nodo de texto en múltiples elementos de texto de múltiples ejecuciones. Aunque en la mayoría de los casos, su código funcionará bien, tarde o temprano usted o un usuario tomarán alguna medida (como seleccionar un personaje y presionar accidentalmente el botón en negrita de la cinta) y luego su código ya no funcionará.

Si realmente quiere trabajar a nivel de texto, a continuación, es necesario utilizar un código como lo que presento en esta pantalla-reparto: http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2011/08/04/introducing-textreplacer-a-new-class-for-powertools-for-open-xml.aspx

De hecho, probablemente podría utilizar ese código pie de la letra para manejar su caso de uso, creo.

Otro enfoque, más flexible y potente, se detalla en:

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

Mientras que la pantalla de fundición es de aproximadamente PresentationML, los mismos principios se aplican a WordprocessingML.

Pero aún mejor, dado que está utilizando WordprocessingML, es usar controles de contenido.Por un enfoque de generación de documentos, consulte:

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

Y para gran cantidad de información acerca del uso de los controles de contenido, en general, ver:

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

-Eric

+0

En realidad, he hecho .ToList(), porque aparecieron otras complicaciones con anterioridad solución. Además, soy consciente de la división de palabras en varias ejecuciones (esto, aquí, fue un mal ejemplo), por lo que mis marcadores de posición no tienen '_'. Y mis marcadores de posición están codificados, así que, aunque conozco las ventajas del control de contenido, no las utilicé porque no las conozco lo suficiente y tengo un horario corto (mínimo) de proyecto. Gracias por la respuesta, fue muy perspicaz, más completa. –

1

Primero debe usar dos ciclos que almacene los elementos que desea eliminar y segundo que borre los elementos. algo como esto:

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

Dios , Soy estúpido. Gracias. Pero, ¿por qué demonios se rompe del loop en primer lugar? (si alguien sabe, entonces lo dejo un tiempo para aceptar la respuesta; sry no puede votar, rep demasiado bajo) –

+0

http://stackoverflow.com/questions/2545027/exception-during-iteration-on-collection-and- remove-items-from-that-collection –

+0

Gracias. Encontré otra buena: http://stackoverflow.com/questions/604831/collection-was-modified-enumeration-operation-may-not-execute –

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 
Cuestiones relacionadas