Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo.number"] = true;
dojo.provide("dojo.number");

dojo.require("dojo.i18n");
dojo.requireLocalization("dojo.cldr", "number", null, "zh-cn,en,en-ca,zh-tw,en-us,it,ja-jp,ROOT,de-de,es-es,fr,pt,ko-kr,es,de");
dojo.require("dojo.string");
dojo.require("dojo.regexp");


/*=====
dojo.number.__formatOptions = function(kwArgs){
        //      pattern: String?
        //              override formatting pattern with this string (see
        //              dojo.number._applyPattern)
        //      type: String?
        //              choose a format type based on the locale from the following:
        //              decimal, scientific, percent, currency. decimal by default.
        //      places: Number?
        //              fixed number of decimal places to show.  This overrides any
        //              information in the provided pattern.
        //      round: NUmber?
        //              5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
        //              means don't round.
        //      currency: String?
        //              iso4217 currency code
        //      symbol: String?
        //              localized currency symbol
        //      locale: String?
        //              override the locale used to determine formatting rules
}
=====*/

dojo.number.format = function(/*Number*/value, /*dojo.number.__formatOptions?*/options){
        // summary:
        //              Format a Number as a String, using locale-specific settings
        // description:
        //              Create a string from a Number using a known localized pattern.
        //              Formatting patterns appropriate to the locale are chosen from the
        //              CLDR http://unicode.org/cldr as well as the appropriate symbols and
        //              delimiters.  See http://www.unicode.org/reports/tr35/#Number_Elements
        // value:
        //              the number to be formatted.  If not a valid JavaScript number,
        //              return null.

        options = dojo.mixin({}, options || {});
        var locale = dojo.i18n.normalizeLocale(options.locale);
        var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
        options.customs = bundle;
        var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
        if(isNaN(value)){ return null; } // null
        return dojo.number._applyPattern(value, pattern, options); // String
};

//dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough
dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough

dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__formatOptions?*/options){
        // summary:
        //              Apply pattern to format value as a string using options. Gives no
        //              consideration to local customs.
        // value:
        //              the number to be formatted.
        // pattern:
        //              a pattern string as described in
        //              http://www.unicode.org/reports/tr35/#Number_Format_Patterns
        // options: dojo.number.__formatOptions?
        //              _applyPattern is usually called via dojo.number.format() which
        //              populates an extra property in the options parameter, "customs".
        //              The customs object specifies group and decimal parameters if set.

        //TODO: support escapes
        options = options || {};
        var group = options.customs.group;
        var decimal = options.customs.decimal;

        var patternList = pattern.split(';');
        var positivePattern = patternList[0];
        pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern);

        //TODO: only test against unescaped
        if(pattern.indexOf('%') != -1){
                value *= 100;
        }else if(pattern.indexOf('\u2030') != -1){
                value *= 1000; // per mille
        }else if(pattern.indexOf('\u00a4') != -1){
                group = options.customs.currencyGroup || group;//mixins instead?
                decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead?
                pattern = pattern.replace(/\u00a4{1,3}/, function(match){
                        var prop = ["symbol", "currency", "displayName"][match.length-1];
                        return options[prop] || options.currency || "";
                });
        }else if(pattern.indexOf('E') != -1){
                throw new Error("exponential notation not supported");
        }
        
        //TODO: support @ sig figs?
        var numberPatternRE = dojo.number._numberPatternRE;
        var numberPattern = positivePattern.match(numberPatternRE);
        if(!numberPattern){
                throw new Error("unable to find a number expression in pattern: "+pattern);
        }
        return pattern.replace(numberPatternRE,
                dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places}));
}

dojo.number.round = function(/*Number*/value, /*Number*/places, /*Number?*/multiple){
        //      summary:
        //              Rounds the number at the given number of places
        //      value:
        //              the number to round
        //      places:
        //              the number of decimal places where rounding takes place
        //      multiple:
        //              rounds next place to nearest multiple

        var pieces = String(value).split(".");
        var length = (pieces[1] && pieces[1].length) || 0;
        if(length > places){
                var factor = Math.pow(10, places);
                if(multiple > 0){factor *= 10/multiple;places++;} //FIXME
                value = Math.round(value * factor)/factor;

                // truncate to remove any residual floating point values
                pieces = String(value).split(".");
                length = (pieces[1] && pieces[1].length) || 0;
                if(length > places){
                        pieces[1] = pieces[1].substr(0, places);
                        value = Number(pieces.join("."));
                }
        }
        return value; //Number
}

