Извлечение файлов из Zip-архива программно с использованием С# и System.IO.Packaging

У меня есть куча ZIP файлов, которые отчаянно нуждаются в некоторой иерархической реорганизации и извлечения. В настоящее время я могу создать структуру каталогов и переместить zip файлы в нужное место. Мистический сыр, который мне не хватает, является частью, которая извлекает файлы из ZIP-архива.

Я видел статьи MSDN в классе ZipArchive и хорошо понимаю их. Я также видел способы VBScript для извлечения. Это не сложный класс, поэтому извлечение материала должно быть довольно простым. Фактически, он работает "в основном". Я включил свой текущий код ниже для справки.

 using (ZipPackage package = (ZipPackage)Package.Open(@"..\..\test.zip", FileMode.Open, FileAccess.Read))
 {
    PackagePartCollection packageParts = package.GetParts();
    foreach (PackageRelationship relation in packageParts)
    {
       //Do Stuff but never gets here since packageParts is empty.
    }
 }

Проблема, кажется, где-то в GetParts (или GetAnything, если на то пошло). Кажется, что пакет, открытый, пуст. Копая глубже, отладчик показывает, что частный член _zipArchive показывает, что на самом деле есть части. Части с правильными именами и всем остальным. Почему функция GetParts не возвращает их? Я попробовал придать открытию ZipArchive, и это не помогло. Хмм.

Ответ 1

Если вы работаете с ZIP файлами, вам может понадобиться изучить стороннюю библиотеку, чтобы помочь вам.

Например, DotNetZip, который был недавно обновлен. Текущая версия теперь v1.8. Вот пример создания zip:

using (ZipFile zip = new ZipFile())
{
  zip.AddFile("c:\\photos\\personal\\7440-N49th.png");
  zip.AddFile("c:\\Desktop\\2005_Annual_Report.pdf");
  zip.AddFile("ReadMe.txt");

  zip.Save("Archive.zip");
}

Здесь приведен пример обновления существующего zip; вам не нужно извлекать файлы для этого:

using (ZipFile zip = ZipFile.Read("ExistingArchive.zip"))
{
  // 1. remove an entry, given the name
  zip.RemoveEntry("README.txt");

  // 2. Update an existing entry, with content from the filesystem
  zip.UpdateItem("Portfolio.doc");

  // 3. modify the filename of an existing entry 
  // (rename it and move it to a sub directory)
  ZipEntry e = zip["Table1.jpg"];
  e.FileName = "images/Figure1.jpg";

  // 4. insert or modify the comment on the zip archive
  zip.Comment = "This zip archive was updated " + System.DateTime.ToString("G"); 

  // 5. finally, save the modified archive
  zip.Save();
}

вот пример, который извлекает записи:

using (ZipFile zip = ZipFile.Read("ExistingZipFile.zip"))
{
  foreach (ZipEntry e in zip)
  {
    e.Extract(TargetDirectory, true);  // true => overwrite existing files
  }
}

DotNetZip поддерживает многобайтовые символы в именах файлов, шифровании Zip, шифровании AES, потоках, Unicode, самораспаковывающихся архивах. Также ZIP64, для длин файлов больше 0xFFFFFFFF, или для архивов с более чем 65535 элементами.

бесплатно. с открытым исходным кодом

получить его на codeplex

Ответ 2

От MSDN,

В этом примере используется класс Package (в отличие от ZipPackage.) Поработав с обоими, я видел только, что flakiness случается, когда в zip файле происходит повреждение. Не обязательно коррупция, которая бросает экстрактор Windows или Winzip, но что-то, что у компонентов упаковки вызывает проблемы с обработкой.

Надеюсь, что это поможет, возможно, это может предоставить вам альтернативу отладке проблемы.

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

