XPS系列:初探用于创建 XML Paper Specification 文档的 API

 

本文讨论:

XPS Document 的物理和逻辑组织

创建和保存 XPS Document

导航 XPS Document

在应用程序中使用 XPS Document 技术

什么是 XPS?


XPS Document 可以维护文档的一致外观(不管环境变量如何),方法是使用一个固定页面布局以及若干新技术,例如,Windows Presentation Foundation (WPF)、Windows Color System、Open Packaging Conventions、XPS 打印路径和 XPS Viewer。基本上,基于 XPS 的技术使作者能够更加确信下一个查看或打印其文档的人将看到与作者的意图完全相同的文档。

XPS 技术为用户提供的好处遍及整个文档工作流,该工作流从制作和查看文档开始,并持续至存储和存档。对于初学者而言,XPS Document 是由一个基于 XML 的语言描述的固定格式文档。即,文档布局是固定的,就像它是在纸上打印出来的一样。结果是,无论用户在窗口内或者在纸上查看文档,XPS Viewer 和 XPS 打印路径都能够以相同的方式为用户呈现文档。有关这一内容的更多信息,请参阅提要栏“高保真的三条通路”。

XPS Document 也支持安全功能(例如,数字签名),以便提供更强的文档安全性。当应用于文档时,这些数字签名将有助于确保作者的身份以及文档内容的有效性。

自定义资源和其他特定于应用程序的元数据也可以包括在 XPS Document 中,从而使应用程序能够创建和使用 XPS Document 包。这些包可以比其他文件格式提供更多的好处,包括针对所有内容和设计细节动态存储和存档文件的功能。XPS Document 包使用一个纯文本、基于 XML 的数据格式(而不是私有的二进制格式)描述它们的内容。而且它们包含与该文档及其特定应用程序相关的所有信息。XML Paper Specification 使用一个公开发布的规范描述这一格式,该规范由 Microsoft 通过一个免版税的许可证提供给用户和开发人员。

XPS Document 包是一个压缩的 ZIP 存档,它利用一种基于 XML 的语言使产生的文件重拾丢失掉的某些空间效率。XPS Document 包遵循开放打包约定 (Open Packaging Conventions)。而且开放打包约定中描述的 ZIP 存档格式是一个发布的开放规范,该规范包含在 XPS 许可证之下。由 Microsoft® Office System 的下一版本(代号为“Office 12”)创建的文档也遵循开放打包约定。将开放规范用于内容和存储格式,有助于确保文档的内容在将来很长一段时间内可访问。有关 XPS 和开放打包约定的详细信息和下载,请参阅 Microsoft Web 站点上的 XML Paper Specification

 

XPS Document 内幕

XPS Document API 紧密遵循 XPS Document 的逻辑组织,因此我将简单概述一下 XPS Document 的组织方式。了解文档的内部配置将更容易地了解 XPS Document API 的工作方式。

XPS Document 存储在一个称为包的文件(该文件符合开放打包约定)中,并且由一组称为部件的文档组件组成。包具有物理和逻辑组织。物理组织包含包中的文档部件和文件夹,而逻辑组织是由文档部件描述的层次结构。XML Paper Specification 将一个特定组织和命名约定应用于 XPS Document 的逻辑层。

XPS Document 的部件以一个逻辑层次结构进行组织,其中 FixedDocumentSequence 部件位于顶部。XPS Document 包可能包含多个文档,而且这些文档的顺序由 FixedDocumentSequence 部件描述。FixedDocumentSequence 部件引用 FixedDocument 部件,后者进而引用该包中每个文档的页面。

每个 FixedDocument 部件将该文档的页面作为 FixedPage 部件引用。每个 FixedPage 部件都包含该文档中页面的文本标记和布局,以及对该页中使用的图像、字体和其他自定义资源的引用。诸如图像和字体这样的资源存储在包中(但是在 FixedPage 部件之外),从而使它们能够由其他页面共享。这不仅对于字体资源特别有用,而且它对于用在多个页面上的任何图像资源(例如,水印和信头标志)也同样有用。

