Compare commits

..

19 Commits
master ... hdf

Author SHA1 Message Date
80e4356442 Update copyright text 2019-03-23 09:21:23 +01:00
aaefd0eb86 Unify MatFileReader tests 2019-03-23 09:18:03 +01:00
6a286ed53d Use latest C# version 2019-03-21 22:04:11 +01:00
e6d236139c Add XML documentation 2019-03-21 21:30:30 +01:00
bdd04a80b7 Unify HDF and old-style datatypes 2019-03-20 22:15:22 +01:00
433dca8c69 Get rid of unnecessary ArrayFlags 2019-03-20 22:01:47 +01:00
ef73a380bb Hdf refactoring 2019-03-17 20:39:11 +01:00
199ab46f0c Support global variables 2019-03-16 14:42:08 +01:00
cfe51d57ae Support sparse arrays 2019-03-16 14:30:43 +01:00
f727c6d430 Rename HDF-related types 2019-03-15 19:39:36 +01:00
82f43a7e5d Careful disposal 2019-03-12 21:15:12 +01:00
99558d96c4 Support logical arrays 2019-03-12 18:54:24 +01:00
67905605d6 Support structure and cell arrays 2019-03-11 22:15:06 +01:00
b0ae15abc9 Support complex numerical arrays 2019-03-10 12:35:28 +01:00
beae9e4429 Provide runtime identifiers for test project 2019-03-09 20:01:56 +01:00
b66e380414 Support numerical arrays 2019-03-09 18:53:44 +01:00
93be86d526 Support char arrays 2019-03-09 17:00:13 +01:00
0e14434bae Move test data 2019-03-07 19:04:16 +01:00
957234d30d Multi-target test project 2019-03-07 18:46:10 +01:00
127 changed files with 3777 additions and 4746 deletions

View File

@ -1,248 +0,0 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# C# files
[*.cs]
dotnet_analyzer_diagnostic.category-Design.severity = warning
dotnet_analyzer_diagnostic.category-Documentation.severity = warning
dotnet_analyzer_diagnostic.category-Globalization.severity = warning
dotnet_analyzer_diagnostic.category-Interoperability.severity = warning
dotnet_analyzer_diagnostic.category-Maintainability.severity = warning
dotnet_analyzer_diagnostic.category-Naming.severity = warning
dotnet_analyzer_diagnostic.category-Performance.severity = warning
dotnet_analyzer_diagnostic.category-Reliability.severity = warning
dotnet_analyzer_diagnostic.category-Security.severity = warning
dotnet_analyzer_diagnostic.category-Style.severity = warning
dotnet_analyzer_diagnostic.category-Usage.severity = warning
dotnet_diagnostic.IDE0010.severity = none
dotnet_diagnostic.IDE0072.severity = none
dotnet_diagnostic.CA1707.severity = none
dotnet_diagnostic.CA1861.severity = none
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_collection_expression = false
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = false
dotnet_style_prefer_conditional_expression_over_return = false
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all:silent
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = true
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true
# Expression-bodied members
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = true:none
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
csharp_style_prefer_readonly_struct_member = true
# Code-block preferences
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = file_scoped:none
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = false
csharp_style_prefer_top_level_statements = true
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = true
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = true
csharp_style_prefer_tuple_swap = true
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright 2017-2025 Alexander Luzgarev
Copyright 2017-2019 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:

View File

@ -0,0 +1,68 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MatFileHandler.Tests
{
/// <summary>
/// Abstract factory of test data.
/// </summary>
/// <typeparam name="TTestData">Type of test data.</typeparam>
public abstract class AbstractTestDataFactory<TTestData>
{
/// <summary>
/// Initializes a new instance of the <see cref="AbstractTestDataFactory{TTestData}"/> class.
/// </summary>
/// <param name="dataDirectory">Directory with test files.</param>
/// <param name="testFilenameConvention">A convention used to filter test files.</param>
protected AbstractTestDataFactory(string dataDirectory, ITestFilenameConvention testFilenameConvention)
{
DataDirectory = dataDirectory;
TestFilenameConvention = testFilenameConvention;
}
private string DataDirectory { get; }
private ITestFilenameConvention TestFilenameConvention { get; }
/// <summary>
/// Get test data set by name.
/// </summary>
/// <param name="dataSet">Name of the data set.</param>
/// <returns>Test data.</returns>
public TTestData this[string dataSet] =>
ReadTestData(FixPath(TestFilenameConvention.ConvertTestNameToFilename(dataSet)));
/// <summary>
/// Get a sequence of all test data sets in the factory.
/// </summary>
/// <returns>A sequence of data sets.</returns>
public IEnumerable<TTestData> GetAllTestData()
{
var files = Directory.EnumerateFiles(DataDirectory).Where(TestFilenameConvention.FilterFile);
foreach (var filename in files)
{
yield return ReadTestData(filename);
}
}
/// <summary>
/// Read test data from a stream.
/// </summary>
/// <param name="stream">Input stream.</param>
/// <returns>Test data.</returns>
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);
}
}
}
}

View File

@ -1,29 +1,32 @@
using System;
// Copyright 2017-2019 Alexander Luzgarev
using System.Collections.Generic;
using System.Numerics;
using Xunit;
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of Matlab array manipulation.
/// </summary>
public sealed class ArrayHandlingTests : IDisposable
[TestFixture]
public class ArrayHandlingTests
{
private DataBuilder Builder { get; set; }
/// <summary>
/// Setup for array handling tests.
/// Set up a DataBuilder.
/// </summary>
public ArrayHandlingTests()
[SetUp]
public void Setup()
{
Builder = new DataBuilder();
}
private DataBuilder Builder { get; set; }
/// <summary>
/// Test numerical array creation.
/// </summary>
[Fact]
[Test]
public void TestCreate()
{
TestCreateArrayOf<sbyte>();
@ -51,78 +54,70 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test numerical array manipulation.
/// </summary>
[Fact]
[Test]
public void TestNumArray()
{
var array = Builder.NewArray<int>(2, 3);
array[0, 1] = 2;
Assert.Equal(2, array[0, 1]);
Assert.That(array[0, 1], Is.EqualTo(2));
}
/// <summary>
/// Test cell array manipulation.
/// </summary>
[Fact]
[Test]
public void TestCellArray()
{
var array = Builder.NewCellArray(2, 3);
Assert.Equal(new[] { 2, 3 }, array.Dimensions);
Assert.That(array.Dimensions, Is.EqualTo(new[] { 2, 3 }));
array[0, 1] = Builder.NewArray<int>(1, 2);
Assert.True(array[1, 2].IsEmpty);
Assert.False(array[0, 1].IsEmpty);
Assert.That(array[1, 2].IsEmpty, Is.True);
Assert.That(array[0, 1].IsEmpty, Is.False);
var assigned = (IArrayOf<int>)array[0, 1];
Assert.NotNull(assigned);
Assert.Equal(new[] { 1, 2 }, assigned.Dimensions);
Assert.That(assigned, Is.Not.Null);
Assert.That(assigned.Dimensions, Is.EqualTo(new[] { 1, 2 }));
}
/// <summary>
/// Test structure array manipulation.
/// </summary>
[Fact]
[Test]
public void TestStructureArray()
{
var array = Builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
array["x", 0, 1] = Builder.NewArray<int>(1, 2);
Assert.True(array["y", 0, 1].IsEmpty);
Assert.False(array["x", 0, 1].IsEmpty);
Assert.That(array["y", 0, 1].IsEmpty, Is.True);
Assert.That(array["x", 0, 1].IsEmpty, Is.False);
var assigned = (IArrayOf<int>)array["x", 0, 1];
Assert.NotNull(assigned);
Assert.Equal(new[] { 1, 2 }, assigned.Dimensions);
Assert.That(assigned, Is.Not.Null);
Assert.That(assigned.Dimensions, Is.EqualTo(new[] { 1, 2 }));
}
/// <summary>
/// Test character array manipulation.
/// </summary>
[Fact]
[Test]
public void TestString()
{
var array = Builder.NewCharArray("🍆");
Assert.Equal(new[] { 1, 2 }, array.Dimensions);
Assert.That(array.Dimensions, Is.EqualTo(new[] { 1, 2 }));
}
/// <summary>
/// Test file creation.
/// </summary>
[Fact]
[Test]
public void TestFile()
{
var file = Builder.NewFile(new List<IVariable>());
Assert.NotNull(file);
Assert.That(file, Is.Not.Null);
}
private static void TestCreateArrayOf<T>()
where T : struct
{
var array = new DataBuilder().NewArray<T>(2, 3);
Assert.NotNull(array);
}
/// <summary>
/// Cleanup.
/// </summary>
public void Dispose()
{
Assert.That(array, Is.Not.Null);
}
}
}
}

View File

@ -1,102 +0,0 @@
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="bytes">Bytes to write.</param>
[Theory]
[MemberData(nameof(TestData))]
public void Test(byte[] bytes)
{
using var stream = new MemoryStream();
var sut = new ChecksumCalculatingStream(stream);
sut.Write(bytes, 0, bytes.Length);
var actual = sut.GetCrc();
var expected = ReferenceCalculation(bytes);
}
/// <summary>
/// Test data for <see cref="Test"/>.
/// </summary>
/// <returns>Test data.</returns>
public static TheoryData<byte[]> TestData()
{
var empty = new byte[1234];
var nonEmpty = new byte[12345];
for (var i = 0; i < 1234; i++)
{
nonEmpty[i] = (byte)((i * i) % 256);
}
return new TheoryData<byte[]>()
{
new byte[] { 0x00 },
new byte[] { 0x01 },
new byte[] { 0xff },
new byte[] { 0xff, 0xff },
new byte[] { 0xff, 0xff, 0xff },
new byte[] { 0xff, 0xff, 0xff, 0xff },
new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff },
new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
new byte[] { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
new byte[] { 0x02, 0x03, 0x05, 0x07, 0x0b, 0x0d, 0x11, 0x13, 0x17, 0x1d },
empty,
nonEmpty,
};
}
private static Action<Stream> BinaryWriterAction(Action<BinaryWriter> action)
{
return stream =>
{
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
action(writer);
};
}
private static uint ReferenceCalculation(byte[] bytes)
{
using var stream = new MemoryStream();
stream.Write(bytes, 0, bytes.Length);
stream.Position = 0;
return CalculateAdler32Checksum(stream);
}
private static uint CalculateAdler32Checksum(MemoryStream 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;
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler.Tests
{
/// <summary>
@ -45,4 +47,4 @@ namespace MatFileHandler.Tests
/// </summary>
public static readonly ulong[] UInt64Limits = { 0UL, 18446744073709551615UL };
}
}
}

View File

