IMPORTXML into a Google Apps Script with Automatic Update [duplicate] - google-apps-script

This question already has answers here:
Periodically refresh IMPORTXML() spreadsheet function
(4 answers)
Closed last month.
I'm trying to get a Google sheets apps script to work for an IMPORTXML I'm using.
A1
=importxml("http://www.nfl.com/liveupdate/scorestrip/ss.xml","//#q")
A2
=importxml("http://www.nfl.com/liveupdate/scorestrip/ss.xml","//#h")
The data fills from A1:B16
According to a script I found on web to have it auto refresh:
function getData() {
var queryString = Math.random();
var cellFunction1 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('A1').getValue() + '?' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('A2').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('C1').setValue(cellFunction1);
var cellFunction2 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('A4').getValue() + '?' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('A5').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('C2').setValue(cellFunction2);
}
I don't know what I'm supposed to be putting/replacing in that code with mine. If someone could help me to explain what I'm supposed to be changing to get it to work in my sheet/provide some examples of how one might look that would be a huge help.
I appreciate

You can update the function by using getFormula() then setFormula() in a time-driven trigger function. Here is a code snippet from a related SO post:
/**
* Go through all sheets in a spreadsheet, identify and remove all spreadsheet
* import functions, then replace them a while later. This causes a "refresh"
* of the "import" functions. For periodic refresh of these formulas, set this
* function up as a time-based trigger.
*
* Caution: Formula changes made to the spreadsheet by other scripts or users
* during the refresh period COULD BE OVERWRITTEN.
*
* From: https://stackoverflow.com/a/33875957/1677912
*/
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
// At this point, we are holding the lock.
var id = "YOUR-SHEET-ID";
var ss = SpreadsheetApp.openById(id);
var sheets = ss.getSheets();
for (var sheetNum=0; sheetNum<sheets.length; sheetNum++) {
var sheet = sheets[sheetNum];
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var tempFormulas = [];
for (var row=0; row<formulas.length; row++) {
for (col=0; col<formulas[0].length; col++) {
// Blank all formulas containing any "import" function
// See https://regex101.com/r/bE7fJ6/2
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
if (formulas[row][col].search(re) !== -1 ) {
tempFormulas.push({row:row+1,
col:col+1,
formula:formulas[row][col]});
sheet.getRange(row+1, col+1).setFormula("");
}
}
}
// After a pause, replace the import functions
Utilities.sleep(5000);
for (var i=0; i<tempFormulas.length; i++) {
var cell = tempFormulas[i];
sheet.getRange( cell.row, cell.col ).setFormula(cell.formula)
}
// Done refresh; release the lock.
lock.releaseLock();
}
}
Hope this helps.

I have been working on something related and finally solved it by using code from this StackOverFlow Post, but I needed to add some extra bits.
It wasn't working well for me, so I made some changes and added extra logging to make it understandable for me. Here is goes:
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var now = new Date();
// Show start time on log
Logger.log("Starting Running at " + now.toLocaleTimeString());
var url = "URL OF YOUR SHEET";
var sheetName = "NAME OF YOUR SHEET";
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName(sheetName);
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var tempFormulas = [];
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
// Blank all formulas containing any "import" function
// See https://regex101.com/r/bE7fJ6/2
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
if (formulas[row][col].search(re) !== -1 ) {
tempFormulas.push({row:row+1,
col:col+1,
formula:formulas[row][col]});
sheet.getRange(row+1, col+1).setFormula(""); //cleans up the formula
}
}
}
// After a pause, replace the import functions
Utilities.sleep(500);
for (var i=0; i<tempFormulas.length; i++) {
var cell = tempFormulas[i];
sheet.getRange( cell.row, cell.col ).setFormula(cell.formula);
var nowLogger = new Date();
Logger.log("Update import from row " + cell.row + " col " + cell.col + " done at " + nowLogger.toLocaleTimeString());
Utilities.sleep(1000); //adding to try to control the amount of parallel connections from the Sheet
}
// Show Finished time on log
var now = new Date();
Logger.log("Sources from URLs were last updated at " + now.toLocaleTimeString());
// Done refresh; release the lock.
lock.releaseLock();
}
Then I added the Time-Based trigger from my project in https://script.google.com/home/triggers, and that one handles the automatic update execution.
I hope this helps!

