Monday 24 February 2014

ColdFusion 11: a lot of string member functions have not been implemented

G'day:
I was writing some scratch code today to parse some strings, and I tried to do this:

matches = myString.reMatch(regex);

And this didn't work. For some reason, Adobe have not implemented it.

So I've just been through all the functions that act on strings, and checked whether Adobe have bothered to implement member functions for them.

Whilst they've done a reasonably good job at implementing a bunch of member functions, they've not exactly gone "above and beyond the call of duty" with this work. An awful lot of them have not been implemented. Both ones that one would automatically expect the to have done, and some other less obvious ones that - had they done a thorough job of the task at hand - one might have expected them to have done.

Here's me test code, doing a test run on each member function I figured they should have implemented. It's pretty repetitive, and the only interesting bit is how I use a generic function which takes a function expression and then runs that expression within a try/catch and either returns the result of the function expression if it works, or the error message if it errored. That's at the bottom... all the rest is just repeated calls to that with each one testing a prospective member function. In hindsight I should have written this as a unit test suite, but it didn't occur to me until just now. Oops.

textString = "G'day world";
numericString = "42";
dateString = "2014-02-23";
listString = "tahi,rua,toru,wha";

writeOutput("<h3>reMatch()</h3>");
writeDump(var=safeCall(function(){
    return textString.rematch("");
}));

writeOutput("<hr><h3>numberFormat()</h3>");
writeDump(var=safeCall(function(){
    return numericString.numberFomat("99");
}));

writeOutput("<hr><h3>val()</h3>");
writeDump(var=safeCall(function(){
    return numericString.val();
}));

writeOutput("<hr><h3>parseDateTime()</h3>");
writeDump(var=safeCall(function(){
    return dateString.parseDateTime();
}));

writeOutput("<hr><h3>htmlEditFormat()</h3>");
writeDump(var=safeCall(function(){
    s = "<h4>#textString#</h4>";
    return s.htmlEditFormat();
}));

writeOutput("<hr><h3>refind()</h3>");
writeDump(var=safeCall(function(){
    return textString.refind("day");
}));

writeOutput("<hr><h3>hash()</h3>");
writeDump(var=safeCall(function(){
    return textString.hash();
}));

writeOutput("<hr><h3>javaCast()</h3>");
writeDump(var=safeCall(function(){
    return textString.javaCast("String");
}));

writeOutput("<hr><h3>jsStringFormat()</h3>");
writeDump(var=safeCall(function(){
    return textString.jsStringFormat("String");
}));

writeOutput("<hr><h3>isNumeric()</h3>");
writeDump(var=safeCall(function(){
    return textString.isNumeric();
}));

writeOutput("<hr><h3>isValid()</h3>");
writeDump(var=safeCall(function(){
    return numericString.isValid("integer");
}));

writeOutput("<hr><h3>repeatString()</h3>");
writeDump(var=safeCall(function(){
    return textString.repeatString(2);
}));

writeOutput("<hr><h3>reReplace()</h3>");
writeDump(var=safeCall(function(){
    return textString.reReplace("[aeiou]", "_", "ALL");
}));

writeOutput("<hr><h3>replaceList()</h3>");
writeDump(var=safeCall(function(){
    return textString.replaceList("G'day,world", "Hello,universe");
}));

writeOutput("<hr><h3>toBase64()</h3>");
writeDump(var=safeCall(function(){
    return textString.toBase64();
}));

writeOutput("<hr><h3>urlDecode()</h3>");
writeDump(var=safeCall(function(){
    var s = textString & "%20" & textString;
    return s.urlDecode();
}));

writeOutput("<hr><h3>urlEncodedFormat()</h3>");
writeDump(var=safeCall(function(){
    return textString.urlEncodedFormat();
}));

writeOutput("<hr><h3>xmlFormat()</h3>");
writeDump(var=safeCall(function(){
    return textString.xmlFormat();
}));

writeOutput("<hr><h3>listValueCount()</h3>");
writeDump(var=safeCall(function(){
    return listString.listValueCount("world");
}));

