Andy in the Cloud

From BBC Basic to and beyond…


Handling Office Files and Zip Files in Apex – Part 1

I recently found a number of developers asking questions about Zip file handling in Apex, which as most find out pretty soon, does not exist. Statement governor concerns aside, nor is there any binary data types to make implementing support for it possible. Most understandably most want to stay on platform and not call out to some external service for this. And rightly so!

Another developer and I recently had the same need before the API provider decided to handle compression over HTTP. However while subsequently doing some work to get the Metadata API working from Apex, which also requires zip support in places. I came across a partial native solution and in addition to that I’ve also had in mind to try out some day the JSZip Javascript library.

And so I decided to write some blog entries covering these two approaches and hopefully help a few people out…

Approach A: Using Static Resources and Metadata API

A static resource is as the name suggests some that changes rarely in your code, typically used for .js, .css and images. Salesforce allows you to upload these type of files individually or within a zip file. Using its development UI and tools.

Allowing you then reference in your Visualforce pages like this

$URLFOR($Resource.myzip, '/folder/file.js').

Further more you can also do this in Apex.

PageReference somefileRef = 
   new PageReference('/resource/myzip/folder/file.js');
Blob contentAsBlob = somefileRef.getContent();
String contentAsText = contextAsBlob.toString();

So in effect Salesforce does have a built in Zip handler, at least for unzipping files anyway. The snag is, uploading a zip file dynamically once your user has provided it to you. If you review the example code that calls the Metadata API from Apex you might have spotted an example of doing just this. To create a Static Resource you can do the following.

MetadataService.MetadataPort service = createService(); 
MetadataService.StaticResource staticResource = 
    new MetadataService.StaticResource();
staticResource.fullName = 'test';
staticResource.contentType = 'text';
staticResource.cacheControl = 'public';
staticResource.content = 
   EncodingUtil.base64Encode(Blob.valueOf('Static stuff'));
MetadataService.AsyncResult[] results = 
      new List; { staticResource });

They key parts are the assignment of the ‘contentType’ and ‘content’ members. The ‘content’ member is a Base64 encoded string. In the example above its a static peace of text, however this could easily be the zip file the user has just uploaded for you and then Base64 encoded using the Apex EncodingUtil. You also need to set the ‘contentType’ to ‘application/zip’.

This example presents a user with the usual file upload, you will also need to ask or know what the path of the file is you want to extract. Of course if you know this or can infer it e.g. via file extension such as .docx or .xslx, which uses the Office Open XML format, then your good to go.

I’ve shared the code for this here.

Known Issues: If your zip file contains files with names that contain spaces or special characters, I have found issues with this approach. Since this also applies to URLFOR and generating links in VF pages, I plan to raise a support case to see if this is a bug or limitation. Also keep in mind the user will need to have the Author Apex permission enabled on their profile to call the Metadata API.

Summary and Part 2

This blog entry covered only unzip for known file types, but what if you wanted to have the code inspect the zip contents? Part 2 will extend some of the components I’ve been using in the apex-mdapi to use the JSZip library, examples of which are here and here. In extending those components, I’ll be looking at making them use JavaScript Remoting and local HTML5 file handling to unzip the file locally in the page and transmit the files to the server via an Apex Interface the controller implements. Likewise I want to use the same approach to prepare a zipped file by requesting zip content from the server.