TemplateEngine.Docx — .NET OpenSource шаблонизатор docx документов

50c731d57e5c4bc58a0798aaa86bc279.PNG

В разработке корпоративных приложений очень часто приходится решать задачу выгрузки данных в документы — от небольших справок до больших отчетов.

Хочу поделиться нашим opensource-решением для генерации docx документов, которое позволяет заполнять документы по шаблону, оформление которого можно менять в Word без переписывания кода.

Для начала — немного вводных.

Что нам было нужно от шаблонизатора


  • Шаблон создается в Word и сразу видно, на что будет похож результирующий документ, шаблон без лишнего мусора.
  • Результирующий документ после скачивания содержит все необходимые данные, не подтягивая их с внешних источников.
  • Возможность заполнять списки, таблицы, и иногда еще и таблицы с вложенными в них списками.
  • Шаблон можно доверить секретарю клиента, чтобы он мог сменить логотип, реквизиты компании, или как-либо еще подкорректировать оформление. И все это уже после сдачи проекта, не модифицируя наш код.

Поиски шаблонизатора


Наши поиски подходящего «шаблонизатора» не увенчались успехом: одни предлагают создавать документ с оформлением на сервере, вторые позволяют заменять только статический текст (например, https://github.com/swxben/docx-template-engine), третьи не поддерживают вложенность элементов (http://livedocx.com/), четвертые добавляют в шаблон разные программерские обозначения, чего мы хотели бы избежать, оставив шаблон максимально чистым (https://github.com/tssoft/TsSoft.Docx.TemplateEngine).

Что получилось у нас


Со стороны кода мы работаем с привычными сущностями, такими как «Таблица», «Список», «Строка», «Ячейка».

Со стороны шаблона используется документ с расставленными по нему Content Controls, которые связаны с данными через свойство tag. Content Controls добавляются достаточно легко, при этом их достаточно сложно испортить при дальнейшей эксплуатации в отличие от текстовых вставок типа {FIO}, а при отключенном режиме конструктора спец-обозначений контролов и вовсе не видно.

Например, необходимо заполнить таблицу, указать дату её заполнения и количество записей.
Создадим шаблон этой таблицы в Word-документе:
62b351c8c5fa4966bc24f0acae5d44b6.PNG

Каждое поле, которое мы будем заполнять, необходимо поместить в контрол, связать его с данными в коде. Для этого:

  1. Переходим на вкладку «Разработчик» (если она отсутствует, включается через Файл → Параметры → Настроить ленту → Ставим галочку возле «Разработчик») и включаем режим конструктора.
  2. Выделяем текст, который будет заполняемым полем.
  3. Нажимаем «Вставить элемент управления содержимым «Форматированный текст».
  4. Нажимаем «Свойства» и заполняем поля «Название» и «Тег».

8bf85d4e7e6b452d836930c0e860cd8f.PNG

Если требуется заполнить таблицу или список, их также нужно поместить в отдельный контент-контрол.

Так выглядит шаблон с добавленными элементами управления содержимым:
59228609b23b47e183a1d16f2b57d763.PNG

Теперь заполним шаблон данными:
нам нужно добавить одно поле и одну таблицу с двумя строчками, и в футере таблицы указать количество записей.

var valuesToFill = new Content(
        new FieldContent("Report date", DateTime.Now.Date.ToString()),
        new TableContent("Team members")
                .AddRow(
                        new FieldContent("Full name", "Семёнов Илья Васильевич"),
                        new FieldContent("Role", "Разработчик"))
                .AddRow(
                        new FieldContent("Full name", "Петров Фёдор Анатольевич"),
                        new FieldContent("Role", "Разработчик"))
                .AddRow(
                        new FieldContent("Full name", "Артемьев Вячеслав Геннадьевич"),
                        new FieldContent("Role", "Ведущий разработчик")),
        new FieldContent("Count", "3")
);

Запускаем TemplateProcessor…

using(var outputDocument = new TemplateProcessor("OutputDocument.docx")
                                .SetRemoveContentControls(true))
        {
                outputDocument.FillContent(valuesToFill);
                outputDocument.SaveChanges();
        }

Если всё получилось, на выходе следующий документ:
8a73f657fced45429554eb6c0ebb8d99.PNG

С помощью метода SetRemoveContentControls(bool value) можно удалить элементы управления содержимым, если они уже не нужны в результирующем документе.

TemplateEngine.Docx позволяет заполнять простые поля, таблицы, списки, вложенные списки, таблицы со списками, списки с таблицами и даже списки с таблицами, в которых есть списки… Структура класса Content позволяет создавать шаблоны с неограниченной вложенностью элементов.
1b4773fd78334e838695e07c3bbe2ff1.PNG

Еще больше примеров!


Заполнение простых полей
77a7ccbbfd874335a7a25ff7f47eaf5b.PNG

var valuesToFill = new Content(new FieldContent("Report date", DateTime.Now.ToString()));

Заполнение таблиц
b7cb042ba86b412785da588fc56dfbac.PNG

var valuesToFill = new Content(
        new TableContent("Team Members Table")
                .AddRow(
                        new FieldContent("Name", "Eric"),
                        new FieldContent("Role", "Program Manager"))
                .AddRow(
                        new FieldContent("Name", "Bob"),
                        new FieldContent("Role", "Developer")),
                new FieldContent("Count", "2"));

Заполнение списков
499cd9266d1443f4884428ab55562eb6.PNG

var valuesToFill = new Content(
        new ListContent("Team Members List")
                .AddItem(
                        new FieldContent("Name", "Eric"), 
                        new FieldContent("Role", "Program Manager"))
                .AddItem(
                        new FieldContent("Name", "Bob"),
                        new FieldContent("Role", "Developer")));

Заполнение вложенных списков
d95c191579364d048ebe5d00ab48fe83.PNG

var valuesToFill = new Content(
        new ListContent("Team Members Nested List")
                .AddItem(new ListItemContent("Role", "Program Manager")
                        .AddNestedItem(new FieldContent("Name", "Eric"))
                        .AddNestedItem(new FieldContent("Name", "Ann")))
                .AddItem(new ListItemContent("Role", "Developer")
                        .AddNestedItem(new FieldContent("Name", "Bob"))
                        .AddNestedItem(new FieldContent("Name", "Richard"))));

Таблица внутри списка
6b149a6a70154e678a00673e21188459.PNG

var valuesToFill = new Content(
        new ListContent("Projects List")
                .AddItem(new ListItemContent("Project", "Project one")
                        .AddTable(TableContent.Create("Team members")
                                .AddRow(
                                        new FieldContent("Name", "Eric"), 
                                        new FieldContent("Role", "Program Manager"))
                                .AddRow(
                                        new FieldContent("Name", "Bob"), 
                                        new FieldContent("Role", "Developer"))))
                .AddItem(new ListItemContent("Project", "Project two")
                        .AddTable(TableContent.Create("Team members")
                                .AddRow(
                                        new FieldContent("Name", "Eric"),
                                        new FieldContent("Role", "Program Manager"))))
                .AddItem(new ListItemContent("Project", "Project three")
                        .AddTable(TableContent.Create("Team members")
                                .AddRow(
                                        new FieldContent("Name", "Bob"),
                                        new FieldContent("Role", "Developer")))));

Список внутри таблицы
45ff62475dcc483f887f67cd0dec2223.PNG

var valuesToFill = new Content(
        new TableContent("Projects Table")
                .AddRow(
                        new FieldContent("Name", "Eric"), 
                        new FieldContent("Role", "Program Manager"), 
                        new ListContent("Projects")
                                .AddItem(new FieldContent("Project", "Project one"))
                                .AddItem(new FieldContent("Project", "Project two")))
                .AddRow(
                        new FieldContent("Name", "Bob"),
                        new FieldContent("Role", "Developer"),
                        new ListContent("Projects")
                                .AddItem(new FieldContent("Project", "Project one"))
                                .AddItem(new FieldContent("Project", "Project three"))));

Таблица, состоящая из нескольких блоков, которые заполняются независимо
4aa074a63fe44fd9809f447e1e0593a4.PNG

var valuesToFill = new Content(
        new TableContent("Team Members Statistics")
                .AddRow(
                        new FieldContent("Name", "Eric"),
                        new FieldContent("Role", "Program Manager"))
                .AddRow(
                        new FieldContent("Name", "Richard"),
                        new FieldContent("Role", "Program Manager"))
                .AddRow(
                        new FieldContent("Name", "Bob"),
                        new FieldContent("Role", "Developer")),

        new TableContent("Team Members Statistics")
                .AddRow(
                        new FieldContent("Statistics Role", "Program Manager"),
                        new FieldContent("Statistics Role Count", "2"))                     
                .AddRow(
                        new FieldContent("Statistics Role", "Developer"),
                        new FieldContent("Statistics Role Count", "1")));

Таблица с объединенными вертикально ячейками
96ec97c8a7ed4770ba8292b3af71d63d.PNG

var valuesToFill = new Content(
        new TableContent("Team members info")
                .AddRow(
                        new FieldContent("Name", "Eric"),
                        new FieldContent("Role", "Program Manager"),
                        new FieldContent("Age", "37"),
                        new FieldContent("Gender", "Male"))
                .AddRow(
                        new FieldContent("Name", "Bob"),
                        new FieldContent("Role", "Developer"),
                        new FieldContent("Age", "33"),
                        new FieldContent("Gender", "Male"))
                .AddRow(
                        new FieldContent("Name", "Ann"),
                        new FieldContent("Role", "Developer"),
                        new FieldContent("Age", "34"),
                        new FieldContent("Gender", "Female")));

Таблица с объединенными горизонтально ячейками
ca44471e576a484e9f25f79b076792f1.PNG

var valuesToFill = new Content(
        new TableContent("Team members projects")
                .AddRow(
                        new FieldContent("Name", "Eric"),
                        new FieldContent("Role", "Program Manager"),
                        new FieldContent("Age", "37"),
                        new FieldContent("Projects", "Project one, Project two"))
                .AddRow(
                        new FieldContent("Name", "Bob"),
                        new FieldContent("Role", "Developer"),
                        new FieldContent("Age", "33"),
                        new FieldContent("Projects", "Project one"))
                .AddRow(
                        new FieldContent("Name", "Ann"),
                        new FieldContent("Role", "Developer"),
                        new FieldContent("Age", "34"),
                        new FieldContent("Projects", "Project two")));

Где скачать


Проект доступен в NuGet (http://www.nuget.org/packages/TemplateEngine.Docx/), и открыт для пулл-реквестов на GitHub (https://github.com/UNIT6-open/TemplateEngine.Docx).

Всем спасибо за внимание, надеемся, что данный инструмент поможет вам в ваших проектах.

© Habrahabr.ru