r25048: From the archives (patch found in one of my old working trees):
[jelmer/samba4-debian.git] / webapps / qooxdoo-0.6.5-sdk / frontend / framework / source / class / qx / util / format / NumberFormat.js
1 /* ************************************************************************
2
3    qooxdoo - the new era of web development
4
5    http://qooxdoo.org
6
7    Copyright:
8      2006 STZ-IDA, Germany, http://www.stz-ida.de
9
10    License:
11      LGPL: http://www.gnu.org/licenses/lgpl.html
12      EPL: http://www.eclipse.org/org/documents/epl-v10.php
13      See the LICENSE file in the project's top-level directory for details.
14
15    Authors:
16      * Til Schneider (til132)
17
18 ************************************************************************ */
19
20 /* ************************************************************************
21
22 * #require(qx.locale.Number)
23
24 ************************************************************************ */
25
26 /**
27  * A formatter and parser for numbers.
28  *
29  * @param locale {String} optional locale to be used
30  */
31 qx.OO.defineClass("qx.util.format.NumberFormat", qx.util.format.Format,
32 function(locale) {
33   qx.util.format.Format.call(this);
34   this._locale = locale;
35 });
36
37
38 /**
39  * The minimum number of integer digits (digits before the decimal separator).
40  * Missing digits will be filled up with 0 ("19" -> "0019").
41  */
42 qx.OO.addProperty({ name:"minimumIntegerDigits", type:"number", defaultValue:0, allowNull:false });
43
44 /**
45  * The maximum number of integer digits (superfluos digits will be cut off
46  * ("1923" -> "23").
47  */
48 qx.OO.addProperty({ name:"maximumIntegerDigits", type:"number", defaultValue:null });
49
50 /**
51  * The minimum number of fraction digits (digits after the decimal separator).
52  * Missing digits will be filled up with 0 ("1.5" -> "1.500")
53  */
54 qx.OO.addProperty({ name:"minimumFractionDigits", type:"number", defaultValue:0, allowNull:false });
55
56 /**
57  * The maximum number of fraction digits (digits after the decimal separator).
58  * Superflous digits will cause rounding ("1.8277" -> "1.83")
59  */
60 qx.OO.addProperty({ name:"maximumFractionDigits", type:"number", defaultValue:null });
61
62 /** Whether thousand groupings should be used {e.g. "1,432,234.65"}. */
63 qx.OO.addProperty({ name:"groupingUsed", type:"boolean", defaultValue:true, allowNull:false });
64
65 /** The prefix to put before the number {"EUR " -> "EUR 12.31"}. */
66 qx.OO.addProperty({ name:"prefix", type:"string", defaultValue:"", allowNull:false });
67
68 /** Sets the postfix to put after the number {" %" -> "56.13 %"}. */
69 qx.OO.addProperty({ name:"postfix", type:"string", defaultValue:"", allowNull:false });
70
71
72 /**
73  * Formats a number.
74  *
75  * @param num {number} the number to format.
76  * @return {String} the formatted number as a string.
77  */
78 qx.Proto.format = function(num) {
79   var NumberFormat = qx.util.format.NumberFormat;
80
81   var negative = (num < 0);
82   if (negative) {
83     num = -num;
84   }
85   if (this.getMaximumFractionDigits() != null) {
86     // Do the rounding
87     var mover = Math.pow(10, this.getMaximumFractionDigits());
88     num = Math.round(num * mover) / mover;
89   }
90
91   if (num != 0) { // Math.log(0) = -Infinity
92     var integerDigits = Math.max(parseInt(Math.log(num) / Math.LN10) + 1, 1);
93   } else {
94     integerDigits = 1;
95   }
96
97   var numStr = "" + num;
98
99   // Prepare the integer part
100   var integerStr = numStr.substring(0, integerDigits);
101   while (integerStr.length < this.getMinimumIntegerDigits()) {
102     integerStr = "0" + integerStr;
103   }
104   if (this.getMaximumIntegerDigits() != null && integerStr.length > this.getMaximumIntegerDigits()) {
105     // NOTE: We cut off even though we did rounding before, because there
106     //     may be rounding errors ("12.24000000000001" -> "12.24")
107     integerStr = integerStr.substring(integerStr.length - this.getMaximumIntegerDigits());
108   }
109
110   // Prepare the fraction part
111   var fractionStr = numStr.substring(integerDigits + 1);
112   while (fractionStr.length < this.getMinimumFractionDigits()) {
113     fractionStr += "0";
114   }
115   if (this.getMaximumFractionDigits() != null && fractionStr.length > this.getMaximumFractionDigits()) {
116     // We have already rounded -> Just cut off the rest
117     fractionStr = fractionStr.substring(0, this.getMaximumFractionDigits());
118   }
119
120   // Add the thousand groupings
121   if (this.getGroupingUsed()) {
122     var origIntegerStr = integerStr;
123     integerStr = "";
124     var groupPos;
125     for (groupPos = origIntegerStr.length; groupPos > 3; groupPos -= 3) {
126       integerStr = "" + qx.locale.Number.getGroupSeparator(this._locale)
127         + origIntegerStr.substring(groupPos - 3, groupPos) + integerStr;
128     }
129     integerStr = origIntegerStr.substring(0, groupPos) + integerStr;
130   }
131
132   // Workaround: prefix and postfix are null even their defaultValue is "" and
133   //             allowNull is set to false?!?
134   var prefix  = this.getPrefix()  ? this.getPrefix()  : "";
135   var postfix = this.getPostfix() ? this.getPostfix() : "";
136
137   // Assemble the number
138   var str = prefix + (negative ? "-" : "") + integerStr;
139   if (fractionStr.length > 0) {
140     str += "" + qx.locale.Number.getDecimalSeparator(this._locale) + fractionStr;
141   }
142   str += postfix;
143
144   return str;
145 };
146
147
148 /**
149  * Parses a number.
150  *
151  * @param str {String} the string to parse.
152  *
153  * @return {Double} the number.
154  */
155 qx.Proto.parse = function(str) {
156   var NumberFormat = qx.util.format.NumberFormat;
157
158   // use the escaped separators for regexp
159   var groupSepEsc = qx.lang.String.escapeRegexpChars(qx.locale.Number.getGroupSeparator(this._locale)+"");
160   var decimalSepEsc = qx.lang.String.escapeRegexpChars(qx.locale.Number.getDecimalSeparator(this._locale)+"");
161
162   var regex = new RegExp(qx.lang.String.escapeRegexpChars(this.getPrefix())
163     + '(-)?([0-9' + groupSepEsc + ']+)'
164     + '(' + decimalSepEsc + '\\d+)?'
165     + qx.lang.String.escapeRegexpChars(this.getPostfix()));
166
167   var hit = regex.exec(str);
168   if (hit == null) {
169     throw new Error("Number string '" + str + "' does not match the number format");
170   }
171
172   var negative = (hit[1] == "-");
173   var integerStr = hit[2];
174   var fractionStr = hit[3];
175
176   // Remove the thousand groupings
177   integerStr = integerStr.replace(new RegExp(groupSepEsc), "");
178
179   var asStr = (negative ? "-" : "") + integerStr;
180   if (fractionStr != null && fractionStr.length != 0) {
181     // Remove the leading decimal separator from the fractions string
182     fractionStr = fractionStr.replace(new RegExp(decimalSepEsc),"");
183     asStr += "." + fractionStr;
184   }
185   return parseFloat(asStr);
186 };
187
188
189 /**
190  * Returns the default number format.
191  *
192  * @return {NumberFormat} the default number format.
193  */
194 qx.Class.getInstance = function() {
195   var NumberFormat = qx.util.format.NumberFormat;
196   if (NumberFormat._instance == null) {
197     NumberFormat._instance = new NumberFormat();
198   }
199   return NumberFormat._instance;
200 };
201
202
203 /**
204  * Returns an integer number format.
205  *
206  * @return {NumberFormat} an integer number format.
207  */
208 qx.Class.getIntegerInstance = function() {
209   var NumberFormat = qx.util.format.NumberFormat;
210   if (NumberFormat._integerInstance == null) {
211     NumberFormat._integerInstance = new NumberFormat();
212     NumberFormat._integerInstance.setMaximumFractionDigits(0);
213   }
214   return NumberFormat._integerInstance;
215 };