PrefixSearch: Avoid notice when no subpage exists
[mediawiki.git] / resources / src / mediawiki.language / mediawiki.language.numbers.js
blob56fa0daf93412734c84251df2e627bce35bfce2a
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          * 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                 }
49                 var buf = [];
50                 while (num) {
51                         buf.push( str );
52                         str += str;
53                 }
54                 return buf.join( '' );
55         }
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                 };
78                 if ( isNaN( value) ) {
79                         return value;
80                 }
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 = [];
96                 if ( patternParts[1] ) {
97                         // Pad fractional with trailing zeros
98                         padLength = ( patternParts[1] && patternParts[1].lastIndexOf( '0' ) + 1 );
100                         if ( padLength > fractional.length ) {
101                                 valueParts[1] = pad( fractional, padLength, '0', true );
102                         }
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                 }
114                 // Pad whole with leading zeros
115                 patternDigits = patternParts[0].replace( ',', '' );
117                 padLength = patternDigits.indexOf( '0' );
119                 if ( padLength !== -1 ) {
120                         padLength = patternDigits.length - padLength;
122                         if ( padLength > valueParts[0].length ) {
123                                 valueParts[0] = pad( valueParts[0], padLength );
124                         }
126                         // Truncate whole
127                         if ( patternDigits.indexOf( '#' ) === -1 ) {
128                                 valueParts[0] = valueParts[0].substr( valueParts[0].length - padLength );
129                         }
130                 }
132                 // Add group separators
133                 index = patternParts[0].lastIndexOf( ',' );
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                 }
144                 for ( whole = valueParts[0]; whole; ) {
145                         off = whole.length - groupSize;
147                         pieces.push( ( off > 0 ) ? whole.substr( off ) : whole );
148                         whole = ( off > 0 ) ? whole.slice( 0, off ) : '';
150                         if ( groupSize2 ) {
151                                 groupSize = groupSize2;
152                         }
153                 }
154                 valueParts[0] = pieces.reverse().join( options.group );
156                 return valueParts.join( options.decimal );
157         }
159         $.extend( mw.language, {
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;
171                         pattern = mw.language.getData( mw.config.get( 'wgUserLanguage' ),
172                                 'digitGroupingPattern' ) || '#,##0.###';
174                         // Set the target transform table:
175                         transformTable = mw.language.getDigitTransformTable();
177                         if ( !transformTable ) {
178                                 return num;
179                         }
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                         }
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                 },
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                 },
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                 },
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];
244                         pattern = patternList[ ( value < 0 ) ? 1 : 0] || ( '-' + positivePattern );
245                         numberPattern = positivePattern.match( numberPatternRE );
247                         if ( !numberPattern ) {
248                                 throw new Error( 'unable to find a number expression in pattern: ' + pattern );
249                         }
251                         return pattern.replace( numberPatternRE, commafyNumber( value, numberPattern[0], {
252                                 decimal: decimal,
253                                 group: group
254                         } ) );
255                 }
257         } );
259 }( mediaWiki, jQuery ) );