Assets/TextExtensions.cs
/// <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>
Parses key/value pair expressions from a string. Parameter pairs are delimited by
and multiple pairs separated by . Supports encapsulated nested expressions.
Parses key / value pair expressions from a string . Parameter pairs are delimited by and multiple pairs separated by . Supports encapsulated nested expressions .
public static Dictionary<string, string> ParseKeyValuePairs(this string value, char parameterDelimiter = ';', char keyValueDelimiter = '=', char startValueDelimiter = '{', char endValueDelimiter = '}', bool ignoreDuplicateKeys = true)
        {
            if (value is null)
                throw new ArgumentNullException(nameof(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();
            bool valueEscaped = false;
            int delimiterDepth = 0;
            foreach (char character in value)
            {
                if (character == startValueDelimiter)
                {
                    if (!valueEscaped)
                    {
                        valueEscaped = true;
                        continue;   
                    }
                    delimiterDepth++;
                }
                if (character == endValueDelimiter)
                {
                    if (valueEscaped)
                    {
                        if (delimiterDepth > 0)
                        {
                            delimiterDepth--;
                        }
                        else
                        {
                            valueEscaped = false;
                            continue;   
                        }
                    }
                    else
                    {
                        throw new FormatException($"Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter \'{endValueDelimiter}\' before start value delimiter \'{startValueDelimiter}\'.");
                    }
                }
                if (valueEscaped)
                {
                    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 (valueEscaped)
                    delimiterDepth = 1;
                throw new FormatException($"Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more {(delimiterDepth > 0 ? $"start value delimiters '{startValueDelimiter}'" : $"end value delimiters '{endValueDelimiter}'")} than {(delimiterDepth < 0 ? $"start value delimiters '{startValueDelimiter}'" : $"end value delimiters '{endValueDelimiter}'")}.");
            }
            foreach (string parameter in escapedValue.ToString().Split(parameterDelimiter))
            {
                string[] elements = parameter.Split(keyValueDelimiter);
                if (elements.Length != 2)
                    continue;
                string key = elements[0].Trim();
                string unescapedValue = elements[1].Trim().
                    Replace(escapedParameterDelimiter, parameterDelimiter.ToString()).
                    Replace(escapedKeyValueDelimiter, keyValueDelimiter.ToString()).
                    Replace(escapedStartValueDelimiter, startValueDelimiter.ToString()).
                    Replace(escapedEndValueDelimiter, endValueDelimiter.ToString()).
                    Replace(backslashDelimiter, "\\");
                if (ignoreDuplicateKeys)
                {
                    keyValuePairs[key] = unescapedValue;
                }
                else
                {
                    if (keyValuePairs.ContainsKey(key))
                        throw new ArgumentException($"Failed to parse key/value pairs: duplicate key encountered. Key \"{key}\" is not unique within the string: \"{value}\"");
                    keyValuePairs.Add(key, unescapedValue);
                }
            }
            return keyValuePairs;
        }
public static Dictionary < string , string > ParseKeyValuePairs ( this string value , char parameterDelimiter = ' ' , char keyValueDelimiter = ' ' , char startValueDelimiter = ' ' , char endValueDelimiter = ' ' , bool ignoreDuplicateKeys = true ) { if ( value is null ) throw new ArgumentNullException ( nameof ( value ) ) ; if ( parameterDelimiter == keyValueDelimiter || parameterDelimiter == startValueDelimiter || parameterDelimiter == endValueDelimiter || keyValueDelimiter == startValueDelimiter || keyValueDelimiter == endValueDelimiter || startValueDelimiter == endValueDelimiter ) throw new ArgumentException ( " " ) ; 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 ( ) ; bool valueEscaped = false ; int delimiterDepth = 0 ; foreach ( char character in value ) { if ( character == startValueDelimiter ) { if ( ! valueEscaped ) { valueEscaped = true ; continue ; } delimiterDepth ++ ; } if ( character == endValueDelimiter ) { if ( valueEscaped ) { if ( delimiterDepth > 0 ) { delimiterDepth -- ; } else { valueEscaped = false ; continue ; } } else { throw new FormatException ( $" Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter  \' { endValueDelimiter } \'  before start value delimiter  \' { startValueDelimiter } \' . " ) ; } } if ( valueEscaped ) { 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 ( valueEscaped ) delimiterDepth = 1 ; throw new FormatException ( $" Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more  { ( delimiterDepth > 0 ? $" start value delimiters ' { startValueDelimiter } ' " : $" end value delimiters ' { endValueDelimiter } ' " ) }  than  { ( delimiterDepth < 0 ? $" start value delimiters ' { startValueDelimiter } ' " : $" end value delimiters ' { endValueDelimiter } ' " ) } . " ) ; } foreach ( string parameter in escapedValue . ToString ( ) . Split ( parameterDelimiter ) ) { string [ ] elements = parameter . Split ( keyValueDelimiter ) ; if ( elements . Length != 2 ) continue ; string key = elements [ 0 ] . Trim ( ) ; string unescapedValue = elements [ 1 ] . Trim ( ) . Replace ( escapedParameterDelimiter , parameterDelimiter . ToString ( ) ) . Replace ( escapedKeyValueDelimiter , keyValueDelimiter . ToString ( ) ) . Replace ( escapedStartValueDelimiter , startValueDelimiter . ToString ( ) ) . Replace ( escapedEndValueDelimiter , endValueDelimiter . ToString ( ) ) . Replace ( backslashDelimiter , " \\ " ) ; if ( ignoreDuplicateKeys ) { keyValuePairs [ key ] = unescapedValue ; } else { if ( keyValuePairs . ContainsKey ( key ) ) throw new ArgumentException ( $" Failed to parse key/value pairs: duplicate key encountered. Key  \" { key } \"  is not unique within the string:  \" { value } \" " ) ; keyValuePairs . Add ( key , unescapedValue ) ; } } return keyValuePairs ; }
Parses key/value pair expressions from a string.
Parses key / value pair expressions from a string .
// 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") // Don't add tag start delimiter to final value // Handle nested delimiters // Handle nested delimiters // Don't add tag stop delimiter to final value // Escape any delimiter characters inside nested key/value pair // If value is still escaped, tagged expression was not terminated // Parse key/value pairs from escaped value // Parse out parameter's key/value elements // Get key expression // Get unescaped value expression // Add key/value pair to dictionary // Add or replace key elements with unescaped value // Add key elements with unescaped value throwing an exception for encountered duplicate keys
Dictionary of key/value pairs.
Dictionary of key / value pairs .
value is null.
value is null .
All delimiters must be unique -or- all keys must be unique when
is set to false
All delimiters must be unique - or - all keys must be unique when is set to false
Total nested key/value pair expressions are mismatched -or- encountered
before .
Total nested key / value pair expressions are mismatched - or - encountered before .
string
this
parameterDelimiter
char
Character that delimits one key/value pair from another.
Character that delimits one key / value pair from another .
keyValueDelimiter
char
Character that delimits key from value.
Character that delimits key from value .
startValueDelimiter
char
Optional character that marks the start of a value such that value could contain other
or  characters.
Optional character that marks the start of a value such that value could contain other or characters .
endValueDelimiter
char
Optional character that marks the end of a value such that value could contain other
or  characters.
Optional character that marks the end of a value such that value could contain other or characters .
ignoreDuplicateKeys
bool
Flag determines whether duplicates are ignored. If flag is set to false will be thrown when all key parameters are not unique.
Flag determines whether duplicates are ignored . If flag is set to false will be thrown when all key parameters are not unique .
value
String containing key/value pair expressions to parse.
String containing key / value pair expressions to parse .
remarks
Parses a string containing key/value pair expressions .
This method treats all "keys" as case-insensitive. Nesting of key/value pair expressions is allowed by encapsulating the
value using the  and  values . There must be one
for each encountered  in the value or a
will be thrown. Multiple levels of nesting is supported. If the
flag is set to false 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.
Parses a string containing key / value pair expressions . This method treats all " keys " as case - insensitive . Nesting of key / value pair expressions is allowed by encapsulating the value using the and values . There must be one for each encountered in the value or a will be thrown . Multiple levels of nesting is supported . If the flag is set to false 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 .