2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / System.XML / Mono.Xml.Xsl / XslDecimalFormat.cs
1 //
2 // XslDecimalFormat.cs
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      
7 // (C) 2003 Ben Maurer
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Collections;
33 using System.Globalization;
34 using System.Text;
35 using System.Xml;
36 using System.Xml.XPath;
37 using System.Xml.Xsl;
38
39 using QName = System.Xml.XmlQualifiedName;
40
41 namespace Mono.Xml.Xsl {
42         internal class XslDecimalFormat {
43                 
44                 NumberFormatInfo info = new NumberFormatInfo ();
45                 char digit = '#', zeroDigit = '0', patternSeparator = ';';
46                 string baseUri;
47                 int lineNumber;
48                 int linePosition;
49
50                 public static readonly XslDecimalFormat Default = new XslDecimalFormat ();
51                 
52                 XslDecimalFormat () {} // Default ctor for default info.
53                 public XslDecimalFormat (Compiler c)
54                 {
55                         XPathNavigator n = c.Input;
56
57                         IXmlLineInfo li = n as IXmlLineInfo;
58                         if (li != null) {
59                                 lineNumber = li.LineNumber;
60                                 linePosition = li.LinePosition;
61                         }
62                         baseUri = n.BaseURI;
63
64                         if (n.MoveToFirstAttribute ()) {
65                                 do {
66                                         if (n.NamespaceURI != String.Empty)
67                                                 continue;
68                                         
69                                         switch (n.LocalName) {
70                                         case "name": break; // already handled
71                                         case "decimal-separator":
72                                                 if (n.Value.Length != 1)
73                                                         throw new XsltCompileException ("XSLT decimal-separator value must be exact one character.", null, n);
74                                                 info.NumberDecimalSeparator = n.Value;
75                                                 break;
76                                                 
77                                         case "grouping-separator":
78                                                 if (n.Value.Length != 1)
79                                                         throw new XsltCompileException ("XSLT grouping-separator value must be exact one character.", null, n);
80                                                 info.NumberGroupSeparator = n.Value;
81                                                 break;
82                                                 
83                                         case "infinity":
84                                                 info.PositiveInfinitySymbol = n.Value;
85                                                 break;
86                                         case "minus-sign":
87                                                 if (n.Value.Length != 1)
88                                                         throw new XsltCompileException ("XSLT minus-sign value must be exact one character.", null, n);
89                                                 info.NegativeSign = n.Value;
90                                                 break;
91                                         case "NaN":
92                                                 info.NaNSymbol = n.Value;
93                                                 break;
94                                         case "percent":
95                                                 if (n.Value.Length != 1)
96                                                         throw new XsltCompileException ("XSLT percent value must be exact one character.", null, n);
97                                                 info.PercentSymbol = n.Value;
98                                                 break;
99                                         case "per-mille":
100                                                 if (n.Value.Length != 1)
101                                                         throw new XsltCompileException ("XSLT per-mille value must be exact one character.", null, n);
102                                                 info.PerMilleSymbol = n.Value;
103                                                 break;
104                                         case "digit":
105                                                 if (n.Value.Length != 1)
106                                                         throw new XsltCompileException ("XSLT digit value must be exact one character.", null, n);
107                                                 digit = n.Value [0];
108                                                 break;
109                                         case "zero-digit":
110                                                 if (n.Value.Length != 1)
111                                                         throw new XsltCompileException ("XSLT zero-digit value must be exact one character.", null, n);
112                                                 zeroDigit = n.Value [0];
113                                                 break;
114                                         case "pattern-separator":
115                                                 if (n.Value.Length != 1)
116                                                         throw new XsltCompileException ("XSLT pattern-separator value must be exact one character.", null, n);
117                                                 patternSeparator = n.Value [0];
118                                                 break;
119                                         }
120                                 } while (n.MoveToNextAttribute ());
121                                 n.MoveToParent ();
122                                 
123                                 info.NegativeInfinitySymbol = info.NegativeSign + info.PositiveInfinitySymbol;
124                         }
125                 }
126
127                 public char Digit { get { return digit; } }
128                 public char ZeroDigit { get { return zeroDigit; } }
129                 public NumberFormatInfo Info { get { return info; } }
130                 public char PatternSeparator { get { return patternSeparator; } }
131
132                 public void CheckSameAs (XslDecimalFormat other)
133                 {
134                         if (this.digit != other.digit ||
135                                 this.patternSeparator != other.patternSeparator ||
136                                 this.zeroDigit != other.zeroDigit ||
137                                 this.info.NumberDecimalSeparator != other.info.NumberDecimalSeparator ||
138                                 this.info.NumberGroupSeparator != other.info.NumberGroupSeparator ||
139                                 this.info.PositiveInfinitySymbol != other.info.PositiveInfinitySymbol ||
140                                 this.info.NegativeSign != other.info.NegativeSign ||
141                                 this.info.NaNSymbol != other.info.NaNSymbol ||
142                                 this.info.PercentSymbol != other.info.PercentSymbol ||
143                                 this.info.PerMilleSymbol != other.info.PerMilleSymbol)
144                                 throw new XsltCompileException (null, other.baseUri, other.lineNumber, other.linePosition);
145                 }
146
147                 public string FormatNumber (double number, string pattern)
148                 {
149                         return ParsePatternSet (pattern).FormatNumber (number);
150                 }
151
152                 private DecimalFormatPatternSet ParsePatternSet (string pattern)
153                 {
154                         return new DecimalFormatPatternSet (pattern, this);
155                 }
156         }
157
158         // set of positive pattern and negative pattern
159         internal class DecimalFormatPatternSet
160         {
161                 DecimalFormatPattern positivePattern;
162                 DecimalFormatPattern negativePattern;
163 //              XslDecimalFormat decimalFormat;
164
165                 public DecimalFormatPatternSet (string pattern, XslDecimalFormat decimalFormat)
166                 {
167                         Parse (pattern, decimalFormat);
168                 }
169
170                 private void Parse (string pattern, XslDecimalFormat format)
171                 {
172                         positivePattern = new DecimalFormatPattern ();
173
174                         int pos = positivePattern.ParsePattern (0, pattern, format);
175                         if (pos < pattern.Length) {
176                                 if (pattern [pos] != format.PatternSeparator)
177                                         // Expecting caught and wrapped by caller,
178                                         // since it cannot provide XPathNavigator.
179                                         throw new ArgumentException ("Invalid number format pattern string.");
180                                 pos++;
181                                 negativePattern = new DecimalFormatPattern ();
182                                 pos = negativePattern.ParsePattern (pos, pattern, format);
183                                 if (pos < pattern.Length)
184                                         throw new ArgumentException ("Number format pattern string ends with extraneous part.");
185                         }
186                         else
187                                 negativePattern = positivePattern;
188                 }
189
190                 public string FormatNumber (double number)
191                 {
192                         if (number >= 0)
193                                 return positivePattern.FormatNumber (number);
194                         else
195                                 return negativePattern.FormatNumber (number);
196                 }
197         }
198
199         internal class DecimalFormatPattern
200         {
201                 public string Prefix;
202                 public string Suffix;
203                 public string NumberPart;
204                 NumberFormatInfo info;
205
206                 StringBuilder builder;
207
208                 internal int ParsePattern (int start, string pattern, XslDecimalFormat format)
209                 {
210                         if (start == 0) // positive pattern
211                                 this.info = format.Info;
212                         else {
213                                 this.info = format.Info.Clone () as NumberFormatInfo;
214                                 info.NegativeSign = String.Empty; // should be specified in Prefix
215                         }
216
217                         // prefix
218                         int pos = start;
219                         while (pos < pattern.Length) {
220                                 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.Info.CurrencySymbol [0])
221                                         break;
222                                 else
223                                         pos++;
224                         }
225
226                         Prefix = pattern.Substring (start, pos - start);
227                         if (pos == pattern.Length)
228                                 throw new ArgumentException ("Invalid number format pattern."); 
229
230                         // number
231                         pos = ParseNumber (pos, pattern, format);
232                         int suffixStart = pos;
233
234                         // suffix
235                         while (pos < pattern.Length) {
236                                 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.PatternSeparator || pattern [pos] == format.Info.CurrencySymbol [0])
237                                         break;
238                                 else
239                                         pos++;
240                         }
241
242                         Suffix = pattern.Substring (suffixStart, pos - suffixStart);
243
244                         return pos;
245                 }
246
247                 [MonoTODO ("Collect grouping digits")]
248                 private int ParseNumber (int start, string pattern, XslDecimalFormat format)
249                 {
250                         builder = new StringBuilder ();
251
252                         int pos = start;
253                         // process non-minint part.
254                         for (; pos < pattern.Length; pos++) {
255                                 if (pattern [pos] == format.Digit)
256                                         builder.Append ('#');
257                                 else if (pattern [pos] == format.Info.NumberGroupSeparator [0])
258                                         builder.Append (',');
259                                 else
260                                         break;
261                         }
262                         
263                         // minint part.
264                         for (; pos < pattern.Length; pos++) {
265                                 if (pattern [pos] == format.ZeroDigit)
266                                         builder.Append ('0');
267                                 else if (pattern [pos] == format.Info.NumberGroupSeparator [0])
268                                         builder.Append (',');
269                                 else
270                                         break;
271                         }
272
273                         // optional fraction part
274                         if (pos < pattern.Length) {
275                                 if (pattern [pos] == format.Info.NumberDecimalSeparator [0]) {
276                                         builder.Append ('.');
277                                         pos++;
278                                 }
279                                 while (pos < pattern.Length) {
280                                         if (pattern [pos] == format.ZeroDigit) {
281                                                 pos++;
282                                                 builder.Append ('0');
283                                         }
284                                         else
285                                                 break;
286                                 }
287                                 while (pos < pattern.Length) {
288                                         if (pattern [pos] == format.Digit) {
289                                                 pos++;
290                                                 builder.Append ('#');
291                                         }
292                                         else
293                                                 break;
294                                 }
295                         }
296                         // optional exponent part
297                         if (pos + 1 < pattern.Length && pattern [pos] == 'E' && pattern [pos + 1] == format.ZeroDigit) {
298                                 pos += 2;
299                                 builder.Append ("E0");
300                                 while (pos < pattern.Length) {
301                                         if (pattern [pos] == format.ZeroDigit) {
302                                                 pos++;
303                                                 builder.Append ('0');
304                                         }
305                                         else
306                                                 break;
307                                 }
308                         }
309                         // misc special characters
310                         if (pos < pattern.Length) {
311                                 if (pattern [pos] == this.info.PercentSymbol [0])
312                                         builder.Append ('%');
313                                 else if (pattern [pos] == this.info.PerMilleSymbol [0])
314                                         builder.Append ('\u2030');
315                                 else if (pattern [pos] == this.info.CurrencySymbol [0])
316                                         throw new ArgumentException ("Currency symbol is not supported for number format pattern string.");
317                                 else
318                                         pos--;
319                                 pos++;
320                         }
321
322                         NumberPart = builder.ToString ();
323                         return pos;
324                 }
325
326                 public string FormatNumber (double number)
327                 {
328                         builder.Length = 0;
329                         builder.Append (Prefix);
330                         builder.Append (number.ToString (NumberPart, info));
331                         builder.Append (Suffix);
332                         return builder.ToString ();
333                 }
334         }
335 }