@ -1,48 +1,51 @@
using Xunit;
// Copyright 2017-2019 Alexander Luzgarev
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of the ComplexOf value type.
/// </summary>
[TestFixture]
public class ComplexOfTests
{
/// <summary>
/// Test getting real and imaginary parts.
/// </summary>
[Fact]
[Test]
public void TestAccessors()
{
var c = new ComplexOf<byte>(1, 2);
Assert.Equal(1, c.Real);
Assert.Equal(2, c.Imaginary);
Assert.That(c.Real, Is.EqualTo(1));
Assert.That(c.Imaginary, Is.EqualTo(2));
}
/// <summary>
/// Test equality operators.
/// </summary>
[Fact]
[Test]
public void TestEquals()
{
var c1 = new ComplexOf<byte>(1, 2);
var c2 = new ComplexOf<byte>(3, 4);
var c3 = new ComplexOf<byte>(1, 2);
Assert.True(c1 == c3);
Assert.True(c1 != c2);
Assert.True(c2 != c3);
Assert.That(c1 == c3, Is.True);
Assert.That(c1 != c2, Is.True);
Assert.That(c2 != c3, Is.True);
}
/// <summary>
/// Test hash code calculation.
/// </summary>
[Fact]
[Test]
public void TestGetHashCode()
{
var c1 = new ComplexOf<byte>(1, 2);
var c2 = new ComplexOf<byte>(1, 2);
var h1 = c1.GetHashCode();
var h2 = c2.GetHashCode();
Assert.Equal(h1, h2);
Assert.That(h1, Is.EqualTo(h2));
}
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// A filename convention based on file extensions.
/// </summary>
internal class ExtensionTestFilenameConvention : ITestFilenameConvention
{
/// <summary>
/// Initializes a new instance of the <see cref="ExtensionTestFilenameConvention"/> class.
/// </summary>
/// <param name="extension">File extension.</param>
public ExtensionTestFilenameConvention(string extension)
{
Extension = extension;
}
private string Extension { get; }
/// <summary>
/// Convert test name to filename by adding the extension.
/// </summary>
/// <param name="testName">Test name.</param>
/// <returns>The corresponding filename.</returns>
public string ConvertTestNameToFilename(string testName)
{
return Path.ChangeExtension(testName, Extension);
}
/// <summary>
/// Compare file's extension to the one specified during initialization.
/// </summary>
/// <param name="filename">Filename.</param>
/// <returns>True iff the file has the extension stored in the class.</returns>
public bool FilterFile(string filename)
{
return Path.GetExtension(filename) == "." + Extension;
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler.Tests
{
/// <summary>
/// Interface for handling filtering tests based on filenames.
/// </summary>
public interface ITestFilenameConvention
{
/// <summary>
/// Convert test name to a filename (e.g., by adding an appropriate extension).
/// </summary>
/// <param name="testName">Name of a test.</param>
/// <returns>Filename.</returns>
string ConvertTestNameToFilename(string testName);
/// <summary>
/// Decide if a file contains a test based on the filename.
/// </summary>
/// <param name="filename">A filename.</param>
/// <returns>True iff the file should contain a test.</returns>
bool FilterFile(string filename);
}
}

View File

@ -1,23 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;net472;net8.0</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<RuntimeIdentifiers>win-x64;win-x86</RuntimeIdentifiers>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>bin\Debug\net5.0\MatFileHandler.Tests.xml</DocumentationFile>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Debug\netcoreapp2.0\MatFileHandler.Tests.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<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>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="NUnit" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.9.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MatFileHandler\MatFileHandler.csproj" />
@ -25,4 +26,9 @@
<ItemGroup>
<Content Include="test-data\**\*.mat" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta004">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

1219
MatFileHandler.Tests/MatFileReaderTests.cs Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
namespace MatFileHandler.Tests;
/// <summary>
/// Method of reading .mat files for testing.
/// </summary>
public enum MatFileReadingMethod
{
/// <summary>
/// Undefined.
/// </summary>
Undefined = 0,
/// <summary>
/// Normal stream (like memory or file stream).
/// </summary>
NormalStream,
/// <summary>
/// Partial stream (only is capable of reading one byte at a time).
/// </summary>
PartialStream,
/// <summary>
/// Unaligned stream (what happens if the data don't start at the beginning?).
/// </summary>
UnalignedStream,
}

View File

@ -1,38 +0,0 @@
using System;
using System.IO;
namespace MatFileHandler.Tests;
internal static class MatFileReadingMethods
{
public static IMatFile ReadMatFile(MatFileReadingMethod method, string fullFileName)
{
using var stream = File.OpenRead(fullFileName);
switch (method)
{
case MatFileReadingMethod.NormalStream:
return ReadFromStream(stream);
case MatFileReadingMethod.PartialStream:
{
using var wrapper = new PartialUnseekableReadStream(stream);
return ReadFromStream(wrapper);
}
case MatFileReadingMethod.UnalignedStream:
{
using var ms = new MemoryStream();
ms.Seek(3, SeekOrigin.Begin);
stream.CopyTo(ms);
ms.Seek(3, SeekOrigin.Begin);
return ReadFromStream(ms);
}
default:
throw new NotImplementedException();
}
}
private static IMatFile ReadFromStream(Stream stream)
{
var reader = new MatFileReader(stream);
return reader.Read();
}
}

View File

@ -1,27 +0,0 @@
namespace MatFileHandler.Tests;
/// <summary>
/// Options to give to MatFileWriter constructor for testing it.
/// </summary>
public enum MatFileWriterOptionsForTests
{
/// <summary>
/// Undefined.
/// </summary>
Undefined = 0,
/// <summary>
/// No options.
/// </summary>
None,
/// <summary>
/// Option to always use compression.
/// </summary>
Always,
/// <summary>
/// Option to never use compression.
/// </summary>
Never,
}

View File

@ -1,13 +1,16 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.IO;
using System.Numerics;
using Xunit;
using NUnit.Framework;
namespace MatFileHandler.Tests
{
/// <summary>
/// Tests of file writing API.
/// </summary>
[TestFixture]
public class MatFileWriterTests
{
private const string TestDirectory = "test-data";
@ -15,8 +18,8 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test writing a simple Double array.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestWrite(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestWrite()
{
var builder = new DataBuilder();
var array = builder.NewArray<double>(1, 2);
@ -24,13 +27,13 @@ namespace MatFileHandler.Tests
array[1] = 17.0;
var variable = builder.NewVariable("test", array);
var actual = builder.NewFile(new[] { variable });
MatCompareWithTestData("good", "double-array", actual, method, options);
MatCompareWithLevel5TestData("double-array", actual);
}
/// <summary>
/// Test writing a large file.
/// </summary>
[Fact]
[Test]
public void TestHuge()
{
var builder = new DataBuilder();
@ -49,8 +52,8 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test writing lower and upper limits of integer data types.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestLimits(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestLimits()
{
var builder = new DataBuilder();
var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2));
@ -62,14 +65,14 @@ namespace MatFileHandler.Tests
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, method, options);
MatCompareWithLevel5TestData("limits", actual);
}
/// <summary>
/// Test writing lower and upper limits of integer-based complex data types.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestLimitsComplex(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestLimitsComplex()
{
var builder = new DataBuilder();
var int8Complex = builder.NewVariable(
@ -101,26 +104,26 @@ namespace MatFileHandler.Tests
int16Complex, int32Complex, int64Complex, int8Complex,
uint16Complex, uint32Complex, uint64Complex, uint8Complex,
});
MatCompareWithTestData("good", "limits_complex", actual, method, options);
MatCompareWithLevel5TestData("limits_complex", actual);
}
/// <summary>
/// Test writing a wide-Unicode symbol.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestUnicodeWide(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[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, method, options);
MatCompareWithLevel5TestData("unicode-wide", actual);
}
/// <summary>
/// Test writing a sparse array.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestSparseArray(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestSparseArray()
{
var builder = new DataBuilder();
var sparseArray = builder.NewSparseArray<double>(4, 5);
@ -130,14 +133,14 @@ namespace MatFileHandler.Tests
sparseArray[2, 3] = 4;
var sparse = builder.NewVariable("sparse_", sparseArray);
var actual = builder.NewFile(new[] { sparse });
MatCompareWithTestData("good", "sparse", actual, method, options);
MatCompareWithLevel5TestData("sparse", actual);
}
/// <summary>
/// Test writing a structure array.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestStructure(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestStructure()
{
var builder = new DataBuilder();
var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
@ -158,27 +161,27 @@ namespace MatFileHandler.Tests
structure["y", 1, 2] = builder.NewEmpty();
var struct_ = builder.NewVariable("struct_", structure);
var actual = builder.NewFile(new[] { struct_ });
MatCompareWithTestData("good", "struct", actual, method, options);
MatCompareWithLevel5TestData("struct", actual);
}
/// <summary>
/// Test writing a logical array.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestLogical(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[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, method, options);
MatCompareWithLevel5TestData("logical", actual);
}
/// <summary>
/// Test writing a sparse logical array.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestSparseLogical(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestSparseLogical()
{
var builder = new DataBuilder();
var array = builder.NewSparseArray<bool>(2, 3);
@ -188,14 +191,14 @@ namespace MatFileHandler.Tests
array[1, 2] = true;
var sparseLogical = builder.NewVariable("sparse_logical", array);
var actual = builder.NewFile(new[] { sparseLogical });
MatCompareWithTestData("good", "sparse_logical", actual, method, options);
MatCompareWithLevel5TestData("sparse_logical", actual);
}
/// <summary>
/// Test writing a sparse complex array.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestSparseComplex(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[Test]
public void TestSparseComplex()
{
var builder = new DataBuilder();
var array = builder.NewSparseArray<Complex>(2, 2);
@ -204,60 +207,38 @@ namespace MatFileHandler.Tests
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, method, options);
MatCompareWithLevel5TestData("sparse_complex", actual);
}
/// <summary>
/// Test writing a global variable.
/// </summary>
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestGlobal(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
[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, method, options);
MatCompareWithLevel5TestData("global", actual);
}
/// <summary>
/// Various writing methods for testing writing of .mat files.
/// </summary>
public static TheoryData<MatFileWritingMethod, MatFileWriterOptionsForTests> MatFileWritingTestData
{
get
{
var always = new MatFileWriterOptions { UseCompression = CompressionUsage.Always};
var never = new MatFileWriterOptions { UseCompression = CompressionUsage.Never };
var data = new TheoryData<MatFileWritingMethod, MatFileWriterOptionsForTests>
{
{ MatFileWritingMethod.NormalStream, MatFileWriterOptionsForTests.None },
{ MatFileWritingMethod.NormalStream, MatFileWriterOptionsForTests.Always },
{ MatFileWritingMethod.NormalStream, MatFileWriterOptionsForTests.Never },
{ MatFileWritingMethod.UnseekableStream, MatFileWriterOptionsForTests.None },
{ MatFileWritingMethod.UnseekableStream, MatFileWriterOptionsForTests.Always },
{ MatFileWritingMethod.UnseekableStream, MatFileWriterOptionsForTests.Never },
{ MatFileWritingMethod.UnalignedStream, MatFileWriterOptionsForTests.None },
{ MatFileWritingMethod.UnalignedStream, MatFileWriterOptionsForTests.Always },
{ MatFileWritingMethod.UnalignedStream, MatFileWriterOptionsForTests.Never },
};
return data;
}
}
private static AbstractTestDataFactory<IMatFile> GetMatTestData(string factoryName) =>
new MatTestDataFactory(Path.Combine(TestDirectory, factoryName));
private static void CompareSparseArrays<T>(ISparseArrayOf<T> expected, ISparseArrayOf<T> actual)
private void CompareSparseArrays<T>(ISparseArrayOf<T> expected, ISparseArrayOf<T> actual)
where T : struct
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.Equal(expected.Data, actual.Data);
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.Data, Is.EquivalentTo(actual.Data));
}
private static void CompareStructureArrays(IStructureArray expected, IStructureArray actual)
private void CompareStructureArrays(IStructureArray expected, IStructureArray actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.Equal(expected.FieldNames, actual.FieldNames);
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.Count; i++)
@ -267,31 +248,31 @@ namespace MatFileHandler.Tests
}
}
private static void CompareCellArrays(ICellArray expected, ICellArray actual)
private void CompareCellArrays(ICellArray expected, ICellArray actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
for (var i = 0; i < expected.Count; i++)
{
CompareMatArrays(expected[i], actual[i]);
}
}
private static void CompareNumericalArrays<T>(IArrayOf<T> expected, IArrayOf<T> actual)
private void CompareNumericalArrays<T>(IArrayOf<T> expected, IArrayOf<T> actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.Equal(expected.Data, actual.Data);
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.Data, Is.EqualTo(actual.Data));
}
private static void CompareCharArrays(ICharArray expected, ICharArray actual)
private void CompareCharArrays(ICharArray expected, ICharArray actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.Equal(expected.String, actual.String);
Assert.That(actual, Is.Not.Null);
Assert.That(expected.Dimensions, Is.EqualTo(actual.Dimensions));
Assert.That(expected.String, Is.EqualTo(actual.String));
}
private static void CompareMatArrays(IArray expected, IArray actual)
private void CompareMatArrays(IArray expected, IArray actual)
{
switch (expected)
{
@ -376,47 +357,70 @@ namespace MatFileHandler.Tests
}
if (expected.IsEmpty)
{
Assert.True(actual.IsEmpty);
Assert.That(actual.IsEmpty, Is.True);
return;
}
throw new NotSupportedException();
}
private static void CompareMatFiles(IMatFile expected, IMatFile actual)
private void CompareMatFiles(IMatFile expected, IMatFile actual)
{
Assert.Equal(expected.Variables.Length, actual.Variables.Length);
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.Equal(expectedVariable.Name, actualVariable.Name);
Assert.Equal(expectedVariable.IsGlobal, actualVariable.IsGlobal);
Assert.That(expectedVariable.Name, Is.EqualTo(actualVariable.Name));
Assert.That(expectedVariable.IsGlobal, Is.EqualTo(actualVariable.IsGlobal));
CompareMatArrays(expectedVariable.Value, actualVariable.Value);
}
}
private static void MatCompareWithTestData(
string factoryName,
string testName,
private void CompareTestDataWithWritingOptions(
IMatFile expected,
IMatFile actual,
MatFileWritingMethod method,
MatFileWriterOptionsForTests options)
MatFileWriterOptions? maybeOptions)
{
var fullFileName = Path.Combine("test-data", "good", $"{testName}.mat");
var expected = MatFileReadingMethods.ReadMatFile(
MatFileReadingMethod.NormalStream,
fullFileName);
var buffer = MatFileWritingMethods.WriteMatFile(method, options, actual);
using var stream = new MemoryStream(buffer);
var reader = new MatFileReader(stream);
var actualRead = reader.Read();
CompareMatFiles(expected, actualRead);
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 reader = new MatFileReader(stream);
var actualRead = reader.Read();
CompareMatFiles(expected, actualRead);
}
}
private static ComplexOf<T>[] CreateComplexLimits<T>(T[] limits)
where T : struct
private void MatCompareWithLevel5TestData(string testName, IMatFile actual)
{
MatCompareWithTestData("level5", testName, actual);
}
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)
where T : struct
{
return new[] { new ComplexOf<T>(limits[0], limits[1]), new ComplexOf<T>(limits[1], limits[0]) };
}
}
}
}

View File

@ -1,27 +0,0 @@
namespace MatFileHandler.Tests;
/// <summary>
/// Method of writing .mat files for testing.
/// </summary>
public enum MatFileWritingMethod
{
/// <summary>
/// Undefined.
/// </summary>
Undefined = 0,
/// <summary>
/// Normal stream (like memory or file stream).
/// </summary>
NormalStream,
/// <summary>
/// A stream that cannot be seeked (like a deflate stream).
/// </summary>
UnseekableStream,
/// <summary>
/// Unaligned stream (what happens if the data don't start at the beginning?).
/// </summary>
UnalignedStream,
}

View File

@ -1,58 +0,0 @@
using System;
using System.IO;
namespace MatFileHandler.Tests;
internal static class MatFileWritingMethods
{
public static byte[] WriteMatFile(MatFileWritingMethod method, MatFileWriterOptionsForTests options, IMatFile matFile)
{
switch (method)
{
case MatFileWritingMethod.NormalStream:
{
using var memoryStream = new MemoryStream();
var matFileWriter = CreateWriter(options, memoryStream);
matFileWriter.Write(matFile);
return memoryStream.ToArray();
}
case MatFileWritingMethod.UnseekableStream:
{
using var memoryStream = new MemoryStream();
using var unseekableStream = new UnseekableWriteStream(memoryStream);
var matFileWriter = CreateWriter(options, unseekableStream);
matFileWriter.Write(matFile);
return memoryStream.ToArray();
}
case MatFileWritingMethod.UnalignedStream:
{
using var memoryStream = new MemoryStream();
memoryStream.Seek(3, SeekOrigin.Begin);
var matFileWriter = CreateWriter(options, 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;
}
default:
throw new NotImplementedException();
}
}
private static MatFileWriter CreateWriter(MatFileWriterOptionsForTests options, Stream stream)
{
return options switch
{
MatFileWriterOptionsForTests.None => new MatFileWriter(stream),
MatFileWriterOptionsForTests.Always => new MatFileWriter(
stream,
new MatFileWriterOptions { UseCompression = CompressionUsage.Always }),
MatFileWriterOptionsForTests.Never => new MatFileWriter(
stream,
new MatFileWriterOptions { UseCompression = CompressionUsage.Never }),
_ => throw new NotImplementedException(),
};
}
}

View File

@ -0,0 +1,35 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// Factory providing the parsed contents of .mat files.
/// </summary>
public class MatTestDataFactory : AbstractTestDataFactory<IMatFile>
{
/// <summary>
/// Initializes a new instance of the <see cref="MatTestDataFactory"/> class.
/// </summary>
/// <param name="testDirectory">Directory containing test files.</param>
public MatTestDataFactory(string testDirectory)
: base(
testDirectory,
new ExtensionTestFilenameConvention("mat"))
{
}
/// <summary>
/// Read and parse data from a .mat file.
/// </summary>
/// <param name="stream">Input stream.</param>
/// <returns>Parsed contents of the file.</returns>
protected override IMatFile ReadDataFromStream(Stream stream)
{
var matFileReader = new MatFileReader(stream);
var matFile = matFileReader.Read();
return matFile;
}
}
}

