Wrap changes lists in <div class="mw-changeslist" />
[mediawiki.git] / resources / mediawiki.language / mediawiki.language.numbers.js
blobfada6ce159b1a59406afa68119d77014c34d491a
1 /*
2  * Number related utilities for mediawiki.language
3  */
4 ( function ( mw, $ ) {
6         /**
7          * Pad a string to guarantee that it is at least `size` length by
8          * filling with the character `ch` at either the start or end of the
9          * string. Pads at the start, by default.
10          * example:
11          * Fill the string to length 10 with '+' characters on the right. Yields 'blah++++++'.
12          *  pad('blah', 10, '+', true);
13          *
14          * @param {string} text The string to pad
15          * @param {Number} size To provide padding
16          * @param {string} ch Character to pad, defaults to '0'
17          * @param {Boolean} end Adds padding at the end if true, otherwise pads at start
18          * @return {string}
19          */
20         function pad ( text, size, ch, end ) {
21                 if ( !ch ) {
22                         ch = '0';
23                 }
25                 var out = String( text ),
26                         padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
28                 return end ? out + padStr : padStr + out;
29         }
31         /**
32          * Efficiently replicate a string n times.
33          *
34          * @param {string} str The string to replicate
35          * @param {Number} num Number of times to replicate the string
36          * @return {string}
37          */
38         function replicate ( str, num ) {
39                 if ( num <= 0 || !str ) {
40                         return '';
41                 }
43                 var buf = [];
44                 while (num) {
45                         buf.push( str );
46                         str += str;
47                 }
48                 return buf.join( '' );
49         }
51         /**
52          * Apply numeric pattern to absolute value using options. Gives no
53          * consideration to local customs.
54          *
55          * Adapted from dojo/number library with thanks
56          * http://dojotoolkit.org/reference-guide/1.8/dojo/number.html
57          *
58          * @param {Number} value the number to be formatted, ignores sign
59          * @param {string} pattern the number portion of a pattern (e.g. `#,##0.00`)
60          * @param {string} options.decimalThe decimal separator
61          * @param {string} options.group The group separator
62          *
63          * @return {string}
64          */
65         function commafyNumber( value, pattern, options ) {
66                 options = options || {
67                         group: ',',
68                         decimal: '.'
69                 };
71                 if ( isNaN( value) ) {
72                         return value;
73                 }
75                 var padLength,
76                         patternDigits,
77                         index,
78                         whole,
79                         off,
80                         remainder,
81                         patternParts = pattern.split( '.' ),
82                         maxPlaces = ( patternParts[1] || [] ).length,
83                         valueParts = String( Math.abs( value ) ).split( '.' ),
84                         fractional = valueParts[1] || '',
85                         groupSize = 0,
86                         groupSize2 = 0,
87                         pieces = [];
89                 if ( patternParts[1] ) {
90                         // Pad fractional with trailing zeros
91                         padLength = ( patternParts[1] && patternParts[1].lastIndexOf( '0' ) + 1 );
93                         if ( padLength > fractional.length ) {
94                                 valueParts[1] = pad( fractional, padLength, '0', true );
95                         }
97                         // Truncate fractional
98                         if ( maxPlaces < fractional.length ) {
99                                 valueParts[1] = fractional.substr( 0, maxPlaces );
100                         }
101                 } else {
102                         if ( valueParts[1] ) {
103                                 valueParts.pop();
104                         }
105                 }
107                 // Pad whole with leading zeros
108                 patternDigits = patternParts[0].replace( ',', '' );
110                 padLength = patternDigits.indexOf( '0' );
112                 if ( padLength !== -1 ) {
113                         padLength = patternDigits.length - padLength;
115                         if ( padLength > valueParts[0].length ) {
116                                 valueParts[0] = pad( valueParts[0], padLength );
117                         }
119                         // Truncate whole
120                         if ( patternDigits.indexOf( '#' ) === -1 ) {
121                                 valueParts[0] = valueParts[0].substr( valueParts[0].length - padLength );
122                         }
123                 }
125                 // Add group separators
126                 index = patternParts[0].lastIndexOf( ',' );
128                 if ( index !== -1 ) {
129                         groupSize = patternParts[0].length - index - 1;
130                         remainder = patternParts[0].substr( 0, index );
131                         index = remainder.lastIndexOf( ',' );
132                         if ( index !== -1 ) {
133                                 groupSize2 = remainder.length - index - 1;
134                         }
135                 }
137                 for ( whole = valueParts[0]; whole; ) {
138                         off = whole.length - groupSize;
140                         pieces.push( ( off > 0 ) ? whole.substr( off ) : whole );
141                         whole = ( off > 0 ) ? whole.slice( 0, off ) : '';
143                         if ( groupSize2 ) {
144                                 groupSize = groupSize2;
145                         }
146                 }
147                 valueParts[0] = pieces.reverse().join( options.group );
149                 return valueParts.join( options.decimal );
150         }
152         $.extend( mw.language, {
154                 /**
155                  * Converts a number using digitTransformTable.
156                  *
157                  * @param {Number} num Value to be converted
158                  * @param {boolean} integer Convert the return value to an integer
159                  * @return {Number|string} Formatted number
160                  */
161                 convertNumber: function ( num, integer ) {
162                         var i, tmp, transformTable, numberString, convertedNumber, pattern;
164                         pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
165                                 'digitGroupingPattern' ) || '#,##0.###';
167                         // Set the target transform table:
168                         transformTable = mw.language.getDigitTransformTable();
170                         if ( !transformTable ) {
171                                 return num;
172                         }
174                         // Check if the 'restore' to Latin number flag is set:
175                         if ( integer ) {
176                                 if ( parseInt( num, 10 ) === num ) {
177                                         return num;
178                                 }
179                                 tmp = [];
180                                 for ( i in transformTable ) {
181                                         tmp[ transformTable[ i ] ] = i;
182                                 }
183                                 transformTable = tmp;
184                                 numberString = num + '';
185                         } else {
186                                 numberString = mw.language.commafy( num, pattern );
187                         }
189                         convertedNumber = '';
190                         for ( i = 0; i < numberString.length; i++ ) {
191                                 if ( transformTable[ numberString[i] ] ) {
192                                         convertedNumber += transformTable[numberString[i]];
193                                 } else {
194                                         convertedNumber += numberString[i];
195                                 }
196                         }
197                         return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
198                 },
200                 getDigitTransformTable: function () {
201                         return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
202                                 'digitTransformTable' ) || [];
203                 },
205                 getSeparatorTransformTable: function () {
206                         return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
207                                 'separatorTransformTable' ) || [];
208                 },
210                 /**
211                  * Apply pattern to format value as a string using as per
212                  * unicode.org TR35 - http://www.unicode.org/reports/tr35/#Number_Format_Patterns.
213                  *
214                  * @param {Number} value
215                  * @param {string} pattern Pattern string as described by Unicode TR35
216                  * @throws Error
217                  * @returns {String}
218                  */
219                 commafy: function ( value, pattern ) {
220                         var numberPattern,
221                                 transformTable = mw.language.getSeparatorTransformTable(),
222                                 group = transformTable[','] || ',',
223                                 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
224                                 decimal = transformTable['.'] || '.',
225                                 patternList = pattern.split( ';' ),
226                                 positivePattern = patternList[0];
228                         pattern = patternList[ ( value < 0 ) ? 1 : 0] || ( '-' + positivePattern );
229                         numberPattern = positivePattern.match( numberPatternRE );
231                         if ( !numberPattern ) {
232                                 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
233                         }
235                         return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[0], {
236                                 decimal: decimal,
237                                 group: group
238                         } ) );
239                 }
241         } );
243 }( mediaWiki, jQuery ) );