图 1 阐释 XPS Document 的逻辑层次结构。该示例显示一个包含两个单独文档(每个文档包含两个页面)的包的内容。该示例中的包可以是一个演示文稿,其中 FixedDocument_1 包含幻灯片,而 FixedDocument_2 包含背景信息。


图 1 XPS Document 的逻辑层次结构


 

XPS Document API 管理文档部件的物理存储,并以逻辑层次结构将它们呈现给应用程序。虽然没必要直接操作包内容,但只要确信程序正确地创建文档部件,了解包中的存储内容就是有用的。

查看 XPS Document 包物理内容最简单的方法是使用 Windows Explorer。XPS Document API 以 ZIP 存档文件格式创建 XPS Document 包。包内容可能以压缩或未压缩格式存储。无论以何种格式存储,它们都保留 ZIP 组织形式。将 XPS Document 包的文件扩展名简单更改为 ZIP 使 XPS Document 能够在文件资源管理器中打开 — 如同它是一个标准的 ZIP 存档一样。图 2 显示在 Windows Explorer 中查看时一个示例 XPS Document 的外观。


图 2 Windows Explorer 中的 SampleDocXPS 物理层次结构


 

虽然文档的不同物理组件在 Windows Explorer 窗口中作为文件和文件夹进行显示,但是该文档组件的逻辑层次结构并不明显。图 3 说明该文档组件层次结构的逻辑组织。


图 3 SampleDocXPS 逻辑层次结构


 

逻辑层次结构在包的文档部件中描述。在每个 XPS Document 中,层次结构中的第一个部件在具有预定义 name: /_rels/.rels 的唯一部件中作为目标进行描述。除了 /_rels/.rels 部件,XPS Document 中的其他部件和文件夹的名称都在文档部件的内容中列出。在该示例 XPS Document 中,/_rels/.rels 文件中的第一个文档部件是 FixedDocumentSequence.fdseq 部件,它位于文档根目录中。

FixedDocumentSequence.fdseq 部件描述 XPS Document 逻辑层次结构中的下一个级别,即 FixedDocument 部件。FixedDocumentSequence.fdseq 部件的内容如下所示:

<FixedDocumentSequence XMLns="http://schemas.microsoft.com/XPS/2005/06">
    <DocumentReference Source="Documents/FixedDocument_1.fdoc" />
</FixedDocumentSequence> 

本质上,该部件描述包的 FixedDocument 的位置。在该特定文档中,只有一个 FixedDocument,因此只有一个 DocumentReference 元素。包含多个文档的 XPS Document 包具有多个列出其显示顺序的 DocumentReference 元素。

该 XPS Document 包的 FixedDocument 的 FixedPage 部件在 Documents 文件夹中的 FixedDocument_1.fdoc 部件中列出。Fixed Document 部件如下所示:

<FixedDocument XMLns="http://schemas.microsoft.com/XPS/2005/06">
    <PageContent Source="../Pages/FixedPage_1.fpage" />
</FixedDocument> 

这也是一个非常简单的示例,其中 XPS Document 中只包含一个页面,因此该列表也非常短。FixedPage_1.fpage 部件在 Pages 文件夹中(以及 _rels 文件夹),并包含文档中页面的布局。描述该页所使用资源的部件位于 Pages/_rels 文件夹中。此外,逻辑文档部件可以在多个物理部件中交叉存取和存储。交叉存取在 XML Paper Specification 中有深入介绍。

在该示例文件的 Windows Explorer 显示中还可以看到包含该文档中所存储资源的文件夹。在该示例文档中,页面包含一张照片并使用一种字体,它们都存储为该文档的资源。该图像位于 XPS Document 包的 /Resources/Images 文件夹中,而字体位于 /Resources/Fonts 文件夹中。

 

XPS 文档编程

在使用 Windows Presentation Foundation 的应用程序中,将应用程序的数据编写为 XPS Document 很简单。Windows Presentation Foundation 应用程序使用可扩展应用程序标记语言(Extensible Application Markup Language,XAML)代码编写它们的图形内容。XPS Documents 将该代码的子集用作它们的标记。WPF 应用程序通过调用一个方法可创建一个 XPS Document,该方法将页面的应用程序 XAML 元素修整为 XPS 页面标记。

