Encoding issues when exporting as CSV - javascript

I have this code to export a filtered html table. I have two issues with it which I can't figure out:
1) I get "•" instead of a bullet point • in my exported .csv file. I've tried changing charset from UTF-8 to iso-8859 in meta head and this script but this didn't help.
2) When I have quotes in the sentence it treats it as a comma and breaks the sentence, putting it into a next cell. Example:
I am a "good" man with a good reputation would be broken into something like:
A1 I am a "good" man with
B1 good reputation
I can't work it out why this happens?Thanks!
<script type='text/javascript'>//<![CDATA[
$(function(){
$('#downloadButton').click(function () {
var keepOnlyA1= true // put this true to remove unused numeric A0 field from each row [ A0, A1=[field,field,...]]
//
var a= tf_table1.GetFilteredData(true) // a= [ [A0,A1=[field,field,...]], [A0,A1=[field,field,...]], ...[..,[...]] ]
// ^a[0] ^a[1] ^a[n]
for (var i=0,row,r1; i<a.length; i++){ //
row= a[i] // row= [A0, A1=[field,field,...]]
r1= row[1] // ^r1
for (var j=0; j<r1.length; j++){ // surround each field with quotes "field"
r1[j]= '"'+r1[j].replace('"','""')+'"' // if there is any " already inside field string, it needs to be doubled. per csv rfc.
} //
if (keepOnlyA1) a[i]= r1 // if true, just replace each row array with its single interior A1=[field,field,...] array
} //
var colvals = a.join("\r\n") +"\r\n" //
var blob = new Blob([ colvals ], {type: 'text/csv;charset=ISO-8859;'});
//var filename = $('#fileName').val();
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, "S7_Won_Tendes_Export_Filtered.csv");
}
else {
var a = document.createElement('a');
a.download = filename;
a.href = window.URL.createObjectURL(blob);
a.style.display= "none" //added// invisible
document.body.appendChild(a) //added// firefox wouldn't click() it without being appended
a.click();
//if (a.remove) a.remove();
a.parentNode.removeChild(a) //added//
}
});
});//]]>
</script>