/*=====
dojo.number.__formatAbsoluteOptions = function(kwArgs){
        //      decimal: String?
        //              the decimal separator
        //      group: String?
        //              the group separator
        //      places: Integer?
        //              number of decimal places
        //      round: Number?
        //              5 rounds to nearest .5; 0 rounds to nearest whole (default). -1
        //              means don't round.
}
=====*/

dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__formatAbsoluteOptions?*/options){
        // summary: 
        //              Apply numeric pattern to absolute value using options. Gives no
        //              consideration to local customs.
        // value:
        //              the number to be formatted, ignores sign
        // pattern:
        //              the number portion of a pattern (e.g. #,##0.00)
        options = options || {};
        if(options.places === true){options.places=0;}
        if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit

        var patternParts = pattern.split(".");
        var maxPlaces = (options.places >= 0) ? options.places : (patternParts[1] && patternParts[1].length) || 0;
        if(!(options.round < 0)){
                value = dojo.number.round(value, maxPlaces, options.round);
        }

        var valueParts = String(Math.abs(value)).split(".");
        var fractional = valueParts[1] || "";
        if(options.places){
                valueParts[1] = dojo.string.pad(fractional.substr(0, options.places), options.places, '0', true);
        }else if(patternParts[1] && options.places !== 0){
                // Pad fractional with trailing zeros
                var pad = patternParts[1].lastIndexOf("0") + 1;
                if(pad > fractional.length){
                        valueParts[1] = dojo.string.pad(fractional, pad, '0', true);
                }

                // Truncate fractional
                var places = patternParts[1].length;
                if(places < fractional.length){
                        valueParts[1] = fractional.substr(0, places);
                }
        }else{
                if(valueParts[1]){ valueParts.pop(); }
        }

        // Pad whole with leading zeros
        var patternDigits = patternParts[0].replace(',', '');
        pad = patternDigits.indexOf("0");
        if(pad != -1){
                pad = patternDigits.length - pad;
                if(pad > valueParts[0].length){
                        valueParts[0] = dojo.string.pad(valueParts[0], pad);
                }

                // Truncate whole
                if(patternDigits.indexOf("#") == -1){
                        valueParts[0] = valueParts[0].substr(valueParts[0].length - pad);
                }
        }

        // Add group separators
        var index = patternParts[0].lastIndexOf(',');
        var groupSize, groupSize2;
        if(index != -1){
                groupSize = patternParts[0].length - index - 1;
                var remainder = patternParts[0].substr(0, index);
                index = remainder.lastIndexOf(',');
                if(index != -1){
                        groupSize2 = remainder.length - index - 1;
                }
        }
        var pieces = [];
        for(var whole = valueParts[0]; whole;){
                var off = whole.length - groupSize;
                pieces.push((off > 0) ? whole.substr(off) : whole);
                whole = (off > 0) ? whole.slice(0, off) : "";
                if(groupSize2){
                        groupSize = groupSize2;
                        delete groupSize2;
                }
        }
        valueParts[0] = pieces.reverse().join(options.group || ",");

        return valueParts.join(options.decimal || ".");
};

/*=====
dojo.number.__regexpOptions = function(kwArgs){
        //      pattern: String?
        //              override pattern with this string.  Default is provided based on
        //              locale.
        //      type: String?
        //              choose a format type based on the locale from the following:
        //              decimal, scientific, percent, currency. decimal by default.
        //      locale: String?
        //              override the locale used to determine formatting rules
        //      strict: Boolean?
        //              strict parsing, false by default
        //      places: Number|String?
        //              number of decimal places to accept: Infinity, a positive number, or
        //              a range "n,m".  By default, defined by pattern.
}
=====*/
dojo.number.regexp = function(/*dojo.number.__regexpOptions?*/options){
        //      summary:
        //              Builds the regular needed to parse a number
        //      description:
        //              Returns regular expression with positive and negative match, group
        //              and decimal separators
        return dojo.number._parseInfo(options).regexp; // String
}

dojo.number._parseInfo = function(/*Object?*/options){
        options = options || {};
        var locale = dojo.i18n.normalizeLocale(options.locale);
        var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale);
        var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"];
