sprintf2.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /**
  2. * Original source: https://github.com/alexei/sprintf.js
  3. * Adapted for use in QCAD
  4. */
  5. var re = {
  6. not_string: /[^s]/,
  7. number: /[diefg]/,
  8. json: /[j]/,
  9. not_json: /[^j]/,
  10. text: /^[^\x25]+/,
  11. modulo: /^\x25{2}/,
  12. placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])/,
  13. key: /^([a-z_][a-z_\d]*)/i,
  14. key_access: /^\.([a-z_][a-z_\d]*)/i,
  15. index_access: /^\[(\d+)\]/,
  16. sign: /^[\+\-]/
  17. }
  18. function sprintf2() {
  19. var key = arguments[0], cache = sprintf2.cache
  20. if (!(cache[key] && cache.hasOwnProperty(key))) {
  21. cache[key] = sprintf2.parse(key)
  22. }
  23. return sprintf2.format.call(null, cache[key], arguments)
  24. }
  25. sprintf2.format = function(parse_tree, argv) {
  26. var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ""
  27. for (i = 0; i < tree_length; i++) {
  28. node_type = get_type(parse_tree[i])
  29. if (node_type === "string") {
  30. output[output.length] = parse_tree[i]
  31. }
  32. else if (node_type === "array") {
  33. match = parse_tree[i] // convenience purposes only
  34. if (match[2]) { // keyword argument
  35. arg = argv[cursor]
  36. for (k = 0; k < match[2].length; k++) {
  37. if (!arg.hasOwnProperty(match[2][k])) {
  38. qDebug(sprintf2("[sprintf2] property '%s' does not exist", match[2][k]))
  39. return;
  40. }
  41. arg = arg[match[2][k]]
  42. }
  43. }
  44. else if (match[1]) { // positional argument (explicit)
  45. arg = argv[match[1]]
  46. }
  47. else { // positional argument (implicit)
  48. arg = argv[cursor++]
  49. }
  50. if (get_type(arg) == "function") {
  51. arg = arg()
  52. }
  53. if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
  54. qDebug(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
  55. return;
  56. }
  57. if (re.number.test(match[8])) {
  58. is_positive = arg >= 0
  59. }
  60. switch (match[8]) {
  61. case "b":
  62. arg = arg.toString(2)
  63. break
  64. case "c":
  65. arg = String.fromCharCode(arg)
  66. break
  67. case "d":
  68. case "i":
  69. arg = parseInt(arg, 10)
  70. break
  71. case "j":
  72. arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
  73. break
  74. case "e":
  75. arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential()
  76. break
  77. case "f":
  78. arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
  79. break
  80. case "g":
  81. arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
  82. break
  83. case "o":
  84. arg = arg.toString(8)
  85. break
  86. case "s":
  87. arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)
  88. break
  89. case "u":
  90. arg = arg >>> 0
  91. break
  92. case "x":
  93. arg = arg.toString(16)
  94. break
  95. case "X":
  96. arg = arg.toString(16).toUpperCase()
  97. break
  98. }
  99. if (re.json.test(match[8])) {
  100. output[output.length] = arg
  101. }
  102. else {
  103. if (re.number.test(match[8]) && (!is_positive || match[3])) {
  104. sign = is_positive ? "+" : "-"
  105. arg = arg.toString().replace(re.sign, "")
  106. }
  107. else {
  108. sign = ""
  109. }
  110. pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " "
  111. pad_length = match[6] - (sign + arg).length
  112. pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : "") : ""
  113. output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg)
  114. }
  115. }
  116. }
  117. return output.join("")
  118. }
  119. sprintf2.cache = {}
  120. sprintf2.parse = function(fmt) {
  121. var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
  122. while (_fmt) {
  123. if ((match = re.text.exec(_fmt)) !== null) {
  124. parse_tree[parse_tree.length] = match[0]
  125. }
  126. else if ((match = re.modulo.exec(_fmt)) !== null) {
  127. parse_tree[parse_tree.length] = "%"
  128. }
  129. else if ((match = re.placeholder.exec(_fmt)) !== null) {
  130. if (match[2]) {
  131. arg_names |= 1
  132. var field_list = [], replacement_field = match[2], field_match = []
  133. if ((field_match = re.key.exec(replacement_field)) !== null) {
  134. field_list[field_list.length] = field_match[1]
  135. while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
  136. if ((field_match = re.key_access.exec(replacement_field)) !== null) {
  137. field_list[field_list.length] = field_match[1]
  138. }
  139. else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
  140. field_list[field_list.length] = field_match[1]
  141. }
  142. else {
  143. qDebug("[sprintf2] failed to parse named argument key")
  144. return;
  145. }
  146. }
  147. }
  148. else {
  149. qDebug("[sprintf2] failed to parse named argument key")
  150. return;
  151. }
  152. match[2] = field_list
  153. }
  154. else {
  155. arg_names |= 2
  156. }
  157. if (arg_names === 3) {
  158. qDebug("[sprintf2] mixing positional and named placeholders is not (yet) supported")
  159. return;
  160. }
  161. parse_tree[parse_tree.length] = match
  162. }
  163. else {
  164. qDebug("[sprintf2] unexpected placeholder")
  165. return;
  166. }
  167. _fmt = _fmt.substring(match[0].length)
  168. }
  169. return parse_tree
  170. }
  171. /**
  172. * helpers
  173. */
  174. function get_type(variable) {
  175. return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
  176. }
  177. function str_repeat(input, multiplier) {
  178. return new Array(multiplier + 1).join(input)
  179. }