Language: s/error_log/wfWarn/
[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 * 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 }
29
30 var out = String( text ),
31 padStr = replicate( ch, Math.ceil( ( size - out.length ) / ch.length ) );
32
33 return end ? out + padStr : padStr + out;
34 }
35
36 /**
37 * Efficiently 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 }
48
49 var buf = [];
50 while (num) {
51 buf.push( str );
52 str += str;
53 }
54 return buf.join( '' );
55 }
56
57 /**
58 * Apply numeric pattern to absolute value using options. Gives no
59 * consideration to local customs.
60 *
61 * Adapted from dojo/number library with thanks
62 * <http://dojotoolkit.org/reference-guide/1.8/dojo/number.html>
63 *
64 * @private
65 * @param {number} value the number to be formatted, ignores sign
66 * @param {string} pattern the number portion of a pattern (e.g. `#,##0.00`)
67 * @param {Object} [options] If provided, both option keys must be present:
68 * @param {string} options.decimal The decimal separator. Defaults to: `'.'`.
69 * @param {string} options.group The group separator. Defaults to: `','`.
70 * @return {string}
71 */
72 function commafyNumber( value, pattern, options ) {
73 options = options || {
74 group: ',',
75 decimal: '.'
76 };
77
78 if ( isNaN( value) ) {
79 return value;
80 }
81
82 var padLength,
83 patternDigits,
84 index,
85 whole,
86 off,
87 remainder,
88 patternParts = pattern.split( '.' ),
89 maxPlaces = ( patternParts[1] || [] ).length,
90 valueParts = String( Math.abs( value ) ).split( '.' ),
91 fractional = valueParts[1] || '',
92 groupSize = 0,
93 groupSize2 = 0,
94 pieces = [];
95
96 if ( patternParts[1] ) {
97 // Pad fractional with trailing zeros
98 padLength = ( patternParts[1] && patternParts[1].lastIndexOf( '0' ) + 1 );
99
100 if ( padLength > fractional.length ) {
101 valueParts[1] = pad( fractional, padLength, '0', true );
102 }
103
104 // Truncate fractional
105 if ( maxPlaces < fractional.length ) {
106 valueParts[1] = fractional.substr( 0, maxPlaces );
107 }
108 } else {
109 if ( valueParts[1] ) {
110 valueParts.pop();
111 }
112 }
113
114 // Pad whole with leading zeros
115 patternDigits = patternParts[0].replace( ',', '' );
116
117 padLength = patternDigits.indexOf( '0' );
118
119 if ( padLength !== -1 ) {
120 padLength = patternDigits.length - padLength;
121
122 if ( padLength > valueParts[0].length ) {
123 valueParts[0] = pad( valueParts[0], padLength );
124 }
125
126 // Truncate whole
127 if ( patternDigits.indexOf( '#' ) === -1 ) {
128 valueParts[0] = valueParts[0].substr( valueParts[0].length - padLength );
129 }
130 }
131
132 // Add group separators
133 index = patternParts[0].lastIndexOf( ',' );
134
135 if ( index !== -1 ) {
136 groupSize = patternParts[0].length - index - 1;
137 remainder = patternParts[0].substr( 0, index );
138 index = remainder.lastIndexOf( ',' );
139 if ( index !== -1 ) {
140 groupSize2 = remainder.length - index - 1;
141 }
142 }
143
144 for ( whole = valueParts[0]; whole; ) {
145 off = whole.length - groupSize;
146
147 pieces.push( ( off > 0 ) ? whole.substr( off ) : whole );
148 whole = ( off > 0 ) ? whole.slice( 0, off ) : '';
149
150 if ( groupSize2 ) {
151 groupSize = groupSize2;
152 }
153 }
154 valueParts[0] = pieces.reverse().join( options.group );
155
156 return valueParts.join( options.decimal );
157 }
158
159 $.extend( mw.language, {
160
161 /**
162 * Converts a number using #getDigitTransformTable.
163 *
164 * @param {number} num Value to be converted
165 * @param {boolean} [integer=false] Whether to convert the return value to an integer
166 * @return {number|string} Formatted number
167 */
168 convertNumber: function ( num, integer ) {
169 var i, tmp, transformTable, numberString, convertedNumber, pattern;
170
171 pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
172 'digitGroupingPattern' ) || '#,##0.###';
173
174 // Set the target transform table:
175 transformTable = mw.language.getDigitTransformTable();
176
177 if ( !transformTable ) {
178 return num;
179 }
180
181 // Check if the 'restore' to Latin number flag is set:
182 if ( integer ) {
183 if ( parseInt( num, 10 ) === num ) {
184 return num;
185 }
186 tmp = [];
187 for ( i in transformTable ) {
188 tmp[ transformTable[ i ] ] = i;
189 }
190 transformTable = tmp;
191 numberString = num + '';
192 } else {
193 numberString = mw.language.commafy( num, pattern );
194 }
195
196 convertedNumber = '';
197 for ( i = 0; i < numberString.length; i++ ) {
198 if ( transformTable[ numberString[i] ] ) {
199 convertedNumber += transformTable[numberString[i]];
200 } else {
201 convertedNumber += numberString[i];
202 }
203 }
204 return integer ? parseInt( convertedNumber, 10 ) : convertedNumber;
205 },
206
207 /**
208 * Get the digit transform table for current UI language.
209 * @return {Object|Array}
210 */
211 getDigitTransformTable: function () {
212 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
213 'digitTransformTable' ) || [];
214 },
215
216 /**
217 * Get the separator transform table for current UI language.
218 * @return {Object|Array}
219 */
220 getSeparatorTransformTable: function () {
221 return mw.language.getData( mw.config.get( 'wgUserLanguage' ),
222 'separatorTransformTable' ) || [];
223 },
224
225 /**
226 * Apply pattern to format value as a string.
227 *
228 * Using patterns from [Unicode TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns).
229 *
230 * @param {number} value
231 * @param {string} pattern Pattern string as described by Unicode TR35
232 * @throws {Error} If unable to find a number expression in `pattern`.
233 * @return {string}
234 */
235 commafy: function ( value, pattern ) {
236 var numberPattern,
237 transformTable = mw.language.getSeparatorTransformTable(),
238 group = transformTable[','] || ',',
239 numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/, // not precise, but good enough
240 decimal = transformTable['.'] || '.',
241 patternList = pattern.split( ';' ),
242 positivePattern = patternList[0];
243
244 pattern = patternList[ ( value < 0 ) ? 1 : 0] || ( '-' + positivePattern );
245 numberPattern = positivePattern.match( numberPatternRE );
246
247 if ( !numberPattern ) {
248 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
249 }
250
251 return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[0], {
252 decimal: decimal,
253 group: group
254 } ) );
255 }
256
257 } );
258
259 }( mediaWiki, jQuery ) );