Перед каждым прикладным разработчиком рано или поздно встает задача экспорта данных из своего приложения в другое. Вот и передо мной она в очередной раз встала: мне потребовалось генерировать сообщения для рассылки (почтовой, которую почтальон носит). Письма должны сохраняться в формате Word. Казалось бы, задача тривиальная, но некоторые тонкости есть. На просторах инетернетов довольно много примеров работы с вордом из сторонних приложений через COM-вызов, но большая часть из них является либо примерами уровня «Hello world!», либо заточенными под определенную задачу. Реализации своей я не нашел, потому предлагаю ознакомиться с очередным велосипедом.
Имеется база данных содержащая информацию об абонентах. Абонентам должны отсылаться бумажные письма. Тексты писем (шаблоны) готовят люди от ИТ очень далекие (юристы, маркетологи и прочие дармоеды), но умеющие пользоваться вордом в том или ином виде (иногда, даже очень хорошо). Т.е. объяснить им, как вставить ключевое слово в текст, вполне возможно, но более сложное требование вызовет у них когнитивный диссонанс.
Второй момент, есть необходимость некоторые письма перед печатью подвергать ручной проверке и правке при необходимости (UPD) и находится в одном файле (это связано, с механизмами их дальнейшей передачи). Т.е. в месте формирования они только готовятся (и иногда печатаются).
.Net случился исторически, основной интерфейс работы с БД написан на нём. Собственно, вполне разумно, что пользователь и вызовы будет осуществлять через него. От использования макросов офиса пришлось отказаться по причинам безопасности и трудоемкости настройки.
Казалось, что задача проста как три копейки: берем шаблон, вставляем его в выходной документ, заменяем ключевые слова, повторяем до конца записей. Не прокатило. Письмо может содержать несколько страниц, и при таком подходе, торможение ворда при росте объема документа приводит к тому, что рассылка на 30 писем может формироваться до часа. Пришлось включать голову и думать.
Первым делом открываем шаблон и ищем в нём вхождения ключевых слов и запоминаем их позиции.
Тут же обнаруживаются первые приколы работы с вордом (точнее они в этом тексте первые, а в процессе изысканий они были почти последнии): массивы элементов документов (Words, Paragraphs, ets) нумеруются с единицы; пробелы стоящие после слова, ворд легко может считать частью слова – пришлось писать логику их сохранения.
Создаем выходной документ на основе шаблона, так мы малой кровью можем получить документ с нужной разметкой страницы, колонтитулами, стилями и т.п.
Заполняем его параграфами по количеству записей в запросе:
И начинаем заполнять от конца к началу, чем получаем бешенный прирост скорости, т.к. обращаемся по индексу параграфа, а не ищем каждый раз конец документа. Само заполнение выглядит следующим образом (sdoc – временный документ, в который подставляем значения, ddoc – тот который должен получится):
По-существу всё, осталось сохранить полученный документ и корректно завершить процесс ворда.
Еще пару слов в догонку: символы '.', ',', '*' и все остальные, ворд считает отдельным словом и если вам нужно вставит, например, дату, то логика слегка усложнится.
Код примера можно скачать тут
Описание задачи
Имеется база данных содержащая информацию об абонентах. Абонентам должны отсылаться бумажные письма. Тексты писем (шаблоны) готовят люди от ИТ очень далекие (юристы, маркетологи и прочие дармоеды), но умеющие пользоваться вордом в том или ином виде (иногда, даже очень хорошо). Т.е. объяснить им, как вставить ключевое слово в текст, вполне возможно, но более сложное требование вызовет у них когнитивный диссонанс.
Второй момент, есть необходимость некоторые письма перед печатью подвергать ручной проверке и правке при необходимости (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.
По-существу всё, осталось сохранить полученный документ и корректно завершить процесс ворда.
Еще пару слов в догонку: символы '.', ',', '*' и все остальные, ворд считает отдельным словом и если вам нужно вставит, например, дату, то логика слегка усложнится.
Код примера можно скачать тут