1 //------------------------------------------------------------------------------
2 // <copyright file="XmlSortKey.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
8 using System.Diagnostics;
9 using System.Globalization;
11 namespace System.Xml.Xsl.Runtime {
14 /// Base internal class for all sort keys.
15 /// Inherits from IComparable, so that Array.Sort can perform comparison.
17 internal abstract class XmlSortKey : IComparable {
18 private int priority; // Original input ordering used to ensure that sort is stable
19 private XmlSortKey nextKey; // Next sort key if there are multiple keys (null otherwise)
22 /// Get or set this key's index, relative to other keys involved in a sort. This priority will
23 /// break ties. If the priority is not set, then the sort will not be stable.
26 //get { return this.priority; }
28 // All linked keys have same priority
29 XmlSortKey key = this;
39 /// Sometimes a key is composed of multiple parts. For example: (LastName, FirstName). Multi-part
40 /// keys are linked together in a list. This method recursively adds a new key part to the end of the list.
41 /// Returns the first (primary) key in the list.
43 public XmlSortKey AddSortKey(XmlSortKey sortKey) {
44 if (this.nextKey != null) {
45 // Add to end of list--this is not it
46 this.nextKey.AddSortKey(sortKey);
49 // This is the end of the list
50 this.nextKey = sortKey;
57 /// When two keys are compared and found to be equal, the tie must be broken. If there is a secondary key,
58 /// then use that to break the tie. Otherwise, use the input ordering to break the tie. Since every key
59 /// has a unique index, this is guaranteed to always break the tie.
61 protected int BreakSortingTie(XmlSortKey that) {
62 if (this.nextKey != null) {
63 // There are multiple keys, so break tie using next key
64 Debug.Assert(this.nextKey != null && that.nextKey != null);
65 return this.nextKey.CompareTo(that.nextKey);
68 Debug.Assert(this.priority != that.priority);
69 return (this.priority < that.priority) ? -1 : 1;
73 /// Compare a non-empty key (this) to an empty key (obj). The empty sequence always sorts either before all
74 /// other values, or after all other values.
76 protected int CompareToEmpty(object obj) {
77 XmlEmptySortKey that = obj as XmlEmptySortKey;
78 Debug.Assert(that != null && !(this is XmlEmptySortKey));
79 return that.IsEmptyGreatest ? -1 : 1;
83 /// Base internal class is abstract and doesn't actually implement CompareTo; derived classes must do this.
85 public abstract int CompareTo(object that);
90 /// Sort key for the empty sequence. Empty sequence always compares sorts either before all other values,
91 /// or after all other values.
93 internal class XmlEmptySortKey : XmlSortKey {
94 private bool isEmptyGreatest;
96 public XmlEmptySortKey(XmlCollation collation) {
97 // Greatest, Ascending: isEmptyGreatest = true
98 // Greatest, Descending: isEmptyGreatest = false
99 // Least, Ascending: isEmptyGreatest = false
100 // Least, Descending: isEmptyGreatest = true
101 this.isEmptyGreatest = collation.EmptyGreatest != collation.DescendingOrder;
104 public bool IsEmptyGreatest {
105 get { return this.isEmptyGreatest; }
108 public override int CompareTo(object obj) {
109 XmlEmptySortKey that = obj as XmlEmptySortKey;
112 // Empty compared to non-empty
113 Debug.Assert(obj is XmlSortKey);
114 return -(obj as XmlSortKey).CompareTo(this);
117 // Empty compared to empty
118 return BreakSortingTie(that);
124 /// Sort key for xs:decimal values.
126 internal class XmlDecimalSortKey : XmlSortKey {
127 private decimal decVal;
129 public XmlDecimalSortKey(decimal value, XmlCollation collation) {
130 // Invert decimal if sorting in descending order
131 this.decVal = collation.DescendingOrder ? -value : value;
134 public override int CompareTo(object obj) {
135 XmlDecimalSortKey that = obj as XmlDecimalSortKey;
139 return CompareToEmpty(obj);
141 cmp = Decimal.Compare(this.decVal, that.decVal);
143 return BreakSortingTie(that);
151 /// Sort key for xs:integer values.
153 internal class XmlIntegerSortKey : XmlSortKey {
154 private long longVal;
156 public XmlIntegerSortKey(long value, XmlCollation collation) {
157 // Invert long if sorting in descending order
158 this.longVal = collation.DescendingOrder ? ~value : value;
161 public override int CompareTo(object obj) {
162 XmlIntegerSortKey that = obj as XmlIntegerSortKey;
165 return CompareToEmpty(obj);
167 if (this.longVal == that.longVal)
168 return BreakSortingTie(that);
170 return (this.longVal < that.longVal) ? -1 : 1;
176 /// Sort key for xs:int values.
178 internal class XmlIntSortKey : XmlSortKey {
181 public XmlIntSortKey(int value, XmlCollation collation) {
182 // Invert integer if sorting in descending order
183 this.intVal = collation.DescendingOrder ? ~value : value;
186 public override int CompareTo(object obj) {
187 XmlIntSortKey that = obj as XmlIntSortKey;
190 return CompareToEmpty(obj);
192 if (this.intVal == that.intVal)
193 return BreakSortingTie(that);
195 return (this.intVal < that.intVal) ? -1 : 1;
201 /// Sort key for xs:string values. Strings are sorted according to a byte-wise sort key calculated by caller.
203 internal class XmlStringSortKey : XmlSortKey {
204 private SortKey sortKey;
205 private byte[] sortKeyBytes;
206 private bool descendingOrder;
208 public XmlStringSortKey(SortKey sortKey, bool descendingOrder) {
209 this.sortKey = sortKey;
210 this.descendingOrder = descendingOrder;
213 public XmlStringSortKey(byte[] sortKey, bool descendingOrder) {
214 this.sortKeyBytes = sortKey;
215 this.descendingOrder = descendingOrder;
218 public override int CompareTo(object obj) {
219 XmlStringSortKey that = obj as XmlStringSortKey;
220 int idx, cntCmp, result;
223 return CompareToEmpty(obj);
225 // Compare either using SortKey.Compare or byte arrays
226 if (this.sortKey != null) {
227 Debug.Assert(that.sortKey != null, "Both keys must have non-null sortKey field");
228 result = SortKey.Compare(this.sortKey, that.sortKey);
231 Debug.Assert(this.sortKeyBytes != null && that.sortKeyBytes != null, "Both keys must have non-null sortKeyBytes field");
233 cntCmp = (this.sortKeyBytes.Length < that.sortKeyBytes.Length) ? this.sortKeyBytes.Length : that.sortKeyBytes.Length;
234 for (idx = 0; idx < cntCmp; idx++) {
235 if (this.sortKeyBytes[idx] < that.sortKeyBytes[idx]) {
240 if (this.sortKeyBytes[idx] > that.sortKeyBytes[idx]) {
246 // So far, keys are equal, so now test length of each key
247 if (this.sortKeyBytes.Length < that.sortKeyBytes.Length)
249 else if (this.sortKeyBytes.Length > that.sortKeyBytes.Length)
256 // Use document order to break sorting tie
258 return BreakSortingTie(that);
260 return this.descendingOrder ? -result : result;
266 /// Sort key for Double values.
268 internal class XmlDoubleSortKey : XmlSortKey {
269 private double dblVal;
272 public XmlDoubleSortKey(double value, XmlCollation collation) {
273 if (Double.IsNaN(value)) {
274 // Treat NaN as if it were the empty sequence
277 // Greatest, Ascending: isEmptyGreatest = true
278 // Greatest, Descending: isEmptyGreatest = false
279 // Least, Ascending: isEmptyGreatest = false
280 // Least, Descending: isEmptyGreatest = true
281 this.dblVal = (collation.EmptyGreatest != collation.DescendingOrder) ? Double.PositiveInfinity : Double.NegativeInfinity;
284 this.dblVal = collation.DescendingOrder ? -value : value;
288 public override int CompareTo(object obj) {
289 XmlDoubleSortKey that = obj as XmlDoubleSortKey;
292 // Compare to empty sequence
294 return BreakSortingTie(obj as XmlSortKey);
296 return CompareToEmpty(obj);
299 if (this.dblVal == that.dblVal) {
301 // NaN sorts equal to NaN
303 return BreakSortingTie(that);
305 // NaN sorts before or after all non-NaN values
306 Debug.Assert(this.dblVal == Double.NegativeInfinity || this.dblVal == Double.PositiveInfinity);
307 return (this.dblVal == Double.NegativeInfinity) ? -1 : 1;
309 else if (that.isNaN) {
310 // NaN sorts before or after all non-NaN values
311 Debug.Assert(that.dblVal == Double.NegativeInfinity || that.dblVal == Double.PositiveInfinity);
312 return (that.dblVal == Double.NegativeInfinity) ? 1 : -1;
315 return BreakSortingTie(that);
318 return (this.dblVal < that.dblVal) ? -1 : 1;
324 /// Sort key for DateTime values (just convert DateTime to ticks and use Long sort key).
326 internal class XmlDateTimeSortKey : XmlIntegerSortKey {
327 public XmlDateTimeSortKey(DateTime value, XmlCollation collation) : base(value.Ticks, collation) {