2013-09-30 9 views
7

У меня есть часть кода ниже. Я бы хотел заменить текст «Text1» на «NewText», это работа. Но когда я помещаю текст «Text1» в таблицу, которая больше не работает для «Text1» внутри таблицы.OpenXML заменить текст во всех документах

Я хотел бы сделать эту замену во всем документе.

using (WordprocessingDocument doc = WordprocessingDocument.Open(String.Format("c:\\temp\\filename.docx"), true)) 
{ 
    var body = doc.MainDocumentPart.Document.Body; 

    foreach (var para in body.Elements<Paragraph>()) 
    { 
     foreach (var run in para.Elements<Run>()) 
     { 
      foreach (var text in run.Elements<Text>()) 
      { 
       if (text.Text.Contains("##Text1##")) 
        text.Text = text.Text.Replace("##Text1##", "NewText"); 
      } 
     } 
    } 
} 

ответ

13

Ваш код не работает, так как элемент таблицы (w:tbl) не содержится в абзаца элемент (w:p). Дополнительную информацию см. В статье MSDN.

Text класс (сериализовать w:t) обычно представляет собой буквальный текст в Run элемента в слова документа. Таким образом, вы могли бы просто найти все w:t элементов (Text класс) и заменить тег, если текстовый элемент (w:t) содержит тег:

using (WordprocessingDocument doc = WordprocessingDocument.Open("yourdoc.docx", true)) 
{ 
    var body = doc.MainDocumentPart.Document.Body; 

    foreach (var text in body.Descendants<Text>()) 
    { 
    if (text.Text.Contains("##Text1##")) 
    { 
     text.Text = text.Text.Replace("##Text1##", "NewText"); 
    } 
    } 
} 
+2

Обратите внимание, что этот ответ и все другие ответы, которые просто захватить блок в основном работают, но они не очень надежны. В OpenXml есть много вещей, которые могут сломать текст. Применение форматирования к части слова, закладок и т. Д. Разбивает текст. Код на https://msdn.microsoft.com/en-us/library/ee441250%28v=office.12%29.aspx?f=255&MSPPError=-2147217396 предположительно исправляет его, но я еще не сделал его работу поэтому не может сообщать об успехе или неудаче. В моих конкретных образцовых документах разбивается около 1 слово из 100-200. –

+0

@WadeHatler: Спасибо за ваш комментарий. Я посмотрю код, указанный в вашей ссылке. – Hans

+0

Счастливые помочь.Я в основном пришел к выводу, что я ненавижу OpenXml. Я нашел код, который почти работает на http://blogs.msdn.com/b/ericwhite/archive/2008/07/09/open-xml-sdk-and-linq-to-xml.aspx, http://blogs.msdn.com/b/ericwhite/archive/2008/03/14/technical-improvements-in-the-open-xml-sdk.aspx и http://blogs.msdn.com/b/ericwhite/ Архив/2009/02/16/выяснение абзацы-по-стиль-имя-или-контент-в-ан-открытым XML-слово-обработка-document.aspx. Это по-прежнему ненадежно, потому что он не может понять, когда помещать пробелы, чтобы получить фрагменты. Я отправлю ответ, если я получу его работу. –

3

Может быть, это решение легче

