0

I am trying to automate a Google Sheets -> pipe-delimited text file download. Basically, I want to create a time-driven script to run on a Google spreadsheet to save it as a .txt with | delimiters.

The spreadsheet itself is a single worksheet that uses IMPORTRANGE to pull data from my main file for the end-user to access.

Right now I manually download it as .csv, open it in notepad, "Replace all" commas with |'s, and save as .txt, and then upload that file where it needs to go (the upload is irrelevant, that has to be manual). I want to automate this for myself but also for another user to get that final .txt file via script when I am unavailable. Most importantly, I want this to be able to be run without user input or active workstations (it's supposed to be run on a Sunday morning) so it has to be in a time-triggered Google Apps Script.

I have very little JavaScript experience, so the only solution I can think of is two nested for loops (sheetID and sheetName withheld):

  var ss= SpreadsheetApp.openById(sheetID)
  var pipeDelimitedString = ""
  var sheet = ss.getSheetByName(sheetName)
  var lastRow = sheet.getLastRow()
  var lastColumn = sheet.getLastColumn()
  var fileName = "PIPE DELIMITED TEST FILE"
  for (var i=1;i<=lastRow;i++){
        for(var j=1;j<=lastColumn;j++){
            if (j==lastColumn){
                 pipeDelimitedString = pipeDelimitedString + "|" + sheet.getRange(i,j).getValue()+"/n"
            } else {
                 pipeDelimitedString = pipeDelimitedString + "|" + sheet.getRange(i,j).getValue()
            }
        }
}
newFile = DriveApp.createFile(fileName,pipeDelimitedString);

And then a small script to email the newFile to the other user (haven't wrote that part yet but I see plenty of solutions on google so don't worry about the emailing portion)

This has to run on a sheet of 66 columns and up to 200 rows (>10k total cells). I tested it on a dataset of only ~ 50 rows and I had to stop it. Even if it does work it would eat up far too much of my Google Apps Script quota for triggered scripts.

I'm sure I've gone the absolute wrong direction with this. Please help? Even just where to begin would be greatly appreciated.

I can't share the actual file, only a dummy copy, but I'm pretty sure the solution shouldn't be content-specific? Aside from the fact that there are no comma's or pipes anywhere in the original file data, only a mix of text and numbers if that helps?

Rubén
  • 34,714
  • 9
  • 70
  • 166
