///
/// Split a string while preserving sections of it.
/// Similar to string.Split, but you can define start-end characters (e.g. quotes, brackets, braces) inside of which it will NOT split.
/// Preservers can also be "recursive" which means it can determine if it's in nested brackets or parens, etc.
/// If the start & end characters are different, there's a good chance you want to set recursive to true.
/// See the associated unit test for an example that can parse json....
/// Also supports escape characters so the separator and start-end characters can be ignored.
///
public class Splitter
{
public static string[] Split(string splitThis, char separator, char separatorEscape, params Preserver[] preservers)
{
if (string.IsNullOrEmpty(splitThis))
{
return null;
}
foreach (var preserver in preservers)
{
if (preserver.Start == separator)
{
throw new Exception("The separator character `" + separator + "` cannot be the same as the start character of a preserver.");
}
preserver.IsInside = false;
if (preserver.Recursive && preserver.Start == preserver.End)
{
throw new Exception("Error. Preserver cannot be recursive if start and end are the same character: " + preserver.Start);
}
}
Queue splitPoints = new Queue();
int position = 0;
while (position < splitThis.Length)
{
Console.WriteLine(splitThis.Substring(0, position + 1));
var currChar = splitThis[position];
var prevChar = position == 0 ? '\0' : splitThis[position - 1];
var insidePreserver = preservers.FirstOrDefault(p => p.IsInside);
if (insidePreserver != null)
{
ShouldWeExitAPreserver(currChar, prevChar, insidePreserver);
}
else
{
if (!preservers.Any(p => ShouldWeEnterAPreserver(currChar, prevChar, p)))
{
if (currChar == separator && prevChar != separatorEscape)
{
splitPoints.Enqueue(position);
Console.WriteLine("Found a split.");
}
}
}
position++;
}
if (splitPoints.Count == 0)
{
return new[] {splitThis};
}
var result = new string[splitPoints.Count + 1];
int resultIndex = 0;
var startAt = 0;
while (splitPoints.Count > 0)
{
var splitPoint = splitPoints.Dequeue();
if (splitPoint - startAt > 0)
{
result[resultIndex] = splitThis.Substring(startAt, splitPoint - startAt);
}
else
{
result[resultIndex] = string.Empty;
}
startAt = splitPoint + 1;
resultIndex++;
if (splitPoints.Count == 0)
{
if (startAt < splitThis.Length)
{
result[resultIndex] = splitThis.Substring(startAt);
}
else
{
result[resultIndex] = string.Empty;
}
}
}
return result;
}
private static bool ShouldWeEnterAPreserver(char currChar, char prevChar, Preserver preserver)
{
if (preserver.IsInside)
{
if (preserver.SubPreserver != null)
{
ShouldWeEnterAPreserver(currChar, prevChar, preserver.SubPreserver);
}
return false;
}
if (preserver.Start == currChar && preserver.Escape != prevChar)
{
preserver.IsInside = true;
Console.WriteLine("Entering with char " + currChar);
return true;
}
return false;
}
private static bool ShouldWeExitAPreserver(char currChar, char prevChar, Preserver preserver)
{
if (!preserver.IsInside) return false;
if (preserver.SubPreserver != null)
{
if (preserver.SubPreserver.IsInside)
{
if (ShouldWeExitAPreserver(currChar, prevChar, preserver.SubPreserver))
{
return false;
}
}
else
{
if (ShouldWeEnterAPreserver(currChar, prevChar, preserver.SubPreserver))
{
return false;
}
}
}
if (preserver.Recursive)
{
var recursivePreserver = new Preserver { IsInside = false, Recursive = false, Start = preserver.Start, End = preserver.End, Escape = preserver.Escape };
if (ShouldWeEnterAPreserver(currChar, prevChar, recursivePreserver))
{
preserver.RecursiveCount++;
Console.WriteLine("Entering Recursive, count=" + preserver.RecursiveCount);
return false;
}
}
if (preserver.End == currChar && preserver.Escape != prevChar)
{
if (preserver.RecursiveCount > 0)
{
preserver.RecursiveCount--;
Console.WriteLine("Exiting recursive preserver, count=" + preserver.RecursiveCount);
}
else
{
preserver.IsInside = false;
Console.WriteLine("Exiting with char " + currChar);
}
return true;
}
return false;
}
public class Preserver
{
public char Start { get; set; }
public char End { get; set; }
public char Escape { get; set; } = '\u0002';
public bool IsInside { get; set; }
public bool Recursive { get; set; }
public int RecursiveCount { get; set; } = 0;
public Preserver SubPreserver { get; set; }
}
}