Amend Navigation

This commit is contained in:
Daniel Clark
2022-12-22 20:31:25 +00:00
parent 757349285a
commit 894bbbd177
11 changed files with 424 additions and 151 deletions
+1
View File
@@ -6,6 +6,7 @@
<Nullable>enable</Nullable>
<AssemblyName>Toolkit.Framework.Foundation</AssemblyName>
<RootNamespace>Toolkit.Framework.Foundation</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@@ -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] = ' ';
}
}
}
}
}
}
+3 -13
View File
@@ -3,23 +3,13 @@ namespace Toolkit.Framework.Foundation;
public record Navigate : IRequest
{
public Navigate(string name, params object?[] parameters)
public Navigate(string path, params object?[] parameters)
{
Name = name;
Path = path;
Parameters = parameters;
}
public Navigate(Type type, params object?[] parameters)
{
Type = type;
Parameters = parameters;
}
public Type? Type { get; }
public object? Route { get; init; }
public string? Name { get; }
public string? Path { get; }
public object?[] Parameters { get; }
}
@@ -0,0 +1,53 @@
using Microsoft.Extensions.Primitives;
using System.Diagnostics;
namespace Toolkit.Framework.Foundation;
public class NavigationSegment
{
private NavigationSegment(string name, IDictionary<string, StringValues> parameters, string target)
{
Name = name;
Parameters = parameters;
Target = target;
}
public string Name { get; }
public IDictionary<string, StringValues> Parameters { get; }
public string Target { get; }
public static IReadOnlyCollection<NavigationSegment> FromPath(string? path)
{
List<NavigationSegment> result = new();
if (path is null)
{
return result;
}
string[] pathParts = path.Split('?');
string[] segments = pathParts is { Length: >= 1 } ? pathParts[0].Split('/', StringSplitOptions.RemoveEmptyEntries)
: Array.Empty<string>();
string query = pathParts is { Length: 2 } ? pathParts[1] : "";
Dictionary<string, StringValues> parameters = QueryHelpers.ParseQuery(query);
foreach (string segment in segments)
{
string[] segmentParts = segment.Split('#');
string name = segmentParts is { Length: >= 1 } ? segmentParts[0] : "";
string target = segmentParts is { Length: 2 } ? segmentParts[1] : "";
Trace.WriteLine(name);
Trace.WriteLine(target);
result.Add(new NavigationSegment(name, parameters, target));
}
return result;
}
}