diff --git a/LICENSE.md b/LICENSE.md
new file mode 100755
index 0000000..9d49079
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,9 @@
+The MIT License (MIT)
+
+Copyright 2017 Alexander Luzgarev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/MatFileHandler.Tests/AbstractTestDataFactory.cs b/MatFileHandler.Tests/AbstractTestDataFactory.cs
new file mode 100755
index 0000000..ad7afdf
--- /dev/null
+++ b/MatFileHandler.Tests/AbstractTestDataFactory.cs
@@ -0,0 +1,68 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Abstract factory of test data.
+ ///
+ /// Type of test data.
+ public abstract class AbstractTestDataFactory
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Directory with test files.
+ /// A convention used to filter test files.
+ protected AbstractTestDataFactory(string dataDirectory, ITestFilenameConvention testFilenameConvention)
+ {
+ DataDirectory = dataDirectory;
+ TestFilenameConvention = testFilenameConvention;
+ }
+
+ private string DataDirectory { get; }
+
+ private ITestFilenameConvention TestFilenameConvention { get; }
+
+ ///
+ /// Get test data set by name.
+ ///
+ /// Name of the data set.
+ /// Test data.
+ public TTestData this[string dataSet] =>
+ ReadTestData(FixPath(TestFilenameConvention.ConvertTestNameToFilename(dataSet)));
+
+ ///
+ /// Get a sequence of all test data sets in the factory.
+ ///
+ /// A sequence of data sets.
+ public IEnumerable GetAllTestData()
+ {
+ var files = Directory.EnumerateFiles(DataDirectory).Where(TestFilenameConvention.FilterFile);
+ foreach (var filename in files)
+ {
+ yield return ReadTestData(filename);
+ }
+ }
+
+ ///
+ /// Read test data from a stream.
+ ///
+ /// Input stream.
+ /// Test data.
+ protected abstract TTestData ReadDataFromStream(Stream stream);
+
+ private string FixPath(string filename) => Path.Combine(DataDirectory, filename);
+
+ private TTestData ReadTestData(string filename)
+ {
+ using (var stream = new FileStream(filename, FileMode.Open))
+ {
+ return ReadDataFromStream(stream);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/ArrayHandlingTests.cs b/MatFileHandler.Tests/ArrayHandlingTests.cs
new file mode 100755
index 0000000..97fa5c9
--- /dev/null
+++ b/MatFileHandler.Tests/ArrayHandlingTests.cs
@@ -0,0 +1,123 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System.Collections.Generic;
+using System.Numerics;
+using NUnit.Framework;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Tests of Matlab array manipulation.
+ ///
+ [TestFixture]
+ public class ArrayHandlingTests
+ {
+ private DataBuilder Builder { get; set; }
+
+ ///
+ /// Set up a DataBuilder.
+ ///
+ [SetUp]
+ public void Setup()
+ {
+ Builder = new DataBuilder();
+ }
+
+ ///
+ /// Test numerical array creation.
+ ///
+ [Test]
+ public void TestCreate()
+ {
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf>();
+ TestCreateArrayOf();
+ TestCreateArrayOf();
+ }
+
+ ///
+ /// Test numerical array manipulation.
+ ///
+ [Test]
+ public void TestNumArray()
+ {
+ var array = Builder.NewArray(2, 3);
+ array[0, 1] = 2;
+ Assert.That(array[0, 1], Is.EqualTo(2));
+ }
+
+ ///
+ /// Test cell array manipulation.
+ ///
+ [Test]
+ public void TestCellArray()
+ {
+ var array = Builder.NewCellArray(2, 3);
+ Assert.That(array.Dimensions, Is.EqualTo(new[] { 2, 3 }));
+ array[0, 1] = Builder.NewArray(1, 2);
+ Assert.That(array[1, 2].IsEmpty, Is.True);
+ Assert.That(array[0, 1].IsEmpty, Is.False);
+ var assigned = (IArrayOf)array[0, 1];
+ Assert.That(assigned, Is.Not.Null);
+ Assert.That(assigned.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ }
+
+ ///
+ /// Test structure array manipulation.
+ ///
+ [Test]
+ public void TestStructureArray()
+ {
+ var array = Builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
+ array["x", 0, 1] = Builder.NewArray(1, 2);
+ Assert.That(array["y", 0, 1].IsEmpty, Is.True);
+ Assert.That(array["x", 0, 1].IsEmpty, Is.False);
+ var assigned = (IArrayOf)array["x", 0, 1];
+ Assert.That(assigned, Is.Not.Null);
+ Assert.That(assigned.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ }
+
+ ///
+ /// Test character array manipulation.
+ ///
+ [Test]
+ public void TestString()
+ {
+ var array = Builder.NewCharArray("🍆");
+ Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ }
+
+ ///
+ /// Test file creation.
+ ///
+ [Test]
+ public void TestFile()
+ {
+ var file = Builder.NewFile(new List());
+ Assert.That(file, Is.Not.Null);
+ }
+
+ private static void TestCreateArrayOf()
+ where T : struct
+ {
+ var array = new DataBuilder().NewArray(2, 3);
+ Assert.That(array, Is.Not.Null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/CommonData.cs b/MatFileHandler.Tests/CommonData.cs
new file mode 100755
index 0000000..5e2e32b
--- /dev/null
+++ b/MatFileHandler.Tests/CommonData.cs
@@ -0,0 +1,50 @@
+// Copyright 2017 Alexander Luzgarev
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Data used in reading/writing tests.
+ ///
+ public static class CommonData
+ {
+ ///
+ /// Limits of Int8.
+ ///
+ public static readonly sbyte[] Int8Limits = { -128, 127 };
+
+ ///
+ /// Limits of UInt8.
+ ///
+ public static readonly byte[] UInt8Limits = { 0, 255 };
+
+ ///
+ /// Limits of Int16.
+ ///
+ public static readonly short[] Int16Limits = { -32768, 32767 };
+
+ ///
+ /// Limits of UInt16.
+ ///
+ public static readonly ushort[] UInt16Limits = { 0, 65535 };
+
+ ///
+ /// Limits of Int32.
+ ///
+ public static readonly int[] Int32Limits = { -2147483648, 2147483647 };
+
+ ///
+ /// Limits of UInt32.
+ ///
+ public static readonly uint[] UInt32Limits = { 0U, 4294967295U };
+
+ ///
+ /// Limits of Int64.
+ ///
+ public static readonly long[] Int64Limits = { -9223372036854775808L, 9223372036854775807L };
+
+ ///
+ /// Limits of UInt64.
+ ///
+ public static readonly ulong[] UInt64Limits = { 0UL, 18446744073709551615UL };
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/ComplexOfTests.cs b/MatFileHandler.Tests/ComplexOfTests.cs
new file mode 100755
index 0000000..17f59fb
--- /dev/null
+++ b/MatFileHandler.Tests/ComplexOfTests.cs
@@ -0,0 +1,51 @@
+// Copyright 2017 Alexander Luzgarev
+
+using NUnit.Framework;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Tests of the ComplexOf value type.
+ ///
+ [TestFixture]
+ public class ComplexOfTests
+ {
+ ///
+ /// Test getting real and imaginary parts.
+ ///
+ [Test]
+ public void TestAccessors()
+ {
+ var c = new ComplexOf(1, 2);
+ Assert.That(c.Real, Is.EqualTo(1));
+ Assert.That(c.Imaginary, Is.EqualTo(2));
+ }
+
+ ///
+ /// Test equality operators.
+ ///
+ [Test]
+ public void TestEquals()
+ {
+ var c1 = new ComplexOf(1, 2);
+ var c2 = new ComplexOf(3, 4);
+ var c3 = new ComplexOf(1, 2);
+ Assert.That(c1 == c3, Is.True);
+ Assert.That(c1 != c2, Is.True);
+ Assert.That(c2 != c3, Is.True);
+ }
+
+ ///
+ /// Test hash code calculation.
+ ///
+ [Test]
+ public void TestGetHashCode()
+ {
+ var c1 = new ComplexOf(1, 2);
+ var c2 = new ComplexOf(1, 2);
+ var h1 = c1.GetHashCode();
+ var h2 = c2.GetHashCode();
+ Assert.That(h1, Is.EqualTo(h2));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/ExtensionTestFilenameConvention.cs b/MatFileHandler.Tests/ExtensionTestFilenameConvention.cs
new file mode 100755
index 0000000..6cd1a4e
--- /dev/null
+++ b/MatFileHandler.Tests/ExtensionTestFilenameConvention.cs
@@ -0,0 +1,43 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System.IO;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// A filename convention based on file extensions.
+ ///
+ internal class ExtensionTestFilenameConvention : ITestFilenameConvention
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// File extension.
+ public ExtensionTestFilenameConvention(string extension)
+ {
+ Extension = extension;
+ }
+
+ private string Extension { get; }
+
+ ///
+ /// Convert test name to filename by adding the extension.
+ ///
+ /// Test name.
+ /// The corresponding filename.
+ public string ConvertTestNameToFilename(string testName)
+ {
+ return Path.ChangeExtension(testName, Extension);
+ }
+
+ ///
+ /// Compare file's extension to the one specified during initialization.
+ ///
+ /// Filename.
+ /// True iff the file has the extension stored in the class.
+ public bool FilterFile(string filename)
+ {
+ return Path.GetExtension(filename) == "." + Extension;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/ITestFilenameConvention.cs b/MatFileHandler.Tests/ITestFilenameConvention.cs
new file mode 100755
index 0000000..1668d01
--- /dev/null
+++ b/MatFileHandler.Tests/ITestFilenameConvention.cs
@@ -0,0 +1,24 @@
+// Copyright 2017 Alexander Luzgarev
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Interface for handling filtering tests based on filenames.
+ ///
+ public interface ITestFilenameConvention
+ {
+ ///
+ /// Convert test name to a filename (e.g., by adding an appropriate extension).
+ ///
+ /// Name of a test.
+ /// Filename.
+ string ConvertTestNameToFilename(string testName);
+
+ ///
+ /// Decide if a file contains a test based on the filename.
+ ///
+ /// A filename.
+ /// True iff the file should contain a test.
+ bool FilterFile(string filename);
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/MatFileHandler.Tests.csproj b/MatFileHandler.Tests/MatFileHandler.Tests.csproj
new file mode 100755
index 0000000..6b2cc62
--- /dev/null
+++ b/MatFileHandler.Tests/MatFileHandler.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+ netcoreapp2.0
+ false
+
+
+ ..\MatFileHandler.ruleset
+
+
+ ..\MatFileHandler.ruleset
+ bin\Debug\netcoreapp2.0\MatFileHandler.Tests.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MatFileHandler.Tests/MatFileReaderTests.cs b/MatFileHandler.Tests/MatFileReaderTests.cs
new file mode 100755
index 0000000..3245736
--- /dev/null
+++ b/MatFileHandler.Tests/MatFileReaderTests.cs
@@ -0,0 +1,318 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System.IO;
+using System.Linq;
+using System.Numerics;
+using NUnit.Framework;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Tests of file reading API.
+ ///
+ [TestFixture]
+ public class MatFileReaderTests
+ {
+ private const string TestDirectory = "test-data";
+
+ ///
+ /// Test reading all files in a given test set.
+ ///
+ /// Name of the set.
+ [TestCase("good")]
+ public void TestReader(string testSet)
+ {
+ foreach (var matFile in GetTests(testSet).GetAllTestData())
+ {
+ Assert.That(matFile.Variables, Is.Not.Empty);
+ }
+ }
+
+ ///
+ /// Test reading lower and upper limits of integer data types.
+ ///
+ [Test]
+ public void TestLimits()
+ {
+ var matFile = GetTests("good")["limits"];
+ IArray array;
+ array = matFile["int8_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.Int8Limits);
+ Assert.That(array.ConvertToDoubleArray(), Is.EqualTo(new[] { -128.0, 127.0 }));
+
+ array = matFile["uint8_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.UInt8Limits);
+
+ array = matFile["int16_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.Int16Limits);
+
+ array = matFile["uint16_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.UInt16Limits);
+
+ array = matFile["int32_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.Int32Limits);
+
+ array = matFile["uint32_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.UInt32Limits);
+
+ array = matFile["int64_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.Int64Limits);
+
+ array = matFile["uint64_"].Value;
+ CheckLimits(array as IArrayOf, CommonData.UInt64Limits);
+ }
+
+ ///
+ /// Test writing lower and upper limits of integer-based complex data types.
+ ///
+ [Test]
+ public void TestComplexLimits()
+ {
+ var matFile = GetTests("good")["limits_complex"];
+ IArray array;
+ array = matFile["int8_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.Int8Limits);
+ Assert.That(
+ array.ConvertToComplexArray(),
+ Is.EqualTo(new[] { -128.0 + (127.0 * Complex.ImaginaryOne), 127.0 - (128.0 * Complex.ImaginaryOne) }));
+
+ array = matFile["uint8_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.UInt8Limits);
+
+ array = matFile["int16_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.Int16Limits);
+
+ array = matFile["uint16_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.UInt16Limits);
+
+ array = matFile["int32_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.Int32Limits);
+
+ array = matFile["uint32_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.UInt32Limits);
+
+ array = matFile["int64_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.Int64Limits);
+
+ array = matFile["uint64_complex"].Value;
+ CheckComplexLimits(array as IArrayOf>, CommonData.UInt64Limits);
+ }
+
+ ///
+ /// Test reading an ASCII-encoded string.
+ ///
+ [Test]
+ public void TestAscii()
+ {
+ var matFile = GetTests("good")["ascii"];
+ var arrayAscii = matFile["s"].Value as ICharArray;
+ Assert.That(arrayAscii, Is.Not.Null);
+ Assert.That(arrayAscii.Dimensions, Is.EqualTo(new[] { 1, 3 }));
+ Assert.That(arrayAscii.String, Is.EqualTo("abc"));
+ Assert.That(arrayAscii[2], Is.EqualTo('c'));
+ }
+
+ ///
+ /// Test reading a Unicode string.
+ ///
+ [Test]
+ public void TestUnicode()
+ {
+ var matFile = GetTests("good")["unicode"];
+ var arrayUnicode = matFile["s"].Value as ICharArray;
+ Assert.That(arrayUnicode, Is.Not.Null);
+ Assert.That(arrayUnicode.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ Assert.That(arrayUnicode.String, Is.EqualTo("必フ"));
+ Assert.That(arrayUnicode[0], Is.EqualTo('必'));
+ Assert.That(arrayUnicode[1], Is.EqualTo('フ'));
+ }
+
+ ///
+ /// Test reading a wide Unicode string.
+ ///
+ [Test]
+ public void TestUnicodeWide()
+ {
+ var matFile = GetTests("good")["unicode-wide"];
+ var arrayUnicodeWide = matFile["s"].Value as ICharArray;
+ Assert.That(arrayUnicodeWide, Is.Not.Null);
+ Assert.That(arrayUnicodeWide.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ Assert.That(arrayUnicodeWide.String, Is.EqualTo("🍆"));
+ }
+
+ ///
+ /// Test converting a structure array to a Double array.
+ ///
+ /// Should return null.
+ [Test(ExpectedResult = null)]
+ public double[] TestConvertToDoubleArray()
+ {
+ var matFile = GetTests("good")["struct"];
+ var array = matFile.Variables[0].Value;
+ return array.ConvertToDoubleArray();
+ }
+
+ ///
+ /// Test converting a structure array to a Complex array.
+ ///
+ /// Should return null.
+ [Test(ExpectedResult = null)]
+ public Complex[] TestConvertToComplexArray()
+ {
+ var matFile = GetTests("good")["struct"];
+ var array = matFile.Variables[0].Value;
+ return array.ConvertToComplexArray();
+ }
+
+ ///
+ /// Test reading a structure array.
+ ///
+ [Test]
+ public void TestStruct()
+ {
+ var matFile = GetTests("good")["struct"];
+ var structure = matFile["struct_"].Value as IStructureArray;
+ Assert.That(structure, Is.Not.Null);
+ Assert.That(structure.FieldNames, Is.EquivalentTo(new[] { "x", "y" }));
+ var element = structure[0, 0];
+ Assert.That(element.ContainsKey("x"), Is.True);
+ Assert.That(element.Count, Is.EqualTo(2));
+ Assert.That(element.TryGetValue("x", out var _), Is.True);
+ Assert.That(element.TryGetValue("z", out var _), Is.False);
+ Assert.That(element.Keys, Has.Exactly(2).Items);
+ Assert.That(element.Values, Has.Exactly(2).Items);
+ var keys = element.Select(pair => pair.Key);
+ Assert.That(keys, Is.EquivalentTo(new[] { "x", "y" }));
+
+ Assert.That((element["x"] as IArrayOf)?[0], Is.EqualTo(12.345));
+
+ Assert.That((structure["x", 0, 0] as IArrayOf)?[0], Is.EqualTo(12.345));
+ Assert.That((structure["y", 0, 0] as ICharArray)?.String, Is.EqualTo("abc"));
+ Assert.That((structure["x", 1, 0] as ICharArray)?.String, Is.EqualTo("xyz"));
+ Assert.That(structure["y", 1, 0].IsEmpty, Is.True);
+ Assert.That((structure["x", 0, 1] as IArrayOf)?[0], Is.EqualTo(2.0));
+ Assert.That((structure["y", 0, 1] as IArrayOf)?[0], Is.EqualTo(13.0));
+ Assert.That(structure["x", 1, 1].IsEmpty, Is.True);
+ Assert.That((structure["y", 1, 1] as ICharArray)?[0, 0], Is.EqualTo('a'));
+ Assert.That(((structure["x", 0, 2] as ICellArray)?[0] as ICharArray)?.String, Is.EqualTo("x"));
+ Assert.That(((structure["x", 0, 2] as ICellArray)?[1] as ICharArray)?.String, Is.EqualTo("yz"));
+ Assert.That((structure["y", 0, 2] as IArrayOf)?.Dimensions, Is.EqualTo(new[] { 2, 3 }));
+ Assert.That((structure["y", 0, 2] as IArrayOf)?[0, 2], Is.EqualTo(3.0));
+ Assert.That((structure["x", 1, 2] as IArrayOf)?[0], Is.EqualTo(1.5f));
+ Assert.That(structure["y", 1, 2].IsEmpty, Is.True);
+ }
+
+ ///
+ /// Test reading a sparse array.
+ ///
+ [Test]
+ public void TestSparse()
+ {
+ var matFile = GetTests("good")["sparse"];
+ var sparseArray = matFile["sparse_"].Value as ISparseArrayOf;
+ Assert.That(sparseArray, Is.Not.Null);
+ Assert.That(sparseArray.Dimensions, Is.EqualTo(new[] { 4, 5 }));
+ Assert.That(sparseArray.Data[(1, 1)], Is.EqualTo(1.0));
+ Assert.That(sparseArray[1, 1], Is.EqualTo(1.0));
+ Assert.That(sparseArray[1, 2], Is.EqualTo(2.0));
+ Assert.That(sparseArray[2, 1], Is.EqualTo(3.0));
+ Assert.That(sparseArray[2, 3], Is.EqualTo(4.0));
+ Assert.That(sparseArray[0, 4], Is.EqualTo(0.0));
+ Assert.That(sparseArray[3, 0], Is.EqualTo(0.0));
+ Assert.That(sparseArray[3, 4], Is.EqualTo(0.0));
+ }
+
+ ///
+ /// Test reading a logical array.
+ ///
+ [Test]
+ public void TestLogical()
+ {
+ var matFile = GetTests("good")["logical"];
+ var array = matFile["logical_"].Value;
+ var logicalArray = array as IArrayOf;
+ Assert.That(logicalArray, Is.Not.Null);
+ Assert.That(logicalArray[0, 0], Is.True);
+ Assert.That(logicalArray[0, 1], Is.True);
+ Assert.That(logicalArray[0, 2], Is.False);
+ Assert.That(logicalArray[1, 0], Is.False);
+ Assert.That(logicalArray[1, 1], Is.True);
+ Assert.That(logicalArray[1, 2], Is.True);
+ }
+
+ ///
+ /// Test reading a sparse logical array.
+ ///
+ [Test]
+ public void TestSparseLogical()
+ {
+ var matFile = GetTests("good")["sparse_logical"];
+ var array = matFile["sparse_logical"].Value;
+ var sparseArray = array as ISparseArrayOf;
+ Assert.That(sparseArray, Is.Not.Null);
+ Assert.That(sparseArray.Data[(0, 0)], Is.True);
+ Assert.That(sparseArray[0, 0], Is.True);
+ Assert.That(sparseArray[0, 1], Is.True);
+ Assert.That(sparseArray[0, 2], Is.False);
+ Assert.That(sparseArray[1, 0], Is.False);
+ Assert.That(sparseArray[1, 1], Is.True);
+ Assert.That(sparseArray[1, 2], Is.True);
+ }
+
+ ///
+ /// Test reading a global variable.
+ ///
+ [Test]
+ public void TestGlobal()
+ {
+ var matFile = GetTests("good")["global"];
+ var variable = matFile.Variables.First();
+ Assert.That(variable.IsGlobal, Is.True);
+ }
+
+ ///
+ /// Test reading a sparse complex array.
+ ///
+ [Test]
+ public void TextSparseComplex()
+ {
+ var matFile = GetTests("good")["sparse_complex"];
+ var array = matFile["sparse_complex"].Value;
+ var sparseArray = array as ISparseArrayOf;
+ Assert.That(sparseArray, Is.Not.Null);
+ Assert.That(sparseArray[0, 0], Is.EqualTo(-1.5 + (2.5 * Complex.ImaginaryOne)));
+ Assert.That(sparseArray[1, 0], Is.EqualTo(2 - (3 * Complex.ImaginaryOne)));
+ Assert.That(sparseArray[0, 1], Is.EqualTo(Complex.Zero));
+ Assert.That(sparseArray[1, 1], Is.EqualTo(0.5 + (1.0 * Complex.ImaginaryOne)));
+ }
+
+ ///
+ /// Test reading an object.
+ ///
+ [Test]
+ public void TestObject()
+ {
+ Assert.That(() => GetTests("bad")["object"], Throws.TypeOf());
+ }
+
+ private static AbstractTestDataFactory GetTests(string factoryName) =>
+ new MatTestDataFactory(Path.Combine(TestDirectory, factoryName));
+
+ private static void CheckLimits(IArrayOf array, T[] limits)
+ where T : struct
+ {
+ Assert.That(array, Is.Not.Null);
+ Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ Assert.That(array.Data, Is.EqualTo(limits));
+ }
+
+ private static void CheckComplexLimits(IArrayOf> array, T[] limits)
+ where T : struct
+ {
+ Assert.That(array, Is.Not.Null);
+ Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
+ Assert.That(array[0], Is.EqualTo(new ComplexOf(limits[0], limits[1])));
+ Assert.That(array[1], Is.EqualTo(new ComplexOf(limits[1], limits[0])));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/MatFileWriterTests.cs b/MatFileHandler.Tests/MatFileWriterTests.cs
new file mode 100755
index 0000000..c9acdf7
--- /dev/null
+++ b/MatFileHandler.Tests/MatFileWriterTests.cs
@@ -0,0 +1,403 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+using System.IO;
+using System.Numerics;
+using NUnit.Framework;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Tests of file writing API.
+ ///
+ [TestFixture]
+ public class MatFileWriterTests
+ {
+ private const string TestDirectory = "test-data";
+
+ ///
+ /// Test writing a simple Double array.
+ ///
+ [Test]
+ public void TestWrite()
+ {
+ var builder = new DataBuilder();
+ var array = builder.NewArray(1, 2);
+ array[0] = -13.5;
+ array[1] = 17.0;
+ var variable = builder.NewVariable("test", array);
+ var actual = builder.NewFile(new[] { variable });
+ MatCompareWithTestData("good", "double-array", actual);
+ }
+
+ ///
+ /// Test writing a large file.
+ ///
+ [Test]
+ public void TestHuge()
+ {
+ var builder = new DataBuilder();
+ var array = builder.NewArray(1000, 10000);
+ array[0] = -13.5;
+ array[1] = 17.0;
+ var variable = builder.NewVariable("test", array);
+ var matFile = builder.NewFile(new[] { variable });
+ using (var stream = new MemoryStream())
+ {
+ var writer = new MatFileWriter(stream);
+ writer.Write(matFile);
+ }
+ }
+
+ ///
+ /// Test writing lower and upper limits of integer data types.
+ ///
+ [Test]
+ public void TestLimits()
+ {
+ var builder = new DataBuilder();
+ var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2));
+ var uint8 = builder.NewVariable("uint8_", builder.NewArray(CommonData.UInt8Limits, 1, 2));
+ var int16 = builder.NewVariable("int16_", builder.NewArray(CommonData.Int16Limits, 1, 2));
+ var uint16 = builder.NewVariable("uint16_", builder.NewArray(CommonData.UInt16Limits, 1, 2));
+ var int32 = builder.NewVariable("int32_", builder.NewArray(CommonData.Int32Limits, 1, 2));
+ var uint32 = builder.NewVariable("uint32_", builder.NewArray(CommonData.UInt32Limits, 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 actual = builder.NewFile(new[] { int16, int32, int64, int8, uint16, uint32, uint64, uint8 });
+ MatCompareWithTestData("good", "limits", actual);
+ }
+
+ ///
+ /// Test writing lower and upper limits of integer-based complex data types.
+ ///
+ [Test]
+ public void TestLimitsComplex()
+ {
+ var builder = new DataBuilder();
+ var int8Complex = builder.NewVariable(
+ "int8_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.Int8Limits), 1, 2));
+ var uint8Complex = builder.NewVariable(
+ "uint8_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.UInt8Limits), 1, 2));
+ var int16Complex = builder.NewVariable(
+ "int16_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.Int16Limits), 1, 2));
+ var uint16Complex = builder.NewVariable(
+ "uint16_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.UInt16Limits), 1, 2));
+ var int32Complex = builder.NewVariable(
+ "int32_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.Int32Limits), 1, 2));
+ var uint32Complex = builder.NewVariable(
+ "uint32_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.UInt32Limits), 1, 2));
+ var int64Complex = builder.NewVariable(
+ "int64_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.Int64Limits), 1, 2));
+ var uint64Complex = builder.NewVariable(
+ "uint64_complex",
+ builder.NewArray(CreateComplexLimits(CommonData.UInt64Limits), 1, 2));
+ var actual = builder.NewFile(new[]
+ {
+ int16Complex, int32Complex, int64Complex, int8Complex,
+ uint16Complex, uint32Complex, uint64Complex, uint8Complex,
+ });
+ MatCompareWithTestData("good", "limits_complex", actual);
+ }
+
+ ///
+ /// Test writing a wide-Unicode symbol.
+ ///
+ [Test]
+ public void TestUnicodeWide()
+ {
+ var builder = new DataBuilder();
+ var s = builder.NewVariable("s", builder.NewCharArray("🍆"));
+ var actual = builder.NewFile(new[] { s });
+ MatCompareWithTestData("good", "unicode-wide", actual);
+ }
+
+ ///
+ /// Test writing a sparse array.
+ ///
+ [Test]
+ public void TestSparseArray()
+ {
+ var builder = new DataBuilder();
+ var sparseArray = builder.NewSparseArray(4, 5);
+ sparseArray[1, 1] = 1;
+ sparseArray[1, 2] = 2;
+ sparseArray[2, 1] = 3;
+ sparseArray[2, 3] = 4;
+ var sparse = builder.NewVariable("sparse_", sparseArray);
+ var actual = builder.NewFile(new[] { sparse });
+ MatCompareWithTestData("good", "sparse", actual);
+ }
+
+ ///
+ /// Test writing a structure array.
+ ///
+ [Test]
+ public void TestStructure()
+ {
+ var builder = new DataBuilder();
+ var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
+ structure["x", 0, 0] = builder.NewArray(new[] { 12.345 }, 1, 1);
+ structure["y", 0, 0] = builder.NewCharArray("abc");
+ structure["x", 1, 0] = builder.NewCharArray("xyz");
+ structure["y", 1, 0] = builder.NewEmpty();
+ structure["x", 0, 1] = builder.NewArray(new[] { 2.0 }, 1, 1);
+ structure["y", 0, 1] = builder.NewArray(new[] { 13.0 }, 1, 1);
+ structure["x", 1, 1] = builder.NewEmpty();
+ structure["y", 1, 1] = builder.NewCharArray("acbd", 2, 2);
+ var cellArray = builder.NewCellArray(1, 2);
+ cellArray[0] = builder.NewCharArray("x");
+ cellArray[1] = builder.NewCharArray("yz");
+ structure["x", 0, 2] = cellArray;
+ structure["y", 0, 2] = builder.NewArray(new[] { 1.0, 4.0, 2.0, 5.0, 3.0, 6.0 }, 2, 3);
+ structure["x", 1, 2] = builder.NewArray(new[] { 1.5f }, 1, 1);
+ structure["y", 1, 2] = builder.NewEmpty();
+ var struct_ = builder.NewVariable("struct_", structure);
+ var actual = builder.NewFile(new[] { struct_ });
+ MatCompareWithTestData("good", "struct", actual);
+ }
+
+ ///
+ /// Test writing a logical array.
+ ///
+ [Test]
+ public void TestLogical()
+ {
+ var builder = new DataBuilder();
+ var logical = builder.NewArray(new[] { true, false, true, true, false, true }, 2, 3);
+ var logicalVariable = builder.NewVariable("logical_", logical);
+ var actual = builder.NewFile(new[] { logicalVariable });
+ MatCompareWithTestData("good", "logical", actual);
+ }
+
+ ///
+ /// Test writing a sparse logical array.
+ ///
+ [Test]
+ public void TestSparseLogical()
+ {
+ var builder = new DataBuilder();
+ var array = builder.NewSparseArray(2, 3);
+ array[0, 0] = true;
+ array[0, 1] = true;
+ array[1, 1] = true;
+ array[1, 2] = true;
+ var sparseLogical = builder.NewVariable("sparse_logical", array);
+ var actual = builder.NewFile(new[] { sparseLogical });
+ MatCompareWithTestData("good", "sparse_logical", actual);
+ }
+
+ ///
+ /// Test writing a sparse complex array.
+ ///
+ [Test]
+ public void TestSparseComplex()
+ {
+ var builder = new DataBuilder();
+ var array = builder.NewSparseArray(2, 2);
+ array[0, 0] = -1.5 + (2.5 * Complex.ImaginaryOne);
+ array[1, 0] = 2 - (3 * Complex.ImaginaryOne);
+ array[1, 1] = 0.5 + Complex.ImaginaryOne;
+ var sparseComplex = builder.NewVariable("sparse_complex", array);
+ var actual = builder.NewFile(new[] { sparseComplex });
+ MatCompareWithTestData("good", "sparse_complex", actual);
+ }
+
+ ///
+ /// Test writing a global variable.
+ ///
+ [Test]
+ public void TestGlobal()
+ {
+ var builder = new DataBuilder();
+ var array = builder.NewArray(new double[] { 1, 3, 5 }, 1, 3);
+ var global = builder.NewVariable("global_", array, true);
+ var actual = builder.NewFile(new[] { global });
+ MatCompareWithTestData("good", "global", actual);
+ }
+
+ private static AbstractTestDataFactory GetMatTestData(string factoryName) =>
+ new MatTestDataFactory(Path.Combine(TestDirectory, factoryName));
+
+ private void CompareSparseArrays(ISparseArrayOf expected, ISparseArrayOf actual)
+ where T : struct
+ {
+ Assert.That(actual, Is.Not.Null);
+ Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
+ Assert.That(expected.Data, Is.EquivalentTo(actual.Data));
+ }
+
+ private void CompareStructureArrays(IStructureArray expected, IStructureArray actual)
+ {
+ Assert.That(actual, Is.Not.Null);
+ Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
+ Assert.That(expected.FieldNames, Is.EquivalentTo(actual.FieldNames));
+ foreach (var name in expected.FieldNames)
+ {
+ for (var i = 0; i < expected.NumberOfElements; i++)
+ {
+ CompareMatArrays(expected[name, i], actual[name, i]);
+ }
+ }
+ }
+
+ private void CompareCellArrays(ICellArray expected, ICellArray actual)
+ {
+ Assert.That(actual, Is.Not.Null);
+ Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
+ for (var i = 0; i < expected.NumberOfElements; i++)
+ {
+ CompareMatArrays(expected[i], actual[i]);
+ }
+ }
+
+ private void CompareNumericalArrays(IArrayOf expected, IArrayOf actual)
+ {
+ Assert.That(actual, Is.Not.Null);
+ Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
+ Assert.That(expected.Data, Is.EqualTo(actual.Data));
+ }
+
+ private void CompareCharArrays(ICharArray expected, ICharArray actual)
+ {
+ Assert.That(actual, Is.Not.Null);
+ Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
+ Assert.That(expected.String, Is.EqualTo(actual.String));
+ }
+
+ private void CompareMatArrays(IArray expected, IArray actual)
+ {
+ switch (expected)
+ {
+ case ISparseArrayOf expectedSparseArrayOfDouble:
+ CompareSparseArrays(expectedSparseArrayOfDouble, actual as ISparseArrayOf);
+ return;
+ case ISparseArrayOf expectedSparseArrayOfComplex:
+ CompareSparseArrays(expectedSparseArrayOfComplex, actual as ISparseArrayOf);
+ return;
+ case IStructureArray expectedStructureArray:
+ CompareStructureArrays(expectedStructureArray, actual as IStructureArray);
+ return;
+ case ICharArray expectedCharArray:
+ CompareCharArrays(expectedCharArray, actual as ICharArray);
+ return;
+ case IArrayOf byteArray:
+ CompareNumericalArrays(byteArray, actual as IArrayOf);
+ return;
+ case IArrayOf sbyteArray:
+ CompareNumericalArrays(sbyteArray, actual as IArrayOf);
+ return;
+ case IArrayOf shortArray:
+ CompareNumericalArrays(shortArray, actual as IArrayOf);
+ return;
+ case IArrayOf ushortArray:
+ CompareNumericalArrays(ushortArray, actual as IArrayOf);
+ return;
+ case IArrayOf intArray:
+ CompareNumericalArrays(intArray, actual as IArrayOf);
+ return;
+ case IArrayOf uintArray:
+ CompareNumericalArrays(uintArray, actual as IArrayOf);
+ return;
+ case IArrayOf longArray:
+ CompareNumericalArrays(longArray, actual as IArrayOf);
+ return;
+ case IArrayOf ulongArray:
+ CompareNumericalArrays(ulongArray, actual as IArrayOf);
+ return;
+ case IArrayOf floatArray:
+ CompareNumericalArrays(floatArray, actual as IArrayOf);
+ return;
+ case IArrayOf doubleArray:
+ CompareNumericalArrays(doubleArray, actual as IArrayOf);
+ return;
+ case IArrayOf> byteArray:
+ CompareNumericalArrays(byteArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> sbyteArray:
+ CompareNumericalArrays(sbyteArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> shortArray:
+ CompareNumericalArrays(shortArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> ushortArray:
+ CompareNumericalArrays(ushortArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> intArray:
+ CompareNumericalArrays(intArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> uintArray:
+ CompareNumericalArrays(uintArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> longArray:
+ CompareNumericalArrays(longArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> ulongArray:
+ CompareNumericalArrays(ulongArray, actual as IArrayOf>);
+ return;
+ case IArrayOf> floatArray:
+ CompareNumericalArrays(floatArray, actual as IArrayOf>);
+ return;
+ case IArrayOf doubleArray:
+ CompareNumericalArrays(doubleArray, actual as IArrayOf);
+ return;
+ case IArrayOf boolArray:
+ CompareNumericalArrays(boolArray, actual as IArrayOf);
+ return;
+ case ICellArray cellArray:
+ CompareCellArrays(cellArray, actual as ICellArray);
+ return;
+ }
+ if (expected.IsEmpty)
+ {
+ Assert.That(actual.IsEmpty, Is.True);
+ return;
+ }
+ throw new NotSupportedException();
+ }
+
+ private void CompareMatFiles(IMatFile expected, IMatFile actual)
+ {
+ Assert.That(expected.Variables.Length, Is.EqualTo(actual.Variables.Length));
+ for (var i = 0; i < expected.Variables.Length; i++)
+ {
+ var expectedVariable = expected.Variables[i];
+ var actualVariable = actual.Variables[i];
+ Assert.That(expectedVariable.Name, Is.EqualTo(actualVariable.Name));
+ Assert.That(expectedVariable.IsGlobal, Is.EqualTo(actualVariable.IsGlobal));
+ CompareMatArrays(expectedVariable.Value, actualVariable.Value);
+ }
+ }
+
+ private void MatCompareWithTestData(string factoryName, string testName, IMatFile actual)
+ {
+ var expected = GetMatTestData(factoryName)[testName];
+ byte[] buffer;
+ using (var stream = new MemoryStream())
+ {
+ var writer = new MatFileWriter(stream);
+ writer.Write(actual);
+ buffer = stream.ToArray();
+ }
+ using (var stream = new MemoryStream(buffer))
+ {
+ var reader = new MatFileReader(stream);
+ var actualRead = reader.Read();
+ CompareMatFiles(expected, actualRead);
+ }
+ }
+
+ private ComplexOf[] CreateComplexLimits(T[] limits)
+ where T : struct
+ {
+ return new[] { new ComplexOf(limits[0], limits[1]), new ComplexOf(limits[1], limits[0]) };
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/MatTestDataFactory.cs b/MatFileHandler.Tests/MatTestDataFactory.cs
new file mode 100755
index 0000000..f343c79
--- /dev/null
+++ b/MatFileHandler.Tests/MatTestDataFactory.cs
@@ -0,0 +1,35 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System.IO;
+
+namespace MatFileHandler.Tests
+{
+ ///
+ /// Factory providing the parsed contents of .mat files.
+ ///
+ public class MatTestDataFactory : AbstractTestDataFactory
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Directory containing test files.
+ public MatTestDataFactory(string testDirectory)
+ : base(
+ testDirectory,
+ new ExtensionTestFilenameConvention("mat"))
+ {
+ }
+
+ ///
+ /// Read and parse data from a .mat file.
+ ///
+ /// Input stream.
+ /// Parsed contents of the file.
+ protected override IMatFile ReadDataFromStream(Stream stream)
+ {
+ var matFileReader = new MatFileReader(stream);
+ var matFile = matFileReader.Read();
+ return matFile;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler.Tests/test-data/bad/object.mat b/MatFileHandler.Tests/test-data/bad/object.mat
new file mode 100755
index 0000000..d75b974
Binary files /dev/null and b/MatFileHandler.Tests/test-data/bad/object.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/ascii.mat b/MatFileHandler.Tests/test-data/good/ascii.mat
new file mode 100755
index 0000000..5354eb8
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/ascii.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/global.mat b/MatFileHandler.Tests/test-data/good/global.mat
new file mode 100755
index 0000000..317f9fa
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/global.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/limits.mat b/MatFileHandler.Tests/test-data/good/limits.mat
new file mode 100755
index 0000000..241eea3
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/limits.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/limits_complex.mat b/MatFileHandler.Tests/test-data/good/limits_complex.mat
new file mode 100755
index 0000000..197f5ee
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/limits_complex.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/logical.mat b/MatFileHandler.Tests/test-data/good/logical.mat
new file mode 100755
index 0000000..75dabe1
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/logical.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/sparse.mat b/MatFileHandler.Tests/test-data/good/sparse.mat
new file mode 100755
index 0000000..b605960
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/sparse.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/sparse_complex.mat b/MatFileHandler.Tests/test-data/good/sparse_complex.mat
new file mode 100755
index 0000000..0706f52
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/sparse_complex.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/sparse_logical.mat b/MatFileHandler.Tests/test-data/good/sparse_logical.mat
new file mode 100755
index 0000000..326f71a
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/sparse_logical.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/struct.mat b/MatFileHandler.Tests/test-data/good/struct.mat
new file mode 100755
index 0000000..a5debe6
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/struct.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/unicode-wide.mat b/MatFileHandler.Tests/test-data/good/unicode-wide.mat
new file mode 100755
index 0000000..f78423b
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/unicode-wide.mat differ
diff --git a/MatFileHandler.Tests/test-data/good/unicode.mat b/MatFileHandler.Tests/test-data/good/unicode.mat
new file mode 100755
index 0000000..07cfcae
Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/unicode.mat differ
diff --git a/MatFileHandler.ruleset b/MatFileHandler.ruleset
new file mode 100755
index 0000000..f3a166b
--- /dev/null
+++ b/MatFileHandler.ruleset
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MatFileHandler.sln b/MatFileHandler.sln
new file mode 100755
index 0000000..5f6ea22
--- /dev/null
+++ b/MatFileHandler.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.0.0
+MinimumVisualStudioVersion = 10.0.0.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatFileHandler", "MatFileHandler/MatFileHandler.csproj", "{C0CD11D3-016A-4FCD-AF0B-D745F79F3749}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatFileHandler.Tests", "MatFileHandler.Tests\MatFileHandler.Tests.csproj", "{4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C0CD11D3-016A-4FCD-AF0B-D745F79F3749}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E09DE2D-13D2-458C-BBD2-BE65AAE30CC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/MatFileHandler/ArrayFlags.cs b/MatFileHandler/ArrayFlags.cs
new file mode 100755
index 0000000..3b5ef5b
--- /dev/null
+++ b/MatFileHandler/ArrayFlags.cs
@@ -0,0 +1,157 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+
+namespace MatFileHandler
+{
+ ///
+ /// Type of a Matlab array.
+ ///
+ internal enum ArrayType
+ {
+ ///
+ /// Cell array.
+ ///
+ MxCell = 1,
+
+ ///
+ /// Structure array.
+ ///
+ MxStruct = 2,
+
+ ///
+ /// Matlab object.
+ ///
+ MxObject = 3,
+
+ ///
+ /// Character array.
+ ///
+ MxChar = 4,
+
+ ///
+ /// Sparse array.
+ ///
+ MxSparse = 5,
+
+ ///
+ /// Double array.
+ ///
+ MxDouble = 6,
+
+ ///
+ /// Single array.
+ ///
+ MxSingle = 7,
+
+ ///
+ /// Int8 array.
+ ///
+ MxInt8 = 8,
+
+ ///
+ /// UInt8 array.
+ ///
+ MxUInt8 = 9,
+
+ ///
+ /// Int16 array.
+ ///
+ MxInt16 = 10,
+
+ ///
+ /// UInt16 array.
+ ///
+ MxUInt16 = 11,
+
+ ///
+ /// Int32 array.
+ ///
+ MxInt32 = 12,
+
+ ///
+ /// UInt32 array.
+ ///
+ MxUInt32 = 13,
+
+ ///
+ /// Int64 array.
+ ///
+ MxInt64 = 14,
+
+ ///
+ /// UInt64 array.
+ ///
+ MxUInt64 = 15,
+
+ ///
+ /// Undocumented object (?) array type.
+ ///
+ MxNewObject = 17,
+ }
+
+ ///
+ /// Variable flags.
+ ///
+ [Flags]
+ internal enum Variable
+ {
+ ///
+ /// Indicates a logical array.
+ ///
+ IsLogical = 2,
+
+ ///
+ /// Indicates a global variable.
+ ///
+ IsGlobal = 4,
+
+ ///
+ /// Indicates a complex array.
+ ///
+ IsComplex = 8,
+ }
+
+ ///
+ /// Array properties.
+ ///
+ internal struct ArrayFlags
+ {
+ ///
+ /// Array type.
+ ///
+ public ArrayType Class;
+
+ ///
+ /// Variable flags.
+ ///
+ public Variable Variable;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Array type.
+ /// Variable flags.
+ public ArrayFlags(ArrayType class_, Variable variable)
+ {
+ Class = class_;
+ Variable = variable;
+ }
+ }
+
+ ///
+ /// Sparse array properties.
+ ///
+ internal struct SparseArrayFlags
+ {
+ ///
+ /// Usual array properties.
+ ///
+ public ArrayFlags ArrayFlags;
+
+ ///
+ /// Maximal number of non-zero elements.
+ ///
+ public uint NzMax;
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler/ComplexOf.cs b/MatFileHandler/ComplexOf.cs
new file mode 100755
index 0000000..b576fff
--- /dev/null
+++ b/MatFileHandler/ComplexOf.cs
@@ -0,0 +1,93 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+
+namespace MatFileHandler
+{
+ ///
+ /// A structure representing a complex number where real and imaginary parts are of type T.
+ ///
+ /// Type of real and imaginary parts.
+ public struct ComplexOf : IEquatable>
+ where T : struct
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// Real part.
+ /// Imaginary part.
+ public ComplexOf(T real, T imaginary)
+ {
+ Real = real;
+ Imaginary = imaginary;
+ }
+
+ ///
+ /// Gets real part.
+ ///
+ public T Real { get; }
+
+ ///
+ /// Gets imaginary part.
+ ///
+ public T Imaginary { get; }
+
+ ///
+ /// Equality operator.
+ ///
+ /// Left argument.
+ /// Right argument.
+ /// True iff the numbers are equal.
+ public static bool operator ==(ComplexOf left, ComplexOf right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Equality operator.
+ ///
+ /// Left argument.
+ /// Right argument.
+ /// True iff the numbers are not equal.
+ public static bool operator !=(ComplexOf left, ComplexOf right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ /// Equality check.
+ ///
+ /// Another complex number.
+ /// True iff the number is equal to another.
+ public bool Equals(ComplexOf other)
+ {
+ return Real.Equals(other.Real) && Imaginary.Equals(other.Imaginary);
+ }
+
+ ///
+ /// Equality check.
+ ///
+ /// Another object.
+ /// True iff another object is a complex number equal to this.
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+ return obj is ComplexOf other && Equals(other);
+ }
+
+ ///
+ /// Gets has code of the number.
+ ///
+ /// Hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Real.GetHashCode() * 397) ^ Imaginary.GetHashCode();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler/DataBuilder.cs b/MatFileHandler/DataBuilder.cs
new file mode 100755
index 0000000..ad34d54
--- /dev/null
+++ b/MatFileHandler/DataBuilder.cs
@@ -0,0 +1,269 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+
+namespace MatFileHandler
+{
+ ///
+ /// Class for building arrays that later can be written to a .mat file.
+ ///
+ public class DataBuilder
+ {
+ ///
+ /// Create a new numerical/logical array.
+ ///
+ /// Element type.
+ /// Dimensions of the array.
+ /// An array of given element type and dimensions, initialized by zeros.
+ ///
+ /// Possible values of T:
+ /// Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
+ /// ComplexOf<TReal> (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
+ /// Complex, Boolean.
+ ///
+ public IArrayOf NewArray(params int[] dimensions)
+ where T : struct
+ {
+ return new MatNumericalArrayOf(
+ GetStandardFlags(),
+ dimensions,
+ string.Empty,
+ new T[dimensions.NumberOfElements()]);
+ }
+
+ ///
+ /// Create a new numerical/logical array and initialize it with the given data.
+ ///
+ /// Element type.
+ /// Initial data.
+ /// Dimensions of the array.
+ /// An array of given dimensions, initialized by the provided data.
+ ///
+ /// Possible values of T:
+ /// Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
+ /// ComplexOf<TReal> (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
+ /// Complex, Boolean.
+ ///
+ public IArrayOf NewArray(T[] data, params int[] dimensions)
+ where T : struct
+ {
+ if (data.Length != dimensions.NumberOfElements())
+ {
+ throw new ArgumentException("Data size does not match the specified dimensions", "data");
+ }
+ return new MatNumericalArrayOf(GetStandardFlags(), dimensions, string.Empty, data);
+ }
+
+ ///
+ /// Create a new cell array.
+ ///
+ /// Dimensions of the array.
+ /// A cell array of given dimensions, consisting of empty arrays.
+ public ICellArray NewCellArray(params int[] dimensions)
+ {
+ var flags = ConstructArrayFlags(ArrayType.MxCell);
+ var elements = Enumerable.Repeat(MatArray.Empty() as IArray, dimensions.NumberOfElements()).ToList();
+ return new MatCellArray(flags, dimensions, string.Empty, elements);
+ }
+
+ ///
+ /// Create a new structure array.
+ ///
+ /// Names of structure fields.
+ /// Dimensions of the array.
+ /// A structure array of given dimensions with given fields, consisting of empty arrays.
+ public IStructureArray NewStructureArray(IEnumerable fields, params int[] dimensions)
+ {
+ var flags = ConstructArrayFlags(ArrayType.MxStruct);
+ var elements = Enumerable.Repeat(MatArray.Empty() as IArray, dimensions.NumberOfElements()).ToList();
+ var dictionary = new Dictionary>();
+ foreach (var field in fields)
+ {
+ dictionary[field] = elements.ToList();
+ }
+ return new MatStructureArray(flags, dimensions, string.Empty, dictionary);
+ }
+
+ ///
+ /// Create a new character array.
+ ///
+ /// A string to initialize the array.
+ /// A 1xn character array with the given string as contents.
+ public ICharArray NewCharArray(string contents)
+ {
+ return NewCharArray(contents, 1, contents.Length);
+ }
+
+ ///
+ /// Create a new character array of specified dimensions.
+ ///
+ /// A string to initialize the array.
+ /// The dimensions of the array.
+ /// A character array of given dimensions with the given string as contents.
+ public ICharArray NewCharArray(string contents, params int[] dimensions)
+ {
+ var flags = ConstructArrayFlags(ArrayType.MxChar);
+ var ushortArray = contents.ToCharArray().Select(c => (ushort)c).ToArray();
+ return new MatCharArrayOf(flags, dimensions, string.Empty, ushortArray, contents);
+ }
+
+ ///
+ /// Create a new empty array.
+ ///
+ /// An empty array.
+ public IArray NewEmpty()
+ {
+ return MatArray.Empty();
+ }
+
+ ///
+ /// Create a new sparse array.
+ ///
+ /// Element type.
+ /// The dimensions of the array.
+ /// An empty sparse array of given type and given dimensions.
+ public ISparseArrayOf NewSparseArray(params int[] dimensions)
+ where T : struct
+ {
+ return new MatSparseArrayOf(
+ GetStandardSparseArrayFlags(),
+ dimensions,
+ string.Empty,
+ new Dictionary<(int, int), T>());
+ }
+
+ ///
+ /// Create a new variable.
+ ///
+ /// Name of the variable.
+ /// Value of the variable.
+ /// Global flag for the variable.
+ /// A new variable with given name and value.
+ public IVariable NewVariable(string name, IArray value, bool isGlobal = false)
+ {
+ return new MatVariable(value, name, isGlobal);
+ }
+
+ ///
+ /// Create a new Matlab file.
+ ///
+ /// Variables in the file.
+ /// A file containing the provided variables.
+ public IMatFile NewFile(IEnumerable variables)
+ {
+ return new MatFile(variables);
+ }
+
+ private ArrayFlags ConstructArrayFlags(ArrayType class_, bool isComplex = false, bool isLogical = false)
+ {
+ return new ArrayFlags
+ {
+ Class = class_,
+ Variable = (isComplex ? Variable.IsComplex : 0) |
+ (isLogical ? Variable.IsLogical : 0),
+ };
+ }
+
+ private ArrayFlags GetStandardFlags()
+ {
+ if (typeof(T) == typeof(sbyte))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt8);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt8, isComplex: true);
+ }
+ if (typeof(T) == typeof(byte))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt8);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt8, isComplex: true);
+ }
+ if (typeof(T) == typeof(short))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt16);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt16, isComplex: true);
+ }
+ if (typeof(T) == typeof(ushort))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt16);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt16, isComplex: true);
+ }
+ if (typeof(T) == typeof(int))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt32);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt32, isComplex: true);
+ }
+ if (typeof(T) == typeof(uint))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt32);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt32, isComplex: true);
+ }
+ if (typeof(T) == typeof(long))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt64);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt64, isComplex: true);
+ }
+ if (typeof(T) == typeof(ulong))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt64);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxUInt64, isComplex: true);
+ }
+ if (typeof(T) == typeof(float))
+ {
+ return ConstructArrayFlags(ArrayType.MxSingle);
+ }
+ if (typeof(T) == typeof(ComplexOf))
+ {
+ return ConstructArrayFlags(ArrayType.MxSingle, isComplex: true);
+ }
+ if (typeof(T) == typeof(double))
+ {
+ return ConstructArrayFlags(ArrayType.MxDouble);
+ }
+ if (typeof(T) == typeof(Complex))
+ {
+ return ConstructArrayFlags(ArrayType.MxDouble, isComplex: true);
+ }
+ if (typeof(T) == typeof(bool))
+ {
+ return ConstructArrayFlags(ArrayType.MxInt8, isLogical: true);
+ }
+ return ConstructArrayFlags(ArrayType.MxObject);
+ }
+
+ private SparseArrayFlags GetStandardSparseArrayFlags()
+ {
+ var arrayFlags = GetStandardFlags();
+ return new SparseArrayFlags
+ {
+ ArrayFlags = arrayFlags,
+ NzMax = 0,
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler/DataElement.cs b/MatFileHandler/DataElement.cs
new file mode 100755
index 0000000..ff8d08d
--- /dev/null
+++ b/MatFileHandler/DataElement.cs
@@ -0,0 +1,11 @@
+// Copyright 2017 Alexander Luzgarev
+
+namespace MatFileHandler
+{
+ ///
+ /// Base class for all data elements in .mat files.
+ ///
+ internal class DataElement
+ {
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler/DataElementConverter.cs b/MatFileHandler/DataElementConverter.cs
new file mode 100755
index 0000000..19652c5
--- /dev/null
+++ b/MatFileHandler/DataElementConverter.cs
@@ -0,0 +1,246 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace MatFileHandler
+{
+ ///
+ /// Static class for constructing various arrays from raw data elements read from .mat files.
+ ///
+ internal static class DataElementConverter
+ {
+ ///
+ /// Construct a complex sparse array.
+ ///
+ /// Array flags.
+ /// Array dimensions.
+ /// Array name.
+ /// Row indices.
+ /// Denotes index ranges for each column.
+ /// Real parts of the values.
+ /// Imaginary parts of the values.
+ /// A constructed array.
+ public static MatArray ConvertToMatSparseArrayOfComplex(
+ SparseArrayFlags flags,
+ int[] dimensions,
+ string name,
+ int[] rowIndex,
+ int[] columnIndex,
+ DataElement data,
+ DataElement imaginaryData)
+ {
+ var realParts = DataExtraction.GetDataAsDouble(data).ToArrayLazily();
+ var imaginaryParts = DataExtraction.GetDataAsDouble(imaginaryData).ToArrayLazily();
+ if (realParts == null)
+ {
+ throw new HandlerException("Couldn't read sparse array.");
+ }
+ var dataDictionary =
+ ConvertMatlabSparseToDictionary(
+ rowIndex,
+ columnIndex,
+ j => new Complex(realParts[j], imaginaryParts[j]));
+ return new MatSparseArrayOf(flags, dimensions, name, dataDictionary);
+ }
+
+ ///
+ /// Construct a double sparse array or a logical sparse array.
+ ///
+ /// Element type (Double or Boolean).
+ /// Array flags.
+ /// Array dimensions.
+ /// Array name.
+ /// Row indices.
+ /// Denotes index ranges for each column.
+ /// The values.
+ /// A constructed array.
+ public static MatArray ConvertToMatSparseArrayOf(
+ SparseArrayFlags flags,
+ int[] dimensions,
+ string name,
+ int[] rowIndex,
+ int[] columnIndex,
+ DataElement data)
+ where T : struct
+ {
+ if (dimensions.Length != 2)
+ {
+ throw new NotSupportedException("Only 2-dimensional sparse arrays are supported");
+ }
+ if (data == null)
+ {
+ throw new ArgumentException("Null data found.", "data");
+ }
+ var elements =
+ ConvertDataToSparseProperType(data, flags.ArrayFlags.Variable.HasFlag(Variable.IsLogical));
+ if (elements == null)
+ {
+ throw new HandlerException("Couldn't read sparse array.");
+ }
+ var dataDictionary =
+ ConvertMatlabSparseToDictionary(rowIndex, columnIndex, j => elements[j]);
+ return new MatSparseArrayOf(flags, dimensions, name, dataDictionary);
+ }
+
+ ///
+ /// Construct a numerical array.
+ ///
+ /// Element type.
+ /// Array flags.
+ /// Array dimensions.
+ /// Array name.
+ /// Real parts of the values.
+ /// Imaginary parts of the values.
+ /// A constructed array.
+ ///
+ /// Possible values for T: Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double,
+ /// ComplexOf<TReal> (where TReal is one of Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single),
+ /// Complex.
+ ///
+ public static MatArray ConvertToMatNumericalArrayOf(
+ ArrayFlags flags,
+ int[] dimensions,
+ string name,
+ DataElement realData,
+ DataElement imaginaryData)
+ where T : struct
+ {
+ if (flags.Variable.HasFlag(Variable.IsLogical))
+ {
+ var data = DataExtraction.GetDataAsUInt8(realData).ToArrayLazily().Select(x => x != 0).ToArray();
+ return new MatNumericalArrayOf(flags, dimensions, name, data);
+ }
+ switch (flags.Class)
+ {
+ case ArrayType.MxChar:
+ switch (realData)
+ {
+ case MiNum dataByte:
+ return ConvertToMatCharArray(flags, dimensions, name, dataByte);
+ case MiNum dataUshort:
+ return ConvertToMatCharArray(flags, dimensions, name, dataUshort);
+ default:
+ throw new NotSupportedException("Only utf8, utf16 or ushort char arrays are supported.");
+ }
+ case ArrayType.MxDouble:
+ case ArrayType.MxSingle:
+ case ArrayType.MxInt8:
+ case ArrayType.MxUInt8:
+ case ArrayType.MxInt16:
+ case ArrayType.MxUInt16:
+ case ArrayType.MxInt32:
+ case ArrayType.MxUInt32:
+ case ArrayType.MxInt64:
+ case ArrayType.MxUInt64:
+ var dataArray = ConvertDataToProperType(realData, flags.Class);
+ if (flags.Variable.HasFlag(Variable.IsComplex))
+ {
+ var dataArray2 = ConvertDataToProperType(imaginaryData, flags.Class);
+ if (flags.Class == ArrayType.MxDouble)
+ {
+ var complexArray =
+ (dataArray as double[])
+ .Zip(dataArray2 as double[], (x, y) => new Complex(x, y))
+ .ToArray();
+ return new MatNumericalArrayOf(flags, dimensions, name, complexArray);
+ }
+ var complexDataArray = dataArray.Zip(dataArray2, (x, y) => new ComplexOf(x, y)).ToArray();
+ return new MatNumericalArrayOf>(flags, dimensions, name, complexDataArray);
+ }
+ return new MatNumericalArrayOf(flags, dimensions, name, dataArray);
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ private static MatCharArrayOf ConvertToMatCharArray(
+ ArrayFlags flags,
+ int[] dimensions,
+ string name,
+ MiNum dataElement)
+ {
+ var data = dataElement?.Data;
+ return new MatCharArrayOf(flags, dimensions, name, data, Encoding.UTF8.GetString(data));
+ }
+
+ private static T[] ConvertDataToProperType(DataElement data, ArrayType arrayType)
+ {
+ switch (arrayType)
+ {
+ case ArrayType.MxDouble:
+ return DataExtraction.GetDataAsDouble(data).ToArrayLazily() as T[];
+ case ArrayType.MxSingle:
+ return DataExtraction.GetDataAsSingle(data).ToArrayLazily() as T[];
+ case ArrayType.MxInt8:
+ return DataExtraction.GetDataAsInt8(data).ToArrayLazily() as T[];
+ case ArrayType.MxUInt8:
+ return DataExtraction.GetDataAsUInt8(data).ToArrayLazily() as T[];
+ case ArrayType.MxInt16:
+ return DataExtraction.GetDataAsInt16(data).ToArrayLazily() as T[];
+ case ArrayType.MxUInt16:
+ return DataExtraction.GetDataAsUInt16(data).ToArrayLazily() as T[];
+ case ArrayType.MxInt32:
+ return DataExtraction.GetDataAsInt32(data).ToArrayLazily() as T[];
+ case ArrayType.MxUInt32:
+ return DataExtraction.GetDataAsUInt32(data).ToArrayLazily() as T[];
+ case ArrayType.MxInt64:
+ return DataExtraction.GetDataAsInt64(data).ToArrayLazily() as T[];
+ case ArrayType.MxUInt64:
+ return DataExtraction.GetDataAsUInt64(data).ToArrayLazily() as T[];
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ private static T[] ConvertDataToSparseProperType(DataElement data, bool isLogical)
+ {
+ if (isLogical)
+ {
+ return DataExtraction.GetDataAsUInt8(data).ToArrayLazily().Select(x => x != 0).ToArray() as T[];
+ }
+ switch (data)
+ {
+ case MiNum _:
+ return DataExtraction.GetDataAsDouble(data).ToArrayLazily() as T[];
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ private static MatCharArrayOf ConvertToMatCharArray(
+ ArrayFlags flags,
+ int[] dimensions,
+ string name,
+ MiNum dataElement)
+ {
+ var data = dataElement?.Data;
+ return new MatCharArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ new string(data.Select(x => (char)x).ToArray()));
+ }
+
+ private static Dictionary<(int, int), T> ConvertMatlabSparseToDictionary(
+ int[] rowIndex,
+ int[] columnIndex,
+ Func get)
+ {
+ var result = new Dictionary<(int, int), T>();
+ for (var column = 0; column < columnIndex.Length - 1; column++)
+ {
+ for (var j = columnIndex[column]; j < columnIndex[column + 1]; j++)
+ {
+ var row = rowIndex[j];
+ result[(row, column)] = get(j);
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler/DataElementReader.cs b/MatFileHandler/DataElementReader.cs
new file mode 100755
index 0000000..ab03dd7
--- /dev/null
+++ b/MatFileHandler/DataElementReader.cs
@@ -0,0 +1,434 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+
+namespace MatFileHandler
+{
+ ///
+ /// Functions for reading data elements from a .mat file.
+ ///
+ internal static class DataElementReader
+ {
+ ///
+ /// Read a data element.
+ ///
+ /// Input reader.
+ /// Data element.
+ public static DataElement Read(BinaryReader reader)
+ {
+ var (dataReader, tag) = ReadTag(reader);
+ DataElement result;
+ switch (tag.Type)
+ {
+ case DataType.MiInt8:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiUInt8:
+ case DataType.MiUtf8:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiInt16:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiUInt16:
+ case DataType.MiUtf16:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiInt32:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiUInt32:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiSingle:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiDouble:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiInt64:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiUInt64:
+ result = ReadNum(tag, dataReader);
+ break;
+ case DataType.MiMatrix:
+ result = ReadMatrix(tag, dataReader);
+ break;
+ case DataType.MiCompressed:
+ result = ReadCompressed(tag, dataReader);
+ break;
+ default:
+ throw new NotSupportedException("Unknown element.");
+ }
+ if (tag.Type != DataType.MiCompressed)
+ {
+ var position = reader.BaseStream.Position;
+ if (position % 8 != 0)
+ {
+ reader.ReadBytes(8 - (int)(position % 8));
+ }
+ }
+ return result;
+ }
+
+ private static (BinaryReader, Tag) ReadTag(BinaryReader reader)
+ {
+ var type = reader.ReadInt32();
+ var typeHi = type >> 16;
+ if (typeHi == 0)
+ {
+ var length = reader.ReadInt32();
+ return (reader, new Tag((DataType)type, length));
+ }
+ else
+ {
+ var length = typeHi;
+ type = type & 0xffff;
+ var smallReader = new BinaryReader(new MemoryStream(reader.ReadBytes(4)));
+ return (smallReader, new Tag((DataType)type, length));
+ }
+ }
+
+ private static ArrayFlags ReadArrayFlags(DataElement element)
+ {
+ var flagData = (element as MiNum)?.Data ?? throw new HandlerException("Couldn't read array flags.");
+ var class_ = (ArrayType)(flagData[0] & 0xff);
+ var variableFlags = (flagData[0] >> 8) & 0x0e;
+ return new ArrayFlags
+ {
+ Class = class_,
+ Variable = (Variable)variableFlags,
+ };
+ }
+
+ private static SparseArrayFlags ReadSparseArrayFlags(DataElement element)
+ {
+ var arrayFlags = ReadArrayFlags(element);
+ var flagData = (element as MiNum)?.Data ??
+ throw new HandlerException("Couldn't read sparse array flags.");
+ var nzMax = flagData[1];
+ return new SparseArrayFlags
+ {
+ ArrayFlags = arrayFlags,
+ NzMax = nzMax,
+ };
+ }
+
+ private static int[] ReadDimensionsArray(DataElement element)
+ {
+ return (element as MiNum)?.Data;
+ }
+
+ private static DataElement ReadData(DataElement element)
+ {
+ return element;
+ }
+
+ private static string ReadName(DataElement element)
+ {
+ return Encoding.ASCII.GetString((element as MiNum)?.Data.Select(x => (byte)x).ToArray());
+ }
+
+ private static DataElement ReadNum(Tag tag, BinaryReader reader)
+ where T : struct
+ {
+ var bytes = reader.ReadBytes(tag.Length);
+ if (tag.Type == DataType.MiUInt8)
+ {
+ return new MiNum(bytes);
+ }
+ var result = new T[bytes.Length / tag.ElementSize];
+ Buffer.BlockCopy(bytes, 0, result, 0, bytes.Length);
+ return new MiNum(result);
+ }
+
+ private static string[] ReadFieldNames(MiNum element, int fieldNameLength)
+ {
+ var numberOfFields = element.Data.Length / fieldNameLength;
+ var result = new string[numberOfFields];
+ for (var i = 0; i < numberOfFields; i++)
+ {
+ var list = new List();
+ var position = i * fieldNameLength;
+ while (element.Data[position] != 0)
+ {
+ list.Add((byte)element.Data[position]);
+ position++;
+ }
+ result[i] = Encoding.ASCII.GetString(list.ToArray());
+ }
+ return result;
+ }
+
+ private static DataElement ContinueReadingSparseArray(
+ BinaryReader reader,
+ DataElement firstElement,
+ int[] dimensions,
+ string name)
+ {
+ var sparseArrayFlags = ReadSparseArrayFlags(firstElement);
+ var rowIndex = Read(reader) as MiNum;
+ var columnIndex = Read(reader) as MiNum;
+ var data = Read(reader);
+ if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
+ {
+ return DataElementConverter.ConvertToMatSparseArrayOf(
+ sparseArrayFlags,
+ dimensions,
+ name,
+ rowIndex?.Data,
+ columnIndex?.Data,
+ data);
+ }
+ if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
+ {
+ var imaginaryData = Read(reader);
+ return DataElementConverter.ConvertToMatSparseArrayOfComplex(
+ sparseArrayFlags,
+ dimensions,
+ name,
+ rowIndex?.Data,
+ columnIndex?.Data,
+ data,
+ imaginaryData);
+ }
+ switch (data)
+ {
+ case MiNum _:
+ return DataElementConverter.ConvertToMatSparseArrayOf(
+ sparseArrayFlags,
+ dimensions,
+ name,
+ rowIndex?.Data,
+ columnIndex?.Data,
+ data);
+ default:
+ throw new NotSupportedException("Only double and logical sparse arrays are supported.");
+ }
+ }
+
+ private static DataElement ContinueReadingCellArray(
+ BinaryReader reader,
+ ArrayFlags flags,
+ int[] dimensions,
+ string name)
+ {
+ var numberOfElements = dimensions.NumberOfElements();
+ var elements = new List();
+ for (var i = 0; i < numberOfElements; i++)
+ {
+ var element = Read(reader) as IArray;
+ elements.Add(element);
+ }
+ return new MatCellArray(flags, dimensions, name, elements);
+ }
+
+ private static DataElement ContinueReadingStructure(
+ BinaryReader reader,
+ ArrayFlags flags,
+ int[] dimensions,
+ string name,
+ int fieldNameLength)
+ {
+ var element = Read(reader);
+ var fieldNames = ReadFieldNames(element as MiNum, fieldNameLength);
+ var fields = new Dictionary>();
+ foreach (var fieldName in fieldNames)
+ {
+ fields[fieldName] = new List();
+ }
+ var numberOfElements = dimensions.NumberOfElements();
+ for (var i = 0; i < numberOfElements; i++)
+ {
+ foreach (var fieldName in fieldNames)
+ {
+ var field = Read(reader) as IArray;
+ fields[fieldName].Add(field);
+ }
+ }
+ return new MatStructureArray(flags, dimensions, name, fields);
+ }
+
+ private static DataElement ContinueReadingNewObject()
+ {
+ throw new HandlerException("Cannot read objects.");
+ }
+
+ private static DataElement ReadMatrix(Tag tag, BinaryReader reader)
+ {
+ if (tag.Length == 0)
+ {
+ return MatArray.Empty();
+ }
+ var element1 = Read(reader);
+ var flags = ReadArrayFlags(element1);
+ if (flags.Class == ArrayType.MxNewObject)
+ {
+ return ContinueReadingNewObject();
+ }
+ var element2 = Read(reader);
+ var dimensions = ReadDimensionsArray(element2);
+ var element3 = Read(reader);
+ var name = ReadName(element3);
+ if (flags.Class == ArrayType.MxCell)
+ {
+ return ContinueReadingCellArray(reader, flags, dimensions, name);
+ }
+ if (flags.Class == ArrayType.MxSparse)
+ {
+ return ContinueReadingSparseArray(reader, element1, dimensions, name);
+ }
+ var element4 = Read(reader);
+ var data = ReadData(element4);
+ DataElement imaginaryData = null;
+ if (flags.Variable.HasFlag(Variable.IsComplex))
+ {
+ var element5 = Read(reader);
+ imaginaryData = ReadData(element5);
+ }
+ if (flags.Class == ArrayType.MxStruct)
+ {
+ var fieldNameLengthData = (data as MiNum)?.Data ??
+ throw new HandlerException("Couldn't read structure field name length.");
+ return ContinueReadingStructure(reader, flags, dimensions, name, fieldNameLengthData[0]);
+ }
+ switch (flags.Class)
+ {
+ case ArrayType.MxChar:
+ switch (data)
+ {
+ case MiNum _:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case MiNum _:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ default:
+ throw new NotSupportedException(
+ $"This type of char array ({data.GetType()}) is not supported.");
+ }
+ case ArrayType.MxInt8:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxUInt8:
+ if (flags.Variable.HasFlag(Variable.IsLogical))
+ {
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ }
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxInt16:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxUInt16:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxInt32:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxUInt32:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxInt64:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxUInt64:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxSingle:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ case ArrayType.MxDouble:
+ return DataElementConverter.ConvertToMatNumericalArrayOf(
+ flags,
+ dimensions,
+ name,
+ data,
+ imaginaryData);
+ default:
+ throw new HandlerException("Unknown data type.");
+ }
+ }
+
+ private static DataElement ReadCompressed(Tag tag, BinaryReader reader)
+ {
+ reader.ReadBytes(2);
+ var compressedData = new byte[tag.Length - 6];
+ reader.BaseStream.Read(compressedData, 0, tag.Length - 6);
+ reader.ReadBytes(4);
+ var resultStream = new MemoryStream();
+ using (var compressedStream = new MemoryStream(compressedData))
+ {
+ using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true))
+ {
+ stream.CopyTo(resultStream);
+ }
+ }
+ resultStream.Position = 0;
+ return Read(resultStream);
+ }
+
+ private static DataElement Read(Stream stream)
+ {
+ using (var reader = new BinaryReader(stream))
+ {
+ return Read(reader);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatFileHandler/DataExtraction.cs b/MatFileHandler/DataExtraction.cs
new file mode 100755
index 0000000..8bfba9f
--- /dev/null
+++ b/MatFileHandler/DataExtraction.cs
@@ -0,0 +1,366 @@
+// Copyright 2017 Alexander Luzgarev
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MatFileHandler
+{
+ ///
+ /// Functions for extracting values from data elements.
+ ///
+ internal static class DataExtraction
+ {
+ ///
+ /// Convert IEnumerable to array.
+ ///
+ /// Element type.
+ /// Input IEnumerable.
+ /// An equivalent array.
+ /// In contrast to the stanard ToArray() method, this doesn't create a copy if the input already was an array.
+ public static T[] ToArrayLazily(this IEnumerable somethingEnumerable)
+ {
+ return somethingEnumerable as T[] ?? somethingEnumerable.ToArray();
+ }
+
+ ///
+ /// Convert the contents of the Matlab data element to a sequence of Double values.
+ ///
+ /// Data element.
+ /// Contents of the elements, converted to Double.
+ public static IEnumerable GetDataAsDouble(DataElement element)
+ {
+ switch (element)
+ {
+ case MiNum sbyteElement:
+ return sbyteElement.Data.Select(Convert.ToDouble);
+ case MiNum byteElement:
+ return byteElement.Data.Select(Convert.ToDouble);
+ case MiNum intElement:
+ return intElement.Data.Select(Convert.ToDouble);
+ case MiNum uintElement:
+ return uintElement.Data.Select(Convert.ToDouble);
+ case MiNum shortElement:
+ return shortElement.Data.Select(Convert.ToDouble);
+ case MiNum ushortElement:
+ return ushortElement.Data.Select(Convert.ToDouble);
+ case MiNum longElement:
+ return longElement.Data.Select(Convert.ToDouble);
+ case MiNum ulongElement:
+ return ulongElement.Data.Select(Convert.ToDouble);
+ case MiNum floatElement:
+ return floatElement.Data.Select(Convert.ToDouble);
+ case MiNum doubleElement:
+ return doubleElement.Data;
+ }
+ throw new HandlerException(
+ $"Expected data element that would be convertible to double, found {element.GetType()}.");
+ }
+
+ ///
+ /// Convert the contents of the Matlab data element to a sequence of Single values.
+ ///
+ /// Data element.
+ /// Contents of the elements, converted to Single.
+ public static IEnumerable GetDataAsSingle(DataElement element)
+ {
+ switch (element)
+ {
+ case MiNum sbyteElement:
+ return sbyteElement.Data.Select(Convert.ToSingle);
+ case MiNum