Compare commits

...

22 Commits

Author SHA1 Message Date
366e8e8ead Merge pull request 'Even better objects' (#39) from dev/even-better-objects into master 2025-04-14 19:03:42 +00:00
c8acc1f93d Remove BOM from .cs files 2025-04-14 20:22:48 +02:00
003186b3b1 Version bump 2025-04-14 18:37:29 +02:00
6c95ea7a4a Update links 2025-04-14 18:36:39 +02:00
d24f3c7250 More file-scoped namespaces 2025-04-14 18:24:34 +02:00
6672ae4d24 Get rid of file headers 2025-04-13 17:44:13 +02:00
919014a109 Detect end of stream better 2025-04-12 11:40:22 +02:00
1822e75fd8 Make tests less oopy 2025-04-12 09:07:16 +02:00
d87cc2c99c Fix all the warnings 2025-04-12 09:06:12 +02:00
306992f969 Enable .NET analyzers 2025-04-08 16:50:37 +02:00
5080f35e69 Even better handling of embedded objects 2025-04-08 16:19:47 +02:00
Rob Hague
cc3bdc000c
Fix release notes link (#38)
* Fix release notes link

* link to latest
2025-04-06 16:27:01 +02:00
841ac432a5
Version bump 2025-04-06 12:52:04 +02:00
489d93eff1
Merge pull request #36 from mahalex/dev/better-writer
Better MatFileWriter
2025-04-06 12:46:33 +02:00
dcfc685b62 Test on net461 2025-04-06 12:42:50 +02:00
e9582723c9 Reduce memory consumption of MatFileWriter 2025-04-06 12:42:50 +02:00
3ae8f06b3e
Merge pull request #34 from Rob-Hague/substream
Read compressed elements without loading into memory
2025-04-06 12:27:16 +02:00
a3602f80b4
Add README to package
Add README to package
2025-04-06 10:25:45 +02:00
Robert Hague
1eecbf2a79 Add README to package 2025-04-05 13:07:41 +02:00
Robert Hague
e8bf3f89ee Read compressed elements without loading into memory 2025-04-05 12:29:25 +02:00
Robert Hague
37feeb5863 Add test coverage and fix for reading unseekable or unaligned streams 2025-04-05 11:51:23 +02:00
7494502062
Better treatment of embedded objects 2025-04-03 18:50:27 +02:00
73 changed files with 2080 additions and 1214 deletions

248
.editorconfig Normal file
View File

@ -0,0 +1,248 @@
# 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,68 +0,0 @@
// Copyright 2017-2018 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,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Numerics;

View File

@ -0,0 +1,102 @@
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,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
namespace MatFileHandler.Tests
{
/// <summary>

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using Xunit;
namespace MatFileHandler.Tests

View File

@ -1,43 +0,0 @@
// Copyright 2017-2018 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

@ -1,24 +0,0 @@
// Copyright 2017-2018 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,16 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net472</TargetFrameworks>
<TargetFrameworks>net461;net472;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
<DocumentationFile>bin\Debug\net5.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 Include="xunit.runner.visualstudio" Version="2.8.2">
<PackageReference Condition=" '$(TargetFramework)' != 'net461' " Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Condition=" '$(TargetFramework)' == 'net461' " Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -1,6 +1,5 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
@ -18,12 +17,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading all files in a given test set.
/// </summary>
/// <param name="testSet">Name of the set.</param>
[Theory]
[InlineData("good")]
public void TestReader(string testSet)
[Theory, MemberData(nameof(TestDataFactories))]
public void TestReader(MatFileReadingMethod method)
{
foreach (var matFile in GetTests(testSet).GetAllTestData())
foreach (var matFile in ReadAllTestFiles(method))
{
Assert.NotEmpty(matFile.Variables);
}
@ -32,10 +29,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading lower and upper limits of integer data types.
/// </summary>
[Fact]
public void TestLimits()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestLimits(MatFileReadingMethod method)
{
var matFile = GetTests("good")["limits"];
var matFile = ReadTestFile("limits", method);
IArray array;
array = matFile["int8_"].Value;
CheckLimits(array as IArrayOf<sbyte>, CommonData.Int8Limits);
@ -66,10 +63,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test writing lower and upper limits of integer-based complex data types.
/// </summary>
[Fact]
public void TestComplexLimits()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestComplexLimits(MatFileReadingMethod method)
{
var matFile = GetTests("good")["limits_complex"];
var matFile = ReadTestFile("limits_complex", method);
IArray array;
array = matFile["int8_complex"].Value;
CheckComplexLimits(array as IArrayOf<ComplexOf<sbyte>>, CommonData.Int8Limits);
@ -102,10 +99,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading an ASCII-encoded string.
/// </summary>
[Fact]
public void TestAscii()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestAscii(MatFileReadingMethod method)
{
var matFile = GetTests("good")["ascii"];
var matFile = ReadTestFile("ascii", method);
var arrayAscii = matFile["s"].Value as ICharArray;
Assert.NotNull(arrayAscii);
Assert.Equal(new[] { 1, 3 }, arrayAscii.Dimensions);
@ -116,10 +113,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a Unicode string.
/// </summary>
[Fact]
public void TestUnicode()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestUnicode(MatFileReadingMethod method)
{
var matFile = GetTests("good")["unicode"];
var matFile = ReadTestFile("unicode", method);
var arrayUnicode = matFile["s"].Value as ICharArray;
Assert.NotNull(arrayUnicode);
Assert.Equal(new[] { 1, 2 }, arrayUnicode.Dimensions);
@ -131,10 +128,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a wide Unicode string.
/// </summary>
[Fact]
public void TestUnicodeWide()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestUnicodeWide(MatFileReadingMethod method)
{
var matFile = GetTests("good")["unicode-wide"];
var matFile = ReadTestFile("unicode-wide", method);
var arrayUnicodeWide = matFile["s"].Value as ICharArray;
Assert.NotNull(arrayUnicodeWide);
Assert.Equal(new[] { 1, 2 }, arrayUnicodeWide.Dimensions);
@ -144,10 +141,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test converting a structure array to a Double array.
/// </summary>
[Fact]
public void TestConvertToDoubleArray()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestConvertToDoubleArray(MatFileReadingMethod method)
{
var matFile = GetTests("good")["struct"];
var matFile = ReadTestFile("struct", method);
var array = matFile.Variables[0].Value;
Assert.Null(array.ConvertToDoubleArray());
}
@ -156,10 +153,10 @@ namespace MatFileHandler.Tests
/// Test converting a structure array to a Complex array.
/// </summary>
/// <returns>Should return null.</returns>
[Fact]
public void TestConvertToComplexArray()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestConvertToComplexArray(MatFileReadingMethod method)
{
var matFile = GetTests("good")["struct"];
var matFile = ReadTestFile("struct", method);
var array = matFile.Variables[0].Value;
Assert.Null(array.ConvertToComplexArray());
}
@ -167,10 +164,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading an enumeration.
/// </summary>
[Fact]
public void TestEnum()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestEnum(MatFileReadingMethod method)
{
var matFile = GetTests("good")["enum"];
var matFile = ReadTestFile("enum", method);
var days = matFile["days"].Value;
var enumeration = new EnumAdapter(days);
Assert.Equal(5, enumeration.Values.Count);
@ -184,10 +181,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a structure array.
/// </summary>
[Fact]
public void TestStruct()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestStruct(MatFileReadingMethod method)
{
var matFile = GetTests("good")["struct"];
var matFile = ReadTestFile("struct", method);
var structure = matFile["struct_"].Value as IStructureArray;
Assert.NotNull(structure);
Assert.Equal(new[] { "x", "y" }, structure.FieldNames);
@ -238,10 +235,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a sparse array.
/// </summary>
[Fact]
public void TestSparse()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestSparse(MatFileReadingMethod method)
{
var matFile = GetTests("good")["sparse"];
var matFile = ReadTestFile("sparse", method);
var sparseArray = matFile["sparse_"].Value as ISparseArrayOf<double>;
Assert.NotNull(sparseArray);
Assert.Equal(new[] { 4, 5 }, sparseArray.Dimensions);
@ -268,10 +265,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a logical array.
/// </summary>
[Fact]
public void TestLogical()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestLogical(MatFileReadingMethod method)
{
var matFile = GetTests("good")["logical"];
var matFile = ReadTestFile("logical", method);
var array = matFile["logical_"].Value;
var logicalArray = array as IArrayOf<bool>;
Assert.NotNull(logicalArray);
@ -286,10 +283,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a sparse logical array.
/// </summary>
[Fact]
public void TestSparseLogical()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestSparseLogical(MatFileReadingMethod method)
{
var matFile = GetTests("good")["sparse_logical"];
var matFile = ReadTestFile("sparse_logical", method);
var array = matFile["sparse_logical"].Value;
var sparseArray = array as ISparseArrayOf<bool>;
Assert.NotNull (sparseArray);
@ -305,10 +302,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a global variable.
/// </summary>
[Fact]
public void TestGlobal()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestGlobal(MatFileReadingMethod method)
{
var matFile = GetTests("good")["global"];
var matFile = ReadTestFile("global", method);
var variable = matFile.Variables.First();
Assert.True(variable.IsGlobal);
}
@ -316,10 +313,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a sparse complex array.
/// </summary>
[Fact]
public void TextSparseComplex()
[Theory, MemberData(nameof(TestDataFactories))]
public void TextSparseComplex(MatFileReadingMethod method)
{
var matFile = GetTests("good")["sparse_complex"];
var matFile = ReadTestFile("sparse_complex", method);
var array = matFile["sparse_complex"].Value;
var sparseArray = array as ISparseArrayOf<Complex>;
Assert.NotNull(sparseArray);
@ -332,10 +329,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading an object.
/// </summary>
[Fact]
public void TestObject()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestObject(MatFileReadingMethod method)
{
var matFile = GetTests("good")["object"];
var matFile = ReadTestFile("object", method);
var obj = matFile["object_"].Value as IMatObject;
Assert.NotNull(obj);
Assert.Equal("Point", obj.ClassName);
@ -349,10 +346,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading another object.
/// </summary>
[Fact]
public void TestObject2()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestObject2(MatFileReadingMethod method)
{
var matFile = GetTests("good")["object2"];
var matFile = ReadTestFile("object2", method);
var obj = matFile["object2"].Value as IMatObject;
Assert.NotNull(obj);
Assert.Equal("Point", obj.ClassName);
@ -372,10 +369,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test reading a table.
/// </summary>
[Fact]
public void TestTable()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestTable(MatFileReadingMethod method)
{
var matFile = GetTests("good")["table"];
var matFile = ReadTestFile("table", method);
var obj = matFile["table_"].Value as IMatObject;
var table = new TableAdapter(obj);
Assert.Equal(3, table.NumberOfRows);
@ -390,13 +387,37 @@ namespace MatFileHandler.Tests
Assert.Equal(new[] { 1.0, 3.0, 5.0, 2.0, 4.0, 6.0 }, variable2.ConvertToDoubleArray());
}
/// <summary>
/// Test reading a deeply nested table.
/// </summary>
[Theory, MemberData(nameof(TestDataFactories))]
public void TestDeepTable(MatFileReadingMethod method)
{
var matFile = ReadTestFile("table-deep", method);
var obj = matFile["t"].Value as IMatObject;
var table = new TableAdapter(obj);
Assert.Equal(1, table.NumberOfRows);
Assert.Equal(2, table.NumberOfVariables);
Assert.Equal(new[] { "s", "another" }, table.VariableNames);
var s = table["s"] as IStructureArray;
Assert.Equal(new[] { "a", "b", "c" }, s.FieldNames);
var c = s["c", 0];
var internalTable = new TableAdapter(c);
Assert.Equal(2, internalTable.NumberOfRows);
Assert.Equal(2, internalTable.NumberOfVariables);
Assert.Equal(new[] { "x", "y" }, internalTable.VariableNames);
var y = new StringAdapter(internalTable["y"]);
Assert.Equal("3", y[0]);
Assert.Equal("abc", y[1]);
}
/// <summary>
/// Test reading a table with strings
/// </summary>
[Fact]
public void TestTableWithStrings()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestTableWithStrings(MatFileReadingMethod method)
{
var matFile = GetTests("good")["table-with-strings"];
var matFile = ReadTestFile("table-with-strings", method);
var obj = matFile["t"].Value as IMatObject;
var table = new TableAdapter(obj);
Assert.Equal(5, table.NumberOfRows);
@ -418,10 +439,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test subobjects within objects.
/// </summary>
[Fact]
public void TestSubobjects()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestSubobjects(MatFileReadingMethod method)
{
var matFile = GetTests("good")["pointWithSubpoints"];
var matFile = ReadTestFile("pointWithSubpoints", method);
var p = matFile["p"].Value as IMatObject;
Assert.Equal("Point", p.ClassName);
var x = p["x"] as IMatObject;
@ -441,10 +462,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test nested objects.
/// </summary>
[Fact]
public void TestNestedObjects()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestNestedObjects(MatFileReadingMethod method)
{
var matFile = GetTests("good")["subsubPoint"];
var matFile = ReadTestFile("subsubPoint", method);
var p = matFile["p"].Value as IMatObject;
Assert.Equal("Point", p.ClassName);
Assert.Equal(new[] { 1.0 }, p["x"].ConvertToDoubleArray());
@ -459,10 +480,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test datetime objects.
/// </summary>
[Fact]
public void TestDatetime()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestDatetime(MatFileReadingMethod method)
{
var matFile = GetTests("good")["datetime"];
var matFile = ReadTestFile("datetime", method);
var d = matFile["d"].Value as IMatObject;
var datetime = new DatetimeAdapter(d);
Assert.Equal(new[] { 1, 2 }, datetime.Dimensions);
@ -473,10 +494,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Another test for datetime objects.
/// </summary>
[Fact]
public void TestDatetime2()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestDatetime2(MatFileReadingMethod method)
{
var matFile = GetTests("good")["datetime2"];
var matFile = ReadTestFile("datetime2", method);
var d = matFile["d"].Value as IMatObject;
var datetime = new DatetimeAdapter(d);
Assert.Equal(new[] { 1, 1 }, datetime.Dimensions);
@ -488,10 +509,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test string objects.
/// </summary>
[Fact]
public void TestString()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestString(MatFileReadingMethod method)
{
var matFile = GetTests("good")["string"];
var matFile = ReadTestFile("string", method);
var s = matFile["s"].Value as IMatObject;
var str = new StringAdapter(s);
Assert.Equal(new[] { 4, 1 }, str.Dimensions);
@ -504,10 +525,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test duration objects.
/// </summary>
[Fact]
public void TestDuration()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestDuration(MatFileReadingMethod method)
{
var matFile = GetTests("good")["duration"];
var matFile = ReadTestFile("duration", method);
var d = matFile["d"].Value as IMatObject;
var duration = new DurationAdapter(d);
Assert.Equal(new[] { 1, 3 }, duration.Dimensions);
@ -519,10 +540,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test unrepresentable datetime.
/// </summary>
[Fact]
public void TestDatetime_Unrepresentable()
[Theory, MemberData(nameof(TestDataFactories))]
public void TestDatetime_Unrepresentable(MatFileReadingMethod method)
{
var matFile = GetTests("good")["datetime-unrepresentable"];
var matFile = ReadTestFile("datetime-unrepresentable", method);
var obj = matFile["d"].Value as IMatObject;
var datetime = new DatetimeAdapter(obj);
var d0 = datetime[0];
@ -532,10 +553,10 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test 3-dimensional arrays.
/// </summary>
[Fact]
public void Test_3DArrays()
[Theory, MemberData(nameof(TestDataFactories))]
public void Test_3DArrays(MatFileReadingMethod method)
{
var matFile = GetTests("good")["issue20.mat"];
var matFile = ReadTestFile("issue20", method);
var obj = matFile["a3d"].Value;
var values = obj.ConvertToDoubleArray();
Assert.Equal(Enumerable.Range(1, 24).Select(x => (double)x).ToArray(), values);
@ -567,17 +588,47 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test four-dimensional arrays.
/// </summary>
[Fact]
public void Test_4DArrays()
[Theory, MemberData(nameof(TestDataFactories))]
public void Test4DArrays(MatFileReadingMethod method)
{
var matFile = GetTests("good")["issue20.mat"];
var matFile = ReadTestFile("issue20", method);
var obj = matFile["a4d"].Value;
Assert.Equal(Enumerable.Range(1, 120).Select(x => (double)x).ToArray(), obj.ConvertToDoubleArray());
Assert.Null(obj.ConvertTo2dDoubleArray());
}
private static AbstractTestDataFactory<IMatFile> GetTests(string factoryName) =>
new PartialReadMatTestDataFactory(Path.Combine(TestDirectory, factoryName));
/// <summary>
/// Returns the factories that provide test data in various configurations.
/// </summary>
public static TheoryData<MatFileReadingMethod> TestDataFactories
{
get
{
return new TheoryData<MatFileReadingMethod>
{
MatFileReadingMethod.NormalStream,
MatFileReadingMethod.PartialStream,
MatFileReadingMethod.UnalignedStream,
};
}
}
private static IMatFile ReadTestFile(string fileName, MatFileReadingMethod method)
{
var fullFileName = Path.Combine("test-data", "good", $"{fileName}.mat");
return MatFileReadingMethods.ReadMatFile(method, fullFileName);
}
private static IEnumerable<IMatFile> ReadAllTestFiles(MatFileReadingMethod method)
{
foreach (var fileName in Directory.EnumerateFiles(
Path.Combine("test-data", "good"),
"*.mat"))
{
var fullFileName = fileName;
yield return MatFileReadingMethods.ReadMatFile(method, fullFileName);
}
}
private static void CheckLimits<T>(IArrayOf<T> array, T[] limits)
where T : struct

View File

@ -0,0 +1,27 @@
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

@ -0,0 +1,38 @@
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

@ -0,0 +1,27 @@
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,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.IO;
using System.Numerics;
@ -17,8 +15,8 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test writing a simple Double array.
/// </summary>
[Fact]
public void TestWrite()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestWrite(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var array = builder.NewArray<double>(1, 2);
@ -26,7 +24,7 @@ namespace MatFileHandler.Tests
array[1] = 17.0;
var variable = builder.NewVariable("test", array);
var actual = builder.NewFile(new[] { variable });
MatCompareWithTestData("good", "double-array", actual);
MatCompareWithTestData("good", "double-array", actual, method, options);
}
/// <summary>
@ -51,8 +49,8 @@ namespace MatFileHandler.Tests
/// <summary>
/// Test writing lower and upper limits of integer data types.
/// </summary>
[Fact]
public void TestLimits()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestLimits(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2));
@ -64,14 +62,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);
MatCompareWithTestData("good", "limits", actual, method, options);
}
/// <summary>
/// Test writing lower and upper limits of integer-based complex data types.
/// </summary>
[Fact]
public void TestLimitsComplex()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestLimitsComplex(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var int8Complex = builder.NewVariable(
@ -103,26 +101,26 @@ namespace MatFileHandler.Tests
int16Complex, int32Complex, int64Complex, int8Complex,
uint16Complex, uint32Complex, uint64Complex, uint8Complex,
});
MatCompareWithTestData("good", "limits_complex", actual);
MatCompareWithTestData("good", "limits_complex", actual, method, options);
}
/// <summary>
/// Test writing a wide-Unicode symbol.
/// </summary>
[Fact]
public void TestUnicodeWide()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestUnicodeWide(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var s = builder.NewVariable("s", builder.NewCharArray("🍆"));
var actual = builder.NewFile(new[] { s });
MatCompareWithTestData("good", "unicode-wide", actual);
MatCompareWithTestData("good", "unicode-wide", actual, method, options);
}
/// <summary>
/// Test writing a sparse array.
/// </summary>
[Fact]
public void TestSparseArray()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestSparseArray(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var sparseArray = builder.NewSparseArray<double>(4, 5);
@ -132,14 +130,14 @@ namespace MatFileHandler.Tests
sparseArray[2, 3] = 4;
var sparse = builder.NewVariable("sparse_", sparseArray);
var actual = builder.NewFile(new[] { sparse });
MatCompareWithTestData("good", "sparse", actual);
MatCompareWithTestData("good", "sparse", actual, method, options);
}
/// <summary>
/// Test writing a structure array.
/// </summary>
[Fact]
public void TestStructure()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestStructure(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3);
@ -160,27 +158,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);
MatCompareWithTestData("good", "struct", actual, method, options);
}
/// <summary>
/// Test writing a logical array.
/// </summary>
[Fact]
public void TestLogical()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestLogical(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
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);
MatCompareWithTestData("good", "logical", actual, method, options);
}
/// <summary>
/// Test writing a sparse logical array.
/// </summary>
[Fact]
public void TestSparseLogical()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestSparseLogical(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var array = builder.NewSparseArray<bool>(2, 3);
@ -190,14 +188,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);
MatCompareWithTestData("good", "sparse_logical", actual, method, options);
}
/// <summary>
/// Test writing a sparse complex array.
/// </summary>
[Fact]
public void TestSparseComplex()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestSparseComplex(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
var builder = new DataBuilder();
var array = builder.NewSparseArray<Complex>(2, 2);
@ -206,26 +204,48 @@ 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);
MatCompareWithTestData("good", "sparse_complex", actual, method, options);
}
/// <summary>
/// Test writing a global variable.
/// </summary>
[Fact]
public void TestGlobal()
[Theory, MemberData(nameof(MatFileWritingTestData))]
public void TestGlobal(MatFileWritingMethod method, MatFileWriterOptionsForTests options)
{
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);
MatCompareWithTestData("good", "global", actual, method, options);
}
private static AbstractTestDataFactory<IMatFile> GetMatTestData(string factoryName) =>
new MatTestDataFactory(Path.Combine(TestDirectory, factoryName));
/// <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 void CompareSparseArrays<T>(ISparseArrayOf<T> expected, ISparseArrayOf<T> actual)
private static void CompareSparseArrays<T>(ISparseArrayOf<T> expected, ISparseArrayOf<T> actual)
where T : struct
{
Assert.NotNull(actual);
@ -233,7 +253,7 @@ namespace MatFileHandler.Tests
Assert.Equal(expected.Data, actual.Data);
}
private void CompareStructureArrays(IStructureArray expected, IStructureArray actual)
private static void CompareStructureArrays(IStructureArray expected, IStructureArray actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
@ -247,7 +267,7 @@ namespace MatFileHandler.Tests
}
}
private void CompareCellArrays(ICellArray expected, ICellArray actual)
private static void CompareCellArrays(ICellArray expected, ICellArray actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
@ -257,21 +277,21 @@ namespace MatFileHandler.Tests
}
}
private void CompareNumericalArrays<T>(IArrayOf<T> expected, IArrayOf<T> actual)
private static void CompareNumericalArrays<T>(IArrayOf<T> expected, IArrayOf<T> actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.Equal(expected.Data, actual.Data);
}
private void CompareCharArrays(ICharArray expected, ICharArray actual)
private static void CompareCharArrays(ICharArray expected, ICharArray actual)
{
Assert.NotNull(actual);
Assert.Equal(expected.Dimensions, actual.Dimensions);
Assert.Equal(expected.String, actual.String);
}
private void CompareMatArrays(IArray expected, IArray actual)
private static void CompareMatArrays(IArray expected, IArray actual)
{
switch (expected)
{
@ -362,7 +382,7 @@ namespace MatFileHandler.Tests
throw new NotSupportedException();
}
private void CompareMatFiles(IMatFile expected, IMatFile actual)
private static void CompareMatFiles(IMatFile expected, IMatFile actual)
{
Assert.Equal(expected.Variables.Length, actual.Variables.Length);
for (var i = 0; i < expected.Variables.Length; i++)
@ -375,44 +395,26 @@ namespace MatFileHandler.Tests
}
}
private void CompareTestDataWithWritingOptions(
IMatFile expected,
private static void MatCompareWithTestData(
string factoryName,
string testName,
IMatFile actual,
MatFileWriterOptions? maybeOptions)
MatFileWritingMethod method,
MatFileWriterOptionsForTests options)
{
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);
}
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);
}
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
private static 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

@ -0,0 +1,27 @@
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

@ -0,0 +1,58 @@
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

@ -1,35 +0,0 @@
// Copyright 2017-2018 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,35 +0,0 @@
// Copyright 2017-2018 Alexander Luzgarev
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// Factory providing the parsed contents of .mat files,
/// wrapped in a <see cref="PartialReadStream"/>.
/// </summary>
public class PartialReadMatTestDataFactory : MatTestDataFactory
{
/// <summary>
/// Initializes a new instance of the <see cref="PartialReadMatTestDataFactory"/> class.
/// </summary>
/// <param name="testDirectory">Directory containing test files.</param>
public PartialReadMatTestDataFactory(string testDirectory)
: base(testDirectory)
{
}
/// <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)
{
using (var wrapper = new PartialReadStream(stream))
{
return base.ReadDataFromStream(wrapper);
}
}
}
}

View File

@ -1,22 +1,21 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.IO;
namespace MatFileHandler.Tests
{
/// <summary>
/// A stream which wraps another stream and only reads one byte at a time.
/// A stream which wraps another stream and only reads one byte at a time,
/// while forbidding seeking in it.
/// </summary>
internal class PartialReadStream : Stream
internal sealed class PartialUnseekableReadStream : Stream
{
private readonly Stream _baseStream;
/// <summary>
/// Initializes a new instance of the <see cref="PartialReadStream"/> class.
/// Initializes a new instance of the <see cref="PartialUnseekableReadStream"/> class.
/// </summary>
/// <param name="baseStream">The stream to wrap.</param>
public PartialReadStream(Stream baseStream)
public PartialUnseekableReadStream(Stream baseStream)
{
_baseStream = baseStream;
}
@ -25,7 +24,7 @@ namespace MatFileHandler.Tests
public override bool CanRead => _baseStream.CanRead;
/// <inheritdoc/>
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanSeek => false;
/// <inheritdoc/>
public override bool CanWrite => false;
@ -36,8 +35,8 @@ namespace MatFileHandler.Tests
/// <inheritdoc/>
public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
/// <inheritdoc/>
@ -55,13 +54,13 @@ namespace MatFileHandler.Tests
/// <inheritdoc/>
public override long Seek(long offset, SeekOrigin origin)
{
return _baseStream.Seek(offset, origin);
throw new NotSupportedException();
}
/// <inheritdoc/>
public override void SetLength(long value)
{
_baseStream.SetLength(value);
throw new NotSupportedException();
}
/// <inheritdoc/>

View File

@ -0,0 +1,68 @@
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.

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler

View File

@ -0,0 +1,92 @@
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,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -8,7 +6,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 struct ComplexOf<T> : IEquatable<ComplexOf<T>>
public readonly struct ComplexOf<T> : IEquatable<ComplexOf<T>>
where T : struct
{
/// <summary>
@ -71,10 +69,11 @@ namespace MatFileHandler
/// <returns>True iff another object is a complex number equal to this.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
if (obj is null)
{
return false;
}
return obj is ComplexOf<T> other && Equals(other);
}

View File

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

View File

@ -1,10 +1,9 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
#pragma warning disable CA1822
namespace MatFileHandler
{
/// <summary>
@ -52,7 +51,7 @@ namespace MatFileHandler
{
if (data.Length != dimensions.NumberOfElements())
{
throw new ArgumentException("Data size does not match the specified dimensions", "data");
throw new ArgumentException("Data size does not match the specified dimensions", nameof(data));
}
return new MatNumericalArrayOf<T>(GetStandardFlags<T>(), dimensions, string.Empty, data);
}
@ -157,7 +156,7 @@ namespace MatFileHandler
return new MatFile(variables);
}
private ArrayFlags ConstructArrayFlags(ArrayType class_, bool isComplex = false, bool isLogical = false)
private static ArrayFlags ConstructArrayFlags(ArrayType class_, bool isComplex = false, bool isLogical = false)
{
return new ArrayFlags
{
@ -167,7 +166,7 @@ namespace MatFileHandler
};
}
private ArrayFlags GetStandardFlags<T>()
private static ArrayFlags GetStandardFlags<T>()
{
if (typeof(T) == typeof(sbyte))
{

View File

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

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
@ -71,16 +69,21 @@ namespace MatFileHandler
{
throw new NotSupportedException("Only 2-dimensional sparse arrays are supported");
}
if (data == null)
if (data is null)
{
throw new ArgumentException("Null data found.", "data");
throw new ArgumentException("Null data found.", nameof(data));
}
var elements =
var maybeElements =
ConvertDataToSparseProperType<T>(data, flags.ArrayFlags.Variable.HasFlag(Variable.IsLogical));
if (elements == null)
if (maybeElements is not { } elements)
{
throw new HandlerException("Couldn't read sparse array.");
}
var dataDictionary =
ConvertMatlabSparseToDictionary(rowIndex, columnIndex, j => elements[j]);
return new MatSparseArrayOf<T>(flags, dimensions, name, dataDictionary);
@ -117,15 +120,12 @@ namespace MatFileHandler
switch (flags.Class)
{
case ArrayType.MxChar:
switch (realData)
return realData switch
{
case MiNum<byte> dataByte:
return ConvertToMatCharArray(flags, dimensions, name, dataByte);
case MiNum<ushort> dataUshort:
return ConvertToMatCharArray(flags, dimensions, name, dataUshort);
default:
throw new NotSupportedException("Only utf8, utf16 or ushort char arrays are supported.");
}
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 ArrayType.MxDouble:
case ArrayType.MxSingle:
case ArrayType.MxInt8:
@ -184,13 +184,12 @@ namespace MatFileHandler
{
return DataExtraction.GetDataAsUInt8(data).Select(x => x != 0).ToArray() as T[];
}
switch (data)
return data switch
{
case MiNum<double> _:
return DataExtraction.GetDataAsDouble(data) as T[];
default:
throw new NotSupportedException();
}
MiNum<double> => DataExtraction.GetDataAsDouble(data) as T[],
_ => throw new NotSupportedException(),
};
}
private static MatCharArrayOf<ushort> ConvertToMatCharArray(

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
@ -30,54 +28,33 @@ namespace MatFileHandler
/// </summary>
/// <param name="reader">Input reader.</param>
/// <returns>Data element.</returns>
public DataElement Read(BinaryReader reader)
public DataElement? Read(BinaryReader reader)
{
var (dataReader, tag) = ReadTag(reader);
DataElement result;
switch (tag.Type)
var maybeTagPair = ReadTag(reader);
if (maybeTagPair is not { } tagPair)
{
case DataType.MiInt8:
result = ReadNum<sbyte>(tag, dataReader);
break;
case DataType.MiUInt8:
case DataType.MiUtf8:
result = ReadNum<byte>(tag, dataReader);
break;
case DataType.MiInt16:
result = ReadNum<short>(tag, dataReader);
break;
case DataType.MiUInt16:
case DataType.MiUtf16:
result = ReadNum<ushort>(tag, dataReader);
break;
case DataType.MiInt32:
result = ReadNum<int>(tag, dataReader);
break;
case DataType.MiUInt32:
result = ReadNum<uint>(tag, dataReader);
break;
case DataType.MiSingle:
result = ReadNum<float>(tag, dataReader);
break;
case DataType.MiDouble:
result = ReadNum<double>(tag, dataReader);
break;
case DataType.MiInt64:
result = ReadNum<long>(tag, dataReader);
break;
case DataType.MiUInt64:
result = ReadNum<ulong>(tag, dataReader);
break;
case DataType.MiMatrix:
result = ReadMatrix(tag, dataReader);
break;
case DataType.MiCompressed:
result = ReadCompressed(tag, dataReader);
break;
default:
throw new NotSupportedException("Unknown element.");
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."),
};
if (tag.Type != DataType.MiCompressed)
{
var position = reader.BaseStream.Position;
@ -122,7 +99,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
@ -194,9 +171,31 @@ namespace MatFileHandler
};
}
private static (BinaryReader reader, Tag tag) ReadTag(BinaryReader reader)
private static int? TryReadInt32(BinaryReader reader)
{
var type = reader.ReadInt32();
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 typeHi = type >> 16;
if (typeHi == 0)
{
@ -206,13 +205,13 @@ namespace MatFileHandler
else
{
var length = typeHi;
type = type & 0xffff;
type &= 0xffff;
var smallReader = new BinaryReader(new MemoryStream(reader.ReadBytes(4)));
return (smallReader, new Tag((DataType)type, length));
}
}
private DataElement ContinueReadingCellArray(
private MatCellArray ContinueReadingCellArray(
BinaryReader reader,
ArrayFlags flags,
int[] dimensions,
@ -241,7 +240,7 @@ namespace MatFileHandler
var classNameElement = Read(reader) as MiNum<sbyte> ??
throw new HandlerException("Unexpected type in class name.");
var className = ReadName(classNameElement);
var dataElement = Read(reader);
var dataElement = Read(reader) ?? throw new HandlerException("Missing opaque data element.");
var data = ReadData(dataElement);
if (data is MatNumericalArrayOf<uint> linkElement)
{
@ -258,11 +257,11 @@ namespace MatFileHandler
}
else
{
return new Opaque(name, typeDescription, className, new int[] { }, data, subsystemData);
return new Opaque(name, typeDescription, className, Array.Empty<int>(), data, subsystemData);
}
}
private DataElement ContinueReadingSparseArray(
private MatArray ContinueReadingSparseArray(
BinaryReader reader,
DataElement firstElement,
int[] dimensions,
@ -273,7 +272,7 @@ namespace MatFileHandler
throw new HandlerException("Unexpected type in row indices of a sparse array.");
var columnIndex = Read(reader) as MiNum<int> ??
throw new HandlerException("Unexpected type in column indices of a sparse array.");
var data = Read(reader);
var data = Read(reader) ?? throw new HandlerException("Missing sparse array data.");
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsLogical))
{
return DataElementConverter.ConvertToMatSparseArrayOf<bool>(
@ -287,7 +286,7 @@ namespace MatFileHandler
if (sparseArrayFlags.ArrayFlags.Variable.HasFlag(Variable.IsComplex))
{
var imaginaryData = Read(reader);
var imaginaryData = Read(reader) ?? throw new HandlerException("Missing imaginary part of sparse array data.");
return DataElementConverter.ConvertToMatSparseArrayOfComplex(
sparseArrayFlags,
dimensions,
@ -298,22 +297,20 @@ namespace MatFileHandler
imaginaryData);
}
switch (data)
return data switch
{
case MiNum<double> _:
return DataElementConverter.ConvertToMatSparseArrayOf<double>(
sparseArrayFlags,
dimensions,
name,
rowIndex.Data,
columnIndex.Data,
data);
default:
throw new NotSupportedException("Only double and logical sparse arrays are supported.");
}
MiNum<double> => DataElementConverter.ConvertToMatSparseArrayOf<double>(
sparseArrayFlags,
dimensions,
name,
rowIndex.Data,
columnIndex.Data,
data),
_ => throw new NotSupportedException("Only double and logical sparse arrays are supported."),
};
}
private DataElement ContinueReadingStructure(
private MatStructureArray ContinueReadingStructure(
BinaryReader reader,
ArrayFlags flags,
int[] dimensions,
@ -343,30 +340,33 @@ namespace MatFileHandler
return new MatStructureArray(flags, dimensions, name, fields);
}
private DataElement Read(Stream stream)
{
using (var reader = new BinaryReader(stream))
{
return Read(reader);
}
}
private DataElement ReadCompressed(Tag tag, BinaryReader reader)
{
reader.ReadBytes(2);
var compressedData = reader.ReadBytes(tag.Length - 6);
reader.ReadBytes(4);
var resultStream = new MemoryStream();
using (var compressedStream = new MemoryStream(compressedData))
DataElement element;
using (var substream = new Substream(reader.BaseStream, tag.Length - 6))
{
using (var stream = new DeflateStream(compressedStream, CompressionMode.Decompress, leaveOpen: true))
using (var deflateStream = new DeflateStream(substream, CompressionMode.Decompress))
using (var bufferedStream = new BufferedStream(deflateStream))
using (var positionTrackingStream = new PositionTrackingStream(bufferedStream))
using (var innerReader = new BinaryReader(positionTrackingStream))
{
stream.CopyTo(resultStream);
element = Read(innerReader) ?? 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));
}
}
resultStream.Position = 0;
return Read(resultStream);
reader.ReadBytes(4);
return element;
}
private DataElement ReadMatrix(Tag tag, BinaryReader reader)
@ -376,7 +376,7 @@ namespace MatFileHandler
return MatArray.Empty();
}
var element1 = Read(reader);
var element1 = Read(reader) ?? throw new HandlerException("Missing matrix data.");
var flags = ReadArrayFlags(element1);
if (flags.Class == ArrayType.MxOpaque)
{
@ -398,12 +398,12 @@ namespace MatFileHandler
return ContinueReadingSparseArray(reader, element1, dimensions, name);
}
var element4 = Read(reader);
var element4 = Read(reader) ?? throw new HandlerException("Missing matrix data.");
var data = ReadData(element4);
DataElement? imaginaryData = null;
if (flags.Variable.HasFlag(Variable.IsComplex))
{
var element5 = Read(reader);
var element5 = Read(reader) ?? throw new HandlerException("Missing complex matrix data.");
imaginaryData = ReadData(element5);
}
@ -418,26 +418,23 @@ namespace MatFileHandler
switch (flags.Class)
{
case ArrayType.MxChar:
switch (data)
return data switch
{
case MiNum<byte> _:
return DataElementConverter.ConvertToMatNumericalArrayOf<byte>(
flags,
dimensions,
name,
data,
imaginaryData);
case MiNum<ushort> _:
return DataElementConverter.ConvertToMatNumericalArrayOf<ushort>(
flags,
dimensions,
name,
data,
imaginaryData);
default:
throw new NotSupportedException(
$"This type of char array ({data.GetType()}) is not supported.");
}
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 ArrayType.MxInt8:
return DataElementConverter.ConvertToMatNumericalArrayOf<sbyte>(
flags,

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -16,31 +14,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to Double.</returns>
public static double[] GetDataAsDouble(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToDouble(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToDouble(byteElement.Data);
case MiNum<int> intElement:
return IntToDouble(intElement.Data);
case MiNum<uint> uintElement:
return UintToDouble(uintElement.Data);
case MiNum<short> shortElement:
return ShortToDouble(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToDouble(ushortElement.Data);
case MiNum<long> longElement:
return LongToDouble(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToDouble(ulongElement.Data);
case MiNum<float> floatElement:
return FloatToDouble(floatElement.Data);
case MiNum<double> doubleElement:
return doubleElement.Data;
}
throw new HandlerException(
$"Expected data element that would be convertible to double, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToDouble(sbyteElement.Data),
MiNum<byte> byteElement => ByteToDouble(byteElement.Data),
MiNum<int> intElement => IntToDouble(intElement.Data),
MiNum<uint> uintElement => UintToDouble(uintElement.Data),
MiNum<short> shortElement => ShortToDouble(shortElement.Data),
MiNum<ushort> ushortElement => UshortToDouble(ushortElement.Data),
MiNum<long> longElement => LongToDouble(longElement.Data),
MiNum<ulong> ulongElement => UlongToDouble(ulongElement.Data),
MiNum<float> floatElement => FloatToDouble(floatElement.Data),
MiNum<double> doubleElement => doubleElement.Data,
_ => throw new HandlerException(
$"Expected data element that would be convertible to double, found {element.GetType()}."),
};
}
/// <summary>
@ -50,31 +38,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to Single.</returns>
public static float[] GetDataAsSingle(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToSingle(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToSingle(byteElement.Data);
case MiNum<int> intElement:
return IntToSingle(intElement.Data);
case MiNum<uint> uintElement:
return UintToSingle(uintElement.Data);
case MiNum<short> shortElement:
return ShortToSingle(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToSingle(ushortElement.Data);
case MiNum<long> longElement:
return LongToSingle(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToSingle(ulongElement.Data);
case MiNum<float> floatElement:
return floatElement.Data;
case MiNum<double> doubleElement:
return DoubleToSingle(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to float, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToSingle(sbyteElement.Data),
MiNum<byte> byteElement => ByteToSingle(byteElement.Data),
MiNum<int> intElement => IntToSingle(intElement.Data),
MiNum<uint> uintElement => UintToSingle(uintElement.Data),
MiNum<short> shortElement => ShortToSingle(shortElement.Data),
MiNum<ushort> ushortElement => UshortToSingle(ushortElement.Data),
MiNum<long> longElement => LongToSingle(longElement.Data),
MiNum<ulong> ulongElement => UlongToSingle(ulongElement.Data),
MiNum<float> floatElement => floatElement.Data,
MiNum<double> doubleElement => DoubleToSingle(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to float, found {element.GetType()}."),
};
}
/// <summary>
@ -84,31 +62,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to Int8.</returns>
public static sbyte[] GetDataAsInt8(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return sbyteElement.Data;
case MiNum<byte> byteElement:
return ByteToSByte(byteElement.Data);
case MiNum<int> intElement:
return IntToSByte(intElement.Data);
case MiNum<uint> uintElement:
return UintToSByte(uintElement.Data);
case MiNum<short> shortElement:
return ShortToSByte(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToSByte(ushortElement.Data);
case MiNum<long> longElement:
return LongToSByte(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToSByte(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToSByte(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToSByte(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to int8, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => sbyteElement.Data,
MiNum<byte> byteElement => ByteToSByte(byteElement.Data),
MiNum<int> intElement => IntToSByte(intElement.Data),
MiNum<uint> uintElement => UintToSByte(uintElement.Data),
MiNum<short> shortElement => ShortToSByte(shortElement.Data),
MiNum<ushort> ushortElement => UshortToSByte(ushortElement.Data),
MiNum<long> longElement => LongToSByte(longElement.Data),
MiNum<ulong> ulongElement => UlongToSByte(ulongElement.Data),
MiNum<float> floatElement => SingleToSByte(floatElement.Data),
MiNum<double> doubleElement => DoubleToSByte(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to int8, found {element.GetType()}."),
};
}
/// <summary>
@ -118,31 +86,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to UInt8.</returns>
public static byte[] GetDataAsUInt8(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToByte(sbyteElement.Data);
case MiNum<byte> byteElement:
return byteElement.Data;
case MiNum<int> intElement:
return IntToByte(intElement.Data);
case MiNum<uint> uintElement:
return UintToByte(uintElement.Data);
case MiNum<short> shortElement:
return ShortToByte(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToByte(ushortElement.Data);
case MiNum<long> longElement:
return LongToByte(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToByte(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToByte(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToByte(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint8, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToByte(sbyteElement.Data),
MiNum<byte> byteElement => byteElement.Data,
MiNum<int> intElement => IntToByte(intElement.Data),
MiNum<uint> uintElement => UintToByte(uintElement.Data),
MiNum<short> shortElement => ShortToByte(shortElement.Data),
MiNum<ushort> ushortElement => UshortToByte(ushortElement.Data),
MiNum<long> longElement => LongToByte(longElement.Data),
MiNum<ulong> ulongElement => UlongToByte(ulongElement.Data),
MiNum<float> floatElement => SingleToByte(floatElement.Data),
MiNum<double> doubleElement => DoubleToByte(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to uint8, found {element.GetType()}."),
};
}
/// <summary>
@ -152,31 +110,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to Int16.</returns>
public static short[] GetDataAsInt16(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToInt16(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToInt16(byteElement.Data);
case MiNum<int> intElement:
return IntToInt16(intElement.Data);
case MiNum<uint> uintElement:
return UintToInt16(uintElement.Data);
case MiNum<short> shortElement:
return shortElement.Data;
case MiNum<ushort> ushortElement:
return UshortToInt16(ushortElement.Data);
case MiNum<long> longElement:
return LongToInt16(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToInt16(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToInt16(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToInt16(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to int16, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToInt16(sbyteElement.Data),
MiNum<byte> byteElement => ByteToInt16(byteElement.Data),
MiNum<int> intElement => IntToInt16(intElement.Data),
MiNum<uint> uintElement => UintToInt16(uintElement.Data),
MiNum<short> shortElement => shortElement.Data,
MiNum<ushort> ushortElement => UshortToInt16(ushortElement.Data),
MiNum<long> longElement => LongToInt16(longElement.Data),
MiNum<ulong> ulongElement => UlongToInt16(ulongElement.Data),
MiNum<float> floatElement => SingleToInt16(floatElement.Data),
MiNum<double> doubleElement => DoubleToInt16(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to int16, found {element.GetType()}."),
};
}
/// <summary>
@ -186,31 +134,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to UInt16.</returns>
public static ushort[] GetDataAsUInt16(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToUInt16(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToUInt16(byteElement.Data);
case MiNum<int> intElement:
return IntToUInt16(intElement.Data);
case MiNum<uint> uintElement:
return UintToUInt16(uintElement.Data);
case MiNum<short> shortElement:
return ShortToUInt16(shortElement.Data);
case MiNum<ushort> ushortElement:
return ushortElement.Data;
case MiNum<long> longElement:
return LongToUInt16(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToUInt16(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToUInt16(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToUInt16(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint16, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToUInt16(sbyteElement.Data),
MiNum<byte> byteElement => ByteToUInt16(byteElement.Data),
MiNum<int> intElement => IntToUInt16(intElement.Data),
MiNum<uint> uintElement => UintToUInt16(uintElement.Data),
MiNum<short> shortElement => ShortToUInt16(shortElement.Data),
MiNum<ushort> ushortElement => ushortElement.Data,
MiNum<long> longElement => LongToUInt16(longElement.Data),
MiNum<ulong> ulongElement => UlongToUInt16(ulongElement.Data),
MiNum<float> floatElement => SingleToUInt16(floatElement.Data),
MiNum<double> doubleElement => DoubleToUInt16(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to uint16, found {element.GetType()}."),
};
}
/// <summary>
@ -220,31 +158,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to Int32.</returns>
public static int[] GetDataAsInt32(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToInt32(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToInt32(byteElement.Data);
case MiNum<int> intElement:
return intElement.Data;
case MiNum<uint> uintElement:
return UintToInt32(uintElement.Data);
case MiNum<short> shortElement:
return ShortToInt32(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToInt32(ushortElement.Data);
case MiNum<long> longElement:
return LongToInt32(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToInt32(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToInt32(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToInt32(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to int32, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToInt32(sbyteElement.Data),
MiNum<byte> byteElement => ByteToInt32(byteElement.Data),
MiNum<int> intElement => intElement.Data,
MiNum<uint> uintElement => UintToInt32(uintElement.Data),
MiNum<short> shortElement => ShortToInt32(shortElement.Data),
MiNum<ushort> ushortElement => UshortToInt32(ushortElement.Data),
MiNum<long> longElement => LongToInt32(longElement.Data),
MiNum<ulong> ulongElement => UlongToInt32(ulongElement.Data),
MiNum<float> floatElement => SingleToInt32(floatElement.Data),
MiNum<double> doubleElement => DoubleToInt32(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to int32, found {element.GetType()}."),
};
}
/// <summary>
@ -254,31 +182,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to UInt32.</returns>
public static uint[] GetDataAsUInt32(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToUInt32(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToUInt32(byteElement.Data);
case MiNum<int> intElement:
return IntToUInt32(intElement.Data);
case MiNum<uint> uintElement:
return uintElement.Data;
case MiNum<short> shortElement:
return ShortToUInt32(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToUInt32(ushortElement.Data);
case MiNum<long> longElement:
return LongToUInt32(longElement.Data);
case MiNum<ulong> ulongElement:
return UlongToUInt32(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToUInt32(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToUInt32(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint32, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToUInt32(sbyteElement.Data),
MiNum<byte> byteElement => ByteToUInt32(byteElement.Data),
MiNum<int> intElement => IntToUInt32(intElement.Data),
MiNum<uint> uintElement => uintElement.Data,
MiNum<short> shortElement => ShortToUInt32(shortElement.Data),
MiNum<ushort> ushortElement => UshortToUInt32(ushortElement.Data),
MiNum<long> longElement => LongToUInt32(longElement.Data),
MiNum<ulong> ulongElement => UlongToUInt32(ulongElement.Data),
MiNum<float> floatElement => SingleToUInt32(floatElement.Data),
MiNum<double> doubleElement => DoubleToUInt32(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to uint32, found {element.GetType()}."),
};
}
/// <summary>
@ -288,31 +206,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to Int64.</returns>
public static long[] GetDataAsInt64(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToInt64(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToInt64(byteElement.Data);
case MiNum<int> intElement:
return IntToInt64(intElement.Data);
case MiNum<uint> uintElement:
return UintToInt64(uintElement.Data);
case MiNum<short> shortElement:
return ShortToInt64(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToInt64(ushortElement.Data);
case MiNum<long> longElement:
return longElement.Data;
case MiNum<ulong> ulongElement:
return UlongToInt64(ulongElement.Data);
case MiNum<float> floatElement:
return SingleToInt64(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToInt64(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to int64, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToInt64(sbyteElement.Data),
MiNum<byte> byteElement => ByteToInt64(byteElement.Data),
MiNum<int> intElement => IntToInt64(intElement.Data),
MiNum<uint> uintElement => UintToInt64(uintElement.Data),
MiNum<short> shortElement => ShortToInt64(shortElement.Data),
MiNum<ushort> ushortElement => UshortToInt64(ushortElement.Data),
MiNum<long> longElement => longElement.Data,
MiNum<ulong> ulongElement => UlongToInt64(ulongElement.Data),
MiNum<float> floatElement => SingleToInt64(floatElement.Data),
MiNum<double> doubleElement => DoubleToInt64(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to int64, found {element.GetType()}."),
};
}
/// <summary>
@ -322,31 +230,21 @@ namespace MatFileHandler
/// <returns>Contents of the elements, converted to UInt64.</returns>
public static ulong[] GetDataAsUInt64(DataElement element)
{
switch (element)
return element switch
{
case MiNum<sbyte> sbyteElement:
return SbyteToUInt64(sbyteElement.Data);
case MiNum<byte> byteElement:
return ByteToUInt64(byteElement.Data);
case MiNum<int> intElement:
return IntToUInt64(intElement.Data);
case MiNum<uint> uintElement:
return UintToUInt64(uintElement.Data);
case MiNum<short> shortElement:
return ShortToUInt64(shortElement.Data);
case MiNum<ushort> ushortElement:
return UshortToUInt64(ushortElement.Data);
case MiNum<long> longElement:
return LongToUInt64(longElement.Data);
case MiNum<ulong> ulongElement:
return ulongElement.Data;
case MiNum<float> floatElement:
return SingleToUInt64(floatElement.Data);
case MiNum<double> doubleElement:
return DoubleToUInt64(doubleElement.Data);
}
throw new HandlerException(
$"Expected data element that would be convertible to uint64, found {element.GetType()}.");
MiNum<sbyte> sbyteElement => SbyteToUInt64(sbyteElement.Data),
MiNum<byte> byteElement => ByteToUInt64(byteElement.Data),
MiNum<int> intElement => IntToUInt64(intElement.Data),
MiNum<uint> uintElement => UintToUInt64(uintElement.Data),
MiNum<short> shortElement => ShortToUInt64(shortElement.Data),
MiNum<ushort> ushortElement => UshortToUInt64(ushortElement.Data),
MiNum<long> longElement => LongToUInt64(longElement.Data),
MiNum<ulong> ulongElement => ulongElement.Data,
MiNum<float> floatElement => SingleToUInt64(floatElement.Data),
MiNum<double> doubleElement => DoubleToUInt64(doubleElement.Data),
_ => throw new HandlerException(
$"Expected data element that would be convertible to uint64, found {element.GetType()}."),
};
}
// * to double

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -16,41 +14,21 @@ namespace MatFileHandler
/// <returns>Size in bytes.</returns>
public static int Size(this DataType type)
{
switch (type)
return type switch
{
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);
}
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),
};
}
}
}

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Linq;
using System.Numerics;
@ -12,8 +10,6 @@ namespace MatFileHandler
public class DatetimeAdapter
{
private readonly double[] data;
private readonly double[] data2;
private readonly int[] dimensions;
private readonly DateTimeOffset epoch;
@ -36,15 +32,13 @@ namespace MatFileHandler
case IArrayOf<double> dataArray:
data = dataArray.ConvertToDoubleArray()
?? throw new HandlerException("Cannot extract data for the datetime adapter.");
data2 = new double[data.Length];
dimensions = dataArray.Dimensions;
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();
data2 = complexData.Select(c => c.Imaginary).ToArray();
dimensions = dataComplex.Dimensions;
Dimensions = dataComplex.Dimensions;
break;
default:
throw new HandlerException("Datetime data not found.");
@ -54,7 +48,7 @@ namespace MatFileHandler
/// <summary>
/// Gets datetime array dimensions.
/// </summary>
public int[] Dimensions => dimensions;
public int[] Dimensions { get; }
/// <summary>
/// Gets values of datetime object at given position in the array converted to <see cref="DateTimeOffset"/>.
@ -66,11 +60,11 @@ namespace MatFileHandler
get
{
var milliseconds = data[Dimensions.DimFlatten(list)];
if (milliseconds < -62_135_596_800_000.0 || milliseconds > 253_402_300_799_999.0)
return milliseconds switch
{
return null;
}
return epoch.AddMilliseconds(milliseconds);
< -62_135_596_800_000.0 or > 253_402_300_799_999.0 => null,
_ => epoch.AddMilliseconds(milliseconds),
};
}
}
}

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Linq;

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler
@ -9,7 +7,6 @@ namespace MatFileHandler
/// </summary>
public class DurationAdapter
{
private readonly int[] dimensions;
private readonly double[] data;
/// <summary>
@ -27,13 +24,13 @@ namespace MatFileHandler
var dataObject = matObject["millis", 0];
data = dataObject.ConvertToDoubleArray()
?? throw new HandlerException("Cannot extract data for the duration adapter.");
dimensions = dataObject.Dimensions;
Dimensions = dataObject.Dimensions;
}
/// <summary>
/// Gets duration array dimensions.
/// </summary>
public int[] Dimensions => dimensions;
public int[] Dimensions { get; }
/// <summary>
/// Gets duration object at given position.

View File

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

View File

@ -0,0 +1,391 @@
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,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler

View File

@ -1,10 +1,10 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Globalization;
using System.IO;
using System.Linq;
#if !NET461
using System.Runtime.InteropServices;
#endif
namespace MatFileHandler
{
@ -72,11 +72,9 @@ namespace MatFileHandler
var version = reader.ReadInt16();
var endian = reader.ReadInt16();
var isLittleEndian = endian == 19785;
if (!isLittleEndian)
{
throw new NotSupportedException("Big-endian files are not supported.");
}
return new Header(text, subsystemDataOffset, version);
return isLittleEndian
? new Header(text, subsystemDataOffset, version)
: throw new NotSupportedException("Big-endian files are not supported.");
}
private static string GetOperatingSystem()

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Numerics;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -27,6 +25,6 @@ namespace MatFileHandler
/// <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>
public bool TryGetVariable(string name, out IVariable? variable);
bool TryGetVariable(string name, out IVariable? variable);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Numerics;
@ -51,7 +49,7 @@ namespace MatFileHandler
/// <returns>Empty array.</returns>
public static MatArray Empty()
{
return new MatArray(new ArrayFlags { Class = ArrayType.MxCell, Variable = 0 }, new int[] { }, string.Empty);
return new MatArray(new ArrayFlags { Class = ArrayType.MxCell, Variable = 0 }, Array.Empty<int>(), string.Empty);
}
/// <inheritdoc />

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System.Collections.Generic;
using System.Linq;

View File

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

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System.Collections.Generic;
using System.Linq;
@ -18,11 +16,7 @@ namespace MatFileHandler
/// <param name="variables">List of variables.</param>
public MatFile(IEnumerable<IVariable> variables)
{
_variables = new Dictionary<string, IVariable>();
foreach (var variable in variables)
{
_variables[variable.Name] = variable;
}
_variables = variables.ToDictionary(v => v.Name, v => v);
}
/// <inheritdoc />

View File

@ -1,47 +1,45 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;net472</TargetFrameworks>
<PackageVersion>1.4.0-beta4</PackageVersion>
<PackageVersion>1.4.0-beta6</PackageVersion>
<PackageId>MatFileHandler</PackageId>
<Title>A library for reading and writing MATLAB .mat files.</Title>
<Authors>Alexander Luzgarev</Authors>
<Description>MatFileHandler provides a simple interface for reading and writing MATLAB .mat files (of so-called "Level 5") and extracting the contents of numerical arrays, logical arrays, sparse arrays, char arrays, cell arrays and structure arrays.</Description>
<Copyright>Copyright 2017-2020 Alexander Luzgarev</Copyright>
<PackageLicenseFile>LICENSE.md</PackageLicenseFile>
<PackageProjectUrl>https://github.com/mahalex/MatFileHandler</PackageProjectUrl>
<PackageReleaseNotes>First release.</PackageReleaseNotes>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://git.mahalex.net/mahalex/MatFileHandler</PackageProjectUrl>
<PackageReleaseNotes>https://git.mahalex.net/mahalex/MatFileHandler/releases/</PackageReleaseNotes>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageTags>Matlab</PackageTags>
<RepositoryUrl>https://github.com/mahalex/MatFileHandler</RepositoryUrl>
<RepositoryUrl>https://git.mahalex.net/mahalex/MatFileHandler</RepositoryUrl>
<OutputPath>bin\$(Configuration)\</OutputPath>
<DocumentationFile>$(OutputPath)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<LangVersion>10.0</LangVersion>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<CodeAnalysisRuleSet>..\MatFileHandler.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.66">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="System.ValueTuple" Version="4.4.0" Condition="'$(TargetFramework)' == 'net461'" />
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE.md" Pack="true" PackagePath=""/>
<PackageReference Include="Microsoft.SourceLink.Gitea" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="MatFileHandler.Tests" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
@ -28,10 +25,8 @@ namespace MatFileHandler
/// <returns>Contents of the file.</returns>
public IMatFile Read()
{
using (var reader = new BinaryReader(Stream))
{
return Read(reader);
}
using var reader = new BinaryReader(new PositionTrackingStream(Stream));
return Read(reader);
}
/// <summary>
@ -51,26 +46,24 @@ namespace MatFileHandler
var dataElementReader = new DataElementReader(subsystemData);
while (true)
{
try
{
var position = reader.BaseStream.Position;
var dataElement = dataElementReader.Read(reader);
if (position == subsystemDataOffset)
{
var subsystemDataElement = dataElement as IArrayOf<byte>
?? throw new HandlerException("Cannot parse subsystem data element.");
var newSubsystemData = ReadSubsystemData(subsystemDataElement.Data, subsystemData);
subsystemData.Set(newSubsystemData);
}
else
{
variables.Add(new RawVariable(position, dataElement));
}
}
catch (EndOfStreamException)
var position = reader.BaseStream.Position;
var dataElement = dataElementReader.Read(reader);
if (dataElement is null)
{
break;
}
if (position == subsystemDataOffset)
{
var subsystemDataElement = dataElement as IArrayOf<byte>
?? throw new HandlerException("Cannot parse subsystem data element.");
var newSubsystemData = ReadSubsystemData(subsystemDataElement.Data, subsystemData);
subsystemData.Set(newSubsystemData);
}
else
{
variables.Add(new RawVariable(position, dataElement));
}
}
return variables;
@ -88,23 +81,21 @@ namespace MatFileHandler
return ReadRawVariables(reader, subsystemDataOffset, subsystemData);
}
private static IMatFile Read(BinaryReader reader)
private static MatFile Read(BinaryReader reader)
{
var header = ReadHeader(reader);
var rawVariables = ReadRawVariables(reader, header.SubsystemDataOffset);
var variables = new List<IVariable>();
foreach (var variable in rawVariables)
{
var array = variable.DataElement as MatArray;
if (array is null)
if (variable.DataElement is MatArray array)
{
continue;
variables.Add(
new MatVariable(
array,
array.Name,
array.Flags.Variable.HasFlag(Variable.IsGlobal)));
}
variables.Add(new MatVariable(
array,
array.Name,
array.Flags.Variable.HasFlag(Variable.IsGlobal)));
}
return new MatFile(variables);

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.IO;
@ -47,22 +45,28 @@ namespace MatFileHandler
public void Write(IMatFile file)
{
var header = Header.CreateNewHeader();
using (var writer = new BinaryWriter(Stream))
using var writer = new BinaryWriter(Stream);
WriteHeader(writer, header);
foreach (var variable in file.Variables)
{
WriteHeader(writer, header);
foreach (var variable in file.Variables)
switch (_options.UseCompression)
{
switch (_options.UseCompression)
{
case CompressionUsage.Always:
WriteCompressedVariable(writer, variable);
break;
case CompressionUsage.Never:
WriteVariable(writer, variable);
break;
default:
throw new ArgumentOutOfRangeException();
}
case CompressionUsage.Always:
if (Stream.CanSeek)
{
WriteCompressedVariableToSeekableStream(writer, variable);
}
else
{
WriteCompressedVariableToUnseekableStream(writer, variable);
}
break;
case CompressionUsage.Never:
WriteVariable(writer, variable);
break;
default:
throw new NotImplementedException();
}
}
}
@ -90,7 +94,7 @@ namespace MatFileHandler
return (s2 << 16) | s1;
}
private void WriteHeader(BinaryWriter writer, Header header)
private static void WriteHeader(BinaryWriter writer, Header header)
{
writer.Write(Encoding.UTF8.GetBytes(header.Text));
writer.Write(header.SubsystemDataOffset);
@ -98,33 +102,30 @@ namespace MatFileHandler
writer.Write((short)19785); // Magic number, 'IM'.
}
private void WriteTag(BinaryWriter writer, Tag tag)
private static void WriteTag(BinaryWriter writer, Tag tag)
{
writer.Write((int)tag.Type);
writer.Write(tag.Length);
}
private void WriteShortTag(BinaryWriter writer, Tag tag)
private static void WriteShortTag(BinaryWriter writer, Tag tag)
{
writer.Write((short)tag.Type);
writer.Write((short)tag.Length);
}
private void WritePadding(BinaryWriter writer)
{
var positionMod8 = writer.BaseStream.Position % 8;
if (positionMod8 != 0)
{
writer.Write(new byte[8 - positionMod8]);
}
}
private void WriteDataElement(BinaryWriter writer, DataType type, byte[] data)
private static void WriteDataElement(BinaryWriter writer, DataType type, byte[] data)
{
if (data.Length > 4)
{
WriteTag(writer, new Tag(type, data.Length));
writer.Write(data);
var rem = data.Length % 8;
if (rem > 0)
{
var padding = new byte[8 - rem];
writer.Write(padding);
}
}
else
{
@ -136,17 +137,16 @@ namespace MatFileHandler
writer.Write(padding);
}
}
WritePadding(writer);
}
private void WriteDimensions(BinaryWriter writer, int[] dimensions)
private static void WriteDimensions(BinaryWriter writer, int[] dimensions)
{
var buffer = ConvertToByteArray(dimensions);
WriteDataElement(writer, DataType.MiInt32, buffer);
}
private byte[] ConvertToByteArray<T>(T[] data)
where T : struct
private static byte[] ConvertToByteArray<T>(T[] data)
where T : struct
{
int size;
if (typeof(T) == typeof(sbyte))
@ -189,6 +189,10 @@ namespace MatFileHandler
{
size = sizeof(double);
}
else if (typeof(T) == typeof(bool))
{
size = sizeof(bool);
}
else
{
throw new NotSupportedException();
@ -198,51 +202,51 @@ namespace MatFileHandler
return buffer;
}
private (byte[] real, byte[] imaginary) ConvertToPairOfByteArrays<T>(ComplexOf<T>[] data)
private static (byte[] real, byte[] imaginary) ConvertToPairOfByteArrays<T>(ComplexOf<T>[] data)
where T : struct
{
return (ConvertToByteArray(data.Select(x => x.Real).ToArray()),
ConvertToByteArray(data.Select(x => x.Imaginary).ToArray()));
}
private (byte[] real, byte[] imaginary) ConvertToPairOfByteArrays(Complex[] data)
private static (byte[] real, byte[] imaginary) ConvertToPairOfByteArrays(Complex[] data)
{
return (ConvertToByteArray(data.Select(x => x.Real).ToArray()),
ConvertToByteArray(data.Select(x => x.Imaginary).ToArray()));
}
private void WriteComplexValues(BinaryWriter writer, DataType type, (byte[] real, byte[] complex) data)
private static void WriteComplexValues(BinaryWriter writer, DataType type, (byte[] real, byte[] complex) data)
{
WriteDataElement(writer, type, data.real);
WriteDataElement(writer, type, data.complex);
}
private void WriteArrayFlags(BinaryWriter writer, ArrayFlags flags)
private static void WriteArrayFlags(BinaryWriter writer, ArrayFlags flags)
{
var flag = (byte)flags.Variable;
WriteTag(writer, new Tag(DataType.MiUInt32, 8));
writer.Write((byte)flags.Class);
writer.Write(flag);
writer.Write(new byte[] { 0, 0, 0, 0, 0, 0 });
writer.Write([0, 0, 0, 0, 0, 0]);
}
private void WriteSparseArrayFlags(BinaryWriter writer, SparseArrayFlags flags)
private static void WriteSparseArrayFlags(BinaryWriter writer, SparseArrayFlags flags)
{
var flag = (byte)flags.ArrayFlags.Variable;
WriteTag(writer, new Tag(DataType.MiUInt32, 8));
writer.Write((byte)flags.ArrayFlags.Class);
writer.Write(flag);
writer.Write(new byte[] { 0, 0 });
writer.Write([0, 0]);
writer.Write(flags.NzMax);
}
private void WriteName(BinaryWriter writer, string name)
private static void WriteName(BinaryWriter writer, string name)
{
var nameBytes = Encoding.ASCII.GetBytes(name);
WriteDataElement(writer, DataType.MiInt8, nameBytes);
}
private void WriteNumericalArrayValues(BinaryWriter writer, IArray value)
private static void WriteNumericalArrayValues(BinaryWriter writer, IArray value)
{
switch (value)
{
@ -277,10 +281,7 @@ namespace MatFileHandler
WriteDataElement(writer, DataType.MiDouble, ConvertToByteArray(doubleArray.Data));
break;
case IArrayOf<bool> boolArray:
WriteDataElement(
writer,
DataType.MiUInt8,
boolArray.Data.Select(element => element ? (byte)1 : (byte)0).ToArray());
WriteDataElement(writer, DataType.MiUInt8, ConvertToByteArray(boolArray.Data));
break;
case IArrayOf<ComplexOf<sbyte>> complexSbyteArray:
WriteComplexValues(writer, DataType.MiInt8, ConvertToPairOfByteArrays(complexSbyteArray.Data));
@ -317,63 +318,39 @@ namespace MatFileHandler
}
}
private ArrayFlags GetArrayFlags(IArray array, bool isGlobal)
private static ArrayFlags GetArrayFlags(IArray array, bool isGlobal)
{
var variableFlags = isGlobal ? Variable.IsGlobal : 0;
switch (array)
return array switch
{
case IArrayOf<sbyte> _:
return new ArrayFlags(ArrayType.MxInt8, variableFlags);
case IArrayOf<byte> _:
return new ArrayFlags(ArrayType.MxUInt8, variableFlags);
case IArrayOf<short> _:
return new ArrayFlags(ArrayType.MxInt16, variableFlags);
case IArrayOf<ushort> _:
return new ArrayFlags(ArrayType.MxUInt16, variableFlags);
case IArrayOf<int> _:
return new ArrayFlags(ArrayType.MxInt32, variableFlags);
case IArrayOf<uint> _:
return new ArrayFlags(ArrayType.MxUInt32, variableFlags);
case IArrayOf<long> _:
return new ArrayFlags(ArrayType.MxInt64, variableFlags);
case IArrayOf<ulong> _:
return new ArrayFlags(ArrayType.MxUInt64, variableFlags);
case IArrayOf<float> _:
return new ArrayFlags(ArrayType.MxSingle, variableFlags);
case IArrayOf<double> _:
return new ArrayFlags(ArrayType.MxDouble, variableFlags);
case IArrayOf<bool> _:
return new ArrayFlags(ArrayType.MxUInt8, variableFlags | Variable.IsLogical);
case IArrayOf<ComplexOf<sbyte>> _:
return new ArrayFlags(ArrayType.MxInt8, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<byte>> _:
return new ArrayFlags(ArrayType.MxUInt8, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<short>> _:
return new ArrayFlags(ArrayType.MxInt16, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<ushort>> _:
return new ArrayFlags(ArrayType.MxUInt16, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<int>> _:
return new ArrayFlags(ArrayType.MxInt32, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<uint>> _:
return new ArrayFlags(ArrayType.MxUInt32, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<long>> _:
return new ArrayFlags(ArrayType.MxInt64, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<ulong>> _:
return new ArrayFlags(ArrayType.MxUInt64, variableFlags | Variable.IsComplex);
case IArrayOf<ComplexOf<float>> _:
return new ArrayFlags(ArrayType.MxSingle, variableFlags | Variable.IsComplex);
case IArrayOf<Complex> _:
return new ArrayFlags(ArrayType.MxDouble, variableFlags | Variable.IsComplex);
case IStructureArray _:
return new ArrayFlags(ArrayType.MxStruct, variableFlags);
case ICellArray _:
return new ArrayFlags(ArrayType.MxCell, variableFlags);
default:
throw new NotSupportedException();
}
IArrayOf<sbyte> => new ArrayFlags(ArrayType.MxInt8, variableFlags),
IArrayOf<byte> => new ArrayFlags(ArrayType.MxUInt8, variableFlags),
IArrayOf<short> => new ArrayFlags(ArrayType.MxInt16, variableFlags),
IArrayOf<ushort> => new ArrayFlags(ArrayType.MxUInt16, variableFlags),
IArrayOf<int> => new ArrayFlags(ArrayType.MxInt32, variableFlags),
IArrayOf<uint> => new ArrayFlags(ArrayType.MxUInt32, variableFlags),
IArrayOf<long> => new ArrayFlags(ArrayType.MxInt64, variableFlags),
IArrayOf<ulong> => new ArrayFlags(ArrayType.MxUInt64, variableFlags),
IArrayOf<float> => new ArrayFlags(ArrayType.MxSingle, variableFlags),
IArrayOf<double> => new ArrayFlags(ArrayType.MxDouble, variableFlags),
IArrayOf<bool> => new ArrayFlags(ArrayType.MxUInt8, variableFlags | Variable.IsLogical),
IArrayOf<ComplexOf<sbyte>> => new ArrayFlags(ArrayType.MxInt8, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<byte>> => new ArrayFlags(ArrayType.MxUInt8, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<short>> => new ArrayFlags(ArrayType.MxInt16, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<ushort>> => new ArrayFlags(ArrayType.MxUInt16, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<int>> => new ArrayFlags(ArrayType.MxInt32, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<uint>> => new ArrayFlags(ArrayType.MxUInt32, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<long>> => new ArrayFlags(ArrayType.MxInt64, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<ulong>> => new ArrayFlags(ArrayType.MxUInt64, variableFlags | Variable.IsComplex),
IArrayOf<ComplexOf<float>> => new ArrayFlags(ArrayType.MxSingle, variableFlags | Variable.IsComplex),
IArrayOf<Complex> => new ArrayFlags(ArrayType.MxDouble, variableFlags | Variable.IsComplex),
IStructureArray => new ArrayFlags(ArrayType.MxStruct, variableFlags),
ICellArray => new ArrayFlags(ArrayType.MxCell, variableFlags),
_ => throw new NotSupportedException(),
};
}
private SparseArrayFlags GetSparseArrayFlags<T>(ISparseArrayOf<T> array, bool isGlobal, uint nonZero)
private static SparseArrayFlags GetSparseArrayFlags<T>(ISparseArrayOf<T> array, bool isGlobal, uint nonZero)
where T : struct
{
var flags = GetArrayFlags(array, isGlobal);
@ -388,12 +365,16 @@ namespace MatFileHandler
};
}
private ArrayFlags GetCharArrayFlags(bool isGlobal)
private static ArrayFlags GetCharArrayFlags(bool isGlobal)
{
return new ArrayFlags(ArrayType.MxChar, isGlobal ? Variable.IsGlobal : 0);
}
private void WriteWrappingContents<T>(BinaryWriter writer, T array, Action<BinaryWriter> writeContents)
private static void WriteWrappingContents<T>(
BinaryWriter writer,
T array,
Action<FakeWriter> lengthCalculator,
Action<BinaryWriter> writeContents)
where T : IArray
{
if (array.IsEmpty)
@ -401,19 +382,15 @@ namespace MatFileHandler
WriteTag(writer, new Tag(DataType.MiMatrix, 0));
return;
}
using (var contents = new MemoryStream())
{
using (var contentsWriter = new BinaryWriter(contents))
{
writeContents(contentsWriter);
WriteTag(writer, new Tag(DataType.MiMatrix, (int)contents.Length));
contents.Position = 0;
contents.CopyTo(writer.BaseStream);
}
}
var fakeWriter = new FakeWriter();
lengthCalculator(fakeWriter);
var calculatedLength = fakeWriter.Position;
WriteTag(writer, new Tag(DataType.MiMatrix, calculatedLength));
writeContents(writer);
}
private void WriteNumericalArrayContents(BinaryWriter writer, IArray array, string name, bool isGlobal)
private static void WriteNumericalArrayContents(BinaryWriter writer, IArray array, string name, bool isGlobal)
{
WriteArrayFlags(writer, GetArrayFlags(array, isGlobal));
WriteDimensions(writer, array.Dimensions);
@ -421,7 +398,7 @@ namespace MatFileHandler
WriteNumericalArrayValues(writer, array);
}
private void WriteNumericalArray(
private static void WriteNumericalArray(
BinaryWriter writer,
IArray numericalArray,
string name = "",
@ -430,10 +407,11 @@ namespace MatFileHandler
WriteWrappingContents(
writer,
numericalArray,
fakeWriter => fakeWriter.WriteNumericalArrayContents(numericalArray, name),
contentsWriter => { WriteNumericalArrayContents(contentsWriter, numericalArray, name, isGlobal); });
}
private void WriteCharArrayContents(BinaryWriter writer, ICharArray charArray, string name, bool isGlobal)
private static void WriteCharArrayContents(BinaryWriter writer, ICharArray charArray, string name, bool isGlobal)
{
WriteArrayFlags(writer, GetCharArrayFlags(isGlobal));
WriteDimensions(writer, charArray.Dimensions);
@ -442,15 +420,16 @@ namespace MatFileHandler
WriteDataElement(writer, DataType.MiUtf16, ConvertToByteArray(array));
}
private void WriteCharArray(BinaryWriter writer, ICharArray charArray, string name, bool isGlobal)
private static void WriteCharArray(BinaryWriter writer, ICharArray charArray, string name, bool isGlobal)
{
WriteWrappingContents(
writer,
charArray,
fakeWriter => fakeWriter.WriteCharArrayContents(charArray, name),
contentsWriter => { WriteCharArrayContents(contentsWriter, charArray, name, isGlobal); });
}
private void WriteSparseArrayValues<T>(
private static void WriteSparseArrayValues<T>(
BinaryWriter writer, int[] rows, int[] columns, T[] data)
where T : struct
{
@ -460,9 +439,8 @@ namespace MatFileHandler
{
WriteDataElement(writer, DataType.MiDouble, ConvertToByteArray(data));
}
else if (data is Complex[])
else if (data is Complex[] complexData)
{
var complexData = data as Complex[];
WriteDataElement(
writer,
DataType.MiDouble,
@ -472,17 +450,16 @@ namespace MatFileHandler
DataType.MiDouble,
ConvertToByteArray(complexData.Select(c => c.Imaginary).ToArray()));
}
else if (data is bool[])
else if (data is bool[] boolData)
{
var boolData = data as bool[];
WriteDataElement(
writer,
DataType.MiUInt8,
boolData.Select(element => element ? (byte)1 : (byte)0).ToArray());
ConvertToByteArray(boolData));
}
}
private (int[] rowIndex, int[] columnIndex, T[] data, uint nonZero) PrepareSparseArrayData<T>(
private static (int[] rowIndex, int[] columnIndex, T[] data, uint nonZero) PrepareSparseArrayData<T>(
ISparseArrayOf<T> array)
where T : struct, IEquatable<T>
{
@ -505,7 +482,7 @@ namespace MatFileHandler
return (rowIndexList.ToArray(), columnIndex, valuesList.ToArray(), (uint)rowIndexList.Count);
}
private void WriteSparseArrayContents<T>(
private static void WriteSparseArrayContents<T>(
BinaryWriter writer,
ISparseArrayOf<T> array,
string name,
@ -519,20 +496,21 @@ namespace MatFileHandler
WriteSparseArrayValues(writer, rows, columns, data);
}
private void WriteSparseArray<T>(BinaryWriter writer, ISparseArrayOf<T> sparseArray, string name, bool isGlobal)
where T : struct, IEquatable<T>
private static void WriteSparseArray<T>(BinaryWriter writer, ISparseArrayOf<T> sparseArray, string name, bool isGlobal)
where T : unmanaged, IEquatable<T>
{
WriteWrappingContents(
writer,
sparseArray,
fakeWriter => fakeWriter.WriteSparseArrayContents(sparseArray, name),
contentsWriter => { WriteSparseArrayContents(contentsWriter, sparseArray, name, isGlobal); });
}
private void WriteFieldNames(BinaryWriter writer, IEnumerable<string> fieldNames)
private static void WriteFieldNames(BinaryWriter writer, IEnumerable<string> fieldNames)
{
var fieldNamesArray = fieldNames.Select(name => Encoding.ASCII.GetBytes(name)).ToArray();
var maxFieldName = fieldNamesArray.Select(name => name.Length).Max() + 1;
WriteDataElement(writer, DataType.MiInt32, ConvertToByteArray(new[] { maxFieldName }));
var maxFieldName = fieldNamesArray.Max(name => name.Length) + 1;
WriteDataElement(writer, DataType.MiInt32, ConvertToByteArray([maxFieldName]));
var buffer = new byte[fieldNamesArray.Length * maxFieldName];
var startPosition = 0;
foreach (var name in fieldNamesArray)
@ -575,6 +553,7 @@ namespace MatFileHandler
WriteWrappingContents(
writer,
structureArray,
fakeWriter => fakeWriter.WriteStructureArrayContents(structureArray, name),
contentsWriter => { WriteStructureArrayContents(contentsWriter, structureArray, name, isGlobal); });
}
@ -599,6 +578,7 @@ namespace MatFileHandler
WriteWrappingContents(
writer,
cellArray,
fakeWriter => fakeWriter.WriteCellArrayContents(cellArray, name),
contentsWriter => { WriteCellArrayContents(contentsWriter, cellArray, name, isGlobal); });
}
@ -635,33 +615,55 @@ namespace MatFileHandler
WriteArray(writer, variable.Value, variable.Name, variable.IsGlobal);
}
private void WriteCompressedVariable(BinaryWriter writer, IVariable variable)
private void WriteCompressedVariableToSeekableStream(BinaryWriter writer, IVariable variable)
{
using (var compressedStream = new MemoryStream())
var position = writer.BaseStream.Position;
WriteTag(writer, new Tag(DataType.MiCompressed, 0));
writer.Write((byte)0x78);
writer.Write((byte)0x9c);
int compressedLength;
uint crc;
var before = writer.BaseStream.Position;
using (var compressionStream = new DeflateStream(writer.BaseStream, CompressionMode.Compress, leaveOpen: true))
{
uint crc;
using (var originalStream = new MemoryStream())
{
using (var internalWriter = new BinaryWriter(originalStream))
{
WriteVariable(internalWriter, variable);
originalStream.Position = 0;
crc = CalculateAdler32Checksum(originalStream);
originalStream.Position = 0;
using (var compressionStream =
new DeflateStream(compressedStream, CompressionMode.Compress, leaveOpen: true))
{
originalStream.CopyTo(compressionStream);
}
}
}
compressedStream.Position = 0;
WriteTag(writer, new Tag(DataType.MiCompressed, (int)(compressedStream.Length + 6)));
writer.Write((byte)0x78);
writer.Write((byte)0x9c);
compressedStream.CopyTo(writer.BaseStream);
writer.Write(BitConverter.GetBytes(crc).Reverse().ToArray());
using var checksumStream = new ChecksumCalculatingStream(compressionStream);
using var internalWriter = new BinaryWriter(checksumStream, Encoding.UTF8, leaveOpen: true);
WriteVariable(internalWriter, variable);
crc = checksumStream.GetCrc();
}
var after = writer.BaseStream.Position;
compressedLength = (int)(after - before) + 6;
writer.Write(BitConverter.GetBytes(crc).Reverse().ToArray());
writer.BaseStream.Position = position;
WriteTag(writer, new Tag(DataType.MiCompressed, compressedLength));
writer.BaseStream.Seek(0, SeekOrigin.End);
}
private void WriteCompressedVariableToUnseekableStream(BinaryWriter writer, IVariable variable)
{
using var compressedStream = new MemoryStream();
uint crc;
using (var originalStream = new MemoryStream())
{
using var internalWriter = new BinaryWriter(originalStream);
WriteVariable(internalWriter, variable);
originalStream.Position = 0;
crc = CalculateAdler32Checksum(originalStream);
originalStream.Position = 0;
using var compressionStream = new DeflateStream(
compressedStream,
CompressionMode.Compress,
leaveOpen: true);
originalStream.CopyTo(compressionStream);
}
compressedStream.Position = 0;
WriteTag(writer, new Tag(DataType.MiCompressed, (int)(compressedStream.Length + 6)));
writer.Write((byte)0x78);
writer.Write((byte)0x9c);
compressedStream.CopyTo(writer.BaseStream);
writer.Write(BitConverter.GetBytes(crc).Reverse().ToArray());
}
}
}

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
namespace MatFileHandler
{
/// <summary>
@ -10,7 +8,7 @@ namespace MatFileHandler
/// <summary>
/// Gets default options.
/// </summary>
public static MatFileWriterOptions Default => new MatFileWriterOptions
public static MatFileWriterOptions Default => new()
{
UseCompression = CompressionUsage.Always,
};

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Numerics;

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Linq;
@ -49,7 +47,7 @@ namespace MatFileHandler
get
{
var rowAndColumn = GetRowAndColumn(list);
return DataDictionary.ContainsKey(rowAndColumn) ? DataDictionary[rowAndColumn] : default(T);
return DataDictionary.TryGetValue(rowAndColumn, out var result) ? result : default;
}
set => DataDictionary[GetRowAndColumn(list)] = value;
}
@ -61,7 +59,7 @@ namespace MatFileHandler
public override double[] ConvertToDoubleArray()
{
var data = ((IArrayOf<T>)this).Data;
return data as double[] ?? data.Select(x => Convert.ToDouble(x)).ToArray();
return data as double[] ?? data.Select(x => Convert.ToDouble(x, System.Globalization.CultureInfo.InvariantCulture)).ToArray();
}
/// <inheritdoc />
@ -75,7 +73,7 @@ namespace MatFileHandler
var result = new double[Dimensions[0], Dimensions[1]];
foreach (var pair in Data)
{
result[pair.Key.row, pair.Key.column] = Convert.ToDouble(pair.Value);
result[pair.Key.row, pair.Key.column] = Convert.ToDouble(pair.Value, System.Globalization.CultureInfo.InvariantCulture);
}
return result;
@ -93,15 +91,12 @@ namespace MatFileHandler
private (int row, int column) GetRowAndColumn(int[] indices)
{
switch (indices.Length)
return indices.Length switch
{
case 1:
return (indices[0] % Dimensions[0], indices[0] / Dimensions[0]);
case 2:
return (indices[0], indices[1]);
default:
throw new NotSupportedException("Invalid index for sparse array.");
}
1 => (indices[0] % Dimensions[0], indices[0] / Dimensions[0]),
2 => (indices[0], indices[1]),
_ => throw new NotSupportedException("Invalid index for sparse array."),
};
}
}
}

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections;
using System.Collections.Generic;
@ -69,7 +67,7 @@ namespace MatFileHandler
"Cannot set structure elements via this[params int[]] indexer. Use this[string, int[]] instead.");
}
private IReadOnlyDictionary<string, IArray> ExtractStructure(int i)
private MatStructureArrayElement ExtractStructure(int i)
{
return new MatStructureArrayElement(this, i);
}

View File

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

View File

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

View File

@ -0,0 +1,27 @@
namespace MatFileHandler;
/// <summary>
/// Parser for object data.
/// </summary>
internal static class ObjectParser
{
/// <summary>
/// Parse object data.
/// </summary>
/// <param name="uintArray">Opaque link array.</param>
/// <param name="subsystemData">Current subsystem data.</param>
/// <returns>Parsed object.</returns>
public static IArray ParseObject(MatNumericalArrayOf<uint> uintArray, SubsystemData subsystemData)
{
var (dimensions, indexToObjectId, classIndex) = DataElementReader.ParseOpaqueData(uintArray.Data);
return new OpaqueLink(
uintArray.Name,
string.Empty,
string.Empty,
dimensions,
uintArray,
indexToObjectId,
classIndex,
subsystemData);
}
}

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Numerics;

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections;
using System.Collections.Generic;
@ -78,15 +76,7 @@ namespace MatFileHandler
/// <inheritdoc />
public IArray this[string field, params int[] list]
{
get
{
if (TryGetValue(field, out var result, list))
{
return result!;
}
throw new IndexOutOfRangeException();
}
get => TryGetValue(field, out var result, list) ? result! : throw new ArgumentOutOfRangeException(nameof(list));
set => throw new NotImplementedException();
}
@ -97,7 +87,7 @@ namespace MatFileHandler
set => throw new NotImplementedException();
}
private IReadOnlyDictionary<string, IArray> ExtractObject(int i)
private OpaqueObjectArrayElement ExtractObject(int i)
{
return new OpaqueObjectArrayElement(this, i);
}
@ -106,7 +96,7 @@ namespace MatFileHandler
{
var index = Dimensions.DimFlatten(list);
var maybeFieldIndex = SubsystemData.ClassInformation[ClassIndex].FindField(field);
if (!(maybeFieldIndex is int fieldIndex))
if (maybeFieldIndex is not int fieldIndex)
{
output = default;
return false;
@ -171,8 +161,8 @@ namespace MatFileHandler
}
}
/// <inheritdoc />
#pragma warning disable CS8767
/// <inheritdoc />
public bool TryGetValue(string key, out IArray? value)
#pragma warning restore CS8767
{

View File

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

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
namespace MatFileHandler

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Text;
@ -10,7 +8,6 @@ namespace MatFileHandler
/// </summary>
public class StringAdapter
{
private readonly int[] dimensions;
private readonly string[] strings;
/// <summary>
@ -28,13 +25,13 @@ namespace MatFileHandler
var binaryData = matObject["any", 0] as IArrayOf<ulong>
?? throw new HandlerException("Cannot extract string data.");
(dimensions, strings) = ParseBinaryData(binaryData.Data);
(Dimensions, strings) = ParseBinaryData(binaryData.Data);
}
/// <summary>
/// Gets string array dimensions.
/// </summary>
public int[] Dimensions => dimensions;
public int[] Dimensions { get; }
/// <summary>
/// Gets string object at given position.

View File

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

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
@ -119,12 +117,7 @@ namespace MatFileHandler
/// <returns>Field index.</returns>
public int? FindField(string fieldName)
{
if (fieldToIndex.TryGetValue(fieldName, out var index))
{
return index;
}
return null;
return fieldToIndex.TryGetValue(fieldName, out var index) ? index : null;
}
}

View File

@ -1,8 +1,5 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@ -317,21 +314,11 @@ namespace MatFileHandler
private static IArray TransformOpaqueData(IArray array, SubsystemData subsystemData)
{
if (array is MatNumericalArrayOf<uint> uintArray)
if (array is MatNumericalArrayOf<uint> uintArray &&
uintArray.Data.Length == 6 &&
uintArray.Data[0] == 3707764736u)
{
if (uintArray.Data[0] == 3707764736u)
{
var (dimensions, indexToObjectId, classIndex) = DataElementReader.ParseOpaqueData(uintArray.Data);
return new OpaqueLink(
uintArray.Name,
string.Empty,
string.Empty,
dimensions,
uintArray,
indexToObjectId,
classIndex,
subsystemData);
}
return ObjectParser.ParseObject(uintArray, subsystemData);
}
if (array is MatCellArray cellArray)
@ -344,10 +331,30 @@ namespace MatFileHandler
}
}
if (array is MatStructureArray structureArray)
{
var newFields = new Dictionary<string, List<IArray>>();
foreach (var pair in structureArray.Fields)
{
var values = pair.Value;
var transformedValues = new List<IArray>(values.Count);
foreach (var value in values)
{
var transformedValue = TransformOpaqueData(value, subsystemData);
transformedValues.Add(transformedValue);
}
newFields[pair.Key] = transformedValues;
}
foreach (var pair in newFields)
{
structureArray.Fields[pair.Key] = pair.Value;
}
}
return array;
}
private struct ObjectClassInformation
private readonly struct ObjectClassInformation
{
public ObjectClassInformation(int embeddedObjectPosition, int objectPosition, int loadingOrder, int classId)
{

View File

@ -1,5 +1,3 @@
// Copyright 2017-2018 Alexander Luzgarev
using System;
using System.Linq;
@ -87,9 +85,9 @@ namespace MatFileHandler
.Where(i => VariableNames[i] == variableName)
.Select(i => (int?)i)
.FirstOrDefault();
if (!(maybeIndex is int index))
if (maybeIndex is not int index)
{
throw new IndexOutOfRangeException($"Variable '{variableName}' not found.");
throw new ArgumentOutOfRangeException(nameof(variableName), $"Variable '{variableName}' not found.");
}
var data = matObject["data"] as ICellArray

View File

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

View File

@ -1,5 +1,7 @@
# MatFileHandler
[![NuGet Version](https://img.shields.io/nuget/vpre/MatFileHandler?color=green)](https://www.nuget.org/packages/MatFileHandler/absoluteLatest)
MatFileHandler is a .NET library for reading and writing MATLAB .mat files
(of so-called "Level 5"). MatFileHandler supports numerical arrays,
logical arrays, sparse arrays, char arrays, cell arrays and structure arrays.
@ -13,9 +15,8 @@ You can find (partial) technical description of MATLAB object data format
This document briefly describes how to perform simple operations with .mat files
using MatFileHandler.
If you have questions and/or ideas, you can [file a new issue]
(https://github.com/mahalex/MatFileHandler/issues/new) or contact me directly at
<mahalex@gmail.com>.
If you have questions and/or ideas, you can [file a new issue](https://git.mahalex.net/mahalex/MatFileHandler/issues/new)
or contact me directly at <mahalex@gmail.com>.
## Changelog
@ -203,7 +204,7 @@ classdef Point
end
end
```
We omit any methods (and constructos) such a class might have, because they are
We omit any methods (and constructors) such a class might have, because they are
not stored when you save an object of a class into a `.mat` file.
Imagine that you have a `1x2 Point` object array `p` (an array of two points)

View File

@ -11,9 +11,9 @@ variables:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 8.x'
displayName: 'Use .NET Core SDK 9.x'
inputs:
version: 8.x
version: 9.x
- script: dotnet build --configuration $(buildConfiguration)
displayName: 'dotnet build $(buildConfiguration)'