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

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XmlCollation.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.Diagnostics;
  17. using System.Globalization;
  18. using System.Runtime.InteropServices;
  19. using System.ComponentModel;
  20. namespace System.Xml.Xsl.Runtime
  21. {
  22.     using Res = System.Xml.Utils.Res;
  23.    
  24.     [EditorBrowsable(EditorBrowsableState.Never)]
  25.     public sealed class XmlCollation
  26.     {
  27.         // lgid support for sort
  28.         private const int deDE = 1031;
  29.         private const int huHU = 1038;
  30.         private const int jaJP = 1041;
  31.         private const int kaGE = 1079;
  32.         private const int koKR = 1042;
  33.         private const int zhTW = 1028;
  34.         private const int zhCN = 2052;
  35.         private const int zhHK = 3076;
  36.         private const int zhSG = 4100;
  37.         private const int zhMO = 5124;
  38.         private const int zhTWbopo = 197636;
  39.         private const int deDEphon = 66567;
  40.         private const int huHUtech = 66574;
  41.         private const int kaGEmode = 66615;
  42.        
  43.         // Sort ID
  44.         private const int strksort = 2;
  45.         // Stroke
  46.         private const int unicsort = 1;
  47.         // Unicode
  48.         // Options
  49.         private const string ignoreCaseStr = "IGNORECASE";
  50.         private const string ignoreKanatypeStr = "IGNOREKANATYPE";
  51.         private const string ignoreNonspaceStr = "IGNORENONSPACE";
  52.         private const string ignoreSymbolsStr = "IGNORESYMBOLS";
  53.         private const string ignoreWidthStr = "IGNOREWIDTH";
  54.         private const string upperFirstStr = "UPPERFIRST";
  55.         private const string emptyGreatestStr = "EMPTYGREATEST";
  56.         private const string descendingOrderStr = "DESCENDINGORDER";
  57.         private const string sortStr = "SORT";
  58.        
  59.         private bool upperFirst;
  60.         private bool emptyGreatest;
  61.         private bool descendingOrder;
  62.         private CultureInfo cultinfo;
  63.         private CompareOptions compops;
  64.        
  65.        
  66.         //-----------------------------------------------
  67.         // Constructors
  68.         //-----------------------------------------------
  69.        
  70.         /// <summary>
  71.         /// By default, create a collation that uses the current thread's culture, and has no compare options set
  72.         /// </summary>
  73.         private XmlCollation() : this(null, CompareOptions.None)
  74.         {
  75.         }
  76.        
  77.         /// <summary>
  78.         /// Construct a collation that uses the specified culture and compare options.
  79.         /// </summary>
  80.         private XmlCollation(CultureInfo cultureInfo, CompareOptions compareOptions)
  81.         {
  82.             this.cultinfo = cultureInfo;
  83.             this.compops = compareOptions;
  84.         }
  85.        
  86.        
  87.         //-----------------------------------------------
  88.         // Create
  89.         //-----------------------------------------------
  90.        
  91.         /// <summary>
  92.         /// Singleton collation that sorts according to Unicode code points.
  93.         /// </summary>
  94.         private static XmlCollation cp = new XmlCollation(CultureInfo.InvariantCulture, CompareOptions.Ordinal);
  95.         static internal XmlCollation CodePointCollation {
  96.             get { return cp; }
  97.         }
  98.        
  99.         /// <summary>
  100.         /// This function is used in both parser and f&o library, so just strictly map valid literals to XmlCollation.
  101.         /// Set compare options one by one:
  102.         /// 0, false: no effect; 1, true: yes
  103.         /// Disregard unrecognized options.
  104.         /// </summary>
  105.         static internal XmlCollation Create(string collationLiteral)
  106.         {
  107.             Debug.Assert(collationLiteral != null, "collation literal should not be null");
  108.            
  109.             if (collationLiteral == XmlReservedNs.NsCollCodePoint) {
  110.                 return CodePointCollation;
  111.             }
  112.            
  113.             XmlCollation coll = new XmlCollation();
  114.             Uri collationUri = new Uri(collationLiteral);
  115.            
  116.             string authority = collationUri.GetLeftPart(UriPartial.Authority);
  117.             if (authority == XmlReservedNs.NsCollationBase) {
  118.                 // Language
  119.                 // at least a '/' will be returned for Uri.LocalPath
  120.                 string lang = collationUri.LocalPath.Substring(1);
  121.                 if (lang.Length == 0) {
  122.                     // Use default culture of current thread (cultinfo = null)
  123.                 }
  124.                 else {
  125.                     // Create culture from RFC 1766 string
  126.                     try {
  127.                         coll.cultinfo = new CultureInfo(lang);
  128.                     }
  129.                     catch (ArgumentException) {
  130.                         throw new XslTransformException(Res.Coll_UnsupportedLanguage, lang);
  131.                     }
  132.                 }
  133.             }
  134.             else if (collationUri.IsBaseOf(new Uri(XmlReservedNs.NsCollCodePoint))) {
  135.                 // language with codepoint collation is not allowed
  136.                 coll.compops = CompareOptions.Ordinal;
  137.             }
  138.             else {
  139.                 // Unrecognized collation
  140.                 throw new XslTransformException(Res.Coll_Unsupported, collationLiteral);
  141.             }
  142.            
  143.             // Sort & Compare option
  144.             // at least a '?' will be returned for Uri.Query if not empty
  145.             string query = collationUri.Query;
  146.             string sort = null;
  147.            
  148.             if (query.Length != 0) {
  149.                 foreach (string option in query.Substring(1).Split('&')) {
  150.                     string[] pair = option.Split('=');
  151.                    
  152.                     if (pair.Length != 2)
  153.                         throw new XslTransformException(Res.Coll_BadOptFormat, option);
  154.                    
  155.                     string optionName = pair[0].ToUpper(CultureInfo.InvariantCulture);
  156.                     string optionValue = pair[1].ToUpper(CultureInfo.InvariantCulture);
  157.                    
  158.                     if (optionName == sortStr) {
  159.                         sort = optionValue;
  160.                     }
  161.                     else if (optionValue == "1" || optionValue == "TRUE") {
  162.                         switch (optionName) {
  163.                             case ignoreCaseStr:
  164.                                 coll.compops |= CompareOptions.IgnoreCase;
  165.                                 break;
  166.                             case ignoreKanatypeStr:
  167.                                 coll.compops |= CompareOptions.IgnoreKanaType;
  168.                                 break;
  169.                             case ignoreNonspaceStr:
  170.                                 coll.compops |= CompareOptions.IgnoreNonSpace;
  171.                                 break;
  172.                             case ignoreSymbolsStr:
  173.                                 coll.compops |= CompareOptions.IgnoreSymbols;
  174.                                 break;
  175.                             case ignoreWidthStr:
  176.                                 coll.compops |= CompareOptions.IgnoreWidth;
  177.                                 break;
  178.                             case upperFirstStr:
  179.                                 coll.upperFirst = true;
  180.                                 break;
  181.                             case emptyGreatestStr:
  182.                                 coll.emptyGreatest = true;
  183.                                 break;
  184.                             case descendingOrderStr:
  185.                                 coll.descendingOrder = true;
  186.                                 break;
  187.                             default:
  188.                                 throw new XslTransformException(Res.Coll_UnsupportedOpt, pair[0]);
  189.                                 break;
  190.                         }
  191.                     }
  192.                     else if (optionValue == "0" || optionValue == "FALSE") {
  193.                         switch (optionName) {
  194.                             case ignoreCaseStr:
  195.                                 coll.compops &= ~CompareOptions.IgnoreCase;
  196.                                 break;
  197.                             case ignoreKanatypeStr:
  198.                                 coll.compops &= ~CompareOptions.IgnoreKanaType;
  199.                                 break;
  200.                             case ignoreNonspaceStr:
  201.                                 coll.compops &= ~CompareOptions.IgnoreNonSpace;
  202.                                 break;
  203.                             case ignoreSymbolsStr:
  204.                                 coll.compops &= ~CompareOptions.IgnoreSymbols;
  205.                                 break;
  206.                             case ignoreWidthStr:
  207.                                 coll.compops &= ~CompareOptions.IgnoreWidth;
  208.                                 break;
  209.                             case upperFirstStr:
  210.                                 coll.upperFirst = false;
  211.                                 break;
  212.                             case emptyGreatestStr:
  213.                                 coll.emptyGreatest = false;
  214.                                 break;
  215.                             case descendingOrderStr:
  216.                                 coll.descendingOrder = false;
  217.                                 break;
  218.                             default:
  219.                                 throw new XslTransformException(Res.Coll_UnsupportedOpt, pair[0]);
  220.                                 break;
  221.                         }
  222.                     }
  223.                     else {
  224.                         throw new XslTransformException(Res.Coll_UnsupportedOptVal, pair[0], pair[1]);
  225.                     }
  226.                 }
  227.             }
  228.            
  229.             // upperfirst option is only meaningful when not ignore case
  230.             if (coll.upperFirst && (coll.compops & CompareOptions.IgnoreCase) != 0)
  231.                 coll.upperFirst = false;
  232.            
  233.             // other CompareOptions are only meaningful if Ordinal comparison is not being used
  234.             if ((coll.compops & CompareOptions.Ordinal) != 0) {
  235.                 coll.compops = CompareOptions.Ordinal;
  236.                 coll.upperFirst = false;
  237.             }
  238.            
  239.             // new cultureinfo based on alternate sorting option
  240.             if (sort != null && coll.cultinfo != null) {
  241.                 int lgid = GetLangID(coll.cultinfo.LCID);
  242.                 switch (sort) {
  243.                     case "bopo":
  244.                         if (lgid == zhTW) {
  245.                             coll.cultinfo = new CultureInfo(zhTWbopo);
  246.                         }
  247.                         break;
  248.                     case "strk":
  249.                         if (lgid == zhCN || lgid == zhHK || lgid == zhSG || lgid == zhMO) {
  250.                             coll.cultinfo = new CultureInfo(MakeLCID(coll.cultinfo.LCID, strksort));
  251.                         }
  252.                         break;
  253.                     case "uni":
  254.                         if (lgid == jaJP || lgid == koKR) {
  255.                             coll.cultinfo = new CultureInfo(MakeLCID(coll.cultinfo.LCID, unicsort));
  256.                         }
  257.                         break;
  258.                     case "phn":
  259.                         if (lgid == deDE) {
  260.                             coll.cultinfo = new CultureInfo(deDEphon);
  261.                         }
  262.                         break;
  263.                     case "tech":
  264.                         if (lgid == huHU) {
  265.                             coll.cultinfo = new CultureInfo(huHUtech);
  266.                         }
  267.                         break;
  268.                     case "mod":
  269.                         // ka-GE(Georgian - Georgia) Modern Sort: 0x00010437
  270.                         if (lgid == kaGE) {
  271.                             coll.cultinfo = new CultureInfo(kaGEmode);
  272.                         }
  273.                         break;
  274.                     case "pron":
  275.                     case "dict":
  276.                     case "trad":
  277.                         // es-ES(Spanish - Spain) Traditional: 0x0000040A
  278.                         // They are removing 0x040a (Spanish Traditional sort) in NLS+.
  279.                         // So if you create 0x040a, it's just like 0x0c0a (Spanish International sort).
  280.                         // Thus I don't handle it differently.
  281.                         break;
  282.                     default:
  283.                         throw new XslTransformException(Res.Coll_UnsupportedSortOpt, sort);
  284.                         break;
  285.                 }
  286.             }
  287.             return coll;
  288.         }
  289.        
  290.        
  291.         //-----------------------------------------------
  292.         // Compare Properties
  293.         //-----------------------------------------------
  294.        
  295.         internal bool EmptyGreatest {
  296.             get { return this.emptyGreatest; }
  297.         }
  298.        
  299.         internal bool DescendingOrder {
  300.             get { return this.descendingOrder; }
  301.         }
  302.        
  303.         internal CultureInfo Culture {
  304.             get {
  305.                 // Use default thread culture if this.cultinfo = null
  306.                 if (this.cultinfo == null)
  307.                     return CultureInfo.CurrentCulture;
  308.                
  309.                 return this.cultinfo;
  310.             }
  311.         }
  312.        
  313.        
  314.         //-----------------------------------------------
  315.         //
  316.         //-----------------------------------------------
  317.        
  318.         /// <summary>
  319.         /// Create a sort key that can be compared quickly with other keys.
  320.         /// </summary>
  321.         internal XmlSortKey CreateSortKey(string s)
  322.         {
  323.             SortKey sortKey;
  324.             byte[] bytesKey;
  325.             int idx;
  326.            
  327.             sortKey = Culture.CompareInfo.GetSortKey(s, this.compops);
  328.            
  329.             // Create an XmlStringSortKey using the SortKey if possible
  330.             #if DEBUG
  331.             // In debug-only code, test other code path more frequently
  332.             if (!this.upperFirst && this.descendingOrder)
  333.                 return new XmlStringSortKey(sortKey, this.descendingOrder);
  334.             #else
  335.             if (!this.upperFirst)
  336.                 return new XmlStringSortKey(sortKey, this.descendingOrder);
  337.             #endif
  338.            
  339.             // Get byte buffer from SortKey and modify it
  340.             bytesKey = sortKey.KeyData;
  341.             if (this.upperFirst && bytesKey.Length != 0) {
  342.                 // By default lower-case is always sorted first for any locale (verified by empirical testing).
  343.                 // In order to place upper-case first, invert the case weights in the generated sort key.
  344.                 // Skip to case weight section (3rd weight section)
  345.                 idx = 0;
  346.                 while (bytesKey[idx] != 1)
  347.                     idx++;
  348.                
  349.                 do {
  350.                     idx++;
  351.                 }
  352.                 while (bytesKey[idx] != 1);
  353.                
  354.                 // Invert all case weights (including terminating 0x1)
  355.                 do {
  356.                     idx++;
  357.                     bytesKey[idx] ^= 255;
  358.                 }
  359.                 while (bytesKey[idx] != 254);
  360.             }
  361.            
  362.             return new XmlStringSortKey(bytesKey, this.descendingOrder);
  363.         }
  364.        
  365.        
  366.        
  367.         //-----------------------------------------------
  368.         // Helper Functions
  369.         //-----------------------------------------------
  370.        
  371.         private static int MakeLCID(int langid, int sortid)
  372.         {
  373.             return (langid & 65535) | ((sortid & 15) << 16);
  374.         }
  375.        
  376.         private static int GetLangID(int lcid)
  377.         {
  378.             return (lcid & 65535);
  379.         }
  380.     }
  381. }

Developer Fusion