using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(document, true)) 
{ 
string docText = null; 
//1. Copy all the file into a string 
using (StreamReader sr = new StreamReader(wordDoc.MainDocumentPart.GetStream())) 
    docText = sr.ReadToEnd(); 

//2. Use regular expression to replace all text 
Regex regexText = new Regex(find); 
docText = regexText.Replace(docText, replace); 

//3. Write the changed string into the file again 
using (StreamWriter sw = new StreamWriter(wordDoc.MainDocumentPart.GetStream(FileMode.Create))) 
     sw.Write(docText); 
+0

-> http://msdn.microsoft.com/en-us/library/bb508261(v=office.12).aspx – ch2o

+0

Внимание! Это также заменяет теги xml. Находка «<» с заменить «string.Empty» заканчивается с коррумпированным документом – Marcel

7

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

  1. Удалите любые предметы h уровня Unicode из вашей строки замены, которую невозможно прочитать из Word (с неправильного ввода пользователя)
  2. Возможность поиска результата поиска по нескольким прогонам или текстовым элементам в абзаце (Word часто разбивает одно предложение на несколько текстовые прогоны)
  3. Возможность включить разрыв строки в текст замены, чтобы вставить многострочный текст в документ.
  4. Возможность передать любой узел в качестве отправной точки для поиска, чтобы ограничить поиск этой частью документа (например, тело, заголовок, нижний колонтитул, конкретную таблицу, строку таблицы или таблицу) ,

Я уверен, что расширенные сценарии, такие как закладки, сложная вложенность, потребуют дополнительной модификации, но он работает для типов базовых текстовых документов, которые я использовал до сих пор, и гораздо полезнее для меня, чем игнорирование прогонов вообще или использование RegEx во всем файле без возможности настроить таргетинг на конкретную часть TableCell или Document (для расширенных сценариев).

Пример:

var body = document.MainDocumentPart.Document.Body; 
ReplaceText(body, replace, with); 

Код:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using DocumentFormat.OpenXml; 
using DocumentFormat.OpenXml.Packaging; 
using DocumentFormat.OpenXml.Wordprocessing; 

namespace My.Web.Api.OpenXml 
{ 
    public static class WordTools 
    { 


/// <summary> 
     /// Find/replace within the specified paragraph. 
     /// </summary> 
     /// <param name="paragraph"></param> 
     /// <param name="find"></param> 
     /// <param name="replaceWith"></param> 
     public static void ReplaceText(Paragraph paragraph, string find, string replaceWith) 
     { 
      var texts = paragraph.Descendants<Text>(); 
      for (int t = 0; t < texts.Count(); t++) 
      { // figure out which Text element within the paragraph contains the starting point of the search string 
       Text txt = texts.ElementAt(t); 
       for (int c = 0; c < txt.Text.Length; c++) 
       { 
        var match = IsMatch(texts, t, c, find); 
        if (match != null) 
        { // now replace the text 
         string[] lines = replaceWith.Replace(Environment.NewLine, "\r").Split('\n', '\r'); // handle any lone n/r returns, plus newline. 

         int skip = lines[lines.Length - 1].Length - 1; // will jump to end of the replacement text, it has been processed. 

         if (c > 0) 
          lines[0] = txt.Text.Substring(0, c) + lines[0]; // has a prefix 
         if (match.EndCharIndex + 1 < texts.ElementAt(match.EndElementIndex).Text.Length) 
          lines[lines.Length - 1] = lines[lines.Length - 1] + texts.ElementAt(match.EndElementIndex).Text.Substring(match.EndCharIndex + 1); 

         txt.Space = new EnumValue<SpaceProcessingModeValues>(SpaceProcessingModeValues.Preserve); // in case your value starts/ends with whitespace 
         txt.Text = lines[0]; 

         // remove any extra texts. 
         for (int i = t + 1; i <= match.EndElementIndex; i++) 
         { 
          texts.ElementAt(i).Text = string.Empty; // clear the text 
         } 

         // if 'with' contained line breaks we need to add breaks back... 
         if (lines.Count() > 1) 
         { 
          OpenXmlElement currEl = txt; 
          Break br; 

          // append more lines 
          var run = txt.Parent as Run; 
          for (int i = 1; i < lines.Count(); i++) 
          { 
           br = new Break(); 
           run.InsertAfter<Break>(br, currEl); 
           currEl = br; 
           txt = new Text(lines[i]); 
           run.InsertAfter<Text>(txt, currEl); 
           t++; // skip to this next text element 
           currEl = txt; 
          } 
          c = skip; // new line 
         } 
         else 
         { // continue to process same line 
          c += skip; 
         } 
        } 
       } 
      } 
     } 



     /// <summary> 
     /// Determine if the texts (starting at element t, char c) exactly contain the find text 
     /// </summary> 
     /// <param name="texts"></param> 
     /// <param name="t"></param> 
     /// <param name="c"></param> 
     /// <param name="find"></param> 
     /// <returns>null or the result info</returns> 
     static Match IsMatch(IEnumerable<Text> texts, int t, int c, string find) 
     { 
      int ix = 0; 
      for (int i = t; i < texts.Count(); i++) 
      { 
       for (int j = c; j < texts.ElementAt(i).Text.Length; j++) 
       { 
        if (find[ix] != texts.ElementAt(i).Text[j]) 
        { 
         return null; // element mismatch 
        } 
        ix++; // match; go to next character 
        if (ix == find.Length) 
         return new Match() { EndElementIndex = i, EndCharIndex = j }; // full match with no issues 
       } 
       c = 0; // reset char index for next text element 
      } 
      return null; // ran out of text, not a string match 
     } 

     /// <summary> 
     /// Defines a match result 
     /// </summary> 
     class Match 
     { 
      /// <summary> 
      /// Last matching element index containing part of the search text 
      /// </summary> 
      public int EndElementIndex { get; set; } 
      /// <summary> 
      /// Last matching char index of the search text in last matching element 
      /// </summary> 
      public int EndCharIndex { get; set; } 
     } 

    } // class 
} // namespace 


public static class OpenXmlTools 
    { 
     // filters control characters but allows only properly-formed surrogate sequences 
     private static Regex _invalidXMLChars = new Regex(
      @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]", 
      RegexOptions.Compiled); 
     /// <summary> 
     /// removes any unusual unicode characters that can't be encoded into XML which give exception on save 
     /// </summary> 
     public static string RemoveInvalidXMLChars(string text) 
     { 
      if (string.IsNullOrEmpty(text)) return ""; 
      return _invalidXMLChars.Replace(text, ""); 
     } 
    } 
+0

Простите меня, я получаю сообщение об ошибке с Visual Studio: 'document.MainDocumentPart.Document.Body' имеет типа тела, но 'public static void ReplaceText (абзац абзаца, поиск строки, строка replaceWith)' требует абзаца. Итак, компилятор останавливается и не продолжает – Ozeta

+1

@Ozeta, добавьте это 'var body = doc.MainDocumentPart.Document.Body; var paragraph = body.Elements (); foreach (var p в параграфах) { ReplaceText (p, заменить, с); } ' Однако это решение работает отлично, спасибо @Amos – ToTa

+0

Спасибо за подход абзаца, мне очень помогли. – Luntri

Смежные вопросы