class ExtractPackagedImages
{
    static void Main(string[] paths)
    {
        foreach (string path in paths)
        {
            using (Package package = Package.Open(
                path, FileMode.Open, FileAccess.Read))
            {
                DirectoryInfo dir = Directory.CreateDirectory(path + " Images");
                foreach (PackagePart part in package.GetParts())
                {
                    if (part.ContentType.ToLowerInvariant().StartsWith("image/"))
                    {
                        string target = Path.Combine(
                            dir.FullName, CreateFilenameFromUri(part.Uri));
                        using (Stream source = part.GetStream(
                            FileMode.Open, FileAccess.Read))
                        using (Stream destination = File.OpenWrite(target))
                        {
                            byte[] buffer = new byte[0x1000];
                            int read;
                            while ((read = source.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                destination.Write(buffer, 0, read);
                            }
                        }
                        Console.WriteLine("Extracted {0}", target);
                    }
                }
            }
        }
        Console.WriteLine("Done");
    }

    private static string CreateFilenameFromUri(Uri uri)
    {
        char [] invalidChars = Path.GetInvalidFileNameChars();
        StringBuilder sb = new StringBuilder(uri.OriginalString.Length);
        foreach (char c in uri.OriginalString)
        {
            sb.Append(Array.IndexOf(invalidChars, c) < 0 ? c : '_');
        }
        return sb.ToString();
    }
}

Ответ 3

От " ZipPackage Class" (MSDN):

Пока пакеты хранятся как Zip файлы * через класс ZipPackage, все Zip файлы не являются ZipPackages. У ZipPackage есть особые требования, такие как имена файлов (часть), совместимые с URI, и файл [Content_Types].xml, который определяет типы MIME для всех файлов, содержащихся в Пакете. Класс ZipPackage не может использоваться для открытия архивных файлов Zip, которые не соответствуют стандарту Open Packaging Conventions.

Подробнее см. Раздел 9.2 "Сопоставление с архивом ZIP" стандарта ECMA International "Стандарты открытых упаковок", http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%20Part%202%20(DOCX).zip ( 342Kb) или http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%20Part%202%20(PDF).zip (1.3Mb)

* Вы можете просто добавить ".zip" к расширению любого файла на основе ZipPackage (.docx,.xlsx,.pptx и т.д.), чтобы открыть его в своей любимой утилите Zip.

Ответ 4

У меня была такая же проблема! Чтобы вернуть метод GetParts(), мне пришлось добавить файл [Content_Types].xml в корень архива с "по умолчанию" node для каждого добавленного расширения файла. Как только я добавил это (просто используя Проводник Windows), мой код смог прочитать и извлечь архивированное содержимое.

Более подробную информацию о файле [Content_Types].xml можно найти здесь:

http://msdn.microsoft.com/en-us/magazine/cc163372.aspx - Ниже приведен пример файла, приведенного ниже на рисунке 13 этой статьи.

var zipFilePath = "c:\\myfile.zip"; 
var tempFolderPath = "c:\\unzipped"; 

using (Package package = ZipPackage.Open(zipFilePath, FileMode.Open, FileAccess.Read)) 
{ 
    foreach (PackagePart part in package.GetParts()) 
    { 
        var target = Path.GetFullPath(Path.Combine(tempFolderPath, part.Uri.OriginalString.TrimStart('/'))); 
        var targetDir = target.Remove(target.LastIndexOf('\\')); 

        if (!Directory.Exists(targetDir)) 
            Directory.CreateDirectory(targetDir); 

        using (Stream source = part.GetStream(FileMode.Open, FileAccess.Read)) 
        { 
            FileStream targetFile = File.OpenWrite(target);
            source.CopyTo(targetFile);
            targetFile.Close();
        } 
    } 
} 

Примечание: этот код использует метод Stream.CopyTo в .NET 4.0

Ответ 5

Я согласен с Чисо. System.IO.Packaging неудобно при работе с универсальными zip файлами, поскольку он предназначен для документов Office Open XML. Я бы предложил использовать DotNetZip или SharpZipLib

Ответ 6

(Это в основном перефразирование этого ответа)

Оказывается, что System.IO.Packaging.ZipPackage не поддерживает PKZIP, поэтому, когда вы открываете "общий" ZIP файл, "части" не возвращаются. Этот класс поддерживает только некоторые специфические особенности ZIP файлов (см. Комментарии внизу описание MSDN), используемые в качестве пакетов услуг Windows Azure до SDK 1.6 - вот почему, если вы распакуете пакет услуг, а затем переупакуете его, используя упаковщик Info-ZIP, он станет недействительным.