.NET
December 2010 11

Генерируем OfficeOpenXML-документы за 5 минут

Часто надо бывает из приложения на ASP.NET сгенерировать отчёт на сервере в OpenXML-формате.

Есть несколько привычных способов сделать это:
  1. «Нашёл, слинковал, заюзал» – идём в Гугл, ищем библиотеку для генерации docx или xlsx, подключаем, разбираемся, генерируем. Это привычно, но долго.
  2. «Фу» – использовать COM. Это не рекомендуется, требует установленного Microsoft Office на сервере, не очень thread-safe, с x64 не дружит и вообще старомодно.
  3. «Ъ» – разобраться с форматом, собрать из XML и зазипать. Брутально.
  4. «Microsoft way» – об этом способе рассказывается под катом.


Небольшое введение


OfficeOpenXML – это то, в чём вы по умолчанию сохраняете документы, работая в Word и Excel: docx и xlsx. Файл представляет собой zip-архив. Его можно переименовать в zip, открыть архиватором и рассмотреть, что внутри:
OfficeOpenXML Folder View
Отчёты в OOXML хорошо воспринимаются и редактируются привычными средствами. Я бы не рекомендовал в серьёзных приложениях ограничиваться именно этим форматом, но советую поддерживать его.

Подготовка


Нам понадобятся:
Качаем OpenXMLSDKTool с сайта Microsoft и устанавливаем его:

Setup

Поехали


Запускаем Open XML SDK 2.0 Productivity Tool:
Productivity Tool
Эта тулза очень простая и умеет делать две маленькие, но важные операции:
  • Сгенерировать код по документу
  • Сравнивать документы на уровне XML
Но обо всём по порядку.

Генерация кода


Загружаем в программулину наш документ и кликаем «Reflect Code»:
Reflect Code

Слева мы видим структуру документа – те же файлы, что присутствуют в архиве, и представление их содержимого.
Ноды в дереве можно выделять: справа видно содержимое ноды в виде XML и код, который может сгенерировать именно этот кусочек. На моём примере виден один абзац из тела документа. Оно как раз живёт в word/document.xml.
Если выделить корень дерева (сам документ) – получим код для всего документа.

Теперь давайте поиспользуем этот код
  1. Делаем проект в Visual Studio. Пусть это будет простое консольное C#-приложение
  2. Добавляем референс на сборку DocumentFormat.OpenXml:
    Add Reference
    У меня она в GAC. Если вы не хотите её туда класть, можно добавить ссылку на сам файл. Отдельно скачать его можно там же, где был OpenXMLSDKTool, но по ссылке OpenXMLSDKv2.msi
  3. Добавляем референс на WindowsBase
  4. Добавляем файл «GeneratedClass.cs»
  5. Копируем туда код из тулзы, из окошка ReflectedCode
  6. Закрываем файл, сохранив его, переходим в Program.cs
  7. Пишем метод Main:
    new GeneratedCode.GeneratedClass().CreatePackage(@"D:\Temp\Output.docx");
  8. Запускаем
Всё. Код для генерации документа готов. Документ будет выглядеть точно так же, как он выглядел перед тем, как вы сохранили его в Word. Быстро, не правда ли?

Что внутри?

Что же внутри сгенерированного класса?
Во-первых, там один единственный открытый метод:
public void CreatePackage(string filePath) {
  using (WordprocessingDocument package = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) {
    CreateParts(package);
  }
}

Вот тут вставляется текст, который будет в документе:
private void GenerateMainDocumentPart1Content(MainDocumentPart mainDocumentPart1) {
  Run run2 = new Run() { RsidRunProperties = "00184031" };
  Text text2 = new Text();
  text2.Text = "Исчисление предикатов, по определению, философски выводит структурализм, изменяя привычную реальность."; // о.О какую траву курил Яндекс?
}

Как видно из названий private-методов в коде, OpenXml-документ состоит из частей (part). Для генерации каждой части сделан отдельный метод.
Наиболее любознательные, конечно же, ехидно улыбнувшись, вставили в документ картинку.
Картинки хранятся прямо в этом файле, в виде base64, вот тут:
#region Binary Data
//...
#endregion

Завязываем бантики

Рефакторинг картинок и замена статического контента на динамический оставим читателю в качестве упражнения.
А вот метод, который генерирует не файл, а массив байтов – для отдачи клиенту из asp.net без временных файлов:
public byte[] CreatePackageAsBytes() {
  using (var mstm = new MemoryStream()) {
    using (WordprocessingDocument package = WordprocessingDocument.Create(mstm, WordprocessingDocumentType.Document)) {
      CreateParts(package);
    }
    mstm.Flush();
    mstm.Close();
    return mstm.ToArray();
  }
}

Всё, код для генерации отчёта в формате docx готов.
Осталось заменить контент на динамический. Мы же не делали всё это ради того, чтобы всё время отдавать одно и то же, ведь правда? И добавить на страничку ссылку «Скачать в формате Word».

Сравнение документов


Итак, мы сгенерировали код по документу. Добавили туда много данных, зарефакторили его, внедрили в production. И вот нам надо поменять шрифт и текст в отчёте. Как же это сделать? Кода много, искать в нём долго.
Оказывается, всё очень просто, нам поможет фича сравнения документов:
  1. Положим рядом старый и новый документы
  2. Открываем Open XML Productivity Tool, выбираем «Compare files...»:
    Compare Dialog
  3. Открываем файлы и жмём OK. Перед нами результат сравнения:
    Result

    На строчки с именами файлов можно тыкнуть и увидеть, в чём именно отличия:
    Comparison Details

    В MoreOprions выбирается, что игнорировать при сравнении.
    View Part Code показывает код той части, XML которой вы видите.
    Уж сопоставить XML и код труда не составит.

Кстати, эту фичу ещё очень удобно использовать, если вы только знакомитесь с форматом OpenXML: добавляете что-то в документ и смотрите, что изменилось. Поможет тем, кто выбрал способ «Ъ», о котором говорилось в начале статьи.

Факты

  • С Xlsx катит. Точно так же, как с docx
  • Если внутри Docx график или диаграмма – всё будет хорошо
  • Это всего лишь strongly-typed обёртка над библиотекой System.IO.Packaging
  • На сервере не нужно ничего, кроме этой библиотеки
  • Никаких проблем с x64
  • Производительность на высоте

Выводы


Я считаю, что использование DocumentFormat.OpenXml для генерации отчётов в web-приложениях – правильный выбор. Полезная тулза из SDK позволит вам не тратить время зря.

Что почитать


Про OpenXML SDK: msdn.microsoft.com/en-us/library/bb448854(office.14).aspx
Про OpenXML (если кто с ним не знаком): en.wikipedia.org/wiki/Office_Open_XML

Удачи! Спасибо за внимание.
+55
48.5k 153
Comments 31