Merge "SpecialMovepage: Convert form to use OOUI controls"
[lhc/web/wiklou.git] / resources / src / mediawiki.language / mediawiki.language.numbers.js
1 /*
2 * Number-related utilities for mediawiki.language.
3 */
4 ( function ( mw, $ ) {
5 /**
6 * @class mw.language
7 */
8
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 }
21
22 var buf = [];
23 while ( num-- ) {
24 buf.push( str );
25 }
26 return buf.join( '' );
27 }
28
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 }
49
50 var out = String( text ),
51 padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
52
53 return end ? out + padStr : padStr + out;
54 }
55
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 };
76
77 if ( isNaN( value ) ) {
78 return value;
79 }
80
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 = [];
94
95 if ( patternParts[ 1 ] ) {
96 // Pad fractional with trailing zeros
97 padLength = ( patternParts[ 1 ] && patternParts[ 1 ].lastIndexOf( '0' ) + 1 );
98
99 if ( padLength > fractional.length ) {
100 valueParts[ 1 ] = pad( fractional, padLength, '0', true );
101 }
102
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 }
112
113 // Pad whole with leading zeros
114 patternDigits = patternParts[ 0 ].replace( ',', '' );
115
116 padLength = patternDigits.indexOf( '0' );
117
118 if ( padLength !== -1 ) {
119 padLength = patternDigits.length - padLength;
120
121 if ( padLength > valueParts[ 0 ].length ) {
122 valueParts[ 0 ] = pad( valueParts[ 0 ], padLength );
123 }
124
125 // Truncate whole
126 if ( patternDigits.indexOf( '#' ) === -1 ) {
127 valueParts[ 0 ] = valueParts[ 0 ].slice( valueParts[ 0 ].length - padLength );
128 }
129 }
130
131 // Add group separators
132 index = patternParts[ 0 ].lastIndexOf( ',' );
133
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 }
142
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 ) : '';
147
148 if ( groupSize2 ) {
149 groupSize = groupSize2;
150 groupSize2 = null;
151 }
152 }
153 valueParts[ 0 ] = pieces.reverse().join( options.group );
154
155 return valueParts.join( options.decimal );
156 }
157
158 $.extend( mw.language, {
159
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;
169
170 pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
171 'digitGroupingPattern' ) || '#,##0.###';
172
173 // Set the target transform table:
174 transformTable = mw.language.getDigitTransformTable();
175
176 if ( !transformTable ) {
177 return num;
178 }
179
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 }
198
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 },
209
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 },
219
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 },
229
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 ];
248
249 pattern = patternList[ ( value < 0 ) ? 1 : 0 ] || ( '-' + positivePattern );
250 numberPattern = positivePattern.match( numberPatternRE );
251
252 if ( !numberPattern ) {
253 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
254 }
255
256 return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[ 0 ], {
257 decimal: decimal,
258 group: group
259 } ) );
260 }
261
262 } );
263
264 }( mediaWiki, jQuery ) );