利用 System.Windows.XPS.Serialization 命名空间中的 XPS Document API,托管代码应用程序也可以创建 XPS Document 的单个部件。这对于保留对 XPS Document 构造方式的精确控制,或者对于将附加元数据添加到文档,都不失为一个更好的选项。即使应用程序不是使用 Windows Presentation Foundation 生成的,该应用程序也可以直接创建 XPS Document。这可通过为单个 XPS Document 部件创建标记完成,但这里描述的、实际创建 XPS Document 的类和方法只运行于托管代码环境中。

示例代码中的 XamlStreamToXPS 方法(如图 4 所示)可使您的 WPF 应用程序创建一个 XPS Document。XamlStreamToXPS 接受要创建的 XPS Document 的文件名作为其参数,并接受一个包含 XAML 的 Stream(描述该文档)。XamlStreamToXPS 将读取输入流并创建一个 XPS Document。

XamlStreamToXPS 方法进行的第一个操作是创建一个带有所需文件名和文件访问的空 XPSDocument 对象。由于该方法要创建一个用于将新内容写入得到的 XPS Document 包的新文档,因此它需要对文件的写访问。执行该操作也需要读访问。

XPSPackagingPolicy 对象用于描述如何将数据打包在 XPS Document 中。打包策略针对序列化管理器描述了文档包的多个方面,以及当它编写 XPS Document 时是否共享资源。该信息由在下一行中创建的序列化管理器使用。在该示例中,将使用无资源共享的默认打包策略。

转换实际上由 XPSSerializationManager 对象完成,并且在 XAML 文件完成分析后由该对象编写 XPS Document。可以配置 XPSSerializationManager 对象,以便当 XPS Document 包进行处理或者批处理时向其写入数据。当将多个页添加到该文档中时,可通过将构造函数的第二个参数设置为真,将它们作为批进行添加。使用批模式需要在分析的数据写入磁盘之前调用一个提交方法。以下代码片段说明如何将一个流数组添加到一个文档:

object [] parsedDocObject = ...; // get the objects to be saved
XPSSerializationManager serializationMgr =
    new XPSSerializationManager(packagePolicy, true);
for (int i = 0; i < numObjects; i++ )
{
    serializationMgr.SaveAsXaml(parsedDocObject[i]);
}
serializationMgr.Commit();

该 XPSSerializationManager 对象还有其他一些定义字体子集策略的配置参数。但在本例中,默认策略值和非批模式将正常工作。

该方法中下面几行代码创建分析的对象,这些对象包含来自输入流的 XAML 代码。仅当流是一个 FileStream 时才使用上下文参数,就像这些页的 XAML 来自一个外部文件一样。如果流是一个来自应用程序的内部流,则上下文参数为空。该选项使该方法能够使用文件流和来自于应用程序的内部流。

以下是一个用于从文件读 XAML 并使用 XamlStreamToXPS 方法分析它的方法:

void XamlFileToXPS(string srcXamlFile, string destXPSFile)
{
   using(Stream fileStream = File.OpenRead(srcXamlFile))
   {
      ParserContext context = new ParserContext();
      context.BaseUri = new Uri(Directory.GetCurrentDirectory() + "//");
      XamlStreamToXPS(fileStream, context, destXPSFile);
   }
}

随着 XAML 代码分析为 parsedDocObject 对象,XPSSerializationManager 现在具有了创建 XPS Document 所需的一切条件。诚然,即使调用了 SaveAsXaml 方法,最终结果也将是 XPS Document 包,因为该方法是 XPSSerializationManager 对象的一部分(显然该方法是在 XPS Document 发布之前命名的)。

XAML 文档引用的任何资源对该方法必须是可用的,以便它们可以打开并读入得到的 XPS Document。这意味着它们必须存在,并且没有被其他应用程序或用户锁定。如果有问题,序列化方法将引发异常。该方法在退出之前,通过调用文档的 Close 方法完成。


