The Labs \ Source Viewer \ SSCLI \ System.Xml.Xsl.Runtime \ DecimalFormatter

  1. //------------------------------------------------------------------------------
  2. // <copyright file="DecimalFormatter.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. using System.Diagnostics;
  16. using System.Globalization;
  17. using System.Text;
  18. namespace System.Xml.Xsl.Runtime
  19. {
  20.     using Res = System.Xml.Utils.Res;
  21.    
  22.     internal class DecimalFormat
  23.     {
  24.         public NumberFormatInfo info;
  25.         public char digit;
  26.         public char zeroDigit;
  27.         public char patternSeparator;
  28.        
  29.         internal DecimalFormat(NumberFormatInfo info, char digit, char zeroDigit, char patternSeparator)
  30.         {
  31.             this.info = info;
  32.             this.digit = digit;
  33.             this.zeroDigit = zeroDigit;
  34.             this.patternSeparator = patternSeparator;
  35.         }
  36.     }
  37.    
  38.     internal class DecimalFormatter
  39.     {
  40.         private NumberFormatInfo posFormatInfo;
  41.         private NumberFormatInfo negFormatInfo;
  42.         private string posFormat;
  43.         private string negFormat;
  44.         private char zeroDigit;
  45.        
  46.         // These characters have special meaning for CLR and must be escaped
  47.         // <spec>http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconcustomnumericformatstrings.asp</spec>
  48.         private const string ClrSpecialChars = "0#.,%‰Ee\\'\";";
  49.        
  50.         // This character is used to escape literal (passive) digits '0'..'9'
  51.         private const char EscChar = '\a';
  52.        
  53.         public DecimalFormatter(string formatPicture, DecimalFormat decimalFormat)
  54.         {
  55.             Debug.Assert(formatPicture != null && decimalFormat != null);
  56.             if (formatPicture.Length == 0) {
  57.                 throw XsltException.Create(Res.Xslt_InvalidFormat);
  58.             }
  59.            
  60.             zeroDigit = decimalFormat.zeroDigit;
  61.             posFormatInfo = (NumberFormatInfo)decimalFormat.info.Clone();
  62.             StringBuilder temp = new StringBuilder();
  63.            
  64.             bool integer = true;
  65.             bool sawPattern = false;
  66.             bool sawZeroDigit = false;
  67.             bool sawDigit = false;
  68.             bool sawDecimalSeparator = false;
  69.             bool digitOrZeroDigit = false;
  70.             char decimalSeparator = posFormatInfo.NumberDecimalSeparator[0];
  71.             char groupSeparator = posFormatInfo.NumberGroupSeparator[0];
  72.             char percentSymbol = posFormatInfo.PercentSymbol[0];
  73.             char perMilleSymbol = posFormatInfo.PerMilleSymbol[0];
  74.            
  75.             int commaIndex = 0;
  76.             int groupingSize = 0;
  77.             int decimalIndex = -1;
  78.             int lastDigitIndex = -1;
  79.            
  80.             for (int i = 0; i < formatPicture.Length; i++) {
  81.                 char ch = formatPicture[i];
  82.                
  83.                 if (ch == decimalFormat.digit) {
  84.                     if (sawZeroDigit && integer) {
  85.                         throw XsltException.Create(Res.Xslt_InvalidFormat1, formatPicture);
  86.                     }
  87.                     lastDigitIndex = temp.Length;
  88.                     sawDigit = digitOrZeroDigit = true;
  89.                     temp.Append('#');
  90.                     continue;
  91.                 }
  92.                 if (ch == decimalFormat.zeroDigit) {
  93.                     if (sawDigit && !integer) {
  94.                         throw XsltException.Create(Res.Xslt_InvalidFormat2, formatPicture);
  95.                     }
  96.                     lastDigitIndex = temp.Length;
  97.                     sawZeroDigit = digitOrZeroDigit = true;
  98.                     temp.Append('0');
  99.                     continue;
  100.                 }
  101.                 if (ch == decimalFormat.patternSeparator) {
  102.                     if (!digitOrZeroDigit) {
  103.                         throw XsltException.Create(Res.Xslt_InvalidFormat8);
  104.                     }
  105.                     if (sawPattern) {
  106.                         throw XsltException.Create(Res.Xslt_InvalidFormat3, formatPicture);
  107.                     }
  108.                     sawPattern = true;
  109.                    
  110.                     if (decimalIndex < 0) {
  111.                         decimalIndex = lastDigitIndex + 1;
  112.                     }
  113.                     groupingSize = RemoveTrailingComma(temp, commaIndex, decimalIndex);
  114.                    
  115.                     if (groupingSize > 9) {
  116.                         groupingSize = 0;
  117.                     }
  118.                     posFormatInfo.NumberGroupSizes = new int[] {groupingSize};
  119.                     if (!sawDecimalSeparator) {
  120.                         posFormatInfo.NumberDecimalDigits = 0;
  121.                     }
  122.                    
  123.                     posFormat = temp.ToString();
  124.                    
  125.                     temp.Length = 0;
  126.                     decimalIndex = -1;
  127.                     lastDigitIndex = -1;
  128.                     commaIndex = 0;
  129.                     sawDigit = sawZeroDigit = digitOrZeroDigit = false;
  130.                     sawDecimalSeparator = false;
  131.                     integer = true;
  132.                     negFormatInfo = (NumberFormatInfo)decimalFormat.info.Clone();
  133.                     negFormatInfo.NegativeSign = string.Empty;
  134.                     continue;
  135.                 }
  136.                 if (ch == decimalSeparator) {
  137.                     if (sawDecimalSeparator) {
  138.                         throw XsltException.Create(Res.Xslt_InvalidFormat5, formatPicture);
  139.                     }
  140.                     decimalIndex = temp.Length;
  141.                     sawDecimalSeparator = true;
  142.                     sawDigit = sawZeroDigit = integer = false;
  143.                     temp.Append('.');
  144.                     continue;
  145.                 }
  146.                 if (ch == groupSeparator) {
  147.                     commaIndex = temp.Length;
  148.                     lastDigitIndex = commaIndex;
  149.                     temp.Append(',');
  150.                     continue;
  151.                 }
  152.                 if (ch == percentSymbol) {
  153.                     temp.Append('%');
  154.                     continue;
  155.                 }
  156.                 if (ch == perMilleSymbol) {
  157.                     temp.Append('‰');
  158.                     continue;
  159.                 }
  160.                 if (ch == '\'') {
  161.                     int pos = formatPicture.IndexOf('\'', i + 1);
  162.                     if (pos < 0) {
  163.                         pos = formatPicture.Length - 1;
  164.                     }
  165.                     temp.Append(formatPicture, i, pos - i + 1);
  166.                     i = pos;
  167.                     continue;
  168.                 }
  169.                 // Escape literal digits with EscChar, double literal EscChar
  170.                 if ('0' <= ch && ch <= '9' || ch == EscChar) {
  171.                     if (decimalFormat.zeroDigit != '0') {
  172.                         temp.Append(EscChar);
  173.                     }
  174.                 }
  175.                 // Escape characters having special meaning for CLR
  176.                 if (ClrSpecialChars.IndexOf(ch) >= 0) {
  177.                     temp.Append('\\');
  178.                 }
  179.                 temp.Append(ch);
  180.             }
  181.            
  182.             if (!digitOrZeroDigit) {
  183.                 throw XsltException.Create(Res.Xslt_InvalidFormat8);
  184.             }
  185.             NumberFormatInfo formatInfo = sawPattern ? negFormatInfo : posFormatInfo;
  186.            
  187.             if (decimalIndex < 0) {
  188.                 decimalIndex = lastDigitIndex + 1;
  189.             }
  190.             groupingSize = RemoveTrailingComma(temp, commaIndex, decimalIndex);
  191.             if (groupingSize > 9) {
  192.                 groupingSize = 0;
  193.             }
  194.             formatInfo.NumberGroupSizes = new int[] {groupingSize};
  195.             if (!sawDecimalSeparator) {
  196.                 formatInfo.NumberDecimalDigits = 0;
  197.             }
  198.            
  199.             if (sawPattern) {
  200.                 negFormat = temp.ToString();
  201.             }
  202.             else {
  203.                 posFormat = temp.ToString();
  204.             }
  205.         }
  206.        
  207.         private static int RemoveTrailingComma(StringBuilder builder, int commaIndex, int decimalIndex)
  208.         {
  209.             if (commaIndex > 0 && commaIndex == (decimalIndex - 1)) {
  210.                 builder.Remove(decimalIndex - 1, 1);
  211.             }
  212.             else if (decimalIndex > commaIndex) {
  213.                 return decimalIndex - commaIndex - 1;
  214.             }
  215.             return 0;
  216.         }
  217.        
  218.         public string Format(double value)
  219.         {
  220.             NumberFormatInfo formatInfo;
  221.             string subPicture;
  222.            
  223.             if (value < 0 && negFormatInfo != null) {
  224.                 formatInfo = this.negFormatInfo;
  225.                 subPicture = this.negFormat;
  226.             }
  227.             else {
  228.                 formatInfo = this.posFormatInfo;
  229.                 subPicture = this.posFormat;
  230.             }
  231.            
  232.             string result = value.ToString(subPicture, formatInfo);
  233.            
  234.             if (this.zeroDigit != '0') {
  235.                 StringBuilder builder = new StringBuilder(result.Length);
  236.                 int shift = this.zeroDigit - '0';
  237.                 for (int i = 0; i < result.Length; i++) {
  238.                     char ch = result[i];
  239.                     if ((uint)(ch - '0') <= 9) {
  240.                         ch += (char)shift;
  241.                     }
  242.                     else if (ch == EscChar) {
  243.                         // This is an escaped literal digit or EscChar, thus unescape it. We make use
  244.                         // of the fact that no extra EscChar could be inserted by value.ToString().
  245.                         Debug.Assert(i + 1 < result.Length);
  246.                         ch = result[++i];
  247.                         Debug.Assert('0' <= ch && ch <= '9' || ch == EscChar);
  248.                     }
  249.                     builder.Append(ch);
  250.                 }
  251.                 result = builder.ToString();
  252.             }
  253.             return result;
  254.         }
  255.        
  256.         public static string Format(double value, string formatPicture, DecimalFormat decimalFormat)
  257.         {
  258.             return new DecimalFormatter(formatPicture, decimalFormat).Format(value);
  259.         }
  260.     }
  261. }

Developer Fusion