aboutsummaryrefslogtreecommitdiff
path: root/Utils/src/Extensions/VnStringExtensions.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Utils/src/Extensions/VnStringExtensions.cs')
-rw-r--r--Utils/src/Extensions/VnStringExtensions.cs418
1 files changed, 418 insertions, 0 deletions
diff --git a/Utils/src/Extensions/VnStringExtensions.cs b/Utils/src/Extensions/VnStringExtensions.cs
new file mode 100644
index 0000000..285fc4f
--- /dev/null
+++ b/Utils/src/Extensions/VnStringExtensions.cs
@@ -0,0 +1,418 @@
+/*
+* Copyright (c) 2022 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Utils
+* File: VnStringExtensions.cs
+*
+* VnStringExtensions.cs is part of VNLib.Utils which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Utils is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published
+* by the Free Software Foundation, either version 2 of the License,
+* or (at your option) any later version.
+*
+* VNLib.Utils is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/.
+*/
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Utils.Memory;
+
+namespace VNLib.Utils.Extensions
+{
+ [SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "<Pending>")]
+ public static class VnStringExtensions
+ {
+ /// <summary>
+ /// Derermines if the character exists within the instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="value">The value to find</param>
+ /// <returns>True if the character exists within the instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static bool Contains(this VnString str, char value) => str.AsSpan().Contains(value);
+ /// <summary>
+ /// Derermines if the sequence exists within the instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="value">The sequence to find</param>
+ /// <param name="stringComparison"></param>
+ /// <returns>True if the character exists within the instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+
+ public static bool Contains(this VnString str, ReadOnlySpan<char> value, StringComparison stringComparison) => str.AsSpan().Contains(value, stringComparison);
+
+ /// <summary>
+ /// Searches for the first occurrance of the specified character within the current instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="value">The character to search for within the instance</param>
+ /// <returns>The 0 based index of the occurance, -1 if the character was not found</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, char value) => str.IsEmpty ? -1 : str.AsSpan().IndexOf(value);
+ /// <summary>
+ /// Searches for the first occurrance of the specified sequence within the current instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <returns>The 0 based index of the occurance, -1 if the sequence was not found</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, ReadOnlySpan<char> search)
+ {
+ //Using spans to avoid memory leaks...
+ ReadOnlySpan<char> self = str.AsSpan();
+ return self.IndexOf(search);
+ }
+ /// <summary>
+ /// Searches for the first occurrance of the specified sequence within the current instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <param name="comparison">The <see cref="StringComparison"/> type to use in searchr</param>
+ /// <returns>The 0 based index of the occurance, -1 if the sequence was not found</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, ReadOnlySpan<char> search, StringComparison comparison)
+ {
+ //Using spans to avoid memory leaks...
+ ReadOnlySpan<char> self = str.AsSpan();
+ return self.IndexOf(search, comparison);
+ }
+ /// <summary>
+ /// Searches for the 0 based index of the first occurance of the search parameter after the start index.
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence of data to search for</param>
+ /// <param name="start">The lower boundry of the search area</param>
+ /// <returns>The absolute index of the first occurrance within the instance, -1 if the sequency was not found in the windowed segment</returns>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int IndexOf(this VnString str, ReadOnlySpan<char> search, int start)
+ {
+ if (start < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(start), "Start cannot be less than 0");
+ }
+ //Get shifted window
+ ReadOnlySpan<char> self = str.AsSpan()[start..];
+ //Check indexof
+ int index = self.IndexOf(search);
+ return index > -1 ? index + start : -1;
+ }
+
+ /// <summary>
+ /// Returns the realtive index after the specified sequence within the <see cref="VnString"/> instance
+ /// </summary>
+ /// <param name="str"></param>
+ /// <param name="search">The sequence to search for</param>
+ /// <returns>The index after the found sequence within the string, -1 if the sequence was not found within the instance</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static int EndOf(this VnString str, ReadOnlySpan<char> search)
+ {
+ //Try to get the index of the data
+ int index = IndexOf(str, search);
+ //If the data was found, add the length to get the end of the string
+ return index > -1 ? index + search.Length : -1;
+ }
+
+ /// <summary>
+ /// Allows for trimming whitespace characters in a realtive sequence from
+ /// within a <see cref="VnString"/> buffer defined by the start and end parameters
+ /// and returning the trimmed entry.
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="start">The starting position within the sequence to trim</param>
+ /// <param name="end">The end of the sequence to trim</param>
+ /// <returns>The trimmed <see cref="VnString"/> instance as a child of the original entry</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ public static VnString AbsoluteTrim(this VnString data, int start, int end)
+ {
+ AbsoluteTrim(data, ref start, ref end);
+ return data[start..end];
+ }
+ /// <summary>
+ /// Finds whitespace characters within the sequence defined between start and end parameters
+ /// and adjusts the specified window to "trim" whitespace
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="start">The starting position within the sequence to trim</param>
+ /// <param name="end">The end of the sequence to trim</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ public static void AbsoluteTrim(this VnString data, ref int start, ref int end)
+ {
+ ReadOnlySpan<char> trimmed = data.AsSpan();
+ //trim leading whitespace
+ while (start < end)
+ {
+ //If whitespace character shift start up
+ if (trimmed[start] != ' ')
+ {
+ break;
+ }
+ //Shift
+ start++;
+ }
+ //remove trailing whitespace characters
+ while (end > start)
+ {
+ //If whiterspace character shift end param down
+ if (trimmed[end - 1] != ' ')
+ {
+ break;
+ }
+ end--;
+ }
+ }
+ /// <summary>
+ /// Allows for trimming whitespace characters in a realtive sequence from
+ /// within a <see cref="VnString"/> buffer and returning the trimmed entry.
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="start">The starting position within the sequence to trim</param>
+ /// <returns>The trimmed <see cref="VnString"/> instance as a child of the original entry</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="IndexOutOfRangeException"></exception>
+ public static VnString AbsoluteTrim(this VnString data, int start) => AbsoluteTrim(data, start, data.Length);
+ /// <summary>
+ /// Trims leading or trailing whitespace characters and returns a new child instance
+ /// without leading or trailing whitespace
+ /// </summary>
+ /// <returns>A child <see cref="VnString"/> of the current instance without leading or trailing whitespaced</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static VnString RelativeTirm(this VnString data) => AbsoluteTrim(data, 0);
+
+ /// <summary>
+ /// Allows for enumeration of segments of data within the specified <see cref="VnString"/> instance that are
+ /// split by the search parameter
+ /// </summary>
+ /// <param name="data"></param>
+ /// <param name="search">The sequence of data to delimit segments</param>
+ /// <param name="options">The options used to split the string instances</param>
+ /// <returns>An iterator to enumerate the split segments</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IEnumerable<VnString> Split(this VnString data, ReadOnlyMemory<char> search, StringSplitOptions options = StringSplitOptions.None)
+ {
+ int lowerBound = 0;
+ //Make sure the length of the search param is not 0
+ if(search.IsEmpty)
+ {
+ //Return the entire string
+ yield return data;
+ }
+ //No string options
+ else if (options == 0)
+ {
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ yield return data[lowerBound..splitIndex];
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Return remaining data
+ yield return data[lowerBound..];
+ }
+ //Trim but do not remove empties
+ else if ((options & StringSplitOptions.RemoveEmptyEntries) == 0)
+ {
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ //trim and return
+ yield return data.AbsoluteTrim(lowerBound, splitIndex);
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Return remaining data
+ yield return data.AbsoluteTrim(lowerBound);
+ }
+ //Remove empty entires but do not trim them
+ else if ((options & StringSplitOptions.TrimEntries) == 0)
+ {
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ //If the split index is the next sequence, then the result is empty, so exclude it
+ else if(splitIndex > 0)
+ {
+ yield return data[lowerBound..splitIndex];
+ }
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Return remaining data if available
+ if (lowerBound < data.Length)
+ {
+ yield return data[lowerBound..];
+ }
+ }
+ //Must mean remove and trim
+ else
+ {
+ //Get stack varables to pass to trim function
+ int trimStart, trimEnd;
+ do
+ {
+ //Capture the first = and store argument + value
+ int splitIndex = data.IndexOf(search.Span, lowerBound);
+ //If no split index is found, then return remaining data
+ if (splitIndex == -1)
+ {
+ break;
+ }
+ //Get stack varables to pass to trim function
+ trimStart = lowerBound;
+ trimEnd = splitIndex; //End of the segment is the relative split index + the lower bound of the window
+ //Trim whitespace chars
+ data.AbsoluteTrim(ref trimStart, ref trimEnd);
+ //See if the string has data
+ if((trimEnd - trimStart) > 0)
+ {
+ yield return data[trimStart..trimEnd];
+ }
+ //Shift the lower window to the end of the last string
+ lowerBound = splitIndex + search.Length;
+ } while (true);
+ //Trim remaining
+ trimStart = lowerBound;
+ trimEnd = data.Length;
+ data.AbsoluteTrim(ref trimStart, ref trimEnd);
+ //If the remaining string is not empty return it
+ if ((trimEnd - trimStart) > 0)
+ {
+ yield return data[trimStart..trimEnd];
+ }
+ }
+ }
+
+ /// <summary>
+ /// Trims any leading or trailing <c>'\r'|'\n'|' '</c>(whitespace) characters from the segment
+ /// </summary>
+ /// <returns>The trimmed segment</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static VnString TrimCRLF(this VnString data)
+ {
+ ReadOnlySpan<char> trimmed = data.AsSpan();
+ int start = 0, end = trimmed.Length;
+ //trim leading \r\n chars
+ while (start < end)
+ {
+ char t = trimmed[start];
+ //If character \r or \n slice it off
+ if (t != '\r' && t != '\n' && t != ' ') {
+ break;
+ }
+ //Shift
+ start++;
+ }
+ //remove trailing crlf characters
+ while (end > start)
+ {
+ char t = trimmed[end - 1];
+ //If character \r or \n slice it off
+ if (t != '\r' && t != '\n' && t != ' ') {
+ break;
+ }
+ end--;
+ }
+ return data[start..end];
+ }
+
+ /// <summary>
+ /// Unoptimized character enumerator. You should use <see cref="VnString.AsSpan"/> to enumerate the unerlying data.
+ /// </summary>
+ /// <returns>The next character in the sequence</returns>
+ /// <exception cref="ObjectDisposedException"></exception>
+ public static IEnumerator<char> GetEnumerator(this VnString data)
+ {
+ int index = 0;
+ while (index < data.Length)
+ {
+ yield return data[index++];
+ }
+ }
+ /// <summary>
+ /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper
+ /// for a memory handle
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="length">The number of characters from the handle to reference (length of the string)</param>
+ /// <returns>The new <see cref="VnString"/> wrapper</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ToVnString(this MemoryHandle<char> handle, int length)
+ {
+ if(handle.Length > int.MaxValue)
+ {
+ throw new OverflowException("The handle is larger than 2GB in size");
+ }
+ return VnString.ConsumeHandle(handle, 0, length);
+ }
+ /// <summary>
+ /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper
+ /// for a memory handle
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <returns>The new <see cref="VnString"/> wrapper</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ToVnString(this MemoryHandle<char> handle)
+ {
+ return VnString.ConsumeHandle(handle, 0, handle.IntLength);
+ }
+ /// <summary>
+ /// Converts the current handle to a <see cref="VnString"/>, a zero-alloc immutable wrapper
+ /// for a memory handle
+ /// </summary>
+ /// <param name="handle"></param>
+ /// <param name="offset">The offset in characters that represents the begining of the string</param>
+ /// <param name="length">The number of characters from the handle to reference (length of the string)</param>
+ /// <returns>The new <see cref="VnString"/> wrapper</returns>
+ /// <exception cref="OverflowException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static VnString ToVnString(this MemoryHandle<char> handle,
+#if TARGET_64_BIT
+ ulong offset,
+#else
+ int offset,
+#endif
+ int length)
+ {
+ if (handle.Length > int.MaxValue)
+ {
+ throw new OverflowException("The handle is larger than 2GB in size");
+ }
+ return VnString.ConsumeHandle(handle, offset, length);
+ }
+ }
+} \ No newline at end of file