Implement extension registration from an extension.json file
[mediawiki.git] / resources / src / mediawiki.language / mediawiki.language.numbers.js
bloba0b814108e6e827075da95730e75aa3187b1acfa
1 /*
2  * Number-related utilities for mediawiki.language.
3  */
4 ( function ( mw, $ ) {
5         /**
6          * @class mw.language
7          */
9         /**
10          * Pad a string to guarantee that it is at least `size` length by
11          * filling with the character `ch` at either the start or end of the
12          * string. Pads at the start, by default.
13          *
14          * Example: Fill the string to length 10 with '+' characters on the right.
15          *
16          *     pad( 'blah', 10, '+', true ); // => 'blah++++++'
17          *
18          * @private
19          * @param {string} text The string to pad
20          * @param {number} size The length to pad to
21          * @param {string} [ch='0'] Character to pad with
22          * @param {boolean} [end=false] Adds padding at the end if true, otherwise pads at start
23          * @return {string}
24          */
25         function pad( text, size, ch, end ) {
26                 if ( !ch ) {
27                         ch = '0';
28                 }
30                 var out = String( text ),
31                         padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
33                 return end ? out + padStr : padStr + out;
34         }
36         /**
37          * Replicate a string 'n' times.
38          *
39          * @private
40          * @param {string} str The string to replicate
41          * @param {number} num Number of times to replicate the string
42          * @return {string}
43          */
44         function replicate( str, num ) {
45                 if ( num <= 0 || !str ) {
46                         return '';
47                 }
49                 var buf = [];
50                 while ( num-- ) {
51                         buf.push( str );
52                 }
53                 return buf.join( '' );
54         }
56         /**
57          * Apply numeric pattern to absolute value using options. Gives no
58          * consideration to local customs.
59          *
60          * Adapted from dojo/number library with thanks
61          * <http://dojotoolkit.org/reference-guide/1.8/dojo/number.html>
62          *
63          * @private
64          * @param {number} value the number to be formatted, ignores sign
65          * @param {string} pattern the number portion of a pattern (e.g. `#,##0.00`)
66          * @param {Object} [options] If provided, both option keys must be present:
67          * @param {string} options.decimal The decimal separator. Defaults to: `'.'`.
68          * @param {string} options.group The group separator. Defaults to: `','`.
69          * @return {string}
70          */
71         function commafyNumber( value, pattern, options ) {
72                 options = options || {
73                         group: ',',
74                         decimal: '.'
75                 };
77                 if ( isNaN( value) ) {
78                         return value;
79                 }
81                 var padLength,
82                         patternDigits,
83                         index,
84                         whole,
85                         off,
86                         remainder,
87                         patternParts = pattern.split( '.' ),
88                         maxPlaces = ( patternParts[1] || [] ).length,
89                         valueParts = String( Math.abs( value ) ).split( '.' ),
90                         fractional = valueParts[1] || '',
91                         groupSize = 0,
92                         groupSize2 = 0,
93                         pieces = [];
95                 if ( patternParts[1] ) {
96                         // Pad fractional with trailing zeros
97                         padLength = ( patternParts[1] && patternParts[1].lastIndexOf( '0' ) + 1 );
99                         if ( padLength > fractional.length ) {
100                                 valueParts[1] = pad( fractional, padLength, '0', true );
101                         }
103                         // Truncate fractional
104                         if ( maxPlaces < fractional.length ) {
105                                 valueParts[1] = fractional.slice( 0, maxPlaces );
106                         }
107                 } else {
108                         if ( valueParts[1] ) {
109                                 valueParts.pop();
110                         }
111                 }
113                 // Pad whole with leading zeros
114                 patternDigits = patternParts[0].replace( ',', '' );
116                 padLength = patternDigits.indexOf( '0' );
118                 if ( padLength !== -1 ) {
119                         padLength = patternDigits.length - padLength;
121                         if ( padLength > valueParts[0].length ) {
122                                 valueParts[0] = pad( valueParts[0], padLength );
123                         }
125                         // Truncate whole
126                         if ( patternDigits.indexOf( '#' ) === -1 ) {
127                                 valueParts[0] = valueParts[0].slice( valueParts[0].length - padLength );
128                         }
129                 }
131                 // Add group separators
132                 index = patternParts[0].lastIndexOf( ',' );
134                 if ( index !== -1 ) {
135                         groupSize = patternParts[0].length - index - 1;
136                         remainder = patternParts[0].slice( 0, index );
137                         index = remainder.lastIndexOf( ',' );
138                         if ( index !== -1 ) {
139                                 groupSize2 = remainder.length - index - 1;
140                         }
141                 }
143                 for ( whole = valueParts[0]; whole; ) {
144                         off = groupSize ? whole.length - groupSize : 0;
145                         pieces.push( ( off > 0 ) ? whole.slice( off ) : whole );
146                         whole = ( off > 0 ) ? whole.slice( 0, off ) : '';
148                         if ( groupSize2 ) {
149                                 groupSize = groupSize2;
150                                 groupSize2 = null;
151                         }
152                 }
153                 valueParts[0] = pieces.reverse().join( options.group );
155                 return valueParts.join( options.decimal );
156         }
158         $.extend( mw.language, {
160                 /**
161                  * Converts a number using #getDigitTransformTable.
162                  *
163                  * @param {number} num Value to be converted
164                  * @param {boolean} [integer=false] Whether to convert the return value to an integer
165                  * @return {number|string} Formatted number
166                  */
167                 convertNumber: function ( num, integer ) {
168                         var i, tmp, transformTable, numberString, convertedNumber, pattern;
170                         pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
171                                 'digitGroupingPattern' ) || '#,##0.###';
173                         // Set the target transform table:
174                         transformTable = mw.language.getDigitTransformTable();
176                         if ( !transformTable ) {
177                                 return num;
178                         }
180                         // Check if the 'restore' to Latin number flag is set:
181                         if ( integer ) {
182                                 if ( parseInt( num, 10 ) === num ) {
183                                         return num;
184                                 }
185                                 tmp = [];
186                                 for ( i in transformTable ) {
187                                         tmp[ transformTable[ i ] ] = i;
188                                 }
189                                 transformTable = tmp;
190                                 numberString = num + '';
191                         } else {
192                                 numberString = mw.language.commafy( num, pattern );
193                         }
195                         convertedNumber = '';
196                         for ( i = 0; i < numberString.length; i++ ) {
197                                 if ( transformTable[ numberString[i] ] ) {
198                                         convertedNumber += transformTable[numberString[i]];
199                                 } else {
200                                         convertedNumber += numberString[i];
201                                 }
202                         }
203                         return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
204                 },
206                 /**
207                  * Get the  digit transform table for current UI language.
208                  * @return {Object|Array}
209                  */
210                 getDigitTransformTable: function () {
211                         return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
212                                 'digitTransformTable' ) || [];
213                 },
215                 /**
216                  * Get the  separator transform table for current UI language.
217                  * @return {Object|Array}
218                  */
219                 getSeparatorTransformTable: function () {
220                         return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
221                                 'separatorTransformTable' ) || [];
222                 },
224                 /**
225                  * Apply pattern to format value as a string.
226                  *
227                  * Using patterns from [Unicode TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
228                  *
229                  * @param {number} value
230                  * @param {string} pattern Pattern string as described by Unicode TR35
231                  * @throws {Error} If unable to find a number expression in `pattern`.
232                  * @return {string}
233                  */
234                 commafy: function ( value, pattern ) {
235                         var numberPattern,
236                                 transformTable = mw.language.getSeparatorTransformTable(),
237                                 group = transformTable[','] || ',',
238                                 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
239                                 decimal = transformTable['.'] || '.',
240                                 patternList = pattern.split( ';' ),
241                                 positivePattern = patternList[0];
243                         pattern = patternList[ ( value < 0 ) ? 1 : 0] || ( '-' + positivePattern );
244                         numberPattern = positivePattern.match( numberPatternRE );
246                         if ( !numberPattern ) {
247                                 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
248                         }
250                         return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[0], {
251                                 decimal: decimal,
252                                 group: group
253                         } ) );
254                 }
256         } );
258 }( mediaWiki, jQuery ) );