It is actually not working. I mean it really does not refresh the cell whereas using a random number it works:
function getData() {
var queryString = Math.random();
var Xpath_1 = "/html/body/text()";
var importXpath_1 = '=IMPORTXML("' + 'http://www.pde-racing.com/trams/tram.php?id=999&valor=1&manega=Entrenament+1&dbTemps=Event999Ex.scdb&dbInscrits=Event999.scdb&name=&_=1616318271525' + '?' + queryString + '";"'+ Xpath_1 + '")';
SpreadsheetApp.getActiveSheet().getRange('B39').setValue(importXpath_1);
}

Related

Auto Refreshing ImportXML Google Sheets

I have a sheet that imports data from a site, there are about 10 imports all slightly different. I want a way that I could update that every five minutes or so. I know there are scrips to do it but when I tried to use one it would just put ?update at the end and mess up the entire import. I can show the script I am using. It is from 4 years ago and maybe it is outdated. Any help would be appreciated.
Also just having a script that changes the = to nothing then adds it back again or something would work I guess.
Here is the script I am using
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var id = "Sheets ID";
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("Sheet Name");
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var content = "";
var now = new Date();
var time = now.getTime();
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
var re2 = /((\?|&)(update=[0-9]*))/gi;
var re3 = /(",)/gi;
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
content = formulas[row][col];
if (content != "") {
var match = content.search(re);
if (match !== -1 ) {
// import function is used in this cell
var updatedContent = content.toString().replace(re2,"$2update=" + time);
if (updatedContent == content) {
// No querystring exists yet in url
updatedContent = content.toString().replace(re3,"?update=" + time + "$1");
}
// Update url in formula with querystring param
sheet.getRange(row+1, col+1).setFormula(updatedContent);
}
}
}
}
// Done refresh; release the lock.
lock.releaseLock();
// Show last updated time on sheet somewhere
sheet.getRange(12,2).setValue("Rates were last updated at " + now.toLocaleTimeString())
}
You can write a script that retrieves the formula from the specified cell and sets it back to the same cell on a timer
For this you need:
Retrieve the formula with getFormula()
Set the formula back into the same cell with getFormula()
Bind an installable time-driven trigger to your function specifying the desired interval
Sample:
function setMeOnTrigger() {
var sheet = SpreadsheetApp.getActive().getActiveSheet();
var cell = sheet.getRange("C1");
cell.setFormula(cell.getFormula())
}

ImportXML with Google Sheet and Auto Refresh Every Minute

