David Meltzer and Andrey Shur
Open Packaging Conventions
Microsoft Office 2007
Microsoft Windows Vista
Microsoft .NET Framework
Summary: This article provides an overview of the addressing model used in the Open Packaging Conventions, including how packages and their parts are addressed, how relative references in package parts are resolved, and how applications can make use of the package addressing model with the help of the .NET Framework and classes. (16 printed pages)
As part of the design of Office 2007 and Windows Vista, Microsoft introduced the Open Packaging Conventions. These conventions describe how content can be organized in a "package." Some examples of content include a document, a collection of media, and an application library. Packages aggregate all of the components of the content into a single object.
A word processing application may use packages, for example, to store the pages of a document, the fonts needed, and the images, charts, and annotations on the pages. A document viewing or management application may display only portions of the content in a package. Applications may use a package-based format, such as the XML Paper Specification (XPS), to send fixed-layout content and resources to a printer.
This article provides an overview of the addressing model used in the Open Packaging Conventions, including how packages and their parts are addressed, and how relative references in package parts are resolved. This article also discusses how applications can make use of the package addressing model with the help of the .NET Framework and .NET Framework 3.0 classes. This article is written primarily for developers of applications that will handle, produce, or consume packages.
For full normative information needed in order to implement the package addressing model, see the specification for the Open Packaging Conventions. The .NET Framework 3.0 and .NET SDKs provide more information about the classes and methods discussed.
The material presented in this overview assumes a basic knowledge of the URI specification. The following terms are used according to RFC 3986: URI, URI reference, scheme component, authority component, path component, path-absolute, and relative reference. The term URI always denotes the absolute form of a URI—the scheme component is present, and all other components match the scheme-specific grammar. The term addressable, as used here, indicates that a URI exists that identifies a resource.
The Open Packaging Conventions define a logical model for organizing the content and resources of a package, and provide a mapping from this logical model to a physical representation, based on ZIP, XML, and other openly available technologies.
The logical packaging model described by the Open Packaging Conventions defines a package abstraction that holds a collection of parts. Parts can have relationships to each other, and the package can have relationships to parts. The packaging model specifies how the parts in a package are named, referenced, and related. The addressing model defined by the Conventions is the foundation for being able to reference and obtain part resources in a package.
A package instance as a whole is an addressable resource, as is each part held in the package instance.
Applications can use a URI with any scheme (for example, "http:", "ftp:", and so on) to address a package as a unit, acquiring the stream of bits comprising the whole package.
Applications can also address a package by using the "pack:" URI scheme defined by the Open Packaging Conventions. This scheme specifies that the complete URI identifying the package is held in the authority component of a "pack:" URI in an encoded form.
Example: Addressing a Package
The package resource is addressed by "http:" and "pack:" URIs (respectively) in the following examples:
The MIME type of the acquired package resource indicates the file format of the package—for example, it can be XPS Document format (.xps), Office Open XML format (.docx), or some other format that conforms to the Open Packaging Conventions.
For various reasons (such as improving performance), applications can use a URI specific to their domain as the authority component of a "pack:" URI. Such a URI is resolvable only in the context of a given application. The programming technique used for such application-specific URIs is described later, in "The PackageStore."
Parts in a package are addressed using "pack:" URIs. The structure of a "pack:" URI that addresses a part is as follows: pack://<authority><path>
Example: Addressing Parts
pack://http%3a,,www.site.com,windows,p1.xps/fonts/arial.ttf addresses the part named /fonts/arial.ttf, in the package addressed by http://www.site.com/windows/p1.xps.
The authority component holds the encoded URI of the entire package; the path component holds the name of the part in that package. Part names conform to the grammar defined for the path-absolute URI component (, section 3.3), with some additional restrictions (, section 184.108.40.206).
Example: Part Names
Part names are case-insensitive ASCII strings. All parts in a package have unique names.
Some applications using the Open Packaging Conventions can reference a part by using a non-"pack:" URI of a package unit with format-specific fragment identifiers.
Example: Using a Non-"pack:" URI to Reference a Part
The URI http://www.site.com/windows/p1.xps#15 is used to reference the part that represents page 15 in the p1.xps document (, sections 9.2.2 and 9.2.3).
Although it is valid and, for certain scenarios, useful to have non-"pack:" URIs refer to parts, such URIs cannot be used as base URIs to resolve relative references in the part content.
A part is the most granular addressable resource within a package. However, applications might need to refer to entries within the content of a part. For certain content types, entries can be referenced by means of fragment identifiers (, section 3.5). The Open Packaging Conventions do not specify fragment identifiers. Applications using fragment identifiers are responsible for processing them properly.
Example: Referencing Entries Within Parts
pack://http%3a,,www.site.com,windows,p1.xps/pages/page1.xaml#//[@Id="A012"] refers to a set of XML nodes within the content of the part named /pages/page1.xaml, and having the Id attribute value of A012.
Packages can be nested. A part in a package can hold content of any type, including a whole package. The parts of a nested package can be addressed by a "pack:" URI, with an authority component that indicates the part holding this nested package (, Appendix D.3).
Example: Addressing Parts in Nested Packages
A package located at http://www.site.com/package contains a part named /nested-package,addressed by the "pack:" URI pack://http%3a,,www.site.com,package/nested-package.
The part addressed by the preceding URI contains a package unit, which contains a part named /p1.xaml.
The address of this part in the nested package is as follows:
Parts having certain types of content, such as XML, can contain URI references. URI references can be URIs or relative references. URI references can be represented in the content by Unicode strings. Applications resolving such URI references must convert the strings to a URI form (, section 3.1).
A relative reference is a URI that is expressed relative to the base URI of the content containing the reference. The default base URI for part content is the "pack:" URI addressing the part.
Example: Base URI
The base URI for a part named /pages/page1.xaml in a package addressed by http://www.site.com/windows/p1.xps is as follows:
If an alternate base URI is needed in order to resolve relative references in the entries of the part content, an application must explicitly specify the alternate. Particular content types expose certain ways of specifying the alternate base URI. For example, XML uses the xml:base attribute, HTML uses the <base> element, and the Open Packaging Conventions use the TargetMode attribute for Relationship elements.
Using the "pack:" URI of a part as the base URI for a relative reference guarantees that the referenced resource will be a part in the same package (, section 5.2), unless the relative reference is in the rarely used network-path form (that is, a relative reference beginning with "//").
Example: Resolving a Relative Reference
The relative reference ../../page2.xaml within the part addressed as pack://http%3a,,www.site.com,windows,p1.xps/pages/page1.xaml is resolved to pack://http%3a,,www.site.com,windows,p1.xps/page2.xaml, addressing the part named /page2.xaml.
Package producers may use part names as a valid form of relative references. However, when using part names as relative references, producers should consider whether referenced parts can be addressed also as extracted resources outside of the package. Once parts have been extracted from a package, part names used as relative references might not be resolved as expected. The mandatory leading slash for part names, specified by part-name grammar, implies that such relative references are resolved from the root of the current authority.
Example: Addressing Extracted Resources
In the content of a part named /doc1/pages/page1.xaml, the relative reference /page2.xaml addresses the part named /page2.xaml, and the relative reference ./page3.xaml addresses the part named /doc1/pages/page3.xaml.
After parts /doc1/pages/page1.xaml, /doc1/pages/page3.xaml, and /part2.xaml are extracted from the package to files named file:///c:/mydocs/doc1/pages/page1.xaml, file:///c:/mydocs/doc1/pages/page3.xaml, and file:///c:/mydocs/page2.xaml (respectively), the relative reference ./page3.xaml addresses the file file:///c:/mydocs/doc1/pages/page3.xaml, which is expected; however, the relative reference /page2.xaml now addresses the file named file:///page2.xaml.
The Open Packaging Conventions define connections between source and target parts in a package as relationships (, section 1.3).
Relationships are grouped and stored based on their sources. A relationships part holds relationships that originate at the same source part. Each relationship is described by an XML element within the content of this relationships part. The relationships part is uniquely associated with this source part (and vice versa) by using a defined naming convention for the relationships part.
The default base URI for the URIs specified in each Relationship element is the "pack:" URI of the source part (, section 1.3.5). The TargetMode attribute of a Relationship element indicates the base URI for the specified relationship.
Example: Relationship Element
The element in the relationships part that defines a relationship from a source part named /pages/page1.xaml to the target part /fonts/arial.ttf within the same package may look like the following:
TargetMode="Internal" Id="A123" Target="../fonts/arial.ttf"/>
The Internal value of the TargetMode attribute indicates that the base URI for the Relationship element is the default for the relationships part content—and the same as the "pack:" URI of the relationships source part. In the preceding example, the base URI for the Relationship element is the "pack:" URI of the /pages/page1.xaml part.
Relationships can also target external resources relative to the location of the whole package.
Example: Relationship to External Target
For a package located at file:///c:/office12/sample.docx, the XML element
defines the relationship targeting the file file:///c:/office12/icon.jpg.
The External value of the TargetMode attribute specifies that the relationship must target a resource outside of the package. If the Target attribute holds a relative reference, a base URI is required. The base URI for this relationship element must be the URI of the whole package.
Some package-based formats might avoid using URI references in the content, delegating references to relationships. This delegation technique is based on using unique Id values on each Relationship element to map relative references in part content to corresponding relationships.
Example: Mapping Relative References in Part Content to Relationships
A package located at file:///c:/office12/sample.docx has a part named /word/document.xml that holds
<a:blip relEmbed="rId6" relLink="" w="0" h="0"/>.
The relationships part attached to this part holds the element
This links the element to the part named /word/media/image1.jpeg.
The benefit of this approach is that an application can identify and maintain all references within a package, without looking at the content in the parts.
However, if references are delegated to relationships, package parts extracted to loose files might not work properly. For relationship targets to work after extraction, a consuming application will require special knowledge about relationships, Open Packaging Conventions for naming relationship parts, and the definition of base URIs for relationship files.
Applications producing and/or consuming packages will work with package and part addresses, and resolve relative references within the content of parts. .NET Framework 3.0, which delivers the set of next-generation managed APIs provided by Microsoft, includes classes that support the addressing model of the Open Packaging Conventions. These classes enable applications to compose and parse references, and to obtain package resources. The PackUriHelper class is used to facilitate the handling of "pack:" URIs. The PackWebRequest class is used to obtain resources addressed using "pack:" URIs.
This section illustrates the functions that these services perform in composing, parsing, and resolving references.
The .NET Framework version 3.0 (or greater) must be installed in order to use the packaging services classes. The classes can be found in the System.IO.Packaging namespace.
Before using System.Uri for operations where "pack:" URIs are involved, the "pack:" URI scheme must be registered for the application domain. The easiest way to register the "pack:" URI scheme is by calling any method of the PackUriHelper class. The scheme can also be registered, without calling the helper class, by using the UriParser.Register method, as shown in the following example. However, using this method requires security permissions.
Example: Registering the "pack:" URI Scheme
//To register the "pack:" URI scheme without calling PackUriHelper UriParser.Register(new GenericUriParser (GenericUriParserOptions.GenericAuthority), "pack", -1);
When consuming a package, the entire package, or one part at a time, can be obtained as an object. In either case, the PackUriHelper.Create method can be used to create the "pack:" URI of the package or part resource. This "pack:" URI is then passed to the PackWebRequest method in order to obtain the resource. PackWebRequest is discussed in more detail in the next section, "Obtaining Package Resources Using PackWebRequest."
A common example of how the PackUriHelper and PackWebRequest classes can be used to support the consumption of a package is detailed in the following steps. If the package URI and part URI are known, an application can:
The "pack:" URI of the package can be created by using the PackUriHelper.Create method.
//Given the URI for a package Uri packageUri = new Uri("http://www.newsdocs.com /local/today.container"); //Use the Create method to create a "pack:" URI from a non-"pack:" URI Uri packUri = PackUriHelper.Create(packageUri); //The resulting packUri value is //"pack://http%3a,,www.newsdocs.com,local,today.container/"
The created "pack:" URI is passed to PackWebRequest in order to obtain the package resource.
The "pack:" URI of the part can be created by using the PackUriHelper.Create method.
//Given the URI for package Uri packageUri = new Uri("http://www.newsdocs.com /local/today.container"); //Given the URI for a part Uri partUri = new Uri("/sports.xml", UriKind.Relative); //Use the PackUriHelper.Create method to create a "pack:" URI Uri packUri = PackUriHelper.Create (packageUri, partUri); //The resulting packUri value is //"pack://http%3a,,www.newsdocs.com,local,today.container/sports.xml"
The created "pack:" URI is passed to PackWebRequest in order to obtain the part resource.
When processing the content of a part, relative references might be found that refer to other parts or resources. Resolving these references is a first step in obtaining the referenced resources.
A relative reference in the content of a part is resolved against the base URI of a part to the "pack:" URI of the target part. The "pack:" URI of the target part is passed to PackWebRequest in order to obtain the part resource from the package. The name of a target part, derived from the "pack:" URI of the target part, can also be used to obtain a targeted part, by passing the part name to the Package.GetPart method.
When resolving relative references to target parts, there are several paths that the resolution can take, depending on what information is available at the start, and whether the package is open (or can be opened). Two of these paths are as follows:
//Given the "pack:" URI for the part "/files/fixeddoc.xaml" //packUri = // "pack://http%3a,,www.newsdocs.com,local,today.container // /files/fixeddoc.xaml" //The part "/files/fixeddoc.xaml" contains //the relative reference "../images/1.jpg" Uri relativeReference = new Uri("../images/1.jpg", UriKind.Relative); //Use System.Uri to directly obtain the absolute target URI Uri targetPackUri = new Uri(packUri, relativeReference); //The value of the resulting targetPackUri is //"pack://http%3a,,www.newsdocs.com,local,today.container // /images/1.jpg" //Now another PackWebRequest can be made using //this targetPackUri value.
//Given "package" as the current instance of the Package class. //Given the relative reference = "../../images/1.jpg" Uri relativeReference = new Uri("../../images/1.jpg", UriKind.Relative); //Given the URI of the part that contains the relative reference Uri partUri = new Uri("/files/fixeddoc.xaml"); //Use PackUriHelper.ResolvePartUri to obtain the resolved part URI //of the target based on the part URI above and the relative //reference in that part Uri targetPartUri = PackUriHelper.ResolvePartUri (partUri, relativeReference); //The resulting targetPartUri value is "fixeddoc.xaml" //Now use the package.GetPart method to obtain the target part PackagePart packagePart = package.GetPart(targetPartUri);
Once a package is open, the Package class is useful for adding parts to packages, getting parts, and deleting parts. Methods of the Package class, such as Package.AddPart, Package.DeletePart, and Package.GetPart, take a part URI as a parameter. The PackUriHelper.CreatePartUri method can be used to create a valid part name from a reference that is relative to the base URI of the package.
//Given a URI Uri partUri = PackUriHelper.CreatePartUri (new Uri "files/a.xaml",UriKind.Relative)) //The URI will be checked for validity, and a leading slash //will be added. The resulting partUri value is "/files/a.xaml"
An authoring application might need to derive a relative reference that, when placed in the content of a source part, points to a target part. The GetRelativeUri method serves this purpose.
Example: GetRelativeUri Example
//Given the URI of the source part Uri sourcePartUri = new Uri("/tiles/pages/a.xaml", UriKind.Relative); //Given the URI of the target part Uri targetPartUri = new Uri("/images/event1/1.jpg", UriKind.Relative); //Use PackUriHelper.GetRelativeUri to generate the relative reference //that will be placed in the content of the source part. Uri relativeReference = PackUriHelper.GetRelativeUri (sourcePartUri, targetPartUri); //The resulting relativeReference value is "../../images/event1/1.jpg"
Applications can obtain packages and parts resources by using PackWebRequest, a class derived from System.Net.WebRequest. PackWebRequest returns a resource addressed by a given "pack:" URI.
In general, initiating a PackWebRequest for a "pack:" URI consists of the following steps:
Otherwise, if the path component is not empty:
PackWebResponse.GetStream returns a stream of bits representing either the whole package (a package stream) or a single part in a package (a part stream).
Unlike most WebResponse streams, a package stream is seekable using Stream.Seek. A package stream can be used to create a package object.
When operating with package resource over an "http:" protocol, PackWebRequest supports progressive loading of parts: that is, the ability to obtain part resources in an arbitrary order, without loading all the data in the package up until the part data.
PackWebRequest only provides facilities for resource consumption. It cannot be used to post or send data to a server.
PackWebRequest currently does not support asynchronous operations (such as BeginGetResponse) nor does PackWebRequest support nested packages (described earlier, in "Addressing Parts Within a Package").
When loading packages that have parts containing numerous references to other parts, response time for resource requests can be improved, and network traffic can be reduced, by using the PackageStore. The PackageStore is an application-local dictionary of references to open packages. Each package registered in the PackageStore is identified by a key URI value.
The PackageStore enables PackWebRequest to obtain resources as needed from a package, without making a server request each time another resource is needed from that package.
The PackageStore is not changed automatically as a result of a call to PackWebRequest—it must be explicitly modified. There are two public methods used to add or remove references to open packages in the PackageStore: Package.AddPackage and Package.RemovePackage.
The default cache policy (CacheIfAvailable) set on PackWebRequest causes the class to attempt to use the PackageStore to obtain the package. PackWebRequest can be forced to ignore the contents of the PackageStore when obtaining resources, by setting the cache policy to BypassCache, as described in the next section, "Cache Policies."
When obtaining the bits of a package or part according to the default cache policy, PackWebRequest first checks the PackageStore to see whether there is a package registered with a key that is equal to the authority component of the "pack:" URI. If the PackageStore does not contain the package for the key, PackWebRequest will create an inner WebRequest to download the resource using the authority component of the "pack:" URI.
Cache policies define the rules used for determining whether a resource request can be filled by using a cached copy of the resource.
When using PackWebRequest, there are two levels of cache policy that can be explicitly set. A cache policy can be set for the PackWebRequest itself, controlling interaction with the PackageStore. And, a cache policy can also be set for the cache controlled by an inner WebRequest. The inner WebRequest can be accessed by PackWebRequest, using PackWebRequest.GetInternalRequest().
The cache policy set on the inner WebRequest will have no effect if the PackWebRequest.CachePolicy is set to CacheOnly, causing the package resource to be obtained from the PackageStore.
PackWebRequest.CachePolicy supports a subset of policies, as listed below, due to the specific capabilities of the PackageStore.
Table 1. PackWebRequest.CachePolicy policies
|BypassCache||Ignore PackageStore entries with a matching URI.|
|CacheOnly||Only consider PackageStore entries (never create a WebRequest to query the server for data).|
|CacheIfAvailable||Inspect the PackageStore, and use any package there if one is found; otherwise, make a network request to the resource indicated by the package URI (the inner URI of the "pack:" URI). This is the default.|
For PackWebRequest, setting all other CachePolicy values results in a WebException.
PackWebRequest can progressively load a package part when the package is accessed over the "http:" protocol. Progressive loading allows applications to access part resources before the entire package is locally available. The progressive loading feature of PackWebRequest is automatic: the calling application experiences the improved performance without intervening.
Progressive loading is based on making "byte-range requests" for resources, as defined in the "http:" 1.1 protocol. The ZIP file format, used to store packages in physical form, benefits from this mechanism, because the ZIP archive format keeps important information in a "central directory" at the physical end of the file.
After requesting an entire package by using PackWebRequest, the service begins returning a stream upon which a caller can seek. When a package is opened on the stream provided by PackWebRequest, the caller can obtain parts more quickly than when making direct requests using, for example, the "http:" protocol.
When managing a collection of parts that was obtained using the Package.GetParts method, relationship parts can be identified so that they can be handled separately from other parts. The PackUriHelper.IsRelationshipPartUri is used to identify whether a part is a relationship part.
//Given the URI for a part Uri partUri = new Uri("/_rels/sports.rels", UriKind.Relative); bool isRelationshipPart = PackUriHelper.IsRelationshipPartUri(PartUri); //The resulting isRelationshipPart value is "TRUE"
Two other PackUriHelper methods are available for working with relationship part names. PackUriHelper.GetRelationshipPartUri returns a relationship part name given a source part name. PackUriHelper.GetSourcePartUriFromRelationshipPartUri returns the source part name for a given relationship part name.
An application that uses a cache for storing parts when producing or consuming a package might need to perform checks for equivalent part names. The PackUriHelper.ComparePartUri method checks the equivalence of part names.
//Given two part names in the same package //firstPartName = "/a.xaml" //secondPartName = "/A.xaml" //Use PackUriHelper.ComparePartUri to identify if the names //are equivalent. Bool isSamePartName = PackUriHelper.ComparePartUri (firstPartName, secondPartName); //The resulting isSamePartName value is "TRUE"
To determine the lexical equivalence of two "pack:" URIs, use the PackUriHelper.ComparePackUri method.
//Given two "pack:" URIs //firstPackUri = // "PACK://HTTP%3A,,WWW.NEWSDOCS.COM,local,today.container // /FILES/FIXEDDOC.XAML" //secondPackUri = // "pack://http%3a,,www.newsdocs.com,local,today.container // /files/fixeddoc.xaml" //Use PackUriHelper.ComparePackUri to identify if the same resource //is targeted. bool isSameResource = PackUriHelper.ComparePackUri (firstPackUri, secondPackUri); //The resulting isSameResource value is "TRUE"
To extract the component package URI and part URI from a "pack:" URI, use the methods PackUriHelper.GetPackageUri and PackUriHelper.GetPartUri, respectively.
//Given the "pack:" URI for a package Uri packUri = "pack://http%3a,,www.newsdocs.com,local,today.container /files/abc.xaml"; //Use PackUriHelper.GetPackageUri to obtain the URI of the package Uri packageUri = new PackUriHelper.GetPackageUri(packUri); //The resulting packageUri value is //"http://www.newsdocs.com/local/today.container"
Example: GetPartUri Example
//Given the "pack:" URI for a part Uri packUri = "pack://http%3a,,www.newsdocs.com,local,today.container /files/abc.xaml"; //Use PackUriHelper.GetPartUri to obtain the URI of the part Uri partUri = new PackUriHelper.GetPartUri(packUri); //The resulting partUri value is "/files/abc.xaml"