0

I'm getting a file from a server with AJAX (Angular).The file is a simple XLSX document, sent like this:

ob_start();
$file = \PHPExcel_IOFactory::createWriter($xls, 'Excel2007');
$file->save('php://output');
$response->setContent(ob_get_clean());
$response->headers->replace(array(
    'Content-Type'          => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'Content-Disposition'   => 'attachment;filename=file.xlsx"'
));

When I make a request from frontend, I use Accept header too. Then I save the file with angular-file-saver using FileSaver.js and Blob.js. But the received file is corrupt and I can't open it in Excel: it's size is (for example) 12446 bytes, but Chrome's DevTools Network tab shows responses Content-Length header as 7141 bytes.

How can I solve this problem?

UPD: I'm sending a request like this:

$http.get(baseURL + '/' + entity + '/export/?' + condition + sort, {
          headers: {'Accept': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8'}
        });

and downloading file just like this:

var data = new Blob([response.data], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'});
          FileSaver.saveAs(data, 'file.xlsx');
  • Have you checked the content of the corrupt file? What's in it? Is there an error message there? Is it compressed? Is it simply truncated? – kichik Feb 04 '16 at 21:38
  • There's no error messages, no server-side compression (except format, xlsx is a zip archive if my memory serves), no truncation - result file size is bigger than Content-Length header data. I think there is some problem either with multibyte encodings or with binary data reception. But Google don't helps here at all. – thatside_ua Feb 04 '16 at 21:46
  • So what's actually in the downloaded file? Comparing it to the original may help understand what happened. – kichik Feb 04 '16 at 21:47
  • I've compared files and the biggest difference is that downloaded file being read like utf-8 shows most symbols like question mark in a square (i use Sublime), and original one shows them just like question marks. In hex almost all bytes are different, but in text view there are some equal text insertions like [Content_Types].xml (seems this is part of file's format). It's really a problem with receiving binary data in browser or something like this. – thatside_ua Feb 04 '16 at 22:14
  • I'd try figuring out which component causes this first. For example, you can write to a file, read it back and output that. You can then compare that output to the result and see if it's the Excel writer or something else. You can also try downloading without AJAX, etc. – kichik Feb 04 '16 at 22:21
  • I'll try first variant. But I've forgot about some other things: previously this file was downloaded via links - I've made a link to URL I use for this request programatically and clicked it and it worked well, but a blank page was opened. But now client wants to use only ajax calls and doesn't want to see this blank page. – thatside_ua Feb 04 '16 at 22:30
  • Sounds like the issue is on the AJAX side. You should include that code as well. – kichik Feb 04 '16 at 22:31
  • Edited the question. I've used HTML File api before without any components, and it does not help too. – thatside_ua Feb 04 '16 at 22:40

1 Answers1

0

The way I got around the problem was using plain JS AJAX, instead of AngularJS. (There might be a problem with AngularJS and JQuery handling binary responses.)

This should work:

var request = new XMLHttpRequest();
request.open('GET', 'http://yourserver/yourpath', true);
request.responseType = 'blob';

request.onload = function (e) {
    if (this.status === 200) {
        var blob = this.response;
        if (window.navigator.msSaveOrOpenBlob) {
            var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            window.navigator.msSaveBlob(blob, fileNamePattern.exec(request.getResponseHeader("content-disposition"))[1]);
        } else {
            var downloadLink = window.document.createElement('a');
            var contentTypeHeader = request.getResponseHeader("Content-Type");
            var b = new Blob([blob], { type: contentTypeHeader });
            downloadLink.href = window.URL.createObjectURL(b);
            var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            downloadLink.download = fileNamePattern.exec(request.getResponseHeader("content-disposition"))[1];
            document.body.appendChild(downloadLink);
            downloadLink.click();
            document.body.removeChild(downloadLink);
            window.URL.revokeObjectURL(b);
        }
    }
};
request.send();

Code is based on this and this.

FYI, I found that new Blob([response.data], ...) returns almost double the size of response.data when response.data is not returned as blob, but text/plain or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. To get around it, you need to pass it an array of bytes instead:

    var i, l, d, array;
    d = this.result;
    l = d.length;
    array = new Uint8Array(l);
    for (var i = 0; i < l; i++){
        array[i] = d.charCodeAt(i);
    }
    var b = new Blob([array], {type: 'application/octet-stream'});
    window.location.href = URL.createObjectURL(b);

Code is from here.

Anyways, since the AJAX response is not correct using AngularJS, you won't get a valid xlsx file this way. You need to go with vanilla JS.

show
  • 69
  • 1
  • 4