//******************************************************************************************************
// StringExtensions.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/23/2003 - J. Ritchie Carroll
// Generated original version of source code.
// 01/24/2006 - J. Ritchie Carroll
// Migrated 2.0 version of source code from 1.1 source (GSF.Shared.String).
// 06/01/2006 - J. Ritchie Carroll
// Added ParseBoolean function to parse strings representing boolean's that may be numeric.
// 07/07/2006 - J. Ritchie Carroll
// Added GetStringSegments function to break a string up into smaller chunks for parsing.
// and/or displaying.
// 08/02/2007 - J. Ritchie Carroll
// Added a CenterText method for centering strings in console applications or fixed width fonts.
// 08/03/2007 - Pinal C. Patel
// Modified the CenterText method to handle multiple lines.
// 08/21/2007 - Darrell Zuercher
// Edited code comments.
// 09/25/2007 - J. Ritchie Carroll
// Added TitleCase function to format a string with the first letter of each word capitalized.
// 04/16/2008 - Pinal C. Patel
// Made the keys of the string dictionary returned by ParseKeyValuePairs function case-insensitive.
// Added JoinKeyValuePairs overloads that does the exact opposite of ParseKeyValuePairs.
// 09/19/2008 - J. Ritchie Carroll
// Converted to C# extensions.
// 12/13/2008 - F. Russell Roberson
// Generalized ParseBoolean to include "Y", and "T".
// Added IndexOfRepeatedChar - Returns the index of the first character that is repeated.
// Added Reverse - Reverses the order of characters in a string.
// Added EnsureEnd - Ensures that a string ends with a specified char or string.
// Added EnsureStart - Ensures that a string begins with a specified char or string.
// Added IsNumeric - Test to see if a string only includes characters that can be interpreted as a number.
// Added TrimWithEllipsisMiddle - Adds an ellipsis in the middle of a string as it is reduced to a specified length.
// Added TrimWithEllipsisEnd - Trims a string to not exceed a fixed length and adds a ellipsis to string end.
// 02/10/2009 - J. Ritchie Carroll
// Added ConvertToType overloaded extensions.
// 02/17/2009 - Josh L. Patterson
// Edited Code Comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 01/11/2010 - Galen K. Riley
// Issue fixes for unit tests:
// ConvertToType - Fix to throw ArgumentNullException instead of NullReferenceException for null value
// ConvertToType - Handling failed conversions better. Calling ConvertToType<int>("\0") returns properly
// JoinKeyValuePairs - Fix to throw ArgumentNullException instead of NullReferenceException
// ReplaceCharacters - Fix to throw ArgumentNullException instead of NullReferenceException for null replacementCharacter
// RemoveCharacters - Fix to throw ArgumentNullException instead of NullReferenceException for null characterTestFunction
// ReplaceCrLfs - Fix to throw ArgumentNullException instead of NullReferenceException for null value
// RegexDecode - Fix to throw ArgumentNullException instead of NullReferenceException for null value
// 12/03/2010 - J. Ritchie Carroll
// Modified ParseKeyValuePairs such that it could handle nested pairs to any needed depth.
// 12/05/2010 - Pinal C. Patel
// Added an overload for ConvertToType() that takes CultureInfo as a parameter.
// 12/07/2010 - Pinal C. Patel
// Updated ConvertToType() to return the type default if passed in string is null or empty.
// 01/04/2011 - J. Ritchie Carroll
// Modified ConvertToType culture default to InvariantCulture for English style parsing defaults.
// 01/14/2011 - J. Ritchie Carroll
// Modified JoinKeyValuePairs to delineate values that contain nested key/value pair expressions
// such that the generated expression can correctly parsed.
// 03/23/2011 - J. Ritchie Carroll
// Modified ParseKeyValuePairs to optionally ignore duplicate keys (default behavior now).
// Removed overloads for ParseKeyValuePairs and JoinKeyValuePairs using optional parameters.
// 08/02/2011 - Pinal C. Patel
// Added RemoveInvalidFileNameCharacters() and ReplaceInvalidFileNameCharacters() methods.
// 10/17/2012 - F Russell Robertson
// Added QuoteWrap() method.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
namespace GSF
{
/// <summary>Defines extension functions related to string manipulation.</summary>
public static class StringExtensions
{
/// <summary>
/// Parses a string intended to represent a boolean value.
/// </summary>
/// <param name="value">String representing a boolean value.</param>
/// <returns>Parsed boolean value.</returns>
/// <remarks>
/// This function, unlike Boolean.Parse, correctly parses a boolean value, even if the string value
/// specified is a number (e.g., 0 or -1). Boolean.Parse expects a string to be represented as
/// "True" or "False" (i.e., Boolean.TrueString or Boolean.FalseString respectively).
/// </remarks>
public static bool ParseBoolean(this string value)
{
if (string.IsNullOrEmpty(value))
return false;
value = value.Trim();
if (value.Length > 0)
{
// Try to parse string a number
int iresult;
if (int.TryParse(value, out iresult))
return (iresult != 0);
// Try to parse string as a boolean
bool bresult;
if (bool.TryParse(value, out bresult))
return bresult;
char test = value.ToUpper()[0];
return (test == 'T' || test == 'Y');
}
return false;
}
/// <summary>
/// Converts this string into the specified type.
/// </summary>
/// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam>
/// <param name="value">Source string to convert to type.</param>
/// <returns><see cref="String"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns>
/// <remarks>
/// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="String"/> to the specified type T,
/// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same
/// <see cref="TypeConverter"/> to convert the original object to a <see cref="String"/>; see the
/// <see cref="Common.TypeConvertToString(object)"/> method for an easy way to do this.
/// </remarks>
public static T ConvertToType<T>(this string value)
{
return ConvertToType<T>(value, null);
}
/// <summary>
/// Converts this string into the specified type.
/// </summary>
/// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam>
/// <param name="value">Source string to convert to type.</param>
/// <param name="type"><see cref="Type"/> to convert string to.</param>
/// <returns><see cref="String"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns>
/// <remarks>
/// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="String"/> to the specified <paramref name="type"/>,
/// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same
/// <see cref="TypeConverter"/> to convert the original object to a <see cref="String"/>; see the
/// <see cref="Common.TypeConvertToString(object)"/> method for an easy way to do this.
/// </remarks>
public static T ConvertToType<T>(this string value, Type type)
{
return ConvertToType<T>(value, type, null);
}
/// <summary>
/// Converts this string into the specified type.
/// </summary>
/// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam>
/// <param name="value">Source string to convert to type.</param>
/// <param name="type"><see cref="Type"/> to convert string to.</param>
/// <param name="culture"><see cref="CultureInfo"/> to use for the conversion.</param>
/// <returns><see cref="String"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns>
/// <remarks>
/// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="String"/> to the specified <paramref name="type"/>,
/// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same
/// <see cref="TypeConverter"/> to convert the original object to a <see cref="String"/>; see the
/// <see cref="Common.TypeConvertToString(object)"/> method for an easy way to do this.
/// </remarks>
public static T ConvertToType<T>(this string value, Type type, CultureInfo culture)
{
// Don't proceed further if string is empty.
if (string.IsNullOrEmpty(value))
return default(T);
// Initialize return type if not specified.
if ((object)type == null)
type = typeof(T);
// Initialize culture info if not specified.
if ((object)culture == null)
culture = CultureInfo.InvariantCulture;
try
{
// Handle booleans as a special case to allow numeric entries as well as true/false
if (type.IsAssignableFrom(typeof(bool)))
return (T)((object)value.ParseBoolean());
// This is faster for native types than using type converter...
if (type.IsAssignableFrom(typeof(IConvertible)))
return (T)Convert.ChangeType(value, type, culture);
// Handle objects that have type converters (e.g., Enum, Color, Point, etc.)
TypeConverter converter = TypeDescriptor.GetConverter(type);
return (T)converter.ConvertFromString(null, culture, value);
}
catch
{
return default(T);
}
}
/// <summary>
/// Turns source string into an array of string segments - each with a set maximum width - for parsing or displaying.
/// </summary>
/// <param name="value">Input string to break up into segments.</param>
/// <param name="segmentSize">Maximum size of returned segment.</param>
/// <returns>Array of string segments as parsed from source string.</returns>
/// <remarks>Returns a single element array with an empty string if source string is null or empty.</remarks>
public static string[] GetSegments(this string value, int segmentSize)
{
if (segmentSize <= 0)
throw new ArgumentOutOfRangeException("segmentSize", "segmentSize must be greater than zero.");
if (string.IsNullOrEmpty(value))
return new[] { "" };
int totalSegments = (int)Math.Ceiling(value.Length / (double)segmentSize);
string[] segments = new string[totalSegments];
for (int x = 0; x < segments.Length; x++)
{
if (x * segmentSize + segmentSize >= value.Length)
segments[x] = value.Substring(x * segmentSize);
else
segments[x] = value.Substring(x * segmentSize, segmentSize);
}
return segments;
}
/// <summary>
/// Combines a dictionary of key-value pairs in to a string.
/// </summary>
/// <param name="pairs">Dictionary of key-value pairs.</param>
/// <param name="parameterDelimiter">Character that delimits one key-value pair from another (e.g. ';').</param>
/// <param name="keyValueDelimiter">Character that delimits a key from its value (e.g. '=').</param>
/// <param name="startValueDelimiter">Optional character that marks the start of a value such that value could contain other
/// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters (e.g., "{").</param>
/// <param name="endValueDelimiter">Optional character that marks the end of a value such that value could contain other
/// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters (e.g., "}").</param>
/// <returns>A string of key-value pairs.</returns>
/// <remarks>
/// Values will be escaped within <paramref name="startValueDelimiter"/> and <paramref name="endValueDelimiter"/>
/// to contain nested key/value pair expressions like the following: <c>normalKVP=-1; nestedKVP={p1=true; p2=0.001}</c>,
/// when either the <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> are detected in the
/// value of the key/value pair.
/// </remarks>
public static string JoinKeyValuePairs(this IDictionary<string, string> pairs, char parameterDelimiter = ';', char keyValueDelimiter = '=', char startValueDelimiter = '{', char endValueDelimiter = '}')
{
if ((object)pairs == null)
throw new ArgumentNullException("pairs");
char[] delimiters = { parameterDelimiter, keyValueDelimiter };
List<string> values = new List<string>();
string value;
foreach (string key in pairs.Keys)
{
value = pairs[key];
if (value.IndexOfAny(delimiters) >= 0)
value = startValueDelimiter + value + endValueDelimiter;
values.Add(string.Format("{0}{1}{2}", key, keyValueDelimiter, value));
}
return string.Join(parameterDelimiter + " ", values);
}
/// <summary>
/// Parses key/value pair expressions from a string. Parameter pairs are delimited by <paramref name="keyValueDelimiter"/>
/// and multiple pairs separated by <paramref name="parameterDelimiter"/>. Supports encapsulated nested expressions.
/// </summary>
/// <param name="value">String containing key/value pair expressions to parse.</param>
/// <param name="parameterDelimiter">Character that delimits one key/value pair from another.</param>
/// <param name="keyValueDelimiter">Character that delimits key from value.</param>
/// <param name="startValueDelimiter">Optional character that marks the start of a value such that value could contain other
/// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters.</param>
/// <param name="endValueDelimiter">Optional character that marks the end of a value such that value could contain other
/// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters.</param>
/// <param name="ignoreDuplicateKeys">Flag determines whether duplicates are ignored. If flag is set to <c>false</c> an
/// <see cref="ArgumentException"/> will be thrown when all key parameters are not unique.</param>
/// <returns>Dictionary of key/value pairs.</returns>
/// <remarks>
/// <para>
/// Parses a string containing key/value pair expressions (e.g., "localPort=5001; transportProtocol=UDP; interface=0.0.0.0").
/// This method treats all "keys" as case-insensitive. Nesting of key/value pair expressions is allowed by encapsulating the
/// value using the <paramref name="startValueDelimiter"/> and <paramref name="endValueDelimiter"/> values (e.g.,
/// "dataChannel={Port=-1;Clients=localhost:8800}; commandChannel={Port=8900}; dataFormat=FloatingPoint;"). There must be one
/// <paramref name="endValueDelimiter"/> for each encountered <paramref name="startValueDelimiter"/> in the value or a
/// <see cref="FormatException"/> will be thrown. Multiple levels of nesting is supported. If the <paramref name="ignoreDuplicateKeys"/>
/// flag is set to <c>false</c> an <see cref="ArgumentException"/> will be thrown when all key parameters are not unique. Note
/// that keys within nested expressions are considered separate key/value pair strings and are not considered when checking
/// for duplicate keys.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">value is null.</exception>
/// <exception cref="ArgumentException">All delimiters must be unique -or- all keys must be unique when
/// <paramref name="ignoreDuplicateKeys"/> is set to <c>false</c>.</exception>
/// <exception cref="FormatException">Total nested key/value pair expressions are mismatched -or- encountered
/// <paramref name="endValueDelimiter"/> before <paramref name="startValueDelimiter"/>.</exception>
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
public static Dictionary<string, string> ParseKeyValuePairs(this string value, char parameterDelimiter = ';', char keyValueDelimiter = '=', char startValueDelimiter = '{', char endValueDelimiter = '}', bool ignoreDuplicateKeys = true)
{
if (value == (string)null)
throw new ArgumentNullException("value");
if (parameterDelimiter == keyValueDelimiter ||
parameterDelimiter == startValueDelimiter ||
parameterDelimiter == endValueDelimiter ||
keyValueDelimiter == startValueDelimiter ||
keyValueDelimiter == endValueDelimiter ||
startValueDelimiter == endValueDelimiter)
throw new ArgumentException("All delimiters must be unique");
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
StringBuilder escapedValue = new StringBuilder();
string escapedParameterDelimiter = parameterDelimiter.RegexEncode();
string escapedKeyValueDelimiter = keyValueDelimiter.RegexEncode();
string escapedStartValueDelimiter = startValueDelimiter.RegexEncode();
string escapedEndValueDelimiter = endValueDelimiter.RegexEncode();
string backslashDelimiter = '\\'.RegexEncode();
string[] elements;
string key, unescapedValue;
bool valueEscaped = false;
int delimiterDepth = 0;
char character;
// Escape any parameter or key/value delimiters within tagged value sequences
// For example, the following string:
// "normalKVP=-1; nestedKVP={p1=true; p2=false}")
// would be encoded as:
// "normalKVP=-1; nestedKVP=p1\\u003dtrue\\u003b p2\\u003dfalse")
for (int x = 0; x < value.Length; x++)
{
character = value[x];
if (character == startValueDelimiter)
{
if (!valueEscaped)
{
valueEscaped = true;
continue; // Don't add tag start delimiter to final value
}
// Handle nested delimiters
delimiterDepth++;
}
if (character == endValueDelimiter)
{
if (valueEscaped)
{
if (delimiterDepth > 0)
{
// Handle nested delimiters
delimiterDepth--;
}
else
{
valueEscaped = false;
continue; // Don't add tag stop delimiter to final value
}
}
else
{
throw new FormatException(string.Format("Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter \'{0}\' before start value delimiter \'{1}\'.", endValueDelimiter, startValueDelimiter));
}
}
if (valueEscaped)
{
// Escape any delimiter characters inside nested key/value pair
if (character == parameterDelimiter)
escapedValue.Append(escapedParameterDelimiter);
else if (character == keyValueDelimiter)
escapedValue.Append(escapedKeyValueDelimiter);
else if (character == startValueDelimiter)
escapedValue.Append(escapedStartValueDelimiter);
else if (character == endValueDelimiter)
escapedValue.Append(escapedEndValueDelimiter);
else if (character == '\\')
escapedValue.Append(backslashDelimiter);
else
escapedValue.Append(character);
}
else
{
if (character == '\\')
escapedValue.Append(backslashDelimiter);
else
escapedValue.Append(character);
}
}
if (delimiterDepth != 0 || valueEscaped)
{
// If value is still escaped, tagged expression was not terminated
if (valueEscaped)
delimiterDepth = 1;
throw new FormatException(string.Format("Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more {0} than {1}.", delimiterDepth > 0 ? "start value delimiters \'" + startValueDelimiter + "\'" : "end value delimiters \'" + endValueDelimiter + "\'", delimiterDepth < 0 ? "start value delimiters \'" + startValueDelimiter + "\'" : "end value delimiters \'" + endValueDelimiter + "\'"));
}
// Parse key/value pairs from escaped value
foreach (string parameter in escapedValue.ToString().Split(parameterDelimiter))
{
// Parse out parameter's key/value elements
elements = parameter.Split(keyValueDelimiter);
if (elements.Length == 2)
{
// Get key expression
key = elements[0].Trim();
// Get unescaped value expression
unescapedValue = elements[1].Trim().
Replace(escapedParameterDelimiter, parameterDelimiter.ToString()).
Replace(escapedKeyValueDelimiter, keyValueDelimiter.ToString()).
Replace(escapedStartValueDelimiter, startValueDelimiter.ToString()).
Replace(escapedEndValueDelimiter, endValueDelimiter.ToString()).
Replace(backslashDelimiter, "\\");
// Add key/value pair to dictionary
if (ignoreDuplicateKeys)
{
// Add or replace key elements with unescaped value
keyValuePairs[key] = unescapedValue;
}
else
{
// Add key elements with unescaped value throwing an exception for encountered duplicate keys
if (keyValuePairs.ContainsKey(key))
throw new ArgumentException(string.Format("Failed to parse key/value pairs: duplicate key encountered. Key \"{0}\" is not unique within the string: \"{1}\"", key, value));
keyValuePairs.Add(key, unescapedValue);
}
}
}
return keyValuePairs;
}
/// <summary>
/// Ensures parameter is not an empty or null string. Returns a single space if test value is empty.
/// </summary>
/// <param name="testValue">Value to test for null or empty.</param>
/// <returns>A non-empty string.</returns>
public static string NotEmpty(this string testValue)
{
return testValue.NotEmpty(" ");
}
/// <summary>
/// Ensures parameter is not an empty or null string.
/// </summary>
/// <param name="testValue">Value to test for null or empty.</param>
/// <param name="nonEmptyReturnValue">Value to return if <paramref name="testValue">testValue</paramref> is null or empty.</param>
/// <returns>A non-empty string.</returns>
public static string NotEmpty(this string testValue, string nonEmptyReturnValue)
{
if (string.IsNullOrEmpty(nonEmptyReturnValue))
throw new ArgumentException("nonEmptyReturnValue cannot be null or empty");
if (string.IsNullOrEmpty(testValue))
return nonEmptyReturnValue;
return testValue;
}
/// <summary>
/// Replaces all characters passing delegate test with specified replacement character.
/// </summary>
/// <param name="value">Input string.</param>
/// <param name="replacementCharacter">Character used to replace characters passing delegate test.</param>
/// <param name="characterTestFunction">Delegate used to determine whether or not character should be replaced.</param>
Lines 1–500 of 1555. Load more
//****************************************************************************************************** // StringExtensions.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/23/2003 - J. Ritchie Carroll // Generated original version of source code. // 01/24/2006 - J. Ritchie Carroll // Migrated 2.0 version of source code from 1.1 source (GSF.Shared.String). // 06/01/2006 - J. Ritchie Carroll // Added ParseBoolean function to parse strings representing boolean's that may be numeric. // 07/07/2006 - J. Ritchie Carroll // Added GetStringSegments function to break a string up into smaller chunks for parsing. // and/or displaying. // 08/02/2007 - J. Ritchie Carroll // Added a CenterText method for centering strings in console applications or fixed width fonts. // 08/03/2007 - Pinal C. Patel // Modified the CenterText method to handle multiple lines. // 08/21/2007 - Darrell Zuercher // Edited code comments. // 09/25/2007 - J. Ritchie Carroll // Added TitleCase function to format a string with the first letter of each word capitalized. // 04/16/2008 - Pinal C. Patel // Made the keys of the string dictionary returned by ParseKeyValuePairs function case-insensitive. // Added JoinKeyValuePairs overloads that does the exact opposite of ParseKeyValuePairs. // 09/19/2008 - J. Ritchie Carroll // Converted to C# extensions. // 12/13/2008 - F. Russell Roberson // Generalized ParseBoolean to include "Y", and "T". // Added IndexOfRepeatedChar - Returns the index of the first character that is repeated. // Added Reverse - Reverses the order of characters in a string. // Added EnsureEnd - Ensures that a string ends with a specified char or string. // Added EnsureStart - Ensures that a string begins with a specified char or string. // Added IsNumeric - Test to see if a string only includes characters that can be interpreted as a number. // Added TrimWithEllipsisMiddle - Adds an ellipsis in the middle of a string as it is reduced to a specified length. // Added TrimWithEllipsisEnd - Trims a string to not exceed a fixed length and adds a ellipsis to string end. // 02/10/2009 - J. Ritchie Carroll // Added ConvertToType overloaded extensions. // 02/17/2009 - Josh L. Patterson // Edited Code Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 01/11/2010 - Galen K. Riley // Issue fixes for unit tests: // ConvertToType - Fix to throw ArgumentNullException instead of NullReferenceException for null value // ConvertToType - Handling failed conversions better. Calling ConvertToType<int>("\0") returns properly // JoinKeyValuePairs - Fix to throw ArgumentNullException instead of NullReferenceException // ReplaceCharacters - Fix to throw ArgumentNullException instead of NullReferenceException for null replacementCharacter // RemoveCharacters - Fix to throw ArgumentNullException instead of NullReferenceException for null characterTestFunction // ReplaceCrLfs - Fix to throw ArgumentNullException instead of NullReferenceException for null value // RegexDecode - Fix to throw ArgumentNullException instead of NullReferenceException for null value // 12/03/2010 - J. Ritchie Carroll // Modified ParseKeyValuePairs such that it could handle nested pairs to any needed depth. // 12/05/2010 - Pinal C. Patel // Added an overload for ConvertToType() that takes CultureInfo as a parameter. // 12/07/2010 - Pinal C. Patel // Updated ConvertToType() to return the type default if passed in string is null or empty. // 01/04/2011 - J. Ritchie Carroll // Modified ConvertToType culture default to InvariantCulture for English style parsing defaults. // 01/14/2011 - J. Ritchie Carroll // Modified JoinKeyValuePairs to delineate values that contain nested key/value pair expressions // such that the generated expression can correctly parsed. // 03/23/2011 - J. Ritchie Carroll // Modified ParseKeyValuePairs to optionally ignore duplicate keys (default behavior now). // Removed overloads for ParseKeyValuePairs and JoinKeyValuePairs using optional parameters. // 08/02/2011 - Pinal C. Patel // Added RemoveInvalidFileNameCharacters() and ReplaceInvalidFileNameCharacters() methods. // 10/17/2012 - F Russell Robertson // Added QuoteWrap() method. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Text.RegularExpressions; namespace GSF { /// <summary>Defines extension functions related to string manipulation.</summary> public static class StringExtensions { /// <summary> /// Parses a string intended to represent a boolean value. /// </summary> /// <param name="value">String representing a boolean value.</param> /// <returns>Parsed boolean value.</returns> /// <remarks> /// This function, unlike Boolean.Parse, correctly parses a boolean value, even if the string value /// specified is a number (e.g., 0 or -1). Boolean.Parse expects a string to be represented as /// "True" or "False" (i.e., Boolean.TrueString or Boolean.FalseString respectively). /// </remarks> public static bool ParseBoolean(this string value) { if (string.IsNullOrEmpty(value)) return false; value = value.Trim(); if (value.Length > 0) { // Try to parse string a number int iresult; if (int.TryParse(value, out iresult)) return (iresult != 0); // Try to parse string as a boolean bool bresult; if (bool.TryParse(value, out bresult)) return bresult; char test = value.ToUpper()[0]; return (test == 'T' || test == 'Y'); } return false; } /// <summary> /// Converts this string into the specified type. /// </summary> /// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam> /// <param name="value">Source string to convert to type.</param> /// <returns><see cref="String"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns> /// <remarks> /// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="String"/> to the specified type T, /// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same /// <see cref="TypeConverter"/> to convert the original object to a <see cref="String"/>; see the /// <see cref="Common.TypeConvertToString(object)"/> method for an easy way to do this. /// </remarks> public static T ConvertToType<T>(this string value) { return ConvertToType<T>(value, null); } /// <summary> /// Converts this string into the specified type. /// </summary> /// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam> /// <param name="value">Source string to convert to type.</param> /// <param name="type"><see cref="Type"/> to convert string to.</param> /// <returns><see cref="String"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns> /// <remarks> /// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="String"/> to the specified <paramref name="type"/>, /// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same /// <see cref="TypeConverter"/> to convert the original object to a <see cref="String"/>; see the /// <see cref="Common.TypeConvertToString(object)"/> method for an easy way to do this. /// </remarks> public static T ConvertToType<T>(this string value, Type type) { return ConvertToType<T>(value, type, null); } /// <summary> /// Converts this string into the specified type. /// </summary> /// <typeparam name="T"><see cref="Type"/> to convert string to.</typeparam> /// <param name="value">Source string to convert to type.</param> /// <param name="type"><see cref="Type"/> to convert string to.</param> /// <param name="culture"><see cref="CultureInfo"/> to use for the conversion.</param> /// <returns><see cref="String"/> converted to specified <see cref="Type"/>; default value of specified type T if conversion fails.</returns> /// <remarks> /// This function makes use of a <see cref="TypeConverter"/> to convert this <see cref="String"/> to the specified <paramref name="type"/>, /// the best way to make sure <paramref name="value"/> can be converted back to its original type is to use the same /// <see cref="TypeConverter"/> to convert the original object to a <see cref="String"/>; see the /// <see cref="Common.TypeConvertToString(object)"/> method for an easy way to do this. /// </remarks> public static T ConvertToType<T>(this string value, Type type, CultureInfo culture) { // Don't proceed further if string is empty. if (string.IsNullOrEmpty(value)) return default(T); // Initialize return type if not specified. if ((object)type == null) type = typeof(T); // Initialize culture info if not specified. if ((object)culture == null) culture = CultureInfo.InvariantCulture; try { // Handle booleans as a special case to allow numeric entries as well as true/false if (type.IsAssignableFrom(typeof(bool))) return (T)((object)value.ParseBoolean()); // This is faster for native types than using type converter... if (type.IsAssignableFrom(typeof(IConvertible))) return (T)Convert.ChangeType(value, type, culture); // Handle objects that have type converters (e.g., Enum, Color, Point, etc.) TypeConverter converter = TypeDescriptor.GetConverter(type); return (T)converter.ConvertFromString(null, culture, value); } catch { return default(T); } } /// <summary> /// Turns source string into an array of string segments - each with a set maximum width - for parsing or displaying. /// </summary> /// <param name="value">Input string to break up into segments.</param> /// <param name="segmentSize">Maximum size of returned segment.</param> /// <returns>Array of string segments as parsed from source string.</returns> /// <remarks>Returns a single element array with an empty string if source string is null or empty.</remarks> public static string[] GetSegments(this string value, int segmentSize) { if (segmentSize <= 0) throw new ArgumentOutOfRangeException("segmentSize", "segmentSize must be greater than zero."); if (string.IsNullOrEmpty(value)) return new[] { "" }; int totalSegments = (int)Math.Ceiling(value.Length / (double)segmentSize); string[] segments = new string[totalSegments]; for (int x = 0; x < segments.Length; x++) { if (x * segmentSize + segmentSize >= value.Length) segments[x] = value.Substring(x * segmentSize); else segments[x] = value.Substring(x * segmentSize, segmentSize); } return segments; } /// <summary> /// Combines a dictionary of key-value pairs in to a string. /// </summary> /// <param name="pairs">Dictionary of key-value pairs.</param> /// <param name="parameterDelimiter">Character that delimits one key-value pair from another (e.g. ';').</param> /// <param name="keyValueDelimiter">Character that delimits a key from its value (e.g. '=').</param> /// <param name="startValueDelimiter">Optional character that marks the start of a value such that value could contain other /// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters (e.g., "{").</param> /// <param name="endValueDelimiter">Optional character that marks the end of a value such that value could contain other /// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters (e.g., "}").</param> /// <returns>A string of key-value pairs.</returns> /// <remarks> /// Values will be escaped within <paramref name="startValueDelimiter"/> and <paramref name="endValueDelimiter"/> /// to contain nested key/value pair expressions like the following: <c>normalKVP=-1; nestedKVP={p1=true; p2=0.001}</c>, /// when either the <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> are detected in the /// value of the key/value pair. /// </remarks> public static string JoinKeyValuePairs(this IDictionary<string, string> pairs, char parameterDelimiter = ';', char keyValueDelimiter = '=', char startValueDelimiter = '{', char endValueDelimiter = '}') { if ((object)pairs == null) throw new ArgumentNullException("pairs"); char[] delimiters = { parameterDelimiter, keyValueDelimiter }; List<string> values = new List<string>(); string value; foreach (string key in pairs.Keys) { value = pairs[key]; if (value.IndexOfAny(delimiters) >= 0) value = startValueDelimiter + value + endValueDelimiter; values.Add(string.Format("{0}{1}{2}", key, keyValueDelimiter, value)); } return string.Join(parameterDelimiter + " ", values); } /// <summary> /// Parses key/value pair expressions from a string. Parameter pairs are delimited by <paramref name="keyValueDelimiter"/> /// and multiple pairs separated by <paramref name="parameterDelimiter"/>. Supports encapsulated nested expressions. /// </summary> /// <param name="value">String containing key/value pair expressions to parse.</param> /// <param name="parameterDelimiter">Character that delimits one key/value pair from another.</param> /// <param name="keyValueDelimiter">Character that delimits key from value.</param> /// <param name="startValueDelimiter">Optional character that marks the start of a value such that value could contain other /// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters.</param> /// <param name="endValueDelimiter">Optional character that marks the end of a value such that value could contain other /// <paramref name="parameterDelimiter"/> or <paramref name="keyValueDelimiter"/> characters.</param> /// <param name="ignoreDuplicateKeys">Flag determines whether duplicates are ignored. If flag is set to <c>false</c> an /// <see cref="ArgumentException"/> will be thrown when all key parameters are not unique.</param> /// <returns>Dictionary of key/value pairs.</returns> /// <remarks> /// <para> /// Parses a string containing key/value pair expressions (e.g., "localPort=5001; transportProtocol=UDP; interface=0.0.0.0"). /// This method treats all "keys" as case-insensitive. Nesting of key/value pair expressions is allowed by encapsulating the /// value using the <paramref name="startValueDelimiter"/> and <paramref name="endValueDelimiter"/> values (e.g., /// "dataChannel={Port=-1;Clients=localhost:8800}; commandChannel={Port=8900}; dataFormat=FloatingPoint;"). There must be one /// <paramref name="endValueDelimiter"/> for each encountered <paramref name="startValueDelimiter"/> in the value or a /// <see cref="FormatException"/> will be thrown. Multiple levels of nesting is supported. If the <paramref name="ignoreDuplicateKeys"/> /// flag is set to <c>false</c> an <see cref="ArgumentException"/> will be thrown when all key parameters are not unique. Note /// that keys within nested expressions are considered separate key/value pair strings and are not considered when checking /// for duplicate keys. /// </para> /// </remarks> /// <exception cref="ArgumentNullException">value is null.</exception> /// <exception cref="ArgumentException">All delimiters must be unique -or- all keys must be unique when /// <paramref name="ignoreDuplicateKeys"/> is set to <c>false</c>.</exception> /// <exception cref="FormatException">Total nested key/value pair expressions are mismatched -or- encountered /// <paramref name="endValueDelimiter"/> before <paramref name="startValueDelimiter"/>.</exception> [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] public static Dictionary<string, string> ParseKeyValuePairs(this string value, char parameterDelimiter = ';', char keyValueDelimiter = '=', char startValueDelimiter = '{', char endValueDelimiter = '}', bool ignoreDuplicateKeys = true) { if (value == (string)null) throw new ArgumentNullException("value"); if (parameterDelimiter == keyValueDelimiter || parameterDelimiter == startValueDelimiter || parameterDelimiter == endValueDelimiter || keyValueDelimiter == startValueDelimiter || keyValueDelimiter == endValueDelimiter || startValueDelimiter == endValueDelimiter) throw new ArgumentException("All delimiters must be unique"); Dictionary<string, string> keyValuePairs = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase); StringBuilder escapedValue = new StringBuilder(); string escapedParameterDelimiter = parameterDelimiter.RegexEncode(); string escapedKeyValueDelimiter = keyValueDelimiter.RegexEncode(); string escapedStartValueDelimiter = startValueDelimiter.RegexEncode(); string escapedEndValueDelimiter = endValueDelimiter.RegexEncode(); string backslashDelimiter = '\\'.RegexEncode(); string[] elements; string key, unescapedValue; bool valueEscaped = false; int delimiterDepth = 0; char character; // Escape any parameter or key/value delimiters within tagged value sequences // For example, the following string: // "normalKVP=-1; nestedKVP={p1=true; p2=false}") // would be encoded as: // "normalKVP=-1; nestedKVP=p1\\u003dtrue\\u003b p2\\u003dfalse") for (int x = 0; x < value.Length; x++) { character = value[x]; if (character == startValueDelimiter) { if (!valueEscaped) { valueEscaped = true; continue; // Don't add tag start delimiter to final value } // Handle nested delimiters delimiterDepth++; } if (character == endValueDelimiter) { if (valueEscaped) { if (delimiterDepth > 0) { // Handle nested delimiters delimiterDepth--; } else { valueEscaped = false; continue; // Don't add tag stop delimiter to final value } } else { throw new FormatException(string.Format("Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter \'{0}\' before start value delimiter \'{1}\'.", endValueDelimiter, startValueDelimiter)); } } if (valueEscaped) { // Escape any delimiter characters inside nested key/value pair if (character == parameterDelimiter) escapedValue.Append(escapedParameterDelimiter); else if (character == keyValueDelimiter) escapedValue.Append(escapedKeyValueDelimiter); else if (character == startValueDelimiter) escapedValue.Append(escapedStartValueDelimiter); else if (character == endValueDelimiter) escapedValue.Append(escapedEndValueDelimiter); else if (character == '\\') escapedValue.Append(backslashDelimiter); else escapedValue.Append(character); } else { if (character == '\\') escapedValue.Append(backslashDelimiter); else escapedValue.Append(character); } } if (delimiterDepth != 0 || valueEscaped) { // If value is still escaped, tagged expression was not terminated if (valueEscaped) delimiterDepth = 1; throw new FormatException(string.Format("Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more {0} than {1}.", delimiterDepth > 0 ? "start value delimiters \'" + startValueDelimiter + "\'" : "end value delimiters \'" + endValueDelimiter + "\'", delimiterDepth < 0 ? "start value delimiters \'" + startValueDelimiter + "\'" : "end value delimiters \'" + endValueDelimiter + "\'")); } // Parse key/value pairs from escaped value foreach (string parameter in escapedValue.ToString().Split(parameterDelimiter)) { // Parse out parameter's key/value elements elements = parameter.Split(keyValueDelimiter); if (elements.Length == 2) { // Get key expression key = elements[0].Trim(); // Get unescaped value expression unescapedValue = elements[1].Trim(). Replace(escapedParameterDelimiter, parameterDelimiter.ToString()). Replace(escapedKeyValueDelimiter, keyValueDelimiter.ToString()). Replace(escapedStartValueDelimiter, startValueDelimiter.ToString()). Replace(escapedEndValueDelimiter, endValueDelimiter.ToString()). Replace(backslashDelimiter, "\\"); // Add key/value pair to dictionary if (ignoreDuplicateKeys) { // Add or replace key elements with unescaped value keyValuePairs[key] = unescapedValue; } else { // Add key elements with unescaped value throwing an exception for encountered duplicate keys if (keyValuePairs.ContainsKey(key)) throw new ArgumentException(string.Format("Failed to parse key/value pairs: duplicate key encountered. Key \"{0}\" is not unique within the string: \"{1}\"", key, value)); keyValuePairs.Add(key, unescapedValue); } } } return keyValuePairs; } /// <summary> /// Ensures parameter is not an empty or null string. Returns a single space if test value is empty. /// </summary> /// <param name="testValue">Value to test for null or empty.</param> /// <returns>A non-empty string.</returns> public static string NotEmpty(this string testValue) { return testValue.NotEmpty(" "); } /// <summary> /// Ensures parameter is not an empty or null string. /// </summary> /// <param name="testValue">Value to test for null or empty.</param> /// <param name="nonEmptyReturnValue">Value to return if <paramref name="testValue">testValue</paramref> is null or empty.</param> /// <returns>A non-empty string.</returns> public static string NotEmpty(this string testValue, string nonEmptyReturnValue) { if (string.IsNullOrEmpty(nonEmptyReturnValue)) throw new ArgumentException("nonEmptyReturnValue cannot be null or empty"); if (string.IsNullOrEmpty(testValue)) return nonEmptyReturnValue; return testValue; } /// <summary> /// Replaces all characters passing delegate test with specified replacement character. /// </summary> /// <param name="value">Input string.</param> /// <param name="replacementCharacter">Character used to replace characters passing delegate test.</param> /// <param name="characterTestFunction">Delegate used to determine whether or not character should be replaced.</param>