Apps Scrip : function IsUnderline() - google-apps-script

I want to write a custom function for Google Sheet called to count the number of underlines cells in a range

When getCell(j, i) and getFontLine() are used in a loop, I'm worried that the process cost becomes a bit high. When the process cost is reduced, how about the following sample script?
Sample script:
function COUNT_UNDERLINED_CELLS(prange) {
return SpreadsheetApp
.getActiveSheet()
.getRange(prange)
.getTextStyles()
.reduce((n, r) => {
r.forEach(c => {
if (c.isUnderline()) {
n++;
}
});
return n;
}, 0);
}
In this case, it supposes that prange is a string value of A1Notation. Please be careful about this.
References:
reduce()
getTextStyles()
isUnderline()

function COUNT_UNDERLINED_CELLS(prange) {
const range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(prange);
const numRows = range.getNumRows();
const numCols = range.getNumColumns();
var count = 0;
for (let i = 1; i <= numCols; i++) {
for (let j = 1; j <= numRows; j++) {
if (range.getCell(j, i).getFontLine() == "underline") {
count++;
}
}
}
return count;
}

Related

Google Apps Script - Matching 2 String's

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

GAS - Find empty cells rather slow

I have the following code in Google Apps Script. It loops through more than 800 rows to find empty cells of a column. But it takes too long. Is there a way to speed it up?
for(var t = 2; t <= lastrow; t++)
{
if (Geojson_Data.getRange(t,col1).getValue() == "")
{
Geojson_Data.deleteRow(t);
}
}
function delRowsWithNothingInColA() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getRange(2,1,sh.getLastRow()-1,1);
var vA=rg.getValues();
var d=0;
for(var i=0;i<vA.length;i++) {
if(vA[i][0]=='') {
sh.deleteRow(i+2-d++)
}
}
}
var values = Geojson_Data.getDataRange().getValues();
var col1Arr = col1 - 1;
var t = values.length;
while(t--)
if(values[t][col1Arr] === '')
Geojson_Data.deleteRow(t + 1);
If you need a really tool then look at the snippet The script removes rows in batches without changing rows queue and without destroying formulas.
/**
* Runs the snippet.
* Removes rows by condition 'A:A=""'.
* #ignore
*/
function run() {
var sheet = SpreadsheetApp.getActiveSheet();
deleteRowsByConditional_(sheet, function(row) {
return row[0] === '';
});
}
function deleteRowsByConditional_(sheet, condition, action) {
var values = sheet.getDataRange().getValues();
values.unshift([]);
values.reverse().forEach(
function() {
var i = this.l - arguments[1];
if (this.condition.apply(null, [arguments[0], i, arguments[2]])) {
this.isContinue++;
} else if (this.isContinue) {
if (action) action(arguments[2], i, this.isContinue);
this.sheet.deleteRows(i, this.isContinue);
this.isContinue = 0;
}
},
{ sheet: sheet, condition: condition, isContinue: 0, l: values.length }
);
}

How to break / exit out of a For loop in Googlesheets Script

How do I break out of a loop / stop a function when a specific logical condition is true in Googlesheets script. In my case, I have a program which, in a loop continuously sets the value of cell B1 and evaluates the result in cell D11.
What I want is that if the result is a string NNN, then the program must stop immediately.
Following is what I have, but the program doesn't exit / stop / quit when the logical condition is true (the program otherwise works fine). Any help appreciated.
function loopX() {
var xx;
var yy;
......
for (var i = 0; i < data.length; i++) {
sheet.getRange('B1').setValue(data[i][0]);
SpreadsheetApp.flush();
Utilities.sleep(4000);
if (sheet.getRange('D11').getValue() == 'NNN')
exit();
}
......
}
Updated
function loopC() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('SheetN'); // name of your sheet
var data = ss.getSheetByName('data').getRange('A625:A2910').getValues()‌​;
if (sheet.getRange('D11').getValue() != "NNN") {
for (var i = 0; i < data.length; i++) {
sheet.getRange('B1').setValue(data[i][0]);
SpreadsheetApp.flush();
Utilities.sleep(4000);
}
}
}
You can use break to go out from FOR LOOP. I think that this is a simple way.
Sample script :
var ar = [["e1"], ["e2"], ["e3"], ["e4"], ["NNN"], ["e6"]];
for (var i = 0; i < ar.length; i++) {
if (ar[i][0] == 'NNN') {
break;
}
}
Logger.log(i) // 4
Modified your script :
If this is reflected to your script, it can modify as follows.
function loopX() {
var xx;
var yy;
......
for (var i = 0; i < data.length; i++) {
sheet.getRange('B1').setValue(data[i][0]);
SpreadsheetApp.flush();
Utilities.sleep(4000);
if (sheet.getRange('D11').getValue() == 'NNN') {
break;
}
}
......
}
But, in your script, I think that sheet.getRange('D11').getValue() might be able to be written to out of FOR LOOP like below. About this, since I don't know the detail your script, please confirm it.
if (sheet.getRange('D11').getValue() != 'NNN') {
for (var i = 0; i < data.length; i++) {
sheet.getRange('B1').setValue(data[i][0]);
SpreadsheetApp.flush();
Utilities.sleep(4000);
}
}

