Задача обработки документов в формате docx, а также таблиц xlsx и презентаций pptx является весьма нетривиальной. В этой статье расскажу как научиться парсить, создавать и обрабатывать такие документы используя только XSLT и ZIP архиватор. docx — самый популярный формат документов, поэтому задача отдавать информацию пользователю в этом формате всегда может возникнуть. Один из вариантов решения этой проблемы — использование готовой библиотеки, может не подходить по ряду причин: Поэтому в этой статье будем использовать только самые базовые инструменты для работы с docx документом. Для начала разоберёмся с тем, что собой представляет docx документ. docx это zip архив который физически содержит 2 типа файлов: А логически — 3 вида элементов: Они подробно описаны в стандарте ECMA-376: Office Open XML File Formats, основная часть которого — PDF документ на 5000 страниц, и ещё 2000 страниц бонусного контента. Простейший docx после распаковки выглядит следующим образом  Давайте посмотрим из чего он состоит. Находится в корне документа и перечисляет MIME типы содержимого документа: Главный список связей документа. В данном случае определена всего одна связь — сопоставление с идентификатором rId1 и файлом word/document.xml — основным телом документа. Основное содержимое документа. Здесь: Если открыть этот документ в текстовом редакторе, то увидим документ из одного слова Test. Здесь содержится список связей части word/document.xml. Название файла связей создаётся из названия части документа к которой он относится и добавления к нему расширения rels. Папка с файлом связей называется _rels и находится на том же уровне, что и часть к которой он относится. Так как связей в word/document.xml никаких нет то и в файле пусто: Даже если связей нет, этот файл должен существовать. docx созданный с помощью Microsoft Word, да в принципе и с помощью любого другого редактора имеет несколько дополнительных файлов.  Вот что в них содержится: В сложных документах частей может быть гораздо больше. Итак, первоначальная задача — узнать как какой-либо фрагмент документа хранится в xml, чтобы потом создавать (или парсить) подобные документы самостоятельно. Для этого нам понадобятся: Также понадобятся скрипты для автоматического (раз)архивирования и форматирования XML.
Использование под Windows: Использование под Linux аналогично, только ./unpack.sh вместо unpack, а pack становится ./pack.sh. Поиск изменений происходит следующим образом: Посмотрим на практике, как найти тег который определяет форматирование текста жирным шрифтом. 
Рассмотрим его подробно: Изменение времени нам не нужно. Изменение версии документа и даты модификации нас также не интересует. Изменения в w:rsidR не интересны — это внутренняя информация для Microsoft Word. Ключевое изменение тут в параграфе с Test. Видимо элемент <w:b/> и делает текст жирным. Оставляем это изменение и отменяем остальные. Также не содержит ничего относящегося к жирному тексту. Отменяем. 7 Запаковываем папку с 1м изменением (добавлением <w:b/>) и проверяем что документ открывается и показывает то, что ожидалось.
8 Коммитим изменение. Теперь разберём пример посложнее — добавление нижнего колонтитула.
Вот первоначальный коммит. Добавляем нижний колонтитул с текстом 123 и распаковываем документ. Такой diff получается первоначально:  Сразу же исключаем изменения в docProps/app.xml и docProps/core.xml — там тоже самое, что и в первом примере. footer явно выглядит как то, что нам нужно, но что делать с footnotes и endnotes? Являются ли они обязательными при добавлении нижнего колонтитула или их создали заодно? Ответить на этот вопрос не всегда просто, вот основные пути: 
Идём пока что дальше. Изначально diff выглядит вот так: Видно, что часть изменений связана с тем, что Word изменил порядок связей, уберём их: Опять появляются footer, footnotes, endnotes. Все они связаны с основным документом, перейдём к нему: Редкий случай когда есть только нужные изменения. Видна явная ссылка на footer из sectPr. А так как ссылок в документе на footnotes и endnotes нет, то можно предположить что они нам не понадобятся. А вот и появились ссылки на footnotes, endnotes добавляющие их в документ. Изменения в стилях нас интересуют только если мы ищем как поменять стиль. В данном случае это изменение можно убрать. Посмотрим теперь собственно на сам нижний колонтитул (часть пространств имён опущена для читабельности, но в документе они должны быть):  Тут виден текст 123. Единственное, что надо исправить — убрать ссылку на <w:pStyle w:val="a6"/>. В результате анализа всех изменений делаем следующие предположения: Уменьшаем diff до этого набора изменений:  Затем запаковываем документ и открываем его.
Если всё сделано правильно, то документ откроется и в нём будет нижний колонтитул с текстом 123. А вот и итоговый коммит. Таким образом процесс поиска изменений сводится к поиску минимального набора изменений, достаточного для достижения заданного результата. Найдя интересующее нас изменение, логично перейти к следующему этапу, это может быть что-либо из: Тут нам потребуются знания XSLT и XPath.  Давайте напишем достаточно простое преобразование — замену или добавление нижнего колонтитула в существующий документ. Писать я буду на языке Caché ObjectScript, но даже если вы его не знаете — не беда. В основном будем вызовать XSLT и архиватор. Ничего более. Итак, приступим. Алгоритм выглядит следующим образом: Приступим. В Caché ObjectScript есть возможность выполнять команды ОС с помощью функции $zf(-1, oscommand). Вызовем unzip для распаковки документа с помощью обёртки над $zf(-1): На вход поступает текст нижнего колонтитула, запишем его в файл in.xml: В XSLT (файл — footer.xsl) будем создавать нижний колонтитул с текстом из тега xml (часть пространств имён опущена, вот полный список):  Теперь вызовем XSLT преобразователь: В результате получится файл нижнего колонтитула footer0.xml:  Сссылки с идентификатором rId0 как правило не существует. Впрочем можно использовать XPath для получения идентификатора которого точно не существует.
Добавляем ссылку на footer0.xml c идентификатором rId0 в word/_rels/document.xml.rels: Далее надо в каждый тег <w:sectPr> добавить тег <w:footerReference> или заменить в нём ссылку на наш нижний колонтитул. Оказалось, что у каждого тега <w:sectPr> может быть 3 тега <w:footerReference> — для первой страницы, четных страниц и всего остального: Добавляем в [Content_Types].xml информацию о том, что /word/footer0.xml имеет тип application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml: Весь код опубликован. Работает он так: Где: Используя только XSLT и ZIP можно успешно работать с документами docx, таблицами xlsx и презентациями pptx.