Skip to content

Instantly share code, notes, and snippets.

@mrkrndvs
Forked from mderazon/export-to-csv.gs
Last active July 3, 2024 06:54
Show Gist options
  • Save mrkrndvs/a2c8ff518b16e9188338cb809e06ccf1 to your computer and use it in GitHub Desktop.
Save mrkrndvs/a2c8ff518b16e9188338cb809e06ccf1 to your computer and use it in GitHub Desktop.
Google apps script to export an individual sheet as csv file
/*
* script to export data of the named sheet as an individual csv files
* sheet downloaded to Google Drive and then downloaded as a CSV file
* file named according to the name of the sheet
* original author: Michael Derazon (https://gist.github.com/mderazon/9655893)
*/
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var csvMenuEntries = [{name: "Download Primary Time File", functionName: "saveAsCSV"}];
ss.addMenu("Creating a Timetable", csvMenuEntries);
};
function saveAsCSV() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Primary Time');
// create a folder from the name of the spreadsheet
var folder = DriveApp.createFolder(ss.getName().toLowerCase().replace(/ /g,'_') + '_csv_' + new Date().getTime());
// append ".csv" extension to the sheet name
fileName = sheet.getName() + ".csv";
// convert all available sheet data to csv format
var csvFile = convertRangeToCsvFile_(fileName, sheet);
// create a file in the Docs List with the given name and the csv data
var file = folder.createFile(fileName, csvFile);
//File downlaod
var downloadURL = file.getDownloadUrl().slice(0, -8);
showurl(downloadURL);
}
function showurl(downloadURL) {
var app = UiApp.createApplication().setHeight('60').setWidth('150');
//Change what the popup says here
app.setTitle("Your timetable CSV is ready!");
var panel = app.createPopupPanel()
//Change what the download button says here
var link = app.createAnchor('Click here to download', downloadURL);
panel.add(link);
app.add(panel);
var doc = SpreadsheetApp.getActive();
doc.show(app);
}
function convertRangeToCsvFile_(csvFileName, sheet) {
// get available data range in the spreadsheet
var activeRange = sheet.getDataRange();
try {
var data = activeRange.getValues();
var csvFile = undefined;
// loop through the data in the range and build a string with the csv data
if (data.length > 1) {
var csv = "";
for (var row = 0; row < data.length; row++) {
for (var col = 0; col < data[row].length; col++) {
if (data[row][col].toString().indexOf(",") != -1) {
data[row][col] = "\"" + data[row][col] + "\"";
}
}
// join each row's columns
// add a carriage return to end of each row, except for the last one
if (row < data.length-1) {
csv += data[row].join(",") + "\r\n";
}
else {
csv += data[row];
}
}
csvFile = csv;
}
return csvFile;
}
catch(err) {
Logger.log(err);
Browser.msgBox(err);
}
}
@AlliePavs
Copy link

Yes! A friend of mine cracked it.

It was something to do with default chrome security settings whereby if you have 2 or more accounts that you switch between, you're actually signed into both of them rather than just the one you're using.

There's a security feature that will check permissions on both accounts and if one of them doesn't have the permissions, it wont let you download the file.

So, by adding the following to the script it circumvents this:

//set permissions to public to avoid double account glitch
folder.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);

It's important to note however, that the folders and files it creates in the users drive account, are downloadable by anyone with the link.

Hope this helps!

@KhaledBinAmir
Copy link

