Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Elliott Sprehn and Chris Phillips and Nathan Strutz and Anant Pradhan and Dave DeVol and Byron Raines

ColdFusion 8 CFloop Array Iteration / XML Bug?

By Ben Nadel on
Tags: ColdFusion

I think I found a bug in the way that ColdFusion 8 handles CFLoop array iteration and XML nodes. I was working with some XML over the weekend (yeah baby, I DO know how to fully take advantage of the holiday weekend!) and was doing some simple XML node iteration like this:

  • <!--- Store actresses in a ColdFusion XML document. --->
  • <cfxml variable="xmlActors">
  •  
  • <actors>
  •  
  • <actor
  • name="Sunrise Adams"
  • dob="14 September 1982"
  • />
  •  
  • <actor
  • name="Briana Banks"
  • dob="21 May 1978"
  • />
  •  
  • <actor
  • name="Belladonna"
  • dob=""
  • />
  •  
  • <actor
  • name="Tawny Roberts"
  • dob=""
  • />
  •  
  • <actor
  • name="Nina Hartley"
  • dob="11 March 1959"
  • />
  •  
  • </actors>
  •  
  • </cfxml>
  •  
  •  
  • <!--- Loop over the array using a traditional index array. --->
  • <cfloop
  • index="intNodeIndex"
  • from="1"
  • to="#ArrayLen( xmlActors.actors.actor )#"
  • step="1">
  •  
  •  
  • <!--- Get a short hand to the current actor XML node. --->
  • <cfset xmlActor = xmlActors.actors.actor[ intNodeIndex ] />
  •  
  • <!--- Output the data. --->
  • <p>
  • #xmlActor.XmlAttributes.name#
  •  
  • <!--- Check to see if date of birth is defined. --->
  • <cfif (
  • StructKeyExists( xmlActor.XmlAttributes, "dob" ) AND
  • Len( xmlActor.XmlAttributes.dob )
  • )>
  •  
  • (DOB: #xmlActor.XmlAttributes.dob#)
  •  
  • </cfif>
  • </p>
  •  
  • </cfloop>

Here, I am just building a simple ColdFusion XML document and then looping over the child nodes of the XML document using a simple indexed CFLoop tag. Running this, we get the expected output:

Sunrise Adams (DOB: 14 September 1982)

Briana Banks (DOB: 21 May 1978)

Belladonna

Tawny Roberts

Nina Hartley (DOB: 11 March 1959)

As I was writing this, I suddenly realized - Hey, I'm working on a ColdFusion 8 box now! Shouldn't I be using the new and easy CFLoop array iteration? Sounds good to me, so I updated my CFLoop to use the array attribute:

  • <!---
  • Loop over the array using a ColdFusion's new Array
  • iteration loop.
  • --->
  • <cfloop
  • index="xmlActor"
  • array="#xmlActors.actors.actor#">
  •  
  • <!--- Output the data. --->
  • <p>
  • #xmlActor.XmlAttributes.name#
  •  
  • <!--- Check to see if date of birth is defined. --->
  • <cfif (
  • StructKeyExists( xmlActor.XmlAttributes, "dob" ) AND
  • Len( xmlActor.XmlAttributes.dob )
  • )>
  •  
  • (DOB: #xmlActor.XmlAttributes.dob#)
  •  
  • </cfif>
  • </p>
  •  
  • </cfloop>

As you can see, this doesn't much change the meat of the CFLoop body; it merely gets rid of the short-hand node pointer creation and moves that directly into the index of the array loop. All is good - shorter, more readable code. The problem is that when I run this code in ColdFusion 8, I now get the following ColdFusion error:

Element XMLATTRIBUTES.NAME is undefined in XMLACTOR.

The way I see it, the only way I could get an error like this would be if the CFLoop index value was not pointing to an XML node in my ColdFusion XML document. To test this, I tried CFDumping out the contents of the xmlActor variable within the loop. Doing so, I get the following output:


 
 
 

 
CFDump of XML Node In ColdFusion 8 Array Loop Iteration  
 
 
 

What the heck is that? I was expecting to see the Short View and the Full View of an XML node complete with XmlAttriutes and XmlChildren; but, this has nothing of the sort. No wonder the Name attribute was undefined. Looking at the CFDump output, we can see that the object does have to do with the Xerces library, so we know that it is part of the ColdFusion XML concept. I guess what's going on here is that the ColdFusion 8 array iteration is revealing the underlying Java object that represents a node rather than the ColdFusion object that wraps around its functionality.

In order to get this to work with ColdFusion 8 array iteration, we have to completely update the body of our loop code to access the underlying Java methods (although, I guess you can't really consider them "underlying" at this point):

  • <!---
  • Loop over the array using a ColdFusion's new Array
  • iteration loop. By using this, we are given a pointer
  • to the underlying Java Xerces node object which can only
  • be accessed using the underlying Java methods.
  • --->
  • <cfloop
  • index="XmlActor"
  • array="#xmlActors.actors.actor#">
  •  
  • <!--- Output the data. --->
  • <p>
  • #xmlActor.GetAttribute(
  • JavaCast( "string", "name" )
  • )#
  •  
  • <!--- Check to see if date of birth is defined. --->
  • <cfif (
  • xmlActor.HasAttribute( JavaCast( "string", "dob" ) ) AND
  • Len( xmlActor.GetAttribute( JavaCast( "string", "dob" ) ) )
  • )>
  •  
  • (DOB: #xmlActor.GetAttribute(
  • JavaCast( "string", "dob" )
  • )#)
  •  
  • </cfif>
  • </p>
  •  
  • </cfloop>

Running this code, we again get the expected output:

Sunrise Adams (DOB: 14 September 1982)

Briana Banks (DOB: 21 May 1978)

Belladonna

Tawny Roberts

Nina Hartley (DOB: 11 March 1959)

Unless I missed something in the documentation, I can only assume that this is a bug. I cannot think of any reason why they would want to purposefully expose the underlying Java XML objects just because someone is iterating over the XML document using ColdFusion 8's new CFLoop-array attribute. The only thing I could think of is that treating an XML document like a bag of arrays and structures is really just a hack and as such, the CFLoop-array construct simply doesn't know how to handle it well (nor should it be? Is that like saying a string is an array of characters and then being surprised when it doesn't work???).

To test this theory, I tried to original round of code, but this time, instead of using the implicit child node array reference, I switched over to using an explicit XmlChildren array reference:

  • <!---
  • Loop over the array using a ColdFusion's new Array
  • iteration loop. This time, however, refer directly to
  • the XmlChildren rather than treating the node name
  • as if it were an array.
  • --->
  • <cfloop
  • index="xmlActor"
  • array="#xmlActors.actors.XmlChildren#">
  •  
  • <!--- Output the data. --->
  • <p>
  • #xmlActor.XmlAttributes.name#
  •  
  • <!--- Check to see if date of birth is defined. --->
  • <cfif (
  • StructKeyExists( xmlActor.XmlAttributes, "dob" ) AND
  • Len( xmlActor.XmlAttributes.dob )
  • )>
  •  
  • (DOB: #xmlActor.XmlAttributes.dob#)
  •  
  • </cfif>
  • </p>
  •  
  • </cfloop>

Notice here, that I am referring to xmlActors.actors.XmlChildren rather than xmlActors.actors.actor. The original way using the implicit array "actor", whereas this final code actually refers the "ColdFusion" array of Xml child nodes. And, indeed, running this code, we get the expected output:

Sunrise Adams (DOB: 14 September 1982)

Briana Banks (DOB: 21 May 1978)

Belladonna

Tawny Roberts

Nina Hartley (DOB: 11 March 1959)

So, here we have a situation where using the array "behavior" of the xml node list doesn't jive with the new CF8 array looping; but, it works fine when we refer directly to the XmlChildren array of child nodes. Is this is a bug? I want to say Yes, but I could see this being argued either way.

Thoughts?

Tweet This Groovy post by @BenNadel - ColdFusion 8 CFloop Array Iteration / XML Bug? Thanks my man — you rock the party that rocks the body!



Reader Comments

@Ben:

I'm not sure Adobe would consider this a bug or not. Remember that XML trees are not true ColdFusion arrays, but array-like objects.

It seems to me like this should be classified as a bug though, so I'd definitely recommend you file an ER for this.

Reply to this Comment

@Dan,

Yeah, that's where I think the murky, gray line is - they aren't *really* arrays. I guess what it comes down to is, does the ColdFusion documentation say that you can treat them as if they were arrays. If that is the case, then I would consider this a bug. If, however, they say that you cannot treat them like arrays in so far as you can access them via indexes, then no, this wouldn't be a bug as they are explicit as to how "Array-like" they really are.

I just looked through some of the XML documentation, and it is unclear. They mention that you can access things via array indexes:

http://livedocs.adobe.com/coldfusion/8/htmldocs/XML_08.html

But, it is never really explicitly defined on how the "name" works vs. the XmlChildren (which is explicitly defined as an array).

Reply to this Comment

Dude, how do you find all this wacky stuff?

I didn't actually realize you could do the implicit XPath navigation thing so I'm in two minds as to whether this is a bug (using xmlChildren gets you *all* the child nodes, not just ones named "actor" so the two are not equivalent).

I would guess that if you call getElementsByTagName("actor") or getChildNodes() you will get an array back...?

Reply to this Comment

@Sean,

I do a good deal of stuff with XML and I am always looking to see where CF8's new features can make my life easier.

As far as using things like getChildNodes(), I think th problem with that is that it returns non-element nodes (such as white space / text nodes??). I can't remember what the issue is, but I am pretty sure it has caused problems before. The nice things about XmlChildren is that is returns just element nodes.

I guess I can just use the XmlChildren; like you are saying, though, it will return all the child nodes, not just those of a given name, but wanting all of them is more often the use case. If I really needed to separate them out, I usually use something like XmlSearch() / Xpath.

Reply to this Comment

@Ben,

I would not consider this a bug really but maybe a feature enhancement. CFLoop does not consider xmlActors.actors.actor an array because it is not really an array. You can use the array notation to get at a specific node if there are more then one with the same name and you can use the array functions, but if you do <cfdump var="#xmlActors.actors.actor#"> right after your CFXML tag it will only dump out the first node so cfdump is not considering it an array automatically either.

I would probably do the loop like:

<cfloop
index="xmlActor"
array="#xmlsearch(xmlActors,'/actors/actor')#">

<!--- Output the data. --->
<p>
#xmlActor.XmlAttributes.name#

<!--- Check to see if date of birth is defined. --->
<cfif (
StructKeyExists( xmlActor.XmlAttributes, "dob" ) AND
Len( xmlActor.XmlAttributes.dob )
)>

(DOB: #xmlActor.XmlAttributes.dob#)

</cfif>
</p>

</cfloop>

What I think might be cool though, is if Adobe adds xmldoc and xpath attributes so you could loop through it like:

<cfloop
index="xmlActor"
XMLDoc="#xmlActors#"
XPath="/actors/actor">

<!--- Output the data. --->
<p>
#xmlActor.XmlAttributes.name#

<!--- Check to see if date of birth is defined. --->
<cfif (
StructKeyExists( xmlActor.XmlAttributes, "dob" ) AND
Len( xmlActor.XmlAttributes.dob )
)>

(DOB: #xmlActor.XmlAttributes.dob#)

</cfif>
</p>

</cfloop>

Reply to this Comment

@Scott,

I don't know why I feel so strongly about this, but when you put the XmlSearch() method call directly in the array attribute.... that strikes me as crazy brilliant! While there is nothing technically special about that, it just feels really clever. Thanks for the tip.

Reply to this Comment

@Ben,

LOL! I don't know about brilliant... I'm sure you would have figured that out eventually with all the work your doing with XML. I'm all about cutting out the middle man as much as possible, mostly because I'm lazy and don't want to type cfset tags when I don't have to =). Now that I'm thinking about it, a lot of my coolest programs stem from me being too lazy to do some tedious task, so I write some code to do it for me, with as little interaction from me as possible.

Reply to this Comment

@Scott,
I understand putting the array in the actual loop, but doesn't that then cause it to be re-evaluated each time slowing the page processing down?

It's sort of the reason (from other things I've read), that they say to set everything outside of the loop declarations

e.g.
instead of:
for (var i=0; i < arrayLen(myArray);i++) {
loop here
}

do something like:
var arrayLength = arrayLen(myArray)
var i = 0;
for ( ; i < arrayLength;i++) {
loop here
}

(I guess this could also just be put in a while loop then, but just wanted to show my reasoning)

Reply to this Comment

@Gareth,

Values inside of attributes with # characters are only evaluated once, when the tag is initially executed. This is why you will go out of bounds if you start deleting array elements within a loop - the ArrayLen() is never re-evaluated.

Reply to this Comment

@Gareth,

Only the code within the body of the cfloop tag would get re-evaluated with every iteration, so you would definitely want to make sure any evaluations or other processing you need to do to set up variables that will be used within the body which are not going to change from iteration to iteration are all done before you start the loop.

So you would do this:

<cfset myFavoriteColor = "Blue">
Scott's Favorite Color is
<cfloop index="ThisColor" array="#listtoarray('Red,Green,Blue,Yellow,Black')#">
<cfif ThisColor is myFavoriteColor>#ThisColor#</cfif>
</cfloop>

instead of this:

Scott's Favorite Color is
<cfloop index="ThisColor" array="#listtoarray('Red,Green,Blue,Yellow,Black')#">
<cfset myFavoriteColor = "Blue">
<cfif ThisColor is myFavoriteColor>#ThisColor#</cfif>
</cfloop>

but as Ben said, the stuff within the attributes of the cfloop tag are only evalutated once, and the same would be true if you are using the cfscript syntax.

Reply to this Comment

@Scott,

I don't use CFScript very much, but I am not sure if there is a difference in CFScript. I think in CFScript the Condition / Step portions do get re-evaluated for every loop. For example, if you have:

for (i = 1 ; i LTE ArrayLen( arrData ) ; i++){

.... ArrayDeleteAt( arrData, ArrayLen( arrData ) );

}

In ColdFusion tags, this would surely go out of bounds since the initial "LTE ArrayLen()" condition is only evaluated once. However, my gut (UN-tested) feeling is that in CFScript, this would function fine the condition gets re-evaluated.

So, I *think* there is a difference, but I have not tested it.

Reply to this Comment

Yeah I just tested it and there is a difference after all:

<cfset arrData = listtoarray('Red,Green,Blue,Yellow,Black')>
<cfdump var="#arrData#">
<cfscript>
for (i = 1 ; i LTE ArrayLen( arrData ) ; i++){

ArrayDeleteAt( arrData, ArrayLen( arrData ) );

}
</cfscript>
<cfdump var="#arrData#">

doesn't throw an error because it does actually evaluate the "i LTE ArrayLen( arrData )" in every iteration. When you use the cfloop syntax though it gives you an out of bounds error.

<cfset arrData = listtoarray('Red,Green,Blue,Yellow,Black')>
<cfloop index="arrayitem" array="#arrData#">
<cfset ArrayDeleteAt(arrData, ArrayLen( arrData ))>
</cfloop>

<cfdump var="#arrData#">

But a for loop in cfscript is not the equivalent of a cfloop over an array. the for loop is just counting, when looping over the array with a cfloop it is actually setting the index variable to the value of the array item, which would be more like this:

<cfset arrData = listtoarray('Red,Green,Blue,Yellow,Black')>

<cfscript>
end = ArrayLen( arrData );
for (i = 1 ; i LTE end ; i++){
arrayitem = arrdata[i];
ArrayDeleteAt( arrData, ArrayLen( arrData ) );

}
</cfscript>

which throws an error just like the cfloop does.

Reply to this Comment

You may have written about it elsewhere, but it looks like this has been changed in CF9. The index now points to the XML node. Very cool!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.