Read compressed elements without loading into memory #34

Merged
Rob-Hague merged 2 commits from substream into master 2025-04-06 10:27:16 +00:00
9 changed files with 313 additions and 117 deletions

View File

@ -59,7 +59,7 @@ namespace MatFileHandler.Tests
private TTestData ReadTestData(string filename) private TTestData ReadTestData(string filename)
{ {
using (var stream = new FileStream(filename, FileMode.Open)) using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
{ {
return ReadDataFromStream(stream); return ReadDataFromStream(stream);
} }

View File

@ -18,12 +18,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading all files in a given test set. /// Test reading all files in a given test set.
/// </summary> /// </summary>
/// <param name="testSet">Name of the set.</param> [Theory, MemberData(nameof(TestDataFactories))]
[Theory] public void TestReader(AbstractTestDataFactory<IMatFile> testFactory)
[InlineData("good")]
public void TestReader(string testSet)
{ {
foreach (var matFile in GetTests(testSet).GetAllTestData()) foreach (var matFile in testFactory.GetAllTestData())
{ {
Assert.NotEmpty(matFile.Variables); Assert.NotEmpty(matFile.Variables);
} }
@ -32,10 +30,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading lower and upper limits of integer data types. /// Test reading lower and upper limits of integer data types.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestLimits() public void TestLimits(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["limits"]; var matFile = testFactory["limits"];
IArray array; IArray array;
array = matFile["int8_"].Value; array = matFile["int8_"].Value;
CheckLimits(array as IArrayOf<sbyte>, CommonData.Int8Limits); CheckLimits(array as IArrayOf<sbyte>, CommonData.Int8Limits);
@ -66,10 +64,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test writing lower and upper limits of integer-based complex data types. /// Test writing lower and upper limits of integer-based complex data types.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestComplexLimits() public void TestComplexLimits(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["limits_complex"]; var matFile = testFactory["limits_complex"];
IArray array; IArray array;
array = matFile["int8_complex"].Value; array = matFile["int8_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<sbyte>>, CommonData.Int8Limits); CheckComplexLimits(array as IArrayOf<ComplexOf<sbyte>>, CommonData.Int8Limits);
@ -102,10 +100,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading an ASCII-encoded string. /// Test reading an ASCII-encoded string.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestAscii() public void TestAscii(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["ascii"]; var matFile = testFactory["ascii"];
var arrayAscii = matFile["s"].Value as ICharArray; var arrayAscii = matFile["s"].Value as ICharArray;
Assert.NotNull(arrayAscii); Assert.NotNull(arrayAscii);
Assert.Equal(new[] { 1, 3 }, arrayAscii.Dimensions); Assert.Equal(new[] { 1, 3 }, arrayAscii.Dimensions);
@ -116,10 +114,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a Unicode string. /// Test reading a Unicode string.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestUnicode() public void TestUnicode(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["unicode"]; var matFile = testFactory["unicode"];
var arrayUnicode = matFile["s"].Value as ICharArray; var arrayUnicode = matFile["s"].Value as ICharArray;
Assert.NotNull(arrayUnicode); Assert.NotNull(arrayUnicode);
Assert.Equal(new[] { 1, 2 }, arrayUnicode.Dimensions); Assert.Equal(new[] { 1, 2 }, arrayUnicode.Dimensions);
@ -131,10 +129,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a wide Unicode string. /// Test reading a wide Unicode string.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestUnicodeWide() public void TestUnicodeWide(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["unicode-wide"]; var matFile = testFactory["unicode-wide"];
var arrayUnicodeWide = matFile["s"].Value as ICharArray; var arrayUnicodeWide = matFile["s"].Value as ICharArray;
Assert.NotNull(arrayUnicodeWide); Assert.NotNull(arrayUnicodeWide);
Assert.Equal(new[] { 1, 2 }, arrayUnicodeWide.Dimensions); Assert.Equal(new[] { 1, 2 }, arrayUnicodeWide.Dimensions);
@ -144,10 +142,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test converting a structure array to a Double array. /// Test converting a structure array to a Double array.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestConvertToDoubleArray() public void TestConvertToDoubleArray(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["struct"]; var matFile = testFactory["struct"];
var array = matFile.Variables[0].Value; var array = matFile.Variables[0].Value;
Assert.Null(array.ConvertToDoubleArray()); Assert.Null(array.ConvertToDoubleArray());
} }
@ -156,10 +154,10 @@ namespace MatFileHandler.Tests
/// Test converting a structure array to a Complex array. /// Test converting a structure array to a Complex array.
/// </summary> /// </summary>
/// <returns>Should return null.</returns> /// <returns>Should return null.</returns>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestConvertToComplexArray() public void TestConvertToComplexArray(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["struct"]; var matFile = testFactory["struct"];
var array = matFile.Variables[0].Value; var array = matFile.Variables[0].Value;
Assert.Null(array.ConvertToComplexArray()); Assert.Null(array.ConvertToComplexArray());
} }
@ -167,10 +165,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading an enumeration. /// Test reading an enumeration.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestEnum() public void TestEnum(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["enum"]; var matFile = testFactory["enum"];
var days = matFile["days"].Value; var days = matFile["days"].Value;
var enumeration = new EnumAdapter(days); var enumeration = new EnumAdapter(days);
Assert.Equal(5, enumeration.Values.Count); Assert.Equal(5, enumeration.Values.Count);
@ -184,10 +182,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a structure array. /// Test reading a structure array.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestStruct() public void TestStruct(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["struct"]; var matFile = testFactory["struct"];
var structure = matFile["struct_"].Value as IStructureArray; var structure = matFile["struct_"].Value as IStructureArray;
Assert.NotNull(structure); Assert.NotNull(structure);
Assert.Equal(new[] { "x", "y" }, structure.FieldNames); Assert.Equal(new[] { "x", "y" }, structure.FieldNames);
@ -238,10 +236,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a sparse array. /// Test reading a sparse array.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestSparse() public void TestSparse(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["sparse"]; var matFile = testFactory["sparse"];
var sparseArray = matFile["sparse_"].Value as ISparseArrayOf<double>; var sparseArray = matFile["sparse_"].Value as ISparseArrayOf<double>;
Assert.NotNull(sparseArray); Assert.NotNull(sparseArray);
Assert.Equal(new[] { 4, 5 }, sparseArray.Dimensions); Assert.Equal(new[] { 4, 5 }, sparseArray.Dimensions);
@ -268,10 +266,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a logical array. /// Test reading a logical array.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestLogical() public void TestLogical(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["logical"]; var matFile = testFactory["logical"];
var array = matFile["logical_"].Value; var array = matFile["logical_"].Value;
var logicalArray = array as IArrayOf<bool>; var logicalArray = array as IArrayOf<bool>;
Assert.NotNull(logicalArray); Assert.NotNull(logicalArray);
@ -286,10 +284,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a sparse logical array. /// Test reading a sparse logical array.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestSparseLogical() public void TestSparseLogical(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["sparse_logical"]; var matFile = testFactory["sparse_logical"];
var array = matFile["sparse_logical"].Value; var array = matFile["sparse_logical"].Value;
var sparseArray = array as ISparseArrayOf<bool>; var sparseArray = array as ISparseArrayOf<bool>;
Assert.NotNull (sparseArray); Assert.NotNull (sparseArray);
@ -305,10 +303,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a global variable. /// Test reading a global variable.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestGlobal() public void TestGlobal(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["global"]; var matFile = testFactory["global"];
var variable = matFile.Variables.First(); var variable = matFile.Variables.First();
Assert.True(variable.IsGlobal); Assert.True(variable.IsGlobal);
} }
@ -316,10 +314,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a sparse complex array. /// Test reading a sparse complex array.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TextSparseComplex() public void TextSparseComplex(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["sparse_complex"]; var matFile = testFactory["sparse_complex"];
var array = matFile["sparse_complex"].Value; var array = matFile["sparse_complex"].Value;
var sparseArray = array as ISparseArrayOf<Complex>; var sparseArray = array as ISparseArrayOf<Complex>;
Assert.NotNull(sparseArray); Assert.NotNull(sparseArray);
@ -332,10 +330,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading an object. /// Test reading an object.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestObject() public void TestObject(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["object"]; var matFile = testFactory["object"];
var obj = matFile["object_"].Value as IMatObject; var obj = matFile["object_"].Value as IMatObject;
Assert.NotNull(obj); Assert.NotNull(obj);
Assert.Equal("Point", obj.ClassName); Assert.Equal("Point", obj.ClassName);
@ -349,10 +347,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading another object. /// Test reading another object.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestObject2() public void TestObject2(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["object2"]; var matFile = testFactory["object2"];
var obj = matFile["object2"].Value as IMatObject; var obj = matFile["object2"].Value as IMatObject;
Assert.NotNull(obj); Assert.NotNull(obj);
Assert.Equal("Point", obj.ClassName); Assert.Equal("Point", obj.ClassName);
@ -372,10 +370,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a table. /// Test reading a table.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestTable() public void TestTable(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["table"]; var matFile = testFactory["table"];
var obj = matFile["table_"].Value as IMatObject; var obj = matFile["table_"].Value as IMatObject;
var table = new TableAdapter(obj); var table = new TableAdapter(obj);
Assert.Equal(3, table.NumberOfRows); Assert.Equal(3, table.NumberOfRows);
@ -393,10 +391,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test reading a table with strings /// Test reading a table with strings
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestTableWithStrings() public void TestTableWithStrings(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["table-with-strings"]; var matFile = testFactory["table-with-strings"];
var obj = matFile["t"].Value as IMatObject; var obj = matFile["t"].Value as IMatObject;
var table = new TableAdapter(obj); var table = new TableAdapter(obj);
Assert.Equal(5, table.NumberOfRows); Assert.Equal(5, table.NumberOfRows);
@ -418,10 +416,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test subobjects within objects. /// Test subobjects within objects.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestSubobjects() public void TestSubobjects(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["pointWithSubpoints"]; var matFile = testFactory["pointWithSubpoints"];
var p = matFile["p"].Value as IMatObject; var p = matFile["p"].Value as IMatObject;
Assert.Equal("Point", p.ClassName); Assert.Equal("Point", p.ClassName);
var x = p["x"] as IMatObject; var x = p["x"] as IMatObject;
@ -441,10 +439,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test nested objects. /// Test nested objects.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestNestedObjects() public void TestNestedObjects(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["subsubPoint"]; var matFile = testFactory["subsubPoint"];
var p = matFile["p"].Value as IMatObject; var p = matFile["p"].Value as IMatObject;
Assert.Equal("Point", p.ClassName); Assert.Equal("Point", p.ClassName);
Assert.Equal(new[] { 1.0 }, p["x"].ConvertToDoubleArray()); Assert.Equal(new[] { 1.0 }, p["x"].ConvertToDoubleArray());
@ -459,10 +457,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test datetime objects. /// Test datetime objects.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestDatetime() public void TestDatetime(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["datetime"]; var matFile = testFactory["datetime"];
var d = matFile["d"].Value as IMatObject; var d = matFile["d"].Value as IMatObject;
var datetime = new DatetimeAdapter(d); var datetime = new DatetimeAdapter(d);
Assert.Equal(new[] { 1, 2 }, datetime.Dimensions); Assert.Equal(new[] { 1, 2 }, datetime.Dimensions);
@ -473,10 +471,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Another test for datetime objects. /// Another test for datetime objects.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestDatetime2() public void TestDatetime2(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["datetime2"]; var matFile = testFactory["datetime2"];
var d = matFile["d"].Value as IMatObject; var d = matFile["d"].Value as IMatObject;
var datetime = new DatetimeAdapter(d); var datetime = new DatetimeAdapter(d);
Assert.Equal(new[] { 1, 1 }, datetime.Dimensions); Assert.Equal(new[] { 1, 1 }, datetime.Dimensions);
@ -488,10 +486,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test string objects. /// Test string objects.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestString() public void TestString(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["string"]; var matFile = testFactory["string"];
var s = matFile["s"].Value as IMatObject; var s = matFile["s"].Value as IMatObject;
var str = new StringAdapter(s); var str = new StringAdapter(s);
Assert.Equal(new[] { 4, 1 }, str.Dimensions); Assert.Equal(new[] { 4, 1 }, str.Dimensions);
@ -504,10 +502,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test duration objects. /// Test duration objects.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestDuration() public void TestDuration(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["duration"]; var matFile = testFactory["duration"];
var d = matFile["d"].Value as IMatObject; var d = matFile["d"].Value as IMatObject;
var duration = new DurationAdapter(d); var duration = new DurationAdapter(d);
Assert.Equal(new[] { 1, 3 }, duration.Dimensions); Assert.Equal(new[] { 1, 3 }, duration.Dimensions);
@ -519,10 +517,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test unrepresentable datetime. /// Test unrepresentable datetime.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void TestDatetime_Unrepresentable() public void TestDatetime_Unrepresentable(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["datetime-unrepresentable"]; var matFile = testFactory["datetime-unrepresentable"];
var obj = matFile["d"].Value as IMatObject; var obj = matFile["d"].Value as IMatObject;
var datetime = new DatetimeAdapter(obj); var datetime = new DatetimeAdapter(obj);
var d0 = datetime[0]; var d0 = datetime[0];
@ -532,10 +530,10 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// Test 3-dimensional arrays. /// Test 3-dimensional arrays.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void Test_3DArrays() public void Test_3DArrays(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["issue20.mat"]; var matFile = testFactory["issue20.mat"];
var obj = matFile["a3d"].Value; var obj = matFile["a3d"].Value;
var values = obj.ConvertToDoubleArray(); var values = obj.ConvertToDoubleArray();
Assert.Equal(Enumerable.Range(1, 24).Select(x => (double)x).ToArray(), values); Assert.Equal(Enumerable.Range(1, 24).Select(x => (double)x).ToArray(), values);
@ -563,21 +561,34 @@ namespace MatFileHandler.Tests
Assert.Equal(expected, obj.ConvertToMultidimensionalDoubleArray()); Assert.Equal(expected, obj.ConvertToMultidimensionalDoubleArray());
Assert.Null(obj.ConvertTo2dDoubleArray()); Assert.Null(obj.ConvertTo2dDoubleArray());
} }
/// <summary> /// <summary>
/// Test four-dimensional arrays. /// Test four-dimensional arrays.
/// </summary> /// </summary>
[Fact] [Theory, MemberData(nameof(TestDataFactories))]
public void Test_4DArrays() public void Test_4DArrays(AbstractTestDataFactory<IMatFile> testFactory)
{ {
var matFile = GetTests("good")["issue20.mat"]; var matFile = testFactory["issue20.mat"];
var obj = matFile["a4d"].Value; var obj = matFile["a4d"].Value;
Assert.Equal(Enumerable.Range(1, 120).Select(x => (double)x).ToArray(), obj.ConvertToDoubleArray()); Assert.Equal(Enumerable.Range(1, 120).Select(x => (double)x).ToArray(), obj.ConvertToDoubleArray());
Assert.Null(obj.ConvertTo2dDoubleArray()); Assert.Null(obj.ConvertTo2dDoubleArray());
} }
private static AbstractTestDataFactory<IMatFile> GetTests(string factoryName) => /// <summary>
new PartialReadMatTestDataFactory(Path.Combine(TestDirectory, factoryName)); /// Returns the factories that provide test data in various configurations.
/// </summary>
public static TheoryData<AbstractTestDataFactory<IMatFile>> TestDataFactories
{
get
{
return new TheoryData<AbstractTestDataFactory<IMatFile>>
{
new MatTestDataFactory(Path.Combine(TestDirectory, "good")),
new PartialReadMatTestDataFactory(Path.Combine(TestDirectory, "good")),
new UnalignedMatTestDataFactory(Path.Combine(TestDirectory, "good")),
};
}
}
private static void CheckLimits<T>(IArrayOf<T> array, T[] limits) private static void CheckLimits<T>(IArrayOf<T> array, T[] limits)
where T : struct where T : struct

