492 lines
20 KiB
C#
492 lines
20 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="basecomparevalidator.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
namespace System.Web.UI.WebControls {
|
||
|
|
||
|
using System.ComponentModel;
|
||
|
using System.Drawing;
|
||
|
using System.Globalization;
|
||
|
using System.Web;
|
||
|
using System.Web.UI.HtmlControls;
|
||
|
using System.Text.RegularExpressions;
|
||
|
using System.Text;
|
||
|
using System.Web.Util;
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para> Serves as the abstract base
|
||
|
/// class for validators that do typed comparisons.</para>
|
||
|
/// </devdoc>
|
||
|
public abstract class BaseCompareValidator : BaseValidator {
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>Gets or sets the data type that specifies how the values
|
||
|
/// being compared should be interpreted.</para>
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Behavior"),
|
||
|
Themeable(false),
|
||
|
DefaultValue(ValidationDataType.String),
|
||
|
WebSysDescription(SR.RangeValidator_Type)
|
||
|
]
|
||
|
public ValidationDataType Type {
|
||
|
get {
|
||
|
object o = ViewState["Type"];
|
||
|
return((o == null) ? ValidationDataType.String : (ValidationDataType)o);
|
||
|
}
|
||
|
set {
|
||
|
if (value < ValidationDataType.String || value > ValidationDataType.Currency) {
|
||
|
throw new ArgumentOutOfRangeException("value");
|
||
|
}
|
||
|
ViewState["Type"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// Whether we should do culture invariant conversion against the
|
||
|
/// string value properties on the control
|
||
|
/// </devdoc>
|
||
|
[
|
||
|
WebCategory("Behavior"),
|
||
|
Themeable(false),
|
||
|
DefaultValue(false),
|
||
|
WebSysDescription(SR.BaseCompareValidator_CultureInvariantValues)
|
||
|
]
|
||
|
public bool CultureInvariantValues {
|
||
|
get {
|
||
|
object o = ViewState["CultureInvariantValues"];
|
||
|
return((o == null) ? false : (bool)o);
|
||
|
}
|
||
|
set {
|
||
|
ViewState["CultureInvariantValues"] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// AddAttributesToRender method
|
||
|
/// </devdoc>
|
||
|
protected override void AddAttributesToRender(HtmlTextWriter writer) {
|
||
|
base.AddAttributesToRender(writer);
|
||
|
if (RenderUplevel) {
|
||
|
ValidationDataType type = Type;
|
||
|
if (type != ValidationDataType.String) {
|
||
|
string id = ClientID;
|
||
|
HtmlTextWriter expandoAttributeWriter = (EnableLegacyRendering || IsUnobtrusive) ? writer : null;
|
||
|
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "type", PropertyConverter.EnumToString(typeof(ValidationDataType), type), false);
|
||
|
|
||
|
NumberFormatInfo info = NumberFormatInfo.CurrentInfo;
|
||
|
if (type == ValidationDataType.Double) {
|
||
|
string decimalChar = info.NumberDecimalSeparator;
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "decimalchar", decimalChar);
|
||
|
}
|
||
|
else if (type == ValidationDataType.Currency) {
|
||
|
string decimalChar = info.CurrencyDecimalSeparator;
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "decimalchar", decimalChar);
|
||
|
|
||
|
string groupChar = info.CurrencyGroupSeparator;
|
||
|
// Map non-break space onto regular space for parsing
|
||
|
if (groupChar[0] == 160)
|
||
|
groupChar = " ";
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "groupchar", groupChar);
|
||
|
|
||
|
int digits = info.CurrencyDecimalDigits;
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "digits", digits.ToString(NumberFormatInfo.InvariantInfo), false);
|
||
|
|
||
|
// VSWhidbey 83165
|
||
|
int groupSize = GetCurrencyGroupSize(info);
|
||
|
if (groupSize > 0) {
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "groupsize", groupSize.ToString(NumberFormatInfo.InvariantInfo), false);
|
||
|
}
|
||
|
}
|
||
|
else if (type == ValidationDataType.Date) {
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "dateorder", GetDateElementOrder(), false);
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "cutoffyear", CutoffYear.ToString(NumberFormatInfo.InvariantInfo), false);
|
||
|
|
||
|
// VSWhidbey 504553: The changes of this bug make client-side script not
|
||
|
// using the century attribute anymore, but still generating it for
|
||
|
// backward compatibility with Everett pages.
|
||
|
int currentYear = DateTime.Today.Year;
|
||
|
int century = currentYear - (currentYear % 100);
|
||
|
AddExpandoAttribute(expandoAttributeWriter, id, "century", century.ToString(NumberFormatInfo.InvariantInfo), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// Check if the text can be converted to the type
|
||
|
/// </devdoc>
|
||
|
public static bool CanConvert(string text, ValidationDataType type) {
|
||
|
return CanConvert(text, type, false);
|
||
|
}
|
||
|
|
||
|
|
||
|
public static bool CanConvert(string text, ValidationDataType type, bool cultureInvariant) {
|
||
|
object value = null;
|
||
|
return Convert(text, type, cultureInvariant, out value);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <internalonly/>
|
||
|
/// <devdoc>
|
||
|
/// Return the order of date elements for the current culture
|
||
|
/// </devdoc>
|
||
|
protected static string GetDateElementOrder() {
|
||
|
DateTimeFormatInfo info = DateTimeFormatInfo.CurrentInfo;
|
||
|
string shortPattern = info.ShortDatePattern;
|
||
|
if (shortPattern.IndexOf('y') < shortPattern.IndexOf('M')) {
|
||
|
return "ymd";
|
||
|
}
|
||
|
else if (shortPattern.IndexOf('M') < shortPattern.IndexOf('d')) {
|
||
|
return "mdy";
|
||
|
}
|
||
|
else {
|
||
|
return "dmy";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// VSWhidbey 83165
|
||
|
private static int GetCurrencyGroupSize(NumberFormatInfo info) {
|
||
|
int [] groupSizes = info.CurrencyGroupSizes;
|
||
|
if (groupSizes != null && groupSizes.Length == 1) {
|
||
|
return groupSizes[0];
|
||
|
}
|
||
|
else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>[To be supplied.]</para>
|
||
|
/// </devdoc>
|
||
|
protected static int CutoffYear {
|
||
|
get {
|
||
|
return DateTimeFormatInfo.CurrentInfo.Calendar.TwoDigitYearMax;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>[To be supplied.]</para>
|
||
|
/// </devdoc>
|
||
|
protected static int GetFullYear(int shortYear) {
|
||
|
Debug.Assert(shortYear >= 0 && shortYear < 100);
|
||
|
return DateTimeFormatInfo.CurrentInfo.Calendar.ToFourDigitYear(shortYear);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// Try to convert the test into the validation data type
|
||
|
/// </devdoc>
|
||
|
protected static bool Convert(string text, ValidationDataType type, out object value) {
|
||
|
return Convert(text, type, false, out value);
|
||
|
}
|
||
|
|
||
|
|
||
|
protected static bool Convert(string text, ValidationDataType type, bool cultureInvariant, out object value) {
|
||
|
|
||
|
value = null;
|
||
|
try {
|
||
|
switch (type) {
|
||
|
case ValidationDataType.String:
|
||
|
value = text;
|
||
|
break;
|
||
|
|
||
|
case ValidationDataType.Integer:
|
||
|
value = Int32.Parse(text, CultureInfo.InvariantCulture);
|
||
|
break;
|
||
|
|
||
|
case ValidationDataType.Double: {
|
||
|
string cleanInput;
|
||
|
if (cultureInvariant) {
|
||
|
cleanInput = ConvertDouble(text, CultureInfo.InvariantCulture.NumberFormat);
|
||
|
}
|
||
|
else {
|
||
|
cleanInput = ConvertDouble(text, NumberFormatInfo.CurrentInfo);
|
||
|
}
|
||
|
|
||
|
if (cleanInput != null) {
|
||
|
value = Double.Parse(cleanInput, CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case ValidationDataType.Date: {
|
||
|
if (cultureInvariant) {
|
||
|
value = ConvertDate(text, "ymd");
|
||
|
}
|
||
|
else {
|
||
|
// if the calendar is not gregorian, we should not enable client-side, so just parse it directly:
|
||
|
if (!(DateTimeFormatInfo.CurrentInfo.Calendar.GetType() == typeof(GregorianCalendar))) {
|
||
|
value = DateTime.Parse(text, CultureInfo.CurrentCulture);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
string dateElementOrder = GetDateElementOrder();
|
||
|
value = ConvertDate(text, dateElementOrder);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case ValidationDataType.Currency: {
|
||
|
string cleanInput;
|
||
|
if (cultureInvariant) {
|
||
|
cleanInput = ConvertCurrency(text, CultureInfo.InvariantCulture.NumberFormat);
|
||
|
}
|
||
|
else {
|
||
|
cleanInput = ConvertCurrency(text, NumberFormatInfo.CurrentInfo);
|
||
|
}
|
||
|
|
||
|
if (cleanInput != null) {
|
||
|
value = Decimal.Parse(cleanInput, CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch {
|
||
|
value = null;
|
||
|
}
|
||
|
return (value != null);
|
||
|
}
|
||
|
|
||
|
private static string ConvertCurrency(string text, NumberFormatInfo info) {
|
||
|
string decimalChar = info.CurrencyDecimalSeparator;
|
||
|
string groupChar = info.CurrencyGroupSeparator;
|
||
|
|
||
|
// VSWhidbey 83165
|
||
|
string beginGroupSize, subsequentGroupSize;
|
||
|
int groupSize = GetCurrencyGroupSize(info);
|
||
|
if (groupSize > 0) {
|
||
|
string groupSizeText = groupSize.ToString(NumberFormatInfo.InvariantInfo);
|
||
|
beginGroupSize = "{1," + groupSizeText + "}";
|
||
|
subsequentGroupSize = "{" + groupSizeText + "}";
|
||
|
}
|
||
|
else {
|
||
|
beginGroupSize = subsequentGroupSize = "+";
|
||
|
}
|
||
|
|
||
|
// Map non-break space onto regular space for parsing
|
||
|
if (groupChar[0] == 160)
|
||
|
groupChar = " ";
|
||
|
int digits = info.CurrencyDecimalDigits;
|
||
|
bool hasDigits = (digits > 0);
|
||
|
string currencyExpression =
|
||
|
"^\\s*([-\\+])?((\\d" + beginGroupSize + "(\\" + groupChar + "\\d" + subsequentGroupSize + ")+)|\\d*)"
|
||
|
+ (hasDigits ? "\\" + decimalChar + "?(\\d{0," + digits.ToString(NumberFormatInfo.InvariantInfo) + "})" : string.Empty)
|
||
|
+ "\\s*$";
|
||
|
|
||
|
Match m = Regex.Match(text, currencyExpression);
|
||
|
if (!m.Success) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Make sure there are some valid digits
|
||
|
if (m.Groups[2].Length == 0 && hasDigits && m.Groups[5].Length == 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return m.Groups[1].Value
|
||
|
+ m.Groups[2].Value.Replace(groupChar, string.Empty)
|
||
|
+ ((hasDigits && m.Groups[5].Length > 0) ? "." + m.Groups[5].Value : string.Empty);
|
||
|
}
|
||
|
|
||
|
private static string ConvertDouble(string text, NumberFormatInfo info) {
|
||
|
// VSWhidbey 83156: If text is empty, it would be default to 0 for
|
||
|
// backward compatibility reason.
|
||
|
if (text.Length == 0) {
|
||
|
return "0";
|
||
|
}
|
||
|
|
||
|
string decimalChar = info.NumberDecimalSeparator;
|
||
|
string doubleExpression = "^\\s*([-\\+])?(\\d*)\\" + decimalChar + "?(\\d*)\\s*$";
|
||
|
|
||
|
Match m = Regex.Match(text, doubleExpression);
|
||
|
if (!m.Success) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Make sure there are some valid digits
|
||
|
if (m.Groups[2].Length == 0 && m.Groups[3].Length == 0) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return m.Groups[1].Value
|
||
|
+ (m.Groups[2].Length > 0 ? m.Groups[2].Value : "0")
|
||
|
+ ((m.Groups[3].Length > 0) ? "." + m.Groups[3].Value: string.Empty);
|
||
|
}
|
||
|
|
||
|
// ****************************************************************************************************************
|
||
|
// ** **
|
||
|
// ** NOTE: When updating the regular expressions in this method, you must also update the regular expressions **
|
||
|
// ** in WebUIValidation.js::ValidatorConvert(). The server and client regular expressions must match. **
|
||
|
// ** **
|
||
|
// ****************************************************************************************************************
|
||
|
private static object ConvertDate(string text, string dateElementOrder) {
|
||
|
// always allow the YMD format, if they specify 4 digits
|
||
|
string dateYearFirstExpression = "^\\s*((\\d{4})|(\\d{2}))([-/]|\\. ?)(\\d{1,2})\\4(\\d{1,2})\\.?\\s*$";
|
||
|
Match m = Regex.Match(text, dateYearFirstExpression);
|
||
|
int day, month, year;
|
||
|
if (m.Success && (m.Groups[2].Success || dateElementOrder == "ymd")) {
|
||
|
day = Int32.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture);
|
||
|
month = Int32.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture);
|
||
|
if (m.Groups[2].Success) {
|
||
|
year = Int32.Parse(m.Groups[2].Value, CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
else {
|
||
|
year = GetFullYear(Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture));
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (dateElementOrder == "ymd") {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// also check for the year last format
|
||
|
string dateYearLastExpression = "^\\s*(\\d{1,2})([-/]|\\. ?)(\\d{1,2})(?:\\s|\\2)((\\d{4})|(\\d{2}))(?:\\s\u0433\\.|\\.)?\\s*$";
|
||
|
m = Regex.Match(text, dateYearLastExpression);
|
||
|
if (!m.Success) {
|
||
|
return null;
|
||
|
}
|
||
|
if (dateElementOrder == "mdy") {
|
||
|
day = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
|
||
|
month = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
else {
|
||
|
day = Int32.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
|
||
|
month = Int32.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
if (m.Groups[5].Success) {
|
||
|
year = Int32.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture);
|
||
|
} else {
|
||
|
year = GetFullYear(Int32.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture));
|
||
|
}
|
||
|
}
|
||
|
return new DateTime(year, month, day);
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// Compare two strings using the type and operator
|
||
|
/// </devdoc>
|
||
|
protected static bool Compare(string leftText, string rightText, ValidationCompareOperator op, ValidationDataType type) {
|
||
|
return Compare(leftText, false, rightText, false, op, type);
|
||
|
}
|
||
|
|
||
|
|
||
|
protected static bool Compare(string leftText, bool cultureInvariantLeftText,
|
||
|
string rightText, bool cultureInvariantRightText,
|
||
|
ValidationCompareOperator op, ValidationDataType type) {
|
||
|
object leftObject;
|
||
|
if (!Convert(leftText, type, cultureInvariantLeftText, out leftObject))
|
||
|
return false;
|
||
|
|
||
|
if (op == ValidationCompareOperator.DataTypeCheck)
|
||
|
return true;
|
||
|
|
||
|
object rightObject;
|
||
|
if (!Convert(rightText, type, cultureInvariantRightText, out rightObject))
|
||
|
return true;
|
||
|
|
||
|
int compareResult;
|
||
|
switch (type) {
|
||
|
case ValidationDataType.String:
|
||
|
compareResult = String.Compare((string)leftObject, (string) rightObject, false, CultureInfo.CurrentCulture);
|
||
|
break;
|
||
|
|
||
|
case ValidationDataType.Integer:
|
||
|
compareResult = ((int)leftObject).CompareTo(rightObject);
|
||
|
break;
|
||
|
|
||
|
case ValidationDataType.Double:
|
||
|
compareResult = ((double)leftObject).CompareTo(rightObject);
|
||
|
break;
|
||
|
|
||
|
case ValidationDataType.Date:
|
||
|
compareResult = ((DateTime)leftObject).CompareTo(rightObject);
|
||
|
break;
|
||
|
|
||
|
case ValidationDataType.Currency:
|
||
|
compareResult = ((Decimal)leftObject).CompareTo(rightObject);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Debug.Fail("Unknown Type");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
switch (op) {
|
||
|
case ValidationCompareOperator.Equal:
|
||
|
return compareResult == 0;
|
||
|
case ValidationCompareOperator.NotEqual:
|
||
|
return compareResult != 0;
|
||
|
case ValidationCompareOperator.GreaterThan:
|
||
|
return compareResult > 0 ;
|
||
|
case ValidationCompareOperator.GreaterThanEqual:
|
||
|
return compareResult >= 0 ;
|
||
|
case ValidationCompareOperator.LessThan:
|
||
|
return compareResult < 0 ;
|
||
|
case ValidationCompareOperator.LessThanEqual:
|
||
|
return compareResult <= 0 ;
|
||
|
default:
|
||
|
Debug.Fail("Unknown Operator");
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/// <devdoc>
|
||
|
/// <para>[To be supplied.]</para>
|
||
|
/// </devdoc>
|
||
|
protected override bool DetermineRenderUplevel() {
|
||
|
// We don't do client-side validation for dates with non gregorian calendars
|
||
|
if (Type == ValidationDataType.Date && DateTimeFormatInfo.CurrentInfo.Calendar.GetType() != typeof(GregorianCalendar)) {
|
||
|
return false;
|
||
|
}
|
||
|
return base.DetermineRenderUplevel();
|
||
|
}
|
||
|
|
||
|
internal string ConvertToShortDateString(string text) {
|
||
|
// VSWhidbey 83099, 85305, we should ignore error if it happens and
|
||
|
// leave text as intact when parsing the date. We assume the caller
|
||
|
// (validator) is able to handle invalid text itself.
|
||
|
DateTime date;
|
||
|
if (DateTime.TryParse(text, CultureInfo.CurrentCulture, DateTimeStyles.None, out date)) {
|
||
|
text = date.ToShortDateString();
|
||
|
}
|
||
|
return text;
|
||
|
}
|
||
|
|
||
|
internal bool IsInStandardDateFormat(string date) {
|
||
|
// VSWhidbey 115454: We identify that date string with only numbers
|
||
|
// and specific punctuation separators is in standard date format.
|
||
|
const string standardDateExpression = "^\\s*(\\d+)([-/]|\\. ?)(\\d+)\\2(\\d+)\\s*$";
|
||
|
return Regex.Match(date, standardDateExpression).Success;
|
||
|
}
|
||
|
|
||
|
internal string ConvertCultureInvariantToCurrentCultureFormat(string valueInString,
|
||
|
ValidationDataType type) {
|
||
|
object value;
|
||
|
Convert(valueInString, type, true, out value);
|
||
|
if (value is DateTime) {
|
||
|
// For Date type we explicitly want the date portion only
|
||
|
return ((DateTime) value).ToShortDateString();
|
||
|
}
|
||
|
else {
|
||
|
return System.Convert.ToString(value, CultureInfo.CurrentCulture);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|