April 2005 Archives

A Real Use for Closures

| No Comments

I think I read about closures first a few years ago in the Camel book (first edition). At the time they seemed to be a neat toy, but not something I would ever need. Now, I've finally come across a scenario where closures provide the best solution.

I'm using XML::Parser (no doubt that's old school but I need something that's solid and portable). With that, I get to define a bunch of callbacks. I want the callbacks to be methods in an object I've created. Problem: XML::Parser wants references to subroutines, not objects. Solution: pass references to anonymous subroutines created on the spot. There's a $self reference in lexical scope, so I can just refer to that in the anonymous subroutine. Thanks to the magic of closures, it's available, even when the sub is being called back later.

    my $parser = new XML::Parser(ErrorContext => 2);
    $parser->setHandlers(
        Start => sub { $self->handle_start(@_) },
        Char  => sub { $self->handle_characters(@_) },
        End   => sub { $self->handle_end(@_) }
    );

I find this surprisingly readable. Even if I didn't know about closures, I would be able to read and understand this code: DWIM at its best. (By the way, syntactic closures seem to have been invented as recently as 1988.)

Tiny Patch for Text::CSV

| No Comments

This is really here in case I need to retrieve it. Text::CSV is a handy little module for reading CSV files. Unfortunately it isn't actively maintained and it handles only US ASCII. My patch merely alters some regular expressions so that the module accepts the full range of bytes allowable in UTF-8.

Transporting Chunks of Text to the Server

| No Comments

I've jumped on the XMLHTTPRequest bandwagon. On some potentially large forms, I'm POSTing answers to individual questions as they are completed. The answers are packaged as small packets of XML.

Creating the XML packets is a two-step process. First, create an XML element.

    if (typeof ActiveXObject != 'undefined') {
        xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
        myElem = xmlDoc.createNode(1, 'FOOTAG', '');
        xmlDoc.appendChild(answerElem);
    } else {
        myElem = document.createElement('FOOTAG');
    }
    myElem.setAttribute('foo', bar);
    myElem.setAttribute('foo2', bar2);

Once all the attributes have been set, I need the element as a string before I can send it.

function serializeElement(elem)
{
    var serialized;
    try {
        // XMLSerializer exists in current Mozilla browsers
        serializer = new XMLSerializer();
        serialized = serializer.serializeToString(elem);
    } catch (e) {
        // Internet Explorer has a different approach to serializing XML
        serialized = elem.xml;
    }
    return serialized;
}

This handles the main browsers I care about: Mozilla Firefox and Internet Explorer (the current versions). From here I just use the XMLHTTPRequest object as documented in a hundred million places by now.

One more small wrinkle. The server software loses line breaks when parsing XML. In order to get around the problem, I decided to encode multi-line answers before sending. Base64 encoding is supported by the Mozilla browsers but not IE (and it's bulky and not human-readable). Strangely enough, I opted for URL encoding. Initially, I was using JavaScript's escape() function. Then I found it put ugly %Uxxxx entities into its output that made the server software choke. A quick search turned up the solution: better to use encodeURI().

Example of arraycopy() usage

| No Comments

Someone asked for a more complete example of copying byte arrays in ColdFusion MX using Java's System.arraycopy() method. (The subject originally came up on Christian Cantrell's weblog.)

Here's a slightly more detailed version of what I posted in comments over there.

 <cfscript>
  <!--- b1 and b2 are two Java byte arrays that I want to concatenate --->
  byteClass = createObject("java", "java.lang.Byte").TYPE;
  refArray = createObject("java","java.lang.reflect.Array");
  byteLen = b1.getLength() + b2.getLength();

  <!--- bdest is the byte array where I want to put the result --->
  bdest = refArray.newInstance(byteClass, javacast("int", byteLen));
  sys = createObject("java","java.lang.System");
  sys.arraycopy(b1, 0, bdest, 0, b1.getLength());
  sys.arraycopy(b2.getBytes(), 0, bdest, b1.getLength(), b2.getLength());
 </cfscript>