2010-07-22 17 views
17

Suppongo che v2.0 sia migliore ... hanno un bel "come: ..." examples ma i segnalibri non sembrano agire come ovviamente come dire una tabella ... un segnalibro è definito da due Elementi XML BookmarkStart & BookmarkEnd. Abbiamo alcuni modelli con testo come segnalibri e vogliamo semplicemente sostituire i segnalibri con un altro testo ... nessuna formattazione strana sta succedendo, ma come faccio a selezionare/sostituire il testo del segnalibro?Sostituire il testo del segnalibro nel file di Word utilizzando Open XML SDK

risposta

13

Ecco il mio approccio dopo aver usato voi ragazzi come ispirazione:

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

stai seguendo uno schema molto semplice qui che non funzionerà in tutti i casi. In molti casi la sostituzione dei segnalibri diventa molto più complicata e non funziona con questo algoritmo. – Arvand

+0

Questo non funziona per me, non mi dà errori e confermo la sua lettura dei segnalibri ma non li sostituisce con il testo. –

0

Ecco come lo faccio in 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

Ho appena capito questo 10 minuti fa così perdonare la natura hacker del codice.

Per prima cosa ho scritto una funzione di supporto ricorsiva aiuto per trovare tutti i segnalibri:

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

Questo mi restituisce un dizionario che posso usare a parte attraverso la mia lista di sostituzione e aggiungere il testo dopo il segnalibro:

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

Da quello che posso dire l'inserimento e la sostituzione dei segnalibri sembra più difficile. Quando ho usato InsertAt invece di InsertIntoSelf ho ottenuto: "Gli elementi non compositi non hanno elementi figlio". YMMV

+0

Suppongo che ciò che voglio fare è l'uso di inizio/fine tag segnalibro a mi permette di selezionare una porzione di testo (una corsa?) E modificarlo. Sembra abbastanza casuale dove sono memorizzati i segnalibri, i miei sono tutti in 'doc.MainDocumentPart.Document.Body.Descendants' –

+0

@John Sono all'interno dell'albero nel punto del documento in cui sono stati aggiunti. Niente di casuale a riguardo. Tutto sarà in Body.Descendants. Body.Elements riceve solo bambini di primo livello. Aspetta, forse dovrei solo cercare Descendants ... – jfar

1

Ecco come lo faccio e VB per aggiungere/sostituire il testo tra il bookmarkstart e 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

Sostituire i segnalibri con un singolo contenuto (eventualmente più blocchi di testo).

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

Innanzitutto, il contenuto esistente tra inizio e fine viene rimosso. Quindi una nuova corsa viene aggiunta direttamente dietro l'inizio (prima della fine).

Tuttavia, non so se il segnalibro è chiusa in un'altra sezione quando è stato aperto o in diverse celle della tabella, ecc ..

Per me è sufficiente per ora.

+7

Nota, ho tradotto questa risposta (con un _lot_ di aiuto da parte di Google). Si prega di controllare per la precisione. In futuro, si prega di inviare in inglese. –

+0

Questo è quello che ha funzionato per me, assicurati di aggiungere le seguenti righe per salvare le modifiche nel tuo documento, file.MainDocumentPart.Document.Save(); File.Close(); file è il file che hai aperto usando WordprocessingDocument.Open ("percorso", vero) –

0

La risposta accettata e alcuni degli altri fanno supposizioni su dove i segnalibri sono nella struttura del documento. Ecco il mio codice C#, che può occuparsi di sostituire i segnalibri che si estendono su più paragrafi e sostituire correttamente i segnalibri che non iniziano e finiscono ai limiti del paragrafo. Ancora non perfetto, ma più vicino ... spero sia utile. Modifica se trovi altri modi per migliorarlo!

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

Ecco cosa ho finito con - non al 100% perfetto, ma funziona per i segnalibri semplici e testo semplice da inserire:

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

La maggior parte delle soluzioni qui assumono un modello bookmarking regolare di iniziare prima e termina dopo corre, che non è sempre vero es se il segnalibro inizia in un para o in un tavolo e finisce da qualche parte in un altro para (come altri hanno notato).Che ne dici di utilizzare l'ordine dei documenti per far fronte al caso in cui i segnalibri non sono collocati in una struttura regolare - l'ordine del documento troverà ancora tutti i nodi di testo rilevanti tra i quali potrà quindi essere sostituito. Basta fare root.DescendantNodes(). Dove (xtext o bookmarkstart o bookmark end) che attraverseranno nell'ordine del documento, allora si possono sostituire i nodi di testo che appaiono dopo aver visto un nodo di inizio segnalibro ma prima di vedere un nodo finale.

1

ho preso il codice dalla risposta, e ha avuto diversi problemi con esso per i casi eccezionali:

  1. Si potrebbe desiderare di ignorare i segnalibri nascosti. I segnalibri sono nascosti se il nome inizia con _ (carattere di sottolineatura)
  2. Se il segnalibro è per un altro più TableCell, lo troverai nel BookmarkStart nella prima cella della riga con la proprietà ColumnFirst che fa riferimento a 0-based indice di colonna della cella in cui inizia il segnalibro. ColumnLast si riferisce alla cella in cui termina il segnalibro, per il mio caso speciale era sempre ColumnFirst == ColumnLast (i segnalibri contrassegnati solo una colonna). In questo caso, inoltre, non troverai un BookmarkEnd.
  3. segnalibri possono essere vuoto, quindi un bookmarkstart segue direttamente un bookmarkend, in questo caso si può chiamare bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  4. anche un segnalibro può contenere molti Text-elementi, per cui si potrebbe desiderare di rimuovere tutti gli altri elementi, altrimenti parti del segnalibro potrebbero essere sostituite, mentre altre parti successive rimarranno.
  5. E non sono sicuro se il mio ultimo attacco è necessario, dal momento che non conosco tutti i limiti di OpenXML, ma dopo aver scoperto i precedenti 4, non mi fido più che ci sarà un fratello di Run , con un figlio di testo. Quindi, invece, guardo tutti i miei fratelli (fino a BookmarEnd che ha lo stesso ID di BookmarkStart) e controllo tutti i bambini finché non trovo testo. - Forse qualcuno con più esperienza con OpenXML può rispondere se è necessario?

È possibile visualizzare la mia specifica implementazione here)

Spero che questo aiuti alcuni di voi che hanno sperimentato gli stessi problemi.

+0

Ti preghiamo di notare che dovresti pubblicare i punti utili di una risposta qui, su questo sito, o il tuo post rischia di essere cancellato come ["Non è una risposta"] (http://meta.stackexchange.com/q/8259). Puoi ancora includere il link se lo desideri, ma solo come un "riferimento". La risposta dovrebbe essere autonoma senza bisogno del collegamento. –

3

Dopo un sacco di ore, ho scritto questo metodo:

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

Dopo di che, io chiamo:

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

Questo codice sta funzionando bene se la u non segnalibri vuoti. Spero possa aiutare qualcuno.

+0

Quello era l'unico che funzionava per me. –

0

Ho dovuto sostituire il testo di un segnalibro (il nome del segnalibro è "Tabella") con una tabella. Questo è il mio approccio:

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

Spero che questo aiuti

Problemi correlati