View File

@ -1,83 +0,0 @@
using System;
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// A stream which wraps another stream and only reads one byte at a time,
/// while forbidding seeking in it.
/// </summary>
internal sealed class PartialUnseekableReadStream : Stream
{
private readonly Stream _baseStream;
/// <summary>
/// Initializes a new instance of the <see cref="PartialUnseekableReadStream"/> class.
/// </summary>
/// <param name="baseStream">The stream to wrap.</param>
public PartialUnseekableReadStream(Stream baseStream)
{
_baseStream = baseStream;
}
/// <inheritdoc/>
public override bool CanRead => _baseStream.CanRead;
/// <inheritdoc/>
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => false;
/// <inheritdoc/>
public override long Length => _baseStream.Length;
/// <inheritdoc/>
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
/// <inheritdoc/>
public override void Flush()
{
_baseStream.Flush();
}
/// <inheritdoc/>
public override int Read(byte[] buffer, int offset, int count)
{
return _baseStream.Read(buffer, offset, Math.Min(1, count));
}
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_baseStream.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -1,68 +0,0 @@
using System;
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// A stream which wraps another stream and forbids seeking in it.
/// </summary>
internal sealed 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);
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -136,20 +138,4 @@ namespace MatFileHandler
Variable = variable;
}
}
/// <summary>
/// Sparse array properties.
/// </summary>
internal struct SparseArrayFlags
{
/// <summary>
/// Usual array properties.
/// </summary>
public ArrayFlags ArrayFlags;
/// <summary>
/// Maximal number of non-zero elements.
/// </summary>
public uint NzMax;
}
}
}

View File

@ -1,92 +0,0 @@
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;
private uint s2;
/// <summary>
/// Initializes a new instance of the <see cref="ChecksumCalculatingStream"/> class.
/// </summary>
/// <param name="stream">Wrapped stream.</param>
public ChecksumCalculatingStream(Stream stream)
{
_stream = stream;
s1 = 1;
s2 = 0;
}
/// <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;
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -6,7 +8,7 @@ namespace MatFileHandler
/// A structure representing a complex number where real and imaginary parts are of type T.
/// </summary>
/// <typeparam name="T">Type of real and imaginary parts.</typeparam>
public readonly struct ComplexOf<T> : IEquatable<ComplexOf<T>>
public struct ComplexOf<T> : IEquatable<ComplexOf<T>>
where T : struct
{
/// <summary>
@ -69,11 +71,10 @@ namespace MatFileHandler
/// <returns>True iff another object is a complex number equal to this.</returns>
public override bool Equals(object obj)
{
if (obj is null)
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is ComplexOf<T> other && Equals(other);
}
@ -89,4 +90,4 @@ namespace MatFileHandler
}
}
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>

View File

