How to Iterate to output data to Google Sheets with App-Script - google-apps-script

Using Google App Script, when I used Logger.log() the for loop iterates properly and I get results for each value. When I try to output this to a google sheet only the last value for each variable is output over and over again for the number of goals.length.
Any help is very much appreciated!
function listGoals() {
var sheet = SpreadsheetApp.getActiveSheet();
var filterList = Analytics.Management.Goals.list(accountId, webPropertyId, profileId)
var goals = filterList.items;
for (var i = 0, goal; goal = goals[i]; i++) {
var accountId = goal.accountId;
var propertyId = goal.webPropertyId;
var goalNumber = goal.id;
var goalName = goal.name;
Logger.log('accountId: ' + accountId);
Logger.log('profileId: ' + propertyId);
Logger.log('goal number: ' + goalNumber);
Logger.log('goal name: ' + goalName);
//Logger.log prints for each result
sheet.getRange(1,1,goals.length).setValue(goalNumber);
sheet.getRange(1,2,goals.length).setValue(goalName);
//this only prints out the last value of goalNumber and goalName to the sheet
}
}

It doesn't only print the last results, it just keeps overwriting the old result with the new one.
goals.length only helps if you then supply an array of arrays containing the values looking as such:
[[1, "Goal 1"],
[2, "Goal 2"]]
If you want to print out a list of goalNumber and goalName you need to offset the cell to write in every time.
something like
sheet.getRange(1+i,1).setValue(goalNumber);
sheet.getRange(1+i,2).setValue(goalName);
To speed up the process a bit and not do two calls for every goal you can store the id name pairs as arrays within an array and do one final setValues call after the loop finishes executing.
function listGoals() {
var sheet = SpreadsheetApp.getActiveSheet();
var filterList = Analytics.Management.Goals.list(accountId, webPropertyId, profileId)
var goals = filterList.items;
var goalsToWrite = [];
for (var i = 0, goal; goal = goals[i]; i++) {
goalsToWrite.push([goal.id, goal.name]);
}
sheet.getRange(1, 1, goals.length, 2).setValues(goalsToWrite);
}

Related

Is there a way to list the array values in one cell by adding one onto another

I'm making a google sheets app function that checks if the ID in one sheet can be associated with any of the patients (each patient receives an ID), then add it into their file (a single cell next to their name).
I'm at a point where I can get the info into the cell with .copyValuesToRange, but the problem is that all the values are copied into the cell one after another. The desired effect is that I get all values separated by ", ".
Here's my code:
function newCaseIn() {
let app = SpreadsheetApp;
let dest = app.getActiveSpreadsheet().getSheetByName("Baza Danych");
let form = app.getActiveSpreadsheet().getSheetByName("Zgloszenia");
for (let i = 2; i < 200; i++) {
if (form.getRange(i, 2).getValue()) {
while (true) {
form.getRange(i, 3).copyValuesToRange(0, 9, 9, 2, 2);
}
}
}
}
And here's how the database looks: Database FormSubmissions
NOTE: There is a form that comes down to the second sheet to allow people submit new patient files to a specified ID
It could be something like this:
function main() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let dest = ss.getSheetByName("Baza Danych");
let form = ss.getSheetByName("Zgloszenia");
// get all data from the form
var source_data = form.getDataRange().getValues();
source_data.shift(); // remove the header
// make the data object
// in: 2d array [[date,id1,doc], [date,id2,doc], ...]
// out: object {id1: [doc, doc, doc], id2: [doc, doc], ...}
var source_obj = {};
while(source_data.length) {
let [date, id, doc] = source_data.shift();
try { source_obj[id].push(doc) } catch(e) { source_obj[id] = [doc] }
}
// get all data from the dest sheet
var dest_data = dest.getDataRange().getValues();
// make a new table from the dest data and the object
var table = [];
while (dest_data.length) {
let row = dest_data.shift();
let id = row[0];
let docs = source_obj[id]; // get docs from the object
if (docs) row[8] = docs.join(', ');
table.push(row);
}
// clear the dest sheet and put the new table
dest.clearContents();
dest.getRange(1,1,table.length,table[0].length).setValues(table);
}
Update
The code from above clears existed docs in the cells of column 9 and fills it with docs from the form sheet (for relevant IDs).
If the dest sheet already has some docs in the column 9 and you want to add new docs you have to change the last loop this way:
// make a new table from the dest data and the object
var table = [];
while (dest_data.length) {
let row = dest_data.shift();
let id = row[0];
let docs = source_obj[id]; // get docs from the object
if (docs) {
let old_docs = row[8];
row[8] = docs.join(', ');
if (old_docs != '') row[8] = old_docs + ', ' + row[8];
}
table.push(row);
}

