Отправляет email-рассылки с помощью сервиса Sendsay

Всё о документообороте

  Все выпуски  

Запись блога "Open Packaging Conventions #3. Немного об API" от Михаил Романов


Все о документообороте

Сайт рассылки
 



Запись блога "Open Packaging Conventions #3. Немного об API" от Михаил Романов
2014-11-26 17:07 Михаил Романов

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

На сегодня мне известны две такие библиотеки (обе представлены Microsoft)

●   Packaging API – нативная COM-based библиотека. По всей видимости, основная её задача исходно была – служить базисом для XPS Document API

●   System.IO.Packaging – managed библиотека, входящая в состав .Net начиная с версии 3.0.

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

Базовые классы пространства System.IO.Packaging

Практически все задачи манипулирования OPC-пакетами решаются следующими 4-я классами:

●   Package – через него идет открытие/создание пакета, создание/удаление/получение компонент и отношений уровня пакета.

●   PackagePart – это компонент. Через этот класс можно узнать тип контента, прочесть содержимое компонента, а также получить список его отношений

●   PackageRelationship – отношение. Этот класс содержит только тип отношения и Uri цели.

●   PackUriHelper – этот класс содержит набор вспомогательных методов для манипулирования Uri отношений. Он позволяет, например, легко решить такие задачи:

-       Зная имя исходного компонента и относительную ссылку в одном из отношений, получить имя конечного компонента.

-       Или наоборот, зная имена 2-х компонент, получит относительную ссылку от одного к другому.

Да, все классы объявлены в сборке WindowsBase.

Создание пакетов

Думаю, особо расписывать что-то здесь нет смысла, достаточно привести пример. В качестве такового мы возьмем созданный в предыдущей статье документ с котом Улыбка.

Вот так выглядит программный вариант его создания:

public void CreateWordDocumentWithTextAndImage()
{
    // Part Names
    const string mainDocumentPartName = "/main.xml";
    const string imagePartName = "/cat.jpeg";
 
    // Content types
    const string mainDocumentPartContentType =
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
    const string imagePartContentType =
        System.Net.Mime.MediaTypeNames.Image.Jpeg;
 
    // Relationsip Types
    const string mainDocumentPartRelationshipType =
        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
    const string imagePartRelationshipType =
        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
 
    // Image relationship Id
    const string imageRelationshipId = "rId1";
 
    // Create new Word document
    using (var document = Package.Open("result.docx", System.IO.FileMode.Create))
    {
        // Add main part
        var mainPartUri = new Uri(mainDocumentPartName, UriKind.Relative);
        var mainPart = document.CreatePart(mainPartUri, mainDocumentPartContentType);

        using (var mainPartStream = mainPart.GetStream())
        {
            mainPartStream.Write(Properties.Resources.main, 0, Properties.Resources.main.Length);
            mainPartStream.Close();
        }

        document.CreateRelationship(mainPartUri, TargetMode.Internal, mainDocumentPartRelationshipType);
 
        // Add image
        var imagePartUri = new Uri(imagePartName, UriKind.Relative);
        var imagePart = document.CreatePart(imagePartUri, imagePartContentType);

        using (var imagePartStream = imagePart.GetStream())
        {
            imagePartStream.Write(Properties.Resources.cat, 0, Properties.Resources.cat.Length);
            imagePartStream.Close();
        }

        // Create relative uri for image
        var relativeUri = PackUriHelper.GetRelativeUri(mainPartUri, imagePartUri);
        mainPart.CreateRelationship(relativeUri, TargetMode.Internal, imagePartRelationshipType,

                 imageRelationshipId);
 
        document.Close();
    }
 
    System.Diagnostics.Process.Start("result.docx");
}

Большая часть примера – это объявление всевозможных констант: типов контента, типов отношений, имен компонент, …

В принципе, все довольно прозрачно. Единственное, на что хочется обратить внимание – две строки добавления отношения от главной части документа к картинке. Вот они:

