Last active
July 1, 2018 20:43
-
-
Save mythz/9825689f0db7464d1d541cb62954614c to your computer and use it in GitHub Desktop.
Removed StringSegmentExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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