sprintf.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /**
  2. * Copyright (c) 2010 Jakob Westhoff
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. * THE SOFTWARE.
  21. */
  22. (function(root, factory) {
  23. // CommonJS module
  24. if (typeof module != 'undefined') {
  25. module.exports = factory();
  26. }
  27. // AMD
  28. else if (typeof define == 'function' && typeof define.amd == 'object') {
  29. define(factory);
  30. }
  31. // Global
  32. else {
  33. this.sprintf = factory();
  34. }
  35. }(this, function() {
  36. var sprintf = function( format ) {
  37. // Check for format definition
  38. if ( typeof format != 'string' ) {
  39. throw "sprintf: The first arguments need to be a valid format string.";
  40. }
  41. /**
  42. * Define the regex to match a formating string
  43. * The regex consists of the following parts:
  44. * percent sign to indicate the start
  45. * (optional) sign specifier
  46. * (optional) padding specifier
  47. * (optional) alignment specifier
  48. * (optional) width specifier
  49. * (optional) precision specifier
  50. * type specifier:
  51. * % - literal percent sign
  52. * b - binary number
  53. * c - ASCII character represented by the given value
  54. * d - signed decimal number
  55. * f - floating point value
  56. * o - octal number
  57. * s - string
  58. * x - hexadecimal number (lowercase characters)
  59. * X - hexadecimal number (uppercase characters)
  60. */
  61. var r = new RegExp( /%(\+)?([0 ]|'(.))?(-)?([0-9]+)?(\.([0-9]+))?([%bcdfosxX])/g );
  62. /**
  63. * Each format string is splitted into the following parts:
  64. * 0: Full format string
  65. * 1: sign specifier (+)
  66. * 2: padding specifier (0/<space>/'<any char>)
  67. * 3: if the padding character starts with a ' this will be the real
  68. * padding character
  69. * 4: alignment specifier
  70. * 5: width specifier
  71. * 6: precision specifier including the dot
  72. * 7: precision specifier without the dot
  73. * 8: type specifier
  74. */
  75. var parts = [];
  76. var paramIndex = 1;
  77. while ( part = r.exec( format ) ) {
  78. // Check if an input value has been provided, for the current
  79. // format string (no argument needed for %%)
  80. if ( ( paramIndex >= arguments.length ) && ( part[8] != '%' ) ) {
  81. throw "sprintf: At least one argument was missing.";
  82. }
  83. parts[parts.length] = {
  84. /* beginning of the part in the string */
  85. begin: part.index,
  86. /* end of the part in the string */
  87. end: part.index + part[0].length,
  88. /* force sign */
  89. sign: ( part[1] == '+' ),
  90. /* is the given data negative */
  91. negative: ( parseFloat( arguments[paramIndex] ) < 0 ) ? true : false,
  92. /* padding character (default: <space>) */
  93. padding: ( part[2] == undefined )
  94. ? ( ' ' ) /* default */
  95. : ( ( part[2].substring( 0, 1 ) == "'" )
  96. ? ( part[3] ) /* use special char */
  97. : ( part[2] ) /* use normal <space> or zero */
  98. ),
  99. /* should the output be aligned left?*/
  100. alignLeft: ( part[4] == '-' ),
  101. /* width specifier (number or false) */
  102. width: ( part[5] != undefined ) ? part[5] : false,
  103. /* precision specifier (number or false) */
  104. precision: ( part[7] != undefined ) ? part[7] : false,
  105. /* type specifier */
  106. type: part[8],
  107. /* the given data associated with this part converted to a string */
  108. data: ( part[8] != '%' ) ? String ( arguments[paramIndex++] ) : false
  109. };
  110. }
  111. var newString = "";
  112. var start = 0;
  113. // Generate our new formated string
  114. for( var i=0; i<parts.length; ++i ) {
  115. // Add first unformated string part
  116. newString += format.substring( start, parts[i].begin );
  117. // Mark the new string start
  118. start = parts[i].end;
  119. // Create the appropriate preformat substitution
  120. // This substitution is only the correct type conversion. All the
  121. // different options and flags haven't been applied to it at this
  122. // point
  123. var preSubstitution = "";
  124. switch ( parts[i].type ) {
  125. case '%':
  126. preSubstitution = "%";
  127. break;
  128. case 'b':
  129. preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 2 );
  130. break;
  131. case 'c':
  132. preSubstitution = String.fromCharCode( Math.abs( parseInt( parts[i].data ) ) );
  133. break;
  134. case 'd':
  135. preSubstitution = String( Math.abs( parseInt( parts[i].data ) ) );
  136. break;
  137. case 'f':
  138. preSubstitution = ( parts[i].precision === false )
  139. ? ( String( ( Math.abs( parseFloat( parts[i].data ) ) ) ) )
  140. : ( Math.abs( parseFloat( parts[i].data ) ).toFixed( parts[i].precision ) );
  141. break;
  142. case 'o':
  143. preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 8 );
  144. break;
  145. case 's':
  146. preSubstitution = parts[i].data.substring( 0, parts[i].precision ? parts[i].precision : parts[i].data.length ); /* Cut if precision is defined */
  147. break;
  148. case 'x':
  149. preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 16 ).toLowerCase();
  150. break;
  151. case 'X':
  152. preSubstitution = Math.abs( parseInt( parts[i].data ) ).toString( 16 ).toUpperCase();
  153. break;
  154. default:
  155. throw 'sprintf: Unknown type "' + parts[i].type + '" detected. This should never happen. Maybe the regex is wrong.';
  156. }
  157. // The % character is a special type and does not need further processing
  158. if ( parts[i].type == "%" ) {
  159. newString += preSubstitution;
  160. continue;
  161. }
  162. // Modify the preSubstitution by taking sign, padding and width
  163. // into account
  164. // Pad the string based on the given width
  165. if ( parts[i].width != false ) {
  166. // Padding needed?
  167. if ( parts[i].width > preSubstitution.length )
  168. {
  169. var origLength = preSubstitution.length;
  170. for( var j = 0; j < parts[i].width - origLength; ++j )
  171. {
  172. preSubstitution = ( parts[i].alignLeft == true )
  173. ? ( preSubstitution + parts[i].padding )
  174. : ( parts[i].padding + preSubstitution );
  175. }
  176. }
  177. }
  178. // Add a sign symbol if neccessary or enforced, but only if we are
  179. // not handling a string
  180. if ( parts[i].type == 'b'
  181. || parts[i].type == 'd'
  182. || parts[i].type == 'o'
  183. || parts[i].type == 'f'
  184. || parts[i].type == 'x'
  185. || parts[i].type == 'X' ) {
  186. if ( parts[i].negative == true ) {
  187. preSubstitution = "-" + preSubstitution;
  188. }
  189. else if ( parts[i].sign == true ) {
  190. preSubstitution = "+" + preSubstitution;
  191. }
  192. }
  193. // Add the substitution to the new string
  194. newString += preSubstitution;
  195. }
  196. // Add the last part of the given format string, which may still be there
  197. newString += format.substring( start, format.length );
  198. return newString;
  199. };
  200. // Allow the sprintf function to be attached to any string or the string prototype
  201. sprintf.attach = function(target) {
  202. target.printf = function() {
  203. var newArguments = Array.prototype.slice.call( arguments );
  204. newArguments.unshift( String( this ) );
  205. return sprintf.apply( undefined, newArguments );
  206. };
  207. };
  208. // Export the sprintf function to the outside world
  209. return sprintf;
  210. }));