Open Packaging Conventions #1. Базовые принципы. Компоненты и связи

imageСегодняшним постом я хочу начать еще одну серию. В ней я планирую немного поговорить о том, что представляют собой файлы MS Office “изнутри”, а также об инструментах (утилитах и библиотеках) для их создания, изучения, изменения, … 

Прежде чем перейти к содержательной части некоторый предваряющий disclaimer (традиционно Улыбка):

  • Я в основном буду касаться современных офисных форматов, тех что появились в редакции Office 2007. Их еще называют XML-based форматами, в противовес старым бинарным (и это закрепилось в расширении файлов: docx, pptx, xlsx, … – в противовес doc, ppt, xls, …), ну или просто Open XML
  • Некоторая часть статей (по крайней мере в самом начале) будет основана на материалах Open XML Developer Workshop (контент и видео), который вел Doug Mahugh. Если вам не хочется ждать моих статей рекомендую обратиться к этим материалам
  • Еще одним хорошим подспорьем для изучающих Open XML будет книга Воутер Ван Вугт. OpenXML. Кратко и доступно. Ранее она в электронном виде была доступна в блоге евангелиста Microsoft Владимира Габриеля, но теперь – увы. Так что, если вам интересно и не хочется тратить время на поиск, можете взять здесь.

Вроде бы все. Можно приступать.

Что такое Open Packaging Conventions?

В двух словах, это формат контейнеров, поддерживающих хранение как структурированных (XML), так и неструктурированных компонентов (картинки, видео, бинарные компоненты, …) в одном файле.

Краткая но довольно информативная статья об OPC есть на wikipedia.

Что можно сказать в общем об этом стандарте/формате? Я бы выделил такие моменты:

  • Формальное описание является частью ECMA-376. Office Open XML File Formats, более конкретно – второй частью Part 2 – Open Packaging Conventions.
  • Сам стандарт описывает только структуру хранения и самые общие метаданные (типа автора, даты, …) поэтому потенциально в таком контейнере можно хранить практически что угодно. Например, вот несколько форматов, основанных на OPC от самого Microsoft:
    • .docx, pptx, xlsx, .vsdx – форматы Word, Power Point, Excel и Visio
    • .xps (.oxps) – формат “электронной бумаги” или формат c фиксированной разметкой, предназначенный для передачи документов без искажения форматирования (в чем-то аналог PDF).
    • .vsix – формат расширений Visual Studio, начиная с версии 2010
    • .cspkg – формат пакетов для Windows Azure Cloud Services
    • .appx – формат пакетов приложений Windows Store (для Windows 8)

Что представляют собой контейнеры в OPC?

Тут нужно сделать одну существенную оговорку: сам стандарт разбивает описание контейнеров на 2 части: абстрактную модель (которая описывает из каких элементов состоят контейнеры, но ни слова не говорит о том как это должно храниться физически) и физический формат пакета, т.е. конкретную реализацию.

Т.е. в чистой теории, контейнер в OPC может храниться единый файл, а может, например,  как набор отдельных ресурсов на Web-сервере. Но (!) на текущий момент определена только 1 реализация – в виде единого файла ZIP-архива.

Структура контейнеров в OPC

Вообще говоря, концептуальная схема пакетов в Open Packaging Conventions очень проста, она включает в себя всего два элемента:

  • компоненты (parts), которые собственно и содержат хранящийся контент (любой: xml, image, video, …)
  • отношения (relationships), которые определяют
    • предназначение (смысл/семантику) каждой части
    • отношения между частями, а также между частями и пакетом целиком

image

Компоненты

Как уже было сказано выше компонент в OPC это и есть основная единица хранения контента. Каждый компонент характеризуется 2-я составляющими: именем и типом содержимого.

Имя компонента состоит из набора сегментов, начинающихся с прямого слэша (“/”), вот несколько примеров:

  • /hello/world/doc.xml
  • /docProps/app.xml
  • /image4.png

