Merge "Import: Handle uploads with sha1 starting with 0 properly"
[mediawiki.git] / resources / src / mediawiki.language / mediawiki.language.numbers.js
blob268985f8e41e073c3bff16fdc882839341bd0283
1 /*
2  * Number-related utilities for mediawiki.language.
3  */
4 ( function ( mw, $ ) {
5         /**
6          * @class mw.language
7          */
9         /**
10          * Replicate a string 'n' times.
11          *
12          * @private
13          * @param {string} str The string to replicate
14          * @param {number} num Number of times to replicate the string
15          * @return {string}
16          */
17         function replicate( str, num ) {
18                 if ( num <= 0 || !str ) {
19                         return '';
20                 }
22                 var buf = [];
23                 while ( num-- ) {
24                         buf.push( str );
25                 }
26                 return buf.join( '' );
27         }
29         /**
30          * Pad a string to guarantee that it is at least `size` length by
31          * filling with the character `ch` at either the start or end of the
32          * string. Pads at the start, by default.
33          *
34          * Example: Fill the string to length 10 with '+' characters on the right.
35          *
36          *     pad( 'blah', 10, '+', true ); // => 'blah++++++'
37          *
38          * @private
39          * @param {string} text The string to pad
40          * @param {number} size The length to pad to
41          * @param {string} [ch='0'] Character to pad with
42          * @param {boolean} [end=false] Adds padding at the end if true, otherwise pads at start
43          * @return {string}
44          */
45         function pad( text, size, ch, end ) {
46                 if ( !ch ) {
47                         ch = '0';
48                 }
50                 var out = String( text ),
51                         padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
53                 return end ? out + padStr : padStr + out;
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 = String( num );
191                         } else {
192                                 // Ignore transform table if wgTranslateNumerals is false
193                                 if ( !mw.config.get( 'wgTranslateNumerals' ) ) {
194                                         transformTable = [];
195                                 }
196                                 numberString = mw.language.commafy( num, pattern );
197                         }
199                         convertedNumber = '';
200                         for ( i = 0; i < numberString.length; i++ ) {
201                                 if ( transformTable[ numberString[ i ] ] ) {
202                                         convertedNumber += transformTable[ numberString[ i ] ];
203                                 } else {
204                                         convertedNumber += numberString[ i ];
205                                 }
206                         }
207                         return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
208                 },
210                 /**
211                  * Get the digit transform table for current UI language.
212                  *
213                  * @return {Object|Array}
214                  */
215                 getDigitTransformTable: function () {
216                         return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
217                                 'digitTransformTable' ) || [];
218                 },
220                 /**
221                  * Get the separator transform table for current UI language.
222                  *
223                  * @return {Object|Array}
224                  */
225                 getSeparatorTransformTable: function () {
226                         return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
227                                 'separatorTransformTable' ) || [];
228                 },
230                 /**
231                  * Apply pattern to format value as a string.
232                  *
233                  * Using patterns from [Unicode TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
234                  *
235                  * @param {number} value
236                  * @param {string} pattern Pattern string as described by Unicode TR35
237                  * @throws {Error} If unable to find a number expression in `pattern`.
238                  * @return {string}
239                  */
240                 commafy: function ( value, pattern ) {
241                         var numberPattern,
242                                 transformTable = mw.language.getSeparatorTransformTable(),
243                                 group = transformTable[ ',' ] || ',',
244                                 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
245                                 decimal = transformTable[ '.' ] || '.',
246                                 patternList = pattern.split( ';' ),
247                                 positivePattern = patternList[ 0 ];
249                         pattern = patternList[ ( value < 0 ) ? 1 : 0 ] || ( '-' + positivePattern );
250                         numberPattern = positivePattern.match( numberPatternRE );
252                         if ( !numberPattern ) {
253                                 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
254                         }
256                         return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[ 0 ], {
257                                 decimal: decimal,
258                                 group: group
259                         } ) );
260                 }
262         } );
264 }( mediaWiki, jQuery ) );