1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
21 // Copyright (c) 2011 Xamarin Inc. (http://www.xamarin.com)
24 // Chris Toshok (toshok@ximian.com)
25 // Alan McGovern (amcgovern@novell.com)
26 // Rolf Bjarne Kvinge (rolf@xamarin.com)
30 using System.Collections.Generic;
33 namespace System.IO.Packaging {
35 public abstract class Package : IDisposable
37 internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml";
38 internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
39 internal static readonly Uri RelationshipUri = new Uri ("/_rels/.rels", UriKind.Relative);
41 PackageProperties packageProperties;
42 PackagePartCollection partsCollection;
43 Dictionary<string, PackageRelationship> relationships;
44 PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection ();
45 Uri Uri = new Uri ("/", UriKind.Relative);
48 public FileAccess FileOpenAccess {
52 public PackageProperties PackageProperties {
54 // PackageProperties are loaded when the relationships are loaded.
55 // Therefore ensure we've already loaded the relationships.
56 int count = Relationships.Count;
58 if (packageProperties == null) {
59 packageProperties = new PackagePropertiesPart ();
60 packageProperties.Package = this;
62 return packageProperties;
66 PackagePartCollection PartsCollection {
68 if (partsCollection == null) {
69 partsCollection = new PackagePartCollection ();
70 partsCollection.Parts.AddRange (GetPartsCore ());
72 return partsCollection;
80 Dictionary<string, PackageRelationship> Relationships {
82 if (relationships == null) {
94 protected Package (FileAccess fileOpenAccess)
95 : this (fileOpenAccess, false)
100 protected Package (FileAccess fileOpenAccess, bool streaming)
102 FileOpenAccess = fileOpenAccess;
103 Streaming = streaming;
107 internal void CheckIsReadOnly ()
109 if (FileOpenAccess == FileAccess.Read)
110 throw new IOException ("Operation not valid when package is read-only");
115 // FIXME: Ensure that Flush is actually called before dispose
120 public PackagePart CreatePart (Uri partUri, string contentType)
122 return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
125 public PackagePart CreatePart (Uri partUri, string contentType, CompressionOption compressionOption)
128 Check.PartUri (partUri);
129 Check.ContentTypeIsValid (contentType);
131 if (PartExists (partUri))
132 throw new InvalidOperationException ("This partUri is already contained in the package");
134 PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
135 PartsCollection.Parts.Add (part);
139 protected abstract PackagePart CreatePartCore (Uri parentUri, string contentType, CompressionOption compressionOption);
141 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
143 return CreateRelationship (targetUri, targetMode, relationshipType, null);
146 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id)
148 return CreateRelationship (targetUri, targetMode, relationshipType, id, false);
151 internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
156 Check.TargetUri (targetUri);
157 if (targetUri.IsAbsoluteUri && targetMode == TargetMode.Internal)
158 throw new ArgumentException ("TargetUri cannot be absolute for an internal relationship");
160 Check.RelationshipTypeIsValid (relationshipType);
161 Check.IdIsValid (id);
166 PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
168 if (!PartExists (RelationshipUri))
169 CreatePartCore (RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed).IsRelationship = true;
171 Relationships.Add (r.Id, r);
172 relationshipsCollection.Relationships.Add (r);
175 using (Stream s = GetPart (RelationshipUri).GetStream ())
176 WriteRelationships (relationships, s);
183 public void DeletePart (Uri partUri)
186 Check.PartUri (partUri);
188 PackagePart part = GetPart (partUri);
191 if (part.Package == null)
192 throw new InvalidOperationException ("This part has already been removed");
194 // FIXME: MS.NET doesn't remove the relationship part
195 // Instead it throws an exception if you try to use it
196 if (PartExists (part.RelationshipsPartUri))
197 GetPart (part.RelationshipsPartUri).Package = null;
200 DeletePartCore (partUri);
201 PartsCollection.Parts.RemoveAll (p => p.Uri == partUri);
205 protected abstract void DeletePartCore (Uri partUri);
207 public void DeleteRelationship (string id)
212 Relationships.Remove (id);
214 relationshipsCollection.Relationships.RemoveAll (r => r.Id == id);
215 if (Relationships.Count > 0)
216 using (Stream s = GetPart (RelationshipUri).GetStream ())
217 WriteRelationships (relationships, s);
219 DeletePart (RelationshipUri);
222 void IDisposable.Dispose ()
228 protected virtual void Dispose (bool disposing)
230 // Nothing here needs to be disposed of
233 bool flushing = false;
236 if (FileOpenAccess == FileAccess.Read || flushing)
241 // Ensure we've loaded the relationships, parts and properties
242 int count = Relationships.Count;
244 if (packageProperties != null)
245 packageProperties.Flush ();
252 protected abstract void FlushCore ();
254 public PackagePart GetPart (Uri partUri)
256 Check.PartUri (partUri);
257 return GetPartCore (partUri);
260 protected abstract PackagePart GetPartCore (Uri partUri);
262 public PackagePartCollection GetParts ()
264 PartsCollection.Parts.Clear ();
265 PartsCollection.Parts.AddRange (GetPartsCore());
266 return PartsCollection;
269 protected abstract PackagePart [] GetPartsCore ();
271 public PackageRelationship GetRelationship (string id)
273 return Relationships [id];
276 public PackageRelationshipCollection GetRelationships ()
278 // Ensure the Relationships dict is instantiated first.
279 ICollection <PackageRelationship> rels = Relationships.Values;
280 relationshipsCollection.Relationships.Clear ();
281 relationshipsCollection.Relationships.AddRange (rels);
282 return relationshipsCollection;
285 public PackageRelationshipCollection GetRelationshipsByType (string relationshipType)
287 PackageRelationshipCollection collection = new PackageRelationshipCollection ();
288 foreach (PackageRelationship r in Relationships.Values)
289 if (r.RelationshipType == relationshipType)
290 collection.Relationships.Add (r);
295 void LoadRelationships ()
297 relationships = new Dictionary<string, PackageRelationship> ();
299 if (!PartExists (RelationshipUri))
302 using (Stream stream = GetPart (RelationshipUri).GetStream ()) {
303 XmlDocument doc = new XmlDocument ();
305 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
306 manager.AddNamespace ("rel", RelationshipNamespace);
308 foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
310 TargetMode mode = TargetMode.Internal;
311 if (node.Attributes["TargetMode"] != null)
312 mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
316 uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative);
318 uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Absolute);
320 CreateRelationship (uri,
322 node.Attributes["Type"].Value.ToString (),
323 node.Attributes["Id"].Value.ToString (),
327 foreach (PackageRelationship r in relationships.Values) {
328 if (r.RelationshipType == System.IO.Packaging.PackageProperties.NSPackagePropertiesRelation) {
329 PackagePart part = GetPart (PackUriHelper.ResolvePartUri (Uri, r.TargetUri));
330 packageProperties = new PackagePropertiesPart ();
331 packageProperties.Package = this;
332 packageProperties.Part = part;
333 packageProperties.LoadFrom (part.GetStream ());
342 string s = "Re" + RelationshipId.ToString ();
343 if (!Relationships.ContainsKey (s))
350 public static Package Open (Stream stream)
352 return Open (stream, FileMode.Open);
355 public static Package Open (string path)
357 return Open (path, FileMode.OpenOrCreate);
360 public static Package Open (Stream stream, FileMode packageMode)
362 FileAccess access = packageMode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite;
363 return Open (stream, packageMode, access);
366 public static Package Open (string path, FileMode packageMode)
368 return Open (path, packageMode, FileAccess.ReadWrite);
371 public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
373 return OpenCore (stream, packageMode, packageAccess);
376 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
378 return Open (path, packageMode, packageAccess, FileShare.None);
381 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare)
383 if (packageShare != FileShare.Read && packageShare != FileShare.None)
384 throw new NotSupportedException ("FileShare.Read and FileShare.None are the only supported options");
386 FileInfo info = new FileInfo (path);
388 // Bug - MS.NET appears to test for FileAccess.ReadWrite, not FileAccess.Write
389 if (packageAccess != FileAccess.ReadWrite && !info.Exists)
390 throw new ArgumentException ("packageAccess", "Cannot create stream with FileAccess.Read");
393 if (info.Exists && packageMode == FileMode.OpenOrCreate && info.Length == 0)
394 throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
396 Stream s = File.Open (path, packageMode, packageAccess, packageShare);
397 return Open (s, packageMode, packageAccess);
400 static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess)
402 if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
403 throw new IOException ("Stream does not support reading");
405 if ((packageAccess & FileAccess.Write) == FileAccess.Write && !stream.CanWrite)
406 throw new IOException ("Stream does not support reading");
409 throw new ArgumentException ("stream", "Stream must support seeking");
411 if (packageMode == FileMode.Open && stream.Length == 0)
412 throw new FileFormatException("Stream length cannot be zero with FileMode.Open");
414 if (packageMode == FileMode.CreateNew && stream.Length > 0)
415 throw new IOException ("Cannot use CreateNew when stream contains data");
417 if (packageMode == FileMode.Append || packageMode == FileMode.Truncate)
420 throw new NotSupportedException (string.Format("PackageMode.{0} is not supported", packageMode));
422 throw new IOException (string.Format("PackageMode.{0} is not supported", packageMode));
425 // Test to see if archive is valid
426 if (stream.Length > 0 && packageAccess == FileAccess.Read) {
428 using (zipsharp.UnzipArchive a = new zipsharp.UnzipArchive (stream)) {
431 throw new FileFormatException ("The specified archive is invalid.");
435 return new ZipPackage (packageAccess, stream);
438 public virtual bool PartExists (Uri partUri)
440 return GetPart (partUri) != null;
443 public bool RelationshipExists (string id)
445 return Relationships.ContainsKey (id);
448 internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
450 XmlDocument doc = new XmlDocument ();
451 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
452 manager.AddNamespace ("rel", RelationshipNamespace);
454 doc.AppendChild (doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
456 XmlNode root = doc.CreateNode (XmlNodeType.Element, "Relationships", RelationshipNamespace);
457 doc.AppendChild (root);
459 foreach (PackageRelationship relationship in relationships.Values)
461 XmlNode node = doc.CreateNode (XmlNodeType.Element, "Relationship", RelationshipNamespace);
463 XmlAttribute idAtt = doc.CreateAttribute ("Id");
464 idAtt.Value = relationship.Id;
465 node.Attributes.Append(idAtt);
467 XmlAttribute targetAtt = doc.CreateAttribute ("Target");
468 targetAtt.Value = relationship.TargetUri.ToString ();
469 node.Attributes.Append(targetAtt);
471 if (relationship.TargetMode != TargetMode.Internal) {
472 XmlAttribute modeAtt = doc.CreateAttribute ("TargetMode");
473 modeAtt.Value = relationship.TargetMode.ToString ();
474 node.Attributes.Append (modeAtt);
476 XmlAttribute typeAtt = doc.CreateAttribute ("Type");
477 typeAtt.Value = relationship.RelationshipType;
478 node.Attributes.Append(typeAtt);
480 root.AppendChild (node);
483 using (XmlTextWriter writer = new XmlTextWriter (stream, System.Text.Encoding.UTF8))
484 doc.WriteTo (writer);