5 // Ben Maurer (bmaurer@users.sourceforge.net)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 using System.Collections;
33 using System.Globalization;
36 using System.Xml.XPath;
39 using QName = System.Xml.XmlQualifiedName;
41 namespace Mono.Xml.Xsl {
42 internal class XslDecimalFormat {
44 NumberFormatInfo info = new NumberFormatInfo ();
45 char digit = '#', zeroDigit = '0', patternSeparator = ';';
50 public static readonly XslDecimalFormat Default = new XslDecimalFormat ();
52 XslDecimalFormat () {} // Default ctor for default info.
53 public XslDecimalFormat (Compiler c)
55 XPathNavigator n = c.Input;
57 IXmlLineInfo li = n as IXmlLineInfo;
59 lineNumber = li.LineNumber;
60 linePosition = li.LinePosition;
64 if (n.MoveToFirstAttribute ()) {
66 if (n.NamespaceURI != String.Empty)
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;
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;
84 info.PositiveInfinitySymbol = n.Value;
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;
92 info.NaNSymbol = n.Value;
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;
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;
105 if (n.Value.Length != 1)
106 throw new XsltCompileException ("XSLT digit value must be exact one character.", null, n);
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];
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];
120 } while (n.MoveToNextAttribute ());
123 info.NegativeInfinitySymbol = info.NegativeSign + info.PositiveInfinitySymbol;
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; } }
132 public void CheckSameAs (XslDecimalFormat other)
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);
147 public string FormatNumber (double number, string pattern)
149 return ParsePatternSet (pattern).FormatNumber (number);
152 private DecimalFormatPatternSet ParsePatternSet (string pattern)
154 return new DecimalFormatPatternSet (pattern, this);
158 // set of positive pattern and negative pattern
159 internal class DecimalFormatPatternSet
161 DecimalFormatPattern positivePattern;
162 DecimalFormatPattern negativePattern;
163 // XslDecimalFormat decimalFormat;
165 public DecimalFormatPatternSet (string pattern, XslDecimalFormat decimalFormat)
167 Parse (pattern, decimalFormat);
170 private void Parse (string pattern, XslDecimalFormat format)
172 positivePattern = new DecimalFormatPattern ();
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.");
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.");
187 negativePattern = positivePattern;
190 public string FormatNumber (double number)
193 return positivePattern.FormatNumber (number);
195 return negativePattern.FormatNumber (number);
199 internal class DecimalFormatPattern
201 public string Prefix;
202 public string Suffix;
203 public string NumberPart;
204 NumberFormatInfo info;
206 StringBuilder builder;
208 internal int ParsePattern (int start, string pattern, XslDecimalFormat format)
210 if (start == 0) // positive pattern
211 this.info = format.Info;
213 this.info = format.Info.Clone () as NumberFormatInfo;
214 info.NegativeSign = String.Empty; // should be specified in Prefix
219 while (pos < pattern.Length) {
220 if (pattern [pos] == format.ZeroDigit || pattern [pos] == format.Digit || pattern [pos] == format.Info.CurrencySymbol [0])
226 Prefix = pattern.Substring (start, pos - start);
227 if (pos == pattern.Length)
228 throw new ArgumentException ("Invalid number format pattern.");
231 pos = ParseNumber (pos, pattern, format);
232 int suffixStart = pos;
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])
242 Suffix = pattern.Substring (suffixStart, pos - suffixStart);
247 [MonoTODO ("Collect grouping digits")]
248 private int ParseNumber (int start, string pattern, XslDecimalFormat format)
250 builder = new StringBuilder ();
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 (',');
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 (',');
273 // optional fraction part
274 if (pos < pattern.Length) {
275 if (pattern [pos] == format.Info.NumberDecimalSeparator [0]) {
276 builder.Append ('.');
279 while (pos < pattern.Length) {
280 if (pattern [pos] == format.ZeroDigit) {
282 builder.Append ('0');
287 while (pos < pattern.Length) {
288 if (pattern [pos] == format.Digit) {
290 builder.Append ('#');
296 // optional exponent part
297 if (pos + 1 < pattern.Length && pattern [pos] == 'E' && pattern [pos + 1] == format.ZeroDigit) {
299 builder.Append ("E0");
300 while (pos < pattern.Length) {
301 if (pattern [pos] == format.ZeroDigit) {
303 builder.Append ('0');
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.");
322 NumberPart = builder.ToString ();
326 public string FormatNumber (double number)
329 builder.Append (Prefix);
330 builder.Append (number.ToString (NumberPart, info));
331 builder.Append (Suffix);
332 return builder.ToString ();