Update an existing ooxml file with C# and System.IO.Compression.ZipArchive - c#

I am trying to update an nearly empty docx/ooxml file. I dont get an exception, the code is running through but the file is not modified.
I think I am missing something :/
The code below is not the actual code, but a simplified example. It is also running without an exception but the file is not updated.
using (var fs = new FileStream(docxFile, FileMode.Open))
{
var archive = new ZipArchive(fs, ZipArchiveMode.Update);
foreach (var entry in archive.Entries)
{
if (entry.Name == "[Content_Types].xml")
{
using (var entryStream = entry.Open())
{
var doc = new XmlDocument();
doc.Load(entryStream);
var element = doc.CreateElement("Override");
element.SetAttribute("PartName", "/docProps/example.xml");
doc.DocumentElement.AppendChild(element);
doc.Save(entryStream);
entryStream.Flush();
}
break;
}
}
}

Seems like it was late yesterday :(
Either use using with the ZipArchive instance or calling Dispose after the loop will do the job ;)
using (var fs = new FileStream(docxFile, FileMode.Open))
{
using (var archive = new ZipArchive(fs, ZipArchiveMode.Update))
{
foreach (var entry in archive.Entries)
{
if (entry.Name == "[Content_Types].xml")
{
using (var entryStream = entry.Open())
{
var doc = new XmlDocument();
doc.Load(entryStream);
var element = doc.CreateElement("Override");
element.SetAttribute("PartName", "/docProps/example.xml");
doc.DocumentElement.AppendChild(element);
doc.Save(entryStream);
entryStream.Flush();
}
break;
}
}
}
}

Related

Moving file directly into zip file

I'm trying move Source_File.mp4 into Destination_Zip.zip directly. I'm currently trying it by creating a new entry Transfer.mp4 and copying the bytes over. The code runs to the end but the file is never added to the ZIP file. I'm not sure if I'm missing something or if this isn't possible.
string sourceFile = basePath + "Source_File.mp4";
string destinationZip = basePath + "Desintation_Zip.zip";
using (var file = File.OpenRead(destinationZip))
{
MemoryStream memoryStream = new MemoryStream();
memoryStream.SetLength(file.Length);
file.Read(memoryStream.GetBuffer(), 0, (int)file.Length);
using (var zip = new ZipArchive(memoryStream, ZipArchiveMode.Update))
{
var entry = zip.CreateEntry("Transfer.mp4");
using (var destinationStream = entry.Open())
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(destinationStream);
}
}
}
My guess is that even though you've read the file and altered it, you didn't write anything back, i.e. changes persisted to MemoryStream, and didn't go anywhere after that.
You could try this (assumes you're using System.IO.Compression.ZipFile):
using (var zip = ZipFile.Open(destinationZip, ZipArchiveMode.Update))
{
var entry = zip.CreateEntry("Transfer.mp4");
using (var destinationStream = entry.Open())
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(destinationStream);
}
}
Or, if you're using ICSharpCode.SharpZipLib.Zip.ZipFile, do this:
using (var fileStream = new FileStream(destinationZip, FileMode.Open))
using (var zip = new ZipArchive(fileStream, ZipArchiveMode.Update))
{
var entry = zip.CreateEntry("Transfer.mp4");
using (var destinationStream = entry.Open())
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(destinationStream);
}
}
I'm still not sure why my initial code did not work, but I got it working by doing this instead:
string sourceFile = basePath + "Source_File.mp4";
string destinationZip = basePath + "Desintation_Zip.zip";
using (var destinationZipFileStream = new FileStream(destinationZip, FileMode.Open))
using (var destinationZipArchive = new ZipArchive(destinationZipFileStream, ZipArchiveMode.Update))
{
ZipArchiveEntry destinationWriter = destinationZipArchive.CreateEntry("Transfer.mp4");
using (var writer = new StreamWriter(destinationWriter.Open()))
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(writer.BaseStream);
}
}
Note: you will need to double check to make sure the file doesn't exist already or else you'll make duplicate files this way.

