Skip to content

Instantly share code, notes, and snippets.

@mythz mythz/StringSegment.cs
Last active Jul 1, 2018

Embed
What would you like to do?
Removed StringSegmentExtensions.cs
#if !NETSTANDARD2_0
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace ServiceStack.Text
{
//From: https://github.com/aspnet/Common/blob/dev/src/Microsoft.Extensions.Primitives/StringSegment.cs
/// <summary>
/// An optimized representation of a substring.
/// </summary>
[Obsolete("Use ReadOnlyMemory<char> instead")]
public readonly struct StringSegment : IEquatable<StringSegment>, IEquatable<string>
{
/// <summary>
/// A <see cref="StringSegment"/> for <see cref="string.Empty"/>.
/// </summary>
public static readonly StringSegment Empty = string.Empty;
/// <summary>
/// Initializes an instance of the <see cref="StringSegment"/> struct.
/// </summary>
/// <param name="buffer">
/// The original <see cref="string"/>. The <see cref="StringSegment"/> includes the whole <see cref="string"/>.
/// </param>
public StringSegment(string buffer)
{
Buffer = buffer;
Offset = 0;
Length = buffer?.Length ?? 0;
}
/// <summary>
/// Initializes an instance of the <see cref="StringSegment"/> struct.
/// </summary>
/// <param name="buffer">The original <see cref="string"/> used as buffer.</param>
/// <param name="offset">The offset of the segment within the <paramref name="buffer"/>.</param>
/// <param name="length">The length of the segment.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StringSegment(string buffer, int offset, int length)
{
// Validate arguments, check is minimal instructions with reduced branching for inlinable fast-path
// Negative values discovered though conversion to high values when converted to unsigned
// Failure should be rare and location determination and message is delegated to failure functions
if (buffer == null || (uint)offset > (uint)buffer.Length || (uint)length > (uint)(buffer.Length - offset))
{
ThrowInvalidArguments(buffer, offset, length);
}
Buffer = buffer;
Offset = offset;
Length = length;
}
private static void ThrowInvalidArguments(string buffer, int offset, int length)
{
// Only have single throw in method so is marked as "does not return" and isn't inlined to caller
throw GetInvalidArgumentException(buffer, offset, length);
}
private static Exception GetInvalidArgumentException(string buffer, int offset, int length)
{
if (buffer == null)
{
return ThrowHelper.GetArgumentNullException(ExceptionArgument.buffer);
}
if (offset < 0)
{
return ThrowHelper.GetArgumentOutOfRangeException(ExceptionArgument.offset);
}
if (length < 0)
{
return ThrowHelper.GetArgumentOutOfRangeException(ExceptionArgument.length);
}
return ThrowHelper.GetArgumentException(ExceptionResource.Argument_InvalidOffsetLength);
}
/// <summary>
/// Gets the <see cref="string"/> buffer for this <see cref="StringSegment"/>.
/// </summary>
public string Buffer { get; }
/// <summary>
/// Gets the offset within the buffer for this <see cref="StringSegment"/>.
/// </summary>
public int Offset { get; }
/// <summary>
/// Gets the length of this <see cref="StringSegment"/>.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the value of this segment as a <see cref="string"/>.
/// </summary>
public string Value
{
get
{
if (!HasValue)
{
return null;
}
else
{
return Buffer.Substring(Offset, Length);
}
}
}
/// <summary>
/// Gets whether or not this <see cref="StringSegment"/> contains a valid value.
/// </summary>
public bool HasValue
{
get { return Buffer != null; }
}
/// <summary>
/// Gets the <see cref="char"/> at a specified position in the current <see cref="StringSegment"/>.
/// </summary>
/// <param name="index">The offset into the <see cref="StringSegment"/></param>
/// <returns>The <see cref="char"/> at a specified position.</returns>
public char this[int index]
{
get
{
if ((uint)index >= (uint)Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}
return Buffer[Offset + index];
}
}
/// <summary>
/// Gets a <see cref="ReadOnlySpan{T}"/> from the current <see cref="StringSegment"/>.
/// </summary>
/// <returns>The <see cref="ReadOnlySpan{T}"/> from this <see cref="StringSegment"/>.</returns>
public ReadOnlySpan<char> AsSpan() => Buffer.AsSpan(Offset, Length);
/// <summary>
/// Gets a <see cref="ReadOnlyMemory{T}"/> from the current <see cref="StringSegment"/>.
/// </summary>
/// <returns>The <see cref="ReadOnlyMemory{T}"/> from this <see cref="StringSegment"/>.</returns>
public ReadOnlyMemory<char> AsMemory() => Buffer.AsMemory(Offset, Length);
/// <summary>
/// Compares substrings of two specified <see cref="StringSegment"/> objects using the specified rules,
/// and returns an integer that indicates their relative position in the sort order.
/// </summary>
/// <param name="a">The first StringSegment to compare.</param>
/// <param name="b">The second StringSegment to compare.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the comparison.</param>
/// <returns>
/// A 32-bit signed integer indicating the lexical relationship between the two comparands.
/// The value is negative if <paramref name="a"/> is less than <paramref name="b"/>, 0 if the two comparands are equal,
/// and positive if <paramref name="a"/> is greater than <paramref name="b"/>.
/// </returns>
public static int Compare(StringSegment a, StringSegment b, StringComparison comparisonType)
{
var minLength = Math.Min(a.Length, b.Length);
var diff = string.Compare(a.Buffer, a.Offset, b.Buffer, b.Offset, minLength, comparisonType);
if (diff == 0)
{
diff = a.Length - b.Length;
}
return diff;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is StringSegment segment && Equals(segment);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns><code>true</code> if the current object is equal to the other parameter; otherwise, <code>false</code>.</returns>
public bool Equals(StringSegment other)
{
return Equals(other, StringComparison.Ordinal);
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
/// <returns><code>true</code> if the current object is equal to the other parameter; otherwise, <code>false</code>.</returns>
public bool Equals(StringSegment other, StringComparison comparisonType)
{
int textLength = other.Length;
if (Length != textLength)
{
return false;
}
return string.Compare(Buffer, Offset, other.Buffer, other.Offset, textLength, comparisonType) == 0;
}
// This handles StringSegment.Equals(string, StringSegment, StringComparison) and StringSegment.Equals(StringSegment, string, StringComparison)
// via the implicit type converter
/// <summary>
/// Determines whether two specified StringSegment objects have the same value. A parameter specifies the culture, case, and
/// sort rules used in the comparison.
/// </summary>
/// <param name="a">The first StringSegment to compare.</param>
/// <param name="b">The second StringSegment to compare.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the comparison.</param>
/// <returns><code>true</code> if the objects are equal; otherwise, <code>false</code>.</returns>
public static bool Equals(StringSegment a, StringSegment b, StringComparison comparisonType)
{
return a.Equals(b, comparisonType);
}
/// <summary>
/// Checks if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>.
/// </summary>
/// <param name="text">The <see cref="string"/> to compare with the current <see cref="StringSegment"/>.</param>
/// <returns><code>true</code> if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>; otherwise, <code>false</code>.</returns>
public bool Equals(string text)
{
return Equals(text, StringComparison.Ordinal);
}
/// <summary>
/// Checks if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>.
/// </summary>
/// <param name="text">The <see cref="string"/> to compare with the current <see cref="StringSegment"/>.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
/// <returns><code>true</code> if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>; otherwise, <code>false</code>.</returns>
public bool Equals(string text, StringComparison comparisonType)
{
if (text == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
}
int textLength = text.Length;
if (!HasValue || Length != textLength)
{
return false;
}
return string.Compare(Buffer, Offset, text, 0, textLength, comparisonType) == 0;
}
/// <inheritdoc />
/// <remarks>
/// This GetHashCode is expensive since it allocates on every call.
/// However this is required to ensure we retain any behavior (such as hash code randomization) that
/// string.GetHashCode has.
/// </remarks>
public override int GetHashCode()
{
if (!HasValue)
{
return 0;
}
else
{
// TODO: PERF; Note that .NET Core strings use randomized hash codes for security reasons.
return Value.GetHashCode();
}
}
/// <summary>
/// Checks if two specified <see cref="StringSegment"/> have the same value.
/// </summary>
/// <param name="left">The first <see cref="StringSegment"/> to compare, or <code>null</code>.</param>
/// <param name="right">The second <see cref="StringSegment"/> to compare, or <code>null</code>.</param>
/// <returns><code>true</code> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <code>false</code>.</returns>
public static bool operator ==(StringSegment left, StringSegment right)
{
return left.Equals(right);
}
/// <summary>
/// Checks if two specified <see cref="StringSegment"/> have different values.
/// </summary>
/// <param name="left">The first <see cref="StringSegment"/> to compare, or <code>null</code>.</param>
/// <param name="right">The second <see cref="StringSegment"/> to compare, or <code>null</code>.</param>
/// <returns><code>true</code> if the value of <paramref name="left"/> is different from the value of <paramref name="right"/>; otherwise, <code>false</code>.</returns>
public static bool operator !=(StringSegment left, StringSegment right)
{
return !left.Equals(right);
}
// PERF: Do NOT add a implicit converter from StringSegment to String. That would negate most of the perf safety.
/// <summary>
/// Creates a new <see cref="StringSegment"/> from the given <see cref="string"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to convert to a <see cref="StringSegment"/></param>
public static implicit operator StringSegment(string value)
{
return new StringSegment(value);
}
/// <summary>
/// Creates a see <see cref="ReadOnlySpan{T}"/> from the given <see cref="StringSegment"/>.
/// </summary>
/// <param name="segment">The <see cref="StringSegment"/> to convert to a <see cref="ReadOnlySpan{T}"/>.</param>
public static implicit operator ReadOnlySpan<char>(StringSegment segment) => segment.AsSpan();
/// <summary>
/// Creates a see <see cref="ReadOnlyMemory{T}"/> from the given <see cref="StringSegment"/>.
/// </summary>
/// <param name="segment">The <see cref="StringSegment"/> to convert to a <see cref="ReadOnlyMemory{T}"/>.</param>
public static implicit operator ReadOnlyMemory<char>(StringSegment segment) => segment.AsMemory();
/// <summary>
/// Checks if the beginning of this <see cref="StringSegment"/> matches the specified <see cref="string"/> when compared using the specified <paramref name="comparisonType"/>.
/// </summary>
/// <param name="text">The <see cref="string"/>to compare.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
/// <returns><code>true</code> if <paramref name="text"/> matches the beginning of this <see cref="StringSegment"/>; otherwise, <code>false</code>.</returns>
public bool StartsWith(string text, StringComparison comparisonType)
{
if (text == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
}
var textLength = text.Length;
if (!HasValue || Length < textLength)
{
return false;
}
return string.Compare(Buffer, Offset, text, 0, textLength, comparisonType) == 0;
}
/// <summary>
/// Checks if the end of this <see cref="StringSegment"/> matches the specified <see cref="string"/> when compared using the specified <paramref name="comparisonType"/>.
/// </summary>
/// <param name="text">The <see cref="string"/>to compare.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
/// <returns><code>true</code> if <paramref name="text"/> matches the end of this <see cref="StringSegment"/>; otherwise, <code>false</code>.</returns>
public bool EndsWith(string text, StringComparison comparisonType)
{
if (text == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
}
var textLength = text.Length;
if (!HasValue || Length < textLength)
{
return false;
}
return string.Compare(Buffer, Offset + Length - textLength, text, 0, textLength, comparisonType) == 0;
}
/// <summary>
/// Retrieves a substring from this <see cref="StringSegment"/>.
/// The substring starts at the position specified by <paramref name="offset"/> and has the remaining length.
/// </summary>
/// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
/// <returns>A <see cref="string"/> that is equivalent to the substring of remaining length that begins at
/// <paramref name="offset"/> in this <see cref="StringSegment"/></returns>
public string Substring(int offset)
{
return Substring(offset, Length - offset);
}
/// <summary>
/// Retrieves a substring from this <see cref="StringSegment"/>.
/// The substring starts at the position specified by <paramref name="offset"/> and has the specified <paramref name="length"/>.
/// </summary>
/// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
/// <param name="length">The number of characters in the substring.</param>
/// <returns>A <see cref="string"/> that is equivalent to the substring of length <paramref name="length"/> that begins at
/// <paramref name="offset"/> in this <see cref="StringSegment"/></returns>
public string Substring(int offset, int length)
{
if (!HasValue)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offset);
}
if (offset < 0 || offset + length > Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offset);
}
if (length < 0 || Offset + offset + length > Buffer.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
}
return Buffer.Substring(Offset + offset, length);
}
/// <summary>
/// Retrieves a <see cref="StringSegment"/> that represents a substring from this <see cref="StringSegment"/>.
/// The <see cref="StringSegment"/> starts at the position specified by <paramref name="offset"/>.
/// </summary>
/// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
/// <returns>A <see cref="StringSegment"/> that begins at <paramref name="offset"/> in this <see cref="StringSegment"/>
/// whose length is the remainder.</returns>
public StringSegment Subsegment(int offset)
{
return Subsegment(offset, Length - offset);
}
/// <summary>
/// Retrieves a <see cref="StringSegment"/> that represents a substring from this <see cref="StringSegment"/>.
/// The <see cref="StringSegment"/> starts at the position specified by <paramref name="offset"/> and has the specified <paramref name="length"/>.
/// </summary>
/// <param name="offset">The zero-based starting character position of a substring in this <see cref="StringSegment"/>.</param>
/// <param name="length">The number of characters in the substring.</param>
/// <returns>A <see cref="StringSegment"/> that is equivalent to the substring of length <paramref name="length"/> that begins at <paramref name="offset"/> in this <see cref="StringSegment"/></returns>
public StringSegment Subsegment(int offset, int length)
{
if (!HasValue)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offset);
}
if (offset < 0 || offset + length > Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offset);
}
if (length < 0 || Offset + offset + length > Buffer.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
}
return new StringSegment(Buffer, Offset + offset, length);
}
/// <summary>
/// Gets the zero-based index of the first occurrence of the character <paramref name="c"/> in this <see cref="StringSegment"/>.
/// The search starts at <paramref name="start"/> and examines a specified number of <paramref name="count"/> character positions.
/// </summary>
/// <param name="c">The Unicode character to seek.</param>
/// <param name="start">The zero-based index position at which the search starts. </param>
/// <param name="count">The number of characters to examine.</param>
/// <returns>The zero-based index position of <paramref name="c"/> from the beginning of the <see cref="StringSegment"/> if that character is found, or -1 if it is not.</returns>
public int IndexOf(char c, int start, int count)
{
if (start < 0 || Offset + start > Buffer.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
}
if (count < 0 || Offset + start + count > Buffer.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
}
var index = Buffer.IndexOf(c, start + Offset, count);
if (index != -1)
{
return index - Offset;
}
else
{
return index;
}
}
/// <summary>
/// Gets the zero-based index of the first occurrence of the character <paramref name="c"/> in this <see cref="StringSegment"/>.
/// The search starts at <paramref name="start"/>.
/// </summary>
/// <param name="c">The Unicode character to seek.</param>
/// <param name="start">The zero-based index position at which the search starts. </param>
/// <returns>The zero-based index position of <paramref name="c"/> from the beginning of the <see cref="StringSegment"/> if that character is found, or -1 if it is not.</returns>
public int IndexOf(char c, int start)
{
return IndexOf(c, start, Length - start);
}
/// <summary>
/// Gets the zero-based index of the first occurrence of the character <paramref name="c"/> in this <see cref="StringSegment"/>.
/// </summary>
/// <param name="c">The Unicode character to seek.</param>
/// <returns>The zero-based index position of <paramref name="c"/> from the beginning of the <see cref="StringSegment"/> if that character is found, or -1 if it is not.</returns>
public int IndexOf(char c)
{
return IndexOf(c, 0, Length);
}
/// <summary>
/// Reports the zero-based index of the first occurrence in this instance of any character in a specified array
/// of Unicode characters. The search starts at a specified character position and examines a specified number
/// of character positions.
/// </summary>
/// <param name="anyOf">A Unicode character array containing one or more characters to seek.</param>
/// <param name="startIndex">The search starting position.</param>
/// <param name="count">The number of character positions to examine.</param>
/// <returns>The zero-based index position of the first occurrence in this instance where any character in anyOf
/// was found; -1 if no character in anyOf was found.</returns>
public int IndexOfAny(char[] anyOf, int startIndex, int count)
{
if (!HasValue)
{
return -1;
}
if (startIndex < 0 || Offset + startIndex > Buffer.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
}
if (count < 0 || Offset + startIndex + count > Buffer.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count);
}
var index = Buffer.IndexOfAny(anyOf, Offset + startIndex, count);
if (index == -1)
{
return index;
}
return index - Offset;
}
/// <summary>
/// Reports the zero-based index of the first occurrence in this instance of any character in a specified array
/// of Unicode characters. The search starts at a specified character position.
/// </summary>
/// <param name="anyOf">A Unicode character array containing one or more characters to seek.</param>
/// <param name="startIndex">The search starting position.</param>
/// <returns>The zero-based index position of the first occurrence in this instance where any character in anyOf
/// was found; -1 if no character in anyOf was found.</returns>
public int IndexOfAny(char[] anyOf, int startIndex)
{
return IndexOfAny(anyOf, startIndex, Length - startIndex);
}
/// <summary>
/// Reports the zero-based index of the first occurrence in this instance of any character in a specified array
/// of Unicode characters.
/// </summary>
/// <param name="anyOf">A Unicode character array containing one or more characters to seek.</param>
/// <returns>The zero-based index position of the first occurrence in this instance where any character in anyOf
/// was found; -1 if no character in anyOf was found.</returns>
public int IndexOfAny(char[] anyOf)
{
return IndexOfAny(anyOf, 0, Length);
}
/// <summary>
/// Reports the zero-based index position of the last occurrence of a specified Unicode character within this instance.
/// </summary>
/// <param name="value">The Unicode character to seek.</param>
/// <returns>The zero-based index position of value if that character is found, or -1 if it is not.</returns>
public int LastIndexOf(char value)
{
if (!HasValue)
{
return -1;
}
var index = Buffer.LastIndexOf(value, Offset + Length - 1, Length);
if (index == -1)
{
return -1;
}
return index - Offset;
}
/// <summary>
/// Removes all leading and trailing whitespaces.
/// </summary>
/// <returns>The trimmed <see cref="StringSegment"/>.</returns>
public StringSegment Trim()
{
return TrimStart().TrimEnd();
}
/// <summary>
/// Removes all leading whitespaces.
/// </summary>
/// <returns>The trimmed <see cref="StringSegment"/>.</returns>
public StringSegment TrimStart()
{
var trimmedStart = Offset;
while (trimmedStart < Offset + Length)
{
if (!char.IsWhiteSpace(Buffer, trimmedStart))
{
break;
}
trimmedStart++;
}
return new StringSegment(Buffer, trimmedStart, Offset + Length - trimmedStart);
}
/// <summary>
/// Removes all trailing whitespaces.
/// </summary>
/// <returns>The trimmed <see cref="StringSegment"/>.</returns>
public StringSegment TrimEnd()
{
var trimmedEnd = Offset + Length - 1;
while (trimmedEnd >= Offset)
{
if (!char.IsWhiteSpace(Buffer, trimmedEnd))
{
break;
}
trimmedEnd--;
}
return new StringSegment(Buffer, Offset, trimmedEnd - Offset + 1);
}
/// <summary>
/// Splits a string into StringSegments that are based on the characters in an array.
/// </summary>
/// <param name="chars">A character array that delimits the substrings in this string, an empty array that
/// contains no delimiters, or null.</param>
/// <returns>An <see cref="StringTokenizer"/> whose elements contain the StringSegmeents from this instance
/// that are delimited by one or more characters in separator.</returns>
// public StringTokenizer Split(char[] chars)
// {
// return new StringTokenizer(this, chars);
// }
/// <summary>
/// Indicates whether the specified StringSegment is null or an Empty string.
/// </summary>
/// <param name="value">The StringSegment to test.</param>
/// <returns></returns>
public static bool IsNullOrEmpty(StringSegment value)
{
return !value.HasValue || value.Length == 0;
}
/// <summary>
/// Returns the <see cref="string"/> represented by this <see cref="StringSegment"/> or <code>String.Empty</code> if the <see cref="StringSegment"/> does not contain a value.
/// </summary>
/// <returns>The <see cref="string"/> represented by this <see cref="StringSegment"/> or <code>String.Empty</code> if the <see cref="StringSegment"/> does not contain a value.</returns>
public override string ToString()
{
return Value ?? string.Empty;
}
}
internal static class ThrowHelper
{
internal static void ThrowArgumentNullException(ExceptionArgument argument)
{
throw new ArgumentNullException(GetArgumentName(argument));
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
{
throw new ArgumentOutOfRangeException(GetArgumentName(argument));
}
internal static ArgumentNullException GetArgumentNullException(ExceptionArgument argument)
{
return new ArgumentNullException(GetArgumentName(argument));
}
internal static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument)
{
return new ArgumentOutOfRangeException(GetArgumentName(argument));
}
internal static ArgumentException GetArgumentException(ExceptionResource resource)
{
return new ArgumentException(resource.ToString());
}
private static string GetArgumentName(ExceptionArgument argument)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument),
"The enum value is not defined, please check the ExceptionArgument Enum.");
return argument.ToString();
}
private static string GetResourceName(ExceptionResource resource)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource),
"The enum value is not defined, please check the ExceptionResource Enum.");
return resource.ToString();
}
}
internal enum ExceptionArgument
{
buffer,
offset,
length,
text,
start,
count,
index,
value,
capacity,
separators
}
internal enum ExceptionResource
{
Argument_InvalidOffsetLength,
Capacity_CannotChangeAfterWriteStarted,
Capacity_NotEnough,
Capacity_NotUsedEntirely
}
}
#endif
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Globalization;
using System.IO;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ServiceStack.Text.Json;
#if NETSTANDARD2_0
using Microsoft.Extensions.Primitives;
#endif
namespace ServiceStack.Text
{
public static class StringSegmentExtensions
{
[Obsolete("Use ReadOnlyMemory<char> or ReadOnlySpan<char>")]
public static readonly StringSegment EmptyStringSegment = new StringSegment(null);
[Obsolete("Use ReadOnlyMemory<char> or ReadOnlySpan<char>")]
public static readonly StringSegment[] EmptyStringSegmentArray = new StringSegment[0];
[Obsolete("Use ReadOnlyMemory<char> or ReadOnlySpan<char>")]
public static readonly List<StringSegment> EmptyStringSegmentList = new List<StringSegment>(0);
const string BadFormat = "Input string was not in a correct format.";
const string OverflowMessage = "Value was either too large or too small for an {0}.";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringSegment ToStringSegment(this string value) => new StringSegment(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringSegment ToStringSegment(this ReadOnlySpan<char> value) => new StringSegment(value.Value());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsOrdinal(this ReadOnlySpan<char> value, StringSegment other) => value.Equals(other.AsSpan(), StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<char> SpanValue(this StringSegment value) => !value.HasValue
? default
: value.Length == 0
? TypeConstants.EmptyStringSpan
: value.AsSpan();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNullOrEmpty(this StringSegment value) => value.Buffer == null || value.Length == 0;
public static bool IsNullOrWhiteSpace(this StringSegment value)
{
if (!value.HasValue)
return true;
for (int index = 0; index < value.Length; ++index)
{
if (!char.IsWhiteSpace(value.GetChar(index)))
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Char GetChar(this StringSegment value, int index) => value.Buffer[value.Offset + index];
public static int IndexOfAny(this StringSegment value, char[] chars, int start, int count)
{
if (start < 0 || value.Offset + start > value.Buffer.Length)
throw new ArgumentOutOfRangeException(nameof(start));
if (count < 0 || value.Offset + start + count > value.Buffer.Length)
throw new ArgumentOutOfRangeException(nameof(count));
var index = value.Buffer.IndexOfAny(chars, start + value.Offset, count);
if (index != -1)
return index - value.Offset;
return index;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOfAny(this StringSegment value, char[] anyOf) => value.LastIndexOfAny(anyOf, value.Length - 1, value.Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOfAny(this StringSegment value, char[] anyOf, int startIndex) => value.LastIndexOfAny(anyOf, startIndex, startIndex + 1);
public static int LastIndexOfAny(this StringSegment value, char[] anyOf, int start, int count)
{
if (start < 0 || value.Offset - start > value.Buffer.Length)
throw new ArgumentOutOfRangeException(nameof(start));
if (count < 0 || value.Offset - start - count > value.Buffer.Length)
throw new ArgumentOutOfRangeException(nameof(count));
var index = value.Buffer.LastIndexOfAny(anyOf, start - value.Offset, count);
if (index != -1)
return index - value.Offset;
return index;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfAny(this StringSegment value, char[] chars) => value.IndexOfAny(chars, 0, value.Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfAny(this StringSegment value, char[] chars, int start) => value.IndexOfAny(chars, start, value.Length - start);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Substring(this StringSegment value, int pos) => value.Substring(pos, value.Length - pos);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CompareIgnoreCase(this StringSegment value, string text) => value.Equals(text, StringComparison.OrdinalIgnoreCase);
public static StringSegment FromCsvField(this StringSegment text)
{
return text.IsNullOrEmpty() || !text.StartsWith(CsvConfig.ItemDelimiterString, StringComparison.Ordinal)
? text
: new StringSegment(
text.Subsegment(CsvConfig.ItemDelimiterString.Length, text.Length - CsvConfig.EscapedItemDelimiterString.Length)
.Value
.Replace(CsvConfig.EscapedItemDelimiterString, CsvConfig.ItemDelimiterString));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ParseBoolean(this StringSegment value)
{
if (!value.TryParseBoolean(out bool result))
throw new FormatException(BadFormat);
return result;
}
public static bool TryParseBoolean(this StringSegment value, out bool result)
{
result = false;
if (value.CompareIgnoreCase(bool.TrueString))
{
result = true;
return true;
}
return value.CompareIgnoreCase(bool.FalseString);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseDecimal(this StringSegment value, out decimal result)
{
return decimal.TryParse(value.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseFloat(this StringSegment value, out float result)
{
return float.TryParse(value.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryParseDouble(this StringSegment value, out double result)
{
return double.TryParse(value.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
}
enum ParseState
{
LeadingWhite,
Sign,
Number,
DecimalPoint,
FractionNumber,
Exponent,
ExponentSign,
ExponentValue,
TrailingWhite
}
private static Exception CreateOverflowException(long maxValue) =>
new OverflowException(string.Format(OverflowMessage, SignedMaxValueToIntType(maxValue)));
private static Exception CreateOverflowException(ulong maxValue) =>
new OverflowException(string.Format(OverflowMessage, UnsignedMaxValueToIntType(maxValue)));
private static string SignedMaxValueToIntType(long maxValue)
{
switch (maxValue)
{
case SByte.MaxValue:
return nameof(SByte);
case Int16.MaxValue:
return nameof(Int16);
case Int32.MaxValue:
return nameof(Int32);
case Int64.MaxValue:
return nameof(Int64);
default:
return "Unknown";
}
}
private static string UnsignedMaxValueToIntType(ulong maxValue)
{
switch (maxValue)
{
case Byte.MaxValue:
return nameof(Byte);
case UInt16.MaxValue:
return nameof(UInt16);
case UInt32.MaxValue:
return nameof(UInt32);
case UInt64.MaxValue:
return nameof(UInt64);
default:
return "Unknown";
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static sbyte ParseSByte(this StringSegment value) => (sbyte)ParseSignedInteger(value, sbyte.MaxValue, sbyte.MinValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte ParseByte(this StringSegment value) => (byte)ParseUnsignedInteger(value, byte.MaxValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ParseInt16(this StringSegment value) => (short)ParseSignedInteger(value, short.MaxValue, short.MinValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ParseUInt16(this StringSegment value) => (ushort)ParseUnsignedInteger(value, ushort.MaxValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ParseInt32(this StringSegment value) => (int)ParseSignedInteger(value, Int32.MaxValue, Int32.MinValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ParseUInt32(this StringSegment value) => (uint)ParseUnsignedInteger(value, UInt32.MaxValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long ParseInt64(this StringSegment value) => ParseSignedInteger(value, Int64.MaxValue, Int64.MinValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ParseUInt64(this StringSegment value) => ParseUnsignedInteger(value, UInt64.MaxValue);
public static ulong ParseUnsignedInteger(this StringSegment value, ulong maxValue)
{
if (value.Length == 0)
throw new FormatException(BadFormat);
ulong result = 0;
int i = value.Offset;
int end = value.Offset + value.Length;
var state = ParseState.LeadingWhite;
//skip leading whitespaces
while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++;
if (i == end)
throw new FormatException(BadFormat);
//skip leading zeros
while (i < end && value.Buffer[i] == '0')
{
state = ParseState.Number;
i++;
}
while (i < end)
{
var c = value.Buffer[i++];
switch (state)
{
case ParseState.LeadingWhite:
if (JsonUtils.IsWhiteSpace(c))
break;
if (c == '0')
{
state = ParseState.TrailingWhite;
}
else if (c > '0' && c <= '9')
{
result = (ulong)(c - '0');
state = ParseState.Number;
}
else throw new FormatException(BadFormat);
break;
case ParseState.Number:
if (c >= '0' && c <= '9')
{
checked
{
result = 10 * result + (ulong)(c - '0');
}
if (result > maxValue) //check only minvalue, because in absolute value it's greater than maxvalue
throw CreateOverflowException(maxValue);
}
else if (JsonUtils.IsWhiteSpace(c))
{
state = ParseState.TrailingWhite;
}
else throw new FormatException(BadFormat);
break;
case ParseState.TrailingWhite:
if (JsonUtils.IsWhiteSpace(c))
{
state = ParseState.TrailingWhite;
}
else throw new FormatException(BadFormat);
break;
}
}
if (state != ParseState.Number && state != ParseState.TrailingWhite)
throw new FormatException(BadFormat);
return result;
}
public static long ParseSignedInteger(this StringSegment value, long maxValue, long minValue)
{
if (value.Buffer == null)
throw new ArgumentNullException(nameof(value));
if (value.Length == 0)
throw new FormatException(BadFormat);
long result = 0;
int i = value.Offset;
int end = value.Offset + value.Length;
var state = ParseState.LeadingWhite;
bool negative = false;
//skip leading whitespaces
while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++;
if (i == end)
throw new FormatException(BadFormat);
//skip leading zeros
while (i < end && value.Buffer[i] == '0')
{
state = ParseState.Number;
i++;
}
while (i < end)
{
var c = value.Buffer[i++];
switch (state)
{
case ParseState.LeadingWhite:
if (c == '-')
{
negative = true;
state = ParseState.Sign;
}
else if (c == '0')
{
state = ParseState.TrailingWhite;
}
else if (c > '0' && c <= '9')
{
result = -(c - '0');
state = ParseState.Number;
}
else throw new FormatException(BadFormat);
break;
case ParseState.Sign:
if (c == '0')
{
state = ParseState.TrailingWhite;
}
else if (c > '0' && c <= '9')
{
result = -(c - '0');
state = ParseState.Number;
}
else throw new FormatException(BadFormat);
break;
case ParseState.Number:
if (c >= '0' && c <= '9')
{
checked
{
result = 10 * result - (c - '0');
}
if (result < minValue) //check only minvalue, because in absolute value it's greater than maxvalue
throw CreateOverflowException(maxValue);
}
else if (JsonUtils.IsWhiteSpace(c))
{
state = ParseState.TrailingWhite;
}
else throw new FormatException(BadFormat);
break;
case ParseState.TrailingWhite:
if (JsonUtils.IsWhiteSpace(c))
{
state = ParseState.TrailingWhite;
}
else throw new FormatException(BadFormat);
break;
}
}
if (state != ParseState.Number && state != ParseState.TrailingWhite)
throw new FormatException(BadFormat);
if (negative)
return result;
checked
{
result = -result;
}
if (result > maxValue)
throw CreateOverflowException(maxValue);
return result;
}
public static object ParseSignedInteger(this StringSegment value)
{
var longValue = value.ParseInt64();
if (longValue >= int.MinValue && longValue <= int.MaxValue)
return (int)longValue;
return longValue;
}
public static decimal ParseDecimal(this StringSegment value, bool allowThousands = false)
{
if (value.Length == 0)
throw new FormatException(BadFormat);
decimal result = 0;
ulong preResult = 0;
bool isLargeNumber = false;
int i = value.Offset;
int end = i + value.Length;
var state = ParseState.LeadingWhite;
bool negative = false;
bool noIntegerPart = false;
sbyte scale = 0;
while (i < end)
{
var c = value.Buffer[i++];
switch (state)
{
case ParseState.LeadingWhite:
if (JsonUtils.IsWhiteSpace(c))
break;
if (c == '-')
{
negative = true;
state = ParseState.Sign;
}
else if (c == '.')
{
noIntegerPart = true;
state = ParseState.FractionNumber;
if (i == end)
throw new FormatException(BadFormat);
}
else if (c == '0')
{
state = ParseState.DecimalPoint;
}
else if (c > '0' && c <= '9')
{
preResult = (ulong)(c - '0');
state = ParseState.Number;
}
else throw new FormatException(BadFormat);
break;
case ParseState.Sign:
if (c == '.')
{
noIntegerPart = true;
state = ParseState.FractionNumber;
if (i == end)
throw new FormatException(BadFormat);
}
else if (c == '0')
{
state = ParseState.DecimalPoint;
}
else if (c > '0' && c <= '9')
{
preResult = (ulong)(c - '0');
state = ParseState.Number;
}
else throw new FormatException(BadFormat);
break;
case ParseState.Number:
if (c == '.')
{
state = ParseState.FractionNumber;
}
else if (c >= '0' && c <= '9')
{
if (isLargeNumber)
{
checked
{
result = 10 * result + (c - '0');
}
}
else
{
preResult = 10 * preResult + (ulong)(c - '0');
if (preResult > ulong.MaxValue / 10 - 10)
{
isLargeNumber = true;
result = preResult;
}
}
}
else if (JsonUtils.IsWhiteSpace(c))
{
state = ParseState.TrailingWhite;
}
else if (allowThousands && c == ',')
{
}
else throw new FormatException(BadFormat);
break;
case ParseState.DecimalPoint:
if (c == '.')
{
state = ParseState.FractionNumber;
}
else throw new FormatException(BadFormat);
break;
case ParseState.FractionNumber:
if (JsonUtils.IsWhiteSpace(c))
{
if (noIntegerPart)
throw new FormatException(BadFormat);
state = ParseState.TrailingWhite;
}
else if (c == 'e' || c == 'E')
{
if (noIntegerPart && scale == 0)
throw new FormatException(BadFormat);
state = ParseState.Exponent;
}
else if (c >= '0' && c <= '9')
{
if (isLargeNumber)
{
checked
{
result = 10 * result + (c - '0');
}
}
else
{
preResult = 10 * preResult + (ulong)(c - '0');
if (preResult > ulong.MaxValue / 10 - 10)
{
isLargeNumber = true;
result = preResult;
}
}
scale++;
}
else throw new FormatException(BadFormat);
break;
case ParseState.Exponent:
bool expNegative = false;
if (c == '-')
{
if (i == end)
throw new FormatException(BadFormat);
expNegative = true;
c = value.Buffer[i++];
}
else if (c == '+')
{
if (i == end)
throw new FormatException(BadFormat);
c = value.Buffer[i++];
}
//skip leading zeroes
while (c == '0' && i < end) c = value.Buffer[i++];
if (c > '0' && c <= '9')
{
var exp = new StringSegment(value.Buffer, i - 1, end - i + 1).ParseSByte();
if (!expNegative)
{
exp = (sbyte)-exp;
}
if (exp >= 0 || scale > -exp)
{
scale += exp;
}
else
{
for (int j = 0; j < -exp - scale; j++)
{
if (isLargeNumber)
{
checked
{
result = 10 * result;
}
}
else
{
preResult = 10 * preResult;
if (preResult > ulong.MaxValue / 10)
{
isLargeNumber = true;
result = preResult;
}
}
}
scale = 0;
}
//set i to end of string, because ParseInt16 eats number and all trailing whites
i = end;
}
else throw new FormatException(BadFormat);
break;
case ParseState.TrailingWhite:
if (!JsonUtils.IsWhiteSpace(c))
throw new FormatException(BadFormat);
break;
}
}
if (!isLargeNumber)
{
var mid = (int)(preResult >> 32);
var lo = (int)(preResult & 0xffffffff);
result = new decimal(lo, mid, 0, negative, (byte)scale);
}
else
{
var bits = decimal.GetBits(result);
result = new decimal(bits[0], bits[1], bits[2], negative, (byte)scale);
}
return result;
}
private static readonly byte[] lo16 = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 0, 1,
2, 3, 4, 5, 6, 7, 8, 9, 255, 255,
255, 255, 255, 255, 255, 10, 11, 12, 13, 14,
15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 10, 11, 12,
13, 14, 15
};
private static readonly byte[] hi16 = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 0, 16,
32, 48, 64, 80, 96, 112, 128, 144, 255, 255,
255, 255, 255, 255, 255, 160, 176, 192, 208, 224,
240, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 160, 176, 192,
208, 224, 240
};
public static Guid ParseGuid(this StringSegment value)
{
if (value.Buffer == null)
throw new ArgumentNullException();
if (value.Length == 0)
throw new FormatException(BadFormat);
//Guid can be in one of 3 forms:
//1. General `{dddddddd-dddd-dddd-dddd-dddddddddddd}` or `(dddddddd-dddd-dddd-dddd-dddddddddddd)` 8-4-4-4-12 chars
//2. Hex `{0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}` 8-4-4-8x2 chars
//3. No style `dddddddddddddddddddddddddddddddd` 32 chars
int i = value.Offset;
int end = value.Offset + value.Length;
while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++;
if (i == end)
throw new FormatException(BadFormat);
var result = ParseGeneralStyleGuid(new StringSegment(value.Buffer, i, end - i), out var guidLen);
i += guidLen;
while (i < end && JsonUtils.IsWhiteSpace(value.Buffer[i])) i++;
if (i < end)
throw new FormatException(BadFormat);
return result;
}
private static Guid ParseGeneralStyleGuid(StringSegment value, out int len)
{
var buf = value.Buffer;
var n = value.Offset;
int dash = 0;
len = 32;
bool hasParentesis = false;
if (value.Length < len)
throw new FormatException(BadFormat);
var cs = value.GetChar(0);
if (cs == '{' || cs == '(')
{
n++;
len += 2;
hasParentesis = true;
if (buf[8 + n] != '-')
throw new FormatException(BadFormat);
}
if (buf[8 + n] == '-')
{
if (buf[13 + n] != '-'
|| buf[18 + n] != '-'
|| buf[23 + n] != '-')
throw new FormatException(BadFormat);
len += 4;
dash = 1;
}
if (value.Length < len)
throw new FormatException(BadFormat);
if (hasParentesis)
{
var ce = buf[value.Offset + len - 1];
if ((cs != '{' || ce != '}') && (cs != '(' || ce != ')'))
throw new FormatException(BadFormat);
}
int a;
short b, c;
byte d, e, f, g, h, i, j, k;
byte a1 = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
byte a2 = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
byte a3 = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
byte a4 = ParseHexByte(buf[n], buf[n + 1]);
a = (a1 << 24) + (a2 << 16) + (a3 << 8) + a4;
n += 2 + dash;
byte b1 = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
byte b2 = ParseHexByte(buf[n], buf[n + 1]);
b = (short)((b1 << 8) + b2);
n += 2 + dash;
byte c1 = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
byte c2 = ParseHexByte(buf[n], buf[n + 1]);
c = (short)((c1 << 8) + c2);
n += 2 + dash;
d = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
e = ParseHexByte(buf[n], buf[n + 1]);
n += 2 + dash;
f = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
g = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
h = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
i = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
j = ParseHexByte(buf[n], buf[n + 1]);
n += 2;
k = ParseHexByte(buf[n], buf[n + 1]);
return new Guid(a, b, c, d, e, f, g, h, i, j, k);
}
private static byte ParseHexByte(char c1, char c2)
{
try
{
byte lo = lo16[c2];
byte hi = hi16[c1];
if (lo == 255 || hi == 255)
throw new FormatException(BadFormat);
return (byte)(hi + lo);
}
catch (IndexOutOfRangeException)
{
throw new FormatException(BadFormat);
}
}
private static readonly char[] CRLF = {'\r', '\n'};
public static bool TryReadLine(this StringSegment text, out StringSegment line, ref int startIndex)
{
if (startIndex >= text.Length)
{
line = EmptyStringSegment;
return false;
}
var nextLinePos = text.IndexOfAny(CRLF, startIndex);
if (nextLinePos == -1)
{
var nextLine = text.Subsegment(startIndex, text.Length - startIndex);
startIndex = text.Length;
line = nextLine;
return true;
}
else
{
var nextLine = text.Subsegment(startIndex, nextLinePos - startIndex);
startIndex = nextLinePos + 1;
if (text.GetChar(nextLinePos) == '\r' && text.Length > nextLinePos + 1 && text.GetChar(nextLinePos + 1) == '\n')
startIndex += 1;
line = nextLine;
return true;
}
}
public static bool TryReadPart(this StringSegment text, string needle, out StringSegment part, ref int startIndex)
{
if (startIndex >= text.Length)
{
part = EmptyStringSegment;
return false;
}
var nextPartPos = text.IndexOf(needle, startIndex);
if (nextPartPos == -1)
{
var nextPart = text.Subsegment(startIndex, text.Length - startIndex);
startIndex = text.Length;
part = nextPart;
return true;
}
else
{
var nextPart = text.Subsegment(startIndex, nextPartPos - startIndex);
startIndex = nextPartPos + needle.Length;
part = nextPart;
return true;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOf(this StringSegment text, string needle) => text.IndexOf(needle, 0, text.Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOf(this StringSegment text, string needle, int start) => text.IndexOf(needle, start, text.Length - start);
public static int IndexOf(this StringSegment text, string needle, int start, int count)
{
if (start < 0 || text.Offset + start > text.Buffer.Length)
throw new ArgumentOutOfRangeException(nameof(start));
if (count < 0 || text.Offset + start + count > text.Buffer.Length)
throw new ArgumentOutOfRangeException(nameof(count));
int num = text.Buffer.IndexOf(needle, start + text.Offset, count, StringComparison.Ordinal);
if (num != -1)
return num - text.Offset;
return num;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOf(this StringSegment text, char needle) => text.LastIndexOf(needle, text.Length - 1, text.Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOf(this StringSegment text, char needle, int start) => text.LastIndexOf(needle, start, start + 1);
public static int LastIndexOf(this StringSegment text, char needle, int start, int count)
{
if (text.Length == 0)
return -1;
if (start < 0 || start >= text.Length)
throw new ArgumentOutOfRangeException(nameof(start));
if (count < 0 || count - 1 > start)
throw new ArgumentOutOfRangeException(nameof(count));
int num = text.Buffer.LastIndexOf(needle, start + text.Offset, count);
if (num != -1)
return num - text.Offset;
return num;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOf(this StringSegment text, string needle) => text.LastIndexOf(needle, text.Length - 1, text.Length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOf(this StringSegment text, string needle, int start) => text.LastIndexOf(needle, start, start + 1);
public static int LastIndexOf(this StringSegment text, string needle, int start, int count)
{
if (text.Length == 0)
return -1;
if (start < 0 || start >= text.Length)
throw new ArgumentOutOfRangeException(nameof(start));
if (count < 0 || count - 1 > start)
throw new ArgumentOutOfRangeException(nameof(count));
int num = text.Buffer.LastIndexOf(needle, start + text.Offset, count, StringComparison.Ordinal);
if (num != -1)
return num - text.Offset;
return num;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringSegment Advance(this StringSegment text, int to) => text.Subsegment(to, text.Length - to);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringSegment Subsegment(this StringSegment text, int startPos) => text.Subsegment(startPos, text.Length - startPos);
public static StringSegment LeftPart(this StringSegment strVal, char needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(0, pos);
}
public static StringSegment LeftPart(this StringSegment strVal, string needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(0, pos);
}
public static StringSegment RightPart(this StringSegment strVal, char needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(pos + 1);
}
public static StringSegment RightPart(this StringSegment strVal, string needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(pos + needle.Length);
}
public static StringSegment LastLeftPart(this StringSegment strVal, char needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.LastIndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(0, pos);
}
public static StringSegment LastLeftPart(this StringSegment strVal, string needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.LastIndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(0, pos);
}
public static StringSegment LastRightPart(this StringSegment strVal, char needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.LastIndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(pos + 1);
}
public static StringSegment LastRightPart(this StringSegment strVal, string needle)
{
if (!strVal.HasValue) return strVal;
var pos = strVal.LastIndexOf(needle);
return pos == -1
? strVal
: strVal.Subsegment(pos + needle.Length);
}
public static StringSegment[] SplitOnFirst(this StringSegment strVal, char needle)
{
if (!strVal.HasValue) return EmptyStringSegmentArray;
var pos = strVal.IndexOf(needle);
return pos == -1
? new[] { strVal }
: new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + 1) };
}
public static StringSegment[] SplitOnFirst(this StringSegment strVal, string needle)
{
if (!strVal.HasValue) return EmptyStringSegmentArray;
var pos = strVal.IndexOf(needle);
return pos == -1
? new[] { strVal }
: new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + needle.Length) };
}
public static StringSegment[] SplitOnLast(this StringSegment strVal, char needle)
{
if (!strVal.HasValue) return EmptyStringSegmentArray;
var pos = strVal.LastIndexOf(needle);
return pos == -1
? new[] { strVal }
: new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + 1) };
}
public static StringSegment[] SplitOnLast(this StringSegment strVal, string needle)
{
if (!strVal.HasValue) return EmptyStringSegmentArray;
var pos = strVal.LastIndexOf(needle);
return pos == -1
? new[] { strVal }
: new[] { strVal.Subsegment(0, pos), strVal.Subsegment(pos + needle.Length) };
}
public static StringSegment WithoutExtension(this StringSegment filePath)
{
if (filePath.IsNullOrEmpty())
return EmptyStringSegment;
var extPos = filePath.LastIndexOf('.');
if (extPos == -1) return filePath;
var dirPos = filePath.LastIndexOfAny(PclExport.DirSeps);
return extPos > dirPos ? filePath.Subsegment(0, extPos) : filePath;
}
public static StringSegment GetExtension(this StringSegment filePath)
{
if (filePath.IsNullOrEmpty())
return EmptyStringSegment;
var extPos = filePath.LastIndexOf('.');
return extPos == -1 ? EmptyStringSegment : filePath.Subsegment(extPos);
}
public static StringSegment ParentDirectory(this StringSegment filePath)
{
if (filePath.IsNullOrEmpty())
return EmptyStringSegment;
var dirSep = filePath.IndexOf(PclExport.Instance.DirSep) != -1
? PclExport.Instance.DirSep
: filePath.IndexOf(PclExport.Instance.AltDirSep) != -1
? PclExport.Instance.AltDirSep
: (char)0;
return dirSep == 0 ? EmptyStringSegment : filePath.TrimEnd(dirSep).SplitOnLast(dirSep)[0];
}
public static StringSegment TrimEnd(this StringSegment value, params char[] trimChars)
{
if (trimChars == null || trimChars.Length == 0)
return value.TrimHelper(1);
return value.TrimHelper(trimChars, 1);
}
private static StringSegment TrimHelper(this StringSegment value, int trimType)
{
int end = value.Length - 1;
int start = 0;
if (trimType != 1)
{
start = 0;
while (start < value.Length && char.IsWhiteSpace(value.GetChar(start)))
++start;
}
if (trimType != 0)
{
end = value.Length - 1;
while (end >= start && char.IsWhiteSpace(value.GetChar(end)))
--end;
}
return value.CreateTrimmedString(start, end);
}
private static StringSegment TrimHelper(this StringSegment value, char[] trimChars, int trimType)
{
int end = value.Length - 1;
int start = 0;
if (trimType != 1)
{
for (start = 0; start < value.Length; ++start)
{
char ch = value.GetChar(start);
int index = 0;
while (index < trimChars.Length && (int)trimChars[index] != (int)ch)
++index;
if (index == trimChars.Length)
break;
}
}
if (trimType != 0)
{
for (end = value.Length - 1; end >= start; --end)
{
char ch = value.GetChar(end);
int index = 0;
while (index < trimChars.Length && (int)trimChars[index] != (int)ch)
++index;
if (index == trimChars.Length)
break;
}
}
return value.CreateTrimmedString(start, end);
}
private static StringSegment CreateTrimmedString(this StringSegment value, int start, int end)
{
int length = end - start + 1;
if (length == value.Length)
return value;
if (length == 0)
return EmptyStringSegment;
return value.Subsegment(start, length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWith(this StringSegment text, string value) => text.StartsWith(value, StringComparison.Ordinal);
public static bool StartsWith(this StringSegment text, string value, StringComparison comparisonType)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
var textLength = value.Length;
if (!text.HasValue || text.Length < textLength)
return false;
return string.Compare(text.Buffer, text.Offset, value, 0, textLength, comparisonType) == 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWith(this StringSegment text, string value) => text.EndsWith(value, StringComparison.Ordinal);
public static bool EndsWith(this StringSegment text, string value, StringComparison comparisonType)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
var textLength = value.Length;
if (!text.HasValue || text.Length < textLength)
return false;
return string.Compare(text.Buffer, text.Offset + text.Length - textLength, value, 0, textLength, comparisonType) == 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringSegment SafeSubsegment(this StringSegment value, int startIndex) => SafeSubsegment(value, startIndex, value.Length);
public static StringSegment SafeSubsegment(this StringSegment value, int startIndex, int length)
{
if (IsNullOrEmpty(value)) return EmptyStringSegment;
if (startIndex < 0) startIndex = 0;
if (value.Length >= startIndex + length)
return value.Subsegment(startIndex, length);
return value.Length > startIndex ? value.Subsegment(startIndex) : EmptyStringSegment;
}
[Obsolete("typo")]
public static string SubstringWithElipsis(this StringSegment value, int startIndex, int length) => SubstringWithEllipsis(value, startIndex, length);
public static string SubstringWithEllipsis(this StringSegment value, int startIndex, int length)
{
var str = value.SafeSubsegment(startIndex, length);
return str.Length == length
? str.Value + "..."
: str.Value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsIgnoreCase(this StringSegment value, string other) => value.Equals(other, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWithIgnoreCase(this StringSegment value, string other) => value.StartsWith(other, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWithIgnoreCase(this StringSegment value, string other) => value.EndsWith(other, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] ToUtf8Bytes(this StringSegment value) => Encoding.UTF8.GetBytes(value.Buffer.ToCharArray(value.Offset, value.Length));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task WriteAsync(this Stream stream, StringSegment value, CancellationToken token = default(CancellationToken))
{
var bytes = value.ToUtf8Bytes();
return stream.WriteAsync(bytes, 0, bytes.Length, token);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringSegment SafeSubstring(this StringSegment value, int startIndex) => SafeSubstring(value, startIndex, value.Length);
public static StringSegment SafeSubstring(this StringSegment value, int startIndex, int length)
{
if (value.IsNullOrEmpty()) return EmptyStringSegment;
if (startIndex < 0) startIndex = 0;
if (value.Length >= (startIndex + length))
return value.Subsegment(startIndex, length);
return value.Length > startIndex ? value.Subsegment(startIndex) : EmptyStringSegment;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<string> ToStringList(this IEnumerable<ReadOnlyMemory<char>> from)
{
var to = new List<string>();
if (from != null)
{
foreach (var item in from)
{
to.Add(item.ToString());
}
}
return to;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.