Friday, November 12, 2010

Creating Zip files with System.IO.Packaging namespace

Originally created to support working with Open Office Xml documents, it’s possible to use this namespace to create zip files as well.
The only drawbacks I have found is that you end up with an additional xml file at the root of your zip file called [Content_Types].xml which lists the mapping of file extension to mime type, and you cannot have spaces or non-ascii characters in your filenames.
If you can live with this, there is no need to rely on an external library.

In Visual Studio create a new project, and make sure you have a reference to the WindowsBase assembly.
image
How it works is that you add an entry to the zip file with the relative path, the type of file and compression mode.
If your file path is c:\folder\a\filename.ext then the relative path in uri form would be /folder/a/filename.ext.
This small function takes care of the conversion from a file path to a relative uri.
private static Uri GetRelativeUri(string currentFile)
{
string relPath = currentFile.Substring(currentFile.IndexOf('\\')).Replace('\\', '/').Replace(' ', '_');
return new Uri( relPath, UriKind.Relative);
}

Creating a new Zip file is a matter of opening a new file.

string zipFileName = @"c:\temp\test.zip";
Package package = ZipPackage.Open(zipFileName, FileMode.Create)
You can also send in a stream instead of a path.
Adding files is a matter of declaring a new PackagePart, and writing the data to it.
PackagePart packagePart = package.CreatePart(relUri, System.Net.Mime.MediaTypeNames.Application.Octet, CompressionOption.Maximum);

I define the file as System.Net.Mime.MediaTypeNames.Application.Octet, which says it’s a binary file of some sort. And I set the compression to maximum. Other options are superfast, fast, normal and not compressed.

I’ve included a short test program below which iterates a folder, and adds all subfolders and files to a zip file.

using System;
using System.IO;
using System.IO.Packaging;
using System.Text;

class Program
{
static void Main(string[] args)
{
string zipFileName = @"c:\temp\test.zip";

using (Package package = ZipPackage.Open(zipFileName, FileMode.Create))
{
string startFolder = @"C:\temp\ephortedata";

foreach (string currentFile in Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories))
{
Console.WriteLine("Packing " + currentFile);
Uri relUri = GetRelativeUri(currentFile);

PackagePart packagePart =
package.CreatePart(relUri, System.Net.Mime.MediaTypeNames.Application.Octet, CompressionOption.Maximum);
using (FileStream fileStream = new FileStream(currentFile, FileMode.Open, FileAccess.Read))
{
CopyStream(fileStream, packagePart.GetStream());
}
}
}
}

private static void CopyStream(Stream source, Stream target)
{
const int bufSize = 16384;
byte[] buf = new byte[bufSize];
int bytesRead = 0;
while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
target.Write(buf, 0, bytesRead);
}

private static Uri GetRelativeUri(string currentFile)
{
string relPath = currentFile.Substring(currentFile
.IndexOf('\\')).Replace('\\', '/').Replace(' ', '_');
return new Uri(RemoveAccents(relPath), UriKind.Relative);
}

private static string RemoveAccents(string input)
{
string normalized = input.Normalize(NormalizationForm.FormKD);
Encoding removal = Encoding.GetEncoding(Encoding.ASCII.CodePage,new EncoderReplacementFallback(""),new DecoderReplacementFallback(""));
byte[] bytes = removal.GetBytes(normalized);
return Encoding.ASCII.GetString(bytes);
}
}