Pull to refresh

Мучаем MS Word из нашего приложения

Reading time 4 min
Views 27K
Перед каждым прикладным разработчиком рано или поздно встает задача экспорта данных из своего приложения в другое. Вот и передо мной она в очередной раз встала: мне потребовалось генерировать сообщения для рассылки (почтовой, которую почтальон носит). Письма должны сохраняться в формате Word. Казалось бы, задача тривиальная, но некоторые тонкости есть. На просторах инетернетов довольно много примеров работы с вордом из сторонних приложений через COM-вызов, но большая часть из них является либо примерами уровня «Hello world!», либо заточенными под определенную задачу. Реализации своей я не нашел, потому предлагаю ознакомиться с очередным велосипедом.

Описание задачи


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

Второй момент, есть необходимость некоторые письма перед печатью подвергать ручной проверке и правке при необходимости (UPD) и находится в одном файле (это связано, с механизмами их дальнейшей передачи). Т.е. в месте формирования они только готовятся (и иногда печатаются).

.Net случился исторически, основной интерфейс работы с БД написан на нём. Собственно, вполне разумно, что пользователь и вызовы будет осуществлять через него. От использования макросов офиса пришлось отказаться по причинам безопасности и трудоемкости настройки.

Лобовое решение, оказавшееся непригодным


Казалось, что задача проста как три копейки: берем шаблон, вставляем его в выходной документ, заменяем ключевые слова, повторяем до конца записей. Не прокатило. Письмо может содержать несколько страниц, и при таком подходе, торможение ворда при росте объема документа приводит к тому, что рассылка на 30 писем может формироваться до часа. Пришлось включать голову и думать.

Что же получилось


Первым делом открываем шаблон и ищем в нём вхождения ключевых слов и запоминаем их позиции.
//загружаем ключевые слова
string[] keyWords = { "FNAME", "LNAME", "DEBT", "MR" };
//Ищем позиции ключевых слов в документе и добавляем в список
List<keyWordEntry> keyWordEntries=new List<keyWordEntry>();
for (int i=0; i<sdoc.Words.Count;i++)
{
  foreach (string keyWord in keyWords)
  {
   if (sdoc.Words[i+1].Text.Trim()==keyWord)
   {
     keyWordEntries.Add(new keyWordEntry(keyWord,i+1,sdoc.Words[i+1].Text.Remove(0,keyWord.Length)));
   };
  };
};

* This source code was highlighted with Source Code Highlighter.

Тут же обнаруживаются первые приколы работы с вордом (точнее они в этом тексте первые, а в процессе изысканий они были почти последнии): массивы элементов документов (Words, Paragraphs, ets) нумеруются с единицы; пробелы стоящие после слова, ворд легко может считать частью слова – пришлось писать логику их сохранения.

Создаем выходной документ на основе шаблона, так мы малой кровью можем получить документ с нужной разметкой страницы, колонтитулами, стилями и т.п.
_Document ddoc = word.Documents.Add(ref template, ref oMissing, ref oMissing, ref oMissing);
//Удаляем из него всё наполнение
ddoc.Range(ref oMissing, ref oMissing).Delete(ref oMissing, ref oMissing);

* This source code was highlighted with Source Code Highlighter.

Заполняем его параграфами по количеству записей в запросе:
for (int i = 0; i < rowCount; i++)
{
   ddoc.Range(ref oMissing, ref oMissing).InsertParagraphAfter();
};

* This source code was highlighted with Source Code Highlighter.

И начинаем заполнять от конца к началу, чем получаем бешенный прирост скорости, т.к. обращаемся по индексу параграфа, а не ищем каждый раз конец документа. Само заполнение выглядит следующим образом (sdoc – временный документ, в который подставляем значения, ddoc – тот который должен получится):
for (int i = rowCount; i > 0; i--)
{
  if (i < rowCount)
  {
     ddoc.Paragraphs[i].Range.InsertParagraphAfter();
     ddoc.Paragraphs[i + 1].Range.InsertBreak(ref pageBreak);
  };
  //подставляем слова во временный документ
  foreach (keyWordEntry ke in keyWordEntries)
  {
     string replaceWith = "";
     switch (ke.keyword)
     {
       //тут логика подстановки
       default:
         replaceWith = ke.keyword+ke.spacesAfter;
         break;
     };
     sdoc.Words[ke.position].Text = replaceWith;
   };
   sdoc.Range(ref oMissing, ref oMissing).Copy();
   ddoc.Paragraphs[i].Range.Paste();
}

* This source code was highlighted with Source Code Highlighter.

По-существу всё, осталось сохранить полученный документ и корректно завершить процесс ворда.

Еще пару слов в догонку: символы '.', ',', '*' и все остальные, ворд считает отдельным словом и если вам нужно вставит, например, дату, то логика слегка усложнится.

Код примера можно скачать тут
Tags:
Hubs:
+5
Comments 43
Comments Comments 43

Articles