Parsing XML Data that I receive from UrlFetch - google-apps-script

I want to parse the data I get from UrlFetch into a spreadsheet, but all I'm getting is undefined can someone show me what i'm doing wrong
The xml is at the address https://dl.dropbox.com/u/11787731/Minecraft/bans.xml
function runevery15mins() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MC Bans");
sheet.clearContents();
var banURL = "https://dl.dropbox.com/u/11787731/Minecraft/bans.xml";
var banXML = UrlFetchApp.fetch(banURL).getContentText();
var banDOC = Xml.parse(banXML, false);
var mcuser = banDOC.bans;
var x = 0;
for(var c=0; c>mcuser.length;c++){
var name = mcuser.getElement("username")[c].getText();
var date = mcuser.getElement("date")[c].getText();
var reason = mcuser.getElement("reason")[c].getText();
var duration = mcuser.getElement("duration")[c].getText();
}
sheet.appendRow([name, date, reason, duration]);
}

You have some small errors in your code.
For example, the second argument in the for loop needs to be c<mcuser.length.
Using the Xml service documentation, this worked for me
function runevery15mins() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MC Bans");
sheet.clearContents();
var banURL = "https://dl.dropbox.com/u/11787731/Minecraft/bans.xml";
var banXML = UrlFetchApp.fetch(banURL).getContentText();
var banDOC = Xml.parse(banXML, false);
// Get all the child nodes from the document element that are 'user' nodes
var mcusers = banDOC.getElement().getElements('user');
for(var c=0; c<mcusers.length;c++){
var user = mcusers[c];
var name = user.getElement('username').getText();
var date = user.getElement('date').getText();
var reason = user.getElement('reason').getText();
var duration = user.getElement('duration').getText();
sheet.appendRow([name, date, reason, duration]);
}
}
Note for example that the sheet.appendRow line is INSIDE the loop, not outside as you had it before. I also deleted the X variable, since I didn't see any purpose for it.
I also created a user variable, which is an XmlElement, to make it easier to understand how to get the different contents of each node.

You were almost there.
Looks like there was another array you needed to drill down into. Also, your call back to the spreadsheet should be in the loop. Try this:
...
var mcuser = banDOC.bans.user;
for(var i in mcuser){
var name = mcuser[i].getElement("username").getText();
var date = mcuser[i].getElement("date").getText();
var reason = mcuser[i].getElement("reason").getText();
var duration = mcuser[i].getElement("duration").getText();
sheet.appendRow([name, date, reason, duration])
}

Related

How can I use a variable as a parameter in getSheetByName?

