//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Collections.Generic; using System.Data.Common; using System.Data.Entity; using System.Data.Metadata.Edm; using System.Diagnostics; using System.Linq; using System.Text; using System.Xml; namespace System.Data.SqlClient { /// /// The Provider Manifest for SQL Server /// internal class SqlProviderManifest : DbXmlEnabledProviderManifest { internal const string TokenSql8 = "2000"; internal const string TokenSql9 = "2005"; internal const string TokenSql10 = "2008"; // '~' is the same escape character that L2S uses internal const char LikeEscapeChar = '~'; internal const string LikeEscapeCharToString = "~"; #region Private Fields // Default to SQL Server 2005 (9.0) private SqlVersion _version = SqlVersion.Sql9; /// /// maximum size of sql server unicode /// private const int varcharMaxSize = 8000; private const int nvarcharMaxSize = 4000; private const int binaryMaxSize = 8000; private System.Collections.ObjectModel.ReadOnlyCollection _primitiveTypes = null; private System.Collections.ObjectModel.ReadOnlyCollection _functions = null; #endregion #region Constructors /// /// Constructor /// /// A token used to infer the capabilities of the store public SqlProviderManifest(string manifestToken) : base(SqlProviderManifest.GetProviderManifest()) { // GetSqlVersion will throw ArgumentException if manifestToken is null, empty, or not recognized. _version = SqlVersionUtils.GetSqlVersion(manifestToken); } #endregion #region Properties internal SqlVersion SqlVersion { get { return this._version; } } #endregion #region Private Methods private static XmlReader GetProviderManifest() { return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices.ProviderManifest.xml"); } private XmlReader GetStoreSchemaMapping(string mslName) { return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices." + mslName + ".msl"); } private XmlReader GetStoreSchemaDescription(string ssdlName) { if (this._version == SqlVersion.Sql8) { return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices." + ssdlName + "_Sql8.ssdl"); } return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices." + ssdlName + ".ssdl"); } #endregion #region Internal Methods /// /// Function to detect wildcard characters %, _, [ and ^ and escape them with a preceding ~ /// This escaping is used when StartsWith, EndsWith and Contains canonical and CLR functions /// are translated to their equivalent LIKE expression /// NOTE: This code has been copied from LinqToSql /// /// Original input as specified by the user /// escape the escape character ~ regardless whether wildcard /// characters were encountered /// true if the escaping was performed, false if no escaping was required /// The escaped string that can be used as pattern in a LIKE expression internal static string EscapeLikeText(string text, bool alwaysEscapeEscapeChar, out bool usedEscapeChar) { usedEscapeChar = false; if (!(text.Contains("%") || text.Contains("_") || text.Contains("[") || text.Contains("^") || alwaysEscapeEscapeChar && text.Contains(LikeEscapeCharToString))) { return text; } StringBuilder sb = new StringBuilder(text.Length); foreach (char c in text) { if (c == '%' || c == '_' || c == '[' || c == '^' || c == LikeEscapeChar) { sb.Append(LikeEscapeChar); usedEscapeChar = true; } sb.Append(c); } return sb.ToString(); } #endregion #region Overrides /// /// Providers should override this to return information specific to their provider. /// /// This method should never return null. /// /// The name of the information to be retrieved. /// An XmlReader at the begining of the information requested. protected override XmlReader GetDbInformation(string informationType) { if (informationType == DbProviderManifest.StoreSchemaDefinitionVersion3 || informationType == DbProviderManifest.StoreSchemaDefinition) { return GetStoreSchemaDescription(informationType); } if (informationType == DbProviderManifest.StoreSchemaMappingVersion3 || informationType == DbProviderManifest.StoreSchemaMapping) { return GetStoreSchemaMapping(informationType); } // Use default Conceptual Schema Definition if (informationType == DbProviderManifest.ConceptualSchemaDefinitionVersion3 || informationType == DbProviderManifest.ConceptualSchemaDefinition) { return null; } throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.ProviderReturnedNullForGetDbInformation(informationType)); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] public override System.Collections.ObjectModel.ReadOnlyCollection GetStoreTypes() { if (this._primitiveTypes == null) { if (this._version == SqlVersion.Sql10) { this._primitiveTypes = base.GetStoreTypes(); } else { List primitiveTypes = new List(base.GetStoreTypes()); Debug.Assert((this._version == SqlVersion.Sql8) || (this._version == SqlVersion.Sql9), "Found verion other than Sql 8, 9 or 10"); //Remove the Katmai types for both Sql8 and Sql9 primitiveTypes.RemoveAll(new Predicate( delegate(PrimitiveType primitiveType) { string name = primitiveType.Name.ToLowerInvariant(); return name.Equals("time", StringComparison.Ordinal) || name.Equals("date", StringComparison.Ordinal) || name.Equals("datetime2", StringComparison.Ordinal) || name.Equals("datetimeoffset", StringComparison.Ordinal) || name.Equals("geography", StringComparison.Ordinal) || name.Equals("geometry", StringComparison.Ordinal); } ) ); //Remove the types that won't work in Sql8 if (this._version == SqlVersion.Sql8) { // SQLBUDT 550667 and 551271: Remove xml and 'max' types for SQL Server 2000 primitiveTypes.RemoveAll(new Predicate( delegate(PrimitiveType primitiveType) { string name = primitiveType.Name.ToLowerInvariant(); return name.Equals("xml", StringComparison.Ordinal) || name.EndsWith("(max)", StringComparison.Ordinal); } ) ); } this._primitiveTypes = primitiveTypes.AsReadOnly(); } } return this._primitiveTypes; } public override System.Collections.ObjectModel.ReadOnlyCollection GetStoreFunctions() { if (this._functions == null) { if (this._version == SqlVersion.Sql10) { this._functions = base.GetStoreFunctions(); } else { //Remove the functions over katmai types from both Sql 9 and Sql 8. IEnumerable functions = base.GetStoreFunctions().Where(f => !IsKatmaiOrNewer(f)); if(this._version == SqlVersion.Sql8) { // SQLBUDT 550998: Remove unsupported overloads from Provider Manifest on SQL 8.0 functions = functions.Where(f => !IsYukonOrNewer(f)); } this._functions = functions.ToList().AsReadOnly(); } } return this._functions; } private static bool IsKatmaiOrNewer(EdmFunction edmFunction) { // Spatial types are only supported from Katmai onward; any functions using them must therefore also be Katmai or newer. if ((edmFunction.ReturnParameter != null && Helper.IsSpatialType(edmFunction.ReturnParameter.TypeUsage)) || edmFunction.Parameters.Any(p => Helper.IsSpatialType(p.TypeUsage))) { return true; } ReadOnlyMetadataCollection funParams = edmFunction.Parameters; switch (edmFunction.Name.ToUpperInvariant()) { case "COUNT": case "COUNT_BIG": case "MAX": case "MIN": { string name = ((CollectionType)funParams[0].TypeUsage.EdmType).TypeUsage.EdmType.Name; return ((name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) || (name.Equals("Time", StringComparison.OrdinalIgnoreCase))); } case "DAY": case "MONTH": case "YEAR": case "DATALENGTH": case "CHECKSUM": { string name = funParams[0].TypeUsage.EdmType.Name; return ((name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) || (name.Equals("Time", StringComparison.OrdinalIgnoreCase))); } case "DATEADD": case "DATEDIFF": { string param1Name = funParams[1].TypeUsage.EdmType.Name; string param2Name = funParams[2].TypeUsage.EdmType.Name; return ((param1Name.Equals("Time", StringComparison.OrdinalIgnoreCase)) || (param2Name.Equals("Time", StringComparison.OrdinalIgnoreCase)) || (param1Name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) || (param2Name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase))); } case "DATENAME": case "DATEPART": { string name = funParams[1].TypeUsage.EdmType.Name; return ((name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) || (name.Equals("Time", StringComparison.OrdinalIgnoreCase))); } case "SYSUTCDATETIME": case "SYSDATETIME": case "SYSDATETIMEOFFSET": return true; default: break; } return false; } private static bool IsYukonOrNewer(EdmFunction edmFunction) { ReadOnlyMetadataCollection funParams = edmFunction.Parameters; if (funParams == null || funParams.Count == 0) { return false; } switch (edmFunction.Name.ToUpperInvariant()) { case "COUNT": case "COUNT_BIG": { string name = ((CollectionType)funParams[0].TypeUsage.EdmType).TypeUsage.EdmType.Name; return name.Equals("Guid", StringComparison.OrdinalIgnoreCase); } case "CHARINDEX": { foreach (FunctionParameter funParam in funParams) { if (funParam.TypeUsage.EdmType.Name.Equals("Int64", StringComparison.OrdinalIgnoreCase)) { return true; } } } break; default: break; } return false; } /// /// This method takes a type and a set of facets and returns the best mapped equivalent type /// in EDM. /// /// A TypeUsage encapsulating a store type and a set of facets /// A TypeUsage encapsulating an EDM type and a set of facets [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")] public override TypeUsage GetEdmType(TypeUsage storeType) { EntityUtil.CheckArgumentNull(storeType, "storeType"); string storeTypeName = storeType.EdmType.Name.ToLowerInvariant(); if (!base.StoreTypeNameToEdmPrimitiveType.ContainsKey(storeTypeName)) { throw EntityUtil.Argument(Strings.ProviderDoesNotSupportType(storeTypeName)); } PrimitiveType edmPrimitiveType = base.StoreTypeNameToEdmPrimitiveType[storeTypeName]; int maxLength = 0; bool isUnicode = true; bool isFixedLen = false; bool isUnbounded = true; PrimitiveTypeKind newPrimitiveTypeKind; switch (storeTypeName) { // for some types we just go with simple type usage with no facets case "tinyint": case "smallint": case "bigint": case "bit": case "uniqueidentifier": case "int": case "geography": case "geometry": return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType); case "varchar": newPrimitiveTypeKind = PrimitiveTypeKind.String; isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength); isUnicode = false; isFixedLen = false; break; case "char": newPrimitiveTypeKind = PrimitiveTypeKind.String; isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength); isUnicode = false; isFixedLen = true; break; case "nvarchar": newPrimitiveTypeKind = PrimitiveTypeKind.String; isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength); isUnicode = true; isFixedLen = false; break; case "nchar": newPrimitiveTypeKind = PrimitiveTypeKind.String; isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength); isUnicode = true; isFixedLen = true; break; case "varchar(max)": case "text": newPrimitiveTypeKind = PrimitiveTypeKind.String; isUnbounded = true; isUnicode = false; isFixedLen = false; break; case "nvarchar(max)": case "ntext": case "xml": newPrimitiveTypeKind = PrimitiveTypeKind.String; isUnbounded = true; isUnicode = true; isFixedLen = false; break; case "binary": newPrimitiveTypeKind = PrimitiveTypeKind.Binary; isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength); isFixedLen = true; break; case "varbinary": newPrimitiveTypeKind = PrimitiveTypeKind.Binary; isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength); isFixedLen = false; break; case "varbinary(max)": case "image": newPrimitiveTypeKind = PrimitiveTypeKind.Binary; isUnbounded = true; isFixedLen = false; break; case "timestamp": case "rowversion": return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, true, 8); case "float": case "real": return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType); case "decimal": case "numeric": { byte precision; byte scale; if (TypeHelpers.TryGetPrecision(storeType, out precision) && TypeHelpers.TryGetScale(storeType, out scale)) { return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, precision, scale); } else { return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType); } } case "money": return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, 19, 4); case "smallmoney": return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, 10, 4); case "datetime": case "datetime2": case "smalldatetime": return TypeUsage.CreateDateTimeTypeUsage(edmPrimitiveType, null); case "date": return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType); case "time": return TypeUsage.CreateTimeTypeUsage(edmPrimitiveType, null); case "datetimeoffset": return TypeUsage.CreateDateTimeOffsetTypeUsage(edmPrimitiveType, null); default: throw EntityUtil.NotSupported(Strings.ProviderDoesNotSupportType(storeTypeName)); } Debug.Assert(newPrimitiveTypeKind == PrimitiveTypeKind.String || newPrimitiveTypeKind == PrimitiveTypeKind.Binary, "at this point only string and binary types should be present"); switch(newPrimitiveTypeKind) { case PrimitiveTypeKind.String: if (!isUnbounded) { return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen, maxLength); } else { return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen); } case PrimitiveTypeKind.Binary: if (!isUnbounded) { return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen, maxLength); } else { return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen); } default: throw EntityUtil.NotSupported(Strings.ProviderDoesNotSupportType(storeTypeName)); } } /// /// This method takes a type and a set of facets and returns the best mapped equivalent type /// in SQL Server, taking the store version into consideration. /// /// A TypeUsage encapsulating an EDM type and a set of facets /// A TypeUsage encapsulating a store type and a set of facets public override TypeUsage GetStoreType(TypeUsage edmType) { EntityUtil.CheckArgumentNull(edmType, "edmType"); System.Diagnostics.Debug.Assert(edmType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType); PrimitiveType primitiveType = edmType.EdmType as PrimitiveType; if (primitiveType == null) { throw EntityUtil.Argument(Strings.ProviderDoesNotSupportType(edmType.Identity)); } ReadOnlyMetadataCollection facets = edmType.Facets; switch (primitiveType.PrimitiveTypeKind) { case PrimitiveTypeKind.Boolean: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bit"]); case PrimitiveTypeKind.Byte: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["tinyint"]); case PrimitiveTypeKind.Int16: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["smallint"]); case PrimitiveTypeKind.Int32: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int"]); case PrimitiveTypeKind.Int64: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bigint"]); case PrimitiveTypeKind.Geography: case PrimitiveTypeKind.GeographyPoint: case PrimitiveTypeKind.GeographyLineString: case PrimitiveTypeKind.GeographyPolygon: case PrimitiveTypeKind.GeographyMultiPoint: case PrimitiveTypeKind.GeographyMultiLineString: case PrimitiveTypeKind.GeographyMultiPolygon: case PrimitiveTypeKind.GeographyCollection: return GetStorePrimitiveTypeIfPostSql9("geography", edmType.Identity, primitiveType.PrimitiveTypeKind); case PrimitiveTypeKind.Geometry: case PrimitiveTypeKind.GeometryPoint: case PrimitiveTypeKind.GeometryLineString: case PrimitiveTypeKind.GeometryPolygon: case PrimitiveTypeKind.GeometryMultiPoint: case PrimitiveTypeKind.GeometryMultiLineString: case PrimitiveTypeKind.GeometryMultiPolygon: case PrimitiveTypeKind.GeometryCollection: return GetStorePrimitiveTypeIfPostSql9("geometry", edmType.Identity, primitiveType.PrimitiveTypeKind); case PrimitiveTypeKind.Guid: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["uniqueidentifier"]); case PrimitiveTypeKind.Double: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["float"]); case PrimitiveTypeKind.Single: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["real"]); case PrimitiveTypeKind.Decimal: // decimal, numeric, smallmoney, money { byte precision; if (!TypeHelpers.TryGetPrecision(edmType, out precision)) { precision = 18; } byte scale; if (!TypeHelpers.TryGetScale(edmType, out scale)) { scale = 0; } TypeUsage tu = TypeUsage.CreateDecimalTypeUsage(StoreTypeNameToStorePrimitiveType["decimal"], precision, scale); return tu; } case PrimitiveTypeKind.Binary: // binary, varbinary, varbinary(max), image, timestamp, rowversion { bool isFixedLength = null != facets[DbProviderManifest.FixedLengthFacetName].Value && (bool)facets[DbProviderManifest.FixedLengthFacetName].Value; Facet f = facets[DbProviderManifest.MaxLengthFacetName]; bool isMaxLength = Helper.IsUnboundedFacetValue(f) || null == f.Value || (int)f.Value > binaryMaxSize; int maxLength = !isMaxLength ? (int)f.Value : Int32.MinValue; TypeUsage tu; if (isFixedLength) { tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["binary"], true, (isMaxLength ? binaryMaxSize : maxLength)); } else { if (isMaxLength) { if (_version != SqlVersion.Sql8) { tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary(max)"], false); Debug.Assert(tu.Facets[DbProviderManifest.MaxLengthFacetName].Description.IsConstant, "varbinary(max) is not constant!"); } else { tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary"], false, binaryMaxSize); } } else { tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary"], false, maxLength); } } return tu; } case PrimitiveTypeKind.String: // char, nchar, varchar, nvarchar, varchar(max), nvarchar(max), ntext, text, xml { bool isUnicode = null == facets[DbProviderManifest.UnicodeFacetName].Value || (bool)facets[DbProviderManifest.UnicodeFacetName].Value; bool isFixedLength = null != facets[DbProviderManifest.FixedLengthFacetName].Value && (bool)facets[DbProviderManifest.FixedLengthFacetName].Value; Facet f = facets[DbProviderManifest.MaxLengthFacetName]; // maxlen is true if facet value is unbounded, the value is bigger than the limited string sizes *or* the facet // value is null. this is needed since functions still have maxlength facet value as null bool isMaxLength = Helper.IsUnboundedFacetValue(f) || null == f.Value || (int)f.Value > (isUnicode ? nvarcharMaxSize : varcharMaxSize); int maxLength = !isMaxLength ? (int)f.Value : Int32.MinValue; TypeUsage tu; if (isUnicode) { if (isFixedLength) { tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nchar"], true, true, (isMaxLength ? nvarcharMaxSize : maxLength)); } else { if (isMaxLength) { // nvarchar(max) (SQL 9) or ntext (SQL 8) if (_version != SqlVersion.Sql8) { tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar(max)"], true, false); Debug.Assert(tu.Facets[DbProviderManifest.MaxLengthFacetName].Description.IsConstant, "NVarchar(max) is not constant!"); } else { // if it is unknown, fallback to nvarchar[4000] instead of ntext since it has limited store semantics tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar"], true, false, nvarcharMaxSize); } } else { tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar"], true, false, maxLength); } } } else // !isUnicode { if (isFixedLength) { tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["char"], false, true, (isMaxLength ? varcharMaxSize : maxLength)); } else { if (isMaxLength) { // nvarchar(max) (SQL 9) or ntext (SQL 8) if (_version != SqlVersion.Sql8) { tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar(max)"], false, false); Debug.Assert(tu.Facets[DbProviderManifest.MaxLengthFacetName].Description.IsConstant, "varchar(max) is not constant!"); } else { // if it is unknown, fallback to varchar[8000] instead of text since it has limited store semantics tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar"], false, false, varcharMaxSize); } } else { tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar"], false, false, maxLength); } } } return tu; } case PrimitiveTypeKind.DateTime: return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["datetime"]); case PrimitiveTypeKind.DateTimeOffset: return GetStorePrimitiveTypeIfPostSql9("datetimeoffset", edmType.Identity, primitiveType.PrimitiveTypeKind); case PrimitiveTypeKind.Time: return GetStorePrimitiveTypeIfPostSql9("time", edmType.Identity, primitiveType.PrimitiveTypeKind); default: throw EntityUtil.NotSupported(Strings.NoStoreTypeForEdmType(edmType.Identity, primitiveType.PrimitiveTypeKind)); } } private TypeUsage GetStorePrimitiveTypeIfPostSql9(string storeTypeName, string edmTypeIdentity, PrimitiveTypeKind primitiveTypeKind) { if ((this.SqlVersion != SqlVersion.Sql8) && (this.SqlVersion != SqlVersion.Sql9)) { return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType[storeTypeName]); } else { throw EntityUtil.NotSupported(Strings.NoStoreTypeForEdmType(edmTypeIdentity, primitiveTypeKind)); } } /// /// Returns true, SqlClient supports escaping strings to be used as arguments to like /// The escape character is '~' /// /// The character '~' /// True public override bool SupportsEscapingLikeArgument(out char escapeCharacter) { escapeCharacter = SqlProviderManifest.LikeEscapeChar; return true; } /// /// Escapes the wildcard characters and the escape character in the given argument. /// /// /// Equivalent to the argument, with the wildcard characters and the escape character escaped public override string EscapeLikeArgument(string argument) { EntityUtil.CheckArgumentNull(argument, "argument"); bool usedEscapeCharacter; return SqlProviderManifest.EscapeLikeText(argument, true, out usedEscapeCharacter); } #endregion } }