Як я розбирав docx з допомогою XSLT

Завдання обробки документів у форматі docx, а також таблиць xlsx і презентацій pptx є досить нетривіальною. У цій статті розповім як навчитися парсити, створювати і обробляти такі документи використовуючи тільки XSLT і ZIP архіватор.
Навіщо?
docx — найпопулярніший формат документів, тому завдання віддавати інформацію користувачеві в цьому форматі завжди може виникнути. Один з варіантів вирішення цієї проблеми — використання готової бібліотеки, може не підходити по ряду причин:
  • бібліотеки може просто не існувати
  • у проекті не потрібен ще один чорний ящик
  • обмеження бібліотеки по платформах і т. п.
  • проблеми з ліцензуванням
  • швидкість роботи
Тому в цій статті будемо використовувати лише самі базові інструменти для роботи з docx документом.
Структура docx
Для початку разоберемся з тим, що собою являє docx документ. docx це zip архів, який фізично містить 2 типи файлів:
  • xml файли з розширеннями
    xml
    та
    rels
  • медіа файли (зображення тощо)
А логічно — 3 види елементів:
  • Типи (Content Types) — список типів медіа-файлів (наприклад png) зустрічаються в документі і типів частин документів (наприклад, документ, верхній колонтитул).
  • Частини (Parts) — окремі частини документа, для нашого документа це document.xml сюди входять як xml-документи так і медіа файли.
  • Зв'язку (Relationships) ідентифікують частини документа для посилань (наприклад зв'язок між розділом документа та колонтитулом), а також тут визначено зовнішні частини (наприклад гіперпосилання).
Вони докладно описані в стандарті ECMA-376: Office Open XML File Formats, основна частина якого — PDF документ на 5000 сторінок, і ще 2000 сторінок бонусного контенту.
Мінімальний docx
Найпростіший docx після розпакування виглядає наступним чином
image
Давайте подивимося з чого він складається.

[Content_Types].xml

Знаходиться в корені документа і перераховує MIME типи вмісту документа:
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml"
ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>

_rels/.rels

Головний список зв'язків документа. В даному випадку визначена лише одна зв'язок — зіставлення з ідентифікатором rId1 і файлом word/document.xml — основним тілом документа.
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship 
Id="rId1" 
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
Target="word/document.xml"/>
</Relationships>

word/document.xml

Основний вміст документа.
word/document.xml<code class=«xml> < w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
mc:Ignorable="w14 wp14">
<w:body>
<w:p w:rsidR="005F670F" w:rsidRDefault="005F79F5">
<w:r>
<w:t>Test</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:sectPr w:rsidR="005F670F">
<w:pgSz w:w="12240" w:h="15840"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
w:header="720" w:footer="720" w:gutter="0"/>
<w:cols w:space="720"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>
</w:body>
</w:document>
Тут:
  • <w:document>
    — сам документ
  • <w:body>
    — тіло документа
  • <w:p>
    пункт
  • <w:r>
    — run (фрагмент) тексту
  • <w:t>
    — сам текст
  • <w:sectPr>
    — опис сторінки
Якщо відкрити цей документ в текстовому редакторі, то побачимо документ з одного слова
Test
.

word/_rels/document.xml.rels

Тут міститься список зв'язків частині
word/document.xml
. Назва файлу зв'язків створюється з назви частини документа до якої він належить і додавання до нього розширення
rels
. Тека з файлом зв'язків називається
_rels
і знаходиться на тому ж рівні, що і частина до якої він відноситься. Так як зв'язків у
word/document.xml
ніяких немає то і у файлі порожньо:
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
</Relationships>

Навіть якщо зв'язків немає, цей файл повинен існувати.
docx і Microsoft Word
docx створений за допомогою Microsoft Word, та в принципі і за допомогою будь-якого іншого редактора має кілька додаткових файлів.
image
Ось що в них міститься:
У складних документах частин може бути набагато більше.
Реверс-інжиніринг docx
Отже, першочергове завдання — дізнатися, як який-небудь фрагмент документа зберігається в xml, щоб потім створювати (або парсити) подібні документи самостійно. Для цього нам знадобляться:
  • Архіватор zip
  • Бібліотека для форматування XML (Word видає XML без відступів, одним рядком)
  • Засіб для перегляду diff між файлами, я буду використовувати git і TortoiseGit

