// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2011 Xamarin Inc. (http://www.xamarin.com)
//
// Authors:
// Chris Toshok (toshok@ximian.com)
// Alan McGovern (amcgovern@novell.com)
+// Rolf Bjarne Kvinge (rolf@xamarin.com)
//
using System;
internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml";
internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
internal static readonly Uri RelationshipUri = new Uri ("/_rels/.rels", UriKind.Relative);
-
- PackagePartCollection partsCollection = new PackagePartCollection ();
+
+ PackageProperties packageProperties;
+ PackagePartCollection partsCollection;
+ Dictionary<string, PackageRelationship> relationships;
PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection ();
+ Uri Uri = new Uri ("/", UriKind.Relative);
- Dictionary<string, PackageRelationship> relationships;
+ bool Disposed {
+ get; set;
+ }
+
+ public FileAccess FileOpenAccess {
+ get; private set;
+ }
+
+ public PackageProperties PackageProperties {
+ get {
+ // PackageProperties are loaded when the relationships are loaded.
+ // Therefore ensure we've already loaded the relationships.
+ int count = Relationships.Count;
+
+ if (packageProperties == null) {
+ packageProperties = new PackagePropertiesPart ();
+ packageProperties.Package = this;
+ }
+ return packageProperties;
+ }
+ }
+
+ PackagePartCollection PartsCollection {
+ get {
+ if (partsCollection == null) {
+ partsCollection = new PackagePartCollection ();
+ partsCollection.Parts.AddRange (GetPartsCore ());
+ }
+ return partsCollection;
+ }
+ }
+
+ int RelationshipId {
+ get; set;
+ }
Dictionary<string, PackageRelationship> Relationships {
get {
if (relationships == null) {
- relationships = new Dictionary<string, PackageRelationship> ();
-
- if (PartExists (RelationshipUri))
- using (Stream stream = GetPart (RelationshipUri).GetStream ())
- LoadRelationships (relationships, stream);
+ LoadRelationships ();
}
return relationships;
}
}
+
+ bool Streaming {
+ get; set;
+ }
- Uri Uri = new Uri ("/", UriKind.Relative);
- protected Package (FileAccess fileOpenAccess)
- : this (fileOpenAccess, false)
+ protected Package (FileAccess openFileAccess)
+ : this (openFileAccess, false)
{
}
- protected Package (FileAccess fileOpenAccess, bool streaming)
+ protected Package (FileAccess openFileAccess, bool streaming)
{
- FileOpenAccess = fileOpenAccess;
+ FileOpenAccess = openFileAccess;
Streaming = streaming;
}
- void IDisposable.Dispose ()
- {
- Flush ();
- Dispose (true);
- }
-
- protected virtual void Dispose (bool disposing)
- {
- // Nothing here needs to be disposed of
- }
-
- public FileAccess FileOpenAccess {
- get; private set;
- }
-
- public PackageProperties PackageProperties {
- get; private set;
- }
-
- private int RelationshipId {
- get; set;
- }
-
- private bool Streaming {
- get; set;
- }
internal void CheckIsReadOnly ()
{
if (FileOpenAccess == FileAccess.Read)
throw new IOException ("Operation not valid when package is read-only");
}
-
+
public void Close ()
{
// FIXME: Ensure that Flush is actually called before dispose
- Flush ();
- Dispose (true);
- }
-
- public void Flush ()
- {
- // I should have a dirty boolean
- if (FileOpenAccess != FileAccess.Read)
- FlushCore ();
+ ((IDisposable) this).Dispose ();
}
- protected abstract void FlushCore ();
-
public PackagePart CreatePart (Uri partUri, string contentType)
{
return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
throw new InvalidOperationException ("This partUri is already contained in the package");
PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
- partsCollection.Parts.Add (part);
+ PartsCollection.Parts.Add (part);
return part;
}
-
- public void DeletePart (Uri partUri)
- {
- CheckIsReadOnly ();
- Check.PartUri (partUri);
-
- PackagePart part = GetPart (partUri);
- if (part != null)
- {
- if (part.Package == null)
- throw new InvalidOperationException ("This part has already been removed");
-
- // FIXME: MS.NET doesn't remove the relationship part
- // Instead it throws an exception if you try to use it
- if (PartExists (part.RelationshipsPartUri))
- GetPart (part.RelationshipsPartUri).Package = null;
-
- part.Package = null;
- DeletePartCore (partUri);
- partsCollection.Parts.RemoveAll (p => p.Uri == partUri);
- }
- }
-
- protected abstract PackagePart CreatePartCore (Uri parentUri, string contentType, CompressionOption compressionOption);
- protected abstract void DeletePartCore (Uri partUri);
+
+ protected abstract PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption);
public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
{
internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
{
- CheckIsReadOnly ();
+ if (!loading)
+ CheckIsReadOnly ();
+
Check.TargetUri (targetUri);
+ if (targetUri.IsAbsoluteUri && targetMode == TargetMode.Internal)
+ throw new ArgumentException ("TargetUri cannot be absolute for an internal relationship");
Check.RelationshipTypeIsValid (relationshipType);
Check.IdIsValid (id);
PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
if (!PartExists (RelationshipUri))
- CreatePart (RelationshipUri, RelationshipContentType).IsRelationship = true;
+ CreatePartCore (RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed).IsRelationship = true;
Relationships.Add (r.Id, r);
relationshipsCollection.Relationships.Add (r);
return r;
}
+
+ public void DeletePart (Uri partUri)
+ {
+ CheckIsReadOnly ();
+ Check.PartUri (partUri);
+
+ PackagePart part = GetPart (partUri);
+ if (part != null)
+ {
+ if (part.Package == null)
+ throw new InvalidOperationException ("This part has already been removed");
+
+ // FIXME: MS.NET doesn't remove the relationship part
+ // Instead it throws an exception if you try to use it
+ if (PartExists (part.RelationshipsPartUri))
+ GetPart (part.RelationshipsPartUri).Package = null;
+
+ part.Package = null;
+ DeletePartCore (partUri);
+ PartsCollection.Parts.RemoveAll (p => p.Uri == partUri);
+ }
+ }
+
+ protected abstract void DeletePartCore (Uri partUri);
+
public void DeleteRelationship (string id)
{
Check.Id (id);
else
DeletePart (RelationshipUri);
}
+
+ void IDisposable.Dispose ()
+ {
+ if (!Disposed) {
+ Flush ();
+ Dispose (true);
+ Disposed = true;
+ }
+ }
+
+ protected virtual void Dispose (bool disposing)
+ {
+ // Nothing here needs to be disposed of
+ }
+
+ bool flushing = false;
+ public void Flush ()
+ {
+ if (FileOpenAccess == FileAccess.Read || flushing)
+ return;
+
+ flushing = true;
+
+ // Ensure we've loaded the relationships, parts and properties
+ int count = Relationships.Count;
+
+ if (packageProperties != null)
+ packageProperties.Flush ();
+
+ FlushCore ();
+
+ flushing = false;
+ }
+
+ protected abstract void FlushCore ();
public PackagePart GetPart (Uri partUri)
{
public PackagePartCollection GetParts ()
{
- partsCollection.Parts.Clear ();
- partsCollection.Parts.AddRange (GetPartsCore());
- return partsCollection;
+ PartsCollection.Parts.Clear ();
+ PartsCollection.Parts.AddRange (GetPartsCore());
+ return PartsCollection;
}
protected abstract PackagePart [] GetPartsCore ();
-
- public virtual bool PartExists (Uri partUri)
- {
- return GetPart (partUri) != null;
- }
-
public PackageRelationship GetRelationship (string id)
{
return Relationships [id];
public PackageRelationshipCollection GetRelationships ()
{
+ // Ensure the Relationships dict is instantiated first.
+ ICollection <PackageRelationship> rels = Relationships.Values;
relationshipsCollection.Relationships.Clear ();
- relationshipsCollection.Relationships.AddRange (Relationships.Values);
+ relationshipsCollection.Relationships.AddRange (rels);
return relationshipsCollection;
}
return collection;
}
- public bool RelationshipExists (string id)
- {
- return Relationships.ContainsKey (id);
- }
-
- void LoadRelationships (Dictionary<string, PackageRelationship> relationships, Stream stream)
+ void LoadRelationships ()
{
- XmlDocument doc = new XmlDocument ();
- doc.Load (stream);
- XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
- manager.AddNamespace ("rel", RelationshipNamespace);
+ relationships = new Dictionary<string, PackageRelationship> ();
- foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
- {
-
- TargetMode mode = TargetMode.Internal;
- if (node.Attributes["TargetMode"] != null)
- mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
+ if (!PartExists (RelationshipUri))
+ return;
+
+ using (Stream stream = GetPart (RelationshipUri).GetStream ()) {
+ XmlDocument doc = new XmlDocument ();
+ doc.Load (stream);
+ XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
+ manager.AddNamespace ("rel", RelationshipNamespace);
+
+ foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
+ {
+ TargetMode mode = TargetMode.Internal;
+ if (node.Attributes["TargetMode"] != null)
+ mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
+
+ Uri uri;
+ try {
+ uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative);
+ } catch {
+ uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Absolute);
+ }
+ CreateRelationship (uri,
+ mode,
+ node.Attributes["Type"].Value.ToString (),
+ node.Attributes["Id"].Value.ToString (),
+ true);
+ }
- CreateRelationship (new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative),
- mode,
- node.Attributes["Type"].Value.ToString (),
- node.Attributes["Id"].Value.ToString (),
- true);
+ foreach (PackageRelationship r in relationships.Values) {
+ if (r.RelationshipType == System.IO.Packaging.PackageProperties.NSPackagePropertiesRelation) {
+ PackagePart part = GetPart (PackUriHelper.ResolvePartUri (Uri, r.TargetUri));
+ packageProperties = new PackagePropertiesPart ();
+ packageProperties.Package = this;
+ packageProperties.Part = part;
+ packageProperties.LoadFrom (part.GetStream ());
+ }
+ }
}
}
- private string NextId ()
+ string NextId ()
{
- while (true)
- {
- string s = RelationshipId.ToString ();
+ while (true) {
+ string s = "Re" + RelationshipId.ToString ();
if (!Relationships.ContainsKey (s))
return s;
RelationshipId++;
}
}
-
+
public static Package Open (Stream stream)
{
return Open (stream, FileMode.Open);
public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
{
- return OpenCore (stream, packageMode, packageAccess);
+ return Open (stream, packageMode, packageAccess, false);
+ }
+
+ static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
+ {
+ return OpenCore (stream, packageMode, packageAccess, ownsStream);
}
public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
Stream s = File.Open (path, packageMode, packageAccess, packageShare);
- return Open (s, packageMode, packageAccess);
+ return Open (s, packageMode, packageAccess, true);
}
- private static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess)
+ static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
{
if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
throw new IOException ("Stream does not support reading");
}
}
- // FIXME: MS docs say that a ZipPackage is returned by default.
- // It looks like if you create a custom package, you cannot use Package.Open.
-
- // FIXME: This is a horrible hack. If a package is opened in readonly mode, i
- // need to pretend i have read/write mode so i can load the PackageParts
- // and PackageRelations. I think the 'streaming' boolean might be used for this.
- ZipPackage package = new ZipPackage (packageAccess);
- package.PackageStream = stream;
- package.FileOpenAccess = FileAccess.ReadWrite;
- package.GetParts ();
- package.GetRelationships();
- package.FileOpenAccess = packageAccess;
- return package;
+ return new ZipPackage (packageAccess, ownsStream, stream);
+ }
+
+ public virtual bool PartExists (Uri partUri)
+ {
+ return GetPart (partUri) != null;
+ }
+
+ public bool RelationshipExists (string id)
+ {
+ return Relationships.ContainsKey (id);
}
internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
XmlAttribute targetAtt = doc.CreateAttribute ("Target");
targetAtt.Value = relationship.TargetUri.ToString ();
node.Attributes.Append(targetAtt);
-
+
+ if (relationship.TargetMode != TargetMode.Internal) {
+ XmlAttribute modeAtt = doc.CreateAttribute ("TargetMode");
+ modeAtt.Value = relationship.TargetMode.ToString ();
+ node.Attributes.Append (modeAtt);
+ }
XmlAttribute typeAtt = doc.CreateAttribute ("Type");
typeAtt.Value = relationship.RelationshipType;
node.Attributes.Append(typeAtt);