DAC
  • 3
  • 2
  • You have to use `getRange().getValues()` for as many cells as you can an once. For all cells on the sheet, probably. And then you can process the 2D-array (well, it can be a classic nested loop, why not?) without spend your quota. To get element of 2D-array (say row `i` and column `j` you can use the notation: `myCell = myArray[i][j]` – Yuri Khristich May 04 '21 at 23:04
  • I see, thank you. If I understand correctly my issue isn't with the nested for loops but the fact that with each iteration I was calling the getValue() function again, which is more intensive than checking an array value. I appreciate it! – DAC May 05 '21 at 21:32
  • Exactly. With Google App Script you need use the methods `getValue()` , `getValues()` and `setValue()` , `setValues()` as few as possible. – Yuri Khristich May 06 '21 at 11:49

2 Answers2

1

If you don't like nested loops you can use more fancy tools:

var sheetID = "";
var sheetName = "";

function myFunction() {

  var ss         = SpreadsheetApp.openById(sheetID);
  var sheet      = ss.getSheetByName(sheetName);
  var lastRow    = sheet.getLastRow();
  var lastColumn = sheet.getLastColumn();
  var range      = sheet.getRange(1,1,lastRow,lastColumn).getValues(); <-- get all values

  var pipeDelimitedString = range.map(r => r.join("|")).join("\n"); <-- instead of loop

}

Or even shorter, without lastRow and lastColumn variables:

function myFunction() {

  var ss    = SpreadsheetApp.openById(sheetID);
  var sheet = ss.getSheetByName(sheetName);
  var range = sheet.getDataRange().getValues();
  var pipeDelimitedString = range.map(r => r.join("|")).join("\n");

}

The main point is to use getRange().getValues() for all cells at once.

UPDATE

To remove empty lines and cells outside the table you can apply replace() method to the string variable:

pipeDelimitedString = pipeDelimitedString.replace(/\|+\s*\|+/g, "");

This is a simpliest solution. But beware, the replace() method will get you wrong result in case your table has emply cells inside. 1| |A will turn into 1A. It's need a more complicated algorithm to keep inner empty cells intact. Implementation heavy depends on your data.

In:

enter image description here

Out:

enter image description here

Yuri Khristich
  • 13,448
  • 2
  • 8
  • 23
  • I really appreciate the help. This did work, but it also pulled several blank rows with it (the rows were represented as just a series of pipes like so "||||||||||||||||||||||"). Is there a way to avoid that using the range.map method? edit: Maybe the "DataRange" is larger than it should be? I can't just delete all the empty rows in the spreadsheet because the number of filled rows vary – DAC May 05 '21 at 21:25
  • I see two way to get rid of blank lines: (1) to change a range or (2) to remove the '||||...' afterward from the output string. Former implementation look pretty simply and straignforward. Just find/replace command: `pipeDelimitedString = pipeDelimitedString.replace(/\|+\s*\|+/g, "");` The `/\|+\s*\|+/g` means `|` + one of more spaces + `|` , `g` means everywhere in the string. – Yuri Khristich May 06 '21 at 12:08
  • Got it, thank you. Unfortunately blank cells in the middle of the data are not only common but actually necessary, so I went with your option (1) with a quick for loop "if blank" down one of the columns that can never have a blank cell to find the last "real" row. – DAC May 06 '21 at 21:18
  • Sorry for the continued bother, but it turns out that when using the range.map method, any dates in the original file get converted into this weird datetime value like "|Mon Jun 01 1955 00:00:00 GMT-0400 (Eastern Daylight Time)|". Do you happen to know how to avoid that? It has to stay in mm/dd/yyyy format. The dates will only appear in specific columns if that helps (columns 4, 23, 24, and 63) – DAC May 06 '21 at 21:53
  • Try `getDisplayValues()` instead of `getValues()` https://stackoverflow.com/questions/34691425/difference-between-getvalue-and-getdisplayvalue-on-google-app-script – Yuri Khristich May 07 '21 at 01:03
1
function myFunction() {
  const ss = SpreadsheetApp.openById(gobj.globals.ssid);//Change your id
  let s = ""
  const sh = ss.getSheetByName('Sheet1');//change your sheet name
  let vs = sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).getValues();//getting all of the data on the sheet
  vs.forEach(r=>{r.forEach(c=>{s+="|" + c;});s+='\n';});//two loops
  newFile = DriveApp.createFile("PIPE DELIMITED TEST FILE", s);//you could add a folder
}

The ascii file looks like this:

|COL1|COL2|COL3|COL4|COL5|COL6|COL7|COL8|COL9|COL10
|6|2|4|9|5|0|5|9|4|0
|5|1|5|7|1|2|5|7|0|0
|6|8|7|2|4|7|6|3|0|9
|7|5|7|6|3|2|7|7|0|0
|3|1|9|4|2|2|5|6|1|8
|7|8|8|3|2|7|1|6|3|0
|4|5|2|4|6|8|5|4|6|2
|9|3|6|7|5|7|9|9|0|6
|3|2|3|0|1|4|3|6|8|0
|9|0|0|2|8|0|0|2|4|7

For this sheet:

COL1 COL2 COL3 COL4 COL5 COL6 COL7 COL8 COL9 COL10
6 2 4 9 5 0 5 9 4 0
5 1 5 7 1 2 5 7 0 0
6 8 7 2 4 7 6 3 0 9
7 5 7 6 3 2 7 7 0 0
3 1 9 4 2 2 5 6 1 8
7 8 8 3 2 7 1 6 3 0
4 5 2 4 6 8 5 4 6 2
9 3 6 7 5 7 9 9 0 6
3 2 3 0 1 4 3 6 8 0
9 0 0 2 8 0 0 2 4 7
Cooper
  • 59,616
  • 6
  • 23
  • 54