Веб-разработчик
0,0
рейтинг
22 октября 2015 в 12:06

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



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

Хочу поделиться нашим 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-документе:


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




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

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


Теперь заполним шаблон данными:
нам нужно добавить одно поле и одну таблицу с двумя строчками, и в футере таблицы указать количество записей.
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();
	}


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



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

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



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


Заполнение простых полей



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


Заполнение таблиц



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


Заполнение списков



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


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



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


Таблица внутри списка



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


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



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


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



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


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



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


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



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).

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

Авторы: Алексей Волков, Руслана Котова
Алексей Волков @AIVolkov
карма
16,0
рейтинг 0,0
Веб-разработчик
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (20)

  • 0
    Еще можно юзать вот эту библиотеку: worddocgenerator.codeplex.com
    Все в порядке кроме картинок, пришлось немного допилить.
    • 0
      А что именно допилили? Никуда не выкладывали?
      • 0
        Процесс добавления картинок через OpenXml SDK немного муторный и в библиотеке он не реализован, пришлось немного модифицировать исходники. Могу вам скинуть, если нужно.
        • 0
          Да, пожалуйста, выложите куда-нибудь.
  • +1
    Выглядит очень интересно. А аналогичный шаблонизатор для Excel не планируете разрабатывать?
    • +7
      Планируем, только пока не готовы сказать, когда он появится.
      • 0
        Не совсем понятно что происходит после обработки шаблона?
        Вставка и вырезание тегов?
        Делали то же самое, но с привязкой к xml внутри, можно было потом легко вытащить эти данные из docx.
        • 0
          После обработки шаблона (которая заполнила документ данными) можно как оставить тэги, так и вырезать (SetRemoveContentControls).
          Эти тэги потом можно так же найти в XML и вытащить из них данные.
    • 0
      Практически закончил экспорт dashboard с использованием графиков от FusionCharts в Excel&Powerpoint. Что хочу сказать: excel таки очень мудреная система внутри, нежели word и сильно сомневаюсь, что получится там что-то шаблонизировать… Таблицы там ерунда, а вот графики… Все очень специфично и приходится чуть-ли не под каждого юзера что-то специфичное выделываться. Пока думаю, как же сделать поддержку шаблонов графиков, а то стандартный набор вообще никого не устраивает…
  • –9
    Классный подход! Чем-то напомнило Фабрику СМС… хотя там рекурсии и итерации ещё есть. Здорово!
    • 0
      Скачать бесплатно без регистрации?
  • 0
    Мы на одном из проектов пользуемся чем-то очень похожим, но закрытым и комерческим. Надо будет взглянуть на ваше решение поближе.
  • +1
    Наши поиски подходящего «шаблонизатора» не увенчались успехом

    javascript-ninja.fr/docxtemplater/v1/examples/demo.html, github.com/open-xml-templating/docxtemplater
  • 0
    Я так понимаю, ваша библиотека требует установки MS Office на сервере. Или нет?
    • 0
      Нет
      • 0
        Хм, странно тогда, что MSDN говорит, что сборка с DocumentFormat.OpenXml.Packaging идет в составе Office, а не .Net.
        • 0
          Ага, разобрался. Нужен Open XML SDK!
          Спасибо за наводку!
  • 0
    У нас используется Aspose Words.
    Вещь платная, но позволяет формировать контент Word-файла как в темплейте AngularJs.
  • +1
    Как и обещал вчера — впечатления по работе с библиотекой: очень понравилось, приятно и комфортно работать, API аккуратное и интуитивно понятное. Однозначно буду предлагать на работе как вариант для движка шаблонов!
  • 0
    Была бы еще возможность колонки вставлять (не только AddRow, но еще и AddColumn), цены бы не было библиотеке.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.