Parse XML to Google Spreadsheet in google Apps Script

I need to parse a XML file to Google Spreadsheet. I need all the data from each row "row".
Every URL should have its own row in spreadsheet for all its values.
XML File, example:
<response>
<method>domain.urls</method>
<answer>
<row url="https://www.example.com/1" top10="3048" top100="4490" visindex="9.1068505804717"/>
<row url="https://www.example.com/2" top10="2633" top100="2720" visindex="8.6659210425021"/>
<row url="https://www.example.com/3" top10="875" top100="964" visindex="2.7381900000597"/>
</answer>
<credits used="4"/>
</response>
I started with this function and got one value back (yay!)
for (var i = 0; i < items.length; i++) {
if(items[i].getName() == 'answer'){
var answer = items[i].getChildren();
return answer[0].getAttribute('visindex').getValue();
}
}
Tis function writes the value (answer) to spreadhseet
var seoValue = getSeoValue(apikey, seoMetric, keyword, country);
outputSheet.getRange(outputLastRow, 6 + i ).setValue(seoValue/1); //aktuell nur 1 outputwert
}
// increase the last output row by one
outputLastRow++;
}
I dont knwo how to collect all the values from a row and save them to spreadhseet.
Output spreadhsheet example:
INPUT - (excerpt)
<row url="https://www.example.com/1" top10="3048" top100="4490" visindex="9.1068505804717"/>
<row url="https://www.example.com/2" top10="2633" top100="2720" visindex="8.6659210425021"/>
<row url="https://www.example.com/3" top10="875" top100="964" visindex="2.7381900000597"/>
OUTPUT - Row A1 | B1 | C1 | D1
values row-1 -> URL-1-value | top-10-value-1 | top-100-value-1 | visindex-value-1
values row-2 -> URL-2-value | top-10-value-2 | top-100-value-2 | visindex-value-2
And one more thing that kills me: as far as I understand, I need to convert the URL to a string.
Apps Script has an XML Service that you can use to parse data. Here's a way you can do it based on one of the examples there. You can just paste it on a new Sheet's Apps Script project to test and modify at your convenience.
function xmlParser() {
//input should be your xml file as text
let xml = '<response><method>domain.urls</method><answer><row url="https://www.example.com/1" top10="3048" top100="4490" visindex="9.1068505804717"/><row url="https://www.example.com/2" top10="2633" top100="2720" visindex="8.6659210425021"/><row url="https://www.example.com/3" top10="875" top100="964" visindex="2.7381900000597"/></answer><credits used="4"/></response>';
let document = XmlService.parse(xml); //have the XML service parse the document
let root = document.getRootElement(); //get the root element of the document
let answers = root.getChild("answer").getChildren("row"); //gets the 'answer' node, and a list of its subnodes, note that we use getChildren() to get them all in an array
//now the answers array contains each <row> element with all its attributes
const list = [] //we create an array that will hold the data
answers.forEach(function (row) {
//forEach function that iterates through all the row nodes and uses
//getAttribute() to get their values based on the names we know already
//we push each element to our list array
list.push([row.getAttribute("url").getValue(), row.getAttribute("top10").getValue(), row.getAttribute("top100").getValue(), row.getAttribute("visindex").getValue()])
}
)
writeToSheet(list) // after the array is populated you can call another function to paste in the Sheet
}
function writeToSheet(list) {
//first set a range where you will paste the data. You have to define the length with the input array
//the first two parameters are "1, 1" for row 1, column 1, but you can change this depending on your needs.
let range = SpreadsheetApp.getActiveSheet().getRange(1, 1, list.length, list[0].length)
//once we have the array set you can just call setValues() on it which pastes the array on its own
range.setValues(list)
}
Output looks like this:
References:
XML Service
getRange()
setValues()
Holy frak that worked Daniel.
I put some scripts together and it gives me what I need.
Google Apps Script: SISTRIX API Call page.urls. Its crap but output is okay.
function getData() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var inputSheet = spreadSheet.getSheets()[0];
var outputSheet = spreadSheet.getSheets()[1];
// get the last non-empty row number in the input sheet
var inputLastRow = inputSheet.getLastRow();
// get the first empty row number in the output sheet
var outputLastRow = outputSheet.getLastRow() + 1;
// get the api key from the input sheet
var apikey = inputSheet.getRange('A2').getValue();
//var week = getWeek();
// get the input for queries
var inputs = inputSheet.getRange('A11:E' + inputLastRow).getValues();
// specify the SISTRIX KPIs for the client and the competitor(s)
var clientSeoMetrics = inputSheet.getRange('A5').getValue().split(',');
// loop over rows in the input
for (var row = 0; row < inputLastRow - 10; row++) {
// specify inputs - which column means what
//var keyword = inputs[row][0].toLowerCase();
//var country = inputs[row][2].toLowerCase();
var domain = inputs[row][0].toLowerCase();
var limit = inputs[row][1];
//write the basic information to output
//outputSheet.getRange('A'+outputLastRow).setValue(keyword);
//B Suchvolumen
//outputSheet.getRange('C'+outputLastRow).setValue(country.toUpperCase());
//D wird Search Intent
//outputSheet.getRange('E'+outputLastRow).setValue(week);
// check if competition or client and take proper KPIs
var seoMetrics;
seoMetrics = clientSeoMetrics; //eigenltich unnoetig - evtl. fuer intent sinnvoll (if intent dann)
// loop over seometrics - falls weitere Metriken
for (var i = 0; i < seoMetrics.length; i++) {
var seoMetric = seoMetrics[i];
if (seoMetric == ""){
break;
}
// run seoMetric query
//var seoValue = getSeoValue(apikey, seoMetric, domain, limit);
//outputSheet.getRange(outputLastRow, 5 + i ).setValue(seoValue/1); //aktuell nur 1 outputwert
//var seoValue = getSeoValue(apikey, seoMetric, domain, limit);
//outputSheet.getRange(outputLastRow, 5 + i ).setValue(seoValue/1); //aktuell nur 1 outputwert
var seoValue = [] ;
var seoValue = getSeoValue(apikey, seoMetric, domain, limit);
}
// increase the last output row by one
outputLastRow++;
}
function getSeoValue(apikey, seoMetric, domain, limit){
var url = "https://api.sistrix.com/"+seoMetric+"?domain="+domain+"&api-key="+apikey+"&country=de&limit="+limit;
var xml = UrlFetchApp.fetch(url).getContentText();
var document = XmlService.parse(xml);
var root = document.getRootElement(); //get the root element of the document
var answers = root.getChild("answer").getChildren("row"); //gets the 'answer' node, and a list of its subnodes, note that we use getChildren() to get them all in an array
//now the answers array contains each <row> element with all its attributes
const list = [] //we create an array that will hold the data
answers.forEach(function (row) {
//forEach function that iterates through all the row nodes and uses
//getAttribute() to get their values based on the names we know already
//we push each element to our list array
list.push([row.getAttribute("url").getValue(), row.getAttribute("top10").getValue(), row.getAttribute("top100").getValue(), row.getAttribute("visindex").getValue()])
}
)
writeToSheet(list) // after the array is populated you can call another function to paste in the Sheet
}
function writeToSheet(list) {
//let range = SpreadsheetApp.getActiveSheet().getRange(1, 1, list.length, list[0].length)
//range.setValues(list)
//outputSheet.getRange(outputLastRow, 5 + i ).setValue(list/1); //aktuell nur 1 outputwert
let range = outputSheet.getRange(outputLastRow, 1, list.length, list[0].length)
range.setValues(list)
}
}

