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

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XslNumber.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.Collections;
  16. using System.Collections.Generic;
  17. using System.Diagnostics;
  18. using System.Text;
  19. using System.Xml.XPath;
  20. namespace System.Xml.Xsl.Runtime
  21. {
  22.    
  23.     internal class TokenInfo
  24.     {
  25.         public char startChar;
  26.         // First element of numbering sequence for format token
  27.         public int startIdx;
  28.         // Start index of separator token
  29.         public string formatString;
  30.         // Format string for separator token
  31.         public int length;
  32.         // Length of separator token, or minimum length of decimal numbers for format token
  33.         // Instances of this internal class must be created via CreateFormat and CreateSeparator
  34.         private TokenInfo()
  35.         {
  36.         }
  37.        
  38.         [Conditional("DEBUG")]
  39.         public void AssertSeparator(bool isSeparator)
  40.         {
  41.             Debug.Assert(isSeparator == (formatString != null), "AssertSeparator");
  42.         }
  43.        
  44.         // Creates a TokenInfo for a separator token.
  45.         public static TokenInfo CreateSeparator(string formatString, int startIdx, int tokLen)
  46.         {
  47.             Debug.Assert(startIdx >= 0 && tokLen > 0);
  48.             TokenInfo token = new TokenInfo();
  49.             {
  50.                 token.startIdx = startIdx;
  51.                 token.formatString = formatString;
  52.                 token.length = tokLen;
  53.             }
  54.             return token;
  55.         }
  56.        
  57.         // Maps a token of alphanumeric characters to a numbering format ID and a
  58.         // minimum length bound. Tokens specify the character(s) that begins a Unicode
  59.         // numbering sequence. For example, "i" specifies lower case roman numeral
  60.         // numbering. Leading "zeros" specify a minimum length to be maintained by
  61.         // padding, if necessary.
  62.         public static TokenInfo CreateFormat(string formatString, int startIdx, int tokLen)
  63.         {
  64.             Debug.Assert(startIdx >= 0 && tokLen > 0);
  65.             TokenInfo token = new TokenInfo();
  66.             token.formatString = null;
  67.             token.length = 1;
  68.            
  69.             bool useDefault = false;
  70.             char ch = formatString[startIdx];
  71.            
  72.             switch (ch) {
  73.                 case '1':
  74.                 case 'A':
  75.                 case 'I':
  76.                 case 'a':
  77.                 case 'i':
  78.                     break;
  79.                 default:
  80.                     // NOTE: We do not support Tamil and Ethiopic numbering systems having no zeros
  81.                     if (CharUtil.IsDecimalDigitOne(ch)) {
  82.                         break;
  83.                     }
  84.                     if (CharUtil.IsDecimalDigitOne((char)(ch + 1))) {
  85.                         // Leading zeros request padding. Track how much.
  86.                         int idx = startIdx;
  87.                         do {
  88.                             token.length++;
  89.                         }
  90.                         while (--tokLen > 0 && ch == formatString[++idx]);
  91.                        
  92.                         // Recognize the token only if the next character is "one"
  93.                         if (formatString[idx] == ++ch) {
  94.                             break;
  95.                         }
  96.                     }
  97.                     useDefault = true;
  98.                     break;
  99.             }
  100.            
  101.             if (tokLen != 1) {
  102.                 // If remaining token length is not 1, do not recognize the token
  103.                 useDefault = true;
  104.             }
  105.            
  106.             if (useDefault) {
  107.                 // Default to Arabic numbering with no zero padding
  108.                 token.startChar = NumberFormatter.DefaultStartChar;
  109.                 token.length = 1;
  110.             }
  111.             else {
  112.                 token.startChar = ch;
  113.             }
  114.             return token;
  115.         }
  116.     }
  117.    
  118.     internal class NumberFormatter : NumberFormatterBase
  119.     {
  120.         private string formatString;
  121.         private int lang;
  122.         private string letterValue;
  123.         private string groupingSeparator;
  124.         private int groupingSize;
  125.        
  126.         private List<TokenInfo> tokens;
  127.        
  128.         public const char DefaultStartChar = '1';
  129.         private static readonly TokenInfo DefaultFormat = TokenInfo.CreateFormat("0", 0, 1);
  130.         private static readonly TokenInfo DefaultSeparator = TokenInfo.CreateSeparator(".", 0, 1);
  131.        
  132.         // Creates a Format object parsing format string into format tokens (alphanumeric) and separators (non-alphanumeric).
  133.         public NumberFormatter(string formatString, int lang, string letterValue, string groupingSeparator, int groupingSize)
  134.         {
  135.             Debug.Assert(groupingSeparator.Length <= 1);
  136.             this.formatString = formatString;
  137.             this.lang = lang;
  138.             this.letterValue = letterValue;
  139.             this.groupingSeparator = groupingSeparator;
  140.             this.groupingSize = groupingSeparator.Length > 0 ? groupingSize : 0;
  141.            
  142.             if (formatString == "1" || formatString.Length == 0) {
  143.                 // Special case of the default format
  144.                 return;
  145.             }
  146.            
  147.             this.tokens = new List<TokenInfo>();
  148.             int idxStart = 0;
  149.             bool isAlphaNumeric = CharUtil.IsAlphaNumeric(formatString[idxStart]);
  150.            
  151.             if (isAlphaNumeric) {
  152.                 // If the first one is alpha num add empty separator as a prefix
  153.                 tokens.Add(null);
  154.             }
  155.            
  156.             for (int idx = 0; idx <= formatString.Length; idx++) {
  157.                 // Loop until a switch from formatString token to separator is detected (or vice-versa)
  158.                 if (idx == formatString.Length || isAlphaNumeric != CharUtil.IsAlphaNumeric(formatString[idx])) {
  159.                     if (isAlphaNumeric) {
  160.                         // Just finished a format token
  161.                         tokens.Add(TokenInfo.CreateFormat(formatString, idxStart, idx - idxStart));
  162.                     }
  163.                     else {
  164.                         // Just finished a separator token
  165.                         tokens.Add(TokenInfo.CreateSeparator(formatString, idxStart, idx - idxStart));
  166.                     }
  167.                    
  168.                     // Begin parsing the next format token or separator
  169.                     idxStart = idx;
  170.                    
  171.                     // Flip flag from format token to separator or vice-versa
  172.                     isAlphaNumeric = !isAlphaNumeric;
  173.                 }
  174.             }
  175.         }
  176.        
  177.         /// <summary>
  178.         /// Format the given xsl:number place marker
  179.         /// </summary>
  180.         /// <param name="val">Place marker - either a sequence of ints, or a double singleton</param>
  181.         /// <returns>Formatted string</returns>
  182.         public string FormatSequence(IList<XPathItem> val)
  183.         {
  184.             StringBuilder sb = new StringBuilder();
  185.            
  186.             // If the value was supplied directly, in the 'value' attribute, check its validity
  187.             if (val.Count == 1 && val[0].ValueType == typeof(double)) {
  188.                 double dblVal = val[0].ValueAsDouble;
  189.                 if (!(0.5 <= dblVal && dblVal < double.PositiveInfinity)) {
  190.                     // Errata E24: It is an error if the number is NaN, infinite or less than 0.5; an XSLT processor may signal
  191.                     // the error; if it does not signal the error, it must recover by converting the number to a string as if
  192.                     // by a call to the 'string' function and inserting the resulting string into the result tree.
  193.                     return XPathConvert.DoubleToString(dblVal);
  194.                 }
  195.             }
  196.            
  197.             if (tokens == null) {
  198.                 // Special case of the default format
  199.                 for (int idx = 0; idx < val.Count; idx++) {
  200.                     if (idx > 0) {
  201.                         sb.Append('.');
  202.                     }
  203.                     FormatItem(sb, val[idx], DefaultStartChar, 1);
  204.                 }
  205.             }
  206.             else {
  207.                 int cFormats = tokens.Count;
  208.                 TokenInfo prefix = tokens[0];
  209.                 TokenInfo suffix;
  210.                
  211.                 if (cFormats % 2 == 0) {
  212.                     suffix = null;
  213.                 }
  214.                 else {
  215.                     suffix = tokens[--cFormats];
  216.                 }
  217.                
  218.                 TokenInfo periodicSeparator = 2 < cFormats ? tokens[cFormats - 2] : DefaultSeparator;
  219.                 TokenInfo periodicFormat = 0 < cFormats ? tokens[cFormats - 1] : DefaultFormat;
  220.                
  221.                 if (prefix != null) {
  222.                     prefix.AssertSeparator(true);
  223.                     sb.Append(prefix.formatString, prefix.startIdx, prefix.length);
  224.                 }
  225.                
  226.                 int valCount = val.Count;
  227.                 for (int i = 0; i < valCount; i++) {
  228.                     int formatIndex = i * 2;
  229.                     bool haveFormat = formatIndex < cFormats;
  230.                    
  231.                     if (i > 0) {
  232.                         TokenInfo thisSeparator = haveFormat ? tokens[formatIndex + 0] : periodicSeparator;
  233.                         thisSeparator.AssertSeparator(true);
  234.                         sb.Append(thisSeparator.formatString, thisSeparator.startIdx, thisSeparator.length);
  235.                     }
  236.                    
  237.                     TokenInfo thisFormat = haveFormat ? tokens[formatIndex + 1] : periodicFormat;
  238.                     thisFormat.AssertSeparator(false);
  239.                     FormatItem(sb, val[i], thisFormat.startChar, thisFormat.length);
  240.                 }
  241.                
  242.                 if (suffix != null) {
  243.                     suffix.AssertSeparator(true);
  244.                     sb.Append(suffix.formatString, suffix.startIdx, suffix.length);
  245.                 }
  246.             }
  247.             return sb.ToString();
  248.         }
  249.        
  250.         private void FormatItem(StringBuilder sb, XPathItem item, char startChar, int length)
  251.         {
  252.             double dblVal;
  253.            
  254.             if (item.ValueType == typeof(int)) {
  255.                 dblVal = (double)item.ValueAsInt;
  256.             }
  257.             else {
  258.                 Debug.Assert(item.ValueType == typeof(double), "Item must be either of type int, or double");
  259.                 dblVal = XsltFunctions.Round(item.ValueAsDouble);
  260.             }
  261.            
  262.             Debug.Assert(1 <= dblVal && dblVal < double.PositiveInfinity);
  263.             char zero = '0';
  264.            
  265.             switch (startChar) {
  266.                 case '1':
  267.                     break;
  268.                 case 'A':
  269.                 case 'a':
  270.                     if (dblVal <= MaxAlphabeticValue) {
  271.                         ConvertToAlphabetic(sb, dblVal, startChar, 26);
  272.                         return;
  273.                     }
  274.                     break;
  275.                 case 'I':
  276.                 case 'i':
  277.                     if (dblVal <= MaxRomanValue) {
  278.                             /*upperCase:*/                        ConvertToRoman(sb, dblVal, startChar == 'I');
  279.                         return;
  280.                     }
  281.                     break;
  282.                 default:
  283.                     Debug.Assert(CharUtil.IsDecimalDigitOne(startChar), "Unexpected startChar: " + startChar);
  284.                     zero = (char)(startChar - 1);
  285.                     break;
  286.             }
  287.            
  288.             sb.Append(ConvertToDecimal(dblVal, length, zero, groupingSeparator, groupingSize));
  289.         }
  290.        
  291.         private static string ConvertToDecimal(double val, int minLen, char zero, string groupSeparator, int groupSize)
  292.         {
  293.             Debug.Assert(val >= 0 && val == Math.Round(val), "ConvertToArabic operates on non-negative integer numbers only");
  294.             string str = XPathConvert.DoubleToString(val);
  295.             int shift = zero - '0';
  296.            
  297.             // Figure out new string length without separators
  298.             int oldLen = str.Length;
  299.             int newLen = Math.Max(oldLen, minLen);
  300.            
  301.             // Calculate length of string with separators
  302.             if (groupSize != 0) {
  303.                 Debug.Assert(groupSeparator.Length == 1);
  304.                 checked {
  305.                     newLen += (newLen - 1) / groupSize;
  306.                 }
  307.             }
  308.            
  309.             // If the new number of characters equals the old one, no changes need to be made
  310.             if (newLen == oldLen && shift == 0) {
  311.                 return str;
  312.             }
  313.            
  314.             // If grouping is not needed, add zero padding only
  315.             if (groupSize == 0 && shift == 0) {
  316.                 return str.PadLeft(newLen, zero);
  317.             }
  318.            
  319.             // Add both grouping separators and zero padding to the string representation of a number
  320.             #if true
  321.             unsafe {
  322.                 char* result = stackalloc char[newLen];
  323.                 char separator = (groupSeparator.Length > 0) ? groupSeparator[0] : ' ';
  324.                
  325.                 fixed (char* pin = str) {
  326.                     char* pOldEnd = pin + oldLen - 1;
  327.                     char* pNewEnd = result + newLen - 1;
  328.                     int cnt = groupSize;
  329.                    
  330.                     while (true) {
  331.                         // Move digit to its new location (zero if we've run out of digits)
  332.                         *pNewEnd-- = (pOldEnd >= pin) ? (char)(*pOldEnd-- + shift) : zero;
  333.                         if (pNewEnd < result) {
  334.                             break;
  335.                         }
  336.                         if (--cnt == 0)/*groupSize > 0 && */ {
  337.                             // Every groupSize digits insert the separator
  338.                             *pNewEnd-- = separator;
  339.                             cnt = groupSize;
  340.                             Debug.Assert(pNewEnd >= result, "Separator cannot be the first character");
  341.                         }
  342.                     }
  343.                 }
  344.                 return new string(result, 0, newLen);
  345.             }
  346.             #else
  347.             // Safe version is about 20% slower after NGEN
  348.             char[] result = new char[newLen];
  349.             char separator = (groupSeparator.Length > 0) ? groupSeparator[0] : ' ';
  350.            
  351.             int oldEnd = oldLen - 1;
  352.             int newEnd = newLen - 1;
  353.             int cnt = groupSize;
  354.            
  355.             while (true) {
  356.                 // Move digit to its new location (zero if we've run out of digits)
  357.                 result[newEnd--] = (oldEnd >= 0) ? (char)(str[oldEnd--] + shift) : zero;
  358.                 if (newEnd < 0) {
  359.                     break;
  360.                 }
  361.                 if (--cnt == 0)/*groupSize > 0 && */ {
  362.                     // Every groupSize digits insert the separator
  363.                     result[newEnd--] = separator;
  364.                     cnt = groupSize;
  365.                     Debug.Assert(newEnd >= 0, "Separator cannot be the first character");
  366.                 }
  367.             }
  368.             return new string(result, 0, newLen);
  369.             #endif
  370.         }
  371.     }
  372. }

Developer Fusion