writeOutput("<hr><h3>LSParseEuroCurrency()</h3>");
writeDump(var=safeCall(function(){
    var currencyString = "€1.000,00";
    var locale = "DE_DE";
    return currencyString.LSParseEuroCurrency(locale);
}));

writeOutput("<hr><h3>serializeJson()</h3>");
writeDump(var=safeCall(function(){
    return textString.serializeJson();
}));

writeOutput("<hr><h3>deserializeJson()</h3>");
writeDump(var=safeCall(function(){
    var a = listToArray(listString);
    var json = serializeJson(a);
    return json.deserializeJson();
}));

writeOutput("<hr><h3>xmlParse()</h3>");
writeDump(var=safeCall(function(){
    var xmlString = "<aaa><bbb /></aaa>";
    var xml = xmlParse(xmlString);
    return xmlString.xmlParse();
}));

writeOutput("<hr><h3>encrypt()</h3>");
writeDump(var=safeCall(function(){
    return textString.encrypt("CFMX_COMPAT");
}));

writeOutput("<hr><h3>decrypt()</h3>");
writeDump(var=safeCall(function(){
    var encrypted = encrypt(textString, "CFMX_COMPAT");
    return encrypted.decrypt("CFMX_COMPAT");
}));

writeOutput("<hr><h3>canonicalize()</h3>");
writeDump(var=safeCall(function(){
    return textString.canonicalize(true, true);
}));

writeOutput("<hr><h3>encodeForUrl()</h3>");
writeDump(var=safeCall(function(){
    return textString.encodeForUrl();
}));

writeOutput("<hr><h3>isDate()</h3>");
writeDump(var=safeCall(function(){
    return dateString.isDate();
}));

writeOutput("<hr><h3>reEscape()</h3>");
writeDump(var=safeCall(function(){
    return textString.reEscape();
}));

function safeCall(required function f, struct args={}){
    try {
        var result = "";
        result = f(argumentCollection=args);
    }catch (any e){
        result = e.message;
    }
    finally {
        return result;
    }
}

I'll spare you the output, but the bottom line is the following methods have not been implemented:

  • canonicalize()
  • decrypt()
  • deserializeJson()
  • encodeForUrl() (and I presume the other encoding functions)
  • encrypt()
  • hash()
  • htmlEditFormat() (and probably htmlCodeFormat())
  • isDate() (and I presume other type-checking functions)
  • isNumeric()
  • isValid()
  • javaCast()
  • jsStringFormat()
  • listValueCount()
  • LSParseEuroCurrency() (and there'll be a few other locale-specific ones that deal with strings; these should be dealt with too)
  • numberFormat()
  • parseDateTime()
  • reEscape()
  • reFind() (and I presume the no-case version. Although I question whether separate methods just to handle case-sensitivity is sensible. This can be done with the regex itself after all)
  • reMatch() (as above)
  • repeatString() (this should just be implemented as repeat(). It's tautological to include "string" in a string method)
  • replaceList()
  • reReplace()
  • serializeJson()
  • toBase64()
  • urlDecode()
  • urlEncodedFormat()
  • val()
  • xmlFormat()
  • xmlParse()
There's also some other functions like fileRead(), which takes a string as the first argument... would it make sense to have something like myPath.fileRead()? I suspect not... instead just having a static File class upon which one can do File.read(myPath).

If I can be frank, I am surprised a few of those didn't show up in testing (as having not been implemented). It makes me question how thorough the testing was here. I'm not gonna check the other data types' methods... I think Adobe need to be a bit more diligent with their work here. I'm going to raise one ticket for all of these (3712186), with the note to revisit the member functions of the other data types too. Life's too short to do too much of Adobe's work for them.

Actually... I hasten to add that Lucee doesn't support any of these ones either. Harrumph (bug raised: 62).

OK, well that's that. Any other member functions you can think of that they should have implemented? Are you against any of the ones I looked at above? Do tell.

--
Adam