Re-create zip file from another zip file

Given a zip file, I need to re-create it with a specified compression level (eg, no compression).
I'm nearly there, but get the error:
Failed: Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
If I save the recreated zip file to windows, it looks like it's correct (correct file size, entries all exist with correct file sizes) but none of the files are extractable.
public static byte[] ReCompress(byte[] originalArchive, CompressionLevel newCompressionLevel)
{
var entries = new Dictionary<string, byte[]>();
///////////////////////////
// STEP 1: EXTRACT ALL FILES
///////////////////////////
using (var ms = new MemoryStream(originalArchive))
using (var originalZip = new ZipArchive(ms, ZipArchiveMode.Read))
{
foreach (var entry in originalZip.Entries)
{
var isFolder = entry.FullName.EndsWith("/");
if (!isFolder)
{
using (var stream = entry.Open())
using (var entryMS = new MemoryStream())
{
stream.CopyTo(entryMS);
entries.Add(entry.FullName, entryMS.ToArray());
}
}
else
{
entries.Add(entry.FullName, new byte[0]);
}
}
}
///////////////////////////
// STEP 2: BUILD ZIP FILE
///////////////////////////
using (var ms = new MemoryStream())
using (var newArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var uncompressedEntry in entries)
{
var newEntry = newArchive.CreateEntry(uncompressedEntry.Key, newCompressionLevel);
using (var entryStream = newEntry.Open())
using (var writer = new BinaryWriter(entryStream, Encoding.UTF8))
{
writer.Write(uncompressedEntry.Value);
}
}
return ms.ToArray();
}
}
At the end of the function if I do:
File.WriteAllBytes(#"D:\test.zip", ms.ToArray());
It creates a correctly structure archive sized 90mb but no files are extractable.
If I end with return ms.ToArray() it returns a ~130kb byte array.
Zip archive is broken because you read its content from MemoryStream before it is finished. In order to finish archive creation you need to call newArchive.Dispose() before calling ms.ToArray().
In this particular case you can do it like this:
using (var ms = new MemoryStream())
{
using (var newArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var uncompressedEntry in entries)
{
var newEntry = newArchive.CreateEntry(uncompressedEntry.Key, newCompressionLevel);
using (var entryStream = newEntry.Open())
using (var writer = new BinaryWriter(entryStream, Encoding.UTF8))
{
writer.Write(uncompressedEntry.Value);
}
}
}
return ms.ToArray();
}

Edit ZipArchive in memory .NET

I'm trying to edit a XmlDocument file contained in a Zip file:
var zip = new ZipArchive(myZipFileInMemoryStream, ZipArchiveMode.Update);
var entry = zip.GetEntry("filenameToEdit");
using (var st = entry.Open())
{
var xml = new XmlDocument();
xml.Load(st);
foreach (XmlElement el in xml.GetElementsByTagName("Relationship"))
{
if(el.HasAttribute("Target") && el.GetAttribute("Target").Contains(".dat")){
el.SetAttribute("Target", path);
}
}
xml.Save(st);
}
After executing this code the contained file is not changed. IF instead of xml.Save(st); I write the xml to disk, I got the edited one.
Why is the edited file not written to the zip? How do I fix it?
EDIT:
I updated the code:
var tmp = new MemoryStream();
using (var zip = new ZipArchive(template, ZipArchiveMode.Read, true))
{
var entry = zip.GetEntry("xml");
using (var st = entry.Open())
{
var xml = new XmlDocument();
xml.Load(st);
foreach (XmlElement el in xml.GetElementsByTagName("Relationship"))
{
if (el.HasAttribute("Target") && el.GetAttribute("Target").Contains(".dat"))
{
el.SetAttribute("Target", path);
}
}
xml.Save(tmp);
}
}
using (var zip = new ZipArchive(template, ZipArchiveMode.Update, true))
{
var entry = zip.GetEntry("xml");
using (var st = entry.Open())
{
tmp.Position = 0;
tmp.CopyTo(st);
}
}
In this way the zip file is edited, but it works only if the length of the streams is equal. If tmp is shorter the rest of the st is still in the file...
Hints?
I use this code to create a Zip InMemory (using the DotNetZip Library) :
MemoryStream saveStream = new MemoryStream();
ZipFile arrangeZipFile = new ZipFile();
arrangeZipFile.AddEntry("test.xml", "content...");
arrangeZipFile.Save(saveStream);
saveStream.Seek(0, SeekOrigin.Begin);
saveStream.Flush(); // might be useless, because it's in memory...
After that I have a valid Zip inside the MemoryStream. I'm not sure why I added the Flush() - I would guess this is redundant.
To edit an existing Zip you could read it in a MemoryStream and instead of creating "new ZipFile()" use "new ZipFile(byteArray...)".

