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)
23 // Chris Toshok (toshok@ximian.com)
24 // Alan McGovern (amcgovern@novell.com)
28 using System.Collections.Generic;
31 namespace System.IO.Packaging {
33 public abstract class Package : IDisposable
35 internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml";
36 internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
37 internal static readonly Uri RelationshipUri = new Uri ("/_rels/.rels", UriKind.Relative);
39 PackagePartCollection partsCollection = new PackagePartCollection ();
40 Dictionary<string, PackageRelationship> relationships;
41 PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection ();
42 Uri Uri = new Uri ("/", UriKind.Relative);
45 public FileAccess FileOpenAccess {
49 public PackageProperties PackageProperties {
57 Dictionary<string, PackageRelationship> Relationships {
59 if (relationships == null) {
60 relationships = new Dictionary<string, PackageRelationship> ();
62 if (PartExists (RelationshipUri))
63 using (Stream stream = GetPart (RelationshipUri).GetStream ())
64 LoadRelationships (relationships, stream);
75 protected Package (FileAccess fileOpenAccess)
76 : this (fileOpenAccess, false)
81 protected Package (FileAccess fileOpenAccess, bool streaming)
83 FileOpenAccess = fileOpenAccess;
84 Streaming = streaming;
88 internal void CheckIsReadOnly ()
90 if (FileOpenAccess == FileAccess.Read)
91 throw new IOException ("Operation not valid when package is read-only");
96 // FIXME: Ensure that Flush is actually called before dispose
101 public PackagePart CreatePart (Uri partUri, string contentType)
103 return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
106 public PackagePart CreatePart (Uri partUri, string contentType, CompressionOption compressionOption)
109 Check.PartUri (partUri);
110 Check.ContentTypeIsValid (contentType);
112 if (PartExists (partUri))
113 throw new InvalidOperationException ("This partUri is already contained in the package");
115 PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
116 partsCollection.Parts.Add (part);
120 protected abstract PackagePart CreatePartCore (Uri parentUri, string contentType, CompressionOption compressionOption);
122 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
124 return CreateRelationship (targetUri, targetMode, relationshipType, null);
127 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id)
129 return CreateRelationship (targetUri, targetMode, relationshipType, id, false);
132 internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
135 Check.TargetUri (targetUri);
137 Check.RelationshipTypeIsValid (relationshipType);
138 Check.IdIsValid (id);
143 PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
145 if (!PartExists (RelationshipUri))
146 CreatePart (RelationshipUri, RelationshipContentType).IsRelationship = true;
148 Relationships.Add (r.Id, r);
149 relationshipsCollection.Relationships.Add (r);
152 using (Stream s = GetPart (RelationshipUri).GetStream ())
153 WriteRelationships (relationships, s);
160 public void DeletePart (Uri partUri)
163 Check.PartUri (partUri);
165 PackagePart part = GetPart (partUri);
168 if (part.Package == null)
169 throw new InvalidOperationException ("This part has already been removed");
171 // FIXME: MS.NET doesn't remove the relationship part
172 // Instead it throws an exception if you try to use it
173 if (PartExists (part.RelationshipsPartUri))
174 GetPart (part.RelationshipsPartUri).Package = null;
177 DeletePartCore (partUri);
178 partsCollection.Parts.RemoveAll (p => p.Uri == partUri);
182 protected abstract void DeletePartCore (Uri partUri);
184 public void DeleteRelationship (string id)
189 Relationships.Remove (id);
191 relationshipsCollection.Relationships.RemoveAll (r => r.Id == id);
192 if (Relationships.Count > 0)
193 using (Stream s = GetPart (RelationshipUri).GetStream ())
194 WriteRelationships (relationships, s);
196 DeletePart (RelationshipUri);
199 void IDisposable.Dispose ()
205 protected virtual void Dispose (bool disposing)
207 // Nothing here needs to be disposed of
212 // I should have a dirty boolean
213 if (FileOpenAccess != FileAccess.Read)
217 protected abstract void FlushCore ();
219 public PackagePart GetPart (Uri partUri)
221 Check.PartUri (partUri);
222 return GetPartCore (partUri);
225 protected abstract PackagePart GetPartCore (Uri partUri);
227 public PackagePartCollection GetParts ()
229 partsCollection.Parts.Clear ();
230 partsCollection.Parts.AddRange (GetPartsCore());
231 return partsCollection;
234 protected abstract PackagePart [] GetPartsCore ();
236 public PackageRelationship GetRelationship (string id)
238 return Relationships [id];
241 public PackageRelationshipCollection GetRelationships ()
243 relationshipsCollection.Relationships.Clear ();
244 relationshipsCollection.Relationships.AddRange (Relationships.Values);
245 return relationshipsCollection;
248 public PackageRelationshipCollection GetRelationshipsByType (string relationshipType)
250 PackageRelationshipCollection collection = new PackageRelationshipCollection ();
251 foreach (PackageRelationship r in Relationships.Values)
252 if (r.RelationshipType == relationshipType)
253 collection.Relationships.Add (r);
258 void LoadRelationships (Dictionary<string, PackageRelationship> relationships, Stream stream)
260 XmlDocument doc = new XmlDocument ();
262 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
263 manager.AddNamespace ("rel", RelationshipNamespace);
265 foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
268 TargetMode mode = TargetMode.Internal;
269 if (node.Attributes["TargetMode"] != null)
270 mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
272 CreateRelationship (new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative),
274 node.Attributes["Type"].Value.ToString (),
275 node.Attributes["Id"].Value.ToString (),
284 string s = RelationshipId.ToString ();
285 if (!Relationships.ContainsKey (s))
292 public static Package Open (Stream stream)
294 return Open (stream, FileMode.Open);
297 public static Package Open (string path)
299 return Open (path, FileMode.OpenOrCreate);
302 public static Package Open (Stream stream, FileMode packageMode)
304 FileAccess access = packageMode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite;
305 return Open (stream, packageMode, access);
308 public static Package Open (string path, FileMode packageMode)
310 return Open (path, packageMode, FileAccess.ReadWrite);
313 public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
315 return OpenCore (stream, packageMode, packageAccess);
318 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
320 return Open (path, packageMode, packageAccess, FileShare.None);
323 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare)
325 if (packageShare != FileShare.Read && packageShare != FileShare.None)
326 throw new NotSupportedException ("FileShare.Read and FileShare.None are the only supported options");
328 FileInfo info = new FileInfo (path);
330 // Bug - MS.NET appears to test for FileAccess.ReadWrite, not FileAccess.Write
331 if (packageAccess != FileAccess.ReadWrite && !info.Exists)
332 throw new ArgumentException ("packageAccess", "Cannot create stream with FileAccess.Read");
335 if (info.Exists && packageMode == FileMode.OpenOrCreate && info.Length == 0)
336 throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
338 Stream s = File.Open (path, packageMode, packageAccess, packageShare);
339 return Open (s, packageMode, packageAccess);
342 static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess)
344 if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
345 throw new IOException ("Stream does not support reading");
347 if ((packageAccess & FileAccess.Write) == FileAccess.Write && !stream.CanWrite)
348 throw new IOException ("Stream does not support reading");
351 throw new ArgumentException ("stream", "Stream must support seeking");
353 if (packageMode == FileMode.Open && stream.Length == 0)
354 throw new FileFormatException("Stream length cannot be zero with FileMode.Open");
356 if (packageMode == FileMode.CreateNew && stream.Length > 0)
357 throw new IOException ("Cannot use CreateNew when stream contains data");
359 if (packageMode == FileMode.Append || packageMode == FileMode.Truncate)
362 throw new NotSupportedException (string.Format("PackageMode.{0} is not supported", packageMode));
364 throw new IOException (string.Format("PackageMode.{0} is not supported", packageMode));
367 // Test to see if archive is valid
368 if (stream.Length > 0 && packageAccess == FileAccess.Read) {
370 using (zipsharp.UnzipArchive a = new zipsharp.UnzipArchive (stream)) {
373 throw new FileFormatException ("The specified archive is invalid.");
377 // FIXME: MS docs say that a ZipPackage is returned by default.
378 // It looks like if you create a custom package, you cannot use Package.Open.
380 // FIXME: This is a horrible hack. If a package is opened in readonly mode, i
381 // need to pretend i have read/write mode so i can load the PackageParts
382 // and PackageRelations. I think the 'streaming' boolean might be used for this.
383 ZipPackage package = new ZipPackage (packageAccess);
384 package.PackageStream = stream;
385 package.FileOpenAccess = FileAccess.ReadWrite;
387 package.GetRelationships();
388 package.FileOpenAccess = packageAccess;
392 public virtual bool PartExists (Uri partUri)
394 return GetPart (partUri) != null;
397 public bool RelationshipExists (string id)
399 return Relationships.ContainsKey (id);
402 internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
404 XmlDocument doc = new XmlDocument ();
405 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
406 manager.AddNamespace ("rel", RelationshipNamespace);
408 doc.AppendChild (doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
410 XmlNode root = doc.CreateNode (XmlNodeType.Element, "Relationships", RelationshipNamespace);
411 doc.AppendChild (root);
413 foreach (PackageRelationship relationship in relationships.Values)
415 XmlNode node = doc.CreateNode (XmlNodeType.Element, "Relationship", RelationshipNamespace);
417 XmlAttribute idAtt = doc.CreateAttribute ("Id");
418 idAtt.Value = relationship.Id;
419 node.Attributes.Append(idAtt);
421 XmlAttribute targetAtt = doc.CreateAttribute ("Target");
422 targetAtt.Value = relationship.TargetUri.ToString ();
423 node.Attributes.Append(targetAtt);
425 XmlAttribute typeAtt = doc.CreateAttribute ("Type");
426 typeAtt.Value = relationship.RelationshipType;
427 node.Attributes.Append(typeAtt);
429 root.AppendChild (node);
432 using (XmlTextWriter writer = new XmlTextWriter (stream, System.Text.Encoding.UTF8))
433 doc.WriteTo (writer);