TypeError when cell contains double figures

The code below is working how I want it too except when cell A1 is large then 9. As soon as I enter anything greater like 10 or 11, I get the error
"TypeError: theSplitString.split is not a function"
What am I doing wrong?
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssSheet = ss.getSheetByName('Quote List');
var copyViewQuote = ss.getSheetByName('New Job');
var quoteNuber = copyViewQuote.getRange("A1").getValue();
Logger.log(+quoteNuber)
var theSplitString = ssSheet.getRange("M" + quoteNuber).getValue(); // WHICH PARTS STRING TO USE
var thePartAmount = ssSheet.getRange("N" + quoteNuber).getValue(); // GIVES YOU THE NUMBER OF PARTS IN THE STRING
var spiltstring = theSplitString.split(","); // WHAT SPILTS THE PARTS IN THE STRING
var data = 1; // START FROM 1 AS 0 GIVES UNDEFINED AS FIRST PART OF STRING
var partListNumer = 1;
copyViewQuote.getRange('H5:L200').clearContent();
for(row=5; row<=5+thePartAmount; row++){
//if (partListNumer <= thePartAmount){
//copyViewQuote.getRange(row, 8).setValue(partListNumer);
//partListNumer = partListNumer + 1
//}
//each spiltstring array data is put on Column 6 or column F that starts from row 10 to 10 + length of the splitstring array
copyViewQuote.getRange(row,9).setValue(spiltstring[data]);
data++;
copyViewQuote.getRange(row,10).setValue(spiltstring[data]);
data++;
copyViewQuote.getRange(row,11).setValue(spiltstring[data]);
data++;
copyViewQuote.getRange(row,12).setValue(spiltstring[data]);
data++;
}
}
Looks like you're trying to split the value of the cell, which can only be done on strings.
Try this on your sheet:
This will force all call data to be strings, which you can work a split function on. That should fix the issue.