//TODO: memoize?
        var group = bundle.group;
        var decimal = bundle.decimal;
        var factor = 1;

        if(pattern.indexOf('%') != -1){
                factor /= 100;
        }else if(pattern.indexOf('\u2030') != -1){
                factor /= 1000; // per mille
        }else{
                var isCurrency = pattern.indexOf('\u00a4') != -1;
                if(isCurrency){
                        group = bundle.currencyGroup || group;
                        decimal = bundle.currencyDecimal || decimal;
                }
        }

        //TODO: handle quoted escapes
        var patternList = pattern.split(';');
        if(patternList.length == 1){
                patternList.push("-" + patternList[0]);
        }

        var re = dojo.regexp.buildGroupRE(patternList, function(pattern){
                pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")";
                return pattern.replace(dojo.number._numberPatternRE, function(format){
                        var flags = {
                                signed: false,
                                separator: options.strict ? group : [group,""],
                                fractional: options.fractional,
                                decimal: decimal,
                                exponent: false};
                        var parts = format.split('.');
                        var places = options.places;
                        if(parts.length == 1 || places === 0){flags.fractional = false;}
                        else{
                                if(typeof places == "undefined"){ places = parts[1].lastIndexOf('0')+1; }
                                if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified
                                if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; }
                                flags.places = places;
                        }
                        var groups = parts[0].split(',');
                        if(groups.length>1){
                                flags.groupSize = groups.pop().length;
                                if(groups.length>1){
                                        flags.groupSize2 = groups.pop().length;
                                }
                        }
                        return "("+dojo.number._realNumberRegexp(flags)+")";
                });
        }, true);

        if(isCurrency){
                // substitute the currency symbol for the placeholder in the pattern
                re = re.replace(/(\s*)(\u00a4{1,3})(\s*)/g, function(match, before, target, after){
                        var prop = ["symbol", "currency", "displayName"][target.length-1];
                        var symbol = dojo.regexp.escapeString(options[prop] || options.currency || "");
                        before = before ? "\\s" : "";
                        after = after ? "\\s" : "";
                        if(!options.strict){
                                if(before){before += "*";}
                                if(after){after += "*";}
                                return "(?:"+before+symbol+after+")?";
                        }
                        return before+symbol+after;
                });
        }

//TODO: substitute localized sign/percent/permille/etc.?

        // normalize whitespace and return
        return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object
}

/*=====
dojo.number.__parseOptions = function(kwArgs){
        //      pattern: String
        //              override pattern with this string.  Default is provided based on
        //              locale.
        //      type: String?
        //              choose a format type based on the locale from the following:
        //              decimal, scientific, percent, currency. decimal by default.
        //      locale: String
        //              override the locale used to determine formatting rules
        //      strict: Boolean?
        //              strict parsing, false by default
        //      currency: Object
        //              object with currency information
}
=====*/
dojo.number.parse = function(/*String*/expression, /*dojo.number.__parseOptions?*/options){
        // summary:
        //              Convert a properly formatted string to a primitive Number, using
        //              locale-specific settings.
        // description:
        //              Create a Number from a string using a known localized pattern.
        //              Formatting patterns are chosen appropriate to the locale.
        //              Formatting patterns are implemented using the syntax described at
        //              *URL*
        // expression:
        //              A string representation of a Number
        var info = dojo.number._parseInfo(options);
        var results = (new RegExp("^"+info.regexp+"$")).exec(expression);
        if(!results){
                return NaN; //NaN
        }
        var absoluteMatch = results[1]; // match for the positive expression
        if(!results[1]){
                if(!results[2]){
                        return NaN; //NaN
                }
                // matched the negative pattern
                absoluteMatch =results[2];
                info.factor *= -1;
        }

        // Transform it to something Javascript can parse as a number.  Normalize
        // decimal point and strip out group separators or alternate forms of whitespace
        absoluteMatch = absoluteMatch.
                replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), "").
                replace(info.decimal, ".");
        // Adjust for negative sign, percent, etc. as necessary
        return Number(absoluteMatch) * info.factor; //Number
};

