estimate Row Height of a Google Sheet when rendered to PDF - html

.getRowHeight() method gives only the default height 21. I want to get the row height and set it to forced. What is the need? 1. I want to add page breaks after desired row, 2. want to add cumulative sub-totals on each page 3. want to restart page numbers, 4. want to write page numbers like : 'Page 1 of 9', 5. want to download the PDF directly (without saving to drive). I have gone through almost all the threads, but I can't find anything for estimation of the row height, if I have missed any, then let me know.
Observations: Different font families occupies different space, different languages fonts occupies different space. When the range of a Google sheet is printed as PDF, it renders the texts differently then the sheet.
I tried to estimate the row height but it is not accurate. I think my approach is not good. To get accurate result, one should follow the same approach, which is used by Google. (I think, they should have used the scroll Height of HTML element text area.)
The whole code is in this Google SpreadSheet. Run the function from custom Menu 'Row Heights' > estimateRowHeightsPDF, It will fetch the data from 'DataSheet', will calculate rowHeights and it will paste the data in 'PrintSheet', with extra columns showing the cellHeight of every cell and noOfLines of every cell. First 11 rows data is protected in DataSheet but you can add your data in the rows 12 onwards. Please keep these functions as they are. Add another .gs or .html files in Apps Script and other sheets if needed. Thank you in advance.
// count number of lines of a cell content depending on colWidths and font size
// This is only for the default font (Arial)
// should modify for other font families and font weight, fonts of different languages too
// Here I have only Gujarati or English Fonts
function countNoOfLinesForDefaultArial(str, colWidth, fontSize){
if(isValidDate(str)){
//format like str = "28/12/2022";
str = Utilities.formatDate(str, timeZone, "dd/MM/yyyy");
}else if(typeof str === "number"){
// to keep preceeding zeros
str = "'" + str.toString();
}else{
str = str.toString();
}
var lines = str.split(/\r\n|\r|\n/);
var noOflines = 0;
// found by trial and error
if(includesGujaratiFont(str)){
var multiplier = colWidth <= 100 ? 8 : colWidth <= 300 ? 7.9 : colWidth <= 400? 8.5 : colWidth <= 500? 8.2 : 7.5;
}else{
var multiplier = colWidth <= 100 ? 7: colWidth <= 300 ? 6.5 : colWidth <= 400? 6.35 : colWidth <= 500? 6.25 : 6.20;
}
multiplier = Math.ceil(multiplier*fontSize/10);
lines.forEach(line => {
// If there is \n\n then split will give a blank string
// of length 0 but we should add 1 line height for that
noOflines += line.length === 0? 1 : Math.ceil(line.length * multiplier / colWidth) ;
})
return noOflines;
}
Then the noOfLines is multiplied with the height to get the cell Height
// it gives estimated cell height
function getRowHeightManuallyFromStrForDefaultArialByIdentifyingFonts(str, colWidth, fontSize){
if(isValidDate(str)){
//format like str = "28/12/2022";
str = Utilities.formatDate(str, timeZone, "dd/MM/yyyy");
}else if(typeof str === "number"){
// to keep preceeding zeros
str = "'" + str.toString();
}else{
str = str.toString();
}
var count = countNoOfLinesForDefaultArial(str, colWidth, fontSize);
// by trial and error
if(includesGujaratiFont(str)){
var calculatedHeight = count == 1? 24: count < 6? (4*24 + (count-4)*23): count < 9? (5*24 + (count-5)*23):count < 10? (4*24 + (count-4)*23): count < 12? (7*24 + (count-7)*23): count < 17? (5*25 + (count-5)*24): (8*24 + (count-8)*23);
}else{
var calculatedHeight = count == 1? 21: count < 4? (1*21 + (count-1)*20): count < 6? (2*20 + (count-2)*19): count < 9? (3*20 + (count-3)*19):count < 10? (4*20 + (count-4)*19): count < 12? count*19: (5*19 + (count-5)*18);
}
calculatedHeight = Math.ceil(calculatedHeight*fontSize/10);
return calculatedHeight;
}
Then I am using these functions in the following function
function estimateRowHeightsPDF() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName("DataSheet");
var range = dataSheet.getRange("A2:D"+dataSheet.getLastRow());
var vals = range.getValues();
var fontSizes = range.getFontSizes();
var printSheet = ss.getSheetByName("PrintSheet");
var colWidths = getColWidthsFromTo(dataSheet, "A", "D");
colWidths.forEach((col, i) => {
printSheet.setColumnWidths(i+1,1,col);
});
var estimatedRowHeights = [];
var noOfLinesLineLengthsHeights = [];
vals.forEach((row, i) => {
var rowHeight = 0;
row.forEach((cell, j) => {
var noOfLines = countNoOfLinesForDefaultArial(cell, colWidths[j], fontSizes[i][j]);
var heightOfCell = getRowHeightManuallyFromStrForDefaultArialByIdentifyingFonts(cell, colWidths[j], fontSizes[i][j]);
var lineLengths = lineLengthForEachLine(cell, colWidths[j], fontSizes[i][j]);
if(heightOfCell > rowHeight) { rowHeight = heightOfCell};
noOfLinesLineLengthsHeights.push([ [noOfLines, lineLengths, heightOfCell].join(" : ") ]);
vals[i].push([noOfLines, lineLengths, fontSizes[i][j], heightOfCell].join(" : "));
});
estimatedRowHeights.push(rowHeight);
});
estimatedRowHeights.forEach((height, i) => {
// forced height to set
// header row to keep as it is
printSheet.setRowHeightsForced(i+2,1,height);
});
printSheet.getRange(2,1,vals.length, vals[0].length).setValues(vals).setWrap(true).setBorder(true,true,true,true,true,true).setVerticalAlignment("top");
printSheet.getRange(2,1,vals.length, 4).setFontSizes(fontSizes);
var fileName = "PrintSheet";
var lastColLetter = "H"
var orientation = "false"; // portrait
var lastRow = printSheet.getLastRow();
var rng = "gid=" + printSheet.getSheetId().toString() + "&range=A1:" + lastColLetter + lastRow + "&";
var url = "https://docs.google.com/spreadsheets/d/" + ss.getId() + "/export" +
"?format=pdf&" +
rng +
"size=7&" +
"fzr=true&" +
"portrait=" + orientation + "&" +
"fitw=true&" +
"gridlines=false&" +
"printtitle=false&" +
"sheetnames=false&" +
"pagenum=CENTER&" +
"horizontal_alignment=CENTER&" +
"vertical_alignment=TOP&" +
"top_margin=0.6&left_margin=0.6&right_margin=0.6&bottom_margin=0.6&" +
"scale=2&" +
"attachment=true";
DownloadPDFFromUrl2(url, fileName, "Downloading PDF: " + fileName);
} // ends estimateRowHeightsPDF() func
The following helping functions are taken from stakeOverFlow only. Other functions used in above code are there in 'HelpingFuncs.gs' file.
// If some fonts (at least one) are Gujarati, then returns true
function includesGujaratiFont(str){
return /[\u0A80-\u0AFF]/.test(str);
}
// From http://stackoverflow.com/questions/1353684
// Returns 'true' if variable d is a date object.
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}

