Linked Google Sheet: https://docs.google.com/spreadsheets/d/1j2P2V0SCiE7QtK7kdCorpLfYQPFRghKMw4tuCYbqce0/edit?usp=sharing
I have a form that outputs all products and quantities into one cell:
"Product: NF900XC, Quantity: 3
Product: NF900, Quantity: 2
Product: N1930CB, Quantity: 2
Product: N2120, Quantity: 1
Product: NLPCR200, Quantity: 2
Product: N272, Quantity: 2"
I need each Product and Quantity on their own Line with its corresponding associated data collection.
I would like the contents (column O) split into their respective columns:
**Product** : **Quantity**
NF900 : 2
N1930CB : 2
N2120 : 1
NLPCR200: 2
N272 : 2
I have used SPLIT(text, delimiter, [split_around_each_character]) command for First Name Last Name but am unsure how to parse the rest of the text into their rows and columns in this situation.
I will also be functionally copying rows with the timestamp in
column A
Submission Date
that correspond to the Products:Quantity in cell O
I hope this makes sense.
Here is some code that I've tested and it works. The code removes all the words "Product: ", and replaces them with a comma. Then the code does something similar with the string "Quantity: ", replacing it with an empty string. Next it creates an array, and converts the array to a two dimensional array, so that the rows and columns can be written all in one action.
function convertData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Form Submission');
var data = sh.getRange("O2").getValue();
//Logger.log(data);
var firstProductRemoved = data.replace("Product: ", "");
//Logger.log(firstProductRemoved);
var allProductReplacedWithComma = firstProductRemoved.replace(/Product: /g,",");
//Logger.log(allProductReplacedWithComma);
var allQuantityReplacedWithNothing = allProductReplacedWithComma.replace(/Quantity: /g,"");
//Logger.log(allQuantityReplacedWithNothing);
var dataAsArray = allQuantityReplacedWithNothing.split(",");
var outerArray = [], innerArray = [];
var i=0;
for (i=0;i<dataAsArray.length;i+=2) {
innerArray = []; //reset every loop
innerArray.push(dataAsArray[i]);
innerArray.push(dataAsArray[i+1]);
outerArray.push(innerArray);
};
//Logger.log(outerArray);
var orderItemsSh = ss.getSheetByName('Order Items');
orderItemsSh.getRange(orderItemsSh.getLastRow()+1, 15,outerArray.length, 2).setValues(outerArray);
};
You might be interested in a more general solution, rather than one that needs to rely on knowing the specific phrased in the input strings.
This kvStringToArray() function will return an array that can be written directly to a spreadsheet use Range.setvalues(). It's pure JavaScript, so it can also be used outside of the Google Apps Script environment.
A RegExp is used to successively identify key:value pairs in the input data, and the function assumes that there are "rows" of information that can be identified by repeats of already-discovered "keys".
var formInput = "Product: NF900XC, Quantity: 3 Product: NF900, Quantity: 2 Product: N1930CB, Quantity: 2 Product: N2120, Quantity: 1 Product: NLPCR200, Quantity: 2 Product: N272, Quantity: 2";
snippet.log(
JSON.stringify(
kvStringToArray(formInput)));
/**
* Convert a given string of key:value pairs into a two-dimensional
* array, with keys as headers, and "items" as rows.
* From: http://stackoverflow.com/a/34847199/1677912
*
* #param {string} str A string containing key:value pairs.
*
* #returns {string[][]} A two-dimensional array of strings.
*/
function kvStringToArray( str ) {
var re = /\s*(\w*?)\s*:\s*([^,\s]*)/g, // See https://regex101.com/r/kM7gY1/1
arr, // array to capture key:value pairs
data = [], // array to return table
row = [], // array for building table rows
headers = []; // array of unique keys, for table header
// Use a RegEx to identify individual key:value pairs
while ((arr = re.exec(str)) !== null) {
var key = arr[1]; // $1 matches key
var value = arr[2]; // $2 matches value
// Check if we should start a new row
if (headers.indexOf(key) == 0) {
data.push(row);
row = [];
}
// Save this value in row
row.push(value);
// If this is the first time we've seen this key, add it to headers.
if (headers.indexOf(key) == -1) headers.push(key);
}
data.push(row); // save last row
data.unshift(headers); // add headers
return data;
}
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Related
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)
}
}
var String = "One,Three,Eight";
var temp = String.split(",");
var test = (temp[1][0]);
Browser.msgBox(''+test+'', Browser.Buttons.OK_CANCEL);
I've searched high and wide for why this won't return:
"One" as (temp[0][0])
"Three" as (temp[1][0])
"Eight" as (temp[2][0])
They are only returning O, T and E respectively in the message box.
I want each word to be its own array index, is it something to do with a limit?
Thanks
You can test the code by using Logger.log().
function sandbox() {
var String = "One,Three,Eight";
var temp = String.split(",");
Logger.log(temp);
}
This will show you the following value for temp: [One, Three, Eight]
If you log the elements of the array:
temp[0] = 'One'
temp[1] = 'Three'
temp[2] = 'Eight'
If you use temp[1][0] you get the first letter from 'Three'.
Reference:
String.prototype.split()
Accessing array elements
String: Character Access
I would like to store each row of data as a separate item in an array. This is the code I have so far:
function data() {
var responses = ss.getSheetByName('Form Responses 263')
var data = responses.getRange(2, 2, responses.getLastRow()-1, responses.getLastColumn()-1).getValues()
var n = []
data.forEach(function(row) {
n.push(row)
Logger.log(row)
Logger.log(n)
})
}
At the moment, this prints each rows contents to the Logger. How would I go about storing each row's data into var n? I have used:
n.push(row)
but this just adds everything from the row, not just the data.
Source row
I would like my array to look like this:
[1 3 4],[2 3 4], [2 4 4 6]
This was the final solution to the issue as answered by #Tanaike in comments:
function data() {
var fr = ...
var data = fr.getRange(2, 2, fr.getLastRow()-1, fr.getLastColumn())
.getValues().map(function(row) {
return row.filter(String)
})
Logger.log(data)
}
It uses the String class constructor as a predicate to remove empty indexes from the source rows.
I'm looking for some help. I am trying to grab an author's publications from PubMed and populate the data into Google Sheets using Apps Script. I've gotten as far as the code below and am now stuck.
Basically, what I have done was first pull all the Pubmed IDs from a particular author whose name comes from the name of the sheet. Then I have tried creating a loop to go through each Pubmed ID JSON summary and pull each field I want. I have been able to pull the pub date. I had set it up with the idea that I would do a loop for each field of that PMID I want, store it in an array, and then return it to my sheet. However, I'm now stuck trying to get the second field - title - and all the subsequent fields (e.g. authors, last author, first author, etc.)
Any help would be greatly appreciated.
function IMPORTPMID(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var author = sheet.getSheetName();
var url = ("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=" + author + "[author]&retmode=json&retmax=1000");
var response = UrlFetchApp.fetch(url);
var AllAuthorPMID = JSON.parse(response.getContentText());
var xpath = "esearchresult/idlist";
var patharray = xpath.split("/");
for (var i = 0; i < patharray.length; i++) {
AllAuthorPMID = AllAuthorPMID[patharray[i]];
}
var PMID = AllAuthorPMID;
var PDparsearray = [PMID.length];
var titleparsearray = [PMID.length];
for (var x = 0; x < PMID.length; x++) {
var urlsum = ("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=" + PMID[x]);
var ressum = UrlFetchApp.fetch(urlsum);
var contentsum = ressum.getContentText();
var jsonsum = JSON.parse(contentsum);
var PDpath = "result/" + PMID[x] + "/pubdate";
var titlepath = "result/" + PMID[x] + "/title";
var PDpatharray = PDpath.split("/");
var titlepatharray = titlepath.split("/");
for (var j = 0; j < PDpatharray.length; j++) {
var jsonsum = jsonsum[PDpatharray[j]];
}
PDparsearray[x] = jsonsum;
}
var tempArr = [];
for (var obj in AllAuthorPMID) {
tempArr.push([obj, AllAuthorPMID[obj], PDparsearray[obj]]);
}
return tempArr;
}
From a PubMed JSON response for a given PubMed ID, you should be able to determine the fieldnames (and paths to them) that you want to include in your summary report. Reading them all is simpler to implement if they are all at the same level, but if some are properties of a sub-field, you can still access them if you give the right path in your setup.
Consider the "source JSON":
[
{ "pubMedId": "1234",
"name": "Jay Sahn",
"publications": [
{ "pubId": "abcd",
"issn": "A1B2C3",
"title": "Dynamic JSON Parsing: A Journey into Madness",
"authors": [
{ "pubMedId": "1234" },
{ "pubMedId": "2345" }
]
},
{ "pubId": "efgh",
...
},
...
],
...
},
...
]
The pubId and issn fields would be at the same level, while the publications and authors would not.
You can retrieve both the pubMedId and publications fields (and others you desire) in the same loop by either 1) hard-coding the field access, or 2) writing code that parses a field path and supplying field paths.
Option 1 is likely to be faster, but much less flexible if you suddenly want to get a new field, since you have to remember how to write the code to access that field, along with where to insert it, etc. God save you if the API changes.
Option 2 is harder to get right, but once right, will (should) work for any field you (properly) specify. Getting a new field is as easy as writing the path to it in the relevant config variable. There are possibly libraries that will do this for you.
To convert the above into spreadsheet rows (one per pubMedId in the outer array, e.g. the IDs you queried their API for), consider this example code:
function foo() {
const sheet = /* get a sheet reference somehow */;
const resp = UrlFetchApp.fetch(...).getContentText();
const data = JSON.parse(resp);
// paths relative to the outermost field, which for the imaginary source is an array of "author" objects
const fields = ['pubMedId', 'name', 'publications/pubId', 'publications/title', 'publications/authors/pubMedId'];
const output = data.map(function (author) {
var row = fields.map(function (f) {
var desiredField = f.split('/').reduce(delve_, author);
return JSON.stringify(desiredField);
});
return row;
});
sheet.getRange(1, 1, output.length, output[0].length).setValues(output);
}
function delve_(parentObj, property, i, fullPath) {
// Dive into the given object to get the path. If the parent is an array, access its elements.
if (parentObj === undefined)
return;
// Simple case: parentObj is an Object, and property exists.
const child = parentObj[property];
if (child)
return child;
// Not a direct property / index, so perhaps a property on an object in an Array.
if (parentObj.constructor === Array)
return collate_(parentObj, fullPath.splice(i));
console.warn({message: "Unhandled case / missing property",
args: {parent: parentObj, prop: property, index: i, pathArray: fullPath}});
return; // property didn't exist, user error.
}
function collate_(arr, fields) {
// Obtain the given property from all elements of the array.
const results = arr.map(function (element) {
return fields.slice().reduce(delve_, element);
});
return results;
}
Executing this yields the following output in Stackdriver:
Obviously you probably want some different (aka real) fields, and probably have other ideas for how to report them, so I leave that portion up to the reader.
Anyone with improvements to the above is welcome to submit a PR.
Recommended Reading:
Array#reduce
Array#map
Array#splice
Array#slice
Internet references on parsing nested JSON. There are a lot.
I have an array, the contents of which are a subset of a list of names that come from a checkbox question in a google form. I need to email the people whose names are in the array, I suppose from a hard coded list (multi-dim array?). I cannot figure out how to perform the search/comparisons/whatever. Apparently I am supposes to use an object literal as in the code below:
var formNames = ["Name One", "Name Three"]; // one possibility for example
var objectMatchingNamesToEmails{
"Name One":"nameone#work.com",
"Name Two":"nametwo#work.com",
"Name Three":"namethree#work.com",
};
You could loop through the array:
var arrayOfEmails,arrayOfNames,L,thisEmail,thisName;
arrayOfNames = ["NameOne","NameTwo"];
arrayOfEmails = [];
L = arrayOfNames.length;//The number of names in the array
for (var i = 0;i<L;i++) {
thisName = arrayOfNames[i];
thisEmail = objectMatchingNamesToEmails[thisName];
arrayOfEmails.push(thisEmail);
};
Create an object literal:
var objectMatchingNamesToEmails;
objectMatchingNamesToEmails = {
"NameOne":"exampleOne#gmail.com",
"NameTwo":"exampleTwo#gmail.com",
"NameThree":"exampleThree#gmail.com",
};
Then after you get the name, the code can look up the correct email:
var userName,userEmail;
userName = code here to get user name;
userEmail = objectMatchingNamesToEmails[userName];
MailApp.sendEmail(userEmail,subject,body);