1 //------------------------------------------------------------------------------
2 // <copyright file="basecomparevalidator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System.Web.UI.WebControls {
9 using System.ComponentModel;
11 using System.Globalization;
13 using System.Web.UI.HtmlControls;
14 using System.Text.RegularExpressions;
16 using System.Web.Util;
20 /// <para> Serves as the abstract base
21 /// class for validators that do typed comparisons.</para>
23 public abstract class BaseCompareValidator : BaseValidator {
27 /// <para>Gets or sets the data type that specifies how the values
28 /// being compared should be interpreted.</para>
31 WebCategory("Behavior"),
33 DefaultValue(ValidationDataType.String),
34 WebSysDescription(SR.RangeValidator_Type)
36 public ValidationDataType Type {
38 object o = ViewState["Type"];
39 return((o == null) ? ValidationDataType.String : (ValidationDataType)o);
42 if (value < ValidationDataType.String || value > ValidationDataType.Currency) {
43 throw new ArgumentOutOfRangeException("value");
45 ViewState["Type"] = value;
51 /// Whether we should do culture invariant conversion against the
52 /// string value properties on the control
55 WebCategory("Behavior"),
58 WebSysDescription(SR.BaseCompareValidator_CultureInvariantValues)
60 public bool CultureInvariantValues {
62 object o = ViewState["CultureInvariantValues"];
63 return((o == null) ? false : (bool)o);
66 ViewState["CultureInvariantValues"] = value;
73 /// AddAttributesToRender method
75 protected override void AddAttributesToRender(HtmlTextWriter writer) {
76 base.AddAttributesToRender(writer);
78 ValidationDataType type = Type;
79 if (type != ValidationDataType.String) {
81 HtmlTextWriter expandoAttributeWriter = (EnableLegacyRendering || IsUnobtrusive) ? writer : null;
83 AddExpandoAttribute(expandoAttributeWriter, id, "type", PropertyConverter.EnumToString(typeof(ValidationDataType), type), false);
85 NumberFormatInfo info = NumberFormatInfo.CurrentInfo;
86 if (type == ValidationDataType.Double) {
87 string decimalChar = info.NumberDecimalSeparator;
88 AddExpandoAttribute(expandoAttributeWriter, id, "decimalchar", decimalChar);
90 else if (type == ValidationDataType.Currency) {
91 string decimalChar = info.CurrencyDecimalSeparator;
92 AddExpandoAttribute(expandoAttributeWriter, id, "decimalchar", decimalChar);
94 string groupChar = info.CurrencyGroupSeparator;
95 // Map non-break space onto regular space for parsing
96 if (groupChar[0] == 160)
98 AddExpandoAttribute(expandoAttributeWriter, id, "groupchar", groupChar);
100 int digits = info.CurrencyDecimalDigits;
101 AddExpandoAttribute(expandoAttributeWriter, id, "digits", digits.ToString(NumberFormatInfo.InvariantInfo), false);
104 int groupSize = GetCurrencyGroupSize(info);
106 AddExpandoAttribute(expandoAttributeWriter, id, "groupsize", groupSize.ToString(NumberFormatInfo.InvariantInfo), false);
109 else if (type == ValidationDataType.Date) {
110 AddExpandoAttribute(expandoAttributeWriter, id, "dateorder", GetDateElementOrder(), false);
111 AddExpandoAttribute(expandoAttributeWriter, id, "cutoffyear", CutoffYear.ToString(NumberFormatInfo.InvariantInfo), false);
113 // VSWhidbey 504553: The changes of this
116 int currentYear = DateTime.Today.Year;
117 int century = currentYear - (currentYear % 100);
118 AddExpandoAttribute(expandoAttributeWriter, id, "century", century.ToString(NumberFormatInfo.InvariantInfo), false);
127 /// Check if the text can be converted to the type
129 public static bool CanConvert(string text, ValidationDataType type) {
130 return CanConvert(text, type, false);
134 public static bool CanConvert(string text, ValidationDataType type, bool cultureInvariant) {
136 return Convert(text, type, cultureInvariant, out value);
142 /// Return the order of date elements for the current culture
144 protected static string GetDateElementOrder() {
145 DateTimeFormatInfo info = DateTimeFormatInfo.CurrentInfo;
146 string shortPattern = info.ShortDatePattern;
147 if (shortPattern.IndexOf('y') < shortPattern.IndexOf('M')) {
150 else if (shortPattern.IndexOf('M') < shortPattern.IndexOf('d')) {
159 private static int GetCurrencyGroupSize(NumberFormatInfo info) {
160 int [] groupSizes = info.CurrencyGroupSizes;
161 if (groupSizes != null && groupSizes.Length == 1) {
162 return groupSizes[0];
171 /// <para>[To be supplied.]</para>
173 protected static int CutoffYear {
175 return DateTimeFormatInfo.CurrentInfo.Calendar.TwoDigitYearMax;
181 /// <para>[To be supplied.]</para>
183 protected static int GetFullYear(int shortYear) {
184 Debug.Assert(shortYear >= 0 && shortYear < 100);
185 return DateTimeFormatInfo.CurrentInfo.Calendar.ToFourDigitYear(shortYear);
190 /// Try to convert the test into the validation data type
192 protected static bool Convert(string text, ValidationDataType type, out object value) {
193 return Convert(text, type, false, out value);
197 protected static bool Convert(string text, ValidationDataType type, bool cultureInvariant, out object value) {
202 case ValidationDataType.String:
206 case ValidationDataType.Integer:
207 value = Int32.Parse(text, CultureInfo.InvariantCulture);
210 case ValidationDataType.Double: {
212 if (cultureInvariant) {
213 cleanInput = ConvertDouble(text, CultureInfo.InvariantCulture.NumberFormat);
216 cleanInput = ConvertDouble(text, NumberFormatInfo.CurrentInfo);
219 if (cleanInput != null) {
220 value = Double.Parse(cleanInput, CultureInfo.InvariantCulture);
225 case ValidationDataType.Date: {
226 if (cultureInvariant) {
227 value = ConvertDate(text, "ymd");
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);
236 string dateElementOrder = GetDateElementOrder();
237 value = ConvertDate(text, dateElementOrder);
242 case ValidationDataType.Currency: {
244 if (cultureInvariant) {
245 cleanInput = ConvertCurrency(text, CultureInfo.InvariantCulture.NumberFormat);
248 cleanInput = ConvertCurrency(text, NumberFormatInfo.CurrentInfo);
251 if (cleanInput != null) {
252 value = Decimal.Parse(cleanInput, CultureInfo.InvariantCulture);
261 return (value != null);
264 private static string ConvertCurrency(string text, NumberFormatInfo info) {
265 string decimalChar = info.CurrencyDecimalSeparator;
266 string groupChar = info.CurrencyGroupSeparator;
269 string beginGroupSize, subsequentGroupSize;
270 int groupSize = GetCurrencyGroupSize(info);
272 string groupSizeText = groupSize.ToString(NumberFormatInfo.InvariantInfo);
273 beginGroupSize = "{1," + groupSizeText + "}";
274 subsequentGroupSize = "{" + groupSizeText + "}";
277 beginGroupSize = subsequentGroupSize = "+";
280 // Map non-break space onto regular space for parsing
281 if (groupChar[0] == 160)
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)
290 Match m = Regex.Match(text, currencyExpression);
295 // Make sure there are some valid digits
296 if (m.Groups[2].Length == 0 && hasDigits && m.Groups[5].Length == 0) {
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);
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) {
312 string decimalChar = info.NumberDecimalSeparator;
313 string doubleExpression = "^\\s*([-\\+])?(\\d*)\\" + decimalChar + "?(\\d*)\\s*$";
315 Match m = Regex.Match(text, doubleExpression);
320 // Make sure there are some valid digits
321 if (m.Groups[2].Length == 0 && m.Groups[3].Length == 0) {
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);
330 // ****************************************************************************************************************
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. **
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);
348 year = GetFullYear(Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture));
352 if (dateElementOrder == "ymd") {
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);
362 if (dateElementOrder == "mdy") {
363 day = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
364 month = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
367 day = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
368 month = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
370 if (m.Groups[5].Success) {
371 year = Int32.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture);
373 year = GetFullYear(Int32.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture));
376 return new DateTime(year, month, day);
381 /// Compare two strings using the type and operator
383 protected static bool Compare(string leftText, string rightText, ValidationCompareOperator op, ValidationDataType type) {
384 return Compare(leftText, false, rightText, false, op, type);
388 protected static bool Compare(string leftText, bool cultureInvariantLeftText,
389 string rightText, bool cultureInvariantRightText,
390 ValidationCompareOperator op, ValidationDataType type) {
392 if (!Convert(leftText, type, cultureInvariantLeftText, out leftObject))
395 if (op == ValidationCompareOperator.DataTypeCheck)
399 if (!Convert(rightText, type, cultureInvariantRightText, out rightObject))
404 case ValidationDataType.String:
405 compareResult = String.Compare((string)leftObject, (string) rightObject, false, CultureInfo.CurrentCulture);
408 case ValidationDataType.Integer:
409 compareResult = ((int)leftObject).CompareTo(rightObject);
412 case ValidationDataType.Double:
413 compareResult = ((double)leftObject).CompareTo(rightObject);
416 case ValidationDataType.Date:
417 compareResult = ((DateTime)leftObject).CompareTo(rightObject);
420 case ValidationDataType.Currency:
421 compareResult = ((Decimal)leftObject).CompareTo(rightObject);
425 Debug.Fail("Unknown Type");
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 ;
443 Debug.Fail("Unknown Operator");
450 /// <para>[To be supplied.]</para>
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)) {
457 return base.DetermineRenderUplevel();
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.
465 if (DateTime.TryParse(text, CultureInfo.CurrentCulture, DateTimeStyles.None, out date)) {
466 text = date.ToShortDateString();
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;
478 internal string ConvertCultureInvariantToCurrentCultureFormat(string valueInString,
479 ValidationDataType type) {
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();
487 return System.Convert.ToString(value, CultureInfo.CurrentCulture);