cosmetic changes to enable compilation with MS 1.1 compiler
[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                         if (pattern.Length == 0)
173                                 throw new ArgumentException ("Invalid number format pattern string.");
174
175                         positivePattern = new DecimalFormatPattern ();
176                         negativePattern = positivePattern;
177
178                         int pos = positivePattern.ParsePattern (0, pattern, format);
179                         if (pos < pattern.Length) {
180                                 if (pattern [pos] != format.PatternSeparator)
181                                         // Expecting caught and wrapped by caller,
182                                         // since it cannot provide XPathNavigator.
183 //                                      throw new ArgumentException ("Invalid number format pattern string.");
184                                         return;
185                                 pos++;
186                                 negativePattern = new DecimalFormatPattern ();
187                                 pos = negativePattern.ParsePattern (pos, pattern, format);
188                                 if (pos < pattern.Length)
189                                         throw new ArgumentException ("Number format pattern string ends with extraneous part.");
190                         }
191                 }
192
193                 public string FormatNumber (double number)
194                 {
195                         if (number >= 0)
196                                 return positivePattern.FormatNumber (number);
197                         else
198                                 return negativePattern.FormatNumber (number);
199                 }
200         }
201
202         internal class DecimalFormatPattern
203         {
204                 public string Prefix = String.Empty;
205                 public string Suffix = String.Empty;
206                 public string NumberPart;
207                 NumberFormatInfo info;
208
209                 StringBuilder builder = new StringBuilder ();
210
211                 internal int ParsePattern (int start, string pattern, XslDecimalFormat format)
212                 {
213                         if (start == 0) // positive pattern
214                                 this.info = format.Info;
215                         else {
216                                 this.info = format.Info.Clone () as NumberFormatInfo;
217                                 info.NegativeSign = String.Empty; // should be specified in Prefix
218                         }
219
220                         // prefix
221                         int pos = start;
222                         while (pos < pattern.Length) {
223                                 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.Info.CurrencySymbol [0])
224                                         break;
225                                 else
226                                         pos++;
227                         }
228
229                         Prefix = pattern.Substring (start, pos - start);
230                         if (pos == pattern.Length) {
231                                 // Invalid number pattern.
232 //                              throw new ArgumentException ("Invalid number format pattern."); 
233                                 return pos;
234                         }
235
236                         // number
237                         pos = ParseNumber (pos, pattern, format);
238                         int suffixStart = pos;
239
240                         // suffix
241                         while (pos < pattern.Length) {
242                                 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.PatternSeparator || pattern [pos] == format.Info.CurrencySymbol [0])
243                                         break;
244                                 else
245                                         pos++;
246                         }
247
248                         Suffix = pattern.Substring (suffixStart, pos - suffixStart);
249
250                         return pos;
251                 }
252
253                 [MonoTODO ("Collect grouping digits")]
254                 private int ParseNumber (int start, string pattern, XslDecimalFormat format)
255                 {
256                         int pos = start;
257                         // process non-minint part.
258                         for (; pos < pattern.Length; pos++) {
259                                 if (pattern [pos] == format.Digit)
260                                         builder.Append ('#');
261                                 else if (pattern [pos] == format.Info.NumberGroupSeparator [0])
262                                         builder.Append (',');
263                                 else
264                                         break;
265                         }
266                         
267                         // minint part.
268                         for (; pos < pattern.Length; pos++) {
269                                 if (pattern [pos] == format.ZeroDigit)
270                                         builder.Append ('0');
271                                 else if (pattern [pos] == format.Info.NumberGroupSeparator [0])
272                                         builder.Append (',');
273                                 else
274                                         break;
275                         }
276
277                         // optional fraction part
278                         if (pos < pattern.Length) {
279                                 if (pattern [pos] == format.Info.NumberDecimalSeparator [0]) {
280                                         builder.Append ('.');
281                                         pos++;
282                                 }
283                                 while (pos < pattern.Length) {
284                                         if (pattern [pos] == format.ZeroDigit) {
285                                                 pos++;
286                                                 builder.Append ('0');
287                                         }
288                                         else
289                                                 break;
290                                 }
291                                 while (pos < pattern.Length) {
292                                         if (pattern [pos] == format.Digit) {
293                                                 pos++;
294                                                 builder.Append ('#');
295                                         }
296                                         else
297                                                 break;
298                                 }
299                         }
300                         // optional exponent part
301                         if (pos + 1 < pattern.Length && pattern [pos] == 'E' && pattern [pos + 1] == format.ZeroDigit) {
302                                 pos += 2;
303                                 builder.Append ("E0");
304                                 while (pos < pattern.Length) {
305                                         if (pattern [pos] == format.ZeroDigit) {
306                                                 pos++;
307                                                 builder.Append ('0');
308                                         }
309                                         else
310                                                 break;
311                                 }
312                         }
313                         // misc special characters
314                         if (pos < pattern.Length) {
315                                 if (pattern [pos] == this.info.PercentSymbol [0])
316                                         builder.Append ('%');
317                                 else if (pattern [pos] == this.info.PerMilleSymbol [0])
318                                         builder.Append ('\u2030');
319                                 else if (pattern [pos] == this.info.CurrencySymbol [0])
320                                         throw new ArgumentException ("Currency symbol is not supported for number format pattern string.");
321                                 else
322                                         pos--;
323                                 pos++;
324                         }
325
326                         NumberPart = builder.ToString ();
327                         return pos;
328                 }
329
330                 public string FormatNumber (double number)
331                 {
332                         builder.Length = 0;
333                         builder.Append (Prefix);
334                         builder.Append (number.ToString (NumberPart, info));
335                         builder.Append (Suffix);
336                         return builder.ToString ();
337                 }
338         }
339 }