Insert table from gmail to google spreadsheet by google script - google-apps-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));
}

Related

Is it possible to export table from Gmail body while preserving the format, to Google sheets using GAS?

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

Google Sheet : The result could not be auto-expanded. Please insert new columns (1)

This script import opportunities that we get. It populates rows which then calculates some numbers such as interest and stuff. However, I now have errors when populating my cells (#REF error, see pictures below). Nothing has changed so my first guess is it has do with the processing of the sheet which has become too heavy.
`
function onInstall(e) {
onOpen(e);
}
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createAddonMenu();
menu.addItem('Créer étude', 'init');
menu.addToUi();
}
function init() {
ss = SpreadsheetApp.getActiveSpreadsheet();
identifyJe();
sheetMaker = ss.getActiveSheet();
currentRow = ss.getActiveCell().getRowIndex();
ssEtudes = SpreadsheetApp.openById(getSheetEtudesId());
var ui = SpreadsheetApp.getUi();
var respon se = ui.alert(
'Ligne sélectionnée : ' + currentRow + '. Exécuter ?',
ui.ButtonSet.OK_CANCEL
);
if (response !== ui.Button.OK) return;
doEtude();
doContrat();
doPhases();
}
function doEtude(nomEtude) {
var etudeData = [];
for (var i = 0; i < etudeNamedRanges.length; i++) {
var column = ss.getRangeByName(etudeNamedRanges[i]).getColumn();
etudeData.push(sheetMaker.getRange(currentRow, column).getValue());
}
var destSheet = ssEtudes.getSheetByName('etudes');
var rowToAppend = getFirstEmptyRow(destSheet, 0);
for (var i = 0; i < etudeData.length; i++) {
var column = ssEtudes.getRangeByName(etudeNamedRanges[i]).getColumn();
destSheet.getRange(rowToAppend, column).setValue(etudeData[i]);
}
}
function doContrat() {
var contratData = [];
for (var i = 0; i < contratNamedRanges.length; i++) {
var column = ss.getRangeByName(contratNamedRanges[i]).getColumn();
contratData.push(sheetMaker.getRange(currentRow, column).getValue());
}
var destSheet = ssEtudes.getSheetByName('contrats');
var rowToAppend = getFirstEmptyRow(destSheet, 11);
for (var i = 0; i < contratData.length; i++) {
destSheet.getRange(rowToAppend, i + 4).setValue(contratData[i]);
}
}
function doPhases() {
var phasesCoords = getPhasesCoords();
var phasesData = [];
for (var i = 0; i < phasesCoords.length; i++) {
var phaseData = getPhaseData(phasesCoords[i]);
phaseData.unshift(i + 1);
phasesData.push(phaseData);
}
var destSheet = ssEtudes.getSheetByName('phases');
var rowToAppend = getFirstEmptyRow(destSheet, 9);
var columns = [];
for (var nPhase = 0; nPhase < phasesData.length; nPhase++) {
var phaseData = phasesData[nPhase];
for (var i = 0; i < phaseData.length; i++) {
if (columns[i] === undefined)
columns[i] = ssEtudes.getRangeByName(phasesNamedRanges[i]).getColumn();
destSheet
.getRange(rowToAppend + nPhase, columns[i])
.setValue(phaseData[i]);
}
}
}
`
Type of formula
The results
The error, which translates into The result could not be auto-expanded. Please insert new columns (1).
The results actually appear for some time and disappear after a few seconds, constantly. Does it have something to do with the size of the spreadsheet ?

How to Change .getMergedRanges() From String to a Range?

The following code will run very slow in my Google Sheets because my getRange is too large. Is there a way to only loop through the columns that are merged? I only want the for loop to get the number of columns in "yourRange" that are merged.
function getUpfrontCosts() {
var sheet = SpreadsheetApp.getActive().getSheetByName('LPB_COST');
var cl , count=0;
var yourRange = sheet.getRange("H13:UV13");
for (var i = 1; i < yourRange.getNumColumns()+1; i++)
{
cl=yourRange.getCell(1, i);
if (cl.isPartOfMerge()){
if (cl.offset(15, 0).getBackground() == "#ff8300" && cl.getMergedRanges()[0].getCell(1, 1).getValue()=='Upfront Costs') {
count = count + cl.offset(15, 0).getValue();
}
else {
}
} else {
}
}
return count;
};
The second code is how I am trying to turn a string to a range. I am getting "Cell reference out of range" error
How can I change cl to not be a string and be a range?
function getUpfrontCosts()
{
var sheet = SpreadsheetApp.getActive().getSheetByName('LPB_COST');
var destSheet = SpreadsheetApp.getActive().getSheetByName('Top Level PN');
var cl , count=0;
var yourRange = sheet.getRange("I13:UZ13");
var mergedRanges = yourRange.getMergedRanges();
for (var i = 0; i < mergedRanges; i++){
}
var newRange = sheet.getRange(mergedRanges[i].getA1Notation());
Logger.log(newRange.getA1Notation());
for (var i = 0; i < newRange.getNumColumns()+1; i++){
cl=newRange.getCell(1, i);
Logger.log(newRange.getA1Notation());
if (cl.offset(15, 0).getBackground() == "#ff8300" && cl.getValue()=='Upfront Costs') {
count = count + cl.offset(15, 0).getValue();
}
else {
}
}
return count;
};
This is the line with the error
cl=newRange.getCell(1, i);
if you want to use merge cell range, you can do this:
function UntitledMacro1()
{
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var cl , count=0 ;
var yourRange = sheet.getRange("I13:UZ13");
var bb = yourRange.getMergedRanges();
for (a=bb[0].getColumn();a<bb[0].getLastColumn()+1;a++)
{
//Your actual columns from I13, for first merge range, here your cl,
//but if your range is ("13:13"), you don't need
//-sheet.getRange("I13").getColumn()+1
cl=yourRange.getCell(1, a-sheet.getRange("I13").getColumn()+1);
//For your offset 15
Logger.log(cl.offset(15, 0).getValue());
//on so on
}
};

Why doesn't this script collect all gmails?

I have a google app script that collects information about Gmail messages and then pastes it into a google sheet. Trouble is it doesn't get ALL of the messages. It only picks up the first one of each thread. I feel like I am missing something to loop through each thread? Any suggestions?
function getMail(){
var myspreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var mysheet = myspreadsheet.getSheetByName("Sheet3");
var start = 0;
var max = 99;
var count =0;
var row = mysheet.getLastRow()+1
var maxDate = mysheet.getRange("B1").getValue()
while(count < 4)
{
var threads = GmailApp.getInboxThreads(start , max);
var messages = GmailApp.getMessagesForThreads(threads);
var froms = [];
messages.get
for(var i = 0; i < threads.length; i++)
{
var msgDate = messages[i][0].getDate();
if(msgDate>maxDate){
froms.push([messages[i][0].getDate(),messages[i][0].getFrom(),messages[i][0].getSubject(),messages[i][0].getPlainBody()]);
}
}
if(froms.length>0){
mysheet.insertRows(2, froms.length)
mysheet.getRange(2,1,froms.length,4).setValues(froms);
}
start = start + 100;
count++;
}
}
Your current script is only grabbing messages[i][0], the first message in that group for the thread. Instead you need to loop through all of the messages using two for loops, as you can see in the script below I use messages[i][j].
function getMail() {
var mySpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var mySheet = mySpreadsheet.getSheetByName("Sheet3");
var start = 0;
var max = 99;
var count = 0;
var maxDate = mySheet.getRange("B1").getValue();
while(count < 4) {
var threads = GmailApp.getInboxThreads(start, max);
var messages = GmailApp.getMessagesForThreads(threads);
var froms = [];
for(var i = 0; i < messages.length; i++) {
for(var j = 0; j < messages[i].length; j++) {
var msgDate = messages[i][j].getDate();
if(msgDate > maxDate) {
froms.push([msgDate,messages[i][j].getFrom(),messages[i][j].getSubject(),messages[i][j].getPlainBody()]);
}
}
}
if(froms.length > 0) {
mySheet.insertRows(2, froms.length);
mySheet.getRange(2, 1, froms.length, 4).setValues(froms);
}
start = start + 100;
count++;
}
}
Notable changes:
removed var rows because it wasn't used anywhere in the script.
changed first for loop to run for messages.length rather than
threads.
added another for loop to loop through every message in
messages[i].
you were getting messages[i][0].getDate() twice, so I just used the variable already defined for adding to the array.
minor grammatical/spacing changes for consistency across script.

google apps script two columns summary

I have a google spreadsheet with two columns corresponding to lessons: the first with names of the porfessors (occasionally repeating themselves) and the second with numbers (number of hours). I would like to have as output two columns, the first with the names of the porfessors and the second with the sum of all the hours
I tried with the following code, but it seems to give me back two arrays with the initial colums, as if the condition if (names[names.length-1] == namesColumn[i]) is never met.
What am I doing wrong?
function resumeProfessors() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var namesColumn = sheet.getRange("C4:C31").getValues();
var lessonsColumn = sheet.getRange("G4:G31").getValues();
var names = [];
names.length = 0;
var lessons = [];
lessons.length = 0;
namesColumn.sort();
for (var i = 0; i < namesColumn.length; i++) {
if (names[names.length-1] == namesColumn[i]){
lessons[lessons.length-1] = lessons[lessons.length-1] + lessonsColumn[i];}
else{
sheet.getRange(i+4, 9).setValue(names[names.length-1] + namesColumn[i]);
names[names.length] = namesColumn[i];
lessons[lessons.length] = lessonsColumn[i];
};}
writeResume(names, lessons);
}
Ty
Given your use-case, I'd recommend using a Pivot table or the =QUERY formula.
However, assuming your input sheet looks something like this -
And the expected output is something like this -
You can try the below code -
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Sheet1');
var output = ss.getSheetByName('Sheet2');
var inputValues = input.getDataRange().getValues();
Logger.log(inputValues)
for (var i = 1; i < inputValues.length; i++) {
var name = inputValues[i][0];
var totalHours = [];
for (var j = 0; j < inputValues.length; j++) {
var hours = inputValues[j][1];
if (name == inputValues[j][0]) {
totalHours.push(inputValues[j][1]);
}
}
var outputValues = output.getDataRange().getValues();
var newEntry = true;
for (var k = 0; k < outputValues.length; k++) {
if (name == outputValues[k][0]) {
newEntry = false;
}
}
if (newEntry) {
output.appendRow([name,totalHours.reduce(function(a, b) {return a + b})]);
}
}
}
Hope this helps.