Use formula inside script

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;
}

Insert table from gmail to google spreadsheet by google script

I recive an email with table many times in one day it can be 10x2 or 40x2 or something like this. Every time I need to paste this table in sheet start from cell A1/ Now I have this code, but it paste whole table to cell A1:
var SEARCH_QUERY = "label:inbox is:unread to:me subject:importnumbers";
// Credit: https://gist.github.com/oshliaer/70e04a67f1f5fd96a708
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
emails.push([msgs[j].getBody().replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '').replace(/^\s*/gm, '').replace(/\s*\n/gm, '\n')
]);
}
}
return emails;
}
function appendData_(sheet, array2d) {
sheet.getRange(1, 1).setValues(array2d);
}
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActiveSpreadsheet().getSheetByName('numberlist'), array2d);
}
markArchivedAsRead ();
}
function markArchivedAsRead() {
var threads = GmailApp.search('label:inbox is:unread to:me subject:importnumberlist');
GmailApp.markThreadsRead(threads);
};
Please, help me to paste table with right dimensions.
Try this one.
Replace getEmails_(q) function
Code#1:
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
var arrStr = msgs[j].getBody()
.replace(/<\/tr>/gm, '[/tr]')
.replace(/<\/td>/gm, '[/td]')
.replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '')
.replace(/^\s*/gm, '')
.replace(/\s*\n/gm, '\n')
.split("[/tr]");
var line = [];
for (var i = 0; i < arrStr.length - 1; i++) {
line = arrStr[i].split("[/td]");
line.length -= 1;
emails.push(line);
}
}
}
return emails;
}
Replace appendData_ function
Code#2:
function appendData_(sheet, array2d) {
var h = array2d.length;
var l = array2d[0].length;
sheet.getRange(1, 1, h, l).setValues(array2d);
}
This code converts the html text into 2d array and then paste it into Spreadsheet.
Note 2020-06
Your email may (1) contain several tables or (2) a table with merged cells. In this case, the proposed script may cause an error:
The number of columns in the data does not match the number of columns
in the range
In this situation, you'll need to create another function to parse the message body. I suggest you see the HTML-code of your message and inspect it in Gmail:
Right Click > Instect
or [Ctrl]+[Shift]+[I]
The other approach in this situation is to paste data "as is", use sample function to convert any 2d array into rectangular:
function convert2ArrayToRectangular_(array2d)
{
// get max width
var res = [];
var w = 0;
for (var i = 0; i < array2d.length; i++)
{
if (array2d[i].length > w) {w = array2d[i].length;}
}
var row = [];
for (var i = 0; i < array2d.length; i++)
{
row = array2d[i];
if(array2d[i].length < w)
{
for (var ii = array2d[i].length; ii < w; ii++)
{
row.push('');
}
}
res.push(row);
}
return res;
}
Sample usage:
function test_get_email() {
var q = 'Test_table_to_sheets';
var array2d = getEmails_(q);
var sheet = SpreadsheetApp.openById('1BKkd5LwBYyGoi2um-S3pTCBKrUEko34m9vJu94K8uOQ').getSheetByName('Test_table_to_sheets2');
appendData_(sheet, convert2ArrayToRectangular_(array2d));
}