Copy link to clipboard
Copied
Hello Scripters,
I have a recursive function that touches every element in a structured FrameMaker file. I am trying to find out how to get text nodes as I process the elements.
#target framemaker
var doc = app.ActiveDoc;
var element = doc.MainFlowInDoc.HighestLevelElement;
processElements (element);
function processElements (element) {
if (element.ElementDef.ObjectValid()) {
$.writeln (element.ElementDef.Name);
}
else {
// I thought I could get text nodes here, but it never gets here.
}
var element2 = element.FirstChildElement;
while(element2.ObjectValid()) {
processElements (element2);
element2 = element2.NextSiblingElement;
}
}
I expected that I would get the text nodes on line 14, but it the else statement never gets there. In FrameScript, if an element has no ElementDef, then it is a text node. I am not sure about the FDK. Any ideas how I can pick up the text nodes in a function like this? Thanks.
-Rick
Copy link to clipboard
Copied
Hi Rick,
Here is how I test for a text node. I don't remember how I came to this methodology, but it seems to be reliable.
Russ
function elem_IsTextNode(elem)
{
if(elem.ElementDef.Name == null || elem.ElementDef.Name == "") return true;
else return false;
}
Copy link to clipboard
Copied
Hi Russ, Thanks for the reply. I am not sure why, but my function is still not hitting any text nodes. -Rick
#target framemaker
var doc = app.ActiveDoc;
var element = doc.MainFlowInDoc.HighestLevelElement;
processElements (element);
function processElements (element) {
if (element.ElementDef.Name != null && element.ElementDef.Name != "") {
$.writeln (element.ElementDef.Name);
}
else {
// I thought I could get text nodes here, but it never gets here.
$.writeln ("************");
}
var element2 = element.FirstChildElement;
while(element2.ObjectValid()) {
processElements (element2);
element2 = element2.NextSiblingElement;
}
}
Copy link to clipboard
Copied
Hi Rick,
Well, I didn't look much past the test for ElementDef. But with a closer analysis, I see what may be a problem, depending on the document setup... You are starting at the root element, then stepping down through all siblings on the root element branch. It would be very unusual for there to be any text nodes on a root element branch, although I guess not impossible. If you are trying to walk the entire tree out to the ends of branches, maybe try:
element2 = element2.NextElementDFS;
...instead. That will do a full tree walk and I think it will stop at text nodes.
Russ
Copy link to clipboard
Copied
Yes, I want to start at the root element and traverse the entire tree, including text nodes. I tried the following code, but it never writes null or a blank string to the JavaScript console, like I would expect for text nodes. It looks like the ExtendScript API skips text nodes unless I am missing something.
#target framemaker
var doc = app.ActiveDoc;
var element = doc.MainFlowInDoc.HighestLevelElement;
while (element.ObjectValid ()) {
$.writeln (element.ElementDef.Name);
element = element.NextElementDFS;
}
Copy link to clipboard
Copied
Well, I guess I was wrong. NextElementDFS does not stop at text nodes. That is unfortunate.
I use my own custom DFS walker for moving through a tree. I've always done it this way, both with the FDK and ExtendScript. Below is a "class" that I use for this. It is a bit complicated to digest and I don't know if it will be helpful to you. Note that you'll see in there:
- calls to an ov() function, which is just a little shortcut I use to test object validity.
function ov(obj)
{
if(obj == null) return false;
else return obj.ObjectValid();
}
- a call to an ElementDefIsText() method for an ElementDef object, to see if the element is a text node. I had forgotten about that handy little thing.
You would create an "object" something like this:
var stepper = new ElementStepper(doc.MainFlowInDoc.HighestLevelElement, false, true);
...then you can get the next step with:
elem = stepper.step();
The first call will return the original start element, then it starts walking with step(). I think it can return null, so a loop might look something like:
while(stepper.step() != null && stepper.step().ObjectValid())
{
elem = stepper.getCurrentStep();
if(elem.ElementDef.ElementDefIsText()) alert("text node!");
}
I'm out of time here and just guessing at a few things. I don't know if this will help you or not. It's just the way I've always done it, without any problems ever.
Russ
// Ye olde element stepper "class".
// First step always returns the startElem
// Return backwards steps will not return the startElem the last time.
function ElementStepper(startElem, returnBackwardSteps, returnTextNodes)
{
this.startElem = null;
this.returnTextNodes = returnTextNodes;
this.currentElem = null;
this.lastStepDir = "step_child";
this.isFirstStep = true;
if(startElem != null && !startElem.ObjectValid())
{
this.objectIsValid = false;
}
else
{
this.startElem = startElem;
this.currentElem = startElem;
this.objectIsValid = true;
}
this.objectValid = function()
{
return this.objectIsValid;
}
this.step = function()
{
if(!this.objectValid()) return null;
if(!ov(this.currentElem)) return null;
var tempElem = null;
var keepGoing = true;
while(keepGoing && ov(this.currentElem))
{
tempElem = null;
keepGoing = false;
//On the first step, we always return the starting element,
//set as the current element
if(this.isFirstStep)
{
this.isFirstStep = false;
return this.currentElem;
}
//we'll handle the case of a starting element with no children
//here. If it is not the first step and the starting element has no
//children, we're done.
if(!this.firstStep && !ov(this.startElem.FirstChildElement))
{
this.currentElem = null;
return null;
}
//now, actual stepping.
//If we did not step to a parent last time,
//we always test for a child
if(this.lastStepDir != "step_parent")
{
tempElem = this.currentElem.FirstChildElement;
this.lastStepDir = "step_child";
}
//if no joy, try a sibling
if(!ov(tempElem))
{
tempElem = this.currentElem.NextSiblingElement;
this.lastStepDir = "step_sibling";
}
//if still no joy, try a parent
if(!ov(tempElem))
{
tempElem = this.currentElem.ParentElement;
this.lastStepDir = "step_parent";
}
//now, let''s see what we got
//if we got nothing, we must have stepped off the root.
//Nothing to do then.
if(!ov(tempElem)) this.currentElem = null;
//if we've reached our starting element, we are done.
else if(tempElem.id == this.startElem.id) this.currentElem = null;
//otherwise, time to make some decisions
else
{
//if we stepped to the parent and we don't allow that, just keep going.
if(!returnBackwardSteps && this.lastStepDir == "step_parent")
keepGoing = true;
//if we stepped to a text node and don't allow that, just keep going.
if(!returnTextNodes && tempElem.ElementDef.ElementDefIsText())
keepGoing = true;
//otherwise, we are someplace to stop. Just set the current element and let the loop end.
this.currentElem = tempElem;
}
} //end main while
return this.currentElem;
}
this.reset = function()
{
if(this.objectIsValid)
{
this.currentElem = this.startElem;
this.firstStep = true;
this.lastStepDir = "step_child";
}
}
this.getCurrentStep = function()
{
return this.currentElem;
}
}
Copy link to clipboard
Copied
Hi Russ, Thanks for your generous help. It appears that the key method is ElementDefIsText. I still find recursion to be easier, so here is how I reworked my original function. Thanks for getting my brain cells working. -Rick
#target framemaker
var doc = app.ActiveDoc;
var element = doc.MainFlowInDoc.HighestLevelElement;
processElements (element);
function processElements (element, doc) {
var text;
if (!element.ElementDef.ElementDefIsText ()) {
$.writeln ("<" + element.ElementDef.Name + ">");
// If we are at the end of a branch, get the text.
if (!element.FirstChildElement.ObjectValid ()) {
text = getText (element, doc);
if (text !== "") {
$.writeln (getText (element, doc));
}
}
}
else {
// Text node in mixed content.
$.writeln (getText (element, doc));
}
var element2 = element.FirstChildElement;
while(element2.ObjectValid()) {
processElements (element2, doc);
element2 = element2.NextSiblingElement;
}
}
function getText (textObj, doc) {
// Gets the text from the text object.
var text = "";
// Get a list of the strings in the text object or text range.
if (textObj.constructor.name !== "TextRange") {
var textItems = textObj.GetText(Constants.FTI_String);
} else {
var textItems = doc.GetTextForRange(textObj, Constants.FTI_String);
}
// Concatenate the strings.
for (var i = 0; i < textItems.len; i += 1) {
text += (textItems.sdata);
}
return text; // Return the text
}