Wednesday, April 22, 2009

XPages: Workarounds for lack of Regular Expression functionality

I wrote these two String methods as workarounds for the previously mentioned missing Regular Expression functionality.

// Finds all occurences of a substring in a string using a regular expression
// Similar to real JavaScript RegExp match with 'g' modifier
String.prototype.matchAll = function( regExp ){
// The input String
var string = this;

// Find all matches/add to matches-array
var matches = [];
while( string.match( regExp ) ){
matches.push( string.match( regExp )[0] );
string = string.replace( regExp, '' );
}
return matches;
}

// Replace all occurences of a substring using a function
// Similar to real JavaScript, where you can pass a function as the second argument
String.prototype.replaceAllFn = function( regExp, fn ){
// The input String
var string = this;

// Array used to find matches withing the string
var matchArray = null;
// JavaScript equivalent keeps track of the position of the substrings
var position = null;

while( matchArray = string.match( regExp ) ){
position = string.search( regExp );

// Replace substring(s) in the string using the supplied function
// Input parameters to the supplied function: all matches withing the
// string, the position of the match, the full string
string = string.replace( matchArray[0],
fn.apply( this, matchArray.concat( position, string ) ) );
}
return string;
}


Examples:

// Find all substrings withing braces
'{lastName}, {firstName}'.matchAll( /\{(\w+)\}/g );
// Result, array with two items: ["{lastName}", "{firstName}"]


// Map object properties to a string template
var object = { firstName: 'Tommy', lastName: 'Valand' };
var template = '{lastName}, {firstName}';
template.replaceAllFn( /\{(\w+)\}/, function( item, key ){
return object[key] || '';
});
// Result: "Valand, Tommy"

// Convert CSS to camelCase
"-moz-border-radius".replaceAllFn(/-\w/g, function( string ){
return string.replace('-','').toUpperCase();
});
// Result: MozBorderRadius

// Calculate celcius from fahrenheit
"212F".replaceAllFn(/(\d+(?:\.\d*)?)F\b/g, function(str, p1, offset, s){
return ((p1-32) * 5/9) + "C";
});
//Result: 100C


As others have mentioned, closures seems to work in XPages' pseudo-JavaScript.

Example:
function testWithClosures(){
var object = { firstName: 'Tommy', lastName: 'Valand' };
var template = '{lastName}, {firstName}';
return template.replaceAllFn( /\{(\w+)\}/, function( item, key ){
return object[key] || '';
});
}

testWithClosures();
// Result: "Valand, Tommy"


If closures weren't supported, replaceAll would crash, since the object is unavailable (undefined) in the global scope. object is only available in the scope that the "replace-function" was created.

Also, if you didn't already notice it, you can extend native objects as you can with the native objects in the web browsers. Thread carefully though.. :)

If you find any bugs in regards to the JavaScript methods being emulated, let me know.

There is (at least) one serious bug with my replaceAllFn method.
'test'.replaceAllFn( /t/, function(){ return 't'; } results in an eternal loop.
I'll try to find a better way.

0 comments: