source: Dev/branches/rest-dojo-ui/client/dojox/string/sprintf.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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.