this is the complete code,
function extractData() {
var url = "https://www.theopenalliance.com/teams/2023/";
var html = UrlFetchApp.fetch(url).getContentText();
var data = parseHtml(html);
var sheet = SpreadsheetApp.getActiveSheet();
sheet.clearContents();
if (data.length > 0) {
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
if (data[i][j].indexOf("http") === 0) {
var button = sheet.getRange(i + 1, j + 1).attachButton({
text: "Link",
url: data[i][j]
});
}
}
}
}
}
function parseHtml(html) {
var startIndex = html.indexOf("<tbody>");
var endIndex = html.indexOf("</tbody>");
var table = html.substring(startIndex, endIndex);
var rows = table.split("<tr>");
var data = [];
for (var i = 1; i < rows.length; i++) {
var cells = rows[i].split("<td");
var row = [];
for (var j = 1; j < cells.length; j++) {
var cell = cells[j];
var linkStartIndex = cell.indexOf("href=");
if (linkStartIndex !== -1) {
var linkEndIndex = cell.indexOf("class");
var link = cell.substring(linkStartIndex + 6, linkEndIndex - 2);
row.push(link);
} else {
row.push(cell.substring(cell.indexOf(">") + 1, cell.indexOf("</td>")));
}
}
data.push(row);
}
return data;
}
however function parseHtml(html) gives an error with this line
var startIndex = html.indexOf("<tbody>");
Anyone has any suggestions? i'm trying to copy and paste tables from the link to a google sheets.
i expected to see every teams numbers and other values (Public links, location etc) in google sheets but nothing shows up. Also i was expecting to see buttons that had links attached to them if the buttons exists, such as github, photos etc. Please check the link and im sure you will have a better idea of im trying to tell. Also please help me fix the code, if possible, copy and edit the code than repost it, i would greatly appreciate it
In your situation, how about using Sheets API? Because I thought that the HTML parser of Sheets API is useful for your situation. When Sheets API is used for your URL, how about the following sample script?
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
function myFunction() {
const url = "https://www.theopenalliance.com/teams/2023/"; // This is from your script.
const html = UrlFetchApp.fetch(url).getContentText();
const table = html.match(/<table[\s\S\w]+?<\/table>/);
if (!table) {
throw new Error("Table was not found.");
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getActiveSheet().clearContents();
SpreadsheetApp.flush();
const requests = { requests: [{ pasteData: { html: true, data: table[0], coordinate: { sheetId: sheet.getSheetId() } } }] };
Sheets.Spreadsheets.batchUpdate(requests, ss.getId());
}
When this script is run, a table is retrieved from the URL and put it into the active sheet.
References:
Method: spreadsheets.batchUpdate
PasteDataRequest
Related
I am trying to get data from Gmail body using GAS. To be specific, I get an email with a table content; I am trying to copy the table from gmail and write it to google sheet for my further analysis. Below is a sample email I get:
The output I am expecting in Google sheets:
UPDATE: I was able to make some modifications to the code I had by referring to Insert table from gmail to google spreadsheet by google script
Here's how the email body and output looks like now.
Email:
GSheet Output:
The issue occurs with merged cells in table. The code does not generate output as how it appears in the gmail body. Is there any workaround for this?
Final code:
var SEARCH_QUERY = "SearchKey";
function getEmailss_(q) {
var emails = [];
var threads = GmailApp.search(q);
if (threads.length == 0) {
console.log("No threads found that match the search query: " + 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]");
if (arrStr.length == 1) {
console.log("No data found in thread: " + threads[i].getFirstMessageSubject());
}
var line = [];
for (var i = 0; i < arrStr.length - 1; i++) {
line = arrStr[i].split("[/td]");
line.length -= 1;
emails.push(line);
}
}
}
if (emails.length == 0) {
console.log("No emails found that match the search query: " + q);
}
return convert2ArrayToRectangular_(emails);
}
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;
}
function appendData_(sheet, array2d) {
var h = array2d.length;
var l = array2d[0].length;
sheet.getRange(1, 1, h, l).setValues(array2d);
}
function saveEmailsss() {
var array2d = getEmailss_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActive().getSheetByName('Sheet1'), convert2ArrayToRectangular_(array2d));
}
markArchivedAsRead();
}
function markArchivedAsRead() {
var threads = GmailApp.search('label:inbox is:unread to:me subject:importnumberlist');
GmailApp.markThreadsRead(threads);
};
As another approach, how about using Sheets API? When Sheets API is used, the HTML table can be parsed by including the merged cells. The sample script is as follows.
Sample script:
This script uses Sheets API. Please enable Sheets API at Advanced Google services.
var SEARCH_QUERY = "SearchKey";
function getEmailss_(q, sheetName) {
var emails = [];
var threads = GmailApp.search(q);
if (threads.length == 0) {
console.log("No threads found that match the search query: " + q);
}
var tables = [];
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
var arrStr = msgs[j].getBody();
var table = arrStr.match(/<table[\s\S\w]+?<\/table>/);
if (table) {
tables.push(table[0]);
}
}
}
if (emails.length == 0) {
console.log("No emails found that match the search query: " + q);
}
if (tables.length == 0) {
console.log("No tables.");
return
};
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var requests = [{ pasteData: { html: true, data: tables.join(""), coordinate: { sheetId: sheet.getSheetId() } } }];
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
function saveEmailsss() {
var sheetName = "Sheet1"; // Please set your sheet name.
getEmailss_(SEARCH_QUERY, sheetName);
markArchivedAsRead();
}
When this script is run, the HTML table included in the email message is retrieved and put to the active Spreadsheet.
References:
Method: spreadsheets.batchUpdate
PasteDataRequest
I have a program that filters and updates data from an existing sheet.
The program works as follows:
1. Find and filter out the required value
2. Enter data in [Adjustment] column then update to database in Record sheet.
I tried to try but my program doesn't seem to work.
I tried to edit the program code but when run it will affect the other columns and the [adjustment] column value is entered wrong.
This is my link program
function Searchold(){
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var shtRecords = ss. getSheetByName ("RECORD");
var shtForm = ss. getSheetByName ("TEST") ;
var records = shtRecords. getDataRange () . getValues ();
var sField = shtForm. getRange ("A3").getValue ();
var sValue = shtForm.getRange ("A6").getValue();
var sCol = records [0].lastIndexOf(sField);
var results = records.filter(function(e){return sValue == e[sCol] });
if(results.length==0){SpreadsheetApp.getUi().alert("not found values");}
else{
shtForm.getRange(9,1,results.length,results[0].length).setValues(results);
}
}
function Updatenew(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var shtRecords = ss.getSheetByName("RECORD");
var shtForm = ss.getSheetByName("TEST");
var LastRow = shtForm.getRange("A8").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow();
var newData = shtForm.getRange(9,1,LastRow -1,7).getValues();
for(var i =0; i<newData.length;i++){
var oldData= shtRecords.getDataRange().getValues();
for(var j= 0;j<oldData.length;j++){
if(newData[i][0] ==oldData[j][0]){
var newData2 = [newData[i]];
shtRecords.getRange(j + 1,1,1,newData2[0].length).setValues(newData2);
}
}
}
}
Can you help me with the update program? Sincerely thank you
Modification points:
When I saw your showing script of Updatenew, I think that each row of var oldData = shtRecords.getDataRange().getValues() is used in each loop of for (var i = 0; i < newData.length; i++) {}. By this, each row is overwritten by each row of newData. By this, all searched rows in "RECORD" sheet are the same value. I thought that this might be the reason for your issue.
var oldData = shtRecords.getDataRange().getValues(); can be used one call.
In order to avoid this issue by modifying your script, as one of several methods, how about the following modification?
From:
for (var i = 0; i < newData.length; i++) {
var oldData = shtRecords.getDataRange().getValues();
for (var j = 0; j < oldData.length; j++) {
if (newData[i][0] == oldData[j][0]) {
var newData2 = [newData[i]];
shtRecords.getRange(j + 1, 1, 1, newData2[0].length).setValues(newData2);
}
}
}
To:
var oldData = shtRecords.getDataRange().getValues();
for (var j = 0; j < oldData.length; j++) {
for (var i = 0; i < newData.length; i++) {
if (newData[0][0] == oldData[j][0]) {
var newData2 = newData.splice(0, 1);
shtRecords.getRange(j + 1, 1, 1, newData2[0].length).setValues(newData2);
break;
}
}
}
Note:
At the above modification, setValues is used in a loop. In this case, the process cost becomes high. If you want to reduce the process cost of the script, how about using Sheets API? When Sheets API is used, how about the following modification? Please enable Sheets API at Advanced Google services.
To
var temp = newData.slice();
var data = shtRecords.getDataRange().getValues().reduce((ar, r, i) => {
if (temp[0][0] == r[0]) {
var t = temp.splice(0, 1);
t[0][2] = Utilities.formatDate(t[0][2], Session.getScriptTimeZone(), "dd/MM/yyyy");
t[0][4] = Utilities.formatDate(t[0][4], Session.getScriptTimeZone(), "dd/MM/yyyy");
ar.push({ range: `'RECORD'!A${i + 1}`, values: t });
}
return ar;
}, []);
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ss.getId());
I am having an issue trying to use a script to extract the URL from one test with hyperlink.
Here is the script I am using:
function URL(reference) {
var sheet = SpreadsheetApp.getActiveSheet();
var formula = SpreadsheetApp.getActiveRange().getFormula();
var args = formula.match(/=\w+\((.*)\)/i);
try {
var range = sheet.getRange(args[1]);
}
catch(e) {
throw new Error(args[1] + 'is not a valid range');
}
var formulas = range.getFormulas();
var output = [];
for (var i = 0; i < formulas.length; i++) {
var row = [];
for (var j = 0; j < formulas[0].length; j++) {
var url = formulas[i][j].match(/=hyperlink\("([^"]+)"/i);
row.push(url ? url[1] : '');
}
output.push(row);
}
return output
}
After I run the script I get this error message:
TypeError: Cannot read property '1' of null (line 9, file "Code")
Any idea where the issue comes from and I can solve this?
You have try/catch block that throws error, but in catch block you are trying to iterate null object again:
try {
var range = sheet.getRange(args[1]);
}
catch(e) {
throw new Error(args[1] + 'is not a valid range'); // <- `args[1]` is producing new error
}
If you change catch content to throw new Error(formula + 'is not a valid range');, it should work.
Retrieve the hyperlink with the Advanced Sheets Service and assign the script to a button
The way you are following so far (extracting a hyperlink from a formula) is an old solution that does not work anymore
Currently, to retrieve a spreadsheet you need to use the Sheets API method spreadsheets:get
To use the Sheest API in Apps Script, you need to enable it in the editor
Sample script:
function URL() {
var spreadsheet =SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var id = spreadsheet.getId();
var range = sheet.getActiveRange();
var linkArray = Sheets.Spreadsheets.get(id, {ranges: sheet.getName() + "!" + range.getA1Notation(), fields: "sheets/data/rowData/values/hyperlink"});
var link = linkArray.sheets[0].data[0].rowData[0].values[0].hyperlink;
var cellInB = sheet.getRange(range.getRow(), 2);
cellInB.setValue(link);
}
As mentioned in the comments, in your use case it is not possible to use custom formulas, instead create a drawing and assign the script to it:
Now, select a cell in column A, click on the button and the link will be populated in column B.
UPDATE:
To retrieve all URLs at once you can use the followign script and run it manually:
function getAllURLs() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var id = spreadsheet.getId();
var lastRow = sheet.getRange("A2").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
var range = sheet.getRange(2,1,lastRow-1, 1);
var linkArray = Sheets.Spreadsheets.get(id, {ranges: sheet.getName() + "!" + range.getA1Notation(), fields: "sheets/data/rowData/values/hyperlink"});
var links = [];
for (var i = 0; i< linkArray.sheets[0].data[0].rowData.length; i++){
var link = linkArray.sheets[0].data[0].rowData[i].values[0].hyperlink;
links[i] = [];
links[i].push(link);
}
var cellsInB = sheet.getRange(2,2,lastRow-1, 1);
cellsInB.setValues(links);
}
I'm facing some issues related to filter data in the columns using google app script editor.
I'm able to set a filter in columns using google app script as you can see in the above screenshot. but problem is when I'm trying to get the filtered data. it returns some number series instead of actual data as you can see below :
[20-03-09 18:19:48:395 IST] [1,2,4,5,6,8,9,10,11,12,13,14,15,19,20,21,22,23,24,26,27,28,29,30]
To set a filter :
function setFilter() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var filterSettings = {};
// The range of data on which you want to apply the filter.
// optional arguments: startRowIndex, startColumnIndex, endRowIndex, endColumnIndex
filterSettings.range = {
sheetId: ss.getActiveSheet().getSheetId()
};
// Criteria for showing/hiding rows in a filter
// https://developers.google.com/sheets/api/reference/rest/v4/FilterCriteria
filterSettings.criteria = {};
var columnIndex = 2;
filterSettings['criteria'][columnIndex] = {
'hiddenValues': ["England", "France"]
};
var request = {
"setBasicFilter": {
"filter": filterSettings
}
};
Sheets.Spreadsheets.batchUpdate({'requests': [request]}, ss.getId());
}
To get the filtered data:
function getFilteredRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var sheetId = ss.getActiveSheet().getSheetId();
let data = getIndexesOfFilteredRows(ssId,sheetId);
Logger.log(JSON.stringify(data));
}
function getIndexesOfFilteredRows(ssId, sheetId) {
var hiddenRows = [];
// limit what's returned from the API
var fields = "sheets(data(rowMetadata(hiddenByFilter)),properties/sheetId)";
var sheets = Sheets.Spreadsheets.get(ssId, {fields: fields}).sheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].properties.sheetId == sheetId) {
var data = sheets[i].data;
var rows = data[0].rowMetadata;
for (var j = 0; j < rows.length; j++) {
if (rows[j].hiddenByFilter) hiddenRows.push(j);
}
}
}
return hiddenRows;
}
How to set a filter in columns and get the filtered data using google app script.
Please help me with this.
In your case, the script for filtering has already worked. You want the script for retrieving the values from the filtered sheet in the Spreadsheet.
You want to achieve this using Sheets API with Google Apps Script.
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
In your case, the function of getIndexesOfFilteredRows is modified. Using hiddenByFilter, the hidden rows and shown rows are retrieved as an object.
Modified script:
function getIndexesOfFilteredRows(ssId, sheetId) {
var object = {hiddenRows: [], hiddenRowValues: [], shownRows: [], shownRowValues: []};
// limit what's returned from the API
var fields = "sheets(data,properties/sheetId)";
var sheets = Sheets.Spreadsheets.get(ssId, {fields: fields}).sheets;
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].properties.sheetId == sheetId) {
var data = sheets[i].data;
var rows = data[0].rowMetadata;
for (var j = 0; j < rows.length; j++) {
var r = [];
if (data[0].rowData[j] && Array.isArray(data[0].rowData[j].values)) {
r = data[0].rowData[j].values.map(function(e) {
var temp = "";
if (e.hasOwnProperty("userEnteredValue")) {
if (e.userEnteredValue.hasOwnProperty("numberValue")) {
temp = e.userEnteredValue.numberValue;
} else if (e.userEnteredValue.hasOwnProperty("stringValue")) {
temp = e.userEnteredValue.stringValue;
}
}
return temp;
});
}
if (r.length > 0) {
if (rows[j].hiddenByFilter) {
object.hiddenRows.push(j);
object.hiddenRowValues.push(r);
} else {
object.shownRows.push(j);
object.shownRowValues.push(r);
}
}
}
}
}
return object;
}
Result:
When above script is run for the filtered sheet, the following object which has the hidden row numbers, hidden row values, shown row numbers and shown row values is returned.
{
"hiddenRows":[0,1],
"hiddenRowValues":[["a1","b1","c1"],["a2","b2","c2"]],
"shownRows":[2,3],
"shownRowValues":[["a3","b3","c3"],["a4","b4","c4"]]
}
Reference:
DimensionProperties
If I misunderstood your question and this was not the direction you want, I apologize.
Added:
How about this sample script? In this sample script, the values filtered with filterValues can be retrieved as an object. In this case, the result is the same with your setFilter() and the modified getIndexesOfFilteredRows(). But the basic filter is not used. So please be careful this.
function myFunction() {
var filterValues = ["England", "France"]; // Please set the filter values.
var column = 3; // In this case, it's the column "C". Please set the column number.
var sheetName = "Sheet1"; // Please set the sheet name.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var values = sheet.getDataRange().getValues();
var object = values.reduce(function(o, e, i) {
if (filterValues.indexOf(e[column - 1]) > -1) {
o.hiddenRows.push(i + 1);
o.hiddenRowValues.push(e);
} else {
o.shownRows.push(i + 1);
o.shownRowValues.push(e);
}
return o;
}, {hiddenRows: [], hiddenRowValues: [], shownRows: [], shownRowValues: []});
Logger.log(object)
}
If you want to retrieve only the filtered values, this script might be suitable.
In this case, the script can run with and without V8. But when V8 is enabled, the loop speed will be fast. Ref
I have a Google Sheets that has D3.JS, i'm trying to use a specific sheet in the spreadsheet file, since that's where the table is located for the graph i'm trying to show. So far, the openById works but still is unable to locate the specific sheet in the spreadsheet.
function getChartData() {
var ss = SpreadsheetApp.openById("some id");
var sheet = ss.getActiveSheet();
var headings = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
.map(function(heading) {
return heading.toLowerCase();
});
...
}
EDIT: I've tried the following code changes below:
function getChartData() {
var ss = SpreadsheetApp.openById("some id");
var sheet = ss.getSheetByName("Tracker1");
// new code above
var headings = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
.map(function(heading) {
return heading.toLowerCase();
});
Logger.log(headings);
var values = sheet.getRange(3, 1, sheet.getLastRow()-2, sheet.getLastColumn()).getValues();
var data = [];
for (var i = 0; i < values.length; i++) {
var obj = {};
for (var j = 0; j < values[i].length; j++) {
obj[headings[j]] = values[i][j];
}
data.push(obj);
}
return data;
}
EDIT 10/2/18 11:00 PM : I'm still getting null, no error messages, no chart showing in app. I'm sure I'm doing something wrong, even without having getActiveSheet();