I have a simple column in column A in a sheet by name Sno. (serial number). I am trying to read the column and identify -
-if there are any empty cells from first cell in the column to the last filled row and
-if the values which are present are numbers only
This will help me do 2 validations, to identify empty cells in between the cell values, like if there are 1 - 200 numbers entered then in between there are no misses in the series and if the values which are present all numbers
I tried the below to check that but not getting it right-
unction siteShieldmaps() {
SS = SpreadsheetApp.getActiveSpreadsheet();
var SS_m = SS.getSheetByName("Cleanup sheet");
var LAST_ROW = SS_m.getLastRow();
console.log(LAST_ROW);
var Sno_values = SS_m.getRange(`A1:A${LAST_ROW}`).getDisplayValues().toString();
console.log(typeof Sno_values);
var result = isNumberOrEmpty(Sno_values);
console.log(result);
}
function isNumberOrEmpty(array) {
var result = [];
for (var i = 0; i < array.length; i++) {
if (array[i] === "") {
result.push("empty");
} else if (!isNaN(array[i])) {
result.push("number");
} else {
result.push("not a number");
}
}
return result;
}
Please guide
Adding to the comment of #TheWizEd, one of the issues of the code is how the array has been called. I made other changes in the code to make sure that both validations are completed.
Make sure if there are any empty cells.
Make sure that all the values are numbers.
Here is the table I made for testing:
Here is the sample code:
function siteShieldmaps() {
ss = SpreadsheetApp.getActiveSpreadsheet();
let ss_m = ss.getSheetByName("Sheet1");
let last_row = ss_m.getLastRow();
console.log(last_row);
// change how the range is call from "A1:A${LAST_ROW} to "2,1,last_row"
// The range "2,1,last_row" will exclude the "A1" cell
// Also, I change "getDisplayValues().toString();" to "getValues()"
// if you keep "getDisplayValues().toString();"
//it will show some cells as not number when they are
let sno_values = ss_m.getRange(2,1,last_row).getValues();
let result = isNumberOrEmpty(sno_values);
console.log(result);
}
function isNumberOrEmpty(array) {
let result = [];
for (let i = 0; i < array.length; i++) {
// create the variable row instead of using array[i][0]
// so I use row[0] in the if statement
let row = array[i]
if (row[0] === "") {
result.push("empty");
} else if (!isNaN(row[0])) {
result.push("number");
} else {
result.push("not a number");
}
}
return result;
}
And the result will be:
I would really comment instead of posting but I don't have enough reputation.
In this line of code you're not actually getting a 2-D array but a string.
var Sno_values = SS_m.getRange(`A1:A${LAST_ROW}`).getDisplayValues().toString();
toString()
It should be just
var Sno_values = SS_m.getRange(`A1:A${LAST_ROW}`).getDisplayValues()
Besides, as TheWizEd remarked you are working with a 2D array, so your function should be:
function isNumberOrEmpty(array) {
var result = [];
for (var i = 0; i < array.length; i++) {
if (array[i][0] === "") {
result.push("empty");
} else if (!isNaN(array[i][0])) {
result.push("number");
} else {
result.push("not a number");
}
}
return result;
}
Related
I'm trying to get a SCORESHEET to populate from a REPORTSHEET, using a REFERENCESHEET to collate search terms and destination cells.
The script I'm running is as below. The idea is that the script finds searchDate's in the REFERENCESHEET and uses them to locate data columns in the REPORTSHEET:
function superAuto() {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var scorecard = SpreadsheetApp.openById('SCORESHEET');
var scorecardData = scorecard.getDataRange().getValues();
var tExpenses = "Total Expenses";
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
}
var column = columnfinder(searchDate);
for (var a = 0; a < referenceData.length; a++) {
var refRow = referenceData[a];
for (var i = 0; i < reportData.length; i++) {
var row = reportData[i];
if (row[0] == tExpenses && refRow[0] == searchDate) {
scorecard.getRange(refRow[5]).setValue(row[column]);
}
}
}
}
function columnfinder(find) {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
for(var j=0, jLen=reportData.length; j<jLen; j++) {
for(var k=0, kLen=reportData[0].length; k<kLen; k++) {
if(find == reportData[j][k]) {
Logger.log(k);
return (k);}
}
}
}
Broadly speaking, the code works, as if I define searchDate as one of the terms I'm looking for (e.g. Jan-21) it all works fine. The issue is that it doesn't seem to be doing so when finding multiple search terms - and therefore populating multiple rows - as per:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
}
The log tells me that it's finding searchDate's in the REFERENCESHEET, but it's not able to run them through function columnfinder (I get no logs for the second logger).
I suspect the answer lay somewhere in an earlier great answer I received to an earlier version of this idea - How to return multiple column values for setValue - but I've not been able to make it fit. Any thoughts?
EDIT: Please find a sample REFERENCESHEET & REPORTSHEET for more info:
The log tells me that it's finding searchDate's in the REFERENCESHEET,
but it's not able to run them through function columnfinder (I get no
logs for the second logger)
You don't execute columnfinder inside the for loop.
Try this:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log(searchDate);
columnfinder(searchDate); // modified code
}
and you will get both logs.
Sorry if I misunderstood your question.
You need to use have it assigned to array since you are returning possible multiple columns/dates:
function superAuto() {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var scorecard = SpreadsheetApp.openById('SCORESHEET');
var scorecardData = scorecard.getDataRange().getValues();
var tExpenses = "Total Expenses";
var searchDates = [];
for (n = 0; n < referenceData.length; ++n) {
searchDates.push(referenceData[n][0])
}
var columns = columnfinder(searchDates);
columns.forEach(function (column, index) {
referenceData.forEach(function (refRow) {
reportData.forEach(function (row) {
if (row[0] == tExpenses && refRow[0] == searchDates[index]) {
scorecard.getRange(refRow[5].toString()).setValue(row[column]);
}
});
});
});
}
function columnfinder(dates) {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var columns = [];
dates.forEach(function (date) {
reportData.forEach(function (row, i) {
row.forEach(function (col, i) {
if (date == reportData[i][j]) {
columns.push(reportData[i][j]);
}
});
});
});
return columns;
}
I changed some variables into proper variable names to avoid confusion.
Additionally, if it doesn't work, you might need to share a proper visualization of the data, or better yet, provide some sample sheet we can work on for us to be able to give you a better and tested answer.
Thanks Marios, that's twice in a week. Much appreciated.
Slight adaptation, in order for it to populate SCORECARD I needed to bring everything into the for loop, as below:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
var column = columnfinder(searchDate);
for (var a = 0; a < referenceData.length; a++) {
var refRow = referenceData[a];
for (var i = 0; i < reportData.length; i++) {
var row = reportData[i];
if (row[0] == tExpenses && refRow[0] == searchDate) {
scorecard.getRange(refRow[5]).setValue(row[column]);
}
}
}
}
I have been working on a new project and I need to create new sheets based on cell value.
function ign_list() {
var nRange = SpreadsheetApp.getActive().getRangeByName("player")
return nRange.getValues()
}
function sheet_list() {
var out = new Array()
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i<sheets.length ; i++) out.push( [ sheets[i].getName() ] )
return out
}
function new_sheet() {
var ui = SpreadsheetApp.getUi();
var sheet_names = sheet_list();
var ign_names = ign_list();
for (var i=0; i<ign_names.length; i++){
for (var n=0; n<sheet_names.length; n++) {
if (ign_names[i] !="") {
if (ign_names[i] == sheet_names[n]) {
ui.alert(ign_names[i]+" Equal "+sheet_names[n])
} else {
ui.alert(ign_names[i]+" Not Equal "+sheet_names[n])
}
}
}
}
}
I have a sheet called Raw Stats and in the range, I have a cell with the value Raw Stats but when comparing it says is not equal.
is there another way of comparing string?
Using indexOf
for (var i=0; i<ign_names.length; i++){
if (sheet_names.indexOf(ign_names[i][0]) != -1) {
SpreadsheetApp.getActiveSpreadsheet().insertSheet().setName(ign_names[i][0]);
} else {
}
}
Always returning -1 even when as the same value
You are using the getValues() method. That returns an array of rows.
Even if you range is compromised of only one column, the result of that would be: [[A1],[A2],[A3],...]
You can fix this by flattening the array or by accounting for this with ign_names[i][0] instead.
Hope this helps
I want to check for new names in a top 30 ranking from an API that refreshes daily, and then append every new name to an other column if it isn't already in there.
I think a for-loop would be the solution. This is what I got so far.
function appendValues(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var top30Names = ss.getRange("A4:A33").getValues();
var eligibleNames = ss.getRange("P4:P300").getValues();
for (i = 0; i < 30; i++){
var searchKey = top30Names[i]; // search if the eligible name is in the top30names
if (isInArray(searchKey, eligibleNames)){
// do nothing
}
else{
getFirstEmptyRow();
ss.getActiveCell().setValue(searchKey);
}
}
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
function getFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSheet(),
values = sheet.getRange("P4:P300") // the range to search for the first blank cell
.getValues(),
row = 0; //start with the first array element in the 2D array retrieved by getValues()
for (row; row < values.length; row++) {
if (!values[row].join("")) break;
}
return sheet.setActiveSelection("P" + (row + 4)).getRow();//.getLastRow() // column between "" and row + starting_row in range
}
This appends the full top 30 each time, but I only need the new values.
I've found a work around using setFormula. If anyone has a more elegant solution, I'd be happy learn.
function appendNewName(){
setFormula();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var newNames = ss.getRangeByName("newEntries").getValues();
for (i = 0; i < 30; i++){
getFirstEmptyRow();
var x = newNames[i][0];
// Logger.log(x); // What does this do???
if (x.length > 1) {
ss.getActiveCell().setValue(newNames[i]);
}
}
}
function setFormula(){
// first run this
clearFormula();
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getRangeByName("newEntries").setFormula("=IF(ISNUMBER(MATCH(A4,AppendNew,0)),\"\",A4)"); // Sets a formula to the range that will show the new daily entries in top 30
}
function clearFormula(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getRangeByName("newEntries").clear();
}
function getFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSheet(),
values = sheet.getRange("appendNew") // the range to search for the first blank cell
.getValues(),
row = 0; //start with the first array element in the 2D array retrieved by getValues()
for (row; row < values.length; row++) {
if (!values[row].join("")) break;
}
return sheet.setActiveSelection("P" + (row + 4)).getRow(); // column between "" and row + starting_row in range
}
I would like to use a formula inside a custom function, like this for example:
function myFunction(range, value) {
var countNumber = COUNTIF(range; value); // COUNTIF is a formula that can be used in the spreadsheet
if (countNumber > 0) {
return "RESULT";
} else {
return "OTHER RESULT";
}
}
And then:
=MYFUNCTION(A1:A5,"VALUETOTEST")
I would like to simplify a huge formula:
Something like:
=IF(SUM(COUNTIFS(G182:G186;"ERROR";H182:H186;"62");COUNTIFS(G182:G186;"ERROR";H182:H186;"ALL"))>0;"ERRO";IF(SUM(COUNTIFS(G182:G186;"RETEST";H182:H186;"62");COUNTIFS(G182:G186;"RETEST";H182:H186;"TODOS"))>0;"RETEST";IF(COUNTIF(G182:G186;"UNIMPLEMENTED")>0;"UNIMPLEMENTED";"SOLVED")))
You have three ways of performing these actions.
Add the Sheet Formulas to the sheet itself in the ranges that you need. Then read the data from the result cells (wherever you set it to write to) using your GAS Function. You can then perform further processing using the results.
Use your GAS function to write Sheet Formulas into your sheet. Then use more GAS to read that result and process the data. The method for this can be found here: https://developers.google.com/apps-script/reference/spreadsheet/range#setFormula(String)
You can create a Custom Sheet Formula using GAS that you then use in your sheet. GAS can then read that result and process the information. This will require some research into JS as a whole to know how to recreate, combine, and perform the operations that you need the data in the sheet to perform.
You can find a guide to make Custom Formulas here: https://developers.google.com/apps-script/guides/sheets/functions
And a guide to JS here: http://www.w3schools.com/js/default.asp
W3 Schools has a quite comprehensive guide to JS. GAS uses all native JS methods as it is a JS coding environment. Check the GAS Reference for more on GAS-specific methods that may perform what you need.
If what you need is to check conditions and/or iterate through rows, try something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getRange(startRow, startColumn, numRows, numColumns);
var values = range.getValues(); //This is a 2D array; iterate appropriately
for (i = 0; i < values.length; i++) {
if (values[i] == conditionToCheck) {
//perform code..OR
//continue; <- This works to skip the row if the condition is met
} else {
//perform alternate code if condition is not met
}
}
}
As I mentioned, .getValues() creates a 2D array. If you need to iterate through columns and rows, you will need 2 for() loops like so:
for (i = 0; i < values.length; i++) { //iterates through the rows
for(j = 0; j < values[i].length; j++) { //iterates through the columns in that current row
It is important to mention how GAS handles 2D arrays. values[i][j] denotes how much i rows there are and j columns. You can visualize like so:
values = [[A1, B1, C1],[A2, B2, C2],[A3, B3, C3]]
This is an array of arrays where the outer array is an array of the rows, while the insides are an array of cell values by column in that row.
Custom functions in google apps script do not have access to spreadsheet function. You may try using this =IF(COUNTIF(A1:A5,"VALUETOTEST")>0,"RESULT","OTHER RESULT")
If there is a huge formula for result, try creating functions for the result
function result1() {
return "RESULT";
}
function result2() {
return "OTHER RESULT";
}
Then use this =IF(COUNTIF(A1:A5,"VALUETOTEST")>0,RESULT1(),RESULT2())
Try this - copy the below function in apps script, and use this as Formula =myFunction("G182:G186","H182:H186") remeber to ensclose the range with ' " ' because you will be passing the range as string, and note both the ranges must be of equal length.
function myFunction(aRange, bRange) {
var cond_1 = "ERROR";
var cond_2 = "62";
var cond_3 = "ALL";
var cond_4 = "RETEST";
var cond_5 = "TODOS";
var cond_6 = "UNIMPLEMENTED";
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var aRange = sheet.getRange(aRange);
var aValues = aRange.getValues();
var bRange = sheet.getRange(bRange);
var bValues = bRange.getValues();
var count = 0;
var tmplength = 0;
if (aValues.length != bValues.length) {
return "Range length does not Match";
}
for (i = 0; i < aValues.length; i++) {
if (aValues[i] == cond_1 && bValues[i] == cond_2) {
count += 1;
}
if (aValues[i] == cond_1 && bValues[i] == cond_3) {
count += 1;
}
if (count > 0) {
return "ERROR";
} else {
count = 0;
if (aValues[i] == cond_4 && bValues[i] == cond_2) {
count += 1;
}
if (aValues[i] == cond_4 && bValues[i] == cond_5) {
count += 1;
}
if (count > 0) {
return "RETEST";
} else {
count = 0;
if (aValues[i] == cond_6) {
count += 1;
}
if (count > 0) {
return "UNIMPLEMENTED";
} else {
return "SOLVED";
}
}
}
}
}
This is how I solved my problem. I thank to people who helped me to reach this result!
// Like COUNTIFS
var countConditionals = function(cells, condition1, condition2) {
var count = 0;
for (i = 0; i < cells.length; i++) {
if (cells[i][0] == condition1 && cells[i][1] == condition2) {
count++;
}
}
return count;
}
// Like COUNTIF
var countConditional = function(cells, condition) {
var count = 0;
for (i = 0; i < cells.length; i++) {
if (cells[i][0] == condition) {
count++;
}
}
return count;
}
//Whole Formula
function verificaStatus(cells, db) {
const ERROR = "ERROR";
const ALL = "ALL";
const RETEST = "RETEST";
const NOTYET = "UNIMPLEMENTADED";
const SOLVED = "SOLVED";
var countErrors = countConditionals(cells, ERROR, db);
var countErrorsAll = countConditionals(cells, ERROR, ALL);
var sumErrors = countErrors + countErrorsAll;
if (sumErrors > 0) {
return ERROR;
} else {
var retest = countConditionals(cells, RETEST, db);
var retestAll = countConditionals(cells, RETEST, db);
var sumRetest = retest + retestAll;
if (sumRetest > 0) {
return RETEST;
} else {
var countNonCreated = countConditional(cells, NOTYET);
if (countNonCreated > 0) {
return NOTYET;
}
}
}
return SOLVED;
}
I have a situation where a script is taking input data and sending it to a spreadsheet. After a while, this spreadsheet becomes too big.
Right now we have to manually move the items from the the primary spreadsheet to a new one. The reason is that not everyone is familiar with the code and are willing to change the ID in the code.
I would like to know if there is a way to open the spreadsheet by name. If not, is there a better way of achieving what we need (described above)
The DocsList service used in one of the answers no longer functions as it has been depreciated. I updated my scripts to look more like the following.
// Open the file
var FileIterator = DriveApp.getFilesByName(FileNameString);
while (FileIterator.hasNext())
{
var file = FileIterator.next();
if (file.getName() == FileNameString)
{
var Sheet = SpreadsheetApp.open(file);
var fileID = file.getId();
}
}
The replacement for DocsList is DriveApp https://developers.google.com/apps-script/reference/drive/drive-app
Update: DocsList has now been sunset. Use DriveApp.getFilesByName(name) instead.
David has provided some good code for shuffling your data around. If all you really did need was just to open a spreadsheet by name then this will do the trick:
function getSpreadsheetByName(filename) {
var files = DocsList.find(filename);
for(var i in files)
{
if(files[i].getName() == filename)
{
// open - undocumented function
return SpreadsheetApp.open(files[i]);
// openById - documented but more verbose
// return SpreadsheetApp.openById(files[i].getId());
}
}
return null;
}
The way that I do something similar to what you are asking for is to first have a script make a copy of my spreadsheet (which I will call a frozen backup) that is getting too big. Once I safely have the copy, I then have the same script delete all those rows from the too big spreadsheet that are no longer required. (I believe that having multiple frozen backups does not cost the google account anything, so this is viable)
Note that I delete rows one by one; this takes time. I do this since I don't delete all the rows below a point, but only certain rows which match a condition.
In my case I do have another small procedure in addition to the above, which is to have the script copy all the rows that I am about to delete into a third spreadsheet (in addition to the frozen backup), but this seems to be more that you have asked for.
This is my code (note that the main spreadsheet, in the sheet we are going to remove rows from, named 'Original', has column A as Time Stamp for each row; the cell A1 is called Time Stamp):
function ssCopy() {
var id = "0ArVhODIsJ2.... spreadsheet key of the master spreadsheet";
var smsSS = SpreadsheetApp.openById(id);
var recordingSS = SpreadsheetApp.openById("0AvhOXv5OGF.... spreadsheet key of archive spreadsheet");// you probably wont be using this
var recordingSMSCopiesSheet = recordingSS.getSheets()[0];
var outgoingSMSsheet = smsSS.getSheetByName("Original");
outgoingSMSsheet.getRange("A1").setValue("Time Stamp");
var startRow = 2;
var numRows = outgoingSMSsheet.getDataRange().getLastRow();
var numCols = 13;
var dataRange = outgoingSMSsheet.getRange(startRow, 1, numRows, numCols);
var objects = getRowsData(outgoingSMSsheet, dataRange); // Create one JavaScript object per row of data.
var rowDataNumberArray = [];
var rowToDeleteIndex = [];
for (var i = 0; i < objects.length; ++i) { // Get a row object
var rowData = objects[i];
if( Date.parse(rowData.timeStamp) > Date.parse(ScriptProperties.getProperty('lastDate')) && (rowData.done == 1) ){ //these are not used if these same 2 lines are inserted instead of here, downbelow
var rowStuff = []
rowToDeleteIndex.push(i);
for(n in objects[i]){
rowStuff.push( objects[i][n] )}
rowDataNumberArray.push( rowStuff )};
Logger.log("rowData.number1 = " + rowStuff);
}
Logger.log(" rowDataNumberArray ~ " + rowDataNumberArray);
if(rowDataNumberArray.length > 0)
{
for(row in rowDataNumberArray)
{recordingSMSCopiesSheet.appendRow(rowDataNumberArray[row]);}
}
spreadsheetFrozenBackup(id)
for( var i = rowToDeleteIndex.length-1; i >= 0; i-- ) //backwards on purpose
outgoingSMSsheet.deleteRow(rowToDeleteIndex[i]+ 0 + startRow); //so we don't have to re-calculate our row indexes (thanks to H. Abreu)
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function spreadsheetFrozenBackup(id) {
// Get current spreadsheet.
var ss = SpreadsheetApp.openById(id);
// Name the backup spreadsheet with date.
var bssName = " Frozen Spreadsheet at: " + Utilities.formatDate(new Date(), "GMT+1:00", "yyyy-MM-dd'T'HH:mm:ss") + " : " + ss.getName() ;
var bs = SpreadsheetApp.openById((DocsList.copy(DocsList.getFileById(ss.getId()), bssName)).getId());
}
//////////////////////////////////////////////////////////////////////////////////////////
//
// The code below is reused from the 'Reading Spreadsheet data using JavaScript Objects'
// tutorial.
//
//////////////////////////////////////////////////////////////////////////////////////////
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData(sheet, range, columnHeadersRowIndex) {
columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
var numColumns = range.getEndColumn() - range.getColumn() + 1;
var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
return getObjects(range.getValues(), normalizeHeaders(headers));
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit(char);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit(char) {
return char >= '0' && char <= '9';
}
// setRowsData fills in one row of data per object defined in the objects Array. // https://developers.google.com/apps-script/storing_data_spreadsheets
// For every Column, it checks if data objects define a value for it.
// Arguments:
// - sheet: the Sheet Object where the data will be written
// - objects: an Array of Objects, each of which contains data for a row
// - optHeadersRange: a Range of cells where the column headers are defined. This
// defaults to the entire first row in sheet.
// - optFirstDataRowIndex: index of the first row where data should be written. This
// defaults to the row immediately below the headers.
function setRowsData(sheet, objects, optHeadersRange, optFirstDataRowIndex) {
var headersRange = optHeadersRange || sheet.getRange(1, 1, 1, sheet.getMaxColumns());
var firstDataRowIndex = optFirstDataRowIndex || headersRange.getRowIndex() + 1;
var headers = normalizeHeaders(headersRange.getValues()[0]);
var data = [];
for (var i = 0; i < objects.length; ++i) {
var values = []
for (j = 0; j < headers.length; ++j) {
var header = headers[j];
values.push(header.length > 0 && objects[i][header] ? objects[i][header] : "");
}
data.push(values);
}
var destinationRange = sheet.getRange(firstDataRowIndex, headersRange.getColumnIndex(),
objects.length, headers.length);
destinationRange.setValues(data);
}