2010-07-22 65 views
17

Supongo que v2.0 es mejor ... tienen algunos buenos "cómo: ..." examples pero los marcadores no parecen actuar tan obviamente como una tabla ... un marcador está definido por dos elementos XML BookmarkStart & BookmarkEnd. Tenemos algunas plantillas con texto como marcadores y simplemente queremos reemplazar los marcadores con algún otro texto ... no está ocurriendo ningún formato extraño, pero ¿cómo selecciono/sustituyo el texto del marcador?Reemplazar el marcador de texto en el archivo de Word utilizando Open XML SDK

Respuesta

13

Aquí está mi enfoque después de usar ustedes como fuente de inspiración:

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

estás siguiendo un patrón muy simple aquí que no funcionará en todos los casos. En muchos casos, el reemplazo de marcadores se vuelve mucho más complicado, lo que no funcionará con este algoritmo. – Arvand

+0

Esto no funciona para mí, no me da ningún error y confirmo que está leyendo los marcadores pero no reemplazándolos con el texto. –

0

Aquí es cómo lo hago en 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

acabo cuenta de esto hace 10 minutos por lo que valga la naturaleza hacker del código.

En primer lugar me escribió una función auxiliar recursiva de ayuda para encontrar todos los marcadores:

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

Esto me devuelve un diccionario que puedo usar a parte a través de mi lista de reemplazo y añadir el texto después del marcador:

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

Por lo que puedo decir, la inserción y el reemplazo de los marcadores se ven con más fuerza. Cuando utilicé InsertAt en lugar de InsertIntoSelf, obtuve: "Los elementos no compuestos no tienen elementos secundarios". YMMV

+0

supongo que lo que yo quiero hacer es marcar las etiquetas utilización de inicio/fin de dejar a seleccionar una parte del texto (una carrera?) Y modificarlo. Parece bastante aleatorio donde están almacenados los marcadores, los míos están todos en 'doc.MainDocumentPart.Document.Body.Descendants' –

+0

@John Están dentro del árbol en el lugar del documento donde fueron agregados. Nada al azar al respecto. Todo va a estar en Body.Descendants. Cuerpo.Los elementos solo reciben niños de primer nivel. Espera, tal vez debería estar buscando Descendientes ... – jfar

1

Aquí es cómo lo hago y VB para añadir/reemplazar texto entre bookmarkstart y 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

Reemplazar marcadores con un solo contenido (posiblemente varios bloques de texto).

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

En primer lugar, se elimina el contenido existente entre el inicio y el final. Luego se agrega una nueva ejecución directamente detrás del inicio (antes del final).

Sin embargo, no estoy seguro si el marcador se cerró en otra sección cuando fue abierto o en diferentes celdas de la tabla, etc ..

para mí es suficiente por ahora.

+7

Nota, he traducido esta respuesta (con un _lot_ de ayuda de Google). Por favor verifíquelo para exactitud. En el futuro, publique en inglés. –

+0

Este es el que funcionó para mí, solo asegúrese de agregar las siguientes líneas para guardar los cambios en su documento, archivo.MainDocumentPart.Document.Save(); file.Close(); archivo es el archivo que abrió con WordprocessingDocument.Open ("ruta", verdadero) –

0

La respuesta aceptada y algunas de las otras hacen suposiciones sobre dónde están los marcadores en la estructura del documento. Aquí está mi código C#, que puede ocuparse de reemplazar marcadores que se extienden a través de múltiples párrafos y, reemplaza correctamente los marcadores que no comienzan y terminan en los límites de los párrafos. Todavía no es perfecto, pero está más cerca ... espero que sea útil. ¡Edita si encuentras más formas de mejorarlo!

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

Esto es lo que terminó con - no es 100% perfecto, pero funciona para marcadores simples y texto simple para insertar:

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

mayoría de las soluciones siguientes asumen que un patrón de marcadores regular de comenzar antes y termina después de se ejecuta, lo que no siempre es cierto, por ejemplo si el marcador comienza en un párrafo o una tabla y termina en algún lugar de otro párrafo (como otros han notado).¿Qué hay de usar el orden de los documentos para lidiar con el caso en el que los marcadores no se colocan en una estructura regular? El orden del documento aún encontrará todos los nodos de texto relevantes entre los cuales se puede reemplazar. Solo haga root.DescendantNodes(). Where (xtext o bookmarkstart o bookmark end) que recorrerá en orden de documento, entonces uno puede reemplazar los nodos de texto que aparecen después de ver un nodo de inicio de marcador pero antes de ver un nodo final.

1

Tomé el código de la respuesta, y tuvo varios problemas con él para casos excepcionales:

  1. Es posible que desee hacer caso omiso de marcadores ocultos. Los marcadores están ocultos si el nombre comienza con un _ (guión bajo)
  2. Si el marcador es para uno más más TableCell, lo encontrará en el BookmarkStart en la primera celda de la fila con la propiedad ColumnFirst referida al 0 índice de columna de la celda donde comienza el marcador. ColumnLast hace referencia a la celda donde termina el marcador, para mi caso especial siempre fue ColumnFirst == ColumnLast (los marcadores marcaron solo una columna). En este caso, tampoco encontrará un BookmarkEnd.
  3. Los marcadores pueden estar vacía, por lo que un bookmarkstart se retoma la bookmarkend, en este caso sólo se puede llamar bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  4. también un marcador puede contener muchos de texto-elementos, por lo que es posible que desee quitar todos los demás elementos, de lo contrario partes del marcador podrían ser reemplazadas, mientras que otras partes siguientes permanecerán.
  5. Y no estoy seguro si mi último truco es necesario, ya que no conozco todas las limitaciones de OpenXML, pero después de descubrir las 4 anteriores, tampoco confiaba más en que haya un hermano de Run , con un hijo de Texto. Así que, en cambio, simplemente miro a todos mis hermanos (hasta BookmarEnd, que tiene la misma identificación que BookmarkStart) y reviso a todos los niños hasta que encuentre cualquier texto. - ¿Tal vez alguien con más experiencia con OpenXML puede responder si es necesario?

Usted puede ver mi aplicación específica here)

Espero que esto ayude a algunos de ustedes que han experimentado los mismos problemas.

+0

Tenga en cuenta que debe publicar los puntos útiles de una respuesta aquí, en este sitio, o su publicación corre el riesgo de ser eliminada como ["No es respuesta"] (http://meta.stackexchange.com/q/8259). Puede incluir el enlace si lo desea, pero solo como una "referencia". La respuesta debería ser independiente sin necesidad del enlace. –

3

Después de muchas horas, he escrito este método:

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

Después de eso, llamo:

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

Este código está trabajando bueno si no tienen Marcadores vacías. Espero que pueda ayudar a alguien.

+0

Ese fue el único que funcionó para mí. –

0

Necesitaba reemplazar el texto de un marcador (el nombre de los marcadores es "Tabla") con una tabla. Este es mi enfoque:

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

Esperanza esto ayuda

Cuestiones relacionadas