Remplacer le texte du signet dans le fichier Word en utilisant Open XML SDK

je suppose v2.0, c'est mieux... ils ont quelques belles "comment:..."exemples mais les signets ne semblent pas agir aussi visiblement qu'une Table... un signet est définie par deux éléments XML BookmarkStart BookmarkEnd. Nous avons quelques modèles avec du texte comme signets et nous voulons simplement remplacer les signets par un autre texte... aucun formatage bizarre n'est en cours, mais comment puis-je sélectionner/remplacer un texte de marque-page?

17
demandé sur Mr. Boy 2010-07-22 15:27:01

11 réponses

Voici mon approche après vous avoir utilisés comme inspiration:

  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";
      }
  }
14
répondu Mr. Boy 2010-07-23 13:08:13

remplacer les signets par un contenu unique (éventuellement plusieurs blocs de texte).

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

tout d'Abord, le contenu existant entre le début et la fin est supprimée. Ensuite, une nouvelle exécution est ajoutée directement derrière le départ (avant la fin).

cependant, pas sûr si le signet est fermé dans une autre section quand il a été ouvert ou dans des cellules de table différentes, etc. ..

Pour moi, c'est suffisant pour l'instant.

5
répondu cyberblast 2012-01-12 12:02:32

je viens de le découvrir il y a 10 minutes donc pardonnez la nature pirate du code.

D'abord j'ai écrit une fonction helper récursive helper pour trouver tous les signets:

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

Que me renvoie un Dictionnaire que je peux utiliser pour partie par le biais de la remplaçante de ma liste et ajouter du texte après le signet:

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

d'après ce que je peux dire, insérer et remplacer les marque-pages semble plus difficile. Quand J'ai utilisé InsertAt au lieu de InsertIntoSelf j'ai eu: "Les éléments non composites n'ont pas d'éléments enfants."YMMV

4
répondu jfar 2012-09-24 15:00:45

Après des heures et des heures, j'ai écrit cette méthode:

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

Après, j'appelle:

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

ce code fonctionne bien si u n'a pas de marque-page vide. J'espère que cela peut aider quelqu'un.

3
répondu gorgonzola 2013-04-22 13:45:30

la plupart des solutions ici supposent un modèle de bookmarking régulier de commencer avant et de finir après les courses, ce qui n'est pas toujours vrai, par exemple si bookmark commence dans un para ou une table et se termine quelque part dans un autre para (comme d'autres ont noté). Que diriez - vous d'utiliser l'ordre des documents pour faire face au cas où les signets ne sont pas placés dans une structure régulière-l'ordre des documents trouvera toujours tous les noeuds de texte pertinents entre lesquels peuvent ensuite être remplacés. Il suffit de ne de la racine.DescendantNodes ().Où (xtext ou bookmarkstart ou bookmark end) qui va traverser dans l'ordre des documents, puis on peut remplacer les noeuds de texte qui apparaissent après avoir vu un noeud de départ de bookmark mais avant de voir un noeud de fin.

2
répondu Sanorita Rm 2013-02-04 15:58:12

Voici comment je le fais et VB pour ajouter/remplacer le texte entre bookmarkStart et 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
1
répondu LSFM 2011-01-18 15:18:35

j'ai pris le code de la réponse, et j'ai eu plusieurs problèmes avec lui pour des cas exceptionnels:

  1. vous pourriez vouloir ignorer les signets cachés. Les signets sont cachés si le nom commence par un tiret bas ( _ )
  2. si le signet est pour un autre Tablecell's, vous le trouverez dans le BookmarkStart dans la première cellule de la ligne avec la propriété ColumnFirst se référant à l'index 0-basé de la colonne de la cellule où le signet commence. Colonnlast se réfère à la cellule lorsque le signet se termine, pour mon cas particulier, il a toujours été ColumnFirst = = ColumnLast (signets marqués d'une seule colonne). Dans ce cas, vous ne trouverez pas non plus de BookmarkEnd.
  3. les signets peuvent être vides, donc un BookmarkStart suit directement un BookmarkEnd, dans ce cas vous pouvez simplement appeler bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  4. un signet peut aussi contenir de nombreux éléments de texte, donc vous pouvez supprimer tous les autres éléments, sinon certaines parties du signet peuvent être remplacées, tandis que d'autres parties suivantes va rester.
  5. et je ne suis pas sûr que mon dernier hack soit nécessaire, car je ne connais pas toutes les limites D'OpenXML, mais après avoir découvert les 4 précédents, je n'avais plus confiance non plus qu'il y aurait un frère de Run, avec un enfant de texte. Donc, au lieu de cela, je regarde juste tous mes frères et sœurs (Jusqu'à BookmarEnd qui a le même ID que BookmarkStart) et vérifier tous les enfants jusqu'à ce que je trouve un texte. - Peut-être quelqu'un avec plus d'expérience avec OpenXML peut répondre si c'est - elle nécessaire?

Vous pouvez voir mon spécifiques de mise en œuvre ici)

J'espère que cela aidera certains d'entre vous qui ont vécu les mêmes problèmes.

1
répondu peter 2013-02-26 16:17:20

j'avais besoin de remplacer le texte d'un signet (le nom des signets est "Table") par une table. C'est mon approche:

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

Espérons que cette aide

1
répondu Gogutz 2014-04-23 13:45:26

Voici comment je fais 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
0
répondu Stephen Study 2010-07-22 17:23:25

la réponse acceptée et certaines autres présupposent où se trouvent les signets dans la structure du document. Voici mon code C, qui permet de remplacer les signets qui s'étendent sur plusieurs paragraphes et remplacer correctement les signets qui ne commencent pas et ne se terminent pas aux limites du paragraphe. Toujours pas parfait, mais plus proche... j'espère que c'est utile. Modifier si vous trouvez d'autres façons de l'améliorer!

    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
répondu Dan Fitch 2012-07-25 12:26:02

voici ce que j'ai fini avec - pas parfait à 100% mais fonctionne pour des signets simples et du texte simple à insérer:

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));
        }
    }
0
répondu Lance 2012-12-05 20:33:01