in a complex XML where I dont know the leaf node names/or the level of depth they are, how could I extract all the leaf nodes inside a XMLList variable directly?
Thanks.
Since no one has replied so far, I am assuming that there is no easy using ECMA to achieve this for a generic XML... and that leaves UDF as the only choice (the function would recursively keep checking if there are any childs left - if not then its a leaf).
Thanks Guys.
/**
* function to check for the leaf nodes and return
* an XMLListCollection of leaf nodes. Give it
* your xml and an empty object of XMLListCollection for result.
**/
private function leafNodes(x:XML, retList:XMLListCollection):void {
var xlist:XMLList;
xlist = x.children();
if (x.children().length() == 0) { // leaf node
retList.addItem(x);
return;
}
for each (var it:XML in xlist)
leafNodes(it, retList);
return;
}
Related
Is there a simple method to locate an XML node by its attribute in Google Apps Script? Here's an XML snippet:
<hd:components>
<hd:text name="ADM Custom admissions TE">
<hd:prompt>Admission</hd:prompt>
<hd:columnWidth widthType="minimum" minWidth="100"/>
</hd:text>
<hd:text name="ADM Insufficient heat end date TE">
<hd:prompt>To</hd:prompt>
</hd:text>
<hd:text name="ADM Insufficient heat start date TE">
<hd:prompt>From</hd:prompt>
</hd:text>
<hd:text name="ADM Third party payment period TE">
<hd:defMergeProps unansweredText="__________"/>
<hd:prompt>When (date or period)?</hd:prompt>
</hd:text>
For purposes of the XML file I'm trying to parse, the "name" attribute is a unique identifier, while what GAS thinks is the "name" for purposes of the XmlService.Element.getChild(name) method ("text" for each node shown in this snippet) is a non-unique classifier for the type of node. I'd like to be able to write a function to retrieve a specific node from this XML file with only the name attribute. XMLPath notation in other languages has this capability using the [# notation. Is there a way to do it in GAS, or do I need to write a function that walks through the XML until it finds a node with the right name attribute, or store it in some different type of data structure for fast searching if the XML file is sufficiently large?
Here's the snippet I started writing: it's fine if there's no built-in function, I just wondered if there was a better/faster way to do this. My function isn't so efficient, and I wondered if the XmlService had a more efficient internal data structure it's using to speed up searching. My approach is just to loop through all of the element's children until there's a match.
function getComponentFromXML(xml,name) {
for (var i = 0; i < xml.length; i++) {
var x = xml[i];
var xname = x.getAttribute('name').getValue();
if (xname == name) {
return getComponentAttributes(x);
}
}
}
There is no built-in search, so the only way is to read the list of elements looking for the one with the desired value of attribute 'name'. If elements is an array of elements to search through, you can do
var searchResults = elements.filter(function (e) {
return e.getAttribute('name') && e.getAttribute('name').getValue() == searchString;
});
(Both checks are needed to avoid an error when there is no 'name' attribute at all.)
How to obtain such an array elements may depend on XML document. If, as in your example, the elements to search are the immediate children of the root element, then
var doc = XmlService.parse(xmlString);
var elements = doc.getRootElement().getChildren();
would be a quick and easy way to do this.
In general, to get all elements without recursion, the getDescendants method can be used. It returns an array of Content object, which can be filtered down to Element objects:
var elements = doc.getDescendants().filter(function (c) {
return c.getType() == XmlService.ContentTypes.ELEMENT;
}).map(function (c) {
return c.asElement();
});
Let's assume that I have a JTree similar to the picture I provided, in which the number of leaves and nodes will vary every time I run the code. Now how could I remove the nodes that are empty (AKA have no children) since I can't check so see if its going to be empty as I add them to the tree?
I have tried using an enumeration to traverse the tree and check for every node to check how many children it has but that didn't help because even if I could tell that the node I am at is a node that needs to be removed I have to tell his parent to remove him I cant tell him to remove himself from his parent node.
What do I need to use to achieve what I am looking for?
Now how could I remove the nodes that are empty...so i want a node to either have only other nodes or only leaves but not both at the same time
Traverse the tree and check for nodes which have the following criteria
Is a leaf
Has siblings that are not leafs.
even if I could tell that the node I am at is a node that needs to be removed I have to tell his parent to remove him I cant tell him to remove himself from his parent node.
That is what the DefaultTreeModel.removeNodeFromParent() method does. So you can traverse the tree recursively and just remove nodes based upon your given criteria.
DefaultMutableTreeNode root = new DefaultMutableTreeNode ("Root");
//other code
DefaultTreeModel treeModel = new DefaultTreeModel(root);
JTree tree = new JTree(treeModel);
//populate tree
recurseTree(root, treeModel);
//method to recursively remove leaf nodes that have non-leaf siblings
private void recurseTree(MutableTreeNode node, DefaultTreeModel treeModel){
if ( node.isLeaf() ){
TreeNode parent = node.getParent();
for ( int i = 0; i < parent.getChildCount(); i++ ){
if ( !parent.getChildAt(i).isLeaf() ){
treeModel.removeNodeFromParent(node);
break;
}
}
}else{
for ( int i = 0; i < node.getChildCount(); i++ ){
recurseTree((MutableTreeNode)node.getChildAt(i), treeModel);
}
}
}
All this being said, it looks like a long route around the initial addition of those nodes. Without knowing the underlying data structure to populate the tree one can only guess as to how to go about preventing those nodes from being added in the first place.
I generally find indexOf very useful, to get an index directly, and not writing 3-4 lines of for loop to get a match.
Is there any similar function, say like indicesOf , to get an array of all possible matches ?
Or may be having a different name, but acts as a shortcut as beautifully as "indexOf" ?
As you don't mind creating a new Array, you can use the filter() function - it executes a function on each item of the array, then returns a new Array with the items that return true:
// our comparison function
function myCompFunction( element:*, index:int, array:Array ):Boolean
{
return ( element > 10 );
}
var ar:Array = [5,10,15,20];
var ar2:Array = ar.filter( myCompFunction ); // ar2 is now [15,20]
It's not exactly indicies, but then again, you don't need to dereference your objects.
NOTE: because it's calling a function on each element, looping through the array yourself will still be quicker
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Array.html#filter()
Unfortunately, there is not such shortcut. indexOf works for only one item, and there is no alternative for multiple items.
The solutions are obvious - iterate over the entire array or use some kind of sort on the array. Maybe there are other methods, but they are almost the same - loop through everything and check. Sorry to say it, but that's the way it is :)
I guess a step back is in order. My original question is at the bottom of this post for reference.
I am writing a word guessing game and wanted a way to:
1. Given a word length of 2 - 10 characters, randomly generate a valid english word to guess
2.given a 2 - 10 character guess, ensure that it is a valid english word.
I created a vector of 9 objects, one for each word length and dynamically created 172000
property/ value pairs using the words from a word list to name the properties and setting their value to true. The inner loop is:
for (i = 0; i < _WordCount[wordLength] - 2; i)
{
_WordsList[wordLength]["" + _WordsVector[wordLength][i++]] = true;
}
To validate a word , the following lookup returns true if valid:
function Validate(key:String):Boolean
{
return _WordsList[key.length - 2][key]
}
I transferred them from a vector to objects to take advantage of the hash take lookup of the properties. Haven't looked at how much memory this all takes but it's been a useful learning exercise.
I just wasn't sure how best to randomly choose a property from one of the objects. I was thinking of validating whatever method I chose by generating 1000 000 words and analyzing the statistics of the distribution.
So I suppose my question should really first be am I better off using some other approach such as keeping the lists in vectors and doing a search each time ?
Original question
Newbie first question:
I read a thread that said that traversal order in a for.. in is determined by a hash table and appears random.
I'm looking for a good way to randomly select a property in an object. Would the first element in a for .. in traversing the properties, or perhaps the random nth element in the iteration be truly random. I'd like to ensure that there is approximately an equal probability of accessing a given property. The Objects have between approximately 100 and 20000 properties. Other approaches ?
thanks.
Looking at the scenario you described in your edited question, I'd suggest using a Vector.<String> and your map object.
You can store all your keys in the vector and map them in the object, then you can select a random numeric key in the vector and use the result as a key in the map object.
To make it clear, take a look at this simple example:
var keys:Vector.<String> = new Vector.<String>();
var map:Object = { };
function add(key:String, value:*):void
{
keys.push(key);
map[key] = value;
}
function getRandom():*
{
var randomKey = keys[int(Math.random() * keys.length)];
return map[randomKey];
}
And you can use it like this:
add("a", "x");
add("b", "y");
add("c", "z");
var radomValue:* = getRandom();
Using Object instead of String
Instead of storing the strings you can store objects that have the string inside of them,
something like:
public class Word
{
public var value:String;
public var length:int;
public function Word(value:String)
{
this.value = value;
this.length = value.length;
}
}
Use this object as value instead of the string, but you need to change your map object to be a Dictionary:
var map:Dictionary = new Dictionary();
function add(key:Word, value:*):void
{
keys.push(key);
map[key] = value;
}
This way you won't duplicate every word (but will have a little class overhead).
I'm working with a large set of hierarchical taxonomic terms, where each term ("203") has a matching "term203" movie clip on the stage, and am having trouble getting a recursive function to return all of a given term's descendants.
There is a main Dictionary() object with the following nested organization for each term:
{ [object Movie Clip] : { "tid":203, "parent":99, "name":"Culture", selected:false, "otherData":"etc" } }
...where the [object Movie Clip]'s instance name would be "term203". All of these object:subObjectArray items ("terms") are stored in a master taxonomy:Dictionary() object.
I've been trying to make a recursive function (which is in itself already a little above my head) that takes the click.target of a movie clip and returns a new Dictionary() object with all of the children and grandchildren and great grandchildren (etc) of that term, in the same, nested organization described above.
The code below traces the right number of recursive loops, but the returned Dictionary() object only contains the first run's terms (only the immediate children of the requested term).
var taxonomy:Dictionary = new Dictionary();
// ...Term info is loaded into taxonomy from a JSON-style text file)
// ...MOUSE_OVER event listeners are added to each
function revealChildren(hvr:MouseEvent):void {
trace("Spotlighting " + taxonomy[hvr.target].name + "'s children...");
for(var key:Object in getAllChildren(taxonomy[hvr.target].tid)) {
trace("Animating " + taxonomy[key].tid); // Traces only immediate children
var revealTween = new Tween(key, "alpha", Regular.easeInOut, key.alpha, 1, 1, true);
}
}
function getAllChildren(origin):Dictionary {
var children:Dictionary = new Dictionary();
for(var element:Object in taxonomy) {
if(taxonomy[element].parent == origin) {
var subSet = getAllChildren(taxonomy[element].tid);
children[element] = subSet; // *CAN'T ACCESS 'subSet' PROPERLY*
trace("Parent = " + origin);
trace("Matched! Adding " + taxonomy[element].tid + " as key and setting its value to " + subSet); // Traces correct amount of times, one for each descendent
}
else {
}
}
return children;
}
I certainly do not claim to be the most efficient AS3 programmer, so I am open to alternative configurations. However, after trying static and nested Arrays, I would prefer to continue using the Dictionary() object as my main pool.
As noted, only the immediate children end up animating in the revealChildren() function. It's mystifying to me then, that in the getAllChildren() function, all of the descendants trace sequentially (well in no particular order) in the output window.
Also I can't get any sort of name or property out of the subSet Object. That could be the problem.
I've only tested it as far as 'two generations,' but it seems that only the first round of calling the function successfully adds those terms to the new Dictionary() object and returns it intact to the animating function.
Too bad dict.filter(getDescendants) won't work. Please help!
To simplify things, I've added an output parameter called children. This is the Dictionary into which our function will store its results. It has a default value, so you don't need to specify one. In that case, it will create a new instance for itself.
function getAllChildren(origin:*, children:Dictionary = null):Dictionary {
if (children = null) children = new Dictionary();
for(var element:* in taxonomy) {
if(taxonomy[element].parent == origin) {
children[element] = taxonomy[element];
getAllChildren(taxonomy[element].tid, children);
}
}
return children;
}
When a child is discovered, it is copied over exactly: children[element] = taxonomy[element];
Next, the function calls itself recursively, supplying it the same output dictionary as it has been using.
Edit:
In response to your comment... Your code originally said this after finding a child named element:
children[element] = getAllChildren(taxonomy[element].tid);
You're making children[element] equal to a Dictionary object here. What you create is a tree structure, mapping MovieClip objects to Dictionary objects containing a similar mapping of its children. Using a for in loop on this structure will only give you the top-level children. It will not recursively traverse the entire tree.