Заменить текст закладки в файле Word с помощью Open XML SDK

17

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

    
задан Mr. Boy 22.07.2010 в 13:27
источник

11 ответов

14

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

  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";
      }
  }
    
ответ дан Mr. Boy 23.07.2010 в 15:08
источник
  • вы следуете очень простой схеме, которая не будет работать во всех случаях. Во многих случаях замена заметок становится намного сложнее, что не будет работать с этим алгоритмом. –  Arvand 10.03.2013 в 14:35
  • Это не работает для меня, это не дает мне никаких ошибок, и я подтверждаю его чтение закладок, но не заменяя их текстом. –  Saad A 27.07.2017 в 17:14
  • это хорошо работает, но оно очень хрупкое, если вы не можете ограничивать однопользовательские закладки только для текста. пример: «BOOKMARK» будет работать, «BOOKMARK1» не будет найден, потому что он будет разделен на «БУКМАРК» и «1» (протестирован с Microsoft Word 2016 на Windows 10, версия для настольных ПК), в результате чего происходит частичная замена. –  dlatikay 28.05.2018 в 22:12
4

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

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

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

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

Для меня этого достаточно.

    
ответ дан cyberblast 12.01.2012 в 11:24
источник
  • Примечание. Я перевел этот ответ (с большой помощью от Google). Пожалуйста, проверьте его точность. В будущем, пожалуйста, напишите на английском языке. –  Tim Post♦ 12.01.2012 в 13:03
  • Это тот, который работал для меня, просто не забудьте добавить следующие строки, чтобы сохранить изменения обратно в документ, файл.MainDocumentPart.Document.Save (); file.close (); файл - это файл, который вы открыли с помощью WordprocessingDocument.Open («путь», «истина») –  Saad A 27.07.2017 в 17:41
4

Я только что понял это 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

    
ответ дан jfar 22.07.2010 в 19:53
источник
  • Я полагаю, что я хочу использовать теги закладки начала и конца, чтобы я мог выбрать часть текста (пробег?) и изменить его. Кажется довольно случайным, когда закладки хранятся, хотя мои все находятся в doc.MainDocumentPart.Document.Body.Descendants –  Mr. Boy 23.07.2010 в 10:06
  • @John Они находятся внутри дерева в месте в документе, который они добавили. Ничего случайного об этом вообще. Все будет в Body.Descendants. Body.Elements получает только детей первого уровня. Подождите, может быть, я просто должен искать потомков ... –  jfar 23.07.2010 в 16:35
3

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

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

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

    
ответ дан gorgonzola 22.04.2013 в 15:45
источник
  • Этот был единственным, который работал на меня. –  Izold Tytykalo 11.08.2015 в 22:56
2

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

    
ответ дан Sanorita Rm 04.02.2013 в 16:58
источник
1

Вот как я это делаю, и 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
    
ответ дан LSFM 18.01.2011 в 16:18
источник
1

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

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

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

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

    
ответ дан peter 26.02.2013 в 08:16
источник
  • Обратите внимание, что вы должны размещать полезные пункты ответа здесь, на этом сайте, или ваши риски, связанные с публикацией, удаляются как «Не ответ». Вы можете по-прежнему включать ссылку, если хотите, но только как ссылку. Ответ должен стоять сам по себе, не нуждаясь в ссылке. –  Andrew Barber 26.02.2013 в 08:18
0

Вот как я это делаю в 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
    
ответ дан Stephen Study 22.07.2010 в 19:00
источник
0

Принятый ответ, а некоторые другие делают предположения о том, где закладки находятся в структуре документа. Вот мой код 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);
        }
    }
    
ответ дан Dan Fitch 25.07.2012 в 14:26
источник
0

Вот что я закончил - не на 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));
        }
    }
    
ответ дан Lance 05.12.2012 в 21:33
источник
0

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

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

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

    
ответ дан Gogutz 23.04.2014 в 15:39
источник