Замените текст закладки в файле Word, используя Open XML SDK - PullRequest
19 голосов
/ 22 июля 2010

Полагаю, v2.0 лучше ... у них есть несколько хороших "как: ..." примеров , но закладки, кажется, не действуют так же очевидно, как, например, Таблица ...закладка определяется двумя элементами XML BookmarkStart & BookmarkEnd .У нас есть несколько шаблонов с текстом в виде закладок, и мы просто хотим заменить закладки другим текстом ... странного форматирования не происходит, но как выбрать / заменить текст закладки?

Ответы [ 11 ]

16 голосов
/ 23 июля 2010

Вот мой подход после использования вас, ребята, как вдохновение:

  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";
      }
  }
6 голосов
/ 12 января 2012

Замена закладок одним содержимым (возможно, несколькими текстовыми блоками).

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

Сначала удаляется существующее содержимое между началом и концом.Затем новый запуск добавляется непосредственно за началом (до конца).

Однако не уверен, закрыта ли закладка в другом разделе, когда она была открыта, или в других ячейках таблицы и т. Д.

Для меня пока достаточно.

4 голосов
/ 22 июля 2010

Я только что понял это 10 минут назад, так что простите за хакерскую природу кода.

Сначала я написал вспомогательную рекурсивную вспомогательную функцию, чтобы найти все закладки:

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

, которая возвращает мне словарь, который я могу использовать, чтобы разделить мой список замены и добавить текст послезакладка:

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

Из того, что я могу сказать, вставить и заменить закладки выглядит сложнее.Когда я использовал InsertAt вместо InsertIntoSelf, я получил: «Несоставные элементы не имеют дочерних элементов».YMMV

3 голосов
/ 22 апреля 2013

Через много часов я написал этот метод:

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

После этого я звоню:

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

Этот код работает хорошо, если у вас нет пустых закладок,Я надеюсь, что это может кому-то помочь.

2 голосов
/ 04 февраля 2013

Большинство решений здесь предполагают регулярную схему закладок, начинающуюся до и заканчивающуюся после прогонов, что не всегда верно, например, если закладка начинается в параграфе или таблице и заканчивается где-то в другом параграфе (как отметили другие). Как насчет использования порядка документов, чтобы справиться со случаем, когда закладки не размещаются в обычной структуре - порядок документов по-прежнему будет находить все соответствующие текстовые узлы между ними, которые затем можно будет заменить. Просто выполните root.DescendantNodes (). Где (xtext или bookmarkstart или конец закладки), который будет проходить в порядке документа, тогда можно заменить текстовые узлы, которые появляются после просмотра начального узла закладки, но до просмотра конечного узла.

1 голос
/ 23 апреля 2014

Мне нужно было заменить текст закладки (имя закладки «Таблица») на таблицу. Это мой подход:

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

Надеюсь, это поможет

1 голос
/ 26 февраля 2013

Я взял код из ответа, и у меня было несколько проблем с ним для исключительных случаев:

  1. Возможно, вы захотите игнорировать скрытые закладки.Закладки скрыты, если имя начинается с _ (подчеркивание)
  2. Если закладка предназначена для еще одной таблицы TableCell, вы найдете ее в BookmarkStart в первой ячейке строки со свойством ColumnFirst, ссылающимся наИндекс столбца на основе 0 ячейки, в которой начинается закладка.ColumnLast ссылается на ячейку, где заканчивается закладка, для моего особого случая это всегда было ColumnFirst == ColumnLast (закладки отмечены только одним столбцом).В этом случае вы также не найдете BookmarkEnd.
  3. Закладки могут быть пустыми, поэтому BookmarkStart следует непосредственно за BookmarkEnd, в этом случае вы можете просто вызвать bookmarkStart.Parent.InsertAfter(new Run(new Text("Hello World")), bookmarkStart)
  4. Также закладкуможет содержать много текстовых элементов, поэтому вы можете удалить все остальные элементы, в противном случае части закладки могут быть заменены, а остальные последующие части останутся.
  5. И я не уверен, что мой последний взломнеобходимо, так как я не знаю всех ограничений OpenXML, но после обнаружения предыдущих 4 я также больше не верил, что у Run будет брат и потомок Text.Поэтому вместо этого я просто смотрю на всех своих братьев и сестер (до BookmarEnd с тем же идентификатором, что и BookmarkStart) и проверяю всех потомков, пока не найду какой-либо текст.- Может быть, кто-то с большим опытом работы с OpenXML может ответить, если это необходимо?

Вы можете посмотреть мою конкретную реализацию здесь )

Надеюсь, это поможет некоторым из васкто испытывал те же проблемы.

1 голос
/ 18 января 2011

Вот как я это делаю и VB для добавления / замены текста между bookmarkStart и 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
0 голосов
/ 06 декабря 2012

Вот что у меня получилось - не на 100% идеально, но работает для простых закладок и простого текста:

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 голосов
/ 08 мая 2012

Принятый ответ и некоторые другие делают предположения о том, где находятся закладки в структуре документа.Вот мой код C #, который может иметь дело с заменой закладок, которые растянуты по нескольким абзацам и , и правильно заменяет закладки, которые не начинаются и не заканчиваются на границах абзацев.Все еще не идеально, но ближе ... надеюсь, что это полезно.Отредактируйте, если найдете больше способов улучшить его!

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