source: Dev/trunk/src/client/dojox/string/sprintf.js @ 536

Last change on this file since 536 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 11.6 KB
Line 
1define([
2        "dojo/_base/kernel",    // dojo.getObject, dojo.mixin
3        "dojo/_base/lang",      // dojo.extend
4        "dojo/_base/sniff",     // dojo.isOpera
5         "./tokenize"
6], function(dojo, lang, has, tokenize){
7        var strLib = lang.getObject("string", true, dojox);
8
9        strLib.sprintf = function(/*String*/ format, /*mixed...*/ filler){
10                for(var args = [], i = 1; i < arguments.length; i++){
11                        args.push(arguments[i]);
12                }
13                var formatter = new strLib.sprintf.Formatter(format);
14                return formatter.format.apply(formatter, args);
15        };
16
17        strLib.sprintf.Formatter = function(/*String*/ format){
18                var tokens = [];
19                this._mapped = false;
20                this._format = format;
21                this._tokens = tokenize(format, this._re, this._parseDelim, this);
22        };
23
24        lang.extend(strLib.sprintf.Formatter, {
25                _re: /\%(?:\(([\w_]+)\)|([1-9]\d*)\$)?([0 +\-\#]*)(\*|\d+)?(\.)?(\*|\d+)?[hlL]?([\%scdeEfFgGiouxX])/g,
26                _parseDelim: function(mapping, intmapping, flags, minWidth, period, precision, specifier){
27                        if(mapping){
28                                this._mapped = true;
29                        }
30                        return {
31                                mapping: mapping,
32                                intmapping: intmapping,
33                                flags: flags,
34                                _minWidth: minWidth, // May be dependent on parameters
35                                period: period,
36                                _precision: precision, // May be dependent on parameters
37                                specifier: specifier
38                        };
39                },
40                _specifiers: {
41                        b: {
42                                base: 2,
43                                isInt: true
44                        },
45                        o: {
46                                base: 8,
47                                isInt: true
48                        },
49                        x: {
50                                base: 16,
51                                isInt: true
52                        },
53                        X: {
54                                extend: ["x"],
55                                toUpper: true
56                        },
57                        d: {
58                                base: 10,
59                                isInt: true
60                        },
61                        i: {
62                                extend: ["d"]
63                        },
64                        u: {
65                                extend: ["d"],
66                                isUnsigned: true
67                        },
68                        c: {
69                                setArg: function(token){
70                                        if(!isNaN(token.arg)){
71                                                var num = parseInt(token.arg);
72                                                if(num < 0 || num > 127){
73                                                        throw new Error("invalid character code passed to %c in sprintf");
74                                                }
75                                                token.arg = isNaN(num) ? "" + num : String.fromCharCode(num);
76                                        }
77                                }
78                        },
79                        s: {
80                                setMaxWidth: function(token){
81                                        token.maxWidth = (token.period == ".") ? token.precision : -1;
82                                }
83                        },
84                        e: {
85                                isDouble: true,
86                                doubleNotation: "e"
87                        },
88                        E: {
89                                extend: ["e"],
90                                toUpper: true
91                        },
92                        f: {
93                                isDouble: true,
94                                doubleNotation: "f"
95                        },
96                        F: {
97                                extend: ["f"]
98                        },
99                        g: {
100                                isDouble: true,
101                                doubleNotation: "g"
102                        },
103                        G: {
104                                extend: ["g"],
105                                toUpper: true
106                        }
107                },
108                format: function(/*mixed...*/ filler){
109                        if(this._mapped && typeof filler != "object"){
110                                throw new Error("format requires a mapping");
111                        }
112
113                        var str = "";
114                        var position = 0;
115                        for(var i = 0, token; i < this._tokens.length; i++){
116                                token = this._tokens[i];
117                                if(typeof token == "string"){
118                                        str += token;
119                                }else{
120                                        if(this._mapped){
121                                                if(typeof filler[token.mapping] == "undefined"){
122                                                        throw new Error("missing key " + token.mapping);
123                                                }
124                                                token.arg = filler[token.mapping];
125                                        }else{
126                                                if(token.intmapping){
127                                                        var position = parseInt(token.intmapping) - 1;
128                                                }
129                                                if(position >= arguments.length){
130                                                        throw new Error("got " + arguments.length + " printf arguments, insufficient for '" + this._format + "'");
131                                                }
132                                                token.arg = arguments[position++];
133                                        }
134
135                                        if(!token.compiled){
136                                                token.compiled = true;
137                                                token.sign = "";
138                                                token.zeroPad = false;
139                                                token.rightJustify = false;
140                                                token.alternative = false;
141
142                                                var flags = {};
143                                                for(var fi = token.flags.length; fi--;){
144                                                        var flag = token.flags.charAt(fi);
145                                                        flags[flag] = true;
146                                                        switch(flag){
147                                                                case " ":
148                                                                        token.sign = " ";
149                                                                        break;
150                                                                case "+":
151                                                                        token.sign = "+";
152                                                                        break;
153                                                                case "0":
154                                                                        token.zeroPad = (flags["-"]) ? false : true;
155                                                                        break;
156                                                                case "-":
157                                                                        token.rightJustify = true;
158                                                                        token.zeroPad = false;
159                                                                        break;
160                                                                case "\#":
161                                                                        token.alternative = true;
162                                                                        break;
163                                                                default:
164                                                                        throw Error("bad formatting flag '" + token.flags.charAt(fi) + "'");
165                                                        }
166                                                }
167
168                                                token.minWidth = (token._minWidth) ? parseInt(token._minWidth) : 0;
169                                                token.maxWidth = -1;
170                                                token.toUpper = false;
171                                                token.isUnsigned = false;
172                                                token.isInt = false;
173                                                token.isDouble = false;
174                                                token.precision = 1;
175                                                if(token.period == '.'){
176                                                        if(token._precision){
177                                                                token.precision = parseInt(token._precision);
178                                                        }else{
179                                                                token.precision = 0;
180                                                        }
181                                                }
182
183                                                var mixins = this._specifiers[token.specifier];
184                                                if(typeof mixins == "undefined"){
185                                                        throw new Error("unexpected specifier '" + token.specifier + "'");
186                                                }
187                                                if(mixins.extend){
188                                                        lang.mixin(mixins, this._specifiers[mixins.extend]);
189                                                        delete mixins.extend;
190                                                }
191                                                lang.mixin(token, mixins);
192                                        }
193
194                                        if(typeof token.setArg == "function"){
195                                                token.setArg(token);
196                                        }
197
198                                        if(typeof token.setMaxWidth == "function"){
199                                                token.setMaxWidth(token);
200                                        }
201
202                                        if(token._minWidth == "*"){
203                                                if(this._mapped){
204                                                        throw new Error("* width not supported in mapped formats");
205                                                }
206                                                token.minWidth = parseInt(arguments[position++]);
207                                                if(isNaN(token.minWidth)){
208                                                        throw new Error("the argument for * width at position " + position + " is not a number in " + this._format);
209                                                }
210                                                // negative width means rightJustify
211                                                if (token.minWidth < 0) {
212                                                        token.rightJustify = true;
213                                                        token.minWidth = -token.minWidth;
214                                                }
215                                        }
216
217                                        if(token._precision == "*" && token.period == "."){
218                                                if(this._mapped){
219                                                        throw new Error("* precision not supported in mapped formats");
220                                                }
221                                                token.precision = parseInt(arguments[position++]);
222                                                if(isNaN(token.precision)){
223                                                        throw Error("the argument for * precision at position " + position + " is not a number in " + this._format);
224                                                }
225                                                // negative precision means unspecified
226                                                if (token.precision < 0) {
227                                                        token.precision = 1;
228                                                        token.period = '';
229                                                }
230                                        }
231
232                                        if(token.isInt){
233                                                // a specified precision means no zero padding
234                                                if(token.period == '.'){
235                                                        token.zeroPad = false;
236                                                }
237                                                this.formatInt(token);
238                                        }else if(token.isDouble){
239                                                if(token.period != '.'){
240                                                        token.precision = 6;
241                                                }
242                                                this.formatDouble(token);
243                                        }
244                                        this.fitField(token);
245
246                                        str += "" + token.arg;
247                                }
248                        }
249
250                        return str;
251                },
252                _zeros10: '0000000000',
253                _spaces10: '          ',
254                formatInt: function(token) {
255                        var i = parseInt(token.arg);
256                        if(!isFinite(i)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
257                                // allow this only if arg is number
258                                if(typeof token.arg != "number"){
259                                        throw new Error("format argument '" + token.arg + "' not an integer; parseInt returned " + i);
260                                }
261                                //return '' + i;
262                                i = 0;
263                        }
264
265                        // if not base 10, make negatives be positive
266                        // otherwise, (-10).toString(16) is '-a' instead of 'fffffff6'
267                        if(i < 0 && (token.isUnsigned || token.base != 10)){
268                                i = 0xffffffff + i + 1;
269                        }
270
271                        if(i < 0){
272                                token.arg = (- i).toString(token.base);
273                                this.zeroPad(token);
274                                token.arg = "-" + token.arg;
275                        }else{
276                                token.arg = i.toString(token.base);
277                                // need to make sure that argument 0 with precision==0 is formatted as ''
278                                if(!i && !token.precision){
279                                        token.arg = "";
280                                }else{
281                                        this.zeroPad(token);
282                                }
283                                if(token.sign){
284                                        token.arg = token.sign + token.arg;
285                                }
286                        }
287                        if(token.base == 16){
288                                if(token.alternative){
289                                        token.arg = '0x' + token.arg;
290                                }
291                                token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
292                        }
293                        if(token.base == 8){
294                                if(token.alternative && token.arg.charAt(0) != '0'){
295                                        token.arg = '0' + token.arg;
296                                }
297                        }
298                },
299                formatDouble: function(token) {
300                        var f = parseFloat(token.arg);
301                        if(!isFinite(f)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
302                                // allow this only if arg is number
303                                if(typeof token.arg != "number"){
304                                        throw new Error("format argument '" + token.arg + "' not a float; parseFloat returned " + f);
305                                }
306                                // C99 says that for 'f':
307                                //       infinity -> '[-]inf' or '[-]infinity' ('[-]INF' or '[-]INFINITY' for 'F')
308                                //       NaN -> a string  starting with 'nan' ('NAN' for 'F')
309                                // this is not commonly implemented though.
310                                //return '' + f;
311                                f = 0;
312                        }
313
314                        switch(token.doubleNotation) {
315                                case 'e': {
316                                        token.arg = f.toExponential(token.precision);
317                                        break;
318                                }
319                                case 'f': {
320                                        token.arg = f.toFixed(token.precision);
321                                        break;
322                                }
323                                case 'g': {
324                                        // C says use 'e' notation if exponent is < -4 or is >= prec
325                                        // ECMAScript for toPrecision says use exponential notation if exponent is >= prec,
326                                        // though step 17 of toPrecision indicates a test for < -6 to force exponential.
327                                        if(Math.abs(f) < 0.0001){
328                                                //print("forcing exponential notation for f=" + f);
329                                                token.arg = f.toExponential(token.precision > 0 ? token.precision - 1 : token.precision);
330                                        }else{
331                                                token.arg = f.toPrecision(token.precision);
332                                        }
333
334                                        // In C, unlike 'f', 'gG' removes trailing 0s from fractional part, unless alternative format flag ("#").
335                                        // But ECMAScript formats toPrecision as 0.00100000. So remove trailing 0s.
336                                        if(!token.alternative){
337                                                //print("replacing trailing 0 in '" + s + "'");
338                                                token.arg = token.arg.replace(/(\..*[^0])0*/, "$1");
339                                                // if fractional part is entirely 0, remove it and decimal point
340                                                token.arg = token.arg.replace(/\.0*e/, 'e').replace(/\.0$/,'');
341                                        }
342                                        break;
343                                }
344                                default: throw new Error("unexpected double notation '" + token.doubleNotation + "'");
345                        }
346
347                        // C says that exponent must have at least two digits.
348                        // But ECMAScript does not; toExponential results in things like "1.000000e-8" and "1.000000e+8".
349                        // Note that s.replace(/e([\+\-])(\d)/, "e$10$2") won't work because of the "$10" instead of "$1".
350                        // And replace(re, func) isn't supported on IE50 or Safari1.
351                        token.arg = token.arg.replace(/e\+(\d)$/, "e+0$1").replace(/e\-(\d)$/, "e-0$1");
352
353                        // Ensure a '0' before the period.
354                        // Opera implements (0.001).toString() as '0.001', but (0.001).toFixed(1) is '.001'
355                        if(has("opera")){
356                                token.arg = token.arg.replace(/^\./, '0.');
357                        }
358
359                        // if alt, ensure a decimal point
360                        if(token.alternative){
361                                token.arg = token.arg.replace(/^(\d+)$/,"$1.");
362                                token.arg = token.arg.replace(/^(\d+)e/,"$1.e");
363                        }
364
365                        if(f >= 0 && token.sign){
366                                token.arg = token.sign + token.arg;
367                        }
368
369                        token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
370                },
371                zeroPad: function(token, /*Int*/ length) {
372                        length = (arguments.length == 2) ? length : token.precision;
373                        if(typeof token.arg != "string"){
374                                token.arg = "" + token.arg;
375                        }
376
377                        var tenless = length - 10;
378                        while(token.arg.length < tenless){
379                                token.arg = (token.rightJustify) ? token.arg + this._zeros10 : this._zeros10 + token.arg;
380                        }
381                        var pad = length - token.arg.length;
382                        token.arg = (token.rightJustify) ? token.arg + this._zeros10.substring(0, pad) : this._zeros10.substring(0, pad) + token.arg;
383                },
384                fitField: function(token) {
385                        if(token.maxWidth >= 0 && token.arg.length > token.maxWidth){
386                                return token.arg.substring(0, token.maxWidth);
387                        }
388                        if(token.zeroPad){
389                                this.zeroPad(token, token.minWidth);
390                                return;
391                        }
392                        this.spacePad(token);
393                },
394                spacePad: function(token, /*Int*/ length) {
395                        length = (arguments.length == 2) ? length : token.minWidth;
396                        if(typeof token.arg != 'string'){
397                                token.arg = '' + token.arg;
398                        }
399
400                        var tenless = length - 10;
401                        while(token.arg.length < tenless){
402                                token.arg = (token.rightJustify) ? token.arg + this._spaces10 : this._spaces10 + token.arg;
403                        }
404                        var pad = length - token.arg.length;
405                        token.arg = (token.rightJustify) ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg;
406                }
407        });
408        return strLib.sprintf;
409});
Note: See TracBrowser for help on using the repository browser.