I have the following code which generates some gslides slides from data in a spreadsheet. The code is attached to the gslides file and it works when the parameters for link/year are entered as strings. However, these parameters will sometimes vary from a set list (e.g. pull from a different sheet or from a different file) so the code sometimes needs to be edited.
I want to share this with colleagues who don't know how to use app scripts and so tried to edit it so that the link/year can be pulled from a textbox in one of the slides in the gslides file. They are pulling through ok but when I try to use it as a parameter I get an error for line 11 (var values = sheet.getRange('F2:aa2').getValues();) as the sheet returns as null.
I've tried wrapping it in quotes (" ' " + year + " ' ") but that doesn't work either.
Please let me know if I need to include more info or a link to the sheets/slides files.
function generateStarters() {
var deck = SlidesApp.getActivePresentation();
var slides = deck.getSlides();
var link = slides[2].getPageElements()[2].asShape().getText().asString()
var year = slides[2].getPageElements()[4].asShape().getText().asString()
var dataSpreadsheetUrl = link
var ss = SpreadsheetApp.openByUrl(dataSpreadsheetUrl);
var sheet = ss.getSheetByName(year);
var values = sheet.getRange('F2:aa2').getValues();
var templateSlide = slides[1];
var presLength = slides.length;
values.forEach(function(page){
if(page[0]){
var Q1 = page[0];
var A1 = page[1];
var Q2 = page[4];
var A2 = page[5];
var Q3 = page[8];
var A3 = page[9];
var Q4 = page[12];
var A4 = page[13];
var QK = page[16];
var AK = page[17];
var QF = page[20];
var AF = page[21];
templateSlide.duplicate(); //duplicate the template page
slides = deck.getSlides(); //update the slides array for indexes and length
newSlide = slides[3]; // declare the new page to update
var shapes = (newSlide.getShapes());
shapes.forEach(function(shape){
shape.getText().replaceAllText('{{q1}}',Q1);
shape.getText().replaceAllText('{{a1}}',A1);
shape.getText().replaceAllText('{{q2}}',Q2);
shape.getText().replaceAllText('{{a2}}',A2);
shape.getText().replaceAllText('{{q3}}',Q3);
shape.getText().replaceAllText('{{a3}}',A3);
shape.getText().replaceAllText('{{q4}}',Q4);
shape.getText().replaceAllText('{{a4}}',A4);
shape.getText().replaceAllText('{{qk}}',QK);
shape.getText().replaceAllText('{{ak}}',AK);
shape.getText().replaceAllText('{{qf}}',QF);
shape.getText().replaceAllText('{{af}}',AF);
});
presLength = slides.length;
newSlide.move(presLength);
}
});
} ```
The string from the slides file seems to always have a space at the end (from the textbox) so I just needed to add an extra line of code to remove that space.

Error in Google Sheets Script when parsing XML

I have this function running in a Google Sheets script that pulls HTML from subreddits and returns them to a spreadsheet. It works for me some/most of the time, but other times I get an error "Could not parse text. (line 13)" which is the line with var doc = Xml.parse(page, true);. Any idea why this is happening or is this just a bug with Google Scripts? Here's the code that works...sometimes.
function getRedditHTML() {
var entries_array = [];
var subreddit_array = ['https://www.reddit.com/r/news/','https://www.reddit.com/r/funny/','https://www.reddit.com/r/science/'];
for (var s = 0; s < subreddit_array.length; s++) {
var page = UrlFetchApp.fetch(subreddit_array[s]);
//this is Line 13 that is breaking
var doc = Xml.parse(page, true);
var bodyHtml = doc.html.body.toXmlString();
doc = XmlService.parse(bodyHtml);
var root = doc.getRootElement();
var entries = getElementsByClassName(root,'thing');
for (var i = 0; i < entries.length; i++) {
var title = getElementsByClassName(entries[i],'title');
title = XmlService.getRawFormat().format(title[1]).replace(/<[^>]*>/g, "");
var link = getElementsByClassName(entries[i],'comments');
link = link[0].getAttribute('href').getValue();
var rank = getElementsByClassName(entries[i],'rank');
rank = rank[0].getValue();
var likes = getElementsByClassName(entries[i],'likes');
likes = likes[0].getValue();
entries_array.push([rank, likes, title, link]);
}
}
return entries_array.sort(function (a, b) {
return b[1] - a[1];
});
}
Here is what I found upon playing with importXML (my usual way of doing this) - for some reason I cannot narrow down - it DOES appear to randomly stall out and return null for a few minutes - so I'm guessing the issue with your thing is not the code but that the site or google temporarily blocks/won't return the data -
however I found the JSON endpoint to the piece you want - and I noticed that when XML went down - the JSON didnt.
You can take that and fix it to push your own array of topics/urls - I just left it for one link for now to show you how the URL breaks down and where it should be modified:
The URL is 'https://www.reddit.com/r/news/hot.json?raw_json=1&subredditName=news&sort=top&t=day&feature=link_preview&sr_detail=true&app=mweb-client
News is mentioned in 2 places so just modify all your URLs to follow that method - you can easily load that javascript in a browser to see all the fields available
Also the portion hot.json is where you can change whether you want the ranked list (called hot), or new,top,promoted, etc. you just change that keyword.
Score is the same as the upvotes/likes
function getSubReddit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet(); //get Active sheet
var subject = 'news';
var url = 'https://www.reddit.com/r/' + subject + '/hot.json?raw_json=1&subredditName=' + subject + '&sort=top&t=day&feature=link_preview&sr_detail=true&app=mweb-client'; //json endpoint for data
var response = UrlFetchApp.fetch(url); // get api endpoint
var json = response.getContentText(); // get the response content as text
var redditData = JSON.parse(json); //parse text into json
Logger.log(redditData); //log data to logger to check
//create empty array to hold data points
var statsRows = [];
var date = new Date(); //create new date for timestamp
//The following lines push the parsed json into empty stats array
for (var j=0;j<25;j++){
for (var i =0;i<25;i++){
var stats=[];
stats.push(date);//timestamp
stats.push(i+1);
stats.push(redditData.data.children[i].data.score); //score
stats.push(redditData.data.children[i].data.title); //title
stats.push(redditData.data.children[i].data.url); //article url
// stats.push('http://www.reddit.com' + redditData.data.children[i].data.permalink); //reddit permalink
statsRows.push(stats)
}
//append the stats array to the active sheet
sheet.appendRow(statsRows[j])
}
}

An alternative way to pass data (eg, array values) through handler without using ScriptProperties

I built an app in which I use ScriptProperties to store data from a handler to its but(e) function. This was working nice, until other people started using the same spreadsheet at the same time. So often happens that one person is taking a time thinking about what item choose from a checkbox menu and another person uses the same function, changing the data stored at scriptProperties and affecting the use of the function by the first person.
What is the best way to fix it, using an alternative way to pass information through the handler?
Here one sample of one of theese functions (in which I'm using ScriptProperties to pass the values ofletterSpreadsheetId and recipientArray):
function letter(letterSpreadsheetId){
ScriptProperties.setProperty('letterSpreadsheetId', letterSpreadsheetId); // different people may have different letterSpreadsheetId;
ScriptProperties.setProperty('letter', 1); // to be used in another function
var activeSheet = ss.getActiveSheet();
var app = UiApp.createApplication().setHeight(400).setWidth(600);
var panel = app.createVerticalPanel(); // you can embed that in a form panel
var label = app.createLabel("Choose a receiver").setStyleAttribute("fontSize", 18);
app.add(label);
var sheet = SpreadsheetApp.openById(letterSpreadsheetId).getSheetByName("receivers");
var recipientArray = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
var item3Panel = app.createHorizontalPanel();
item3Panel.add(app.createLabel("receiver"));
var listBox = app.createListBox().setName('item3');
for(var i = 0; i < (recipientArray.length); i++){
listBox.addItem(recipientArray[i][1]);
}
item3Panel.add(listBox);
var recipientArrayStr = JSON.stringify(recipientArray);
ScriptProperties.setProperty('recipientArr', recipientArrayStr);
var handlerBut = app.createServerHandler("butAnswerLetter").addCallbackElement(panel);
var but = app.createButton("submit").setId("submitButton2").addClickHandler(handlerBut);
panel.add(item1Panel)
.add(item2Panel)
.add(item3Panel)
.add(but)
.add(app.createLabel().setId("answer"));
var scroll = app.createScrollPanel().setPixelSize(600, 400).setTitle("My title 1");
scroll.add(panel);
app.add(scroll);
ss.show(app);
}
function butAnswerLetter(e){
var letterSpreadsheetId = ScriptProperties.getProperty('letterSpreadsheetId');
var recipient = e.parameter.item3;
ScriptProperties.setProperty('recipient', recipient);
var recipientArrayRecovery = ScriptProperties.getProperty('recipientArr');
var recipientArray = JSON.parse(recipientArrayRecovery);
for(var i=0;i<recipientArray.length;i++){
if(recipient == recipientArray[i][1]){
var usedRecipientArray = recipientArray[i];
}
}
You have 2 possibilities (that I know), either use userProperties instead of script-Properties as these are associated with the user but it will require the user to login and authorize, or - and this will work in every case even if the app is accessed anonymously, use the tags that you can write on almost any widget.
the syntax is quite simple, here is a small code example :
function doGet(){
var app = UiApp.createApplication().setTitle('test_TAG');
var list = app.createListBox(true).setVisibleItemCount(5).setPixelSize(30,450).setName('list');
var handler = app.createServerHandler('show').addCallbackElement(list);
list.addChangeHandler(handler);
var data = [];
for(var n = 0;n<20;n++){
list.addItem(n+' ');
data.push('available value = '+Number(n+1));
}
list.setTag(data.toString());
app.add(list);
return app
}
function show(e){
var app = UiApp.getActiveApplication();
var data = e.parameter.list_tag.split()
var selected = e.parameter.list;
app.add(app.createTextBox().setText(selected).setPixelSize(200,20));
app.add(app.createTextArea().setText(data.join()).setPixelSize(200,300));
return app;
}
testable here
Edit
following Zig's pertinent comment :
I forgot to mention the hidden widget (or a textBox / area set to invisible, useful for debugging when you want to check what it contains ! ) that is also useable of course...
The comment about a user having multiple windows showing the same app is also worth mentioning !
All in all you have 3 possibilities after all !
(thanks to Zig Mandel)

Google Apps Scrollpanel not updating with information

My script is iterating through some spreadsheet information. I read the documentation and the scrollpanel is only allowed one child. I therefore wrapped the horizontal panel information inside a scrollpanel but for some reason the scrollpanel never shows up with contents just the colored background. Any ideas why this may be?
var myscrollpanel = app.createScrollPanel().setPixelSize(100, 100);
myscrollpanel.setWidth("100%");
myscrollpanel.setStyleAttribute("background", "silver");
var vpanel = app.createVerticalPanel();
for (i=1; i <= mylastrow;++i)
{
var cellsname= mydatarange.getCell(i,1).getValue().toLowerCase();
// Browser.msgBox(cellsname );
// Browser.msgBox(searchstr.toString());
if (cellsname.toString() == searchstr.toString())
{
var panelrow = app.createHorizontalPanel();
var ddate = app.createTextBox();
var sbehavior = app.createTextBox();
var sconsequence = app.createTextBox();
var scomment = app.createLabel();
var steacher = app.createTextBox();
ddate.setText(mydatarange.getCell(i,5).getValue());
sbehavior.setText(mydatarange.getCell(i,8).getValue());
sconsequence.setText(mydatarange.getCell(i,6).getValue());
scomment.setText(mydatarange.getCell(i,10).getValue());
steacher.setText(mydatarange.getCell(i,7).getValue());
panelrow.add(ddate);
panelrow.add(sbehavior);
panelrow.add(sconsequence);
panelrow.add(steacher);
panelrow.add(scomment);
vpanel.add(panelrow);
app.add(panelrow);
cnt = ++cnt;
}
}
// Browser.msgBox(cnt);
myscrollpanel.add(vpanel);
app.add(myscrollpanel);
// return myscrollpanel;
return app ;// update UI
there are a few "anomalies" in your code that could cause some issues, I suggest a couple of changes (see code below).
Another point is that it is not a good idea to read a spreadsheet in a loop, it is mush more efficient to read the whole range in one time and then use the array values to iterate.
Here is a code proposal, I didn't test it (and it will probably need some debugging) but put a few comments to explain the changes.
var myscrollpanel = app.createScrollPanel()//.setPixelSize(100, 100);// 100 pixel is quite small to put all the items you add !!
myscrollpanel.setWidth("100%");//
myscrollpanel.setStyleAttribute("background", "silver");// this will not affect the widgets you add to the panel...
var vpanel = app.createVerticalPanel();
var data = mydatarange.getValues();// get all data in an array
for (i=0; i <data.length;++i) // and work directly with array values (indexed to 0 instead of cells indexed to 1)
{
var cellsname= data[i][0].toString().toLowerCase();
if (cellsname == searchstr.toString()){
var panelrow = app.createHorizontalPanel();
var ddate = app.createTextBox();
var sbehavior = app.createTextBox();
var sconsequence = app.createTextBox();
var scomment = app.createLabel();
var steacher = app.createTextBox();
ddate.setText(data[i][4].getValue());
sbehavior.setText(data[i][7].getValue());
sconsequence.setText(data[i][5].getValue());
scomment.setText(data[i][9].getValue());
steacher.setText(data[i][6].getValue());
panelrow.add(ddate);
panelrow.add(sbehavior);
panelrow.add(sconsequence);
panelrow.add(steacher);
panelrow.add(scomment);
vpanel.add(panelrow);// add only to the panel, not to the app or the row will be outside the panel
cnt = ++cnt;
}
}
myscrollpanel.add(vpanel);
app.add(myscrollpanel);
return app ;// update UI
}

Google Script Error - Cannot find method add

I have been puzzled by this error. I have not been able to figure out what I am missing. Without the calls to headPanel the UI was working but would not insert the title or text label so I added the Horizontal panel in hopes of adding the text, but alas error. Please help me with this problem and I need to know what I am messing up.
function doGet(e){
var app = UiApp.createApplication();
var headPanel = app.createHorizontalPanel().setId('headPanel');
var header = app.setTitle('Detention Attendance');
var label = app.createLabel("Please enter attendance for Detention by clicking the checkbox next to the student's name if they were present. Then click Sumbit.");
headPanel.add(header); //* ERROR OCCURS HERE
headPanel.add(label);
app.add(pane);
var panel = app.createVerticalPanel();
var flexTable = app.createFlexTable().setStyleAttribute('border', '2px solid black')
.setStyleAttribute('borderCollapse','collapse')
.setBorderWidth(2);
//Get Data from spreadsheet
var spreadsheetId = 'xxxxxxxxxxxxxxxxxxxxxxxx';//Change it to your Spreadsheet ID
var dataArray = getData(spreadsheetId);
//Load data into table cells
Logger.log(dataArray);
for (var row = 0; row<dataArray.length; row++){
if (row > 0) {
var ticketDate = dataArray[row] [0];
var dateStamp = Utilities.formatDate(ticketDate, "America/Chicago", "MM/dd/yyyy");
Logger.log("dateStamp = " +dateStamp);
dataArray[row] [0] = dateStamp;
var ticketDate2 = dataArray[row] [16];
var dateStamp2 = Utilities.formatDate(ticketDate2, "America/Chicago", "MM/dd/yyyy");
dataArray[row] [16] = dateStamp2;
Logger.log("DateStamp = " +dateStamp2);
}
flexTable.setStyleAttribute("color", "purple").setText(row, 2, dataArray[row][2].toString()).setWidth('300px');
flexTable.setText(row, 0, dataArray[row][0].toString()).setWidth('600px');
flexTable.setText(row, 4, dataArray[row][16].toString()).setWidth('300px');
}
panel.add(flexTable);
app.add(panel);
return app;
}
function getData(spreadsheetId){
var ss = SpreadsheetApp.openById(spreadsheetId);
var sheet = ss.getSheets()[0].getDataRange();
Logger.log(sheet);
return sheet.getValues();
}
Your header var is a UiInstance - in fact it's the same instance as app, returned as a convenience for chaining by the call to .setTitle().
The .add() method is looking for a Widget as a parameter, and not finding it.
You don't need to keep that header var - it's only causing you trouble. In fact, you could set the Title as you create the UiApp instance:
var app = UiApp.createApplication().setTitle('Detention Attendance');