Merge pull request #36 from mahalex/dev/better-writer
Better MatFileWriter
This commit is contained in:
commit
489d93eff1
106
MatFileHandler.Tests/ChecksumCalculatingStreamTests.cs
Normal file
106
MatFileHandler.Tests/ChecksumCalculatingStreamTests.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace MatFileHandler.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for the <see cref="ChecksumCalculatingStream"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public class ChecksumCalculatingStreamTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test writing various things.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action"></param>
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TestData))]
|
||||||
|
public void Test(Action<Stream> action)
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
var sut = new ChecksumCalculatingStream(stream);
|
||||||
|
action(sut);
|
||||||
|
var actual = sut.GetCrc();
|
||||||
|
var expected = ReferenceCalculation(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test data for <see cref="Test"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Test data.</returns>
|
||||||
|
public static IEnumerable<object[]> TestData()
|
||||||
|
{
|
||||||
|
foreach (var data in TestData_Typed())
|
||||||
|
{
|
||||||
|
yield return new object[] { data };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Action<Stream>> TestData_Typed()
|
||||||
|
{
|
||||||
|
yield return BinaryWriterAction(w => w.Write(true));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(false));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(byte.MinValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(byte.MaxValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(short.MinValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(short.MaxValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(int.MinValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(int.MaxValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(long.MinValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(long.MaxValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(decimal.MinValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(decimal.MaxValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(double.MinValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(double.MaxValue));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(double.PositiveInfinity));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(double.NaN));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(new byte[] { 1, 2, 3, 4, 5, 6, 7 }));
|
||||||
|
yield return BinaryWriterAction(w => w.Write(Enumerable.Range(0, 255).SelectMany(x => Enumerable.Range(0, 255)).Select(x => (byte)x).ToArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Action<Stream> BinaryWriterAction(Action<BinaryWriter> action)
|
||||||
|
{
|
||||||
|
return stream =>
|
||||||
|
{
|
||||||
|
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||||
|
action(writer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint ReferenceCalculation(Action<Stream> action)
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
action(stream);
|
||||||
|
stream.Position = 0;
|
||||||
|
return CalculateAdler32Checksum(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint CalculateAdler32Checksum(Stream stream)
|
||||||
|
{
|
||||||
|
uint s1 = 1;
|
||||||
|
uint s2 = 0;
|
||||||
|
const uint bigPrime = 0xFFF1;
|
||||||
|
const int bufferSize = 2048;
|
||||||
|
var buffer = new byte[bufferSize];
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var bytesRead = stream.Read(buffer, 0, bufferSize);
|
||||||
|
for (var i = 0; i < bytesRead; i++)
|
||||||
|
{
|
||||||
|
s1 = (s1 + buffer[i]) % bigPrime;
|
||||||
|
s2 = (s2 + s1) % bigPrime;
|
||||||
|
}
|
||||||
|
if (bytesRead < bufferSize)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (s2 << 16) | s1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net472</TargetFrameworks>
|
<TargetFrameworks>net461;net472;net8.0</TargetFrameworks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
<LangVersion>10.0</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
|
||||||
@ -10,7 +11,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Condition=" '$(TargetFramework)' != 'net461' " Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Condition=" '$(TargetFramework)' == 'net461' " Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -17,8 +17,8 @@ namespace MatFileHandler.Tests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a simple Double array.
|
/// Test writing a simple Double array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestWrite()
|
public void TestWrite(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var array = builder.NewArray<double>(1, 2);
|
var array = builder.NewArray<double>(1, 2);
|
||||||
@ -26,7 +26,7 @@ namespace MatFileHandler.Tests
|
|||||||
array[1] = 17.0;
|
array[1] = 17.0;
|
||||||
var variable = builder.NewVariable("test", array);
|
var variable = builder.NewVariable("test", array);
|
||||||
var actual = builder.NewFile(new[] { variable });
|
var actual = builder.NewFile(new[] { variable });
|
||||||
MatCompareWithTestData("good", "double-array", actual);
|
MatCompareWithTestData("good", "double-array", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -51,8 +51,8 @@ namespace MatFileHandler.Tests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing lower and upper limits of integer data types.
|
/// Test writing lower and upper limits of integer data types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestLimits()
|
public void TestLimits(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2));
|
var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2));
|
||||||
@ -64,14 +64,14 @@ namespace MatFileHandler.Tests
|
|||||||
var int64 = builder.NewVariable("int64_", builder.NewArray(CommonData.Int64Limits, 1, 2));
|
var int64 = builder.NewVariable("int64_", builder.NewArray(CommonData.Int64Limits, 1, 2));
|
||||||
var uint64 = builder.NewVariable("uint64_", builder.NewArray(CommonData.UInt64Limits, 1, 2));
|
var uint64 = builder.NewVariable("uint64_", builder.NewArray(CommonData.UInt64Limits, 1, 2));
|
||||||
var actual = builder.NewFile(new[] { int16, int32, int64, int8, uint16, uint32, uint64, uint8 });
|
var actual = builder.NewFile(new[] { int16, int32, int64, int8, uint16, uint32, uint64, uint8 });
|
||||||
MatCompareWithTestData("good", "limits", actual);
|
MatCompareWithTestData("good", "limits", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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(MatFileWritingMethods))]
|
||||||
public void TestLimitsComplex()
|
public void TestLimitsComplex(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var int8Complex = builder.NewVariable(
|
var int8Complex = builder.NewVariable(
|
||||||
@ -103,26 +103,26 @@ namespace MatFileHandler.Tests
|
|||||||
int16Complex, int32Complex, int64Complex, int8Complex,
|
int16Complex, int32Complex, int64Complex, int8Complex,
|
||||||
uint16Complex, uint32Complex, uint64Complex, uint8Complex,
|
uint16Complex, uint32Complex, uint64Complex, uint8Complex,
|
||||||
});
|
});
|
||||||
MatCompareWithTestData("good", "limits_complex", actual);
|
MatCompareWithTestData("good", "limits_complex", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a wide-Unicode symbol.
|
/// Test writing a wide-Unicode symbol.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestUnicodeWide()
|
public void TestUnicodeWide(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var s = builder.NewVariable("s", builder.NewCharArray("🍆"));
|
var s = builder.NewVariable("s", builder.NewCharArray("🍆"));
|
||||||
var actual = builder.NewFile(new[] { s });
|
var actual = builder.NewFile(new[] { s });
|
||||||
MatCompareWithTestData("good", "unicode-wide", actual);
|
MatCompareWithTestData("good", "unicode-wide", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a sparse array.
|
/// Test writing a sparse array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestSparseArray()
|
public void TestSparseArray(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var sparseArray = builder.NewSparseArray<double>(4, 5);
|
var sparseArray = builder.NewSparseArray<double>(4, 5);
|
||||||
@ -132,14 +132,14 @@ namespace MatFileHandler.Tests
|
|||||||
sparseArray[2, 3] = 4;
|
sparseArray[2, 3] = 4;
|
||||||
var sparse = builder.NewVariable("sparse_", sparseArray);
|
var sparse = builder.NewVariable("sparse_", sparseArray);
|
||||||
var actual = builder.NewFile(new[] { sparse });
|
var actual = builder.NewFile(new[] { sparse });
|
||||||
MatCompareWithTestData("good", "sparse", actual);
|
MatCompareWithTestData("good", "sparse", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a structure array.
|
/// Test writing a structure array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestStructure()
|
public void TestStructure(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
|
var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
|
||||||
@ -160,27 +160,27 @@ namespace MatFileHandler.Tests
|
|||||||
structure["y", 1, 2] = builder.NewEmpty();
|
structure["y", 1, 2] = builder.NewEmpty();
|
||||||
var struct_ = builder.NewVariable("struct_", structure);
|
var struct_ = builder.NewVariable("struct_", structure);
|
||||||
var actual = builder.NewFile(new[] { struct_ });
|
var actual = builder.NewFile(new[] { struct_ });
|
||||||
MatCompareWithTestData("good", "struct", actual);
|
MatCompareWithTestData("good", "struct", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a logical array.
|
/// Test writing a logical array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestLogical()
|
public void TestLogical(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var logical = builder.NewArray(new[] { true, false, true, true, false, true }, 2, 3);
|
var logical = builder.NewArray(new[] { true, false, true, true, false, true }, 2, 3);
|
||||||
var logicalVariable = builder.NewVariable("logical_", logical);
|
var logicalVariable = builder.NewVariable("logical_", logical);
|
||||||
var actual = builder.NewFile(new[] { logicalVariable });
|
var actual = builder.NewFile(new[] { logicalVariable });
|
||||||
MatCompareWithTestData("good", "logical", actual);
|
MatCompareWithTestData("good", "logical", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a sparse logical array.
|
/// Test writing a sparse logical array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestSparseLogical()
|
public void TestSparseLogical(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var array = builder.NewSparseArray<bool>(2, 3);
|
var array = builder.NewSparseArray<bool>(2, 3);
|
||||||
@ -190,14 +190,14 @@ namespace MatFileHandler.Tests
|
|||||||
array[1, 2] = true;
|
array[1, 2] = true;
|
||||||
var sparseLogical = builder.NewVariable("sparse_logical", array);
|
var sparseLogical = builder.NewVariable("sparse_logical", array);
|
||||||
var actual = builder.NewFile(new[] { sparseLogical });
|
var actual = builder.NewFile(new[] { sparseLogical });
|
||||||
MatCompareWithTestData("good", "sparse_logical", actual);
|
MatCompareWithTestData("good", "sparse_logical", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a sparse complex array.
|
/// Test writing a sparse complex array.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestSparseComplex()
|
public void TestSparseComplex(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var array = builder.NewSparseArray<Complex>(2, 2);
|
var array = builder.NewSparseArray<Complex>(2, 2);
|
||||||
@ -206,20 +206,42 @@ namespace MatFileHandler.Tests
|
|||||||
array[1, 1] = 0.5 + Complex.ImaginaryOne;
|
array[1, 1] = 0.5 + Complex.ImaginaryOne;
|
||||||
var sparseComplex = builder.NewVariable("sparse_complex", array);
|
var sparseComplex = builder.NewVariable("sparse_complex", array);
|
||||||
var actual = builder.NewFile(new[] { sparseComplex });
|
var actual = builder.NewFile(new[] { sparseComplex });
|
||||||
MatCompareWithTestData("good", "sparse_complex", actual);
|
MatCompareWithTestData("good", "sparse_complex", actual, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test writing a global variable.
|
/// Test writing a global variable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Theory, MemberData(nameof(MatFileWritingMethods))]
|
||||||
public void TestGlobal()
|
public void TestGlobal(MatFileWritingMethod method)
|
||||||
{
|
{
|
||||||
var builder = new DataBuilder();
|
var builder = new DataBuilder();
|
||||||
var array = builder.NewArray(new double[] { 1, 3, 5 }, 1, 3);
|
var array = builder.NewArray(new double[] { 1, 3, 5 }, 1, 3);
|
||||||
var global = builder.NewVariable("global_", array, true);
|
var global = builder.NewVariable("global_", array, true);
|
||||||
var actual = builder.NewFile(new[] { global });
|
var actual = builder.NewFile(new[] { global });
|
||||||
MatCompareWithTestData("good", "global", actual);
|
MatCompareWithTestData("good", "global", actual, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Various writing methods for testing writing of .mat files.
|
||||||
|
/// </summary>
|
||||||
|
public static TheoryData<MatFileWritingMethod> MatFileWritingMethods
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<MatFileWritingMethod>
|
||||||
|
{
|
||||||
|
new MatFileWritingToMemoryStream(null),
|
||||||
|
new MatFileWritingToMemoryStream(new MatFileWriterOptions { UseCompression = CompressionUsage.Always }),
|
||||||
|
new MatFileWritingToMemoryStream(new MatFileWriterOptions { UseCompression = CompressionUsage.Never }),
|
||||||
|
new MatFileWritingToUnseekableStream(null),
|
||||||
|
new MatFileWritingToUnseekableStream(new MatFileWriterOptions { UseCompression = CompressionUsage.Always }),
|
||||||
|
new MatFileWritingToUnseekableStream(new MatFileWriterOptions { UseCompression = CompressionUsage.Never }),
|
||||||
|
new MatFileWritingToUnalignedMemoryStream(null),
|
||||||
|
new MatFileWritingToUnalignedMemoryStream(new MatFileWriterOptions { UseCompression = CompressionUsage.Always }),
|
||||||
|
new MatFileWritingToUnalignedMemoryStream(new MatFileWriterOptions { UseCompression = CompressionUsage.Never }),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AbstractTestDataFactory<IMatFile> GetMatTestData(string factoryName) =>
|
private static AbstractTestDataFactory<IMatFile> GetMatTestData(string factoryName) =>
|
||||||
@ -375,41 +397,19 @@ namespace MatFileHandler.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CompareTestDataWithWritingOptions(
|
private void MatCompareWithTestData(
|
||||||
IMatFile expected,
|
string factoryName,
|
||||||
|
string testName,
|
||||||
IMatFile actual,
|
IMatFile actual,
|
||||||
MatFileWriterOptions? maybeOptions)
|
MatFileWritingMethod method)
|
||||||
{
|
|
||||||
byte[] buffer;
|
|
||||||
using (var stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
var writer = maybeOptions is MatFileWriterOptions options
|
|
||||||
? new MatFileWriter(stream, options)
|
|
||||||
: new MatFileWriter(stream);
|
|
||||||
writer.Write(actual);
|
|
||||||
buffer = stream.ToArray();
|
|
||||||
}
|
|
||||||
using (var stream = new MemoryStream(buffer))
|
|
||||||
{
|
{
|
||||||
|
var expected = GetMatTestData(factoryName)[testName];
|
||||||
|
var buffer = method.WriteMatFile(actual);
|
||||||
|
using var stream = new MemoryStream(buffer);
|
||||||
var reader = new MatFileReader(stream);
|
var reader = new MatFileReader(stream);
|
||||||
var actualRead = reader.Read();
|
var actualRead = reader.Read();
|
||||||
CompareMatFiles(expected, actualRead);
|
CompareMatFiles(expected, actualRead);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void MatCompareWithTestData(string factoryName, string testName, IMatFile actual)
|
|
||||||
{
|
|
||||||
var expected = GetMatTestData(factoryName)[testName];
|
|
||||||
CompareTestDataWithWritingOptions(expected, actual, null);
|
|
||||||
CompareTestDataWithWritingOptions(
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
new MatFileWriterOptions { UseCompression = CompressionUsage.Always });
|
|
||||||
CompareTestDataWithWritingOptions(
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
new MatFileWriterOptions { UseCompression = CompressionUsage.Never });
|
|
||||||
}
|
|
||||||
|
|
||||||
private ComplexOf<T>[] CreateComplexLimits<T>(T[] limits)
|
private ComplexOf<T>[] CreateComplexLimits<T>(T[] limits)
|
||||||
where T : struct
|
where T : struct
|
||||||
|
17
MatFileHandler.Tests/MatFileWritingMethod.cs
Normal file
17
MatFileHandler.Tests/MatFileWritingMethod.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
namespace MatFileHandler.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A method of writing IMatFile into a byte buffer.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MatFileWritingMethod
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Write an IMatFile into a byte buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="matFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract byte[] WriteMatFile(IMatFile matFile);
|
||||||
|
}
|
||||||
|
}
|
37
MatFileHandler.Tests/MatFileWritingToMemoryStream.cs
Normal file
37
MatFileHandler.Tests/MatFileWritingToMemoryStream.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MatFileHandler.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A method of writing an IMatFile into a MemoryStream.
|
||||||
|
/// </summary>
|
||||||
|
public class MatFileWritingToMemoryStream : MatFileWritingMethod
|
||||||
|
{
|
||||||
|
private readonly MatFileWriterOptions? _maybeOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MatFileWritingToMemoryStream"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maybeOptions">Options for the <see cref="MatFileWriter"/>.</param>
|
||||||
|
public MatFileWritingToMemoryStream(MatFileWriterOptions? maybeOptions)
|
||||||
|
{
|
||||||
|
_maybeOptions = maybeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override byte[] WriteMatFile(IMatFile matFile)
|
||||||
|
{
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
var matFileWriter = _maybeOptions switch
|
||||||
|
{
|
||||||
|
{ } options => new MatFileWriter(memoryStream, options),
|
||||||
|
_ => new MatFileWriter(memoryStream),
|
||||||
|
};
|
||||||
|
|
||||||
|
matFileWriter.Write(matFile);
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MatFileHandler.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A method of writing an IMatFile into a stream that is "unaligned".
|
||||||
|
/// </summary>
|
||||||
|
public class MatFileWritingToUnalignedMemoryStream : MatFileWritingMethod
|
||||||
|
{
|
||||||
|
private readonly MatFileWriterOptions? _maybeOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MatFileWritingToUnalignedMemoryStream"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maybeOptions">Options for the <see cref="MatFileWriter"/>.</param>
|
||||||
|
public MatFileWritingToUnalignedMemoryStream(MatFileWriterOptions? maybeOptions)
|
||||||
|
{
|
||||||
|
_maybeOptions = maybeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override byte[] WriteMatFile(IMatFile matFile)
|
||||||
|
{
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
memoryStream.Seek(3, SeekOrigin.Begin);
|
||||||
|
var matFileWriter = _maybeOptions switch
|
||||||
|
{
|
||||||
|
{ } options => new MatFileWriter(memoryStream, options),
|
||||||
|
_ => new MatFileWriter(memoryStream),
|
||||||
|
};
|
||||||
|
|
||||||
|
matFileWriter.Write(matFile);
|
||||||
|
var fullArray = memoryStream.ToArray();
|
||||||
|
var length = fullArray.Length - 3;
|
||||||
|
var result = new byte[length];
|
||||||
|
Buffer.BlockCopy(fullArray, 3, result, 0, length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
MatFileHandler.Tests/MatFileWritingToUnseekableStream.cs
Normal file
38
MatFileHandler.Tests/MatFileWritingToUnseekableStream.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MatFileHandler.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A method of writing an IMatFile into a stream that is not seekable.
|
||||||
|
/// </summary>
|
||||||
|
public class MatFileWritingToUnseekableStream : MatFileWritingMethod
|
||||||
|
{
|
||||||
|
private readonly MatFileWriterOptions? _maybeOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MatFileWritingToUnseekableStream"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maybeOptions">Options for the <see cref="MatFileWriter"/>.</param>
|
||||||
|
public MatFileWritingToUnseekableStream(MatFileWriterOptions? maybeOptions)
|
||||||
|
{
|
||||||
|
_maybeOptions = maybeOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override byte[] WriteMatFile(IMatFile matFile)
|
||||||
|
{
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
using var unseekableStream = new UnseekableWriteStream(memoryStream);
|
||||||
|
var matFileWriter = _maybeOptions switch
|
||||||
|
{
|
||||||
|
{ } options => new MatFileWriter(unseekableStream, options),
|
||||||
|
_ => new MatFileWriter(unseekableStream),
|
||||||
|
};
|
||||||
|
|
||||||
|
matFileWriter.Write(matFile);
|
||||||
|
return memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,8 @@ using System.IO;
|
|||||||
namespace MatFileHandler.Tests
|
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,
|
||||||
|
/// while forbidding seeking in it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class PartialUnseekableReadStream : Stream
|
internal class PartialUnseekableReadStream : Stream
|
||||||
{
|
{
|
||||||
|
70
MatFileHandler.Tests/UnseekableWriteStream.cs
Normal file
70
MatFileHandler.Tests/UnseekableWriteStream.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MatFileHandler.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A stream which wraps another stream and forbids seeking in it.
|
||||||
|
/// </summary>
|
||||||
|
internal class UnseekableWriteStream : Stream
|
||||||
|
{
|
||||||
|
public UnseekableWriteStream(Stream baseStream)
|
||||||
|
{
|
||||||
|
_baseStream = baseStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Stream _baseStream;
|
||||||
|
|
||||||
|
public override bool CanRead => false;
|
||||||
|
|
||||||
|
public override bool CanSeek => false;
|
||||||
|
|
||||||
|
public override bool CanWrite => _baseStream.CanWrite;
|
||||||
|
|
||||||
|
public override long Length => _baseStream.Length;
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => throw new NotSupportedException();
|
||||||
|
set => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
_baseStream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
_baseStream.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_baseStream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
MatFileHandler/ChecksumCalculatingStream.cs
Normal file
92
MatFileHandler/ChecksumCalculatingStream.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A stream that calculates Adler32 checksum of everything
|
||||||
|
/// written to it before passing to another stream.
|
||||||
|
/// </summary>
|
||||||
|
internal class ChecksumCalculatingStream : Stream
|
||||||
|
{
|
||||||
|
private const uint BigPrime = 0xFFF1;
|
||||||
|
private readonly Stream _stream;
|
||||||
|
private uint s1 = 1;
|
||||||
|
private uint s2 = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChecksumCalculatingStream"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">Wrapped stream.</param>
|
||||||
|
public ChecksumCalculatingStream(Stream stream)
|
||||||
|
{
|
||||||
|
_stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanRead => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanSeek => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool CanWrite => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override long Length => throw new NotImplementedException();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
_stream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
for (var i = offset; i < offset + count; i++)
|
||||||
|
{
|
||||||
|
s1 = (s1 + buffer[i]) % BigPrime;
|
||||||
|
s2 = (s2 + s1) % BigPrime;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stream.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate the checksum of everything written to the stream so far.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Checksum of everything written to the stream so far.</returns>
|
||||||
|
public uint GetCrc()
|
||||||
|
{
|
||||||
|
return (s2 << 16) | s1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
398
MatFileHandler/FakeWriter.cs
Normal file
398
MatFileHandler/FakeWriter.cs
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
// Copyright 2017-2018 Alexander Luzgarev
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MatFileHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A simulated writer of .mat files that just calculate the length of data that would be written.
|
||||||
|
/// </summary>
|
||||||
|
internal class FakeWriter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets current position of the writer.
|
||||||
|
/// </summary>
|
||||||
|
public int Position { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write contents of a numerical array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">A numerical array.</param>
|
||||||
|
/// <param name="name">Name of the array.</param>
|
||||||
|
public void WriteNumericalArrayContents(IArray array, string name)
|
||||||
|
{
|
||||||
|
WriteArrayFlags();
|
||||||
|
WriteDimensions(array.Dimensions);
|
||||||
|
WriteName(name);
|
||||||
|
WriteNumericalArrayValues(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write contents of a char array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="charArray">A char array.</param>
|
||||||
|
/// <param name="name">Name of the array.</param>
|
||||||
|
public void WriteCharArrayContents(ICharArray charArray, string name)
|
||||||
|
{
|
||||||
|
WriteArrayFlags();
|
||||||
|
WriteDimensions(charArray.Dimensions);
|
||||||
|
WriteName(name);
|
||||||
|
WriteDataElement(GetLengthOfByteArray<ushort>(charArray.String.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write contents of a sparse array.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Array element type.</typeparam>
|
||||||
|
/// <param name="array">A sparse array.</param>
|
||||||
|
/// <param name="name">Name of the array.</param>
|
||||||
|
public void WriteSparseArrayContents<T>(
|
||||||
|
ISparseArrayOf<T> array,
|
||||||
|
string name)
|
||||||
|
where T : unmanaged, IEquatable<T>
|
||||||
|
{
|
||||||
|
(var rowsLength, var columnsLength, var dataLength, var nonZero) = PrepareSparseArrayData(array);
|
||||||
|
WriteSparseArrayFlags();
|
||||||
|
WriteDimensions(array.Dimensions);
|
||||||
|
WriteName(name);
|
||||||
|
WriteSparseArrayValues<T>(rowsLength, columnsLength, dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write contents of a structure array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">A structure array.</param>
|
||||||
|
/// <param name="name">Name of the array.</param>
|
||||||
|
public void WriteStructureArrayContents(IStructureArray array, string name)
|
||||||
|
{
|
||||||
|
WriteArrayFlags();
|
||||||
|
WriteDimensions(array.Dimensions);
|
||||||
|
WriteName(name);
|
||||||
|
WriteFieldNames(array.FieldNames);
|
||||||
|
WriteStructureArrayValues(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write contents of a cell array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">A cell array.</param>
|
||||||
|
/// <param name="name">Name of the array.</param>
|
||||||
|
public void WriteCellArrayContents(ICellArray array, string name)
|
||||||
|
{
|
||||||
|
WriteArrayFlags();
|
||||||
|
WriteDimensions(array.Dimensions);
|
||||||
|
WriteName(name);
|
||||||
|
WriteCellArrayValues(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteTag()
|
||||||
|
{
|
||||||
|
Position += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteShortTag()
|
||||||
|
{
|
||||||
|
Position += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteWrappingContents<T>(T array, Action<FakeWriter> writeContents)
|
||||||
|
where T : IArray
|
||||||
|
{
|
||||||
|
if (array.IsEmpty)
|
||||||
|
{
|
||||||
|
WriteTag();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteTag();
|
||||||
|
writeContents(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteNumericalArrayValues(IArray value)
|
||||||
|
{
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case IArrayOf<sbyte> sbyteArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<sbyte>(sbyteArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<byte> byteArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<byte>(byteArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<short> shortArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<short>(shortArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ushort> ushortArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<ushort>(ushortArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<int> intArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<int>(intArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<uint> uintArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<uint>(uintArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<long> longArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<long>(longArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ulong> ulongArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<ulong>(ulongArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<float> floatArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<float>(floatArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<double> doubleArray:
|
||||||
|
WriteDataElement(GetLengthOfByteArray<double>(doubleArray.Data.Length));
|
||||||
|
break;
|
||||||
|
case IArrayOf<bool> boolArray:
|
||||||
|
WriteDataElement(boolArray.Data.Length);
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<sbyte>> complexSbyteArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexSbyteArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<byte>> complexByteArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexByteArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<short>> complexShortArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexShortArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<ushort>> complexUshortArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexUshortArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<int>> complexIntArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexIntArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<uint>> complexUintArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexUintArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<long>> complexLongArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexLongArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<ulong>> complexUlongArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexUlongArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<ComplexOf<float>> complexFloatArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexFloatArray.Data));
|
||||||
|
break;
|
||||||
|
case IArrayOf<Complex> complexDoubleArray:
|
||||||
|
WriteComplexValues(GetLengthOfPairOfByteArrays(complexDoubleArray.Data));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteName(string name)
|
||||||
|
{
|
||||||
|
var nameBytes = Encoding.ASCII.GetBytes(name);
|
||||||
|
WriteDataElement(nameBytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteArrayFlags()
|
||||||
|
{
|
||||||
|
WriteTag();
|
||||||
|
Position += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteDimensions(int[] dimensions)
|
||||||
|
{
|
||||||
|
var buffer = GetLengthOfByteArray<int>(dimensions.Length);
|
||||||
|
WriteDataElement(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe int GetLengthOfByteArray<T>(int dataLength)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
return dataLength * sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe int GetLengthOfPairOfByteArrays<T>(ComplexOf<T>[] data)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
return data.Length * sizeof(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe int GetLengthOfPairOfByteArrays(Complex[] data)
|
||||||
|
{
|
||||||
|
return data.Length * sizeof(double);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CalculatePadding(int length)
|
||||||
|
{
|
||||||
|
var rem = length % 8;
|
||||||
|
if (rem == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 8 - rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteDataElement(int dataLength)
|
||||||
|
{
|
||||||
|
var maybePadding = 0;
|
||||||
|
if (dataLength > 4)
|
||||||
|
{
|
||||||
|
WriteTag();
|
||||||
|
Position += dataLength;
|
||||||
|
maybePadding = CalculatePadding(dataLength + 8);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteShortTag();
|
||||||
|
Position += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position += maybePadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteComplexValues(
|
||||||
|
int dataLength)
|
||||||
|
{
|
||||||
|
WriteDataElement(dataLength);
|
||||||
|
WriteDataElement(dataLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSparseArrayValues<T>(int rowsLength, int columnsLength, int dataLength)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
WriteDataElement(GetLengthOfByteArray<int>(rowsLength));
|
||||||
|
WriteDataElement(GetLengthOfByteArray<int>(columnsLength));
|
||||||
|
if (typeof(T) == typeof(double))
|
||||||
|
{
|
||||||
|
WriteDataElement(GetLengthOfByteArray<double>(dataLength));
|
||||||
|
}
|
||||||
|
else if (typeof(T) == typeof(Complex))
|
||||||
|
{
|
||||||
|
WriteDataElement(GetLengthOfByteArray<double>(dataLength));
|
||||||
|
WriteDataElement(GetLengthOfByteArray<double>(dataLength));
|
||||||
|
}
|
||||||
|
else if (typeof(T) == typeof(bool))
|
||||||
|
{
|
||||||
|
WriteDataElement(dataLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int rowIndexLength, int columnIndexLength, int dataLength, uint nonZero) PrepareSparseArrayData<T>(
|
||||||
|
ISparseArrayOf<T> array)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
var numberOfColumns = array.Dimensions[1];
|
||||||
|
var numberOfElements = array.Data.Values.Count(value => !value.Equals(default));
|
||||||
|
return (numberOfElements, numberOfColumns + 1, numberOfElements, (uint)numberOfElements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSparseArrayFlags()
|
||||||
|
{
|
||||||
|
WriteTag();
|
||||||
|
Position += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteFieldNames(IEnumerable<string> fieldNames)
|
||||||
|
{
|
||||||
|
var fieldNamesArray = fieldNames.Select(name => Encoding.ASCII.GetBytes(name)).ToArray();
|
||||||
|
var maxFieldName = fieldNamesArray.Select(name => name.Length).Max() + 1;
|
||||||
|
WriteDataElement(GetLengthOfByteArray<int>(1));
|
||||||
|
var buffer = new byte[fieldNamesArray.Length * maxFieldName];
|
||||||
|
var startPosition = 0;
|
||||||
|
foreach (var name in fieldNamesArray)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < name.Length; i++)
|
||||||
|
{
|
||||||
|
buffer[startPosition + i] = name[i];
|
||||||
|
}
|
||||||
|
startPosition += maxFieldName;
|
||||||
|
}
|
||||||
|
WriteDataElement(buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteStructureArrayValues(IStructureArray array)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < array.Count; i++)
|
||||||
|
{
|
||||||
|
foreach (var name in array.FieldNames)
|
||||||
|
{
|
||||||
|
WriteArray(array[name, i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteArray(IArray array, string variableName = "", bool isGlobal = false)
|
||||||
|
{
|
||||||
|
switch (array)
|
||||||
|
{
|
||||||
|
case ICharArray charArray:
|
||||||
|
WriteCharArray(charArray, variableName);
|
||||||
|
break;
|
||||||
|
case ISparseArrayOf<double> doubleSparseArray:
|
||||||
|
WriteSparseArray(doubleSparseArray, variableName);
|
||||||
|
break;
|
||||||
|
case ISparseArrayOf<Complex> complexSparseArray:
|
||||||
|
WriteSparseArray(complexSparseArray, variableName);
|
||||||
|
break;
|
||||||
|
case ISparseArrayOf<bool> boolSparseArray:
|
||||||
|
WriteSparseArray(boolSparseArray, variableName);
|
||||||
|
break;
|
||||||
|
case ICellArray cellArray:
|
||||||
|
WriteCellArray(cellArray, variableName);
|
||||||
|
break;
|
||||||
|
case IStructureArray structureArray:
|
||||||
|
WriteStructureArray(structureArray, variableName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WriteNumericalArray(array, variableName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCharArray(ICharArray charArray, string name)
|
||||||
|
{
|
||||||
|
WriteWrappingContents(
|
||||||
|
charArray,
|
||||||
|
fakeWriter => fakeWriter.WriteCharArrayContents(charArray, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSparseArray<T>(ISparseArrayOf<T> sparseArray, string name)
|
||||||
|
where T : unmanaged, IEquatable<T>
|
||||||
|
{
|
||||||
|
WriteWrappingContents(
|
||||||
|
sparseArray,
|
||||||
|
fakeWriter => fakeWriter.WriteSparseArrayContents(sparseArray, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCellArray(ICellArray cellArray, string name)
|
||||||
|
{
|
||||||
|
WriteWrappingContents(
|
||||||
|
cellArray,
|
||||||
|
fakeWriter => fakeWriter.WriteCellArrayContents(cellArray, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCellArrayValues(ICellArray array)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < array.Count; i++)
|
||||||
|
{
|
||||||
|
WriteArray(array[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteStructureArray(
|
||||||
|
IStructureArray structureArray,
|
||||||
|
string name)
|
||||||
|
{
|
||||||
|
WriteWrappingContents(
|
||||||
|
structureArray,
|
||||||
|
fakeWriter => fakeWriter.WriteStructureArrayContents(structureArray, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteNumericalArray(
|
||||||
|
IArray numericalArray,
|
||||||
|
string name = "")
|
||||||
|
{
|
||||||
|
WriteWrappingContents(
|
||||||
|
numericalArray,
|
||||||
|
fakeWriter => fakeWriter.WriteNumericalArrayContents(numericalArray, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -46,4 +47,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="..\README.md" Pack="true" PackagePath="\"/>
|
<None Include="..\README.md" Pack="true" PackagePath="\"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="MatFileHandler.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -55,7 +55,15 @@ namespace MatFileHandler
|
|||||||
switch (_options.UseCompression)
|
switch (_options.UseCompression)
|
||||||
{
|
{
|
||||||
case CompressionUsage.Always:
|
case CompressionUsage.Always:
|
||||||
WriteCompressedVariable(writer, variable);
|
if (Stream.CanSeek)
|
||||||
|
{
|
||||||
|
WriteCompressedVariableToSeekableStream(writer, variable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteCompressedVariableToUnseekableStream(writer, variable);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case CompressionUsage.Never:
|
case CompressionUsage.Never:
|
||||||
WriteVariable(writer, variable);
|
WriteVariable(writer, variable);
|
||||||
@ -125,6 +133,12 @@ namespace MatFileHandler
|
|||||||
{
|
{
|
||||||
WriteTag(writer, new Tag(type, data.Length));
|
WriteTag(writer, new Tag(type, data.Length));
|
||||||
writer.Write(data);
|
writer.Write(data);
|
||||||
|
var rem = data.Length % 8;
|
||||||
|
if (rem > 0)
|
||||||
|
{
|
||||||
|
var padding = new byte[8 - rem];
|
||||||
|
writer.Write(padding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -136,7 +150,6 @@ namespace MatFileHandler
|
|||||||
writer.Write(padding);
|
writer.Write(padding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WritePadding(writer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteDimensions(BinaryWriter writer, int[] dimensions)
|
private void WriteDimensions(BinaryWriter writer, int[] dimensions)
|
||||||
@ -393,7 +406,11 @@ namespace MatFileHandler
|
|||||||
return new ArrayFlags(ArrayType.MxChar, isGlobal ? Variable.IsGlobal : 0);
|
return new ArrayFlags(ArrayType.MxChar, isGlobal ? Variable.IsGlobal : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteWrappingContents<T>(BinaryWriter writer, T array, Action<BinaryWriter> writeContents)
|
private void WriteWrappingContents<T>(
|
||||||
|
BinaryWriter writer,
|
||||||
|
T array,
|
||||||
|
Action<FakeWriter> lengthCalculator,
|
||||||
|
Action<BinaryWriter> writeContents)
|
||||||
where T : IArray
|
where T : IArray
|
||||||
{
|
{
|
||||||
if (array.IsEmpty)
|
if (array.IsEmpty)
|
||||||
@ -401,16 +418,12 @@ namespace MatFileHandler
|
|||||||
WriteTag(writer, new Tag(DataType.MiMatrix, 0));
|
WriteTag(writer, new Tag(DataType.MiMatrix, 0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using (var contents = new MemoryStream())
|
|
||||||
{
|
var fakeWriter = new FakeWriter();
|
||||||
using (var contentsWriter = new BinaryWriter(contents))
|
lengthCalculator(fakeWriter);
|
||||||
{
|
var calculatedLength = fakeWriter.Position;
|
||||||
writeContents(contentsWriter);
|
WriteTag(writer, new Tag(DataType.MiMatrix, calculatedLength));
|
||||||
WriteTag(writer, new Tag(DataType.MiMatrix, (int)contents.Length));
|
writeContents(writer);
|
||||||
contents.Position = 0;
|
|
||||||
contents.CopyTo(writer.BaseStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteNumericalArrayContents(BinaryWriter writer, IArray array, string name, bool isGlobal)
|
private void WriteNumericalArrayContents(BinaryWriter writer, IArray array, string name, bool isGlobal)
|
||||||
@ -430,6 +443,7 @@ namespace MatFileHandler
|
|||||||
WriteWrappingContents(
|
WriteWrappingContents(
|
||||||
writer,
|
writer,
|
||||||
numericalArray,
|
numericalArray,
|
||||||
|
fakeWriter => fakeWriter.WriteNumericalArrayContents(numericalArray, name),
|
||||||
contentsWriter => { WriteNumericalArrayContents(contentsWriter, numericalArray, name, isGlobal); });
|
contentsWriter => { WriteNumericalArrayContents(contentsWriter, numericalArray, name, isGlobal); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,6 +461,7 @@ namespace MatFileHandler
|
|||||||
WriteWrappingContents(
|
WriteWrappingContents(
|
||||||
writer,
|
writer,
|
||||||
charArray,
|
charArray,
|
||||||
|
fakeWriter => fakeWriter.WriteCharArrayContents(charArray, name),
|
||||||
contentsWriter => { WriteCharArrayContents(contentsWriter, charArray, name, isGlobal); });
|
contentsWriter => { WriteCharArrayContents(contentsWriter, charArray, name, isGlobal); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,11 +535,12 @@ namespace MatFileHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void WriteSparseArray<T>(BinaryWriter writer, ISparseArrayOf<T> sparseArray, string name, bool isGlobal)
|
private void WriteSparseArray<T>(BinaryWriter writer, ISparseArrayOf<T> sparseArray, string name, bool isGlobal)
|
||||||
where T : struct, IEquatable<T>
|
where T : unmanaged, IEquatable<T>
|
||||||
{
|
{
|
||||||
WriteWrappingContents(
|
WriteWrappingContents(
|
||||||
writer,
|
writer,
|
||||||
sparseArray,
|
sparseArray,
|
||||||
|
fakeWriter => fakeWriter.WriteSparseArrayContents(sparseArray, name),
|
||||||
contentsWriter => { WriteSparseArrayContents(contentsWriter, sparseArray, name, isGlobal); });
|
contentsWriter => { WriteSparseArrayContents(contentsWriter, sparseArray, name, isGlobal); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,6 +591,7 @@ namespace MatFileHandler
|
|||||||
WriteWrappingContents(
|
WriteWrappingContents(
|
||||||
writer,
|
writer,
|
||||||
structureArray,
|
structureArray,
|
||||||
|
fakeWriter => fakeWriter.WriteStructureArrayContents(structureArray, name),
|
||||||
contentsWriter => { WriteStructureArrayContents(contentsWriter, structureArray, name, isGlobal); });
|
contentsWriter => { WriteStructureArrayContents(contentsWriter, structureArray, name, isGlobal); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,6 +616,7 @@ namespace MatFileHandler
|
|||||||
WriteWrappingContents(
|
WriteWrappingContents(
|
||||||
writer,
|
writer,
|
||||||
cellArray,
|
cellArray,
|
||||||
|
fakeWriter => fakeWriter.WriteCellArrayContents(cellArray, name),
|
||||||
contentsWriter => { WriteCellArrayContents(contentsWriter, cellArray, name, isGlobal); });
|
contentsWriter => { WriteCellArrayContents(contentsWriter, cellArray, name, isGlobal); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -635,7 +653,33 @@ namespace MatFileHandler
|
|||||||
WriteArray(writer, variable.Value, variable.Name, variable.IsGlobal);
|
WriteArray(writer, variable.Value, variable.Name, variable.IsGlobal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteCompressedVariable(BinaryWriter writer, IVariable variable)
|
private void WriteCompressedVariableToSeekableStream(BinaryWriter writer, IVariable variable)
|
||||||
|
{
|
||||||
|
var position = writer.BaseStream.Position;
|
||||||
|
WriteTag(writer, new Tag(DataType.MiCompressed, 0));
|
||||||
|
writer.Write((byte)0x78);
|
||||||
|
writer.Write((byte)0x9c);
|
||||||
|
int compressedLength;
|
||||||
|
uint crc;
|
||||||
|
var before = writer.BaseStream.Position;
|
||||||
|
using (var compressionStream = new DeflateStream(writer.BaseStream, CompressionMode.Compress, leaveOpen: true))
|
||||||
|
{
|
||||||
|
using var checksumStream = new ChecksumCalculatingStream(compressionStream);
|
||||||
|
using var internalWriter = new BinaryWriter(checksumStream, Encoding.UTF8, leaveOpen: true);
|
||||||
|
WriteVariable(internalWriter, variable);
|
||||||
|
crc = checksumStream.GetCrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
var after = writer.BaseStream.Position;
|
||||||
|
compressedLength = (int)(after - before) + 6;
|
||||||
|
|
||||||
|
writer.Write(BitConverter.GetBytes(crc).Reverse().ToArray());
|
||||||
|
writer.BaseStream.Position = position;
|
||||||
|
WriteTag(writer, new Tag(DataType.MiCompressed, compressedLength));
|
||||||
|
writer.BaseStream.Seek(0, SeekOrigin.End);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCompressedVariableToUnseekableStream(BinaryWriter writer, IVariable variable)
|
||||||
{
|
{
|
||||||
using (var compressedStream = new MemoryStream())
|
using (var compressedStream = new MemoryStream())
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user