图 5 示例文档


 

当它对一个文档起作用时,要遵循该过程,请参阅图 5 中的文档。在我的应用程序中描述该文档的 XAML 代码如图 6 所示。请注意,该 XAML 代码已进行了极大地简化并使用了许多属性的默认值。在实际应用程序中使用的 XAML 代码将更明显。

将分析后的 XAML 代码传递到 XPSSerializationManager 可以创建 XPS Document。序列化例程读取来自 Windows Presentation Foundation 应用程序的 XAML 代码,并在 XPS Document 中创建相应的部件。例如,针对该示例页的 FixedPage 部件的摘录如图 7 所示。XPS Document 标记代码与 Windows Presentation Foundation 应用程序使用的 XAML 类似,但并不完全相同。

 

生成一个 XPS Document

您的应用程序能够直接创建 XPS Document 的元素,从而可以更好地控制最终输出。使用该方法,应用程序也可以将元数据资源嵌入到文档中。

编写用于使用 Windows Presentation Foundation 的应用程序将具有对 XAML 的访问,该 XAML 用于对文档页面进行格式化和布局。但是,通过添加例程以创建表示文本和图形布局的标记,那么不是为 WPF 编写的现有应用程序也可以创建 XPS Document。究竟是将这些功能添加到您的应用程序?还是针对 Windows Presentation Foundation 重写该应用程序?这取决于您具体的要求。

请注意,这里的示例仅使用 XPS 提供的一个子命令集。有关 XPS 命令的完整列表,请参阅 XML Paper Specification 中的规范。

要从头创建一个文档,该程序需要使用 System.Windows.XPS.Packaging 命名空间中的以下方法创建文档部件及其界面。然后,它可以开始使用从应用程序派生的内容填充文档部件:

// create a new document object
XPSDocument document = new
    XPSDocument(destFileName,FileAccess.ReadWrite);

// create a new FixedDocumentSequence object in the document
IXPSFixedDocumentSequenceWriter docSeqWriter = 
document.AddFixedDocumentSequence();

// create a new FixedDocument object in in the document sequence
IXPSFixedDocumentWriter docWriter = docSeqWriter.AddFixedDocument();

// add a new FixedPage to the FixedDocument
IXPSFixedPageWriter pageWriter = docWriter.AddFixedPage();

当打开文档包以进行写操作并且对所需接口进行实例化之后,应用程序可以开始将应用程序内容写入 XPS Document。请注意,这些接口是如何与从文档对象创建的 IXPSFixedDocumentSequenceWriter 接口以及从 IXPSFixedDocumentSequenceWriter 创建的 IXPSFixedDocumentWriter 接口等进行“嵌套”的。该结构反射 XPS Document 的逻辑层次结构,当您规划如何使应用程序的文档内容适应 XPS Document 时需要注意这一点。

在一个更实际的应用程序中,程序很可能以类似于如图 8 所示的伪代码方式调用这些方法。当文档对象关闭时,得到的 XPS Document 存储在一个独立的包中,该包包括了呈现和显示与存储时一样的文档所需的资源。因此,除了文本和布局,非文本资源也写入该文档。

 

存储文本和资源

XPS Document 中每页的文本存储在 FixedPage 对象中,这些对象包含描述每页布局的标记。您的应用程序需要生成用于应用程序文档的这种标记。此操作的正确方法取决于布局类型,而且需要格式化应用程序。

例如,以我的示例文档为例。IXPSFixedPageWriter 接口将该页的标记作为一个未格式化的原始字符串写入文档。对于该方法,应用程序需要在可以传递给 XMLWriter 对象的字符串变量中使用这一文本。该示例中显示的代码使用了一个名为 pageContents 的、包含 FixedPage 描述字符串。以下代码会添加标记以便在 XPS Document 中创建一个页:

string pageContents = ...; // XPS formatted description of page content
XMLWriter XMLWriter = pageWriter.XMLWriter;
XMLWriter.WriteRaw(pageContents);
pageWriter.Commit();
XMLWriter.Close();