/*=====
dojo.number.__realNumberRegexpFlags = function(kwArgs){
        //      places: Number?
        //              The integer number of decimal places or a range given as "n,m".  If
        //              not given, the decimal part is optional and the number of places is
        //              unlimited.
        //      decimal: String?
        //              A string for the character used as the decimal point.  Default
        //              is ".".
        //      fractional: Boolean|Array?
        //              Whether decimal places are allowed.  Can be true, false, or [true,
        //              false].  Default is [true, false]
        //      exponent: Boolean|Array?
        //              Express in exponential notation.  Can be true, false, or [true,
        //              false]. Default is [true, false], (i.e. will match if the
        //              exponential part is present are not).
        //      eSigned: Boolean|Array?
        //              The leading plus-or-minus sign on the exponent.  Can be true,
        //              false, or [true, false].  Default is [true, false], (i.e. will
        //              match if it is signed or unsigned).  flags in regexp.integer can be
        //              applied.
}
=====*/

dojo.number._realNumberRegexp = function(/*dojo.number.__realNumberRegexpFlags?*/flags){
        // summary:
        //              Builds a regular expression to match a real number in exponential
        //              notation
        // flags:
        //              An object

        // assign default values to missing paramters
        flags = flags || {};
        if(typeof flags.places == "undefined"){ flags.places = Infinity; }
        if(typeof flags.decimal != "string"){ flags.decimal = "."; }
        if(typeof flags.fractional == "undefined" || /^0/.test(flags.places)){ flags.fractional = [true, false]; }
        if(typeof flags.exponent == "undefined"){ flags.exponent = [true, false]; }
        if(typeof flags.eSigned == "undefined"){ flags.eSigned = [true, false]; }

        // integer RE
        var integerRE = dojo.number._integerRegexp(flags);

        // decimal RE
        var decimalRE = dojo.regexp.buildGroupRE(flags.fractional,
                function(q){
                        var re = "";
                        if(q && (flags.places!==0)){
                                re = "\\" + flags.decimal;
                                if(flags.places == Infinity){ 
                                        re = "(?:" + re + "\\d+)?"; 
                                }else{
                                        re += "\\d{" + flags.places + "}"; 
                                }
                        }
                        return re;
                },
                true
        );

        // exponent RE
        var exponentRE = dojo.regexp.buildGroupRE(flags.exponent,
                function(q){ 
                        if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; }
                        return ""; 
                }
        );

        // real number RE
        var realRE = integerRE + decimalRE;
        // allow for decimals without integers, e.g. .25
        if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";}
        return realRE + exponentRE; // String
};

/*=====
dojo.number.__integerRegexpFlags = function(kwArgs){
        //      signed: Boolean?
        //              The leading plus-or-minus sign. Can be true, false, or [true,
        //              false]. Default is [true, false], (i.e. will match if it is signed
        //              or unsigned).
        //      separator: String?
        //              The character used as the thousands separator. Default is no
        //              separator. For more than one symbol use an array, e.g. [",", ""],
        //              makes ',' optional.
        //      groupSize: Number?
        //              group size between separators
        //      flags.groupSize2: Number?
        //              second grouping (for India)
}
=====*/

dojo.number._integerRegexp = function(/*dojo.number.__integerRegexpFlags?*/flags){
        // summary: 
        //              Builds a regular expression that matches an integer
        // flags: 
        //              An object

        // assign default values to missing paramters
        flags = flags || {};
        if(typeof flags.signed == "undefined"){ flags.signed = [true, false]; }
        if(typeof flags.separator == "undefined"){
                flags.separator = "";
        }else if(typeof flags.groupSize == "undefined"){
                flags.groupSize = 3;
        }
        // build sign RE
        var signRE = dojo.regexp.buildGroupRE(flags.signed,
                function(q) { return q ? "[-+]" : ""; },
                true
        );

        // number RE
        var numberRE = dojo.regexp.buildGroupRE(flags.separator,
                function(sep){
                        if(!sep){
                                return "(?:0|[1-9]\\d*)";
                        }

                        sep = dojo.regexp.escapeString(sep);
                        if(sep == " "){ sep = "\\s"; }
                        else if(sep == "\xa0"){ sep = "\\s\\xa0"; }

                        var grp = flags.groupSize, grp2 = flags.groupSize2;
                        if(grp2){
                                var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})";
                                return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE;
                        }
                        return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)";
                },
                true
        );

        // integer RE
        return signRE + numberRE; // String
}

}