In sortable tables:
authorTim Starling <tstarling@users.mediawiki.org>
Tue, 28 Oct 2008 08:07:00 +0000 (08:07 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Tue, 28 Oct 2008 08:07:00 +0000 (08:07 +0000)
* (bug 8063) Use the content language digit transform table.
* Don't recognise C-style hexadecimal notation as a number, that feature never actually worked.
* Be more forgiving about things that look like numbers but turn out not to be. Sort them stringwise.
* Optimised ts_resortTable by having it calculate the sort keys at the start, and then using a single trivial comparison function. There are potentially many more comparisons than rows. Observed factor of 2 speedup.
* Use RegExp.test() instead of String.match() when a true/false value is desired, as recommended by the Mozilla reference

RELEASE-NOTES
includes/DefaultSettings.php
includes/Skin.php
skins/common/wikibits.js

index 3c7a093..c5b3032 100644 (file)
@@ -289,6 +289,7 @@ The following extensions are migrated into MediaWiki 1.14:
   JavaScript is disabled.
 * (bug 4253) Recentchanges IRC messages no longer include title in diff URLs 
 * Allow '0' to be an accesskey.
+* (bug 8063) Use language-dependent sorting in client-side sortable tables
 
 === API changes in 1.14 ===
 
index ea3c851..76b4071 100644 (file)
@@ -1399,7 +1399,7 @@ $wgCacheEpoch = '20030516000000';
  * to ensure that client-side caches don't keep obsolete copies of global
  * styles.
  */
-$wgStyleVersion = '182';
+$wgStyleVersion = '183';
 
 
 # Server-side caching:
index b1336ee..0fe06cc 100644 (file)
@@ -341,6 +341,18 @@ class Skin extends Linker {
 
                $ns = $wgTitle->getNamespace();
                $nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText();
+               $separatorTransTable = $wgContLang->separatorTransformTable();
+               $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
+               $compactSeparatorTransTable = array(
+                       implode( "\t", array_keys( $separatorTransTable ) ),
+                       implode( "\t", $separatorTransTable ),
+               );
+               $digitTransTable = $wgContLang->digitTransformTable();
+               $digitTransTable = $digitTransTable ? $digitTransTable : array();
+               $compactDigitTransTable = array(
+                       implode( "\t", array_keys( $digitTransTable ) ),
+                       implode( "\t", $digitTransTable ),
+               );
 
                $vars = array(
                        'skin' => $data['skinname'],
@@ -368,6 +380,8 @@ class Skin extends Linker {
                        'wgVersion' => $wgVersion,
                        'wgEnableAPI' => $wgEnableAPI,
                        'wgEnableWriteAPI' => $wgEnableWriteAPI,
+                       'wgSeparatorTransformTable' => $compactSeparatorTransTable,
+                       'wgDigitTransformTable' => $compactDigitTransTable,
                );
                
                if( $wgUseAjax && $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false )){
index 62ef574..5e7905f 100644 (file)
@@ -503,6 +503,8 @@ var ts_image_down = "sort_down.gif";
 var ts_image_none = "sort_none.gif";
 var ts_europeandate = wgContentLanguage != "en"; // The non-American-inclined can change to "true"
 var ts_alternate_row_colors = false;
+var ts_number_transform_table = null;
+var ts_number_regex = null;
 
 function sortables_init() {
        var idnum = 0;
@@ -575,9 +577,14 @@ function ts_resortTable(lnk) {
                table = table.parentNode;
        if (!table) return;
 
-       // Work out a type for the column
        if (table.rows.length <= 1) return;
 
+       // Generate the number transform table if it's not done already
+       if (ts_number_transform_table == null) {
+               ts_initTransformTable();
+       }
+
+       // Work out a type for the column
        // Skip the first row if that's where the headings are
        var rowStart = (table.tHead && table.tHead.rows.length > 0 ? 0 : 1);
 
@@ -590,22 +597,21 @@ function ts_resortTable(lnk) {
                }
        }
 
-       var sortfn = ts_sort_caseinsensitive;
-       if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
-               sortfn = ts_sort_date;
-       else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
-               sortfn = ts_sort_date;
-       else if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
-               sortfn = ts_sort_date;
+       // TODO: bug 8226, localised date formats
+       var sortfn = ts_sort_generic;
+       var preprocessor = ts_toLowerCase;
+       if (/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/.test(itm)) {
+               preprocessor = ts_dateToSortKey;
+       } else if (/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/.test(itm)) {
+               preprocessor = ts_dateToSortKey;
+       } else if (/^\d\d[\/.-]\d\d[\/.-]\d\d$/.test(itm)) {
+               preprocessor = ts_dateToSortKey;
        // pound dollar euro yen currency cents
-       else if (itm.match(/(^[\u00a3$\u20ac\u00a4\u00a5]|\u00a2$)/))
-               sortfn = ts_sort_currency;
-       // We allow a trailing percent sign, which we just strip.  This works fine
-       // if percents and regular numbers aren't being mixed.
-       else if (itm.match(/^[+-]?\d[\d,]*(\.[\d,]*)?([eE][+-]?\d[\d,]*)?\%?$/) ||
-       itm.match(/^[+-]?\.\d[\d,]*([eE][+-]?\d[\d,]*)?\%?$/) ||
-       itm.match(/^0x[\da-f]+$/i))
-               sortfn = ts_sort_numeric;
+       } else if (/(^[\u00a3$\u20ac\u00a4\u00a5]|\u00a2$)/.test(itm)) {
+               preprocessor = ts_currencyToSortKey;
+       } else if (ts_number_regex.test(itm)) {
+               preprocessor = ts_parseFloat;
+       }
 
        var reverse = (span.getAttribute("sortdir") == 'down');
 
@@ -614,8 +620,9 @@ function ts_resortTable(lnk) {
                var row = table.rows[j];
                var keyText = ts_getInnerText(row.cells[column]);
                var oldIndex = (reverse ? -j : j);
+               var preprocessed = preprocessor( keyText );
 
-               newRows[newRows.length] = new Array(row, keyText, oldIndex);
+               newRows[newRows.length] = new Array(row, preprocessed, oldIndex);
        }
 
        newRows.sort(sortfn);
@@ -654,6 +661,63 @@ function ts_resortTable(lnk) {
        }
 }
 
+function ts_initTransformTable() {
+       if ( typeof wgSeparatorTransformTable == "undefined"
+                       || ( wgSeparatorTransformTable[0] == '' && wgDigitTransformTable[2] == '' ) )
+       {
+               digitClass = "[0-9,.]";
+               ts_number_transform_table = false;
+       } else {
+               ts_number_transform_table = {};
+               // Unpack the transform table
+               // Separators
+               ascii = wgSeparatorTransformTable[0].split("\t");
+               localised = wgSeparatorTransformTable[1].split("\t");
+               for ( var i = 0; i < ascii.length; i++ ) { 
+                       ts_number_transform_table[localised[i]] = ascii[i];
+               }
+               // Digits
+               ascii = wgDigitTransformTable[0].split("\t");
+               localised = wgDigitTransformTable[1].split("\t");
+               for ( var i = 0; i < ascii.length; i++ ) { 
+                       ts_number_transform_table[localised[i]] = ascii[i];
+               }
+
+               // Construct regex for number identification
+               digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '\\.'];
+               maxDigitLength = 1;
+               for ( var digit in ts_number_transform_table ) {
+                       // Escape regex metacharacters
+                       digits.push( 
+                               digit.replace( /[\\\\$\*\+\?\.\(\)\|\{\}\[\]\-]/,
+                                       function( s ) { return '\\' + s; } )
+                       );
+                       if (digit.length > maxDigitLength) {
+                               maxDigitLength = digit.length;
+                       }
+               }
+               if ( maxDigitLength > 1 ) {
+                       digitClass = '[' + digits.join( '', digits ) + ']';
+               } else {
+                       digitClass = '(' + digits.join( '|', digits ) + ')';
+               }
+       }
+
+       // We allow a trailing percent sign, which we just strip.  This works fine
+       // if percents and regular numbers aren't being mixed.
+       ts_number_regex = new RegExp(
+               "^(" +
+                       "[+-]?[0-9][0-9,]*(\\.[0-9,]*)?(E[+-]?[0-9][0-9,]*)?" + // Fortran-style scientific
+                       "|" +
+                       "[+-]?" + digitClass + "+%?" + // Generic localised
+               ")$", "i"
+       );
+}
+
+function ts_toLowerCase( s ) {
+       return s.toLowerCase();
+}
+
 function ts_dateToSortKey(date) {      
        // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
        if (date.length == 11) {
@@ -695,38 +759,34 @@ function ts_dateToSortKey(date) {
        return "00000000";
 }
 
-function ts_parseFloat(num) {
-       if (!num) return 0;
-       num = parseFloat(num.replace(/,/g, ""));
-       return (isNaN(num) ? 0 : num);
-}
-
-function ts_sort_date(a,b) {
-       var aa = ts_dateToSortKey(a[1]);
-       var bb = ts_dateToSortKey(b[1]);
-       return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
-}
-
-function ts_sort_currency(a,b) {
-       var aa = ts_parseFloat(a[1].replace(/[^0-9.,]/g,''));
-       var bb = ts_parseFloat(b[1].replace(/[^0-9.,]/g,''));
-       return (aa != bb ? aa - bb : a[2] - b[2]);
-}
+function ts_parseFloat( s ) {
+       if ( !s ) {
+               return 0;
+       }
+       if (ts_number_transform_table != false) {
+               var newNum = '', c;
+               
+               for ( var p = 0; p < s.length; p++ ) {
+                       c = s.charAt( p );
+                       if (c in ts_number_transform_table) {
+                               newNum += ts_number_transform_table[c];
+                       } else {
+                               newNum += c;
+                       }
+               }
+               s = newNum;
+       }
 
-function ts_sort_numeric(a,b) {
-       var aa = ts_parseFloat(a[1]);
-       var bb = ts_parseFloat(b[1]);
-       return (aa != bb ? aa - bb : a[2] - b[2]);
+       num = parseFloat(s.replace(/,/g, ""));
+       return (isNaN(num) ? s : num);
 }
 
-function ts_sort_caseinsensitive(a,b) {
-       var aa = a[1].toLowerCase();
-       var bb = b[1].toLowerCase();
-       return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
+function ts_currencyToSortKey( s ) {
+       return ts_parseFloat(s.replace(/[^0-9.,]/g,''));
 }
 
-function ts_sort_default(a,b) {
-       return (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[2] - b[2]);
+function ts_sort_generic(a, b) {
+       return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[2] - b[2];
 }
 
 function ts_alternate(table) {