840709301611b9814066673bb45caa2224c17100
[mono.git] / mcs / class / referencesource / System.Web / UI / WebControls / basecomparevalidator.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="basecomparevalidator.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.UI.WebControls {
8
9     using System.ComponentModel;
10     using System.Drawing;
11     using System.Globalization;
12     using System.Web;
13     using System.Web.UI.HtmlControls;
14     using System.Text.RegularExpressions;
15     using System.Text;
16     using System.Web.Util;
17
18
19     /// <devdoc>
20     ///    <para> Serves as the abstract base
21     ///       class for validators that do typed comparisons.</para>
22     /// </devdoc>
23     public abstract class BaseCompareValidator : BaseValidator {
24
25
26         /// <devdoc>
27         ///    <para>Gets or sets the data type that specifies how the values
28         ///       being compared should be interpreted.</para>
29         /// </devdoc>
30         [
31         WebCategory("Behavior"),
32         Themeable(false),
33         DefaultValue(ValidationDataType.String),
34         WebSysDescription(SR.RangeValidator_Type)
35         ]
36         public ValidationDataType Type {
37             get {
38                 object o = ViewState["Type"];
39                 return((o == null) ? ValidationDataType.String : (ValidationDataType)o);
40             }
41             set {
42                 if (value < ValidationDataType.String || value > ValidationDataType.Currency) {
43                     throw new ArgumentOutOfRangeException("value");
44                 }
45                 ViewState["Type"] = value;
46             }
47         }
48
49
50         /// <devdoc>
51         ///     Whether we should do culture invariant conversion against the
52         ///     string value properties on the control
53         /// </devdoc>
54         [
55         WebCategory("Behavior"),
56         Themeable(false),
57         DefaultValue(false),
58         WebSysDescription(SR.BaseCompareValidator_CultureInvariantValues)
59         ]
60         public bool CultureInvariantValues {
61             get {
62                 object o = ViewState["CultureInvariantValues"];
63                 return((o == null) ? false : (bool)o);
64             }
65             set {
66                 ViewState["CultureInvariantValues"] = value;
67             }
68         }
69
70
71         /// <internalonly/>
72         /// <devdoc>
73         ///    AddAttributesToRender method
74         /// </devdoc>
75         protected override void AddAttributesToRender(HtmlTextWriter writer) {
76             base.AddAttributesToRender(writer);
77             if (RenderUplevel) {
78                 ValidationDataType type = Type;
79                 if (type != ValidationDataType.String) {
80                     string id = ClientID;
81                     HtmlTextWriter expandoAttributeWriter = (EnableLegacyRendering || IsUnobtrusive) ? writer : null;
82
83                     AddExpandoAttribute(expandoAttributeWriter, id, "type", PropertyConverter.EnumToString(typeof(ValidationDataType), type), false);
84
85                     NumberFormatInfo info = NumberFormatInfo.CurrentInfo;
86                     if (type == ValidationDataType.Double) {
87                         string decimalChar = info.NumberDecimalSeparator;
88                         AddExpandoAttribute(expandoAttributeWriter, id, "decimalchar", decimalChar);
89                     }
90                     else if (type == ValidationDataType.Currency) {
91                         string decimalChar = info.CurrencyDecimalSeparator;
92                         AddExpandoAttribute(expandoAttributeWriter, id, "decimalchar", decimalChar);
93
94                         string groupChar = info.CurrencyGroupSeparator;
95                         // Map non-break space onto regular space for parsing
96                         if (groupChar[0] == 160)
97                             groupChar = " ";
98                         AddExpandoAttribute(expandoAttributeWriter, id, "groupchar", groupChar);
99
100                         int digits = info.CurrencyDecimalDigits;
101                         AddExpandoAttribute(expandoAttributeWriter, id, "digits", digits.ToString(NumberFormatInfo.InvariantInfo), false);
102
103                         // VSWhidbey 83165
104                         int groupSize = GetCurrencyGroupSize(info);
105                         if (groupSize > 0) {
106                             AddExpandoAttribute(expandoAttributeWriter, id, "groupsize", groupSize.ToString(NumberFormatInfo.InvariantInfo), false);
107                         }
108                     }
109                     else if (type == ValidationDataType.Date) {
110                         AddExpandoAttribute(expandoAttributeWriter, id, "dateorder", GetDateElementOrder(), false);
111                         AddExpandoAttribute(expandoAttributeWriter, id, "cutoffyear", CutoffYear.ToString(NumberFormatInfo.InvariantInfo), false);
112
113                         // VSWhidbey 504553: The changes of this 
114
115
116                         int currentYear = DateTime.Today.Year;
117                         int century = currentYear - (currentYear % 100);
118                         AddExpandoAttribute(expandoAttributeWriter, id, "century", century.ToString(NumberFormatInfo.InvariantInfo), false);
119                     }
120                 }
121             }
122         }
123
124
125
126         /// <devdoc>
127         ///    Check if the text can be converted to the type
128         /// </devdoc>
129         public static bool CanConvert(string text, ValidationDataType type) {
130             return CanConvert(text, type, false);
131         }
132
133
134         public static bool CanConvert(string text, ValidationDataType type, bool cultureInvariant) {
135             object value = null;
136             return Convert(text, type, cultureInvariant, out value);
137         }
138
139
140         /// <internalonly/>
141         /// <devdoc>
142         ///    Return the order of date elements for the current culture
143         /// </devdoc>
144         protected static string GetDateElementOrder() {
145             DateTimeFormatInfo info = DateTimeFormatInfo.CurrentInfo;
146             string shortPattern = info.ShortDatePattern;
147             if (shortPattern.IndexOf('y') < shortPattern.IndexOf('M')) {
148                 return "ymd";
149             }
150             else if (shortPattern.IndexOf('M') < shortPattern.IndexOf('d')) {
151                 return "mdy";
152             }
153             else {
154                 return "dmy";
155             }
156         }
157
158         // VSWhidbey 83165
159         private static int GetCurrencyGroupSize(NumberFormatInfo info) {
160             int [] groupSizes = info.CurrencyGroupSizes;
161             if (groupSizes != null && groupSizes.Length == 1) {
162                 return groupSizes[0];
163             }
164             else {
165                 return -1;
166             }
167         }
168
169
170         /// <devdoc>
171         ///    <para>[To be supplied.]</para>
172         /// </devdoc>
173         protected static int CutoffYear {
174             get {
175                 return DateTimeFormatInfo.CurrentInfo.Calendar.TwoDigitYearMax;
176             }
177         }
178
179
180         /// <devdoc>
181         ///    <para>[To be supplied.]</para>
182         /// </devdoc>
183         protected static int GetFullYear(int shortYear) {
184             Debug.Assert(shortYear >= 0 && shortYear < 100);
185             return DateTimeFormatInfo.CurrentInfo.Calendar.ToFourDigitYear(shortYear);
186         }
187
188
189         /// <devdoc>
190         ///    Try to convert the test into the validation data type
191         /// </devdoc>
192         protected static bool Convert(string text, ValidationDataType type, out object value) {
193             return Convert(text, type, false, out value);
194         }
195
196
197         protected static bool Convert(string text, ValidationDataType type, bool cultureInvariant, out object value) {
198
199             value = null;
200             try {
201                 switch (type) {
202                     case ValidationDataType.String:
203                         value = text;
204                         break;
205
206                     case ValidationDataType.Integer:
207                         value = Int32.Parse(text, CultureInfo.InvariantCulture);
208                         break;
209
210                     case ValidationDataType.Double: {
211                         string cleanInput;
212                         if (cultureInvariant) {
213                             cleanInput = ConvertDouble(text, CultureInfo.InvariantCulture.NumberFormat);
214                         }
215                         else {
216                             cleanInput = ConvertDouble(text, NumberFormatInfo.CurrentInfo);
217                         }
218
219                         if (cleanInput != null) {
220                             value = Double.Parse(cleanInput, CultureInfo.InvariantCulture);
221                         }
222                         break;
223                     }
224
225                     case ValidationDataType.Date: {
226                         if (cultureInvariant) {
227                             value = ConvertDate(text, "ymd");
228                         }
229                         else {
230                             // if the calendar is not gregorian, we should not enable client-side, so just parse it directly:
231                             if (!(DateTimeFormatInfo.CurrentInfo.Calendar.GetType() == typeof(GregorianCalendar))) {
232                                 value = DateTime.Parse(text, CultureInfo.CurrentCulture);
233                                 break;
234                             }
235
236                             string dateElementOrder = GetDateElementOrder();
237                             value = ConvertDate(text, dateElementOrder);
238                         }
239                         break;
240                     }
241
242                     case ValidationDataType.Currency: {
243                         string cleanInput;
244                         if (cultureInvariant) {
245                             cleanInput = ConvertCurrency(text, CultureInfo.InvariantCulture.NumberFormat);
246                         }
247                         else {
248                             cleanInput = ConvertCurrency(text, NumberFormatInfo.CurrentInfo);
249                         }
250
251                         if (cleanInput != null) {
252                             value = Decimal.Parse(cleanInput, CultureInfo.InvariantCulture);
253                         }
254                         break;
255                     }
256                 }
257             }
258             catch {
259                 value = null;
260             }
261             return (value != null);
262         }
263
264         private static string ConvertCurrency(string text, NumberFormatInfo info) {
265             string decimalChar = info.CurrencyDecimalSeparator;
266             string groupChar = info.CurrencyGroupSeparator;
267
268             // VSWhidbey 83165
269             string beginGroupSize, subsequentGroupSize;
270             int groupSize = GetCurrencyGroupSize(info);
271             if (groupSize > 0) {
272                 string groupSizeText = groupSize.ToString(NumberFormatInfo.InvariantInfo);
273                 beginGroupSize = "{1," + groupSizeText + "}";
274                 subsequentGroupSize = "{" + groupSizeText + "}";
275             }
276             else {
277                 beginGroupSize = subsequentGroupSize = "+";
278             }
279
280             // Map non-break space onto regular space for parsing
281             if (groupChar[0] == 160)
282                 groupChar = " ";
283             int digits = info.CurrencyDecimalDigits;
284             bool hasDigits = (digits > 0);
285             string currencyExpression =
286                 "^\\s*([-\\+])?((\\d" + beginGroupSize + "(\\" + groupChar + "\\d" + subsequentGroupSize + ")+)|\\d*)"
287                 + (hasDigits ? "\\" + decimalChar + "?(\\d{0," + digits.ToString(NumberFormatInfo.InvariantInfo) + "})" : string.Empty)
288                 + "\\s*$";
289
290             Match m = Regex.Match(text, currencyExpression);
291             if (!m.Success) {
292                 return null;
293             }
294
295             // Make sure there are some valid digits
296             if (m.Groups[2].Length == 0 && hasDigits && m.Groups[5].Length == 0) {
297                 return null;
298             }
299
300             return m.Groups[1].Value
301                    + m.Groups[2].Value.Replace(groupChar, string.Empty)
302                    + ((hasDigits && m.Groups[5].Length > 0) ? "." + m.Groups[5].Value : string.Empty);
303         }
304
305         private static string ConvertDouble(string text, NumberFormatInfo info) {
306             // VSWhidbey 83156: If text is empty, it would be default to 0 for
307             // backward compatibility reason.
308             if (text.Length == 0) {
309                 return "0";
310             }
311
312             string decimalChar = info.NumberDecimalSeparator;
313             string doubleExpression = "^\\s*([-\\+])?(\\d*)\\" + decimalChar + "?(\\d*)\\s*$";
314
315             Match m = Regex.Match(text, doubleExpression);
316             if (!m.Success) {
317                 return null;
318             }
319
320             // Make sure there are some valid digits
321             if (m.Groups[2].Length == 0 && m.Groups[3].Length == 0) {
322                 return null;
323             }
324
325             return m.Groups[1].Value
326                    + (m.Groups[2].Length > 0 ? m.Groups[2].Value : "0")
327                    + ((m.Groups[3].Length > 0) ? "." + m.Groups[3].Value: string.Empty);
328         }
329
330         // ****************************************************************************************************************
331         // **                                                                                                            **
332         // ** NOTE: When updating the regular expressions in this method, you must also update the regular expressions   **
333         // **       in WebUIValidation.js::ValidatorConvert().  The server and client regular expressions must match.    **
334         // **                                                                                                            **
335         // ****************************************************************************************************************
336         private static object ConvertDate(string text, string dateElementOrder) {
337             // always allow the YMD format, if they specify 4 digits
338             string dateYearFirstExpression = "^\\s*((\\d{4})|(\\d{2}))([-/]|\\. ?)(\\d{1,2})\\4(\\d{1,2})\\.?\\s*$";
339             Match m = Regex.Match(text, dateYearFirstExpression);
340             int day, month, year;
341             if (m.Success && (m.Groups[2].Success || dateElementOrder == "ymd")) {
342                 day = Int32.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture);
343                 month = Int32.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture);
344                 if (m.Groups[2].Success) {
345                     year = Int32.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture);
346                 }
347                 else {
348                     year = GetFullYear(Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture));
349                 }
350             }
351             else {
352                 if (dateElementOrder == "ymd") {
353                     return null;
354                 }
355
356                 // also check for the year last format
357                 string dateYearLastExpression = "^\\s*(\\d{1,2})([-/]|\\. ?)(\\d{1,2})(?:\\s|\\2)((\\d{4})|(\\d{2}))(?:\\s\u0433\\.|\\.)?\\s*$";
358                 m = Regex.Match(text, dateYearLastExpression);
359                 if (!m.Success) {
360                     return null;
361                 }
362                 if (dateElementOrder == "mdy") {
363                     day = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
364                     month = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
365                 }
366                 else {
367                     day = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
368                     month = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
369                 }
370                 if (m.Groups[5].Success) {
371                     year = Int32.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture);
372                 } else {
373                     year = GetFullYear(Int32.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture));
374                 }
375             }
376             return new DateTime(year, month, day);
377         }
378
379
380         /// <devdoc>
381         ///    Compare two strings using the type and operator
382         /// </devdoc>
383         protected static bool Compare(string leftText, string rightText, ValidationCompareOperator op, ValidationDataType type) {
384             return Compare(leftText, false, rightText, false, op, type);
385         }
386
387
388         protected static bool Compare(string leftText, bool cultureInvariantLeftText,
389                                       string rightText, bool cultureInvariantRightText,
390                                       ValidationCompareOperator op, ValidationDataType type) {
391             object leftObject;
392             if (!Convert(leftText, type, cultureInvariantLeftText, out leftObject))
393                 return false;
394
395             if (op == ValidationCompareOperator.DataTypeCheck)
396                 return true;
397
398             object rightObject;
399             if (!Convert(rightText, type, cultureInvariantRightText, out rightObject))
400                 return true;
401
402             int compareResult;
403             switch (type) {
404                 case ValidationDataType.String:
405                     compareResult = String.Compare((string)leftObject, (string) rightObject, false, CultureInfo.CurrentCulture);
406                     break;
407
408                 case ValidationDataType.Integer:
409                     compareResult = ((int)leftObject).CompareTo(rightObject);
410                     break;
411
412                 case ValidationDataType.Double:
413                     compareResult = ((double)leftObject).CompareTo(rightObject);
414                     break;
415
416                 case ValidationDataType.Date:
417                     compareResult = ((DateTime)leftObject).CompareTo(rightObject);
418                     break;
419
420                 case ValidationDataType.Currency:
421                     compareResult = ((Decimal)leftObject).CompareTo(rightObject);
422                     break;
423
424                 default:
425                     Debug.Fail("Unknown Type");
426                     return true;
427             }
428
429             switch (op) {
430                 case ValidationCompareOperator.Equal:
431                     return compareResult == 0;
432                 case ValidationCompareOperator.NotEqual:
433                     return compareResult != 0;
434                 case ValidationCompareOperator.GreaterThan:
435                     return compareResult > 0 ;
436                 case ValidationCompareOperator.GreaterThanEqual:
437                     return compareResult >= 0 ;
438                 case ValidationCompareOperator.LessThan:
439                     return compareResult < 0 ;
440                 case ValidationCompareOperator.LessThanEqual:
441                     return compareResult <= 0 ;
442                 default:
443                     Debug.Fail("Unknown Operator");
444                     return true;
445             }
446         }
447
448
449         /// <devdoc>
450         ///    <para>[To be supplied.]</para>
451         /// </devdoc>
452         protected override bool DetermineRenderUplevel() {
453             // We don't do client-side validation for dates with non gregorian calendars
454             if (Type == ValidationDataType.Date && DateTimeFormatInfo.CurrentInfo.Calendar.GetType() != typeof(GregorianCalendar)) {
455                 return false;
456             }
457             return base.DetermineRenderUplevel();
458         }
459
460         internal string ConvertToShortDateString(string text) {
461             // VSWhidbey 83099, 85305, we should ignore error if it happens and
462             // leave text as intact when parsing the date.  We assume the caller
463             // (validator) is able to handle invalid text itself.
464             DateTime date;
465             if (DateTime.TryParse(text, CultureInfo.CurrentCulture, DateTimeStyles.None, out date)) {
466                 text = date.ToShortDateString();
467             }
468             return text;
469         }
470
471         internal bool IsInStandardDateFormat(string date) {
472             // VSWhidbey 115454: We identify that date string with only numbers
473             // and specific punctuation separators is in standard date format.
474             const string standardDateExpression = "^\\s*(\\d+)([-/]|\\. ?)(\\d+)\\2(\\d+)\\s*$";
475             return Regex.Match(date, standardDateExpression).Success;
476         }
477
478         internal string ConvertCultureInvariantToCurrentCultureFormat(string valueInString,
479                                                                       ValidationDataType type) {
480             object value;
481             Convert(valueInString, type, true, out value);
482             if (value is DateTime) {
483                 // For Date type we explicitly want the date portion only
484                 return ((DateTime) value).ToShortDateString();
485             }
486             else {
487                 return System.Convert.ToString(value, CultureInfo.CurrentCulture);
488             }
489         }
490     }
491 }