Thursday, December 22, 2011

Preorder Element Tree Traversal

Elements differ from other objects in a FrameMaker document in that they are organized not into a list but into a tree. The tree structure is exactly that you would see in the document's Structure View window.

Typically, working with elements means starting with the highest level element in the structured flow, the root element, and visiting each of the elements in the tree in a predictable order.

The algorithm described below uses a preorder traversal which visits the example element tree shown below in the order marked.



The following script traverses the element tree using a recursive algorithm. The walkTree() function starts at the root (or whatever node is passed in). It  gets that node's children starting with the first child. It is obtained using the element property FirstChildElement.

After processing that element, walkTree() calls itself this time passing in the child element. When that branch of the tree is processed, it continues on seeking any sibling elements using the element property NextSiblingElement.

NOTE: While not strictly necessary, the script keeps track of the element count to help make the association with the element numbers shown in the example above.

// Determines associated element definition name
function getElementName(elem) {
    var elemDef, name = null;

    elemDef = elem.ElementDef;
    if (elemDef.ObjectValid()) {
        name = elemDef.Name;
    }
    return name;
}

//Recursively traverses element tree in preorder fashion
function walkTree(elem, count) {
    var child, name;

    name = getElementName(elem);
    Console(count + ":  " + name);
    child = elem.FirstChildElement; //get first child if any
    while (child.ObjectValid()) {
        count = count + 1; //upate the element count to reflect element found
        count = walkTree(child, count); //traverse subtree with child root
        child = child.NextSiblingElement; //get the siblings
    }
    return count;
 }

var doc, flow, root;

doc = app.ActiveDoc;
flow = doc.MainFlowInDoc;
root = flow.HighestLevelElement;
if (root.ObjectValid()) {
    walkTree(root, 1);
}

The script output appears in the FrameMaker console and is shown here:


1 comment:

  1. Debra--

    While this post is a couple of months old now, I wanted to come back to it and say thank you. This script opened my eyes to the power of recursive functions, and formed the basis of one of my most frequently used functions (building an array of elements)

    ReplyDelete