Andy in the Cloud

From BBC Basic to Force.com and beyond…

Handling Office Files and Zip Files in Apex – Part 1

23 Comments

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 = 
    service.create(
      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.

23 thoughts on “Handling Office Files and Zip Files in Apex – Part 1

  1. Pingback: Handling Office Files and Zip Files in Apex – Part 2 | andrewfawcett

  2. Hi Andy – looks like a wondefully useful bit of code – I’ve imported it to test it out but I get “IO Exception: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = https://c.eu2.visual.force.com/services/Soap/m/25.0” I’m running as a system admin with Author Apex.

    The error comes here:

    Error is in expression ‘{!upload}’ in component in page importstaticresource

    If there anything else I need to set up?

    Many thanks for your help.

    Alex

    • Hi Alex, thanks for the feedback. Even if you are running as Sys Admin with Author Apex, you still need to provide the URL to the Remote Site settings (Setup>Security>Remote Site Settings). Give it the full URL and it will take what it needs. It sounds odd but you do need to authorise your Apex code to call out to a Salesforce provided endpoint. If you do this, it should work just fine. Note that this approach has some limits on the path into the zip file your looking for. Take a look at Part 2 for something more flexible if you think you might hit this.

      • Thanks Andy, that’s done the trick, as well as teaching me a bit more about end points!

        Running through part 2 now, which does indeed look more flexible – is the size limit set by JSZip or SalesForce?

      • Excellent, your very welcome! Looking at what the JSLib author says about the size of files it can handle. I suspect Salesforce will limit the size before the JSZip library starts to have an impact.

      • Hello sir,
        I m new in this technology i have download your example and upload excel file but my question is ,
        it is possible to convert excel file to csv file after uploaded…?
        plz help me …

        Thanx,
        Parthiv

    • Hi am new to salesforce. If i want to import this to my developer org how would i do it. Please suggest.

      • – Download the Force.com IDE from Salesforce
        – Download the source via this link as a zip and unzip it https://github.com/financialforcedev/apex-zip/archive/master.zip
        – Start a Force.com IDE, from the File menu select Import > General > Existing Projects into Workspace
        – Point the wizard to the /apex-zip subfolder, it will prompt you for you DE org login details.
        – Once it returns to the Force.com IDE workspace, it should start in the background uploading the files (see bottom right corner), you will also see any errors in Eclipse Problems pane.

  3. BTW, would love to know more about your use case, if you can share something?

  4. Hi Alex,
    I am getting below issue while upload the file via Unzip via Static Resource Tab:
    Issue is:

    System.CalloutException: IO Exception: Unauthorized endpoint, please check Setup->Security->Remote site settings. endpoint = https://c.na15.visual.force.com/services/Soap/m/25.0
    Error is in expression ‘{!upload}’ in component in page importstaticresource

    Class.MetadataService.MetadataPort.listMetadata: line 1498, column 1
    Class.ImportStaticResourceController.upload: line 46, column 1

    Could you plz help me for the above issue

  5. Hi Andy, I have created a zip attachment with some text files in it. And i am trying to access the attachment from the VF page. Can you please suggest as to how should I go about??

  6. Hii Andrew Fawcett , your post is great … But i found that the library you added in the unzip component are not in the jszip …please please send me the libraries if you have . my email id is nitin.om2@gmail.com thanks in advance

  7. I have followed your post for unzip but i am unable to make a record on choosing a zip file and i also do not find the deflate.js please answer…

  8. Hi Andrew,

    I have requirement in which I need to dynamically create a zip file containing multiple csv file and send the zip file as an attachment. Can you let me know in this dimension.I want to perform the process using apex and not using libraries. Is it necessary to create Static resource

    • The only way on Force.com i have found, as per Part 2 of this blog, is to use a JavaScript library hosted in a Visualforce page. In my part 2 blog i try to make this as easy as possible by wrapping all this in Visualforce Components.

      The Static Resource approach only works for reading (some) zip files, but even then the draw back is the user needs to have admin permissions as that solution uses the Metadata API.

      The best all round solution for create and read, if you can tolerate needing to use the browser to do the processing is the one i describe in part 2 of this blog.

  9. This code (based on your sample above) works great in developer console

    String resourceName = ‘DealcAssetManagementLayout’;

    List resourceList= [SELECT Name, Description, NamespacePrefix, SystemModStamp FROM StaticResource WHERE Name = :resourceName];
    String namespace = resourceList[0].NamespacePrefix;
    resourceName = ‘/resource/’ + resourceList[0].SystemModStamp.getTime() + ‘/’ + (namespace != null && namespace != ” ? namespace + ‘__’ : ”) + resourceName;
    System.debug(resourceName);

    PageReference somefileRef = new PageReference(resourceName);
    Blob contentAsBlob = somefileRef.getContent();
    String layoutString = contentAsBlob.toString();
    System.debug(layoutString);

    But firing it from a trigger gives me a redirect page

    01:53:41:000 USER_DEBUG if (this.SfdcApp && this.SfdcApp.projectOneNavigator) { SfdcApp.projectOneNavigator.handleRedirect(‘https://login.salesforce.com/?ec=302&startURL=%2Fvisualforce%2Fsession%3Furl%3Dhttps%253A%252F%252Fc.na10.visual.force.com%252Fresource%252F1417627556000%252F%252Fresource%252F1417627556000%252FDealcAssetManagementLayout%253Finline%253D1’); } else …

    Am I doing something wrong?

  10. Maybe this is why ==> System.VisualforceException: Getting content from within triggers is currently not supported. So somefileRef.getContent(); will not work.

    https://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_PageReference_getContent.htm

  11. LOL….this works. Well, I hope it helps somebody out there.

    List resourceList= [SELECT Name, Body, Description, NamespacePrefix, SystemModStamp FROM StaticResource WHERE Name = :resourceName];

    Blob contentAsBlob = resourceList[0].Body;
    String layoutString = contentAsBlob.toString();
    System.Debug(layoutString);

  12. Hi Andrew,

    Wonderful article!
    I am trying to create a app where you select a zip file on visualforce and we save each file from zip as a attachment to a contact. The zip will mostly contain doc, docx and pdf files.
    I could not fully follow your component, maybe because I am just starting with salesforce development…
    Please answer below questions if you can:

    1) For the above requirement, how should I configure FileReader to read the Zipfile? I am currently using readAsArrayBuffer()
    2) I am initiating JSZIP as below, is this correct?
    var zip = new JSZip(e.target.result, {type:’base64′});
    3) While iterating through the zip files, I call a remote action method, and I am confused as to how the file body should be sent…
    From visualforce, should I send the body as zipEntry.asBinary() or asText() or something else?
    In the remote method, what variable type should I use to accept the file body? I am getting error if I use Blob, and the file gets corrupted if I use String.

    Any help with be greatly appreciated 🙂

    Thanks,
    Arun K

    • Good news, what you want to do is pretty much the same as the demo code linked in this blog! Suggest you deploy to a test de org and experience it m.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s