Skip to content

Commit

Permalink
Validated objects switched from byte[] to ReadOnlyMemory<byte>, remov…
Browse files Browse the repository at this point in the history
…ing null checks and reducing complexity.
  • Loading branch information
RyanLamansky committed Aug 3, 2024
1 parent 6fca665 commit efe4579
Show file tree
Hide file tree
Showing 15 changed files with 85 additions and 127 deletions.
8 changes: 0 additions & 8 deletions HtmlUtilities.Tests/Validated/ValidatedAttributeNameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@

public static class ValidatedAttributeNameTests
{
[Fact]
public static void ValidatedAttributeNameUnconstructedThrows()
{
var array = new ValidatedAttributeName[1];

Assert.Throws<InvalidOperationException>(array[0].ToString);
}

[Theory]
[InlineData("")]
[InlineData(" ")]
Expand Down
8 changes: 0 additions & 8 deletions HtmlUtilities.Tests/Validated/ValidatedElementNameTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,6 @@

public static class ValidatedElementNameTests
{
[Fact]
public static void ValidatedElementNameUnconstructedThrows()
{
var array = new ValidatedElementName[1];

Assert.Throws<InvalidOperationException>(array[0].ToString);
}

[Theory]
[InlineData("")]
[InlineData(" ")]
Expand Down
14 changes: 0 additions & 14 deletions HtmlUtilities.Tests/Validated/ValidatedElementTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,6 @@

public static class ValidatedElementTests
{
[Fact]
public static void ValidatedElementUnconstructedThrows()
{
var array = new ValidatedElement[1];

Assert.Throws<InvalidOperationException>(array[0].ToString);
}

[Fact]
public static void ValidatedElementThrowsForUnconstructedElementName()
{
var array = new ValidatedElementName[1];
Assert.Equal("name", Assert.Throws<ArgumentException>(() => new ValidatedElement(array[0])).ParamName);
}

[Fact]
public static void ValidatedElementDiscardsUnconstructedAttributes()
Expand Down
7 changes: 5 additions & 2 deletions HtmlUtilities/ArrayBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,18 @@ public void Write(ReadOnlySpan<T> values)
written += length;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlyMemory<T> values) => Write(values.Span);

internal readonly T[] Buffer => buffer;

internal readonly ReadOnlyMemory<T> WrittenMemory => buffer.AsMemory(0, written);

public readonly ReadOnlySpan<T> WrittenSpan => buffer.AsSpan(0, written);

public readonly T[] ToArray() => WrittenSpan.ToArray();

public readonly void Release() => ArrayPool<T>.Shared.Return(buffer);

public static implicit operator ReadOnlyMemory<T>(ArrayBuilder<T> builder) => builder.WrittenSpan.ToArray();
}

internal static class ArrayBuilderExtensions
Expand Down
18 changes: 3 additions & 15 deletions HtmlUtilities/AttributeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,20 @@ public readonly struct AttributeWriter
{
private readonly IBufferWriter<byte> writer;

internal AttributeWriter(IBufferWriter<byte> writer)
{
System.Diagnostics.Debug.Assert(writer is not null);

this.writer = writer;
}
internal AttributeWriter(IBufferWriter<byte> writer) => this.writer = writer;

/// <summary>
/// Writes a validated attribute name with no value.
/// </summary>
/// <param name="name">The validated attribute name to write.</param>
/// <exception cref="ArgumentException"><paramref name="name"/> was never initialized.</exception>
public void Write(ValidatedAttributeName name)
{
writer.Write(name.value ?? throw new ArgumentException("name was never initialized.", nameof(name)));
}
public void Write(ValidatedAttributeName name) => writer.Write(name.value);

/// <summary>
/// Writes a validated attribute.
/// </summary>
/// <param name="attribute">The validated attribute to write.</param>
/// <exception cref="ArgumentException"><paramref name="attribute"/> was never initialized.</exception>
public void Write(ValidatedAttribute attribute)
{
writer.Write(attribute.value ?? throw new ArgumentException("attribute was never initialized.", nameof(attribute)));
}
public void Write(ValidatedAttribute attribute) => writer.Write(attribute.value);

/// <summary>
/// Writes an attribute that consists only of a name, no value.
Expand Down
13 changes: 13 additions & 0 deletions HtmlUtilities/DotNetExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace HtmlUtilities;

/// <summary>
/// Adds functionality I wish were included in the base .NET code.
/// </summary>
internal static class DotNetExtensions
{
public static string GetString(this System.Text.Encoding encoding, ReadOnlyMemory<byte> bytes)
=> encoding.GetString(bytes.Span);

public static void Write<T>(this System.Buffers.IBufferWriter<T> writer, ReadOnlyMemory<T> value)
=> System.Buffers.BuffersExtensions.Write(writer, value.Span);
}
8 changes: 4 additions & 4 deletions HtmlUtilities/HtmlDocumentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,24 @@ public static Task WriteToAsync(this IHtmlDocument document, HttpContext context

writer.Write("<!DOCTYPE html><html"u8);

byte[]? validated;
ReadOnlyMemory<byte> validated;

if ((validated = document.Language.value) is not null)
if (!(validated = document.Language.value).IsEmpty)
{
writer.Write(" lang"u8);
writer.Write(validated);
}

writer.Write("><head><meta charset=utf-8><meta name=viewport content=\"width=device-width, initial-scale=1\">"u8);

if ((validated = document.Title.value) is not null)
if (!(validated = document.Title.value).IsEmpty)
{
writer.Write("<title>"u8);
writer.Write(validated);
writer.Write("</title>"u8);
}

if ((validated = document.Description.value) is not null)
if (!(validated = document.Description.value).IsEmpty)
{
writer.Write("<meta name=description content"u8);
writer.Write(validated);
Expand Down
42 changes: 21 additions & 21 deletions HtmlUtilities/HtmlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public void WriteElement(ValidatedElement element)
var start = element.start;
var end = element.end;

if (start is null || end is null)
if (start.IsEmpty)
throw new ArgumentException("element was never initialized.", nameof(element));

writer.Write(start);
Expand Down Expand Up @@ -134,14 +134,14 @@ public void WriteElement(ValidatedElement element, Action<AttributeWriter>? attr
var start = element.start;
var end = element.end;

if (start is null || end is null)
if (start.IsEmpty)
throw new ArgumentException("element was never initialized.", nameof(element));

if (attributes is null)
writer.Write(start);
else
{
writer.Write(element.start.AsSpan(0, start.Length - 1));
writer.Write(start[..^1]);

attributes(new AttributeWriter(this.writer));

Expand Down Expand Up @@ -200,7 +200,10 @@ public void WriteElement(ReadOnlySpan<char> name, Action<AttributeWriter>? attri
/// <exception cref="ArgumentException"><paramref name="element"/> was never initialized.</exception>
public void WriteElementSelfClosing(ValidatedElement element)
{
var start = element.start ?? throw new ArgumentException("element was never initialized.", nameof(element));
var start = element.start;
if (start.IsEmpty)
throw new ArgumentException("element was never initialized.", nameof(element));

this.writer.Write(start);
}

Expand Down Expand Up @@ -236,12 +239,15 @@ public void WriteElementSelfClosing(ReadOnlySpan<char> name)
public void WriteElementSelfClosing(ValidatedElement element, Action<AttributeWriter>? attributes = null)
{
var writer = this.writer;
var start = element.start ?? throw new ArgumentException("element was never initialized.", nameof(element));
var start = element.start;
if (start.IsEmpty)
throw new ArgumentException("element was never initialized.", nameof(element));

if (attributes is null)
writer.Write(start);
else
{
writer.Write(element.start.AsSpan(0, start.Length - 1));
writer.Write(start[..^1]);

attributes(new AttributeWriter(this.writer));

Expand Down Expand Up @@ -284,13 +290,7 @@ public void WriteElementSelfClosing(ReadOnlySpan<char> name, Action<AttributeWri
/// </summary>
/// <param name="text">The text to write.</param>
public void WriteText(ValidatedText text)
{
var value = text.value;
if (value is null)
return;

this.writer.Write(value);
}
=> this.writer.Write(text.value);

/// <summary>
/// Writes text.
Expand All @@ -317,15 +317,15 @@ public void WriteText(ReadOnlySpan<char> text)
/// Writes a pre-validated script element.
/// </summary>
/// <param name="script">The script element to write.</param>
/// <exception cref="ArgumentException"><paramref name="script"/> was never initialized.</exception>
public void WriteScript(ValidatedScript script)
{
if (script.value is null)
throw new ArgumentException("script was never initialized.", nameof(script));
var value = script.value;
if (value.IsEmpty)
return;

writer.Write("<script"u8);
writer.Write(this.cspNonce.value);
writer.Write(script.value);
writer.Write(value);
writer.Write("</script>"u8);
}

Expand All @@ -351,14 +351,14 @@ public async Task WriteElementAsync(
var start = element.start;
var end = element.end;

if (start is null || end is null)
if (start.IsEmpty)
throw new ArgumentException("element was never initialized.", nameof(element));

if (attributes is null)
writer.Write(start);
else
{
writer.Write(start.AsSpan(0, start.Length - 1));
writer.Write(start[..^1]);

attributes(new AttributeWriter(this.writer));

Expand Down Expand Up @@ -415,7 +415,7 @@ public Task WriteElementAsync(
var validatedName = elementNameWriter.WrittenMemory;

WriteLessThan(writer);
writer.Write(validatedName.Span);
writer.Write(validatedName);

if (attributes is not null)
attributes(new AttributeWriter(writer));
Expand All @@ -437,7 +437,7 @@ static async Task WriteElementAsync(

var writer = htmlWriterAsync.writer;
writer.Write("</"u8);
writer.Write(validatedName.Span);
writer.Write(validatedName);
WriteGreaterThan(writer);
}
finally
Expand Down
19 changes: 8 additions & 11 deletions HtmlUtilities/Validated/ValidatedAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace HtmlUtilities.Validated;
public readonly struct ValidatedAttribute
#pragma warning restore
{
internal readonly byte[]? value;
internal readonly ReadOnlyMemory<byte> value;

/// <summary>
/// Creates a new <see cref="ValidatedAttribute"/> from the provided validated name and value.
Expand All @@ -19,18 +19,15 @@ public readonly struct ValidatedAttribute
/// <exception cref="ArgumentException"><paramref name="name"/> was never initialized.</exception>
public ValidatedAttribute(ValidatedAttributeName name, ValidatedAttributeValue value)
{
var nv = name.value ?? throw new ArgumentException("name was never initialized.", nameof(name));
var nv = name.value;
var vv = value.value;
if (vv is null)
{
this.value = nv;
return;
}

var v = this.value = new byte[nv.Length + vv.Length];
var v = new byte[nv.Length + vv.Length];

Array.Copy(nv, v, nv.Length);
Array.Copy(vv, 0, v, nv.Length, vv.Length);
nv.CopyTo(v);
vv.CopyTo(v.AsMemory(nv.Length));

this.value = v;
}

/// <summary>
Expand Down Expand Up @@ -63,7 +60,7 @@ public ValidatedAttribute(ReadOnlySpan<char> name) : this(new ValidatedAttribute
/// Returns a string of this attribute as it would be written.
/// </summary>
/// <returns>A string representation of this value.</returns>
public override string ToString() => value is null ? "" : Encoding.UTF8.GetString(value);
public override string ToString() => Encoding.UTF8.GetString(value);

/// <summary>
/// Creates a new <see cref="ValidatedAttribute"/> from the provided unvalidated name and no value.
Expand Down
7 changes: 3 additions & 4 deletions HtmlUtilities/Validated/ValidatedAttributeName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace HtmlUtilities.Validated;
/// </summary>
public readonly struct ValidatedAttributeName
{
internal readonly byte[]? value;
internal readonly ReadOnlyMemory<byte> value;

/// <summary>
/// Creates a new <see cref="ValidatedAttributeName"/> value from the provided name.
Expand All @@ -20,7 +20,7 @@ public ValidatedAttributeName(ReadOnlySpan<char> name)
try
{
Validate(name, ref writer);
this.value = writer.ToArray();
this.value = writer;
}
finally
{
Expand Down Expand Up @@ -64,8 +64,7 @@ internal static void Validate(ReadOnlySpan<char> name, ref ArrayBuilder<byte> wr
/// Converts the validated name to a string.
/// </summary>
/// <returns>The string representation of the validated name.</returns>
/// <exception cref="InvalidOperationException">This ValidatedAttributeName was never initialized.</exception>
public override string ToString() => Encoding.UTF8.GetString(value ?? throw new InvalidOperationException("This ValidatedAttributeName was never initialized."));
public override string ToString() => Encoding.UTF8.GetString(value);

/// <summary>
/// Creates a new <see cref="ValidatedAttribute"/> from the provided validated name and no value.
Expand Down
6 changes: 3 additions & 3 deletions HtmlUtilities/Validated/ValidatedAttributeValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public readonly struct ValidatedAttributeValue
{
private static readonly byte[] Empty = "=\"\""u8.ToArray();

internal readonly byte[]? value;
internal readonly ReadOnlyMemory<byte> value;

/// <summary>
/// Creates a new <see cref="ValidatedAttributeValue"/> from the provided <see cref="ReadOnlySpan{T}"/> of type <see cref="char"/>.
Expand All @@ -29,7 +29,7 @@ public ValidatedAttributeValue(ReadOnlySpan<char> value)
try
{
Validate(value, ref writer);
this.value = writer.ToArray();
this.value = writer;
}
finally
{
Expand Down Expand Up @@ -302,7 +302,7 @@ private static void EmitQuoted(ReadOnlySpan<char> value, ref ArrayBuilder<byte>
/// Returns a string of this attribute value as it would be written.
/// </summary>
/// <returns>A string representation of this value.</returns>
public override string ToString() => value is null ? "" : Encoding.UTF8.GetString(value);
public override string ToString() => Encoding.UTF8.GetString(value);

/// <summary>
/// Creates a new <see cref="ValidatedAttributeValue"/> from the provided <see cref="ReadOnlySpan{T}"/> of type <see cref="char"/>.
Expand Down
Loading

0 comments on commit efe4579

Please sign in to comment.