В предыдущей статье я постарался “просто и доступно” рассказать о том, что такое Open Package Convention (или иначе говоря, как устроены изнутри документы MS Office 2007+).
Как водится статья писалась долго, вышла большой, съела много сил… в общем, я так и не показал реальный пример документа в формате OPC. В результате получилось несколько оторвано от практики и не очень наглядно, на что мне сразу указали коллеги.
Что ж, исправляюсь… Сегодня мы в качестве практики создадим пару документов для MS Word, не используя никаких специальных инструментов (за исключением XML-редактора и Zip-архиватора).
Сразу же оговорюсь, что мы не будем сильно вдаваться в особенности разметки документов Word (хотя, конечно же, минимальные представления о ней все же понадобятся, но всё необходимое для понимания я постараюсь рассказать по мере развития статьи)! Наша задача: увидеть как строятся реальные пакеты на основе OPC – что такое компоненты, связи и как они хранятся.
Документ #1 – простой текст
Если попробовать заглянуть внутрь готового Word-документа, созданного в Office, можно легко прийти в ужас от обилия различных компонент непонятного формата назначения.
В реальности структура самого простого рабочего документа (такого, который сможет открыть и показать Word) включает всего 3 элемента:
- главный компонент, который содержит разметку всего документа
- 1 компонент связи (который содержит связь между пакетом и главным компонентом
- описание типов (файл [Content_Types].xml)
Примерно так:
Давайте теперь создадим пустую папку, которая будет представлять содержимое всего пакета, и будем последовательно её заполнять.
Главный компонент документа. Создадим в нашей папке файл main.xml (в стандарте OpenXML нет жесткого требования к именованию компонент, поэтому мы будем использовать свои имена, не такие как в Word).
Этот файл будет представлять содержимое главного компонента (Main Document ы терминологии стандарта). В лучших традициях книг по программированию зададим ему следующее содержимое:
<document xmlns="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> <body> <!-- Структра тела документа включает в себя: параграф (тэг <p>) элемент текста с форматированием (тэг <r>) собственно отображаемый текст (тэг <t>) --> <p> <r> <t>Hello, World!</t> </r> </p> </body> </document>
Компонент связи. Теперь мы должны указать что именно компонент /main.xml содержит разметку документа. В OpenXML для этого используется механизм связей. В нашем документе будет только одна связь – от пакета к главному компоненту (главный компонент пока связей не имеет)
Как я писал в предыдущей статье у компонента, который хранит связи всего пакета имя будет /_rels/.rels. Для эмуляции такого имени (чтобы оно потом правильно создалось в конечном ZIP-архиве) мы создадим подпапку _rels, а в ней файл с именем .rels. Содержать этот файл будет всего одну связь:
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="main.xml" /> </Relationships>
Описание типов. По большому счету, в нашем примере используется всего 2 типа контента: в компоненте с основным содержимым документа и в компоненте связи. Однако, хотя мы дали главному компоненту “расширение” .xml, его тип содержимого по стандарту OpenXML должен быть не просто application/xml, поэтому мы опишем 3 типа контента: для всех компонентов связей, для “некого произвольного xml” и явно для компонента /main.xml.
Итак, создадим в нашей папке файл [Content_Types].xml следующего содержания:
<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="/main.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> </Types>
Итак, содержимое нашей папки составляют 3 файла:
- \_rels\.rels
- \[Content_Types].xml
- \main.xml
Осталось упаковать их в отдельный архив, переименовать архив в, например, result.docx, и открыть полученный файл в Word. Наш результат будет:
Давайте теперь усложним пример, добавив в документ изображение.
Документ #2 – тот же текст и картинка
Какие изменения нам потребуется внести в предыдущий пример? Вот они:
- добавить компонент с изображением и дополнить описание типов содержимого
- создать связь от главного компонента к компоненту с изображением (а это значит, что добавить еще один компонент связей)
- дополнить разметку самого документа (указать место и параметры вставляемой картинки)
Структура нашего пакета приобретет во такой вид:
В принципе, ничего сверхъестественного, поэтому приступим.
Компонент картинки и тип содержимого для него. Для добавления компонента просто скопируем готовый файл с картинкой в папку, к остальным компонентам (пусть это будет файл cat.jpeg).
После этого обновим содержимое файла типов содержимого ([Content_Types].xml):
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> <Default Extension="jpeg" ContentType="image/jpeg"/> <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/> <Default Extension="xml" ContentType="application/xml"/> <Override PartName="/main.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/> </Types>
Связь от /main.xml к /cat.jpeg. Так как мы создаем связь от компонента /main.xml, имя компонента связей для него будет /_rels/main.xml.rels, а значит создадим в папке _rels еще один файл с именем main.xml.rels и содержащем описание 1 связи:
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="cat.jpeg" /> </Relationships>
Осталось самое сложное – поправить разметку самого документа.
Разметка главного компонента. Вообще, надо признать, что разметка документов OpenXML местами весьма далека от “интуитивно понятной” и это справедливо, в том числе для описания изображений (в OpenXML используется единый подъязык для описания любых изображений – DrawingML, у которого есть еще несколько внутренних диалектов: для описания картинок, графиков, …).
Единственный предварительный комментарий нужно дать по поводу размерности единиц… В OpenXML используется специальная придуманная единица EMU (English Metric Unit) – единица, которая позволяет относительно удобно переводить размеры из метрической (метры/сантиметры) и американской (дюймы) систем единиц. Соотношения следующие:
1 см | 360000 EMU |
1 дюйм | 914400 EMU |
Все, можно оценивать (размеры областей вычислены на основе размеров картинки):
<document xmlns="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:draw="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:rel="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:drawmain="http://schemas.openxmlformats.org/drawingml/2006/main"> <body> <p> <r> <t>Hello, World!</t> </r> </p> <p> <r> <!--Добавляем область рисования--> <drawing> <!-- Область будет внутритекстовой (есть также с жестким позиционированием) атрибуты - отступы по краям картинки от текста --> <draw:inline distT="0" distB="0" distL="0" distR="0"> <!-- Набор параметров <draw:extent> - размер области под изображение <draw:docPr> - метаданные изображения (ид, имя, ...) <draw:cNvGraphicFramePr> - некоторые параметры отображения --> <draw:extent cx="5940425" cy="3920490"/> <draw:docPr id="1" name="1"/> <draw:cNvGraphicFramePr> <drawmain:graphicFrameLocks noChangeAspect="true"/> </draw:cNvGraphicFramePr> <!-- Собственно само изображение --> <drawmain:graphic> <!-- Здесь определяется что именно будет вставляться - картинка, график, OLE, ... и в зависимости от этого выбирается диалект описания. У нас картинка --> <drawmain:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"> <pic:pic> <!-- Вновь метаданные ... --> <pic:nvPicPr> <pic:cNvPr id="1" name="1"/> <pic:cNvPicPr> </pic:cNvPicPr> </pic:nvPicPr> <!-- Собственно вот здесь описывается как заполняется область изображения в нашем случае, это ссылка на связь с id: rId1--> <pic:blipFill> <drawmain:blip rel:embed="rId1"/> <drawmain:stretch> <drawmain:fillRect/> </drawmain:stretch> </pic:blipFill> <!-- Фигура, в которую вписывается изображение <drawmain:xfrm> - размеры фрейма для вписывание + его смещение относительно общей области рисования <drawmain:prstGeom> - указывает, что мы заполняем стандартную фигуру (у нас прямоугольник) --> <pic:spPr> <drawmain:xfrm> <drawmain:off x="0" y="0"/> <drawmain:ext cx="5940425" cy="3920490"/> </drawmain:xfrm> <drawmain:prstGeom prst="rect" /> </pic:spPr> </pic:pic> </drawmain:graphicData> </drawmain:graphic> </draw:inline> </drawing> </r> </p> </body> </document>
- \_rels\.rels
- \_rels\main.xml.rels
- \[Content_Types].xml
- \cat.jpeg
- \main.xml
Вновь собираем все в один Zip-архив и открываем в Word:
Вот и все.
P.S. Для желающих продолжить эксперименты – все исходные файлы, а также результаты можно найти на Codeplex в проекте https://github.com/MihailRomanov/msosamples (здесь и далее я планирую размещать все приводимые примеры). Прямая ссылка на нужную папку.
http://blogs.office.com/2013/01/29/10-tips-for-developers-working-with-the-visio-vsdx-file-format/ вот полезная статейка на тему (хоть и про визио, но формат-то один).
Статья и правда хорошая. Спасибо.
У этого формата сейчас единственная проблема – манипулировать разметкой можно только или правя напрямую XML, или создавая свой собственный DOM-велосипед (тогда как для остальный OPC-based форматов в Office есть Open XML SDK – не фонтан по удобству, но все же хоть что-то).