ES6 Set - How to iterate to previous value? - ecmascript-6

In ES6's Set next Property is available like
var mySet = new Set();
mySet.add("foobar");
mySet.add(1);
mySet.add("baz");
var setIter = mySet.entries();
console.log(setIter.next().value); // ["foobar", "foobar"]
How to navigate to the previous property, Is something like below is available?
console.log(setIter.previous().value);

Iterators do not have a previous function. You can however iterate using for..of instead and keep track of the previous value:
var mySet = new Set();
mySet.add("foobar");
mySet.add(1);
mySet.add("baz");
let previous;
for(const current of mySet.entries()) {
console.log(previous, current);
previous = current;
}
Then you can wrap in a function which returns a generator like so:
var mySet = new Set();
mySet.add("foobar");
mySet.add(1);
mySet.add("baz");
const previousNextIterator = theSet => {
return {
previous: undefined,
*[Symbol.iterator]() {
for(const e of theSet.entries()) {
yield e;
this.previous = e;
}
}
};
};
const iter = previousNextIterator(mySet);
for(const current of iter) {
console.log(current, iter.previous);
}

Related

Google-Apps-Script Convert XML to JSON

I've got an XML string, like this:
'<ALEXA VER="0.9" URL="davidwalsh.name/" HOME="0" AID="="><SD TITLE="A" FLAGS="" HOST="davidwalsh.name"><TITLE TEXT="David Walsh Blog :: PHP, MySQL, CSS, Javascript, MooTools, and Everything Else"/><LINKSIN NUM="1102"/><SPEED TEXT="1421" PCT="51"/></SD><SD><POPULARITY URL="davidwalsh.name/" TEXT="7131"/><REACH RANK="5952"/><RANK DELTA="-1648"/></SD></ALEXA>'
I'd like to convert it into JSON format:
{
"ALEXA":{
"#attributes":{
"VER":"0.9",
"URL":"davidwalsh.name/",
"HOME":"0",
"AID":"="
},
"SD":[
{
"#attributes":{
"TITLE":"A",
"FLAGS":"",
"HOST":"davidwalsh.name"
},
"TITLE":{
"#attributes":{
"TEXT":"David Walsh Blog :: PHP, MySQL, CSS, Javascript, MooTools, and Everything Else"
}
...
I've found lot's of solutions for js, but none of them worked in google-apps-script.
I've also seen this question:
Parsing XML on a Google Apps script
but it does not exactly my case: I'de like to parse any XML into JSON, not just the provided sample.
I've found own solution (in the answer), and not sure it matches all cases.
I thought the solution should be a recursion function. After some research, I've found this great code by David Walsh and was able to adopt it. Here's what I've come to:
// Changes XML to JSON
// Original code: https://davidwalsh.name/convert-xml-json
function xmlToJson_(xml) {
// Create the return object
var obj = {};
// get type
var type = '';
try { type = xml.getType(); } catch(e){}
if (type == 'ELEMENT') {
// do attributes
var attributes = xml.getAttributes();
if (attributes.length > 0) {
obj["#attributes"] = {};
for (var j = 0; j < attributes.length; j++) {
var attribute = attributes[j];
obj["#attributes"][attribute.getName()] = attribute.getValue();
}
}
} else if (type == 'TEXT') {
obj = xml.getValue();
}
// get children
var elements = [];
try { elements = xml.getAllContent(); } catch(e){}
// do children
if (elements.length > 0) {
for(var i = 0; i < elements.length; i++) {
var item = elements[i];
var nodeName = false;
try { nodeName = item.getName(); } catch(e){}
if (nodeName)
{
if (typeof(obj[nodeName]) == "undefined") {
obj[nodeName] = xmlToJson_(item);
} else {
if (typeof(obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson_(item));
}
}
}
}
return obj;
};
I've posted the sample on GitHub.
Usage:
var xml = XmlService.parse(xmltext);
Logger.log(JSON.stringify(xmlToJson_(xml)));
Reference:
XmlService
The original answer didn't work for me. There may have been a change in the apps script XML API but it wouldn't include the text content of a node without children. Here is the code I wrote that seems to work well.
Note, it outputs in a slightly different fashion than the example you provided. I found that this might be a more consistent format for a broader range of use cases. I also found that including the attributes wasn't necessary for everything I was doing and created clutter, so I've included a version that doesn't parse attributes.
If you include attributes, the output follows this pattern:
{foo:{attributes:{...},content:{...}}
To Include Attributes:
function xmlParse(element) {
/*
* Takes an XML element and returns an object containing its children or text
* If children are present, recursively calls xmlTest() on them
*
* If multiple children share a name, they are added as objects in an array
* If children have unique names, they are simply added as keys
* i.e.
* <foo><bar>one</bar><baz>two</baz></foo> === {foo: {bar: 'one', baz: 'two'}}
* <foo><bar>one</bar><bar>two</bar></foo> === {foo: [{bar: 'one'},{bar: 'two'}]}
*/
let obj = {}
const rootName = element.getName();
// Parse attributes
const attributes = element.getAttributes();
const attributesObj = {};
for(const attribute of attributes) {
attributesObj[attribute.getName()] = attribute.getValue();
}
obj[rootName] = {
attributes: attributesObj,
content: {}
}
const children = element.getChildren();
const childNames = children.map(child => child.getName());
if (children.length === 0) {
// Base case - get text content if no children
obj = {
content: element.getText(),
attributes: attributesObj
}
} else if (new Set(childNames).size !== childNames.length) {
// If nonunique child names, add children as an array
obj[rootName].content = [];
for (const child of children) {
if (child.getChildren().length === 0) {
const childObj = {};
childObj[child.getName()] = xmlParse(child);
obj[rootName].content.push(childObj)
} else {
const childObj = xmlParse(child);
obj[rootName].content.push(childObj)
}
}
} else {
// If unique child names, add children as keys
obj[rootName].content = {};
for (const child of children) {
if (child.getChildren().length === 0) {
obj[rootName].content[child.getName()] = xmlParse(child);
} else {
obj[rootName].content = xmlParse(child);
}
}
}
return obj;
}
Without Attributes:
function xmlParse(element) {
/*
* Takes an XML element and returns an object containing its children or text
* If children are present, recursively calls xmlTest() on them
*
* If multiple children share a name, they are added as objects in an array
* If children have unique names, they are simply added as keys
* i.e.
* <foo><bar>one</bar><baz>two</baz></foo> === {foo: {bar: 'one', baz: 'two'}}
* <foo><bar>one</bar><bar>two</bar></foo> === {foo: [{bar: 'one'},{bar: 'two'}]}
*/
let obj = {}
const rootName = element.getName();
const children = element.getChildren();
const childNames = children.map(child => child.getName());
if (children.length === 0) {
// Base case - get text content if no children
obj = element.getText();
} else if (new Set(childNames).size !== childNames.length) {
// If nonunique child names, add children as an array
obj[rootName] = [];
for (const child of children) {
if (child.getChildren().length === 0) {
const childObj = {};
childObj[child.getName()] = xmlParse(child);
obj[rootName].push(childObj)
} else {
const childObj = xmlParse(child);
obj[rootName].push(childObj)
}
}
} else {
// If unique child names, add children as keys
obj[rootName] = {};
for (const child of children) {
if (child.getChildren().length === 0) {
obj[rootName][child.getName()] = xmlParse(child);
} else {
obj[rootName] = xmlParse(child);
}
}
}
return obj;
}
Usage for both of these:
const xml = XmlService.parse(xmlText);
const rootElement = xml.getRootElement();
const obj = xmlParse(rootElement);
const asJson = JSON.stringify(obj);
Reference:
XMLService

Angular 2 - Parsing Excel worksheet to Json

I have an Excel file with the following content:
Inside my component.ts, I extract the Excel's content as follow:
var testUrl= "excel.xlsx";
var oReq = new XMLHttpRequest();
oReq.open("GET", testUrl, true);
oReq.responseType = "arraybuffer";
oReq.onload = function(e) {
var arraybuffer = oReq.response;
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for(var i = 0; i != data.length; ++i){
arr[i] = String.fromCharCode(data[i]);
}
var bstr = arr.join("");
var workbook = XLSX.read(bstr, {type:"binary"});
var first_sheet_name = workbook.SheetNames[0];
var worksheet = workbook.Sheets[first_sheet_name];
var json = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], {header:1, raw:true});
var jsonOut = JSON.stringify(json);
console.log("test"+jsonOut);
}
oReq.onerror = function(e) {
console.log(e);
}
oReq.send();
XLSX.utils.sheet_to_json will format JSON as follow:
However, I would like the JSON to be as follow:
Most probably I would need to manually create the JSON, but can anyone help me point to the direction on how I can accomplish this?
In your case we need to modify the JSON data by looping over XLSX.utils.sheet_to_json JSON object:
// This object will contain the data in the format we want
var finalObj = { "object": []};
// Variables to track where to insert the data
var locIndex, firstCondIndex, secondCondIndex,
lockey, firstCondKey, secondCondkey;
// We need to initialize all indexes to -1 so that on first time we can get 0, as arrays start with 0 in javascript
locIndex = -1;
// here obj is XLSX.utils.sheet_to_json
obj.object.map((value, index) => {
// we don't want to consider name of columns which is first element of array
if(!index) return;
// Go inside only if not null
if(value[0]) {
// For Location
finalObj.object.push(createObj(value[0]));
locIndex++;
// We also need to store key names to push it's children
lockey = value[0];
firstCondIndex = -1;
}
if(value[1]) {
// For First Condition
finalObj.object[locIndex][lockey].push(createObj(value[1]));
firstCondIndex++;
firstCondKey = value[1];
secondCondIndex = -1;
}
if(value[2]) {
// For Second Condition
finalObj.object[locIndex][lockey][firstCondIndex][firstCondKey].push(createObj(value[2]));
secondCondIndex++;
secondCondkey = value[2];
}
if(value[3]) {
// For Products
// We just push the string
finalObj.object[locIndex][lockey][firstCondIndex][firstCondKey][secondCondIndex][secondCondkey].push(value[3]);
}
});
function createObj(val) {
// We need to initialize blank array so we can push the children of that element later on
var obj = {};
obj[val] = [];
return obj;
}
console.log(finalObj);

How to get nested deep property value from JSON where key is in a variable?

I want to bind my ng-model with JSON object nested key where my key is in a variable.
var data = {"course":{"sections":{"chapter_index":5}}};
var key = "course['sections']['chapter_index']"
Here I want to get value 5 from data JSON object.
I found the solution to convert "course.sections.chapter_index" to array notation like course['sections']['chapter_index'] this. but don't know how to extract value from data now
<script type="text/javascript">
var BRACKET_REGEXP = /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/;
var APOS_REGEXP = /'/g;
var DOT_REGEXP = /\./g;
var FUNC_REGEXP = /(\([^)]*\))?$/;
var preEval = function (path) {
var m = BRACKET_REGEXP.exec(path);
if (m) {
return (m[1] ? preEval(m[1]) : m[1]) + m[2] + (m[3] ? preEval(m[3]) : m[3]);
} else {
path = path.replace(APOS_REGEXP, '\\\'');
var parts = path.split(DOT_REGEXP);
var preparsed = [parts.shift()]; // first item must be var notation, thus skip
angular.forEach(parts, function (part) {
preparsed.push(part.replace(FUNC_REGEXP, '\']$1'));
});
return preparsed.join('[\'');
}
};
var data = {"course":{"sections":{"chapter_index":5}}};
var obj = preEval('course.sections.chapter_index');
console.log(obj);
</script>
Hope this also help others. I am near to close the solution,but don't know how can I get nested value from JSON.
This may be a good solution too
getDeepnestedValue(object: any, keys: string[]) {
keys.forEach((key: string) => {
object = object[key];
});
return object;
}
var jsonObject = {"address": {"line": {"line1": "","line2": ""}}};
var modelName = "address.line.line1";
var result = getDescendantPropValue(jsonObject, modelName);
function getDescendantPropValue(obj, modelName) {
console.log("modelName " + modelName);
var arr = modelName.split(".");
var val = obj;
for (var i = 0; i < arr.length; i++) {
val = val[arr[i]];
}
console.log("Val values final : " + JSON.stringify(val));
return val;
}
You are trying to combine 'dot notation' and 'bracket notation' to access properties in an object, which is generally not a good idea.
Source: "The Secret Life of Objects"
Here is an alternative.
var stringInput = 'course.sections.chapter_index'
var splitInput = stringInput.split(".")
data[splitInput[1]]][splitInput[2]][splitInput[3]] //5
//OR: Note that if you can construct the right string, you can also do this:
eval("data[splitInput[1]]][splitInput[2]][splitInput[3]]")
Essentially, if you use eval on a string, it'll evaluate a statement.
Now you just need to create the right string! You could use the above method, or tweak your current implementation and simply go
eval("data.course.sections.chapter_index") //5
Source MDN Eval docs.
var data = {
"course": {
"sections": {
"chapter_index": 5
}
}
};
var key = "course['sections']['chapter_index']";
var keys = key.replace(/'|]/g, '').split('[');
for (var i = 0; i < keys.length; i++) {
data = data[keys[i]];
}
console.log(data);
The simplest possible solution that will do what you want:
var data = {"course":{"sections":{"chapter_index":5}}};
var key = "course['sections']['chapter_index']";
with (data) {
var value = eval(key);
}
console.log(value);
//=> 5
Note that you should make sure key comes from a trusted source since it is eval'd.
Using with or eval is considered dangerous, and for a good reason, but this may be one of a few its legitimate use cases.
If you don't want to use eval you can do a one liner reduce:
var data = {"course":{"sections":{"chapter_index":5}}};
var key = "course['sections']['chapter_index']"
key.split(/"|'|\]|\.|\[/).reduce((s,c)=>c===""?s:s&&s[c], data)

How to pass a whole dojox.grid.DataGrid store(items json data) to servlet?

I have a button on page - when clicked, it passes all the data to the servlet that could update each row data. My question is how to pass the whole store to the servlet as json data? Is there any easy way? Thanks
Here is some code I wrote to get the store to an object. Then it can be converted to JSON using dojo.toJson(obj);. I learned about this from the dojotoolkit website originally. (Give credit where credit is due). I realize this code is huge and nasty. When I looked for a better way about a year back I could not find one.
JsonHelper.storeToObject = function(store) {
var object = [];
var index = -1;
store.fetch({
onItem : function(item, request) {
object[++index] = JsonHelper.itemToObject(store, item);
}
});
return object;
};
JsonHelper.itemToObject = function(store, item) {
// store:
// The datastore the item came from.
// item:
// The item in question.
var obj = {};
if (item && store) {
// Determine the attributes we need to process.
var attributes = store.getAttributes(item);
if (attributes && attributes.length > 0) {
var i;
for (i = 0; i < attributes.length; i++) {
var values = store.getValues(item, attributes[i]);
if (values) {
// Handle multivalued and single-valued attributes.
if (values.length > 1) {
var j;
obj[attributes[i]] = [];
for (j = 0; j < values.length; j++) {
var value = values[j];
// Check that the value isn't another item. If
// it is, process it as an item.
if (store.isItem(value)) {
obj[attributes[i]].push(itemToObject(store,
value));
} else {
obj[attributes[i]].push(value);
}
}
} else {
if (store.isItem(values[0])) {
obj[attributes[i]] = itemToObject(store,
values[0]);
} else {
obj[attributes[i]] = values[0];
}
}
}
}
}
}
return obj;
};

find the matching items given in an array

i don't understand arrays in functions, but how do you find the matching items given in an array? for example:
var fruit:Array = ["apples", "oranges", "grapes", "oranges", "apples", "grapes"];
how can i get it to show only the number of apples in the array?
Simply, you can do this:
private function getCount(fruitArray:Array, fruitName:String):int {
var count:int=0;
for (var i:int=0; i<fruitArray.length; i++) {
if(fruitArray[i].toLowerCase()==fruitName.toLowerCase()) {
count++;
}
}
return count;
}
var fruit:Array = ["apples", "oranges", "grapes", "oranges", "apples", "grapes"];
var appleCount=getCount(fruit, "apples"); //returns 2
var grapeCount=getCount(fruit, "grapes"); //returns 2
var orangeCount=getCount(fruit, "oranges"); //returns 2
Depending on how many items are in the array, and how often you want to search for different items, it can get very costly to have a for loop iterate over the entire array, every time you want to count things (which would happen if you did what the other two answers suggest). You might want to have a count function that iterates over the array just once, and returns a list of counters, instead of a single number:
function countItems( arr:Array ) : Object {
var counts : Object = {};
for each ( var item : String in arr ) {
if (counts.hasOwnProperty(item)) counts[item]++;
else counts[item] = 1;
}
return counts;
}
var fruit:Array = ["apples", "oranges", "grapes", "oranges", "apples", "grapes"];
var counts : Object = countItems(fruit);
trace (counts["apples"]); // => 2
trace (counts["oranges"]); // => 2
trace (counts["grapes"]); // => 2
function countOccurrences( haystack, needle ){
var index : int = -1;
var counter : int = -1;
do{
counter++;
index = haystack.indexOf( needle, index + 1 );
}while( index >= 0 );
return counter;
}
var fruit:Array = ["apples", "oranges", "grapes", "oranges", "apples", "grapes"];
trace( countOccurrences( fruit, "apples" ) ); //outputs '2'
var stringToSearch:String = "apples";
var pattern:RegExp = new RegExp(/\b\b/.source+stringToSearch, "g");
var matches:Array = fruit.toString().match(pattern);
//
trace(matches.length);
Disclaimer: don't use this if you have many elements in your array (eg. fruit.length>1000): it is compact but it can be slower than any other solution posted here. Another thing to point out is that here I'm assuming your "matching items" are "strings".
Try something like
private function makeFilter(filterValue:String):Function {
var fn:Function = function (obj:Object, index:int=0, arr:Array=[]):Boolean {
if (String(obj)==filterValue) {
return true;
}
return false;
}
return fn;
}
//somewhere else:
var filterFunction:Function = makeFilter('apples');
var appleCount:int = fruit.filter(filterFunction).length;
FYI, more about this approach: http://www.gasi.ch/blog/functional-actionscript-1/
var matches = 0
var arrayMatcher = (array1, array2) => {
for (let i = 0; i <= array1 - 1; i++) {
var myFirstItem = array1[i];
for (a = 0; a <= array2.length - 1; a++) {
var mySecondItem = array2[a];
if (mySecondItem === myFirstItem) {
matches += 1;
}
}
}
return matches;
}
I used this to find matches in a list of a patient's symptoms with a list of common symptoms for a certain disease and it worked for me. You could give it a try! :)