Amend Navigation
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Toolkit.Framework.Foundation;
|
||||
|
||||
public struct KeyValueAccumulator
|
||||
{
|
||||
private Dictionary<string, StringValues> accumulator;
|
||||
private Dictionary<string, List<string>> expandingAccumulator;
|
||||
|
||||
public void Append(string key, string value)
|
||||
{
|
||||
accumulator ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (accumulator.TryGetValue(key, out StringValues values))
|
||||
{
|
||||
if (values.Count == 0)
|
||||
{
|
||||
expandingAccumulator[key].Add(value);
|
||||
}
|
||||
else if (values.Count == 1)
|
||||
{
|
||||
accumulator[key] = new string[] { values[0]!, value };
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulator[key] = default;
|
||||
|
||||
expandingAccumulator ??= new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
List<string> list = new(8);
|
||||
string?[] array = values.ToArray();
|
||||
|
||||
list.Add(array[0]!);
|
||||
list.Add(array[1]!);
|
||||
list.Add(value);
|
||||
|
||||
expandingAccumulator[key] = list;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulator[key] = new StringValues(value);
|
||||
}
|
||||
|
||||
ValueCount++;
|
||||
}
|
||||
|
||||
public bool HasValues => ValueCount > 0;
|
||||
|
||||
public int KeyCount => accumulator?.Count ?? 0;
|
||||
|
||||
public int ValueCount { get; private set; }
|
||||
|
||||
public Dictionary<string, StringValues> GetResults()
|
||||
{
|
||||
if (expandingAccumulator != null)
|
||||
{
|
||||
foreach (var entry in expandingAccumulator)
|
||||
{
|
||||
accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
return accumulator ?? new Dictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Toolkit.Framework.Foundation;
|
||||
|
||||
public static class QueryHelpers
|
||||
{
|
||||
public static Dictionary<string, StringValues>? ParseNullableQuery(string? queryString)
|
||||
{
|
||||
KeyValueAccumulator accumulator = new();
|
||||
QueryStringEnumerable enumerable = new(queryString);
|
||||
|
||||
foreach (QueryStringEnumerable.EncodedNameValuePair pair in enumerable)
|
||||
{
|
||||
accumulator.Append(pair.DecodeName().ToString(), pair.DecodeValue().ToString());
|
||||
}
|
||||
|
||||
if (!accumulator.HasValues)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return accumulator.GetResults();
|
||||
}
|
||||
|
||||
public static Dictionary<string, StringValues> ParseQuery(string? queryString)
|
||||
{
|
||||
Dictionary<string, StringValues>? result = ParseNullableQuery(queryString);
|
||||
if (result == null)
|
||||
{
|
||||
return new Dictionary<string, StringValues>();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
readonly struct QueryStringEnumerable
|
||||
{
|
||||
private readonly ReadOnlyMemory<char> queryString;
|
||||
|
||||
public QueryStringEnumerable(string? queryString) : this(queryString.AsMemory())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public QueryStringEnumerable(ReadOnlyMemory<char> queryString)
|
||||
{
|
||||
this.queryString = queryString;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new(queryString);
|
||||
|
||||
public readonly struct EncodedNameValuePair
|
||||
{
|
||||
internal EncodedNameValuePair(ReadOnlyMemory<char> encodedName, ReadOnlyMemory<char> encodedValue)
|
||||
{
|
||||
EncodedName = encodedName;
|
||||
EncodedValue = encodedValue;
|
||||
}
|
||||
|
||||
public readonly ReadOnlyMemory<char> EncodedName { get; }
|
||||
|
||||
public readonly ReadOnlyMemory<char> EncodedValue { get; }
|
||||
|
||||
public ReadOnlyMemory<char> DecodeName() => Decode(EncodedName);
|
||||
|
||||
public ReadOnlyMemory<char> DecodeValue() => Decode(EncodedValue);
|
||||
|
||||
private static ReadOnlyMemory<char> Decode(ReadOnlyMemory<char> chars)
|
||||
{
|
||||
return chars.Length < 16 && chars.Span.IndexOfAny('%', '+') < 0 ? chars : Uri.UnescapeDataString(SpanHelper.ReplacePlusWithSpace(chars.Span)).AsMemory();
|
||||
}
|
||||
}
|
||||
|
||||
public struct Enumerator
|
||||
{
|
||||
private ReadOnlyMemory<char> query;
|
||||
|
||||
internal Enumerator(ReadOnlyMemory<char> query)
|
||||
{
|
||||
Current = default;
|
||||
this.query = query.IsEmpty || query.Span[0] != '?' ? query : query[1..];
|
||||
}
|
||||
|
||||
public EncodedNameValuePair Current { get; private set; }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (!query.IsEmpty)
|
||||
{
|
||||
ReadOnlyMemory<char> segment;
|
||||
var delimiterIndex = query.Span.IndexOf('&');
|
||||
if (delimiterIndex >= 0)
|
||||
{
|
||||
segment = query[..delimiterIndex];
|
||||
query = query[(delimiterIndex + 1)..];
|
||||
}
|
||||
else
|
||||
{
|
||||
segment = query;
|
||||
query = default;
|
||||
}
|
||||
|
||||
int equalIndex = segment.Span.IndexOf('=');
|
||||
if (equalIndex >= 0)
|
||||
{
|
||||
Current = new EncodedNameValuePair(segment[..equalIndex], segment[(equalIndex + 1)..]);
|
||||
return true;
|
||||
}
|
||||
else if (!segment.IsEmpty)
|
||||
{
|
||||
Current = new EncodedNameValuePair(segment, default);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Current = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpanHelper
|
||||
{
|
||||
private static readonly SpanAction<char, IntPtr> replacePlusWithSpace = ReplacePlusWithSpaceCore;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe string ReplacePlusWithSpace(ReadOnlySpan<char> span)
|
||||
{
|
||||
fixed (char* ptr = &MemoryMarshal.GetReference(span))
|
||||
{
|
||||
return string.Create(span.Length, (IntPtr)ptr, replacePlusWithSpace);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ReplacePlusWithSpaceCore(Span<char> buffer, IntPtr state)
|
||||
{
|
||||
fixed (char* ptr = &MemoryMarshal.GetReference(buffer))
|
||||
{
|
||||
ushort* input = (ushort*)state.ToPointer();
|
||||
ushort* output = (ushort*)ptr;
|
||||
|
||||
nint i = 0;
|
||||
nint n = (nint)(uint)buffer.Length;
|
||||
|
||||
if (Vector256.IsHardwareAccelerated && n >= Vector256<ushort>.Count)
|
||||
{
|
||||
Vector256<ushort> vecPlus = Vector256.Create((ushort)'+');
|
||||
Vector256<ushort> vecSpace = Vector256.Create((ushort)' ');
|
||||
|
||||
do
|
||||
{
|
||||
Vector256<ushort> vec = Vector256.Load(input + i);
|
||||
Vector256<ushort> mask = Vector256.Equals(vec, vecPlus);
|
||||
Vector256<ushort> res = Vector256.ConditionalSelect(mask, vecSpace, vec);
|
||||
res.Store(output + i);
|
||||
i += Vector256<ushort>.Count;
|
||||
} while (i <= n - Vector256<ushort>.Count);
|
||||
}
|
||||
|
||||
if (Vector128.IsHardwareAccelerated && n - i >= Vector128<ushort>.Count)
|
||||
{
|
||||
do
|
||||
{
|
||||
Vector128<ushort> vec = Vector128.Load(input + i);
|
||||
Vector128<ushort> vecPlus = Vector128.Create((ushort)'+');
|
||||
Vector128<ushort> mask = Vector128.Equals(vec, vecPlus);
|
||||
Vector128<ushort> vecSpace = Vector128.Create((ushort)' ');
|
||||
Vector128<ushort> res = Vector128.ConditionalSelect(mask, vecSpace, vec);
|
||||
res.Store(output + i);
|
||||
i += Vector128<ushort>.Count;
|
||||
} while (i <= n - Vector128<ushort>.Count);
|
||||
}
|
||||
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (input[i] != '+')
|
||||
{
|
||||
output[i] = input[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
output[i] = ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user