В спецификации приведены более формальные правила построения имен, из которых я укажу только основные (на мой взгляд):

  • все имена должны начинаться с прямого слэша (“/”) и не должны им заканчиваться
  • имя недолжно содержать пустых сегментов (т.е. /images//image1.jpg – неправильное имя)
  • сегменты могут состоять из букв, цифр и знаков “!“, “$“, “&“, ““, “(“, “)“, “*“, “+“, “,“, “;“, “=“, ““, “.“, “_“, “~“, “:“, “@
  • ни одно имя компонента не должно строиться как имя уже существующего компонента + новый сегмент. Т.е. если есть компонент с именем /abc/abc, то компонент с именем /abc/abc/a существовать не может, зато вполне может существовать компонент с именем /abc/abcde
  • имена могут записываться Unicode-символами или использовать кодирование в виде /a/%D1%86.xml

Тип содержимого компонента задается в соответствии с RFC 2616 (раздел Media Types) т.е. в виде <type>/<subtype>:

  • application/xml
  • image/jpeg
  • application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml

Связи

Любой компонент в пакете (а также сам пакет) может ссылаться на другие компоненты или некоторые ресурсы за пределами пакета. Для представления этих ссылок введен специальный механизм связей. По большому счету, связи позволяют решить 2 задачи:

  • получить список связанных с компонентом ресурсов, без необходимости анализировать его содержимое (которое может быть очень большим, иметь разную структуру, быть зашифрованным или вообще не поддерживать хранение ссылок)
  • поменять набор связей компонента, не меняя его содержимого (которое может быть, например, зашифровано или защищено цифровой подписью)

Создавая свой пакет вы, конечно же, можете не использовать связи. Вместо этого везде в коде использовать фиксированные имена компонент, а где нужны списки связанных ресурсов, ссылаться на них прямо из самих компонент. Однако, рекомендации “лучших собаководов” все же советуют использовать связи везде, где это возможно.

Информация о связях для каждого компонента (а также самого пакета), хранится в специальных компонентах связей (relationships parts) тип содержимого которых application/vnd.openxmlformats-package.relationships+xml

Имена компонентов связи строятся из имени исходных компонент, к которым:

  • добавляется предпоследний сегмент с именем _rels
  • дописывается “расширение” .rels

Связи самого пакета хранятся в специальном компоненте с именем /_rels/.rels

Например, если в пакете у нас есть компонент с именем /document/mainPart.xml и два связанных компонента с картинками (пусть их мена будут /images/image1.png и /images/image2.jpeg), то пакет для них будет иметь следующую структуру:

image

Содержимое компонента связи представляет собой XML следующего формата:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
Target="styles.xml" />
<Relationship Id="rId2"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"
Target="theme/theme1.xml" />
<Relationship Id="rId3"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable"
Target="fontTable.xml" />
<Relationship Id="rId4"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
Target="file:///C:\Users\Public\Pictures\Sample%20Pictures\Desert.jpg"
TargetMode="External" />
</Relationships>

Как уже наверняка понятно из приведенного фрагмента, каждый тэг <Relationship> определяет одну связь. Его атрибуты:

Id Идентификатор связи. На него ссылаются из содержимого компонент, когда необходимо использовать конкретную связь.
Type Тип связи. По сути дела тип указывает семантику связи. Например, две разных связи могут указывать на 2 компонента типа image/jpeg, но одно изображение будет картинкой в тексте документа, а второе – миниатюрой (thumbnail) всей страницы целиком.
В качестве типа может использоваться любой валидный URI
TargetMode

(Необязательный) Принимает одно из возможных значений:

  • Internal (значение по умолчанию) – указывает, что связь ссылается на компонент пакета
  • External – связь указывает на ресурс за пределами пакета
  • Target Адрес ресурса или компонента на который ссылается связь

    Важный момент: для обращения к компонентам и внешним ресурсам можно использовать как абсолютные адреса (для компонент это будет их полное имя), так и относительные. В последнем случае полное имя компоненты рассматривается как путь в файловой системе, каждый сегмент, кроме последнего – имя “папки”, а последний – имя “файла”. Вот несколько примеров такой адресации:

    Имя исходного компонента

    Относительный адрес

    Результирующий адрес

    /mydoc/markup/page.xml picture.jpg /mydoc/markup/picture.jpg
    /mydoc/markup/page.xml images/picture.jpg /mydoc/markup/images/picture.jpg
    /mydoc/markup/page.xml ./picture.jpg /mydoc/markup/picture.jpg
    /mydoc/markup/page.xml ../picture.jpg /mydoc/picture.jpg
    /mydoc/markup/page.xml ../images/picture.jpg /mydoc/images/picture.jpg
    / images/picture.jpg /images/picture.jpg 

    Вот, по большому счету и все, что касается модели пакета в OPC. Осталось сказать несколько слов о физической реализации пакетов

    Пакеты на основе ZIP-архивов

    Как уже было сказано выше, в спецификации OPC определена только одна реализация пакетов – на основе ZIP архивов. Она достаточно проста, поэтому я приведу её обзорно:

    • все компоненты, как обычные, так и компоненты связей, хранятся в виде одного или нескольких файлов внутри архива (при этом логически они все равно адресуются как единое целое)
    • для хранения типа контента каждого компонента в архиве создается специальный файл с именем [Content_Types].xml

    Внутри файла [Content_Types].xml хранится XML следующего вида:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
    <Default Extension="png" ContentType="image/png"/>
    <Default Extension="jpeg" ContentType="image/jpeg"/>
    <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"/>
    <Override PartName="/word/styles.xml"
    ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
    </Types>

    Собственно, общая схема, я думаю, понятна и так: для описания типов используется два подхода:

    • указание типа по расширению (тэг <Default>)
    • явное указание типа для конкретного компонента (тэг <Override>)

    Как заглянуть внутрь OPC-пакета?

    Итак, надеюсь вас уже заинтриговало мое объяснение базовых принципов и вы уже жаждите приступить к практическому изучению (т.е. взять и разобрать по косточкам какой-нибудь офисный файл) Улыбка.

    Вот несколько способов как это можно сделать:

    • Прямой (“рукопашный”) способ. Т.к. физическая реализация OPC есть ни что иное, как обычный ZIP-архив, то самый простой способ его изучить – распаковать и работать как с обычной папкой (ну или воспользоваться любимым архиватором).
      • Плюс подхода – будете глубже понимать устройство.
      • Минусы:
        • Сложно отслеживать связи между компонентами (а именно они образуют структуру, а вовсе не “папки” архива)
        • Довольно муторно редактировать, если захочется экспериментов (нужно добавлять файлы для компонентов, править файлы связей да еще и не забывать про указание типа контента, если он не стандартный)

    image

    • Open XML Package Editor Power Tool for Visual Studio 2010. Расширение для Visual Studio. Умеет открывать файлы в формате OPC, показывать и редактировать их логическую структуру (добавлять/удалять/редактировать компоненты и связи). Для редактирования содержимого компонент используется редакторы самой VS, что очень удобно для экспериментов (XML редактор в VS явно не самый плохой, особенно если в наличии есть хорошие XSD-описания).
      • Минусы:
        • Так и не нашел способа создать пакет с 0 (но это редко нужно, да и обходится созданием пакета вручную).
        • Требует Visual Studio.
        • Работает только в 2010 версии VS. Увы, для более новых версий студии пакет так и не обновился, хотя почти наверняка он заработает без доработок в любой последующей. А доработать установщик пакета руками не получается, т.к. это не обычный vsix Печальная рожица

    image

    • Standalone приложение от Воутера Ван Вугта Open XML Package Explorer. По возможностям оно близко к предыдущему расширению для VS, но не требует ничего для установки, кроме .Net 3.0. У него даже есть встроенный редактор XML, правда уступающий редактору от VS. К сожалению, приложение давно не обновлялось имеет ряд неприятных ошибок… но пользоваться можно.

    image

    Пара слов в заключение

    Как видите, стандарт OPC в своей основе довольно прост и функционален. Однако остались еще ряд нераскрытых тем. В частности мы не рассмотрели ряд возможностей, заложенных в формат OPC:

    • базовые метаданные пакета (core properties)
    • иконки пакета (thumbnails)
    • цифровые подписи (digital signatures)

    Ну и, конечно, мы еще ни слова не проговорили об API для работы с OPC пакетами.

    This entry was posted in MS Office and tagged , , . Bookmark the permalink.

    5 Responses to Open Packaging Conventions #1. Базовые принципы. Компоненты и связи

    1. Pingback: Open Packaging Conventions #2. Собираем MS Word документ руками | Михаил Романов

    2. Serendipity says:

      Огромное спасибо за статью – это было то, что мне требовалось в данный момент!

    3. Sergej S says:

      в книге Воутера Ван Вугта. “OpenXML. Кратко и доступно”. Используется какой то класс Package, я не нашёл ссылку на него, может кто подскажет, что это за объект из какой он сборки? Я встретил его на странице 15, вот такая конструкция: using (Package package = Package.Open(“HelloWorld.docx”)) {…}

    Leave a comment