| /******************************************************************************* | 
 |  * You may amend and distribute as you like, but don't remove this header! | 
 |  * | 
 |  * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. | 
 |  * See http://www.codeplex.com/EPPlus for details. | 
 |  * | 
 |  * Copyright (C) 2011  Jan Källman | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   | 
 |  * See the GNU Lesser General Public License for more details. | 
 |  * | 
 |  * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php | 
 |  * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html | 
 |  * | 
 |  * All code and executables are provided "as is" with no warranty either express or implied.  | 
 |  * The author accepts no liability for any damage or loss of business that this product may cause. | 
 |  * | 
 |  * Code change notes: | 
 |  *  | 
 |  * Author							Change						Date | 
 |  ******************************************************************************* | 
 |  * Jan Källman		Added		25-Oct-2012 | 
 |  *******************************************************************************/ | 
 | using System; | 
 | using System.Collections.Generic; | 
 | using System.Globalization; | 
 | using System.Linq; | 
 | using System.Text; | 
 | using System.IO; | 
 | using System.IO.Compression; | 
 | using System.Xml; | 
 | using OfficeOpenXml.Utils; | 
 | namespace OfficeOpenXml.Packaging | 
 | { | 
 |     /// <summary> | 
 |     /// Specifies whether the target is inside or outside the System.IO.Packaging.Package. | 
 |     /// </summary> | 
 |     public enum TargetMode | 
 |     { | 
 |         /// <summary> | 
 |         /// The relationship references a part that is inside the package. | 
 |         /// </summary> | 
 |         Internal = 0, | 
 |         /// <summary> | 
 |         /// The relationship references a resource that is external to the package. | 
 |         /// </summary> | 
 |         External = 1, | 
 |     } | 
 |     /// <summary> | 
 |     /// Represent an OOXML Zip package. | 
 |     /// </summary> | 
 |     public class ZipPackage : ZipPackageRelationshipBase | 
 |     { | 
 |         internal class ContentType | 
 |         { | 
 |             internal string Name; | 
 |             internal bool IsExtension; | 
 |             internal string Match; | 
 |             public ContentType(string name, bool isExtension, string match) | 
 |             { | 
 |                 Name = name; | 
 |                 IsExtension = isExtension; | 
 |                 Match = match; | 
 |             } | 
 |         } | 
 |         Dictionary<string, ZipPackagePart> Parts = new Dictionary<string, ZipPackagePart>(StringComparer.InvariantCultureIgnoreCase); | 
 |         internal Dictionary<string, ContentType> _contentTypes = new Dictionary<string, ContentType>(StringComparer.InvariantCultureIgnoreCase); | 
 |         internal ZipPackage() | 
 |         { | 
 |             AddNew(); | 
 |         } | 
 |  | 
 |         private void AddNew() | 
 |         { | 
 |             _contentTypes.Add("xml", new ContentType(ExcelPackage.schemaXmlExtension, true, "xml")); | 
 |             _contentTypes.Add("rels", new ContentType(ExcelPackage.schemaRelsExtension, true, "rels")); | 
 |         } | 
 |  | 
 |         internal ZipPackage(Stream stream) | 
 |         { | 
 |             bool hasContentTypeXml = false; | 
 |             if (stream == null || stream.Length == 0) | 
 |             { | 
 |                 AddNew(); | 
 |             } | 
 |             else | 
 |             { | 
 |                 var rels = new Dictionary<string, ZipArchiveEntry>(); | 
 |                 stream.Seek(0, SeekOrigin.Begin); | 
 |                 using var zip = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true); | 
 |                 foreach (var e in zip.Entries) | 
 |                 { | 
 |                     if (e.Length > 0) | 
 |                     { | 
 |                         if (e.FullName .Equals("[content_types].xml", StringComparison.InvariantCultureIgnoreCase)) | 
 |                         { | 
 |                             using var inputStream = e.Open(); | 
 |                             AddContentTypes(inputStream); | 
 |                             hasContentTypeXml = true; | 
 |                         } | 
 |                         else if (e.FullName .Equals("_rels/.rels", StringComparison.InvariantCultureIgnoreCase))  | 
 |                         { | 
 |                             using var inputStream = e.Open(); | 
 |                             ReadRelation(inputStream, ""); | 
 |                         } | 
 |                         else | 
 |                         { | 
 |                             if (e.FullName.EndsWith(".rels", StringComparison.InvariantCultureIgnoreCase)) | 
 |                             { | 
 |                                 rels.Add(GetUriKey(e.FullName), e); | 
 |                             } | 
 |                             else | 
 |                             { | 
 |                                 using var inputStream = e.Open(); | 
 |                                 var part = new ZipPackagePart(this, e); | 
 |                                 part.Stream = new MemoryStream(); | 
 |                                 inputStream.CopyTo(part.Stream); | 
 |                                 Parts.Add(GetUriKey(e.FullName), part); | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                 } | 
 |                 foreach (var p in Parts) | 
 |                 { | 
 |                     string name = Path.GetFileName(p.Key); | 
 |                     string extension = Path.GetExtension(p.Key); | 
 |                     string relFile = string.Format("{0}_rels/{1}.rels", p.Key.Substring(0, p.Key.Length - name.Length), name); | 
 |                     if (rels.TryGetValue(relFile, out var zipArchiveEntry)) | 
 |                     { | 
 |                         using var inputStream = zipArchiveEntry.Open(); | 
 |                         p.Value.ReadRelation(inputStream, p.Value.Uri.OriginalString); | 
 |                     } | 
 |                     if (_contentTypes.TryGetValue(p.Key, out var type)) | 
 |                     { | 
 |                         p.Value.ContentType = type.Name; | 
 |                     } | 
 |                     else if (extension.Length > 1 && _contentTypes.ContainsKey(extension.Substring(1))) | 
 |                     { | 
 |                         p.Value.ContentType = _contentTypes[extension.Substring(1)].Name; | 
 |                     } | 
 |                 } | 
 |                 if (!hasContentTypeXml) | 
 |                 { | 
 |                     throw new InvalidDataException("The file is not an valid Package file."); | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         private void AddContentTypes(Stream inputStream) | 
 |         { | 
 |             var doc = new XmlDocument(); | 
 |             XmlHelper.LoadXmlSafe(doc, inputStream); | 
 |  | 
 |             foreach (XmlElement c in doc.DocumentElement.ChildNodes) | 
 |             { | 
 |                 ContentType ct; | 
 |                 if (string.IsNullOrEmpty(c.GetAttribute("Extension"))) | 
 |                 { | 
 |                     ct = new ContentType(c.GetAttribute("ContentType"), false, c.GetAttribute("PartName")); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     ct = new ContentType(c.GetAttribute("ContentType"), true, c.GetAttribute("Extension")); | 
 |                 } | 
 |                 _contentTypes.Add(GetUriKey(ct.Match), ct); | 
 |             } | 
 |         } | 
 |  | 
 |         #region Methods | 
 |         internal ZipPackagePart CreatePart(Uri partUri, string contentType) | 
 |         { | 
 |             return CreatePart(partUri, contentType, CompressionLevel.Default); | 
 |         } | 
 |         internal ZipPackagePart CreatePart(Uri partUri, string contentType, CompressionLevel compressionLevel) | 
 |         { | 
 |             if (PartExists(partUri)) | 
 |             { | 
 |                 throw (new InvalidOperationException("Part already exist")); | 
 |             } | 
 |  | 
 |             var part = new ZipPackagePart(this, partUri, contentType, compressionLevel); | 
 |             _contentTypes.Add(GetUriKey(part.Uri.OriginalString), new ContentType(contentType, false, part.Uri.OriginalString)); | 
 |             Parts.Add(GetUriKey(part.Uri.OriginalString), part); | 
 |             return part; | 
 |         } | 
 |         internal ZipPackagePart GetPart(Uri partUri) | 
 |         { | 
 |             if (PartExists(partUri)) | 
 |             { | 
 |                 return Parts.Single(x => x.Key.Equals(GetUriKey(partUri.OriginalString),StringComparison.InvariantCultureIgnoreCase)).Value; | 
 |             } | 
 |             else | 
 |             { | 
 |                 throw (new InvalidOperationException("Part does not exist.")); | 
 |             } | 
 |         } | 
 |  | 
 |         internal string GetUriKey(string uri) | 
 |         { | 
 |             string ret = uri; | 
 |             if (ret[0] != '/') | 
 |             { | 
 |                 ret = "/" + ret; | 
 |             } | 
 |             return ret; | 
 |         } | 
 |         internal bool PartExists(Uri partUri) | 
 |         { | 
 |             var uriKey = GetUriKey(partUri.OriginalString.ToLower(CultureInfo.InvariantCulture)); | 
 |             return Parts.Keys.Any(x => x.Equals(uriKey, StringComparison.InvariantCultureIgnoreCase)); | 
 |         } | 
 |         #endregion | 
 |  | 
 |         internal void DeletePart(Uri Uri) | 
 |         { | 
 |             var delList=new List<object[]>();  | 
 |             foreach (var p in Parts.Values) | 
 |             { | 
 |                 foreach (var r in p.GetRelationships()) | 
 |                 { | 
 |                     if (UriHelper.ResolvePartUri(p.Uri, r.TargetUri).OriginalString.Equals(Uri.OriginalString, StringComparison.InvariantCultureIgnoreCase)) | 
 |                     {                         | 
 |                         delList.Add(new object[]{r.Id, p}); | 
 |                     } | 
 |                 } | 
 |             } | 
 |             foreach (var o in delList) | 
 |             { | 
 |                 ((ZipPackagePart)o[1]).DeleteRelationship(o[0].ToString()); | 
 |             } | 
 |             var rels = GetPart(Uri).GetRelationships(); | 
 |             while (rels.Count > 0) | 
 |             { | 
 |                 rels.Remove(rels.First().Id); | 
 |             } | 
 |             rels=null; | 
 |             _contentTypes.Remove(GetUriKey(Uri.OriginalString)); | 
 |             //remove all relations | 
 |             Parts.Remove(GetUriKey(Uri.OriginalString)); | 
 |              | 
 |         } | 
 |         internal void Save(Stream stream) | 
 |         { | 
 |             using var zipArchive = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true); | 
 |             /**** ContentType****/ | 
 |             var contentTypesEntry = zipArchive.CreateEntry("[Content_Types].xml"); | 
 |             using (var contentTypesWriter = new StreamWriter(contentTypesEntry.Open())) | 
 |             { | 
 |                 contentTypesWriter.Write(GetContentTypeXml()); | 
 |             } | 
 |             /**** Top Rels ****/ | 
 |             _rels.WriteZip(zipArchive, "_rels\\.rels"); | 
 |             ZipPackagePart ssPart=null; | 
 |             foreach(var part in Parts.Values) | 
 |             { | 
 |                 if (part.ContentType != ExcelPackage.contentTypeSharedString) | 
 |                 { | 
 |                     part.WriteZip(zipArchive); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     ssPart = part; | 
 |                 } | 
 |             } | 
 |             //Shared strings must be saved after all worksheets. The ss dictionary is populated when that workheets are saved (to get the best performance). | 
 |             if (ssPart != null) | 
 |             { | 
 |                 ssPart.WriteZip(zipArchive); | 
 |             } | 
 |             //return ms; | 
 |         } | 
 |  | 
 |         private string GetContentTypeXml() | 
 |         { | 
 |             StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">"); | 
 |             foreach (ContentType ct in _contentTypes.Values) | 
 |             { | 
 |                 if (ct.IsExtension) | 
 |                 { | 
 |                     xml.AppendFormat("<Default ContentType=\"{0}\" Extension=\"{1}\"/>", ct.Name, ct.Match); | 
 |                 } | 
 |                 else | 
 |                 { | 
 |                     xml.AppendFormat("<Override ContentType=\"{0}\" PartName=\"{1}\" />", ct.Name, GetUriKey(ct.Match)); | 
 |                 } | 
 |             } | 
 |             xml.Append("</Types>"); | 
 |             return xml.ToString(); | 
 |         } | 
 |         CompressionLevel _compression = CompressionLevel.Default; | 
 |         public CompressionLevel Compression  | 
 |         {  | 
 |             get | 
 |             { | 
 |                 return _compression; | 
 |             } | 
 |             set | 
 |             { | 
 |                 foreach (var part in Parts.Values) | 
 |                 { | 
 |                     if (part.CompressionLevel == _compression) | 
 |                     { | 
 |                         part.CompressionLevel = value; | 
 |                     } | 
 |                 } | 
 |                 _compression = value; | 
 |             } | 
 |         } | 
 |     } | 
 | } |