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; } } }