5 // Ben Maurer (bmaurer@users.sourceforge.net)
11 using System.Collections;
12 using System.Globalization;
15 using System.Xml.XPath;
18 using QName = System.Xml.XmlQualifiedName;
20 namespace Mono.Xml.Xsl {
21 internal class XslDecimalFormat {
23 NumberFormatInfo info = new NumberFormatInfo ();
24 char digit = '#', zeroDigit = '0', patternSeparator = ';';
29 public static readonly XslDecimalFormat Default = new XslDecimalFormat ();
31 XslDecimalFormat () {} // Default ctor for default info.
32 public XslDecimalFormat (Compiler c)
34 XPathNavigator n = c.Input;
36 IXmlLineInfo li = n as IXmlLineInfo;
38 lineNumber = li.LineNumber;
39 linePosition = li.LinePosition;
43 if (n.MoveToFirstAttribute ()) {
45 if (n.NamespaceURI != String.Empty)
48 switch (n.LocalName) {
49 case "name": break; // already handled
50 case "decimal-separator":
51 if (n.Value.Length != 1)
52 throw new XsltCompileException ("XSLT decimal-separator value must be exact one character.", null, n);
53 info.NumberDecimalSeparator = n.Value;
56 case "grouping-separator":
57 if (n.Value.Length != 1)
58 throw new XsltCompileException ("XSLT grouping-separator value must be exact one character.", null, n);
59 info.NumberGroupSeparator = n.Value;
63 info.PositiveInfinitySymbol = n.Value;
66 if (n.Value.Length != 1)
67 throw new XsltCompileException ("XSLT minus-sign value must be exact one character.", null, n);
68 info.NegativeSign = n.Value;
71 info.NaNSymbol = n.Value;
74 if (n.Value.Length != 1)
75 throw new XsltCompileException ("XSLT percent value must be exact one character.", null, n);
76 info.PercentSymbol = n.Value;
79 if (n.Value.Length != 1)
80 throw new XsltCompileException ("XSLT per-mille value must be exact one character.", null, n);
81 info.PerMilleSymbol = n.Value;
84 if (n.Value.Length != 1)
85 throw new XsltCompileException ("XSLT digit value must be exact one character.", null, n);
89 if (n.Value.Length != 1)
90 throw new XsltCompileException ("XSLT zero-digit value must be exact one character.", null, n);
91 zeroDigit = n.Value [0];
93 case "pattern-separator":
94 if (n.Value.Length != 1)
95 throw new XsltCompileException ("XSLT pattern-separator value must be exact one character.", null, n);
96 patternSeparator = n.Value [0];
99 } while (n.MoveToNextAttribute ());
102 info.NegativeInfinitySymbol = info.NegativeSign + info.PositiveInfinitySymbol;
106 public char Digit { get { return digit; } }
107 public char ZeroDigit { get { return zeroDigit; } }
108 public NumberFormatInfo Info { get { return info; } }
109 public char PatternSeparator { get { return patternSeparator; } }
111 public void CheckSameAs (XslDecimalFormat other)
113 if (this.digit != other.digit ||
114 this.patternSeparator != other.patternSeparator ||
115 this.zeroDigit != other.zeroDigit ||
116 this.info.NumberDecimalSeparator != other.info.NumberDecimalSeparator ||
117 this.info.NumberGroupSeparator != other.info.NumberGroupSeparator ||
118 this.info.PositiveInfinitySymbol != other.info.PositiveInfinitySymbol ||
119 this.info.NegativeSign != other.info.NegativeSign ||
120 this.info.NaNSymbol != other.info.NaNSymbol ||
121 this.info.PercentSymbol != other.info.PercentSymbol ||
122 this.info.PerMilleSymbol != other.info.PerMilleSymbol)
123 throw new XsltCompileException (null, other.baseUri, other.lineNumber, other.linePosition);
126 public string FormatNumber (double number, string pattern)
128 return ParsePatternSet (pattern).FormatNumber (number);
131 private DecimalFormatPatternSet ParsePatternSet (string pattern)
133 return new DecimalFormatPatternSet (pattern, this);
137 // set of positive pattern and negative pattern
138 internal class DecimalFormatPatternSet
140 DecimalFormatPattern positivePattern;
141 DecimalFormatPattern negativePattern;
142 // XslDecimalFormat decimalFormat;
144 public DecimalFormatPatternSet (string pattern, XslDecimalFormat decimalFormat)
146 Parse (pattern, decimalFormat);
149 private void Parse (string pattern, XslDecimalFormat format)
151 positivePattern = new DecimalFormatPattern ();
153 int pos = positivePattern.ParsePattern (0, pattern, format);
154 if (pos < pattern.Length) {
155 if (pattern [pos] != format.PatternSeparator)
156 // Expecting caught and wrapped by caller,
157 // since it cannot provide XPathNavigator.
158 throw new ArgumentException ("Invalid number format pattern string.");
160 negativePattern = new DecimalFormatPattern ();
161 pos = negativePattern.ParsePattern (pos, pattern, format);
162 if (pos < pattern.Length)
163 throw new ArgumentException ("Number format pattern string ends with extraneous part.");
166 negativePattern = positivePattern;
169 public string FormatNumber (double number)
172 return positivePattern.FormatNumber (number);
174 return negativePattern.FormatNumber (number);
178 internal class DecimalFormatPattern
180 public string Prefix;
181 public string Suffix;
182 public string NumberPart;
183 NumberFormatInfo info;
185 StringBuilder builder;
187 internal int ParsePattern (int start, string pattern, XslDecimalFormat format)
189 if (start == 0) // positive pattern
190 this.info = format.Info;
192 this.info = format.Info.Clone () as NumberFormatInfo;
193 info.NegativeSign = String.Empty; // should be specified in Prefix
198 while (pos < pattern.Length) {
199 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.Info.CurrencySymbol [0])
205 Prefix = pattern.Substring (start, pos - start);
206 if (pos == pattern.Length)
207 throw new ArgumentException ("Invalid number format pattern.");
210 pos = ParseNumber (pos, pattern, format);
211 int suffixStart = pos;
214 while (pos < pattern.Length) {
215 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.PatternSeparator || pattern [pos] == format.Info.CurrencySymbol [0])
221 Suffix = pattern.Substring (suffixStart, pos - suffixStart);
226 [MonoTODO ("Collect grouping digits")]
227 private int ParseNumber (int start, string pattern, XslDecimalFormat format)
229 builder = new StringBuilder ();
232 // process non-minint part.
233 for (; pos < pattern.Length; pos++) {
234 if (pattern [pos] == format.Digit)
235 builder.Append ('#');
236 else if (pattern [pos] == format.Info.NumberGroupSeparator [0])
237 builder.Append (',');
243 for (; pos < pattern.Length; pos++) {
244 if (pattern [pos] == format.ZeroDigit)
245 builder.Append ('0');
246 else if (pattern [pos] == format.Info.NumberGroupSeparator [0])
247 builder.Append (',');
252 // optional fraction part
253 if (pos < pattern.Length) {
254 if (pattern [pos] == format.Info.NumberDecimalSeparator [0]) {
255 builder.Append ('.');
258 while (pos < pattern.Length) {
259 if (pattern [pos] == format.ZeroDigit) {
261 builder.Append ('0');
266 while (pos < pattern.Length) {
267 if (pattern [pos] == format.Digit) {
269 builder.Append ('#');
275 // optional exponent part
276 if (pos + 1 < pattern.Length && pattern [pos] == 'E' && pattern [pos + 1] == format.ZeroDigit) {
278 builder.Append ("E0");
279 while (pos < pattern.Length) {
280 if (pattern [pos] == format.ZeroDigit) {
282 builder.Append ('0');
288 // misc special characters
289 if (pos < pattern.Length) {
290 if (pattern [pos] == this.info.PercentSymbol [0])
291 builder.Append ('%');
292 else if (pattern [pos] == this.info.PerMilleSymbol [0])
293 builder.Append ('\u2030');
294 else if (pattern [pos] == this.info.CurrencySymbol [0])
295 throw new ArgumentException ("Currency symbol is not supported for number format pattern string.");
301 NumberPart = builder.ToString ();
305 public string FormatNumber (double number)
308 builder.Append (Prefix);
309 builder.Append (number.ToString (NumberPart, info));
310 builder.Append (Suffix);
311 return builder.ToString ();