Sunday, 23 September 2007

BetterXML Beta Release!

http://betterxml.riaforge.org/

After some initial learning about XML and handling it in ColdFusion 6.1 & 7, I came to the conclusion that ColdFusion wasn't the best when it came to XML.

When reading and transforming XML, ColdFusion does a great job and is at its usual brilliantly quick. Where I think it gets cumbersome is in maintenance scenarios where you have to edit a given XML file. For example, you may have some XML that is being used as the sole data source and needs updating whenever a user adds item x or deletes item z, etc.

An ideal solution to this is to use XPath to quickly navigate the XML in readiness for your CRUD operations. ColdFusion's XMLSearch() method is not suitable in this case however, as it only returns data from an XPath query. To navigate the XML using XPath, we need the XPath query to return a nodeset that is a collection of pointers to the original data. Once we have this collection of pointers, we can go about performing our CRUD operations on the data.

This is where BetterXML comes in. BetterXML is a set of two ColdFusion Components; an XML reader and an XML editor, the editor extending the reader. They both use the org.apache.xpath.XPathAPI java object that lets them quickly navigate the data using XPath.

The public methods are as follows:

XML Reader:
  • Init( src ) - Returns an instance of the object, loading the XML if supplied
  • Load( src ) - loads XML from either an xml string, file or URL
  • Search( XPath, filter ) - returns an array of either strings or simplified structures based on the results of the XPath query
  • Count( XPath ) - returns the number of results returned by the XPath query
  • Names( XPath ) - returns the names of the attributes and elements returned by the XPath query
  • XML() - returns the XML as a string
XML Editor:
  • Init( src ) - Returns an instance of the object, loading the XML if supplied
  • Write( destination ) - Writes the XML to file
  • CreateElement( XPath, Name, Value ) - Creates an element in all the parents returned by the XPath query. The value of the element can be a simple value or a structure to write nested elements.
  • CreateAttribute( XPath, Name, Value ) - Creates an attribute in all the elements returned by the XPath query
  • Update( XPath, Value ) - Updates all the elements returned by the XPath query. The updated value can be a simple value or a structure to write nested elements.
  • Delete( XPath ) - Deletes all the elements returned by the XPath query
A quick example
Lets say you have a huge XML file that is full of comments that are now redundant. To delete all the comments from the file we can do:

<cfinvoke component="BetterXML_Editor" method="init" src="#myFile#" returnVariable="xmlFile"/>
<cfscript>
xmlFile.Delete('//comment()');
xmlFile.Write(myFile);
</cfscript>

The code is minimal and the load times hugely improved when compared to using native ColdFusion functions to achieve the same end :)

Dom

Loopy Loo

What is the best way to loop a structure in a cfscript block? On discovering the elements() and keys() methods that are inherited from java.util.dictionary, I presumed these would be faster i.e.
<cfscript>
keys = myStruct.keys();
while( keys.hasMoreElements() ){
key = keys.nextElement();
value = myStruct[key];
// do stuff...

}
// or
values = myStruct.elements();
while( values.hasMoreElements() ){
value = values.nextElement();
// do stuff...
}
// or
keys = myStruct.keys();
values = myStruct.elements();
while( keys.hasMoreElements() ){
key = keys.nextElement();
value = values.nextElement();
// do stuff...
}
</cfscript>
Normally, I would use StructKeyArray() and one might conceivably use StructKeyList():
<cfscript>
// StructKeyArray()
keys = StructKeyArray(myStruct);
size = ArrayLen(keys);
for (i=1; i LTE size; i=i+1){
key = keys[i];
value = myStruct[key];
// do stuff...
}

// StructKeyList()
keys = StructKeyList(myStruct);
size = ListLen(keys);
for (i=1; i LTE size; i=i+1){
key = ListGetAt(keys, i);
value = myStruct[key];
// do stuff...
}
</cfscript>
So which is faster? Well, it all depends:

For small structures, say 10 keys, StructKeyArray() is the winner followed closely by the inherited Java methods and then StructKeyList(). The differences in performance aren't huge here, but StructKeyList() is still clearly slower.

As the structure gets larger however, the results change. Using StructKeyList() becomes exponentially slower and the inherited Java methods overtake StructKeyArray() somewhere in the region of a structure with 150 keys.

Conclusion: use StructKeyArray() unless you happen to be dealing with a huge structure and you need to squeeze every bit of performance necessary. I can't see why you should ever use StructKeyList() to loop a structure, perhaps someone could give an example where it is necessary?

On a similar note, I had also presumed that using myArray.size() would be faster than ArrayLen(myArray), it certainly looks prettier to me anyway! It turns out I was wrong. Some magic in ArrayLen() means it well outperforms size(). Still, size() is OO and looks nice ;)

Saturday, 22 September 2007

Singular URL and FORM variable

After reading this discussion on IsDefined() vs StructKeyExists(), I got to thinking about how one might use the underlying Java methods of ColdFusion structures to combine the URL and FORM variables efficiently (it is related, really).

A quick inspection of the java docs for java.util.Hashtable turned up the putAll() method. With it, you can 'put' one struct into another, overwriting any matching keys.

So, to very quickly and elegantly combine the form and url scopes into a request variable called 'args', we can do:
<cfset request.args = StructNew()>
<cfset request.args.putAll(url)>
<cfset request.args.putAll(form)>
Et voila!

ColdFusion objects are Java objects...

A good while back I read the following post about using the methods that ColdFusion objects inherit from their Java parents. It prompted me to write a little function for finding out what Java objects any ColdFusion object inherits from:

http://coldfused.blogspot.com/2007/01/extend-cf-native-objects-harnessing.html

<cfscript>
function GetClassHeirarchy(obj)
{
var thisClass = obj.GetClass();
var sReturn = thisClass.GetName();

do{
thisClass = thisClass.GetSuperClass();
sReturn = sReturn & " EXTENDS: #thisClass.GetName()#";
}while(CompareNoCase(thisClass.GetName(), 'java.lang.Object'));

return sReturn;
}
</cfscript>

<!--- so, for example: --->
<cfoutput>#GetClassHeirarchy(StructNew())#</cfoutput>

I think it fairly useless having the function just sitting around and have decided to post results, with links to java and other docs, here on the interweb! You will be able to use most, if not all, the inherited methods that you see in the JavaDocs.

ColdFusion Query
java.lang.Object -> coldfusion.sql.imq.imqTable -> coldfusion.sql.Table -> coldfusion.sql.QueryTable

ColdFusion Struct
java.lang.Object -> java.util.Dictionary -> java.util.Hashtable -> coldfusion.util.FastHashtable -> coldfusion.runtime.Struct

ColdFusion Array
java.lang.Object -> java.util.AbstractCollection -> java.util.AbstractList -> java.util.Vector -> coldfusion.runtime.Array

ColdFusion XML Document Object
java.lang.Object -> org.apache.xerces.dom.NodeImpl -> org.apache.xerces.dom.ChildNode -> org.apache.xerces.dom.ParentNode -> org.apache.xerces.dom.CoreDocumentImpl -> org.apache.xerces.dom.DocumentImpl

That's all for now, will be posting examples of use in the coming hours...

Dom