1555 lines · 70.2 KB · C#
RawDownload
//******************************************************************************************************
//  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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t