Converting Dataset from 'Wide' to 'Long' in Google Apps Script for Google Sheet

I need some help generating a google script AND/or Formula for this one..
In the attached gsheet below, I have the following raw dataset...
For Phase 1, I need to convert this 'wide' format to a 'long' format whereby new columns are created for each attribute in 'Data Type' and 'T1' and 'T2' are condensed in their own column called 'Time'. Please see below...
For Phase 2, I need to join all info across each row but split by 'Time'. Please see below...
Finally for Phase 3, I need to create one additional step where each is split by Col A. Please see below...
I have tried to achieve the reshaping of the data via 'QUERY'and 'Transpose' but can't seem to do it. If anyone has a custom build function that addresses this very common task, I would very much appreciate your help on this one!
Dataset is below...
https://docs.google.com/spreadsheets/d/1Ujxki1wmaLmkBgZQHI-OTwSubKdlN5mvLDdGWhCBn3E/edit?usp=sharing
Thanks.
How about this sample script? I'm interested in the situation of your issue. So I challenged this. This is also for my study. In my answer, I tried to solve your question using GAS. I think that there are several answers for your situation. So please think of this as one of them.
In this sample script, the values for "Phase 1", "Phase 2" and "Phase 3" are created every 1 cycle of the data. From your question and sample spreadsheet, I thought that the cycle of data is 5. Then, the created values are imported the 3 sheets.
Sample script :
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var rawData = ss.getSheetByName("Raw Data").getDataRange().getValues();
var header = rawData[0];
var delimiter = "| "; // Please input delimiter.
var cycle = 5; // From your question, I thought the cycle of data is 5.
var phase1 = [];
var phase2 = [];
var phase3 = [];
rawData.shift();
for (var i = 0; i < rawData.length; i += cycle) {
var temp1 = [];
var temp2 = [];
for (var j = i; j < i + cycle; j++) {
temp1.push([rawData[j][0], rawData[j][1]]);
temp2.push([rawData[j][3], rawData[j][4]]);
}
var converted = temp2[0].map(function(_, i){return temp2.map(function(f){return f[i]})}) // Transpose
.map(function(e, i){return temp1[i].concat(e).concat(header[i + 3])}); // Add T1 and T2
// Create value for phase1.
Array.prototype.push.apply(phase1, converted);
// Create value for phase2.
phase2.push([converted[0].slice(0, 7).join(delimiter), converted[1].slice(0, 7).join(delimiter)]);
// Create value for phase3.
phase3.push([converted[0][0], header[3], header[4]]);
phase3.push(["", converted[0].slice(1, 7).join(delimiter), converted[1].slice(1, 7).join(delimiter)]);
phase3.push(["", "", ""]);
}
// If you want to change the sheet name, please modify this part.
var all = [
[ss.getSheetByName("Phase 1"), phase1],
[ss.getSheetByName("Phase 2"), phase2],
[ss.getSheetByName("Phase 3(Desired Final Output)"), phase3]
];
all.forEach(function(e) {
// Import values to 3 sheets.
e[0].getRange(e[0].getLastRow() + 1, 1, e[1].length, e[1][0].length).setValues(e[1]);
});
}
If I misunderstand your question, I'm sorry.
Edit
When the modified script is run, all converted values are overwrote for 3 sheets of phase 1, phase 2 and phase 3.
The name of Data Melt can't be used for the function name. So DataMelt was used for it.
For example, =DataMelt('Raw Data'!A2:E6) is put to a cell, the converted data for phase 1 is imported.
In this function, the data that 5 rows are 1 cycle can be used.
Modified script
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var rawData = ss.getSheetByName("Raw Data").getDataRange().getValues();
var header = rawData[0];
var delimiter = "| "; // Please input delimiter.
var cycle = 5; // From your question, I thought the cycle of data is 5.
var phase1 = [];
var phase2 = [];
var phase3 = [];
rawData.shift();
for (var i = 0; i < rawData.length; i += cycle) {
var temp1 = [];
var temp2 = [];
for (var j = i; j < i + cycle; j++) {
temp1.push([rawData[j][0], rawData[j][1]]);
temp2.push([rawData[j][3], rawData[j][4]]);
}
var converted = temp2[0].map(function(_, i){return temp2.map(function(f){return f[i]})}) // Transpose
.map(function(e, i){return temp1[i].concat(e).concat(header[i + 3])}); // Add T1 and T2
// Create value for phase1.
Array.prototype.push.apply(phase1, converted);
// Create value for phase2.
phase2.push([converted[0].slice(0, 7).join(delimiter), converted[1].slice(0, 7).join(delimiter)]);
// Create value for phase3.
phase3.push([converted[0][0], header[3], header[4]]);
phase3.push(["", converted[0].slice(1, 7).join(delimiter), converted[1].slice(1, 7).join(delimiter)]);
phase3.push(["", "", ""]);
}
phase1.unshift(["Col A", "Col B", "Catalogue", "Display", "Equivalent Single Price", "In-Store_Shopper", "Mechanic", "Time"]);
phase2.unshift(["T1", "T2"]);
var all = [
[ss.getSheetByName("Phase 1"), phase1],
[ss.getSheetByName("Phase 2"), phase2],
[ss.getSheetByName("Phase 3(Desired Final Output)"), phase3]
];
all.forEach(function(e) {
// Import values to 3 sheets.
e[0].getRange(1, 1, e[1].length, e[1][0].length).setValues(e[1]);
});
}
// Added new function
function DataMelt(e) {
var e = [["Chalk","A0C-Len Superior Kids TP 80gm","Catalogue","No","Yes"],["Chalk","A0C-Len Superior Kids TP 80gm","Display","Shelf","GE"],["Chalk","A0C-Len Superior Kids TP 80gm","Equivalent Single Price",2.49,2.49],["Chalk","A0C-Len Superior Kids TP 80gm","In-Store_Shopper","",""],["Chalk","A0C-Len Superior Kids TP 80gm","Mechanic","LDLP","Off"]];
var rawData = e;
var header = ["Col A", "Col B", "Data Type", "T1", "T2"];
var cycle = 5; // From your question, I thought the cycle of data is 5.
var phase1 = [];
var temp1 = [];
var temp2 = [];
for (var j = 0; j < cycle; j++) {
temp1.push([rawData[j][0], rawData[j][1]]);
temp2.push([rawData[j][3], rawData[j][4]]);
}
var converted = temp2[0].map(function(_, i){return temp2.map(function(f){return f[i]})}) // Transpose
.map(function(e, i){return temp1[i].concat(e).concat(header[i + 3])}); // Add T1 and T2
// Create value for phase1.
Array.prototype.push.apply(phase1, converted);
return phase1;
}