View File

@ -6,7 +6,7 @@ namespace MatFileHandler.Tests
{ {
/// <summary> /// <summary>
/// Factory providing the parsed contents of .mat files, /// Factory providing the parsed contents of .mat files,
/// wrapped in a <see cref="PartialReadStream"/>. /// wrapped in a <see cref="PartialUnseekableReadStream"/>.
/// </summary> /// </summary>
public class PartialReadMatTestDataFactory : MatTestDataFactory public class PartialReadMatTestDataFactory : MatTestDataFactory
{ {
@ -26,7 +26,7 @@ namespace MatFileHandler.Tests
/// <returns>Parsed contents of the file.</returns> /// <returns>Parsed contents of the file.</returns>
protected override IMatFile ReadDataFromStream(Stream stream) protected override IMatFile ReadDataFromStream(Stream stream)
{ {
using (var wrapper = new PartialReadStream(stream)) using (var wrapper = new PartialUnseekableReadStream(stream))
{ {
return base.ReadDataFromStream(wrapper); return base.ReadDataFromStream(wrapper);
} }

View File

@ -8,15 +8,15 @@ namespace MatFileHandler.Tests
/// <summary> /// <summary>
/// A stream which wraps another stream and only reads one byte at a time. /// A stream which wraps another stream and only reads one byte at a time.
/// </summary> /// </summary>
internal class PartialReadStream : Stream internal class PartialUnseekableReadStream : Stream
{ {
private readonly Stream _baseStream; private readonly Stream _baseStream;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PartialReadStream"/> class. /// Initializes a new instance of the <see cref="PartialUnseekableReadStream"/> class.
/// </summary> /// </summary>
/// <param name="baseStream">The stream to wrap.</param> /// <param name="baseStream">The stream to wrap.</param>
public PartialReadStream(Stream baseStream) public PartialUnseekableReadStream(Stream baseStream)
{ {
_baseStream = baseStream; _baseStream = baseStream;
} }
@ -25,7 +25,7 @@ namespace MatFileHandler.Tests
public override bool CanRead => _baseStream.CanRead; public override bool CanRead => _baseStream.CanRead;
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanSeek => _baseStream.CanSeek; public override bool CanSeek => false;
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanWrite => false; public override bool CanWrite => false;
@ -36,8 +36,8 @@ namespace MatFileHandler.Tests
/// <inheritdoc/> /// <inheritdoc/>
public override long Position public override long Position
{ {
get => _baseStream.Position; get => throw new NotSupportedException();
set => _baseStream.Position = value; set => throw new NotSupportedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -55,13 +55,13 @@ namespace MatFileHandler.Tests
/// <inheritdoc/> /// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin) public override long Seek(long offset, SeekOrigin origin)
{ {
return _baseStream.Seek(offset, origin); throw new NotSupportedException();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void SetLength(long value) public override void SetLength(long value)
{ {
_baseStream.SetLength(value); throw new NotSupportedException();
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@ -0,0 +1,37 @@
// Copyright 2017-2018 Alexander Luzgarev
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// Factory providing the parsed contents of .mat files,
/// which start at a non-8 byte-aligned offset in the stream.
/// </summary>
public class UnalignedMatTestDataFactory : MatTestDataFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="PartialReadMatTestDataFactory"/> class.
/// </summary>
/// <param name="testDirectory">Directory containing test files.</param>
public UnalignedMatTestDataFactory(string testDirectory)
: base(testDirectory)
{
}
/// <inheritdoc/>
protected override IMatFile ReadDataFromStream(Stream stream)
{
using (var ms = new MemoryStream())
{
ms.Seek(3, SeekOrigin.Begin);
stream.CopyTo(ms);
ms.Seek(3, SeekOrigin.Begin);
return base.ReadDataFromStream(ms);
}
}
}
}

View File

@ -343,30 +343,33 @@ namespace MatFileHandler
return new MatStructureArray(flags, dimensions, name, fields); return new MatStructureArray(flags, dimensions, name, fields);
} }
private DataElement Read(Stream stream)
{
using (var reader = new BinaryReader(stream))
{
return Read(reader);
}
}
private DataElement ReadCompressed(Tag tag, BinaryReader reader) private DataElement ReadCompressed(Tag tag, BinaryReader reader)
{ {
reader.ReadBytes(2); reader.ReadBytes(2);
var compressedData = reader.ReadBytes(tag.Length - 6);
reader.ReadBytes(4); DataElement element;
var resultStream = new MemoryStream();
using (var compressedStream = new MemoryStream(compressedData)) using (var substream = new Substream(reader.BaseStream, tag.Length - 6))
{ {
using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true)) using (var deflateStream = new DeflateStream(substream, CompressionMode.Decompress))
using (var bufferedStream = new BufferedStream(deflateStream))
using (var positionTrackingStream = new PositionTrackingStream(bufferedStream))
using (var innerReader = new BinaryReader(positionTrackingStream))
{ {
stream.CopyTo(resultStream); element = Read(innerReader);
}
if (substream.Position != substream.Length)
{
// In the pathological case that the deflate stream did not read the full
// length, then read out the rest manually (normally 1 byte).
reader.ReadBytes((int)(substream.Length - substream.Position));
} }
} }
resultStream.Position = 0; reader.ReadBytes(4);
return Read(resultStream);
return element;
} }
private DataElement ReadMatrix(Tag tag, BinaryReader reader) private DataElement ReadMatrix(Tag tag, BinaryReader reader)

View File

@ -28,7 +28,7 @@ namespace MatFileHandler
/// <returns>Contents of the file.</returns> /// <returns>Contents of the file.</returns>
public IMatFile Read() public IMatFile Read()
{ {
using (var reader = new BinaryReader(Stream)) using (var reader = new BinaryReader(new PositionTrackingStream(Stream)))
{ {
return Read(reader); return Read(reader);
} }

View File

@ -0,0 +1,77 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.IO;
namespace MatFileHandler;
/// <summary>
/// A stream which wraps another stream and tracks the number of bytes read
/// for the purpose of adjusting for padding.
/// </summary>
internal sealed class PositionTrackingStream : Stream
{
private readonly Stream _baseStream;
private long _position;
/// <summary>
/// Initializes a new instance of the <see cref="PositionTrackingStream"/> class.
/// </summary>
/// <param name="baseStream">The stream to wrap.</param>
public PositionTrackingStream(Stream baseStream)
{
_baseStream = baseStream;
}
/// <inheritdoc/>
public override bool CanRead => _baseStream.CanRead;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <inheritdoc/>
public override long Length => _baseStream.Length;
/// <inheritdoc/>
public override long Position
{
get => _position;
set => throw new NotSupportedException();
}
/// <inheritdoc/>
public override void Flush() => _baseStream.Flush();
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
int bytesRead = _baseStream.Read(buffer, offset, count);
_position += bytesRead;
return bytesRead;
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
/// <inheritdoc/>
public override void SetLength(long value) => throw new NotSupportedException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_baseStream.Dispose();
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,68 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.IO;
namespace MatFileHandler
{
/// <summary>
/// A stream which reads a finite section of another stream.
/// </summary>
internal sealed class Substream : Stream
{
private readonly Stream _baseStream;
private long _bytesRead;
/// <summary>
/// Initializes a new instance of the <see cref="Substream"/> class.
/// </summary>
/// <param name="baseStream">The <see cref="Stream"/> to wrap.</param>
/// <param name="length">The number of bytes readable from this <see cref="Substream"/>.</param>
public Substream(Stream baseStream, long length)
{
_baseStream = baseStream;
Length = length;
}
/// <inheritdoc/>
public override bool CanRead => _baseStream.CanRead;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <inheritdoc/>
public override long Length { get; }
/// <inheritdoc/>
public override long Position
{
get => _bytesRead;
set => throw new NotSupportedException();
}
/// <inheritdoc/>
public override void Flush() => _baseStream.Flush();
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
int bytesRead = _baseStream.Read(buffer, offset, (int)Math.Min(count, Length - _bytesRead));
_bytesRead += bytesRead;
return bytesRead;
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc/>
public override void SetLength(long value) => throw new NotSupportedException();
}
}