using System;
namespace TwitchDownloaderCore.Extensions
{
public static class ReadOnlySpanExtensions
{
/// Replaces all occurrences of not prepended by a backslash or contained within quotation marks with .
public static bool TryReplaceNonEscaped(this ReadOnlySpan str, Span destination, char oldChar, char newChar)
{
const string ESCAPE_CHARS = @"\'""";
if (oldChar is '\\' or '\'' or '\"')
return false;
if (destination.Length < str.Length)
return false;
str.CopyTo(destination);
var firstIndex = destination.IndexOf(oldChar);
if (firstIndex == -1)
return true;
var firstEscapeIndex = destination.IndexOfAny(ESCAPE_CHARS);
if (firstEscapeIndex != -1 && firstEscapeIndex < firstIndex)
firstIndex = firstEscapeIndex;
var lastIndex = destination.LastIndexOf(oldChar);
var lastEscapeIndex = destination.LastIndexOfAny(ESCAPE_CHARS);
if (lastEscapeIndex != -1 && lastEscapeIndex > lastIndex)
lastIndex = lastEscapeIndex;
lastIndex++;
for (var i = firstIndex; i < lastIndex; i++)
{
var readChar = destination[i];
switch (readChar)
{
case '\\':
i++;
break;
case '\'':
case '\"':
{
i = FindCloseQuoteChar(destination, i, lastIndex, readChar);
if (i == -1)
{
destination.Clear();
return false;
}
break;
}
default:
{
if (readChar == oldChar)
{
destination[i] = newChar;
}
break;
}
}
}
return true;
}
private static int FindCloseQuoteChar(ReadOnlySpan destination, int openQuoteIndex, int endIndex, char openQuoteChar)
{
var i = openQuoteIndex + 1;
while (i < endIndex)
{
var readChar = destination[i];
i++;
if (readChar == '\\')
{
i++;
continue;
}
if (readChar == openQuoteChar)
{
i--;
return i;
}
}
return -1;
}
public static int UnEscapedIndexOf(this ReadOnlySpan str, char character)
{
if (character is '\\' or '\'' or '\"')
throw new ArgumentOutOfRangeException(nameof(character), character, "Escape characters are not supported.");
var firstIndex = str.IndexOf(character);
if (firstIndex == -1)
return firstIndex;
var firstEscapeIndex = str.IndexOfAny(@"\'""");
if (firstEscapeIndex == -1 || firstEscapeIndex > firstIndex)
return firstIndex;
var length = str.Length;
for (var i = firstEscapeIndex; i < length; i++)
{
var readChar = str[i];
switch (readChar)
{
case '\\':
i++;
break;
case '\'':
case '\"':
{
var closeQuoteMark = FindCloseQuoteChar(str, i, length, readChar);
if (closeQuoteMark == -1)
throw new FormatException($"Unbalanced quote mark at {i}.");
i = closeQuoteMark;
break;
}
default:
{
if (readChar == character)
{
return i;
}
break;
}
}
}
return -1;
}
public static int UnEscapedIndexOfAny(this ReadOnlySpan str, ReadOnlySpan characters)
{
const string ESCAPE_CHARS = @"\'""";
if (characters.IndexOfAny(ESCAPE_CHARS) != -1)
throw new ArgumentOutOfRangeException(nameof(characters), characters.ToString(), "Escape characters are not supported.");
var firstIndex = str.IndexOfAny(characters);
if (firstIndex == -1)
return firstIndex;
var firstEscapeIndex = str.IndexOfAny(ESCAPE_CHARS);
if (firstEscapeIndex == -1 || firstEscapeIndex > firstIndex)
return firstIndex;
var length = str.Length;
for (var i = firstEscapeIndex; i < length; i++)
{
var readChar = str[i];
switch (readChar)
{
case '\\':
i++;
break;
case '\'':
case '\"':
{
var closeQuoteMark = FindCloseQuoteChar(str, i, length, readChar);
if (closeQuoteMark == -1)
throw new FormatException($"Unbalanced quote mark at {i}.");
i = closeQuoteMark;
break;
}
default:
{
if (characters.Contains(readChar))
{
return i;
}
break;
}
}
}
return -1;
}
public static int Count(this ReadOnlySpan str, char character)
{
if (str.IsEmpty)
return -1;
var count = 0;
var temp = str;
int index;
while ((index = temp.IndexOf(character)) != -1)
{
count++;
temp = temp[(index + 1)..];
}
return count == 0 ? -1 : count;
}
}
}