Not Able to Scrape table in Google Sheets

With the help of this SO questionsI am trying to scrape the following website. I would like the two teams and the time. For example, the first entry would be Chicago | Miami | 12:30 PM, and the last entry would be Colorado | Arizona | 10:10 PM. My code is as follows
function espn_schedule() {
var url = "http://www.espn.com/mlb/schedule/_/date/20180329";
var content = UrlFetchApp.fetch(url).getContentText();
var scraped = Parser.data(content).from('class="schedule has-team-logos align-left"').to('</tbody>').iterate();
var res = [];
var temp = [];
var away_ticker = "";
scraped.forEach(function(e){
var away_team = Parser.data(e).from('href="mlb/team/_/name/').to('"').build();
var time = Parser.data(e).from('a data-dateformat="time1"').to('</a>').build();
if (away_ticker == "") away_ticker = away_team;
if (away_team != away_ticker) {
temp.splice(1, 0, away_ticker);
res.push(temp);
temp = [];
away_ticker = away_team;
temp.push(time);
}
});
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Schedule");
ss.getRange(ss.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
}
I get the following error:
TypeError: Cannot read property "length" from undefined. (line 42, file "Code")
Here is a modified solution that works
function espn_schedule() {
var url = "http://www.espn.com/mlb/schedule/_/date/20180329";
var content = UrlFetchApp.fetch(url).getContentText();
var e = Parser.data(content).from('class="schedule has-team-logos align-left"').to('</tbody>').build();
var res = [];
//Logger.log(scraped[0])
var temp = [];
var away_ticker = "";
var teams = Parser.data(e).from('<abbr title="').to('">').iterate();
Logger.log(teams)
var time = Parser.data(e).from('data-date="').to('">').iterate()
Logger.log(time)
for( var i = 0; i<teams.length ; i = i+2)
{
res[i/2] = []
res[i/2][0] = teams[i]
res[i/2][1] = teams[i+1]
res[i/2][2] = new Date(time[i/2]).toLocaleTimeString('en-US')
}
Logger.log(res)
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Schedule");
ss.getRange(ss.getLastRow() + 1, 1, res.length, res[0].length).setValues(res);
}
Modification explained:
1) Since you access only the first table you don't need to iterate during parsing and just get the first table. Also, since you get just the first table, you don't need to use forEach to loop through each element.
var e = Parser.data(content)
.from('class="schedule has-team-logos align-left"')
.to('</tbody>')
.build(); //Use build instead of iterate
2) Instead of parsing the HTML link to get the team name, you can use <abbr title=" element to scrape the name. Furthermore, you can iterate over all the team names in the table to get an array of team names.
var teams = Parser.data(e).from('<abbr title="').to('">').iterate();
3) Similar to the above modification, you can get the time by using the data-date tag. This gives you date which can read by Date() class. Again, we iterate over the table to get all the times
var time = Parser.data(e).from('data-date="').to('">').iterate()
4) Finally, we use for loop to rearrange the teams and time in the array called res. This allows for inserting the data into the sheet directly.
for( var i = 0; i<teams.length ; i = i+2) //each loop adds 2 to the counter
{
res[i/2] = []
res[i/2][0] = teams[i] //even team (starts at zero)
res[i/2][1] = teams[i+1] //vs odd teams
res[i/2][2] = new Date(time[i/2]).toLocaleTimeString('en-US')
}
Reference:
Date(),Date.toLocaleTimeString()
Edit:
Reason for error, in the below code
Parser.data(e).from('href="mlb/team/_/name/').to('"').build()
you are looking for string 'href="mlb/team/_/name/', however it should be href="/mlb/team/_/name/'. Note the difference mlb vs /mlb.
Secondly, in the following code
Parser.data(e).from('a data-dateformat="time1"').to('</a>').build();
The string should be a data-dateFormat, when you inspect the website it shown as dateformat. However, when you call it using URLfetch and log the text, it is shown as dateFormat