@Matt-McDonald-AS: That's because the CSV formatting code above isn't correct (it only checks for commas) - but there's more things to take into account:

  • Quotes themselves must be escaped (double them -> " becomes "")
  • Commas must be escaped (put string in quotes)
  • New lines must be escaped (put string in quotes)
  • Basically, all the values should be quoted, just to be safe.

I've updated the function to the following, and it works great:

function convertRangeToCsvFile_(csvFileName, sheet) {
  try {
    var data = sheet.getDataRange().getDisplayValues();
    if (data.length > 1) {
      var rows = [];
      data.forEach(row => {
        var cols = [];
        row.forEach(col => {
          cols.push(`"${col.replace(/"/g, '""')}"`);
        });

        rows.push(cols.join(','));
      });
      
      return rows.join('\n');
    }
  } catch(err) {
    Logger.log(err);
    Browser.msgBox(err);
  }
}

Hi, there is an error
TypeError: col.replace is not a function

@mroy-seedbox
Copy link

mroy-seedbox commented Jul 5, 2021

@hiimkhaled: Hmmmm, looks like sometimes col isn't a string somehow. 🤔

Try with this: cols.push(`"${col.toString().replace(/"/g, '""')}"`);

@debruine
Copy link

Lines 53-72 could be simplified a little:

csvFile =  lapply(data, sapply, toString) %>% 
    as.data.frame() %>% 
    readr::format_csv()

@mroy-seedbox
Copy link

@debruine: How do you make that work? I don't think Google Apps Script supports the R language.

@AnthonyMalbranque
Copy link

Many thanks for @mrkrndvs for this valued share.
Many thanks too for @maedox for his response

Indeed, I got an error on the csv file generated.
In france the decimal is a comma.
But in the generated csv with getValues() function, the commas was remplaced by points.
Now with getDisplayValues() all is ok.

@amaralc
Copy link

amaralc commented Dec 16, 2021

Thanks @mrkrndvs, this is great!

@Laysags
Copy link

Laysags commented Aug 16, 2022

I'm having some problems with the function "onOpen"

Exception: Cannot call SpreadsheetApp.newMenu() from this context.

Can anyeone help me?

@Sizzlemanizzle
Copy link

@Matt-McDonald-AS: That's because the CSV formatting code above isn't correct (it only checks for commas) - but there's more things to take into account:

  • Quotes themselves must be escaped (double them -> " becomes "")
  • Commas must be escaped (put string in quotes)
  • New lines must be escaped (put string in quotes)
  • Basically, all the values should be quoted, just to be safe.

I've updated the function to the following, and it works great:

function convertRangeToCsvFile_(csvFileName, sheet) {
  try {
    var data = sheet.getDataRange().getDisplayValues();
    if (data.length > 1) {
      var rows = [];
      data.forEach(row => {
        var cols = [];
        row.forEach(col => {
          cols.push(`"${col.replace(/"/g, '""')}"`);
        });

        rows.push(cols.join(','));
      });
      
      return rows.join('\n');
    }
  } catch(err) {
    Logger.log(err);
    Browser.msgBox(err);
  }
}

I'd rather recommend keeping the original code to keep the split functionality, but else a good improvement.

`function convertRangeToCsvFile_(sheet, rowsPerFile) {
// get available data range in the spreadsheet
var activeRange = sheet.getDataRange();
try {
var data = activeRange.getValues();
var csvFiles = [];

// loop through the data in the range and build a string with the csv data
if (data.length > 1) {
  var csv = "";
  for (var row = 0; row < data.length; row++) {
    for (var col = 0; col < data[row].length; col++) {
      if (data[row][col].toString().indexOf(",") != -1) {
        data[row][col] = (`"${data[row][col].replace(/"/g, '""')}"`) ; //  modification of original code
      }
    }

    // join each row's columns
    // add a carriage return to end of each row, except for the last one
    if (row < data.length-1) {
      csv += data[row].join(",") + "\r\n";
    }
    else {
      csv += data[row];
    }

    if ((row + 1) % rowsPerFile === 0) {
      csvFiles.push(csv);
      csv = "";
    }
  }
}
// If there are remainders that doesnt reach the number of files needed.
// It will add another file, containing all remainder rows there.
// 10010 rows / 20 files = 10 rows excess
// 21st file will contain those 10 rows excess
// No excess means no 21st file
if(csv) {
  csvFiles.push(csv);
}
return csvFiles;

}
catch(err) {
Logger.log(err);
}
}
`

@BarryLaminack
Copy link

This was great, thank you!

@BarryLaminack
Copy link

@gabrielgz92 I got it to work by replacing the showurl function with this:

function showurl(downloadURL) {
  //Change what the download button says here
  var link = HtmlService.createHtmlOutput('<a href="' + downloadURL + '">Click here to download</a>');
  SpreadsheetApp.getUi().showModalDialog(link, 'Your CSV file is ready!');
}

This helped a TON. Thank you for this update! Worked perfect.

@MarcAguilar1
Copy link

MarcAguilar1 commented Nov 3, 2023

Hello, in my case I got a correct downloadURL but only working if I manually introduce it to the browser. However if url is called within the Apps Script (showModalDialog()) I got initial 303 status redirection followed up 18 302 status redirection since I end up with ERR_TOO_MANY_REDIRECTS. Any clue?

  • Folder is created OK
  • Csv file is created OK
  • URL file is created OK
  • By changing folder permissions is not working aswell.

Other approach I did--> If I just copy the GET request from manually get File > Download > CSV in the browser works but within the dialog got this error:
image

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment