src/cm/media/js/lib/flexible-js-formatting/numbers/number-functions.js
changeset 0 40c8f766c9b8
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 /*
       
     2  * Copyright (C) 2006 Baron Schwartz <baron at sequent dot org>
       
     3  *
       
     4  * This program is free software; you can redistribute it and/or modify it
       
     5  * under the terms of the GNU Lesser General Public License as published by the
       
     6  * Free Software Foundation, version 2.1.
       
     7  *
       
     8  * This program is distributed in the hope that it will be useful, but WITHOUT
       
     9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    10  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    11  * details.
       
    12  *
       
    13  * $Revision: 1.3 $
       
    14  */
       
    15 
       
    16 // Abbreviations: LODP = Left Of Decimal Point, RODP = Right Of Decimal Point
       
    17 Number.formatFunctions = {count:0};
       
    18 
       
    19 // Constants useful for controlling the format of numbers in special cases.
       
    20 Number.prototype.NaN         = 'NaN';
       
    21 Number.prototype.posInfinity = 'Infinity';
       
    22 Number.prototype.negInfinity = '-Infinity';
       
    23 
       
    24 Number.prototype.numberFormat = function(format, context) {
       
    25     if (isNaN(this) ) {
       
    26         return Number.prototype.NaNstring;
       
    27     }
       
    28     else if (this == +Infinity ) {
       
    29         return Number.prototype.posInfinity;
       
    30     }
       
    31     else if ( this == -Infinity) {
       
    32         return Number.prototype.negInfinity;
       
    33     }
       
    34     else if (Number.formatFunctions[format] == null) {
       
    35         Number.createNewFormat(format);
       
    36     }
       
    37     return this[Number.formatFunctions[format]](context);
       
    38 }
       
    39 
       
    40 Number.createNewFormat = function(format) {
       
    41     var funcName = "format" + Number.formatFunctions.count++;
       
    42     Number.formatFunctions[format] = funcName;
       
    43     var code = "Number.prototype." + funcName + " = function(context){\n";
       
    44 
       
    45     // Decide whether the function is a terminal or a pos/neg/zero function
       
    46     var formats = format.split(";");
       
    47     switch (formats.length) {
       
    48         case 1:
       
    49             code += Number.createTerminalFormat(format);
       
    50             break;
       
    51         case 2:
       
    52             code += "return (this < 0) ? this.numberFormat(\""
       
    53                 + String.escape(formats[1])
       
    54                 + "\", 1) : this.numberFormat(\""
       
    55                 + String.escape(formats[0])
       
    56                 + "\", 2);";
       
    57             break;
       
    58         case 3:
       
    59             code += "return (this < 0) ? this.numberFormat(\""
       
    60                 + String.escape(formats[1])
       
    61                 + "\", 1) : ((this == 0) ? this.numberFormat(\""
       
    62                 + String.escape(formats[2])
       
    63                 + "\", 2) : this.numberFormat(\""
       
    64                 + String.escape(formats[0])
       
    65                 + "\", 3));";
       
    66             break;
       
    67         default:
       
    68             code += "throw 'Too many semicolons in format string';";
       
    69             break;
       
    70     }
       
    71     eval(code + "}");
       
    72 }
       
    73 
       
    74 Number.createTerminalFormat = function(format) {
       
    75     // If there is no work to do, just return the literal value
       
    76     if (format.length > 0 && format.search(/[0#?]/) == -1) {
       
    77         return "return '" + String.escape(format) + "';\n";
       
    78     }
       
    79     // Negative values are always displayed without a minus sign when section separators are used.
       
    80     var code = "var val = (context == null) ? new Number(this) : Math.abs(this);\n";
       
    81     var thousands = false;
       
    82     var lodp = format;
       
    83     var rodp = "";
       
    84     var ldigits = 0;
       
    85     var rdigits = 0;
       
    86     var scidigits = 0;
       
    87     var scishowsign = false;
       
    88     var sciletter = "";
       
    89     // Look for (and remove) scientific notation instructions, which can be anywhere
       
    90     m = format.match(/\..*(e)([+-]?)(0+)/i);
       
    91     if (m) {
       
    92         sciletter = m[1];
       
    93         scishowsign = (m[2] == "+");
       
    94         scidigits = m[3].length;
       
    95         format = format.replace(/(e)([+-]?)(0+)/i, "");
       
    96     }
       
    97     // Split around the decimal point
       
    98     var m = format.match(/^([^.]*)\.(.*)$/);
       
    99     if (m) {
       
   100         lodp = m[1].replace(/\./g, "");
       
   101         rodp = m[2].replace(/\./g, "");
       
   102     }
       
   103     // Look for %
       
   104     if (format.indexOf('%') >= 0) {
       
   105         code += "val *= 100;\n";
       
   106     }
       
   107     // Look for comma-scaling to the left of the decimal point
       
   108     m = lodp.match(/(,+)(?:$|[^0#?,])/);
       
   109     if (m) {
       
   110         code += "val /= " + Math.pow(1000, m[1].length) + "\n;";
       
   111     }
       
   112     // Look for comma-separators
       
   113     if (lodp.search(/[0#?],[0#?]/) >= 0) {
       
   114         thousands = true;
       
   115     }
       
   116     // Nuke any extraneous commas
       
   117     if ((m) || thousands) {
       
   118         lodp = lodp.replace(/,/g, "");
       
   119     }
       
   120     // Figure out how many digits to the l/r of the decimal place
       
   121     m = lodp.match(/0[0#?]*/);
       
   122     if (m) {
       
   123         ldigits = m[0].length;
       
   124     }
       
   125     m = rodp.match(/[0#?]*/);
       
   126     if (m) {
       
   127         rdigits = m[0].length;
       
   128     }
       
   129     // Scientific notation takes precedence over rounding etc
       
   130     if (scidigits > 0) {
       
   131         code += "var sci = Number.toScientific(val,"
       
   132             + ldigits + ", " + rdigits + ", " + scidigits + ", " + scishowsign + ");\n"
       
   133             + "var arr = [sci.l, sci.r];\n";
       
   134     }
       
   135     else {
       
   136         // If there is no decimal point, round to nearest integer, AWAY from zero
       
   137         if (format.indexOf('.') < 0) {
       
   138             code += "val = (val > 0) ? Math.ceil(val) : Math.floor(val);\n";
       
   139         }
       
   140         // Numbers are rounded to the correct number of digits to the right of the decimal
       
   141         code += "var arr = val.round(" + rdigits + ").toFixed(" + rdigits + ").split('.');\n";
       
   142         // There are at least "ldigits" digits to the left of the decimal, so add zeros if needed.
       
   143         code += "arr[0] = (val < 0 ? '-' : '') + String.leftPad((val < 0 ? arr[0].substring(1) : arr[0]), "
       
   144             + ldigits + ", '0');\n";
       
   145     }
       
   146     // Add thousands separators
       
   147     if (thousands) {
       
   148         code += "arr[0] = Number.addSeparators(arr[0]);\n";
       
   149     }
       
   150     // Insert the digits into the formatting string.  On the LHS, extra digits are copied
       
   151     // into the result.  On the RHS, rounding has chopped them off.
       
   152     code += "arr[0] = Number.injectIntoFormat(arr[0].reverse(), '"
       
   153         + String.escape(lodp.reverse()) + "', true).reverse();\n";
       
   154     if (rdigits > 0) {
       
   155         code += "arr[1] = Number.injectIntoFormat(arr[1], '" + String.escape(rodp) + "', false);\n";
       
   156     }
       
   157     if (scidigits > 0) {
       
   158         code += "arr[1] = arr[1].replace(/(\\d{" + rdigits + "})/, '$1" + sciletter + "' + sci.s);\n";
       
   159     }
       
   160     return code + "return arr.join('.');\n";
       
   161 }
       
   162 
       
   163 Number.toScientific = function(val, ldigits, rdigits, scidigits, showsign) {
       
   164     var result = {l:"", r:"", s:""};
       
   165     var ex = "";
       
   166     // Make ldigits + rdigits significant figures
       
   167     var before = Math.abs(val).toFixed(ldigits + rdigits + 1).trim('0');
       
   168     // Move the decimal point to the right of all digits we want to keep,
       
   169     // and round the resulting value off
       
   170     var after = Math.round(new Number(before.replace(".", "").replace(
       
   171         new RegExp("(\\d{" + (ldigits + rdigits) + "})(.*)"), "$1.$2"))).toFixed(0);
       
   172     // Place the decimal point in the new string
       
   173     if (after.length >= ldigits) {
       
   174         after = after.substring(0, ldigits) + "." + after.substring(ldigits);
       
   175     }
       
   176     else {
       
   177         after += '.';
       
   178     }
       
   179     // Find how much the decimal point moved.  This is #places to LODP in the original
       
   180     // number, minus the #places in the new number.  There are no left-padded zeroes in
       
   181     // the new number, so the calculation for it is simpler than for the old number.
       
   182     result.s = (before.indexOf(".") - before.search(/[1-9]/)) - after.indexOf(".");
       
   183     // The exponent is off by 1 when it gets moved to the left.
       
   184     if (result.s < 0) {
       
   185         result.s++;
       
   186     }
       
   187     // Split the value around the decimal point and pad the parts appropriately.
       
   188     result.l = (val < 0 ? '-' : '') + String.leftPad(after.substring(0, after.indexOf(".")), ldigits, "0");
       
   189     result.r = after.substring(after.indexOf(".") + 1);
       
   190     if (result.s < 0) {
       
   191         ex = "-";
       
   192     }
       
   193     else if (showsign) {
       
   194         ex = "+";
       
   195     }
       
   196     result.s = ex + String.leftPad(Math.abs(result.s).toFixed(0), scidigits, "0");
       
   197     return result;
       
   198 }
       
   199 
       
   200 Number.prototype.round = function(decimals) {
       
   201     if (decimals > 0) {
       
   202         var m = this.toFixed(decimals + 1).match(
       
   203             new RegExp("(-?\\d*)\.(\\d{" + decimals + "})(\\d)\\d*$"));
       
   204         if (m && m.length) {
       
   205             return new Number(m[1] + "." + String.leftPad(Math.round(m[2] + "." + m[3]), decimals, "0"));
       
   206         }
       
   207     }
       
   208     return this;
       
   209 }
       
   210 
       
   211 Number.injectIntoFormat = function(val, format, stuffExtras) {
       
   212     var i = 0;
       
   213     var j = 0;
       
   214     var result = "";
       
   215     var revneg = val.charAt(val.length - 1) == '-';
       
   216     if ( revneg ) {
       
   217        val = val.substring(0, val.length - 1);
       
   218     }
       
   219     while (i < format.length && j < val.length && format.substring(i).search(/[0#?]/) >= 0) {
       
   220         if (format.charAt(i).match(/[0#?]/)) {
       
   221             // It's a formatting character; copy the corresponding character
       
   222             // in the value to the result
       
   223             if (val.charAt(j) != '-') {
       
   224                 result += val.charAt(j);
       
   225             }
       
   226             else {
       
   227                 result += "0";
       
   228             }
       
   229             j++;
       
   230         }
       
   231         else {
       
   232             result += format.charAt(i);
       
   233         }
       
   234         ++i;
       
   235     }
       
   236     if ( revneg && j == val.length ) {
       
   237         result += '-';
       
   238     }
       
   239     if (j < val.length) {
       
   240         if (stuffExtras) {
       
   241             result += val.substring(j);
       
   242         }
       
   243         if ( revneg ) {
       
   244              result += '-';
       
   245         }
       
   246     }
       
   247     if (i < format.length) {
       
   248         result += format.substring(i);
       
   249     }
       
   250     return result.replace(/#/g, "").replace(/\?/g, " ");
       
   251 }
       
   252 
       
   253 Number.addSeparators = function(val) {
       
   254     return val.reverse().replace(/(\d{3})/g, "$1,").reverse().replace(/^(-)?,/, "$1");
       
   255 }
       
   256 
       
   257 String.prototype.reverse = function() {
       
   258     var res = "";
       
   259     for (var i = this.length; i > 0; --i) {
       
   260         res += this.charAt(i - 1);
       
   261     }
       
   262     return res;
       
   263 }
       
   264 
       
   265 String.prototype.trim = function(ch) {
       
   266     if (!ch) ch = ' ';
       
   267     return this.replace(new RegExp("^" + ch + "+|" + ch + "+$", "g"), "");
       
   268 }
       
   269 
       
   270 String.leftPad = function (val, size, ch) {
       
   271     var result = new String(val);
       
   272     if (ch == null) {
       
   273         ch = " ";
       
   274     }
       
   275     while (result.length < size) {
       
   276         result = ch + result;
       
   277     }
       
   278     return result;
       
   279 }
       
   280 
       
   281 String.escape = function(string) {
       
   282     return string.replace(/('|\\)/g, "\\$1");
       
   283 }