@ -1,9 +1,10 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
#pragma warning disable CA1822
namespace MatFileHandler
{
/// <summary>
@ -27,9 +28,7 @@ namespace MatFileHandler
where T : struct
{
return new MatNumericalArrayOf<T>(
GetStandardFlags<T>(),
dimensions,
string.Empty,
new T[dimensions.NumberOfElements()]);
}
@ -51,9 +50,9 @@ namespace MatFileHandler
{
if (data.Length != dimensions.NumberOfElements())
{
throw new ArgumentException("Data size does not match the specified dimensions", nameof(data));
throw new ArgumentException("Data size does not match the specified dimensions", "data");
}
return new MatNumericalArrayOf<T>(GetStandardFlags<T>(), dimensions, string.Empty, data);
return new MatNumericalArrayOf<T>(dimensions, data);
}
/// <summary>
@ -65,7 +64,7 @@ namespace MatFileHandler
{
var flags = ConstructArrayFlags(ArrayType.MxCell);
var elements = Enumerable.Repeat(MatArray.Empty() as IArray, dimensions.NumberOfElements()).ToList();
return new MatCellArray(flags, dimensions, string.Empty, elements);
return new MatCellArray(dimensions, elements);
}
/// <summary>
@ -83,7 +82,7 @@ namespace MatFileHandler
{
dictionary[field] = elements.ToList();
}
return new MatStructureArray(flags, dimensions, string.Empty, dictionary);
return new MatStructureArray(dimensions, dictionary);
}
/// <summary>
@ -106,7 +105,7 @@ namespace MatFileHandler
{
var flags = ConstructArrayFlags(ArrayType.MxChar);
var ushortArray = contents.ToCharArray().Select(c => (ushort)c).ToArray();
return new MatCharArrayOf<ushort>(flags, dimensions, string.Empty, ushortArray, contents);
return new MatCharArrayOf<ushort>(dimensions, ushortArray, contents);
}
/// <summary>
@ -128,9 +127,7 @@ namespace MatFileHandler
where T : struct
{
return new MatSparseArrayOf<T>(
GetStandardSparseArrayFlags<T>(),
dimensions,
string.Empty,
new Dictionary<(int, int), T>());
}
@ -156,7 +153,7 @@ namespace MatFileHandler
return new MatFile(variables);
}
private static ArrayFlags ConstructArrayFlags(ArrayType class_, bool isComplex = false, bool isLogical = false)
private ArrayFlags ConstructArrayFlags(ArrayType class_, bool isComplex = false, bool isLogical = false)
{
return new ArrayFlags
{
@ -166,7 +163,7 @@ namespace MatFileHandler
};
}
private static ArrayFlags GetStandardFlags<T>()
private ArrayFlags GetStandardFlags<T>()
{
if (typeof(T) == typeof(sbyte))
{
@ -254,15 +251,5 @@ namespace MatFileHandler
}
return ConstructArrayFlags(ArrayType.MxObject);
}
private SparseArrayFlags GetStandardSparseArrayFlags<T>()
{
var arrayFlags = GetStandardFlags<T>();
return new SparseArrayFlags
{
ArrayFlags = arrayFlags,
NzMax = 0,
};
}
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -6,4 +8,4 @@ namespace MatFileHandler
internal class DataElement
{
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
@ -14,35 +16,31 @@ namespace MatFileHandler
/// <summary>
/// Construct a complex sparse array.
/// </summary>
/// <param name="flags">Array flags.</param>
/// <param name="dimensions">Array dimensions.</param>
/// <param name="name">Array name.</param>
/// <param name="rowIndex">Row indices.</param>
/// <param name="columnIndex">Denotes index ranges for each column.</param>
/// <param name="data">Real parts of the values.</param>
/// <param name="imaginaryData">Imaginary parts of the values.</param>
/// <returns>A constructed array.</returns>
public static MatArray ConvertToMatSparseArrayOfComplex(
SparseArrayFlags flags,
int[] dimensions,
string name,
int[] rowIndex,
int[] columnIndex,
DataElement data,
DataElement imaginaryData)
{
var realParts = DataExtraction.GetDataAsDouble(data);
var imaginaryParts = DataExtraction.GetDataAsDouble(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(
DataExtraction.ConvertMatlabSparseToDictionary(
rowIndex,
columnIndex,
j => new Complex(realParts[j], imaginaryParts[j]));
return new MatSparseArrayOf<Complex>(flags, dimensions, name, dataDictionary);
return new MatSparseArrayOf<Complex>(dimensions, dataDictionary);
}
/// <summary>
@ -51,15 +49,13 @@ namespace MatFileHandler
/// <typeparam name="T">Element type (Double or Boolean).</typeparam>
/// <param name="flags">Array flags.</param>
/// <param name="dimensions">Array dimensions.</param>
/// <param name="name">Array name.</param>
/// <param name="rowIndex">Row indices.</param>
/// <param name="columnIndex">Denotes index ranges for each column.</param>
/// <param name="data">The values.</param>
/// <returns>A constructed array.</returns>
public static MatArray ConvertToMatSparseArrayOf<T>(
SparseArrayFlags flags,
ArrayFlags flags,
int[] dimensions,
string name,
int[] rowIndex,
int[] columnIndex,
DataElement data)
@ -69,24 +65,19 @@ namespace MatFileHandler
{
throw new NotSupportedException("Only 2-dimensional sparse arrays are supported");
}
if (data is null)
if (data == null)
{
throw new ArgumentException("Null data found.", nameof(data));
throw new ArgumentException("Null data found.", "data");
}
var maybeElements =
ConvertDataToSparseProperType<T>(data, flags.ArrayFlags.Variable.HasFlag(Variable.IsLogical));
if (maybeElements is not { } elements)
var elements =
ConvertDataToSparseProperType<T>(data, flags.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<T>(flags, dimensions, name, dataDictionary);
DataExtraction.ConvertMatlabSparseToDictionary(rowIndex, columnIndex, j => elements[j]);
return new MatSparseArrayOf<T>(dimensions, dataDictionary);
}
/// <summary>
@ -95,7 +86,6 @@ namespace MatFileHandler
/// <typeparam name="T">Element type.</typeparam>
/// <param name="flags">Array flags.</param>
/// <param name="dimensions">Array dimensions.</param>
/// <param name="name">Array name.</param>
/// <param name="realData">Real parts of the values.</param>
/// <param name="imaginaryData">Imaginary parts of the values.</param>
/// <returns>A constructed array.</returns>
@ -107,25 +97,27 @@ namespace MatFileHandler
public static MatArray ConvertToMatNumericalArrayOf<T>(
ArrayFlags flags,
int[] dimensions,
string name,
DataElement realData,
DataElement? imaginaryData)
DataElement imaginaryData)
where T : struct
{
if (flags.Variable.HasFlag(Variable.IsLogical))
{
var data = DataExtraction.GetDataAsUInt8(realData).Select(x => x != 0).ToArray();
return new MatNumericalArrayOf<bool>(flags, dimensions, name, data);
var data = DataExtraction.GetDataAsUInt8(realData).ToArrayLazily().Select(x => x != 0).ToArray();
return new MatNumericalArrayOf<bool>(dimensions, data);
}
switch (flags.Class)
{
case ArrayType.MxChar:
return realData switch
switch (realData)
{
MiNum<byte> dataByte => ConvertToMatCharArray(flags, dimensions, name, dataByte),
MiNum<ushort> dataUshort => ConvertToMatCharArray(flags, dimensions, name, dataUshort),
_ => throw new NotSupportedException("Only utf8, utf16 or ushort char arrays are supported."),
};
case MiNum<byte> dataByte:
return ConvertToMatCharArray(dimensions, dataByte);
case MiNum<ushort> dataUshort:
return ConvertToMatCharArray(dimensions, dataUshort);
default:
throw new NotSupportedException("Only utf8, utf16 or ushort char arrays are supported.");
}
case ArrayType.MxDouble:
case ArrayType.MxSingle:
case ArrayType.MxInt8:
@ -139,11 +131,6 @@ namespace MatFileHandler
var dataArray = ConvertDataToProperType<T>(realData, flags.Class);
if (flags.Variable.HasFlag(Variable.IsComplex))
{
if (imaginaryData is null)
{
throw new HandlerException("Imaginary part of a complex variable not found.");
}
var dataArray2 = ConvertDataToProperType<T>(imaginaryData, flags.Class);
if (flags.Class == ArrayType.MxDouble)
{
@ -151,95 +138,78 @@ namespace MatFileHandler
(dataArray as double[])
.Zip(dataArray2 as double[], (x, y) => new Complex(x, y))
.ToArray();
return new MatNumericalArrayOf<Complex>(flags, dimensions, name, complexArray);
return new MatNumericalArrayOf<Complex>(dimensions, complexArray);
}
var complexDataArray = dataArray.Zip(dataArray2, (x, y) => new ComplexOf<T>(x, y)).ToArray();
return new MatNumericalArrayOf<ComplexOf<T>>(flags, dimensions, name, complexDataArray);
return new MatNumericalArrayOf<ComplexOf<T>>(dimensions, complexDataArray);
}
return new MatNumericalArrayOf<T>(flags, dimensions, name, dataArray);
return new MatNumericalArrayOf<T>(dimensions, dataArray);
default:
throw new NotSupportedException();
}
}
private static MatCharArrayOf<byte> ConvertToMatCharArray(
ArrayFlags flags,
int[] dimensions,
string name,
MiNum<byte> dataElement)
{
var data = dataElement.Data;
return new MatCharArrayOf<byte>(flags, dimensions, name, data, Encoding.UTF8.GetString(data));
var data = dataElement?.Data;
return new MatCharArrayOf<byte>(dimensions, data, Encoding.UTF8.GetString(data));
}
private static T[] ConvertDataToProperType<T>(DataElement data, ArrayType arrayType)
{
return TryConvertDataToProperType<T>(data, arrayType)
?? throw new HandlerException($"Unexpected data type.");
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<T>(DataElement data, bool isLogical)
private static T[] ConvertDataToSparseProperType<T>(DataElement data, bool isLogical)
{
if (isLogical)
{
return DataExtraction.GetDataAsUInt8(data).Select(x => x != 0).ToArray() as T[];
return DataExtraction.GetDataAsUInt8(data).ToArrayLazily().Select(x => x != 0).ToArray() as T[];
}
return data switch
switch (data)
{
MiNum<double> => DataExtraction.GetDataAsDouble(data) as T[],
_ => throw new NotSupportedException(),
};
case MiNum<double> _:
return DataExtraction.GetDataAsDouble(data).ToArrayLazily() as T[];
default:
throw new NotSupportedException();
}
}
private static MatCharArrayOf<ushort> ConvertToMatCharArray(
ArrayFlags flags,
int[] dimensions,
string name,
MiNum<ushort> dataElement)
{
var data = dataElement.Data;
var data = dataElement?.Data;
return new MatCharArrayOf<ushort>(
flags,
dimensions,
name,
data,
new string(data.Select(x => (char)x).ToArray()));
}
private static Dictionary<(int row, int column), T> ConvertMatlabSparseToDictionary<T>(
int[] rowIndex,
int[] columnIndex,
Func<int, T> 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;
}
private static T[]? TryConvertDataToProperType<T>(DataElement data, ArrayType arrayType)
{
return arrayType switch
{
ArrayType.MxDouble => DataExtraction.GetDataAsDouble(data) as T[],
ArrayType.MxSingle => DataExtraction.GetDataAsSingle(data) as T[],
ArrayType.MxInt8 => DataExtraction.GetDataAsInt8(data) as T[],
ArrayType.MxUInt8 => DataExtraction.GetDataAsUInt8(data) as T[],
ArrayType.MxInt16 => DataExtraction.GetDataAsInt16(data) as T[],
ArrayType.MxUInt16 => DataExtraction.GetDataAsUInt16(data) as T[],
ArrayType.MxInt32 => DataExtraction.GetDataAsInt32(data) as T[],
ArrayType.MxUInt32 => DataExtraction.GetDataAsUInt32(data) as T[],
ArrayType.MxInt64 => DataExtraction.GetDataAsInt64(data) as T[],
ArrayType.MxUInt64 => DataExtraction.GetDataAsUInt64(data) as T[],
_ => throw new NotSupportedException()
};
}
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
@ -28,33 +30,10 @@ namespace MatFileHandler
/// </summary>
/// <param name="reader">Input reader.</param>
/// <returns>Data element.</returns>
public DataElement? Read(BinaryReader reader)
public DataElementWithMetadata Read(BinaryReader reader)
{
var maybeTagPair = ReadTag(reader);
if (maybeTagPair is not { } tagPair)
{
return null;
}
var (dataReader, tag) = tagPair;
var result = tag.Type switch
{
DataType.MiInt8 => ReadNum<sbyte>(tag, dataReader),
DataType.MiUInt8 or DataType.MiUtf8 => ReadNum<byte>(tag, dataReader),
DataType.MiInt16 => ReadNum<short>(tag, dataReader),
DataType.MiUInt16 or DataType.MiUtf16 => ReadNum<ushort>(tag, dataReader),
DataType.MiInt32 => ReadNum<int>(tag, dataReader),
DataType.MiUInt32 => ReadNum<uint>(tag, dataReader),
DataType.MiSingle => ReadNum<float>(tag, dataReader),
DataType.MiDouble => ReadNum<double>(tag, dataReader),
DataType.MiInt64 => ReadNum<long>(tag, dataReader),
DataType.MiUInt64 => ReadNum<ulong>(tag, dataReader),
DataType.MiMatrix => ReadMatrix(tag, dataReader),
DataType.MiCompressed => ReadCompressed(tag, dataReader),
_ => throw new NotSupportedException("Unknown element."),
};
var (dataReader, tag) = ReadTag(reader);
DataElementWithMetadata result = ReadElementWithFlags(tag, dataReader);
if (tag.Type != DataType.MiCompressed)
{
var position = reader.BaseStream.Position;
@ -99,7 +78,7 @@ namespace MatFileHandler
private static ArrayFlags ReadArrayFlags(DataElement element)
{
var flagData = (element as MiNum<uint>)?.Data ??
throw new HandlerException("Unexpected type in array flags.");
throw new HandlerException("Unexpected type in array flags.");
var class_ = (ArrayType)(flagData[0] & 0xff);
var variableFlags = (flagData[0] >> 8) & 0x0e;
return new ArrayFlags
@ -158,44 +137,18 @@ namespace MatFileHandler
return new MiNum<T>(result);
}
private static SparseArrayFlags ReadSparseArrayFlags(DataElement element)
private static (ArrayFlags flags, uint nzMax) ReadSparseArrayFlags(DataElement element)
{
var arrayFlags = ReadArrayFlags(element);
var flagData = (element as MiNum<uint>)?.Data ??
throw new HandlerException("Unexpected type in sparse array flags.");
var nzMax = flagData[1];
return new SparseArrayFlags
{
ArrayFlags = arrayFlags,
NzMax = nzMax,
};
return (arrayFlags, nzMax);
}
private static int? TryReadInt32(BinaryReader reader)
private static (BinaryReader, Tag) ReadTag(BinaryReader reader)
{
var buffer = new byte[4];
var position = 0;
while (position < 4)
{
var actually = reader.BaseStream.Read(buffer, position, 4 - position);
if (actually == 0)
{
return null;
}
position += actually;
}
return BitConverter.ToInt32(buffer, 0);
}
private static (BinaryReader reader, Tag tag)? ReadTag(BinaryReader reader)
{
var maybeType = TryReadInt32(reader);
if (maybeType is not int type)
{
return null;
}
var type = reader.ReadInt32();
var typeHi = type >> 16;
if (typeHi == 0)
{
@ -205,121 +158,134 @@ namespace MatFileHandler
else
{
var length = typeHi;
type &= 0xffff;
type = type & 0xffff;
var smallReader = new BinaryReader(new MemoryStream(reader.ReadBytes(4)));
return (smallReader, new Tag((DataType)type, length));
}
}
private MatCellArray ContinueReadingCellArray(
private DataElement ContinueReadingCellArray(
BinaryReader reader,
ArrayFlags flags,
int[] dimensions,
string name)
int[] dimensions)
{
var numberOfElements = dimensions.NumberOfElements();
var elements = new List<IArray>();
for (var i = 0; i < numberOfElements; i++)
{
var element = Read(reader) as IArray
?? throw new HandlerException("Unable to read cell array.");
var element = Read(reader).Element as IArray;
elements.Add(element);
}
return new MatCellArray(flags, dimensions, name, elements);
return new MatCellArray(dimensions, elements);
}
private DataElement ContinueReadingOpaque(BinaryReader reader)
private DataElementWithMetadata ContinueReadingOpaque(BinaryReader reader)
{
var nameElement = Read(reader) as MiNum<sbyte> ??
var nameElement = Read(reader).Element as MiNum<sbyte> ??
throw new HandlerException("Unexpected type in object name.");
var name = ReadName(nameElement);
var anotherElement = Read(reader) as MiNum<sbyte> ??
var anotherElement = Read(reader).Element as MiNum<sbyte> ??
throw new HandlerException("Unexpected type in object type description.");
var typeDescription = ReadName(anotherElement);
var classNameElement = Read(reader) as MiNum<sbyte> ??
var classNameElement = Read(reader).Element as MiNum<sbyte> ??
throw new HandlerException("Unexpected type in class name.");
var className = ReadName(classNameElement);
var dataElement = Read(reader) ?? throw new HandlerException("Missing opaque data element.");
var dataElement = Read(reader).Element;
var data = ReadData(dataElement);
if (data is MatNumericalArrayOf<uint> linkElement)
{
var (dimensions, indexToObjectId, classIndex) = ParseOpaqueData(linkElement.Data);
return new OpaqueLink(
name,
typeDescription,
className,
dimensions,
data,
indexToObjectId,
classIndex,
subsystemData);
return new DataElementWithMetadata(
new OpaqueLink(
typeDescription,
className,
dimensions,
data,
indexToObjectId,
classIndex,
subsystemData),
default,
name);
}
else
{
return new Opaque(name, typeDescription, className, Array.Empty<int>(), data, subsystemData);
return new DataElementWithMetadata(
new Opaque(
typeDescription,
className,
new int[] { },
data),
default,
name);
}
}
private MatArray ContinueReadingSparseArray(
private DataElementWithMetadata ContinueReadingSparseArray(
BinaryReader reader,
DataElement firstElement,
int[] dimensions,
string name)
{
var sparseArrayFlags = ReadSparseArrayFlags(firstElement);
var rowIndex = Read(reader) as MiNum<int> ??
var (arrayFlags, nzMax) = ReadSparseArrayFlags(firstElement);
var rowIndex = Read(reader).Element as MiNum<int> ??
throw new HandlerException("Unexpected type in row indices of a sparse array.");
var columnIndex = Read(reader) as MiNum<int> ??
var columnIndex = Read(reader).Element as MiNum<int> ??
throw new HandlerException("Unexpected type in column indices of a sparse array.");
var data = Read(reader) ?? throw new HandlerException("Missing sparse array data.");
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
var data = Read(reader).Element;
if (arrayFlags.Variable.HasFlag(Variable.IsLogical))
{
return DataElementConverter.ConvertToMatSparseArrayOf<bool>(
sparseArrayFlags,
dimensions,
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatSparseArrayOf<bool>(
arrayFlags,
dimensions,
rowIndex.Data,
columnIndex.Data,
data),
arrayFlags,
name,
rowIndex.Data,
columnIndex.Data,
data);
nzMax);
}
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
if (arrayFlags.Variable.HasFlag(Variable.IsComplex))
{
var imaginaryData = Read(reader) ?? throw new HandlerException("Missing imaginary part of sparse array data.");
return DataElementConverter.ConvertToMatSparseArrayOfComplex(
sparseArrayFlags,
dimensions,
var imaginaryData = Read(reader).Element;
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatSparseArrayOfComplex(
dimensions,
rowIndex.Data,
columnIndex.Data,
data,
imaginaryData),
arrayFlags,
name,
rowIndex.Data,
columnIndex.Data,
data,
imaginaryData);
nzMax);
}
return data switch
switch (data)
{
MiNum<double> => DataElementConverter.ConvertToMatSparseArrayOf<double>(
sparseArrayFlags,
dimensions,
name,
rowIndex.Data,
columnIndex.Data,
data),
_ => throw new NotSupportedException("Only double and logical sparse arrays are supported."),
};
case MiNum<double> _:
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatSparseArrayOf<double>(
arrayFlags,
dimensions,
rowIndex.Data,
columnIndex.Data,
data),
arrayFlags,
name,
nzMax);
default:
throw new NotSupportedException("Only double and logical sparse arrays are supported.");
}
}
private MatStructureArray ContinueReadingStructure(
private DataElement ContinueReadingStructure(
BinaryReader reader,
ArrayFlags flags,
int[] dimensions,
string name,
int fieldNameLength)
{
var element = Read(reader) as MiNum<sbyte>
?? throw new HandlerException("Unable to parse structure field names.");
var fieldNames = ReadFieldNames(element, fieldNameLength);
var element = Read(reader).Element;
var fieldNames = ReadFieldNames(element as MiNum<sbyte>, fieldNameLength);
var fields = new Dictionary<string, List<IArray>>();
foreach (var fieldName in fieldNames)
{
@ -331,66 +297,111 @@ namespace MatFileHandler
{
foreach (var fieldName in fieldNames)
{
var field = Read(reader) as IArray
?? throw new HandlerException("Unable to parse field name.");
var field = Read(reader).Element as IArray;
fields[fieldName].Add(field);
}
}
return new MatStructureArray(flags, dimensions, name, fields);
return new MatStructureArray(dimensions, fields);
}
private DataElement ReadCompressed(Tag tag, BinaryReader reader)
private DataElementWithMetadata Read(Stream stream)
{
using (var reader = new BinaryReader(stream))
{
return Read(reader);
}
}
private DataElementWithMetadata ReadCompressed(Tag tag, BinaryReader reader)
{
reader.ReadBytes(2);
DataElement element;
using (var substream = new Substream(reader.BaseStream, tag.Length - 6))
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 deflateStream = new DeflateStream(substream, CompressionMode.Decompress))
using (var bufferedStream = new BufferedStream(deflateStream))
using (var positionTrackingStream = new PositionTrackingStream(bufferedStream))
using (var innerReader = new BinaryReader(positionTrackingStream))
using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true))
{
element = Read(innerReader) ?? throw new HandlerException("Missing compressed data.");
}
if (substream.Position != substream.Length)
{
// In the pathological case that the deflate stream did not read the full
// length, then read out the rest manually (normally 1 byte).
reader.ReadBytes((int)(substream.Length - substream.Position));
stream.CopyTo(resultStream);
}
}
reader.ReadBytes(4);
return element;
resultStream.Position = 0;
return Read(resultStream);
}
private DataElement ReadMatrix(Tag tag, BinaryReader reader)
private DataElementWithMetadata ReadElementWithFlags(Tag tag, BinaryReader reader)
{
switch (tag.Type)
{
case DataType.MiMatrix:
return ReadMatrix(tag, reader);
case DataType.MiCompressed:
return ReadCompressed(tag, reader);
default:
var element = ReadElementWithoutFlags(tag, reader);
return new DataElementWithMetadata(element);
}
}
private DataElement ReadElementWithoutFlags(Tag tag, BinaryReader reader)
{
switch (tag.Type)
{
case DataType.MiInt8:
return ReadNum<sbyte>(tag, reader);
case DataType.MiUInt8:
case DataType.MiUtf8:
return ReadNum<byte>(tag, reader);
case DataType.MiInt16:
return ReadNum<short>(tag, reader);
case DataType.MiUInt16:
case DataType.MiUtf16:
return ReadNum<ushort>(tag, reader);
case DataType.MiInt32:
return ReadNum<int>(tag, reader);
case DataType.MiUInt32:
return ReadNum<uint>(tag, reader);
case DataType.MiSingle:
return ReadNum<float>(tag, reader);
case DataType.MiDouble:
return ReadNum<double>(tag, reader);
case DataType.MiInt64:
return ReadNum<long>(tag, reader);
case DataType.MiUInt64:
return ReadNum<ulong>(tag, reader);
default:
throw new NotSupportedException("Unknown element.");
}
}
private DataElementWithMetadata ReadMatrix(Tag tag, BinaryReader reader)
{
if (tag.Length == 0)
{
return MatArray.Empty();
return new DataElementWithMetadata(MatArray.Empty());
}
var element1 = Read(reader) ?? throw new HandlerException("Missing matrix data.");
var element1 = Read(reader).Element;
var flags = ReadArrayFlags(element1);
if (flags.Class == ArrayType.MxOpaque)
{
return ContinueReadingOpaque(reader);
}
var element2 = Read(reader) as MiNum<int> ??
throw new HandlerException("Unexpected type in array dimensions data.");
var element2 =
Read(reader).Element as MiNum<int>
?? throw new HandlerException("Unexpected type in array dimensions data.");
var dimensions = ReadDimensionsArray(element2);
var element3 = Read(reader) as MiNum<sbyte> ?? throw new HandlerException("Unexpected type in array name.");
var element3 =
Read(reader).Element as MiNum<sbyte>
?? throw new HandlerException("Unexpected type in array name.");
var name = ReadName(element3);
if (flags.Class == ArrayType.MxCell)
{
return ContinueReadingCellArray(reader, flags, dimensions, name);
return new DataElementWithMetadata(ContinueReadingCellArray(reader, dimensions));
}
if (flags.Class == ArrayType.MxSparse)
@ -398,12 +409,12 @@ namespace MatFileHandler
return ContinueReadingSparseArray(reader, element1, dimensions, name);
}
var element4 = Read(reader) ?? throw new HandlerException("Missing matrix data.");
var element4 = Read(reader).Element;
var data = ReadData(element4);
DataElement? imaginaryData = null;
DataElement imaginaryData = null;
if (flags.Variable.HasFlag(Variable.IsComplex))
{
var element5 = Read(reader) ?? throw new HandlerException("Missing complex matrix data.");
var element5 = Read(reader).Element;
imaginaryData = ReadData(element5);
}
@ -412,112 +423,144 @@ namespace MatFileHandler
var fieldNameLengthElement = data as MiNum<int> ??
throw new HandlerException(
"Unexpected type in structure field name length.");
return ContinueReadingStructure(reader, flags, dimensions, name, fieldNameLengthElement.Data[0]);
return new DataElementWithMetadata(
ContinueReadingStructure(reader, dimensions, fieldNameLengthElement.Data[0]),
flags,
name);
}
switch (flags.Class)
{
case ArrayType.MxChar:
return data switch
switch (data)
{
MiNum<byte> => DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
flags,
dimensions,
name,
data,
imaginaryData),
MiNum<ushort> => DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
flags,
dimensions,
name,
data,
imaginaryData),
_ => throw new NotSupportedException(
$"This type of char array ({data.GetType()}) is not supported."),
};
case MiNum<byte> _:
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
flags,
dimensions,
data,
imaginaryData),
flags,
name);
case MiNum<ushort> _:
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
flags,
dimensions,
data,
imaginaryData),
flags,
name);
default:
throw new NotSupportedException(
$"This type of char array ({data.GetType()}) is not supported.");
}
case ArrayType.MxInt8:
return DataElementConverter.ConvertToMatNumericalArrayOf<sbyte>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<sbyte>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxUInt8:
if (flags.Variable.HasFlag(Variable.IsLogical))
{
return DataElementConverter.ConvertToMatNumericalArrayOf<bool>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<bool>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
}
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxInt16:
return DataElementConverter.ConvertToMatNumericalArrayOf<short>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<short>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxUInt16:
return DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxInt32:
return DataElementConverter.ConvertToMatNumericalArrayOf<int>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<int>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxUInt32:
return DataElementConverter.ConvertToMatNumericalArrayOf<uint>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<uint>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxInt64:
return DataElementConverter.ConvertToMatNumericalArrayOf<long>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<long>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxUInt64:
return DataElementConverter.ConvertToMatNumericalArrayOf<ulong>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<ulong>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxSingle:
return DataElementConverter.ConvertToMatNumericalArrayOf<float>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<float>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
case ArrayType.MxDouble:
return DataElementConverter.ConvertToMatNumericalArrayOf<double>(
return new DataElementWithMetadata(
DataElementConverter.ConvertToMatNumericalArrayOf<double>(
flags,
dimensions,
data,
imaginaryData),
flags,
dimensions,
name,
data,
imaginaryData);
name);
default:
throw new HandlerException("Unknown data type.");
}
}
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
/// Data element together with array flags, variable name, and sparse array's nzMax value.
/// </summary>
internal class DataElementWithMetadata
{
/// <summary>
/// Initializes a new instance of the <see cref="DataElementWithMetadata"/> class.
/// </summary>
/// <param name="element">Data element.</param>
/// <param name="flags">Array flags.</param>
/// <param name="name">Variable name.</param>
/// <param name="nzMax">nzMax (for sparse arrays).</param>
public DataElementWithMetadata(DataElement element, ArrayFlags flags, string name, uint nzMax = 0)
{
Element = element;
Flags = flags;
Name = name;
NzMax = nzMax;
}
/// <summary>
/// Initializes a new instance of the <see cref="DataElementWithMetadata"/> class.
/// </summary>
/// <param name="element">Data element.</param>
public DataElementWithMetadata(DataElement element)
{
Element = element;
}
/// <summary>
/// Gets data element.
/// </summary>
public DataElement Element { get; }
/// <summary>
/// Gets array flags.
/// </summary>
public ArrayFlags Flags { get; }
/// <summary>
/// Gets variable name.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets nzMax (for sparse arrays).
/// </summary>
public uint NzMax { get; }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -14,21 +16,41 @@ namespace MatFileHandler
/// <returns>Size in bytes.</returns>
public static int Size(this DataType type)
{
return type switch
switch (type)
{
DataType.MiInt8 or DataType.MiUInt8 => 1,
DataType.MiInt16 or DataType.MiUInt16 => 2,
DataType.MiInt32 or DataType.MiUInt32 => 4,
DataType.MiSingle => 4,
DataType.MiDouble => 8,
DataType.MiInt64 or DataType.MiUInt64 => 8,
DataType.MiMatrix => 0,
DataType.MiCompressed => 0,
DataType.MiUtf8 => 1,
DataType.MiUtf16 => 2,
DataType.MiUtf32 => 4,
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
case DataType.MiInt8:
return 1;
case DataType.MiUInt8:
return 1;
case DataType.MiInt16:
return 2;
case DataType.MiUInt16:
return 2;
case DataType.MiInt32:
return 4;
case DataType.MiUInt32:
return 4;
case DataType.MiSingle:
return 4;
case DataType.MiDouble:
return 8;
case DataType.MiInt64:
return 8;
case DataType.MiUInt64:
return 8;
case DataType.MiMatrix:
return 0;
case DataType.MiCompressed:
return 0;
case DataType.MiUtf8:
return 1;
case DataType.MiUtf16:
return 2;
case DataType.MiUtf32:
return 4;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Linq;
using System.Numerics;
@ -10,6 +12,8 @@ namespace MatFileHandler
public class DatetimeAdapter
{
private readonly double[] data;
private readonly double[] data2;
private readonly int[] dimensions;
private readonly DateTimeOffset epoch;
@ -26,29 +30,27 @@ namespace MatFileHandler
}
epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
switch (matObject["data", 0])
var dataArray = matObject["data", 0] as IArrayOf<double>;
if (dataArray is null)
{
case IArrayOf<double> dataArray:
data = dataArray.ConvertToDoubleArray()
?? throw new HandlerException("Cannot extract data for the datetime adapter.");
Dimensions = dataArray.Dimensions;
break;
case IArrayOf<Complex> dataComplex:
var complexData = dataComplex.ConvertToComplexArray()
?? throw new HandlerException("Cannot extract data for the datetime adapter.");
data = complexData.Select(c => c.Real).ToArray();
Dimensions = dataComplex.Dimensions;
break;
default:
throw new HandlerException("Datetime data not found.");
var dataComplex = matObject["data", 0] as IArrayOf<Complex>;
var complexData = dataComplex.ConvertToComplexArray();
data = complexData.Select(c => c.Real).ToArray();
data2 = complexData.Select(c => c.Imaginary).ToArray();
dimensions = dataComplex.Dimensions;
}
else
{
data = dataArray.ConvertToDoubleArray();
data2 = new double[data.Length];
dimensions = dataArray.Dimensions;
}
}
/// <summary>
/// Gets datetime array dimensions.
/// </summary>
public int[] Dimensions { get; }
public int[] Dimensions => dimensions;
/// <summary>
/// Gets values of datetime object at given position in the array converted to <see cref="DateTimeOffset"/>.
@ -60,12 +62,12 @@ namespace MatFileHandler
get
{
var milliseconds = data[Dimensions.DimFlatten(list)];
return milliseconds switch
if (milliseconds < -62_135_596_800_000.0 || milliseconds > 253_402_300_799_999.0)
{
< -62_135_596_800_000.0 or > 253_402_300_799_999.0 => null,
_ => epoch.AddMilliseconds(milliseconds),
};
return null;
}
return epoch.AddMilliseconds(milliseconds);
}
}
}
}
}

View File

@ -1,4 +1,5 @@
using System;
// Copyright 2017-2019 Alexander Luzgarev
using System.Linq;
namespace MatFileHandler
@ -35,48 +36,5 @@ namespace MatFileHandler
{
return dimensions.Aggregate(1, (x, y) => x * y);
}
/// <summary>
/// Rearrange data from a flat (column-major) array into a multi-dimensional array.
/// </summary>
/// <typeparam name="T">Array element type.</typeparam>
/// <param name="dimensions">Target array dimensions.</param>
/// <param name="data">Flat (column-major) data to rearrange.</param>
/// <returns>An array of specified dimensions containing data from the original flat array, layed out according to column-major order.</returns>
public static Array UnflattenArray<T>(this int[] dimensions, T[] data)
{
var result = Array.CreateInstance(typeof(T), dimensions);
var n = dimensions.NumberOfElements();
var indices = new int[dimensions.Length];
for (var i = 0; i < n; i++)
{
result.SetValue(data[i], indices);
IncrementMultiIndex(dimensions, indices);
}
return result;
}
private static void IncrementMultiIndex(int[] dimensions, int[] indices)
{
var currentPosition = 0;
while (true)
{
if (currentPosition >= indices.Length)
{
break;
}
indices[currentPosition]++;
if (indices[currentPosition] >= dimensions[currentPosition])
{
indices[currentPosition] = 0;
currentPosition++;
}
else
{
break;
}
}
}
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -7,6 +9,7 @@ namespace MatFileHandler
/// </summary>
public class DurationAdapter
{
private readonly int[] dimensions;
private readonly double[] data;
/// <summary>
@ -22,15 +25,14 @@ namespace MatFileHandler
}
var dataObject = matObject["millis", 0];
data = dataObject.ConvertToDoubleArray()
?? throw new HandlerException("Cannot extract data for the duration adapter.");
Dimensions = dataObject.Dimensions;
data = dataObject.ConvertToDoubleArray();
dimensions = dataObject.Dimensions;
}
/// <summary>
/// Gets duration array dimensions.
/// </summary>
public int[] Dimensions { get; }
public int[] Dimensions => dimensions;
/// <summary>
/// Gets duration object at given position.
@ -39,4 +41,4 @@ namespace MatFileHandler
/// <returns>Value.</returns>
public TimeSpan this[params int[] list] => TimeSpan.FromTicks((long)(10000.0 * data[Dimensions.DimFlatten(list)]));
}
}
}

View File

@ -1,58 +0,0 @@
namespace MatFileHandler
{
/// <summary>
/// A better interface for using enum adapter.
/// </summary>
public class EnumAdapter
{
/// <summary>
/// Initializes a new instance of the <see cref="EnumAdapter"/> class.
/// </summary>
/// <param name="array">Source enum object.</param>
public EnumAdapter(IArray array)
{
var matObject = array as Opaque;
if (matObject?.RawData is not IStructureArray rawData)
{
throw new HandlerException("Cannot extract data for the enum adapter.");
}
if (rawData["ValueNames"] is not IArrayOf<uint> valueNamesData)
{
throw new HandlerException("Cannot extract data for the enum adapter.");
}
var numberOfNames = valueNamesData.Count;
var valueNames = new string[numberOfNames];
var names = matObject.SubsystemData.FieldNames;
for (var i = 0; i < numberOfNames; i++)
{
valueNames[i] = names[valueNamesData[i] - 1];
}
if (rawData["ValueIndices"] is not IArrayOf<uint> valueIndices)
{
throw new HandlerException("Cannot extract data for the enum adapter.");
}
ClassName = matObject.ClassName;
ValueNames = valueNames;
Values = valueIndices;
}
/// <summary>
/// Gets name of the enumeration class.
/// </summary>
public string ClassName { get; }
/// <summary>
/// Gets names of the enumeration values.
/// </summary>
public string[] ValueNames { get; }
/// <summary>
/// Gets indices of values stored in the variable.
/// </summary>
public IArrayOf<uint> Values { get; }
}
}

View File

@ -1,391 +0,0 @@
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 static unsafe int GetLengthOfByteArray<T>(int dataLength)
where T : unmanaged
{
return dataLength * sizeof(T);
}
private static unsafe int GetLengthOfPairOfByteArrays<T>(ComplexOf<T>[] data)
where T : unmanaged
{
return data.Length * sizeof(T);
}
private static unsafe int GetLengthOfPairOfByteArrays(Complex[] data)
{
return data.Length * sizeof(double);
}
private static int CalculatePadding(int length)
{
var rem = length % 8;
return rem == 0 ? 0 : 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 static (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.Max(name => name.Length) + 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));
}
}
}

View File

