629 lines
22 KiB
C#
629 lines
22 KiB
C#
using Sarand.Net.Standard.Base.App;
|
|
using Sarand.Net.Standard.Base.Bus;
|
|
using Sarand.Net.Standard.Base.Core;
|
|
using System;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Sarand.Net.Standard.Base.Utils
|
|
{
|
|
//=======================================================================
|
|
public static class StringUtil
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Definitions.
|
|
public static Regex RegEx_Whitespace = new Regex("\\s+");
|
|
public static Regex RegEx_Whitespace_Match = new Regex("^\\s+$");
|
|
public static Regex RegEx_LineDelimiters = new Regex(@"\r\n?|\n");
|
|
|
|
//-------------------------------------------------------------------
|
|
// Properties.
|
|
//-------------------------------------------------------------------
|
|
public static int GetLength(String value)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return 0;
|
|
return value.Length;
|
|
}
|
|
|
|
/// <summary>Test for null or empty value.</summary>
|
|
public static bool IsEmpty(this string value)
|
|
{
|
|
return String.IsNullOrEmpty(value);
|
|
}
|
|
|
|
/// <summary>Test for null, empty or a value that consists of whitespace characters only.</summary>
|
|
public static bool IsBlank(this string value)
|
|
{
|
|
return String.IsNullOrEmpty(value) || Match(value, RegEx_Whitespace_Match);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Comparison.
|
|
//-------------------------------------------------------------------
|
|
public static bool EqualsCI(this String value1, String value2)
|
|
{
|
|
return String.Equals(value1, value2, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
public static bool StartsWithCI(this String value1, String value2)
|
|
{
|
|
if (IsEmpty(value1) || IsEmpty(value2)) return false;
|
|
return (value1.StartsWith(value2, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
public static bool EndsWithCI(this String value1, String value2)
|
|
{
|
|
if (IsEmpty(value1) || IsEmpty(value2)) return false;
|
|
return (value1.EndsWith(value2, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
public static bool ContainsCI(this String value1, String value2)
|
|
{
|
|
if (IsEmpty(value1) || IsEmpty(value2)) return false;
|
|
return (value1.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Regular expressions.
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Create Regex from (quoted) expression and apply Regex.Match to String value and return Match.Success.
|
|
/// <para>- Case sensitive.</para>
|
|
/// </summary>
|
|
public static bool Match(this String value, String expression)
|
|
{
|
|
return value.Match(expression, RegexOptions.None);
|
|
}
|
|
/// <summary>
|
|
/// Create Regex from (quoted) expression and apply Regex.Match to String value and return Match.Success.
|
|
/// <para>- Case insensitive.</para>
|
|
/// </summary>
|
|
public static bool MatchCI(this String value, String expression)
|
|
{
|
|
return value.Match(expression, RegexOptions.IgnoreCase);
|
|
}
|
|
/// <summary>
|
|
/// Create Regex from (quoted) expression and apply Regex.Match to String value and return Match.Success.
|
|
/// <para>- Supply options as required.</para>
|
|
/// </summary>
|
|
public static bool Match(this String value, String expression, RegexOptions options)
|
|
{
|
|
return new Regex(Regex.Escape(expression), options).Match(value).Success;
|
|
}
|
|
/// <summary>
|
|
/// Apply Regex.Match to String value and return Match.Success.
|
|
/// </summary>
|
|
public static bool Match(this String value, Regex re)
|
|
{
|
|
return re.Match(value).Success;
|
|
}
|
|
/// <summary>
|
|
/// Apply Regex.Matches to String value and return true if any matches found.
|
|
/// </summary>
|
|
public static bool Matches(this String value, Regex re)
|
|
{
|
|
return re.Matches(value).Count < 1;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Alternative Content.
|
|
//-------------------------------------------------------------------
|
|
public static String EmptyOnNull(String value)
|
|
{
|
|
return value ?? String.Empty;
|
|
}
|
|
/// <summary>Return the first value in the list that is not empty. Default if all are empty is empty string.</summary>
|
|
public static String OnEmpty(params String[] values)
|
|
{
|
|
foreach (String value in values)
|
|
if (!IsEmpty(value)) return value;
|
|
return "";
|
|
}
|
|
/// <summary>Return the first value in the list that is not blank. Default if all are blank is empty string.</summary>
|
|
public static String OnBlank(params String[] values)
|
|
{
|
|
foreach (String value in values)
|
|
if (!IsBlank(value)) return value;
|
|
return "";
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// C o n t e n t
|
|
//-------------------------------------------------------------------
|
|
public static bool IsAlpha(this string value)
|
|
{
|
|
return EmptyOnNull(value).All(Char.IsLetter);
|
|
}
|
|
public static bool IsAlphaNumeric(this string value)
|
|
{
|
|
return EmptyOnNull(value).All(Char.IsLetterOrDigit);
|
|
}
|
|
public static bool IsNumeric(this string value, bool allowNegative = false)
|
|
{
|
|
Int64 number = 0;
|
|
if (!Int64.TryParse(value, out number)) return false;
|
|
return (allowNegative || number >= 0);
|
|
}
|
|
public static bool IsDecimal(this string value, bool allowNegative = false)
|
|
{
|
|
Decimal number = 0;
|
|
if (!Decimal.TryParse(value, out number)) return false;
|
|
return (allowNegative || number >= 0);
|
|
}
|
|
//-------------------------------------------------------------------
|
|
/// <summary>Test for Hex character.</summary>
|
|
public static bool IsHex(this char c)
|
|
{
|
|
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
}
|
|
/// <summary>Test for Hex string.</summary>
|
|
public static bool IsHex(this String value)
|
|
{
|
|
if (IsEmpty(value)) return false;
|
|
foreach (char c in value)
|
|
if (!IsHex(c)) return false;
|
|
return true;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// P a d d i n g
|
|
//-------------------------------------------------------------------
|
|
public static String PadLeft(this String value, int length, char pad = ' ')
|
|
{
|
|
value = StringUtil.EmptyOnNull(value);
|
|
while (value.Length < length)
|
|
value = pad + value;
|
|
return value;
|
|
}
|
|
public static String PadRight(this String value, int length, char pad = ' ')
|
|
{
|
|
value = StringUtil.EmptyOnNull(value);
|
|
while (value.Length < length)
|
|
value = value + pad;
|
|
return value;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// C o n v e r t
|
|
//-------------------------------------------------------------------
|
|
public static String CapitaliseFirst(this String value, TextInfo textInfo = null)
|
|
{
|
|
textInfo = (textInfo ?? CultureInfo.CurrentCulture.TextInfo);
|
|
if (StringUtil.IsEmpty(value)) return value;
|
|
return textInfo.ToUpper(value.First()) + value.Substring(1);
|
|
}
|
|
public static string ToTitleCase(this string value, TextInfo textInfo = null)
|
|
{
|
|
textInfo = (textInfo ?? CultureInfo.CurrentCulture.TextInfo);
|
|
String[] tokens = value.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
|
for (int i = 0; i < tokens.Length; i++)
|
|
{
|
|
String token = tokens[i];
|
|
tokens[i] = textInfo.ToUpper(token.First()) + token.Substring(1);
|
|
}
|
|
|
|
return string.Join(" ", tokens);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// C l e a n s e
|
|
//-------------------------------------------------------------------
|
|
/// <summary>Trim leading, trailling and multiple internal spaces.</summary>
|
|
public static String TrimAll(this String value)
|
|
{
|
|
if (IsEmpty(value)) return value;
|
|
value = value.Trim().TrimInternalSpaces();
|
|
return value;
|
|
}
|
|
/// <summary>
|
|
/// Trim any combination of the specified characters from the start,
|
|
/// end and where more than one found together internally,
|
|
/// replacing with the specified string.
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <param name="trimCharacters"></param>
|
|
/// <returns></returns>
|
|
public static String TrimAll(this String value, char[] trimChars, String replacement = " ")
|
|
{
|
|
if (IsEmpty(value) || trimChars.Length < 1) return value;
|
|
// Trim leading and trailing.
|
|
value = value.Trim(trimChars);
|
|
// Use regex to replace multiple internal.
|
|
Regex regexMultipleInternal = new Regex("[" + Regex.Escape(new String(trimChars)) + "]{2,}", RegexOptions.None);
|
|
return regexMultipleInternal.Replace(value, replacement);
|
|
}
|
|
private static Regex regexMultipleSpaces = new Regex("[ ]{2,}", RegexOptions.Compiled);
|
|
public static String TrimInternalSpaces(this String value)
|
|
{
|
|
return regexMultipleSpaces.Replace(value, " ");
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// R e p l a c e
|
|
//-------------------------------------------------------------------
|
|
/// <summary>Case-insensitive String replacement, invariant culture.</summary>
|
|
public static String ReplaceCI(this String value, String search, String replace)
|
|
{
|
|
return value.ReplaceCI(search, replace, CultureInfo.InvariantCulture);
|
|
}
|
|
/// <summary>Case-insensitive String replacement.</summary>
|
|
public static String ReplaceCI(this String value, String search, String replace, CultureInfo cultureInfo)
|
|
{
|
|
return value.Replace(search, replace, true, cultureInfo);
|
|
}
|
|
/// <summary>Replace any of the characters in the search string with the specified replacement character.</summary>
|
|
public static string ReplaceAny(this string value, String searchChars, char replaceChar)
|
|
{
|
|
for (int i = 0; i < searchChars.Length; i++)
|
|
value = value.Replace(searchChars[i], replaceChar);
|
|
return value;
|
|
}
|
|
/// <summary>
|
|
/// Replace all occurences of any of the search strings with the replace string, even where search values remain after the each replacement.
|
|
/// <para>NB Use String.Replace if the replacement contains the search string</para>
|
|
/// </summary>
|
|
public static string ReplaceAll(this string value, String search, String replace)
|
|
{
|
|
return value.ReplaceAll(new String[] { search }, replace);
|
|
}
|
|
public static string ReplaceAll(this string value, String[] searches, String replace)
|
|
{
|
|
foreach (String search in searches)
|
|
{
|
|
while (value.Contains(search))
|
|
{
|
|
value = value.Replace(search, replace);
|
|
if (value.Contains(search) && replace.Contains(search))
|
|
throw new NotSupportedException("StringUtil.ReplaceAll Replace value cannot contain search value");
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
/// <summary>Duplicate every instance of the specified character.</summary>
|
|
public static String Duplicate(this String value, char search)
|
|
{
|
|
return value.Replace(search.ToString(), search.ToString() + search.ToString());
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Replace all line delimiters (\r\n or \n).
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <param name="replace"></param>
|
|
/// <returns></returns>
|
|
public static String ConsolidateLineDelimiters(this String value, String replace = "\n")
|
|
{
|
|
return RegEx_LineDelimiters.Replace(value, replace);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// E x t r a c t i o n
|
|
//-------------------------------------------------------------------
|
|
public static string Left(this string value, int length)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return value;
|
|
if (length < 0) return String.Empty;
|
|
return (length >= value.Length ? value : value.Substring(0, length));
|
|
}
|
|
public static string Right(this string value, int length)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return value;
|
|
if (length < 0) return String.Empty;
|
|
return
|
|
(
|
|
length >= value.Length
|
|
? value
|
|
: value.Substring(value.Length - length)
|
|
);
|
|
}
|
|
/// <summary>
|
|
/// <para>Safe SubString - avoinding out of bounds exceptions etc.</para>
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <param name="start"></param>
|
|
/// <param name="length"></param>
|
|
/// <returns></returns>
|
|
public static string SubstringSafe(this string value, int start, int length = -1)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return String.Empty;
|
|
start = Math.Max(0, start);
|
|
if (start >= value.Length) return String.Empty;
|
|
value = value.Substring(start);
|
|
if (length < 0 || length > value.Length) return value;
|
|
return value.Substring(0, length);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
/// <summary>Return the common previx for the values in the list.</summary>
|
|
public static String GetPrefix(params String[] values)
|
|
{
|
|
String prefix = values.Any() ? values[0] : "";
|
|
foreach (String value in values)
|
|
{
|
|
// Determine how much o fthe prefix matches this value.
|
|
for (int i = 0; i < prefix.Length; i++)
|
|
{
|
|
if (value.Length <= i || value[i] != prefix[i])
|
|
prefix = prefix.Substring(0, i);
|
|
}
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
/// <summary>
|
|
/// Concatenate the partial path values, stripping leading and trailing slashes, and converting slashes (one or more in sequence) to a single path delimiter.
|
|
/// </summary>
|
|
public static String CombineFilePath(params String[] values)
|
|
{
|
|
return CombineFilePath(Path.DirectorySeparatorChar, values);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenate the partial path values with forward slashes ('/'), stripping leading and trailing slashes and converting slashes (one or more in sequence) to a single forward slash.
|
|
/// </summary>
|
|
public static String CombineForwardSlashDelimited(params String[] values)
|
|
{
|
|
return CombineFilePath('/', values);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenate the partial path values with the specified dlimiter, stripping leading and trailing slashes and delimiters, and converting slashes and delimiters to the required delimiter.
|
|
/// </summary>
|
|
private static String CombineFilePath(char delim, params String[] values)
|
|
{
|
|
String path = "";
|
|
String delimPattern = "[\\\\\\/" + (delim != '\\' && delim != '/' ? "\\" + delim : "") + "]";
|
|
foreach (String value in values)
|
|
{
|
|
if (IsBlank(path))
|
|
// Path not yet started - being with this value.
|
|
path = value;
|
|
else
|
|
// Path is already started.
|
|
// Remove trailing delimiters from path and leading delimiters from next partial value before concatenating with path separator.
|
|
path = Regex.Replace(path, delimPattern + "+$", "") + delim.ToString() + Regex.Replace(value, "^" + delimPattern + "+", "");
|
|
}
|
|
// Convert any combination of the alternative delimiters with requested delimiter, excluding start of path, eg. '\\share\...'.
|
|
return Regex.Replace(path, delimPattern, delim.ToString());
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// C o n v e r s i o n
|
|
//-------------------------------------------------------------------
|
|
// Integer values.
|
|
public static short ToShort(this string value, short defaultValue = 0)
|
|
{
|
|
return (short)ToLong(value, defaultValue);
|
|
}
|
|
public static int ToInt(this string value, int defaultValue = 0)
|
|
{
|
|
return (int)ToLong(value, defaultValue);
|
|
}
|
|
public static long ToLong(this string value, long defaultValue = 0)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return defaultValue;
|
|
try
|
|
{
|
|
Double returnValue = defaultValue;
|
|
if (Double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue))
|
|
return (Int64)returnValue;
|
|
}
|
|
catch (Exception) { }
|
|
return defaultValue;
|
|
}
|
|
public static short ToShort(Object value, short defaultValue = 0)
|
|
{
|
|
return (short)ToLong(value, defaultValue);
|
|
}
|
|
public static int ToInt(Object value, int defaultValue = 0)
|
|
{
|
|
return (int)ToLong(value, defaultValue);
|
|
}
|
|
public static long ToLong(Object value, long defaultValue = 0)
|
|
{
|
|
if (value == null) return defaultValue;
|
|
try
|
|
{
|
|
return ToLong(value.ToString());
|
|
}
|
|
catch (Exception) { }
|
|
return defaultValue;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// SysId.
|
|
public static SysId ToSysId(this String value)
|
|
{
|
|
return new SysId(StringUtil.ToLong(value));
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Hex values to integer.
|
|
/// <summary>Convert hex character to integer value.</summary>
|
|
public static byte ToHex(this char value)
|
|
{
|
|
if (value >= '0' && value <= '9') return (byte)(value - '0');
|
|
if (value >= 'A' && value <= 'F') return (byte)(value - 'A' + 10);
|
|
if (value >= 'a' && value <= 'f') return (byte)(value - 'a' + 10);
|
|
return 0;
|
|
}
|
|
/// <summary>Convert pair of hex character to integer value.</summary>
|
|
public static byte ToHex(char cHex1, char cHex2)
|
|
{
|
|
return (byte)((ToHex(cHex1) * 16) + ToHex(cHex2));
|
|
}
|
|
/// <summary>Convert hex strings to integer value.</summary>
|
|
public static ulong ToHex(this String value)
|
|
{
|
|
if (IsEmpty(value)) return 0;
|
|
ulong intValue = 0;
|
|
foreach (char c in value)
|
|
{
|
|
// Shift previous value half a byte (nibble) - equivalent to single hex character.
|
|
intValue <<= 4;
|
|
// Get value of next hex character.
|
|
byte charValue = ToHex(c);
|
|
intValue += charValue;
|
|
}
|
|
return intValue;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Floating point values.
|
|
public static float ToSingle(this string value, float defaultValue = 0)
|
|
{
|
|
return Convert.ToSingle(ToDouble(value, defaultValue));
|
|
}
|
|
public static double ToDouble(this string value, double defaultValue = 0)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return defaultValue;
|
|
try
|
|
{
|
|
Double returnValue = defaultValue;
|
|
if (Double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue))
|
|
return returnValue;
|
|
}
|
|
catch (Exception) { }
|
|
return defaultValue;
|
|
}
|
|
public static float ToSingle(Object value, float defaultValue = 0)
|
|
{
|
|
return Convert.ToSingle(ToDouble(value, defaultValue));
|
|
}
|
|
public static double ToDouble(Object value, double defaultValue = 0)
|
|
{
|
|
if (value == null) return defaultValue;
|
|
try
|
|
{
|
|
return ToDouble(value.ToString());
|
|
}
|
|
catch (Exception) { }
|
|
return defaultValue;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Decimal values.
|
|
public static decimal ToDecimal(this string value, decimal defaultValue = 0)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return defaultValue;
|
|
try
|
|
{
|
|
Decimal returnValue = defaultValue;
|
|
if (Decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out returnValue))
|
|
return returnValue;
|
|
}
|
|
catch (Exception) { }
|
|
return defaultValue;
|
|
}
|
|
public static decimal ToDecimal(Object value, decimal defaultValue = 0)
|
|
{
|
|
if (value == null) return defaultValue;
|
|
try
|
|
{
|
|
return ToDecimal(value.ToString());
|
|
}
|
|
catch (Exception) { }
|
|
return defaultValue;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Boolean values.
|
|
public static bool ToBool(this string value)
|
|
{
|
|
return value.IsTrue();
|
|
}
|
|
public static bool IsTrue(this string value)
|
|
{
|
|
if (String.IsNullOrEmpty(value)) return false;
|
|
if (value.ToLong() != 0) return true;
|
|
if (value.ToDouble() != 0.0) return true;
|
|
if (value.Left(1).EqualsCI("Y")) return true;
|
|
if (value.EqualsCI("True")) return true;
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// F o r m a t t i n g
|
|
//-------------------------------------------------------------------
|
|
public static String FormatNumeric(double numericValue, ushort decimalPlaces = 0, ushort sigFigs = 0, char thousandsDelim = '\0', char decimalDelim = '.')
|
|
{
|
|
String value = String.Empty;
|
|
try
|
|
{
|
|
String format = "F" + decimalPlaces.ToString();
|
|
if (thousandsDelim != '\0')
|
|
format = "N" + decimalPlaces.ToString();
|
|
value = numericValue.ToString(format);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
SaLogger.Debug("StringUtil.FormatNumeric(double) " + exc.GetType().Name + ": " + exc.Message);
|
|
}
|
|
return value;
|
|
}
|
|
public static String FormatNumeric(decimal numericValue, ushort decimalPlaces = 0, ushort sigFigs = 0, char thousandsDelim = '\0', char decimalDelim = '.')
|
|
{
|
|
String value = String.Empty;
|
|
try
|
|
{
|
|
String format = "F" + decimalPlaces.ToString();
|
|
if (thousandsDelim != '\0')
|
|
format = "N" + decimalPlaces.ToString();
|
|
value = numericValue.ToString(format);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
SaLogger.Debug("StringUtil.FormatNumeric(decimal) " + exc.GetType().Name + ": " + exc.Message);
|
|
}
|
|
return value;
|
|
}
|
|
//-------------------------------------------------------------------
|
|
// Specialist finding.
|
|
public static int FindMatchingBrace(this String value, int braceOpenPos)
|
|
{
|
|
if (String.IsNullOrEmpty(value) || braceOpenPos < 0 || braceOpenPos >= value.Length) return -1;
|
|
char sourceBrace = value[braceOpenPos];
|
|
char targetBrace = '\0'; // Target character to be found.
|
|
bool openBraceFound = false; // Identifies forward or backwards search (close or open bracket to be found).
|
|
|
|
// Identify the target bracket character:
|
|
switch (sourceBrace)
|
|
{
|
|
case '(': targetBrace = ')'; openBraceFound = true; break;
|
|
case ')': targetBrace = '('; break;
|
|
case '[': targetBrace = ']'; openBraceFound = true; break;
|
|
case ']': targetBrace = '['; break;
|
|
case '{': targetBrace = '}'; openBraceFound = true; break;
|
|
case '}': targetBrace = '{'; break;
|
|
case '<': targetBrace = '>'; openBraceFound = true; break;
|
|
case '>': targetBrace = '<'; break;
|
|
}
|
|
// Verify that a valid bracket has been found:
|
|
if (targetBrace == 0) return -1;
|
|
// Start search from the 'source' bracket position:
|
|
int braceCount = 1; // Search continues until this count gets back down to '0'.
|
|
int searchPos = braceOpenPos;
|
|
while (true)
|
|
{
|
|
// Navigate to next character to be checked (direction depends on whether target is open or close bracket):
|
|
if (openBraceFound) searchPos++; else searchPos--;
|
|
if (searchPos < 0 || searchPos >= value.Length) return -1;
|
|
// If character at this position is the 'source' bracket, increment count (another level of embedded brackets):
|
|
char searchChar = value[searchPos];
|
|
if (searchChar == sourceBrace) braceCount++;
|
|
else if (searchChar == targetBrace)
|
|
{
|
|
braceCount--;
|
|
if (braceCount == 0) return searchPos; // Matching bracket found.
|
|
else if (braceCount < 0) return -1; // Invalid combination of brackets.
|
|
}
|
|
}
|
|
//return -1; Unreachable code!
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|