Start parting out IO.Extensions a bit more

This commit is contained in:
Matt Nadareski
2026-03-21 21:45:14 -04:00
parent 22a90019a2
commit 6dc531dad7
34 changed files with 1702 additions and 1452 deletions

View File

@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using Xunit;
namespace SabreTools.IO.Extensions.Test
@@ -18,59 +17,6 @@ namespace SabreTools.IO.Extensions.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadNullTerminatedStringTest()
{
// Encoding.ASCII
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.ASCII);
Assert.Equal("ABC", actual);
// Encoding.Latin1
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
// Encoding.UTF8
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.UTF8);
Assert.Equal("ABC", actual);
// Encoding.Unicode
bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.Unicode);
Assert.Equal("ABC", actual);
// Encoding.BigEndianUnicode
bytes = [0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.BigEndianUnicode);
Assert.Equal("ABC", actual);
// Encoding.UTF32
bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.UTF32);
Assert.Equal("ABC", actual);
// Encoding.Latin1
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
br = new BinaryReader(stream);
actual = br.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadTypeTest()
{

View File

@@ -17,78 +17,6 @@ namespace SabreTools.IO.Extensions.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = bw.WriteNullTerminatedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = bw.WriteNullTerminatedUTF8String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = bw.WriteNullTerminatedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = bw.WriteNullTerminatedUTF32String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = bw.WritePrefixedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = bw.WritePrefixedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteTypeTest()
{

View File

@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Numerics;
using System.Text;
using Xunit;
namespace SabreTools.IO.Extensions.Test
@@ -17,52 +16,6 @@ namespace SabreTools.IO.Extensions.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadNullTerminatedStringTest()
{
// Encoding.ASCII
int offset = 0;
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.ASCII);
Assert.Equal("ABC", actual);
// Encoding.Latin1
offset = 0;
bytes = [0x41, 0x42, 0x43, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Latin1);
Assert.Equal("ABC", actual);
// Encoding.UTF8
offset = 0;
bytes = [0x41, 0x42, 0x43, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF8);
Assert.Equal("ABC", actual);
// Encoding.Unicode
offset = 0;
bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Unicode);
Assert.Equal("ABC", actual);
// Encoding.BigEndianUnicode
offset = 0;
bytes = [0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.BigEndianUnicode);
Assert.Equal("ABC", actual);
// Encoding.UTF32
offset = 0;
bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF32);
Assert.Equal("ABC", actual);
// Encoding.Latin1
offset = 0;
bytes = [0x41, 0x42, 0x43, 0x00];
actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadTypeTest()
{

View File

@@ -16,78 +16,6 @@ namespace SabreTools.IO.Extensions.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = buffer.WriteNullTerminatedAnsiString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = buffer.WriteNullTerminatedUTF8String(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
int offset = 0;
byte[] buffer = new byte[8];
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = buffer.WriteNullTerminatedUnicodeString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
int offset = 0;
byte[] buffer = new byte[16];
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = buffer.WriteNullTerminatedUTF32String(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = buffer.WritePrefixedAnsiString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
int offset = 0;
byte[] buffer = new byte[8];
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = buffer.WritePrefixedUnicodeString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteTypeTest()
{

View File

@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
using Xunit;
namespace SabreTools.IO.Extensions.Test
@@ -18,52 +17,6 @@ namespace SabreTools.IO.Extensions.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void ReadNullTerminatedStringTest()
{
// Encoding.ASCII
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.ASCII);
Assert.Equal("ABC", actual);
// Encoding.Latin1
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
// Encoding.UTF8
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.UTF8);
Assert.Equal("ABC", actual);
// Encoding.Unicode
bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.Unicode);
Assert.Equal("ABC", actual);
// Encoding.BigEndianUnicode
bytes = [0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.BigEndianUnicode);
Assert.Equal("ABC", actual);
// Encoding.UTF32
bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.UTF32);
Assert.Equal("ABC", actual);
// Encoding.Latin1
bytes = [0x41, 0x42, 0x43, 0x00];
stream = new MemoryStream(bytes);
actual = stream.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadTypeTest()
{

View File

@@ -17,72 +17,6 @@ namespace SabreTools.IO.Extensions.Test
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
];
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = stream.WriteNullTerminatedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = stream.WriteNullTerminatedUTF8String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = stream.WriteNullTerminatedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = stream.WriteNullTerminatedUTF32String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = stream.WritePrefixedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = stream.WritePrefixedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteTypeTest()
{

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
@@ -13,165 +13,6 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static class BinaryReaderExtensions
{
/// <summary>
/// Read a null-terminated string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedString(this BinaryReader reader, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.CodePage == Encoding.ASCII.CodePage)
return reader.ReadNullTerminatedAnsiString();
#if NET5_0_OR_GREATER
else if (encoding.CodePage == Encoding.Latin1.CodePage)
return reader.ReadNullTerminatedLatin1String();
#endif
else if (encoding.CodePage == Encoding.UTF8.CodePage)
return reader.ReadNullTerminatedUTF8String();
else if (encoding.CodePage == Encoding.Unicode.CodePage)
return reader.ReadNullTerminatedUnicodeString();
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
return reader.ReadNullTerminatedBigEndianUnicodeString();
else if (encoding.CodePage == Encoding.UTF32.CodePage)
return reader.ReadNullTerminatedUTF32String();
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a null-terminated Latin1 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedLatin1String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a null-terminated UTF-8 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUTF8String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(reader);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedBigEndianUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(reader);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUTF32String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(reader);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the underlying stream
/// </summary>
public static string? ReadPrefixedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte size = reader.ReadByte();
if (reader.BaseStream.Position + size >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the underlying stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
ushort size = reader.ReadUInt16();
if (reader.BaseStream.Position + (size * 2) >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size * 2);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the underlying stream
/// </summary>
public static string? ReadPrefixedBigEndianUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
ushort size = reader.ReadUInt16();
if (reader.BaseStream.Position + (size * 2) >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size * 2);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a <typeparamref name="T"/> from the underlying stream
/// </summary>
@@ -394,59 +235,5 @@ namespace SabreTools.IO.Extensions
}
#pragma warning restore IDE0010
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte next = reader.ReadByte();
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
ushort next = reader.ReadUInt16();
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
uint next = reader.ReadUInt32();
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
@@ -12,137 +13,6 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static class BinaryWriterExtensions
{
/// <summary>
/// Write a null-terminated string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedString(this BinaryWriter writer, string? value, Encoding encoding)
{
// If the value is null
if (value is null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedAnsiString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.ASCII);
#if NET5_0_OR_GREATER
/// <summary>
/// Write a null-terminated Latin1 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedLatin1String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.Latin1);
#endif
/// <summary>
/// Write a null-terminated UTF-8 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUTF8String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedBigEndianUnicodeString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.BigEndianUnicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUTF32String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF32);
/// <summary>
/// Write a byte-prefixed ASCII string to the underlying stream
/// </summary>
public static bool WritePrefixedAnsiString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
writer.Write((byte)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Write a byte-prefixed Latin1 string to the underlying stream
/// </summary>
public static bool WritePrefixedLatin1String(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Latin1.GetBytes(value);
// Write the length as a byte
writer.Write((byte)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
#endif
/// <summary>
/// Write a ushort-prefixed Unicode string to the underlying stream
/// </summary>
public static bool WritePrefixedUnicodeString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
writer.Write((ushort)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the underlying stream
/// </summary>
public static bool WritePrefixedBigEndianUnicodeString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
// Write the length as a ushort
writer.Write((ushort)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a <typeparamref name="T"/> to the underlying stream
/// </summary>

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
@@ -12,183 +12,6 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static class ByteArrayReaderExtensions
{
/// <summary>
/// Read a null-terminated string from the array
/// </summary>
public static string? ReadNullTerminatedString(this byte[] content, ref int offset, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.CodePage == Encoding.ASCII.CodePage)
return content.ReadNullTerminatedAnsiString(ref offset);
#if NET5_0_OR_GREATER
else if (encoding.CodePage == Encoding.Latin1.CodePage)
return content.ReadNullTerminatedAnsiString(ref offset);
#endif
else if (encoding.CodePage == Encoding.UTF8.CodePage)
return content.ReadNullTerminatedUTF8String(ref offset);
else if (encoding.CodePage == Encoding.Unicode.CodePage)
return content.ReadNullTerminatedUnicodeString(ref offset);
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
return content.ReadNullTerminatedBigEndianUnicodeString(ref offset);
else if (encoding.CodePage == Encoding.UTF32.CodePage)
return content.ReadNullTerminatedUTF32String(ref offset);
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte ch = content.ReadByteValue(ref offset);
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the byte array
/// </summary>
public static string? ReadNullTerminatedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a null-terminated Latin1 string from the byte array
/// </summary>
public static string? ReadNullTerminatedLatin1String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a null-terminated UTF-8 string from the byte array
/// </summary>
public static string? ReadNullTerminatedUTF8String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the byte array
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(content, ref offset);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the byte array
/// </summary>
public static string? ReadNullTerminatedBigEndianUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(content, ref offset);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the byte array
/// </summary>
public static string? ReadNullTerminatedUTF32String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(content, ref offset);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the byte array
/// </summary>
public static string? ReadPrefixedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte size = content.ReadByteValue(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a byte-prefixed Latin1 string from the byte array
/// </summary>
public static string? ReadPrefixedLatin1String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte size = content.ReadByteValue(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a ushort-prefixed Unicode string from the byte array
/// </summary>
public static string? ReadPrefixedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
ushort size = content.ReadUInt16(ref offset);
if (offset + (size * 2) >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size * 2);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the byte array
/// </summary>
public static string? ReadPrefixedBigEndianUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
ushort size = content.ReadUInt16(ref offset);
if (offset + (size * 2) >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size * 2);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a <typeparamref name="T"/> and increment the pointer to an array
/// </summary>
@@ -403,60 +226,6 @@ namespace SabreTools.IO.Extensions
#pragma warning restore IDE0010
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
byte next = content.ReadByte(ref offset);
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
ushort next = content.ReadUInt16(ref offset);
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
uint next = content.ReadUInt32(ref offset);
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read a number of bytes from the byte array to a buffer
/// </summary>

View File

@@ -3,6 +3,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
@@ -11,141 +12,6 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static class ByteArrayWriterExtensions
{
/// <summary>
/// Write a null-terminated string to the array
/// </summary>
public static bool WriteNullTerminatedString(this byte[] content, ref int offset, string? value, Encoding encoding)
{
// If the value is null
if (value is null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the byte array
/// </summary>
public static bool WriteNullTerminatedAnsiString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.ASCII);
#if NET5_0_OR_GREATER
/// <summary>
/// Write a null-terminated Latin1 string to the byte array
/// </summary>
public static bool WriteNullTerminatedLatin1String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Latin1);
#endif
/// <summary>
/// Write a null-terminated UTF-8 string to the byte array
/// </summary>
public static bool WriteNullTerminatedUTF8String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the byte array
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the byte array
/// </summary>
public static bool WriteNullTerminatedBigEndianUnicodeString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.BigEndianUnicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the byte array
/// </summary>
public static bool WriteNullTerminatedUTF32String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF32);
/// <summary>
/// Write a byte-prefixed ASCII string to the byte array
/// </summary>
public static bool WritePrefixedAnsiString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!content.Write(ref offset, (byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Write a byte-prefixed Latin1 string to the byte array
/// </summary>
public static bool WritePrefixedLatin1String(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Latin1.GetBytes(value);
// Write the length as a byte
if (!content.Write(ref offset, (byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
#endif
/// <summary>
/// Write a ushort-prefixed Unicode string to the byte array
/// </summary>
public static bool WritePrefixedUnicodeString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!content.Write(ref offset, (ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the byte array
/// </summary>
public static bool WritePrefixedBigEndianUnicodeString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
// Write the length as a ushort
if (!content.Write(ref offset, (ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a <typeparamref name="T"/> to the byte array
/// </summary>

View File

@@ -6,11 +6,11 @@ Relies on functionality found in `SabreTools.IO.Matching`, `SabreTools.Numerics`
| Class | Description |
| --- | --- |
| `BinaryReaderExtensions` | Extensions to `BinaryReader` for specialized string and type reading. |
| `BinaryWriterExtensions` | Extensions to `BinaryWriter` for specialized string and type writing. |
| `BinaryReaderExtensions` | Extensions to `BinaryReader` for specialized type reading. |
| `BinaryWriterExtensions` | Extensions to `BinaryWriter` for specialized type writing. |
| `ByteArrayExtensions` | Utility `byte[]` and `byte?[]` extensions commonly used in SabreTools projects. |
| `ByteArrayReaderExtensions` | Extensions to `byte[]` for specialized string and type reading. |
| `ByteArrayWriterExtensions` | Extensions to `byte[]` for specialized string and type writing. |
| `ByteArrayReaderExtensions` | Extensions to `byte[]` for specialized type reading. |
| `ByteArrayWriterExtensions` | Extensions to `byte[]` for specialized type writing. |
| `DateTimeExtensions` | `DateTime` conversion, specifically between standard and MS-DOS formats. |
| `DictionaryExtensions` | Utility `Dictionary<string, List<string>>` extensions commonly used in SabreTools projects. |
| `EnumerableExtensions` | Specialized enumeration wrapper functionality. |
@@ -19,8 +19,5 @@ Relies on functionality found in `SabreTools.IO.Matching`, `SabreTools.Numerics`
| `ParentablePathExtensions` | Extensions to `SabreTools.IO.ParentablePath` for filtering. |
| `ReadOnlyBitStreamExtensions` | Extensions to `SabreTools.IO.ReadOnlyBitStream` to allow non-bitwise reads. |
| `StreamExtensions` | Utility `Stream` extensions commonly used in other SabreTools projects. |
| `StreamReaderExtensions` | Extensions to `Stream` for specialized string and type reading. |
| `StreamWriterExtensions` | Extensions to `Stream` for specialized string and type writing. |
| `StringBuilderExtensions` | Formatting overloads for `StringBuilder` to allow for type-specific handling. |
| `StringExtensions` | Optional overloads to `string.Contains`, `string.EndsWith`, `string.Equals`, and `string.StartsWith`. |
| `XmlTextWriterExtensions` | Overloads to `XmlTextWriter.WriteAttributeString` and `XmlTextWriter.WriteElementString`. |
| `StreamReaderExtensions` | Extensions to `Stream` for specialized type reading. |
| `StreamWriterExtensions` | Extensions to `Stream` for specialized type writing. |

View File

@@ -35,6 +35,7 @@
<ProjectReference Include="..\SabreTools.Numerics\SabreTools.Numerics.csproj" />
<ProjectReference Include="..\SabreTools.Numerics.Extensions\SabreTools.Numerics.Extensions.csproj" />
<ProjectReference Include="..\SabreTools.Text.Compare\SabreTools.Text.Compare.csproj" />
<ProjectReference Include="..\SabreTools.Text.Extensions\SabreTools.Text.Extensions.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
@@ -13,183 +13,6 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static class StreamReaderExtensions
{
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadNullTerminatedString(this Stream stream, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.CodePage == Encoding.ASCII.CodePage)
return stream.ReadNullTerminatedAnsiString();
#if NET5_0_OR_GREATER
else if (encoding.CodePage == Encoding.Latin1.CodePage)
return stream.ReadNullTerminatedLatin1String();
#endif
else if (encoding.CodePage == Encoding.UTF8.CodePage)
return stream.ReadNullTerminatedUTF8String();
else if (encoding.CodePage == Encoding.Unicode.CodePage)
return stream.ReadNullTerminatedUnicodeString();
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
return stream.ReadNullTerminatedBigEndianUnicodeString();
else if (encoding.CodePage == Encoding.UTF32.CodePage)
return stream.ReadNullTerminatedUTF32String();
if (stream.Position >= stream.Length)
return null;
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
byte ch = stream.ReadByteValue();
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a null-terminated Latin1 string from the stream
/// </summary>
public static string? ReadNullTerminatedLatin1String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a null-terminated UTF-8 string from the stream
/// </summary>
public static string? ReadNullTerminatedUTF8String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(stream);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the stream
/// </summary>
public static string? ReadNullTerminatedBigEndianUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(stream);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the stream
/// </summary>
public static string? ReadNullTerminatedUTF32String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(stream);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the stream
/// </summary>
public static string? ReadPrefixedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte size = stream.ReadByteValue();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a byte-prefixed Latin1 string from the stream
/// </summary>
public static string? ReadPrefixedLatin1String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte size = stream.ReadByteValue();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a ushort-prefixed Unicode string from the stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
ushort size = stream.ReadUInt16();
if (stream.Position + (size * 2) >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size * 2);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the stream
/// </summary>
public static string? ReadPrefixedBigEndianUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
ushort size = stream.ReadUInt16();
if (stream.Position + (size * 2) >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size * 2);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a <typeparamref name="T"/> from the stream
/// </summary>
@@ -404,60 +227,6 @@ namespace SabreTools.IO.Extensions
#pragma warning restore IDE0010
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
byte next = stream.ReadByteValue();
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
ushort next = stream.ReadUInt16();
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
uint next = stream.ReadUInt32();
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read a number of bytes from the stream to a buffer
/// </summary>

View File

@@ -4,6 +4,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
namespace SabreTools.IO.Extensions
{
@@ -12,141 +13,6 @@ namespace SabreTools.IO.Extensions
/// </summary>
public static class StreamWriterExtensions
{
/// <summary>
/// Write a null-terminated string to the stream
/// </summary>
public static bool WriteNullTerminatedString(this Stream stream, string? value, Encoding encoding)
{
// If the value is null
if (value is null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the stream
/// </summary>
public static bool WriteNullTerminatedAnsiString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.ASCII);
#if NET5_0_OR_GREATER
/// <summary>
/// Write a null-terminated Latin1 string to the stream
/// </summary>
public static bool WriteNullTerminatedLatin1String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.Latin1);
#endif
/// <summary>
/// Write a null-terminated UTF-8 string to the stream
/// </summary>
public static bool WriteNullTerminatedUTF8String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the stream
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the stream
/// </summary>
public static bool WriteNullTerminatedBigEndianUnicodeString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.BigEndianUnicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the stream
/// </summary>
public static bool WriteNullTerminatedUTF32String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF32);
//// <summary>
/// Write a byte-prefixed ASCII string to the stream
/// </summary>
public static bool WritePrefixedAnsiString(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!stream.Write((byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
#if NET5_0_OR_GREATER
//// <summary>
/// Write a byte-prefixed Latin1 string to the stream
/// </summary>
public static bool WritePrefixedLatin1String(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Latin1.GetBytes(value);
// Write the length as a byte
if (!stream.Write((byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
#endif
/// <summary>
/// Write a ushort-prefixed Unicode string to the stream
/// </summary>
public static bool WritePrefixedUnicodeString(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!stream.Write((ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the stream
/// </summary>
public static bool WritePrefixedBigEndianUnicodeString(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
// Write the length as a ushort
if (!stream.Write((ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a <typeparamref name="T"/> to the stream
/// </summary>

View File

@@ -48,9 +48,9 @@
<Compile Include="..\SabreTools.Security.Cryptography\*.cs" />
<Compile Include="..\SabreTools.Text.ClrMamePro\*.cs" />
<Compile Include="..\SabreTools.Text.Compare\*.cs" />
<Compile Include="..\SabreTools.Text.Extensions\*.cs" Exclude="..\SabreTools.Text.Extensions\ExtensionAttribute.cs" />
<Compile Include="..\SabreTools.Text.INI\*.cs" />
<Compile Include="..\SabreTools.Text.SeparatedValue\*.cs" />
<Compile Include="..\SabreTools.Text.Extensions\*.cs" Exclude="..\SabreTools.Text.Extensions\ExtensionAttribute.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,69 @@
using System.IO;
using System.Text;
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class BinaryReaderExtensionsTests
{
[Fact]
public void ReadNullTerminatedAnsiStringTest()
{
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.ASCII);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedLatin1StringTest()
{
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUTF8StringTest()
{
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.UTF8);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUnicodeStringTest()
{
byte[] bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.Unicode);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedBigEndianUnicodeStringTest()
{
byte[] bytes = [0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.BigEndianUnicode);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUTF32StringTest()
{
byte[] bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
var stream = new MemoryStream(bytes);
var br = new BinaryReader(stream);
string? actual = br.ReadNullTerminatedString(Encoding.UTF32);
Assert.Equal("ABC", actual);
}
}
}

View File

@@ -0,0 +1,91 @@
using System.IO;
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class BinaryWriterExtensionsTests
{
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = bw.WriteNullTerminatedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = bw.WriteNullTerminatedUTF8String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = bw.WriteNullTerminatedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = bw.WriteNullTerminatedUTF32String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = bw.WritePrefixedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
var bw = new BinaryWriter(stream);
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = bw.WritePrefixedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
/// <summary>
/// Validate that a set of actual bytes matches the expected bytes
/// </summary>
private static void ValidateBytes(byte[] expected, byte[] actual)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}

View File

@@ -0,0 +1,62 @@
using System.Text;
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class ByteArrayReaderExtensionsTests
{
[Fact]
public void ReadNullTerminatedAnsiStringTest()
{
int offset = 0;
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.ASCII);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedLatin1StringTest()
{
int offset = 0;
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUTF8StringTest()
{
int offset = 0;
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF8);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUnicodeStringTest()
{
int offset = 0;
byte[] bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.Unicode);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedBigEndianUnicodeStringTest()
{
int offset = 0;
byte[] bytes = [0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.BigEndianUnicode);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUTF32StringTest()
{
int offset = 0;
byte[] bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
string? actual = bytes.ReadNullTerminatedString(ref offset, Encoding.UTF32);
Assert.Equal("ABC", actual);
}
}
}

View File

@@ -0,0 +1,90 @@
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class ByteArrayWriterExtensionsTests
{
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = buffer.WriteNullTerminatedAnsiString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = buffer.WriteNullTerminatedUTF8String(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
int offset = 0;
byte[] buffer = new byte[8];
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = buffer.WriteNullTerminatedUnicodeString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
int offset = 0;
byte[] buffer = new byte[16];
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = buffer.WriteNullTerminatedUTF32String(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
int offset = 0;
byte[] buffer = new byte[4];
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = buffer.WritePrefixedAnsiString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
int offset = 0;
byte[] buffer = new byte[8];
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = buffer.WritePrefixedUnicodeString(ref offset, "ABC");
Assert.True(write);
ValidateBytes(expected, buffer);
}
/// <summary>
/// Validate that a set of actual bytes matches the expected bytes
/// </summary>
private static void ValidateBytes(byte[] expected, byte[] actual)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System.IO;
using System.Text;
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class StreamReaderExtensionsTests
{
[Fact]
public void ReadNullTerminatedAnsiStringTest()
{
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.ASCII);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedLatin1StringTest()
{
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.Latin1);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUTF8StringTest()
{
byte[] bytes = [0x41, 0x42, 0x43, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.UTF8);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUnicodeStringTest()
{
byte[] bytes = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.Unicode);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedBigEndianUnicodeStringTest()
{
byte[] bytes = [0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.BigEndianUnicode);
Assert.Equal("ABC", actual);
}
[Fact]
public void ReadNullTerminatedUTF32StringTest()
{
byte[] bytes = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
var stream = new MemoryStream(bytes);
string? actual = stream.ReadNullTerminatedString(Encoding.UTF32);
Assert.Equal("ABC", actual);
}
}
}

View File

@@ -0,0 +1,85 @@
using System.IO;
using Xunit;
namespace SabreTools.Text.Extensions.Test
{
public class StreamWriterExtensionsTests
{
[Fact]
public void WriteNullTerminatedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = stream.WriteNullTerminatedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF8StringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x41, 0x42, 0x43, 0x00];
bool write = stream.WriteNullTerminatedUTF8String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
byte[] expected = [0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00];
bool write = stream.WriteNullTerminatedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WriteNullTerminatedUTF32StringTest()
{
var stream = new MemoryStream(new byte[16], 0, 16, true, true);
byte[] expected = [0x41, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
bool write = stream.WriteNullTerminatedUTF32String("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedAnsiStringTest()
{
var stream = new MemoryStream(new byte[4], 0, 4, true, true);
byte[] expected = [0x03, 0x41, 0x42, 0x43];
bool write = stream.WritePrefixedAnsiString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
[Fact]
public void WritePrefixedUnicodeStringTest()
{
var stream = new MemoryStream(new byte[8], 0, 8, true, true);
byte[] expected = [0x03, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00];
bool write = stream.WritePrefixedUnicodeString("ABC");
Assert.True(write);
ValidateBytes(expected, stream.GetBuffer());
}
/// <summary>
/// Validate that a set of actual bytes matches the expected bytes
/// </summary>
private static void ValidateBytes(byte[] expected, byte[] actual)
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}

View File

@@ -1,6 +1,6 @@
using Xunit;
namespace SabreTools.IO.Extensions.Test
namespace SabreTools.Text.Extensions.Test
{
public class StringExtensionsTests
{

View File

@@ -4,7 +4,7 @@ using System.Text;
using System.Xml;
using Xunit;
namespace SabreTools.IO.Extensions.Test
namespace SabreTools.Text.Extensions.Test
{
public class XmlTextWriterExtensionsTests
{

View File

@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Extensions for BinaryReader
/// </summary>
public static class BinaryReaderExtensions
{
/// <summary>
/// Read a null-terminated string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedString(this BinaryReader reader, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.CodePage == Encoding.ASCII.CodePage)
return reader.ReadNullTerminatedAnsiString();
#if NET5_0_OR_GREATER
else if (encoding.CodePage == Encoding.Latin1.CodePage)
return reader.ReadNullTerminatedLatin1String();
#endif
else if (encoding.CodePage == Encoding.UTF8.CodePage)
return reader.ReadNullTerminatedUTF8String();
else if (encoding.CodePage == Encoding.Unicode.CodePage)
return reader.ReadNullTerminatedUnicodeString();
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
return reader.ReadNullTerminatedBigEndianUnicodeString();
else if (encoding.CodePage == Encoding.UTF32.CodePage)
return reader.ReadNullTerminatedUTF32String();
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
List<byte> buffer = [];
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte ch = reader.ReadByte();
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a null-terminated Latin1 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedLatin1String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a null-terminated UTF-8 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUTF8String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(reader);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(reader);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedBigEndianUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(reader);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the underlying stream
/// </summary>
public static string? ReadNullTerminatedUTF32String(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(reader);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the underlying stream
/// </summary>
public static string? ReadPrefixedAnsiString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
byte size = reader.ReadByte();
if (reader.BaseStream.Position + size >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the underlying stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
ushort size = reader.ReadUInt16();
if (reader.BaseStream.Position + (size * 2) >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size * 2);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the underlying stream
/// </summary>
public static string? ReadPrefixedBigEndianUnicodeString(this BinaryReader reader)
{
if (reader.BaseStream.Position >= reader.BaseStream.Length)
return null;
ushort size = reader.ReadUInt16();
if (reader.BaseStream.Position + (size * 2) >= reader.BaseStream.Length)
return null;
byte[] buffer = reader.ReadBytes(size * 2);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
byte next = reader.ReadByte();
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
ushort next = reader.ReadUInt16();
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(BinaryReader reader)
{
var bytes = new List<byte>();
while (reader.BaseStream.Position < reader.BaseStream.Length)
{
uint next = reader.ReadUInt32();
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
}
}

View File

@@ -0,0 +1,160 @@
using System.IO;
using System.Text;
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Extensions for BinaryWriter
/// </summary>
public static class BinaryWriterExtensions
{
/// <summary>
/// Write a null-terminated string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedString(this BinaryWriter writer, string? value, Encoding encoding)
{
// If the value is null
if (value is null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedAnsiString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.ASCII);
#if NET5_0_OR_GREATER
/// <summary>
/// Write a null-terminated Latin1 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedLatin1String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.Latin1);
#endif
/// <summary>
/// Write a null-terminated UTF-8 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUTF8String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedBigEndianUnicodeString(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.BigEndianUnicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the underlying stream
/// </summary>
public static bool WriteNullTerminatedUTF32String(this BinaryWriter writer, string? value)
=> writer.WriteNullTerminatedString(value, Encoding.UTF32);
/// <summary>
/// Write a byte-prefixed ASCII string to the underlying stream
/// </summary>
public static bool WritePrefixedAnsiString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
writer.Write((byte)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Write a byte-prefixed Latin1 string to the underlying stream
/// </summary>
public static bool WritePrefixedLatin1String(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Latin1.GetBytes(value);
// Write the length as a byte
writer.Write((byte)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
#endif
/// <summary>
/// Write a ushort-prefixed Unicode string to the underlying stream
/// </summary>
public static bool WritePrefixedUnicodeString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
writer.Write((ushort)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the underlying stream
/// </summary>
public static bool WritePrefixedBigEndianUnicodeString(this BinaryWriter writer, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
// Write the length as a ushort
writer.Write((ushort)value.Length);
// Write the buffer
return WriteFromBuffer(writer, buffer);
}
/// <summary>
/// Write an array of bytes to the underlying stream
/// </summary>
private static bool WriteFromBuffer(BinaryWriter writer, byte[] value)
{
// If the stream is not writable
if (!writer.BaseStream.CanWrite)
return false;
// Handle the 0-byte case
if (value.Length == 0)
return true;
// Handle the general case, forcing a write of the correct length
writer.Write(value, 0, value.Length);
return true;
}
}
}

View File

@@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using System.Text;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Extensions for byte arrays
/// </summary>
public static class ByteArrayReaderExtensions
{
/// <summary>
/// Read a null-terminated string from the array
/// </summary>
public static string? ReadNullTerminatedString(this byte[] content, ref int offset, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.CodePage == Encoding.ASCII.CodePage)
return content.ReadNullTerminatedAnsiString(ref offset);
#if NET5_0_OR_GREATER
else if (encoding.CodePage == Encoding.Latin1.CodePage)
return content.ReadNullTerminatedAnsiString(ref offset);
#endif
else if (encoding.CodePage == Encoding.UTF8.CodePage)
return content.ReadNullTerminatedUTF8String(ref offset);
else if (encoding.CodePage == Encoding.Unicode.CodePage)
return content.ReadNullTerminatedUnicodeString(ref offset);
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
return content.ReadNullTerminatedBigEndianUnicodeString(ref offset);
else if (encoding.CodePage == Encoding.UTF32.CodePage)
return content.ReadNullTerminatedUTF32String(ref offset);
if (offset >= content.Length)
return null;
List<byte> buffer = [];
while (offset < content.Length)
{
byte ch = content.ReadByteValue(ref offset);
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the byte array
/// </summary>
public static string? ReadNullTerminatedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a null-terminated Latin1 string from the byte array
/// </summary>
public static string? ReadNullTerminatedLatin1String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a null-terminated UTF-8 string from the byte array
/// </summary>
public static string? ReadNullTerminatedUTF8String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(content, ref offset);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the byte array
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(content, ref offset);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the byte array
/// </summary>
public static string? ReadNullTerminatedBigEndianUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(content, ref offset);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the byte array
/// </summary>
public static string? ReadNullTerminatedUTF32String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(content, ref offset);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the byte array
/// </summary>
public static string? ReadPrefixedAnsiString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte size = content.ReadByteValue(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a byte-prefixed Latin1 string from the byte array
/// </summary>
public static string? ReadPrefixedLatin1String(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
byte size = content.ReadByteValue(ref offset);
if (offset + size >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a ushort-prefixed Unicode string from the byte array
/// </summary>
public static string? ReadPrefixedUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
ushort size = content.ReadUInt16(ref offset);
if (offset + (size * 2) >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size * 2);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the byte array
/// </summary>
public static string? ReadPrefixedBigEndianUnicodeString(this byte[] content, ref int offset)
{
if (offset >= content.Length)
return null;
ushort size = content.ReadUInt16(ref offset);
if (offset + (size * 2) >= content.Length)
return null;
byte[] buffer = content.ReadBytes(ref offset, size * 2);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
byte next = content.ReadByte(ref offset);
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
ushort next = content.ReadUInt16(ref offset);
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(byte[] content, ref int offset)
{
var bytes = new List<byte>();
while (offset < content.Length)
{
uint next = content.ReadUInt32(ref offset);
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
}
}

View File

@@ -0,0 +1,171 @@
using System;
using System.Text;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Extensions for byte arrays
/// </summary>
public static class ByteArrayWriterExtensions
{
/// <summary>
/// Write a null-terminated string to the array
/// </summary>
public static bool WriteNullTerminatedString(this byte[] content, ref int offset, string? value, Encoding encoding)
{
// If the value is null
if (value is null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the byte array
/// </summary>
public static bool WriteNullTerminatedAnsiString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.ASCII);
#if NET5_0_OR_GREATER
/// <summary>
/// Write a null-terminated Latin1 string to the byte array
/// </summary>
public static bool WriteNullTerminatedLatin1String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Latin1);
#endif
/// <summary>
/// Write a null-terminated UTF-8 string to the byte array
/// </summary>
public static bool WriteNullTerminatedUTF8String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the byte array
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the byte array
/// </summary>
public static bool WriteNullTerminatedBigEndianUnicodeString(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.BigEndianUnicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the byte array
/// </summary>
public static bool WriteNullTerminatedUTF32String(this byte[] content, ref int offset, string? value)
=> content.WriteNullTerminatedString(ref offset, value, Encoding.UTF32);
/// <summary>
/// Write a byte-prefixed ASCII string to the byte array
/// </summary>
public static bool WritePrefixedAnsiString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!content.Write(ref offset, (byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Write a byte-prefixed Latin1 string to the byte array
/// </summary>
public static bool WritePrefixedLatin1String(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Latin1.GetBytes(value);
// Write the length as a byte
if (!content.Write(ref offset, (byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
#endif
/// <summary>
/// Write a ushort-prefixed Unicode string to the byte array
/// </summary>
public static bool WritePrefixedUnicodeString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!content.Write(ref offset, (ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the byte array
/// </summary>
public static bool WritePrefixedBigEndianUnicodeString(this byte[] content, ref int offset, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
// Write the length as a ushort
if (!content.Write(ref offset, (ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(content, ref offset, buffer);
}
/// <summary>
/// Write an array of bytes to the byte array
/// </summary>
/// <exception cref="System.IO.EndOfStreamException">
/// Thrown if <paramref name="offset"/> into <paramref name="content"/>
/// would not accomodate <paramref name="value"/>.
/// </exception>
private static bool WriteFromBuffer(byte[] content, ref int offset, byte[] value)
{
// Handle the 0-byte case
if (value.Length == 0)
return true;
// If there are not enough bytes
if (offset + value.Length > content.Length)
throw new System.IO.EndOfStreamException(nameof(content));
// Handle the general case, forcing a write of the correct length
Array.Copy(value, 0, content, offset, value.Length);
offset += value.Length;
return true;
}
}
}

View File

@@ -4,5 +4,13 @@ This library contains various extensions and helper classes for dealing with str
| Class | Description |
| --- | --- |
| `BinaryReaderExtensions` | Extensions to `BinaryReader` for specialized string reading. |
| `BinaryWriterExtensions` | Extensions to `BinaryWriter` for specialized string writing. |
| `ByteArrayReaderExtensions` | Extensions to `byte[]` for specialized string reading. |
| `ByteArrayWriterExtensions` | Extensions to `byte[]` for specialized string writing. |
| `NumberHelper` | Numeric string helpers, including suffix parsing and human-readable outputs. |
| `StreamReaderExtensions` | Extensions to `Stream` for specialized string reading. |
| `StreamWriterExtensions` | Extensions to `Stream` for specialized string writing. |
| `StringBuilderExtensions` | Formatting overloads for `StringBuilder` to allow for type-specific handling. |
| `TextHelper` | String normalization commonly used in other projects. |
| `XmlTextWriterExtensions` | Overloads to `XmlTextWriter.WriteAttributeString` and `XmlTextWriter.WriteElementString`. |

View File

@@ -28,4 +28,8 @@
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Numerics.Extensions\SabreTools.Numerics.Extensions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Extensions for Streams
/// </summary>
public static class StreamReaderExtensions
{
/// <summary>
/// Read a null-terminated string from the stream
/// </summary>
public static string? ReadNullTerminatedString(this Stream stream, Encoding encoding)
{
// Short-circuit to explicit implementations
if (encoding.CodePage == Encoding.ASCII.CodePage)
return stream.ReadNullTerminatedAnsiString();
#if NET5_0_OR_GREATER
else if (encoding.CodePage == Encoding.Latin1.CodePage)
return stream.ReadNullTerminatedLatin1String();
#endif
else if (encoding.CodePage == Encoding.UTF8.CodePage)
return stream.ReadNullTerminatedUTF8String();
else if (encoding.CodePage == Encoding.Unicode.CodePage)
return stream.ReadNullTerminatedUnicodeString();
else if (encoding.CodePage == Encoding.BigEndianUnicode.CodePage)
return stream.ReadNullTerminatedBigEndianUnicodeString();
else if (encoding.CodePage == Encoding.UTF32.CodePage)
return stream.ReadNullTerminatedUTF32String();
if (stream.Position >= stream.Length)
return null;
List<byte> buffer = [];
while (stream.Position < stream.Length)
{
byte ch = stream.ReadByteValue();
if (ch == '\0')
break;
buffer.Add(ch);
}
return encoding.GetString([.. buffer]);
}
/// <summary>
/// Read a null-terminated ASCII string from the stream
/// </summary>
public static string? ReadNullTerminatedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a null-terminated Latin1 string from the stream
/// </summary>
public static string? ReadNullTerminatedLatin1String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a null-terminated UTF-8 string from the stream
/// </summary>
public static string? ReadNullTerminatedUTF8String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull1Byte(stream);
return Encoding.UTF8.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the stream
/// </summary>
public static string? ReadNullTerminatedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(stream);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-16 (Unicode) string from the stream
/// </summary>
public static string? ReadNullTerminatedBigEndianUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull2Byte(stream);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read a null-terminated UTF-32 string from the stream
/// </summary>
public static string? ReadNullTerminatedUTF32String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte[] buffer = ReadUntilNull4Byte(stream);
return Encoding.UTF32.GetString(buffer);
}
/// <summary>
/// Read a byte-prefixed ASCII string from the stream
/// </summary>
public static string? ReadPrefixedAnsiString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte size = stream.ReadByteValue();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.ASCII.GetString(buffer);
}
#if NET5_0_OR_GREATER
/// <summary>
/// Read a byte-prefixed Latin1 string from the stream
/// </summary>
public static string? ReadPrefixedLatin1String(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
byte size = stream.ReadByteValue();
if (stream.Position + size >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size);
return Encoding.Latin1.GetString(buffer);
}
#endif
/// <summary>
/// Read a ushort-prefixed Unicode string from the stream
/// </summary>
public static string? ReadPrefixedUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
ushort size = stream.ReadUInt16();
if (stream.Position + (size * 2) >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size * 2);
return Encoding.Unicode.GetString(buffer);
}
/// <summary>
/// Read a ushort-prefixed Unicode string from the stream
/// </summary>
public static string? ReadPrefixedBigEndianUnicodeString(this Stream stream)
{
if (stream.Position >= stream.Length)
return null;
ushort size = stream.ReadUInt16();
if (stream.Position + (size * 2) >= stream.Length)
return null;
byte[] buffer = stream.ReadBytes(size * 2);
return Encoding.BigEndianUnicode.GetString(buffer);
}
/// <summary>
/// Read bytes until a 1-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull1Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
byte next = stream.ReadByteValue();
if (next == 0x00)
break;
bytes.Add(next);
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 2-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull2Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
ushort next = stream.ReadUInt16();
if (next == 0x0000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
/// <summary>
/// Read bytes until a 4-byte null terminator is found
/// </summary>
private static byte[] ReadUntilNull4Byte(Stream stream)
{
var bytes = new List<byte>();
while (stream.Position < stream.Length)
{
uint next = stream.ReadUInt32();
if (next == 0x00000000)
break;
bytes.AddRange(BitConverter.GetBytes(next));
}
return [.. bytes];
}
}
}

View File

@@ -0,0 +1,165 @@
using System.IO;
using System.Text;
using SabreTools.Numerics.Extensions;
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Extensions for Streams
/// </summary>
public static class StreamWriterExtensions
{
/// <summary>
/// Write a null-terminated string to the stream
/// </summary>
public static bool WriteNullTerminatedString(this Stream stream, string? value, Encoding encoding)
{
// If the value is null
if (value is null)
return false;
// Add the null terminator and write
value += "\0";
byte[] buffer = encoding.GetBytes(value);
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a null-terminated ASCII string to the stream
/// </summary>
public static bool WriteNullTerminatedAnsiString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.ASCII);
#if NET5_0_OR_GREATER
/// <summary>
/// Write a null-terminated Latin1 string to the stream
/// </summary>
public static bool WriteNullTerminatedLatin1String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.Latin1);
#endif
/// <summary>
/// Write a null-terminated UTF-8 string to the stream
/// </summary>
public static bool WriteNullTerminatedUTF8String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF8);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the stream
/// </summary>
public static bool WriteNullTerminatedUnicodeString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.Unicode);
/// <summary>
/// Write a null-terminated UTF-16 (Unicode) string to the stream
/// </summary>
public static bool WriteNullTerminatedBigEndianUnicodeString(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.BigEndianUnicode);
/// <summary>
/// Write a null-terminated UTF-32 string to the stream
/// </summary>
public static bool WriteNullTerminatedUTF32String(this Stream stream, string? value)
=> stream.WriteNullTerminatedString(value, Encoding.UTF32);
//// <summary>
/// Write a byte-prefixed ASCII string to the stream
/// </summary>
public static bool WritePrefixedAnsiString(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.ASCII.GetBytes(value);
// Write the length as a byte
if (!stream.Write((byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
#if NET5_0_OR_GREATER
//// <summary>
/// Write a byte-prefixed Latin1 string to the stream
/// </summary>
public static bool WritePrefixedLatin1String(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Latin1.GetBytes(value);
// Write the length as a byte
if (!stream.Write((byte)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
#endif
/// <summary>
/// Write a ushort-prefixed Unicode string to the stream
/// </summary>
public static bool WritePrefixedUnicodeString(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.Unicode.GetBytes(value);
// Write the length as a ushort
if (!stream.Write((ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write a ushort-prefixed Unicode string to the stream
/// </summary>
public static bool WritePrefixedBigEndianUnicodeString(this Stream stream, string? value)
{
// If the value is null
if (value is null)
return false;
// Get the buffer
byte[] buffer = Encoding.BigEndianUnicode.GetBytes(value);
// Write the length as a ushort
if (!stream.Write((ushort)value.Length))
return false;
// Write the buffer
return WriteFromBuffer(stream, buffer);
}
/// <summary>
/// Write an array of bytes to the stream
/// </summary>
private static bool WriteFromBuffer(Stream stream, byte[] value)
{
// If the stream is not writable
if (!stream.CanWrite)
return false;
// Handle the 0-byte case
if (value.Length == 0)
return true;
// Handle the general case, forcing a write of the correct length
stream.Write(value, 0, value.Length);
return true;
}
}
}

View File

@@ -2,7 +2,7 @@ using System;
using System.Text;
using SabreTools.Numerics;
namespace SabreTools.IO.Extensions
namespace SabreTools.Text.Extensions
{
// TODO: Add extension for printing enums, if possible
public static class StringBuilderExtensions

View File

@@ -1,6 +1,6 @@
using System;
namespace SabreTools.IO.Extensions
namespace SabreTools.Text.Extensions
{
public static class StringExtensions
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Xml;
namespace SabreTools.IO.Extensions
namespace SabreTools.Text.Extensions
{
/// <summary>
/// Additional methods for XmlTextWriter