Related

GoogleScript stopping at random and not completing its given task to sort through a response sheet

So I have this script pulling from a response sheet and running an if statement on each response to check what type of response it is at the current moment I have to types it checks for one being "Register a new Fox" and "Submit a Transaction" I'm pretty new to googlescripts with a decent knowledge of other languages. I'm looking for a solution for why my loop is stopping and how it can be fixed rather, any examples will help a lot. Thanks in advance.
function SetCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var debugSheet = ss.getSheetByName("Debug")
var famTree = ss.getSheetByName("Family")
var transaction = ss.getSheetByName("Transactions")
var sheetMaster = ss.getSheetByName("Form Responses")
debugSheet.getRange("Debug!A1").setValue(sheetMaster.getRange("Form Responses!C2").getValue());
debugSheet.getRange("Debug!A2").setValue(sheetMaster.getRange("Form Responses!C:C").getLastRow());
var mainRange = sheetMaster.getRange("Form Responses!B2:B");
var registeredFoxes = 0
var transactionsMade = 0
for (i=2; i <= sheetMaster.getRange("Form Responses!C:C").getLastRow();i++){
// Checks to see if they are trying to add a new fox.
if (sheetMaster.getRange("Form Responses!C" + i.toString()).getValue() == "Register a New Fox"){
famTree.getRange("Family!B"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!D" + i.toString()).getValue());
famTree.getRange("Family!C"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!E" + i.toString()).getValue());
famTree.getRange("Family!D"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!F" + i.toString()).getValue());
famTree.getRange("Family!E"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!G" + i.toString()).getValue());
famTree.getRange("Family!H"+(6+registeredFoxes)).setValue(sheetMaster.getRange("Form Responses!H" + i.toString()).getValue());
registeredFoxes = registeredFoxes + 1;
// Checks to see if they are submiting a transaction.
}else if (sheetMaster.getRange("Form Responses!C" + i.toString()).getValue() == "Submit a Transaction"){
transaction.getRange("Transactions!E"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!I" + i.toString()).getValue());
transaction.getRange("Transactions!F"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!L" + i.toString()).getValue());
if (sheetMaster.getRange("Form Responses!J" + i.toString()).getValue() == "Profit"){
transaction.getRange("Transactions!C"+(8+transactionsMade)).setValue("+");
transaction.getRange("Transactions!D"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!K" + i.toString()).getValue());
}else{
transaction.getRange("Transactions!C"+(8+transactionsMade)).setValue("-");
transaction.getRange("Transactions!D"+(8+transactionsMade)).setValue("-"+ sheetMaster.getRange("Form Responses!K" + i.toString()).getValue());
}
transaction.getRange("Transactions!B"+(8+transactionsMade)).setValue(sheetMaster.getRange("Form Responses!M" + i.toString()).getValue());
transactionsMade = transactionsMade + 1
}else{
debugSheet.getRange("Debug!A3").setValue(registeredFoxes);
}
}
famTree.getRange("Family!B2").setValue("Fox Family Members:\n" + registeredFoxes)
transaction.getRange("Transactions!B4").setValue("Transactions Made:\n" + transactionsMade)
}
The only way that you can speed things up is to get all the data out of all 3 sheet tabs, manipulate the data in the 2D arrays, and then set the new values all at once. Currently you are getting and setting values on every iteration of the loop, so there are read and write operations happening constantly which takes time. You need to read all the data once, and write all the data once.
The loop might be terminating for some reason. And the only reason it might be doing that is if the count hits the last row number, or there is an error in your code:
I would change:
for (i=2; i <= sheetMaster.getRange("Form Responses!C:C").getLastRow();i++){
To:
var L = sheetMaster.getRange("Form Responses!C:C").getLastRow();
Logger.log("L: " + L)
for (i=2; i<=L;i++){
This seems to work but it's not orthodox any adjustments that could be made let me know
Pretty much it was taking to much effort and memory to find the fields that I keep calling so I got all of them at once and then ran statements off that.
function SetCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var debugSheet = ss.getSheetByName("Debug")
var family = ss.getSheetByName("Family")
var transaction = ss.getSheetByName("Transactions")
var sheetMaster = ss.getSheetByName("Form Responses")
// Starts from Row 2 Column C
var vA=sheetMaster.getRange(2, 3,(sheetMaster.getLastRow()-1),(sheetMaster.getLastColumn()-2)).getValues();
var lastColumn = sheetMaster.getLastColumn()
var registeredFoxes = 0
var transactionsMade = 0
var slotsScanned = 0
var L = sheetMaster.getRange("Form Responses!C:C").getLastRow();
Logger.log("L: " + L)
for (i=2; i<=L;i++){
// Checks to see if they are trying to add a new fox.
if (vA[i-2][0] == "Register a New Fox"){
for (j=1;j<=5;j++){
if (j==5){
family.getRange(registeredFoxes+6,j+3).setValue(vA[i-2][j]);
}else{
family.getRange(registeredFoxes+6,j+1).setValue(vA[i-2][j]);
}
}
registeredFoxes ++
// Checks to see if they are submiting a transaction.
}else if (vA[i-2][0] == "Submit a Transaction"){
for (j=7; j<=11;j++){
transaction.getRange(transactionsMade+8,j-5).setValue(vA[i-2][j-1]);
if (vA[i-2][j-1] == "Profit"){
transaction.getRange(transactionsMade+8,j-5).setValue("+");
}else if (vA[i-2][j-1] == "Loss"){
transaction.getRange(transactionsMade+8,j-5).setValue("-");
}else if (j == 9 && vA[i-2][7] == "Loss"){
transaction.getRange(transactionsMade+8,j-5).setValue("-" + vA[i-2][j-1]);
}
}
transactionsMade ++
}else{
Logger.log("Tried to read something...")
}
}
family.getRange(2,2).setValue("Fox Family Members:\n" + registeredFoxes);
transaction.getRange(4,2).setValue("Transactions Made:\n" + transactionsMade);
}

google script : how can we extract hyperlink of label using script, which wont see its URL on your Google Sheets formula bar? [duplicate]

I have a sheet where hyperlink is set in cell, but not through formula. When clicked on the cell, in "fx" bar it only shows the value.
I searched on web but everywhere, the info is to extract hyperlink by using getFormula().
But in my case there is no formula set at all.
I can see hyperlink as you can see in image, but it's not there in "formula/fx" bar.
How to get hyperlink of that cell using Apps Script or any formula?
When a cell has only one URL, you can retrieve the URL from the cell using the following simple script.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var url = sheet.getRange("A2").getRichTextValue().getLinkUrl(); //removed empty parentheses after getRange in line 2
Source: https://gist.github.com/tanaikech/d39b4b5ccc5a1d50f5b8b75febd807a6
When Excel file including the cells with the hyperlinks is converted to Google Spreadsheet, such situation can be also seen. In my case, I retrieve the URLs using Sheets API. A sample script is as follows. I think that there might be several solutions. So please think of this as one of them.
When you use this script, please enable Sheets API at Advanced Google Services and API console. You can see about how to enable Sheets API at here.
Sample script:
var spreadsheetId = "### spreadsheetId ###";
var res = Sheets.Spreadsheets.get(spreadsheetId, {ranges: "Sheet1!A1:A10", fields: "sheets/data/rowData/values/hyperlink"});
var sheets = res.sheets;
for (var i = 0; i < sheets.length; i++) {
var data = sheets[i].data;
for (var j = 0; j < data.length; j++) {
var rowData = data[j].rowData;
for (var k = 0; k < rowData.length; k++) {
var values = rowData[k].values;
for (var l = 0; l < values.length; l++) {
Logger.log(values[l].hyperlink) // You can see the URL here.
}
}
}
}
Note:
Please set spreadsheetId.
Sheet1!A1:A10 is a sample. Please set the range for your situation.
In this case, each element of rowData is corresponding to the index of row. Each element of values is corresponding to the index of column.
References:
Method: spreadsheets.get
If this was not what you want, please tell me. I would like to modify it.
Hey all,
I hope this helps you save some dev time, as it was a rather slippery one to pin down...
This custom function will take all hyperlinks in a Google Sheets cell, and return them as text formatted based on the second parameter as either [JSON|HTML|NAMES_ONLY|URLS_ONLY].
Parameters:
cellRef : You must provide an A1 style cell reference to a cell.
Hint: To do this within a cell without hard-coding
a string reference, you can use the CELL function.
eg: "=linksToTEXT(CELL("address",C3))"
style : Defines the formatting of the output string.
Valid arguments are : [JSON|HTML|NAMES_ONLY|URLS_ONLY].
Sample Script
/**
* Custom Google Sheet Function to convert rich-text
* links into Readable links.
* Author: Isaac Dart ; 2022-01-25
*
* Params
* cellRef : You must provide an A1 style cell reference to a cell.
* Hint: To do this within a cell without hard-coding
* a string reference, you can use the CELL function.
* eg: "=linksToTEXT(CELL("address",C3))"
*
* style : Defines the formatting of the output string.
* Valid arguments are : [JSON|HTML|NAMES_ONLY|URLS_ONLY].
*
*/
function convertCellLinks(cellRef = "H2", style = "JSON") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.getRange(cellRef).getCell(1,1);
var runs = cell.getRichTextValue().getRuns();
var ret = "";
var lf = String.fromCharCode(10);
runs.map(r => {
var _url = r.getLinkUrl();
var _text = r.getText();
if (_url !== null && _text !== null) {
_url = _url.trim(); _text = _text.trim();
if (_url.length > 0 && _text.length > 0) {
switch(style.toUpperCase()) {
case "HTML": ret += '' + _text + '}' + lf; break;
case "TEXT": ret += _text + ' : "' + _url + '"' + lf; break;
case "NAMES_ONLY" : ret += _text + lf; break;
case "URLS_ONLY" : ret += _url + lf; break;
//JSON default : ...
default: ret += (ret.length>0?(','+ lf): '') +'{name : "' + _text + '", url : "' + _url + '"}' ; break;
}
ret += lf;
}
}
});
if (style.toUpperCase() == "JSON") ret = '[' + ret + ']';
//Logger.log(ret);
return ret;
}
Cheers,
Isaac
I tried solution 2:
var urls = sheet.getRange('A1:A10').getRichTextValues().map( r => r[0].getLinkUrl() ) ;
I got some links, but most of them yielded null.
I made a shorter version of solution 1, which yielded all the links.
const id = SpreadsheetApp.getActive().getId() ;
let res = Sheets.Spreadsheets.get(id,
{ranges: "Sheet1!A1:A10", fields: "sheets/data/rowData/values/hyperlink"});
var urls = res.sheets[0].data[0].rowData.map(r => r.values[0].hyperlink) ;

#REF! being added to formula by setFormula() instead of an actual reference: IF(NOT(ISBLANK(N3)),#REF!,0))

I've created a script that generates markbooks for teachers in a school.
The script adds formulas that convert 'raw marks' into individual test grades.
Additionally, an overview of how a pupil is doing over all the tests they have sat so far is calculated from a formula looks up the percentage to assign an overall grade.
The part of the script I've written to accomplish this is below.
The issue I am having is that although the formula is being constructed correctly, some of the cell references are not being added. Instead of the cell ref I'm just getting #Ref!
In the case of the example sheet below, the 3 #ref! should actually be O1, R1 and U1.
I've separated this part out in my script as a variable (totalRef) on it's own to try to help me debug.
My first thought was that this was because the formula was being added and ins some cases the column that was being referenced did not yet exist, but I've eliminated that and the problem still persists.
Example sheet:
https://docs.google.com/spreadsheets/d/1QXDinhu6Ywlf0lNe3dZBLWMf0pxPrKHgTqfPeEfn7ug/edit?usp=sharing
var noOfAssessments = assessments.length;
var currentCol = 14;//first raw mark assessment column
var startCumul= "";
var endCumul = "";
for (var no = 0; no < noOfAssessments; no ++)
{
var totalRef = columnToLetter(currentCol+1)+"1";
Logger.log(years[y] + " TOTALREF IS "+totalRef);
startCumul = startCumul + "(IF(NOT (ISBLANK("+columnToLetter(currentCol)+(fRow +1)+")),"+columnToLetter(currentCol)+(fRow +1)+",0))";
endCumul = endCumul + "(IF(NOT(ISBLANK("+columnToLetter(currentCol)+(fRow +1)+")),"+totalRef+",0))"
if (no < noOfAssessments - 1)
{
startCumul = startCumul + "+";
endCumul = endCumul + "+";
}
currentCol = currentCol + 3;//3 IS THE NUMBER OF COLS BETWEEN ASSESSMENTS. THIS VAL MIGHT NEED TO BE CHANGE IF USING QLA
}
var wholeCumulFormula = "=IFERROR(LOOKUP(ROUND((" + startCumul + ")/("+endCumul+")*"+getYearPercentages(years[y],setupData)+"),{";
//add the base percentage boundaries
var pBoundaries = getPercentageBoundaries(setupData);
wholeCumulFormula = wholeCumulFormula + pBoundaries + "},{\"1C\",\"1B\",\"1A\",\"2C\",\"2B\",\"2A\",\"3C\",\"3B\",\"3A\",\"4C\",\"4B\",\"4A\",\"5C\",\"5B\",\"5A\",\"6C\",\"6B\",\"6A\",\"7C\",\"7B\",\"7A\",\"8C\",\"8B\",\"8A\",\"9C\",\"9B\",\"9A\"}),\"U\")";
if (!yearData[fRow][12])//only overwrite blank cells
{
yearSheet.getRange(fRow+1,13,1,1).setFormula(wholeCumulFormula);
}
This is the function used to create the ref:
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}

Apps Script: how to get hyperlink from a cell where there is no formula

I have a sheet where hyperlink is set in cell, but not through formula. When clicked on the cell, in "fx" bar it only shows the value.
I searched on web but everywhere, the info is to extract hyperlink by using getFormula().
But in my case there is no formula set at all.
I can see hyperlink as you can see in image, but it's not there in "formula/fx" bar.
How to get hyperlink of that cell using Apps Script or any formula?
When a cell has only one URL, you can retrieve the URL from the cell using the following simple script.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var url = sheet.getRange("A2").getRichTextValue().getLinkUrl(); //removed empty parentheses after getRange in line 2
Source: https://gist.github.com/tanaikech/d39b4b5ccc5a1d50f5b8b75febd807a6
When Excel file including the cells with the hyperlinks is converted to Google Spreadsheet, such situation can be also seen. In my case, I retrieve the URLs using Sheets API. A sample script is as follows. I think that there might be several solutions. So please think of this as one of them.
When you use this script, please enable Sheets API at Advanced Google Services and API console. You can see about how to enable Sheets API at here.
Sample script:
var spreadsheetId = "### spreadsheetId ###";
var res = Sheets.Spreadsheets.get(spreadsheetId, {ranges: "Sheet1!A1:A10", fields: "sheets/data/rowData/values/hyperlink"});
var sheets = res.sheets;
for (var i = 0; i < sheets.length; i++) {
var data = sheets[i].data;
for (var j = 0; j < data.length; j++) {
var rowData = data[j].rowData;
for (var k = 0; k < rowData.length; k++) {
var values = rowData[k].values;
for (var l = 0; l < values.length; l++) {
Logger.log(values[l].hyperlink) // You can see the URL here.
}
}
}
}
Note:
Please set spreadsheetId.
Sheet1!A1:A10 is a sample. Please set the range for your situation.
In this case, each element of rowData is corresponding to the index of row. Each element of values is corresponding to the index of column.
References:
Method: spreadsheets.get
If this was not what you want, please tell me. I would like to modify it.
Hey all,
I hope this helps you save some dev time, as it was a rather slippery one to pin down...
This custom function will take all hyperlinks in a Google Sheets cell, and return them as text formatted based on the second parameter as either [JSON|HTML|NAMES_ONLY|URLS_ONLY].
Parameters:
cellRef : You must provide an A1 style cell reference to a cell.
Hint: To do this within a cell without hard-coding
a string reference, you can use the CELL function.
eg: "=linksToTEXT(CELL("address",C3))"
style : Defines the formatting of the output string.
Valid arguments are : [JSON|HTML|NAMES_ONLY|URLS_ONLY].
Sample Script
/**
* Custom Google Sheet Function to convert rich-text
* links into Readable links.
* Author: Isaac Dart ; 2022-01-25
*
* Params
* cellRef : You must provide an A1 style cell reference to a cell.
* Hint: To do this within a cell without hard-coding
* a string reference, you can use the CELL function.
* eg: "=linksToTEXT(CELL("address",C3))"
*
* style : Defines the formatting of the output string.
* Valid arguments are : [JSON|HTML|NAMES_ONLY|URLS_ONLY].
*
*/
function convertCellLinks(cellRef = "H2", style = "JSON") {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.getRange(cellRef).getCell(1,1);
var runs = cell.getRichTextValue().getRuns();
var ret = "";
var lf = String.fromCharCode(10);
runs.map(r => {
var _url = r.getLinkUrl();
var _text = r.getText();
if (_url !== null && _text !== null) {
_url = _url.trim(); _text = _text.trim();
if (_url.length > 0 && _text.length > 0) {
switch(style.toUpperCase()) {
case "HTML": ret += '' + _text + '}' + lf; break;
case "TEXT": ret += _text + ' : "' + _url + '"' + lf; break;
case "NAMES_ONLY" : ret += _text + lf; break;
case "URLS_ONLY" : ret += _url + lf; break;
//JSON default : ...
default: ret += (ret.length>0?(','+ lf): '') +'{name : "' + _text + '", url : "' + _url + '"}' ; break;
}
ret += lf;
}
}
});
if (style.toUpperCase() == "JSON") ret = '[' + ret + ']';
//Logger.log(ret);
return ret;
}
Cheers,
Isaac
I tried solution 2:
var urls = sheet.getRange('A1:A10').getRichTextValues().map( r => r[0].getLinkUrl() ) ;
I got some links, but most of them yielded null.
I made a shorter version of solution 1, which yielded all the links.
const id = SpreadsheetApp.getActive().getId() ;
let res = Sheets.Spreadsheets.get(id,
{ranges: "Sheet1!A1:A10", fields: "sheets/data/rowData/values/hyperlink"});
var urls = res.sheets[0].data[0].rowData.map(r => r.values[0].hyperlink) ;

Row count off by one in google spreadsheet

I want to delete empty rows. Row 885 is a non-empty row and should be retained. However, my code says (and shows in the msgbox) that 885 is empty and should be deleted. When it gets to i=884, it prints what is really in row 885 and says it will NOT be deleted. So, I'm confused. It appears to be acting upon the data properly, yet it's reporting the row number wrong. I suspect you're going to tell me offsets are zero based and rows start at 1, so if lastrow=888 (1 based), then I need to subtract 1 to match the offset. so what I see as row 885 in the spreadsheet is really row 884 as an offset.
But... when i=row 885 has data, the offset is 884. And when i=884, the offset is 883... so why is it printing what's in row 885??? Seriously confused here. What am I getting wrong?
Last but most importantly... it's deleting the wrong row! How can it be referencing the right data, yet still deleting the wrong row???
var s = SpreadsheetApp.getActiveSheet();
var range = SpreadsheetApp.getActiveSheet().getDataRange();
var i = range.getLastRow();
var msg;
var colNum;
for (; i > 1; i--) { // I like to start at the end
var foundAvalue=0; // reset flag for each row
rowRange = range.offset(i, 0, 1);
var valArray=rowRange.getValues();
var foundAvalue=0;
var msg;
var totalColumns=rowRange.getNumColumns();
colNum=0;
while (colNum < totalColumns && (foundAvalue==0)) {
if (valArray[0][colNum] != '') {
foundAvalue=1;
msg="Row " + i + " =" + valArray[0];
Browser.msgBox(msg);
msg="Row " + i + " will not be deleted";
Browser.msgBox(msg);
}
colNum++;
}
if (foundAvalue == 0) {
msg="Row " + i + " =" + valArray[0] + "WILL be deleted";
Browser.msgBox(msg);
// delete empty row
// s.deleteRow(i);
}
} // end for(i)
Below is some code which should accomplish what you want. You are correct in that the root of the problem is in the 0-based indexing. The tricky part is that JavaScript/Apps Script arrays use 0-based indexing, but when you call something like getLastRow(), the returned range is 1-based. All-in-all, your code is good - it is only that issue that is tripping you up. Hope this helps:
function DeleteFun(){
var mySheet = SpreadsheetApp.getActiveSheet();
var range = mySheet.getDataRange();
// Iterate using a counter, starting at the last row and stopping at
// the header (assumes the header is in the first row)
for (i = mySheet.getLastRow() - 1; i > 0; i--) {
var foundAvalue = 0;
// Here we get the values for the current row and store in an array
rowRange = range.getValues()[i]
// Now we iterate through that array
for (j = 0; j <= rowRange.length; j++) {
// If any non-nulls are found, alert they won't be deleted and move on
if (rowRange[j] != null && rowRange[j] != '') {
foundAvalue = 1;
msg="Row " + (i+1) + " =" + rowRange[0] + " and will not be deleted";
Browser.msgBox(msg);
break;
}
}
if (foundAvalue == 0) {
msg="Row " + (i+1) + " =" + rowRange[0] + "WILL be deleted";
Browser.msgBox(msg);
// Delete empty row
mySheet.deleteRow(i+1);
}
}
}
You could do it in 'pure' array as well if you don't use formulas in your sheet.
Like this for example :
function deleteEmptyRows(){
var sh = SpreadsheetApp.getActiveSheet();
var data = sh.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};// checks the whole row
Logger.log(data[n].join().replace(/,/g,''))
}
sh.getDataRange().clear(); // clear the whole sheet
sh.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);//write back all non empty rows
}