在该示例代码中,文档由一个包含页面标记以及图像和字体资源的 XML 文件描述。在 XML 文件的一个元素中,以 CDATA 作为该页面标记的原始字符串。它描述文档并将其赋给 pageContents 变量。

第一行获取该页的 XML 编写器对象。然后,标记字符串写入 FixedPage 对象,将内容提交给文档,关闭编写器。

文档中最后的路径元素(如以下代码所示)描述了一个有色矩形以及所创建的一个填充有图像的矩形。第二个路径元素中引用的图像位于 Images 文件夹中,并需要保存为文档中的资源以便完整呈现该页面。代码如下所示:

<Path 
    Fill="#FFFFA500" 
    RenderTransform="1, 0, 0, 1, 60, 120" 
    Data="F0 M 0, 0 L 150, 0 150, 150 0, 150Z" />
<Path 
    Fill="{StaticResource b0}" 
    RenderTransform="1, 0, 0, 1, 40, 140" 
    Data="F0 M 0, 0 L 150, 0 150, 150 0, 150Z" />

文档中使用的所有字体必须以类似方式嵌入在文档中。此外,自定义资源可以由应用程序创建,而且它们可以写入 XPS Document 包。

在该示例中,页面上所使用图像的文件名和页面内容一起存储在示例应用程序文档中。应用程序在创建每个页面时将这些图像添加到 XPS Document 中,使用的方法如图 9 所示。该方法接收来自文档的页面编写器接口,它作为一个参数与该图像的文件名一起传递。在该方法内部,一个新图像资源添加到 FixedPage 部件中,并且从该对象获取图像文件流。然后使用本地流复制方法打开并复制图像的文件流,如图 10 所示。然后,数据提交到文档包,本地流对象关闭,并且返回方法。

该文档中使用的字体以类似方式保存到 XPS Document 包中。图 11 中的方法将一个字体存储到 XPS Document 的一个 FixedPage 部件中。

页面编写器接口的 AddFont 方法使您能够在将字体存储在文档中时模糊字体,以便获取安全性和智能属性保护。该方法旨在防止临时用户提取该字体,并在其系统上进行安装。虽然人们并不认为它是一种强加密,但它可以有效防止用户使用操作系统的 ZIP 存档功能提取字体文件,并在其系统上安装。要使字体模糊,将 AddFont 方法中的参数设置为真。在我的示例中,该参数设置为假,字体将以不模糊的方式存储。

当所有文档部件添加到该文档时,以下代码刷新所有缓冲区并关闭这些对象:

pageWriter.Commit();
docWriter.Commit();
docSeqWriter.Commit();
document.Close();
 
 

对一个 XPS Document 进行签名

 


数字签名可以应用于 XPS Document,类似于以物理方式对文档进行签

名。但是,在某些方面,这比使用笔墨的物理签名更好。除了具有可

验证的身份外,XPS Document 中的数字签名还指示应用签名的时间和

日期,以及应用签名之后该文档是否经过更改。XPS Document 也可以

具有多个数字签名。

将一个数字签名附加到一个 XPS Document 非常简单,如以下代码所示:

void SignDocument(string srcXPSDocument, string certFilename)
{
    XPSDocument document = 
        new XPSDocument(srcXPSDocument, FileAccess.ReadWrite);

    X509Certificate certificate = 
        X509Certificate.CreateFromCertFile(certFilename);
    document.SignDigitally(certificate, true, 
        XPSDigSigPartAlteringRestrictions.None);

    document.Close();
}

XPS Document 的文件名和数字证书的文件名作为参数传递,并且打开

XPS Document 以进行读写访问。然后从证书文件创建一个证书对象。

通过将签名添加到文档的数字签名集合,调用 XPSDocument 对象的

SignDigitally 方法将数字签名应用到文档。SignDigitally 方法的参数

包括一个证书对象、一个指示证书应该嵌入在文档中的标志,以及一

个指示不应禁止更改数字签名核心组件的标志。使用 XPSDigSigPartAlteringRestrictions

