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 }