@ -1,9 +1,11 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
namespace MatFileHandler
{
/// <summary>
/// Exception related to Matlab data handling.
/// Exception related to Matlab data handling
/// </summary>
public class HandlerException : Exception
{
@ -12,9 +14,9 @@ namespace MatFileHandler
/// </summary>
/// <param name="message">Error message.</param>
/// <param name="innerException">Inner exception.</param>
public HandlerException(string message, Exception? innerException = null)
public HandlerException(string message, Exception innerException = null)
: base(message, innerException)
{
}
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Runtime.InteropServices;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// Wrapper for HDF attribute.
/// </summary>
internal struct Attribute : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="Attribute"/> struct.
/// </summary>
/// <param name="locationId">Containing location id.</param>
/// <param name="name">Attribute name.</param>
public Attribute(long locationId, string name)
{
Id = H5A.open_by_name(locationId, ".", name);
}
/// <summary>
/// Gets attribute id.
/// </summary>
public long Id { get; private set; }
/// <inheritdoc />
public void Dispose()
{
if (Id != -1)
{
H5A.close(Id);
Id = -1;
}
}
/// <summary>
/// Get HDF type of the attribute.
/// </summary>
/// <returns>HDF type.</returns>
public Type GetHdfType()
{
return new Type(H5A.get_type(Id));
}
/// <summary>
/// Get HDF space of the attribute.
/// </summary>
/// <returns>HDF space.</returns>
public Space GetSpace()
{
return new Space(H5A.get_space(Id));
}
/// <summary>
/// Read attribute value as boolean.
/// </summary>
/// <returns>Attribute value.</returns>
public bool ReadBool()
{
using (var h = new MemoryHandle(sizeof(int)))
{
H5A.read(Id, H5T.NATIVE_INT, h.Handle);
var result = Marshal.ReadInt32(h.Handle);
return result != 0;
}
}
/// <summary>
/// Read attribute value to the provided memory handle.
/// </summary>
/// <param name="handle">Target memory handle.</param>
/// <param name="type">HDF type to read from the attribute.</param>
public void ReadToHandle(MemoryHandle handle, Type type)
{
H5A.read(Id, type.Id, handle.Handle);
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// HDF class.
/// </summary>
internal struct Class : IEquatable<Class>
{
/// <summary>
/// Compound class.
/// </summary>
public static readonly Class Compound = new Class(H5T.class_t.COMPOUND);
/// <summary>
/// Reference class.
/// </summary>
public static readonly Class Reference = new Class(H5T.class_t.REFERENCE);
/// <summary>
/// String class.
/// </summary>
public static readonly Class String = new Class(H5T.class_t.STRING);
private readonly H5T.class_t classT;
/// <summary>
/// Initializes a new instance of the <see cref="Class"/> struct.
/// </summary>
/// <param name="classT">HDF class_t.</param>
public Class(H5T.class_t classT)
{
this.classT = classT;
}
public static bool operator ==(Class one, Class other)
{
return one.Equals(other);
}
public static bool operator !=(Class one, Class other)
{
return !one.Equals(other);
}
/// <summary>
/// Check if the class is equal to the other class.
/// </summary>
/// <param name="other">Other class.</param>
/// <returns>True iff the classes are equal.</returns>
public bool Equals(Class other)
{
return classT == other.classT;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is Class other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return (int)classT;
}
}
}

View File

@ -0,0 +1,104 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// HDF dataset.
/// </summary>
internal struct Dataset : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="Dataset"/> struct.
/// </summary>
/// <param name="datasetId">Dataset id.</param>
public Dataset(long datasetId)
{
Id = datasetId;
}
/// <summary>
/// Initializes a new instance of the <see cref="Dataset"/> struct.
/// </summary>
/// <param name="groupId">Containing group id.</param>
/// <param name="name">Name of the dataset in the group.</param>
public Dataset(long groupId, string name)
{
Id = H5D.open(groupId, name);
}
/// <summary>
/// Gets dataset id.
/// </summary>
public long Id { get; private set; }
/// <summary>
/// Check if dataset attribute with the given name exists.
/// </summary>
/// <param name="name">Attribute name.</param>
/// <returns>True iff dataset has an attribute with this name.</returns>
public bool AttributeExists(string name)
{
return H5A.exists_by_name(Id, ".", name) != 0;
}
/// <inheritdoc />
public void Dispose()
{
if (Id != -1)
{
H5D.close(Id);
Id = -1;
}
}
/// <summary>
/// Open attribute with given name.
/// </summary>
/// <param name="name">Attribute name.</param>
/// <returns>Attribute.</returns>
public Attribute GetAttribute(string name)
{
return new Attribute(Id, name);
}
/// <summary>
/// Get HDF space of the dataset.
/// </summary>
/// <returns>HDF space.</returns>
public Space GetHdfSpace()
{
return new Space(H5D.get_space(Id));
}
/// <summary>
/// Get HDF type of the dataset.
/// </summary>
/// <returns>HDF type.</returns>
public Type GetHdfType()
{
return new Type(H5D.get_type(Id));
}
/// <summary>
/// Get storage size of the dataset.
/// </summary>
/// <returns>Storage size.</returns>
public int GetStorageSize()
{
return (int)H5D.get_storage_size(Id);
}
/// <summary>
/// Read the contents of the dataset into the memory handle.
/// </summary>
/// <param name="type">HDF type of the data to read.</param>
/// <param name="handle">Memory handle.</param>
public void ReadToHandle(Type type, MemoryHandle handle)
{
H5D.read(Id, type.Id, H5S.ALL, H5S.ALL, H5P.DEFAULT, handle.Handle);
}
}
}

View File

@ -0,0 +1,58 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// Hdf group.
/// </summary>
internal struct Group : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="Group"/> struct.
/// </summary>
/// <param name="groupId">Containing group id.</param>
/// <param name="name">Name of the subgroup in the containing group.</param>
public Group(long groupId, string name)
{
Id = H5G.open(groupId, name);
}
/// <summary>
/// Gets group id.
/// </summary>
public long Id { get; private set; }
/// <summary>
/// Check if group attribute with the given name exists.
/// </summary>
/// <param name="name">Attribute name.</param>
/// <returns>True iff group has an attribute with this name.</returns>
public bool AttributeExists(string name)
{
return H5A.exists_by_name(Id, ".", name) != 0;
}
/// <inheritdoc />
public void Dispose()
{
if (Id != -1)
{
H5G.close(Id);
Id = -1;
}
}
/// <summary>
/// Get group attribute.
/// </summary>
/// <param name="name">Attribute name.</param>
/// <returns>Attribute.</returns>
public Attribute GetAttribute(string name)
{
return new Attribute(Id, name);
}
}
}

View File

@ -0,0 +1,80 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler.Hdf
{
/// <summary>
/// Matlab classes as they appear in HDF files.
/// </summary>
internal enum MatlabClass
{
/// <summary>
/// Empty array.
/// </summary>
MEmpty,
/// <summary>
/// Char array.
/// </summary>
MChar,
/// <summary>
/// Int8 array.
/// </summary>
MInt8,
/// <summary>
/// UInt8 array.
/// </summary>
MUInt8,
/// <summary>
/// Int16 array.
/// </summary>
MInt16,
/// <summary>
/// UInt16 array.
/// </summary>
MUInt16,
/// <summary>
/// Int32 array.
/// </summary>
MInt32,
/// <summary>
/// UInt32 array.
/// </summary>
MUInt32,
/// <summary>
/// Int64 array.
/// </summary>
MInt64,
/// <summary>
/// UInt64 array.
/// </summary>
MUInt64,
/// <summary>
/// Single-precision floating point array.
/// </summary>
MSingle,
/// <summary>
/// Double-precision floating point array.
/// </summary>
MDouble,
/// <summary>
/// Cell array.
/// </summary>
MCell,
/// <summary>
/// Logical array.
/// </summary>
MLogical,
}
}

View File

@ -0,0 +1,37 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Runtime.InteropServices;
namespace MatFileHandler.Hdf
{
/// <summary>
/// Wrapper around IntPtr to array in unmanaged memory.
/// </summary>
internal sealed class MemoryHandle : IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="MemoryHandle"/> class.
/// </summary>
/// <param name="sizeInBytes">Size of the memory to be allocated.</param>
internal MemoryHandle(int sizeInBytes)
{
Handle = Marshal.AllocHGlobal(sizeInBytes);
}
/// <summary>
/// Gets wrapped IntPtr.
/// </summary>
internal IntPtr Handle { get; private set; }
/// <inheritdoc/>
public void Dispose()
{
if (Handle != IntPtr.Zero)
{
Marshal.FreeHGlobal(Handle);
Handle = IntPtr.Zero;
}
}
}
}

View File

