blob: c47d5eaf5cc0677c8574a77dc654de88171b0323 [file] [log] [blame]
/*******************************************************************************
* 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);
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. If the file is encrypted, please supply the password in the constructor."));
}
if (!hasContentTypeXml)
{
throw (new InvalidDataException("The file is not an valid Package file. If the file is encrypted, please supply the password in the constructor."));
}
}
}
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);
/**** 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();
}
internal void Flush()
{
}
internal void Close()
{
}
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;
}
}
}
}