Make a ZIP archive flat

Your zip archive may have other zip archives inside it. One may want to extract the nested zip archive’s contents into the parent archive to get a flat structure.

Current archive structure
outer.zip
 ├first.txt
 ├inner.zip
 │ ├game.exe
 │ └subitem.bin
 └picture.gif
Desired archive structure
flatten.zip
 ├first.txt
 ├picture.gif
 ├game.exe
 └subitem.bin

If you are not familiar with Aspose.Zip read how to extract zip archive first.

Overal explanation

First, we need to list all the entries of the archive. Regular entries should be kept as they are, we should not even decompress them. Entries that are archives themselves need to be extracted to memory and removed from the outer archive. Their content needs to be included in the main archive.

Detecting entries that are archives

Lets decide which entries are archives itself. We can figure this out by extension of entry name. Later we will remove those entries from main archive, so keep such entries in a list.

1if (entry.Name.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) {
2    entriesToDelete.Add(entry);
3    ...
4}

Extracting entry’s content to memory

Aspose.Zip allows extracting the content of zip entry into any writable stream, not only to a file. So, we can extract a nested archive to a memory stream.

Please note: the virtual memory must be big enough to keep all extracted content.

1MemoryStream innerCompressed = new MemoryStream();
2entry.Open().CopyTo(innerCompressed); 

After that innerCompressed stream contains the inner archive itself. The Archive constructor allows decompressing the stream provided. So, we can extract it as well:

1Archive inner = new Archive(innerCompressed);

Excluding entries

We can remove an entry from zip archive with particular method.

1foreach (ArchiveEntry e in entriesToDelete) { outer.DeleteEntry(e); }

Put it all together

Here is the complete algorithm.

 1    using (Archive outer = new Archive("outer.zip"))
 2    {
 3        List<ArchiveEntry> entriesToDelete = new List<ArchiveEntry>();
 4        List<string> namesToInsert = new List<string>();
 5        List<MemoryStream> contentToInsert = new List<MemoryStream>();
 6
 7        foreach (ArchiveEntry entry in outer.Entries)
 8        {
 9            if (entry.Name.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) // Find an entry which is an archive itself
10            {
11                entriesToDelete.Add(entry); // Keep reference to the entry in order to remove it from the archive later
12                MemoryStream innerCompressed = new MemoryStream();
13                entry.Open().CopyTo(innerCompressed); //This extracts the entry to a memory stream
14
15                using (Archive inner = new Archive(innerCompressed)) // We know that content of the entry is an zip archive so we may extract
16                {
17                    foreach (ArchiveEntry ie in inner.Entries) // Loop over entries of inner archive
18                    {
19                        namesToInsert.Add(ie.Name); // Keep the name of inner entry.
20                        MemoryStream content = new MemoryStream();
21                        ie.Open().CopyTo(content);
22                        contentToInsert.Add(content); // Keep the content of inner entry.
23                    }
24                }
25            }
26        }
27
28        foreach (ArchiveEntry e in entriesToDelete) 
29            outer.DeleteEntry(e); // Delete all the entries which are archives itself
30
31        for (int i = 0; i < namesToInsert.Count; i++)    
32            outer.CreateEntry(namesToInsert[i], contentToInsert[i]); // Adds entries which were entries of inner archives
33        
34        outer.Save("flatten.zip");
35    }
Subscribe to Aspose Product Updates

Get monthly newsletters & offers directly delivered to your mailbox.