I am using the =IMAGE(URL) formula in google sheets.
The images are loaded from the url and then displayed in the cells.
I want to stop using the url to fetch images, and instead "download" them to display them.
So if the url changes it remains active.
How can I do that ? :)
Thanks!
If there are just a couple of images, copy the URL from each image() formula and use Insert > Image > Image in cell > By URL to replace the formula with the image.
To automate that, you will need a script. Try something like this:
function test() {
const sheetRegex = /^(Sheet1|Sheet2|Sheet3)$/i;
let numRangesModified = 0;
let totalNumReplaced = 0;
const ss = SpreadsheetApp.getActive();
let sheets;
try {
ss.toast(`Replacing image formulas with in-cell images...`, 'Please wait', 30);
const sheets = ss.getSheets()
.filter(sheet => sheet.getName().match(sheetRegex));
if (!sheets.length) {
ss.toast(`Cannot find any sheets that match ${sheetRegex.toString()}.`);
return;
}
const ranges = sheets.map(sheet => sheet.getDataRange());
ranges.forEach(range => {
const numReplaced = replaceImageFormulasWithImages_(range);
if (numReplaced) {
numRangesModified += 1;
totalNumReplaced += numReplaced;
}
});
ss.toast(`Replaced ${totalNumReplaced} image formula(s) with in-cell images in ${numRangesModified} range(s).`, 'Done', 10);
} catch (error) {
ss.toast(`Replaced ${totalNumReplaced} image formula(s) in ${numRangesModified} range(s). ${error.message}`, 'Error', 30);
}
}
/**
* Replaces all image() formulas in a range with in-cell images.
*
* Supports formulas like image(E2) where E2 contains a URL, and
* formulas like image("https://www.example.com/pict/image01.png").
* The replacement is done cell by cell to work around an issue
* in the SpreadsheetApp API.
*
* #param {SpreadsheetApp.Range} range The range where to replace image() formulas with in-cell images.
* #return {Number} The number of image() formulas replaced with in-cell images.
*/
function replaceImageFormulasWithImages_(range) {
// version 1.1, written by --Hyde, 9 December 2022
// - add cellA1
// version 1.0, written by --Hyde, 8 December 2022
// - see https://stackoverflow.com/a/74736461/13045193
const sheet = range.getSheet();
let numReplaced = 0;
range.getFormulas().forEach((row, rowIndex) =>
row.forEach((formula, columnIndex) => {
if (formula.match(/^(=image)/i)) {
let url;
let match = formula.match(/^=image\("(http[^"]+)"/i);
if (match) {
url = match[1];
} else {
match = formula.match(/^=image\(([A-Z]{1,3}\d{1,6})\)/i);
if (match) {
let cellA1 = match[1];
try {
url = sheet.getRange(cellA1).getDisplayValue();
} catch (error) {
;
}
}
}
if (url) {
range.offset(rowIndex, columnIndex, 1, 1).setValue(
SpreadsheetApp
.newCellImage()
.setSourceUrl(url)
.build()
);
numReplaced += 1;
}
}
})
);
return numReplaced;
}
Replace /^(Sheet1|Sheet2|Sheet3)$/i with a regular expression that matches the sheet names where you want to replace image() formulas with in-cell images. To replace on all sheets, use /./i.
Related
I'm doing a project with Arduino, I'm trying to post variable's data into google sheet integration but the code doesn't work.
I tried to correct it, but it doesn't post anyway....this is the code.
The error was
ss.sheet.getSheetByName it's not a function
I took the code from Arduino IoT Cloud Google sheet Integration
function myFunction() {
// get sheet named RawData
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dati");
var MAX_ROWS = 1440; // max number of data rows to display
// 3600s / cloud_int(30s) * num_ore(12h)
var HEADER_ROW = 1; // row index of header
var TIMESTAMP_COL = 1; // column index of the timestamp column
function doPost(e) {
var cloudData = JSON.parse(e.postData.contents); // this is a json object containing all info coming from IoT Cloud
//var webhook_id = cloudData.webhook_id; // really not using these three
//var device_id = cloudData.device_id;
//var thing_id = cloudData.thing_id;
var values = cloudData.values; // this is an array of json objects
// store names and values from the values array
// just for simplicity
var incLength = values.length;
var incNames = [];
var incValues = [];
for (var i = 0; i < incLength; i++) {
incNames[i] = values[i].name;
incValues[i] = values[i].value;
}
// read timestamp of incoming message
var timestamp = values[0].updated_at; // format: yyyy-MM-ddTHH:mm:ss.mmmZ
var date = new Date(Date.parse(timestamp));
/*
This if statement is due to the fact that duplicate messages arrive from the cloud!
If that occurs, the timestamp is not read correctly and date variable gets compromised.
Hence, execute the rest of the script if the year of the date is well defined and it is greater
then 2018 (or any other year before)
*/
if (date.getYear() > 2018) {
// discard all messages that arrive 'late'
if (sheet.getRange(HEADER_ROW+1, 1).getValue() != '') { // for the first time app is run
var now = new Date(); // now
var COMM_TIME = 5; // rough overestimate of communication time between cloud and app
if (now.getTime() - date.getTime() > COMM_TIME * 1000) {
return;
}
}
// this section write property names
sheet.getRange(HEADER_ROW, 1).setValue('timestamp');
for (var i = 0; i < incLength; i++) {
var lastCol = sheet.getLastColumn(); // at the very beginning this should return 1 // second cycle -> it is 2
if (lastCol == 1) {
sheet.getRange(HEADER_ROW, lastCol + 1).setValue(incNames[i]);
} else {
// check if the name is already in header
var found = 0;
for (var col = 2; col <= lastCol; col++) {
if (sheet.getRange(HEADER_ROW, col).getValue() == incNames[i]) {
found = 1;
break;
}
}
if (found == 0) {
sheet.getRange(HEADER_ROW, lastCol+1).setValue(incNames[i]);
}
}
}
// redefine last column and last row since new names could have been added
var lastCol = sheet.getLastColumn();
var lastRow = sheet.getLastRow();
// delete last row to maintain constant the total number of rows
if (lastRow > MAX_ROWS + HEADER_ROW - 1) {
sheet.deleteRow(lastRow);
}
// insert new row after deleting the last one
sheet.insertRowAfter(HEADER_ROW);
// reset style of the new row, otherwise it will inherit the style of the header row
var range = sheet.getRange('A2:Z2');
//range.setBackground('#ffffff');
range.setFontColor('#000000');
range.setFontSize(10);
range.setFontWeight('normal');
// write the timestamp
sheet.getRange(HEADER_ROW+1, TIMESTAMP_COL).setValue(date).setNumberFormat("yyyy-MM-dd HH:mm:ss");
// write values in the respective columns
for (var col = 1+TIMESTAMP_COL; col <= lastCol; col++) {
// first copy previous values
// this is to avoid empty cells if not all properties are updated at the same time
sheet.getRange(HEADER_ROW+1, col).setValue(sheet.getRange(HEADER_ROW+2, col).getValue());
for (var i = 0; i < incLength; i++) {
var currentName = sheet.getRange(HEADER_ROW, col).getValue();
if (currentName == incNames[i]) {
// turn boolean values into 0/1, otherwise google sheets interprets them as labels in the graph
if (incValues[i] == true) {
incValues[i] = 1;
} else if (incValues[i] == false) {
incValues[i] = 0;
}
sheet.getRange(HEADER_ROW+1, col).setValue(incValues[i]);
}
}
}
} // end if (date.getYear() > 2018)
}
}
You are trying to push data directly to the Google sheet on the cloud but it's URL uses https for security reasons. The encryption for https is rather complex and SSL crypto is required. Arduino hardware is normally not fast enough to do SSL. You need more powerful hardware like Raspberry Pi to write directly to the Google Cloud.
However, you can use PushingBox, a cloud that can send notifications based on API calls, to do the hard work for you if you insist on using Arduino ( any variant) in your IOT project. Here you send your data to Pushingbox, it uses http URL, and it will in turn the data to Google sheet.
I need to take a sheet maintained by someone else and do the following (so that I can export to a csv):
unmerge all cells
fill values down
merged cells are in multiple columns, so I need to iterate over a range
It's too much to do it by hand, and it will need done periodically. My javascript and google sheets object model knowledge approximate zero, but I know it's possible because I could do it in VBA. I searched but can only find programmatic answers for VBA/Excel.
How can I do this efficiently in Google Sheets?
You can use the breakapart() class to do this. I am assuming that the merged range is not static and has multiple occurrences. This script will unmerge all merged ranges in the active sheet.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).breakApart();
}
Adapted from #lreeder's answer
The following breaks and fill blank with above on the selected range:
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('BreakAndFill')
.addItem('Break Fill Blank Cells', 'menuItem1')
.addToUi();
}
function menuItem1() {
BreakandfillBlankWithAbove()
}
//Breaks range
//Iterates over the range from top to bottom
//and left to right, and fills blank cells
//with the value right above them.
function BreakandfillBlankWithAbove() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveRange();
Logger.log('Range height is: ' + range.getHeight());
var values = range.getValues();
range.breakApart();
var width = values[0].length;
var height = values.length;
Logger.log('Width is ' + width);
Logger.log('Height is ' + height);
for (var i = 0; i < width; i++) {
var lastVal = '';
for(var j = 0; j < height; j++) {
var currValue = values[j][i];
var dataType = typeof(currValue);
//Empty string check returns true if dataType
//is a number, and value is zero. In that case
//we don't want to overwrite the zero, so only
//check emptyString for string types.
if(currValue === undefined ||
(dataType === 'string' && currValue == '')
) {
//getCell parameters are relative to the current range
//and ones based
var cell = range.getCell(j+1, i+1);
cell.setValue(lastVal);
}
else {
lastVal = currValue;
}
}
}
SpreadsheetApp.flush();
}
I want to create a spreadsheet where I can add people on it and see which hours everyone is available to do a online meeting. It's mandatory to consider the timezone but I have no clue how to do it.
Person1 sheet example:
Matching hours:
I think my idea of showing 'matches 4 out of 5' is nice, cause the remaining one can make an effort to show up and edit it so it can be like 'matches 5 out of 5'. But any other suggestion is welcome.
The link of the actual spreadsheet(copy to your own drive so you can edit): https://docs.google.com/spreadsheets/d/1yun8uMW2LZUumlm6cy3hqPT-FLQipADSIjLQPNiwXl4/edit?usp=sharing
PS: It would be nice to support DST (daylight save time) but it's not mandatory. The person who is in DST will adjust it.
Assuming your timezone values are all offsets of the Match Sheet (if the match sheet says 01:00 and your timezone is -1, your local time would be 00:00), here is some code that will show you the text in the way that you want:
function availablePeople(startTime, dayOfWeek) { //All slots are 1 hour long
const sheetsNames = ["person1", "person2", "person3", "person4"]; //Edit this to update possibilities
const dayHeaderRow = 2;
const startTimeCol = 1;
const endTimeCol = 2;
var sheets = SpreadsheetApp.getActive();
var referenceHourRow = sheets.getSheetByName("match").getRange(dayHeaderRow+1, startTimeCol, 24).getValues().map(function (x) {return x.toString()}).indexOf(startTime.toString());
var referenceDayCol = sheets.getSheetByName(sheetsNames[0]).getRange(dayHeaderRow, endTimeCol+1, 1, 7).getValues()[0].indexOf(dayOfWeek);
var availablePeople = 0;
if (referenceHourRow != -1) {
for (var i = 0; i<sheetsNames.length; i++) {
var personSheet = sheets.getSheetByName(sheetsNames[i]);
var timezone = -personSheet.getRange(1, 4).getValue();
var thisDayCol = referenceDayCol;
var thisHourRow = referenceHourRow;
if (timezone!=0) {
if (thisHourRow+timezone<0) {
//Went back a day.
thisDayCol = (thisDayCol+6)%7;
thisHourRow = 24-(referenceHourRow-timezone);
} else if (thisHourRow+timezone>=24) {
//Went forward a day
thisDayCol = (thisDayCol+1)%7;
thisHourRow = (thisHourRow+timezone)%24;
} else {
thisHourRow += timezone;
}
}
var cell = personSheet.getRange(dayHeaderRow+1+thisHourRow, endTimeCol+1+thisDayCol);
if (cell.getValue()=="Available") {
availablePeople++;
}
}
}
return availablePeople+" out of "+sheetsNames.length;
}
This is how to use this function: =availablePeople(<START TIME>,<DAY OF THE WEEK>).
To allow this to be dragged and autocompleted, write =availablePeople($A3,C$2) in the "Monday" "00:00" and then drag it horizontally and vertically to update the formula.
I’m using Google Sheets to conduct some text analysis. I would like to automate the process by linking a spreadsheet to a Google doc file on my GDrive to extract text directly. It doesn’t have to structured/formatted. It just has to be a plain text. Does it take a comprehensive scripting or the task of copying text is simple?
I searched the web but couldn’t find one.
To help you start, here is an SO thread.
Same scenario: Get data from the Google Sheets and copy it to the Google Docs.
A reference from Open Source Hacker: Script for generating Google documents from Google spreadsheet data source.
Here is the code provided in the article. You can modify it by yourself depending on your use.
/**
* Generate Google Docs based on a template document and data incoming from a Google Spreadsheet
*
* License: MIT
*
* Copyright 2013 Mikko Ohtamaa, http://opensourcehacker.com
*/
// Row number from where to fill in the data (starts as 1 = first row)
var CUSTOMER_ID = 1;
// Google Doc id from the document template
// (Get ids from the URL)
var SOURCE_TEMPLATE = "xxx";
// In which spreadsheet we have all the customer data
var CUSTOMER_SPREADSHEET = "yyy";
// In which Google Drive we toss the target documents
var TARGET_FOLDER = "zzz";
/**
* Return spreadsheet row content as JS array.
*
* Note: We assume the row ends when we encounter
* the first empty cell. This might not be
* sometimes the desired behavior.
*
* Rows start at 1, not zero based!!! 🙁
*
*/
function getRowAsArray(sheet, row) {
var dataRange = sheet.getRange(row, 1, 1, 99);
var data = dataRange.getValues();
var columns = [];
for (i in data) {
var row = data[i];
Logger.log("Got row", row);
for(var l=0; l<99; l++) {
var col = row[l];
// First empty column interrupts
if(!col) {
break;
}
columns.push(col);
}
}
return columns;
}
/**
* Duplicates a Google Apps doc
*
* #return a new document with a given name from the orignal
*/
function createDuplicateDocument(sourceId, name) {
var source = DocsList.getFileById(sourceId);
var newFile = source.makeCopy(name);
var targetFolder = DocsList.getFolderById(TARGET_FOLDER);
newFile.addToFolder(targetFolder);
return DocumentApp.openById(newFile.getId());
}
/**
* Search a paragraph in the document and replaces it with the generated text
*/
function replaceParagraph(doc, keyword, newText) {
var ps = doc.getParagraphs();
for(var i=0; i<ps.length; i++) {
var p = ps[i];
var text = p.getText();
if(text.indexOf(keyword) >= 0) {
p.setText(newText);
p.setBold(false);
}
}
}
/**
* Script entry point
*/
function generateCustomerContract() {
var data = SpreadsheetApp.openById(CUSTOMER_SPREADSHEET);
// XXX: Cannot be accessed when run in the script editor?
// WHYYYYYYYYY? Asking one number, too complex?
//var CUSTOMER_ID = Browser.inputBox("Enter customer number in the spreadsheet", Browser.Buttons.OK_CANCEL);
if(!CUSTOMER_ID) {
return;
}
// Fetch variable names
// they are column names in the spreadsheet
var sheet = data.getSheets()[0];
var columns = getRowAsArray(sheet, 1);
Logger.log("Processing columns:" + columns);
var customerData = getRowAsArray(sheet, CUSTOMER_ID);
Logger.log("Processing data:" + customerData);
// Assume first column holds the name of the customer
var customerName = customerData[0];
var target = createDuplicateDocument(SOURCE_TEMPLATE, customerName + " agreement");
Logger.log("Created new document:" + target.getId());
for(var i=0; i<columns.length; i++) {
var key = columns[i] + ":";
// We don't replace the whole text, but leave the template text as a label
var text = customerData[i] || ""; // No Javascript undefined
var value = key + " " + text;
replaceParagraph(target, key, value);
}
}
Can any1 explain how we can create Excel WITHOUT using INTEROP in c# window service.
So that I can apply styles also to the generating excel as I wish.
Rigin
You can use one of the Excel libraries. I use this C# Excel library.
See also this sample of code:
http://www.easyxls.com/manual/FAQ/export-to-excel-in-dot-net.html
You can create both XLS or XLSX documents.
You can create excel in windows services like below:
public static void GenerateExcel(DataTable DT, string fullFileName, string rptHeader, string SheetName)
{
try
{
var file = new FileInfo(fullFileName);
string currentFileName = System.IO.Path.GetFileName(fullFileName);
ExcelPackage excel = new ExcelPackage(file);
var sheetcreate = excel.Workbook.Worksheets.Add("Sheet1");
//rptHeader = getCaption(rptHeader);
char c = 'A';
c = (char)(((int)c) + DT.Columns.Count - 1);
//sheetcreate.Cells["A1:" + c+"1"].Value = rptHeader;
sheetcreate.Cells["A1:D1"].Value = rptHeader;
sheetcreate.Cells["A1:" + c + "1"].Style.Fill.PatternType = ExcelFillStyle.Solid;
//sheetcreate.Cells["A1:" + c + "1"].Style.Fill.BackgroundColor.SetColor(System.Drawing.ColorTranslator.FromHtml("#c6c6c6"));
sheetcreate.Cells[1, 1, 1, DT.Columns.Count].Merge = true;
sheetcreate.Cells[1, 1, 1, DT.Columns.Count].Style.Font.Bold = true;
int col = 0;
foreach (DataColumn column in DT.Columns)
{
sheetcreate.Cells[2, ++col].Value = column.ColumnName;
sheetcreate.Cells[2, col].Style.Font.Bold = true;
sheetcreate.Cells[2, col].Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Thin);
sheetcreate.Cells[2, col].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
}
if (DT.Rows.Count > 0)
{
int row = 2;
for (int eachRow = 0; eachRow < DT.Rows.Count; ) //looping each row
{
bool havingText = false;
for (int eachColumn = 1; eachColumn <= col; eachColumn++) //looping each column in a row
{
var eachRowObject = sheetcreate.Cells[row + 1, eachColumn];
eachRowObject.Style.Fill.PatternType = ExcelFillStyle.Solid;
eachRowObject.Value = DT.Rows[eachRow][(eachColumn - 1)].ToString();
if (!havingText) //checking if 'totoa' in string and setting up 'havingText' variable to color it differently
havingText = DT.Rows[eachRow][(eachColumn - 1)].ToString().ToLower().Contains("total");
//Making all cell value to left align
eachRowObject.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
//if (CL.isDecimal(DT.Rows[eachRow][(eachColumn - 1)].ToString())) //if it is number with decimal value make it right align
//{
// eachRowObject.Style.HorizontalAlignment = ExcelHorizontalAlignment.Right;
//}
//eachRowObject.Style.Border.BorderAround(OfficeOpenXml.Style.ExcelBorderStyle.Thin); // adding border to each cells
//if (eachRow % 2 == 0) //alternatively adding color to each cell.
// eachRowObject.Style.Fill.BackgroundColor.SetColor(System.Drawing.ColorTranslator.FromHtml("#e0e0e0"));
//else
// eachRowObject.Style.Fill.BackgroundColor.SetColor(System.Drawing.ColorTranslator.FromHtml("#ffffff"));
}
if (havingText) //if any cell data containt 'total' color complete.
{
for (int eachColumn = 1; eachColumn <= col; eachColumn++)
{
sheetcreate.Cells[row + 1, eachColumn].Style.Fill.PatternType = ExcelFillStyle.Solid;
//sheetcreate.Cells[row + 1, eachColumn].Style.Fill.BackgroundColor.SetColor(System.Drawing.ColorTranslator.FromHtml("#86a9ef"));
}
}
eachRow++;
row++;
}
getLog("batch controller: in loop");
}
getLog("batch controller: 485");
sheetcreate.Cells.AutoFitColumns();
excel.Save();
}
catch (Exception e)
{
getLog("Error while generating excel=>"+e);
}
}
You can download EPPlus from : https://www.nuget.org/packages/EPPlus/