using System; using System.Globalization; namespace PKHeX.Core; /// /// Utility for searching strings within arrays or within another string. /// public static class StringUtil { private static readonly CompareInfo CompareInfo = CultureInfo.CurrentCulture.CompareInfo; /// /// Finds the index of the string within the array by ignoring casing, spaces, and punctuation. /// /// Array of strings to search in /// Value to search for /// Index within public static int FindIndexIgnoreCase(ReadOnlySpan arr, ReadOnlySpan value) { for (int i = 0; i < arr.Length; i++) { if (IsMatchIgnoreCase(arr[i], value)) return i; } return -1; } public static bool IsMatchIgnoreCase(ReadOnlySpan string1, ReadOnlySpan string2) { if (string1.Length != string2.Length) return false; const CompareOptions options = CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreWidth; var compare = CompareInfo.Compare(string1, string2, options); return compare == 0; } /// /// Gets the string entry within the input , based on the . /// public static ReadOnlySpan GetNthEntry(ReadOnlySpan line, int nth, char separator = '\t') { int start = 0; if (nth != 0) start = line.IndexOfNth(separator, nth) + 1; // 1 char after separator var tail = line[start..]; var end = tail.IndexOf(separator); if (end == -1) return tail; return tail[..end]; } private static int IndexOfNth(this ReadOnlySpan s, char t, int n) { int count = 0; for (int i = 0; i < s.Length; i++) { if (s[i] != t) continue; if (++count == n) return i; } return -1; } /// /// Converts an all-caps hex string to a byte array. Expects no separation between byte tuples. /// public static byte[] ToByteArray(this string toTransform) { var result = new byte[toTransform.Length / 2]; LoadHexBytesTo(toTransform, result, 2); return result; } /// /// Converts an all-caps encoded ASCII-Text hex string to a byte array. /// public static void LoadHexBytesTo(Span dest, ReadOnlySpan str, int tupleSize) { // The input string is 2-char hex values optionally separated. // The destination array should always be larger or equal than the bytes written. Let the runtime bounds check us. // Iterate through the string without allocating. for (int i = 0, j = 0; i < str.Length; i += tupleSize) dest[j++] = DecodeTuple((char)str[i + 0], (char)str[i + 1]); } /// /// Converts an all-caps hex string to a byte array. /// public static void LoadHexBytesTo(ReadOnlySpan str, Span dest, int tupleSize) { // The input string is 2-char hex values optionally separated. // The destination array should always be larger or equal than the bytes written. Let the runtime bounds check us. // Iterate through the string without allocating. for (int i = 0, j = 0; i < str.Length; i += tupleSize) dest[j++] = DecodeTuple(str[i + 0], str[i + 1]); } private static byte DecodeTuple(char _0, char _1) { byte result; if (char.IsAsciiDigit(_0)) result = (byte)((_0 - '0') << 4); else if (char.IsAsciiHexDigitUpper(_0)) result = (byte)((_0 - 'A' + 10) << 4); else throw new ArgumentOutOfRangeException(nameof(_0)); if (char.IsAsciiDigit(_1)) result |= (byte)(_1 - '0'); else if (char.IsAsciiHexDigitUpper(_1)) result |= (byte)(_1 - 'A' + 10); else throw new ArgumentOutOfRangeException(nameof(_1)); return result; } }