using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.CodeAnalysis; using System.Data; namespace Wayne.Lib { /// /// Static support class for string manipulation. /// public static class Strings { /// /// Tries to parse an integer just like the full framework's int.TryParse() /// /// String containing integer /// Contains the integer values if the operation is successful /// True if the parse was successful, false otherwise public static bool TryParseInt(string possibleInt, out int i) { if (!string.IsNullOrEmpty(possibleInt)) { try { if (possibleInt.All(Char.IsNumber)) { i = int.Parse(possibleInt); return true; } } catch (FormatException) { } } i = 0; return false; } /// /// Tries to pares a byte just like the full framework's byte.TryParse() /// /// String containing the byte value /// Out parameter for the result. /// True if parsed successfully. [System.Diagnostics.DebuggerStepThrough] public static bool TryParseByte(string possibleInt, out byte b) { if (!string.IsNullOrEmpty(possibleInt)) { try { b = byte.Parse(possibleInt); return true; } catch (FormatException) { } catch (OverflowException) { } } b = 0; return false; } /// /// Tries to parse an integer and returning null if it is not possible. /// /// String containing integer /// The parsed integer or null if System.Int32.Parse() threw an FormatException. public static int? ParseInt(string possibleInt) { #if WindowsCE if (!string.IsNullOrEmpty(possibleInt)) { try { int i = int.Parse(possibleInt); return i; } catch (FormatException) { } } #else int result; if (int.TryParse(possibleInt, out result)) return result; #endif return null; } #region Connection string encoding and decoding. /// /// Parses a standard Wayne connection string on the form "ParamName=Value,ParamName2=Value2" and /// returns it as a Dictionary<string,string>, with key/value corresponding to the original string. /// /// A Wayne connection string. /// A dictionary with the parsed param/values. public static Dictionary ParseConnectionString(string connectionString) { Dictionary connectionStringParamDict = new Dictionary(); string[] listItems = connectionString.Split(','); foreach (string listItem in listItems) { string trimmedListItem = listItem.Trim(); if (trimmedListItem.Length > 0) { int equalSignIndex = trimmedListItem.IndexOf('='); if (equalSignIndex <= 0) throw new ArgumentException("ParseConnectionString, badly formatted connectionString: " + connectionString + " at " + trimmedListItem); string paramName = trimmedListItem.Substring(0, equalSignIndex); string paramValue = trimmedListItem.Substring(equalSignIndex + 1, trimmedListItem.Length - equalSignIndex - 1); connectionStringParamDict[paramName] = paramValue; } } return connectionStringParamDict; } /// /// Creates a connection string from a string,string dictionary. /// /// /// public static string CreateConnectionString(Dictionary connectionStringDictionary) { if (connectionStringDictionary == null) throw new ArgumentNullException("connectionStringDictionary"); StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair pair in connectionStringDictionary) { stringBuilder.Append(pair.Key); stringBuilder.Append("="); stringBuilder.Append(pair.Value); stringBuilder.Append(","); } //Remove the last comma if (stringBuilder.Length > 0) stringBuilder.Remove(stringBuilder.Length - 1, 1); return stringBuilder.ToString(); } #endregion #region ASCII numeric string to BCD in a string conversions /// /// 8-bit encoding - uses codepage 1252 right now /// public static System.Text.Encoding EightBitEncoding = System.Text.Encoding.GetEncoding(1252); /// /// turns a string containing numeric ASCII to a string containing BCD /// /// input string, containing (e.g.) "1324" /// desired number of digits, rounded up to nearest even number. output will contain length/2 bytes /// output string, containing BCD form public static string NumericAsciiToBCD(string ascii, int length) { length = (length % 2) == 0 ? length : length + 1; string f = (ascii.Length > length) ? ascii.Substring(0, length) : ascii.PadLeft(length, '0'); byte[] fb = EightBitEncoding.GetBytes(f); byte[] ob = new byte[fb.Length / 2]; byte bip = 0; for (int i = 0; i < fb.Length; i++) { if (fb[i] < 0x30 || fb[i] > 0x39) { throw new ArgumentException("Input contains non-numeric ASCII character '" + fb[i] + "' at position " + i); } if ((i % 2) == 0) { bip = fb[i]; bip = (byte)(bip - 0x30); bip = (byte)(bip << 4); } else { bip = (byte)(bip | ((byte)(fb[i] - 0x30))); ob[i / 2] = bip; } } return EightBitEncoding.GetString(ob, 0, ob.Length); } private static char[] BCDMap = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; /// /// Converts BCD coded data into ASCII. /// /// /// public static string BCDToNumericAscii(string ascii) { int length = ascii.Length; byte[] fb = EightBitEncoding.GetBytes(ascii); StringBuilder sb = new StringBuilder(); byte bip0, bip1; for (int i = 0; i < fb.Length; i++) { bip0 = (byte)((fb[i] >> 4) & 0x0f); bip1 = (byte)(fb[i] & 0xf0); if (bip0 > 9 || bip1 > 9) { throw new ArgumentException("Input contains non-BCD character '" + fb[i] + "' at position " + i); } sb.Append(BCDMap[bip0] - 0x30); sb.Append(BCDMap[bip1] - 0x30); } return sb.ToString(); } /// /// Shows a hex dump of a string taken as bytes /// /// /// public static string StringAsHexString(string s) { byte[] bytes = EightBitEncoding.GetBytes(s); return ByteArrayToString(bytes, StringsByteArrayFormat.Hex).Replace(',', ' '); } #endregion #region Byte array conversions /// /// Converts a byte array to a hexadecimal representation in the form of a string. /// Use StringToByteArray to get the array back. /// /// Byte array. /// The index of the first byte to read. /// The number of bytes to read. /// The format of the output /// /// /// Thrown when the conversion is not possible. public static string ByteArrayToString(byte[] bytes, int offset, int length, StringsByteArrayFormat stringsByteArrayFormat) { if ((bytes == null) || (bytes.Length == 0) || (length == 0)) return string.Empty; if (stringsByteArrayFormat == StringsByteArrayFormat.ANSI) { char[] charArray = System.Text.Encoding.Default.GetChars(bytes, offset, length); if (length != charArray.Length) throw new ArgumentException("Unable to convert the byte array to an ANSI string."); StringBuilder result = new StringBuilder(charArray.Length); for (int i = 0; i < charArray.Length; i++) { if (charArray[i] == '<') result.Append("<<"); else if ((charArray[i] == bytes[i + offset]) && (charArray[i] >= ' ')) result.Append(charArray[i]); else { result.Append("<"); result.Append(bytes[i + offset].ToString("X2")); result.Append(">"); } } return result.ToString(); } else if (stringsByteArrayFormat == StringsByteArrayFormat.CompactANSI) { char[] charArray = System.Text.Encoding.Default.GetChars(bytes, offset, length); if (length != charArray.Length) throw new ArgumentException("Unable to convert the byte array to an ANSI string."); for (int i = 0; i < charArray.Length; i++) if ((charArray[i] != bytes[i + offset]) || (charArray[i] < ' ')) charArray[i] = '?'; return new string(charArray); } else if (stringsByteArrayFormat == StringsByteArrayFormat.String) { return bytes == null ? null : EightBitEncoding.GetString(bytes, 0, bytes.Length); } else { bool commaSeparated; string format; switch (stringsByteArrayFormat) { case StringsByteArrayFormat.Bytes: commaSeparated = true; format = "G"; break; case StringsByteArrayFormat.Hex: commaSeparated = true; format = "X2"; break; case StringsByteArrayFormat.CompactHex: commaSeparated = false; format = "X2"; break; default: throw new ArgumentException("Unsupported StringsByteArrayFormat", "stringsByteArrayFormat"); } StringBuilder result = new StringBuilder(length); for (int arrayIndex = 0; arrayIndex < length; arrayIndex++) { if (commaSeparated && (arrayIndex > 0)) result.Append(","); result.Append(bytes[arrayIndex + offset].ToString(format, System.Globalization.CultureInfo.InvariantCulture)); } return result.ToString(); } } /// /// Converts a byte array to a hexadecimal representation in the form of a string. /// Use StringToByteArray to get the array back. /// /// Byte array. /// The format of the output /// /// /// Thrown when the conversion is not possible. public static string ByteArrayToString(byte[] bytes, StringsByteArrayFormat stringsByteArrayFormat) { if (bytes == null) return string.Empty; return ByteArrayToString(bytes, 0, bytes.Length, stringsByteArrayFormat); } /// /// Converts a byte array string back to a byte array. /// /// Byte array string. /// /// /// /// /// Input string does not have the correct format. [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.ArgumentException.#ctor(System.String)")] public static byte[] StringToByteArray(string bytes, StringsByteArrayFormat stringsByteArrayFormat) { if (string.IsNullOrEmpty(bytes)) return new byte[0]; switch (stringsByteArrayFormat) { case StringsByteArrayFormat.Raw: { byte[] results = EightBitEncoding.GetBytes(bytes); return results; } //------------------------------------- case StringsByteArrayFormat.Bytes: { string[] stringArray = bytes.Split(','); byte[] result = new byte[stringArray.Length]; for (int i = 0; i < stringArray.Length; i++) result[i] = Convert.ToByte(stringArray[i]); return result; } //------------------------------------- case StringsByteArrayFormat.Hex: { string[] stringArray = bytes.Split(','); byte[] result = new byte[stringArray.Length]; for (int i = 0; i < stringArray.Length; i++) { string hexNumber = stringArray[i]; if (hexNumber.Length == 2) result[i] = (byte)((byte)(GetHex(hexNumber[0]) << 4) + GetHex(hexNumber[1])); else throw new ArgumentException("The hex string had a bad hex value (2 hex digits required)."); } return result; } //------------------------------------- case StringsByteArrayFormat.CompactHex: { int hexStringLength = bytes.Length; int arrayLength = (hexStringLength + 1) / 2; byte[] result = new byte[arrayLength]; int hexStringIndex = 0; int arrayIndex = 0; if (hexStringLength % 2 != 0) result[arrayIndex++] = GetHex(bytes[hexStringIndex++]); for (; arrayIndex < arrayLength; arrayIndex++, hexStringIndex += 2) result[arrayIndex] = (byte)((byte)(GetHex(bytes[hexStringIndex]) << 4) + GetHex(bytes[hexStringIndex + 1])); return result; } //------------------------------------- case StringsByteArrayFormat.ANSI: { // Calculate the length of the resulting byte array. int byteArrayLength = bytes.Length; for (int i = 0; i < bytes.Length; i++) { if (bytes[i] == '<') { // Escape character found. Is it a '<<' or ''? if ((i == bytes.Length - 1) || (bytes[i + 1] == '<')) { // It's a '<<' (or a badly formatted string ending in one '<'). byteArrayLength--; // The byte array is one characted shorter than expected. i++; // Skip the next character. } else { // It's a ''. byteArrayLength -= 3; // The byte array is three characted shorter than expected. i += 3; // Skip the next three characters. } } } byte[] result = new byte[byteArrayLength]; int byteArrayIndex = 0; for (int i = 0; i < bytes.Length; i++) { if (bytes[i] == '<') { // Escape character found. Is it a '<<' or ''? if ((i == bytes.Length - 1) || (bytes[i + 1] == '<')) { // It's a '<<' (or a badly formatted string ending in one '<'). result[byteArrayIndex] = (byte)bytes[i]; i++; // Skip the next character. } else if (i <= bytes.Length - 4) { // It's a ''. result[byteArrayIndex] = (byte)((byte)(GetHex(bytes[i + 1]) << 4) + GetHex(bytes[i + 2])); i += 3; // Skip the next three characters. } else throw new ArgumentException("Badly formatted ANSI string. An uncompleted '' found in the end of the string."); } else { result[byteArrayIndex] = (byte)bytes[i]; } byteArrayIndex++; } return result; } //------------------------------------- case StringsByteArrayFormat.CompactANSI: default: throw new ArgumentException("Unsupported StringsByteArrayFormat", "stringsByteArrayFormat"); } } [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.ArgumentException.#ctor(System.String)")] private static byte GetHex(char hex) { if ((hex >= '0') && (hex <= '9')) return (byte)(hex - '0'); else if ((hex >= 'a') && (hex <= 'f')) return (byte)(hex - 'a' + 10); else if ((hex >= 'A') && (hex <= 'F')) return (byte)(hex - 'A' + 10); throw new ArgumentException("The hexString contains a non-hex character ('" + hex + "'). Allowed characters are 0-9, a-f and A-F."); } #endregion #region SaveToFile / LoadFromFile (==OBSOLETE==) /// /// Saves an array of strings to a file. /// /// The path and file name. /// The lines to save. [Obsolete("This method is now obsolete. Use the Wayne.Lib.IO.FileSupport.SaveToFile() instead.", true)] public static void SaveToFile(string fileName, string[] lines) { } /// /// Saves the content of a StringBuilder to a file. /// /// The path and file name. /// The lines to save. [Obsolete("This method is now obsolete. Use the Wayne.Lib.IO.FileSupport.SaveToFile() instead.", true)] public static void SaveToFile(string fileName, StringBuilder lines) { } /// /// Loads a text file into an array of strings. /// /// The path and file name. [Obsolete("This method is now obsolete. Use the Wayne.Lib.IO.FileSupport.LoadToStringArray() instead.", true)] public static string[] LoadFromFile(string fileName) { return null; } #endregion #region Indenting private static int tabLength = 8; /// /// The length of a Tab. Used by the Indent-method. Default is 8. /// public static int TabLength { get { return tabLength; } set { tabLength = value; } } /// /// Returns a padded string (e.g. used for indenting). /// /// The indent to be used if many lines. /// Is tab-characters allowed to be used? The TabLength-property tells the length of a tab-character. /// public static string Indent(int indentLength, bool allowTabCharacter) { if (allowTabCharacter) { StringBuilder indent = new StringBuilder(); int tabCount = indentLength / tabLength; for (int i = 0; i < tabCount; i++) indent.Append("\t"); int spaceCount = indentLength - tabCount * tabLength; if (spaceCount > 0) indent.Append("".PadRight(spaceCount, ' ')); return indent.ToString(); } else return "".PadRight(indentLength, ' '); } #endregion #region DataSet text formatting /// /// Returns an array of strings that forms a document with a formatted dataset, table after table. /// /// /// public static string[] FormatDataSet(DataSet dataSet) { List result = new List(); result.Add(" DataSetName : " + dataSet.DataSetName); result.Add(" DataSetLocale : " + dataSet.Locale.ToString()); foreach (DataTable dataTable in dataSet.Tables) { result.Add("Table " + dataTable.TableName); result.Add("======================================================"); int colCount = dataTable.Columns.Count; int[] colWidths = new int[colCount]; //Take the column name for starter. for (int i = 0; i < dataTable.Columns.Count; i++) { colWidths[i] = dataTable.Columns[i].ColumnName.Length; } //Run through the data, to find the column width foreach (DataRow dataRow in dataTable.Rows) { for (int col = 0; col < colCount; col++) { string text = dataRow[col].ToString(); colWidths[col] = Math.Max(colWidths[col], text.Length); } } StringBuilder rowBuilder = new StringBuilder(); //Build header row. //Take the column name for starter. string[] formatStrings = new string[colCount]; for (int i = 0; i < formatStrings.Length; i++) formatStrings[i] = "{0,-" + colWidths[i].ToString() + "} "; for (int i = 0; i < colCount; i++) { rowBuilder.Append(string.Format(formatStrings[i], dataTable.Columns[i].ColumnName)); } string headerRow = rowBuilder.ToString(); result.Add(headerRow); rowBuilder.Length = 0; //Put in column undescore for (int i = 0; i < headerRow.Length; i++) { if (headerRow[i] == ' ') rowBuilder.Append(' '); else rowBuilder.Append("-"); } result.Add(rowBuilder.ToString()); rowBuilder.Length = 0; foreach (DataRow dataRow in dataTable.Rows) { for (int col = 0; col < colCount; col++) { string colStr = string.Format(formatStrings[col], dataRow[col].ToString()); rowBuilder.Append(colStr); } result.Add(rowBuilder.ToString()); rowBuilder.Length = 0; } result.Add(""); } return result.ToArray(); } #endregion #region NaturalComparer private static readonly NaturalStringComparer naturalStringComparer = new NaturalStringComparer(); /// /// An IComparer used to sort strings naturally (i.e. so that "5" comes before "10" and "x5" comes before "x10"). /// public static IComparer NaturalComparer { get { return naturalStringComparer; } } #endregion #region Join /// /// Concatenates a specified separator string between each element of a specified array, yielding a single concatenated string. /// /// A string. /// Objects to join /// public static string Join(string separator, IEnumerable objects) { StringBuilder text = new StringBuilder(); bool first = true; foreach (T data in objects) { if (first) first = false; else text.Append(separator); text.Append(data); } return text.ToString(); } #endregion #region ISO8583 encoding helpers /// /// Adds decimal in string form to a buffer, preceded by optional sign. Returns total number of characters added /// /// /// /// /// /// /// public static int DecimalToString(decimal p, StringBuilder sb, bool addSign, int totalLength, int decimalPlaces, char paddingChar) { int rc = 0; if (sb != null) { if (addSign) { sb.Append(p >= 0m ? '+' : '-'); rc++; } double multiplier = Math.Pow(10, decimalPlaces); long po = (long)Math.Abs(Decimal.Round((decimal)((double) p * multiplier), 0)); sb.Append(po.ToString().PadLeft(totalLength, paddingChar)); rc += totalLength; } return rc; } /// /// Adds decimal in string form to a buffer, preceded by optional sign. Returns total number of characters added /// /// /// /// /// /// public static int DecimalToString(decimal p, StringBuilder sb, bool addSign, int totalLength, int decimalPlaces) { int rc = 0; if (sb != null) { if (addSign) { sb.Append(p >= 0m ? '+' : '-'); rc++; } double multiplier = Math.Pow(10, decimalPlaces); long po = (long)Math.Abs(Decimal.Round((decimal) ((double)p * multiplier), 0)); sb.Append(po.ToString()); rc = sb.Length; } return rc; } #endregion } }