Ok I managed to find answers:
Replace
var blob = new Blob([ colvals ],
with
var blob = new Blob(["\uFEFF" + buffer],
and
r1[j]= '"'+r1[j].replace('"','""')+'"'
with
r1[j]= '"'+r1[j].replace(/"/g, '""')+'"'

Related

What causes these JS scripts to prepend undefined to values in first tsv output column?

I've created a number of JS scripts similar to below which generate a .tsv download of the webscraped data (this particular example assumes you're on the URL of a repo's Contributors page on Gitlab). Everything outputs fine when I open the .tsv in Microsoft Excel, except that the string 'undefined' appears prepended to every value after the header row in the first column only
How do I edit the script to omit undefined from appearing? Even if it's a simple fix, it will allow me to clean up a bunch of scripts' similar output scraping other websites.
javascript:(function(){
var arr = new Array, i, commitsemail, commitsnum, email, fullname, matchnum;
var regexnum = /.+?(?=commits)/g; var regexemail = /(?<=\().*(?=\))/g;
var glab = document.querySelectorAll('div.col-lg-6.col-12.gl-my-5');
var strings='Full name'+'\t'+'Email'+'\t'+'# of commits'+'\r\n';
var endurl = document.URL.split(/[/]+/).pop(); if (endurl != 'master') {
alert('You are not on the contributors page of a Gitlab repo. Press Esc key, go to URL ending with /master and re-run this bookmarklet'); } else {
for (i = 0; i<glab.length; i++) {
fullname = glab[i].getElementsByTagName('h4')[0].textContent;
commitsemail = glab[i].getElementsByTagName('p')[0].textContent;
commitsnum = [...commitsemail.match(regexnum)];
email = [...commitsemail.match(regexemail)];
arr[i] += fullname + '\t' + email + '\t' + commitsnum;
strings += arr[i]+'\r\n'; }
var pom = document.createElement('a');
var csvContent = strings; var blob = new Blob([csvContent],{type: 'text/tsv;charset=utf-8;'});
var url = URL.createObjectURL(blob); pom.href = url; pom.setAttribute('download','gitlab-contributors.tsv'); pom.click(); }
})();
It's because of the += on the line with arr[i] += fullname + '\t' + email + '\t' + commitsnum;. Change that to an = instead.
Before the assignment, arr[i] is undefined. Maybe you mixed up the syntax for assigning an array entry by index, with appending to an array (arr.push(...)), thinking += would push, but it doesn't. It appends the new value to the current value. And since that line is the first time arr[i] is assigned anything, the current value is undefined.

skip comma (,) in csv export

So, I have found several other SO questions with related answers and tried most of them but nothing seems to be working for me. May be because my case is much complicated than the previous ones.
Here is my csv file which has several special characters [, ' "]
ID,Name,Executable,Host,Timeout,Tags,Visible,
65,one test,SomePROXY=http://10.10.10.10 /abc/cde/efg/some.py -u tom -g 'some-abc-test' --passparams "one,two",example.com,1800,nothing,true,
My Expected excel format is:
ID: 65
Name: one test
Executable: SomePROXY=http://10.10.10.10 /abc/cde/efg/some.py -u tom -g 'some-abc-test' --passparams "one,two"
Host: example.com
Timeout: 1800
Tags: nothing
Visible: true
But due to comma in Executable field, "two" comes in new column.
I tried quoting everything under """ (as suggested in other answers) but that doesn't seems worked.
Can anyone please help me here.
Here is my code, which I am trying to use here (for ag-grid table )
$scope.export_data_csv = function(){
var LINE_SEPARATOR = '\r\n';
var COLUMN_SEPARATOR = ',';
var fileName = 'export.csv';
let csvString = '';
let columnsToExport = $scope.ag_grid_options.api.columnController.getAllDisplayedColumns();
// adding column headers.
columnsToExport.map((column) => {
csvString+= column.colDef.headerName;
csvString+= COLUMN_SEPARATOR;
});
csvString+= LINE_SEPARATOR;
// adding content of data currently loaded in the grid.
$scope.ag_grid_options.api.forEachNode( function(node) {
node.columnController.allDisplayedColumns.map((column) => {
let cellContent = node.valueService.getValue(column, node);
if(typeof(cellContent) == 'object'){
cellContent = cellContent.join("; ")
}
csvString+= (cellContent != null) ? cellContent : "";
csvString+= COLUMN_SEPARATOR;
});
csvString+= LINE_SEPARATOR;
});
// for Excel, we need \ufeff at the start
var blobObj = new Blob(["\ufeff", csvString], {
type: "text/csv;charset=utf-8;"
});
// Internet Explorer
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blobObj, fileName);
}
else {
// Chrome
var downloadLink = document.createElement("a");
downloadLink.href = window.URL.createObjectURL(blobObj);
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
}
;
You have to declare the whole value of 'Excecutable' as string and escape the double quotes inside with an additional double quote.
ID,Name,Executable,Host,Timeout,Tags,Visible,
65,one test,"SomePROXY=http://10.10.10.10 /abc/cde/efg/some.py -u tom -g 'some-abc-test' --passparams ""one,two""",example.com,1800,nothing,true,

AngularJS - Receive and download CSV

I am using a nodeJS program as a server and an AngularJS web application as the client.
To create the CSV I'm using the "express-csv" library (https://www.npmjs.com/package/express-csv)
Here is my server side code:
Defines:
var app = express();
var csv = require('express-csv');
Get code:
app.get('/exportDB', function(req, res){
res.csv([
["a", "b", "c"]
, ["d", "e", "f"]
]);
Here is my client side code:
$http.get("http://"+$localStorage.ip+":"+$localStorage.port+"/exportDB").success(function(response){
// HERE I NEED A WAY TO DOWNLOAD THE RECEIVED CSV
});
Needless to say it reaches the server and everything else is working just fine, but I couldnt find a way to download the CSV. Help please.
P.S
Please don't say it's a duplicate of Prompt a csv file to download as pop up using node.js and node-csv-parser (node module) since the client side isn't really mentioned there.
Also, other questions are focused on server side instead of client.
There is no other question referring to AngularJS client.
You can just navigate:
location.href = "http://"+$localStorage.ip+":"+$localStorage.port+"/exportDB";
I faced same issue that the solutions mentioned above with works well with Chrome and Firefox, but not with safari/IE.
Tried out following hack and it worked for me-
var url="http://'+$localStorage.ip+':"+$localStorage.port+'/exportDB';
var form = angular.element("<form action='" + url +"'></form>");
form.submit();
File download will be handled by browser itself.
though following is limitation to it -
You won't be able to handle the errors (if any) from your http get call :( For this tried using try-catch block of javascript, but didn't work well...
..you wont be able to unit test this piece of code :( :(
There is another approach that worked and it was -
var url="http://'+$localStorage.ip+':"+$localStorage.port+'/exportDB';
$window.location.href = url;
Suggestions and Discussions welcome!
You can create a tag and click on it:
$http.get("http://"+$localStorage.ip+":"+$localStorage.port+"/exportDB").success(function(response) {
var dataURI = 'data:application/octet-stream;base64,' + btoa(response);
$('<a></a>').attr({
download: 'db.csv',
href: dataURI
})[0].click();
});
There are ways of downloading csv. First approach is to create a tag and click it
Add the mimeType in below code data:application/octet-stream
var a = document.createElement('a');
a.href = 'data:'+mimeType+';charset=utf-8;base64,' + response;
a.target = '_blank';
a.download = "name the file here";
document.body.appendChild(a);
a.click();
But this solution doesn't work on IE>9 and safari>6
because safari doesn't follow download attribute for anchor tag
so for safari you can use filesaver.js
and IE this solution will work
if (window.navigator.msSaveOrOpenBlob){
// base64 string
var base64str = response;
// decode base64 string, remove space for IE compatibility
var newstr =base64str.replace(/\s/g, '');
var binary = atob(newstr);
// get binary length
var len = binary.length;
// create ArrayBuffer with binary length
var buffer = new ArrayBuffer(len);
// create 8-bit Array
var view = new Uint8Array(buffer);
// save unicode of binary data into 8-bit Array
for (var i = 0; i < len; i++) {
view[i] = binary.charCodeAt(i);
}
// create the blob object with content-type "application/csv"
var blob = new Blob( [view], { type: mimeType });
window.navigator.msSaveOrOpenBlob(blob, "Name your file here");
}
Here I tried to make it simple. Assign all the back end records that you want to display in the file in the variable called obj. I will just say it as var obj = []. And inside the function just add the below code.
var a = document.createElement("a");
var csvContent = "Name, Address\n";
for(var i =0; i <obj.lenght; i++ ) {
var dataString = obj[i].name + ", " + obj[i].address + "\n";
csvContent += dataString;
}
a.href = 'data:attachment/csv;charset=utf-8,' + encodeURI(csvContent);
a.target = '_blank';
a.download = 'myFile.csv';
document.body.appendChild(a);
a.click();

Javascript: create UTF-16 text file?

I have some string need to be a UTF-16 text file. For example:
var s = "aosjdfkzlzkdoaslckjznx";
var file = "data:text/plain;base64," + btoa(s);
This will result a UTF-8 encoding text file. How can I get a UTF-16 text file with string s?
Related: Javascript to csv export encoding issue
This should do it:
document.getElementById('download').addEventListener('click', function(){
downloadUtf16('Hello, World', 'myFile.csv')
});
function downloadUtf16(str, filename) {
// ref: https://stackoverflow.com/q/6226189
var charCode, byteArray = [];
// BE BOM
byteArray.push(254, 255);
// LE BOM
// byteArray.push(255, 254);
for (var i = 0; i < str.length; ++i) {
charCode = str.charCodeAt(i);
// BE Bytes
byteArray.push((charCode & 0xFF00) >>> 8);
byteArray.push(charCode & 0xFF);
// LE Bytes
// byteArray.push(charCode & 0xff);
// byteArray.push(charCode / 256 >>> 0);
}
var blob = new Blob([new Uint8Array(byteArray)], {type:'text/plain;charset=UTF-16BE;'});
var blobUrl = URL.createObjectURL(blob);
// ref: https://stackoverflow.com/a/18197511
var link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
link.dispatchEvent(event);
} else {
link.click();
}
}
<button id="download">Download</button>
You can use a legacy polyfill of the native TextEncoder API to transform a JavaScript string into an ArrayBuffer. As you'll see in that documentation, UTF16 with either endianness is was supported. Libraries that provide UTF-16 support in a Text-Encoder-compatible way will probably appear soon, if they haven't already. Let's assume that one such library exposes a constructor called ExtendedTextEncoder.
Then you can easily create a Blob URL to allow users to download the file, without the inefficient base-64 conversion.
Something like this:
s = "aosjdfkzlzkdoaslckjznx"
var encoder = new ExtendedTextEncoder("utf-16be")
var blob = new Blob(encoder.encode(s), "text/plain")
var url = URL.createObjectURL(blob)
Now you can use url instead of your data: URL.

Downloading generated binary content contains utf-8 encoded chars in disk-file

I am trying to save a generated zip-file to disk from within a chrome extension with the follwing code:
function sendFile (nm, file) {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(file);
a.download = nm; // file name
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
function downloadZip (nm) {
window.URL = window.webkitURL || window.URL;
var content;
content = zip.generate();
var file = new Blob ([content], {type:'application/base64'});
sendFile ("x.b64", file);
content = zip.generate({base64:false});
var file = new Blob ([content], {type:'application/binary'});
sendFile ("x.zip", file);
}
Currently this saves the contents of my zip in two versions, the first one is base64 encoded, and when I decode it with base64 -d the resulting zip is ok.
The second version should just save the raw data (the zip file), but this raw data arrives utf-8 encoded on my disk. (each value >= 0x80 is preprended with 0xc2). So how to get rid of this utf-8 encoding? Tried various type-strings like application/zip, or ommitting the type info completely, it just arrives always with utf-8 encoding. I am also curious how to make the browser store/convert base64-data (the first case) by itself, so that they arrive as decoded binary data on my disk... I'm using Chrome Version 23.0.1271.95 m
PS: The second content I analysed with a hexdump-utility inside the browser: it does not contain utf-8 encodings (or my hexdump calls something which does implicit conversion). For completeness (sorry, its just transposed from c, so it might not be that cool js-code), I append it here:
function hex (bytes, val) {
var ret="";
var tmp="";
for (var i=0;i<bytes;i++) {
tmp=val.toString (16);
if (tmp.length<2)
tmp="0"+tmp;
ret=tmp+ret;
val>>=8;
}
return ret;
}
function hexdump (buf, len) {
var p=0;
while (p<len) {
line=hex (2,p);
var i;
for (i=0;i<16;i++) {
if (i==8)
line +=" ";
if (p+i<len)
line+=" "+hex(1,buf.charCodeAt(p+i));
else
line+=" ";
}
line+=" |";
for (i=0;i<16;i++) {
if (p+i<len) {
var cc=buf.charCodeAt (p+i);
line+= ((cc>=32)&&(cc<=127)&&(cc!='|')?String.fromCharCode(cc):'.');
}
}
p+=16;
console.log (line);
}
}
From working draft:
If element is a DOMString, run the following substeps:
Let s be the result of converting element to a sequence of Unicode characters [Unicode] using the algorithm for doing so in WebIDL
[WebIDL].
Encode s as UTF-8 and append the resulting bytes to bytes.
So strings are always converted to UTF-8, and there is no parameter to affect this. This doesn't affect base64 strings because they only contain characters that match single byte per codepoint, with the codepoint and byte having the same value. Luckily Blob exposes lower level interface (direct bytes), so that limitation doesn't really matter.
You could do this:
var binaryString = zip.generate({base64: false}), //By glancing over the source I trust the string is in "binary" form
len = binaryString.length, //I.E. having only code points 0 - 255 that represent bytes
bytes = new Uint8Array(len);
for( var i = 0; i < len; ++i ) {
bytes[i] = binaryString.charCodeAt(i);
}
var file = new Blob([bytes], {type:'application/zip'});
sendFile( "myzip.zip", file );

Categories