@ -0,0 +1,85 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// Array of HDF references stored in an HDF dataset.
/// </summary>
internal struct ReferenceArray : IDisposable, IEnumerable<Dataset>
{
private readonly Dataset[] references;
/// <summary>
/// Initializes a new instance of the <see cref="ReferenceArray"/> struct.
/// </summary>
/// <param name="dataset">Containing dataset.</param>
/// <param name="size">Array size.</param>
public ReferenceArray(Dataset dataset, int size)
{
Dataset = dataset;
Size = size;
Buf = new MemoryHandle(Marshal.SizeOf(default(IntPtr)) * size);
Dataset.ReadToHandle(Type.Reference, Buf);
references = new Dataset[size];
for (var i = 0; i < size; i++)
{
references[i] =
new Dataset(H5R.dereference(
dataset.Id,
H5P.DEFAULT,
H5R.type_t.OBJECT,
Buf.Handle + (i * Marshal.SizeOf(default(IntPtr)))));
}
}
/// <summary>
/// Gets containing dataset.
/// </summary>
public Dataset Dataset { get; }
/// <summary>
/// Gets references.
/// </summary>
public IReadOnlyList<Dataset> References => references;
/// <summary>
/// Gets array size.
/// </summary>
public int Size { get; }
private MemoryHandle Buf { get; }
/// <inheritdoc />
public void Dispose()
{
Buf?.Dispose();
if (References is null)
{
return;
}
foreach (var reference in References)
{
reference.Dispose();
}
}
/// <inheritdoc />
public IEnumerator<Dataset> GetEnumerator()
{
return ((IEnumerable<Dataset>)References).GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return References.GetEnumerator();
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.Linq;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// HDF space.
/// </summary>
internal struct Space
{
/// <summary>
/// Initializes a new instance of the <see cref="Space"/> struct.
/// </summary>
/// <param name="id">Space id.</param>
public Space(long id)
{
Id = id;
}
/// <summary>
/// Gets space id.
/// </summary>
public long Id { get; }
/// <summary>
/// Get space rank.
/// </summary>
/// <returns>Space rank.</returns>
public int GetRank()
{
return H5S.get_simple_extent_ndims(Id);
}
/// <summary>
/// Get dimensions of the space.
/// </summary>
/// <returns>Space dimensions.</returns>
public int[] GetDimensions()
{
var dims = new ulong[GetRank()];
H5S.get_simple_extent_dims(Id, dims, null);
System.Array.Reverse(dims);
return dims.Select(x => (int)x).ToArray();
}
}
}

147
MatFileHandler/Hdf/Type.cs Normal file
View File

@ -0,0 +1,147 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using HDF.PInvoke;
namespace MatFileHandler.Hdf
{
/// <summary>
/// HDF type.
/// </summary>
internal struct Type
{
/// <summary>
/// Initializes a new instance of the <see cref="Type"/> struct.
/// </summary>
/// <param name="id">Type id.</param>
public Type(long id)
{
Id = id;
}
/// <summary>
/// Gets HDF string type.
/// </summary>
public static Type CS1 => new Type(H5T.C_S1);
/// <summary>
/// Gets HDF double type.
/// </summary>
public static Type NativeDouble => new Type(H5T.NATIVE_DOUBLE);
/// <summary>
/// Gets HDF float (single) type.
/// </summary>
public static Type NativeFloat => new Type(H5T.NATIVE_FLOAT);
/// <summary>
/// Gets HDF int type.
/// </summary>
public static Type NativeInt => new Type(H5T.NATIVE_INT);
/// <summary>
/// Gets HDF int16 type.
/// </summary>
public static Type NativeInt16 => new Type(H5T.NATIVE_INT16);
/// <summary>
/// Gets HDF int32 type.
/// </summary>
public static Type NativeInt32 => new Type(H5T.NATIVE_INT32);
/// <summary>
/// Gets HDF int64 type.
/// </summary>
public static Type NativeInt64 => new Type(H5T.NATIVE_INT64);
/// <summary>
/// Gets HDF int8 type.
/// </summary>
public static Type NativeInt8 => new Type(H5T.NATIVE_INT8);
/// <summary>
/// Gets HDF uint type.
/// </summary>
public static Type NativeUInt => new Type(H5T.NATIVE_UINT);
/// <summary>
/// Gets HDF uint16 type
/// </summary>
public static Type NativeUInt16 => new Type(H5T.NATIVE_UINT16);
/// <summary>
/// Gets HDF uint32 type.
/// </summary>
public static Type NativeUInt32 => new Type(H5T.NATIVE_UINT32);
/// <summary>
/// Gets HDF uint64 type.
/// </summary>
public static Type NativeUInt64 => new Type(H5T.NATIVE_UINT64);
/// <summary>
/// Gets HDF uint8 type.
/// </summary>
public static Type NativeUInt8 => new Type(H5T.NATIVE_UINT8);
/// <summary>
/// Gets HDF reference type.
/// </summary>
public static Type Reference => new Type(H5T.STD_REF_OBJ);
/// <summary>
/// Gets type id.
/// </summary>
public long Id { get; }
/// <summary>
/// Create compound type of given size.
/// </summary>
/// <param name="size">Size of the type.</param>
/// <returns>The created type.</returns>
public static Type CreateCompound(int size)
{
return new Type(H5T.create(H5T.class_t.COMPOUND, (IntPtr)size));
}
/// <summary>
/// Get class of the type.
/// </summary>
/// <returns>Class of the type.</returns>
public Class GetClass()
{
return new Class(H5T.get_class(Id));
}
/// <summary>
/// Get size of the type.
/// </summary>
/// <returns>Size of the type.</returns>
public int GetSize()
{
return (int)H5T.get_size(Id);
}
/// <summary>
/// Insert a field into the type.
/// </summary>
/// <param name="name">Field name.</param>
/// <param name="fieldType">Field type.</param>
public void InsertField(string name, Type fieldType)
{
H5T.insert(Id, name, IntPtr.Zero, fieldType.Id);
}
/// <summary>
/// Create type copy with same class and given size.
/// </summary>
/// <param name="size">New size.</param>
/// <returns>New type.</returns>
public Type WithSize(int size)
{
var classId = H5T.copy(Id);
H5T.set_size(classId, (IntPtr)size);
return new Type(classId);
}
}
}

View File

@ -0,0 +1,667 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using HDF.PInvoke;
using MatFileHandler.Hdf;
namespace MatFileHandler
{
/// <summary>
/// Reader of HDF files containing MATLAB data.
/// </summary>
internal class HdfFileReader
{
private const string ClassAttributeName = "MATLAB_class";
private const string GlobalAttributeName = "MATLAB_global";
private const string SparseAttributeName = "MATLAB_sparse";
private readonly long fileId;
private List<IVariable> variables;
/// <summary>
/// Initializes a new instance of the <see cref="HdfFileReader"/> class.
/// </summary>
/// <param name="fileId">File id to read data from.</param>
internal HdfFileReader(long fileId)
{
this.fileId = fileId;
}
/// <summary>
/// Read MATLAB data from the HDF file.
/// </summary>
/// <returns>MATLAB data file contents.</returns>
internal IMatFile Read()
{
variables = new List<IVariable>();
var group_info = default(H5G.info_t);
H5G.get_info(fileId, ref group_info);
var numberOfVariables = group_info.nlinks;
ulong idx = 0;
while (idx < numberOfVariables)
{
H5L.iterate(
fileId,
H5.index_t.NAME,
H5.iter_order_t.NATIVE,
ref idx,
VariableIterator,
IntPtr.Zero);
}
return new MatFile(variables);
}
private static MatlabClass ArrayTypeFromMatlabClassName(string matlabClassName)
{
switch (matlabClassName)
{
case "canonical empty":
return MatlabClass.MEmpty;
case "logical":
return MatlabClass.MLogical;
case "char":
return MatlabClass.MChar;
case "int8":
return MatlabClass.MInt8;
case "uint8":
return MatlabClass.MUInt8;
case "int16":
return MatlabClass.MInt16;
case "uint16":
return MatlabClass.MUInt16;
case "int32":
return MatlabClass.MInt32;
case "uint32":
return MatlabClass.MUInt32;
case "int64":
return MatlabClass.MInt64;
case "uint64":
return MatlabClass.MUInt64;
case "single":
return MatlabClass.MSingle;
case "double":
return MatlabClass.MDouble;
case "cell":
return MatlabClass.MCell;
}
throw new NotImplementedException();
}
private static T[] ConvertDataToProperType<T>(byte[] bytes, MatlabClass arrayType)
where T : struct
{
var length = bytes.Length;
var arrayElementSize = SizeOfArrayElement(arrayType);
var data = new T[length / arrayElementSize];
Buffer.BlockCopy(bytes, 0, data, 0, length);
return data;
}
private static int[] GetDimensionsOfDataset(Dataset dataset)
{
return dataset.GetHdfSpace().GetDimensions();
}
private static string GetMatlabClassFromAttribute(Hdf.Attribute attribute)
{
var type = attribute.GetHdfType();
var cl = type.GetClass();
if (cl != Class.String)
{
throw new NotImplementedException();
}
var typeIdSize = type.GetSize();
var copiedType = Hdf.Type.CS1.WithSize(type.GetSize());
var matlabClassNameBytes = new byte[typeIdSize];
using (var buf = new MemoryHandle(typeIdSize))
{
attribute.ReadToHandle(buf, copiedType);
Marshal.Copy(buf.Handle, matlabClassNameBytes, 0, typeIdSize);
}
var length = typeIdSize;
for (var i = 0; i < typeIdSize; i++)
{
if (matlabClassNameBytes[i] == 0)
{
length = i;
break;
}
}
return Encoding.ASCII.GetString(matlabClassNameBytes, 0, length);
}
private static string GetMatlabClassOfDataset(Dataset dataset)
{
using (var attribute = dataset.GetAttribute(ClassAttributeName))
{
return GetMatlabClassFromAttribute(attribute);
}
}
private static string GetMatlabClassOfGroup(Group group)
{
using (var attribute = group.GetAttribute(ClassAttributeName))
{
return GetMatlabClassFromAttribute(attribute);
}
}
private static H5O.type_t GetObjectType(long groupId, string fieldName)
{
var objectInfo = default(H5O.info_t);
H5O.get_info_by_name(groupId, fieldName, ref objectInfo);
return objectInfo.type;
}
private static Hdf.Type H5tTypeFromHdfMatlabClass(MatlabClass arrayType)
{
switch (arrayType)
{
case MatlabClass.MInt8:
return Hdf.Type.NativeInt8;
case MatlabClass.MUInt8:
case MatlabClass.MLogical:
return Hdf.Type.NativeUInt8;
case MatlabClass.MInt16:
return Hdf.Type.NativeInt16;
case MatlabClass.MUInt16:
return Hdf.Type.NativeUInt16;
case MatlabClass.MInt32:
return Hdf.Type.NativeInt32;
case MatlabClass.MUInt32:
return Hdf.Type.NativeUInt32;
case MatlabClass.MInt64:
return Hdf.Type.NativeInt64;
case MatlabClass.MUInt64:
return Hdf.Type.NativeUInt64;
case MatlabClass.MSingle:
return Hdf.Type.NativeFloat;
case MatlabClass.MDouble:
return Hdf.Type.NativeDouble;
}
throw new NotImplementedException();
}
private static IArray ReadCellArray(Dataset dataset, int[] dims)
{
var numberOfElements = dims.NumberOfElements();
var elements = new IArray[numberOfElements];
using (var array = new ReferenceArray(dataset, numberOfElements))
{
var i = 0;
foreach (var reference in array)
{
elements[i++] = ReadDataset(reference);
}
}
return new MatCellArray(dims, elements);
}
private static IArray ReadCharArray(Dataset dataset, int[] dims)
{
var storageSize = dataset.GetStorageSize();
var data = ReadDataset(dataset, Hdf.Type.NativeUInt16, storageSize);
var uInt16Data = new ushort[data.Length / sizeof(ushort)];
Buffer.BlockCopy(data, 0, uInt16Data, 0, data.Length);
var str = Encoding.Unicode.GetString(data);
return new MatCharArrayOf<ushort>(dims, uInt16Data, str);
}
private static (T[] real, T[] imaginary) ReadComplexData<T>(
Dataset dataset,
int dataSize,
MatlabClass arrayType)
where T : struct
{
var h5Type = H5tTypeFromHdfMatlabClass(arrayType);
var h5Size = h5Type.GetSize();
var h5tComplexReal = Hdf.Type.CreateCompound(h5Size);
h5tComplexReal.InsertField("real", h5Type);
var realData = ReadDataset(dataset, h5tComplexReal, dataSize);
var h5tComplexImaginary = Hdf.Type.CreateCompound(h5Size);
h5tComplexImaginary.InsertField("imag", h5Type);
var imaginaryData = ReadDataset(dataset, h5tComplexImaginary, dataSize);
var convertedRealData = ConvertDataToProperType<T>(realData, arrayType);
var convertedImaginaryData = ConvertDataToProperType<T>(imaginaryData, arrayType);
return (convertedRealData, convertedImaginaryData);
}
private static IArray ReadDataset(Dataset dataset)
{
var dims = GetDimensionsOfDataset(dataset);
var matlabClass = GetMatlabClassOfDataset(dataset);
var arrayType = ArrayTypeFromMatlabClassName(matlabClass);
switch (arrayType)
{
case MatlabClass.MEmpty:
return MatArray.Empty();
case MatlabClass.MLogical:
return ReadNumericalArray<bool>(dataset, dims, arrayType);
case MatlabClass.MChar:
return ReadCharArray(dataset, dims);
case MatlabClass.MInt8:
return ReadNumericalArray<sbyte>(dataset, dims, arrayType);
case MatlabClass.MUInt8:
return ReadNumericalArray<byte>(dataset, dims, arrayType);
case MatlabClass.MInt16:
return ReadNumericalArray<short>(dataset, dims, arrayType);
case MatlabClass.MUInt16:
return ReadNumericalArray<ushort>(dataset, dims, arrayType);
case MatlabClass.MInt32:
return ReadNumericalArray<int>(dataset, dims, arrayType);
case MatlabClass.MUInt32:
return ReadNumericalArray<uint>(dataset, dims, arrayType);
case MatlabClass.MInt64:
return ReadNumericalArray<long>(dataset, dims, arrayType);
case MatlabClass.MUInt64:
return ReadNumericalArray<ulong>(dataset, dims, arrayType);
case MatlabClass.MSingle:
return ReadNumericalArray<float>(dataset, dims, arrayType);
case MatlabClass.MDouble:
return ReadNumericalArray<double>(dataset, dims, arrayType);
case MatlabClass.MCell:
return ReadCellArray(dataset, dims);
}
throw new NotImplementedException($"Unknown array type: {arrayType}.");
}
private static byte[] ReadDataset(Dataset dataset, Hdf.Type elementType, int dataSize)
{
var data = new byte[dataSize];
using (var dataBuffer = new MemoryHandle(dataSize))
{
dataset.ReadToHandle(elementType, dataBuffer);
Marshal.Copy(dataBuffer.Handle, data, 0, dataSize);
}
return data;
}
private static string[] ReadFieldNames(long groupId)
{
// Try to read fields from MATLAB_fields.
using (var attr = new Hdf.Attribute(groupId, "MATLAB_fields"))
{
if (attr.Id == 0)
{
throw new NotImplementedException();
}
var dimensions = attr.GetSpace().GetDimensions();
var numberOfFields = dimensions.NumberOfElements();
var fieldType = attr.GetHdfType();
var fieldNamePointersSizeInBytes = numberOfFields * Marshal.SizeOf(default(H5T.hvl_t));
var fieldNamePointers = new IntPtr[numberOfFields * 2];
using (var fieldNamesBuf = new MemoryHandle(fieldNamePointersSizeInBytes))
{
attr.ReadToHandle(fieldNamesBuf, fieldType);
Marshal.Copy(fieldNamesBuf.Handle, fieldNamePointers, 0, numberOfFields * 2);
}
var fieldNames = new string[numberOfFields];
for (var i = 0; i < numberOfFields; i++)
{
var stringLength = fieldNamePointers[i * 2];
var stringPointer = fieldNamePointers[(i * 2) + 1];
fieldNames[i] = Marshal.PtrToStringAnsi(stringPointer, (int)stringLength);
}
return fieldNames;
}
}
private static IArray ReadGroup(Group group)
{
var matlabClass = GetMatlabClassOfGroup(group);
if (matlabClass == "struct")
{
return ReadStruct(group.Id);
}
if (group.AttributeExists(SparseAttributeName))
{
var arrayType = ArrayTypeFromMatlabClassName(matlabClass);
switch (arrayType)
{
case MatlabClass.MEmpty:
return MatArray.Empty();
case MatlabClass.MLogical:
return ReadSparseArray<bool>(group.Id, arrayType);
case MatlabClass.MInt8:
return ReadSparseArray<sbyte>(group.Id, arrayType);
case MatlabClass.MUInt8:
return ReadSparseArray<byte>(group.Id, arrayType);
case MatlabClass.MInt16:
return ReadSparseArray<short>(group.Id, arrayType);
case MatlabClass.MUInt16:
return ReadSparseArray<ushort>(group.Id, arrayType);
case MatlabClass.MInt32:
return ReadSparseArray<int>(group.Id, arrayType);
case MatlabClass.MUInt32:
return ReadSparseArray<uint>(group.Id, arrayType);
case MatlabClass.MInt64:
return ReadSparseArray<long>(group.Id, arrayType);
case MatlabClass.MUInt64:
return ReadSparseArray<ulong>(group.Id, arrayType);
case MatlabClass.MSingle:
return ReadSparseArray<float>(group.Id, arrayType);
case MatlabClass.MDouble:
return ReadSparseArray<double>(group.Id, arrayType);
default:
throw new NotSupportedException();
}
}
throw new NotImplementedException();
}
private static IEnumerable<ComplexOf<T>> CombineComplexOfData<T>(
IEnumerable<T> realData,
IEnumerable<T> imaginaryData)
where T : struct
{
return realData.Zip(
imaginaryData,
(x, y) => new ComplexOf<T>(x, y));
}
private static IEnumerable<Complex> CombineComplexData(
IEnumerable<double> realData,
IEnumerable<double> imaginaryData)
{
return realData.Zip(
imaginaryData,
(x, y) => new Complex(x, y));
}
private static IArray ReadNumericalArray<T>(Dataset dataset, int[] dims, MatlabClass arrayType)
where T : struct
{
var numberOfElements = dims.NumberOfElements();
var dataSize = numberOfElements * SizeOfArrayElement(arrayType);
var dataSetType = dataset.GetHdfType();
var dataSetTypeClass = dataSetType.GetClass();
var isCompound = dataSetTypeClass == Class.Compound;
if (isCompound)
{
var (convertedRealData, convertedImaginaryData) = ReadComplexData<T>(dataset, dataSize, arrayType);
if (arrayType == MatlabClass.MDouble)
{
var complexData =
CombineComplexData(
convertedRealData as double[],
convertedImaginaryData as double[])
.ToArray();
return new MatNumericalArrayOf<Complex>(dims, complexData);
}
else
{
var complexData =
CombineComplexOfData(
convertedRealData,
convertedImaginaryData)
.ToArray();
return new MatNumericalArrayOf<ComplexOf<T>>(dims, complexData);
}
}
var data = ReadDataset(dataset, H5tTypeFromHdfMatlabClass(arrayType), dataSize);
var convertedData = ConvertDataToProperType<T>(data, arrayType);
return new MatNumericalArrayOf<T>(dims, convertedData);
}
private static IArray ReadSparseArray<T>(long groupId, MatlabClass arrayType)
where T : struct
{
using (var sparseAttribute = new Hdf.Attribute(groupId, SparseAttributeName))
{
using (var numberOfRowsHandle = new MemoryHandle(sizeof(uint)))
{
sparseAttribute.ReadToHandle(numberOfRowsHandle, Hdf.Type.NativeUInt);
var numberOfRows = Marshal.ReadInt32(numberOfRowsHandle.Handle);
int[] rowIndex;
int[] columnIndex;
using (var irData = new Dataset(groupId, "ir"))
{
var ds = GetDimensionsOfDataset(irData);
var numberOfIr = ds.NumberOfElements();
var irBytes = ReadDataset(irData, Hdf.Type.NativeInt, numberOfIr * sizeof(int));
rowIndex = new int[numberOfIr];
Buffer.BlockCopy(irBytes, 0, rowIndex, 0, irBytes.Length);
}
using (var jcData = new Dataset(groupId, "jc"))
{
var ds = GetDimensionsOfDataset(jcData);
var numberOfJc = ds.NumberOfElements();
var jcBytes = ReadDataset(jcData, Hdf.Type.NativeInt, numberOfJc * sizeof(int));
columnIndex = new int[numberOfJc];
Buffer.BlockCopy(jcBytes, 0, columnIndex, 0, jcBytes.Length);
}
using (var data = new Dataset(groupId, "data"))
{
var ds = GetDimensionsOfDataset(data);
var dims = new int[2];
dims[0] = numberOfRows;
dims[1] = columnIndex.Length - 1;
var dataSize = ds.NumberOfElements() * SizeOfArrayElement(arrayType);
var storageSize = data.GetStorageSize();
var dataSetType = data.GetHdfType();
var dataSetTypeClass = dataSetType.GetClass();
var isCompound = dataSetTypeClass == Class.Compound;
if (isCompound)
{
var (convertedRealData, convertedImaginaryData) =
ReadComplexData<T>(data, dataSize, arrayType);
if (arrayType == MatlabClass.MDouble)
{
var complexData =
CombineComplexData(
convertedRealData as double[],
convertedImaginaryData as double[])
.ToArray();
var complexDataDictionary =
DataExtraction.ConvertMatlabSparseToDictionary(
rowIndex,
columnIndex,
j => complexData[j]);
return new MatSparseArrayOf<Complex>(dims, complexDataDictionary);
}
else
{
var complexData =
CombineComplexOfData<T>(
convertedRealData,
convertedImaginaryData)
.ToArray();
var complexDataDictionary =
DataExtraction.ConvertMatlabSparseToDictionary(
rowIndex,
columnIndex,
j => complexData[j]);
return new MatSparseArrayOf<ComplexOf<T>>(dims, complexDataDictionary);
}
}
var d = ReadDataset(data, H5tTypeFromHdfMatlabClass(arrayType), dataSize);
var elements = ConvertDataToProperType<T>(d, arrayType);
var dataDictionary =
DataExtraction.ConvertMatlabSparseToDictionary(rowIndex, columnIndex, j => elements[j]);
return new MatSparseArrayOf<T>(dims, dataDictionary);
}
}
}
}
private static IArray ReadStruct(long groupId)
{
var fieldNames = ReadFieldNames(groupId);
var firstObjectType = GetObjectType(groupId, fieldNames[0]);
if (firstObjectType == H5O.type_t.DATASET)
{
using (var firstField = new Dataset(groupId, fieldNames[0]))
{
var firstFieldType = firstField.GetHdfType();
if (firstFieldType.GetClass() == Class.Reference)
{
if (firstField.AttributeExists(ClassAttributeName))
{
throw new NotImplementedException();
}
else
{
var dimensions = GetDimensionsOfDataset(firstField);
var numberOfElements = dimensions.NumberOfElements();
var dictionary = new Dictionary<string, List<IArray>>();
foreach (var fieldName in fieldNames)
{
var fieldType = GetObjectType(groupId, fieldName);
dictionary[fieldName] = new List<IArray>();
switch (fieldType)
{
case H5O.type_t.DATASET:
using (var field = new Dataset(groupId, fieldName))
{
using (var array = new ReferenceArray(field, numberOfElements))
{
foreach (var reference in array)
{
var value = ReadDataset(reference);
dictionary[fieldName].Add(value);
}
}
}
break;
default:
throw new NotImplementedException();
}
}
return new MatStructureArray(dimensions, dictionary);
}
}
else
{
throw new NotImplementedException();
}
}
}
else
{
throw new NotImplementedException();
}
throw new NotImplementedException();
}
private static int SizeOfArrayElement(MatlabClass arrayType)
{
switch (arrayType)
{
case MatlabClass.MInt8:
case MatlabClass.MUInt8:
case MatlabClass.MLogical:
return 1;
case MatlabClass.MInt16:
case MatlabClass.MUInt16:
return 2;
case MatlabClass.MInt32:
case MatlabClass.MUInt32:
case MatlabClass.MSingle:
return 4;
case MatlabClass.MInt64:
case MatlabClass.MUInt64:
case MatlabClass.MDouble:
return 8;
}
throw new NotImplementedException();
}
private bool ReadGlobalFlag(Group group)
{
if (!group.AttributeExists(GlobalAttributeName))
{
return false;
}
using (var globalAttribute = group.GetAttribute(GlobalAttributeName))
{
return globalAttribute.ReadBool();
}
}
private bool ReadGlobalFlag(Dataset dataset)
{
if (!dataset.AttributeExists(GlobalAttributeName))
{
return false;
}
using (var globalAttribute = dataset.GetAttribute(GlobalAttributeName))
{
return globalAttribute.ReadBool();
}
}
private int VariableIterator(long group, IntPtr name, ref H5L.info_t info, IntPtr op_data)
{
var variableName = Marshal.PtrToStringAnsi(name);
var object_info = default(H5O.info_t);
H5O.get_info_by_name(group, variableName, ref object_info);
switch (object_info.type)
{
case H5O.type_t.DATASET:
using (var dataset = new Dataset(group, variableName))
{
var isGlobal = ReadGlobalFlag(dataset);
var value = ReadDataset(dataset);
variables.Add(new MatVariable(value, variableName, isGlobal));
}
break;
case H5O.type_t.GROUP:
if (variableName == "#refs#")
{
return 0;
}
using (var subGroup = new Group(group, variableName))
{
var isGlobal = ReadGlobalFlag(subGroup);
var groupValue = ReadGroup(subGroup);
variables.Add(new MatVariable(groupValue, variableName, isGlobal));
}
break;
default:
throw new NotImplementedException();
}
return 0;
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright 2017-2019 Alexander Luzgarev
using System;
using System.Globalization;
using System.IO;
using System.Linq;
#if !NET461
using System.Runtime.InteropServices;
#endif
namespace MatFileHandler
{
@ -13,13 +13,19 @@ namespace MatFileHandler
/// </summary>
internal class Header
{
private Header(string text, long subsystemDataOffset, int version)
private Header(byte[] rawBytes, string text, long subsystemDataOffset, int version)
{
RawBytes = rawBytes;
Text = text;
SubsystemDataOffset = subsystemDataOffset;
Version = version;
}
/// <summary>
/// Gets raw byte contents of the header.
/// </summary>
public byte[] RawBytes { get; }
/// <summary>
/// Gets the header text.
/// </summary>
@ -55,7 +61,7 @@ namespace MatFileHandler
platform = platform.Remove(length);
}
var text = $"MATLAB 5.0 MAT-file, Platform: {platform}, Created on: {dateTime}{padding}";
return new Header(text, 0, 256);
return new Header(null, text, 0, 256);
}
/// <summary>
@ -65,16 +71,26 @@ namespace MatFileHandler
/// <returns>The header read.</returns>
public static Header Read(BinaryReader reader)
{
var textBytes = reader.ReadBytes(116);
var text = System.Text.Encoding.UTF8.GetString(textBytes);
var subsystemDataOffsetBytes = reader.ReadBytes(8);
var subsystemDataOffset = BitConverter.ToInt64(subsystemDataOffsetBytes, 0);
var version = reader.ReadInt16();
var endian = reader.ReadInt16();
var isLittleEndian = endian == 19785;
return isLittleEndian
? new Header(text, subsystemDataOffset, version)
: throw new NotSupportedException("Big-endian files are not supported.");
var rawBytes = reader.ReadBytes(128);
using (var stream = new MemoryStream(rawBytes))
{
using (var newReader = new BinaryReader(stream))
{
var textBytes = newReader.ReadBytes(116);
var text = System.Text.Encoding.UTF8.GetString(textBytes);
var subsystemDataOffsetBytes = newReader.ReadBytes(8);
var subsystemDataOffset = BitConverter.ToInt64(subsystemDataOffsetBytes, 0);
var version = newReader.ReadInt16();
var endian = newReader.ReadInt16();
var isLittleEndian = endian == 19785;
if (!isLittleEndian)
{
throw new NotSupportedException("Big-endian files are not supported.");
}
return new Header(rawBytes, text, subsystemDataOffset, version);
}
}
}
private static string GetOperatingSystem()
@ -98,4 +114,4 @@ namespace MatFileHandler
#endif
}
}
}
}

