0

So my question is basically this, how can I return an array of all the leaf nodes of an html document using a javascript function. I need to perform operations on that list later. for example if I have html:

<body>
   <div>
      <div>
         <p id="para1"></p>
      </div>
   </div>
   <p id="para2"></p>
</body>

then the function should return a array containing the two nodes with id para1 and para2.

Note: I want the nodes themselves not their id. Although given the id i can extract the nodes so its not a big issue.

imtheman
  • 4,713
  • 1
  • 30
  • 30
user3309362
  • 3
  • 1
  • 3
  • What is it exactly you are having problems with? I assume you know how to call a function. – Felix Kling Mar 09 '14 at 23:01
  • I know i must use some sort of recursion but how to get an array updated in that recursive function such that it only contains the leaf nodes upon its final return – user3309362 Mar 09 '14 at 23:03

4 Answers4

3

Here's a simple function to get leafNodes where you look at all nodes, including text nodes (this means it won't ever return an element that contains text nodes):

function getLeafNodes(master) {
    var nodes = Array.prototype.slice.call(master.getElementsByTagName("*"), 0);
    var leafNodes = nodes.filter(function(elem) {
        return !elem.hasChildNodes();
    });
    return leafNodes;
}

Working demo: http://jsfiddle.net/jfriend00/e9D5n/

FYI, the .filter() method requires IE9. If you want to use this method with earlier versions of IE, you can install a polyfill for .filter() or change to a manual iteration of the array.


And, here's a version if you don't want to consider text nodes, so you're looking for the leaf elements, even if they have text nodes in them:

function getLeafNodes(master) {
    var nodes = Array.prototype.slice.call(master.getElementsByTagName("*"), 0);
    var leafNodes = nodes.filter(function(elem) {
        if (elem.hasChildNodes()) {
            // see if any of the child nodes are elements
            for (var i = 0; i < elem.childNodes.length; i++) {
                if (elem.childNodes[i].nodeType == 1) {
                    // there is a child element, so return false to not include
                    // this parent element
                    return false;
                }
            }
        }
        return true;
    });
    return leafNodes;
}

Working demo: http://jsfiddle.net/jfriend00/xu7rv/


And, here's a recursive solution that ignores text nodes:

function getLeafNodes(master) {
    var results = [];
    var children = master.childNodes;
    for (var i = 0; i < children.length; i++) {
        if (children[i].nodeType == 1) {
            var childLeafs = getLeafNodes(children[i]);
            if (childLeafs.length) {
                // if we had child leafs, then concat them onto our current results
                results = results.concat(childLeafs);
            } else {
                // if we didn't have child leafs, then this must be a leaf
                results.push(children[i]);
            }
        }
    }
    // if we didn't find any leaves at this level, then this must be a leaf
    if (!results.length) {
        results.push(master);
    }
    return results;
}

Working demo: http://jsfiddle.net/jfriend00/jNn8H/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
0

I assume by "leaf node", you mean an element with no children. In that case, you can get all elements, and then iterate through each one, check if it has any children, and if not add it to an array:

var leaves = new Array();
var els = document.body.getElementsByTagName("*");
for (var i = 0; i < els.length; i++) {
    if (els[i].children.length === 0) {
        leaves.push(els[i]);
    }
}
Alex Yuly
  • 180
  • 1
  • 9
-1

Perhaps the following will work?

var paraEl=new Array();
for(i=0;i<document.getElementsByTagName("p").length;i++){
    paraEl[i]=document.getElementsByTagName("p")[i];
}

But you know, document.getElementsByTagName("p") is already an array, right?

LeiMagnus
  • 253
  • 2
  • 13
-1

So, it seems you are wondering how to add the nodes to the array and return the array. Simply always return an array from the function and merge them:

function getLeaves(element) {
    if (element.children.length === 0) {
        return [element];
    }
    var leaves = [];
    for (var i = 0, l = element.children.length; i < l; i++) {
        leaves.push.apply(leaves, getLeaves(element.children[i]));
    }
    return leaves;
}

DEMO

Alternatively, you can pass an array down and when the element is a leave, it just adds itself to it:

function getLeaves(element, arr) {
    // create an array if none is passed. This happens in the first call.
    arr = arr || [];
    if (element.children.length === 0) {
        arr.push(element);
    }
    var leaves = [];
    for (var i = 0, l = element.children.length; i < l; i++) {
        getLeaves(element.children[i], arr);
    }
    return arr;
}

DEMO

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143