diff --git a/README.md b/README.md index 3230ca0..415254e 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,10 @@ class MyBasicObj public string NullTerminatedASCIIString { get; set; } // String encoded in UTF-16 that will only read/write 10 chars + // The BinaryStringTrimNullTerminatorsAttribute will indicate that every char from the first \0 will be removed from the string. This attribute also works with char arrays [BinaryEncoding(EncodingType.UTF16)] [BinaryStringFixedLength(10)] + [BinaryStringTrimNullTerminators(true)] public string UTF16String { get; set; } } ``` diff --git a/Source/Attributes.cs b/Source/Attributes.cs index f1e2ce4..20a9726 100644 --- a/Source/Attributes.cs +++ b/Source/Attributes.cs @@ -103,4 +103,14 @@ public BinaryStringVariableLengthAttribute(string anchor) Value = anchor; } } + [AttributeUsage(AttributeTargets.Property)] + public sealed class BinaryStringTrimNullTerminatorsAttribute : Attribute, IBinaryAttribute + { + public bool Value { get; } + + public BinaryStringTrimNullTerminatorsAttribute(bool trim = true) + { + Value = trim; + } + } } diff --git a/Source/EndianBinaryReader.cs b/Source/EndianBinaryReader.cs index 4ee7cf2..1c4a92b 100644 --- a/Source/EndianBinaryReader.cs +++ b/Source/EndianBinaryReader.cs @@ -276,16 +276,16 @@ public char ReadChar(EncodingType encodingType, long offset) BaseStream.Position = offset; return ReadChar(encodingType); } - public char[] ReadChars(int count) + public char[] ReadChars(int count, bool trimNullTerminators) { - return ReadChars(count, Encoding); + return ReadChars(count, trimNullTerminators, Encoding); } - public char[] ReadChars(int count, long offset) + public char[] ReadChars(int count, bool trimNullTerminators, long offset) { BaseStream.Position = offset; - return ReadChars(count); + return ReadChars(count, trimNullTerminators); } - public char[] ReadChars(int count, EncodingType encodingType) + public char[] ReadChars(int count, bool trimNullTerminators, EncodingType encodingType) { if (Utils.ValidateReadArraySize(count, out char[] array)) { @@ -294,12 +294,21 @@ public char[] ReadChars(int count, EncodingType encodingType) Encoding encoding = Utils.EncodingFromEnum(encodingType); int encodingSize = Utils.EncodingSize(encoding); ReadBytesIntoBuffer(count * encodingSize); - return encoding.GetChars(_buffer, 0, encodingSize * count); + array = encoding.GetChars(_buffer, 0, encodingSize * count); + if (trimNullTerminators) + { + int i = Array.IndexOf(array, '\0'); + if (i != -1) + { + Array.Resize(ref array, i); + } + } + return array; } - public char[] ReadChars(int count, EncodingType encodingType, long offset) + public char[] ReadChars(int count, bool trimNullTerminators, EncodingType encodingType, long offset) { BaseStream.Position = offset; - return ReadChars(count, encodingType); + return ReadChars(count, trimNullTerminators, encodingType); } public string ReadStringNullTerminated() { @@ -329,23 +338,23 @@ public string ReadStringNullTerminated(EncodingType encodingType, long offset) BaseStream.Position = offset; return ReadStringNullTerminated(encodingType); } - public string ReadString(int charCount) + public string ReadString(int charCount, bool trimNullTerminators) { - return ReadString(charCount, Encoding); + return ReadString(charCount, trimNullTerminators, Encoding); } - public string ReadString(int charCount, long offset) + public string ReadString(int charCount, bool trimNullTerminators, long offset) { BaseStream.Position = offset; - return ReadString(charCount); + return ReadString(charCount, trimNullTerminators); } - public string ReadString(int charCount, EncodingType encodingType) + public string ReadString(int charCount, bool trimNullTerminators, EncodingType encodingType) { - return new string(ReadChars(charCount, encodingType)); + return new string(ReadChars(charCount, trimNullTerminators, encodingType)); } - public string ReadString(int charCount, EncodingType encodingType, long offset) + public string ReadString(int charCount, bool trimNullTerminators, EncodingType encodingType, long offset) { BaseStream.Position = offset; - return ReadString(charCount, encodingType); + return ReadString(charCount, trimNullTerminators, encodingType); } public string[] ReadStringsNullTerminated(int count) { @@ -373,31 +382,31 @@ public string[] ReadStringsNullTerminated(int count, EncodingType encodingType, BaseStream.Position = offset; return ReadStringsNullTerminated(count, encodingType); } - public string[] ReadStrings(int count, int charCount) + public string[] ReadStrings(int count, int charCount, bool trimNullTerminators) { - return ReadStrings(count, charCount, Encoding); + return ReadStrings(count, charCount, trimNullTerminators, Encoding); } - public string[] ReadStrings(int count, int charCount, long offset) + public string[] ReadStrings(int count, int charCount, bool trimNullTerminators, long offset) { BaseStream.Position = offset; - return ReadStrings(count, charCount); + return ReadStrings(count, charCount, trimNullTerminators); } - public string[] ReadStrings(int count, int charCount, EncodingType encodingType) + public string[] ReadStrings(int count, int charCount, bool trimNullTerminators, EncodingType encodingType) { if (!Utils.ValidateReadArraySize(count, out string[] array)) { array = new string[count]; for (int i = 0; i < count; i++) { - array[i] = ReadString(charCount, encodingType); + array[i] = ReadString(charCount, trimNullTerminators, encodingType); } } return array; } - public string[] ReadStrings(int count, int charCount, EncodingType encodingType, long offset) + public string[] ReadStrings(int count, int charCount, bool trimNullTerminators, EncodingType encodingType, long offset) { BaseStream.Position = offset; - return ReadStrings(count, charCount, encodingType); + return ReadStrings(count, charCount, trimNullTerminators, encodingType); } public short ReadInt16() { @@ -739,7 +748,8 @@ public void ReadIntoObject(object obj) case TypeCode.Char: { EncodingType encodingType = Utils.AttributeValueOrDefault(propertyInfo, Encoding); - value = ReadChars(arrayLength, encodingType); + bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); + value = ReadChars(arrayLength, trimNullTerminators, encodingType); break; } case TypeCode.Int16: value = ReadInt16s(arrayLength); break; @@ -762,7 +772,8 @@ public void ReadIntoObject(object obj) } else { - value = ReadStrings(arrayLength, stringLength, encodingType); + bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); + value = ReadStrings(arrayLength, stringLength, trimNullTerminators, encodingType); } break; } @@ -834,7 +845,8 @@ public void ReadIntoObject(object obj) } else { - value = ReadString(stringLength, encodingType); + bool trimNullTerminators = Utils.AttributeValueOrDefault(propertyInfo, false); + value = ReadString(stringLength, trimNullTerminators, encodingType); } break; } diff --git a/Source/Utils.cs b/Source/Utils.cs index a5f8ad1..7f612b1 100644 --- a/Source/Utils.cs +++ b/Source/Utils.cs @@ -154,6 +154,10 @@ int Validate(int value) { throw new ArgumentException($"A string property in \"{objType.FullName}\" has a string length attribute and a {nameof(BinaryStringNullTerminatedAttribute)}; cannot use both."); } + if (propertyInfo.IsDefined(typeof(BinaryStringTrimNullTerminatorsAttribute))) + { + throw new ArgumentException($"A string property in \"{objType.FullName}\" has a {nameof(BinaryStringNullTerminatedAttribute)} and a {nameof(BinaryStringTrimNullTerminatorsAttribute)}; cannot use both."); + } bool nt = GetAttributeValue(nullTermAttribute); if (forReads && !nt) // Not forcing BinaryStringNullTerminatedAttribute to be treated as true since you may only write objects without reading them. { diff --git a/Testing/BasicTests.cs b/Testing/BasicTests.cs index 4633b91..960b3ce 100644 --- a/Testing/BasicTests.cs +++ b/Testing/BasicTests.cs @@ -37,6 +37,7 @@ private sealed class MyBasicObj // String encoded in UTF-16 that will only read/write 10 chars [BinaryEncoding(EncodingType.UTF16)] [BinaryStringFixedLength(10)] + [BinaryStringTrimNullTerminators(true)] public string UTF16String { get; set; } } @@ -95,7 +96,7 @@ public void ReadObject() Assert.False(obj.Bool32); // bool32 works Assert.True(obj.NullTerminatedASCIIString == "EndianBinaryIO"); // Stops reading at null terminator - Assert.True(obj.UTF16String == "Kermalis\0\0"); // Fixed size (10 chars) utf16 + Assert.True(obj.UTF16String == "Kermalis"); // Fixed size (10 chars) utf16, with the \0s trimmed } [Fact] @@ -126,8 +127,8 @@ public void ReadManually() obj.NullTerminatedASCIIString = reader.ReadStringNullTerminated(EncodingType.ASCII); Assert.True(obj.NullTerminatedASCIIString == "EndianBinaryIO"); // Stops reading at null terminator - obj.UTF16String = reader.ReadString(10, EncodingType.UTF16); - Assert.True(obj.UTF16String == "Kermalis\0\0"); // Fixed size (10 chars) utf16 + obj.UTF16String = reader.ReadString(10, true, EncodingType.UTF16); + Assert.True(obj.UTF16String == "Kermalis"); // Fixed size (10 chars) utf16, with the \0s trimmed } }