Інструменти

Також знадобляться сценарії для автоматичного (раз)архівування та форматування XML.
Використання під Windows:
  • unpack file dir
    — розпаковує документ
    file
    в каталог
    dir
    і форматує xml
  • pack dir file
    — запаковує теку
    dir
    документ
    file
Використання під Linux аналогічно, тільки
./unpack.sh
замість
unpack
, а
pack
стає
./pack
.

Використання

Пошук змін відбувається наступним чином:
  1. Створюємо порожній docx файл в редакторі.
  2. Розпаковуємо його з допомогою
    unpack
    в нову папку.
  3. Коммитим нову папку.
  4. Додаємо файл із п. 1. досліджуваний елемент (гіперпосилання, таблиці тощо).
  5. Розпаковуємо файл у вже існуючу папку.
  6. Вивчаємо diff, прибираючи небажані зміни (перестановки зв'язків, порядок просторів імен і тощо).
  7. Запаковуємо папку і перевіряємо що отриманий файл відкривається.
  8. Коммитим змінену папку.

Приклад 1. Виділення тексту жирним

Подивимося на практиці, як знайти тег, який визначає форматування тексту жирним шрифтом.
  1. Створюємо документ
    bold.docx
    з звичайним (не жирним) текстом Test.
  2. Розпаковуємо його:
    unpack bold.docx bold
    .
  3. Коммитим результат.
  4. Виділяємо текст Test жирним.
  5. Розпаковуємо
    unpack bold.docx bold
    .
  6. Спочатку diff виглядав наступним чином:
diff
Розглянемо його докладно:

docProps/app.xml

@@ -1,9 +1,9 @@
- <TotalTime>0</TotalTime>
+ <TotalTime>1</TotalTime>

Зміна часу нам не потрібно.

docProps/core.xml

@@ -4,9 +4,9 @@
- <cp:revision>1</cp:revision>
+ <cp:revision>2</cp:revision>
<dcterms:created xsi:type="dcterms:W3CDTF">2017-02-07T19:37:00Z</dcterms:created>
- <dcterms:modified xsi:type="dcterms:W3CDTF">2017-02-07T19:37:00Z</dcterms:modified>
+ <dcterms:modified xsi:type="dcterms:W3CDTF">2017-02-08T10:01:00Z</dcterms:modified>

Зміна версії документа і дати модифікації нас також не цікавить.

word/document.xml

diff
@@ -1,24 +1,26 @@
<w:body>
- <w:p w:rsidR="0076695C" w:rsidRPr="00290C70" w:rsidRDefault="00290C70">
+ <w:p w:rsidR="0076695C" w:rsidRPr="00F752CF" w:rsidRDefault="00290C70">
<w:pPr>
<w:rPr>
+ <w:b/>
<w:lang w:val="en-US"/>
</w:rPr>
</w:pPr>
- <w:r>
+ <w:r w:rsidRPr="00F752CF">
<w:rPr>
+ <w:b/>
<w:lang w:val="en-US"/>
</w:rPr>
<w:t>Test</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
- <w:sectPr w:rsidR="0076695C" w:rsidRPr="00290C70">
+ <w:sectPr w:rsidR="0076695C" w:rsidRPr="00F752CF">

Зміни
w:rsidR
не цікаві — це внутрішня інформація для Microsoft Word. Ключова зміна тут
<w:rPr>
+ <w:b/>

у параграфі з Test. Мабуть елемент
<w:b/>
і робить текст жирним. Залишаємо це зміна і скасовуємо інші.

word/settings.xml

@@ -1,8 +1,9 @@
+ <w:proofState w:spelling="clean"/>
@@ -17,10 +18,11 @@
+ <w:rsid w:val="00F752CF"/>

Також не містить нічого відноситься до жирного тексту. Скасовуємо.
7 Запаковуємо папку з 1м зміною (додаванням
<w:b/>
), і перевіряємо, що документ відкривається і показує те, що очікувалося.
8 Коммитим зміна.

Приклад 2. Нижній колонтитул

Тепер розберемо приклад складніше — додавання нижнього колонтитула.
Ось початковий комміт. Додаємо нижній колонтитул з текстом 123 і розпаковуємо документ. Такий diff виходить спочатку:
diff
Одразу ж виключаємо зміни в
docProps/app.xml
та
docProps/core.xml
— там теж саме, що і в першому прикладі.

[Content_Types].xml

@@ -4,10 +4,13 @@
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
+ <Override PartName="/word/footnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"/>
+ <Override PartName="/word/endnotes.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"/>
+ <Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>

footer явно виглядає як те, що нам потрібно, але що робити з footnotes і endnotes? Вони є обов'язковими при додаванні нижнього колонтитула або їх створили заодно? Відповісти на це питання не завжди просто, ось основні шляхи:
  • Подивитися, чи пов'язані зміни один з одним
  • Експериментувати
  • Ну а якщо зовсім не зрозуміло що відбувається:
Читати документацію
Йдемо поки що далі.

word/_rels/document.xml.rels

Спочатку diff виглядає ось так:
diff
@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="так"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+ <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" Target="webSettings.xml"/>
+ <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" Target="settings.xml"/>
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
- <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/>
- <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
+ <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>
+ <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" Target="endnotes.xml"/>
+ <Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml"/>
</Relationships>

Видно, що частина змін пов'язана з тим, що Word змінив порядок зв'язків, приберемо їх:
@@ -3,6 +3,9 @@
+ <Relationship Id="rId6" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>
+ <Relationship Id="rId7" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" Target="endnotes.xml"/>
+ <Relationship Id="rId8" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" Target="footnotes.xml"/>

Знову з'являються footer, footnotes, endnotes. Всі вони пов'язані з основним документом, перейдемо до нього:

word/document.xml

@@ -15,10 +15,11 @@
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:sectPr w:rsidR="0076695C" w:rsidRPr="00290C70">
+ <w:footerReference w:type="default" r:id="rId6"/>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1134" w:right="850" w:bottom="1134" w:left="1701" w:header="708" w:footer="708" w:gutter="0"/>
<w:cols w:space="708"/>
<w:docGrid w:linePitch="360"/>
</w:sectPr>

Рідкісний випадок коли є тільки потрібні зміни. Видно явне посилання на footer з sectPr. А так як посилань в документі на footnotes і endnotes немає, то можна припустити що вони нам не знадобляться.

word/settings.xml

diff
@@ -1,19 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="так"?>
<w:settings xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" mc:Ignorable="w14 w15">
<w:zoom w:percent="100"/>
+ <w:proofState w:spelling="clean"/>
<w:defaultTabStop w:val="708"/>
<w:characterSpacingControl w:val="doNotCompress"/>
+ <w:footnotePr>
+ <w:footnote w:id="-1"/>
+ <w:footnote w:id="0"/>
+ </w:footnotePr>
+ <w:endnotePr>
+ <w:endnote w:id="-1"/>
+ <w:endnote w:id="0"/>
+ </w:endnotePr>
<w:compat>
<w:compatSetting w:name="compatibilityMode" w:uri="http://schemas.microsoft.com/office/word" w:val="15"/>
<w:compatSetting w:name="overrideTableStyleFontSizeAndJustification" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/>
<w:compatSetting w:name="enableOpenTypeFeatures" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/>
<w:compatSetting w:name="doNotFlipMirrorIndents" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/>
<w:compatSetting w:name="differentiateMultirowTableHeaders" w:uri="http://schemas.microsoft.com/office/word" w:val="1"/>
</w:compat>
<w:rsids>
<w:rsidRoot w:val="00290C70"/>
+ <w:rsid w:val="000A7B7B"/>
+ <w:rsid w:val="001B0DE6"/>

А ось і з'явилися посилання на footnotes, endnotes додають їх у документ.

word/styles.xml

diff
@@ -480,6 +480,50 @@
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"/>
<w:b/>
<w:sz w:val="28"/>
</w:rPr>
</w:style>
+ <w:style w:type="paragraph" w:styleId="a4">
+ <w:name w:val="header"/>
+ <w:basedOn w:val="a"/>
+ <w:link w:val="a5"/>
+ <w:uiPriority w:val="99"/>
+ <w:unhideWhenUsed/>
+ <w:rsid w:val="000A7B7B"/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val="center" w:pos="4677"/>
+ <w:tab w:val="right" w:pos="9355"/>
+ </w:tabs>
+ <w:spacing w:after="0" w:line="240" w:lineRule="авто"/>
+ </w:pPr>
+ </w:style>
+ <w:style w:type="character" w:customStyle="1" w:styleId="a5">
+ <w:name w:val="Верхній колонтитул Знак"/>
+ <w:basedOn w:val="a0"/>
+ <w:link w:val="a4"/>
+ <w:uiPriority w:val="99"/>
+ <w:rsid w:val="000A7B7B"/>
+ </w:style>
+ <w:style w:type="paragraph" w:styleId="a6">
+ <w:name w:val="footer"/>
+ <w:basedOn w:val="a"/>
+ <w:link w:val="a7"/>
+ <w:uiPriority w:val="99"/>
+ <w:unhideWhenUsed/>
+ <w:rsid w:val="000A7B7B"/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val="center" w:pos="4677"/>
+ <w:tab w:val="right" w:pos="9355"/>
+ </w:tabs>
+ <w:spacing w:after="0" w:line="240" w:lineRule="авто"/>
+ </w:pPr>
+ </w:style>
+ <w:style w:type="character" w:customStyle="1" w:styleId="a7">
+ <w:name w:val="Нижній колонтитул Знак"/>
+ <w:basedOn w:val="a0"/>
+ <w:link w:val="a6"/>
+ <w:uiPriority w:val="99"/>
+ <w:rsid w:val="000A7B7B"/>
+ </w:style>
</w:styles>

Зміни в стилях нас цікавлять тільки якщо ми шукаємо як поміняти стиль. В даному випадку це зміна можна прибрати.

word/footer1.xml

Подивимося тепер власне на сам нижній колонтитул (частина просторів імен опущена для читабельності, але в документі, вони повинні бути):
<code class=»xml> < w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:p w:rsidR="000A7B7B" w:rsidRDefault="000A7B7B">
<w:pPr>
<w:pStyle w:val="a6"/>
</w:pPr>
<w:r>
<w:t>123</w:t>
</w:r>
</w:p>
</w:ftr>
Тут видно текст 123. Єдине, що треба виправити — прибрати посилання на
<w:pStyle w:val="a6"/>
.
У результаті аналізу всіх змін робимо наступні припущення:
  • footnotes і endnotes не потрібні
  • [Content_Types].xml
    треба додати footer
  • word/_rels/document.xml.rels
    треба додати посилання на footer
  • word/document.xml
    в тег
    <w:sectPr>
    треба додати
    <w:footerReference>
Зменшуємо diff до цього набору змін:
final diff
Потім запаковуємо документ і відкриваємо його.
Якщо все зроблено правильно, то документ відкриється і в ньому буде колонтитул з текстом 123. А ось і підсумковий коммит.
Таким чином процес пошуку змін зводиться до пошуку мінімального набору змін, достатнього для досягнення заданого результату.
Практика
Знайшовши нас цікавить зміна, логічно перейти до наступного етапу, це може бути що-небудь з:
  • Створення docx
  • Парсинг docx
  • Перетворення docx
Тут нам потрібні знання XSLT і XPath.
Давайте напишемо досить просте перетворення — заміну або додавання нижнього колонтитула в існуючий документ. Писати я буду мовою Caché ObjectScript, але навіть якщо ви не знаєте — не біда. В основному будемо вызовать XSLT і архіватор. Нічого більше. Отже, приступимо.
Алгоритм
Алгоритм виглядає наступним чином:
  1. Розпаковуємо документ
  2. Додаємо наш нижній колонтитул
  3. Прописуємо посилання на нього в
    [Content_Types].xml
    та
    word/_rels/document.xml.rels
  4. word/document.xml
    в тег
    <w:sectPr>
    додаємо тег
    <w:footerReference>
    або замінюємо в ньому посилання на наш нижній колонтитул.
  5. Запаковуємо документ
Приступимо.

Розпакування

У Caché ObjectScript є можливість виконувати команди ОС з допомогою функції $zf(-1, oscommand). Викличемо unzip для розпакування документа за допомогою обгортки над $zf(-1):
/// Використовуючи %3 (unzip) розпакувати файл %1 в папку %2
Parameter UNZIP = "%3 %1 -d %2";

/// Розпакувати архів source в папку targetDir
ClassMethod executeUnzip(source, targetDir) As %Status
{
set timeout = 100
set cmd = $$$FormatText(..#UNZIP, source, targetDir, ..getUnzip())
return ..execute(cmd, timeout)
}

Створюємо файл нижнього колонтитула

На вхід надходить текст нижнього колонтитула, запишемо його у файл in.xml:
<xml>TEST</xml>

В XSLT (файл — footer.xsl) будемо створювати нижній колонтитул з текстом з тега xml (частина просторів імен опущена, ось повний список):
<xsl:stylesheet 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns="http://schemas.openxmlformats.org/package/2006/relationships" version="1.0">
<xsl:output method="xml" omit-xml-declaration="ні" indent="так" standalone="так"/>
<xsl:template match="/">

<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:p>
<w:r>
<w:rPr>
<w:lang w:val="en-US"/>
</w:rPr>
<w:t>
<xsl:value-of select="//xml/text()"/>
</w:t>
</w:r>
</w:p>
</w:ftr>
</xsl:template>
</xsl:stylesheet>

Тепер викличемо XSLT перетворювач:
do ##class(%XML.XSLT.Transformer).TransformFile("in.xml", "footer.xsl", footer0.xml") 

У результаті вийде файл нижнього колонтитула
footer0.xml
:
<code class=«xml> < w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:p>
<w:r>
<w:rPr>
<w:lang w:val="en-US"/>
</w:rPr>
<w:t>TEST</w:t>
</w:r>
</w:p>
</w:ftr>

Додаємо посилання на колонтитул список зв'язків основного документа

Сссылки з ідентифікатором
rId0
, як правило, не існує. Втім можна використовувати XPath для отримання ідентифікатора якого точно не існує.
Додаємо посилання на
footer0.xml
з ідентифікатором rId0
word/_rels/document.xml.rels
:
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://schemas.openxmlformats.org/package/2006/relationships" version="1.0">
<xsl:output method="xml" omit-xml-declaration="так" indent="ні" />
<xsl:param name="new">
<Relationship 
Id="rId0" 
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" 
Target="footer0.xml"/>
</xsl:param>

<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="$new"/>
<xsl:copy-of select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Прописуємо посилання в документі

Далі треба в кожен тег
<w:sectPr>
додати тег
<w:footerReference>
або замінити в ньому посилання на наш нижній колонтитул. Оказалось, що у кожного тега
<w:sectPr>
може бути 3 тега
<w:footerReference>
— для першої сторінки, парних сторінок і всього іншого:
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
version="1.0">
<xsl:output method="xml" omit-xml-declaration="так" indent="так" />
<xsl:template match="//@* | //node()">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//w:sectPr">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="./namespace::*"/>
<xsl:apply-templates select="@*"/>
<xsl:copy-of select="./*[local-name() != 'footerReference']"/>
<w:footerReference w:type="default" r:id="rId0"/>
<w:footerReference w:type="first" r:id="rId0"/>
<w:footerReference w:type="even" r:id="rId0"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Додаємо колонтитул
[Content_Types].xml

Додаємо в
[Content_Types].xml
інформацію про те, що
/word/footer0.xml
має тип
application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml
:
XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://schemas.openxmlformats.org/package/2006/content-types" version="1.0">
<xsl:output method="xml" omit-xml-declaration="так" indent="ні" />
<xsl:param name="new">
<Override 
PartName="/word/footer0.xml" 
ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>
</xsl:param>

<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="@* | node()"/> 
<xsl:copy-of select="$new"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

В результаті

Весь код опубликован. Працює він так:
do ##class(Converter.Footer).modifyFooter("in.docx", "out.docx", "TEST")

Де:
  • in.docx
    вихідний документ
  • out.docx
    — виходить документ
  • TEST
    — текст, який додається в нижній колонтитул
Висновки
Використовуючи тільки XSLT і ZIP можна успішно працювати з документами docx, таблицями xlsx і презентаціями pptx.
Відкриті питання
  1. Спочатку хотів використовувати 7z замість zip/unzip т… к. це одна утиліта і вона більш поширена на Windows. Однак я зіткнувся з такою проблемою, що документи запаковані 7z під Linux не відкриваються в Microsoft Office. Я спробував досить багато варіантів виклику, однак позитивного результату досягти не вдалося.
  2. Шукаю XSD зі схемами ECMA-376 версії 5 і коментарями. XSD версії 5 без коментарів доступний до завантаження на сайті ECMA, але без коментарів в ньому складно розібратися. XSD версії 2 з коментарями доступний до завантаження.
Посилання
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.