I have the script below which is importing some XML data in a google sheet called prices. Everything works fine except that I have set up a time driven trigger to run every minute but the data won't get updated.
The trigger seems to work fine, as I can see the last run time being updated every minute.
The script calling the XML data works fine as I can see the data being populated in the spreadsheet.
The XML feed works fine too, as I can see the time being updated every minute, also have a cron job.
I only have this function as a project.
function getData() {
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("prices");
var queryString = Math.random();
var cellFunction = '=ImportXML("http://myxmldata.com/data-xml.php","//data/date")';
sheetName.getRange('A2').setValue(cellFunction);
}
So what's wrong?
Here is how I solved my problem:
On your spreadsheet go to the top menu > click Tool > then Script Editor and add the following scripts:
This is the script to call your data e.g. XML. Please update the script with your own information. YOUR-SHEET-NAME, is the tab name e.g. "prices".
function getData() {
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("YOUR-
SHEET-NAME");
var queryString = Math.random();
var cellFunction = '=ImportXML("https://yoururl-xml.php","//trade/price")';
var range = sheetName.getRange('A2');
range.clearContent(); // You can also use range.setFormula("");
SpreadsheetApp.flush();
range.setFormula(cellFunction);
}
Below that script, add the following script, more information on this page: Periodically refresh IMPORTXML() spreadsheet function
YOUR-SHEET-ID is the long number in the spreadsheet url e.g. 1YTB12xSTMSNdoT_S1U67MtOUDTf6n4OL2tJLnTNAXYZ
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var id = "YOUR-SHEET-ID";
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("YOUR-SHEET-NAME");
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var content = "";
var now = new Date();
var time = now.getTime();
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
var re2 = /((\?|&)(update=[0-9]*))/gi;
var re3 = /(",)/gi;
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
content = formulas[row][col];
if (content != "") {
var match = content.search(re);
if (match !== -1 ) {
// import function is used in this cell
var updatedContent = content.toString().replace(re2,"$2update=" + time);
if (updatedContent == content) {
// No querystring exists yet in url
updatedContent = content.toString().replace(re3,"?update=" + time + "$1");
}
// Update url in formula with querystring param
sheet.getRange(row+1, col+1).setFormula(updatedContent);
}
}
}
}
// Done refresh; release the lock.
lock.releaseLock();
}
Here is a screenshot of both scripts:
Then add the timer, go to the top menu click on the clock and add trigger. Make sure to select the right function i.e. RefreshImports.
Done!

Google Apps Script function works when run manually, but fails when run as a trigger

I have two scripts:
One which adds an IMPORTJSON sheets function from bradjasper, works great.
The second one is that I want to refresh the scripts automatically, that does not work. The script does work when I ran it manually, and also the trigger does work according to the logs, but the data does not get refreshed.
I have tried all the different scripts, they do work, but the data imported on the sheet is not actually updated.
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var id = "1Vt-rqQZ7iXsui8Nrr2XABusd4lUbGqxj4HkzsRkZFNA";
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("Blad2");
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var content = "";
var now = new Date();
var time = now.getTime();
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|json|range)\(.*/gi;
var re2 = /((\?|&)(update=[0-9]*))/gi;
var re3 = /(",)/gi;
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
content = formulas[row][col];
if (content != "") {
var match = content.search(re);
if (match !== -1 ) {
// import function is used in this cell
var updatedContent = content.toString().replace(re2,"$2update=" + time);
if (updatedContent == content) {
// No querystring exists yet in url
updatedContent = content.toString().replace(re3,"?update=" + time + "$1");
}
// Update url in formula with querystring param
sheet.getRange(row+1, col+1).setFormula(updatedContent);
}
}
}
}
// Done refresh; release the lock.
lock.releaseLock();
// Show last updated time on sheet somewhere
sheet.getRange(1,1).setValue("Rates were last updated at " + now.toLocaleTimeString())
}
This script does run, as there are no errors in the logs. However, the data shown on the sheet does not change to reflect the current information from the API / JSON file.

Force refresh ImportXML