的标志可以限制对核心文档组件(例如,Core Metadata、Annotations 或

嵌入在文档中的证书的 Signature Origin 属性)进行更改。

至时,已经对文档进行了签名,但是看看其他哪些数字签名也应用到

了该文档可能是有好处的。您可以通过在 XPS Viewer 中查看该文档,

从而查看 XPS Document 中的数字签名。或者,您可以使用以下示例方法

列出已经应用到文档的所有数字签名:

void ListSignatures(string srcXPSDocument)
{
    XPSDocument document = 
        new XPSDocument(srcXPSDocument, FileAccess.Read);
    foreach (XPSDigitalSignature digitalSignature in document.Signatures)
    {
        Console.WriteLine(digitalSignature.SigningTime.ToString() + " " +
            digitalSignature.SignerCertificate.Issuer + " " +
            digitalSignature.SignerCertificate.Subject);
    }
    document.Close();
}

在该方法中,XPS Document 的文件名传入了参数列表。XPS Document 被打开

,而且该方法迭代通过数字签名集合,从而写入应用该签名的日期和

时间,以及与用于该签名的证书源有关的一些信息。在迭代通过签名

集合后,该文档对象关闭并且返回该功能。

 

 

 

贯穿于一个 XPS Document 进行导航


 

针对 Windows Presentation Foundation 编写的应用程序可以使用我已经

描述的序列化方法将其文档作为 XPS Document 保存。但是,如果应用

程序有附加的自定义资源,则它们也调用显式 XPS Document 方法来将

这些资源添加到文档。

至此,我已经着重介绍了如何创建一个 XPS Document。但有时也可能

需要访问 XPS Document 以读取其内容。这里还应该注意的是,XPS

Document 是一个固定文档,不应该直接进行修改。因此,XPS Document

API 被分成一组写接口和方法,以及一组单独的读接口和方法。要修

改 XPS Document,应用程序必须读入当前文档,修改其内容,并从应

用程序中将修改后的版本写为一个新文档。

正如前面显示的,Windows Explorer 可用于查看并提取 XPS Document 的

内容,而 XPS Viewer 可用于以预期形式查看文档。但是,通过使用

XPS Document API,其他程序可用于纵贯 XPS Document 进行导航。

图 12 所示的方法显示如何纵贯于一个 XPS Document 进行导航。在该

示例中,代码迭代通过所有不同的文档部件,以便列出图像资源引用

,从而将计数作为整数返回。ListImagesInXPSDoc 方法接受 XPS

Document 的文件名作为参数,列出对控制台的图像引用,并返回该

文档中图像资源引用的计数。虽然该示例本身并没有什么实际用途

,但它演示了 XPS Document API 的嵌套、分层组织,以及 XPS Document

本身。使用该主干做为起始点,可以创建更多有趣的方法。

因此,我们将详细看一下这里的情况。该文档使用读访问进行打开,

而且 IXPSFixedDocumentSequenceReader 接口从文档对象获取以便开始纵

贯于 XPS Document 的部件进行导航。然后,代码迭代通过该文档中所

有固定文档部件的所有固定页面,并对文档中的图像资源引用进行计数。

固定文档序列中的每个固定文档都要获取一个 IXPSFixedDocumentReader

接口。使用固定文档读取器,代码随后会迭代通过固定文档,从而为每

个页面获取一个 IXPSFixedPageReader 接口。一个固定文档的图像和字体

资源由使用它们的页面引用。使用页面读取器,代码迭代通过页面中的

图像资源引用,从而增加计数并编写所找到的每个图像引用的统一资源

定位符(Uniform Resource Identifier,URI)。该方法通过关闭文档并返回

文档中的图像资源引用数结束。本例使用读方法的嵌套与前面使用的写

函数一样。

虽然该示例说明了一个程序如何纵贯于 XPS Document 中文档部件的分层

结构进行导航,但请记住,这些部件的物理存储层次结构不一定反射用

于访问它们的逻辑层次结构。因此,用于在文档中查找单个部件的方法

会因其所需的访问方式的不同而异。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值