var relativeUri = PackUriHelper.GetRelativeUri(mainPartUri, imagePartUri);
mainPart.CreateRelationship(relativeUri, TargetMode.Internal, imagePartRelationshipType,
            imageRelationshipId);

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

Вторая строка интересна тем, что при добавлении связи между главной частью документа и картинкой мы явно задаем Id отношения. Этот параметр не обязателен – id в любом случае будет присвоено. Но в нашем случае автоматическая генерация Id не удобна тем, что на отношение есть ссылка из главной части документа, а значит ее пришлось бы менять.

Чтение пакета

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

Вот этот пример:

public void ReadWordDocumentImages()
{
    // Relationsip Types
    const string mainDocumentPartRelationshipType =
        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
    const string imagePartRelationshipType =
        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
 
    using (var document = Package.Open("result.docx", System.IO.FileMode.Open))
    {
        var mainPartRelationship = document.GetRelationshipsByType(mainDocumentPartRelationshipType).Single();
       
        var mainPartName = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), mainPartRelationship.TargetUri);
        var mainPart = document.GetPart(mainPartName);
 
        foreach (var imageRelationship in mainPart.GetRelationshipsByType(imagePartRelationshipType))
        {
            var imagePartName = PackUriHelper.ResolvePartUri(mainPartName, imageRelationship.TargetUri);
            var imagePart = document.GetPart(imagePartName);
 
            var fileName = Path.GetFileName(imagePartName.OriginalString);
 
            using (var file = new FileStream(fileName, FileMode.Create))
            {
                var imageStream = imagePart.GetStream();
                imageStream.CopyTo(file);
 
                file.Close();
                imageStream.Close();
            }
        }
    }

}

Из интересного я бы отметил 2 вещи:

●   Метод GetRelationshipsByType(), который присутствует, как у пакета, так и у компонента и возвращает все связи пакета/компонента требуемого типа.

●   Для того, чтобы получить имя компонента из пути, который хранится в отношении используется метод ResolvePartUri(). Причем первый параметр у него

-       либо имя компонента от которого выходит отношение;

-       либо “/” – если мы смотрим на отношение уровня пакета.

Ну и наконец, смена содержимого

Редактирование содержимого компонентов

Пример еще проще предыдущих:

public void ChangeWordDocumentMainPart()
{
    const string mainDocumentPartRelationshipType =
        "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
 
    using (var document = Package.Open("result.docx", System.IO.FileMode.Open))
    {
        var mainPartRelationship = document.GetRelationshipsByType(mainDocumentPartRelationshipType).Single();

        var mainPartName = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), mainPartRelationship.TargetUri);
        var mainPart = document.GetPart(mainPartName);
 
        using (var mainPartStream = mainPart.GetStream())
        {
            mainPartStream.SetLength(0);
            mainPartStream.Write(Properties.Resources.main2, 0, Properties.Resources.main2.Length);

            mainPartStream.Close();
        }

        document.Close();
    }

}

Из важного, только 1 строчка:

mainPartStream.SetLength(0);

- которая отбрасывает предыдущее содержимое.

Почему это важно? По умолчанию поток компонента производит запись поверх уже имеющегося контента. И если предыдущее содержимое было больше, чем новое, то в хвосте компонента у вас окажется всякий мусор. Как правило, с точки зрения потребителя, такой пакет будет безнадежно испорченным.

В заключение

Если вы внимательно посмотрели на пространство System.IO.Packaging, то уже обнаружили там много других интересных возможностей. О них мы обязательно поговорим, но чуть позже, когда узнаем немного о дополнительных возможностях стандарта Open Package Convention.

Все приведенные в статье примеры кода можно найти все там же на https://msosamples.codeplex.com в папке проекта PackagingAPI.


 



 
 
С пожеланиями успехов,
Михаил Кузьмин
 

В избранное