I want to force an importXML to auto-refresh every five minutes. This is the script I am trying to run and getting the error "Bad value (line 7, file "RefreshImports" . I do not know why. I found it here: Periodically refresh IMPORTXML() spreadsheet function
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous
refresh to end.
var id = "[YOUR SPREADSHEET ID]";
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheetByName("[SHEET NAME]");
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var content = "";
var now = new Date();
var time = now.getTime();
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
var re2 = /((\?|&)(update=[0-9]*))/gi;
var re3 = /(",)/gi;
for (var row = 0; row < formulas.length; row++) {
for (var col = 0; col < formulas[0].length; col++) {
content = formulas[row][col];
if (content != "") {
var match = content.search(re);
if (match !== -1) {
// import function is used in this cell
var updatedContent = content.toString().replace(re2, "$2update=" +
time);
if (updatedContent == content) {
// No querystring exists yet in url
updatedContent = content.toString().replace(re3, "?update=" + time +
"$1");
}
// Update url in formula with querystring param
sheet.getRange(row + 1, col + 1).setFormula(updatedContent);
}
}
}
}
// Done refresh; release the lock.
lock.releaseLock();
// Show last updated time on sheet somewhere
sheet.getRange(7, 2).setValue("Rates were last updated at " +
now.toLocaleTimeString())
}
In the code where it says "[YOUR SPREADSHEET ID]", I am to enter the name of my spreadsheet correct? I do not know anything about this.
On [YOUR SPREADSHEET ID] you should add the spreadsheet id, not it's name.
The spreadsheet id for
https://docs.google.com/spreadsheets/d/1Xhgfr3z4EwPtjS4aahytU_3TOVxjNb8JvHo88h3nZaE/edit#gid=14522064
is
1Xhgfr3z4EwPtjS4aahytU_3TOVxjNb8JvHo88h3nZaE
I found it easier to use the URL instead the id, here is the bit of code:
var url = "URL OF SPREADSHEET";
var sheetName = "NAME OF SPECIFIC SHEET";
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName(sheetName);

Protecting Cells Based on Contents of Other Cells in Google Sheets

I have a single worksheet that contains user entered responses in Columns C & D, Rows 3 - 20. The responses are time dependent and look at Column E Rows 3-20 to see if it is "Locked" or "Open".
Using protection, I lock the entire sheet for editing with the exception of C3:D20. The sheet is set to calculate every minute.
I am trying to write a script that checks the column E to see if it is set for locked or open. If it is set for locked, I would like to lock (protect) columns C&D in that row for editing from everyone but myself. I run the script every 5 minutes and I have the for loop and if statement handled, but when I go to use the RemoveEditors function it does 2 things:
Creates a new protected range (so after 5 minutes I have 1 additional protected range, 10 minutes, I have 2 additional, etc.)
Does not remove the other editors from those able to edit the cells.
I tried using Google's example code, but their code adds the current user as an editor, which is what I'm trying to avoid doing since then that editor can just remove the protection that the code is putting in place.
Any help you could provide would be appreciated.
Current Code is below:
function Lock_Cells() {
var sheet = SpreadsheetApp.getActive();
for (var i = 3; i <= 20; i++)
{
var Check_Cell = "E" + i;
var Temp = sheet.getRange(Check_Cell).getValue();
if (Temp == "Locked")
{
var Lock_Range = "C" + (i + 2) + ":D" + "i";
var protection = sheet.getRange(Lock_Range).protect();
var description = "Row " + i;
protection.setDescription(description);
var eds = protection.getEditors();
protection.removeEditors(eds);
}
}
}
To avoid creating a new set of protected ranges, you can add logic to check which rows are already locked. With that information you just need to skip those rows:
note: there was a mistake in this line: var Lock_Range = "C" + (i + 2) + ":D" + "i"; the variable i should not have quotation.
function Lock_Cells() {
var sheet = SpreadsheetApp.getActive();
var rows = get_protected_Rows();
for (var i =3; i <= 20; i++)
{
var Check_Cell = "E" + i;
var cell = sheet.getRange(Check_Cell);
var Temp = sheet.getRange(Check_Cell).getValue();
if (Temp == "Locked" && rows.indexOf(i) <0)
{
var Lock_Range = "C" + i + ":D" + i; //In this line you put "i"
.....
...
}
function get_protected_Rows()
{
var ss = SpreadsheetApp.getActive();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
var rows = [];
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
var anotation = protection.getRange().getRow();
rows.push(anotation);
}
return rows
}
You are right, when the code is executed by one of the users, the protection gives that user the ability to edit those rows. I would recommend that as the owner of the file, you also run a task to remove every other editor from those rows. The function would be very similar to the previous. And I know is not the best but it may help you with your use case.
function remove_editors()
{
var ss = SpreadsheetApp.getActive();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
var anotation = protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
}
}
By doing that i was able to restrict the permission to other users. Hope it helps.