C# Create ZIP Archive with multiple files

I'm trying to create a ZIP archive with multiple text files as follows:
Dictionary<string, string> Values = new Dictionary<string, string>();
using (var memoryStream = new MemoryStream())
{
string zip = #"C:\Temp\ZipFile.zip";
foreach (var item in Values)
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var file = archive.CreateEntry(item.Key + ".txt");
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write(item.Value);
}
}
}
using (var fileStream = new FileStream(zip, FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
However, the ZIP is created with only the last text file, what's wrong?
You are creating ZipArchive on each iteration. Swapping foreach and using should solve it:
Dictionary<string, string> Values = new Dictionary<string, string>();
using (var memoryStream = new MemoryStream())
{
string zip = #"C:\Temp\ZipFile.zip";
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var item in Values)
{
var file = archive.CreateEntry(item.Key + ".txt");
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write(item.Value);
}
}
}
using (var fileStream = new FileStream(zip, FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
Each time your foreach loop runs it has the ZipArchiveMode as Create. That should be the problem, so it generates new zip everytime with new content on it, such as the last text file. Create an exception for each loop run after the first one it should be solved.

Getting "Operation not permitted on IsolatedStorageFileStream." Error

"Operation not permitted on IsolatedStorageFileStream." is pointing towards the line of codes:
var fileStream = storage.OpenFile(item.FileName, FileMode.Open, FileAccess.Read)
in the codes below:
private void OnReadSelected()
{
IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
List<FileItem> readItems = new List<FileItem>();
foreach (var item in Files)
{
if (item.IsChecked)
if (storage.FileExists(item.FileName))
{
storage.DeleteFile(item.FileName);
readItems.Add(item);
}
}
foreach (var item in readItems)
using (var fileStream = storage.OpenFile(item.FileName, FileMode.Open, FileAccess.Read))
{
using (var reader = new StreamReader(fileStream))
{
item.FileName = reader.ReadLine();
item.FileText1 = reader.ReadLine();
item.RdbText1 = reader.ReadLine();
}
}
}
Am I to use another derivative besides StreamReader?
foreach (var item in Files)
{
if (item.IsChecked)
if (storage.FileExists(item.FileName))
{
storage.DeleteFile(item.FileName);
readItems.Add(item);
}
}
You have used this code to delete some files from the store depending upon the condition if(item.IsChecked). And you are adding those items to the readItems collection.
But in this code
foreach (var item in readItems)
using (var fileStream = storage.OpenFile(item.FileName, FileMode.Open, FileAccess.Read))
{
using (var reader = new StreamReader(fileStream))
{
item.FileName = reader.ReadLine();
item.FileText1 = reader.ReadLine();
item.RdbText1 = reader.ReadLine();
}
}
you are trying to open the files which you have just deleted from the store.
So, you are getting the exception Operation not permitted on IsolatedStorageFileStream as the files doesn't exist in the store.

Categories