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);
51 public FileAccess FileOpenAccess {
55 public PackageProperties PackageProperties {
57 // PackageProperties are loaded when the relationships are loaded.
58 // Therefore ensure we've already loaded the relationships.
59 int count = Relationships.Count;
61 if (packageProperties == null) {
62 packageProperties = new PackagePropertiesPart ();
63 packageProperties.Package = this;
65 return packageProperties;
69 PackagePartCollection PartsCollection {
71 if (partsCollection == null) {
72 partsCollection = new PackagePartCollection ();
73 partsCollection.Parts.AddRange (GetPartsCore ());
75 return partsCollection;
83 Dictionary<string, PackageRelationship> Relationships {
85 if (relationships == null) {
97 protected Package (FileAccess openFileAccess)
98 : this (openFileAccess, false)
103 protected Package (FileAccess openFileAccess, bool streaming)
105 FileOpenAccess = openFileAccess;
106 Streaming = streaming;
110 internal void CheckIsReadOnly ()
112 if (FileOpenAccess == FileAccess.Read)
113 throw new IOException ("Operation not valid when package is read-only");
118 // FIXME: Ensure that Flush is actually called before dispose
119 ((IDisposable) this).Dispose ();
122 public PackagePart CreatePart (Uri partUri, string contentType)
124 return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
127 public PackagePart CreatePart (Uri partUri, string contentType, CompressionOption compressionOption)
130 Check.PartUri (partUri);
131 Check.ContentTypeIsValid (contentType);
133 if (PartExists (partUri))
134 throw new InvalidOperationException ("This partUri is already contained in the package");
136 PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
137 PartsCollection.Parts.Add (part);
141 protected abstract PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption);
143 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
145 return CreateRelationship (targetUri, targetMode, relationshipType, null);
148 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id)
150 return CreateRelationship (targetUri, targetMode, relationshipType, id, false);
153 internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
158 Check.TargetUri (targetUri);
159 if (targetUri.IsAbsoluteUri && targetMode == TargetMode.Internal)
160 throw new ArgumentException ("TargetUri cannot be absolute for an internal relationship");
162 Check.RelationshipTypeIsValid (relationshipType);
163 Check.IdIsValid (id);
168 PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
170 if (!PartExists (RelationshipUri))
171 CreatePartCore (RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed).IsRelationship = true;
173 Relationships.Add (r.Id, r);
174 relationshipsCollection.Relationships.Add (r);
177 using (Stream s = GetPart (RelationshipUri).GetStream ())
178 WriteRelationships (relationships, s);
185 public void DeletePart (Uri partUri)
188 Check.PartUri (partUri);
190 PackagePart part = GetPart (partUri);
193 if (part.Package == null)
194 throw new InvalidOperationException ("This part has already been removed");
196 // FIXME: MS.NET doesn't remove the relationship part
197 // Instead it throws an exception if you try to use it
198 if (PartExists (part.RelationshipsPartUri))
199 GetPart (part.RelationshipsPartUri).Package = null;
202 DeletePartCore (partUri);
203 PartsCollection.Parts.RemoveAll (p => p.Uri == partUri);
207 protected abstract void DeletePartCore (Uri partUri);
209 public void DeleteRelationship (string id)
214 Relationships.Remove (id);
216 relationshipsCollection.Relationships.RemoveAll (r => r.Id == id);
217 if (Relationships.Count > 0)
218 using (Stream s = GetPart (RelationshipUri).GetStream ())
219 WriteRelationships (relationships, s);
221 DeletePart (RelationshipUri);
224 void IDisposable.Dispose ()
233 protected virtual void Dispose (bool disposing)
235 // Nothing here needs to be disposed of
238 bool flushing = false;
241 if (FileOpenAccess == FileAccess.Read || flushing)
246 // Ensure we've loaded the relationships, parts and properties
247 int count = Relationships.Count;
249 if (packageProperties != null)
250 packageProperties.Flush ();
257 protected abstract void FlushCore ();
259 public PackagePart GetPart (Uri partUri)
261 Check.PartUri (partUri);
262 return GetPartCore (partUri);
265 protected abstract PackagePart GetPartCore (Uri partUri);
267 public PackagePartCollection GetParts ()
269 PartsCollection.Parts.Clear ();
270 PartsCollection.Parts.AddRange (GetPartsCore());
271 return PartsCollection;
274 protected abstract PackagePart [] GetPartsCore ();
276 public PackageRelationship GetRelationship (string id)
278 return Relationships [id];
281 public PackageRelationshipCollection GetRelationships ()
283 // Ensure the Relationships dict is instantiated first.
284 ICollection <PackageRelationship> rels = Relationships.Values;
285 relationshipsCollection.Relationships.Clear ();
286 relationshipsCollection.Relationships.AddRange (rels);
287 return relationshipsCollection;
290 public PackageRelationshipCollection GetRelationshipsByType (string relationshipType)
292 PackageRelationshipCollection collection = new PackageRelationshipCollection ();
293 foreach (PackageRelationship r in Relationships.Values)
294 if (r.RelationshipType == relationshipType)
295 collection.Relationships.Add (r);
300 void LoadRelationships ()
302 relationships = new Dictionary<string, PackageRelationship> ();
304 if (!PartExists (RelationshipUri))
307 using (Stream stream = GetPart (RelationshipUri).GetStream ()) {
308 XmlDocument doc = new XmlDocument ();
310 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
311 manager.AddNamespace ("rel", RelationshipNamespace);
313 foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
315 TargetMode mode = TargetMode.Internal;
316 if (node.Attributes["TargetMode"] != null)
317 mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
321 uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative);
323 uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Absolute);
325 CreateRelationship (uri,
327 node.Attributes["Type"].Value.ToString (),
328 node.Attributes["Id"].Value.ToString (),
332 foreach (PackageRelationship r in relationships.Values) {
333 if (r.RelationshipType == System.IO.Packaging.PackageProperties.NSPackagePropertiesRelation) {
334 PackagePart part = GetPart (PackUriHelper.ResolvePartUri (Uri, r.TargetUri));
335 packageProperties = new PackagePropertiesPart ();
336 packageProperties.Package = this;
337 packageProperties.Part = part;
338 packageProperties.LoadFrom (part.GetStream ());
347 string s = "Re" + RelationshipId.ToString ();
348 if (!Relationships.ContainsKey (s))
355 public static Package Open (Stream stream)
357 return Open (stream, FileMode.Open);
360 public static Package Open (string path)
362 return Open (path, FileMode.OpenOrCreate);
365 public static Package Open (Stream stream, FileMode packageMode)
367 FileAccess access = packageMode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite;
368 return Open (stream, packageMode, access);
371 public static Package Open (string path, FileMode packageMode)
373 return Open (path, packageMode, FileAccess.ReadWrite);
376 public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
378 return Open (stream, packageMode, packageAccess, false);
381 static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
383 return OpenCore (stream, packageMode, packageAccess, ownsStream);
386 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
388 return Open (path, packageMode, packageAccess, FileShare.None);
391 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare)
393 if (packageShare != FileShare.Read && packageShare != FileShare.None)
394 throw new NotSupportedException ("FileShare.Read and FileShare.None are the only supported options");
396 FileInfo info = new FileInfo (path);
398 // Bug - MS.NET appears to test for FileAccess.ReadWrite, not FileAccess.Write
399 if (packageAccess != FileAccess.ReadWrite && !info.Exists)
400 throw new ArgumentException ("packageAccess", "Cannot create stream with FileAccess.Read");
403 if (info.Exists && packageMode == FileMode.OpenOrCreate && info.Length == 0)
404 throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
406 Stream s = File.Open (path, packageMode, packageAccess, packageShare);
407 return Open (s, packageMode, packageAccess, true);
410 static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
412 if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
413 throw new IOException ("Stream does not support reading");
415 if ((packageAccess & FileAccess.Write) == FileAccess.Write && !stream.CanWrite)
416 throw new IOException ("Stream does not support reading");
419 throw new ArgumentException ("stream", "Stream must support seeking");
421 if (packageMode == FileMode.Open && stream.Length == 0)
422 throw new FileFormatException("Stream length cannot be zero with FileMode.Open");
424 if (packageMode == FileMode.CreateNew && stream.Length > 0)
425 throw new IOException ("Cannot use CreateNew when stream contains data");
427 if (packageMode == FileMode.Append || packageMode == FileMode.Truncate)
430 throw new NotSupportedException (string.Format("PackageMode.{0} is not supported", packageMode));
432 throw new IOException (string.Format("PackageMode.{0} is not supported", packageMode));
435 // Test to see if archive is valid
436 if (stream.Length > 0 && packageAccess == FileAccess.Read) {
438 using (zipsharp.UnzipArchive a = new zipsharp.UnzipArchive (stream)) {
441 throw new FileFormatException ("The specified archive is invalid.");
445 return new ZipPackage (packageAccess, ownsStream, stream);
448 public virtual bool PartExists (Uri partUri)
450 return GetPart (partUri) != null;
453 public bool RelationshipExists (string id)
455 return Relationships.ContainsKey (id);
458 internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
460 XmlDocument doc = new XmlDocument ();
461 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
462 manager.AddNamespace ("rel", RelationshipNamespace);
464 doc.AppendChild (doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
466 XmlNode root = doc.CreateNode (XmlNodeType.Element, "Relationships", RelationshipNamespace);
467 doc.AppendChild (root);
469 foreach (PackageRelationship relationship in relationships.Values)
471 XmlNode node = doc.CreateNode (XmlNodeType.Element, "Relationship", RelationshipNamespace);
473 XmlAttribute idAtt = doc.CreateAttribute ("Id");
474 idAtt.Value = relationship.Id;
475 node.Attributes.Append(idAtt);
477 XmlAttribute targetAtt = doc.CreateAttribute ("Target");
478 targetAtt.Value = relationship.TargetUri.ToString ();
479 node.Attributes.Append(targetAtt);
481 if (relationship.TargetMode != TargetMode.Internal) {
482 XmlAttribute modeAtt = doc.CreateAttribute ("TargetMode");
483 modeAtt.Value = relationship.TargetMode.ToString ();
484 node.Attributes.Append (modeAtt);
486 XmlAttribute typeAtt = doc.CreateAttribute ("Type");
487 typeAtt.Value = relationship.RelationshipType;
488 node.Attributes.Append(typeAtt);
490 root.AppendChild (node);
493 using (XmlTextWriter writer = new XmlTextWriter (stream, System.Text.Encoding.UTF8))
494 doc.WriteTo (writer);