sprintf.js 12 KB

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