View File

@ -1,4 +1,5 @@
using System;
// Copyright 2017-2019 Alexander Luzgarev
using System.Numerics;
namespace MatFileHandler
@ -27,24 +28,12 @@ namespace MatFileHandler
/// Tries to convert the array to an array of Double values.
/// </summary>
/// <returns>Array of values of the array, converted to Double, or null if the conversion is not possible.</returns>
double[]? ConvertToDoubleArray();
/// <summary>
/// Tries to convert the array to a 2-dimensional array of Double values.
/// </summary>
/// <returns>2-dimensional array of values of the array, converted to Double, or null if the conversion is not possible (for example, when the array has more than 2 dimensions).</returns>
double[,]? ConvertTo2dDoubleArray();
/// <summary>
/// Tries to convert the array to a multidimensional array of Double values.
/// </summary>
/// <returns>Multidimensional array of values of the array, converted to Double, or null if the conversion is not possible.</returns>
Array? ConvertToMultidimensionalDoubleArray();
double[] ConvertToDoubleArray();
/// <summary>
/// Tries to convert the array to an array of Complex values.
/// </summary>
/// <returns>Array of values of the array, converted to Complex, or null if the conversion is not possible.</returns>
Complex[]? ConvertToComplexArray();
Complex[] ConvertToComplexArray();
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -19,7 +21,7 @@ namespace MatFileHandler
/// * for cell arrays:
/// IArray;
/// * for structure arrays:
/// IReadOnlyDictionary&lt;string, IArray&gt;.
/// IReadOnlyDictionary&lt;string, IArray&gt;;
/// </remarks>
public interface IArrayOf<T> : IArray
{
@ -34,4 +36,4 @@ namespace MatFileHandler
/// <param name="list">Index of the element.</param>
T this[params int[] list] { get; set; }
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -6,4 +8,4 @@ namespace MatFileHandler
public interface ICellArray : IArrayOf<IArray>
{
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -8,8 +10,6 @@ namespace MatFileHandler
/// <summary>
/// Gets the contained string.
/// </summary>
#pragma warning disable CA1716, CA1720
string String { get; }
#pragma warning restore CA1716, CA1720
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -18,13 +20,5 @@ namespace MatFileHandler
/// </summary>
/// <param name="variableName">Variable name.</param>
IVariable this[string variableName] { get; set; }
/// <summary>
/// Gets the variable with the specified name.
/// </summary>
/// <param name="name">The name of the variable to get.</param>
/// <param name="variable">When this method returns, contains the variable with the specified name, if it is found; otherwise, null.</param>
/// <returns>True if the file contains a variable with the specified name; otherwise, false.</returns>
bool TryGetVariable(string name, out IVariable? variable);
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.Collections.Generic;
namespace MatFileHandler

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.Collections.Generic;
namespace MatFileHandler
@ -13,6 +15,6 @@ namespace MatFileHandler
/// <summary>
/// Gets a dictionary mapping indices to values.
/// </summary>
new IReadOnlyDictionary<(int row, int column), T> Data { get; }
new IReadOnlyDictionary<(int, int), T> Data { get; }
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.Collections.Generic;
namespace MatFileHandler
@ -19,4 +21,4 @@ namespace MatFileHandler
/// <param name="list">Index of the element in the structure array.</param>
IArray this[string field, params int[] list] { get; set; }
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -20,4 +22,4 @@ namespace MatFileHandler
/// </summary>
bool IsGlobal { get; }
}
}
}

View File

@ -1,4 +1,5 @@
using System;
// Copyright 2017-2019 Alexander Luzgarev
using System.Numerics;
namespace MatFileHandler
@ -11,69 +12,40 @@ namespace MatFileHandler
/// <summary>
/// Initializes a new instance of the <see cref="MatArray"/> class.
/// </summary>
/// <param name="flags">Array properties.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
protected MatArray(
ArrayFlags flags,
int[] dimensions,
string name)
protected MatArray(int[] dimensions)
{
Flags = flags;
Dimensions = dimensions;
Name = name;
}
/// <inheritdoc />
public int[] Dimensions { get; }
/// <summary>
/// Gets the array name.
/// </summary>
public string Name { get; }
/// <inheritdoc />
public int Count => Dimensions.NumberOfElements();
/// <inheritdoc />
public bool IsEmpty => Dimensions.Length == 0;
/// <summary>
/// Gets properties of the array.
/// </summary>
internal ArrayFlags Flags { get; }
/// <summary>
/// Returns a new empty array.
/// </summary>
/// <returns>Empty array.</returns>
public static MatArray Empty()
{
return new MatArray(new ArrayFlags { Class = ArrayType.MxCell, Variable = 0 }, Array.Empty<int>(), string.Empty);
return new MatArray(new int[] { });
}
/// <inheritdoc />
public virtual double[]? ConvertToDoubleArray()
public virtual double[] ConvertToDoubleArray()
{
return null;
}
/// <inheritdoc />
public virtual double[,]? ConvertTo2dDoubleArray()
{
return ConvertToMultidimensionalDoubleArray() as double[,];
}
/// <inheritdoc />
public virtual Array? ConvertToMultidimensionalDoubleArray()
{
return null;
}
/// <inheritdoc />
public virtual Complex[]? ConvertToComplexArray()
public virtual Complex[] ConvertToComplexArray()
{
return null;
}
}
}
}

View File

@ -1,3 +1,5 @@
// Copyright 2017-2019 Alexander Luzgarev
using System.Collections.Generic;
using System.Linq;
@ -11,12 +13,10 @@ namespace MatFileHandler
/// <summary>
/// Initializes a new instance of the <see cref="MatCellArray"/> class.
/// </summary>
/// <param name="flags">Array properties.</param>
/// <param name="dimensions">Dimensions of the array.</param>
/// <param name="name">Array name.</param>
/// <param name="elements">Array elements.</param>
public MatCellArray(ArrayFlags flags, int[] dimensions, string name, IEnumerable<IArray> elements)
: base(flags, dimensions, name)
public MatCellArray(int[] dimensions, IEnumerable<IArray> elements)
: base(dimensions)
{
Data = elements.ToArray();
}
@ -31,4 +31,4 @@ namespace MatFileHandler
set => Data[Dimensions.DimFlatten(indices)] = value;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More